diff --git a/pcsx2-gsrunner/Main.cpp b/pcsx2-gsrunner/Main.cpp index e69581036f..b9eb2fa416 100644 --- a/pcsx2-gsrunner/Main.cpp +++ b/pcsx2-gsrunner/Main.cpp @@ -148,6 +148,8 @@ static std::deque> s_cpu_thread_tasks; static std::string s_stats_json_path; static std::string s_drawlog_path; static std::vector s_frame_samples; +static std::string s_device_name; +static std::string s_driver_info; static u64 s_frame_timer_last = 0; static double s_last_prims = 0; static double s_last_tc_source_hit = 0; @@ -339,6 +341,15 @@ void Host::BeginPresentFrame() if (GSIsHardwareRenderer()) { + // Captured here rather than at shutdown: this runs on the GS thread with the + // device definitely live, and it is the axis that decides whether a settings + // A/B was even applied (several GS features are force-overridden per-driver). + if (s_device_name.empty() && g_gs_device) + { + s_device_name = g_gs_device->GetName(); + s_driver_info = g_gs_device->GetDriverInfo(); + } + const u32 last_draws = s_total_internal_draws; const u32 last_uploads = s_total_uploads; @@ -1161,7 +1172,25 @@ static void WriteStatsJson(const std::string& path) } } + // GetDriverInfo() is multi-line on Vulkan, and neither string is JSON-safe as-is. + const auto json_escape = [](const std::string& in) { + std::string out; + out.reserve(in.size()); + for (const char c : in) + { + if (c == '\n' || c == '\r' || c == '\t') + out.push_back(' '); + else if (c == '"' || c == '\\') + out.push_back('\''); + else + out.push_back(c); + } + return out; + }; + std::fprintf(fp.get(), "{\n \"run\": {\n"); + std::fprintf(fp.get(), " \"device_name\": \"%s\",\n \"driver_info\": \"%s\",\n", + json_escape(s_device_name).c_str(), json_escape(s_driver_info).c_str()); std::fprintf(fp.get(), " \"frames\": %u,\n \"drawn_frames\": %u,\n", s_total_frames, s_total_drawn_frames); std::fprintf(fp.get(), " \"prims\": %" PRIu64 ",\n \"draws\": %" PRIu64 ",\n \"draw_calls\": %" PRIu64 ",\n", s_total_prims, s_total_internal_draws, s_total_draws); diff --git a/pcsx2/PINE.cpp b/pcsx2/PINE.cpp index 852337ea21..8ac5f3c370 100644 --- a/pcsx2/PINE.cpp +++ b/pcsx2/PINE.cpp @@ -7,6 +7,7 @@ #include "Elfheader.h" #include "GS.h" #include "GS/GSPerfMon.h" +#include "GS/Renderers/Common/GSDevice.h" #include "MTGS.h" #include "PerformanceMetrics.h" #include "SaveState.h" @@ -306,11 +307,35 @@ namespace PINEServer static std::string BuildStatsJson() { SmallString gs_memory; + // Device identity is the axis nearly every mobile GPU bug turns on, so a stats + // blob without it is not attributable to a driver. Adreno tiers run drivers + // (Mesa Turnip vs Qualcomm proprietary) that behave oppositely for fbfetch and + // push descriptors, so the driver string matters more than the device name. + std::string device_name, driver_info; if (MTGS::IsOpen()) { - MTGS::RunOnGSThread([&gs_memory]() { GSgetMemoryStats(gs_memory); }); + MTGS::RunOnGSThread([&gs_memory, &device_name, &driver_info]() { + GSgetMemoryStats(gs_memory); + if (g_gs_device) + { + device_name = g_gs_device->GetName(); + driver_info = g_gs_device->GetDriverInfo(); + } + }); MTGS::WaitGS(false); } + // Newlines and quotes would break the JSON; GetDriverInfo() is multi-line on Vulkan. + const auto sanitize = [](std::string& s) { + for (char& c : s) + { + if (c == '\n' || c == '\r' || c == '\t') + c = ' '; + else if (c == '"' || c == '\\') + c = '\''; + } + }; + sanitize(device_name); + sanitize(driver_info); const auto counter = [](GSPerfMon::counter_t c) { return g_perfmon.Get(c); }; @@ -330,7 +355,8 @@ namespace PINEServer "\"tc_source_hit\":{:.1f},\"tc_source_miss\":{:.1f}," "\"tc_target_hit\":{:.1f},\"tc_target_miss\":{:.1f}," "\"hash_cache_hit\":{:.1f},\"hash_cache_miss\":{:.1f}," - "\"gs_memory\":\"{}\",\"frame_number\":{}" + "\"gs_memory\":\"{}\",\"frame_number\":{}," + "\"renderer\":\"{}\",\"device_name\":\"{}\",\"driver_info\":\"{}\"" "}}", PerformanceMetrics::GetFPS(), PerformanceMetrics::GetInternalFPS(), PerformanceMetrics::GetSpeed(), PerformanceMetrics::GetAverageFrameTime(), PerformanceMetrics::GetMinimumFrameTime(), @@ -348,7 +374,8 @@ namespace PINEServer counter(GSPerfMon::TCSourceHit), counter(GSPerfMon::TCSourceMiss), counter(GSPerfMon::TCTargetHit), counter(GSPerfMon::TCTargetMiss), counter(GSPerfMon::HashCacheHit), counter(GSPerfMon::HashCacheMiss), - gs_memory.view(), PerformanceMetrics::GetFrameNumber()); + gs_memory.view(), PerformanceMetrics::GetFrameNumber(), + Pcsx2Config::GSOptions::GetRendererName(EmuConfig.GS.Renderer), device_name, driver_info); } } // namespace PINEServer