186 lines
5.5 KiB
C++
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
|