summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/core/core.cpp25
-rw-r--r--src/core/file_sys/submission_package.cpp26
-rw-r--r--src/core/loader/nsp.cpp27
-rw-r--r--src/core/memory.cpp9
-rw-r--r--src/core/perf_stats.cpp47
-rw-r--r--src/core/perf_stats.h21
-rw-r--r--src/core/settings.h1
-rw-r--r--src/video_core/renderer_opengl/maxwell_to_gl.h4
-rw-r--r--src/yuzu/configuration/config.cpp5
-rw-r--r--src/yuzu/configuration/configure_input.cpp2
-rw-r--r--src/yuzu_cmd/config.cpp2
-rw-r--r--src/yuzu_cmd/default_ini.h2
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