evan 2026.1.4
Evan
Loading...
Searching...
No Matches
FrameStats.hpp
1/*
2** ETIB PROJECT, 2026
3** evan
4** File description:
5** FrameStats
6*/
7
8#pragma once
9
10#include "evan/EvanPlatform.hpp"
11
12#include <array>
13#include <chrono>
14#include <cstdint>
15#include <optional>
16
17namespace evan
18{
26 struct PhaseStats {
30 uint64_t lastNs = 0;
31
35 uint64_t totalNs = 0;
36
40 uint32_t samples = 0;
41
47 void add(uint64_t ns) noexcept
48 {
49 lastNs = ns;
50 totalNs += ns;
51 ++samples;
52 }
53
60 void resetWindow() noexcept
61 {
62 totalNs = 0;
63 samples = 0;
64 }
65
72 [[nodiscard]] uint64_t averageNs() const noexcept
73 {
74 return samples == 0 ? 0 : totalNs / samples;
75 }
76 };
77
86 {
87 public:
93 explicit ScopedTimer(PhaseStats *phase) noexcept
94 : _phase(phase)
95 , _start(phase != nullptr ? Clock::now() : TimePoint {})
96 {
97 }
98
102 ~ScopedTimer() noexcept
103 {
104 if (_phase == nullptr) {
105 return;
106 }
107 _phase->add(static_cast<uint64_t>(
108 std::chrono::duration_cast<std::chrono::nanoseconds>(
109 Clock::now() - _start)
110 .count()));
111 }
112
113 ScopedTimer(const ScopedTimer &) = delete;
114 ScopedTimer &operator=(const ScopedTimer &) = delete;
115 ScopedTimer(ScopedTimer &&) = delete;
116 ScopedTimer &operator=(ScopedTimer &&) = delete;
117
118 private:
119 using Clock = std::chrono::steady_clock;
120 using TimePoint = Clock::time_point;
121
122 PhaseStats *_phase;
123 TimePoint _start;
124 };
125
135 struct FrameStats {
139 bool enabled = false;
140
148 bool gpuAvailable = false;
149
150 // Frame-level CPU phases, one sample per frame each.
161
162 // Per-view-slot detail, indexed like the frame's view slots.
163 std::array<PhaseStats, MAX_SWAPCHAINS>
165 std::array<PhaseStats, MAX_SWAPCHAINS>
167 std::array<PhaseStats, MAX_SWAPCHAINS>
169 std::array<PhaseStats, MAX_SWAPCHAINS>
171
172 // Counters, accumulated over the current window.
173 uint64_t frames = 0;
174 uint64_t totalMeshes = 0;
175 uint64_t visibleMeshes = 0;
176 uint64_t culledMeshes = 0;
177 uint64_t skippedMeshes = 0;
178 uint64_t drawCalls = 0;
179 uint64_t instancedDraws = 0;
180 uint64_t pipelineBinds = 0;
181 uint64_t descriptorBinds = 0;
182 uint64_t syncCalls = 0;
183 uint64_t syncSkips = 0;
184 uint64_t meshCreations = 0;
185 uint64_t meshDestructions = 0;
186 uint64_t uploadWaits = 0;
187
195 [[nodiscard]] ScopedTimer timer(PhaseStats &phase) noexcept
196 {
197 return ScopedTimer(enabled ? &phase : nullptr);
198 }
199
206 void resetWindow() noexcept;
207 };
208
219 namespace stats
220 {
227 [[nodiscard]] bool enabled() noexcept;
228
237 void setEnabledForTesting(std::optional<bool> state) noexcept;
238
242 void countSync() noexcept;
243
247 void countSyncSkip() noexcept;
248
252 void countMeshCreation() noexcept;
253
257 void countMeshDestruction() noexcept;
258
263 void countUploadWait() noexcept;
264
274 void takeGlobalDeltas(FrameStats &stats) noexcept;
275
287 [[nodiscard]] inline uint64_t maskTimestamp(uint64_t value,
288 uint32_t validBits) noexcept
289 {
290 if (validBits >= 64) {
291 return value;
292 }
293 if (validBits == 0) {
294 return 0;
295 }
296 return value & ((uint64_t { 1 } << validBits) - 1);
297 }
298
310 [[nodiscard]] inline bool timestampDeltaNs(uint64_t begin, uint64_t end,
311 float period,
312 uint64_t &deltaNs) noexcept
313 {
314 if (end < begin || !(period > 0.0f)) {
315 return false;
316 }
317 deltaNs = static_cast<uint64_t>(static_cast<double>(end - begin)
318 * static_cast<double>(period));
319 return true;
320 }
321 } // namespace stats
322} // namespace evan
RAII timer feeding one PhaseStats.
~ScopedTimer() noexcept
Adds the elapsed time to the destination phase.
ScopedTimer(PhaseStats *phase) noexcept
Starts measuring into phase.
bool timestampDeltaNs(uint64_t begin, uint64_t end, float period, uint64_t &deltaNs) noexcept
Converts a timestamp delta to nanoseconds.
Per-frame instrumentation data (CPU phases and counters).
uint64_t uploadWaits
Blocking upload fence waits.
bool gpuAvailable
Whether GPU timestamp queries were created and are readable.
uint64_t drawCalls
vkCmdDrawIndexed(Indirect) count.
uint64_t totalMeshes
Meshes registered at draw time.
std::array< PhaseStats, MAX_SWAPCHAINS > submitView
submit, per view slot.
uint64_t pipelineBinds
vkCmdBindPipeline count.
std::array< PhaseStats, MAX_SWAPCHAINS > updateUniformView
updateUniform, per view slot.
PhaseStats record
Per-view command recording.
PhaseStats inFlightWait
Slot-reuse fence wait.
PhaseStats updateUniform
Per-view uniform buffer updates.
uint64_t culledMeshes
Meshes removed by culling.
uint64_t descriptorBinds
vkCmdBindDescriptorSets count.
uint64_t meshCreations
GPUMesh constructions.
PhaseStats submit
Per-view queue submissions.
ScopedTimer timer(PhaseStats &phase) noexcept
Creates a scoped timer for phase.
PhaseStats renderWait
OpenXR end-of-frame fence wait.
uint64_t frames
Frames in the window.
PhaseStats postprocess
OpenXR frame postprocess.
std::array< PhaseStats, MAX_SWAPCHAINS > recordView
record, per view slot.
PhaseStats transferFlush
Renderer::drawFrame: upload flush.
bool enabled
Whether CPU timers and the interval log are active.
uint64_t instancedDraws
Draws with instanceCount > 1.
uint64_t syncSkips
sync() calls short-circuited.
uint64_t syncCalls
RessourceManager::sync() calls.
uint64_t meshDestructions
GPUMesh destructions.
PhaseStats present
Present/composition (all sets).
void resetWindow() noexcept
Starts a new report window.
uint64_t visibleMeshes
Meshes surviving culling.
std::array< PhaseStats, MAX_SWAPCHAINS > gpuPass
GPU pass time, per view slot.
PhaseStats preprocess
OpenXR frame preprocess.
uint64_t skippedMeshes
Meshes skipped (bad state).
PhaseStats acquire
Swapchain image acquire (all sets).
One accumulated CPU phase, in nanoseconds.
void add(uint64_t ns) noexcept
Records one sample.
uint64_t averageNs() const noexcept
Average duration of the window.
uint32_t samples
Number of samples of the current window.
uint64_t totalNs
Sum of the samples of the current window, in nanoseconds.
uint64_t lastNs
Most recent sample, in nanoseconds.
void resetWindow() noexcept
Starts a new window.