/** * @file perf/counter.h * @brief Performance counter registry and profiler * * @copyright Copyright (c) 2026 Tom Clay * All rights reserved. * * @license BSD 3-Clause License * See LICENSE file in the project root for full license text. */ #pragma once #include #include #ifdef REXGLUE_ENABLE_PROFILING #include #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