diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/video_core/engines/shader_bytecode.h | 17 | ||||
-rw-r--r-- | src/video_core/renderer_opengl/gl_shader_cache.h | 2 | ||||
-rw-r--r-- | src/video_core/renderer_opengl/gl_shader_decompiler.cpp | 49 | ||||
-rw-r--r-- | src/yuzu/CMakeLists.txt | 4 | ||||
-rw-r--r-- | src/yuzu/game_list.cpp | 65 | ||||
-rw-r--r-- | src/yuzu/game_list.h | 6 | ||||
-rw-r--r-- | src/yuzu/game_list_p.h | 62 | ||||
-rw-r--r-- | src/yuzu/main.cpp | 19 | ||||
-rw-r--r-- | src/yuzu/main.h | 3 | ||||
-rw-r--r-- | src/yuzu/util/util.cpp | 11 | ||||
-rw-r--r-- | src/yuzu/util/util.h | 7 |
11 files changed, 237 insertions, 8 deletions
diff --git a/src/video_core/engines/shader_bytecode.h b/src/video_core/engines/shader_bytecode.h index 96b745db8..3e4efbe0c 100644 --- a/src/video_core/engines/shader_bytecode.h +++ b/src/video_core/engines/shader_bytecode.h @@ -147,6 +147,7 @@ enum class PredCondition : u64 { LessThanWithNan = 9, GreaterThanWithNan = 12, NotEqualWithNan = 13, + GreaterEqualWithNan = 14, // TODO(Subv): Other condition types }; @@ -242,6 +243,8 @@ enum class TextureType : u64 { TextureCube = 3, }; +enum class IpaMode : u64 { Pass = 0, None = 1, Constant = 2, Sc = 3 }; + union Instruction { Instruction& operator=(const Instruction& instr) { value = instr.value; @@ -325,6 +328,10 @@ union Instruction { } alu; union { + BitField<54, 3, IpaMode> mode; + } ipa; + + union { BitField<48, 1, u64> negate_b; } fmul; @@ -339,6 +346,10 @@ union Instruction { } alu_integer; union { + BitField<40, 1, u64> invert; + } popc; + + union { BitField<39, 3, u64> pred; BitField<42, 1, u64> neg_pred; } sel; @@ -665,6 +676,9 @@ public: ISCADD_C, // Scale and Add ISCADD_R, ISCADD_IMM, + POPC_C, + POPC_R, + POPC_IMM, SEL_C, SEL_R, SEL_IMM, @@ -886,6 +900,9 @@ private: INST("0100110000011---", Id::ISCADD_C, Type::ArithmeticInteger, "ISCADD_C"), INST("0101110000011---", Id::ISCADD_R, Type::ArithmeticInteger, "ISCADD_R"), INST("0011100-00011---", Id::ISCADD_IMM, Type::ArithmeticInteger, "ISCADD_IMM"), + INST("0100110000001---", Id::POPC_C, Type::ArithmeticInteger, "POPC_C"), + INST("0101110000001---", Id::POPC_R, Type::ArithmeticInteger, "POPC_R"), + INST("0011100-00001---", Id::POPC_IMM, Type::ArithmeticInteger, "POPC_IMM"), INST("0100110010100---", Id::SEL_C, Type::ArithmeticInteger, "SEL_C"), INST("0101110010100---", Id::SEL_R, Type::ArithmeticInteger, "SEL_R"), INST("0011100-10100---", Id::SEL_IMM, Type::ArithmeticInteger, "SEL_IMM"), diff --git a/src/video_core/renderer_opengl/gl_shader_cache.h b/src/video_core/renderer_opengl/gl_shader_cache.h index 44156dcab..658f9e994 100644 --- a/src/video_core/renderer_opengl/gl_shader_cache.h +++ b/src/video_core/renderer_opengl/gl_shader_cache.h @@ -30,7 +30,7 @@ public: /// Gets the size of the shader in guest memory, required for cache management size_t GetSizeInBytes() const { - return sizeof(GLShader::ProgramCode); + return GLShader::MAX_PROGRAM_CODE_LENGTH * sizeof(u64); } /// Gets the shader entries for the shader diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp index 7e5ebfe24..391c92d47 100644 --- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp +++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp @@ -729,8 +729,7 @@ private: {PredCondition::LessEqual, "<="}, {PredCondition::GreaterThan, ">"}, {PredCondition::NotEqual, "!="}, {PredCondition::GreaterEqual, ">="}, {PredCondition::LessThanWithNan, "<"}, {PredCondition::NotEqualWithNan, "!="}, - {PredCondition::GreaterThanWithNan, ">"}, - }; + {PredCondition::GreaterThanWithNan, ">"}, {PredCondition::GreaterEqualWithNan, ">="}}; const auto& comparison{PredicateComparisonStrings.find(condition)}; ASSERT_MSG(comparison != PredicateComparisonStrings.end(), @@ -739,7 +738,8 @@ private: std::string predicate{'(' + op_a + ") " + comparison->second + " (" + op_b + ')'}; if (condition == PredCondition::LessThanWithNan || condition == PredCondition::NotEqualWithNan || - condition == PredCondition::GreaterThanWithNan) { + condition == PredCondition::GreaterThanWithNan || + condition == PredCondition::GreaterEqualWithNan) { predicate += " || isnan(" + op_a + ") || isnan(" + op_b + ')'; } @@ -1363,6 +1363,15 @@ private: "((" + op_a + " << " + shift + ") + " + op_b + ')', 1, 1); break; } + case OpCode::Id::POPC_C: + case OpCode::Id::POPC_R: + case OpCode::Id::POPC_IMM: { + if (instr.popc.invert) { + op_b = "~(" + op_b + ')'; + } + regs.SetRegisterToInteger(instr.gpr0, true, 0, "bitCount(" + op_b + ')', 1, 1); + break; + } case OpCode::Id::SEL_C: case OpCode::Id::SEL_R: case OpCode::Id::SEL_IMM: { @@ -2100,7 +2109,39 @@ private: } case OpCode::Id::IPA: { const auto& attribute = instr.attribute.fmt28; - regs.SetRegisterToInputAttibute(instr.gpr0, attribute.element, attribute.index); + const auto& reg = instr.gpr0; + switch (instr.ipa.mode) { + case Tegra::Shader::IpaMode::Pass: + if (stage == Maxwell3D::Regs::ShaderStage::Fragment && + attribute.index == Attribute::Index::Position) { + switch (attribute.element) { + case 0: + shader.AddLine(regs.GetRegisterAsFloat(reg) + " = gl_FragCoord.x;"); + break; + case 1: + shader.AddLine(regs.GetRegisterAsFloat(reg) + " = gl_FragCoord.y;"); + break; + case 2: + shader.AddLine(regs.GetRegisterAsFloat(reg) + " = gl_FragCoord.z;"); + break; + case 3: + shader.AddLine(regs.GetRegisterAsFloat(reg) + " = 1.0;"); + break; + } + } else { + regs.SetRegisterToInputAttibute(reg, attribute.element, attribute.index); + } + break; + case Tegra::Shader::IpaMode::None: + regs.SetRegisterToInputAttibute(reg, attribute.element, attribute.index); + break; + default: + LOG_CRITICAL(HW_GPU, "Unhandled IPA mode: {}", + static_cast<u32>(instr.ipa.mode.Value())); + UNREACHABLE(); + regs.SetRegisterToInputAttibute(reg, attribute.element, attribute.index); + } + break; } case OpCode::Id::SSY: { diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index 46ed232d8..ea9ea69e4 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -70,6 +70,9 @@ set(UIS main.ui ) +file(GLOB COMPAT_LIST + ${CMAKE_BINARY_DIR}/dist/compatibility_list/compatibility_list.qrc + ${CMAKE_BINARY_DIR}/dist/compatibility_list/compatibility_list.json) file(GLOB_RECURSE ICONS ${CMAKE_SOURCE_DIR}/dist/icons/*) file(GLOB_RECURSE THEMES ${CMAKE_SOURCE_DIR}/dist/qt_themes/*) @@ -77,6 +80,7 @@ qt5_wrap_ui(UI_HDRS ${UIS}) target_sources(yuzu PRIVATE + ${COMPAT_LIST} ${ICONS} ${THEMES} ${UI_HDRS} diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index 867a3c6f1..27525938a 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp @@ -7,10 +7,14 @@ #include <QDir> #include <QFileInfo> #include <QHeaderView> +#include <QJsonArray> +#include <QJsonDocument> +#include <QJsonObject> #include <QKeyEvent> #include <QMenu> #include <QThreadPool> #include <boost/container/flat_map.hpp> +#include <fmt/format.h> #include "common/common_paths.h" #include "common/logging/log.h" #include "common/string_util.h" @@ -224,6 +228,7 @@ GameList::GameList(FileSys::VirtualFilesystem vfs, GMainWindow* parent) item_model->insertColumns(0, COLUMN_COUNT); item_model->setHeaderData(COLUMN_NAME, Qt::Horizontal, "Name"); + item_model->setHeaderData(COLUMN_COMPATIBILITY, Qt::Horizontal, "Compatibility"); item_model->setHeaderData(COLUMN_FILE_TYPE, Qt::Horizontal, "File type"); item_model->setHeaderData(COLUMN_SIZE, Qt::Horizontal, "Size"); @@ -325,12 +330,62 @@ void GameList::PopupContextMenu(const QPoint& menu_location) { QMenu context_menu; QAction* open_save_location = context_menu.addAction(tr("Open Save Data Location")); + QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry")); + open_save_location->setEnabled(program_id != 0); + auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); + navigate_to_gamedb_entry->setVisible(it != compatibility_list.end() && program_id != 0); + connect(open_save_location, &QAction::triggered, [&]() { emit OpenFolderRequested(program_id, GameListOpenTarget::SaveData); }); + connect(navigate_to_gamedb_entry, &QAction::triggered, + [&]() { emit NavigateToGamedbEntryRequested(program_id, compatibility_list); }); + context_menu.exec(tree_view->viewport()->mapToGlobal(menu_location)); } +void GameList::LoadCompatibilityList() { + QFile compat_list{":compatibility_list/compatibility_list.json"}; + + if (!compat_list.open(QFile::ReadOnly | QFile::Text)) { + LOG_ERROR(Frontend, "Unable to open game compatibility list"); + return; + } + + if (compat_list.size() == 0) { + LOG_WARNING(Frontend, "Game compatibility list is empty"); + return; + } + + const QByteArray content = compat_list.readAll(); + if (content.isEmpty()) { + LOG_ERROR(Frontend, "Unable to completely read game compatibility list"); + return; + } + + const QString string_content = content; + QJsonDocument json = QJsonDocument::fromJson(string_content.toUtf8()); + QJsonArray arr = json.array(); + + for (const QJsonValue& value : arr) { + QJsonObject game = value.toObject(); + + if (game.contains("compatibility") && game["compatibility"].isDouble()) { + int compatibility = game["compatibility"].toInt(); + QString directory = game["directory"].toString(); + QJsonArray ids = game["releases"].toArray(); + + for (const QJsonValue& value : ids) { + QJsonObject object = value.toObject(); + QString id = object["id"].toString(); + compatibility_list.emplace( + id.toUpper().toStdString(), + std::make_pair(QString::number(compatibility), directory)); + } + } + } +} + void GameList::PopulateAsync(const QString& dir_path, bool deep_scan) { if (!FileUtil::Exists(dir_path.toStdString()) || !FileUtil::IsDirectory(dir_path.toStdString())) { @@ -345,7 +400,7 @@ void GameList::PopulateAsync(const QString& dir_path, bool deep_scan) { emit ShouldCancelWorker(); - GameListWorker* worker = new GameListWorker(vfs, dir_path, deep_scan); + GameListWorker* worker = new GameListWorker(vfs, dir_path, deep_scan, compatibility_list); connect(worker, &GameListWorker::EntryReady, this, &GameList::AddEntry, Qt::QueuedConnection); connect(worker, &GameListWorker::Finished, this, &GameList::DonePopulating, @@ -523,11 +578,19 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign } } + auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); + + // The game list uses this as compatibility number for untested games + QString compatibility("99"); + if (it != compatibility_list.end()) + compatibility = it->second.first; + emit EntryReady({ new GameListItemPath( FormatGameName(physical_name), icon, QString::fromStdString(name), QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())), program_id), + new GameListItemCompat(compatibility), new GameListItem( QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))), new GameListItemSize(FileUtil::GetSize(physical_name)), diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h index 20252e778..c01351dc9 100644 --- a/src/yuzu/game_list.h +++ b/src/yuzu/game_list.h @@ -29,6 +29,7 @@ class GameList : public QWidget { public: enum { COLUMN_NAME, + COLUMN_COMPATIBILITY, COLUMN_FILE_TYPE, COLUMN_SIZE, COLUMN_COUNT, // Number of columns @@ -68,6 +69,7 @@ public: void setFilterFocus(); void setFilterVisible(bool visibility); + void LoadCompatibilityList(); void PopulateAsync(const QString& dir_path, bool deep_scan); void SaveInterfaceLayout(); @@ -79,6 +81,9 @@ signals: void GameChosen(QString game_path); void ShouldCancelWorker(); void OpenFolderRequested(u64 program_id, GameListOpenTarget target); + void NavigateToGamedbEntryRequested( + u64 program_id, + std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list); private slots: void onTextChanged(const QString& newText); @@ -100,6 +105,7 @@ private: QStandardItemModel* item_model = nullptr; GameListWorker* current_worker = nullptr; QFileSystemWatcher* watcher = nullptr; + std::unordered_map<std::string, std::pair<QString, QString>> compatibility_list; }; Q_DECLARE_METATYPE(GameListOpenTarget); diff --git a/src/yuzu/game_list_p.h b/src/yuzu/game_list_p.h index 1d6c85400..b9676d069 100644 --- a/src/yuzu/game_list_p.h +++ b/src/yuzu/game_list_p.h @@ -8,11 +8,15 @@ #include <atomic> #include <map> #include <memory> +#include <unordered_map> #include <utility> +#include <QCoreApplication> #include <QImage> +#include <QObject> #include <QRunnable> #include <QStandardItem> #include <QString> +#include "common/logging/log.h" #include "common/string_util.h" #include "core/file_sys/content_archive.h" #include "ui_settings.h" @@ -29,6 +33,17 @@ static QPixmap GetDefaultIcon(u32 size) { return icon; } +static auto FindMatchingCompatibilityEntry( + const std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list, + u64 program_id) { + return std::find_if( + compatibility_list.begin(), compatibility_list.end(), + [program_id](const std::pair<std::string, std::pair<QString, QString>>& element) { + std::string pid = fmt::format("{:016X}", program_id); + return element.first == pid; + }); +} + class GameListItem : public QStandardItem { public: @@ -96,6 +111,45 @@ public: } }; +class GameListItemCompat : public GameListItem { + Q_DECLARE_TR_FUNCTIONS(GameListItemCompat) +public: + static const int CompatNumberRole = Qt::UserRole + 1; + GameListItemCompat() = default; + explicit GameListItemCompat(const QString& compatiblity) { + struct CompatStatus { + QString color; + const char* text; + const char* tooltip; + }; + // clang-format off + static const std::map<QString, CompatStatus> status_data = { + {"0", {"#5c93ed", QT_TR_NOOP("Perfect"), QT_TR_NOOP("Game functions flawless with no audio or graphical glitches, all tested functionality works as intended without\nany workarounds needed.")}}, + {"1", {"#47d35c", QT_TR_NOOP("Great"), QT_TR_NOOP("Game functions with minor graphical or audio glitches and is playable from start to finish. May require some\nworkarounds.")}}, + {"2", {"#94b242", QT_TR_NOOP("Okay"), QT_TR_NOOP("Game functions with major graphical or audio glitches, but game is playable from start to finish with\nworkarounds.")}}, + {"3", {"#f2d624", QT_TR_NOOP("Bad"), QT_TR_NOOP("Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches\neven with workarounds.")}}, + {"4", {"#FF0000", QT_TR_NOOP("Intro/Menu"), QT_TR_NOOP("Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start\nScreen.")}}, + {"5", {"#828282", QT_TR_NOOP("Won't Boot"), QT_TR_NOOP("The game crashes when attempting to startup.")}}, + {"99", {"#000000", QT_TR_NOOP("Not Tested"), QT_TR_NOOP("The game has not yet been tested.")}}}; + // clang-format on + + auto iterator = status_data.find(compatiblity); + if (iterator == status_data.end()) { + LOG_WARNING(Frontend, "Invalid compatibility number {}", compatiblity.toStdString()); + return; + } + CompatStatus status = iterator->second; + setData(compatiblity, CompatNumberRole); + setText(QObject::tr(status.text)); + setToolTip(QObject::tr(status.tooltip)); + setData(CreateCirclePixmapFromColor(status.color), Qt::DecorationRole); + } + + bool operator<(const QStandardItem& other) const override { + return data(CompatNumberRole) < other.data(CompatNumberRole); + } +}; + /** * A specialization of GameListItem for size values. * This class ensures that for every numerical size value it holds (in bytes), a correct @@ -141,8 +195,11 @@ class GameListWorker : public QObject, public QRunnable { Q_OBJECT public: - GameListWorker(FileSys::VirtualFilesystem vfs, QString dir_path, bool deep_scan) - : vfs(std::move(vfs)), dir_path(std::move(dir_path)), deep_scan(deep_scan) {} + GameListWorker( + FileSys::VirtualFilesystem vfs, QString dir_path, bool deep_scan, + const std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list) + : vfs(std::move(vfs)), dir_path(std::move(dir_path)), deep_scan(deep_scan), + compatibility_list(compatibility_list) {} public slots: /// Starts the processing of directory tree information. @@ -170,6 +227,7 @@ private: QStringList watch_list; QString dir_path; bool deep_scan; + const std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list; std::atomic_bool stop_processing; void AddInstalledTitlesToGameList(std::shared_ptr<FileSys::RegisteredCache> cache); diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index ffa9f72aa..1501aedc4 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -16,6 +16,7 @@ #include <QMessageBox> #include <QtGui> #include <QtWidgets> +#include <fmt/format.h> #include "common/common_paths.h" #include "common/logging/backend.h" #include "common/logging/filter.h" @@ -35,6 +36,7 @@ #include "core/gdbstub/gdbstub.h" #include "core/loader/loader.h" #include "core/settings.h" +#include "game_list_p.h" #include "video_core/debug_utils/debug_utils.h" #include "yuzu/about_dialog.h" #include "yuzu/bootmanager.h" @@ -134,6 +136,7 @@ GMainWindow::GMainWindow() // Necessary to load titles from nand in gamelist. Service::FileSystem::CreateFactories(vfs); + game_list->LoadCompatibilityList(); game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan); // Show one-time "callout" messages to the user @@ -349,6 +352,8 @@ void GMainWindow::RestoreUIState() { void GMainWindow::ConnectWidgetEvents() { connect(game_list, &GameList::GameChosen, this, &GMainWindow::OnGameListLoadFile); connect(game_list, &GameList::OpenFolderRequested, this, &GMainWindow::OnGameListOpenFolder); + connect(game_list, &GameList::NavigateToGamedbEntryRequested, this, + &GMainWindow::OnGameListNavigateToGamedbEntry); connect(this, &GMainWindow::EmulationStarting, render_window, &GRenderWindow::OnEmulationStarting); @@ -678,6 +683,20 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target QDesktopServices::openUrl(QUrl::fromLocalFile(qpath)); } +void GMainWindow::OnGameListNavigateToGamedbEntry( + u64 program_id, + std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list) { + + auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); + + QString directory; + + if (it != compatibility_list.end()) + directory = it->second.second; + + QDesktopServices::openUrl(QUrl("https://yuzu-emu.org/game/" + directory)); +} + void GMainWindow::OnMenuLoadFile() { QString extensions; for (const auto& piece : game_list->supported_file_extensions) diff --git a/src/yuzu/main.h b/src/yuzu/main.h index d1d34552b..fd2436f4d 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -124,6 +124,9 @@ private slots: /// Called whenever a user selects a game in the game list widget. void OnGameListLoadFile(QString game_path); void OnGameListOpenFolder(u64 program_id, GameListOpenTarget target); + void OnGameListNavigateToGamedbEntry( + u64 program_id, + std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list); void OnMenuLoadFile(); void OnMenuLoadFolder(); void OnMenuInstallToNAND(); diff --git a/src/yuzu/util/util.cpp b/src/yuzu/util/util.cpp index 91d3f7def..e99042a23 100644 --- a/src/yuzu/util/util.cpp +++ b/src/yuzu/util/util.cpp @@ -4,6 +4,7 @@ #include <array> #include <cmath> +#include <QPainter> #include "yuzu/util/util.h" QFont GetMonospaceFont() { @@ -24,3 +25,13 @@ QString ReadableByteSize(qulonglong size) { .arg(size / std::pow(1024, digit_groups), 0, 'f', 1) .arg(units[digit_groups]); } + +QPixmap CreateCirclePixmapFromColor(const QColor& color) { + QPixmap circle_pixmap(16, 16); + circle_pixmap.fill(Qt::transparent); + QPainter painter(&circle_pixmap); + painter.setPen(color); + painter.setBrush(color); + painter.drawEllipse(0, 0, 15, 15); + return circle_pixmap; +} diff --git a/src/yuzu/util/util.h b/src/yuzu/util/util.h index ab443ef9b..e6790f260 100644 --- a/src/yuzu/util/util.h +++ b/src/yuzu/util/util.h @@ -12,3 +12,10 @@ QFont GetMonospaceFont(); /// Convert a size in bytes into a readable format (KiB, MiB, etc.) QString ReadableByteSize(qulonglong size); + +/** + * Creates a circle pixmap from a specified color + * @param color The color the pixmap shall have + * @return QPixmap circle pixmap + */ +QPixmap CreateCirclePixmapFromColor(const QColor& color); |