diff --git a/CMakeLists.txt b/CMakeLists.txt
index ec22f64164..8c32e17af1 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -715,6 +715,7 @@ add_library(Common STATIC
Common/Data/Text/I18n.h
Common/Data/Text/Parsers.cpp
Common/Data/Text/Parsers.h
+ Common/Data/Text/StringWriter.h
Common/Data/Text/WrapText.cpp
Common/Data/Text/WrapText.h
Common/Data/Random/Rng.h
diff --git a/Common/Buffer.h b/Common/Buffer.h
index 892f496e7f..f9c3a474ec 100644
--- a/Common/Buffer.h
+++ b/Common/Buffer.h
@@ -57,7 +57,7 @@ public:
int SkipLineCRLF();
// Utility functions.
- void Printf(const char *fmt, ...);
+ ATTR_FORMAT_PRINTF(2, 3) void Printf(MSVC_FORMAT_PRINTF const char *fmt, ...);
// Dumps the entire buffer to the string, but keeps it around.
// Only to be used for debugging, since it might not be fast at all.
diff --git a/Common/Common.vcxproj b/Common/Common.vcxproj
index 86bdff49a9..41021a051c 100644
--- a/Common/Common.vcxproj
+++ b/Common/Common.vcxproj
@@ -411,6 +411,7 @@
+
diff --git a/Common/Common.vcxproj.filters b/Common/Common.vcxproj.filters
index 34c66be029..048031eb65 100644
--- a/Common/Common.vcxproj.filters
+++ b/Common/Common.vcxproj.filters
@@ -781,6 +781,9 @@
ext\pugixml
+
+ Data\Text
+
diff --git a/Common/CommonFuncs.h b/Common/CommonFuncs.h
index d3916a8ad8..b9153ba0f3 100644
--- a/Common/CommonFuncs.h
+++ b/Common/CommonFuncs.h
@@ -132,3 +132,19 @@ inline u64 __rotr64(u64 x, unsigned int shift){
return (x >> n) | (x << (64 - n));
#endif
}
+
+// --- GCC / Clang / Intel Definition ---
+#if defined(__GNUC__) || defined(__clang__)
+#define ATTR_FORMAT_PRINTF(fmt_idx, first_arg_idx) \
+ __attribute__((format(printf, fmt_idx, first_arg_idx)))
+#else
+#define ATTR_FORMAT_PRINTF(fmt_idx, first_arg_idx)
+#endif
+
+// --- MSVC Definition ---
+#if defined(_MSC_VER)
+#include
+#define MSVC_FORMAT_PRINTF _Printf_format_string_
+#else
+#define MSVC_FORMAT_PRINTF
+#endif
diff --git a/Common/Data/Text/Parsers.cpp b/Common/Data/Text/Parsers.cpp
index 4e2789b82c..b5b5c0992c 100644
--- a/Common/Data/Text/Parsers.cpp
+++ b/Common/Data/Text/Parsers.cpp
@@ -6,6 +6,7 @@
#include "Common/Data/Text/Parsers.h"
#include "Common/Data/Text/I18n.h"
+#include "Common/Data/Text/StringWriter.h"
#include "Common/StringUtils.h"
// Not strictly a parser...
diff --git a/Common/Data/Text/Parsers.h b/Common/Data/Text/Parsers.h
index defe17ff40..f743ac9c7b 100644
--- a/Common/Data/Text/Parsers.h
+++ b/Common/Data/Text/Parsers.h
@@ -79,74 +79,3 @@ std::string NiceSizeFormat(uint64_t size);
// seconds, or minutes, or hours.
// Uses I18N strings.
std::string NiceTimeFormat(int seconds);
-
-// Not a parser, needs a better location.
-// Simplified version of ShaderWriter. Would like to have that inherit from this but can't figure out how
-// due to the return value chaining.
-// TODO: We actually also have Buffer, with .Printf(), which is almost as convenient. Should maybe improve that instead?
-class StringWriter {
-public:
- StringWriter(char *buffer, size_t bufSize) : start_(buffer), p_(buffer), bufSize_(bufSize) {
- buffer[0] = '\0';
- }
- template
- explicit StringWriter(char (&buffer)[sz]) : start_(buffer), p_(buffer), bufSize_(sz) {
- buffer[0] = '\0';
- }
- StringWriter(const StringWriter &) = delete;
-
- std::string_view as_view() const {
- return std::string_view(start_, p_ - start_);
- }
-
- size_t size() const {
- return p_ - start_;
- }
-
- // Assumes the input is zero-terminated.
- // C: Copies a string literal (which always are zero-terminated, the count includes the zero) directly to the stream.
- template
- StringWriter &C(const char(&text)[T]) {
- const size_t remainder = bufSize_ - (p_ - start_);
- if (remainder <= T)
- return *this;
- memcpy(p_, text, T);
- p_ += T - 1;
- return *this;
- }
- // Assumes the input is zero-terminated.
- // C: Copies a string literal (which always are zero-terminated, the count includes the zero) directly to the stream.
- StringWriter &B(bool b) {
- return W(b ? "true" : "false");
- }
-
- // W: Writes a string_view to the stream.
- StringWriter &W(std::string_view text) {
- const size_t remainder = bufSize_ - (p_ - start_);
- if (remainder <= text.length())
- return *this;
- memcpy(p_, text.data(), text.length());
- p_ += text.length();
- *p_ = '\0';
- return *this;
- }
-
- // F: Formats into the buffer.
- StringWriter &F(const char *format, ...);
- StringWriter &endl() {
- const size_t remainder = bufSize_ - (p_ - start_);
- if (remainder <= 2)
- return *this;
-
- return C("\n");
- }
-
- void Rewind(size_t offset) {
- p_ -= offset;
- }
-
-private:
- char *start_;
- char *p_;
- size_t bufSize_;
-};
diff --git a/Common/Data/Text/StringWriter.cpp b/Common/Data/Text/StringWriter.cpp
new file mode 100644
index 0000000000..8d8ba6f53a
--- /dev/null
+++ b/Common/Data/Text/StringWriter.cpp
@@ -0,0 +1,2 @@
+#include "Common/Data/Text/StringWriter.h"
+
diff --git a/Common/Data/Text/StringWriter.h b/Common/Data/Text/StringWriter.h
new file mode 100644
index 0000000000..1dfa50f181
--- /dev/null
+++ b/Common/Data/Text/StringWriter.h
@@ -0,0 +1,78 @@
+#pragma once
+
+#include
+#include // for memcpy
+
+#include "Common/CommonFuncs.h"
+
+// Not a parser, needs a better location.
+// Simplified version of ShaderWriter. Would like to have that inherit from this but can't figure out how
+// due to the return value chaining. Maybe with the curiously recurring template pattern?
+// TODO: We actually also have Buffer, with .Printf(), which is almost as convenient. Should maybe improve that instead?
+class StringWriter {
+public:
+ StringWriter(char *buffer, size_t bufSize) : start_(buffer), p_(buffer), bufSize_(bufSize) {
+ buffer[0] = '\0';
+ }
+ template
+ explicit StringWriter(char(&buffer)[sz]) : start_(buffer), p_(buffer), bufSize_(sz) {
+ buffer[0] = '\0';
+ }
+ StringWriter(const StringWriter &) = delete;
+
+ std::string_view as_view() const {
+ return std::string_view(start_, p_ - start_);
+ }
+
+ size_t size() const {
+ return p_ - start_;
+ }
+
+ // Assumes the input is zero-terminated.
+ // C: Copies a string literal (which always are zero-terminated, the count includes the zero) directly to the stream.
+ template
+ StringWriter &C(const char(&text)[T]) {
+ const size_t remainder = bufSize_ - (p_ - start_);
+ if (remainder <= T)
+ return *this;
+ memcpy(p_, text, T);
+ p_ += T - 1;
+ return *this;
+ }
+ // Assumes the input is zero-terminated.
+ // C: Copies a string literal (which always are zero-terminated, the count includes the zero) directly to the stream.
+ StringWriter &B(bool b) {
+ return W(b ? "true" : "false");
+ }
+
+ // W: Writes a string_view to the stream.
+ StringWriter &W(std::string_view text) {
+ const size_t remainder = bufSize_ - (p_ - start_);
+ if (remainder <= text.length())
+ return *this;
+ memcpy(p_, text.data(), text.length());
+ p_ += text.length();
+ *p_ = '\0';
+ return *this;
+ }
+
+ // F: Formats into the buffer.
+ // This one is implemented in Parsers.cpp.
+ StringWriter &F(MSVC_FORMAT_PRINTF const char *format, ...) ATTR_FORMAT_PRINTF(2, 3);
+ StringWriter &endl() {
+ const size_t remainder = bufSize_ - (p_ - start_);
+ if (remainder <= 2)
+ return *this;
+
+ return C("\n");
+ }
+
+ void Rewind(size_t offset) {
+ p_ -= offset;
+ }
+
+private:
+ char *start_;
+ char *p_;
+ size_t bufSize_;
+};
diff --git a/Common/GPU/OpenGL/GLProfiler.h b/Common/GPU/OpenGL/GLProfiler.h
index 3290134547..c585cd2c08 100644
--- a/Common/GPU/OpenGL/GLProfiler.h
+++ b/Common/GPU/OpenGL/GLProfiler.h
@@ -4,6 +4,7 @@
#include
#include
+#include "Common/CommonFuncs.h"
#include "Common/GPU/OpenGL/GLCommon.h"
#include "Common/GPU/OpenGL/gl3stub.h"
@@ -25,11 +26,8 @@ public:
void BeginFrame();
- void Begin(const char *fmt, ...)
-#ifdef __GNUC__
- __attribute__((format(printf, 2, 3)))
-#endif
- ;
+ ATTR_FORMAT_PRINTF(2, 3)
+ void Begin(MSVC_FORMAT_PRINTF const char *fmt, ...);
void End();
void SetEnabledPtr(bool *enabledPtr) {
diff --git a/Common/GPU/ShaderWriter.h b/Common/GPU/ShaderWriter.h
index bb686eda35..7ffcaaef99 100644
--- a/Common/GPU/ShaderWriter.h
+++ b/Common/GPU/ShaderWriter.h
@@ -35,6 +35,7 @@ enum class ShaderWriterFlags {
};
ENUM_CLASS_BITOPS(ShaderWriterFlags);
+// TODO: Somehow merge with StringWriter from Parsers.h
class ShaderWriter {
public:
// Extensions are supported for both OpenGL ES and Vulkan (though of course, they're different).
diff --git a/Common/GPU/Vulkan/VulkanProfiler.h b/Common/GPU/Vulkan/VulkanProfiler.h
index 8d33d63ca4..1c5cfd8c42 100644
--- a/Common/GPU/Vulkan/VulkanProfiler.h
+++ b/Common/GPU/Vulkan/VulkanProfiler.h
@@ -3,6 +3,7 @@
#include
#include
+#include "Common/CommonFuncs.h"
#include "VulkanLoader.h"
// Simple scoped based profiler, initially meant for instant one-time tasks like texture uploads
@@ -29,11 +30,7 @@ public:
void BeginFrame(VulkanContext *vulkan, VkCommandBuffer firstCommandBuffer);
- void Begin(VkCommandBuffer cmdBuf, VkPipelineStageFlagBits stage, const char *fmt, ...)
-#ifdef __GNUC__
- __attribute__((format(printf, 4, 5)))
-#endif
- ;
+ ATTR_FORMAT_PRINTF(4, 5) void Begin(VkCommandBuffer cmdBuf, VkPipelineStageFlagBits stage, MSVC_FORMAT_PRINTF const char *fmt, ...);
void End(VkCommandBuffer cmdBuf, VkPipelineStageFlagBits stage);
void SetEnabledPtr(bool *enabledPtr) {
diff --git a/Common/GPU/thin3d.h b/Common/GPU/thin3d.h
index 9458df4137..f99673d5cb 100644
--- a/Common/GPU/thin3d.h
+++ b/Common/GPU/thin3d.h
@@ -896,7 +896,7 @@ public:
// Not very elegant, but more elegant than the old passId hack.
virtual void SetInvalidationCallback(InvalidationCallback callback) = 0;
- // Total amount of frames rendered. Unaffected by game pause, so more robust than gpuStats.numFlips
+ // Total amount of frames rendered. Unaffected by game pause, so more robust than gpuStats.totals.numFlips
virtual int GetFrameCount() = 0;
virtual std::string GetGpuProfileString() const {
diff --git a/Common/Log.h b/Common/Log.h
index 268bfc35fb..c9845dfe93 100644
--- a/Common/Log.h
+++ b/Common/Log.h
@@ -109,11 +109,7 @@ inline bool GenericLogEnabled(Log type, LogLevel level) {
return g_log[(int)type].IsEnabled(level) && (*g_bLogEnabledSetting);
}
-void GenericLog(Log type, LogLevel level, const char *file, int line, const char *fmt, ...)
-#ifdef __GNUC__
- __attribute__((format(printf, 5, 6)))
-#endif
- ;
+ATTR_FORMAT_PRINTF(5, 6) void GenericLog(Log type, LogLevel level, const char *file, int line, MSVC_FORMAT_PRINTF const char *fmt, ...);
// If you want to see verbose logs, change this to VERBOSE_LEVEL.
@@ -134,11 +130,7 @@ void GenericLog(Log type, LogLevel level, const char *file, int line, const char
#define VERBOSE_LOG(t,...) do { GENERIC_LOG(t, LogLevel::LVERBOSE, __VA_ARGS__) } while (false)
// Currently only actually shows a dialog box on Windows.
-bool HandleAssert(bool isDebugAssert, const char *function, const char *file, int line, const char *expression, const char* format, ...)
-#ifdef __GNUC__
-__attribute__((format(printf, 6, 7)))
-#endif
-;
+ATTR_FORMAT_PRINTF(6, 7) bool HandleAssert(bool isDebugAssert, const char *function, const char *file, int line, const char *expression, MSVC_FORMAT_PRINTF const char *format, ...);
// These allow us to get a small amount of information into assert messages.
// They can have a value between 0 and 15.
diff --git a/Core/ControlMapper.cpp b/Core/ControlMapper.cpp
index 8b0e4759d9..4553d50bbd 100644
--- a/Core/ControlMapper.cpp
+++ b/Core/ControlMapper.cpp
@@ -3,6 +3,7 @@
#include "Common/Math/math_util.h"
#include "Common/TimeUtil.h"
+#include "Common/Data/Text/StringWriter.h"
#include "Common/StringUtils.h"
#include "Common/Log.h"
@@ -765,21 +766,20 @@ void ControlMapper::onVKey(VirtKey vkey, bool down) {
}
}
-void ControlMapper::GetDebugString(char *buffer, size_t bufSize) const {
- std::stringstream str;
+void ControlMapper::GetDebugString(StringWriter &w) const {
for (auto &iter : curInput_) {
char temp[256];
iter.first.FormatDebug(temp, sizeof(temp));
- str << temp << ": " << iter.second.value << std::endl;
+ w.F("%s: %f\n", temp, iter.second.value);
}
for (int i = 0; i < ARRAY_SIZE(virtKeys_); i++) {
int vkId = VIRTKEY_FIRST + i;
if ((vkId >= VIRTKEY_AXIS_X_MIN && vkId <= VIRTKEY_AXIS_Y_MAX) || vkId == VIRTKEY_ANALOG_LIGHTLY || vkId == VIRTKEY_SPEED_ANALOG) {
- str << KeyMap::GetPspButtonName(vkId) << ": " << virtKeys_[i] << std::endl;
+ w.F("%s: %f\n", KeyMap::GetPspButtonName(vkId).c_str(), virtKeys_[i]);
}
}
- str << "Lstick: " << converted_[0][0] << ", " << converted_[0][1] << std::endl;
- truncate_cpy(buffer, bufSize, str.str().c_str());
+ w.F("Lstick: %f, %f\n", converted_[0][0], converted_[0][1]);
+ w.F("Rstick: %f, %f\n", converted_[1][0], converted_[1][1]);
}
void ControlMapper::RemoveListener(ControlListener *listener) {
diff --git a/Core/ControlMapper.h b/Core/ControlMapper.h
index 9f496eeaed..5ededf210c 100644
--- a/Core/ControlMapper.h
+++ b/Core/ControlMapper.h
@@ -21,6 +21,8 @@ public:
virtual void SetRawAnalog(int stick, float x, float y) {}
};
+class StringWriter;
+
// Utilities for mapping input events to PSP inputs and virtual keys.
// Main use is of course from EmuScreen.cpp, but also useful from control settings etc.
class ControlMapper {
@@ -56,7 +58,7 @@ public:
// Call when the emu screen gets pushed behind some other screen, like the pause screen, to release all "down" inputs.
void ReleaseAll();
- void GetDebugString(char *buffer, size_t bufSize) const;
+ void GetDebugString(StringWriter &w) const;
struct InputSample {
float value;
diff --git a/Core/Debugger/WebSocket/GPUStatsSubscriber.cpp b/Core/Debugger/WebSocket/GPUStatsSubscriber.cpp
index 6c17842c81..4da650889f 100644
--- a/Core/Debugger/WebSocket/GPUStatsSubscriber.cpp
+++ b/Core/Debugger/WebSocket/GPUStatsSubscriber.cpp
@@ -17,6 +17,8 @@
#include
#include
+
+#include "Common/Data/Text/StringWriter.h"
#include "Core/Debugger/WebSocket/GPUStatsSubscriber.h"
#include "Core/HW/Display.h"
#include "Core/System.h"
@@ -120,7 +122,9 @@ void WebSocketGPUStatsState::FlipListener() {
CollectedStats &stats = pendingStats_[pendingStats_.size() - 1];
__DisplayGetFPS(&stats.vps, &stats.fps, &stats.actual_fps);
- __DisplayGetDebugStats(stats.statbuf, sizeof(stats.statbuf));
+
+ StringWriter w(stats.statbuf);
+ __DisplayGetDebugStats(w);
int valid;
float *sleepHistory;
diff --git a/Core/HLE/HLE.h b/Core/HLE/HLE.h
index 3c18ff2e47..10d1af1ec8 100644
--- a/Core/HLE/HLE.h
+++ b/Core/HLE/HLE.h
@@ -190,11 +190,8 @@ void hleDoLogInternal(Log t, LogLevel level, u64 res, const char *file, int line
template
[[nodiscard]]
-#ifdef __GNUC__
-__attribute__((format(printf, 7, 8)))
-#endif
NO_INLINE
-T hleDoLog(Log t, LogLevel level, T res, const char *file, int line, const char *reportTag, const char *reasonFmt, ...) {
+ATTR_FORMAT_PRINTF(7, 8) T hleDoLog(Log t, LogLevel level, T res, const char *file, int line, const char *reportTag, MSVC_FORMAT_PRINTF const char *reasonFmt, ...) {
if (!GenericLogEnabled(t, level)) {
if (leave) {
hleLeave();
diff --git a/Core/HLE/sceDisplay.cpp b/Core/HLE/sceDisplay.cpp
index 9cc9bc7af6..ea75c803bc 100644
--- a/Core/HLE/sceDisplay.cpp
+++ b/Core/HLE/sceDisplay.cpp
@@ -28,6 +28,7 @@
#endif
#include "Common/Data/Text/I18n.h"
+#include "Common/Data/Text/StringWriter.h"
#include "Common/Profiler/Profiler.h"
#include "Common/System/System.h"
#include "Common/System/OSD.h"
@@ -273,7 +274,7 @@ void __DisplayDoState(PointerWrap &p) {
gstate_c.DoState(p);
if (s < 2) {
// This shouldn't have been savestated anyway, but it was.
- // It's unlikely to overlap with the first value in gpuStats.
+ // It's unlikely to overlap with the first value in gpuStats.perFrame.
int gpuVendorTemp = 0;
p.ExpectVoid(&gpuVendorTemp, sizeof(gpuVendorTemp));
}
@@ -395,7 +396,8 @@ static void DoFrameDropLogging(float scaledTimestep) {
const double actualTimestep = curFrameTime - lastFrameTime;
char stats[4096];
- __DisplayGetDebugStats(stats, sizeof(stats));
+ StringWriter w(stats);
+ __DisplayGetDebugStats(w);
NOTICE_LOG(Log::sceDisplay, "Dropping frames - budget = %.2fms / %.1ffps, actual = %.2fms (+%.2fms) / %.1ffps\n%s", scaledTimestep * 1000.0, 1.0 / scaledTimestep, actualTimestep * 1000.0, (actualTimestep - scaledTimestep) * 1000.0, 1.0 / actualTimestep, stats);
}
}
@@ -674,7 +676,7 @@ void __DisplayFlip(int cyclesLate) {
}
if (fbDirty) {
- gpuStats.numFlips++;
+ gpuStats.totals.numFlips++;
}
float scaledTimestep = (float)numVBlanksSinceFlip * timePerVblank;
diff --git a/Core/HLE/sceKernelModule.cpp b/Core/HLE/sceKernelModule.cpp
index b6423ee395..7f36b7e1bf 100644
--- a/Core/HLE/sceKernelModule.cpp
+++ b/Core/HLE/sceKernelModule.cpp
@@ -21,7 +21,6 @@
#include "zlib.h"
#include "Common/Data/Convert/SmallDataConvert.h"
-#include "Common/Data/Text/Parsers.h"
#include "Common/Serialize/Serializer.h"
#include "Common/Serialize/SerializeFuncs.h"
#include "Common/Serialize/SerializeSet.h"
@@ -31,6 +30,7 @@
#include "Common/System/System.h"
#include "Common/System/OSD.h"
#include "Common/Data/Text/I18n.h"
+#include "Common/Data/Text/StringWriter.h"
#include "Core/Config.h"
#include "Core/Core.h"
@@ -363,7 +363,7 @@ void PSPModule::GetLongInfo(char *ptr, int bufSize) const {
}
w.F("Text: %08x (%08x bytes)\n", nm.text_addr, nm.text_size);
w.F("Data: %08x (%08x bytes)\n", GetDataAddr(), nm.data_size);
- w.F("BSS: % 08x(% 08x bytes)\n", GetBSSAddr(), nm.bss_size);
+ w.F("BSS: %08x (%08x bytes)\n", GetBSSAddr(), nm.bss_size);
w.F("Entry: %08x GP: %08x\n", nm.entry_addr, nm.gp_value);
w.F("Status: %08x\n", nm.status);
}
diff --git a/Core/HLE/sceNet.cpp b/Core/HLE/sceNet.cpp
index 592f31db6b..ed17563880 100644
--- a/Core/HLE/sceNet.cpp
+++ b/Core/HLE/sceNet.cpp
@@ -22,6 +22,7 @@
#include "Common/Net/Resolve.h"
#include "Common/Net/SocketCompat.h"
#include "Common/Data/Text/Parsers.h"
+#include "Common/Data/Text/StringWriter.h"
#include "Common/Data/Text/I18n.h"
#include "Common/File/VFS/VFS.h"
#include "Common/File/FileUtil.h"
diff --git a/Core/HW/Display.cpp b/Core/HW/Display.cpp
index 1ac986aa1d..162718b237 100644
--- a/Core/HW/Display.cpp
+++ b/Core/HW/Display.cpp
@@ -23,6 +23,7 @@
#include "Common/Serialize/SerializeFuncs.h"
#include "Common/System/System.h"
#include "Common/TimeUtil.h"
+#include "Common/Data/Text/StringWriter.h"
#include "Core/Config.h"
#include "Core/System.h"
#include "Core/CoreTiming.h"
@@ -79,10 +80,10 @@ static void CalculateFPS() {
actualFps = (float)(actualFlips - lastActualFlips);
fps = frames / (now - lastFpsTime);
- flips = (float)(g_Config.iDisplayRefreshRate * (double)(gpuStats.numFlips - lastNumFlips) / frames);
+ flips = (float)(g_Config.iDisplayRefreshRate * (double)(gpuStats.totals.numFlips - lastNumFlips) / frames);
lastFpsFrame = numVBlanks;
- lastNumFlips = gpuStats.numFlips;
+ lastNumFlips = gpuStats.totals.numFlips;
lastActualFlips = actualFlips;
lastFpsTime = now;
@@ -187,24 +188,12 @@ void DisplayNotifySleep(double t, int pos) {
frameSleepHistory[pos] += t;
}
-void __DisplayGetDebugStats(char *stats, size_t bufsize) {
- char statbuf[4096];
+void __DisplayGetDebugStats(StringWriter &w) {
if (!gpu) {
- snprintf(stats, bufsize, "N/A");
+ w.C("No GPU stats available").endl();
return;
}
- gpu->GetStats(statbuf, sizeof(statbuf));
-
- snprintf(stats, bufsize,
- "Kernel processing time: %0.2f ms\n"
- "Slowest syscall: %s : %0.2f ms\n"
- "Most active syscall: %s : %0.2f ms\n%s",
- kernelStats.msInSyscalls * 1000.0f,
- kernelStats.slowestSyscallName ? kernelStats.slowestSyscallName : "(none)",
- kernelStats.slowestSyscallTime * 1000.0f,
- kernelStats.summedSlowestSyscallName ? kernelStats.summedSlowestSyscallName : "(none)",
- kernelStats.summedSlowestSyscallTime * 1000.0f,
- statbuf);
+ gpu->GetStats(w);
}
// On like 90hz, 144hz, etc, we return 60.0f as the framerate target. We only target other
diff --git a/Core/HW/Display.h b/Core/HW/Display.h
index 53a46dee06..75c6278942 100644
--- a/Core/HW/Display.h
+++ b/Core/HW/Display.h
@@ -21,6 +21,7 @@
#include
class PointerWrap;
+class StringWriter;
typedef void (*FlipCallback)(void *userdata);
void __DisplayListenFlip(FlipCallback callback, void *userdata);
@@ -35,7 +36,7 @@ uint32_t __DisplayGetCurrentHcount();
uint32_t __DisplayGetAccumulatedHcount();
void DisplayAdjustAccumulatedHcount(uint32_t diff);
-void __DisplayGetDebugStats(char *stats, size_t bufsize);
+void __DisplayGetDebugStats(StringWriter &w);
void __DisplayGetAveragedFPS(float *out_vps, float *out_fps);
void __DisplayGetFPS(float *out_vps, float *out_fps, float *out_actual_fps);
void __DisplayGetVPS(float *out_vps);
diff --git a/Core/Util/PPGeDraw.cpp b/Core/Util/PPGeDraw.cpp
index 8effbb102f..e3d28df4a8 100644
--- a/Core/Util/PPGeDraw.cpp
+++ b/Core/Util/PPGeDraw.cpp
@@ -911,7 +911,7 @@ static PPGeTextDrawerImage PPGeGetTextImage(std::string_view text, const PPGeSty
auto cacheItem = textDrawerImages.find(key);
if (cacheItem != textDrawerImages.end()) {
im = cacheItem->second;
- cacheItem->second.entry.lastUsedFrame = gpuStats.numFlips;
+ cacheItem->second.entry.lastUsedFrame = gpuStats.totals.numFlips;
} else {
std::vector bitmapData;
textDrawer->SetFontScale(style.scale, style.scale);
@@ -944,7 +944,7 @@ static PPGeTextDrawerImage PPGeGetTextImage(std::string_view text, const PPGeSty
memset(ramPtr + im.entry.bmHeight * bufwBytes, 0, bufwBytes + sz - origSz);
}
- im.entry.lastUsedFrame = gpuStats.numFlips;
+ im.entry.lastUsedFrame = gpuStats.totals.numFlips;
textDrawerImages[key] = im;
}
@@ -1049,7 +1049,7 @@ static void PPGeDrawTextImage(const PPGeTextDrawerImage &im, float x, float y, c
static void PPGeDecimateTextImages(int age) {
// Do this always, in case the platform has no TextDrawer but save state did.
for (auto it = textDrawerImages.begin(); it != textDrawerImages.end(); ) {
- if (gpuStats.numFlips - it->second.entry.lastUsedFrame >= age) {
+ if (gpuStats.totals.numFlips - it->second.entry.lastUsedFrame >= age) {
kernelMemory.Free(it->second.ptr);
it = textDrawerImages.erase(it);
} else {
@@ -1409,7 +1409,7 @@ bool PPGeImage::Load() {
Memory::Memset(texture_ + dataSize, 0, texSize - dataSize, "PPGeTexClear");
free(textureData);
- lastFrame_ = gpuStats.numFlips;
+ lastFrame_ = gpuStats.totals.numFlips;
loadedTextures_.push_back(this);
return true;
}
@@ -1467,7 +1467,7 @@ void PPGeImage::CompatLoad(u32 texture, int width, int height) {
}
void PPGeImage::Decimate(int age) {
- int tooOldFrame = gpuStats.numFlips - age;
+ int tooOldFrame = gpuStats.totals.numFlips - age;
for (size_t i = 0; i < loadedTextures_.size(); ++i) {
if (loadedTextures_[i]->lastFrame_ < tooOldFrame) {
loadedTextures_[i]->Free();
@@ -1484,7 +1484,7 @@ void PPGeImage::SetTexture() {
}
if (texture_ != 0) {
- lastFrame_ = gpuStats.numFlips;
+ lastFrame_ = gpuStats.totals.numFlips;
PPGeSetTexture(texture_, width_, height_);
} else {
PPGeDisableTexture();
diff --git a/GPU/Common/DepthRaster.cpp b/GPU/Common/DepthRaster.cpp
index e467af4f7e..273950df71 100644
--- a/GPU/Common/DepthRaster.cpp
+++ b/GPU/Common/DepthRaster.cpp
@@ -518,7 +518,7 @@ int DepthRasterClipIndexedTriangles(int *tx, int *ty, float *tz, const float *tr
// Still good for backface culling early and pretty cheap to compute.
Vec4F32 doubleTriArea = (x1 - x0) * (y2 - y0) - (x2 - x0) * (y1 - y0) - Vec4F32::Splat((float)(MIN_TWICE_TRI_AREA));
if (!AnyZeroSignBit(doubleTriArea)) {
- gpuStats.numDepthRasterEarlySize += 4;
+ gpuStats.perFrame.numDepthRasterEarlySize += 4;
continue;
}
@@ -560,8 +560,8 @@ int DepthRasterClipIndexedTriangles(int *tx, int *ty, float *tz, const float *tr
}
}
- gpuStats.numDepthRasterZCulled += planeCulled;
- gpuStats.numDepthEarlyBoxCulled += boxCulled;
+ gpuStats.perFrame.numDepthRasterZCulled += planeCulled;
+ gpuStats.perFrame.numDepthEarlyBoxCulled += boxCulled;
return outCount;
}
@@ -578,7 +578,7 @@ void DepthRasterScreenVerts(uint16_t *depth, int depthStride, const int *tx, con
// We remove the subpixel information here.
DepthRasterRect(depth, depthStride, scissor, tx[i], ty[i], tx[i + 1], ty[i + 1], z, draw.compareMode);
}
- gpuStats.numDepthRasterPrims += count / 2;
+ gpuStats.perFrame.numDepthRasterPrims += count / 2;
break;
case GE_PRIM_TRIANGLES:
{
@@ -633,9 +633,9 @@ void DepthRasterScreenVerts(uint16_t *depth, int depthStride, const int *tx, con
}
}
}
- gpuStats.numDepthRasterNoPixels += stats[(int)TriangleStat::NoPixels];
- gpuStats.numDepthRasterTooSmall += stats[(int)TriangleStat::SmallOrBackface];
- gpuStats.numDepthRasterPrims += stats[(int)TriangleStat::OK];
+ gpuStats.perFrame.numDepthRasterNoPixels += stats[(int)TriangleStat::NoPixels];
+ gpuStats.perFrame.numDepthRasterTooSmall += stats[(int)TriangleStat::SmallOrBackface];
+ gpuStats.perFrame.numDepthRasterPrims += stats[(int)TriangleStat::OK];
break;
}
default:
diff --git a/GPU/Common/DrawEngineCommon.cpp b/GPU/Common/DrawEngineCommon.cpp
index bada491ea0..36613e220c 100644
--- a/GPU/Common/DrawEngineCommon.cpp
+++ b/GPU/Common/DrawEngineCommon.cpp
@@ -571,7 +571,7 @@ void DrawEngineCommon::ApplyFramebufferRead(FBOTexState *fboTexState) {
if (gstate_c.Use(GPU_USE_FRAMEBUFFER_FETCH)) {
*fboTexState = FBO_TEX_READ_FRAMEBUFFER;
} else {
- gpuStats.numCopiesForShaderBlend++;
+ gpuStats.perFrame.numCopiesForShaderBlend++;
*fboTexState = FBO_TEX_COPY_BIND_TEX;
}
gstate_c.Dirty(DIRTY_SHADERBLEND);
@@ -1037,7 +1037,7 @@ void DrawEngineCommon::DepthRasterSubmitRaw(GEPrimitiveType prim, const VertexDe
return;
}
- TimeCollector collectStat(&gpuStats.msPrepareDepth, coreCollectDebugStats);
+ TimeCollector collectStat(&gpuStats.perFrame.msPrepareDepth, coreCollectDebugStats);
// Decode.
int numDecoded = 0;
@@ -1079,7 +1079,7 @@ void DrawEngineCommon::DepthRasterPredecoded(GEPrimitiveType prim, const void *i
return;
}
- TimeCollector collectStat(&gpuStats.msPrepareDepth, coreCollectDebugStats);
+ TimeCollector collectStat(&gpuStats.perFrame.msPrepareDepth, coreCollectDebugStats);
// Make sure these have already been indexed away.
_dbg_assert_(prim != GE_PRIM_TRIANGLE_STRIP && prim != GE_PRIM_TRIANGLE_FAN);
@@ -1112,7 +1112,7 @@ void DrawEngineCommon::DepthRasterPredecoded(GEPrimitiveType prim, const void *i
void DrawEngineCommon::FlushQueuedDepth() {
if (rasterTimeStart_ != 0.0) {
- gpuStats.msRasterTimeAvailable += time_now_d() - rasterTimeStart_;
+ gpuStats.perFrame.msRasterTimeAvailable += time_now_d() - rasterTimeStart_;
rasterTimeStart_ = 0.0;
}
@@ -1131,7 +1131,7 @@ void DrawEngineCommon::FlushQueuedDepth() {
DepthScissor tileScissor = draw.scissor.Tile(0, 1);
{
- TimeCollector collectStat(&gpuStats.msCullDepth, collectStats);
+ TimeCollector collectStat(&gpuStats.perFrame.msCullDepth, collectStats);
switch (draw.prim) {
case GE_PRIM_RECTANGLES:
outVertCount = DepthRasterClipIndexedRectangles(tx, ty, tz, vertices, indices, draw, tileScissor);
@@ -1145,7 +1145,7 @@ void DrawEngineCommon::FlushQueuedDepth() {
}
}
if (outVertCount > 0) {
- TimeCollector collectStat(&gpuStats.msRasterizeDepth, collectStats);
+ TimeCollector collectStat(&gpuStats.perFrame.msRasterizeDepth, collectStats);
if (!Memory::IsValid4AlignedAddress(draw.depthAddr)) {
continue;
}
diff --git a/GPU/Common/DrawEngineCommon.h b/GPU/Common/DrawEngineCommon.h
index 76344e82ed..5f32f00d8a 100644
--- a/GPU/Common/DrawEngineCommon.h
+++ b/GPU/Common/DrawEngineCommon.h
@@ -213,11 +213,11 @@ protected:
}
inline void ResetAfterDrawInline() {
- gpuStats.numFlushes++;
- gpuStats.numDrawCalls += numDrawInds_;
- gpuStats.numVertexDecodes += numDrawVerts_;
- gpuStats.numVertsSubmitted += vertexCountInDrawCalls_;
- gpuStats.numVertsDecoded += numDecodedVerts_;
+ gpuStats.perFrame.numFlushes++;
+ gpuStats.perFrame.numDrawCalls += numDrawInds_;
+ gpuStats.perFrame.numVertexDecodes += numDrawVerts_;
+ gpuStats.perFrame.numVertsSubmitted += vertexCountInDrawCalls_;
+ gpuStats.perFrame.numVertsDecoded += numDecodedVerts_;
indexGen.Reset();
numDecodedVerts_ = 0;
diff --git a/GPU/Common/FramebufferManagerCommon.cpp b/GPU/Common/FramebufferManagerCommon.cpp
index 3bad4401b1..09644bc3c7 100644
--- a/GPU/Common/FramebufferManagerCommon.cpp
+++ b/GPU/Common/FramebufferManagerCommon.cpp
@@ -479,8 +479,8 @@ VirtualFramebuffer *FramebufferManagerCommon::DoSetRenderFrameBuffer(Framebuffer
// If it's newly wrong, or changing every frame, just keep track.
vfb->newWidth = drawing_width;
vfb->newHeight = drawing_height;
- vfb->lastFrameNewSize = gpuStats.numFlips;
- } else if (vfb->lastFrameNewSize + FBO_OLD_AGE < gpuStats.numFlips) {
+ vfb->lastFrameNewSize = gpuStats.totals.numFlips;
+ } else if (vfb->lastFrameNewSize + FBO_OLD_AGE < gpuStats.totals.numFlips) {
// Okay, it's changed for a while (and stayed that way.) Let's start over.
// But only if we really need to, to avoid blinking.
bool needsRecreate = vfb->bufferWidth > params.fb_stride;
@@ -502,7 +502,7 @@ VirtualFramebuffer *FramebufferManagerCommon::DoSetRenderFrameBuffer(Framebuffer
}
} else {
// It's not different, let's keep track of that too.
- vfb->lastFrameNewSize = gpuStats.numFlips;
+ vfb->lastFrameNewSize = gpuStats.totals.numFlips;
}
if (!resized && renderScaleFactor_ != 1 && vfb->renderScaleFactor == 1) {
@@ -530,7 +530,7 @@ VirtualFramebuffer *FramebufferManagerCommon::DoSetRenderFrameBuffer(Framebuffer
vfb->height = drawing_height;
vfb->newWidth = drawing_width;
vfb->newHeight = drawing_height;
- vfb->lastFrameNewSize = gpuStats.numFlips;
+ vfb->lastFrameNewSize = gpuStats.totals.numFlips;
vfb->fb_format = params.fb_format;
vfb->usageFlags = FB_USAGE_RENDER_COLOR;
@@ -557,8 +557,8 @@ VirtualFramebuffer *FramebufferManagerCommon::DoSetRenderFrameBuffer(Framebuffer
INFO_LOG(Log::FrameBuf, "Creating FBO for %08x (z: %08x) : %d x %d x %s", vfb->fb_address, vfb->z_address, vfb->width, vfb->height, GeBufferFormatToString(vfb->fb_format));
- vfb->last_frame_render = gpuStats.numFlips;
- frameLastFramebufUsed_ = gpuStats.numFlips;
+ vfb->last_frame_render = gpuStats.totals.numFlips;
+ frameLastFramebufUsed_ = gpuStats.totals.numFlips;
vfbs_.push_back(vfb);
currentRenderVfb_ = vfb;
@@ -577,8 +577,8 @@ VirtualFramebuffer *FramebufferManagerCommon::DoSetRenderFrameBuffer(Framebuffer
// Use it as a render target.
DEBUG_LOG(Log::FrameBuf, "Switching render target to FBO for %08x: %d x %d x %d ", vfb->fb_address, vfb->width, vfb->height, vfb->fb_format);
vfb->usageFlags |= FB_USAGE_RENDER_COLOR;
- vfb->last_frame_render = gpuStats.numFlips;
- frameLastFramebufUsed_ = gpuStats.numFlips;
+ vfb->last_frame_render = gpuStats.totals.numFlips;
+ frameLastFramebufUsed_ = gpuStats.totals.numFlips;
vfb->dirtyAfterDisplay = true;
if ((skipDrawReason & SKIPDRAW_SKIPFRAME) == 0)
vfb->reallyDirtyAfterDisplay = true;
@@ -593,8 +593,8 @@ VirtualFramebuffer *FramebufferManagerCommon::DoSetRenderFrameBuffer(Framebuffer
} else {
// Something changed, but we still got the same framebuffer we were already rendering to.
// Might not be a lot to do here, we check in NotifyRenderFramebufferUpdated
- vfb->last_frame_render = gpuStats.numFlips;
- frameLastFramebufUsed_ = gpuStats.numFlips;
+ vfb->last_frame_render = gpuStats.totals.numFlips;
+ frameLastFramebufUsed_ = gpuStats.totals.numFlips;
vfb->dirtyAfterDisplay = true;
if ((skipDrawReason & SKIPDRAW_SKIPFRAME) == 0)
vfb->reallyDirtyAfterDisplay = true;
@@ -715,8 +715,8 @@ void FramebufferManagerCommon::CopyToDepthFromOverlappingFramebuffers(VirtualFra
if (source.channel == RASTER_DEPTH) {
// Good old depth->depth copy.
BlitFramebufferDepth(source.vfb, dest);
- gpuStats.numDepthCopies++;
- dest->last_frame_depth_updated = gpuStats.numFlips;
+ gpuStats.perFrame.numDepthCopies++;
+ dest->last_frame_depth_updated = gpuStats.totals.numFlips;
} else if (source.channel == RASTER_COLOR && draw_->GetDeviceCaps().fragmentShaderDepthWriteSupported) {
VirtualFramebuffer *src = source.vfb;
if (src->fb_format != GE_FORMAT_565) {
@@ -730,7 +730,7 @@ void FramebufferManagerCommon::CopyToDepthFromOverlappingFramebuffers(VirtualFra
shader = DRAW2D_565_TO_DEPTH_DESWIZZLE;
}
- gpuStats.numReinterpretCopies++;
+ gpuStats.perFrame.numReinterpretCopies++;
src->usageFlags |= FB_USAGE_COLOR_MIXED_DEPTH;
dest->usageFlags |= FB_USAGE_COLOR_MIXED_DEPTH;
@@ -879,7 +879,7 @@ void FramebufferManagerCommon::CopyToColorFromOverlappingFramebuffers(VirtualFra
const char *pass_name = "N/A";
float scaleFactorX = 1.0f;
if (src->fb_format == dst->fb_format) {
- gpuStats.numColorCopies++;
+ gpuStats.perFrame.numColorCopies++;
pipeline = Get2DPipeline(DRAW2D_COPY_COLOR);
pass_name = "copy_color";
} else {
@@ -898,7 +898,7 @@ void FramebufferManagerCommon::CopyToColorFromOverlappingFramebuffers(VirtualFra
pass_name = reinterpretStrings[(int)src->fb_format][(int)dst->fb_format];
- gpuStats.numReinterpretCopies++;
+ gpuStats.perFrame.numReinterpretCopies++;
}
if (pipeline) {
@@ -1137,7 +1137,7 @@ void FramebufferManagerCommon::PerformWriteFormattedFromMemory(u32 addr, int siz
VirtualFramebuffer *vfb = ResolveVFB(addr, stride, fmt);
if (vfb) {
// Let's count this as a "render". This will also force us to use the correct format.
- vfb->last_frame_render = gpuStats.numFlips;
+ vfb->last_frame_render = gpuStats.totals.numFlips;
vfb->colorBindSeq = GetBindSeqCount();
if (vfb->fb_stride < stride) {
@@ -1173,7 +1173,7 @@ void FramebufferManagerCommon::UpdateFromMemory(u32 addr, int size) {
if (useBufferedRendering_ && vfb->fbo) {
GEBufferFormat fmt = vfb->fb_format;
- if (vfb->last_frame_render + 1 < gpuStats.numFlips && isDisplayBuf) {
+ if (vfb->last_frame_render + 1 < gpuStats.totals.numFlips && isDisplayBuf) {
// If we're not rendering to it, format may be wrong. Use displayFormat_ instead.
// TODO: This doesn't seem quite right anymore.
fmt = displayFormat_;
@@ -1307,7 +1307,7 @@ bool FramebufferManagerCommon::BindFramebufferAsColorTexture(int stage, VirtualF
if (!partial && (flags & BINDFBCOLOR_UNCACHED) == 0) {
currentFramebufferCopy_ = renderCopy;
}
- gpuStats.numCopiesForSelfTex++;
+ gpuStats.perFrame.numCopiesForSelfTex++;
} else {
// Failed to get temp FBO? Weird.
draw_->BindFramebufferAsTexture(framebuffer->fbo, stage, Draw::Aspect::COLOR_BIT, layer);
@@ -1487,7 +1487,7 @@ Draw::Texture *FramebufferManagerCommon::MakePixelTexture(const u8 *srcPixels, G
for (auto &iter : drawPixelsCache_) {
if (iter.contentsHash == imageHash && iter.tex->Width() == width && iter.tex->Height() == height && iter.tex->Format() == texFormat) {
iter.frameNumber = frameNumber;
- gpuStats.numCachedUploads++;
+ gpuStats.perFrame.numCachedUploads++;
return iter.tex;
}
}
@@ -1499,7 +1499,7 @@ Draw::Texture *FramebufferManagerCommon::MakePixelTexture(const u8 *srcPixels, G
}
// OK, current one seems good, let's use it (and mark it used).
- gpuStats.numUploads++;
+ gpuStats.perFrame.numUploads++;
draw_->UpdateTextureLevels(iter.tex, &srcPixels, generateTexture, 1);
// NOTE: numFlips is no good - this is called every frame when paused sometimes!
iter.frameNumber = frameNumber;
@@ -1536,7 +1536,7 @@ Draw::Texture *FramebufferManagerCommon::MakePixelTexture(const u8 *srcPixels, G
DrawPixelsEntry entry{ tex, imageHash, frameNumber };
drawPixelsCache_.push_back(entry);
- gpuStats.numUploads++;
+ gpuStats.perFrame.numUploads++;
return tex;
}
@@ -1677,7 +1677,7 @@ void FramebufferManagerCommon::PrepareCopyDisplayToOutput(const DisplayLayoutCon
}
vfb->usageFlags |= FB_USAGE_DISPLAYED_FRAMEBUFFER;
- vfb->last_frame_displayed = gpuStats.numFlips;
+ vfb->last_frame_displayed = gpuStats.totals.numFlips;
vfb->dirtyAfterDisplay = false;
vfb->reallyDirtyAfterDisplay = false;
@@ -1874,7 +1874,7 @@ void FramebufferManagerCommon::ResizeFramebufFBO(VirtualFramebuffer *vfb, int w,
}
return;
}
- if (!old.fbo && vfb->last_frame_failed != 0 && vfb->last_frame_failed - gpuStats.numFlips < 63) {
+ if (!old.fbo && vfb->last_frame_failed != 0 && vfb->last_frame_failed - gpuStats.totals.numFlips < 63) {
// Don't constantly retry FBOs which failed to create.
return;
}
@@ -1883,7 +1883,7 @@ void FramebufferManagerCommon::ResizeFramebufFBO(VirtualFramebuffer *vfb, int w,
char tag[128];
size_t len = FormatFramebufferName(vfb, tag, sizeof(tag));
- gpuStats.numFBOsCreated++;
+ gpuStats.perFrame.numFBOsCreated++;
vfb->fbo = draw_->CreateFramebuffer({ vfb->renderWidth, vfb->renderHeight, 1, GetFramebufferLayers(), msaaLevel_, true, tag });
if (Memory::IsVRAMAddress(vfb->fb_address) && vfb->fb_stride != 0) {
@@ -1915,7 +1915,7 @@ void FramebufferManagerCommon::ResizeFramebufFBO(VirtualFramebuffer *vfb, int w,
if (!vfb->fbo) {
ERROR_LOG(Log::FrameBuf, "Error creating FBO during resize! %dx%d", vfb->renderWidth, vfb->renderHeight);
- vfb->last_frame_failed = gpuStats.numFlips;
+ vfb->last_frame_failed = gpuStats.totals.numFlips;
}
}
@@ -2024,7 +2024,7 @@ bool FramebufferManagerCommon::NotifyFramebufferCopy(u32 src, u32 dst, int size,
continue;
}
- if ((u32)size > vfb_size + 0x1000 && vfb->fb_format != GE_FORMAT_8888 && vfb->last_frame_render < gpuStats.numFlips) {
+ if ((u32)size > vfb_size + 0x1000 && vfb->fb_format != GE_FORMAT_8888 && vfb->last_frame_render < gpuStats.totals.numFlips) {
// Seems likely we are looking at a potential copy of 32-bit pixels (like video) to an old 16-bit buffer,
// which is very likely simply the wrong target, so skip it. See issue #17740 where this happens in Naruto Ultimate Ninja Heroes 2.
// Probably no point to give it a bad score and let it pass to sorting, as we're pretty sure here.
@@ -2165,7 +2165,7 @@ bool FramebufferManagerCommon::NotifyFramebufferCopy(u32 src, u32 dst, int size,
}
}
if (dstBuffer) {
- dstBuffer->last_frame_used = gpuStats.numFlips;
+ dstBuffer->last_frame_used = gpuStats.totals.numFlips;
if (channel == RASTER_DEPTH && !srcBuffer)
dstBuffer->usageFlags |= FB_USAGE_COLOR_MIXED_DEPTH;
}
@@ -2185,7 +2185,7 @@ bool FramebufferManagerCommon::NotifyFramebufferCopy(u32 src, u32 dst, int size,
return false;
} else if (dstBuffer) {
if (flags & GPUCopyFlag::MEMSET) {
- gpuStats.numClears++;
+ gpuStats.perFrame.numClears++;
WARN_LOG_N_TIMES(btucpy, 5, Log::FrameBuf, "Memcpy fbo memset-clear %08x (size: %x)", dst, size);
} else {
WARN_LOG_N_TIMES(btucpy, 5, Log::FrameBuf, "Memcpy fbo upload %08x -> %08x (size: %x)", src, dst, size);
@@ -2411,7 +2411,7 @@ VirtualFramebuffer *FramebufferManagerCommon::CreateRAMFramebuffer(uint32_t fbAd
vfb->height = height;
vfb->newWidth = vfb->width;
vfb->newHeight = vfb->height;
- vfb->lastFrameNewSize = gpuStats.numFlips;
+ vfb->lastFrameNewSize = gpuStats.totals.numFlips;
vfb->renderScaleFactor = renderScaleFactor_;
vfb->renderWidth = (u16)(vfb->width * renderScaleFactor_);
vfb->renderHeight = (u16)(vfb->height * renderScaleFactor_);
@@ -2496,7 +2496,7 @@ VirtualFramebuffer *FramebufferManagerCommon::FindDownloadTempBuffer(VirtualFram
}
nvfb->usageFlags |= FB_USAGE_RENDER_COLOR;
- nvfb->last_frame_render = gpuStats.numFlips;
+ nvfb->last_frame_render = gpuStats.totals.numFlips;
nvfb->dirtyAfterDisplay = true;
return nvfb;
@@ -2657,7 +2657,7 @@ bool FramebufferManagerCommon::NotifyBlockTransferBefore(u32 dstBasePtr, int dst
}
if (dstBuffer) {
- dstRect.vfb->last_frame_used = gpuStats.numFlips;
+ dstRect.vfb->last_frame_used = gpuStats.totals.numFlips;
// Mark the destination as fresh.
if (dstRect.channel == RASTER_COLOR) {
dstRect.vfb->colorBindSeq = GetBindSeqCount();
@@ -2760,7 +2760,7 @@ bool FramebufferManagerCommon::NotifyBlockTransferBefore(u32 dstBasePtr, int dst
srcX1 /= scaleFactorX;
srcX2 /= scaleFactorX;
- gpuStats.numReinterpretCopies++;
+ gpuStats.perFrame.numReinterpretCopies++;
FlushBeforeCopy();
BlitUsingRaster(src->fbo, srcX1, srcY1, srcX2, srcY2,
dst->fbo, dstX1, dstY1, dstX2, dstY2, false, dst->renderScaleFactor, pipeline, pass_name);
@@ -2875,7 +2875,7 @@ void FramebufferManagerCommon::NotifyBlockTransferAfter(u32 dstBasePtr, int dstS
// Make sure we don't flop back and forth.
dstRect.vfb->newWidth = std::max(dstRect.w_bytes / bpp, (int)dstRect.vfb->width);
dstRect.vfb->newHeight = std::max(dstRect.h, (int)dstRect.vfb->height);
- dstRect.vfb->lastFrameNewSize = gpuStats.numFlips;
+ dstRect.vfb->lastFrameNewSize = gpuStats.totals.numFlips;
// Resizing may change the viewport/etc.
gstate_c.Dirty(DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE);
}
@@ -2979,7 +2979,7 @@ Draw::Framebuffer *FramebufferManagerCommon::GetTempFBO(TempFBO reason, u16 w, u
u64 key = ((u64)reason << 48) | ((u32)w << 16) | h;
auto it = tempFBOs_.find(key);
if (it != tempFBOs_.end()) {
- it->second.last_frame_used = gpuStats.numFlips;
+ it->second.last_frame_used = gpuStats.totals.numFlips;
return it->second.fbo;
}
@@ -2992,7 +2992,7 @@ Draw::Framebuffer *FramebufferManagerCommon::GetTempFBO(TempFBO reason, u16 w, u
return nullptr;
}
- const TempFBOInfo info = { fbo, gpuStats.numFlips };
+ const TempFBOInfo info = { fbo, gpuStats.totals.numFlips };
tempFBOs_[key] = info;
return fbo;
}
@@ -3255,9 +3255,9 @@ void FramebufferManagerCommon::ReadbackFramebuffer(VirtualFramebuffer *vfb, int
NotifyMemInfo(MemBlockFlags::WRITE, fb_address + dstByteOffset, dstSize, tag, len);
if (mode == Draw::ReadbackMode::BLOCK) {
- gpuStats.numBlockingReadbacks++;
+ gpuStats.perFrame.numBlockingReadbacks++;
} else {
- gpuStats.numReadbacks++;
+ gpuStats.perFrame.numReadbacks++;
}
}
@@ -3294,8 +3294,8 @@ void FramebufferManagerCommon::ReadFramebufferToMemory(VirtualFramebuffer *vfb,
static int frameLastCopy = 0;
static u32 bufferLastCopy = 0;
static int copiesThisFrame = 0;
- if (frameLastCopy != gpuStats.numFlips || bufferLastCopy != vfb->fb_address) {
- frameLastCopy = gpuStats.numFlips;
+ if (frameLastCopy != gpuStats.totals.numFlips || bufferLastCopy != vfb->fb_address) {
+ frameLastCopy = gpuStats.totals.numFlips;
bufferLastCopy = vfb->fb_address;
copiesThisFrame = 0;
}
diff --git a/GPU/Common/FramebufferManagerCommon.h b/GPU/Common/FramebufferManagerCommon.h
index 883a51a77b..0072a48d18 100644
--- a/GPU/Common/FramebufferManagerCommon.h
+++ b/GPU/Common/FramebufferManagerCommon.h
@@ -313,7 +313,7 @@ public:
VirtualFramebuffer *SetRenderFrameBuffer(bool framebufChanged, int skipDrawReason, bool *changed) {
// Inlining this part since it's so frequent.
if (!framebufChanged && currentRenderVfb_) {
- currentRenderVfb_->last_frame_render = gpuStats.numFlips;
+ currentRenderVfb_->last_frame_render = gpuStats.totals.numFlips;
currentRenderVfb_->dirtyAfterDisplay = true;
if (!skipDrawReason)
currentRenderVfb_->reallyDirtyAfterDisplay = true;
diff --git a/GPU/Common/GPUDebugInterface.h b/GPU/Common/GPUDebugInterface.h
index a76db41bf5..2e67408a39 100644
--- a/GPU/Common/GPUDebugInterface.h
+++ b/GPU/Common/GPUDebugInterface.h
@@ -214,6 +214,8 @@ struct GPUDebugVertex {
float nz;
};
+class StringWriter;
+
class GPUDebugInterface {
public:
virtual ~GPUDebugInterface() = default;
@@ -236,7 +238,7 @@ public:
virtual void SetCmdValue(u32 op) = 0;
virtual void Flush() = 0;
- virtual void GetStats(char *buffer, size_t bufsize) = 0;
+ virtual void GetStats(StringWriter &w) = 0;
virtual uint32_t SetAddrTranslation(uint32_t value) = 0;
virtual uint32_t GetAddrTranslation() = 0;
diff --git a/GPU/Common/SoftwareTransformCommon.cpp b/GPU/Common/SoftwareTransformCommon.cpp
index ab650da196..490177156f 100644
--- a/GPU/Common/SoftwareTransformCommon.cpp
+++ b/GPU/Common/SoftwareTransformCommon.cpp
@@ -233,7 +233,7 @@ void SoftwareTransform::Transform(int prim, u32 vertType, const DecVtxFormat &de
result->color = transformed[1].color0_32;
result->depth = depth;
result->action = SW_CLEAR;
- gpuStats.numClears++;
+ gpuStats.perFrame.numClears++;
return;
}
}
@@ -556,7 +556,7 @@ void SoftwareTransform::ProjectClipAndExpand(int prim, int vertexCount, u32 vert
}
if (gstate.isModeClear()) {
- gpuStats.numClears++;
+ gpuStats.perFrame.numClears++;
}
result->action = SW_DRAW_INDEXED;
diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp
index 67e9e99732..0dd630ea41 100644
--- a/GPU/Common/TextureCacheCommon.cpp
+++ b/GPU/Common/TextureCacheCommon.cpp
@@ -150,8 +150,8 @@ void TextureCacheCommon::StartFrame() {
replacementFrameBudgetSeconds_ = baseValue / fps;
if ((DebugOverlay)g_Config.iDebugOverlay == DebugOverlay::DEBUG_STATS) {
- gpuStats.numReplacerTrackedTex = replacer_.GetNumTrackedTextures();
- gpuStats.numCachedReplacedTextures = replacer_.GetNumCachedReplacedTextures();
+ gpuStats.perFrame.numReplacerTrackedTex = replacer_.GetNumTrackedTextures();
+ gpuStats.perFrame.numCachedReplacedTextures = replacer_.GetNumCachedReplacedTextures();
}
if (texelsScaledThisFrame_) {
@@ -511,8 +511,8 @@ TexCacheEntry *TextureCacheCommon::SetTexture() {
}
if (match) {
- if (entry->lastFrame != gpuStats.numFlips) {
- u32 diff = gpuStats.numFlips - entry->lastFrame;
+ if (entry->lastFrame != gpuStats.totals.numFlips) {
+ u32 diff = gpuStats.totals.numFlips - entry->lastFrame;
entry->numFrames++;
if (entry->framesUntilNextFullHash < diff) {
@@ -710,7 +710,7 @@ TexCacheEntry *TextureCacheCommon::SetTexture() {
}
bool TextureCacheCommon::GetBestFramebufferCandidate(const TextureDefinition &entry, u32 texAddrOffset, AttachCandidate *bestCandidate, const char *context) const {
- gpuStats.numFramebufferEvaluations++;
+ gpuStats.perFrame.numFramebufferEvaluations++;
TinySet candidates;
@@ -831,7 +831,7 @@ void TextureCacheCommon::Decimate(TexCacheEntry *exceptThisOne, bool forcePressu
}
bool hasClut = (iter->second->status & TexCacheEntry::STATUS_CLUT_VARIANTS) != 0;
int killAge = hasClut ? TEXTURE_KILL_AGE_CLUT : killAgeBase;
- if (iter->second->lastFrame + killAge < gpuStats.numFlips) {
+ if (iter->second->lastFrame + killAge < gpuStats.totals.numFlips) {
DeleteTexture(iter++);
} else {
++iter;
@@ -851,7 +851,7 @@ void TextureCacheCommon::Decimate(TexCacheEntry *exceptThisOne, bool forcePressu
continue;
}
// In low memory mode, we kill them all since secondary cache is disabled.
- if (lowMemoryMode_ || iter->second->lastFrame + TEXTURE_SECOND_KILL_AGE < gpuStats.numFlips) {
+ if (lowMemoryMode_ || iter->second->lastFrame + TEXTURE_SECOND_KILL_AGE < gpuStats.totals.numFlips) {
ReleaseTexture(iter->second.get(), true);
secondCacheSizeEstimate_ -= EstimateTexMemoryUsage(iter->second.get());
iter = secondCache_.erase(iter);
@@ -869,7 +869,7 @@ void TextureCacheCommon::Decimate(TexCacheEntry *exceptThisOne, bool forcePressu
void TextureCacheCommon::DecimateVideos() {
for (auto iter = videos_.begin(); iter != videos_.end(); ) {
- if (iter->flips + VIDEO_DECIMATE_AGE < gpuStats.numFlips) {
+ if (iter->flips + VIDEO_DECIMATE_AGE < gpuStats.totals.numFlips) {
iter = videos_.erase(iter);
} else {
++iter;
@@ -893,7 +893,7 @@ bool TextureCacheCommon::IsVideo(u32 texaddr) const {
void TextureCacheCommon::HandleTextureChange(TexCacheEntry *const entry, const char *reason, bool initialMatch, bool doDelete) {
cacheSizeEstimate_ -= EstimateTexMemoryUsage(entry);
entry->numInvalidated++;
- gpuStats.numTextureInvalidations++;
+ gpuStats.perFrame.numTextureInvalidations++;
DEBUG_LOG(Log::G3D, "Texture different or overwritten, reloading at %08x: %s", entry->addr, reason);
if (doDelete) {
ForgetLastTexture();
@@ -956,7 +956,7 @@ void TextureCacheCommon::NotifyFramebuffer(VirtualFramebuffer *framebuffer, Fram
// Color - no need to look in the mirrors.
for (auto it = cache_.lower_bound(cacheKey), end = cache_.upper_bound(cacheKeyEnd); it != end; ++it) {
it->second->status |= TexCacheEntry::STATUS_FRAMEBUFFER_OVERLAP;
- gpuStats.numTextureInvalidationsByFramebuffer++;
+ gpuStats.perFrame.numTextureInvalidationsByFramebuffer++;
}
if (z_stride != 0) {
@@ -966,11 +966,11 @@ void TextureCacheCommon::NotifyFramebuffer(VirtualFramebuffer *framebuffer, Fram
cacheKeyEnd = (u64)z_endAddr << 32;
for (auto it = cache_.lower_bound(cacheKey | 0x200000), end = cache_.upper_bound(cacheKeyEnd | 0x200000); it != end; ++it) {
it->second->status |= TexCacheEntry::STATUS_FRAMEBUFFER_OVERLAP;
- gpuStats.numTextureInvalidationsByFramebuffer++;
+ gpuStats.perFrame.numTextureInvalidationsByFramebuffer++;
}
for (auto it = cache_.lower_bound(cacheKey | 0x600000), end = cache_.upper_bound(cacheKeyEnd | 0x600000); it != end; ++it) {
it->second->status |= TexCacheEntry::STATUS_FRAMEBUFFER_OVERLAP;
- gpuStats.numTextureInvalidationsByFramebuffer++;
+ gpuStats.perFrame.numTextureInvalidationsByFramebuffer++;
}
}
break;
@@ -1179,7 +1179,7 @@ void TextureCacheCommon::SetTextureFramebuffer(const AttachCandidate &candidate)
framebuffer->usageFlags |= FB_USAGE_TEXTURE;
// Keep the framebuffer alive.
- framebuffer->last_frame_used = gpuStats.numFlips;
+ framebuffer->last_frame_used = gpuStats.totals.numFlips;
nextFramebufferTextureChannel_ = RASTER_COLOR;
@@ -1335,7 +1335,7 @@ void TextureCacheCommon::NotifyConfigChanged() {
void TextureCacheCommon::NotifyWriteFormattedFromMemory(u32 addr, int size, int width, GEBufferFormat fmt) {
addr &= 0x3FFFFFFF;
- videos_.push_back({ addr, (u32)size, gpuStats.numFlips });
+ videos_.push_back({ addr, (u32)size, gpuStats.totals.numFlips });
}
void TextureCacheCommon::LoadClut(u32 clutAddr, u32 loadBytes, GPURecord::Recorder *recorder) {
@@ -1394,14 +1394,14 @@ void TextureCacheCommon::LoadClut(u32 clutAddr, u32 loadBytes, GPURecord::Record
// the framebuffer with the smallest offset. This is yet another framebuffer matching
// loop with its own rules, eventually we'll probably want to do something
// more systematic.
- bool okAge = !PSP_CoreParameter().compat.flags().LoadCLUTFromCurrentFrameOnly || framebuffer->last_frame_render == gpuStats.numFlips; // Here we can try heuristics.
+ bool okAge = !PSP_CoreParameter().compat.flags().LoadCLUTFromCurrentFrameOnly || framebuffer->last_frame_render == gpuStats.totals.numFlips; // Here we can try heuristics.
if (matchRange && !inMargin && offset < (int)clutRenderOffset_) {
if (okAge) {
WARN_LOG_N_TIMES(clutfb, 5, Log::G3D, "Detected LoadCLUT(%d bytes) from framebuffer %08x (%s), last render %d frames ago, byte offset %d, pixel offset %d",
- loadBytes, fb_address, GeBufferFormatToString(framebuffer->fb_format), gpuStats.numFlips - framebuffer->last_frame_render, offset, offset / fb_bpp);
- framebuffer->last_frame_clut = gpuStats.numFlips;
+ loadBytes, fb_address, GeBufferFormatToString(framebuffer->fb_format), gpuStats.totals.numFlips - framebuffer->last_frame_render, offset, offset / fb_bpp);
+ framebuffer->last_frame_clut = gpuStats.totals.numFlips;
// Also mark used so it's not decimated.
- framebuffer->last_frame_used = gpuStats.numFlips;
+ framebuffer->last_frame_used = gpuStats.totals.numFlips;
framebuffer->usageFlags |= FB_USAGE_CLUT;
bestClutAddress = framebuffer->fb_address;
clutRenderOffset_ = (u32)offset;
@@ -1411,7 +1411,7 @@ void TextureCacheCommon::LoadClut(u32 clutAddr, u32 loadBytes, GPURecord::Record
break;
}
} else {
- WARN_LOG(Log::G3D, "Ignoring CLUT load from %d frames old buffer at %08x", gpuStats.numFlips - framebuffer->last_frame_render, fb_address);
+ WARN_LOG(Log::G3D, "Ignoring CLUT load from %d frames old buffer at %08x", gpuStats.totals.numFlips - framebuffer->last_frame_render, fb_address);
}
}
}
@@ -2203,13 +2203,13 @@ void TextureCacheCommon::ApplyTexture(bool doBind) {
if (entry->status & TexCacheEntry::STATUS_CLUT_GPU) {
// Special process.
ApplyTextureDepal(entry);
- entry->lastFrame = gpuStats.numFlips;
+ entry->lastFrame = gpuStats.totals.numFlips;
gstate_c.SetTextureFullAlpha(false);
gstate_c.SetTextureIs3D(false);
gstate_c.SetTextureIsArray(false);
gstate_c.SetTextureIsBGRA(false);
} else {
- entry->lastFrame = gpuStats.numFlips;
+ entry->lastFrame = gpuStats.totals.numFlips;
if (doBind) {
BindTexture(entry);
}
@@ -2419,7 +2419,7 @@ void TextureCacheCommon::ApplyTextureFramebuffer(VirtualFramebuffer *framebuffer
draw2D_->Blit(textureShader, u1, v1, u2, v2, u1, v1, u2, v2, framebuffer->renderWidth, framebuffer->renderHeight, depalWidth, framebuffer->renderHeight, false, framebuffer->renderScaleFactor);
- gpuStats.numDepal++;
+ gpuStats.perFrame.numDepal++;
gstate_c.curTextureWidth = texWidth;
gstate_c.Dirty(DIRTY_UVSCALEOFFSET);
@@ -2521,7 +2521,7 @@ void TextureCacheCommon::ApplyTextureDepal(TexCacheEntry *entry) {
draw2D_->Blit(textureShader, u1, v1, u2, v2, u1, v1, u2, v2, texWidth, texHeight, texWidth, texHeight, false, 1);
- gpuStats.numDepal++;
+ gpuStats.perFrame.numDepal++;
gstate_c.curTextureWidth = texWidth;
gstate_c.Dirty(DIRTY_UVSCALEOFFSET);
@@ -2718,11 +2718,11 @@ void TextureCacheCommon::Invalidate(u32 addr, int size, GPUInvalidationType type
entry->minihash = (entry->minihash ^ 0x89ABCDEF) + 89;
}
if (type != GPU_INVALIDATE_ALL) {
- gpuStats.numTextureInvalidations++;
+ gpuStats.perFrame.numTextureInvalidations++;
// Start it over from 0 (unless it's safe.)
entry->numFrames = type == GPU_INVALIDATE_SAFE ? 256 : 0;
if (type == GPU_INVALIDATE_SAFE) {
- u32 diff = gpuStats.numFlips - entry->lastFrame;
+ u32 diff = gpuStats.totals.numFlips - entry->lastFrame;
// We still need to mark if the texture is frequently changing, even if it's safely changing.
if (diff < TEXCACHE_FRAME_CHANGE_FREQUENT) {
entry->status |= TexCacheEntry::STATUS_CHANGE_FREQUENT;
@@ -2770,7 +2770,7 @@ std::string AttachCandidate::ToString() const {
}
bool TextureCacheCommon::PrepareBuildTexture(BuildTexturePlan &plan, TexCacheEntry *entry) {
- gpuStats.numTexturesDecoded++;
+ gpuStats.perFrame.numTexturesDecoded++;
// For the estimate, we assume cluts always point to 8888 for simplicity.
cacheSizeEstimate_ += EstimateTexMemoryUsage(entry);
diff --git a/GPU/Common/TextureCacheCommon.h b/GPU/Common/TextureCacheCommon.h
index 978dcbbc2b..42e92f91e2 100644
--- a/GPU/Common/TextureCacheCommon.h
+++ b/GPU/Common/TextureCacheCommon.h
@@ -485,7 +485,7 @@ protected:
}
const u32 *checkp = (const u32 *)Memory::GetPointer(addr);
- gpuStats.numTextureDataBytesHashed += sizeInRAM;
+ gpuStats.perFrame.numTextureDataBytesHashed += sizeInRAM;
if (Memory::IsValidAddress(addr + sizeInRAM)) {
return StableQuickTexHash(checkp, sizeInRAM);
diff --git a/GPU/Common/TextureShaderCommon.cpp b/GPU/Common/TextureShaderCommon.cpp
index 08733466d1..757644db51 100644
--- a/GPU/Common/TextureShaderCommon.cpp
+++ b/GPU/Common/TextureShaderCommon.cpp
@@ -59,7 +59,7 @@ ClutTexture TextureShaderCache::GetClutTexture(GEPaletteFormat clutFormat, const
auto oldtex = texCache_.find(clutId);
if (oldtex != texCache_.end()) {
- oldtex->second->lastFrame = gpuStats.numFlips;
+ oldtex->second->lastFrame = gpuStats.totals.numFlips;
return *oldtex->second;
}
@@ -132,7 +132,7 @@ ClutTexture TextureShaderCache::GetClutTexture(GEPaletteFormat clutFormat, const
}
tex->texture = draw_->CreateTexture(desc);
- tex->lastFrame = gpuStats.numFlips;
+ tex->lastFrame = gpuStats.totals.numFlips;
texCache_[clutId] = tex;
return *tex;
@@ -187,7 +187,7 @@ Draw::SamplerState *TextureShaderCache::GetSampler(bool linearFilter) {
void TextureShaderCache::Decimate() {
for (auto tex = texCache_.begin(); tex != texCache_.end(); ) {
- if (tex->second->lastFrame + DEPAL_TEXTURE_OLD_AGE < gpuStats.numFlips) {
+ if (tex->second->lastFrame + DEPAL_TEXTURE_OLD_AGE < gpuStats.totals.numFlips) {
tex->second->texture->Release();
delete tex->second;
texCache_.erase(tex++);
@@ -195,7 +195,7 @@ void TextureShaderCache::Decimate() {
++tex;
}
}
- gpuStats.numClutTextures = (int)texCache_.size();
+ gpuStats.perFrame.numClutTextures = (int)texCache_.size();
}
Draw2DPipeline *TextureShaderCache::GetDepalettizeShader(uint32_t clutMode, GETextureFormat textureFormat, GEBufferFormat bufferFormat, bool smoothedDepal, u32 depthUpperBits) {
diff --git a/GPU/D3D11/DrawEngineD3D11.cpp b/GPU/D3D11/DrawEngineD3D11.cpp
index de3c6bfb68..1873da61ca 100644
--- a/GPU/D3D11/DrawEngineD3D11.cpp
+++ b/GPU/D3D11/DrawEngineD3D11.cpp
@@ -304,7 +304,7 @@ void DrawEngineD3D11::Flush() {
bool useElements;
DecodeVerts(dec_, decoded_);
DecodeIndsAndGetData(&prim, &vertexCount, &maxIndex, &useElements, false);
- gpuStats.numUncachedVertsDrawn += vertexCount;
+ gpuStats.perFrame.numUncachedVertsDrawn += vertexCount;
bool hasColor = (lastVType_ & GE_VTYPE_COL_MASK) != GE_VTYPE_COL_NONE;
if (gstate.isModeThrough()) {
@@ -392,7 +392,7 @@ void DrawEngineD3D11::Flush() {
gstate_c.vertexFullAlpha = gstate_c.vertexFullAlpha && ((hasColor && (gstate.materialupdate & 1)) || gstate.getMaterialAmbientA() == 255) && (!gstate.isLightingEnabled() || gstate.getAmbientA() == 255);
}
- gpuStats.numUncachedVertsDrawn += vertexCount;
+ gpuStats.perFrame.numUncachedVertsDrawn += vertexCount;
prim = IndexGenerator::GeneralPrim((GEPrimitiveType)drawInds_[0].prim);
VERBOSE_LOG(Log::G3D, "Flush prim %i SW! %i verts in one go", prim, vertexCount);
diff --git a/GPU/D3D11/GPU_D3D11.cpp b/GPU/D3D11/GPU_D3D11.cpp
index 72f683e249..3bd73d7d72 100644
--- a/GPU/D3D11/GPU_D3D11.cpp
+++ b/GPU/D3D11/GPU_D3D11.cpp
@@ -20,6 +20,7 @@
#include "Common/Log.h"
#include "Common/GraphicsContext.h"
#include "Common/Profiler/Profiler.h"
+#include "Common/Data/Text/StringWriter.h"
#include "GPU/GPUState.h"
@@ -140,14 +141,9 @@ void GPU_D3D11::FinishDeferred() {
drawEngine_.FinishDeferred();
}
-void GPU_D3D11::GetStats(char *buffer, size_t bufsize) {
- size_t offset = FormatGPUStatsCommon(buffer, bufsize);
- buffer += offset;
- bufsize -= offset;
- if ((int)bufsize < 0)
- return;
- snprintf(buffer, bufsize,
- "Vertex, Fragment shaders loaded: %d, %d\n",
+void GPU_D3D11::GetStats(StringWriter &w) {
+ FormatGPUStatsCommon(w);
+ w.F("Vertex, Fragment shaders loaded: %d, %d\n",
shaderManagerD3D11_->GetNumVertexShaders(),
shaderManagerD3D11_->GetNumFragmentShaders()
);
diff --git a/GPU/D3D11/GPU_D3D11.h b/GPU/D3D11/GPU_D3D11.h
index 511b63eb9d..c7a0cf3fe4 100644
--- a/GPU/D3D11/GPU_D3D11.h
+++ b/GPU/D3D11/GPU_D3D11.h
@@ -28,6 +28,7 @@
class FramebufferManagerD3D11;
class ShaderManagerD3D11;
class TextureCacheD3D11;
+class StringWriter;
class GPU_D3D11 : public GPUCommonHW {
public:
@@ -36,7 +37,7 @@ public:
u32 CheckGPUFeatures() const override;
- void GetStats(char *buffer, size_t bufsize) override;
+ void GetStats(StringWriter &w) override;
void DeviceLost() override; // Destroy all device objects.
void DeviceRestore(Draw::DrawContext *draw) override;
diff --git a/GPU/Debugger/Record.cpp b/GPU/Debugger/Record.cpp
index 38e3f86807..73b1d85171 100644
--- a/GPU/Debugger/Record.cpp
+++ b/GPU/Debugger/Record.cpp
@@ -135,7 +135,7 @@ bool Recorder::BeginRecording() {
nextFrame = false;
lastTextures.clear();
lastRenderTargets.clear();
- flipLastAction = gpuStats.numFlips;
+ flipLastAction = gpuStats.totals.numFlips;
flipFinishAt = -1;
u32 ptr = (u32)pushbuf.size();
@@ -575,7 +575,7 @@ void Recorder::EmitBezierSpline(u32 op) {
bool Recorder::RecordNextFrame(const std::function callback) {
if (!nextFrame) {
- flipLastAction = gpuStats.numFlips;
+ flipLastAction = gpuStats.totals.numFlips;
flipFinishAt = -1;
writeCallback = callback;
nextFrame = true;
@@ -597,7 +597,7 @@ void Recorder::FinishRecording() {
NOTICE_LOG(Log::System, "Recording finished");
active = false;
- flipLastAction = gpuStats.numFlips;
+ flipLastAction = gpuStats.totals.numFlips;
flipFinishAt = -1;
lastEdramTrans = 0x400;
@@ -785,9 +785,9 @@ void Recorder::NotifyDisplay(u32 framebuf, int stride, int fmt) {
}
void Recorder::NotifyBeginFrame() {
- const bool noDisplayAction = flipLastAction + 4 < gpuStats.numFlips;
+ const bool noDisplayAction = flipLastAction + 4 < gpuStats.totals.numFlips;
// We do this only to catch things that don't call NotifyDisplay.
- if (active && HasDrawCommands() && (noDisplayAction || gpuStats.numFlips == flipFinishAt)) {
+ if (active && HasDrawCommands() && (noDisplayAction || gpuStats.totals.numFlips == flipFinishAt)) {
NOTICE_LOG(Log::System, "Recording complete on frame");
CheckEdramTrans();
@@ -813,7 +813,7 @@ void Recorder::NotifyBeginFrame() {
NOTICE_LOG(Log::System, "Recording starting on frame...");
BeginRecording();
// If we began on a BeginFrame, end on a BeginFrame.
- flipFinishAt = gpuStats.numFlips + 1;
+ flipFinishAt = gpuStats.totals.numFlips + 1;
}
}
diff --git a/GPU/GLES/DrawEngineGLES.cpp b/GPU/GLES/DrawEngineGLES.cpp
index 230787c710..05b355f4fa 100644
--- a/GPU/GLES/DrawEngineGLES.cpp
+++ b/GPU/GLES/DrawEngineGLES.cpp
@@ -337,7 +337,7 @@ void DrawEngineGLES::Flush() {
gstate_c.vertexFullAlpha = gstate_c.vertexFullAlpha && ((hasColor && (gstate.materialupdate & 1)) || gstate.getMaterialAmbientA() == 255) && (!gstate.isLightingEnabled() || gstate.getAmbientA() == 255);
}
- gpuStats.numUncachedVertsDrawn += vertexCount;
+ gpuStats.perFrame.numUncachedVertsDrawn += vertexCount;
prim = IndexGenerator::GeneralPrim((GEPrimitiveType)drawInds_[0].prim);
// We need correct viewport values in gstate_c already.
diff --git a/GPU/GLES/FragmentTestCacheGLES.cpp b/GPU/GLES/FragmentTestCacheGLES.cpp
index e5ef804410..973a3e58a2 100644
--- a/GPU/GLES/FragmentTestCacheGLES.cpp
+++ b/GPU/GLES/FragmentTestCacheGLES.cpp
@@ -48,7 +48,7 @@ void FragmentTestCacheGLES::BindTestTexture(int slot) {
const FragmentTestID id = GenerateTestID();
const auto cached = cache_.find(id);
if (cached != cache_.end()) {
- cached->second.lastFrame = gpuStats.numFlips;
+ cached->second.lastFrame = gpuStats.totals.numFlips;
GLRTexture *tex = cached->second.texture;
if (tex == lastTexture_) {
// Already bound, hurray.
@@ -78,7 +78,7 @@ void FragmentTestCacheGLES::BindTestTexture(int slot) {
// We only need to do this once for the texture.
render_->SetTextureSampler(slot, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, GL_NEAREST, GL_NEAREST, 0.0f);
FragmentTestTexture item;
- item.lastFrame = gpuStats.numFlips;
+ item.lastFrame = gpuStats.totals.numFlips;
item.texture = tex;
cache_[id] = item;
}
@@ -156,7 +156,7 @@ void FragmentTestCacheGLES::Clear(bool deleteThem) {
void FragmentTestCacheGLES::Decimate() {
if (--decimationCounter_ <= 0) {
for (auto tex = cache_.begin(); tex != cache_.end(); ) {
- if (tex->second.lastFrame + FRAGTEST_TEXTURE_OLD_AGE < gpuStats.numFlips) {
+ if (tex->second.lastFrame + FRAGTEST_TEXTURE_OLD_AGE < gpuStats.totals.numFlips) {
render_->DeleteTexture(tex->second.texture);
cache_.erase(tex++);
} else {
diff --git a/GPU/GLES/GPU_GLES.cpp b/GPU/GLES/GPU_GLES.cpp
index d1d49f2076..75c7462186 100644
--- a/GPU/GLES/GPU_GLES.cpp
+++ b/GPU/GLES/GPU_GLES.cpp
@@ -21,6 +21,7 @@
#include "Common/Log.h"
#include "Common/Serialize/Serializer.h"
#include "Common/File/FileUtil.h"
+#include "Common/Data/Text/StringWriter.h"
#include "Common/GraphicsContext.h"
#include "Common/System/OSD.h"
#include "Common/VR/PPSSPPVR.h"
@@ -248,7 +249,7 @@ void GPU_GLES::BeginHostFrame(const DisplayLayoutConfig &config) {
// Save the cache from time to time. TODO: How often? We save on exit, so shouldn't need to do this all that often.
constexpr int saveShaderCacheFrameInterval = 32767; // power of 2 - 1. About every 10 minutes at 60fps.
- if (shaderCachePath_.Valid() && !(gpuStats.numFlips & saveShaderCacheFrameInterval) && coreState == CORE_RUNNING_CPU) {
+ if (shaderCachePath_.Valid() && !(gpuStats.totals.numFlips & saveShaderCacheFrameInterval) && coreState == CORE_RUNNING_CPU) {
shaderManagerGL_->SaveCache(shaderCachePath_, &drawEngine_);
}
shaderManagerGL_->DirtyLastShader();
@@ -278,16 +279,11 @@ void GPU_GLES::FinishDeferred() {
drawEngine_.FinishDeferred();
}
-void GPU_GLES::GetStats(char *buffer, size_t bufsize) {
- size_t offset = FormatGPUStatsCommon(buffer, bufsize);
- buffer += offset;
- bufsize -= offset;
- if ((int)bufsize < 0)
- return;
- snprintf(buffer, bufsize,
- "Vertex, Fragment, Programs loaded: %d, %d, %d\n",
+void GPU_GLES::GetStats(StringWriter &w) {
+ w.F("Vertex, Fragment, Programs loaded: %d, %d, %d\n",
shaderManagerGL_->GetNumVertexShaders(),
shaderManagerGL_->GetNumFragmentShaders(),
shaderManagerGL_->GetNumPrograms()
);
+ FormatGPUStatsCommon(w);
}
diff --git a/GPU/GLES/GPU_GLES.h b/GPU/GLES/GPU_GLES.h
index a1c6056734..d013b01f16 100644
--- a/GPU/GLES/GPU_GLES.h
+++ b/GPU/GLES/GPU_GLES.h
@@ -39,7 +39,7 @@ public:
// This gets called on startup and when we get back from settings.
u32 CheckGPUFeatures() const override;
- void GetStats(char *buffer, size_t bufsize) override;
+ void GetStats(StringWriter &w) override;
void DeviceLost() override; // Only happens on Android. Drop all textures and shaders.
void DeviceRestore(Draw::DrawContext *draw) override;
diff --git a/GPU/GPU.h b/GPU/GPU.h
index 107db847bd..09f6ca92d2 100644
--- a/GPU/GPU.h
+++ b/GPU/GPU.h
@@ -68,61 +68,8 @@ inline unsigned int toFloat24(float f) {
return i >> 8;
}
-// The ToString function lives in GPUCommonHW.cpp.
-struct GPUStatistics {
- void Reset() {
- ResetFrame();
- numFlips = 0;
- }
-
- void ResetFrame() {
- numDrawCalls = 0;
- numVertexDecodes = 0;
- numCulledDraws = 0;
- numDrawSyncs = 0;
- numListSyncs = 0;
- numVertsSubmitted = 0;
- numVertsDecoded = 0;
- numUncachedVertsDrawn = 0;
- numTextureInvalidations = 0;
- numTextureInvalidationsByFramebuffer = 0;
- numTexturesHashed = 0;
- numTextureDataBytesHashed = 0;
- numFlushes = 0;
- numBBOXJumps = 0;
- numTexturesDecoded = 0;
- numFramebufferEvaluations = 0;
- numFBOsCreated = 0;
- numBlockingReadbacks = 0;
- numReadbacks = 0;
- numUploads = 0;
- numCachedUploads = 0;
- numDepal = 0;
- numClears = 0;
- numDepthCopies = 0;
- numReinterpretCopies = 0;
- numColorCopies = 0;
- numCopiesForShaderBlend = 0;
- numCopiesForSelfTex = 0;
- numBlockTransfers = 0;
- numReplacerTrackedTex = 0;
- numCachedReplacedTextures = 0;
- numClutTextures = 0;
- msProcessingDisplayLists = 0;
- msPrepareDepth = 0.0;
- msCullDepth = 0.0;
- msRasterizeDepth = 0.0;
- msRasterTimeAvailable = 0.0;
- numDepthRasterPrims = 0;
- numDepthRasterEarlySize = 0;
- numDepthRasterNoPixels = 0;
- numDepthRasterTooSmall = 0;
- numDepthRasterZCulled = 0;
- numDepthEarlyBoxCulled = 0;
- vertexGPUCycles = 0;
- otherGPUCycles = 0;
- }
-
+// TODO: Possibly use macros to disable expensive parts of stats tracking in release builds.
+struct GPUStatsPerFrame {
// Per frame statistics
int numDrawCalls;
int numVertexDecodes;
@@ -169,10 +116,28 @@ struct GPUStatistics {
int numDepthRasterTooSmall;
int numDepthRasterZCulled;
int numDepthEarlyBoxCulled;
- // Flip count. Doesn't really belong here.
+};
+
+struct GPUStatsTotals {
int numFlips;
};
+// The ToString function lives in GPUCommonHW.cpp.
+struct GPUStatistics {
+ void Reset() {
+ ResetFrame();
+ totals = {};
+ }
+
+ void ResetFrame() {
+ perFrame = {};
+ }
+
+ // Flip count. Doesn't really belong here.
+ GPUStatsPerFrame perFrame;
+ GPUStatsTotals totals;
+};
+
extern GPUStatistics gpuStats;
extern GPUCommon *gpu;
extern GPUDebugInterface *gpuDebug;
diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp
index 7b0b363efe..2716e453a7 100644
--- a/GPU/GPUCommon.cpp
+++ b/GPU/GPUCommon.cpp
@@ -55,7 +55,7 @@ GPUCommon::GPUCommon(GraphicsContext *gfxCtx, Draw::DrawContext *draw) :
Reinitialize();
gstate.Reset();
gstate_c.Reset();
- gpuStats.Reset();
+ gpuStats.ResetFrame();
PPGeSetDrawContext(draw);
ResetMatrices();
@@ -172,7 +172,7 @@ void GPUCommon::DumpNextFrame() {
}
u32 GPUCommon::DrawSync(int mode) {
- gpuStats.numDrawSyncs++;
+ gpuStats.perFrame.numDrawSyncs++;
if (mode < 0 || mode > 1)
return SCE_KERNEL_ERROR_INVALID_MODE;
@@ -222,7 +222,7 @@ void GPUCommon::CheckDrawSync() {
}
int GPUCommon::ListSync(int listid, int mode) {
- gpuStats.numListSyncs++;
+ gpuStats.perFrame.numListSyncs++;
if (listid < 0 || listid >= DisplayListMaxCount)
return SCE_KERNEL_ERROR_INVALID_ID;
@@ -737,7 +737,7 @@ DLResult GPUCommon::ProcessDLQueue() {
}
}
- TimeCollector collectStat(&gpuStats.msProcessingDisplayLists, coreCollectDebugStats);
+ TimeCollector collectStat(&gpuStats.perFrame.msProcessingDisplayLists, coreCollectDebugStats);
auto GetNextListIndex = [&]() -> int {
if (dlQueue.empty())
@@ -866,7 +866,7 @@ DLResult GPUCommon::ProcessDLQueue() {
currentList = nullptr;
if (coreCollectDebugStats) {
- gpuStats.otherGPUCycles += cyclesExecuted;
+ gpuStats.perFrame.otherGPUCycles += cyclesExecuted;
}
drawCompleteTicks = startingTicks + cyclesExecuted;
@@ -915,7 +915,7 @@ void GPUCommon::Execute_BJump(u32 op, u32 diff) {
if (!currentList->bboxResult) {
// bounding box jump.
const u32 target = gstate_c.getRelativeAddress(op & 0x00FFFFFC);
- gpuStats.numBBOXJumps++;
+ gpuStats.perFrame.numBBOXJumps++;
if (Memory::IsValidAddress(target)) {
UpdatePC(currentList->pc, target - 4);
currentList->pc = target - 4; // pc will be increased after we return, counteract that
@@ -1400,7 +1400,7 @@ void GPUCommon::FastLoadBoneMatrix(u32 target) {
cyclesExecuted += 2 * 14; // one to reset the counter, 12 to load the matrix, and a return.
if (coreCollectDebugStats) {
- gpuStats.otherGPUCycles += 2 * 14;
+ gpuStats.perFrame.otherGPUCycles += 2 * 14;
}
}
@@ -1720,7 +1720,7 @@ void GPUCommon::DoBlockTransfer(u32 skipDrawReason) {
int bpp = gstate.getTransferBpp();
DEBUG_LOG(Log::G3D, "Block transfer: %08x/%x -> %08x/%x, %ix%ix%i (%i,%i)->(%i,%i)", srcBasePtr, srcStride, dstBasePtr, dstStride, width, height, bpp, srcX, srcY, dstX, dstY);
- gpuStats.numBlockTransfers++;
+ gpuStats.perFrame.numBlockTransfers++;
// For VRAM, we wrap around when outside valid memory (mirrors still work.)
if ((srcBasePtr & 0x04800000) == 0x04800000)
@@ -2090,10 +2090,10 @@ GPUDebug::NotifyResult GPUCommon::NotifyCommand(u32 pc, GPUBreakpoints *breakpoi
u32 op = Memory::ReadUnchecked_U32(pc);
u32 cmd = op >> 24;
- if (thisFlipNum_ != gpuStats.numFlips) {
+ if (thisFlipNum_ != gpuStats.totals.numFlips) {
primsLastFrame_ = primsThisFrame_;
primsThisFrame_ = 0;
- thisFlipNum_ = gpuStats.numFlips;
+ thisFlipNum_ = gpuStats.totals.numFlips;
}
bool isPrim = false;
diff --git a/GPU/GPUCommonHW.cpp b/GPU/GPUCommonHW.cpp
index 06dea0b896..53ce543832 100644
--- a/GPU/GPUCommonHW.cpp
+++ b/GPU/GPUCommonHW.cpp
@@ -3,6 +3,7 @@
#include "Common/GPU/thin3d.h"
#include "Common/Serialize/Serializer.h"
#include "Common/System/System.h"
+#include "Common/Data/Text/StringWriter.h"
#include "Core/System.h"
#include "Core/Config.h"
@@ -770,9 +771,9 @@ void GPUCommonHW::CheckDepthUsage(VirtualFramebuffer *vfb) {
if (isWritingDepth || isReadingDepth) {
gstate_c.usingDepth = true;
gstate_c.clearingDepth = isClearingDepth;
- vfb->last_frame_depth_render = gpuStats.numFlips;
+ vfb->last_frame_depth_render = gpuStats.totals.numFlips;
if (isWritingDepth) {
- vfb->last_frame_depth_updated = gpuStats.numFlips;
+ vfb->last_frame_depth_updated = gpuStats.totals.numFlips;
}
framebufferManager_->SetDepthFrameBuffer(isClearingDepth);
}
@@ -980,7 +981,7 @@ void GPUCommonHW::Execute_Prim(u32 op, u32 diff) {
// Rough estimate, not sure what's correct.
cyclesExecuted += vertexCost_ * count;
if (gstate.isModeClear()) {
- gpuStats.numClears++;
+ gpuStats.perFrame.numClears++;
}
return;
}
@@ -1027,9 +1028,9 @@ void GPUCommonHW::Execute_Prim(u32 op, u32 diff) {
if ((vertexType & (GE_VTYPE_THROUGH_MASK | GE_VTYPE_POS_MASK | GE_VTYPE_IDX_MASK)) == (GE_VTYPE_THROUGH_MASK | GE_VTYPE_POS_FLOAT | GE_VTYPE_IDX_NONE)) {
int bytesRead = 0;
if (!drawEngineCommon_->TestBoundingBoxThrough(verts, count, decoder, vertexType, &bytesRead)) {
- gpuStats.numCulledDraws++;
+ gpuStats.perFrame.numCulledDraws++;
int cycles = vertexCost_ * count;
- gpuStats.vertexGPUCycles += cycles;
+ gpuStats.perFrame.vertexGPUCycles += cycles;
cyclesExecuted += cycles;
// NOTE! We still have to advance vertex pointers!
gstate_c.vertexAddr += bytesRead; // We know from the above check that it's not an indexed draw.
@@ -1057,7 +1058,7 @@ void GPUCommonHW::Execute_Prim(u32 op, u32 diff) {
if (drawEngineCommon_->TestBoundingBoxFast(gstate_c.worldviewproj, verts, count, decoder, vertexType)) {
passCulling = true;
} else {
- gpuStats.numCulledDraws++;
+ gpuStats.perFrame.numCulledDraws++;
}
}
@@ -1147,7 +1148,7 @@ void GPUCommonHW::Execute_Prim(u32 op, u32 diff) {
if (drawEngineCommon_->TestBoundingBoxFast(gstate_c.worldviewproj, verts, count, decoder, vertexType)) {
passCulling = true;
} else {
- gpuStats.numCulledDraws++;
+ gpuStats.perFrame.numCulledDraws++;
}
}
if (passCulling) {
@@ -1301,7 +1302,7 @@ bail:
}
int cycles = vertexCost_ * totalVertCount;
- gpuStats.vertexGPUCycles += cycles;
+ gpuStats.perFrame.vertexGPUCycles += cycles;
if (!PSP_CoreParameter().compat.flags().FastEmulatedGPU) {
cyclesExecuted += cycles;
@@ -1825,9 +1826,9 @@ int GPUCommonHW::ListSync(int listid, int mode) {
return GPUCommon::ListSync(listid, mode);
}
-size_t GPUCommonHW::FormatGPUStatsCommon(char *buffer, size_t size) {
- float vertexAverageCycles = gpuStats.numVertsSubmitted > 0 ? (float)gpuStats.vertexGPUCycles / (float)gpuStats.numVertsSubmitted : 0.0f;
- return snprintf(buffer, size,
+void GPUCommonHW::FormatGPUStatsCommon(StringWriter &w) {
+ float vertexAverageCycles = gpuStats.perFrame.numVertsSubmitted > 0 ? (float)gpuStats.perFrame.vertexGPUCycles / (float)gpuStats.perFrame.numVertsSubmitted : 0.0f;
+ w.F(
"DL processing time: %0.2f ms, %d drawsync, %d listsync\n"
"Draw: %d (%d dec, %d culled), flushes %d, clears %d, bbox jumps %d\n"
"Vertices: %d dec: %d drawn: %d\n"
@@ -1840,52 +1841,52 @@ size_t GPUCommonHW::FormatGPUStatsCommon(char *buffer, size_t size) {
"GPU cycles: %d (%0.1f per vertex)\n"
"Z-rast: %0.2f+%0.2f+%0.2f (total %0.2f/%0.2f) ms\n"
"Z-rast: %d prim, %d nopix, %d small, %d earlysize, %d zcull, %d box\n%s",
- gpuStats.msProcessingDisplayLists * 1000.0f,
- gpuStats.numDrawSyncs,
- gpuStats.numListSyncs,
- gpuStats.numDrawCalls,
- gpuStats.numVertexDecodes,
- gpuStats.numCulledDraws,
- gpuStats.numFlushes,
- gpuStats.numClears,
- gpuStats.numBBOXJumps,
- gpuStats.numVertsSubmitted,
- gpuStats.numVertsDecoded,
- gpuStats.numUncachedVertsDrawn,
+ gpuStats.perFrame.msProcessingDisplayLists * 1000.0f,
+ gpuStats.perFrame.numDrawSyncs,
+ gpuStats.perFrame.numListSyncs,
+ gpuStats.perFrame.numDrawCalls,
+ gpuStats.perFrame.numVertexDecodes,
+ gpuStats.perFrame.numCulledDraws,
+ gpuStats.perFrame.numFlushes,
+ gpuStats.perFrame.numClears,
+ gpuStats.perFrame.numBBOXJumps,
+ gpuStats.perFrame.numVertsSubmitted,
+ gpuStats.perFrame.numVertsDecoded,
+ gpuStats.perFrame.numUncachedVertsDrawn,
(int)framebufferManager_->NumVFBs(),
- gpuStats.numFramebufferEvaluations,
- gpuStats.numFBOsCreated,
+ gpuStats.perFrame.numFramebufferEvaluations,
+ gpuStats.perFrame.numFBOsCreated,
(int)textureCache_->NumLoadedTextures(),
- gpuStats.numTexturesDecoded,
- gpuStats.numTextureInvalidations,
- gpuStats.numTextureDataBytesHashed / 1024,
- gpuStats.numClutTextures,
- gpuStats.numBlockingReadbacks,
- gpuStats.numReadbacks,
- gpuStats.numUploads,
- gpuStats.numCachedUploads,
- gpuStats.numDepal,
- gpuStats.numBlockTransfers,
- gpuStats.numReplacerTrackedTex,
- gpuStats.numCachedReplacedTextures,
- gpuStats.numDepthCopies,
- gpuStats.numColorCopies,
- gpuStats.numReinterpretCopies,
- gpuStats.numCopiesForShaderBlend,
- gpuStats.numCopiesForSelfTex,
- gpuStats.vertexGPUCycles + gpuStats.otherGPUCycles,
+ gpuStats.perFrame.numTexturesDecoded,
+ gpuStats.perFrame.numTextureInvalidations,
+ gpuStats.perFrame.numTextureDataBytesHashed / 1024,
+ gpuStats.perFrame.numClutTextures,
+ gpuStats.perFrame.numBlockingReadbacks,
+ gpuStats.perFrame.numReadbacks,
+ gpuStats.perFrame.numUploads,
+ gpuStats.perFrame.numCachedUploads,
+ gpuStats.perFrame.numDepal,
+ gpuStats.perFrame.numBlockTransfers,
+ gpuStats.perFrame.numReplacerTrackedTex,
+ gpuStats.perFrame.numCachedReplacedTextures,
+ gpuStats.perFrame.numDepthCopies,
+ gpuStats.perFrame.numColorCopies,
+ gpuStats.perFrame.numReinterpretCopies,
+ gpuStats.perFrame.numCopiesForShaderBlend,
+ gpuStats.perFrame.numCopiesForSelfTex,
+ gpuStats.perFrame.vertexGPUCycles + gpuStats.perFrame.otherGPUCycles,
vertexAverageCycles,
- gpuStats.msPrepareDepth * 1000.0,
- gpuStats.msCullDepth * 1000.0,
- gpuStats.msRasterizeDepth * 1000.0,
- (gpuStats.msPrepareDepth + gpuStats.msCullDepth + gpuStats.msRasterizeDepth) * 1000.0,
- gpuStats.msRasterTimeAvailable * 1000.0,
- gpuStats.numDepthRasterPrims,
- gpuStats.numDepthRasterNoPixels,
- gpuStats.numDepthRasterTooSmall,
- gpuStats.numDepthRasterEarlySize,
- gpuStats.numDepthRasterZCulled,
- gpuStats.numDepthEarlyBoxCulled,
+ gpuStats.perFrame.msPrepareDepth * 1000.0,
+ gpuStats.perFrame.msCullDepth * 1000.0,
+ gpuStats.perFrame.msRasterizeDepth * 1000.0,
+ (gpuStats.perFrame.msPrepareDepth + gpuStats.perFrame.msCullDepth + gpuStats.perFrame.msRasterizeDepth) * 1000.0,
+ gpuStats.perFrame.msRasterTimeAvailable * 1000.0,
+ gpuStats.perFrame.numDepthRasterPrims,
+ gpuStats.perFrame.numDepthRasterNoPixels,
+ gpuStats.perFrame.numDepthRasterTooSmall,
+ gpuStats.perFrame.numDepthRasterEarlySize,
+ gpuStats.perFrame.numDepthRasterZCulled,
+ gpuStats.perFrame.numDepthEarlyBoxCulled,
debugRecording_ ? "(debug-recording)" : ""
);
}
diff --git a/GPU/GPUCommonHW.h b/GPU/GPUCommonHW.h
index 6b1032c91e..b795c1d37e 100644
--- a/GPU/GPUCommonHW.h
+++ b/GPU/GPUCommonHW.h
@@ -2,6 +2,8 @@
#include "GPUCommon.h"
+class StringWriter;
+
// Shared GPUCommon implementation for the HW backends.
// Things that are irrelevant for SoftGPU should live here.
class GPUCommonHW : public GPUCommon {
@@ -88,7 +90,7 @@ private:
void CheckFlushOp(int cmd, u32 diff);
protected:
- size_t FormatGPUStatsCommon(char *buf, size_t size);
+ void FormatGPUStatsCommon(StringWriter &w);
void UpdateCmdInfo() override;
void PreExecuteOp(u32 op, u32 diff) override;
diff --git a/GPU/GPUState.h b/GPU/GPUState.h
index 25eb866939..cdb5e9920f 100644
--- a/GPU/GPUState.h
+++ b/GPU/GPUState.h
@@ -17,6 +17,8 @@
#pragma once
+// Note: GPU statistics are in GPU.h.
+
#include "ppsspp_config.h"
#include "Common/CommonTypes.h"
diff --git a/GPU/Software/BinManager.cpp b/GPU/Software/BinManager.cpp
index 8b4f79c64b..fddafdd28e 100644
--- a/GPU/Software/BinManager.cpp
+++ b/GPU/Software/BinManager.cpp
@@ -21,6 +21,7 @@
#include "Common/Profiler/Profiler.h"
#include "Common/Thread/ThreadManager.h"
+#include "Common/Data/Text/StringWriter.h"
#include "Common/TimeUtil.h"
#include "Core/System.h"
#include "GPU/Common/TextureDecoder.h"
@@ -185,8 +186,8 @@ void BinManager::UpdateState() {
ClearDirty(SoftDirty::PIXEL_ALL | SoftDirty::SAMPLER_ALL | SoftDirty::RAST_ALL);
}
- if (lastFlipstats_ != gpuStats.numFlips) {
- lastFlipstats_ = gpuStats.numFlips;
+ if (lastFlipstats_ != gpuStats.totals.numFlips) {
+ lastFlipstats_ = gpuStats.totals.numFlips;
ResetStats();
}
@@ -672,7 +673,7 @@ bool BinManager::HasPendingRead(uint32_t start, uint32_t stride, uint32_t w, uin
return false;
}
-void BinManager::GetStats(char *buffer, size_t bufsize) {
+void BinManager::GetStats(StringWriter &w) {
double allTotal = 0.0;
double slowestTotalTime = 0.0;
const char *slowestTotalReason = nullptr;
@@ -695,9 +696,7 @@ void BinManager::GetStats(char *buffer, size_t bufsize) {
}
recentTotal += it.second;
}
-
- snprintf(buffer, bufsize,
- "Slowest individual flush: %s (%0.4f)\n"
+ w.F("Slowest individual flush: %s (%0.4f)\n"
"Slowest frame flush: %s (%0.4f)\n"
"Slowest recent flush: %s (%0.4f)\n"
"Total flush time: %0.4f (%05.2f%%, last 2: %05.2f%%)\n"
diff --git a/GPU/Software/BinManager.h b/GPU/Software/BinManager.h
index 45f5f964e2..73f9d5c8a5 100644
--- a/GPU/Software/BinManager.h
+++ b/GPU/Software/BinManager.h
@@ -182,6 +182,7 @@ struct BinDirtyRange {
void Expand(uint32_t newBase, uint32_t bpp, uint32_t stride, const DrawingCoords &tl, const DrawingCoords &br);
};
+class StringWriter;
class BinManager {
public:
BinManager();
@@ -207,7 +208,7 @@ public:
// Assumes you've also checked for a write (writes are partial so are automatically reads.)
bool HasPendingRead(uint32_t start, uint32_t stride, uint32_t w, uint32_t h);
- void GetStats(char *buffer, size_t bufsize);
+ void GetStats(StringWriter &w);
void ResetStats();
void SetDirty(SoftDirty flags) {
diff --git a/GPU/Software/SoftGpu.cpp b/GPU/Software/SoftGpu.cpp
index 02848f4bc9..e79b8fc39e 100644
--- a/GPU/Software/SoftGpu.cpp
+++ b/GPU/Software/SoftGpu.cpp
@@ -1273,8 +1273,8 @@ u32 SoftGPU::DrawSync(int mode) {
return GPUCommon::DrawSync(mode);
}
-void SoftGPU::GetStats(char *buffer, size_t bufsize) {
- drawEngine_->transformUnit.GetStats(buffer, bufsize);
+void SoftGPU::GetStats(StringWriter &w) {
+ drawEngine_->transformUnit.GetStats(w);
}
void SoftGPU::InvalidateCache(u32 addr, int size, GPUInvalidationType type)
diff --git a/GPU/Software/SoftGpu.h b/GPU/Software/SoftGpu.h
index 639e80ce6f..e75e9deac7 100644
--- a/GPU/Software/SoftGpu.h
+++ b/GPU/Software/SoftGpu.h
@@ -139,7 +139,7 @@ public:
void SetCurFramebufferDirty(bool dirty) override {}
void PrepareCopyDisplayToOutput(const DisplayLayoutConfig &config) override;
void CopyDisplayToOutput(const DisplayLayoutConfig &config) override;
- void GetStats(char *buffer, size_t bufsize) override;
+ void GetStats(StringWriter &w) override;
std::vector GetFramebufferList() const override { return std::vector(); }
void InvalidateCache(u32 addr, int size, GPUInvalidationType type) override;
void PerformWriteFormattedFromMemory(u32 addr, int size, int width, GEBufferFormat format) override;
diff --git a/GPU/Software/TransformUnit.cpp b/GPU/Software/TransformUnit.cpp
index cdabe8a1f9..3f6f31f257 100644
--- a/GPU/Software/TransformUnit.cpp
+++ b/GPU/Software/TransformUnit.cpp
@@ -892,9 +892,9 @@ void TransformUnit::Flush(GPUCommon *common, const char *reason) {
hasDraws_ = false;
}
-void TransformUnit::GetStats(char *buffer, size_t bufsize) {
+void TransformUnit::GetStats(StringWriter &w) {
// TODO: More stats?
- binner_->GetStats(buffer, bufsize);
+ binner_->GetStats(w);
}
void TransformUnit::FlushIfOverlap(GPUCommon *common, const char *reason, bool modifying, uint32_t addr, uint32_t stride, uint32_t w, uint32_t h) {
diff --git a/GPU/Software/TransformUnit.h b/GPU/Software/TransformUnit.h
index 29b1d07f54..eeb7b02e72 100644
--- a/GPU/Software/TransformUnit.h
+++ b/GPU/Software/TransformUnit.h
@@ -111,6 +111,7 @@ class VertexReader;
class SoftwareDrawEngine;
class SoftwareVertexReader;
+class StringWriter;
class TransformUnit {
public:
@@ -141,7 +142,7 @@ public:
void FlushIfOverlap(GPUCommon *common, const char *reason, bool modifying, uint32_t addr, uint32_t stride, uint32_t w, uint32_t h);
void NotifyClutUpdate(const void *src);
- void GetStats(char *buffer, size_t bufsize);
+ void GetStats(StringWriter &w);
void SetDirty(SoftDirty flags);
SoftDirty GetDirty();
diff --git a/GPU/Vulkan/DrawEngineVulkan.cpp b/GPU/Vulkan/DrawEngineVulkan.cpp
index 416184806e..79e911d500 100644
--- a/GPU/Vulkan/DrawEngineVulkan.cpp
+++ b/GPU/Vulkan/DrawEngineVulkan.cpp
@@ -395,7 +395,7 @@ void DrawEngineVulkan::Flush() {
gstate_c.vertexFullAlpha = gstate_c.vertexFullAlpha && ((hasColor && (gstate.materialupdate & 1)) || gstate.getMaterialAmbientA() == 255) && (!gstate.isLightingEnabled() || gstate.getAmbientA() == 255);
}
- gpuStats.numUncachedVertsDrawn += vertexCount;
+ gpuStats.perFrame.numUncachedVertsDrawn += vertexCount;
prim = IndexGenerator::GeneralPrim((GEPrimitiveType)drawInds_[0].prim);
// At this point, the output is always an indexed triangle/line/point list, no strips/fans.
diff --git a/GPU/Vulkan/GPU_Vulkan.cpp b/GPU/Vulkan/GPU_Vulkan.cpp
index 0f8246b506..799103fe53 100644
--- a/GPU/Vulkan/GPU_Vulkan.cpp
+++ b/GPU/Vulkan/GPU_Vulkan.cpp
@@ -25,6 +25,7 @@
#include "Common/File/FileUtil.h"
#include "Common/GraphicsContext.h"
#include "Common/StringUtils.h"
+#include "Common/Data/Text/StringWriter.h"
#include "Core/Config.h"
#include "Core/Reporting.h"
@@ -468,26 +469,17 @@ void GPU_Vulkan::DeviceRestore(Draw::DrawContext *draw) {
InitDeviceObjects();
}
-void GPU_Vulkan::GetStats(char *buffer, size_t bufsize) {
- size_t offset = FormatGPUStatsCommon(buffer, bufsize);
- buffer += offset;
- bufsize -= offset;
- if ((int)bufsize < 0)
- return;
+void GPU_Vulkan::GetStats(StringWriter &w) {
const DrawEngineVulkanStats &drawStats = drawEngine_.GetStats();
- char texStats[256];
- textureCacheVulkan_->GetStats(texStats, sizeof(texStats));
- snprintf(buffer, bufsize,
- "Vertex, Fragment, Pipelines loaded: %i, %i, %i\n"
- "Pushbuffer space used: Vtx %d, Idx %d\n"
- "%s\n",
+ w.F("Vertex, Fragment, Pipelines loaded: %i, %i, %i\n"
+ "Pushbuffer space used: Vtx %d, Idx %d\n",
shaderManagerVulkan_->GetNumVertexShaders(),
shaderManagerVulkan_->GetNumFragmentShaders(),
pipelineManager_->GetNumPipelines(),
drawStats.pushVertexSpaceUsed,
- drawStats.pushIndexSpaceUsed,
- texStats
- );
+ drawStats.pushIndexSpaceUsed);
+ textureCacheVulkan_->GetStats(w);
+ FormatGPUStatsCommon(w);
}
std::vector GPU_Vulkan::DebugGetShaderIDs(DebugShaderType type) {
diff --git a/GPU/Vulkan/GPU_Vulkan.h b/GPU/Vulkan/GPU_Vulkan.h
index 25475bc477..9ff6816ab0 100644
--- a/GPU/Vulkan/GPU_Vulkan.h
+++ b/GPU/Vulkan/GPU_Vulkan.h
@@ -32,6 +32,7 @@ class FramebufferManagerVulkan;
class ShaderManagerVulkan;
class LinkedShader;
class TextureCacheVulkan;
+class StringWriter;
class GPU_Vulkan : public GPUCommonHW {
public:
@@ -47,7 +48,7 @@ public:
void BeginHostFrame(const DisplayLayoutConfig &config) override;
void EndHostFrame() override;
- void GetStats(char *buffer, size_t bufsize) override;
+ void GetStats(StringWriter &w) override;
void DeviceLost() override; // Only happens on Android. Drop all textures and shaders.
void DeviceRestore(Draw::DrawContext *draw) override;
diff --git a/GPU/Vulkan/TextureCacheVulkan.cpp b/GPU/Vulkan/TextureCacheVulkan.cpp
index 242421e7f2..859963af67 100644
--- a/GPU/Vulkan/TextureCacheVulkan.cpp
+++ b/GPU/Vulkan/TextureCacheVulkan.cpp
@@ -1111,9 +1111,7 @@ bool TextureCacheVulkan::GetCurrentTextureDebug(GPUDebugBuffer &buffer, int leve
return true;
}
-void TextureCacheVulkan::GetStats(char *ptr, size_t size) {
- snprintf(ptr, size, "N/A");
-}
+void TextureCacheVulkan::GetStats(StringWriter &w) {}
std::vector TextureCacheVulkan::DebugGetSamplerIDs() const {
return samplerCache_.DebugGetSamplerIDs();
diff --git a/GPU/Vulkan/TextureCacheVulkan.h b/GPU/Vulkan/TextureCacheVulkan.h
index 20c5bbe299..58f458cff7 100644
--- a/GPU/Vulkan/TextureCacheVulkan.h
+++ b/GPU/Vulkan/TextureCacheVulkan.h
@@ -33,6 +33,7 @@ class DrawEngineVulkan;
class VulkanContext;
class VulkanTexture;
+class StringWriter;
class SamplerCache {
public:
@@ -76,7 +77,7 @@ public:
bool GetCurrentTextureDebug(GPUDebugBuffer &buffer, int level, bool *isFramebuffer) override;
- void GetStats(char *ptr, size_t size);
+ void GetStats(StringWriter &w);
std::vector DebugGetSamplerIDs() const;
std::string DebugGetSamplerString(const std::string &id, DebugShaderStringType stringType);
diff --git a/UI/DebugOverlay.cpp b/UI/DebugOverlay.cpp
index 5e517c8910..85d5ebe1c3 100644
--- a/UI/DebugOverlay.cpp
+++ b/UI/DebugOverlay.cpp
@@ -4,7 +4,7 @@
#include "Common/Data/Text/I18n.h"
#include "Common/CPUDetect.h"
#include "Common/StringUtils.h"
-#include "Common/Data/Text/Parsers.h"
+#include "Common/Data/Text/StringWriter.h"
#include "Core/MIPS/MIPS.h"
#include "Core/HW/Display.h"
@@ -46,9 +46,20 @@ static void DrawDebugStats(UIContext *ctx, const Bounds &bounds) {
ctx->BindFontTexture();
ctx->Draw()->SetFontScale(.7f, .7f);
- __DisplayGetDebugStats(statbuf, sizeof(statbuf));
- ctx->Draw()->DrawTextRect(ubuntu24, statbuf, bounds.x + 11, bounds.y + 31, left, bounds.h - 30, 0xc0000000, FLAG_DYNAMIC_ASCII);
- ctx->Draw()->DrawTextRect(ubuntu24, statbuf, bounds.x + 10, bounds.y + 30, left, bounds.h - 30, 0xFFFFFFFF, FLAG_DYNAMIC_ASCII);
+ StringWriter w(statbuf, sizeof(statbuf));
+ w.F("Kernel processing time: %0.2f ms\n"
+ "Slowest syscall: %s : %0.2f ms\n"
+ "Most active syscall: %s : %0.2f ms\n",
+ kernelStats.msInSyscalls * 1000.0f,
+ kernelStats.slowestSyscallName ? kernelStats.slowestSyscallName : "(none)",
+ kernelStats.slowestSyscallTime * 1000.0f,
+ kernelStats.summedSlowestSyscallName ? kernelStats.summedSlowestSyscallName : "(none)",
+ kernelStats.summedSlowestSyscallTime * 1000.0f);
+
+ __DisplayGetDebugStats(w);
+
+ ctx->Draw()->DrawTextRect(ubuntu24, w.as_view(), bounds.x + 11, bounds.y + 31, left, bounds.h - 30, 0xc0000000, FLAG_DYNAMIC_ASCII);
+ ctx->Draw()->DrawTextRect(ubuntu24, w.as_view(), bounds.x + 10, bounds.y + 30, left, bounds.h - 30, 0xFFFFFFFF, FLAG_DYNAMIC_ASCII);
ctx->Draw()->SetFontScale(1.0f, 1.0f);
ctx->Flush();
@@ -58,7 +69,7 @@ static void DrawDebugStats(UIContext *ctx, const Bounds &bounds) {
static void DrawAudioDebugStats(UIContext *ctx, const Bounds &bounds) {
FontID ubuntu24("UBUNTU24");
- char statbuf[4096] = { 0 };
+ char statbuf[4096];
System_AudioGetDebugStats(statbuf, sizeof(statbuf));
ctx->Flush();
@@ -82,14 +93,15 @@ static void DrawAudioDebugStats(UIContext *ctx, const Bounds &bounds) {
static void DrawControlDebug(UIContext *ctx, const ControlMapper &mapper, const Bounds &bounds) {
FontID ubuntu24("UBUNTU24");
- char statbuf[4096] = { 0 };
- mapper.GetDebugString(statbuf, sizeof(statbuf));
+ char statbuf[2048];
+ StringWriter w(statbuf, sizeof(statbuf));
+ mapper.GetDebugString(w);
ctx->Flush();
ctx->BindFontTexture();
ctx->Draw()->SetFontScale(0.5f, 0.5f);
- ctx->Draw()->DrawTextRect(ubuntu24, statbuf, bounds.x + 11, bounds.y + 31, bounds.w - 20, bounds.h - 30, 0xc0000000, FLAG_DYNAMIC_ASCII);
- ctx->Draw()->DrawTextRect(ubuntu24, statbuf, bounds.x + 10, bounds.y + 30, bounds.w - 20, bounds.h - 30, 0xFFFFFFFF, FLAG_DYNAMIC_ASCII);
+ ctx->Draw()->DrawTextRect(ubuntu24, w.as_view(), bounds.x + 11, bounds.y + 31, bounds.w - 20, bounds.h - 30, 0xc0000000, FLAG_DYNAMIC_ASCII);
+ ctx->Draw()->DrawTextRect(ubuntu24, w.as_view(), bounds.x + 10, bounds.y + 30, bounds.w - 20, bounds.h - 30, 0xFFFFFFFF, FLAG_DYNAMIC_ASCII);
ctx->Draw()->SetFontScale(1.0f, 1.0f);
ctx->Flush();
ctx->RebindTexture();
diff --git a/UI/ImDebugger/ImDebugger.cpp b/UI/ImDebugger/ImDebugger.cpp
index 8e8ffc073b..f27f8ab22c 100644
--- a/UI/ImDebugger/ImDebugger.cpp
+++ b/UI/ImDebugger/ImDebugger.cpp
@@ -7,7 +7,7 @@
#include "Common/StringUtils.h"
#include "Common/File/FileUtil.h"
#include "Common/Data/Format/IniFile.h"
-#include "Common/Data/Text/Parsers.h"
+#include "Common/Data/Text/StringWriter.h"
#include "Common/Log/LogManager.h"
#include "Common/TimeUtil.h"
#include "Core/Config.h"
@@ -2273,7 +2273,7 @@ void DrawHLEModules(ImConfig &config) {
break;
}
}
- w.F("%s 0x%08x %d %s", func.name, func.ID, strlen(func.argmask), amask.c_str()).endl();
+ w.F("%s 0x%08x %d %s", func.name, func.ID, (int)strlen(func.argmask), amask.c_str()).endl();
}
System_CopyStringToClipboard(w.as_view());
delete[] buffer;
diff --git a/UI/ImDebugger/ImGe.cpp b/UI/ImDebugger/ImGe.cpp
index ab38ea7eac..794f5f81ac 100644
--- a/UI/ImDebugger/ImGe.cpp
+++ b/UI/ImDebugger/ImGe.cpp
@@ -3,6 +3,7 @@
#include "ext/imgui/imgui_extras.h"
#include "ext/imgui/imgui_impl_thin3d.h"
#include "Common/Data/Convert/ColorConv.h"
+#include "Common/Data/Text/StringWriter.h"
#include "UI/ImDebugger/ImGe.h"
#include "UI/ImDebugger/ImDebugger.h"
#include "GPU/Common/GPUDebugInterface.h"
@@ -33,7 +34,7 @@ void DrawFramebuffersWindow(ImConfig &cfg, FramebufferManagerCommon *framebuffer
return;
}
- ImGui::Text("Cur frame: %d seq: %d", gpuStats.numFlips, framebufferManager->PeekBindSeqCount());
+ ImGui::Text("Cur frame: %d seq: %d", gpuStats.totals.numFlips, framebufferManager->PeekBindSeqCount());
ImGui::BeginTable("framebuffers", 7);
ImGui::TableSetupColumn("Tag", ImGuiTableColumnFlags_WidthFixed);
@@ -282,7 +283,8 @@ void DrawDebugStatsWindow(ImConfig &cfg) {
return;
}
char statbuf[4096];
- __DisplayGetDebugStats(statbuf, sizeof(statbuf));
+ StringWriter w(statbuf);
+ __DisplayGetDebugStats(w);
ImGui::TextUnformatted(statbuf);
ImGui::End();
}
diff --git a/UI/SystemInfoScreen.cpp b/UI/SystemInfoScreen.cpp
index 9c56946120..b31cabf19b 100644
--- a/UI/SystemInfoScreen.cpp
+++ b/UI/SystemInfoScreen.cpp
@@ -13,7 +13,7 @@
#include "Common/File/AndroidStorage.h"
#include "Common/Audio/AudioBackend.h"
#include "Common/Data/Text/I18n.h"
-#include "Common/Data/Text/Parsers.h"
+#include "Common/Data/Text/StringWriter.h"
#include "Common/Data/Encoding/Utf8.h"
#include "Common/Render/Text/draw_text.h"
#include "Common/System/Request.h"