Compare commits

..

9 Commits

Author SHA1 Message Date
Pengfei Zhu
4048b54ef7 yuzu: fix title bar display
Previously the version number got hidden after starting a game.
2018-09-08 19:10:50 +08:00
bunnei
9cd79c25ed Merge pull request #1246 from degasus/instanced_rendering
gl_rasterizer: Use baseInstance instead of moving the buffer points.
2018-09-08 04:49:24 -04:00
bunnei
2515d2433b Merge pull request #1259 from lioncash/relocate
yuzu: Move GameListWorker to its own source files
2018-09-08 04:10:11 -04:00
bunnei
8b08cb925b gl_rasterizer: Use baseInstance instead of moving the buffer points.
This hopefully helps our cache not to redundant upload the vertex buffer.

# Conflicts:
#	src/video_core/renderer_opengl/gl_rasterizer.cpp
2018-09-08 04:05:56 -04:00
Patrick Elsässer
a8974f0556 video_core: Arithmetic overflow warning fix for gl_rasterizer (#1262)
* video_core: Arithmetic overflow fix for gl_rasterizer

- Fixed warnings, which were indicating incorrect behavior from integral
promotion rules and types larger than those in which arithmetic is
typically performed.

- Added const for variables where possible and meaningful.

* Changed the casts from C to C++ style

Changed the C-style casts to C++ casts as proposed.
Took also care about signed / unsigned behaviour.
2018-09-08 02:59:59 -04:00
bunnei
460ebc8187 Merge pull request #1257 from lioncash/process
core: Migrate current_process pointer to the kernel
2018-09-07 22:34:05 -04:00
bunnei
6ac1bd9f5d Merge pull request #1260 from MerryMage/dynarmic
externals: Update dynarmic to 9594465
2018-09-07 22:33:38 -04:00
Lioncash
564b7fdc9c yuzu: Move GameListWorker to its own source files
This has gotten sufficiently large enough to warrant moving it to its
own source files. Especially given it dumps the file_sys headers around
code that doesn't use it for the most part.

This'll also make it easier to introduce a type alias for the
compatibility list, so a large unordered_map type declaration doesn't
need to be specified all the time (we don't want to propagate the
game_list_p.h include via the main game_list.h header).
2018-09-07 16:25:28 -04:00
Lioncash
3f17fe7133 core: Migrate current_process pointer to the kernel
Given we now have the kernel as a class, it doesn't make sense to keep
the current process pointer within the System class, as processes are
related to the kernel.

This also gets rid of a subtle case where memory wouldn't be freed on
core shutdown, as the current_process pointer would never be reset,
causing the pointed to contents to continue to live.
2018-09-06 20:52:58 -04:00
12 changed files with 401 additions and 324 deletions

View File

@@ -136,7 +136,7 @@ struct System::Impl {
if (virtual_filesystem == nullptr)
virtual_filesystem = std::make_shared<FileSys::RealVfsFilesystem>();
current_process = Kernel::Process::Create(kernel, "main");
kernel.MakeCurrentProcess(Kernel::Process::Create(kernel, "main"));
cpu_barrier = std::make_shared<CpuBarrier>();
cpu_exclusive_monitor = Cpu::MakeExclusiveMonitor(cpu_cores.size());
@@ -202,7 +202,7 @@ struct System::Impl {
return init_result;
}
const Loader::ResultStatus load_result{app_loader->Load(current_process)};
const Loader::ResultStatus load_result{app_loader->Load(kernel.CurrentProcess())};
if (load_result != Loader::ResultStatus::Success) {
LOG_CRITICAL(Core, "Failed to load ROM (Error {})!", static_cast<int>(load_result));
Shutdown();
@@ -281,7 +281,6 @@ struct System::Impl {
std::unique_ptr<VideoCore::RendererBase> renderer;
std::unique_ptr<Tegra::GPU> gpu_core;
std::shared_ptr<Tegra::DebugContext> debug_context;
Kernel::SharedPtr<Kernel::Process> current_process;
std::shared_ptr<ExclusiveMonitor> cpu_exclusive_monitor;
std::shared_ptr<CpuBarrier> cpu_barrier;
std::array<std::shared_ptr<Cpu>, NUM_CPU_CORES> cpu_cores;
@@ -363,7 +362,11 @@ const std::shared_ptr<Kernel::Scheduler>& System::Scheduler(size_t core_index) {
}
Kernel::SharedPtr<Kernel::Process>& System::CurrentProcess() {
return impl->current_process;
return impl->kernel.CurrentProcess();
}
const Kernel::SharedPtr<Kernel::Process>& System::CurrentProcess() const {
return impl->kernel.CurrentProcess();
}
ARM_Interface& System::ArmInterface(size_t core_index) {

View File

@@ -174,9 +174,12 @@ public:
/// Gets the scheduler for the CPU core with the specified index
const std::shared_ptr<Kernel::Scheduler>& Scheduler(size_t core_index);
/// Gets the current process
/// Provides a reference to the current process
Kernel::SharedPtr<Kernel::Process>& CurrentProcess();
/// Provides a constant reference to the current process.
const Kernel::SharedPtr<Kernel::Process>& CurrentProcess() const;
/// Provides a reference to the kernel instance.
Kernel::KernelCore& Kernel();

View File

@@ -116,6 +116,7 @@ struct KernelCore::Impl {
next_thread_id = 1;
process_list.clear();
current_process.reset();
handle_table.Clear();
resource_limits.fill(nullptr);
@@ -206,6 +207,7 @@ struct KernelCore::Impl {
// Lists all processes that exist in the current session.
std::vector<SharedPtr<Process>> process_list;
SharedPtr<Process> current_process;
Kernel::HandleTable handle_table;
std::array<SharedPtr<ResourceLimit>, 4> resource_limits;
@@ -264,6 +266,18 @@ void KernelCore::AppendNewProcess(SharedPtr<Process> process) {
impl->process_list.push_back(std::move(process));
}
void KernelCore::MakeCurrentProcess(SharedPtr<Process> process) {
impl->current_process = std::move(process);
}
SharedPtr<Process>& KernelCore::CurrentProcess() {
return impl->current_process;
}
const SharedPtr<Process>& KernelCore::CurrentProcess() const {
return impl->current_process;
}
void KernelCore::AddNamedPort(std::string name, SharedPtr<ClientPort> port) {
impl->named_ports.emplace(std::move(name), std::move(port));
}

View File

@@ -65,6 +65,15 @@ public:
/// Adds the given shared pointer to an internal list of active processes.
void AppendNewProcess(SharedPtr<Process> process);
/// Makes the given process the new current process.
void MakeCurrentProcess(SharedPtr<Process> process);
/// Retrieves a reference to the current process.
SharedPtr<Process>& CurrentProcess();
/// Retrieves a const reference to the current process.
const SharedPtr<Process>& CurrentProcess() const;
/// Adds a port to the named port table
void AddNamedPort(std::string name, SharedPtr<ClientPort> port);

View File

@@ -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();
index_buffer_offset += static_cast<GLintptr>(regs.index_array.first) *
static_cast<GLintptr>(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);
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;
@@ -682,7 +688,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.

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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;
});
}

View File

@@ -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;
}

View File

@@ -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;
};

View File

@@ -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();

View File

@@ -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)