rexglue-skate3/include/rex/perf/counter.h
2026-05-18 22:41:13 +01:00

186 lines
5.5 KiB
C++

/**
* @file perf/counter.h
* @brief Performance counter registry and profiler
*
* @copyright Copyright (c) 2026 Tom Clay <tomc@tctechstuff.com>
* All rights reserved.
*
* @license BSD 3-Clause License
* See LICENSE file in the project root for full license text.
*/
#pragma once
#include <cstdint>
#include <string>
#ifdef REXGLUE_ENABLE_PROFILING
#include <tracy/Tracy.hpp>
#endif
namespace rex::perf {
enum class CounterId : uint16_t {
// Frame
kFrameTimeUs,
kFps,
// GPU
kDrawCalls,
kCommandBufferStalls,
kVerticesProcessed,
// Audio
kXmaFramesDecoded,
kAudioFrameLatencyUs,
kBufferQueueDepth,
// Dispatch
kFunctionsDispatched,
kInterruptDispatches,
// Threading
kActiveThreads,
kApcQueueDepth,
kCriticalRegionContentions,
// Caches
kTextureCacheHits,
kTextureCacheMisses,
kPipelineCacheHits,
kPipelineCacheMisses,
kCount // sentinel -- must be last
};
// Returns human-readable name for a counter (e.g. "frame_time_us")
const char* CounterName(CounterId id);
// Set a counter to an absolute value
void SetCounter(CounterId id, int64_t value);
// Atomically add to a counter
void IncrementCounter(CounterId id, int64_t delta = 1);
// Read a counter's current live value
int64_t GetCounter(CounterId id);
// Snapshot current values into the read buffer and zero the live counters.
// Called once per frame by Profiler::Flip().
void ResetFrameCounters();
// Read a counter from the last-frame snapshot (stable between frames).
int64_t GetSnapshotCounter(CounterId id);
// Initialize the counter system (zeroes everything). Safe to call multiple times.
void Init();
// CSV logging
void SetCsvLogPath(const std::string& path);
void WriteCsvFrame();
void FlushCsv();
// Profiler -- coordinates Tracy frame marks and counter snapshots.
// Moved here from rex::debug to consolidate all perf code under rex::perf.
class Profiler {
public:
// Call once at runtime startup to enable Tracy's network threads.
// CLI tools should skip this to avoid socket listeners entirely.
static void Startup() {
#ifdef REXGLUE_ENABLE_PROFILING
tracy::StartupProfiler();
#endif
}
static void OnThreadEnter(const char* name = nullptr) {
#ifdef REXGLUE_ENABLE_PROFILING
if (name)
tracy::SetThreadName(name);
#else
(void)name;
#endif
}
static void OnThreadExit() {}
static void ThreadEnter(const char* name = nullptr) { OnThreadEnter(name); }
static void ThreadExit() {}
static void Flip() {
#ifdef REXGLUE_ENABLE_PROFILING
FrameMark;
#endif
#ifdef REXGLUE_ENABLE_PERF_COUNTERS
ResetFrameCounters();
WriteCsvFrame();
#endif
}
static void Flush() {}
static void Shutdown() {
#ifdef REXGLUE_ENABLE_PROFILING
tracy::ShutdownProfiler();
#endif
#ifdef REXGLUE_ENABLE_PERF_COUNTERS
FlushCsv();
#endif
}
static bool is_enabled() {
#ifdef REXGLUE_ENABLE_PROFILING
return tracy::IsProfilerStarted();
#else
return false;
#endif
}
};
} // namespace rex::perf
// Perf counter macros -- compile to no-ops when counters are disabled.
#ifdef REXGLUE_ENABLE_PERF_COUNTERS
// Generic helpers for easily adding new counters
#define PERF_counter_set(id, value) rex::perf::SetCounter(rex::perf::CounterId::id, value)
#define PERF_counter_inc(id) rex::perf::IncrementCounter(rex::perf::CounterId::id)
#define PERF_counter_add(id, delta) rex::perf::IncrementCounter(rex::perf::CounterId::id, delta)
// Purpose-specific macros so callsites stay clean
#define PROFILE_FRAME_TIME_US(value) PERF_counter_set(kFrameTimeUs, value)
#define PROFILE_FPS(value) PERF_counter_set(kFps, value)
#define PROFILE_FUNCTION_DISPATCHED() PERF_counter_inc(kFunctionsDispatched)
#define PROFILE_INTERRUPT_DISPATCHED() PERF_counter_inc(kInterruptDispatches)
#define PROFILE_XMA_FRAME_DECODED() PERF_counter_inc(kXmaFramesDecoded)
#define PROFILE_DRAW_CALL() PERF_counter_inc(kDrawCalls)
#define PROFILE_VERTICES(n) PERF_counter_add(kVerticesProcessed, n)
#define PROFILE_CMD_BUFFER_STALL() PERF_counter_inc(kCommandBufferStalls)
#define PROFILE_AUDIO_LATENCY_US(value) PERF_counter_set(kAudioFrameLatencyUs, value)
#define PROFILE_BUFFER_QUEUE_DEPTH(value) PERF_counter_set(kBufferQueueDepth, value)
#define PROFILE_THREAD_CREATED() PERF_counter_inc(kActiveThreads)
#define PROFILE_THREAD_EXITED() PERF_counter_add(kActiveThreads, -1)
#define PROFILE_APC_QUEUE_DEPTH(value) PERF_counter_set(kApcQueueDepth, value)
#define PROFILE_CRITICAL_REGION_CONTENTION() PERF_counter_inc(kCriticalRegionContentions)
#define PROFILE_TEXTURE_CACHE_HIT() PERF_counter_inc(kTextureCacheHits)
#define PROFILE_TEXTURE_CACHE_MISS() PERF_counter_inc(kTextureCacheMisses)
#define PROFILE_PIPELINE_CACHE_HIT() PERF_counter_inc(kPipelineCacheHits)
#define PROFILE_PIPELINE_CACHE_MISS() PERF_counter_inc(kPipelineCacheMisses)
#else
#define PERF_counter_set(id, value)
#define PERF_counter_inc(id)
#define PERF_counter_add(id, delta)
#define PROFILE_FRAME_TIME_US(value)
#define PROFILE_FPS(value)
#define PROFILE_FUNCTION_DISPATCHED()
#define PROFILE_INTERRUPT_DISPATCHED()
#define PROFILE_XMA_FRAME_DECODED()
#define PROFILE_DRAW_CALL()
#define PROFILE_VERTICES(n)
#define PROFILE_CMD_BUFFER_STALL()
#define PROFILE_AUDIO_LATENCY_US(value)
#define PROFILE_BUFFER_QUEUE_DEPTH(value)
#define PROFILE_THREAD_CREATED()
#define PROFILE_THREAD_EXITED()
#define PROFILE_APC_QUEUE_DEPTH(value)
#define PROFILE_CRITICAL_REGION_CONTENTION()
#define PROFILE_TEXTURE_CACHE_HIT()
#define PROFILE_TEXTURE_CACHE_MISS()
#define PROFILE_PIPELINE_CACHE_HIT()
#define PROFILE_PIPELINE_CACHE_MISS()
#endif