summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/audio_core/audio_renderer.cpp12
-rw-r--r--src/audio_core/audio_renderer.h2
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.cpp70
-rw-r--r--src/yuzu/CMakeLists.txt2
-rw-r--r--src/yuzu/game_list.cpp221
-rw-r--r--src/yuzu/game_list_p.h76
-rw-r--r--src/yuzu/game_list_worker.cpp239
-rw-r--r--src/yuzu/game_list_worker.h72
-rw-r--r--src/yuzu/main.cpp6
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2.cpp2
10 files changed, 375 insertions, 327 deletions
diff --git a/src/audio_core/audio_renderer.cpp b/src/audio_core/audio_renderer.cpp
index 397b107f5..a75cd3be5 100644
--- a/src/audio_core/audio_renderer.cpp
+++ b/src/audio_core/audio_renderer.cpp
@@ -17,10 +17,10 @@ AudioRenderer::AudioRenderer(AudioRendererParameter params,
Kernel::SharedPtr<Kernel::Event> buffer_event)
: worker_params{params}, buffer_event{buffer_event}, voices(params.voice_count) {
- audio_core = std::make_unique<AudioCore::AudioOut>();
- stream = audio_core->OpenStream(STREAM_SAMPLE_RATE, STREAM_NUM_CHANNELS, "AudioRenderer",
- [=]() { buffer_event->Signal(); });
- audio_core->StartStream(stream);
+ audio_out = std::make_unique<AudioCore::AudioOut>();
+ stream = audio_out->OpenStream(STREAM_SAMPLE_RATE, STREAM_NUM_CHANNELS, "AudioRenderer",
+ [=]() { buffer_event->Signal(); });
+ audio_out->StartStream(stream);
QueueMixedBuffer(0);
QueueMixedBuffer(1);
@@ -236,11 +236,11 @@ void AudioRenderer::QueueMixedBuffer(Buffer::Tag tag) {
}
}
}
- audio_core->QueueBuffer(stream, tag, std::move(buffer));
+ audio_out->QueueBuffer(stream, tag, std::move(buffer));
}
void AudioRenderer::ReleaseAndQueueBuffers() {
- const auto released_buffers{audio_core->GetTagsAndReleaseBuffers(stream, 2)};
+ const auto released_buffers{audio_out->GetTagsAndReleaseBuffers(stream, 2)};
for (const auto& tag : released_buffers) {
QueueMixedBuffer(tag);
}
diff --git a/src/audio_core/audio_renderer.h b/src/audio_core/audio_renderer.h
index eba67f28e..6d069d693 100644
--- a/src/audio_core/audio_renderer.h
+++ b/src/audio_core/audio_renderer.h
@@ -204,7 +204,7 @@ private:
AudioRendererParameter worker_params;
Kernel::SharedPtr<Kernel::Event> buffer_event;
std::vector<VoiceState> voices;
- std::unique_ptr<AudioCore::AudioOut> audio_core;
+ std::unique_ptr<AudioCore::AudioOut> audio_out;
AudioCore::StreamPtr stream;
};
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index e6eb829eb..e6d6917fa 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -151,24 +151,17 @@ void RasterizerOpenGL::SetupVertexArrays() {
Tegra::GPUVAddr start = vertex_array.StartAddress();
const Tegra::GPUVAddr end = regs.vertex_array_limit[index].LimitAddress();
- if (regs.instanced_arrays.IsInstancingEnabled(index) && vertex_array.divisor != 0) {
- start += vertex_array.stride * (gpu.state.current_instance / vertex_array.divisor);
- }
-
ASSERT(end > start);
- u64 size = end - start + 1;
-
- GLintptr vertex_buffer_offset = buffer_cache.UploadMemory(start, size);
+ const u64 size = end - start + 1;
+ const GLintptr vertex_buffer_offset = buffer_cache.UploadMemory(start, size);
// Bind the vertex array to the buffer at the current offset.
glBindVertexBuffer(index, buffer_cache.GetHandle(), vertex_buffer_offset,
vertex_array.stride);
if (regs.instanced_arrays.IsInstancingEnabled(index) && vertex_array.divisor != 0) {
- // Tell OpenGL that this is an instanced vertex buffer to prevent accessing different
- // indexes on each vertex. We do the instance indexing manually by incrementing the
- // start address of the vertex buffer.
- glVertexBindingDivisor(index, 1);
+ // Enable vertex buffer instancing with the specified divisor.
+ glVertexBindingDivisor(index, vertex_array.divisor);
} else {
// Disable the vertex buffer instancing.
glVertexBindingDivisor(index, 0);
@@ -178,7 +171,7 @@ void RasterizerOpenGL::SetupVertexArrays() {
void RasterizerOpenGL::SetupShaders() {
MICROPROFILE_SCOPE(OpenGL_Shader);
- auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
+ const auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
// Next available bindpoints to use when uploading the const buffers and textures to the GLSL
// shaders. The constbuffer bindpoint starts after the shader stage configuration bind points.
@@ -186,7 +179,7 @@ void RasterizerOpenGL::SetupShaders() {
u32 current_texture_bindpoint = 0;
for (size_t index = 0; index < Maxwell::MaxShaderProgram; ++index) {
- auto& shader_config = gpu.regs.shader_config[index];
+ const auto& shader_config = gpu.regs.shader_config[index];
const Maxwell::ShaderProgram program{static_cast<Maxwell::ShaderProgram>(index)};
// Skip stages that are not enabled
@@ -198,7 +191,7 @@ void RasterizerOpenGL::SetupShaders() {
GLShader::MaxwellUniformData ubo{};
ubo.SetFromRegs(gpu.state.shader_stages[stage]);
- GLintptr offset = buffer_cache.UploadHostMemory(
+ const GLintptr offset = buffer_cache.UploadHostMemory(
&ubo, sizeof(ubo), static_cast<size_t>(uniform_buffer_alignment));
// Bind the buffer
@@ -432,11 +425,12 @@ void RasterizerOpenGL::DrawArrays() {
return;
MICROPROFILE_SCOPE(OpenGL_Drawing);
- const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
+ const auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
+ const auto& regs = gpu.regs;
ScopeAcquireGLContext acquire_context{emu_window};
- auto [dirty_color_surface, dirty_depth_surface] =
+ const auto [dirty_color_surface, dirty_depth_surface] =
ConfigureFramebuffers(true, regs.zeta.Address() != 0 && regs.zeta_enable != 0, true);
SyncDepthTestState();
@@ -450,7 +444,8 @@ void RasterizerOpenGL::DrawArrays() {
// Draw the vertex batch
const bool is_indexed = accelerate_draw == AccelDraw::Indexed;
- const u64 index_buffer_size{regs.index_array.count * regs.index_array.FormatSizeInBytes()};
+ const u64 index_buffer_size{static_cast<u64>(regs.index_array.count) *
+ static_cast<u64>(regs.index_array.FormatSizeInBytes())};
state.draw.vertex_buffer = buffer_cache.GetHandle();
state.Apply();
@@ -493,13 +488,29 @@ void RasterizerOpenGL::DrawArrays() {
const GLint base_vertex{static_cast<GLint>(regs.vb_element_base)};
// Adjust the index buffer offset so it points to the first desired index.
- index_buffer_offset += regs.index_array.first * regs.index_array.FormatSizeInBytes();
-
- glDrawElementsBaseVertex(primitive_mode, regs.index_array.count,
- MaxwellToGL::IndexFormat(regs.index_array.format),
- reinterpret_cast<const void*>(index_buffer_offset), base_vertex);
+ index_buffer_offset += static_cast<GLintptr>(regs.index_array.first) *
+ static_cast<GLintptr>(regs.index_array.FormatSizeInBytes());
+
+ if (gpu.state.current_instance > 0) {
+ glDrawElementsInstancedBaseVertexBaseInstance(
+ primitive_mode, regs.index_array.count,
+ MaxwellToGL::IndexFormat(regs.index_array.format),
+ reinterpret_cast<const void*>(index_buffer_offset), 1, base_vertex,
+ gpu.state.current_instance);
+ } else {
+ glDrawElementsBaseVertex(primitive_mode, regs.index_array.count,
+ MaxwellToGL::IndexFormat(regs.index_array.format),
+ reinterpret_cast<const void*>(index_buffer_offset),
+ base_vertex);
+ }
} else {
- glDrawArrays(primitive_mode, regs.vertex_buffer.first, regs.vertex_buffer.count);
+ if (gpu.state.current_instance > 0) {
+ glDrawArraysInstancedBaseInstance(primitive_mode, regs.vertex_buffer.first,
+ regs.vertex_buffer.count, 1,
+ gpu.state.current_instance);
+ } else {
+ glDrawArrays(primitive_mode, regs.vertex_buffer.first, regs.vertex_buffer.count);
+ }
}
// Disable scissor test
@@ -516,13 +527,9 @@ void RasterizerOpenGL::DrawArrays() {
void RasterizerOpenGL::NotifyMaxwellRegisterChanged(u32 method) {}
-void RasterizerOpenGL::FlushAll() {
- MICROPROFILE_SCOPE(OpenGL_CacheManagement);
-}
+void RasterizerOpenGL::FlushAll() {}
-void RasterizerOpenGL::FlushRegion(VAddr addr, u64 size) {
- MICROPROFILE_SCOPE(OpenGL_CacheManagement);
-}
+void RasterizerOpenGL::FlushRegion(VAddr addr, u64 size) {}
void RasterizerOpenGL::InvalidateRegion(VAddr addr, u64 size) {
MICROPROFILE_SCOPE(OpenGL_CacheManagement);
@@ -532,7 +539,6 @@ void RasterizerOpenGL::InvalidateRegion(VAddr addr, u64 size) {
}
void RasterizerOpenGL::FlushAndInvalidateRegion(VAddr addr, u64 size) {
- MICROPROFILE_SCOPE(OpenGL_CacheManagement);
InvalidateRegion(addr, size);
}
@@ -588,7 +594,7 @@ void RasterizerOpenGL::SamplerInfo::Create() {
}
void RasterizerOpenGL::SamplerInfo::SyncWithConfig(const Tegra::Texture::TSCEntry& config) {
- GLuint s = sampler.handle;
+ const GLuint s = sampler.handle;
if (mag_filter != config.mag_filter) {
mag_filter = config.mag_filter;
@@ -687,7 +693,7 @@ u32 RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, Shader& shader,
for (u32 bindpoint = 0; bindpoint < entries.size(); ++bindpoint) {
const auto& entry = entries[bindpoint];
- u32 current_bindpoint = current_unit + bindpoint;
+ const u32 current_bindpoint = current_unit + bindpoint;
// Bind the uniform to the sampler.
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index ea9ea69e4..a2b6e984e 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -43,6 +43,8 @@ add_executable(yuzu
game_list.cpp
game_list.h
game_list_p.h
+ game_list_worker.cpp
+ game_list_worker.h
hotkeys.cpp
hotkeys.h
main.cpp
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp
index a3b841684..86532e4a9 100644
--- a/src/yuzu/game_list.cpp
+++ b/src/yuzu/game_list.cpp
@@ -18,17 +18,10 @@
#include "common/common_types.h"
#include "common/file_util.h"
#include "common/logging/log.h"
-#include "core/file_sys/content_archive.h"
-#include "core/file_sys/control_metadata.h"
-#include "core/file_sys/nca_metadata.h"
#include "core/file_sys/patch_manager.h"
-#include "core/file_sys/registered_cache.h"
-#include "core/file_sys/romfs.h"
-#include "core/file_sys/vfs_real.h"
-#include "core/hle/service/filesystem/filesystem.h"
-#include "core/loader/loader.h"
#include "yuzu/game_list.h"
#include "yuzu/game_list_p.h"
+#include "yuzu/game_list_worker.h"
#include "yuzu/main.h"
#include "yuzu/ui_settings.h"
@@ -436,45 +429,6 @@ void GameList::LoadInterfaceLayout() {
const QStringList GameList::supported_file_extensions = {"nso", "nro", "nca", "xci", "nsp"};
-static bool HasSupportedFileExtension(const std::string& file_name) {
- const QFileInfo file = QFileInfo(QString::fromStdString(file_name));
- return GameList::supported_file_extensions.contains(file.suffix(), Qt::CaseInsensitive);
-}
-
-static bool IsExtractedNCAMain(const std::string& file_name) {
- return QFileInfo(QString::fromStdString(file_name)).fileName() == "main";
-}
-
-static QString FormatGameName(const std::string& physical_name) {
- const QString physical_name_as_qstring = QString::fromStdString(physical_name);
- const QFileInfo file_info(physical_name_as_qstring);
-
- if (IsExtractedNCAMain(physical_name)) {
- return file_info.dir().path();
- }
-
- return physical_name_as_qstring;
-}
-
-static QString FormatPatchNameVersions(const FileSys::PatchManager& patch_manager,
- bool updatable = true) {
- QString out;
- for (const auto& kv : patch_manager.GetPatchVersionNames()) {
- if (!updatable && kv.first == FileSys::PatchType::Update)
- continue;
-
- if (kv.second.empty()) {
- out.append(fmt::format("{}\n", FileSys::FormatPatchTypeName(kv.first)).c_str());
- } else {
- out.append(fmt::format("{} ({})\n", FileSys::FormatPatchTypeName(kv.first), kv.second)
- .c_str());
- }
- }
-
- out.chop(1);
- return out;
-}
-
void GameList::RefreshGameDirectory() {
if (!UISettings::values.gamedir.isEmpty() && current_worker != nullptr) {
LOG_INFO(Frontend, "Change detected in the games directory. Reloading game list.");
@@ -482,176 +436,3 @@ void GameList::RefreshGameDirectory() {
PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan);
}
}
-
-static void GetMetadataFromControlNCA(const FileSys::PatchManager& patch_manager,
- const std::shared_ptr<FileSys::NCA>& nca,
- std::vector<u8>& icon, std::string& name) {
- auto [nacp, icon_file] = patch_manager.ParseControlNCA(nca);
- if (icon_file != nullptr)
- icon = icon_file->ReadAllBytes();
- if (nacp != nullptr)
- name = nacp->GetApplicationName();
-}
-
-GameListWorker::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) {}
-
-GameListWorker::~GameListWorker() = default;
-
-void GameListWorker::AddInstalledTitlesToGameList() {
- const auto cache = Service::FileSystem::GetUnionContents();
- const auto installed_games = cache->ListEntriesFilter(FileSys::TitleType::Application,
- FileSys::ContentRecordType::Program);
-
- for (const auto& game : installed_games) {
- const auto& file = cache->GetEntryUnparsed(game);
- std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(file);
- if (!loader)
- continue;
-
- std::vector<u8> icon;
- std::string name;
- u64 program_id = 0;
- loader->ReadProgramId(program_id);
-
- const FileSys::PatchManager patch{program_id};
- const auto& control = cache->GetEntry(game.title_id, FileSys::ContentRecordType::Control);
- if (control != nullptr)
- GetMetadataFromControlNCA(patch, control, icon, name);
-
- 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(file->GetFullPath()), icon, QString::fromStdString(name),
- QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())),
- program_id),
- new GameListItemCompat(compatibility),
- new GameListItem(FormatPatchNameVersions(patch)),
- new GameListItem(
- QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))),
- new GameListItemSize(file->GetSize()),
- });
- }
-
- const auto control_data = cache->ListEntriesFilter(FileSys::TitleType::Application,
- FileSys::ContentRecordType::Control);
-
- for (const auto& entry : control_data) {
- const auto nca = cache->GetEntry(entry);
- if (nca != nullptr)
- nca_control_map.insert_or_assign(entry.title_id, nca);
- }
-}
-
-void GameListWorker::FillControlMap(const std::string& dir_path) {
- const auto nca_control_callback = [this](u64* num_entries_out, const std::string& directory,
- const std::string& virtual_name) -> bool {
- std::string physical_name = directory + DIR_SEP + virtual_name;
-
- if (stop_processing)
- return false; // Breaks the callback loop.
-
- bool is_dir = FileUtil::IsDirectory(physical_name);
- QFileInfo file_info(physical_name.c_str());
- if (!is_dir && file_info.suffix().toStdString() == "nca") {
- auto nca =
- std::make_shared<FileSys::NCA>(vfs->OpenFile(physical_name, FileSys::Mode::Read));
- if (nca->GetType() == FileSys::NCAContentType::Control)
- nca_control_map.insert_or_assign(nca->GetTitleId(), nca);
- }
- return true;
- };
-
- FileUtil::ForeachDirectoryEntry(nullptr, dir_path, nca_control_callback);
-}
-
-void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion) {
- const auto callback = [this, recursion](u64* num_entries_out, const std::string& directory,
- const std::string& virtual_name) -> bool {
- std::string physical_name = directory + DIR_SEP + virtual_name;
-
- if (stop_processing)
- return false; // Breaks the callback loop.
-
- bool is_dir = FileUtil::IsDirectory(physical_name);
- if (!is_dir &&
- (HasSupportedFileExtension(physical_name) || IsExtractedNCAMain(physical_name))) {
- std::unique_ptr<Loader::AppLoader> loader =
- Loader::GetLoader(vfs->OpenFile(physical_name, FileSys::Mode::Read));
- if (!loader || ((loader->GetFileType() == Loader::FileType::Unknown ||
- loader->GetFileType() == Loader::FileType::Error) &&
- !UISettings::values.show_unknown))
- return true;
-
- std::vector<u8> icon;
- const auto res1 = loader->ReadIcon(icon);
-
- u64 program_id = 0;
- const auto res2 = loader->ReadProgramId(program_id);
-
- std::string name = " ";
- const auto res3 = loader->ReadTitle(name);
-
- const FileSys::PatchManager patch{program_id};
-
- if (res1 != Loader::ResultStatus::Success && res3 != Loader::ResultStatus::Success &&
- res2 == Loader::ResultStatus::Success) {
- // Use from metadata pool.
- if (nca_control_map.find(program_id) != nca_control_map.end()) {
- const auto nca = nca_control_map[program_id];
- GetMetadataFromControlNCA(patch, nca, icon, name);
- }
- }
-
- 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(FormatPatchNameVersions(patch, loader->IsRomFSUpdatable())),
- new GameListItem(
- QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))),
- new GameListItemSize(FileUtil::GetSize(physical_name)),
- });
- } else if (is_dir && recursion > 0) {
- watch_list.append(QString::fromStdString(physical_name));
- AddFstEntriesToGameList(physical_name, recursion - 1);
- }
-
- return true;
- };
-
- FileUtil::ForeachDirectoryEntry(nullptr, dir_path, callback);
-}
-
-void GameListWorker::run() {
- stop_processing = false;
- watch_list.append(dir_path);
- FillControlMap(dir_path.toStdString());
- AddInstalledTitlesToGameList();
- AddFstEntriesToGameList(dir_path.toStdString(), deep_scan ? 256 : 0);
- nca_control_map.clear();
- emit Finished(watch_list);
-}
-
-void GameListWorker::Cancel() {
- this->disconnect();
- stop_processing = true;
-}
diff --git a/src/yuzu/game_list_p.h b/src/yuzu/game_list_p.h
index a70a151c5..2720bf143 100644
--- a/src/yuzu/game_list_p.h
+++ b/src/yuzu/game_list_p.h
@@ -6,9 +6,7 @@
#include <algorithm>
#include <array>
-#include <atomic>
#include <map>
-#include <memory>
#include <string>
#include <unordered_map>
#include <utility>
@@ -16,7 +14,6 @@
#include <QCoreApplication>
#include <QImage>
#include <QObject>
-#include <QRunnable>
#include <QStandardItem>
#include <QString>
@@ -26,12 +23,6 @@
#include "yuzu/ui_settings.h"
#include "yuzu/util/util.h"
-namespace FileSys {
-class NCA;
-class RegisteredCache;
-class VfsFilesystem;
-} // namespace FileSys
-
/**
* Gets the default icon (for games without valid SMDH)
* @param large If true, returns large icon (48x48), otherwise returns small icon (24x24)
@@ -43,17 +34,6 @@ 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:
@@ -197,49 +177,13 @@ public:
}
};
-/**
- * Asynchronous worker object for populating the game list.
- * Communicates with other threads through Qt's signal/slot system.
- */
-class GameListWorker : public QObject, public QRunnable {
- Q_OBJECT
-
-public:
- GameListWorker(
- std::shared_ptr<FileSys::VfsFilesystem> vfs, QString dir_path, bool deep_scan,
- const std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list);
- ~GameListWorker() override;
-
-public slots:
- /// Starts the processing of directory tree information.
- void run() override;
- /// Tells the worker that it should no longer continue processing. Thread-safe.
- void Cancel();
-
-signals:
- /**
- * The `EntryReady` signal is emitted once an entry has been prepared and is ready
- * to be added to the game list.
- * @param entry_items a list with `QStandardItem`s that make up the columns of the new entry.
- */
- void EntryReady(QList<QStandardItem*> entry_items);
-
- /**
- * After the worker has traversed the game directory looking for entries, this signal is emmited
- * with a list of folders that should be watched for changes as well.
- */
- void Finished(QStringList watch_list);
-
-private:
- std::shared_ptr<FileSys::VfsFilesystem> vfs;
- std::map<u64, std::shared_ptr<FileSys::NCA>> nca_control_map;
- 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();
- void FillControlMap(const std::string& dir_path);
- void AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion = 0);
-};
+inline 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;
+ });
+}
diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp
new file mode 100644
index 000000000..9f26935d6
--- /dev/null
+++ b/src/yuzu/game_list_worker.cpp
@@ -0,0 +1,239 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <QDir>
+#include <QFileInfo>
+
+#include "common/common_paths.h"
+#include "common/file_util.h"
+#include "core/file_sys/content_archive.h"
+#include "core/file_sys/control_metadata.h"
+#include "core/file_sys/mode.h"
+#include "core/file_sys/nca_metadata.h"
+#include "core/file_sys/patch_manager.h"
+#include "core/file_sys/registered_cache.h"
+#include "core/hle/service/filesystem/filesystem.h"
+#include "core/loader/loader.h"
+#include "yuzu/game_list.h"
+#include "yuzu/game_list_p.h"
+#include "yuzu/game_list_worker.h"
+#include "yuzu/ui_settings.h"
+
+namespace {
+void GetMetadataFromControlNCA(const FileSys::PatchManager& patch_manager,
+ const std::shared_ptr<FileSys::NCA>& nca, std::vector<u8>& icon,
+ std::string& name) {
+ auto [nacp, icon_file] = patch_manager.ParseControlNCA(nca);
+ if (icon_file != nullptr)
+ icon = icon_file->ReadAllBytes();
+ if (nacp != nullptr)
+ name = nacp->GetApplicationName();
+}
+
+bool HasSupportedFileExtension(const std::string& file_name) {
+ const QFileInfo file = QFileInfo(QString::fromStdString(file_name));
+ return GameList::supported_file_extensions.contains(file.suffix(), Qt::CaseInsensitive);
+}
+
+bool IsExtractedNCAMain(const std::string& file_name) {
+ return QFileInfo(QString::fromStdString(file_name)).fileName() == "main";
+}
+
+QString FormatGameName(const std::string& physical_name) {
+ const QString physical_name_as_qstring = QString::fromStdString(physical_name);
+ const QFileInfo file_info(physical_name_as_qstring);
+
+ if (IsExtractedNCAMain(physical_name)) {
+ return file_info.dir().path();
+ }
+
+ return physical_name_as_qstring;
+}
+
+QString FormatPatchNameVersions(const FileSys::PatchManager& patch_manager, bool updatable = true) {
+ QString out;
+ for (const auto& kv : patch_manager.GetPatchVersionNames()) {
+ if (!updatable && kv.first == FileSys::PatchType::Update)
+ continue;
+
+ if (kv.second.empty()) {
+ out.append(fmt::format("{}\n", FileSys::FormatPatchTypeName(kv.first)).c_str());
+ } else {
+ out.append(fmt::format("{} ({})\n", FileSys::FormatPatchTypeName(kv.first), kv.second)
+ .c_str());
+ }
+ }
+
+ out.chop(1);
+ return out;
+}
+} // Anonymous namespace
+
+GameListWorker::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) {}
+
+GameListWorker::~GameListWorker() = default;
+
+void GameListWorker::AddInstalledTitlesToGameList() {
+ const auto cache = Service::FileSystem::GetUnionContents();
+ const auto installed_games = cache->ListEntriesFilter(FileSys::TitleType::Application,
+ FileSys::ContentRecordType::Program);
+
+ for (const auto& game : installed_games) {
+ const auto& file = cache->GetEntryUnparsed(game);
+ std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(file);
+ if (!loader)
+ continue;
+
+ std::vector<u8> icon;
+ std::string name;
+ u64 program_id = 0;
+ loader->ReadProgramId(program_id);
+
+ const FileSys::PatchManager patch{program_id};
+ const auto& control = cache->GetEntry(game.title_id, FileSys::ContentRecordType::Control);
+ if (control != nullptr)
+ GetMetadataFromControlNCA(patch, control, icon, name);
+
+ 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(file->GetFullPath()), icon, QString::fromStdString(name),
+ QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())),
+ program_id),
+ new GameListItemCompat(compatibility),
+ new GameListItem(FormatPatchNameVersions(patch)),
+ new GameListItem(
+ QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))),
+ new GameListItemSize(file->GetSize()),
+ });
+ }
+
+ const auto control_data = cache->ListEntriesFilter(FileSys::TitleType::Application,
+ FileSys::ContentRecordType::Control);
+
+ for (const auto& entry : control_data) {
+ const auto nca = cache->GetEntry(entry);
+ if (nca != nullptr)
+ nca_control_map.insert_or_assign(entry.title_id, nca);
+ }
+}
+
+void GameListWorker::FillControlMap(const std::string& dir_path) {
+ const auto nca_control_callback = [this](u64* num_entries_out, const std::string& directory,
+ const std::string& virtual_name) -> bool {
+ std::string physical_name = directory + DIR_SEP + virtual_name;
+
+ if (stop_processing)
+ return false; // Breaks the callback loop.
+
+ bool is_dir = FileUtil::IsDirectory(physical_name);
+ QFileInfo file_info(physical_name.c_str());
+ if (!is_dir && file_info.suffix().toStdString() == "nca") {
+ auto nca =
+ std::make_shared<FileSys::NCA>(vfs->OpenFile(physical_name, FileSys::Mode::Read));
+ if (nca->GetType() == FileSys::NCAContentType::Control)
+ nca_control_map.insert_or_assign(nca->GetTitleId(), nca);
+ }
+ return true;
+ };
+
+ FileUtil::ForeachDirectoryEntry(nullptr, dir_path, nca_control_callback);
+}
+
+void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion) {
+ const auto callback = [this, recursion](u64* num_entries_out, const std::string& directory,
+ const std::string& virtual_name) -> bool {
+ std::string physical_name = directory + DIR_SEP + virtual_name;
+
+ if (stop_processing)
+ return false; // Breaks the callback loop.
+
+ bool is_dir = FileUtil::IsDirectory(physical_name);
+ if (!is_dir &&
+ (HasSupportedFileExtension(physical_name) || IsExtractedNCAMain(physical_name))) {
+ std::unique_ptr<Loader::AppLoader> loader =
+ Loader::GetLoader(vfs->OpenFile(physical_name, FileSys::Mode::Read));
+ if (!loader || ((loader->GetFileType() == Loader::FileType::Unknown ||
+ loader->GetFileType() == Loader::FileType::Error) &&
+ !UISettings::values.show_unknown))
+ return true;
+
+ std::vector<u8> icon;
+ const auto res1 = loader->ReadIcon(icon);
+
+ u64 program_id = 0;
+ const auto res2 = loader->ReadProgramId(program_id);
+
+ std::string name = " ";
+ const auto res3 = loader->ReadTitle(name);
+
+ const FileSys::PatchManager patch{program_id};
+
+ if (res1 != Loader::ResultStatus::Success && res3 != Loader::ResultStatus::Success &&
+ res2 == Loader::ResultStatus::Success) {
+ // Use from metadata pool.
+ if (nca_control_map.find(program_id) != nca_control_map.end()) {
+ const auto nca = nca_control_map[program_id];
+ GetMetadataFromControlNCA(patch, nca, icon, name);
+ }
+ }
+
+ 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(FormatPatchNameVersions(patch, loader->IsRomFSUpdatable())),
+ new GameListItem(
+ QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))),
+ new GameListItemSize(FileUtil::GetSize(physical_name)),
+ });
+ } else if (is_dir && recursion > 0) {
+ watch_list.append(QString::fromStdString(physical_name));
+ AddFstEntriesToGameList(physical_name, recursion - 1);
+ }
+
+ return true;
+ };
+
+ FileUtil::ForeachDirectoryEntry(nullptr, dir_path, callback);
+}
+
+void GameListWorker::run() {
+ stop_processing = false;
+ watch_list.append(dir_path);
+ FillControlMap(dir_path.toStdString());
+ AddInstalledTitlesToGameList();
+ AddFstEntriesToGameList(dir_path.toStdString(), deep_scan ? 256 : 0);
+ nca_control_map.clear();
+ emit Finished(watch_list);
+}
+
+void GameListWorker::Cancel() {
+ this->disconnect();
+ stop_processing = true;
+}
diff --git a/src/yuzu/game_list_worker.h b/src/yuzu/game_list_worker.h
new file mode 100644
index 000000000..42c93fc31
--- /dev/null
+++ b/src/yuzu/game_list_worker.h
@@ -0,0 +1,72 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <atomic>
+#include <map>
+#include <memory>
+#include <string>
+#include <unordered_map>
+
+#include <QList>
+#include <QObject>
+#include <QRunnable>
+#include <QString>
+
+#include "common/common_types.h"
+
+class QStandardItem;
+
+namespace FileSys {
+class NCA;
+class VfsFilesystem;
+} // namespace FileSys
+
+/**
+ * Asynchronous worker object for populating the game list.
+ * Communicates with other threads through Qt's signal/slot system.
+ */
+class GameListWorker : public QObject, public QRunnable {
+ Q_OBJECT
+
+public:
+ GameListWorker(
+ std::shared_ptr<FileSys::VfsFilesystem> vfs, QString dir_path, bool deep_scan,
+ const std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list);
+ ~GameListWorker() override;
+
+ /// Starts the processing of directory tree information.
+ void run() override;
+
+ /// Tells the worker that it should no longer continue processing. Thread-safe.
+ void Cancel();
+
+signals:
+ /**
+ * The `EntryReady` signal is emitted once an entry has been prepared and is ready
+ * to be added to the game list.
+ * @param entry_items a list with `QStandardItem`s that make up the columns of the new entry.
+ */
+ void EntryReady(QList<QStandardItem*> entry_items);
+
+ /**
+ * After the worker has traversed the game directory looking for entries, this signal is emitted
+ * with a list of folders that should be watched for changes as well.
+ */
+ void Finished(QStringList watch_list);
+
+private:
+ void AddInstalledTitlesToGameList();
+ void FillControlMap(const std::string& dir_path);
+ void AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion = 0);
+
+ std::shared_ptr<FileSys::VfsFilesystem> vfs;
+ std::map<u64, std::shared_ptr<FileSys::NCA>> nca_control_map;
+ 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;
+};
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index cb8135c42..811e7cd3f 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -444,6 +444,8 @@ QStringList GMainWindow::GetUnsupportedGLExtensions() {
unsupported_ext.append("ARB_vertex_type_10f_11f_11f_rev");
if (!GLAD_GL_ARB_texture_mirror_clamp_to_edge)
unsupported_ext.append("ARB_texture_mirror_clamp_to_edge");
+ if (!GLAD_GL_ARB_base_instance)
+ unsupported_ext.append("ARB_base_instance");
// Extensions required to support some texture formats.
if (!GLAD_GL_EXT_texture_compression_s3tc)
@@ -606,7 +608,7 @@ void GMainWindow::BootGame(const QString& filename) {
}
setWindowTitle(QString("yuzu %1| %4 | %2-%3")
- .arg(Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc,
+ .arg(Common::g_build_fullname, Common::g_scm_branch, Common::g_scm_desc,
QString::fromStdString(title_name)));
render_window->show();
@@ -641,7 +643,7 @@ void GMainWindow::ShutdownGame() {
game_list->show();
game_list->setFilterFocus();
setWindowTitle(QString("yuzu %1| %2-%3")
- .arg(Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc));
+ .arg(Common::g_build_fullname, Common::g_scm_branch, Common::g_scm_desc));
// Disable status bar updates
status_bar_update_timer.stop();
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
index c87e96b2d..2f7916256 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
@@ -91,6 +91,8 @@ bool EmuWindow_SDL2::SupportsRequiredGLExtensions() {
unsupported_ext.push_back("ARB_vertex_type_10f_11f_11f_rev");
if (!GLAD_GL_ARB_texture_mirror_clamp_to_edge)
unsupported_ext.push_back("ARB_texture_mirror_clamp_to_edge");
+ if (!GLAD_GL_ARB_base_instance)
+ unsupported_ext.push_back("ARB_base_instance");
// Extensions required to support some texture formats.
if (!GLAD_GL_EXT_texture_compression_s3tc)