diff options
-rw-r--r-- | src/core/core.cpp | 25 | ||||
-rw-r--r-- | src/core/file_sys/submission_package.cpp | 26 | ||||
-rw-r--r-- | src/core/loader/nsp.cpp | 27 | ||||
-rw-r--r-- | src/core/memory.cpp | 9 | ||||
-rw-r--r-- | src/core/perf_stats.cpp | 47 | ||||
-rw-r--r-- | src/core/perf_stats.h | 21 | ||||
-rw-r--r-- | src/core/settings.h | 1 | ||||
-rw-r--r-- | src/video_core/renderer_opengl/maxwell_to_gl.h | 4 | ||||
-rw-r--r-- | src/yuzu/configuration/config.cpp | 5 | ||||
-rw-r--r-- | src/yuzu/configuration/configure_input.cpp | 2 | ||||
-rw-r--r-- | src/yuzu_cmd/config.cpp | 2 | ||||
-rw-r--r-- | src/yuzu_cmd/default_ini.h | 2 |
12 files changed, 143 insertions, 28 deletions
diff --git a/src/core/core.cpp b/src/core/core.cpp index 3d0978cbf..9ab174de2 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -160,10 +160,6 @@ struct System::Impl { LOG_DEBUG(Core, "Initialized OK"); - // Reset counters and set time origin to current frame - GetAndResetPerfStats(); - perf_stats.BeginSystemFrame(); - return ResultStatus::Success; } @@ -206,6 +202,16 @@ struct System::Impl { main_process->Run(load_parameters->main_thread_priority, load_parameters->main_thread_stack_size); + u64 title_id{0}; + if (app_loader->ReadProgramId(title_id) != Loader::ResultStatus::Success) { + LOG_ERROR(Core, "Failed to find title id for ROM (Error {})", + static_cast<u32>(load_result)); + } + perf_stats = std::make_unique<PerfStats>(title_id); + // Reset counters and set time origin to current frame + GetAndResetPerfStats(); + perf_stats->BeginSystemFrame(); + status = ResultStatus::Success; return status; } @@ -219,6 +225,8 @@ struct System::Impl { perf_results.game_fps); telemetry_session->AddField(Telemetry::FieldType::Performance, "Shutdown_Frametime", perf_results.frametime * 1000.0); + telemetry_session->AddField(Telemetry::FieldType::Performance, "Mean_Frametime_MS", + perf_stats->GetMeanFrametime()); is_powered_on = false; @@ -229,6 +237,7 @@ struct System::Impl { service_manager.reset(); cheat_engine.reset(); telemetry_session.reset(); + perf_stats.reset(); gpu_core.reset(); // Close all CPU/threading state @@ -286,7 +295,7 @@ struct System::Impl { } PerfStatsResults GetAndResetPerfStats() { - return perf_stats.GetAndResetStats(core_timing.GetGlobalTimeUs()); + return perf_stats->GetAndResetStats(core_timing.GetGlobalTimeUs()); } Timing::CoreTiming core_timing; @@ -327,7 +336,7 @@ struct System::Impl { ResultStatus status = ResultStatus::Success; std::string status_details = ""; - Core::PerfStats perf_stats; + std::unique_ptr<Core::PerfStats> perf_stats; Core::FrameLimiter frame_limiter; }; @@ -480,11 +489,11 @@ const Timing::CoreTiming& System::CoreTiming() const { } Core::PerfStats& System::GetPerfStats() { - return impl->perf_stats; + return *impl->perf_stats; } const Core::PerfStats& System::GetPerfStats() const { - return impl->perf_stats; + return *impl->perf_stats; } Core::FrameLimiter& System::FrameLimiter() { diff --git a/src/core/file_sys/submission_package.cpp b/src/core/file_sys/submission_package.cpp index 8b3b14e25..730221fd6 100644 --- a/src/core/file_sys/submission_package.cpp +++ b/src/core/file_sys/submission_package.cpp @@ -14,6 +14,7 @@ #include "core/file_sys/content_archive.h" #include "core/file_sys/nca_metadata.h" #include "core/file_sys/partition_filesystem.h" +#include "core/file_sys/program_metadata.h" #include "core/file_sys/submission_package.h" #include "core/loader/loader.h" @@ -78,6 +79,10 @@ Loader::ResultStatus NSP::GetStatus() const { } Loader::ResultStatus NSP::GetProgramStatus(u64 title_id) const { + if (IsExtractedType() && GetExeFS() != nullptr && FileSys::IsDirectoryExeFS(GetExeFS())) { + return Loader::ResultStatus::Success; + } + const auto iter = program_status.find(title_id); if (iter == program_status.end()) return Loader::ResultStatus::ErrorNSPMissingProgramNCA; @@ -85,12 +90,29 @@ Loader::ResultStatus NSP::GetProgramStatus(u64 title_id) const { } u64 NSP::GetFirstTitleID() const { + if (IsExtractedType()) { + return GetProgramTitleID(); + } + if (program_status.empty()) return 0; return program_status.begin()->first; } u64 NSP::GetProgramTitleID() const { + if (IsExtractedType()) { + if (GetExeFS() == nullptr || !IsDirectoryExeFS(GetExeFS())) { + return 0; + } + + ProgramMetadata meta; + if (meta.Load(GetExeFS()->GetFile("main.npdm")) == Loader::ResultStatus::Success) { + return meta.GetTitleID(); + } else { + return 0; + } + } + const auto out = GetFirstTitleID(); if ((out & 0x800) == 0) return out; @@ -102,6 +124,10 @@ u64 NSP::GetProgramTitleID() const { } std::vector<u64> NSP::GetTitleIDs() const { + if (IsExtractedType()) { + return {GetProgramTitleID()}; + } + std::vector<u64> out; out.reserve(ncas.size()); for (const auto& kv : ncas) diff --git a/src/core/loader/nsp.cpp b/src/core/loader/nsp.cpp index b1171ce65..35c82c99d 100644 --- a/src/core/loader/nsp.cpp +++ b/src/core/loader/nsp.cpp @@ -26,20 +26,18 @@ AppLoader_NSP::AppLoader_NSP(FileSys::VirtualFile file) if (nsp->GetStatus() != ResultStatus::Success) return; - if (nsp->IsExtractedType()) - return; - - const auto control_nca = - nsp->GetNCA(nsp->GetProgramTitleID(), FileSys::ContentRecordType::Control); - if (control_nca == nullptr || control_nca->GetStatus() != ResultStatus::Success) - return; - - std::tie(nacp_file, icon_file) = - FileSys::PatchManager(nsp->GetProgramTitleID()).ParseControlNCA(*control_nca); if (nsp->IsExtractedType()) { secondary_loader = std::make_unique<AppLoader_DeconstructedRomDirectory>(nsp->GetExeFS()); } else { + const auto control_nca = + nsp->GetNCA(nsp->GetProgramTitleID(), FileSys::ContentRecordType::Control); + if (control_nca == nullptr || control_nca->GetStatus() != ResultStatus::Success) + return; + + std::tie(nacp_file, icon_file) = + FileSys::PatchManager(nsp->GetProgramTitleID()).ParseControlNCA(*control_nca); + if (title_id == 0) return; @@ -56,11 +54,11 @@ FileType AppLoader_NSP::IdentifyType(const FileSys::VirtualFile& file) { if (nsp.GetStatus() == ResultStatus::Success) { // Extracted Type case if (nsp.IsExtractedType() && nsp.GetExeFS() != nullptr && - FileSys::IsDirectoryExeFS(nsp.GetExeFS()) && nsp.GetRomFS() != nullptr) { + FileSys::IsDirectoryExeFS(nsp.GetExeFS())) { return FileType::NSP; } - // Non-Ectracted Type case + // Non-Extracted Type case if (!nsp.IsExtractedType() && nsp.GetNCA(nsp.GetFirstTitleID(), FileSys::ContentRecordType::Program) != nullptr && AppLoader_NCA::IdentifyType(nsp.GetNCAFile( @@ -77,7 +75,7 @@ AppLoader_NSP::LoadResult AppLoader_NSP::Load(Kernel::Process& process) { return {ResultStatus::ErrorAlreadyLoaded, {}}; } - if (title_id == 0) { + if (!nsp->IsExtractedType() && title_id == 0) { return {ResultStatus::ErrorNSPMissingProgramNCA, {}}; } @@ -91,7 +89,8 @@ AppLoader_NSP::LoadResult AppLoader_NSP::Load(Kernel::Process& process) { return {nsp_program_status, {}}; } - if (nsp->GetNCA(title_id, FileSys::ContentRecordType::Program) == nullptr) { + if (!nsp->IsExtractedType() && + nsp->GetNCA(title_id, FileSys::ContentRecordType::Program) == nullptr) { if (!Core::Crypto::KeyManager::KeyFileExists(false)) { return {ResultStatus::ErrorMissingProductionKeyFile, {}}; } diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 8555691c0..9e030789d 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -43,8 +43,13 @@ static void MapPages(Common::PageTable& page_table, VAddr base, u64 size, u8* me // During boot, current_page_table might not be set yet, in which case we need not flush if (Core::System::GetInstance().IsPoweredOn()) { - Core::System::GetInstance().GPU().FlushAndInvalidateRegion(base << PAGE_BITS, - size * PAGE_SIZE); + auto& gpu = Core::System::GetInstance().GPU(); + for (u64 i = 0; i < size; i++) { + const auto page = base + i; + if (page_table.attributes[page] == Common::PageType::RasterizerCachedMemory) { + gpu.FlushAndInvalidateRegion(page << PAGE_BITS, PAGE_SIZE); + } + } } VAddr end = base + size; diff --git a/src/core/perf_stats.cpp b/src/core/perf_stats.cpp index 4afd6c8a3..d2c69d1a0 100644 --- a/src/core/perf_stats.cpp +++ b/src/core/perf_stats.cpp @@ -4,8 +4,14 @@ #include <algorithm> #include <chrono> +#include <iterator> #include <mutex> +#include <numeric> +#include <sstream> #include <thread> +#include <fmt/chrono.h> +#include <fmt/format.h> +#include "common/file_util.h" #include "common/math_util.h" #include "core/perf_stats.h" #include "core/settings.h" @@ -15,8 +21,31 @@ using DoubleSecs = std::chrono::duration<double, std::chrono::seconds::period>; using std::chrono::duration_cast; using std::chrono::microseconds; +// Purposefully ignore the first five frames, as there's a significant amount of overhead in +// booting that we shouldn't account for +constexpr std::size_t IgnoreFrames = 5; + namespace Core { +PerfStats::PerfStats(u64 title_id) : title_id(title_id) {} + +PerfStats::~PerfStats() { + if (!Settings::values.record_frame_times || title_id == 0) { + return; + } + + const std::time_t t = std::time(nullptr); + std::ostringstream stream; + std::copy(perf_history.begin() + IgnoreFrames, perf_history.begin() + current_index, + std::ostream_iterator<double>(stream, "\n")); + const std::string& path = FileUtil::GetUserPath(FileUtil::UserPath::LogDir); + // %F Date format expanded is "%Y-%m-%d" + const std::string filename = + fmt::format("{}/{:%F-%H-%M}_{:016X}.csv", path, *std::localtime(&t), title_id); + FileUtil::IOFile file(filename, "w"); + file.WriteString(stream.str()); +} + void PerfStats::BeginSystemFrame() { std::lock_guard lock{object_mutex}; @@ -27,7 +56,12 @@ void PerfStats::EndSystemFrame() { std::lock_guard lock{object_mutex}; auto frame_end = Clock::now(); - accumulated_frametime += frame_end - frame_begin; + const auto frame_time = frame_end - frame_begin; + if (current_index < perf_history.size()) { + perf_history[current_index++] = + std::chrono::duration<double, std::milli>(frame_time).count(); + } + accumulated_frametime += frame_time; system_frames += 1; previous_frame_length = frame_end - previous_frame_end; @@ -40,6 +74,17 @@ void PerfStats::EndGameFrame() { game_frames += 1; } +double PerfStats::GetMeanFrametime() { + std::lock_guard lock{object_mutex}; + + if (current_index <= IgnoreFrames) { + return 0; + } + const double sum = std::accumulate(perf_history.begin() + IgnoreFrames, + perf_history.begin() + current_index, 0); + return sum / (current_index - IgnoreFrames); +} + PerfStatsResults PerfStats::GetAndResetStats(microseconds current_system_time_us) { std::lock_guard lock{object_mutex}; diff --git a/src/core/perf_stats.h b/src/core/perf_stats.h index 222ac1a63..d9a64f072 100644 --- a/src/core/perf_stats.h +++ b/src/core/perf_stats.h @@ -4,7 +4,9 @@ #pragma once +#include <array> #include <chrono> +#include <cstddef> #include <mutex> #include "common/common_types.h" @@ -27,6 +29,10 @@ struct PerfStatsResults { */ class PerfStats { public: + explicit PerfStats(u64 title_id); + + ~PerfStats(); + using Clock = std::chrono::high_resolution_clock; void BeginSystemFrame(); @@ -36,13 +42,26 @@ public: PerfStatsResults GetAndResetStats(std::chrono::microseconds current_system_time_us); /** + * Returns the Arthimetic Mean of all frametime values stored in the performance history. + */ + double GetMeanFrametime(); + + /** * Gets the ratio between walltime and the emulated time of the previous system frame. This is * useful for scaling inputs or outputs moving between the two time domains. */ double GetLastFrameTimeScale(); private: - std::mutex object_mutex; + std::mutex object_mutex{}; + + /// Title ID for the game that is running. 0 if there is no game running yet + u64 title_id{0}; + /// Current index for writing to the perf_history array + std::size_t current_index{0}; + /// Stores an hour of historical frametime data useful for processing and tracking performance + /// regressions with code changes. + std::array<double, 216000> perf_history = {}; /// Point when the cumulative counters were reset Clock::time_point reset_point = Clock::now(); diff --git a/src/core/settings.h b/src/core/settings.h index 6638ce8f9..d4b70ec4c 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -409,6 +409,7 @@ struct Values { float volume; // Debugging + bool record_frame_times; bool use_gdbstub; u16 gdbstub_port; std::string program_args; diff --git a/src/video_core/renderer_opengl/maxwell_to_gl.h b/src/video_core/renderer_opengl/maxwell_to_gl.h index ea77dd211..9ed738171 100644 --- a/src/video_core/renderer_opengl/maxwell_to_gl.h +++ b/src/video_core/renderer_opengl/maxwell_to_gl.h @@ -145,7 +145,7 @@ inline GLenum TextureFilterMode(Tegra::Texture::TextureFilter filter_mode, case Tegra::Texture::TextureMipmapFilter::None: return GL_LINEAR; case Tegra::Texture::TextureMipmapFilter::Nearest: - return GL_NEAREST_MIPMAP_LINEAR; + return GL_LINEAR_MIPMAP_NEAREST; case Tegra::Texture::TextureMipmapFilter::Linear: return GL_LINEAR_MIPMAP_LINEAR; } @@ -157,7 +157,7 @@ inline GLenum TextureFilterMode(Tegra::Texture::TextureFilter filter_mode, case Tegra::Texture::TextureMipmapFilter::Nearest: return GL_NEAREST_MIPMAP_NEAREST; case Tegra::Texture::TextureMipmapFilter::Linear: - return GL_LINEAR_MIPMAP_NEAREST; + return GL_NEAREST_MIPMAP_LINEAR; } } } diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index f594106bf..3f54f54fb 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -466,6 +466,9 @@ void Config::ReadDataStorageValues() { void Config::ReadDebuggingValues() { qt_config->beginGroup(QStringLiteral("Debugging")); + // Intentionally not using the QT default setting as this is intended to be changed in the ini + Settings::values.record_frame_times = + qt_config->value(QStringLiteral("record_frame_times"), false).toBool(); Settings::values.use_gdbstub = ReadSetting(QStringLiteral("use_gdbstub"), false).toBool(); Settings::values.gdbstub_port = ReadSetting(QStringLiteral("gdbstub_port"), 24689).toInt(); Settings::values.program_args = @@ -879,6 +882,8 @@ void Config::SaveDataStorageValues() { void Config::SaveDebuggingValues() { qt_config->beginGroup(QStringLiteral("Debugging")); + // Intentionally not using the QT default setting as this is intended to be changed in the ini + qt_config->setValue(QStringLiteral("record_frame_times"), Settings::values.record_frame_times); WriteSetting(QStringLiteral("use_gdbstub"), Settings::values.use_gdbstub, false); WriteSetting(QStringLiteral("gdbstub_port"), Settings::values.gdbstub_port, 24689); WriteSetting(QStringLiteral("program_args"), diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp index 7613197f2..f2977719c 100644 --- a/src/yuzu/configuration/configure_input.cpp +++ b/src/yuzu/configuration/configure_input.cpp @@ -182,6 +182,8 @@ void ConfigureInput::UpdateUIEnabled() { players_configure[i]->setEnabled(players_controller[i]->currentIndex() != 0); } + ui->handheld_connected->setChecked(ui->handheld_connected->isChecked() && + !ui->use_docked_mode->isChecked()); ui->handheld_connected->setEnabled(!ui->use_docked_mode->isChecked()); ui->handheld_configure->setEnabled(ui->handheld_connected->isChecked() && !ui->use_docked_mode->isChecked()); diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp index 067d58d80..5cadfd191 100644 --- a/src/yuzu_cmd/config.cpp +++ b/src/yuzu_cmd/config.cpp @@ -374,6 +374,8 @@ void Config::ReadValues() { Settings::values.use_dev_keys = sdl2_config->GetBoolean("Miscellaneous", "use_dev_keys", false); // Debugging + Settings::values.record_frame_times = + sdl2_config->GetBoolean("Debugging", "record_frame_times", false); Settings::values.use_gdbstub = sdl2_config->GetBoolean("Debugging", "use_gdbstub", false); Settings::values.gdbstub_port = static_cast<u16>(sdl2_config->GetInteger("Debugging", "gdbstub_port", 24689)); diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h index 0cfc111a6..f9f244522 100644 --- a/src/yuzu_cmd/default_ini.h +++ b/src/yuzu_cmd/default_ini.h @@ -213,6 +213,8 @@ region_value = log_filter = *:Trace [Debugging] +# Record frame time data, can be found in the log directory. Boolean value +record_frame_times = # Port for listening to GDB connections. use_gdbstub=false gdbstub_port=24689 |