From e10f55d9db903fb51651ed8c5b9280e7724d18db Mon Sep 17 00:00:00 2001 From: wildcard Date: Sat, 7 Feb 2026 22:44:32 +0100 Subject: [PATCH 01/14] [host_shaders] Store the value of results instead of discarding it (#3464) the function generated the value but we never stored it effectively discarding the result. Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3464 Reviewed-by: MaranBr Reviewed-by: Maufeat Co-authored-by: wildcard Co-committed-by: wildcard --- src/video_core/host_shaders/queries_prefix_scan_sum.comp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/video_core/host_shaders/queries_prefix_scan_sum.comp b/src/video_core/host_shaders/queries_prefix_scan_sum.comp index 6faa8981f2..7ecd5a096d 100644 --- a/src/video_core/host_shaders/queries_prefix_scan_sum.comp +++ b/src/video_core/host_shaders/queries_prefix_scan_sum.comp @@ -86,7 +86,7 @@ void WriteResults(uvec2 results[LOCAL_RESULTS]) { const uvec2 accum = accumulated_data; for (uint i = 0; i < LOCAL_RESULTS; i++) { uvec2 base_data = current_id * LOCAL_RESULTS + i < min_accumulation_base ? accum : uvec2(0, 0); - AddUint64(results[i], base_data); + results[i] = AddUint64(results[i], base_data); } for (uint i = 0; i < LOCAL_RESULTS; i++) { output_data[buffer_offset + current_id * LOCAL_RESULTS + i] = results[i]; From ca9f2d43beeb03ca2127a21ef34ead9f497bcb03 Mon Sep 17 00:00:00 2001 From: crueter Date: Sat, 7 Feb 2026 22:48:39 +0100 Subject: [PATCH 02/14] [desktop] Add icon-only mode to grid and improve design (#3485) - Move Game Icon Size to the main toolbar. It's cleaner that way - Add a "Show Game Name" toggle that does as it says. Disabling it basically creates an "icons-only" mode. Useful for controller-only nav with big icons (TODO: maybe make a 192 size?) - Fixed a crash with controller nav. Oops - Rounded corners of the game icon in grid mode - Fixed the scroll bar creating extra clamping range on the grid icons - Item can be deselected if user clicks on the blank space outside of the view As a bonus fixed a crash on mod manager Future TODOs for design: - [ ] Row 1 type. Not sure what to do here tbh. - [ ] Move around game list settings in configure_ui to make it clear that nothing there affects the grid view. - [ ] 192x192 size? 256 feels too big on my 1440p screen whereas 128 feels too small. - Set text space as a function of fontMetrics. Signed-off-by: crueter Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3485 Reviewed-by: DraVee Reviewed-by: Maufeat --- src/frontend_common/mod_manager.cpp | 8 +- src/qt_common/config/uisettings.h | 1 + src/qt_common/util/mod.cpp | 2 + .../configure_per_game_addons.cpp | 8 +- .../configuration/configure_per_game_addons.h | 2 +- src/yuzu/configuration/configure_ui.cpp | 29 +--- src/yuzu/configuration/configure_ui.ui | 20 +-- src/yuzu/game/game_card.cpp | 61 +++++--- src/yuzu/game/game_card.h | 3 +- src/yuzu/game/game_list.cpp | 147 ++++++++++++------ src/yuzu/main.ui | 25 +++ src/yuzu/main_window.cpp | 91 ++++++++++- src/yuzu/main_window.h | 6 + 13 files changed, 273 insertions(+), 130 deletions(-) diff --git a/src/frontend_common/mod_manager.cpp b/src/frontend_common/mod_manager.cpp index 6ac79d22e2..cbbb3b3400 100644 --- a/src/frontend_common/mod_manager.cpp +++ b/src/frontend_common/mod_manager.cpp @@ -18,10 +18,10 @@ std::vector GetModFolder(const std::string& root) { auto callback = [&paths](const std::filesystem::directory_entry& entry) -> bool { const auto name = entry.path().filename().string(); - static constexpr const std::array valid_names = {"exefs", - "romfs" - "romfs_ext", - "cheats", "romfslite"}; + static const std::array valid_names = {"exefs", + "romfs" + "romfs_ext", + "cheats", "romfslite"}; if (std::ranges::find(valid_names, name) != valid_names.end()) { paths.emplace_back(entry.path().parent_path()); diff --git a/src/qt_common/config/uisettings.h b/src/qt_common/config/uisettings.h index 89e3833508..3c75268377 100644 --- a/src/qt_common/config/uisettings.h +++ b/src/qt_common/config/uisettings.h @@ -211,6 +211,7 @@ struct Values { Setting row_1_text_id{linkage, 3, "row_1_text_id", Category::UiGameList}; Setting row_2_text_id{linkage, 2, "row_2_text_id", Category::UiGameList}; Setting game_list_mode{linkage, Settings::GameListMode::TreeView, "game_list_mode", Category::UiGameList}; + Setting show_game_name{linkage, true, "show_game_name", Category::UiGameList}; std::atomic_bool is_game_list_reload_pending{false}; Setting cache_game_list{linkage, true, "cache_game_list", Category::UiGameList}; diff --git a/src/qt_common/util/mod.cpp b/src/qt_common/util/mod.cpp index 1168bde2f6..f32076fada 100644 --- a/src/qt_common/util/mod.cpp +++ b/src/qt_common/util/mod.cpp @@ -43,6 +43,8 @@ QStringList GetModFolders(const QString& root, const QString& fallbackName) { QString name = QtCommon::Frontend::GetTextInput( tr("Mod Name"), tr("What should this mod be called?"), default_name); + if (name.isEmpty()) return {}; + // if std_path is empty, frontend_common could not determine mod type and/or name. // so we have to prompt the user and set up the structure ourselves if (paths.empty()) { diff --git a/src/yuzu/configuration/configure_per_game_addons.cpp b/src/yuzu/configuration/configure_per_game_addons.cpp index ecafa6826a..da84d23876 100644 --- a/src/yuzu/configuration/configure_per_game_addons.cpp +++ b/src/yuzu/configuration/configure_per_game_addons.cpp @@ -161,14 +161,14 @@ void ConfigurePerGameAddons::InstallMods(const QStringList& mods) { } } -void ConfigurePerGameAddons::InstallModPath(const QString& path) { - const auto mods = QtCommon::Mod::GetModFolders(path, {}); +void ConfigurePerGameAddons::InstallModPath(const QString& path, const QString &fallbackName) { + const auto mods = QtCommon::Mod::GetModFolders(path, fallbackName); if (mods.size() > 1) { ModSelectDialog* dialog = new ModSelectDialog(mods, this); connect(dialog, &ModSelectDialog::modsSelected, this, &ConfigurePerGameAddons::InstallMods); dialog->show(); - } else { + } else if (!mods.empty()) { InstallMods(mods); } } @@ -194,7 +194,7 @@ void ConfigurePerGameAddons::InstallModZip() { const QString extracted = QtCommon::Mod::ExtractMod(path); if (!extracted.isEmpty()) - InstallModPath(extracted); + InstallModPath(extracted, QFileInfo(path).baseName()); } void ConfigurePerGameAddons::changeEvent(QEvent* event) { diff --git a/src/yuzu/configuration/configure_per_game_addons.h b/src/yuzu/configuration/configure_per_game_addons.h index 3c7c0b0ff0..d2f361139b 100644 --- a/src/yuzu/configuration/configure_per_game_addons.h +++ b/src/yuzu/configuration/configure_per_game_addons.h @@ -44,7 +44,7 @@ public: public slots: void InstallMods(const QStringList &mods); - void InstallModPath(const QString& path); + void InstallModPath(const QString& path, const QString& fallbackName = {}); void InstallModFolder(); void InstallModZip(); diff --git a/src/yuzu/configuration/configure_ui.cpp b/src/yuzu/configuration/configure_ui.cpp index b88f41b756..0e91a1a9fd 100644 --- a/src/yuzu/configuration/configure_ui.cpp +++ b/src/yuzu/configuration/configure_ui.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #include "yuzu/configuration/configure_ui.h" @@ -6,7 +6,6 @@ #include #include #include -#include #include #include @@ -21,7 +20,6 @@ #include "common/common_types.h" #include "common/fs/path_util.h" -#include "common/logging/log.h" #include "common/settings.h" #include "common/settings_enums.h" #include "core/core.h" @@ -32,13 +30,6 @@ #include "qt_common/config/uisettings.h" namespace { -constexpr std::array default_game_icon_sizes{ - std::make_pair(0, QT_TRANSLATE_NOOP("ConfigureUI", "None")), - std::make_pair(32, QT_TRANSLATE_NOOP("ConfigureUI", "Small (32x32)")), - std::make_pair(64, QT_TRANSLATE_NOOP("ConfigureUI", "Standard (64x64)")), - std::make_pair(128, QT_TRANSLATE_NOOP("ConfigureUI", "Large (128x128)")), - std::make_pair(256, QT_TRANSLATE_NOOP("ConfigureUI", "Full Size (256x256)")), -}; constexpr std::array default_folder_icon_sizes{ std::make_pair(0, QT_TRANSLATE_NOOP("ConfigureUI", "None")), @@ -57,10 +48,6 @@ constexpr std::array row_text_names{ }; // clang-format on -QString GetTranslatedGameIconSize(size_t index) { - return QCoreApplication::translate("ConfigureUI", default_game_icon_sizes[index].second); -} - QString GetTranslatedFolderIconSize(size_t index) { return QCoreApplication::translate("ConfigureUI", default_folder_icon_sizes[index].second); } @@ -127,8 +114,6 @@ ConfigureUi::ConfigureUi(Core::System& system_, QWidget* parent) connect(ui->show_types, &QCheckBox::STATE_CHANGED, this, &ConfigureUi::RequestGameListUpdate); connect(ui->show_play_time, &QCheckBox::STATE_CHANGED, this, &ConfigureUi::RequestGameListUpdate); - connect(ui->game_icon_size_combobox, QOverload::of(&QComboBox::currentIndexChanged), this, - &ConfigureUi::RequestGameListUpdate); connect(ui->folder_icon_size_combobox, QOverload::of(&QComboBox::currentIndexChanged), this, &ConfigureUi::RequestGameListUpdate); connect(ui->row_1_text_combobox, QOverload::of(&QComboBox::currentIndexChanged), this, @@ -172,7 +157,6 @@ void ConfigureUi::ApplyConfiguration() { UISettings::values.show_size = ui->show_size->isChecked(); UISettings::values.show_types = ui->show_types->isChecked(); UISettings::values.show_play_time = ui->show_play_time->isChecked(); - UISettings::values.game_icon_size = ui->game_icon_size_combobox->currentData().toUInt(); UISettings::values.folder_icon_size = ui->folder_icon_size_combobox->currentData().toUInt(); UISettings::values.row_1_text_id = ui->row_1_text_combobox->currentData().toUInt(); UISettings::values.row_2_text_id = ui->row_2_text_combobox->currentData().toUInt(); @@ -202,8 +186,6 @@ void ConfigureUi::SetConfiguration() { ui->show_size->setChecked(UISettings::values.show_size.GetValue()); ui->show_types->setChecked(UISettings::values.show_types.GetValue()); ui->show_play_time->setChecked(UISettings::values.show_play_time.GetValue()); - ui->game_icon_size_combobox->setCurrentIndex( - ui->game_icon_size_combobox->findData(UISettings::values.game_icon_size.GetValue())); ui->folder_icon_size_combobox->setCurrentIndex( ui->folder_icon_size_combobox->findData(UISettings::values.folder_icon_size.GetValue())); @@ -231,11 +213,6 @@ void ConfigureUi::changeEvent(QEvent* event) { void ConfigureUi::RetranslateUI() { ui->retranslateUi(this); - for (int i = 0; i < ui->game_icon_size_combobox->count(); i++) { - ui->game_icon_size_combobox->setItemText(i, - GetTranslatedGameIconSize(static_cast(i))); - } - for (int i = 0; i < ui->folder_icon_size_combobox->count(); i++) { ui->folder_icon_size_combobox->setItemText( i, GetTranslatedFolderIconSize(static_cast(i))); @@ -270,10 +247,6 @@ void ConfigureUi::InitializeLanguageComboBox() { } void ConfigureUi::InitializeIconSizeComboBox() { - for (size_t i = 0; i < default_game_icon_sizes.size(); i++) { - const auto size = default_game_icon_sizes[i].first; - ui->game_icon_size_combobox->addItem(GetTranslatedGameIconSize(i), size); - } for (size_t i = 0; i < default_folder_icon_sizes.size(); i++) { const auto size = default_folder_icon_sizes[i].first; ui->folder_icon_size_combobox->addItem(GetTranslatedFolderIconSize(i), size); diff --git a/src/yuzu/configuration/configure_ui.ui b/src/yuzu/configuration/configure_ui.ui index b8e6483814..123068c9e2 100644 --- a/src/yuzu/configuration/configure_ui.ui +++ b/src/yuzu/configuration/configure_ui.ui @@ -7,7 +7,7 @@ 0 0 363 - 603 + 613 @@ -111,20 +111,6 @@ - - - - - - Game Icon Size: - - - - - - - - @@ -221,7 +207,7 @@ TextLabel - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter @@ -251,7 +237,7 @@ - Qt::Vertical + Qt::Orientation::Vertical diff --git a/src/yuzu/game/game_card.cpp b/src/yuzu/game/game_card.cpp index d172d3e535..26ab99faaf 100644 --- a/src/yuzu/game/game_card.cpp +++ b/src/yuzu/game/game_card.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include +#include #include "game_card.h" #include "qt_common/config/uisettings.h" @@ -18,7 +19,7 @@ void GameCard::paint(QPainter* painter, const QStyleOptionViewItem& option, painter->setRenderHint(QPainter::Antialiasing); // padding - QRect cardRect = option.rect.adjusted(4, 4, -4, -4); + QRect cardRect = option.rect.adjusted(4 + m_padding / 2, 4, -4 - m_padding / 2, -4); // colors QPalette palette = option.palette; @@ -32,7 +33,7 @@ void GameCard::paint(QPainter* painter, const QStyleOptionViewItem& option, borderColor = palette.highlight().color().lighter(150); textColor = palette.highlightedText().color(); } else if (option.state & QStyle::State_MouseOver) { - backgroundColor = backgroundColor.lighter(110); + backgroundColor = backgroundColor.lighter(120); } // bg @@ -40,7 +41,7 @@ void GameCard::paint(QPainter* painter, const QStyleOptionViewItem& option, painter->setPen(QPen(borderColor, 1)); painter->drawRoundedRect(cardRect, 10, 10); - static constexpr const int padding = 10; + static constexpr const int padding = 8; // icon int _iconsize = UISettings::values.game_icon_size.GetValue(); @@ -58,7 +59,18 @@ void GameCard::paint(QPainter* painter, const QStyleOptionViewItem& option, iconRect = QRect(x, y, scaledSize.width(), scaledSize.height()); painter->setRenderHint(QPainter::SmoothPixmapTransform, true); + + // Put this in a separate thing on the painter stack to prevent clipping the text. + painter->save(); + + // round image edges + QPainterPath path; + path.addRoundedRect(iconRect, 10, 10); + painter->setClipPath(path); + painter->drawPixmap(iconRect, iconPixmap); + + painter->restore(); } else { // if there is no icon just draw a blank rect iconRect = QRect(cardRect.left() + padding, @@ -66,31 +78,33 @@ void GameCard::paint(QPainter* painter, const QStyleOptionViewItem& option, _iconsize, _iconsize); } - // if "none" is selected, pretend there's a - _iconsize = _iconsize ? _iconsize : 96; + if (UISettings::values.show_game_name.GetValue()) { + // if "none" is selected, pretend there's a + _iconsize = _iconsize ? _iconsize : 96; - // padding + text - QRect textRect = cardRect; - textRect.setTop(iconRect.bottom() + 8); - textRect.adjust(padding, 0, -padding, -padding); + // padding + text + QRect textRect = cardRect; + textRect.setTop(iconRect.bottom() + 8); + textRect.adjust(padding, 0, -padding, -padding); - // We are already crammed on space, ignore the row 2 - QString title = index.data(Qt::DisplayRole).toString(); - title = title.split(QLatin1Char('\n')).first(); + // We are already crammed on space, ignore the row 2 + QString title = index.data(Qt::DisplayRole).toString(); + title = title.split(QLatin1Char('\n')).first(); - // now draw text - painter->setPen(textColor); - QFont font = option.font; - font.setBold(true); + // now draw text + painter->setPen(textColor); + QFont font = option.font; + font.setBold(true); - // TODO(crueter): fix this abysmal scaling - // If "none" is selected, then default to 8.5 point font. - font.setPointSize(1 + std::max(7.0, _iconsize ? std::sqrt(_iconsize * 0.6) : 7.5)); + // TODO(crueter): fix this abysmal scaling + // If "none" is selected, then default to 8.5 point font. + font.setPointSize(1 + std::max(7.0, _iconsize ? std::sqrt(_iconsize * 0.6) : 7.5)); - // TODO(crueter): elide mode - painter->setFont(font); + // TODO(crueter): elide mode + painter->setFont(font); - painter->drawText(textRect, Qt::AlignHCenter | Qt::AlignTop | Qt::TextWordWrap, title); + painter->drawText(textRect, Qt::AlignHCenter | Qt::AlignTop | Qt::TextWordWrap, title); + } painter->restore(); } @@ -99,6 +113,7 @@ QSize GameCard::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& return m_size; } -void GameCard::setSize(const QSize& newSize) { +void GameCard::setSize(const QSize& newSize, const int padding) { m_size = newSize; + m_padding = padding; } diff --git a/src/yuzu/game/game_card.h b/src/yuzu/game/game_card.h index 3c695c9047..9fd0ec081a 100644 --- a/src/yuzu/game/game_card.h +++ b/src/yuzu/game/game_card.h @@ -20,8 +20,9 @@ public: QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; - void setSize(const QSize& newSize); + void setSize(const QSize& newSize, const int padding); private: QSize m_size; + int m_padding; }; diff --git a/src/yuzu/game/game_list.cpp b/src/yuzu/game/game_list.cpp index 515fed1a8d..80995a0e79 100644 --- a/src/yuzu/game/game_list.cpp +++ b/src/yuzu/game/game_list.cpp @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later +#include #include #include #include @@ -13,13 +14,12 @@ #include #include #include +#include #include #include #include #include -#include -#include -#include +#include #include "common/common_types.h" #include "common/logging/log.h" #include "common/settings.h" @@ -28,6 +28,7 @@ #include "core/file_sys/registered_cache.h" #include "game/game_card.h" #include "qt_common/config/uisettings.h" +#include "qt_common/qt_common.h" #include "qt_common/util/game.h" #include "yuzu/compatibility_list.h" #include "yuzu/game/game_list.h" @@ -35,7 +36,6 @@ #include "yuzu/game/game_list_worker.h" #include "yuzu/main_window.h" #include "yuzu/util/controller_navigation.h" -#include "qt_common/qt_common.h" GameListSearchField::KeyReleaseEater::KeyReleaseEater(GameList* gamelist_, QObject* parent) : QObject(parent), gamelist{gamelist_} {} @@ -393,16 +393,21 @@ GameList::GameList(FileSys::VirtualFilesystem vfs_, FileSys::ManualContentProvid tree_view->setStyleSheet(QStringLiteral("QTreeView{ border: none; }")); // list view setup - list_view->setViewMode(QListView::IconMode); - list_view->setResizeMode(QListView::Adjust); - list_view->setUniformItemSizes(false); + list_view->setViewMode(QListView::ListMode); + list_view->setResizeMode(QListView::Fixed); + list_view->setUniformItemSizes(true); list_view->setSelectionMode(QAbstractItemView::SingleSelection); list_view->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); list_view->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel); + + // Forcefully disable scroll bar, prevents thing where game list items + // will start clamping prematurely. + list_view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + list_view->setEditTriggers(QAbstractItemView::NoEditTriggers); list_view->setContextMenuPolicy(Qt::CustomContextMenu); list_view->setGridSize(QSize(140, 160)); - m_gameCard->setSize(list_view->gridSize()); + m_gameCard->setSize(list_view->gridSize(), 0); list_view->setSpacing(10); list_view->setWordWrap(true); @@ -438,8 +443,8 @@ GameList::GameList(FileSys::VirtualFilesystem vfs_, FileSys::ManualContentProvid return; } QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, key, Qt::NoModifier); - QCoreApplication::postEvent(tree_view, event); - QCoreApplication::postEvent(list_view, event); + + QCoreApplication::postEvent(m_currentView, event); }); // We must register all custom types with the Qt Automoc system so that we are able to use @@ -488,28 +493,27 @@ void GameList::ResetViewMode() { break; } + auto view = m_currentView->viewport(); + view->installEventFilter(this); + + // touch gestures + view->grabGesture(Qt::SwipeGesture); + view->grabGesture(Qt::PanGesture); + + // TODO: touch? + QScroller::grabGesture(view, QScroller::LeftMouseButtonGesture); + + auto scroller = QScroller::scroller(view); + QScrollerProperties props; + props.setScrollMetric(QScrollerProperties::HorizontalOvershootPolicy, + QScrollerProperties::OvershootAlwaysOff); + props.setScrollMetric(QScrollerProperties::VerticalOvershootPolicy, + QScrollerProperties::OvershootAlwaysOff); + scroller->setScrollerProperties(props); + if (m_isTreeMode != newTreeMode) { m_isTreeMode = newTreeMode; - auto view = m_currentView->viewport(); - - view->installEventFilter(this); - - // touch gestures - view->grabGesture(Qt::SwipeGesture); - view->grabGesture(Qt::PanGesture); - - // TODO: touch? - QScroller::grabGesture(view, QScroller::LeftMouseButtonGesture); - - auto scroller = QScroller::scroller(view); - QScrollerProperties props; - props.setScrollMetric(QScrollerProperties::HorizontalOvershootPolicy, - QScrollerProperties::OvershootAlwaysOff); - props.setScrollMetric(QScrollerProperties::VerticalOvershootPolicy, - QScrollerProperties::OvershootAlwaysOff); - scroller->setScrollerProperties(props); - RefreshGameDirectory(); } } @@ -1007,30 +1011,55 @@ void GameList::UpdateIconSize() { // Update sizes and stuff for the list view const u32 icon_size = UISettings::values.game_icon_size.GetValue(); - // the scaling on the card is kinda abysmal. - // TODO(crueter): refactor int heightMargin = 0; int widthMargin = 80; - switch (icon_size) { - case 128: - heightMargin = 70; - break; - case 0: - widthMargin = 120; - heightMargin = 120; - break; - case 64: - heightMargin = 80; - break; - case 32: - case 256: - heightMargin = 81; - break; + + if (UISettings::values.show_game_name) { + // the scaling on the card is kinda abysmal. + // TODO(crueter): refactor + switch (icon_size) { + case 128: + heightMargin = 65; + break; + case 0: + widthMargin = 120; + heightMargin = 120; + break; + case 64: + heightMargin = 77; + break; + case 32: + case 256: + heightMargin = 81; + break; + } + } else { + widthMargin = 24; + heightMargin = 24; } - // TODO(crueter): Auto size - list_view->setGridSize(QSize(icon_size + widthMargin, icon_size + heightMargin)); - m_gameCard->setSize(list_view->gridSize()); + // "auto" resize // + const int view_width = list_view->viewport()->width(); + + // Tiny space padding to prevent the list view from forcing its own resize operation. + const double spacing = 0.01; + const int min_item_width = icon_size + widthMargin; + + // And now stretch it a bit to fill out remaining space. + // Not perfect but works well enough for now + int columns = std::max(1, view_width / min_item_width); + int stretched_width = (view_width - (spacing * (columns - 1))) / columns; + + // only updates things if grid size is changed + QSize grid_size(stretched_width, icon_size + heightMargin); + if (list_view->gridSize() != grid_size) { + list_view->setUpdatesEnabled(false); + + list_view->setGridSize(grid_size); + m_gameCard->setSize(grid_size, stretched_width - min_item_width); + + list_view->setUpdatesEnabled(true); + } } void GameList::PopulateAsync(QVector& game_dirs) { @@ -1043,7 +1072,8 @@ void GameList::PopulateAsync(QVector& game_dirs) { tree_view->setColumnHidden(COLUMN_SIZE, !UISettings::values.show_size); tree_view->setColumnHidden(COLUMN_PLAY_TIME, !UISettings::values.show_play_time); - UpdateIconSize(); + if (!m_isTreeMode) + UpdateIconSize(); // Cancel any existing worker. current_worker.reset(); @@ -1266,6 +1296,23 @@ bool GameList::eventFilter(QObject* obj, QEvent* event) { return true; } + + if (obj == m_currentView->viewport() && event->type() == QEvent::MouseButtonPress) { + QMouseEvent* mouseEvent = static_cast(event); + + // if the user clicks outside of the list, deselect the current item. + QModelIndex index = m_currentView->indexAt(mouseEvent->pos()); + if (!index.isValid()) { + m_currentView->selectionModel()->clearSelection(); + m_currentView->setCurrentIndex(QModelIndex()); + } + } + + if (obj == list_view->viewport() && event->type() == QEvent::Resize) { + UpdateIconSize(); + return true; + } + return QWidget::eventFilter(obj, event); } diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui index 54fc778f80..327a7dffd2 100644 --- a/src/yuzu/main.ui +++ b/src/yuzu/main.ui @@ -112,6 +112,11 @@ + + + Game &Icon Size + + Reset Window Size to &720p @@ -145,6 +150,8 @@ + + @@ -586,6 +593,24 @@ &Grid View + + + Game Icon Size + + + + + None + + + + + true + + + Show Game &Name + + diff --git a/src/yuzu/main_window.cpp b/src/yuzu/main_window.cpp index def24ef5d3..2e5548c775 100644 --- a/src/yuzu/main_window.cpp +++ b/src/yuzu/main_window.cpp @@ -73,6 +73,7 @@ #include #include #include +#include // Qt Common // #include "qt_common/config/uisettings.h" @@ -377,6 +378,22 @@ static QString PrettyProductName() { return QSysInfo::prettyProductName(); } +namespace { + +constexpr std::array, 5> default_game_icon_sizes{ + std::make_pair(0, QT_TRANSLATE_NOOP("MainWindow", "None")), + std::make_pair(32, QT_TRANSLATE_NOOP("MainWindow", "Small (32x32)")), + std::make_pair(64, QT_TRANSLATE_NOOP("MainWindow", "Standard (64x64)")), + std::make_pair(128, QT_TRANSLATE_NOOP("MainWindow", "Large (128x128)")), + std::make_pair(256, QT_TRANSLATE_NOOP("MainWindow", "Full Size (256x256)")), +}; + +QString GetTranslatedGameIconSize(size_t index) { + return QCoreApplication::translate("MainWindow", default_game_icon_sizes[index].second); +} + +} + #ifndef _WIN32 // TODO(crueter): carboxyl does this, is it needed in qml? inline static bool isDarkMode() { @@ -1607,6 +1624,33 @@ void MainWindow::ConnectMenuEvents() { connect_menu(ui->action_Grid_View, &MainWindow::SetGridView); connect_menu(ui->action_Tree_View, &MainWindow::SetTreeView); + game_size_actions = new QActionGroup(this); + game_size_actions->setExclusive(true); + + for (size_t i = 0; i < default_game_icon_sizes.size(); i++) { + const auto current_size = UISettings::values.game_icon_size.GetValue(); + const auto size = default_game_icon_sizes[i].first; + QAction *action = ui->menuGame_Icon_Size->addAction(GetTranslatedGameIconSize(i)); + action->setCheckable(true); + + if (current_size == size) action->setChecked(true); + + game_size_actions->addAction(action); + + connect(action, &QAction::triggered, this, [this, size](bool checked) { + if (checked) { + UISettings::values.game_icon_size.SetValue(size); + CheckIconSize(); + game_list->RefreshGameDirectory(); + } + }); + } + + CheckIconSize(); + + ui->action_Show_Game_Name->setChecked(UISettings::values.show_game_name.GetValue()); + connect(ui->action_Show_Game_Name, &QAction::triggered, this, &MainWindow::ToggleShowGameName); + // Multiplayer connect(ui->action_View_Lobby, &QAction::triggered, multiplayer_state, &MultiplayerState::OnViewLobby); @@ -3385,6 +3429,9 @@ void MainWindow::SetGameListMode(Settings::GameListMode mode) { ui->action_Tree_View->setChecked(mode == Settings::GameListMode::TreeView); UISettings::values.game_list_mode = mode; + ui->action_Show_Game_Name->setEnabled(mode == Settings::GameListMode::GridView); + + CheckIconSize(); game_list->ResetViewMode(); } @@ -3396,6 +3443,43 @@ void MainWindow::SetTreeView() { SetGameListMode(Settings::GameListMode::TreeView); } +void MainWindow::CheckIconSize() { + // When in grid view mode, with text off + // there is no point in having icons turned off.. + auto actions = game_size_actions->actions(); + if (UISettings::values.game_list_mode.GetValue() == Settings::GameListMode::GridView && + !UISettings::values.show_game_name.GetValue()) { + u32 newSize = UISettings::values.game_icon_size.GetValue(); + if (newSize == 0) { + newSize = 64; + UISettings::values.game_icon_size.SetValue(newSize); + } + + // Then disable the "none" action and update that menu. + for (size_t i = 0; i < default_game_icon_sizes.size(); i++) { + const auto current_size = newSize; + const auto size = default_game_icon_sizes[i].first; + if (current_size == size) actions.at(i)->setChecked(true); + } + + // Update this if you add anything before None. + actions.at(0)->setEnabled(false); + } else { + actions.at(0)->setEnabled(true); + } +} + +void MainWindow::ToggleShowGameName() { + auto &setting = UISettings::values.show_game_name; + const bool newValue = !setting.GetValue(); + ui->action_Show_Game_Name->setChecked(newValue); + setting.SetValue(newValue); + + CheckIconSize(); + + game_list->RefreshGameDirectory(); +} + void MainWindow::OnConfigure() { const auto old_theme = UISettings::values.theme; const bool old_discord_presence = UISettings::values.enable_discord_presence.GetValue(); @@ -3916,7 +4000,6 @@ void MainWindow::OnDataDialog() { // refresh stuff in case it was cleared OnGameListRefresh(); - } void MainWindow::OnToggleFilterBar() { @@ -3939,7 +4022,6 @@ void MainWindow::OnGameListRefresh() { SetFirmwareVersion(); } - void MainWindow::LaunchFirmwareApplet(u64 raw_program_id, std::optional cabinet_mode) { auto const program_id = Service::AM::AppletProgramId(raw_program_id); auto result = FirmwareManager::VerifyFirmware(*QtCommon::system.get()); @@ -4658,6 +4740,11 @@ void MainWindow::OnLanguageChanged(const QString& locale) { qApp->removeTranslator(&translator); } + QList actions = game_size_actions->actions(); + for (size_t i = 0; i < default_game_icon_sizes.size(); i++) { + actions.at(i)->setText(GetTranslatedGameIconSize(i)); + } + UISettings::values.language = locale.toStdString(); LoadTranslation(); ui->retranslateUi(this); diff --git a/src/yuzu/main_window.h b/src/yuzu/main_window.h index 3261ccc9a1..ddabe21ae0 100644 --- a/src/yuzu/main_window.h +++ b/src/yuzu/main_window.h @@ -15,6 +15,7 @@ #include #include #include +#include #include "common/common_types.h" #include "common/settings_enums.h" @@ -407,6 +408,9 @@ private slots: void SetGridView(); void SetTreeView(); + void CheckIconSize(); + void ToggleShowGameName(); + void LaunchFirmwareApplet(u64 program_id, std::optional mode); void OnCreateHomeMenuDesktopShortcut(); void OnCreateHomeMenuApplicationMenuShortcut(); @@ -532,6 +536,8 @@ private: QString startup_icon_theme; + QActionGroup *game_size_actions; + // Debugger panes ControllerDialog* controller_dialog = nullptr; From e544cb3cf6702fdec9ce2edc9f601a9e37d91153 Mon Sep 17 00:00:00 2001 From: Maufeat Date: Sat, 7 Feb 2026 22:59:38 +0100 Subject: [PATCH 03/14] [nce] add split patch mode to support modules bigger than 128MB (#3489) nce patcher was extended to support modules larger than 128MB due to ARM64 branch limit. now added a pre-patch and (existing) post-patch module code. Allowwing MRS/MSR/SVC handler to remain within branch branch range Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3489 Reviewed-by: MaranBr Reviewed-by: CamilleLaVey Co-authored-by: Maufeat Co-committed-by: Maufeat --- src/core/arm/nce/patcher.cpp | 479 ++++++++++++++++++++---------- src/core/arm/nce/patcher.h | 54 +++- src/core/hle/kernel/code_set.h | 12 + src/core/hle/kernel/k_process.cpp | 11 +- src/core/loader/nso.cpp | 36 ++- 5 files changed, 424 insertions(+), 168 deletions(-) diff --git a/src/core/arm/nce/patcher.cpp b/src/core/arm/nce/patcher.cpp index 28862c80b9..3a620bff12 100644 --- a/src/core/arm/nce/patcher.cpp +++ b/src/core/arm/nce/patcher.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #include @@ -20,9 +20,13 @@ namespace Core::NCE { Patcher::Patcher(Patcher&& other) noexcept : patch_cache(std::move(other.patch_cache)), m_patch_instructions(std::move(other.m_patch_instructions)), + m_patch_instructions_pre(std::move(other.m_patch_instructions_pre)), c(m_patch_instructions), + c_pre(m_patch_instructions_pre), m_save_context(other.m_save_context), m_load_context(other.m_load_context), + m_save_context_pre(other.m_save_context_pre), + m_load_context_pre(other.m_load_context_pre), mode(other.mode), total_program_size(other.total_program_size), m_relocate_module_index(other.m_relocate_module_index), @@ -42,20 +46,25 @@ using NativeExecutionParameters = Kernel::KThread::NativeExecutionParameters; constexpr size_t MaxRelativeBranch = 128_MiB; constexpr u32 ModuleCodeIndex = 0x24 / sizeof(u32); -Patcher::Patcher() : c(m_patch_instructions) { +Patcher::Patcher() : c(m_patch_instructions), c_pre(m_patch_instructions_pre) { LOG_WARNING(Core_ARM, "Patcher initialized with LRU cache {}", patch_cache.isEnabled() ? "enabled" : "disabled"); // The first word of the patch section is always a branch to the first instruction of the // module. c.dw(0); + c_pre.dw(0); // Write save context helper function. c.l(m_save_context); WriteSaveContext(); + c_pre.l(m_save_context_pre); + WriteSaveContext(c_pre); // Write load context helper function. c.l(m_load_context); WriteLoadContext(); + c_pre.l(m_load_context_pre); + WriteLoadContext(c_pre); } Patcher::~Patcher() = default; @@ -64,7 +73,16 @@ bool Patcher::PatchText(const Kernel::PhysicalMemory& program_image, const Kernel::CodeSet::Segment& code) { // If we have patched modules but cannot reach the new module, then it needs its own patcher. const size_t image_size = program_image.size(); - if (total_program_size + image_size > MaxRelativeBranch && total_program_size > 0) { + + // Check if we need split mode for large modules. A64 max takes 128MB + // tests showed that, with update, some are larger. (In this case 208MB) + bool use_split = false; + if (image_size > MaxRelativeBranch) { + if (total_program_size > 0) { + return false; + } + use_split = true; + } else if (total_program_size + image_size > MaxRelativeBranch && total_program_size > 0) { return false; } @@ -74,7 +92,12 @@ bool Patcher::PatchText(const Kernel::PhysicalMemory& program_image, // The first word of the patch section is always a branch to the first instruction of the // module. - curr_patch->m_branch_to_module_relocations.push_back({0, 0}); + if (use_split) { + // curr_patch->m_branch_to_module_relocations.push_back({0, 0}); + curr_patch->m_branch_to_module_relocations_pre.push_back({0, 0}); + } else { + curr_patch->m_branch_to_module_relocations.push_back({0, 0}); + } // Retrieve text segment data. const auto text = std::span{program_image}.subspan(code.offset, code.size); @@ -85,12 +108,18 @@ bool Patcher::PatchText(const Kernel::PhysicalMemory& program_image, for (u32 i = ModuleCodeIndex; i < static_cast(text_words.size()); i++) { const u32 inst = text_words[i]; - const auto AddRelocations = [&] { + const auto AddRelocations = [&](bool& pre_buffer) { const uintptr_t this_offset = i * sizeof(u32); const uintptr_t next_offset = this_offset + sizeof(u32); - // Relocate from here to patch. - this->BranchToPatch(this_offset); + pre_buffer = use_split && (this_offset < MaxRelativeBranch); + + // Relocate to pre- or post-patch + if (pre_buffer) { + this->BranchToPatchPre(this_offset); + } else { + this->BranchToPatch(this_offset); + } // Relocate from patch to next instruction. return next_offset; @@ -98,7 +127,13 @@ bool Patcher::PatchText(const Kernel::PhysicalMemory& program_image, // SVC if (auto svc = SVC{inst}; svc.Verify()) { - WriteSvcTrampoline(AddRelocations(), svc.GetValue()); + bool pre_buffer = false; + auto ret = AddRelocations(pre_buffer); + if (pre_buffer) { + WriteSvcTrampoline(ret, svc.GetValue(), c_pre, m_save_context_pre, m_load_context_pre); + } else { + WriteSvcTrampoline(ret, svc.GetValue(), c, m_save_context, m_load_context); + } continue; } @@ -109,13 +144,25 @@ bool Patcher::PatchText(const Kernel::PhysicalMemory& program_image, const auto src_reg = mrs.GetSystemReg() == TpidrroEl0 ? oaknut::SystemReg::TPIDRRO_EL0 : oaknut::SystemReg::TPIDR_EL0; const auto dest_reg = oaknut::XReg{static_cast(mrs.GetRt())}; - WriteMrsHandler(AddRelocations(), dest_reg, src_reg); + bool pre_buffer = false; + auto ret = AddRelocations(pre_buffer); + if (pre_buffer) { + WriteMrsHandler(ret, dest_reg, src_reg, c_pre); + } else { + WriteMrsHandler(ret, dest_reg, src_reg, c); + } continue; } // MRS Xn, CNTPCT_EL0 if (auto mrs = MRS{inst}; mrs.Verify() && mrs.GetSystemReg() == CntpctEl0) { - WriteCntpctHandler(AddRelocations(), oaknut::XReg{static_cast(mrs.GetRt())}); + bool pre_buffer = false; + auto ret = AddRelocations(pre_buffer); + if (pre_buffer) { + WriteCntpctHandler(ret, oaknut::XReg{static_cast(mrs.GetRt())}, c_pre); + } else { + WriteCntpctHandler(ret, oaknut::XReg{static_cast(mrs.GetRt())}, c); + } continue; } @@ -126,7 +173,13 @@ bool Patcher::PatchText(const Kernel::PhysicalMemory& program_image, // MSR TPIDR_EL0, Xn if (auto msr = MSR{inst}; msr.Verify() && msr.GetSystemReg() == TpidrEl0) { - WriteMsrHandler(AddRelocations(), oaknut::XReg{static_cast(msr.GetRt())}); + bool pre_buffer = false; + auto ret = AddRelocations(pre_buffer); + if (pre_buffer) { + WriteMsrHandler(ret, oaknut::XReg{static_cast(msr.GetRt())}, c_pre); + } else { + WriteMsrHandler(ret, oaknut::XReg{static_cast(msr.GetRt())}, c); + } continue; } @@ -137,7 +190,11 @@ bool Patcher::PatchText(const Kernel::PhysicalMemory& program_image, // Determine patching mode for the final relocation step total_program_size += image_size; - this->mode = image_size > MaxRelativeBranch ? PatchMode::PreText : PatchMode::PostData; + if (use_split) { + this->mode = PatchMode::Split; + } else { + this->mode = image_size > MaxRelativeBranch ? PatchMode::PreText : PatchMode::PostData; + } return true; } @@ -146,7 +203,9 @@ bool Patcher::RelocateAndCopy(Common::ProcessAddress load_base, Kernel::PhysicalMemory& program_image, EntryTrampolines* out_trampolines) { const size_t patch_size = GetSectionSize(); - const size_t image_size = program_image.size(); + const size_t pre_patch_size = GetPreSectionSize(); + + const size_t image_size = (mode == PatchMode::Split) ? program_image.size() - pre_patch_size : program_image.size(); // Retrieve text segment data. const auto text = std::span{program_image}.subspan(code.offset, code.size); @@ -162,6 +221,16 @@ bool Patcher::RelocateAndCopy(Common::ProcessAddress load_base, } }; + const auto ApplyBranchToPatchRelocationPre = [&](u32* target, const Relocation& rel) { + oaknut::CodeGenerator rc{target}; + rc.B(static_cast(rel.patch_offset) - static_cast(pre_patch_size) - static_cast(rel.module_offset)); + }; + + const auto ApplyBranchToPatchRelocationPostSplit = [&](u32* target, const Relocation& rel) { + oaknut::CodeGenerator rc{target}; + rc.B(static_cast(image_size) + static_cast(rel.patch_offset) - static_cast(rel.module_offset)); + }; + const auto ApplyBranchToModuleRelocation = [&](u32* target, const Relocation& rel) { oaknut::CodeGenerator rc{target}; if (mode == PatchMode::PreText) { @@ -171,6 +240,16 @@ bool Patcher::RelocateAndCopy(Common::ProcessAddress load_base, } }; + const auto ApplyBranchToModuleRelocationPre = [&](u32* target, const Relocation& rel) { + oaknut::CodeGenerator rc{target}; + rc.B(static_cast(pre_patch_size) + static_cast(rel.module_offset) - static_cast(rel.patch_offset)); + }; + + const auto ApplyBranchToModuleRelocationPostSplit = [&](u32* target, const Relocation& rel) { + oaknut::CodeGenerator rc{target}; + rc.B(static_cast(rel.module_offset) - static_cast(image_size) - static_cast(rel.patch_offset)); + }; + const auto RebasePatch = [&](ptrdiff_t patch_offset) { if (mode == PatchMode::PreText) { return GetInteger(load_base) + patch_offset; @@ -182,28 +261,87 @@ bool Patcher::RelocateAndCopy(Common::ProcessAddress load_base, const auto RebasePc = [&](uintptr_t module_offset) { if (mode == PatchMode::PreText) { return GetInteger(load_base) + patch_size + module_offset; - } else { - return GetInteger(load_base) + module_offset; } + if (mode == PatchMode::Split) { + return GetInteger(load_base) + pre_patch_size + module_offset; + } + + return GetInteger(load_base) + module_offset; }; // We are now ready to relocate! auto& patch = modules[m_relocate_module_index++]; - for (const Relocation& rel : patch.m_branch_to_patch_relocations) { - ApplyBranchToPatchRelocation(text_words.data() + rel.module_offset / sizeof(u32), rel); - } - for (const Relocation& rel : patch.m_branch_to_module_relocations) { - ApplyBranchToModuleRelocation(m_patch_instructions.data() + rel.patch_offset / sizeof(u32), - rel); + + if (mode == PatchMode::Split) { + for (const Relocation& rel : patch.m_branch_to_pre_patch_relocations) { + ApplyBranchToPatchRelocationPre(text_words.data() + rel.module_offset / sizeof(u32), rel); + } + LOG_DEBUG(Core_ARM, "applied Pre: {}", patch.m_branch_to_pre_patch_relocations.size()); + + for (const Relocation& rel : patch.m_branch_to_patch_relocations) { + ApplyBranchToPatchRelocationPostSplit(text_words.data() + rel.module_offset / sizeof(u32), rel); + } + LOG_DEBUG(Core_ARM, "applied Post: {}", patch.m_branch_to_patch_relocations.size()); + + for (const Relocation& rel : patch.m_branch_to_module_relocations_pre) { + ApplyBranchToModuleRelocationPre(m_patch_instructions_pre.data() + rel.patch_offset / sizeof(u32), rel); + } + LOG_DEBUG(Core_ARM, "aplied Pre-module {}", patch.m_branch_to_module_relocations_pre.size()); + + for (const Relocation& rel : patch.m_branch_to_module_relocations) { + ApplyBranchToModuleRelocationPostSplit(m_patch_instructions.data() + rel.patch_offset / sizeof(u32), rel); + } + LOG_DEBUG(Core_ARM, "applied Post-module {}", patch.m_branch_to_module_relocations.size()); + + // Pre + for (const Relocation& rel : patch.m_write_module_pc_relocations_pre) { + oaknut::CodeGenerator rc{m_patch_instructions_pre.data() + rel.patch_offset / sizeof(u32)}; + rc.dx(RebasePc(rel.module_offset)); + } + // Post + for (const Relocation& rel : patch.m_write_module_pc_relocations) { + oaknut::CodeGenerator rc{m_patch_instructions.data() + rel.patch_offset / sizeof(u32)}; + rc.dx(RebasePc(rel.module_offset)); + } + + // Trampolines (split pre + post) + for (const Trampoline& rel : patch.m_trampolines_pre) { + out_trampolines->insert({RebasePc(rel.module_offset), + GetInteger(load_base) + rel.patch_offset}); + } + for (const Trampoline& rel : patch.m_trampolines) { + out_trampolines->insert({RebasePc(rel.module_offset), + GetInteger(load_base) + pre_patch_size + image_size + rel.patch_offset}); + } + + if (!m_patch_instructions_pre.empty()) { + u32 insn = m_patch_instructions_pre[0]; + if ((insn & 0xFC000000) == 0x14000000) { + s32 imm26 = insn & 0x3FFFFFF; + // Sign extend + if (imm26 & 0x2000000) imm26 |= 0xFC000000; + } + } + } else { + for (const Relocation& rel : patch.m_branch_to_patch_relocations) { + ApplyBranchToPatchRelocation(text_words.data() + rel.module_offset / sizeof(u32), rel); + } + for (const Relocation& rel : patch.m_branch_to_module_relocations) { + ApplyBranchToModuleRelocation(m_patch_instructions.data() + rel.patch_offset / sizeof(u32), + rel); + } + + // Rewrite PC constants + for (const Relocation& rel : patch.m_write_module_pc_relocations) { + oaknut::CodeGenerator rc{m_patch_instructions.data() + rel.patch_offset / sizeof(u32)}; + rc.dx(RebasePc(rel.module_offset)); + } } - // Rewrite PC constants and record post trampolines - for (const Relocation& rel : patch.m_write_module_pc_relocations) { - oaknut::CodeGenerator rc{m_patch_instructions.data() + rel.patch_offset / sizeof(u32)}; - rc.dx(RebasePc(rel.module_offset)); - } - for (const Trampoline& rel : patch.m_trampolines) { - out_trampolines->insert({RebasePc(rel.module_offset), RebasePatch(rel.patch_offset)}); + if (mode != PatchMode::Split) { + for (const Trampoline& rel : patch.m_trampolines) { + out_trampolines->insert({RebasePc(rel.module_offset), RebasePatch(rel.patch_offset)}); + } } // Cortex-A57 seems to treat all exclusives as ordered, but newer processors do not. @@ -223,6 +361,15 @@ bool Patcher::RelocateAndCopy(Common::ProcessAddress load_base, ASSERT(image_size == total_program_size); std::memcpy(program_image.data(), m_patch_instructions.data(), m_patch_instructions.size() * sizeof(u32)); + } else if (this->mode == PatchMode::Split) { + const size_t current_size = program_image.size(); + program_image.resize(current_size + patch_size); + // Copy pre-patch buffer to the beginning + std::memcpy(program_image.data(), m_patch_instructions_pre.data(), + m_patch_instructions_pre.size() * sizeof(u32)); + // Same for post-patch buffer to the end + std::memcpy(program_image.data() + current_size, m_patch_instructions.data(), + m_patch_instructions.size() * sizeof(u32)); } else { program_image.resize(image_size + patch_size); std::memcpy(program_image.data() + image_size, m_patch_instructions.data(), @@ -238,202 +385,225 @@ size_t Patcher::GetSectionSize() const noexcept { return Common::AlignUp(m_patch_instructions.size() * sizeof(u32), Core::Memory::YUZU_PAGESIZE); } -void Patcher::WriteLoadContext() { +size_t Patcher::GetPreSectionSize() const noexcept { + return Common::AlignUp(m_patch_instructions_pre.size() * sizeof(u32), Core::Memory::YUZU_PAGESIZE); +} + +void Patcher::WriteLoadContext(oaknut::VectorCodeGenerator& cg) { // This function was called, which modifies X30, so use that as a scratch register. // SP contains the guest X30, so save our return X30 to SP + 8, since we have allocated 16 bytes // of stack. - c.STR(X30, SP, 8); - c.MRS(X30, oaknut::SystemReg::TPIDR_EL0); - c.LDR(X30, X30, offsetof(NativeExecutionParameters, native_context)); + cg.STR(X30, SP, 8); + cg.MRS(X30, oaknut::SystemReg::TPIDR_EL0); + cg.LDR(X30, X30, offsetof(NativeExecutionParameters, native_context)); // Load system registers. - c.LDR(W0, X30, offsetof(GuestContext, fpsr)); - c.MSR(oaknut::SystemReg::FPSR, X0); - c.LDR(W0, X30, offsetof(GuestContext, fpcr)); - c.MSR(oaknut::SystemReg::FPCR, X0); - c.LDR(W0, X30, offsetof(GuestContext, nzcv)); - c.MSR(oaknut::SystemReg::NZCV, X0); + cg.LDR(W0, X30, offsetof(GuestContext, fpsr)); + cg.MSR(oaknut::SystemReg::FPSR, X0); + cg.LDR(W0, X30, offsetof(GuestContext, fpcr)); + cg.MSR(oaknut::SystemReg::FPCR, X0); + cg.LDR(W0, X30, offsetof(GuestContext, nzcv)); + cg.MSR(oaknut::SystemReg::NZCV, X0); // Load all vector registers. static constexpr size_t VEC_OFF = offsetof(GuestContext, vector_registers); for (int i = 0; i <= 30; i += 2) { - c.LDP(oaknut::QReg{i}, oaknut::QReg{i + 1}, X30, VEC_OFF + 16 * i); + cg.LDP(oaknut::QReg{i}, oaknut::QReg{i + 1}, X30, VEC_OFF + 16 * i); } // Load all general-purpose registers except X30. for (int i = 0; i <= 28; i += 2) { - c.LDP(oaknut::XReg{i}, oaknut::XReg{i + 1}, X30, 8 * i); + cg.LDP(oaknut::XReg{i}, oaknut::XReg{i + 1}, X30, 8 * i); } // Reload our return X30 from the stack and return. // The patch code will reload the guest X30 for us. - c.LDR(X30, SP, 8); - c.RET(); + cg.LDR(X30, SP, 8); + cg.RET(); } -void Patcher::WriteSaveContext() { +void Patcher::WriteSaveContext(oaknut::VectorCodeGenerator& cg) { // This function was called, which modifies X30, so use that as a scratch register. // SP contains the guest X30, so save our X30 to SP + 8, since we have allocated 16 bytes of // stack. - c.STR(X30, SP, 8); - c.MRS(X30, oaknut::SystemReg::TPIDR_EL0); - c.LDR(X30, X30, offsetof(NativeExecutionParameters, native_context)); + cg.STR(X30, SP, 8); + cg.MRS(X30, oaknut::SystemReg::TPIDR_EL0); + cg.LDR(X30, X30, offsetof(NativeExecutionParameters, native_context)); // Store all general-purpose registers except X30. for (int i = 0; i <= 28; i += 2) { - c.STP(oaknut::XReg{i}, oaknut::XReg{i + 1}, X30, 8 * i); + cg.STP(oaknut::XReg{i}, oaknut::XReg{i + 1}, X30, 8 * i); } // Store all vector registers. static constexpr size_t VEC_OFF = offsetof(GuestContext, vector_registers); for (int i = 0; i <= 30; i += 2) { - c.STP(oaknut::QReg{i}, oaknut::QReg{i + 1}, X30, VEC_OFF + 16 * i); + cg.STP(oaknut::QReg{i}, oaknut::QReg{i + 1}, X30, VEC_OFF + 16 * i); } // Store guest system registers, X30 and SP, using X0 as a scratch register. - c.STR(X0, SP, PRE_INDEXED, -16); - c.LDR(X0, SP, 16); - c.STR(X0, X30, 8 * 30); - c.ADD(X0, SP, 32); - c.STR(X0, X30, offsetof(GuestContext, sp)); - c.MRS(X0, oaknut::SystemReg::FPSR); - c.STR(W0, X30, offsetof(GuestContext, fpsr)); - c.MRS(X0, oaknut::SystemReg::FPCR); - c.STR(W0, X30, offsetof(GuestContext, fpcr)); - c.MRS(X0, oaknut::SystemReg::NZCV); - c.STR(W0, X30, offsetof(GuestContext, nzcv)); - c.LDR(X0, SP, POST_INDEXED, 16); + cg.STR(X0, SP, PRE_INDEXED, -16); + cg.LDR(X0, SP, 16); + cg.STR(X0, X30, 8 * 30); + cg.ADD(X0, SP, 32); + cg.STR(X0, X30, offsetof(GuestContext, sp)); + cg.MRS(X0, oaknut::SystemReg::FPSR); + cg.STR(W0, X30, offsetof(GuestContext, fpsr)); + cg.MRS(X0, oaknut::SystemReg::FPCR); + cg.STR(W0, X30, offsetof(GuestContext, fpcr)); + cg.MRS(X0, oaknut::SystemReg::NZCV); + cg.STR(W0, X30, offsetof(GuestContext, nzcv)); + cg.LDR(X0, SP, POST_INDEXED, 16); // Reload our return X30 from the stack, and return. - c.LDR(X30, SP, 8); - c.RET(); + cg.LDR(X30, SP, 8); + cg.RET(); } -void Patcher::WriteSvcTrampoline(ModuleDestLabel module_dest, u32 svc_id) { +void Patcher::WriteSvcTrampoline(ModuleDestLabel module_dest, u32 svc_id, oaknut::VectorCodeGenerator& cg, oaknut::Label& save_ctx, oaknut::Label& load_ctx) { + // Determine if we're writing to the pre-patch buffer + const bool is_pre = (&cg == &c_pre); + // We are about to start saving state, so we need to lock the context. - this->LockContext(); + this->LockContext(cg); // Store guest X30 to the stack. Then, save the context and restore the stack. // This will save all registers except PC, but we know PC at patch time. - c.STR(X30, SP, PRE_INDEXED, -16); - c.BL(m_save_context); - c.LDR(X30, SP, POST_INDEXED, 16); + cg.STR(X30, SP, PRE_INDEXED, -16); + cg.BL(save_ctx); + cg.LDR(X30, SP, POST_INDEXED, 16); // Now that we've saved all registers, we can use any registers as scratch. // Store PC + 4 to arm interface, since we know the instruction offset from the entry point. oaknut::Label pc_after_svc; - c.MRS(X1, oaknut::SystemReg::TPIDR_EL0); - c.LDR(X1, X1, offsetof(NativeExecutionParameters, native_context)); - c.LDR(X2, pc_after_svc); - c.STR(X2, X1, offsetof(GuestContext, pc)); + cg.MRS(X1, oaknut::SystemReg::TPIDR_EL0); + cg.LDR(X1, X1, offsetof(NativeExecutionParameters, native_context)); + cg.LDR(X2, pc_after_svc); + cg.STR(X2, X1, offsetof(GuestContext, pc)); // Store SVC number to execute when we return - c.MOV(X2, svc_id); - c.STR(W2, X1, offsetof(GuestContext, svc)); + cg.MOV(X2, svc_id); + cg.STR(W2, X1, offsetof(GuestContext, svc)); // We are calling a SVC. Clear esr_el1 and return it. static_assert(std::is_same_v, u64>); oaknut::Label retry; - c.ADD(X2, X1, offsetof(GuestContext, esr_el1)); - c.l(retry); - c.LDAXR(X0, X2); - c.STLXR(W3, XZR, X2); - c.CBNZ(W3, retry); + cg.ADD(X2, X1, offsetof(GuestContext, esr_el1)); + cg.l(retry); + cg.LDAXR(X0, X2); + cg.STLXR(W3, XZR, X2); + cg.CBNZ(W3, retry); // Add "calling SVC" flag. Since this is X0, this is now our return value. - c.ORR(X0, X0, static_cast(HaltReason::SupervisorCall)); + cg.ORR(X0, X0, static_cast(HaltReason::SupervisorCall)); // Offset the GuestContext pointer to the HostContext member. // STP has limited range of [-512, 504] which we can't reach otherwise // NB: Due to this all offsets below are from the start of HostContext. - c.ADD(X1, X1, offsetof(GuestContext, host_ctx)); + cg.ADD(X1, X1, offsetof(GuestContext, host_ctx)); // Reload host TPIDR_EL0 and SP. static_assert(offsetof(HostContext, host_sp) + 8 == offsetof(HostContext, host_tpidr_el0)); - c.LDP(X2, X3, X1, offsetof(HostContext, host_sp)); - c.MOV(SP, X2); - c.MSR(oaknut::SystemReg::TPIDR_EL0, X3); + cg.LDP(X2, X3, X1, offsetof(HostContext, host_sp)); + cg.MOV(SP, X2); + cg.MSR(oaknut::SystemReg::TPIDR_EL0, X3); // Load callee-saved host registers and return to host. static constexpr size_t HOST_REGS_OFF = offsetof(HostContext, host_saved_regs); static constexpr size_t HOST_VREGS_OFF = offsetof(HostContext, host_saved_vregs); - c.LDP(X19, X20, X1, HOST_REGS_OFF); - c.LDP(X21, X22, X1, HOST_REGS_OFF + 2 * sizeof(u64)); - c.LDP(X23, X24, X1, HOST_REGS_OFF + 4 * sizeof(u64)); - c.LDP(X25, X26, X1, HOST_REGS_OFF + 6 * sizeof(u64)); - c.LDP(X27, X28, X1, HOST_REGS_OFF + 8 * sizeof(u64)); - c.LDP(X29, X30, X1, HOST_REGS_OFF + 10 * sizeof(u64)); - c.LDP(Q8, Q9, X1, HOST_VREGS_OFF); - c.LDP(Q10, Q11, X1, HOST_VREGS_OFF + 2 * sizeof(u128)); - c.LDP(Q12, Q13, X1, HOST_VREGS_OFF + 4 * sizeof(u128)); - c.LDP(Q14, Q15, X1, HOST_VREGS_OFF + 6 * sizeof(u128)); - c.RET(); + cg.LDP(X19, X20, X1, HOST_REGS_OFF); + cg.LDP(X21, X22, X1, HOST_REGS_OFF + 2 * sizeof(u64)); + cg.LDP(X23, X24, X1, HOST_REGS_OFF + 4 * sizeof(u64)); + cg.LDP(X25, X26, X1, HOST_REGS_OFF + 6 * sizeof(u64)); + cg.LDP(X27, X28, X1, HOST_REGS_OFF + 8 * sizeof(u64)); + cg.LDP(X29, X30, X1, HOST_REGS_OFF + 10 * sizeof(u64)); + cg.LDP(Q8, Q9, X1, HOST_VREGS_OFF); + cg.LDP(Q10, Q11, X1, HOST_VREGS_OFF + 2 * sizeof(u128)); + cg.LDP(Q12, Q13, X1, HOST_VREGS_OFF + 4 * sizeof(u128)); + cg.LDP(Q14, Q15, X1, HOST_VREGS_OFF + 6 * sizeof(u128)); + cg.RET(); // Write the post-SVC trampoline address, which will jump back to the guest after restoring its // state. - curr_patch->m_trampolines.push_back({c.offset(), module_dest}); + if (is_pre) { + curr_patch->m_trampolines_pre.push_back({cg.offset(), module_dest}); + } else { + curr_patch->m_trampolines.push_back({cg.offset(), module_dest}); + } // Host called this location. Save the return address so we can // unwind the stack properly when jumping back. - c.MRS(X2, oaknut::SystemReg::TPIDR_EL0); - c.LDR(X2, X2, offsetof(NativeExecutionParameters, native_context)); - c.ADD(X0, X2, offsetof(GuestContext, host_ctx)); - c.STR(X30, X0, offsetof(HostContext, host_saved_regs) + 11 * sizeof(u64)); + cg.MRS(X2, oaknut::SystemReg::TPIDR_EL0); + cg.LDR(X2, X2, offsetof(NativeExecutionParameters, native_context)); + cg.ADD(X0, X2, offsetof(GuestContext, host_ctx)); + cg.STR(X30, X0, offsetof(HostContext, host_saved_regs) + 11 * sizeof(u64)); // Reload all guest registers except X30 and PC. // The function also expects 16 bytes of stack already allocated. - c.STR(X30, SP, PRE_INDEXED, -16); - c.BL(m_load_context); - c.LDR(X30, SP, POST_INDEXED, 16); + cg.STR(X30, SP, PRE_INDEXED, -16); + cg.BL(load_ctx); + cg.LDR(X30, SP, POST_INDEXED, 16); // Use X1 as a scratch register to restore X30. - c.STR(X1, SP, PRE_INDEXED, -16); - c.MRS(X1, oaknut::SystemReg::TPIDR_EL0); - c.LDR(X1, X1, offsetof(NativeExecutionParameters, native_context)); - c.LDR(X30, X1, offsetof(GuestContext, cpu_registers) + sizeof(u64) * 30); - c.LDR(X1, SP, POST_INDEXED, 16); + cg.STR(X1, SP, PRE_INDEXED, -16); + cg.MRS(X1, oaknut::SystemReg::TPIDR_EL0); + cg.LDR(X1, X1, offsetof(NativeExecutionParameters, native_context)); + cg.LDR(X30, X1, offsetof(GuestContext, cpu_registers) + sizeof(u64) * 30); + cg.LDR(X1, SP, POST_INDEXED, 16); // Unlock the context. - this->UnlockContext(); + this->UnlockContext(cg); // Jump back to the instruction after the emulated SVC. - this->BranchToModule(module_dest); + if (&cg == &c_pre) + this->BranchToModulePre(module_dest); + else + this->BranchToModule(module_dest); // Store PC after call. - c.l(pc_after_svc); - this->WriteModulePc(module_dest); + cg.l(pc_after_svc); + if (&cg == &c_pre) + this->WriteModulePcPre(module_dest); + else + this->WriteModulePc(module_dest); } void Patcher::WriteMrsHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg, - oaknut::SystemReg src_reg) { + oaknut::SystemReg src_reg, oaknut::VectorCodeGenerator& cg) { // Retrieve emulated TLS register from GuestContext. - c.MRS(dest_reg, oaknut::SystemReg::TPIDR_EL0); + cg.MRS(dest_reg, oaknut::SystemReg::TPIDR_EL0); if (src_reg == oaknut::SystemReg::TPIDRRO_EL0) { - c.LDR(dest_reg, dest_reg, offsetof(NativeExecutionParameters, tpidrro_el0)); + cg.LDR(dest_reg, dest_reg, offsetof(NativeExecutionParameters, tpidrro_el0)); } else { - c.LDR(dest_reg, dest_reg, offsetof(NativeExecutionParameters, tpidr_el0)); + cg.LDR(dest_reg, dest_reg, offsetof(NativeExecutionParameters, tpidr_el0)); } // Jump back to the instruction after the emulated MRS. - this->BranchToModule(module_dest); + if (&cg == &c_pre) + this->BranchToModulePre(module_dest); + else + this->BranchToModule(module_dest); } -void Patcher::WriteMsrHandler(ModuleDestLabel module_dest, oaknut::XReg src_reg) { +void Patcher::WriteMsrHandler(ModuleDestLabel module_dest, oaknut::XReg src_reg, oaknut::VectorCodeGenerator& cg) { const auto scratch_reg = src_reg.index() == 0 ? X1 : X0; - c.STR(scratch_reg, SP, PRE_INDEXED, -16); + cg.STR(scratch_reg, SP, PRE_INDEXED, -16); // Save guest value to NativeExecutionParameters::tpidr_el0. - c.MRS(scratch_reg, oaknut::SystemReg::TPIDR_EL0); - c.STR(src_reg, scratch_reg, offsetof(NativeExecutionParameters, tpidr_el0)); + cg.MRS(scratch_reg, oaknut::SystemReg::TPIDR_EL0); + cg.STR(src_reg, scratch_reg, offsetof(NativeExecutionParameters, tpidr_el0)); // Restore scratch register. - c.LDR(scratch_reg, SP, POST_INDEXED, 16); + cg.LDR(scratch_reg, SP, POST_INDEXED, 16); // Jump back to the instruction after the emulated MSR. - this->BranchToModule(module_dest); + if (&cg == &c_pre) + this->BranchToModulePre(module_dest); + else + this->BranchToModule(module_dest); } -void Patcher::WriteCntpctHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg) { +void Patcher::WriteCntpctHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg, oaknut::VectorCodeGenerator& cg) { static Common::Arm64::NativeClock clock{}; const auto factor = clock.GetGuestCNTFRQFactor(); const auto raw_factor = std::bit_cast>(factor); @@ -446,80 +616,83 @@ void Patcher::WriteCntpctHandler(ModuleDestLabel module_dest, oaknut::XReg dest_ oaknut::Label factorhi; // Save scratches. - c.STP(scratch0, scratch1, SP, PRE_INDEXED, -16); + cg.STP(scratch0, scratch1, SP, PRE_INDEXED, -16); // Load counter value. - c.MRS(dest_reg, oaknut::SystemReg::CNTVCT_EL0); + cg.MRS(dest_reg, oaknut::SystemReg::CNTVCT_EL0); // Load scaling factor. - c.LDR(scratch0, factorlo); - c.LDR(scratch1, factorhi); + cg.LDR(scratch0, factorlo); + cg.LDR(scratch1, factorhi); // Multiply low bits and get result. - c.UMULH(scratch0, dest_reg, scratch0); + cg.UMULH(scratch0, dest_reg, scratch0); // Multiply high bits and add low bit result. - c.MADD(dest_reg, dest_reg, scratch1, scratch0); + cg.MADD(dest_reg, dest_reg, scratch1, scratch0); // Reload scratches. - c.LDP(scratch0, scratch1, SP, POST_INDEXED, 16); + cg.LDP(scratch0, scratch1, SP, POST_INDEXED, 16); // Jump back to the instruction after the emulated MRS. - this->BranchToModule(module_dest); + if (&cg == &c_pre) + this->BranchToModulePre(module_dest); + else + this->BranchToModule(module_dest); // Scaling factor constant values. - c.l(factorlo); - c.dx(raw_factor[0]); - c.l(factorhi); - c.dx(raw_factor[1]); + cg.l(factorlo); + cg.dx(raw_factor[0]); + cg.l(factorhi); + cg.dx(raw_factor[1]); } -void Patcher::LockContext() { +void Patcher::LockContext(oaknut::VectorCodeGenerator& cg) { oaknut::Label retry; // Save scratches. - c.STP(X0, X1, SP, PRE_INDEXED, -16); + cg.STP(X0, X1, SP, PRE_INDEXED, -16); // Reload lock pointer. - c.l(retry); - c.CLREX(); - c.MRS(X0, oaknut::SystemReg::TPIDR_EL0); - c.ADD(X0, X0, offsetof(NativeExecutionParameters, lock)); + cg.l(retry); + cg.CLREX(); + cg.MRS(X0, oaknut::SystemReg::TPIDR_EL0); + cg.ADD(X0, X0, offsetof(NativeExecutionParameters, lock)); static_assert(SpinLockLocked == 0); // Load-linked with acquire ordering. - c.LDAXR(W1, X0); + cg.LDAXR(W1, X0); // If the value was SpinLockLocked, clear monitor and retry. - c.CBZ(W1, retry); + cg.CBZ(W1, retry); // Store-conditional SpinLockLocked with relaxed ordering. - c.STXR(W1, WZR, X0); + cg.STXR(W1, WZR, X0); // If we failed to store, retry. - c.CBNZ(W1, retry); + cg.CBNZ(W1, retry); // We succeeded! Reload scratches. - c.LDP(X0, X1, SP, POST_INDEXED, 16); + cg.LDP(X0, X1, SP, POST_INDEXED, 16); } -void Patcher::UnlockContext() { +void Patcher::UnlockContext(oaknut::VectorCodeGenerator& cg) { // Save scratches. - c.STP(X0, X1, SP, PRE_INDEXED, -16); + cg.STP(X0, X1, SP, PRE_INDEXED, -16); // Load lock pointer. - c.MRS(X0, oaknut::SystemReg::TPIDR_EL0); - c.ADD(X0, X0, offsetof(NativeExecutionParameters, lock)); + cg.MRS(X0, oaknut::SystemReg::TPIDR_EL0); + cg.ADD(X0, X0, offsetof(NativeExecutionParameters, lock)); // Load SpinLockUnlocked. - c.MOV(W1, SpinLockUnlocked); + cg.MOV(W1, SpinLockUnlocked); // Store value with release ordering. - c.STLR(W1, X0); + cg.STLR(W1, X0); // Load scratches. - c.LDP(X0, X1, SP, POST_INDEXED, 16); + cg.LDP(X0, X1, SP, POST_INDEXED, 16); } } // namespace Core::NCE diff --git a/src/core/arm/nce/patcher.h b/src/core/arm/nce/patcher.h index 31b122477f..f2b11fcda0 100644 --- a/src/core/arm/nce/patcher.h +++ b/src/core/arm/nce/patcher.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #pragma once @@ -41,6 +41,7 @@ enum class PatchMode : u32 { None, PreText, ///< Patch section is inserted before .text PostData, ///< Patch section is inserted after .data + Split, ///< Patch sections are inserted before .text and after .data }; using ModuleTextAddress = u64; @@ -63,6 +64,7 @@ public: bool RelocateAndCopy(Common::ProcessAddress load_base, const Kernel::CodeSet::Segment& code, Kernel::PhysicalMemory& program_image, EntryTrampolines* out_trampolines); size_t GetSectionSize() const noexcept; + size_t GetPreSectionSize() const noexcept; [[nodiscard]] PatchMode GetPatchMode() const noexcept { return mode; @@ -76,15 +78,25 @@ private: uintptr_t module_offset; }; - void WriteLoadContext(); - void WriteSaveContext(); - void LockContext(); - void UnlockContext(); - void WriteSvcTrampoline(ModuleDestLabel module_dest, u32 svc_id); - void WriteMrsHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg, - oaknut::SystemReg src_reg); - void WriteMsrHandler(ModuleDestLabel module_dest, oaknut::XReg src_reg); - void WriteCntpctHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg); + // Core implementations with explicit code generator + void WriteLoadContext(oaknut::VectorCodeGenerator& code); + void WriteSaveContext(oaknut::VectorCodeGenerator& code); + void LockContext(oaknut::VectorCodeGenerator& code); + void UnlockContext(oaknut::VectorCodeGenerator& code); + void WriteSvcTrampoline(ModuleDestLabel module_dest, u32 svc_id, oaknut::VectorCodeGenerator& code, oaknut::Label& save_ctx, oaknut::Label& load_ctx); + void WriteMrsHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg, oaknut::SystemReg src_reg, oaknut::VectorCodeGenerator& code); + void WriteMsrHandler(ModuleDestLabel module_dest, oaknut::XReg src_reg, oaknut::VectorCodeGenerator& code); + void WriteCntpctHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg, oaknut::VectorCodeGenerator& code); + + // Convenience wrappers using default code generator + void WriteLoadContext() { WriteLoadContext(c); } + void WriteSaveContext() { WriteSaveContext(c); } + void LockContext() { LockContext(c); } + void UnlockContext() { UnlockContext(c); } + void WriteSvcTrampoline(ModuleDestLabel module_dest, u32 svc_id) { WriteSvcTrampoline(module_dest, svc_id, c, m_save_context, m_load_context); } + void WriteMrsHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg, oaknut::SystemReg src_reg) { WriteMrsHandler(module_dest, dest_reg, src_reg, c); } + void WriteMsrHandler(ModuleDestLabel module_dest, oaknut::XReg src_reg) { WriteMsrHandler(module_dest, src_reg, c); } + void WriteCntpctHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg) { WriteCntpctHandler(module_dest, dest_reg, c); } private: static constexpr size_t CACHE_SIZE = 16384; // Cache size for patch entries @@ -111,19 +123,34 @@ private: } } + void BranchToPatchPre(uintptr_t module_dest) { + curr_patch->m_branch_to_pre_patch_relocations.push_back({c_pre.offset(), module_dest}); + } + void BranchToModule(uintptr_t module_dest) { curr_patch->m_branch_to_module_relocations.push_back({c.offset(), module_dest}); c.dw(0); } + void BranchToModulePre(uintptr_t module_dest) { + curr_patch->m_branch_to_module_relocations_pre.push_back({c_pre.offset(), module_dest}); + c_pre.dw(0); + } + void WriteModulePc(uintptr_t module_dest) { curr_patch->m_write_module_pc_relocations.push_back({c.offset(), module_dest}); c.dx(0); } + void WriteModulePcPre(uintptr_t module_dest) { + curr_patch->m_write_module_pc_relocations_pre.push_back({c_pre.offset(), module_dest}); + c_pre.dx(0); + } + private: // List of patch instructions we have generated. std::vector m_patch_instructions{}; + std::vector m_patch_instructions_pre{}; // Relocation type for relative branch from module to patch. struct Relocation { @@ -133,15 +160,22 @@ private: struct ModulePatch { std::vector m_trampolines; + std::vector m_trampolines_pre; std::vector m_branch_to_patch_relocations{}; + std::vector m_branch_to_pre_patch_relocations{}; std::vector m_branch_to_module_relocations{}; + std::vector m_branch_to_module_relocations_pre{}; std::vector m_write_module_pc_relocations{}; + std::vector m_write_module_pc_relocations_pre{}; std::vector m_exclusives{}; }; oaknut::VectorCodeGenerator c; + oaknut::VectorCodeGenerator c_pre; oaknut::Label m_save_context{}; oaknut::Label m_load_context{}; + oaknut::Label m_save_context_pre{}; + oaknut::Label m_load_context_pre{}; PatchMode mode{PatchMode::None}; size_t total_program_size{}; size_t m_relocate_module_index{}; diff --git a/src/core/hle/kernel/code_set.h b/src/core/hle/kernel/code_set.h index 4d2d0098e7..1416fc52b1 100644 --- a/src/core/hle/kernel/code_set.h +++ b/src/core/hle/kernel/code_set.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -83,6 +86,14 @@ struct CodeSet final { const Segment& PatchSegment() const { return patch_segment; } + + Segment& PostPatchSegment() { + return post_patch_segment; + } + + const Segment& PostPatchSegment() const { + return post_patch_segment; + } #endif /// The overall data that backs this code set. @@ -93,6 +104,7 @@ struct CodeSet final { #ifdef HAS_NCE Segment patch_segment; + Segment post_patch_segment; #endif /// The entry point address for this code set. diff --git a/src/core/hle/kernel/k_process.cpp b/src/core/hle/kernel/k_process.cpp index 082049f957..bd50f74c27 100644 --- a/src/core/hle/kernel/k_process.cpp +++ b/src/core/hle/kernel/k_process.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project @@ -1258,6 +1258,7 @@ void KProcess::LoadModule(CodeSet code_set, KProcessAddress base_addr) { #ifdef HAS_NCE const auto& patch = code_set.PatchSegment(); + const auto& post_patch = code_set.PostPatchSegment(); if (this->IsApplication() && Settings::IsNceEnabled() && patch.size != 0) { auto& buffer = m_kernel.System().DeviceMemory().buffer; const auto& code = code_set.CodeSegment(); @@ -1265,7 +1266,15 @@ void KProcess::LoadModule(CodeSet code_set, KProcessAddress base_addr) { Common::MemoryPermission::Read | Common::MemoryPermission::Execute); buffer.Protect(GetInteger(base_addr + patch.addr), patch.size, Common::MemoryPermission::Read | Common::MemoryPermission::Execute); + // Protect post-patch segment if it exists like abve + if (post_patch.size != 0) { + buffer.Protect(GetInteger(base_addr + post_patch.addr), post_patch.size, + Common::MemoryPermission::Read | Common::MemoryPermission::Execute); + } ReprotectSegment(code_set.PatchSegment(), Svc::MemoryPermission::None); + if (post_patch.size != 0) { + ReprotectSegment(code_set.PostPatchSegment(), Svc::MemoryPermission::None); + } } #endif } diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp index e3e3f83ca2..458df110b6 100644 --- a/src/core/loader/nso.cpp +++ b/src/core/loader/nso.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project @@ -102,6 +102,8 @@ std::optional AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core:: auto* patch = &patches->operator[](patch_index); if (patch->GetPatchMode() == Core::NCE::PatchMode::PreText) { return patch->GetSectionSize(); + } else if (patch->GetPatchMode() == Core::NCE::PatchMode::Split) { + return patch->GetPreSectionSize(); } } #endif @@ -178,12 +180,26 @@ std::optional AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core:: } } else if (patch) { // Relocate code patch and copy to the program_image. + // Save size before RelocateAndCopy (which may resize) + const size_t size_before_relocate = program_image.size(); if (patch->RelocateAndCopy(load_base, code, program_image, &process.GetPostHandlers())) { // Update patch section. auto& patch_segment = codeset.PatchSegment(); - patch_segment.addr = - patch->GetPatchMode() == Core::NCE::PatchMode::PreText ? 0 : image_size; - patch_segment.size = static_cast(patch->GetSectionSize()); + auto& post_patch_segment = codeset.PostPatchSegment(); + const auto patch_mode = patch->GetPatchMode(); + if (patch_mode == Core::NCE::PatchMode::PreText) { + patch_segment.addr = 0; + patch_segment.size = static_cast(patch->GetSectionSize()); + } else if (patch_mode == Core::NCE::PatchMode::Split) { + // For Split-mode, we are using pre-patch buffer at start, post-patch buffer at end + patch_segment.addr = 0; + patch_segment.size = static_cast(patch->GetPreSectionSize()); + post_patch_segment.addr = size_before_relocate; + post_patch_segment.size = static_cast(patch->GetSectionSize()); + } else { + patch_segment.addr = image_size; + patch_segment.size = static_cast(patch->GetSectionSize()); + } } // Refresh image_size to take account the patch section if it was added by RelocateAndCopy @@ -193,6 +209,18 @@ std::optional AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core:: // If we aren't actually loading (i.e. just computing the process code layout), we are done if (!load_into_process) { +#ifdef HAS_NCE + // Ok, so for Split mode, we need to account for pre-patch and post-patch space + // which will be added during RelocateAndCopy in the second pass. Where it crashed + // in Android Studio at PreText. May be a better way. Works for now. + if (patch && patch->GetPatchMode() == Core::NCE::PatchMode::Split) { + return load_base + patch->GetPreSectionSize() + image_size + patch->GetSectionSize(); + } else if (patch && patch->GetPatchMode() == Core::NCE::PatchMode::PreText) { + return load_base + patch->GetSectionSize() + image_size; + } else if (patch && patch->GetPatchMode() == Core::NCE::PatchMode::PostData) { + return load_base + image_size + patch->GetSectionSize(); + } +#endif return load_base + image_size; } From a56b8d3de8af670fb1d8c70211ca5275c1aa4c52 Mon Sep 17 00:00:00 2001 From: lizzie Date: Mon, 9 Feb 2026 01:14:32 +0100 Subject: [PATCH 04/14] [core, windows] remove microSleep() and simply wait on events like on linux (#3498) we shall see if the original code was put there for a reason or if the microsleeps actually are horrid Signed-off-by: lizzie Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3498 Reviewed-by: CamilleLaVey Co-authored-by: lizzie Co-committed-by: lizzie --- src/common/CMakeLists.txt | 4 +- src/common/x64/cpu_wait.cpp | 78 ------------------------------------- src/common/x64/cpu_wait.h | 10 ----- src/core/core_timing.cpp | 29 +------------- 4 files changed, 3 insertions(+), 118 deletions(-) delete mode 100644 src/common/x64/cpu_wait.cpp delete mode 100644 src/common/x64/cpu_wait.h diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 3d09c1caea..042f5c7bd9 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project # SPDX-License-Identifier: GPL-3.0-or-later # SPDX-FileCopyrightText: 2018 yuzu Emulator Project SPDX-License-Identifier: @@ -186,8 +186,6 @@ if(ARCHITECTURE_x86_64) common PRIVATE x64/cpu_detect.cpp x64/cpu_detect.h - x64/cpu_wait.cpp - x64/cpu_wait.h x64/native_clock.cpp x64/native_clock.h x64/rdtsc.cpp diff --git a/src/common/x64/cpu_wait.cpp b/src/common/x64/cpu_wait.cpp deleted file mode 100644 index 12f2e9d4b1..0000000000 --- a/src/common/x64/cpu_wait.cpp +++ /dev/null @@ -1,78 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - -// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include - -#ifdef _MSC_VER -#include -#endif - -#include "common/x64/cpu_detect.h" -#include "common/x64/cpu_wait.h" -#include "common/x64/rdtsc.h" - -namespace Common::X64 { - -namespace { - -// 100,000 cycles is a reasonable amount of time to wait to save on CPU resources. -// For reference: -// At 1 GHz, 100K cycles is 100us -// At 2 GHz, 100K cycles is 50us -// At 4 GHz, 100K cycles is 25us -constexpr auto PauseCycles = 100'000U; - -} // Anonymous namespace - -#if defined(_MSC_VER) && !defined(__clang__) -__forceinline static void TPAUSE() { - static constexpr auto RequestC02State = 0U; - _tpause(RequestC02State, FencedRDTSC() + PauseCycles); -} - -__forceinline static void MWAITX() { - static constexpr auto EnableWaitTimeFlag = 1U << 1; - static constexpr auto RequestC1State = 0U; - - // monitor_var should be aligned to a cache line. - alignas(64) u64 monitor_var{}; - _mm_monitorx(&monitor_var, 0, 0); - _mm_mwaitx(EnableWaitTimeFlag, RequestC1State, PauseCycles); -} -#else -static void TPAUSE() { - static constexpr auto RequestC02State = 0U; - const auto tsc = FencedRDTSC() + PauseCycles; - const auto eax = static_cast(tsc & 0xFFFFFFFF); - const auto edx = static_cast(tsc >> 32); - asm volatile("tpause %0" : : "r"(RequestC02State), "d"(edx), "a"(eax)); -} - -static void MWAITX() { - static constexpr auto EnableWaitTimeFlag = 1U << 1; - static constexpr auto RequestC1State = 0U; - - // monitor_var should be aligned to a cache line. - alignas(64) u64 monitor_var{}; - asm volatile("monitorx" : : "a"(&monitor_var), "c"(0), "d"(0)); - asm volatile("mwaitx" : : "a"(RequestC1State), "b"(PauseCycles), "c"(EnableWaitTimeFlag)); -} -#endif - -void MicroSleep() { - static const bool has_waitpkg = GetCPUCaps().waitpkg; - static const bool has_monitorx = GetCPUCaps().monitorx; - - if (has_waitpkg) { - TPAUSE(); - } else if (has_monitorx) { - MWAITX(); - } else { - std::this_thread::yield(); - } -} - -} // namespace Common::X64 diff --git a/src/common/x64/cpu_wait.h b/src/common/x64/cpu_wait.h deleted file mode 100644 index 99d3757a76..0000000000 --- a/src/common/x64/cpu_wait.h +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -namespace Common::X64 { - -void MicroSleep(); - -} // namespace Common::X64 diff --git a/src/core/core_timing.cpp b/src/core/core_timing.cpp index 5a582c8cff..b0fa7cd503 100644 --- a/src/core/core_timing.cpp +++ b/src/core/core_timing.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project @@ -13,10 +13,6 @@ #include "common/windows/timer_resolution.h" #endif -#ifdef ARCHITECTURE_x86_64 -#include "common/x64/cpu_wait.h" -#endif - #include "common/settings.h" #include "core/core_timing.h" #include "core/hardware_properties.h" @@ -287,28 +283,7 @@ void CoreTiming::ThreadLoop() { if (next_time) { // There are more events left in the queue, wait until the next event. auto wait_time = *next_time - GetGlobalTimeNs().count(); - if (wait_time > 0) { -#ifdef _WIN32 - while (!paused && !event.IsSet() && wait_time > 0) { - wait_time = *next_time - GetGlobalTimeNs().count(); - if (wait_time >= timer_resolution_ns) { - Common::Windows::SleepForOneTick(); - } else { -#ifdef ARCHITECTURE_x86_64 - Common::X64::MicroSleep(); -#else - std::this_thread::yield(); -#endif - } - } - - if (event.IsSet()) { - event.Reset(); - } -#else - event.WaitFor(std::chrono::nanoseconds(wait_time)); -#endif - } + event.WaitFor(std::chrono::nanoseconds(wait_time)); } else { // Queue is empty, wait until another event is scheduled and signals us to // continue. From 866881d0e36721f52ce3d788c2a33c28ad1fcaeb Mon Sep 17 00:00:00 2001 From: MrPurple666 Date: Mon, 9 Feb 2026 04:26:43 +0100 Subject: [PATCH 05/14] [android] add FD_DEV_FEATURES in env loader (#3493) This environment variable fixes some glitches in OneUI 7 and HyperOS 3. Thanks StevenMX for letting me know. Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3493 Reviewed-by: CamilleLaVey Co-authored-by: MrPurple666 Co-committed-by: MrPurple666 --- .../yuzu_emu/fragments/FreedrenoSettingsFragment.kt | 4 ++-- .../org/yuzu/yuzu_emu/utils/NativeFreedrenoConfig.kt | 12 +++++++++++- src/android/app/src/main/jni/native_freedreno.cpp | 7 ++++++- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/FreedrenoSettingsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/FreedrenoSettingsFragment.kt index c86ef0b800..40111272d5 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/FreedrenoSettingsFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/FreedrenoSettingsFragment.kt @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later package org.yuzu.yuzu_emu.fragments @@ -110,7 +110,7 @@ class FreedrenoSettingsFragment : Fragment() { val commonVars = listOf( "TU_DEBUG", "FD_MESA_DEBUG", "IR3_SHADER_DEBUG", "FD_RD_DUMP", "FD_RD_DUMP_FRAMES", "FD_RD_DUMP_TESTNAME", - "TU_BREADCRUMBS" + "TU_BREADCRUMBS", "FD_DEV_FEATURES" ) for (varName in commonVars) { diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeFreedrenoConfig.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeFreedrenoConfig.kt index 44d5ac5b7f..f9c3f71c36 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeFreedrenoConfig.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeFreedrenoConfig.kt @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later package org.yuzu.yuzu_emu.utils @@ -144,6 +144,15 @@ object FreedrenoPresets { ) ) + val DEV_FEATURES_UBWC_HINT = FreedrenoPreset( + name = "Dev - UBWC Flag Hint", + description = "Enable TP UBWC flag hint for development", + icon = "ic_dev_features", + variables = mapOf( + "FD_DEV_FEATURES" to "enable_tp_ubwc_flag_hint=1" + ) + ) + val PERFORMANCE_DEFAULT = FreedrenoPreset( name = "Performance - Default", description = "Clear all debug options for performance", @@ -159,6 +168,7 @@ object FreedrenoPresets { CAPTURE_FRAMES, SHADER_DEBUG, GPU_HANG_TRACE, + DEV_FEATURES_UBWC_HINT, PERFORMANCE_DEFAULT ) } diff --git a/src/android/app/src/main/jni/native_freedreno.cpp b/src/android/app/src/main/jni/native_freedreno.cpp index 0879a85ea8..15c798e5ef 100644 --- a/src/android/app/src/main/jni/native_freedreno.cpp +++ b/src/android/app/src/main/jni/native_freedreno.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later /** @@ -258,6 +258,11 @@ Java_org_yuzu_yuzu_1emu_utils_NativeFreedrenoConfig_setFreedrenoEnv( } LOG_INFO(Frontend, "[Freedreno] Set {}={}", var_name, value); + + if (var_name == "FD_DEV_FEATURES") { + LOG_INFO(Frontend, "[Freedreno] FD_DEV_FEATURES enabled: {}", value); + } + return JNI_TRUE; } From 0f8b35c4cd0f8d09b38edc52b006472cd98540fc Mon Sep 17 00:00:00 2001 From: lizzie Date: Mon, 9 Feb 2026 21:21:14 +0100 Subject: [PATCH 06/14] [vk, macos] fix missing VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT sType (#3510) sType wasn't properly set for the creationInfo struct (was init to 0) Signed-off-by: lizzie Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3510 Reviewed-by: CamilleLaVey Reviewed-by: DraVee Co-authored-by: lizzie Co-committed-by: lizzie --- src/video_core/vulkan_common/vulkan_surface.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/video_core/vulkan_common/vulkan_surface.cpp b/src/video_core/vulkan_common/vulkan_surface.cpp index f1b56cc0a8..dc65d3960a 100644 --- a/src/video_core/vulkan_common/vulkan_surface.cpp +++ b/src/video_core/vulkan_common/vulkan_surface.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project @@ -32,13 +32,15 @@ vk::SurfaceKHR CreateSurface( } #elif defined(__APPLE__) if (window_info.type == Core::Frontend::WindowSystemType::Cocoa) { - const VkMetalSurfaceCreateInfoEXT macos_ci = { + const VkMetalSurfaceCreateInfoEXT metal_ci = { + .sType = VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT, + .pNext = nullptr, + .flags = 0, .pLayer = static_cast(window_info.render_surface), }; - const auto vkCreateMetalSurfaceEXT = reinterpret_cast( - dld.vkGetInstanceProcAddr(*instance, "vkCreateMetalSurfaceEXT")); + const auto vkCreateMetalSurfaceEXT = reinterpret_cast(dld.vkGetInstanceProcAddr(*instance, "vkCreateMetalSurfaceEXT")); if (!vkCreateMetalSurfaceEXT || - vkCreateMetalSurfaceEXT(*instance, &macos_ci, nullptr, &unsafe_surface) != VK_SUCCESS) { + vkCreateMetalSurfaceEXT(*instance, &metal_ci, nullptr, &unsafe_surface) != VK_SUCCESS) { LOG_ERROR(Render_Vulkan, "Failed to initialize Metal surface"); throw vk::Exception(VK_ERROR_INITIALIZATION_FAILED); } From a8093c2a3cf98ccccabeb1d6a2c1ccc5e191557f Mon Sep 17 00:00:00 2001 From: lizzie Date: Tue, 10 Feb 2026 03:34:07 +0100 Subject: [PATCH 07/14] [*] change all std::unordered_map and std::unordered_set into ankerl::unordered_dense::map/set variants (#3442) mainly doing this to reduce memory footprint; we all know how nice ankerl::unordered_dense is in theory 4x faster - in practice these maps arent that "hot" anyways so not likely to have much perf gained i just want to reduce mem fragmentation to ease my porting process, plus it helps other platforms as well (ahem weak Mediatek devices) :) Signed-off-by: lizzie Co-authored-by: Caio Oliveira Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3442 Reviewed-by: DraVee Reviewed-by: CamilleLaVey Co-authored-by: lizzie Co-committed-by: lizzie --- ...-memset-when-clearing-an-empty-table.patch | 26 +++++++++++++++++++ CMakeLists.txt | 1 + externals/CMakeLists.txt | 6 ++--- externals/cpmfile.json | 9 ++++--- .../app/src/main/jni/game_metadata.cpp | 5 +++- src/android/app/src/main/jni/native_input.cpp | 5 +++- src/common/CMakeLists.txt | 2 +- src/common/fs/path_util.cpp | 8 +++--- src/common/host_memory.cpp | 6 ++--- src/common/input.h | 7 +++-- src/common/param_package.h | 7 +++-- src/core/arm/dynarmic/arm_dynarmic_64.h | 4 +-- src/core/arm/nce/lru_cache.h | 6 ++--- src/core/arm/nce/patcher.h | 4 +-- src/core/file_sys/vfs/vfs_layered.cpp | 9 ++++--- src/core/hle/kernel/k_process.h | 6 ++--- src/core/hle/kernel/kernel.cpp | 8 +++--- src/core/hle/kernel/kernel.h | 4 +-- .../am/frontend/applet_web_browser_types.h | 6 ++--- .../hle/service/bcat/news/builtin_news.cpp | 4 +-- src/core/hle/service/bcat/news/news_storage.h | 6 ++--- src/core/hle/service/caps/caps_manager.h | 7 +++-- src/core/hle/service/ldn/lan_discovery.h | 6 ++--- src/core/hle/service/lm/lm.cpp | 6 ++--- src/core/hle/service/nifm/nifm.cpp | 4 +-- src/core/hle/service/nvdrv/core/container.h | 5 +++- src/core/hle/service/nvdrv/core/nvmap.h | 7 +++-- .../hle/service/nvdrv/devices/nvhost_as_gpu.h | 6 ++--- .../hle/service/nvdrv/devices/nvhost_gpu.h | 4 +-- .../nvdrv/devices/nvhost_nvdec_common.h | 7 +++-- src/core/hle/service/nvdrv/devices/nvmap.h | 7 +++-- src/core/hle/service/nvdrv/nvdrv.h | 9 ++++--- .../nvnflinger/hos_binder_driver_server.h | 9 ++++--- src/core/hle/service/olsc/daemon_controller.h | 10 +++---- src/core/hle/service/sm/sm.h | 8 +++--- src/core/hle/service/vi/conductor.h | 7 +++-- src/core/launch_timestamp_cache.cpp | 6 ++--- src/dynarmic/CMakeLists.txt | 1 + src/dynarmic/src/dynarmic/CMakeLists.txt | 18 +++---------- src/dynarmic/tests/A64/fibonacci.cpp | 6 ++--- src/dynarmic/tests/A64/testenv.h | 6 ++--- src/hid_core/frontend/emulated_console.h | 7 +++-- src/hid_core/frontend/emulated_controller.h | 6 ++--- src/hid_core/frontend/emulated_devices.h | 7 +++-- src/input_common/drivers/android.h | 5 +++- src/input_common/drivers/sdl_driver.cpp | 4 +-- src/input_common/drivers/sdl_driver.h | 7 +++-- src/input_common/input_engine.h | 17 +++++++----- src/input_common/main.h | 11 +++++--- src/shader_recompiler/environment.h | 8 +++--- .../maxwell/structured_control_flow.cpp | 6 ++--- .../ir_opt/ssa_rewrite_pass.cpp | 14 +++++++--- src/shader_recompiler/ir_opt/texture_pass.cpp | 2 +- src/tests/common/fibers.cpp | 7 +++-- src/tests/video_core/memory_tracker.cpp | 4 +-- .../buffer_cache/buffer_cache_base.h | 4 +-- .../buffer_cache/memory_tracker_base.h | 4 +-- src/video_core/control/channel_state_cache.h | 8 +++--- src/video_core/control/scheduler.h | 7 +++-- src/video_core/engines/maxwell_3d.h | 4 +-- .../engines/sw_blitter/converter.cpp | 6 ++--- src/video_core/gpu.cpp | 4 +-- src/video_core/gpu_logging/gpu_logging.h | 4 +-- src/video_core/host1x/codecs/decoder.h | 5 +++- src/video_core/host1x/host1x.h | 12 ++++----- src/video_core/macro.h | 8 +++--- src/video_core/query_cache.h | 8 +++--- src/video_core/query_cache/query_cache.h | 4 +-- src/video_core/query_cache/query_cache_base.h | 9 ++++--- .../renderer_opengl/gl_buffer_cache.h | 6 ++--- .../renderer_opengl/gl_shader_cache.h | 9 ++++--- .../renderer_opengl/gl_texture_cache.h | 4 +-- .../renderer_vulkan/vk_pipeline_cache.h | 9 ++++--- .../renderer_vulkan/vk_query_cache.cpp | 8 +++--- .../renderer_vulkan/vk_render_pass_cache.cpp | 2 +- .../renderer_vulkan/vk_render_pass_cache.h | 7 +++-- src/video_core/shader_cache.h | 8 +++--- src/video_core/shader_environment.h | 18 ++++++------- src/video_core/texture_cache/texture_cache.h | 10 +++---- .../texture_cache/texture_cache_base.h | 23 ++++++++-------- .../vulkan_common/vulkan_device.cpp | 10 +++---- src/video_core/vulkan_common/vulkan_device.h | 4 +-- src/yuzu/compatibility_list.h | 7 +++-- src/yuzu/configuration/input_profiles.h | 6 ++--- src/yuzu/loading_screen.cpp | 5 +++- src/yuzu/loading_screen.h | 7 +++-- src/yuzu/multiplayer/chat_room.h | 9 +++---- 87 files changed, 368 insertions(+), 254 deletions(-) create mode 100644 .patch/unordered-dense/0001-avoid-memset-when-clearing-an-empty-table.patch mode change 100755 => 100644 src/video_core/host1x/codecs/decoder.h diff --git a/.patch/unordered-dense/0001-avoid-memset-when-clearing-an-empty-table.patch b/.patch/unordered-dense/0001-avoid-memset-when-clearing-an-empty-table.patch new file mode 100644 index 0000000000..f7d7c7ebe2 --- /dev/null +++ b/.patch/unordered-dense/0001-avoid-memset-when-clearing-an-empty-table.patch @@ -0,0 +1,26 @@ +From b3622608433c183ba868a1dc8dd9cf285eb3b916 Mon Sep 17 00:00:00 2001 +From: Dario Petrillo +Date: Thu, 27 Nov 2025 23:12:38 +0100 +Subject: [PATCH] avoid extra memset when clearing an empty table + +--- + include/ankerl/unordered_dense.h | 6 ++++-- + 1 file changed, 4 insertions(+), 2 deletions(-) + +diff --git a/include/ankerl/unordered_dense.h b/include/ankerl/unordered_dense.h +index 0835342..4938212 100644 +--- a/include/ankerl/unordered_dense.h ++++ b/include/ankerl/unordered_dense.h +@@ -1490,8 +1490,10 @@ class table : public std::conditional_t, base_table_type_map, bas + // modifiers ////////////////////////////////////////////////////////////// + + void clear() { +- m_values.clear(); +- clear_buckets(); ++ if (!empty()) { ++ m_values.clear(); ++ clear_buckets(); ++ } + } + + auto insert(value_type const& value) -> std::pair { diff --git a/CMakeLists.txt b/CMakeLists.txt index dbe947c1bf..0568a49ec8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -576,6 +576,7 @@ add_subdirectory(externals) # pass targets from externals find_package(enet) find_package(MbedTLS) +find_package(unordered_dense REQUIRED) if (ARCHITECTURE_x86 OR ARCHITECTURE_x86_64) find_package(xbyak) diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index 980d8f668d..739cebe2e9 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -66,6 +66,9 @@ if (NOT TARGET LLVM::Demangle) add_library(LLVM::Demangle ALIAS demangle) endif() +# unordered_dense +AddJsonPackage(unordered-dense) + if (YUZU_STATIC_ROOM) return() endif() @@ -244,9 +247,6 @@ if (ENABLE_WEB_SERVICE OR ENABLE_UPDATE_CHECKER) AddJsonPackage(cpp-jwt) endif() -# unordered_dense -AddJsonPackage(unordered-dense) - # FFMpeg if (YUZU_USE_EXTERNAL_FFMPEG OR YUZU_USE_BUNDLED_FFMPEG) add_subdirectory(ffmpeg) diff --git a/externals/cpmfile.json b/externals/cpmfile.json index 7c04e389e2..cb1cdf3e4a 100644 --- a/externals/cpmfile.json +++ b/externals/cpmfile.json @@ -91,10 +91,13 @@ "unordered-dense": { "package": "unordered_dense", "repo": "martinus/unordered_dense", - "tag": "v%VERSION%", - "hash": "b98b5d4d96f8e0081b184d6c4c1181fae4e41723b54bed4296717d7f417348b48fad0bbcc664cac142b8c8a47e95aa57c1eb1cf6caa855fd782fad3e3ab99e5e", + "sha": "7b55cab841", + "hash": "d2106f6640f6bfb81755e4b8bfb64982e46ec4a507cacdb38f940123212ccf35a20b43c70c6f01d7bfb8c246d1a16f7845d8052971949cea9def1475e3fa02c8", "find_args": "CONFIG", - "git_version": "4.8.1" + "bundled": true, + "patches": [ + "0001-avoid-memset-when-clearing-an-empty-table.patch" + ] }, "mbedtls": { "package": "MbedTLS", diff --git a/src/android/app/src/main/jni/game_metadata.cpp b/src/android/app/src/main/jni/game_metadata.cpp index c33763b471..e9c03b6440 100644 --- a/src/android/app/src/main/jni/game_metadata.cpp +++ b/src/android/app/src/main/jni/game_metadata.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -18,7 +21,7 @@ struct RomMetadata { bool isHomebrew; }; -std::unordered_map m_rom_metadata_cache; +ankerl::unordered_dense::map m_rom_metadata_cache; RomMetadata CacheRomMetadata(const std::string& path) { const auto file = diff --git a/src/android/app/src/main/jni/native_input.cpp b/src/android/app/src/main/jni/native_input.cpp index 5b69826c75..730d4f0a27 100644 --- a/src/android/app/src/main/jni/native_input.cpp +++ b/src/android/app/src/main/jni/native_input.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -18,7 +21,7 @@ #include "input_common/drivers/virtual_gamepad.h" #include "native.h" -std::unordered_map> map_profiles; +ankerl::unordered_dense::map> map_profiles; bool IsHandheldOnly() { const auto npad_style_set = diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 042f5c7bd9..58d2b2279e 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -253,7 +253,7 @@ if (lz4_ADDED) target_include_directories(common PRIVATE ${lz4_SOURCE_DIR}/lib) endif() -target_link_libraries(common PUBLIC fmt::fmt stb::headers Threads::Threads) +target_link_libraries(common PUBLIC fmt::fmt stb::headers Threads::Threads unordered_dense::unordered_dense) target_link_libraries(common PRIVATE lz4::lz4 LLVM::Demangle zstd::zstd) if(ANDROID) diff --git a/src/common/fs/path_util.cpp b/src/common/fs/path_util.cpp index 9b57fda295..3f928ac327 100644 --- a/src/common/fs/path_util.cpp +++ b/src/common/fs/path_util.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project @@ -7,7 +7,7 @@ #include #include #include -#include +#include #include "common/assert.h" #include "common/fs/fs.h" @@ -193,8 +193,8 @@ private: SetLegacyPathImpl(legacy_path, new_path); } - std::unordered_map eden_paths; - std::unordered_map legacy_paths; + ankerl::unordered_dense::map eden_paths; + ankerl::unordered_dense::map legacy_paths; }; bool ValidatePath(const fs::path& path) { diff --git a/src/common/host_memory.cpp b/src/common/host_memory.cpp index 51ee0d333b..a1e99a4c9d 100644 --- a/src/common/host_memory.cpp +++ b/src/common/host_memory.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project @@ -7,7 +7,7 @@ #ifdef _WIN32 #include -#include +#include #include #include #include "common/dynamic_library.h" @@ -391,7 +391,7 @@ private: std::mutex placeholder_mutex; ///< Mutex for placeholders boost::icl::separate_interval_set placeholders; ///< Mapped placeholders - std::unordered_map placeholder_host_pointers; ///< Placeholder backing offset + ankerl::unordered_dense::map placeholder_host_pointers; ///< Placeholder backing offset }; #else // ^^^ Windows ^^^ vvv POSIX vvv diff --git a/src/common/input.h b/src/common/input.h index 2c4ccea22e..b076fd7e6b 100644 --- a/src/common/input.h +++ b/src/common/input.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2017 Citra Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -6,7 +9,7 @@ #include #include #include -#include +#include #include #include #include "common/logging/log.h" @@ -409,7 +412,7 @@ public: namespace Impl { template -using FactoryListType = std::unordered_map>>; +using FactoryListType = ankerl::unordered_dense::map>>; template struct FactoryList { diff --git a/src/common/param_package.h b/src/common/param_package.h index d7c13cb1fc..0216863a39 100644 --- a/src/common/param_package.h +++ b/src/common/param_package.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2017 Citra Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -5,14 +8,14 @@ #include #include -#include +#include namespace Common { /// A string-based key-value container supporting serializing to and deserializing from a string class ParamPackage { public: - using DataType = std::unordered_map; + using DataType = ankerl::unordered_dense::map; ParamPackage() = default; explicit ParamPackage(const std::string& serialized); diff --git a/src/core/arm/dynarmic/arm_dynarmic_64.h b/src/core/arm/dynarmic/arm_dynarmic_64.h index 2ea1505ce7..07f6bb9449 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_64.h +++ b/src/core/arm/dynarmic/arm_dynarmic_64.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project @@ -8,7 +8,7 @@ #include #include -#include +#include #include #include "common/common_types.h" diff --git a/src/core/arm/nce/lru_cache.h b/src/core/arm/nce/lru_cache.h index 1bc00c8f14..947b66f675 100644 --- a/src/core/arm/nce/lru_cache.h +++ b/src/core/arm/nce/lru_cache.h @@ -1,11 +1,11 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #pragma once #include #include #include -#include +#include #include #include "common/logging/log.h" @@ -144,7 +144,7 @@ private: using list_type = std::list; using list_iterator = typename list_type::iterator; using map_value_type = std::pair; - using map_type = std::unordered_map; + using map_type = ankerl::unordered_dense::map; template void insert_or_update(const key_type& key, V&& value) { diff --git a/src/core/arm/nce/patcher.h b/src/core/arm/nce/patcher.h index f2b11fcda0..8612cfc63d 100644 --- a/src/core/arm/nce/patcher.h +++ b/src/core/arm/nce/patcher.h @@ -4,7 +4,7 @@ #pragma once #include -#include +#include #include #include #include @@ -46,7 +46,7 @@ enum class PatchMode : u32 { using ModuleTextAddress = u64; using PatchTextAddress = u64; -using EntryTrampolines = std::unordered_map; +using EntryTrampolines = ankerl::unordered_dense::map; class Patcher { public: diff --git a/src/core/file_sys/vfs/vfs_layered.cpp b/src/core/file_sys/vfs/vfs_layered.cpp index 47b2a3c780..8dabe03e16 100644 --- a/src/core/file_sys/vfs/vfs_layered.cpp +++ b/src/core/file_sys/vfs/vfs_layered.cpp @@ -1,9 +1,12 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include #include -#include +#include #include #include "core/file_sys/vfs/vfs_layered.h" @@ -60,7 +63,7 @@ std::string LayeredVfsDirectory::GetFullPath() const { std::vector LayeredVfsDirectory::GetFiles() const { std::vector out; - std::unordered_set out_names; + ankerl::unordered_dense::set out_names; for (const auto& layer : dirs) { for (auto& file : layer->GetFiles()) { @@ -76,7 +79,7 @@ std::vector LayeredVfsDirectory::GetFiles() const { std::vector LayeredVfsDirectory::GetSubdirectories() const { std::vector out; - std::unordered_set out_names; + ankerl::unordered_dense::set out_names; for (const auto& layer : dirs) { for (const auto& sd : layer->GetSubdirectories()) { diff --git a/src/core/hle/kernel/k_process.h b/src/core/hle/kernel/k_process.h index 13717cc090..975448f0dd 100644 --- a/src/core/hle/kernel/k_process.h +++ b/src/core/hle/kernel/k_process.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project @@ -78,7 +78,7 @@ private: std::array m_watchpoints{}; std::map m_debug_page_refcounts{}; #ifdef HAS_NCE - std::unordered_map m_post_handlers{}; + ankerl::unordered_dense::map m_post_handlers{}; #endif std::unique_ptr m_exclusive_monitor; Core::Memory::Memory m_memory; @@ -493,7 +493,7 @@ public: static void Switch(KProcess* cur_process, KProcess* next_process); #ifdef HAS_NCE - std::unordered_map& GetPostHandlers() noexcept { + ankerl::unordered_dense::map& GetPostHandlers() noexcept { return m_post_handlers; } #endif diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index 6986a98e35..5c2b6a4a88 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project @@ -10,7 +10,7 @@ #include #include #include -#include +#include #include #include "common/assert.h" @@ -786,8 +786,8 @@ struct KernelCore::Impl { std::optional object_name_global_data; - std::unordered_set registered_objects; - std::unordered_set registered_in_use_objects; + ankerl::unordered_dense::set registered_objects; + ankerl::unordered_dense::set registered_in_use_objects; std::mutex server_lock; std::vector> server_managers; diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h index 5d1a34281a..3b754e9397 100644 --- a/src/core/hle/kernel/kernel.h +++ b/src/core/hle/kernel/kernel.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project @@ -11,7 +11,7 @@ #include #include #include -#include +#include #include #include "common/polyfill_thread.h" diff --git a/src/core/hle/service/am/frontend/applet_web_browser_types.h b/src/core/hle/service/am/frontend/applet_web_browser_types.h index 0892a6a716..3574568f6f 100644 --- a/src/core/hle/service/am/frontend/applet_web_browser_types.h +++ b/src/core/hle/service/am/frontend/applet_web_browser_types.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project @@ -7,7 +7,7 @@ #pragma once #include -#include +#include #include #include "common/common_funcs.h" @@ -176,6 +176,6 @@ struct WebCommonReturnValue { }; static_assert(sizeof(WebCommonReturnValue) == 0x1010, "WebCommonReturnValue has incorrect size."); -using WebArgInputTLVMap = std::unordered_map>; +using WebArgInputTLVMap = ankerl::unordered_dense::map>; } // namespace Service::AM::Frontend diff --git a/src/core/hle/service/bcat/news/builtin_news.cpp b/src/core/hle/service/bcat/news/builtin_news.cpp index 2c93da71e0..43b0445039 100644 --- a/src/core/hle/service/bcat/news/builtin_news.cpp +++ b/src/core/hle/service/bcat/news/builtin_news.cpp @@ -44,8 +44,8 @@ std::vector default_logo_small; std::vector default_logo_large; bool default_logos_loaded = false; -std::unordered_map> news_images_small; -std::unordered_map> news_images_large; +ankerl::unordered_dense::map> news_images_small; +ankerl::unordered_dense::map> news_images_large; std::mutex images_mutex; diff --git a/src/core/hle/service/bcat/news/news_storage.h b/src/core/hle/service/bcat/news/news_storage.h index ad84e4080c..531e2adb9a 100644 --- a/src/core/hle/service/bcat/news/news_storage.h +++ b/src/core/hle/service/bcat/news/news_storage.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project @@ -12,7 +12,7 @@ #include #include #include -#include +#include #include #include "common/common_types.h" @@ -93,7 +93,7 @@ private: static s64 Now(); mutable std::mutex mtx; - std::unordered_map items; + ankerl::unordered_dense::map items; size_t open_counter{}; }; diff --git a/src/core/hle/service/caps/caps_manager.h b/src/core/hle/service/caps/caps_manager.h index 893a9075a0..22f5672e47 100644 --- a/src/core/hle/service/caps/caps_manager.h +++ b/src/core/hle/service/caps/caps_manager.h @@ -1,9 +1,12 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once -#include +#include #include "common/fs/fs.h" #include "core/hle/result.h" @@ -85,7 +88,7 @@ private: AlbumFileDateTime ConvertToAlbumDateTime(u64 posix_time) const; bool is_mounted{}; - std::unordered_map album_files; + ankerl::unordered_dense::map album_files; Core::System& system; }; diff --git a/src/core/hle/service/ldn/lan_discovery.h b/src/core/hle/service/ldn/lan_discovery.h index 0d15583172..d39197efff 100644 --- a/src/core/hle/service/ldn/lan_discovery.h +++ b/src/core/hle/service/ldn/lan_discovery.h @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #pragma once @@ -15,7 +15,7 @@ #include #include #include -#include +#include #include "common/logging/log.h" #include "common/socket_types.h" @@ -119,7 +119,7 @@ protected: std::array stations; std::array node_changes{}; std::array node_last_states{}; - std::unordered_map scan_results{}; + ankerl::unordered_dense::map scan_results{}; NodeInfo node_info{}; NetworkInfo network_info{}; State state{State::None}; diff --git a/src/core/hle/service/lm/lm.cpp b/src/core/hle/service/lm/lm.cpp index b8a34d34eb..2fda8a3b90 100644 --- a/src/core/hle/service/lm/lm.cpp +++ b/src/core/hle/service/lm/lm.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project @@ -7,7 +7,7 @@ #include #include -#include +#include #include #include "common/logging/log.h" #include "core/core.h" @@ -331,7 +331,7 @@ private: }; static_assert(sizeof(LogPacketHeader) == 0x18, "LogPacketHeader is an invalid size"); - std::unordered_map> entries{}; + ankerl::unordered_dense::map> entries{}; LogDestination destination{LogDestination::All}; }; diff --git a/src/core/hle/service/nifm/nifm.cpp b/src/core/hle/service/nifm/nifm.cpp index 8fecef96d2..710a95a455 100644 --- a/src/core/hle/service/nifm/nifm.cpp +++ b/src/core/hle/service/nifm/nifm.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project @@ -23,7 +23,7 @@ #include #include #include -#include +#include #include #ifdef _WIN32 diff --git a/src/core/hle/service/nvdrv/core/container.h b/src/core/hle/service/nvdrv/core/container.h index cf549d7f32..62404e14a7 100644 --- a/src/core/hle/service/nvdrv/core/container.h +++ b/src/core/hle/service/nvdrv/core/container.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2022 yuzu Emulator Project // SPDX-FileCopyrightText: 2022 Skyline Team and Contributors // SPDX-License-Identifier: GPL-3.0-or-later @@ -6,7 +9,7 @@ #include #include -#include +#include #include "core/device_memory_manager.h" #include "core/hle/service/nvdrv/nvdata.h" diff --git a/src/core/hle/service/nvdrv/core/nvmap.h b/src/core/hle/service/nvdrv/core/nvmap.h index b8be599ae9..17b9ccd1c7 100644 --- a/src/core/hle/service/nvdrv/core/nvmap.h +++ b/src/core/hle/service/nvdrv/core/nvmap.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2022 yuzu Emulator Project // SPDX-FileCopyrightText: 2022 Skyline Team and Contributors // SPDX-License-Identifier: GPL-3.0-or-later @@ -9,7 +12,7 @@ #include #include #include -#include +#include #include #include "common/bit_field.h" @@ -158,7 +161,7 @@ private: std::list> unmap_queue{}; std::mutex unmap_queue_lock{}; //!< Protects access to `unmap_queue` - std::unordered_map> + ankerl::unordered_dense::map> handles{}; //!< Main owning map of handles std::mutex handles_lock; //!< Protects access to `handles` diff --git a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h index c9a6737ba9..7d50fd4c33 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2021 yuzu Emulator Project @@ -13,7 +13,7 @@ #include #include #include -#include +#include #include #include "common/address_space.h" @@ -113,7 +113,7 @@ private: }; static_assert(sizeof(IoctlRemapEntry) == 20, "IoctlRemapEntry is incorrect size"); - std::unordered_set map_buffer_offsets{}; + ankerl::unordered_dense::set map_buffer_offsets{}; struct IoctlMapBufferEx { MappingFlags flags{}; // bit0: fixed_offset, bit2: cacheable diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_gpu.h index b8854aab95..c29231dc0c 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project @@ -210,7 +210,7 @@ private: NvCore::SyncpointManager& syncpoint_manager; NvCore::NvMap& nvmap; std::shared_ptr channel_state; - std::unordered_map sessions; + ankerl::unordered_dense::map sessions; u32 channel_syncpoint; std::mutex channel_mutex; diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.h b/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.h index 63e6377603..6b66a0d28d 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.h @@ -1,10 +1,13 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once #include -#include +#include #include #include "common/common_types.h" @@ -128,7 +131,7 @@ protected: NvCore::NvMap& nvmap; NvCore::ChannelType channel_type; std::array device_syncpoints{}; - std::unordered_map sessions; + ankerl::unordered_dense::map sessions; }; }; // namespace Devices } // namespace Service::Nvidia diff --git a/src/core/hle/service/nvdrv/devices/nvmap.h b/src/core/hle/service/nvdrv/devices/nvmap.h index d07d85f888..19c5b479df 100644 --- a/src/core/hle/service/nvdrv/devices/nvmap.h +++ b/src/core/hle/service/nvdrv/devices/nvmap.h @@ -1,10 +1,13 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once #include -#include +#include #include #include "common/common_funcs.h" #include "common/common_types.h" @@ -115,7 +118,7 @@ private: NvCore::Container& container; NvCore::NvMap& file; - std::unordered_map sessions; + ankerl::unordered_dense::map sessions; }; } // namespace Service::Nvidia::Devices diff --git a/src/core/hle/service/nvdrv/nvdrv.h b/src/core/hle/service/nvdrv/nvdrv.h index b76f81e594..329ac91c41 100644 --- a/src/core/hle/service/nvdrv/nvdrv.h +++ b/src/core/hle/service/nvdrv/nvdrv.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2021 yuzu Emulator Project // SPDX-FileCopyrightText: 2021 Skyline Team and Contributors // SPDX-License-Identifier: GPL-3.0-or-later @@ -9,7 +12,7 @@ #include #include #include -#include +#include #include "common/common_types.h" #include "core/hle/service/kernel_helpers.h" @@ -100,7 +103,7 @@ private: /// Id to use for the next open file descriptor. DeviceFD next_fd = 1; - using FilesContainerType = std::unordered_map>; + using FilesContainerType = ankerl::unordered_dense::map>; /// Mapping of file descriptors to the devices they reference. FilesContainerType open_files; @@ -108,7 +111,7 @@ private: EventInterface events_interface; - std::unordered_map> builders; + ankerl::unordered_dense::map> builders; }; void LoopProcess(Core::System& system); diff --git a/src/core/hle/service/nvnflinger/hos_binder_driver_server.h b/src/core/hle/service/nvnflinger/hos_binder_driver_server.h index f4b4060115..b66f1776b0 100644 --- a/src/core/hle/service/nvnflinger/hos_binder_driver_server.h +++ b/src/core/hle/service/nvnflinger/hos_binder_driver_server.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later @@ -5,7 +8,7 @@ #include #include -#include +#include #include "common/common_types.h" #include "core/hle/service/nvnflinger/binder.h" @@ -36,8 +39,8 @@ private: mutable std::mutex lock; s32 last_id = 0; - std::unordered_map> binders; - std::unordered_map refcounts; + ankerl::unordered_dense::map> binders; + ankerl::unordered_dense::map refcounts; }; } // namespace Service::Nvnflinger diff --git a/src/core/hle/service/olsc/daemon_controller.h b/src/core/hle/service/olsc/daemon_controller.h index 4a4b89317b..fc7dd7bbcb 100644 --- a/src/core/hle/service/olsc/daemon_controller.h +++ b/src/core/hle/service/olsc/daemon_controller.h @@ -4,7 +4,7 @@ // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include +#include #include "common/uuid.h" #include "core/hle/service/cmif_types.h" @@ -54,10 +54,10 @@ private: } }; - std::unordered_map app_auto_transfer_{}; - std::unordered_map global_auto_upload_{}; - std::unordered_map global_auto_download_{}; - std::unordered_map autonomy_task_status_{}; + ankerl::unordered_dense::map app_auto_transfer_{}; + ankerl::unordered_dense::map global_auto_upload_{}; + ankerl::unordered_dense::map global_auto_download_{}; + ankerl::unordered_dense::map autonomy_task_status_{}; }; } // namespace Service::OLSC diff --git a/src/core/hle/service/sm/sm.h b/src/core/hle/service/sm/sm.h index d5f372b97d..039d53ebf8 100644 --- a/src/core/hle/service/sm/sm.h +++ b/src/core/hle/service/sm/sm.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project @@ -10,7 +10,7 @@ #include #include #include -#include +#include #include #include "core/hle/kernel/k_port.h" @@ -97,8 +97,8 @@ private: /// Map of registered services, retrieved using GetServicePort. std::mutex lock; - std::unordered_map registered_services; - std::unordered_map service_ports; + ankerl::unordered_dense::map registered_services; + ankerl::unordered_dense::map service_ports; /// Kernel context Kernel::KernelCore& kernel; diff --git a/src/core/hle/service/vi/conductor.h b/src/core/hle/service/vi/conductor.h index 6dd105dd40..59eb2fd022 100644 --- a/src/core/hle/service/vi/conductor.h +++ b/src/core/hle/service/vi/conductor.h @@ -1,10 +1,13 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once #include -#include +#include #include "common/common_types.h" #include "common/polyfill_thread.h" @@ -45,7 +48,7 @@ private: private: Core::System& m_system; Container& m_container; - std::unordered_map m_vsync_managers; + ankerl::unordered_dense::map m_vsync_managers; std::shared_ptr m_event; Common::Event m_signal; std::jthread m_thread; diff --git a/src/core/launch_timestamp_cache.cpp b/src/core/launch_timestamp_cache.cpp index 95b6bc8c41..217b3f28da 100644 --- a/src/core/launch_timestamp_cache.cpp +++ b/src/core/launch_timestamp_cache.cpp @@ -8,7 +8,7 @@ #include #include #include -#include +#include #include #include @@ -21,8 +21,8 @@ namespace Core::LaunchTimestampCache { namespace { -using CacheMap = std::unordered_map; -using CountMap = std::unordered_map; +using CacheMap = ankerl::unordered_dense::map; +using CountMap = ankerl::unordered_dense::map; std::mutex g_mutex; CacheMap g_cache; diff --git a/src/dynarmic/CMakeLists.txt b/src/dynarmic/CMakeLists.txt index fc22f073f7..e0d1f942f0 100644 --- a/src/dynarmic/CMakeLists.txt +++ b/src/dynarmic/CMakeLists.txt @@ -118,6 +118,7 @@ endif() find_package(fmt 8 CONFIG) find_package(mcl 0.1.12 REQUIRED) +find_package(unordered_dense REQUIRED) if ("arm64" IN_LIST ARCHITECTURE OR DYNARMIC_TESTS) find_package(oaknut 2.0.1 CONFIG) diff --git a/src/dynarmic/src/dynarmic/CMakeLists.txt b/src/dynarmic/src/dynarmic/CMakeLists.txt index f2ea9de455..8b55a864a3 100644 --- a/src/dynarmic/src/dynarmic/CMakeLists.txt +++ b/src/dynarmic/src/dynarmic/CMakeLists.txt @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project # SPDX-License-Identifier: GPL-3.0-or-later include(TargetArchitectureSpecificSources) @@ -356,20 +356,10 @@ set_target_properties(dynarmic PROPERTIES SOVERSION ${dynarmic_VERSION_MAJOR}.${dynarmic_VERSION_MINOR} ) -if (TARGET unordered_dense::unordered_dense) - # weird quirk of system installs - target_link_libraries(dynarmic - PRIVATE - unordered_dense::unordered_dense - ) -endif() - target_compile_options(dynarmic PRIVATE ${DYNARMIC_CXX_FLAGS}) -target_link_libraries(dynarmic - PUBLIC - fmt::fmt - merry::mcl -) + +target_link_libraries(dynarmic PRIVATE unordered_dense::unordered_dense) +target_link_libraries(dynarmic PUBLIC fmt::fmt merry::mcl) if (BOOST_NO_HEADERS) target_link_libraries(dynarmic PRIVATE Boost::variant Boost::icl Boost::pool) diff --git a/src/dynarmic/tests/A64/fibonacci.cpp b/src/dynarmic/tests/A64/fibonacci.cpp index 713a48cab7..9a10f52614 100644 --- a/src/dynarmic/tests/A64/fibonacci.cpp +++ b/src/dynarmic/tests/A64/fibonacci.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later /* This file is part of the dynarmic project. @@ -8,7 +8,7 @@ #include #include -#include +#include #include #include "dynarmic/common/common_types.h" @@ -23,7 +23,7 @@ namespace { class MyEnvironment final : public A64::UserCallbacks { public: u64 ticks_left = 0; - std::unordered_map memory{}; + ankerl::unordered_dense::map memory{}; u8 MemoryRead8(u64 vaddr) override { return memory[vaddr]; diff --git a/src/dynarmic/tests/A64/testenv.h b/src/dynarmic/tests/A64/testenv.h index 05eb0e2d39..67b0f97a65 100644 --- a/src/dynarmic/tests/A64/testenv.h +++ b/src/dynarmic/tests/A64/testenv.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later /* This file is part of the dynarmic project. @@ -8,7 +8,7 @@ #pragma once -#include +#include #include "dynarmic/common/assert.h" #include "dynarmic/common/common_types.h" #include "dynarmic/interface/A64/a64.h" @@ -23,7 +23,7 @@ public: u64 code_mem_start_address = 0; std::vector code_mem; - std::unordered_map modified_memory; + ankerl::unordered_dense::map modified_memory; std::vector interrupts; bool IsInCodeMem(u64 vaddr) const { diff --git a/src/hid_core/frontend/emulated_console.h b/src/hid_core/frontend/emulated_console.h index 847551395c..b8ce45f05c 100644 --- a/src/hid_core/frontend/emulated_console.h +++ b/src/hid_core/frontend/emulated_console.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -8,7 +11,7 @@ #include #include #include -#include +#include #include "common/common_funcs.h" #include "common/common_types.h" @@ -182,7 +185,7 @@ private: mutable std::mutex mutex; mutable std::mutex callback_mutex; - std::unordered_map callback_list; + ankerl::unordered_dense::map callback_list; int last_callback_key = 0; // Stores the current status of all console input diff --git a/src/hid_core/frontend/emulated_controller.h b/src/hid_core/frontend/emulated_controller.h index b8eafbae5c..e4256faa3e 100644 --- a/src/hid_core/frontend/emulated_controller.h +++ b/src/hid_core/frontend/emulated_controller.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project @@ -11,7 +11,7 @@ #include #include #include -#include +#include #include #include "common/common_types.h" @@ -636,7 +636,7 @@ private: mutable std::shared_mutex callback_mutex; mutable std::shared_mutex npad_mutex; mutable std::shared_mutex connect_mutex; - std::unordered_map callback_list; + ankerl::unordered_dense::map callback_list; int last_callback_key = 0; // Stores the current status of all controller input diff --git a/src/hid_core/frontend/emulated_devices.h b/src/hid_core/frontend/emulated_devices.h index b2e57318cc..fb6c982a70 100644 --- a/src/hid_core/frontend/emulated_devices.h +++ b/src/hid_core/frontend/emulated_devices.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -7,7 +10,7 @@ #include #include #include -#include +#include #include #include "common/common_types.h" @@ -202,7 +205,7 @@ private: mutable std::mutex mutex; mutable std::mutex callback_mutex; - std::unordered_map callback_list; + ankerl::unordered_dense::map callback_list; int last_callback_key = 0; // Stores the current status of all external device input diff --git a/src/input_common/drivers/android.h b/src/input_common/drivers/android.h index 03e2b2c983..18678de602 100644 --- a/src/input_common/drivers/android.h +++ b/src/input_common/drivers/android.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later @@ -87,7 +90,7 @@ public: Common::Input::ButtonNames GetUIName(const Common::ParamPackage& params) const override; private: - std::unordered_map input_devices; + ankerl::unordered_dense::map input_devices; /// Returns the correct identifier corresponding to the player index PadIdentifier GetIdentifier(const std::string& guid, size_t port) const; diff --git a/src/input_common/drivers/sdl_driver.cpp b/src/input_common/drivers/sdl_driver.cpp index ddee11035c..81b0a2313a 100644 --- a/src/input_common/drivers/sdl_driver.cpp +++ b/src/input_common/drivers/sdl_driver.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2018 Citra Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -570,7 +570,7 @@ SDLDriver::~SDLDriver() { std::vector SDLDriver::GetInputDevices() const { std::vector devices; - std::unordered_map> joycon_pairs; + ankerl::unordered_dense::map> joycon_pairs; for (const auto& [key, value] : joystick_map) { for (const auto& joystick : value) { if (!joystick->GetSDLJoystick()) { diff --git a/src/input_common/drivers/sdl_driver.h b/src/input_common/drivers/sdl_driver.h index a140ad072c..1677ab8289 100644 --- a/src/input_common/drivers/sdl_driver.h +++ b/src/input_common/drivers/sdl_driver.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2018 Citra Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -6,7 +9,7 @@ #include #include #include -#include +#include #include @@ -116,7 +119,7 @@ private: Common::SPSCQueue vibration_queue; /// Map of GUID of a list of corresponding virtual Joysticks - std::unordered_map>> joystick_map; + ankerl::unordered_dense::map>> joystick_map; std::mutex joystick_map_mutex; bool start_thread = false; diff --git a/src/input_common/input_engine.h b/src/input_common/input_engine.h index 3e328509b1..7ad40027e4 100644 --- a/src/input_common/input_engine.h +++ b/src/input_common/input_engine.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -5,7 +8,7 @@ #include #include -#include +#include #include "common/common_types.h" #include "common/input.h" @@ -267,10 +270,10 @@ protected: private: struct ControllerData { - std::unordered_map buttons; - std::unordered_map hat_buttons; - std::unordered_map axes; - std::unordered_map motions; + ankerl::unordered_dense::map buttons; + ankerl::unordered_dense::map hat_buttons; + ankerl::unordered_dense::map axes; + ankerl::unordered_dense::map motions; Common::Input::BatteryLevel battery{}; Common::Input::BodyColorStatus color{}; Common::Input::CameraStatus camera{}; @@ -298,8 +301,8 @@ private: bool configuring{false}; const std::string input_engine; int last_callback_key = 0; - std::unordered_map controller_list; - std::unordered_map callback_list; + ankerl::unordered_dense::map controller_list; + ankerl::unordered_dense::map callback_list; MappingCallback mapping_callback; }; diff --git a/src/input_common/main.h b/src/input_common/main.h index 1d19019ee8..d0e447ec2d 100644 --- a/src/input_common/main.h +++ b/src/input_common/main.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2017 Citra Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -5,7 +8,7 @@ #include #include -#include +#include #include namespace Common { @@ -53,9 +56,9 @@ enum class InputType { None, Button, Stick, Motion, Touch }; * Given a ParamPackage for a Device returned from `GetInputDevices`, attempt to get the default * mapping for the device. */ -using AnalogMapping = std::unordered_map; -using ButtonMapping = std::unordered_map; -using MotionMapping = std::unordered_map; +using AnalogMapping = ankerl::unordered_dense::map; +using ButtonMapping = ankerl::unordered_dense::map; +using MotionMapping = ankerl::unordered_dense::map; class InputSubsystem { public: diff --git a/src/shader_recompiler/environment.h b/src/shader_recompiler/environment.h index 106fe8d32d..0a6e4bfcaa 100644 --- a/src/shader_recompiler/environment.h +++ b/src/shader_recompiler/environment.h @@ -7,7 +7,7 @@ #pragma once #include -#include +#include #include "common/common_types.h" #include "shader_recompiler/program_header.h" @@ -125,9 +125,9 @@ protected: u32 start_address{}; bool is_proprietary_driver{}; public: - std::unordered_map cbuf_word_cache; - std::unordered_map handle_cache; - std::unordered_map track_cache; + ankerl::unordered_dense::map cbuf_word_cache; + ankerl::unordered_dense::map handle_cache; + ankerl::unordered_dense::map track_cache; }; } // namespace Shader diff --git a/src/shader_recompiler/frontend/maxwell/structured_control_flow.cpp b/src/shader_recompiler/frontend/maxwell/structured_control_flow.cpp index 6d325b4aad..eaf1d88f2e 100644 --- a/src/shader_recompiler/frontend/maxwell/structured_control_flow.cpp +++ b/src/shader_recompiler/frontend/maxwell/structured_control_flow.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project @@ -7,7 +7,7 @@ #include #include #include -#include +#include #include #include @@ -390,7 +390,7 @@ private: std::optional return_label) { Statement* const false_stmt{pool.Create(Identity{}, IR::Condition{false}, &root_stmt)}; Tree& root{root_stmt.children}; - std::unordered_map local_labels; + ankerl::unordered_dense::map local_labels; local_labels.reserve(function.blocks.size()); for (Flow::Block& block : function.blocks) { diff --git a/src/shader_recompiler/ir_opt/ssa_rewrite_pass.cpp b/src/shader_recompiler/ir_opt/ssa_rewrite_pass.cpp index 07cabca43e..2bde38a661 100644 --- a/src/shader_recompiler/ir_opt/ssa_rewrite_pass.cpp +++ b/src/shader_recompiler/ir_opt/ssa_rewrite_pass.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -16,7 +19,7 @@ #include #include #include -#include +#include #include #include @@ -50,8 +53,8 @@ struct IndirectBranchVariable { auto operator<=>(const IndirectBranchVariable&) const noexcept = default; }; -using Variant = std::variant; +using Variant = std::variant; +// TODO: majority of these require stable iterators, test with XC beforehand using ValueMap = std::unordered_map; struct DefTable { @@ -112,6 +115,7 @@ struct DefTable { } std::array preds; + // TODO: Requires stable iterators std::unordered_map goto_vars; ValueMap indirect_branch_var; ValueMap zero_flag; @@ -165,7 +169,8 @@ public: template IR::Value ReadVariable(Type variable, IR::Block* root_block) { - boost::container::small_vector, 64> stack{ + // TODO: Windows commits sodoku if you use small_vector + std::vector> stack{ ReadState(nullptr), ReadState(root_block), }; @@ -295,6 +300,7 @@ private: return same; } + // TODO: Windows dies with stack exhaustion? std::unordered_map> incomplete_phis; DefTable current_def; }; diff --git a/src/shader_recompiler/ir_opt/texture_pass.cpp b/src/shader_recompiler/ir_opt/texture_pass.cpp index 0288db0ae1..20b8591072 100644 --- a/src/shader_recompiler/ir_opt/texture_pass.cpp +++ b/src/shader_recompiler/ir_opt/texture_pass.cpp @@ -7,7 +7,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/src/tests/common/fibers.cpp b/src/tests/common/fibers.cpp index 83c0946c9e..ce17a5c52c 100644 --- a/src/tests/common/fibers.cpp +++ b/src/tests/common/fibers.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -8,7 +11,7 @@ #include #include #include -#include +#include #include #include @@ -36,7 +39,7 @@ public: private: mutable std::mutex mutex; - std::unordered_map ids; + ankerl::unordered_dense::map ids; }; class TestControl1 { diff --git a/src/tests/video_core/memory_tracker.cpp b/src/tests/video_core/memory_tracker.cpp index 08ebf05237..5fd8037446 100644 --- a/src/tests/video_core/memory_tracker.cpp +++ b/src/tests/video_core/memory_tracker.cpp @@ -3,7 +3,7 @@ #include #include -#include +#include #include #include #include @@ -65,7 +65,7 @@ public: } private: - std::unordered_map page_table; + ankerl::unordered_dense::map page_table; std::vector> calls; size_t update_calls = 0; }; diff --git a/src/video_core/buffer_cache/buffer_cache_base.h b/src/video_core/buffer_cache/buffer_cache_base.h index ed50634683..3c0bc81758 100644 --- a/src/video_core/buffer_cache/buffer_cache_base.h +++ b/src/video_core/buffer_cache/buffer_cache_base.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project @@ -14,7 +14,7 @@ #include #include #include -#include +#include #include #include "common/common_types.h" diff --git a/src/video_core/buffer_cache/memory_tracker_base.h b/src/video_core/buffer_cache/memory_tracker_base.h index 4181b0ae54..2c571271d6 100644 --- a/src/video_core/buffer_cache/memory_tracker_base.h +++ b/src/video_core/buffer_cache/memory_tracker_base.h @@ -11,7 +11,7 @@ #include #include #include -#include +#include #include #include "common/alignment.h" @@ -257,7 +257,7 @@ private: std::array top_tier{}; std::deque> manager_pool; std::deque free_managers; - std::unordered_set cached_pages; + ankerl::unordered_dense::set cached_pages; DeviceTracker* device_tracker = nullptr; }; diff --git a/src/video_core/control/channel_state_cache.h b/src/video_core/control/channel_state_cache.h index 58f6a34c16..f8b8abcac6 100644 --- a/src/video_core/control/channel_state_cache.h +++ b/src/video_core/control/channel_state_cache.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2022 yuzu Emulator Project @@ -10,7 +10,7 @@ #include #include #include -#include +#include #include #include "common/common_types.h" @@ -88,14 +88,14 @@ protected: std::deque

channel_storage; std::deque free_channel_ids; - std::unordered_map channel_map; + ankerl::unordered_dense::map channel_map; std::vector active_channel_ids; struct AddressSpaceRef { size_t ref_count; size_t storage_id; Tegra::MemoryManager* gpu_memory; }; - std::unordered_map address_spaces; + ankerl::unordered_dense::map address_spaces; mutable std::mutex config_mutex; virtual void OnGPUASRegister([[maybe_unused]] size_t map_id) {} diff --git a/src/video_core/control/scheduler.h b/src/video_core/control/scheduler.h index 44addf61c4..9c2b9de32b 100644 --- a/src/video_core/control/scheduler.h +++ b/src/video_core/control/scheduler.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later @@ -5,7 +8,7 @@ #include #include -#include +#include #include "video_core/dma_pusher.h" @@ -27,7 +30,7 @@ public: void DeclareChannel(std::shared_ptr new_channel); private: - std::unordered_map> channels; + ankerl::unordered_dense::map> channels; std::mutex scheduling_guard; GPU& gpu; }; diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h index 8c50a4ea2f..5312c04b6f 100644 --- a/src/video_core/engines/maxwell_3d.h +++ b/src/video_core/engines/maxwell_3d.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project @@ -3056,7 +3056,7 @@ public: void SetHLEReplacementAttributeType(u32 bank, u32 offset, HLEReplacementAttributeType name); - std::unordered_map replace_table; + ankerl::unordered_dense::map replace_table; static_assert(sizeof(Regs) == Regs::NUM_REGS * sizeof(u32), "Maxwell3D Regs has wrong size"); static_assert(std::is_trivially_copyable_v, "Maxwell3D Regs must be trivially copyable"); diff --git a/src/video_core/engines/sw_blitter/converter.cpp b/src/video_core/engines/sw_blitter/converter.cpp index b4722f7976..6682c96444 100644 --- a/src/video_core/engines/sw_blitter/converter.cpp +++ b/src/video_core/engines/sw_blitter/converter.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project @@ -7,7 +7,7 @@ #include #include #include -#include +#include #include #include #include "common/assert.h" @@ -924,7 +924,7 @@ public: }; struct ConverterFactory::ConverterFactoryImpl { - std::unordered_map> converters_cache; + ankerl::unordered_dense::map> converters_cache; }; ConverterFactory::ConverterFactory() { diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp index 36d726c929..d65658323e 100644 --- a/src/video_core/gpu.cpp +++ b/src/video_core/gpu.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project @@ -373,7 +373,7 @@ struct GPU::Impl { std::unique_ptr cpu_context; std::unique_ptr scheduler; - std::unordered_map> channels; + ankerl::unordered_dense::map> channels; Tegra::Control::ChannelState* current_channel; s32 bound_channel{-1}; diff --git a/src/video_core/gpu_logging/gpu_logging.h b/src/video_core/gpu_logging/gpu_logging.h index c248e9b036..134fa94c0f 100644 --- a/src/video_core/gpu_logging/gpu_logging.h +++ b/src/video_core/gpu_logging/gpu_logging.h @@ -10,7 +10,7 @@ #include #include #include -#include +#include #include #include "common/common_types.h" @@ -154,7 +154,7 @@ private: mutable std::mutex ring_buffer_mutex; // Memory tracking - std::unordered_map memory_allocations; + ankerl::unordered_dense::map memory_allocations; mutable std::mutex memory_mutex; // Statistics diff --git a/src/video_core/host1x/codecs/decoder.h b/src/video_core/host1x/codecs/decoder.h old mode 100755 new mode 100644 index 22e6db8151..d25da81fd4 --- a/src/video_core/host1x/codecs/decoder.h +++ b/src/video_core/host1x/codecs/decoder.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -7,7 +10,7 @@ #include #include #include -#include +#include #include #include "common/common_types.h" diff --git a/src/video_core/host1x/host1x.h b/src/video_core/host1x/host1x.h index 98dd593185..d7534da23a 100644 --- a/src/video_core/host1x/host1x.h +++ b/src/video_core/host1x/host1x.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2021 yuzu Emulator Project @@ -6,8 +6,8 @@ #pragma once +#include #include -#include #include #include "common/common_types.h" @@ -134,15 +134,15 @@ private: if (it == map->second.end()) { return {}; } - + // TODO: this "mapped" prevents us from fully embracing ankerl return std::move(map->second.extract(it).mapped()); } using FramePtr = std::shared_ptr; std::mutex m_mutex{}; - std::unordered_map>> m_presentation_order; - std::unordered_map> m_decode_order; + ankerl::unordered_dense::map>> m_presentation_order; + ankerl::unordered_dense::map> m_decode_order; static constexpr size_t MAX_PRESENT_QUEUE = 100; static constexpr size_t MAX_DECODE_MAP = 200; @@ -218,7 +218,7 @@ private: Tegra::MemoryManager gmmu_manager; std::unique_ptr> allocator; FrameQueue frame_queue; - std::unordered_map> devices; + ankerl::unordered_dense::map> devices; #ifdef YUZU_LEGACY std::once_flag nvdec_first_init; std::once_flag vic_first_init; diff --git a/src/video_core/macro.h b/src/video_core/macro.h index 685097a693..9bdb4219ce 100644 --- a/src/video_core/macro.h +++ b/src/video_core/macro.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project @@ -7,7 +7,7 @@ #pragma once #include -#include +#include #include #include "common/bit_field.h" #include "common/common_types.h" @@ -147,8 +147,8 @@ private: bool has_hle_program{}; }; - std::unordered_map macro_cache; - std::unordered_map> uploaded_macro_code; + ankerl::unordered_dense::map macro_cache; + ankerl::unordered_dense::map> uploaded_macro_code; std::optional hle_macros; Engines::Maxwell3D& maxwell3d; bool is_interpreted; diff --git a/src/video_core/query_cache.h b/src/video_core/query_cache.h index e1019f2285..ebb87fe65a 100644 --- a/src/video_core/query_cache.h +++ b/src/video_core/query_cache.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -12,8 +15,7 @@ #include #include #include -#include -#include +#include #include #include "common/assert.h" @@ -348,7 +350,7 @@ private: mutable std::recursive_mutex mutex; - std::unordered_map> cached_queries; + ankerl::unordered_dense::map> cached_queries; std::array streams; diff --git a/src/video_core/query_cache/query_cache.h b/src/video_core/query_cache/query_cache.h index 6e084cc079..3aeeb8dcab 100644 --- a/src/video_core/query_cache/query_cache.h +++ b/src/video_core/query_cache/query_cache.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project @@ -10,7 +10,7 @@ #include #include #include -#include +#include #include #include "common/assert.h" diff --git a/src/video_core/query_cache/query_cache_base.h b/src/video_core/query_cache/query_cache_base.h index 00c25c8d63..242e24388f 100644 --- a/src/video_core/query_cache/query_cache_base.h +++ b/src/video_core/query_cache/query_cache_base.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later @@ -7,7 +10,7 @@ #include #include #include -#include +#include #include #include "common/assert.h" @@ -157,7 +160,7 @@ protected: } } - using ContentCache = std::unordered_map>; + using ContentCache = ankerl::unordered_dense::map>; void InvalidateQuery(QueryLocation location); bool IsQueryDirty(QueryLocation location); @@ -165,7 +168,7 @@ protected: void RequestGuestHostSync(); void UnregisterPending(); - std::unordered_map> cached_queries; + ankerl::unordered_dense::map> cached_queries; std::mutex cache_mutex; struct QueryCacheBaseImpl; diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.h b/src/video_core/renderer_opengl/gl_buffer_cache.h index 23ebe50196..a0acfc48c1 100644 --- a/src/video_core/renderer_opengl/gl_buffer_cache.h +++ b/src/video_core/renderer_opengl/gl_buffer_cache.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project @@ -8,7 +8,7 @@ #include #include -#include +#include #include "common/common_types.h" #include "video_core/buffer_cache/buffer_cache_base.h" @@ -242,7 +242,7 @@ private: u32 index_buffer_offset = 0; u64 device_access_memory; - std::unordered_map tfb_objects; + ankerl::unordered_dense::map tfb_objects; }; struct BufferCacheParams { diff --git a/src/video_core/renderer_opengl/gl_shader_cache.h b/src/video_core/renderer_opengl/gl_shader_cache.h index 2b46c22c70..3398ba6f51 100644 --- a/src/video_core/renderer_opengl/gl_shader_cache.h +++ b/src/video_core/renderer_opengl/gl_shader_cache.h @@ -1,10 +1,13 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once #include -#include +#include #include "common/common_types.h" #include "common/thread_worker.h" @@ -79,8 +82,8 @@ private: GraphicsPipeline* current_pipeline{}; ShaderContext::ShaderPools main_pools; - std::unordered_map> graphics_cache; - std::unordered_map> compute_cache; + ankerl::unordered_dense::map> graphics_cache; + ankerl::unordered_dense::map> compute_cache; Shader::Profile profile; Shader::HostTranslateInfo host_info; diff --git a/src/video_core/renderer_opengl/gl_texture_cache.h b/src/video_core/renderer_opengl/gl_texture_cache.h index e2a2022cb2..dfcef4b0b6 100644 --- a/src/video_core/renderer_opengl/gl_texture_cache.h +++ b/src/video_core/renderer_opengl/gl_texture_cache.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project @@ -157,7 +157,7 @@ private: UtilShaders util_shaders; FormatConversionPass format_conversion_pass; - std::array, 3> format_properties; + std::array, 3> format_properties; bool has_broken_texture_view_formats = false; OGLTexture null_image_1d_array; diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.h b/src/video_core/renderer_vulkan/vk_pipeline_cache.h index 7909bd8cf0..1f7dab935c 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.h +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -8,7 +11,7 @@ #include #include #include -#include +#include #include #include "common/common_types.h" @@ -155,8 +158,8 @@ private: GraphicsPipelineCacheKey graphics_key{}; GraphicsPipeline* current_pipeline{}; - std::unordered_map> compute_cache; - std::unordered_map> graphics_cache; + ankerl::unordered_dense::map> compute_cache; + ankerl::unordered_dense::map> graphics_cache; ShaderPools main_pools; diff --git a/src/video_core/renderer_vulkan/vk_query_cache.cpp b/src/video_core/renderer_vulkan/vk_query_cache.cpp index b9ff4cd00e..c035eb7cd9 100644 --- a/src/video_core/renderer_vulkan/vk_query_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_query_cache.cpp @@ -10,7 +10,7 @@ #include #include #include -#include +#include #include #include #include "video_core/renderer_vulkan/vk_texture_cache.h" @@ -230,7 +230,7 @@ public: sync_values_stash.emplace_back(); std::vector* sync_values = &sync_values_stash.back(); sync_values->reserve(num_slots_used); - std::unordered_map> offsets; + ankerl::unordered_dense::map> offsets; resolve_buffers.clear(); size_t resolve_buffer_index = ObtainBuffer(num_slots_used); resolve_buffers.push_back(resolve_buffer_index); @@ -412,7 +412,7 @@ private: template void ApplyBanksWideOp(std::vector& queries, Func&& func) { std::conditional_t>, - std::unordered_map>> + ankerl::unordered_dense::map>> indexer; for (auto q : queries) { auto* query = GetQuery(q); @@ -722,7 +722,7 @@ public: void SyncWrites() override { CloseCounter(); - std::unordered_map> sync_values_stash; + ankerl::unordered_dense::map> sync_values_stash; for (auto q : pending_sync) { auto* query = GetQuery(q); if (True(query->flags & VideoCommon::QueryFlagBits::IsRewritten)) { diff --git a/src/video_core/renderer_vulkan/vk_render_pass_cache.cpp b/src/video_core/renderer_vulkan/vk_render_pass_cache.cpp index f1e15595b3..1d7af349d1 100644 --- a/src/video_core/renderer_vulkan/vk_render_pass_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_render_pass_cache.cpp @@ -4,7 +4,7 @@ // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include +#include #include diff --git a/src/video_core/renderer_vulkan/vk_render_pass_cache.h b/src/video_core/renderer_vulkan/vk_render_pass_cache.h index 91ad4bf577..5c7b3c2aed 100644 --- a/src/video_core/renderer_vulkan/vk_render_pass_cache.h +++ b/src/video_core/renderer_vulkan/vk_render_pass_cache.h @@ -1,10 +1,13 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once #include -#include +#include #include "video_core/surface.h" #include "video_core/vulkan_common/vulkan_wrapper.h" @@ -47,7 +50,7 @@ public: private: const Device* device{}; - std::unordered_map cache; + ankerl::unordered_dense::map cache; std::mutex mutex; }; diff --git a/src/video_core/shader_cache.h b/src/video_core/shader_cache.h index 244146fe52..7d93a542ef 100644 --- a/src/video_core/shader_cache.h +++ b/src/video_core/shader_cache.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project @@ -10,7 +10,7 @@ #include #include #include -#include +#include #include #include @@ -148,8 +148,8 @@ private: mutable std::mutex lookup_mutex; std::mutex invalidation_mutex; - std::unordered_map> lookup_cache; - std::unordered_map> invalidation_cache; + ankerl::unordered_dense::map> lookup_cache; + ankerl::unordered_dense::map> invalidation_cache; std::vector> storage; std::vector marked_for_removal; }; diff --git a/src/video_core/shader_environment.h b/src/video_core/shader_environment.h index c5bd0e7339..183b6b903b 100644 --- a/src/video_core/shader_environment.h +++ b/src/video_core/shader_environment.h @@ -14,7 +14,7 @@ #include #include #include -#include +#include #include #include "common/common_types.h" @@ -79,10 +79,10 @@ protected: GPUVAddr program_base{}; std::vector code; - std::unordered_map texture_types; - std::unordered_map texture_pixel_formats; - std::unordered_map cbuf_values; - std::unordered_map cbuf_replacements; + ankerl::unordered_dense::map texture_types; + ankerl::unordered_dense::map texture_pixel_formats; + ankerl::unordered_dense::map cbuf_values; + ankerl::unordered_dense::map cbuf_replacements; u32 local_memory_size{}; u32 texture_bound{}; @@ -201,10 +201,10 @@ public: private: std::vector code; - std::unordered_map texture_types; - std::unordered_map texture_pixel_formats; - std::unordered_map cbuf_values; - std::unordered_map cbuf_replacements; + ankerl::unordered_dense::map texture_types; + ankerl::unordered_dense::map texture_pixel_formats; + ankerl::unordered_dense::map cbuf_values; + ankerl::unordered_dense::map cbuf_replacements; std::array workgroup_size{}; u32 local_memory_size{}; u32 shared_memory_size{}; diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index 2564a67780..0ed65e29e9 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h @@ -9,7 +9,7 @@ #include #include #include -#include +#include #include #include "common/alignment.h" @@ -1989,8 +1989,8 @@ SamplerId TextureCache

::FindSampler(const TSCEntry& config) { } const auto [pair, is_new] = channel_state->samplers.try_emplace(config); if (is_new) { - EnforceSamplerBudget(); pair->second = slot_samplers.insert(runtime, config); + EnforceSamplerBudget(); } return pair->second; } @@ -2035,7 +2035,7 @@ void TextureCache

::TrimInactiveSamplers(size_t budget) { } set.insert(id); }; - std::unordered_set active; + ankerl::unordered_dense::set active; active.reserve(channel_state->graphics_sampler_ids.size() + channel_state->compute_sampler_ids.size()); for (const SamplerId id : channel_state->graphics_sampler_ids) { @@ -2363,9 +2363,7 @@ void TextureCache

::UnregisterImage(ImageId image_id) { image.flags &= ~ImageFlagBits::BadOverlap; lru_cache.Free(image.lru_index); const auto& clear_page_table = - [image_id](u64 page, - std::unordered_map, Common::IdentityHash>& - selected_page_table) { + [image_id](u64 page, ankerl::unordered_dense::map, Common::IdentityHash>& selected_page_table) { const auto page_it = selected_page_table.find(page); if (page_it == selected_page_table.end()) { ASSERT_MSG(false, "Unregistering unregistered page=0x{:x}", page << YUZU_PAGEBITS); diff --git a/src/video_core/texture_cache/texture_cache_base.h b/src/video_core/texture_cache/texture_cache_base.h index 42f1a158d9..cff300da8f 100644 --- a/src/video_core/texture_cache/texture_cache_base.h +++ b/src/video_core/texture_cache/texture_cache_base.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2023 yuzu Emulator Project @@ -12,8 +12,9 @@ #include #include #include +// TODO: find out which don't require stable iters #include -#include +#include #include #include #include @@ -66,7 +67,7 @@ struct AsyncDecodeContext { std::atomic_bool complete; }; -using TextureCacheGPUMap = std::unordered_map, Common::IdentityHash>; +using TextureCacheGPUMap = ankerl::unordered_dense::map, Common::IdentityHash>; class TextureCacheChannelInfo : public ChannelInfo { public: @@ -85,6 +86,7 @@ public: std::vector compute_sampler_ids; std::vector compute_image_view_ids; + // TODO: still relies on bad iterators :( std::unordered_map image_views; std::unordered_map samplers; @@ -454,10 +456,9 @@ private: RenderTargets render_targets; - std::unordered_map framebuffers; - - std::unordered_map, Common::IdentityHash> page_table; - std::unordered_map> sparse_views; + ankerl::unordered_dense::map framebuffers; + ankerl::unordered_dense::map, Common::IdentityHash> page_table; + ankerl::unordered_dense::map> sparse_views; DAddr virtual_invalid_space{}; @@ -514,7 +515,7 @@ private: DelayedDestructionRing sentenced_image_view; DelayedDestructionRing sentenced_framebuffers; - std::unordered_map image_allocs_table; + ankerl::unordered_dense::map image_allocs_table; Common::ScratchBuffer swizzle_data_buffer; Common::ScratchBuffer unswizzle_data_buffer; @@ -531,17 +532,17 @@ private: // Join caching boost::container::small_vector join_overlap_ids; - std::unordered_set join_overlaps_found; + ankerl::unordered_dense::set join_overlaps_found; boost::container::small_vector join_left_aliased_ids; boost::container::small_vector join_right_aliased_ids; - std::unordered_set join_ignore_textures; + ankerl::unordered_dense::set join_ignore_textures; boost::container::small_vector join_bad_overlap_ids; struct JoinCopy { bool is_alias; ImageId id; }; boost::container::small_vector join_copies_to_do; - std::unordered_map join_alias_indices; + ankerl::unordered_dense::map join_alias_indices; }; } // namespace VideoCommon diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index f06551b91e..8cb2ebfb62 100644 --- a/src/video_core/vulkan_common/vulkan_device.cpp +++ b/src/video_core/vulkan_common/vulkan_device.cpp @@ -9,7 +9,7 @@ #include #include #include -#include +#include #include #include @@ -145,7 +145,7 @@ VkFormatFeatureFlags GetFormatFeatures(VkFormatProperties properties, FormatType } } -std::unordered_map GetFormatProperties(vk::PhysicalDevice physical) { +ankerl::unordered_dense::map GetFormatProperties(vk::PhysicalDevice physical) { static constexpr std::array formats{ VK_FORMAT_A1R5G5B5_UNORM_PACK16, VK_FORMAT_A2B10G10R10_SINT_PACK32, @@ -287,7 +287,7 @@ std::unordered_map GetFormatProperties(vk::Physica VK_FORMAT_R8_USCALED, VK_FORMAT_S8_UINT, }; - std::unordered_map format_properties; + ankerl::unordered_dense::map format_properties; for (const auto format : formats) { format_properties.emplace(format, physical.GetFormatProperties(format)); } @@ -295,7 +295,7 @@ std::unordered_map GetFormatProperties(vk::Physica } #if defined(ANDROID) && defined(ARCHITECTURE_arm64) -void OverrideBcnFormats(std::unordered_map& format_properties) { +void OverrideBcnFormats(ankerl::unordered_dense::map& format_properties) { // These properties are extracted from Adreno driver 512.687.0 constexpr VkFormatFeatureFlags tiling_features{VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | @@ -1612,7 +1612,7 @@ void Device::CollectToolingInfo() { std::vector Device::GetDeviceQueueCreateInfos() const { static constexpr float QUEUE_PRIORITY = 1.0f; - std::unordered_set unique_queue_families{graphics_family, present_family}; + ankerl::unordered_dense::set unique_queue_families{graphics_family, present_family}; std::vector queue_cis; queue_cis.reserve(unique_queue_families.size()); diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h index 73a232ddee..7d738a81df 100644 --- a/src/video_core/vulkan_common/vulkan_device.h +++ b/src/video_core/vulkan_common/vulkan_device.h @@ -10,7 +10,7 @@ #include #include #include -#include +#include #include #include "common/common_types.h" @@ -1074,7 +1074,7 @@ private: std::vector valid_heap_memory; ///< Heaps used. /// Format properties dictionary. - std::unordered_map format_properties; + ankerl::unordered_dense::map format_properties; /// Nsight Aftermath GPU crash tracker std::unique_ptr nsight_aftermath_tracker; diff --git a/src/yuzu/compatibility_list.h b/src/yuzu/compatibility_list.h index c0675d7936..bb1e4b6205 100644 --- a/src/yuzu/compatibility_list.h +++ b/src/yuzu/compatibility_list.h @@ -1,16 +1,19 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once #include -#include +#include #include #include "common/common_types.h" -using CompatibilityList = std::unordered_map>; +using CompatibilityList = ankerl::unordered_dense::map>; CompatibilityList::const_iterator FindMatchingCompatibilityEntry( const CompatibilityList& compatibility_list, u64 program_id); diff --git a/src/yuzu/configuration/input_profiles.h b/src/yuzu/configuration/input_profiles.h index 67f823ec83..326e7f9a1d 100644 --- a/src/yuzu/configuration/input_profiles.h +++ b/src/yuzu/configuration/input_profiles.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project @@ -7,7 +7,7 @@ #pragma once #include -#include +#include #include "qt_common/config/qt_config.h" @@ -35,5 +35,5 @@ public: private: bool ProfileExistsInMap(const std::string& profile_name) const; - std::unordered_map> map_profiles; + ankerl::unordered_dense::map> map_profiles; }; diff --git a/src/yuzu/loading_screen.cpp b/src/yuzu/loading_screen.cpp index b081fff6b6..d6550a2c81 100644 --- a/src/yuzu/loading_screen.cpp +++ b/src/yuzu/loading_screen.cpp @@ -1,7 +1,10 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include +#include #include #include #include diff --git a/src/yuzu/loading_screen.h b/src/yuzu/loading_screen.h index 17045595d3..a39d5f6605 100644 --- a/src/yuzu/loading_screen.h +++ b/src/yuzu/loading_screen.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -77,8 +80,8 @@ private: std::unique_ptr fadeout_animation; // Definitions for the differences in text and styling for each stage - std::unordered_map progressbar_style; - std::unordered_map stage_translations; + ankerl::unordered_dense::map progressbar_style; + ankerl::unordered_dense::map stage_translations; // newly generated shaders are added to the end of the file, so when loading and compiling // shaders, it will start quickly but end slow if new shaders were added since previous launch. diff --git a/src/yuzu/multiplayer/chat_room.h b/src/yuzu/multiplayer/chat_room.h index e9eb48e824..a94683ef58 100644 --- a/src/yuzu/multiplayer/chat_room.h +++ b/src/yuzu/multiplayer/chat_room.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -6,8 +6,7 @@ #pragma once #include -#include -#include +#include #include #include #include @@ -66,8 +65,8 @@ private: bool has_mod_perms = false; QStandardItemModel* player_list; std::unique_ptr ui; - std::unordered_set block_list; - std::unordered_map icon_cache; + ankerl::unordered_dense::set block_list; + ankerl::unordered_dense::map icon_cache; }; Q_DECLARE_METATYPE(Network::ChatEntry); From efd26c3e4d400ab87b8d419c6a3c3d895664fdaa Mon Sep 17 00:00:00 2001 From: lizzie Date: Tue, 10 Feb 2026 03:45:02 +0100 Subject: [PATCH 08/14] [android, nce] Remove LRU-Cache (#3500) This PR removes the obsolete logic of LRU-cache within, removing old and dead code, allows Android to avoid unnecesary usage of memory caching, aside to prevent some old bugs to arise in other systems that allows NCE, improves a small margin of performance and makes memory ram consumption overall better, by 300 - 500mb, revealing that part of the code was still active, even if LRU wasn't enabled. Signed-off-by: lizzie Signed-off-by: CamilleLaVey Co-authored-by: CamilleLaVey Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3500 Reviewed-by: DraVee Reviewed-by: CamilleLaVey Co-authored-by: lizzie Co-committed-by: lizzie --- .../features/settings/model/BooleanSetting.kt | 2 - .../settings/model/view/SettingsItem.kt | 7 - .../settings/ui/SettingsFragmentPresenter.kt | 1 - .../app/src/main/jni/android_settings.h | 4 - .../app/src/main/res/values-ar/strings.xml | 3 - .../app/src/main/res/values-ckb/strings.xml | 3 - .../app/src/main/res/values-cs/strings.xml | 3 - .../app/src/main/res/values-de/strings.xml | 3 - .../app/src/main/res/values-es/strings.xml | 3 - .../app/src/main/res/values-fa/strings.xml | 2 - .../app/src/main/res/values-fr/strings.xml | 3 - .../app/src/main/res/values-he/strings.xml | 3 - .../app/src/main/res/values-hu/strings.xml | 3 - .../app/src/main/res/values-id/strings.xml | 3 - .../app/src/main/res/values-it/strings.xml | 3 - .../app/src/main/res/values-ja/strings.xml | 3 - .../app/src/main/res/values-ko/strings.xml | 3 - .../app/src/main/res/values-nb/strings.xml | 3 - .../app/src/main/res/values-pl/strings.xml | 3 - .../src/main/res/values-pt-rBR/strings.xml | 3 - .../src/main/res/values-pt-rPT/strings.xml | 3 - .../app/src/main/res/values-ru/strings.xml | 3 - .../app/src/main/res/values-sr/strings.xml | 3 - .../app/src/main/res/values-uk/strings.xml | 3 - .../app/src/main/res/values-vi/strings.xml | 3 - .../src/main/res/values-zh-rCN/strings.xml | 3 - .../src/main/res/values-zh-rTW/strings.xml | 3 - .../app/src/main/res/values/strings.xml | 3 - src/common/settings.h | 5 - src/core/arm/nce/lru_cache.h | 187 ------------------ src/core/arm/nce/patcher.cpp | 23 --- src/core/arm/nce/patcher.h | 30 +-- 32 files changed, 4 insertions(+), 326 deletions(-) delete mode 100644 src/core/arm/nce/lru_cache.h diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt index 157cc502b7..996c67367f 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt @@ -73,8 +73,6 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting { SHOW_SHADERS_BUILDING("show_shaders_building"), DEBUG_FLUSH_BY_LINE("flush_line"), - USE_LRU_CACHE("use_lru_cache"), - DONT_SHOW_DRIVER_SHADER_WARNING("dont_show_driver_shader_warning"), ENABLE_OVERLAY("enable_overlay"), diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt index 47932c2dd8..8bb618289e 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt @@ -123,13 +123,6 @@ abstract class SettingsItem( // List of all general val settingsItems = HashMap().apply { put(StringInputSetting(StringSetting.DEVICE_NAME, titleId = R.string.device_name)) - put( - SwitchSetting( - BooleanSetting.USE_LRU_CACHE, - titleId = R.string.use_lru_cache, - descriptionId = R.string.use_lru_cache_description - ) - ) put( SwitchSetting( BooleanSetting.RENDERER_USE_SPEED_LIMIT, diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt index e7c2dbf609..8135b6043c 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt @@ -235,7 +235,6 @@ class SettingsFragmentPresenter( add(HeaderSetting(R.string.cpu)) add(IntSetting.FAST_CPU_TIME.key) - add(BooleanSetting.USE_LRU_CACHE.key) add(BooleanSetting.CORE_SYNC_CORE_SPEED.key) add(IntSetting.MEMORY_LAYOUT.key) diff --git a/src/android/app/src/main/jni/android_settings.h b/src/android/app/src/main/jni/android_settings.h index 7643d49532..ac64251e20 100644 --- a/src/android/app/src/main/jni/android_settings.h +++ b/src/android/app/src/main/jni/android_settings.h @@ -54,10 +54,6 @@ namespace AndroidSettings { Settings::SwitchableSetting driver_path{linkage, "", "driver_path", Settings::Category::GpuDriver}; - // LRU Cache - Settings::SwitchableSetting use_lru_cache{linkage, true, "use_lru_cache", - Settings::Category::System}; - Settings::Setting theme{linkage, 0, "theme", Settings::Category::Android}; Settings::Setting theme_mode{linkage, -1, "theme_mode", Settings::Category::Android}; Settings::Setting black_backgrounds{linkage, false, "black_backgrounds", diff --git a/src/android/app/src/main/res/values-ar/strings.xml b/src/android/app/src/main/res/values-ar/strings.xml index ea8c9c340a..05f65ecf78 100644 --- a/src/android/app/src/main/res/values-ar/strings.xml +++ b/src/android/app/src/main/res/values-ar/strings.xml @@ -94,9 +94,6 @@ مزامنة سرعة النواة قم بمزامنة سرعة النواة مع النسبة المئوية للسرعة القصوى لتحسين الأداء دون تغيير السرعة الفعلية للعبة. - تمكين ذاكرة التخزين المؤقتة LRU - قم بتمكين أو تعطيل ذاكرة التخزين المؤقتة الأقل استخدامًا (LRU)، مما يزيد من الأداء عن طريق توفير استخدام معالج وحدة المعالجة المركزية. قد تواجه بعض الألعاب مشكلات مع هذا الإعداد، لذا قم بتعطيله إذا لم يتم تشغيل اللعبة أو تعطلت بشكل عشوائي. - تمكين محاكاة MMU المضيف يعمل هذا التحسين على تسريع وصول الذاكرة بواسطة البرنامج الضيف. يؤدي تمكينه إلى إجراء عمليات قراءة/كتابة ذاكرة الضيف مباشرة في الذاكرة والاستفادة من MMU المضيف. يؤدي تعطيل هذا إلى إجبار جميع عمليات الوصول إلى الذاكرة على استخدام محاكاة MMU البرمجية. مقابض تصحيح الأخطاء diff --git a/src/android/app/src/main/res/values-ckb/strings.xml b/src/android/app/src/main/res/values-ckb/strings.xml index 3ba262a436..ea5208a26b 100644 --- a/src/android/app/src/main/res/values-ckb/strings.xml +++ b/src/android/app/src/main/res/values-ckb/strings.xml @@ -58,9 +58,6 @@ مزامنة سرعة النواة خێرایی تیکەکانی ناوک ڕێکبخە لەگەڵ ڕێژەی خێرایی بەرزترین بۆ باشترکردنی کارایی بەبێ گۆڕینی خێرایی ڕاستەقینەی یارییەکە. - تمكين ذاكرة التخزين المؤقت LRU - چالاک یان ناچالاککردنی کاشەی LRU، کارایی باشتر دەکات بە هەڵگرتنی بەکارهێنانی پرۆسەی CPU. هەندێک یاری کێشەی لەگەڵ هەیە، بەتایبەتی TotK 1.2.1، بۆیە بیخەوێنە ئەگەر یاریەکە نەگەڕێت یان بە هەڕەمەکی بشکێت. - چالاککردنی میمیکردنی MMU میواندە ئەم باشکردنە خێرایی دەستکەوتنی بیرگە لەلایەن پرۆگرامی میوانەکە زیاد دەکات. چالاککردنی وای لێدەکات کە خوێندنەوە/نووسینەکانی بیرگەی میوانەکە ڕاستەوخۆ لە بیرگە ئەنجام بدرێت و میمیکردنی MMU میواندە بەکاربهێنێت. ناچالاککردنی ئەمە هەموو دەستکەوتنەکانی بیرگە ڕەت دەکاتەوە لە بەکارهێنانی میمیکردنی MMU نەرمەکاڵا. diff --git a/src/android/app/src/main/res/values-cs/strings.xml b/src/android/app/src/main/res/values-cs/strings.xml index 99aa22271b..b56c21c9b0 100644 --- a/src/android/app/src/main/res/values-cs/strings.xml +++ b/src/android/app/src/main/res/values-cs/strings.xml @@ -90,9 +90,6 @@ Synchronizovat rychlost jádra Synchronizuje rychlost jádra s nastaveným limitem rychlosti, aby se zlepšil výkon bez zrychlení samotné hry. - Zapnout mezipaměť LRU - Zapne, nebo vypne mezipaměť LRU (Least Recently Used). Ta zvyšuje výkon tím, že šetří využití procesoru. Některé hry s tímto mohou mít problém, takže pokud se hra nespustí, nebo náhodně padá, tuto volbu vypněte. - Povolit emulaci hostitelské MMU Tato optimalizace zrychluje hostovanému programu přístupy do paměti. Zapnutím této funkce se čtení i zápisy do paměti provádějí přímo a využívají hostitelskou MMU. Při vypnutí musí všechny přístupy do paměti použít softwarovou emulaci MMU. Ladící parametry diff --git a/src/android/app/src/main/res/values-de/strings.xml b/src/android/app/src/main/res/values-de/strings.xml index 1962a84257..7524402e6e 100644 --- a/src/android/app/src/main/res/values-de/strings.xml +++ b/src/android/app/src/main/res/values-de/strings.xml @@ -90,9 +90,6 @@ Kern-Geschwindigkeit synchronisieren Synchronisiert die Taktrate des Kerns mit der maximalen Geschwindigkeit, um die Leistung zu verbessern, ohne die tatsächliche Spielgeschwindigkeit zu verändern. - LRU-Cache aktivieren - Aktivieren oder deaktivieren Sie den LRU-Cache, um die Leistung durch Einsparung von CPU-Prozessorauslastung zu verbessern. Einige Spiele haben Probleme damit, insbesondere TotK 1.2.1, deaktivieren Sie es also, wenn das Spiel nicht startet oder zufällig abstürzt. - Host-MMU-Emulation aktivieren Diese Optimierung beschleunigt Speicherzugriffe durch das Gastprogramm. Wenn aktiviert, erfolgen Speicherlese- und -schreibvorgänge des Gastes direkt im Speicher und nutzen die MMU des Hosts. Das Deaktivieren erzwingt die Verwendung der Software-MMU-Emulation für alle Speicherzugriffe. Debug-Regler diff --git a/src/android/app/src/main/res/values-es/strings.xml b/src/android/app/src/main/res/values-es/strings.xml index 358b892887..c0334ba8ba 100644 --- a/src/android/app/src/main/res/values-es/strings.xml +++ b/src/android/app/src/main/res/values-es/strings.xml @@ -75,9 +75,6 @@ Sincronizar la velocidad del núcleo Sincroniza la velocidad del núcleo con el porcentaje máximo de velocidad para mejorar el rendimiento sin alterar la velocidad real del juego. - Habilitar caché LRU - Habilite o deshabilite la caché menos utilizada recientemente (LRU), aumentando el rendimiento al ahorrar el uso del proceso de la CPU. Algunos juegos pueden ver problemas con esta configuración, así que desactívela si el juego no arranca o se bloquea aleatoriamente. - Habilitar emulación de MMU del anfitrión Esta optimización acelera el acceso a la memoria del programa invitado. Al habilitarla, las lecturas y escrituras de la memoria del invitado se realizan directamente en la memoria y utilizan la MMU del anfitrión. Al deshabilitarla, todos los accesos a la memoria utilizan el software de emulación de la MMU. diff --git a/src/android/app/src/main/res/values-fa/strings.xml b/src/android/app/src/main/res/values-fa/strings.xml index 3f123e872a..80e40444c2 100644 --- a/src/android/app/src/main/res/values-fa/strings.xml +++ b/src/android/app/src/main/res/values-fa/strings.xml @@ -60,8 +60,6 @@ اطمینان از سازگاری داده‌ها بین عملیات محاسباتی و حافظه. این گزینه ممکن است مشکلات برخی بازی‌ها را رفع کند، اما در برخی موارد ممکن است عملکرد را کاهش دهد. به نظر می‌رسد بازی‌های با Unreal Engine 4 بیشترین تأثیر را داشته باشند. غیرفعال کردن مرتب‌سازی مجدد بافر در صورت انتخاب، مرتب‌سازی مجدد آپلودهای حافظه نگاشت‌شده غیرفعال می‌شود که امکان ارتباط آپلودها با ترسیمات خاص را فراهم می‌کند. ممکن است در برخی موارد عملکرد را کاهش دهد. - فعال‌سازی حافظه نهان LRU - حافظه پنهان LRU را فعال یا غیرفعال کنید تا با کاهش استفاده از پردازنده، عملکرد بهبود یابد. برخی بازی‌ها مانند TotK 1.2.1 با این ویژگی مشکل دارند، در صورت عدم راه‌اندازی یا قطعی تصادفی بازی، آن را غیرفعال کنید. حالت پویای گسترده تعداد قابلیت‌هایی که می‌توان در حالت Extended Dynamic State استفاده کرد را کنترل می‌کند. اعداد بالاتر قابلیت‌های بیشتری را فعال کرده و می‌توانند عملکرد را افزایش دهند، اما ممکن است با برخی درایورها و فروشندگان مشکلاتی ایجاد کنند. مقدار پیش‌فرض بسته به سیستم و قابلیت‌های سخت‌افزاری شما ممکن است متفاوت باشد. این مقدار را می‌توان تغییر داد تا زمانی که پایداری و کیفیت بصری بهتری حاصل شود. غیرفعال diff --git a/src/android/app/src/main/res/values-fr/strings.xml b/src/android/app/src/main/res/values-fr/strings.xml index 5501244f6e..add275870d 100644 --- a/src/android/app/src/main/res/values-fr/strings.xml +++ b/src/android/app/src/main/res/values-fr/strings.xml @@ -75,9 +75,6 @@ Synchroniser la vitesse du cœur Synchronise la vitesse du cœur avec le pourcentage de vitesse maximal pour améliorer les performances sans modifier la vitesse réelle du jeu. - Activer le cache LRU - Active ou désactive le cache LRU (Least Recently Used) pour améliorer les performances en réduisant l’utilisation du processeur. Certains jeux peuvent rencontrer des problèmes avec ce réglage ; désactivez-le si le jeu ne démarre pas ou plante de manière aléatoire. - Activer l\'émulation de la MMU hôte Cette optimisation accélère les accès mémoire par le programme invité. L\'activer entraîne que les lectures/écritures mémoire de l\'invité sont effectuées directement en mémoire et utilisent la MMU de l\'hôte. Désactiver cela force tous les accès mémoire à utiliser l\'émulation logicielle de la MMU. Boutons de débogage diff --git a/src/android/app/src/main/res/values-he/strings.xml b/src/android/app/src/main/res/values-he/strings.xml index 978f017fd5..bb156ba308 100644 --- a/src/android/app/src/main/res/values-he/strings.xml +++ b/src/android/app/src/main/res/values-he/strings.xml @@ -62,9 +62,6 @@ סנכרון מהירות ליבה סנכרן את מהירות הליבה לאחוז המהירות המרבי כדי לשפר ביצועים מבלי לשנות את מהירות המשחק בפועל. - הפעלת מטמון LRU - הפעל או השבת מטמון LRU לשיפור ביצועים על ידי חיסכון בשימוש במעבד. לחלק מהמשחקים כמו TotK 1.2.1 יש בעיות - השבת אם המשחק לא עולה או קורס באקראי. - הפעל אמולציית MMU מארח אופטימיזציה זו מאיצה את גישת הזיכרון על ידי התוכנית האורחת. הפעלתה גורמת לכך שפעולות קריאה/כתיבה לזיכרון האורח מתבצעות ישירות לזיכרון ומשתמשות ב-MMU של המארח. השבתת זאת מאלצת את כל גישות הזיכרון להשתמש באמולציית MMU תוכנתית. לשימוש בפיתוח בלבד. diff --git a/src/android/app/src/main/res/values-hu/strings.xml b/src/android/app/src/main/res/values-hu/strings.xml index fcfcba403b..606da0b470 100644 --- a/src/android/app/src/main/res/values-hu/strings.xml +++ b/src/android/app/src/main/res/values-hu/strings.xml @@ -58,9 +58,6 @@ Magsebesség szinkronizálása A mag sebességének szinkronizálása a maximális sebesség százalékával a teljesítmény javítása érdekében a játék tényleges sebességének megváltoztatása nélkül. - LRU gyorsítótár engedélyezése - Engedélyezi vagy letiltja az LRU gyorsítótárat, növelve a teljesítményt a CPU használat csökkentésével. Néhány játéknak (különösen a TotK 1.2.1-nek) problémája lehet vele - tiltsa le, ha a játék nem indul el vagy véletlenszerűen összeomlik. - Gazda MMU emuláció engedélyezése Ez az optimalizáció gyorsítja a vendégprogram memória-hozzáférését. Engedélyezése esetén a vendég memóriaolvasási/írási műveletei közvetlenül a memóriában történnek, és kihasználják a gazda MMU-ját. Letiltás esetén minden memória-hozzáférés a szoftveres MMU emulációt használja. diff --git a/src/android/app/src/main/res/values-id/strings.xml b/src/android/app/src/main/res/values-id/strings.xml index 921030d07b..1189e7444b 100644 --- a/src/android/app/src/main/res/values-id/strings.xml +++ b/src/android/app/src/main/res/values-id/strings.xml @@ -77,9 +77,6 @@ Sinkronisasi Kecepatan Inti Sinkronkan kecepatan inti dengan persentase kecepatan maksimum untuk meningkatkan performa tanpa mengubah kecepatan sebenarnya dari permainan. - Aktifkan LRU Cache - Aktifkan atau nonaktifkan cache LRU untuk meningkatkan performa dengan mengurangi penggunaan proses CPU. Beberapa game seperti TotK 1.2.1 memiliki masalah - nonaktifkan jika game tidak mau berjalan atau crash acak. - Aktifkan Emulasi MMU Host Optimasi ini mempercepat akses memori oleh program tamu. Mengaktifkannya menyebabkan pembacaan/penulisan memori tamu dilakukan langsung ke memori dan memanfaatkan MMU Host. Menonaktifkan ini memaksa semua akses memori menggunakan Emulasi MMU Perangkat Lunak. diff --git a/src/android/app/src/main/res/values-it/strings.xml b/src/android/app/src/main/res/values-it/strings.xml index 9edb175879..cf0efab261 100644 --- a/src/android/app/src/main/res/values-it/strings.xml +++ b/src/android/app/src/main/res/values-it/strings.xml @@ -75,9 +75,6 @@ Sincronizza velocità core Sincronizza la velocità del core con la percentuale massima di velocità per migliorare le prestazioni senza alterare la velocità effettiva del gioco. - Abilita cache LRU - Abilita o disabilita la cache LRU per migliorare le prestazioni riducendo l\'uso della CPU. Alcuni giochi come TotK 1.2.1 hanno problemi - disabilitalo se il gioco non si avvia o crasha casualmente. - Abilita l\'emulazione della MMU nell\'host Questa ottimizzazione accelera gli accessi alla memoria da parte del programma guest. Abilitandola, le letture/scritture della memoria guest vengono eseguite direttamente in memoria e sfruttano la MMU host. Disabilitandola, tutti gli accessi alla memoria sono costretti a utilizzare l\'emulazione software della MMU. diff --git a/src/android/app/src/main/res/values-ja/strings.xml b/src/android/app/src/main/res/values-ja/strings.xml index ecfb367728..cf382a9792 100644 --- a/src/android/app/src/main/res/values-ja/strings.xml +++ b/src/android/app/src/main/res/values-ja/strings.xml @@ -58,9 +58,6 @@ コア速度の同期 コアの速度を最大速度パーセンテージに同期させ、ゲームの実際の速度を変えずにパフォーマンスを向上させます。 - LRUキャッシュを有効化 - LRUキャッシュを有効/無効にし、CPUプロセスの使用を節約してパフォーマンスを向上させます。TotK 1.2.1など一部のゲームで問題が発生する可能性があるため、ゲームが起動しない場合やランダムにクラッシュする場合は無効にしてください。 - ホストMMUエミュレーションを有効化 この最適化により、ゲストプログラムによるメモリアクセスが高速化されます。有効にすると、ゲストのメモリ読み書きが直接メモリ内で実行され、ホストのMMUを利用します。無効にすると、すべてのメモリアクセスでソフトウェアMMUエミュレーションが使用されます。 diff --git a/src/android/app/src/main/res/values-ko/strings.xml b/src/android/app/src/main/res/values-ko/strings.xml index 2a094d6996..173d9b5678 100644 --- a/src/android/app/src/main/res/values-ko/strings.xml +++ b/src/android/app/src/main/res/values-ko/strings.xml @@ -58,9 +58,6 @@ 코어 속도 동기화 코어 틱 속도를 최대 속도 백분율과 동기화하여 게임의 실제 속도를 변경하지 않고 성능을 향상시킵니다. - LRU 캐시 사용 - LRU 캐시를 활성화 또는 비활성화하여 CPU 프로세스 사용을 절약하고 성능을 향상시킵니다. TotK 1.2.1을 포함한 일부 게임에서 문제가 발생할 수 있으므로 게임이 부팅되지 않거나 무작위로 충돌하는 경우 비활성화하세요. - 호스트 MMU 에뮬레이션 사용 이 최적화는 게스트 프로그램의 메모리 접근 속도를 높입니다. 활성화하면 게스트의 메모리 읽기/쓰기가 메모리에서 직접 수행되고 호스트의 MMU를 활용합니다. 비활성화하면 모든 메모리 접근에 소프트웨어 MMU 에뮬레이션을 사용하게 됩니다. diff --git a/src/android/app/src/main/res/values-nb/strings.xml b/src/android/app/src/main/res/values-nb/strings.xml index b1be0f3fdc..b3f8f4902c 100644 --- a/src/android/app/src/main/res/values-nb/strings.xml +++ b/src/android/app/src/main/res/values-nb/strings.xml @@ -58,9 +58,6 @@ Synkroniser kjernespeed Synkroniser kjernens hastighet med maksimal hastighetsprosent for å forbedre ytelsen uten å endre spillets faktiske hastighet. - Aktiver LRU-mellomlager - Aktiver eller deaktiver LRU-mellomlager for å forbedre ytelsen ved å spare CPU-prosessorbruk. Noen spill som TotK 1.2.1 har problemer med dette - deaktiver hvis spillet ikke starter eller krasjer tilfeldig. - Aktiver verts-MMU-emulering Denne optimaliseringen fremskynder minnetilgang av gjesteprogrammet. Hvis aktivert, utføres gjestens minnelesing/skriving direkte i minnet og bruker vertens MMU. Deaktivering tvinger alle minnetilganger til å bruke programvarebasert MMU-emulering. diff --git a/src/android/app/src/main/res/values-pl/strings.xml b/src/android/app/src/main/res/values-pl/strings.xml index 0b0c524405..3d69cce8f3 100644 --- a/src/android/app/src/main/res/values-pl/strings.xml +++ b/src/android/app/src/main/res/values-pl/strings.xml @@ -90,9 +90,6 @@ Synchronizuj prędkość rdzenia Synchronizuje prędkość rdzenia z maksymalnym procentem prędkości, aby poprawić wydajność bez zmiany rzeczywistej prędkości gry. - Włącz pamięć podręczną LRU - Włącz lub wyłącz pamięć podręczną LRU, aby poprawić wydajność poprzez zmniejszenie użycia procesora. Niektóre gry, takie jak TotK 1.2.1, mogą mieć problemy - wyłącz, jeśli gra się nie uruchamia lub losowo zawiesza. - Włącz emulację MMU hosta Ta optymalizacja przyspiesza dostęp do pamięci przez program gościa. Włączenie powoduje, że odczyty/zapisy pamięci gościa są wykonywane bezpośrednio w pamięci i wykorzystują MMU hosta. Wyłączenie wymusza użycie programowej emulacji MMU dla wszystkich dostępów do pamięci. Przełączniki debugowania diff --git a/src/android/app/src/main/res/values-pt-rBR/strings.xml b/src/android/app/src/main/res/values-pt-rBR/strings.xml index 55ad2cb098..08e2695d2e 100644 --- a/src/android/app/src/main/res/values-pt-rBR/strings.xml +++ b/src/android/app/src/main/res/values-pt-rBR/strings.xml @@ -84,9 +84,6 @@ Sincronizar Velocidade do Núcleo Aumenta a velocidade do ciclo do núcleo até o máximo para melhorar o desempenho, mantendo a velocidade real do jogo inalterada. - Ativar Cache LRU - O cache Least Recently Used (LRU, ou Itens Menos Usados) aumenta o desempenho, economizando o uso do processador. Alguns jogos podem apresentar problemas com esta configuração. Desative-a caso o jogo não inicie ou trave aleatoriamente. - Ativar Emulação da MMU do Sistema Esta otimização acelera o acesso à memória pelo programa. Ao ativá-la, leituras e gravações são feitas diretamente na memória, usando a MMU (Unidade de Gerenciamento de Memória) do sistema. Ao desativar, todos os acessos passam a utilizar a Emulação de MMU por Software. Controles de Depuração diff --git a/src/android/app/src/main/res/values-pt-rPT/strings.xml b/src/android/app/src/main/res/values-pt-rPT/strings.xml index 9aba51007e..1cbbb74c34 100644 --- a/src/android/app/src/main/res/values-pt-rPT/strings.xml +++ b/src/android/app/src/main/res/values-pt-rPT/strings.xml @@ -58,9 +58,6 @@ Sincronizar velocidade do núcleo Sincroniza a velocidade do núcleo com a percentagem máxima de velocidade para melhorar o desempenho sem alterar a velocidade real do jogo. - Ativar cache LRU - Ative ou desative a cache LRU para melhorar desempenho poupando uso do processador. Alguns jogos como TotK 1.2.1 têm problemas - desative se o jogo não iniciar ou falhar aleatoriamente. - Ativar Emulação de MMU do Anfitrião Esta otimização acelera os acessos à memória pelo programa convidado. Ativar faz com que as leituras/escritas de memória do convidado sejam efetuadas diretamente na memória e utilizem a MMU do Anfitrião. Desativar força todos os acessos à memória a usar a Emulação de MMU por Software. diff --git a/src/android/app/src/main/res/values-ru/strings.xml b/src/android/app/src/main/res/values-ru/strings.xml index 7b3ae7bdc7..a9a3cceaae 100644 --- a/src/android/app/src/main/res/values-ru/strings.xml +++ b/src/android/app/src/main/res/values-ru/strings.xml @@ -92,9 +92,6 @@ Синхронизация скорости ядра Синхронизирует скорость ядра с максимальным процентом скорости для улучшения производительности без изменения фактической скорости игры. - Включить LRU-кеш - Включите или отключите кэш LRU (наименее недавно использованный), повышая производительность за счёт снижения нагрузки на ЦП. Некоторые игры могут работать с ним некорректно (например, TotK 1.2.1), поэтому отключите, если игра не запускается или случайно вылетает. - Включить эмуляцию MMU хоста Эта оптимизация ускоряет доступ к памяти гостевой программой. При включении операции чтения/записи памяти гостя выполняются напрямую в памяти с использованием MMU хоста. Отключение заставляет все обращения к памяти использовать программную эмуляцию MMU. Настройки отладки diff --git a/src/android/app/src/main/res/values-sr/strings.xml b/src/android/app/src/main/res/values-sr/strings.xml index 306ae44e57..62fe3f2fa3 100644 --- a/src/android/app/src/main/res/values-sr/strings.xml +++ b/src/android/app/src/main/res/values-sr/strings.xml @@ -56,9 +56,6 @@ Синхронизујте брзину језгре Синхронизујте језгро Тицк Брзина брзине максималне брзине да бисте побољшали перформансе без измене стварне брзине игре. - Омогући ЛРУ кеш - Омогућите или онемогућите најмање недавно коришћене (ЛРУ) кеш меморије, све веће перформансе штедећи употребу процеса ЦПУ-а. Неке игре са њом имају проблеме, посебно тотк 1.2.1, тако да онемогућите ако се игра не покрене или се руши насумично. - Омогући емулацију MMU домаћина Ова оптимизација убрзава приступ меморији од стране гостујућег програма. Укључивање изазива да се читања/уписа меморије госта обављају директно у меморији и користе MMU домаћина. Искључивање присиљава све приступе меморији да користе софтверску емулацију MMU. diff --git a/src/android/app/src/main/res/values-uk/strings.xml b/src/android/app/src/main/res/values-uk/strings.xml index 9b8b84581d..ada2445d05 100644 --- a/src/android/app/src/main/res/values-uk/strings.xml +++ b/src/android/app/src/main/res/values-uk/strings.xml @@ -94,9 +94,6 @@ Синхронізувати швидкість ядра Синхронізує швидкість ядра з максимальним відсотком швидкості для покращення продуктивності без зміни реальної швидкості гри. - Увімкнути LRU-кеш - Увімкніть або вимкніть кеш LRU (Least Recently Used) для покращення продуктивності шляхом зменшення навантаження на CPU. Деякі ігри (зокрема TotK 1.2.1) можуть працювати некоректно - вимкніть, якщо гра не запускається або раптово вилітає. - Увімкнути емуляцію MMU хоста Ця оптимізація пришвидшує доступ до пам\'яті гостьовою програмою. Увімкнення призводить до того, що читання/запис пам\'яті гостя виконуються безпосередньо в пам\'яті та використовують MMU хоста. Вимкнення змушує всі звернення до пам\'яті використовувати програмну емуляцію MMU. Зневаджувальні регулятори diff --git a/src/android/app/src/main/res/values-vi/strings.xml b/src/android/app/src/main/res/values-vi/strings.xml index 5445ded6d0..82331efb1c 100644 --- a/src/android/app/src/main/res/values-vi/strings.xml +++ b/src/android/app/src/main/res/values-vi/strings.xml @@ -58,9 +58,6 @@ Đồng bộ tốc độ lõi Đồng bộ tốc độ lõi với tỷ lệ phần trăm tốc độ tối đa để cải thiện hiệu suất mà không làm thay đổi tốc độ thực tế của trò chơi. - Bật bộ nhớ đệm LRU - Bật hoặc tắt bộ nhớ đệm LRU để cải thiện hiệu suất bằng cách tiết kiệm quy trình sử dụng CPU. Một số trò chơi như TotK 1.2.1 có vấn đề - hãy tắt nếu trò chơi không khởi động hoặc bị treo ngẫu nhiên. - Bật giả lập MMU Máy chủ Tối ưu hóa này tăng tốc độ truy cập bộ nhớ của chương trình khách. Bật nó lên khiến các thao tác đọc/ghi bộ nhớ khách được thực hiện trực tiếp vào bộ nhớ và sử dụng MMU của Máy chủ. Tắt tính năng này buộc tất cả quyền truy cập bộ nhớ phải sử dụng Giả lập MMU Phần mềm. diff --git a/src/android/app/src/main/res/values-zh-rCN/strings.xml b/src/android/app/src/main/res/values-zh-rCN/strings.xml index 34a7eace23..08b55297a7 100644 --- a/src/android/app/src/main/res/values-zh-rCN/strings.xml +++ b/src/android/app/src/main/res/values-zh-rCN/strings.xml @@ -94,9 +94,6 @@ 同步核心速度 将核心速度与最大速度百分比同步,在不改变游戏实际速度的情况下提高性能。 - 启用LRU缓存 - 启用或禁用LRU缓存,通过节省CPU进程使用来提高性能。某些游戏可能存在问题,特别是TotK 1.2.1,如果游戏无法启动或随机崩溃,请禁用此选项。 - 启用主机 MMU 模拟 此优化可加速来宾程序的内存访问。启用后,来宾内存读取/写入将直接在内存中执行并利用主机的 MMU。禁用此功能将强制所有内存访问使用软件 MMU 模拟。 调试开关 diff --git a/src/android/app/src/main/res/values-zh-rTW/strings.xml b/src/android/app/src/main/res/values-zh-rTW/strings.xml index 8c7ec7d172..c7061ebc03 100644 --- a/src/android/app/src/main/res/values-zh-rTW/strings.xml +++ b/src/android/app/src/main/res/values-zh-rTW/strings.xml @@ -90,9 +90,6 @@ 同步核心速度 將核心速度與最大速度百分比同步,在不改變遊戲實際速度的情況下提高效能。 - 啟用LRU快取 - 啟用或停用LRU快取,透過節省CPU進程使用率來提高效能。某些遊戲可能存在問題,特別是薩爾達傳說:王國之淚 v1.2.1(更新),如果遊戲無法啟動或隨機崩潰,請停用此選項。 - 啟用主機 MMU 模擬 此最佳化可加速來賓程式的記憶體存取。啟用後,來賓記憶體讀取/寫入將直接在記憶體中執行並利用主機的 MMU。停用此功能將強制所有記憶體存取使用軟體 MMU 模擬。 偵錯開關 diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index 93331ec2d8..97aa054e8a 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -99,9 +99,6 @@ Synchronize Core Speed Synchronize the core tick speed to the maximum speed percentage to improve performance without altering the game\'s actual speed. - Enable LRU Cache - Enable or disable the Least Recently Used (LRU) cache, increasing performance by saving CPU process usage. Some games may see issues with this setting, so disable it if the game doesn\'t boot or crashes randomly. - Enable Host MMU Emulation This optimization speeds up memory accesses by the guest program. Enabling it causes guest memory reads/writes to be done directly into memory and make use of Host\'s MMU. Disabling this forces all memory accesses to use Software MMU Emulation. Debug knobs diff --git a/src/common/settings.h b/src/common/settings.h index 41f766a5e7..a609894027 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -217,11 +217,6 @@ struct Values { SwitchableSetting sync_core_speed{linkage, false, "sync_core_speed", Category::Core, Specialization::Default}; - // Memory -#ifdef HAS_NCE - SwitchableSetting lru_cache_enabled{linkage, false, "use_lru_cache", Category::System}; -#endif - // Cpu SwitchableSetting cpu_backend{linkage, #ifdef HAS_NCE diff --git a/src/core/arm/nce/lru_cache.h b/src/core/arm/nce/lru_cache.h deleted file mode 100644 index 947b66f675..0000000000 --- a/src/core/arm/nce/lru_cache.h +++ /dev/null @@ -1,187 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once -#include -#include -#include -#include -#include - -#include "common/logging/log.h" - -template -class LRUCache { -public: - using key_type = KeyType; - using value_type = ValueType; - using size_type = std::size_t; - - struct Statistics { - size_type hits = 0; - size_type misses = 0; - void reset() noexcept { hits = misses = 0; } - }; - - explicit LRUCache(size_type capacity, bool enabled = true) - : enabled_{enabled}, capacity_{capacity} { - cache_map_.reserve(capacity_); - LOG_WARNING(Core, "LRU Cache initialised (state: {} | capacity: {})", enabled_ ? "enabled" : "disabled", capacity_); - } - - // Non-movable copy semantics - LRUCache(const LRUCache&) = delete; - LRUCache& operator=(const LRUCache&) = delete; - LRUCache(LRUCache&& other) noexcept { *this = std::move(other); } - LRUCache& operator=(LRUCache&& other) noexcept { - if (this == &other) return *this; - std::unique_lock this_lock(mutex_, std::defer_lock); - std::unique_lock other_lock(other.mutex_, std::defer_lock); - std::lock(this_lock, other_lock); - enabled_ = other.enabled_; - capacity_ = other.capacity_; - cache_list_ = std::move(other.cache_list_); - cache_map_ = std::move(other.cache_map_); - stats_ = other.stats_; - return *this; - } - ~LRUCache() = default; - - [[nodiscard]] value_type* get(const key_type& key) { - if (!enabled_) [[unlikely]] return nullptr; - std::unique_lock lock(mutex_); - auto it = cache_map_.find(key); - if (it == cache_map_.end()) { - ++stats_.misses; - return nullptr; - } - move_to_front(it); - ++stats_.hits; - return &it->second.second; - } - - [[nodiscard]] value_type* peek(const key_type& key) const { - if (!enabled_) [[unlikely]] return nullptr; - std::shared_lock lock(mutex_); - auto it = cache_map_.find(key); - return it == cache_map_.end() ? nullptr : &it->second.second; - } - - template - void put(const key_type& key, V&& value) { - if (!enabled_) [[unlikely]] return; - std::unique_lock lock(mutex_); - insert_or_update(key, std::forward(value)); - } - - template - value_type& get_or_emplace(const key_type& key, ValueFactory&& factory) { - std::unique_lock lock(mutex_); - auto it = cache_map_.find(key); - if (it != cache_map_.end()) { - move_to_front(it); - return it->second.second; - } - value_type new_value = factory(); - insert_or_update(key, std::move(new_value)); - return cache_map_.find(key)->second.second; - } - - [[nodiscard]] bool contains(const key_type& key) const { - if (!enabled_) return false; - std::shared_lock lock(mutex_); - return cache_map_.find(key) != cache_map_.end(); - } - - bool erase(const key_type& key) { - if (!enabled_) return false; - std::unique_lock lock(mutex_); - auto it = cache_map_.find(key); - if (it == cache_map_.end()) return false; - cache_list_.erase(it->second.first); - cache_map_.erase(it); - return true; - } - - void clear() { - std::unique_lock lock(mutex_); - cache_list_.clear(); - cache_map_.clear(); - stats_.reset(); - } - - [[nodiscard]] size_type size() const { - if (!enabled_) return 0; - std::shared_lock lock(mutex_); - return cache_map_.size(); - } - - [[nodiscard]] size_type get_capacity() const { return capacity_; } - - void resize(size_type new_capacity) { - if (!enabled_) return; - std::unique_lock lock(mutex_); - capacity_ = new_capacity; - shrink_if_needed(); - cache_map_.reserve(capacity_); - } - - void setEnabled(bool state) { - std::unique_lock lock(mutex_); - enabled_ = state; - LOG_WARNING(Core, "LRU Cache state changed to: {}", state ? "enabled" : "disabled"); - if (!enabled_) clear(); - } - - [[nodiscard]] bool isEnabled() const { return enabled_; } - - [[nodiscard]] Statistics stats() const { - std::shared_lock lock(mutex_); - return stats_; - } - -private: - using list_type = std::list; - using list_iterator = typename list_type::iterator; - using map_value_type = std::pair; - using map_type = ankerl::unordered_dense::map; - - template - void insert_or_update(const key_type& key, V&& value) { - auto it = cache_map_.find(key); - if (it != cache_map_.end()) { - it->second.second = std::forward(value); - move_to_front(it); - return; - } - // evict LRU if full - if (cache_map_.size() >= capacity_) { - const auto& lru_key = cache_list_.back(); - cache_map_.erase(lru_key); - cache_list_.pop_back(); - } - cache_list_.push_front(key); - cache_map_[key] = {cache_list_.begin(), std::forward(value)}; - } - - void move_to_front(typename map_type::iterator it) { - cache_list_.splice(cache_list_.begin(), cache_list_, it->second.first); - it->second.first = cache_list_.begin(); - } - - void shrink_if_needed() { - while (cache_map_.size() > capacity_) { - const auto& lru_key = cache_list_.back(); - cache_map_.erase(lru_key); - cache_list_.pop_back(); - } - } - -private: - mutable std::shared_mutex mutex_; - bool enabled_{true}; - size_type capacity_; - list_type cache_list_; - map_type cache_map_; - mutable Statistics stats_; -}; diff --git a/src/core/arm/nce/patcher.cpp b/src/core/arm/nce/patcher.cpp index 3a620bff12..bd8e4fd7fb 100644 --- a/src/core/arm/nce/patcher.cpp +++ b/src/core/arm/nce/patcher.cpp @@ -17,27 +17,6 @@ namespace Core::NCE { -Patcher::Patcher(Patcher&& other) noexcept - : patch_cache(std::move(other.patch_cache)), - m_patch_instructions(std::move(other.m_patch_instructions)), - m_patch_instructions_pre(std::move(other.m_patch_instructions_pre)), - c(m_patch_instructions), - c_pre(m_patch_instructions_pre), - m_save_context(other.m_save_context), - m_load_context(other.m_load_context), - m_save_context_pre(other.m_save_context_pre), - m_load_context_pre(other.m_load_context_pre), - mode(other.mode), - total_program_size(other.total_program_size), - m_relocate_module_index(other.m_relocate_module_index), - modules(std::move(other.modules)), - curr_patch(nullptr) { - if (!modules.empty()) { - curr_patch = &modules.back(); - } -} - - using namespace Common::Literals; using namespace oaknut::util; @@ -47,8 +26,6 @@ constexpr size_t MaxRelativeBranch = 128_MiB; constexpr u32 ModuleCodeIndex = 0x24 / sizeof(u32); Patcher::Patcher() : c(m_patch_instructions), c_pre(m_patch_instructions_pre) { - LOG_WARNING(Core_ARM, "Patcher initialized with LRU cache {}", - patch_cache.isEnabled() ? "enabled" : "disabled"); // The first word of the patch section is always a branch to the first instruction of the // module. c.dw(0); diff --git a/src/core/arm/nce/patcher.h b/src/core/arm/nce/patcher.h index 8612cfc63d..303cf68ec6 100644 --- a/src/core/arm/nce/patcher.h +++ b/src/core/arm/nce/patcher.h @@ -9,12 +9,12 @@ #include #include +#include "common/logging/log.h" #include "common/common_types.h" #include "common/settings.h" #include "core/hle/kernel/code_set.h" #include "core/hle/kernel/k_typed_address.h" #include "core/hle/kernel/physical_memory.h" -#include "lru_cache.h" #include using ModuleID = std::array; // NSO build ID struct PatchCacheKey { @@ -44,6 +44,7 @@ enum class PatchMode : u32 { Split, ///< Patch sections are inserted before .text and after .data }; + using ModuleTextAddress = u64; using PatchTextAddress = u64; using EntryTrampolines = ankerl::unordered_dense::map; @@ -53,10 +54,6 @@ public: void SetModuleID(const ModuleID& id) { module_id = id; } - Patcher(const Patcher&) = delete; - Patcher& operator=(const Patcher&) = delete; - Patcher(Patcher&& other) noexcept; - Patcher& operator=(Patcher&&) noexcept = delete; explicit Patcher(); ~Patcher(); bool PatchText(const Kernel::PhysicalMemory& program_image, @@ -99,28 +96,9 @@ private: void WriteCntpctHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg) { WriteCntpctHandler(module_dest, dest_reg, c); } private: - static constexpr size_t CACHE_SIZE = 16384; // Cache size for patch entries - LRUCache patch_cache{CACHE_SIZE, Settings::values.lru_cache_enabled.GetValue()}; - void BranchToPatch(uintptr_t module_dest) { - if (patch_cache.isEnabled()) { - PatchCacheKey key{module_id, module_dest}; - LOG_DEBUG(Core_ARM, "LRU cache lookup for module={}, offset={:#x}", fmt::ptr(module_id.data()), module_dest); - // Try to get existing patch entry from cache - if (auto* cached_patch = patch_cache.get(key)) { - LOG_WARNING(Core_ARM, "LRU cache hit for module offset {:#x}", module_dest); - curr_patch->m_branch_to_patch_relocations.push_back({c.offset(), *cached_patch}); - return; - } - LOG_DEBUG(Core_ARM, "LRU cache miss for module offset {:#x}, creating new patch", module_dest); - // Not in cache: create and store - const auto patch_addr = c.offset(); - curr_patch->m_branch_to_patch_relocations.push_back({patch_addr, module_dest}); - patch_cache.put(key, patch_addr); - } else { - LOG_DEBUG(Core_ARM, "LRU cache disabled - direct patch for offset {:#x}", module_dest); - curr_patch->m_branch_to_patch_relocations.push_back({c.offset(), module_dest}); - } + LOG_DEBUG(Core_ARM, "Patch for offset {:#x}", module_dest); + curr_patch->m_branch_to_patch_relocations.push_back({c.offset(), module_dest}); } void BranchToPatchPre(uintptr_t module_dest) { From 8376153729b7cfe710268bd8ae0ccb9a46244b28 Mon Sep 17 00:00:00 2001 From: MaranBr Date: Sat, 7 Feb 2026 14:47:14 -0400 Subject: [PATCH 09/14] Add support for target FPS frame pacing --- src/common/settings.h | 7 +++++++ src/common/settings_enums.h | 1 + src/qt_common/config/shared_translation.cpp | 9 +++++++++ src/video_core/renderer_vulkan/vk_scheduler.h | 16 +++++++++++++++ .../renderer_vulkan/vk_swapchain.cpp | 20 ++++++++++++++++++- 5 files changed, 52 insertions(+), 1 deletion(-) diff --git a/src/common/settings.h b/src/common/settings.h index a609894027..79028fcaa2 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -434,6 +434,13 @@ struct Values { "accelerate_astc", Category::RendererAdvanced}; + SwitchableSetting frame_pacing_mode{linkage, + FramePacingMode::Default, + FramePacingMode::Default, + FramePacingMode::Target_120, + "frame_pacing_mode", + Category::RendererAdvanced}; + SwitchableSetting astc_recompression{linkage, AstcRecompression::Uncompressed, "astc_recompression", diff --git a/src/common/settings_enums.h b/src/common/settings_enums.h index 33c553dc3c..509bd414ae 100644 --- a/src/common/settings_enums.h +++ b/src/common/settings_enums.h @@ -129,6 +129,7 @@ ENUM(TimeZone, Auto, Default, Cet, Cst6Cdt, Cuba, Eet, Egypt, Eire, Est, Est5Edt ENUM(AnisotropyMode, Automatic, Default, X2, X4, X8, X16, X32, X64, None); ENUM(AstcDecodeMode, Cpu, Gpu, CpuAsynchronous); ENUM(AstcRecompression, Uncompressed, Bc1, Bc3); +ENUM(FramePacingMode, Default, Target_30, Target_60, Target_120); ENUM(VSyncMode, Immediate, Mailbox, Fifo, FifoRelaxed); ENUM(VramUsageMode, Conservative, Aggressive); ENUM(RendererBackend, OpenGL_GLSL, Vulkan, Null, OpenGL_GLASM, OpenGL_SPIRV); diff --git a/src/qt_common/config/shared_translation.cpp b/src/qt_common/config/shared_translation.cpp index 5d4185b47d..fdcfbea9e9 100644 --- a/src/qt_common/config/shared_translation.cpp +++ b/src/qt_common/config/shared_translation.cpp @@ -225,6 +225,8 @@ std::unique_ptr InitializeTranslations(QObject* parent) "intermediate format: RGBA8.\n" "BC1/BC3: The intermediate format will be recompressed to BC1 or BC3 format,\n" " saving VRAM but degrading image quality.")); + INSERT(Settings, frame_pacing_mode, tr("Frame Pacing Mode (Vulkan only)"), + tr("Controls how the emulator manages frame pacing to reduce stuttering and make the frame rate smoother and more consistent.")); INSERT(Settings, vram_usage_mode, tr("VRAM Usage Mode:"), @@ -497,6 +499,13 @@ std::unique_ptr ComboboxEnumeration(QObject* parent) PAIR(AstcRecompression, Bc1, tr("BC1 (Low quality)")), PAIR(AstcRecompression, Bc3, tr("BC3 (Medium quality)")), }}); + translations->insert({Settings::EnumMetadata::Index(), + { + PAIR(FramePacingMode, Default, tr("Default")), + PAIR(FramePacingMode, Target_30, tr("30 FPS")), + PAIR(FramePacingMode, Target_60, tr("60 FPS")), + PAIR(FramePacingMode, Target_120, tr("120 FPS")), + }}); translations->insert({Settings::EnumMetadata::Index(), { PAIR(VramUsageMode, Conservative, tr("Conservative")), diff --git a/src/video_core/renderer_vulkan/vk_scheduler.h b/src/video_core/renderer_vulkan/vk_scheduler.h index 5216a436c8..5eeaa1731c 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.h +++ b/src/video_core/renderer_vulkan/vk_scheduler.h @@ -120,6 +120,22 @@ public: master_semaphore->Wait(tick); } + /// Waits until the next game frame based on the current game FPS. + void WaitFPS(u64 tick, double target_fps) { + if (master_semaphore->CurrentTick() >= tick) { + return; + } + static auto next_frame_time = std::chrono::steady_clock::now(); + auto frame_duration = std::chrono::duration(1.0 / target_fps); + next_frame_time += std::chrono::duration_cast(frame_duration); + auto now = std::chrono::steady_clock::now(); + if (next_frame_time > now) { + std::this_thread::sleep_until(next_frame_time); + } else { + next_frame_time = now; + } + } + /// Returns the master timeline semaphore. [[nodiscard]] MasterSemaphore& GetMasterSemaphore() const noexcept { return *master_semaphore; diff --git a/src/video_core/renderer_vulkan/vk_swapchain.cpp b/src/video_core/renderer_vulkan/vk_swapchain.cpp index 7418ad934e..d0502f6c2b 100644 --- a/src/video_core/renderer_vulkan/vk_swapchain.cpp +++ b/src/video_core/renderer_vulkan/vk_swapchain.cpp @@ -194,7 +194,25 @@ bool Swapchain::AcquireNextImage() { break; } - scheduler.Wait(resource_ticks[image_index]); + if (!Settings::values.use_speed_limit.GetValue()) { + scheduler.Wait(resource_ticks[image_index]); + } else { + switch (Settings::values.frame_pacing_mode.GetValue()) { + case Settings::FramePacingMode::Default: + scheduler.Wait(resource_ticks[image_index]); + break; + case Settings::FramePacingMode::Target_30: + scheduler.WaitFPS(resource_ticks[image_index], 30.0); + break; + case Settings::FramePacingMode::Target_60: + scheduler.WaitFPS(resource_ticks[image_index], 60.0); + break; + case Settings::FramePacingMode::Target_120: + scheduler.WaitFPS(resource_ticks[image_index], 120.0); + break; + } + } + resource_ticks[image_index] = scheduler.CurrentTick(); return is_suboptimal || is_outdated; From f67363b8b4f2e5b140fbfbb2ef59cd1ee67fd06f Mon Sep 17 00:00:00 2001 From: MaranBr Date: Sat, 7 Feb 2026 14:54:03 -0400 Subject: [PATCH 10/14] Fix headers --- src/video_core/renderer_vulkan/vk_scheduler.h | 2 +- src/video_core/renderer_vulkan/vk_swapchain.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/video_core/renderer_vulkan/vk_scheduler.h b/src/video_core/renderer_vulkan/vk_scheduler.h index 5eeaa1731c..6de9b6b1ca 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.h +++ b/src/video_core/renderer_vulkan/vk_scheduler.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project diff --git a/src/video_core/renderer_vulkan/vk_swapchain.cpp b/src/video_core/renderer_vulkan/vk_swapchain.cpp index d0502f6c2b..ce1c3b9442 100644 --- a/src/video_core/renderer_vulkan/vk_swapchain.cpp +++ b/src/video_core/renderer_vulkan/vk_swapchain.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project From fd15519cbc29b060168a7c61586ed0aa2f7babf3 Mon Sep 17 00:00:00 2001 From: MaranBr Date: Mon, 9 Feb 2026 08:21:47 -0400 Subject: [PATCH 11/14] Add Android option --- .../yuzu_emu/features/settings/model/IntSetting.kt | 1 + .../features/settings/model/view/SettingsItem.kt | 9 +++++++++ .../settings/ui/SettingsFragmentPresenter.kt | 1 + src/android/app/src/main/res/values/arrays.xml | 13 +++++++++++++ src/android/app/src/main/res/values/strings.xml | 8 ++++++++ 5 files changed, 32 insertions(+) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt index be3b2f4a48..c8985cd45d 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt @@ -29,6 +29,7 @@ enum class IntSetting(override val key: String) : AbstractIntSetting { RENDERER_DYNA_STATE("dyna_state"), DMA_ACCURACY("dma_accuracy"), + FRAME_PACING_MODE("frame_pacing_mode"), AUDIO_OUTPUT_ENGINE("output_engine"), MAX_ANISOTROPY("max_anisotropy"), THEME("theme"), diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt index 8bb618289e..0aecc0d253 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt @@ -641,6 +641,15 @@ abstract class SettingsItem( valuesId = R.array.dmaAccuracyValues ) ) + put( + SingleChoiceSetting( + IntSetting.FRAME_PACING_MODE, + titleId = R.string.frame_pacing_mode, + descriptionId = R.string.frame_pacing_mode_description, + choicesId = R.array.framePacingModeNames, + valuesId = R.array.framePacingModeValues + ) + ) put( SwitchSetting( BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS, diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt index 8135b6043c..cac0022edc 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt @@ -265,6 +265,7 @@ class SettingsFragmentPresenter( add(IntSetting.RENDERER_ACCURACY.key) add(IntSetting.DMA_ACCURACY.key) + add(IntSetting.FRAME_PACING_MODE.key) add(IntSetting.MAX_ANISOTROPY.key) add(IntSetting.RENDERER_VRAM_USAGE_MODE.key) add(IntSetting.RENDERER_ASTC_DECODE_METHOD.key) diff --git a/src/android/app/src/main/res/values/arrays.xml b/src/android/app/src/main/res/values/arrays.xml index 69f1590844..1f45383a86 100644 --- a/src/android/app/src/main/res/values/arrays.xml +++ b/src/android/app/src/main/res/values/arrays.xml @@ -530,6 +530,19 @@ 2 + + @string/frame_pacing_mode_default + @string/frame_pacing_mode_target_30 + @string/frame_pacing_mode_target_60 + @string/frame_pacing_mode_target_120 + + + 0 + 1 + 2 + 3 + + @string/applet_hle @string/applet_lle diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index 97aa054e8a..99e225192f 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -473,6 +473,8 @@ Controls the GPU emulation mode. Most games render fine with Fast or Balanced modes, but Accurate is still required for some. Particles tend to only render correctly with Accurate mode. DMA Accuracy Controls the DMA precision accuracy. Safe precision can fix issues in some games, but it can also impact performance in some cases. If unsure, leave this on Default. + Frame Pacing Mode + Controls how the emulator manages frame pacing to reduce stuttering and make the frame rate smoother and more consistent. Anisotropic filtering Improves the quality of textures when viewed at oblique angles VRAM Usage Mode @@ -1023,6 +1025,12 @@ Unsafe Safe + + Default + 30 FPS + 60 FPS + 120 FPS + CPU GPU From 7f1a36fb5277d8ed801ef9d9955329828def835e Mon Sep 17 00:00:00 2001 From: MaranBr Date: Mon, 9 Feb 2026 11:03:25 -0400 Subject: [PATCH 12/14] Fix frame pacing when switching games --- src/android/app/src/main/res/values/arrays.xml | 2 +- src/android/app/src/main/res/values/strings.xml | 2 +- src/common/settings.h | 4 ++-- src/common/settings_enums.h | 2 +- src/qt_common/config/shared_translation.cpp | 2 +- src/video_core/renderer_vulkan/vk_scheduler.h | 16 ++++++++++++---- src/video_core/renderer_vulkan/vk_swapchain.cpp | 5 ++++- 7 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/android/app/src/main/res/values/arrays.xml b/src/android/app/src/main/res/values/arrays.xml index 1f45383a86..8b6ae5405c 100644 --- a/src/android/app/src/main/res/values/arrays.xml +++ b/src/android/app/src/main/res/values/arrays.xml @@ -531,7 +531,7 @@ - @string/frame_pacing_mode_default + @string/frame_pacing_mode_target_Auto @string/frame_pacing_mode_target_30 @string/frame_pacing_mode_target_60 @string/frame_pacing_mode_target_120 diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index 99e225192f..1c8f009132 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -1026,7 +1026,7 @@ Safe - Default + Auto 30 FPS 60 FPS 120 FPS diff --git a/src/common/settings.h b/src/common/settings.h index 79028fcaa2..5dc2f30cf8 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -435,8 +435,8 @@ struct Values { Category::RendererAdvanced}; SwitchableSetting frame_pacing_mode{linkage, - FramePacingMode::Default, - FramePacingMode::Default, + FramePacingMode::Target_Auto, + FramePacingMode::Target_Auto, FramePacingMode::Target_120, "frame_pacing_mode", Category::RendererAdvanced}; diff --git a/src/common/settings_enums.h b/src/common/settings_enums.h index 509bd414ae..def99db21b 100644 --- a/src/common/settings_enums.h +++ b/src/common/settings_enums.h @@ -129,7 +129,7 @@ ENUM(TimeZone, Auto, Default, Cet, Cst6Cdt, Cuba, Eet, Egypt, Eire, Est, Est5Edt ENUM(AnisotropyMode, Automatic, Default, X2, X4, X8, X16, X32, X64, None); ENUM(AstcDecodeMode, Cpu, Gpu, CpuAsynchronous); ENUM(AstcRecompression, Uncompressed, Bc1, Bc3); -ENUM(FramePacingMode, Default, Target_30, Target_60, Target_120); +ENUM(FramePacingMode, Target_Auto, Target_30, Target_60, Target_120); ENUM(VSyncMode, Immediate, Mailbox, Fifo, FifoRelaxed); ENUM(VramUsageMode, Conservative, Aggressive); ENUM(RendererBackend, OpenGL_GLSL, Vulkan, Null, OpenGL_GLASM, OpenGL_SPIRV); diff --git a/src/qt_common/config/shared_translation.cpp b/src/qt_common/config/shared_translation.cpp index fdcfbea9e9..b5ad941cc5 100644 --- a/src/qt_common/config/shared_translation.cpp +++ b/src/qt_common/config/shared_translation.cpp @@ -501,7 +501,7 @@ std::unique_ptr ComboboxEnumeration(QObject* parent) }}); translations->insert({Settings::EnumMetadata::Index(), { - PAIR(FramePacingMode, Default, tr("Default")), + PAIR(FramePacingMode, Target_Auto, tr("Auto")), PAIR(FramePacingMode, Target_30, tr("30 FPS")), PAIR(FramePacingMode, Target_60, tr("60 FPS")), PAIR(FramePacingMode, Target_120, tr("120 FPS")), diff --git a/src/video_core/renderer_vulkan/vk_scheduler.h b/src/video_core/renderer_vulkan/vk_scheduler.h index 6de9b6b1ca..094607775c 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.h +++ b/src/video_core/renderer_vulkan/vk_scheduler.h @@ -125,10 +125,12 @@ public: if (master_semaphore->CurrentTick() >= tick) { return; } - static auto next_frame_time = std::chrono::steady_clock::now(); - auto frame_duration = std::chrono::duration(1.0 / target_fps); - next_frame_time += std::chrono::duration_cast(frame_duration); - auto now = std::chrono::steady_clock::now(); + const auto frame_duration = std::chrono::duration_cast(std::chrono::duration(1.0 / target_fps)); + const auto now = std::chrono::steady_clock::now(); + if (next_frame_time == std::chrono::steady_clock::time_point{}) { + next_frame_time = now; + } + next_frame_time += frame_duration; if (next_frame_time > now) { std::this_thread::sleep_until(next_frame_time); } else { @@ -136,6 +138,10 @@ public: } } + void ResetFramePacing() { + next_frame_time = std::chrono::steady_clock::now(); + } + /// Returns the master timeline semaphore. [[nodiscard]] MasterSemaphore& GetMasterSemaphore() const noexcept { return *master_semaphore; @@ -277,6 +283,8 @@ private: std::mutex queue_mutex; std::condition_variable_any event_cv; std::jthread worker_thread; + + std::chrono::steady_clock::time_point next_frame_time{}; }; } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_swapchain.cpp b/src/video_core/renderer_vulkan/vk_swapchain.cpp index ce1c3b9442..3f56a950a3 100644 --- a/src/video_core/renderer_vulkan/vk_swapchain.cpp +++ b/src/video_core/renderer_vulkan/vk_swapchain.cpp @@ -146,6 +146,9 @@ void Swapchain::Create( { is_outdated = false; is_suboptimal = false; + + scheduler.ResetFramePacing(); + width = width_; height = height_; #ifdef ANDROID @@ -198,7 +201,7 @@ bool Swapchain::AcquireNextImage() { scheduler.Wait(resource_ticks[image_index]); } else { switch (Settings::values.frame_pacing_mode.GetValue()) { - case Settings::FramePacingMode::Default: + case Settings::FramePacingMode::Target_Auto: scheduler.Wait(resource_ticks[image_index]); break; case Settings::FramePacingMode::Target_30: From 6e859f0c1a879ea2f6ed3e3859520ba67a1634ac Mon Sep 17 00:00:00 2001 From: MaranBr Date: Tue, 10 Feb 2026 08:35:58 -0400 Subject: [PATCH 13/14] Improve frame pacing precision and stability --- .../app/src/main/res/values/arrays.xml | 2 + .../app/src/main/res/values/strings.xml | 1 + src/common/settings.h | 2 +- src/common/settings_enums.h | 2 +- src/qt_common/config/shared_translation.cpp | 1 + .../renderer_vulkan/vk_scheduler.cpp | 11 +--- src/video_core/renderer_vulkan/vk_scheduler.h | 61 ++++++++++++------- .../renderer_vulkan/vk_swapchain.cpp | 31 +++++----- 8 files changed, 64 insertions(+), 47 deletions(-) diff --git a/src/android/app/src/main/res/values/arrays.xml b/src/android/app/src/main/res/values/arrays.xml index 8b6ae5405c..e85bc3592a 100644 --- a/src/android/app/src/main/res/values/arrays.xml +++ b/src/android/app/src/main/res/values/arrays.xml @@ -535,12 +535,14 @@ @string/frame_pacing_mode_target_30 @string/frame_pacing_mode_target_60 @string/frame_pacing_mode_target_120 + @string/frame_pacing_mode_target_240 0 1 2 3 + 4 diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index 1c8f009132..79282c18e3 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -1030,6 +1030,7 @@ 30 FPS 60 FPS 120 FPS + 240 FPS CPU diff --git a/src/common/settings.h b/src/common/settings.h index 5dc2f30cf8..669d38b17c 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -437,7 +437,7 @@ struct Values { SwitchableSetting frame_pacing_mode{linkage, FramePacingMode::Target_Auto, FramePacingMode::Target_Auto, - FramePacingMode::Target_120, + FramePacingMode::Target_240, "frame_pacing_mode", Category::RendererAdvanced}; diff --git a/src/common/settings_enums.h b/src/common/settings_enums.h index def99db21b..7e3bef9bea 100644 --- a/src/common/settings_enums.h +++ b/src/common/settings_enums.h @@ -129,7 +129,7 @@ ENUM(TimeZone, Auto, Default, Cet, Cst6Cdt, Cuba, Eet, Egypt, Eire, Est, Est5Edt ENUM(AnisotropyMode, Automatic, Default, X2, X4, X8, X16, X32, X64, None); ENUM(AstcDecodeMode, Cpu, Gpu, CpuAsynchronous); ENUM(AstcRecompression, Uncompressed, Bc1, Bc3); -ENUM(FramePacingMode, Target_Auto, Target_30, Target_60, Target_120); +ENUM(FramePacingMode, Target_Auto, Target_30, Target_60, Target_120, Target_240); ENUM(VSyncMode, Immediate, Mailbox, Fifo, FifoRelaxed); ENUM(VramUsageMode, Conservative, Aggressive); ENUM(RendererBackend, OpenGL_GLSL, Vulkan, Null, OpenGL_GLASM, OpenGL_SPIRV); diff --git a/src/qt_common/config/shared_translation.cpp b/src/qt_common/config/shared_translation.cpp index b5ad941cc5..40fd42720f 100644 --- a/src/qt_common/config/shared_translation.cpp +++ b/src/qt_common/config/shared_translation.cpp @@ -505,6 +505,7 @@ std::unique_ptr ComboboxEnumeration(QObject* parent) PAIR(FramePacingMode, Target_30, tr("30 FPS")), PAIR(FramePacingMode, Target_60, tr("60 FPS")), PAIR(FramePacingMode, Target_120, tr("120 FPS")), + PAIR(FramePacingMode, Target_240, tr("240 FPS")), }}); translations->insert({Settings::EnumMetadata::Index(), { diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp index aafcfdf65b..8830c5a506 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.cpp +++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp @@ -48,14 +48,9 @@ Scheduler::Scheduler(const Device& device_, StateTracker& state_tracker_) master_semaphore{std::make_unique(device)}, command_pool{std::make_unique(*master_semaphore, device)} { - /*// PRE-OPTIMIZATION: Warm up the pool to prevent mid-frame spikes - { - std::scoped_lock rl{reserve_mutex}; - chunk_reserve.reserve(2048); // Prevent vector resizing - for (int i = 0; i < 1024; ++i) { - chunk_reserve.push_back(std::make_unique()); - } - }*/ +#ifdef _WIN32 + high_res_timer = std::make_unique(); +#endif AcquireNewChunk(); AllocateWorkerCommandBuffer(); diff --git a/src/video_core/renderer_vulkan/vk_scheduler.h b/src/video_core/renderer_vulkan/vk_scheduler.h index 094607775c..a2adb795e9 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.h +++ b/src/video_core/renderer_vulkan/vk_scheduler.h @@ -6,6 +6,11 @@ #pragma once +#ifdef _WIN32 +#include +#include +#endif + #include #include #include @@ -16,6 +21,7 @@ #include "common/alignment.h" #include "common/common_types.h" +#include "common/settings.h" #include "common/polyfill_thread.h" #include "video_core/renderer_vulkan/vk_master_semaphore.h" #include "video_core/vulkan_common/vulkan_wrapper.h" @@ -111,33 +117,30 @@ public: return master_semaphore->IsFree(tick); } - /// Waits for the given tick to trigger on the GPU. - void Wait(u64 tick) { + /// Waits for the given GPU tick, optionally pacing frames. + void Wait(u64 tick, double target_fps = 0.0) { + if (Settings::values.use_speed_limit.GetValue() && target_fps > 0.0) { + const auto frame_duration = std::chrono::duration(1.0 / target_fps); + auto now = std::chrono::steady_clock::now(); + if (next_frame_time == std::chrono::steady_clock::time_point{} || now > next_frame_time + frame_duration) { + next_frame_time = now + std::chrono::duration_cast(frame_duration); + } + if (now < next_frame_time) { + std::this_thread::sleep_until(next_frame_time); + now = std::chrono::steady_clock::now(); + } + next_frame_time += std::chrono::duration_cast(frame_duration); + if (now > next_frame_time) { + next_frame_time = now + std::chrono::duration_cast(frame_duration); + } + } if (tick >= master_semaphore->CurrentTick()) { - // Make sure we are not waiting for the current tick without signalling Flush(); } master_semaphore->Wait(tick); } - /// Waits until the next game frame based on the current game FPS. - void WaitFPS(u64 tick, double target_fps) { - if (master_semaphore->CurrentTick() >= tick) { - return; - } - const auto frame_duration = std::chrono::duration_cast(std::chrono::duration(1.0 / target_fps)); - const auto now = std::chrono::steady_clock::now(); - if (next_frame_time == std::chrono::steady_clock::time_point{}) { - next_frame_time = now; - } - next_frame_time += frame_duration; - if (next_frame_time > now) { - std::this_thread::sleep_until(next_frame_time); - } else { - next_frame_time = now; - } - } - + /// Resets the frame pacing state by updating the next frame time to now. void ResetFramePacing() { next_frame_time = std::chrono::steady_clock::now(); } @@ -150,6 +153,18 @@ public: std::mutex submit_mutex; private: +#ifdef _WIN32 + class HighResolutionTimer { + public: + HighResolutionTimer() { + timeBeginPeriod(1); + } + ~HighResolutionTimer() { + timeEndPeriod(1); + } + }; +#endif + class Command { public: virtual ~Command() = default; @@ -285,6 +300,10 @@ private: std::jthread worker_thread; std::chrono::steady_clock::time_point next_frame_time{}; + +#ifdef _WIN32 + std::unique_ptr high_res_timer; +#endif }; } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_swapchain.cpp b/src/video_core/renderer_vulkan/vk_swapchain.cpp index 3f56a950a3..1f31c8d5a1 100644 --- a/src/video_core/renderer_vulkan/vk_swapchain.cpp +++ b/src/video_core/renderer_vulkan/vk_swapchain.cpp @@ -197,23 +197,22 @@ bool Swapchain::AcquireNextImage() { break; } - if (!Settings::values.use_speed_limit.GetValue()) { + switch (Settings::values.frame_pacing_mode.GetValue()) { + case Settings::FramePacingMode::Target_Auto: scheduler.Wait(resource_ticks[image_index]); - } else { - switch (Settings::values.frame_pacing_mode.GetValue()) { - case Settings::FramePacingMode::Target_Auto: - scheduler.Wait(resource_ticks[image_index]); - break; - case Settings::FramePacingMode::Target_30: - scheduler.WaitFPS(resource_ticks[image_index], 30.0); - break; - case Settings::FramePacingMode::Target_60: - scheduler.WaitFPS(resource_ticks[image_index], 60.0); - break; - case Settings::FramePacingMode::Target_120: - scheduler.WaitFPS(resource_ticks[image_index], 120.0); - break; - } + break; + case Settings::FramePacingMode::Target_30: + scheduler.Wait(resource_ticks[image_index], 30.0); + break; + case Settings::FramePacingMode::Target_60: + scheduler.Wait(resource_ticks[image_index], 60.0); + break; + case Settings::FramePacingMode::Target_120: + scheduler.Wait(resource_ticks[image_index], 120.0); + break; + case Settings::FramePacingMode::Target_240: + scheduler.Wait(resource_ticks[image_index], 240.0); + break; } resource_ticks[image_index] = scheduler.CurrentTick(); From d879fffb4a2c31ef5b712ea8704f9ecf2de7eeb7 Mon Sep 17 00:00:00 2001 From: MaranBr Date: Tue, 10 Feb 2026 11:43:33 -0400 Subject: [PATCH 14/14] Ensure correct first-frame timing --- src/video_core/renderer_vulkan/vk_scheduler.cpp | 2 -- src/video_core/renderer_vulkan/vk_scheduler.h | 11 ++++------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp index 8830c5a506..0acaf39b36 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.cpp +++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp @@ -47,11 +47,9 @@ Scheduler::Scheduler(const Device& device_, StateTracker& state_tracker_) : device{device_}, state_tracker{state_tracker_}, master_semaphore{std::make_unique(device)}, command_pool{std::make_unique(*master_semaphore, device)} { - #ifdef _WIN32 high_res_timer = std::make_unique(); #endif - AcquireNewChunk(); AllocateWorkerCommandBuffer(); worker_thread = std::jthread([this](std::stop_token token) { WorkerThread(token); }); diff --git a/src/video_core/renderer_vulkan/vk_scheduler.h b/src/video_core/renderer_vulkan/vk_scheduler.h index a2adb795e9..e762aa1019 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.h +++ b/src/video_core/renderer_vulkan/vk_scheduler.h @@ -122,17 +122,14 @@ public: if (Settings::values.use_speed_limit.GetValue() && target_fps > 0.0) { const auto frame_duration = std::chrono::duration(1.0 / target_fps); auto now = std::chrono::steady_clock::now(); - if (next_frame_time == std::chrono::steady_clock::time_point{} || now > next_frame_time + frame_duration) { + if (next_frame_time == std::chrono::steady_clock::time_point{}) { next_frame_time = now + std::chrono::duration_cast(frame_duration); } - if (now < next_frame_time) { - std::this_thread::sleep_until(next_frame_time); - now = std::chrono::steady_clock::now(); + while (now > next_frame_time) { + next_frame_time += std::chrono::duration_cast(frame_duration); } + std::this_thread::sleep_until(next_frame_time); next_frame_time += std::chrono::duration_cast(frame_duration); - if (now > next_frame_time) { - next_frame_time = now + std::chrono::duration_cast(frame_duration); - } } if (tick >= master_semaphore->CurrentTick()) { Flush();