#ifndef KYTY_COMMON_DEBUG_COUNTERS_H_ #define KYTY_COMMON_DEBUG_COUNTERS_H_ #include #include #include namespace Common::DebugCounters { static_assert(std::atomic::is_always_lock_free && std::atomic::is_always_lock_free); // Collect events only while the panel is visible. Relaxed, lock-free atomics keep updates // cheap and safe in signal handlers. Rates come from comparing two snapshots. enum class Counter { // GPU work. Draws, DrawInstances, IndirectDraws, Dispatches, QueueSubmits, GpuWaits, // CPU blocked on a GPU timeline semaphore. GpuWaitNs, GpuFaults, // Guest writes to GPU-tracked memory. ShadersCompiled, ShaderCompileNs, PipelinesCreated, PipelineCreateNs, // Caches. TextureUploadBytes, TextureDownloadBytes, TextureEvictions, BufferUploadBytes, BufferDownloadBytes, BufferEvictions, // Presentation. FlipsRequested, FlipsRejected, // Guest flip queue was full. LateVblanks, // The present thread fell behind the vblank rate. SwapchainRecreations, DroppedPresents, // Emulator. EmulatedInstructions, // Host CPU lacked an instruction the guest used. UnresolvedImportCalls, GuestFileReadBytes, GuestFileWriteBytes, AudioUnderruns, Count }; enum class Peak { ShaderCompileNs, PipelineCreateNs, Count }; namespace Detail { inline std::atomic g_enabled {false}; inline std::atomic g_counters[static_cast(Counter::Count)] {}; inline std::atomic g_peaks[static_cast(Peak::Count)] {}; } // namespace Detail [[nodiscard]] inline bool Enabled() noexcept { return Detail::g_enabled.load(std::memory_order_relaxed); } inline void SetEnabled(bool enabled) noexcept { Detail::g_enabled.store(enabled, std::memory_order_relaxed); } inline void Add(Counter counter, uint64_t value = 1) noexcept { if (!Enabled()) { return; } Detail::g_counters[static_cast(counter)].fetch_add(value, std::memory_order_relaxed); } class ScopedTimer final { public: ScopedTimer(Counter count, Counter duration, Peak peak = Peak::Count) : m_count(count), m_duration(duration), m_peak(peak), m_start(Enabled() ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point {}) {} ~ScopedTimer() { if (m_start == std::chrono::steady_clock::time_point {} || !Enabled()) { return; } const auto elapsed = static_cast(std::chrono::duration_cast( std::chrono::steady_clock::now() - m_start) .count()); Add(m_count); Add(m_duration, elapsed); if (m_peak != Peak::Count) { auto& peak = Detail::g_peaks[static_cast(m_peak)]; auto previous = peak.load(std::memory_order_relaxed); while (previous < elapsed && !peak.compare_exchange_weak(previous, elapsed, std::memory_order_relaxed)) { } } } ScopedTimer(const ScopedTimer&) = delete; ScopedTimer& operator=(const ScopedTimer&) = delete; private: Counter m_count; Counter m_duration; Peak m_peak; std::chrono::steady_clock::time_point m_start; }; [[nodiscard]] inline uint64_t Get(Counter counter) noexcept { return Detail::g_counters[static_cast(counter)].load(std::memory_order_relaxed); } [[nodiscard]] inline uint64_t Get(Peak peak) noexcept { return Detail::g_peaks[static_cast(peak)].load(std::memory_order_relaxed); } } // namespace Common::DebugCounters #endif /* KYTY_COMMON_DEBUG_COUNTERS_H_ */