4#include "imgui_widgets/utils.h"
5#include <imgui/imgui.h>
6#include <imgui/imgui_internal.h>
25constexpr float overlay_width = 460.0f;
26constexpr float overlay_padding = 8.0f;
27constexpr float overlay_rounding = 4.0f;
28constexpr float overlay_bg_alpha = 0.75f;
29constexpr ImVec4 overlay_bg_color{0.08f, 0.08f, 0.08f, overlay_bg_alpha};
31constexpr ImVec4 color_good{0.2f, 0.8f, 0.2f, 1.0f};
32constexpr ImVec4 color_warning{1.0f, 0.7f, 0.0f, 1.0f};
33constexpr ImVec4 color_bad{1.0f, 0.3f, 0.3f, 1.0f};
34constexpr ImVec4 color_label{0.6f, 0.6f, 0.6f, 1.0f};
37constexpr ImVec4 bar_good{0.30f, 0.49f, 0.32f, 1.0f};
38constexpr ImVec4 bar_warning{0.58f, 0.45f, 0.20f, 1.0f};
39constexpr ImVec4 bar_bad{0.55f, 0.28f, 0.28f, 1.0f};
41auto get_fps_color(
float fps) -> ImVec4
54auto get_memory_color(
float percentage) -> ImVec4
56 if(percentage > 80.0f)
60 if(percentage > 60.0f)
67void draw_label(
const char* label)
69 ImGui::TextColored(color_label,
"%s", label);
70 ImGui::SameLine(200.0f);
73void draw_eviction_overlay_stats()
76 if(evict_stats.registered_count == 0)
85 ImGui::SetItemTooltipEx(
"GPU resource eviction / paging. Idle CPU-backed resources are released\n"
86 "from GPU memory under pressure and restored automatically on next use.");
89 if(evict_stats.budget_bytes > 0)
91 const bool over_budget = evict_stats.budget_used_bytes > evict_stats.budget_bytes;
92 const auto used_str =
format_bytes(
static_cast<int64_t
>(evict_stats.budget_used_bytes));
93 const auto budget_str =
format_bytes(
static_cast<int64_t
>(evict_stats.budget_bytes));
96 draw_label(
" Budget");
99 ImGui::TextColored(color_bad,
"%s / %s " ICON_MDI_ALERT " OVER", used_str.c_str(), budget_str.c_str());
103 ImGui::TextColored(color_good,
"%s / %s", used_str.c_str(), budget_str.c_str());
106 ImGui::SetItemTooltipEx(
"GPU memory used vs. the eviction budget. When usage exceeds the\n"
107 "budget, resources are evicted down to the target watermark.\n"
108 "Marked red when over budget.");
111 draw_label(
" Target");
112 ImGui::TextUnformatted(
format_bytes(
static_cast<int64_t
>(evict_stats.target_bytes)).c_str());
114 ImGui::SetItemTooltipEx(
"Watermark the pager evicts down to once over budget\n"
115 "(hysteresis below the budget to avoid thrashing).");
119 const auto resident_str = fmt::format(
"{} ({})",
120 evict_stats.resident_count,
121 format_bytes(
static_cast<int64_t
>(evict_stats.resident_bytes)));
123 draw_label(
" Resident");
124 ImGui::TextUnformatted(resident_str.c_str());
126 ImGui::SetItemTooltipEx(
"Resources currently resident on the GPU that can still be paged out,\n"
127 "and their total GPU memory.");
129 const auto evicted_str = fmt::format(
"{} ({})",
130 evict_stats.evicted_count,
131 format_bytes(
static_cast<int64_t
>(evict_stats.evicted_bytes)));
133 draw_label(
" Evicted");
134 if(evict_stats.evicted_count > 0)
136 ImGui::TextColored(color_warning,
"%s", evicted_str.c_str());
140 ImGui::TextUnformatted(evicted_str.c_str());
143 ImGui::SetItemTooltipEx(
"Resources currently evicted (their GPU memory reclaimed).\n"
144 "They restore automatically the next time they are used.");
148 const double pool_bytes =
static_cast<double>(evict_stats.resident_bytes + evict_stats.evicted_bytes);
149 const float resident_fraction =
150 pool_bytes > 0.0 ?
static_cast<float>(
static_cast<double>(evict_stats.resident_bytes) / pool_bytes) : 0.0f;
151 ImVec4 headroom_color = bar_good;
152 if(evict_stats.resident_bytes == 0)
154 headroom_color = bar_bad;
156 else if(resident_fraction < 0.2f)
158 headroom_color = bar_warning;
160 const auto headroom_overlay = fmt::format(
"{} free",
format_bytes(
static_cast<int64_t
>(evict_stats.resident_bytes)));
162 draw_label(
" Headroom");
163 ImGui::PushStyleColor(ImGuiCol_PlotHistogram, headroom_color);
165 ImGui::ProgressBar(resident_fraction, ImVec2(ImGui::GetContentRegionAvail().
x, 0.0f), headroom_overlay.c_str());
166 ImGui::PopStyleColor();
168 ImGui::SetItemTooltipEx(
"Share of the managed pool still resident on the GPU - how much can\n"
169 "still be paged out under further memory pressure. Red when nothing\n"
170 "is left to evict.");
174 draw_label(
" Lifetime");
175 ImGui::TextUnformatted(fmt::format(
"{} evicted", evict_stats.total_evictions).c_str());
176 if(evict_stats.total_restores > 0)
179 ImGui::TextColored(color_label,
"%s", fmt::format(
"/ {} restored", evict_stats.total_restores).c_str());
182 ImGui::SetItemTooltipEx(
"Lifetime evictions (and restores). If the eviction count keeps\n"
183 "climbing every frame, paging is happening constantly - consider\n"
184 "raising the budget or the min-age window.");
186 if(evict_stats.thrash_events > 0)
189 draw_label(
" Thrash");
190 ImGui::TextColored(color_warning,
"%s", fmt::format(
"{}", evict_stats.thrash_events).c_str());
192 ImGui::SetItemTooltipEx(
"Resources restored shortly after being evicted. High values\n"
193 "indicate the budget is too tight; raise it or the min-age window.");
197void draw_performance_section()
199 if(!ImGui::CollapsingSection(
ICON_MDI_SPEEDOMETER "\tPerformance", ImGuiTreeNodeFlags_DefaultOpen))
204 auto& io = ImGui::GetIO();
206 const double to_cpu_ms = 1000.0 /
static_cast<double>(
stats->cpuTimerFreq);
207 const double to_gpu_ms = 1000.0 /
static_cast<double>(
stats->gpuTimerFreq);
209 const float fps = io.Framerate;
210 const float frame_ms = 1000.0f / fps;
213 ImGui::TextColored(get_fps_color(fps),
" FPS: %.1f",
static_cast<double>(fps));
215 ImGui::TextColored(color_label,
"(%.2f ms)",
static_cast<double>(frame_ms));
217 ImGui::SetItemTooltipEx(
"Frames per second (higher is better)\n"
218 "Green: >55 FPS (smooth)\n"
219 "Yellow: 30-55 FPS (acceptable)\n"
220 "Red: <30 FPS (poor performance)");
222 const double cpu_submit_ms =
static_cast<double>(
stats->cpuTimeEnd -
stats->cpuTimeBegin) * to_cpu_ms;
223 const double gpu_submit_ms =
static_cast<double>(
stats->gpuTimeEnd -
stats->gpuTimeBegin) * to_gpu_ms;
226 draw_label(
" CPU Submit");
227 ImGui::Text(
"%.3f ms", cpu_submit_ms);
229 ImGui::SetItemTooltipEx(
"Time the CPU spent submitting render commands\n"
230 "to the graphics driver. High values indicate\n"
231 "a CPU-bound rendering bottleneck.");
234 draw_label(
" GPU Submit");
235 ImGui::Text(
"%.3f ms", gpu_submit_ms);
237 ImGui::SetItemTooltipEx(
"Time the GPU spent executing render commands.\n"
238 "High values indicate a GPU-bound bottleneck\n"
239 "such as complex shaders or high fill rate.");
242 draw_label(
" GPU Latency");
243 ImGui::Text(
"%d frames",
stats->maxGpuLatency);
245 ImGui::SetItemTooltipEx(
"Number of frames the GPU is behind the CPU.\n"
246 "Higher latency means more input lag but can\n"
247 "improve throughput. Typically 1-3 frames.");
250void draw_scene_section()
258 const auto& io = ImGui::GetIO();
260 const std::uint32_t total_primitives = std::accumulate(
261 std::begin(
stats->numPrims), std::end(
stats->numPrims), 0
u);
262 std::uint32_t ui_primitives = io.MetricsRenderIndices / 3;
263 ui_primitives = std::min(ui_primitives, total_primitives);
264 const auto scene_primitives = total_primitives - ui_primitives;
266 std::uint32_t total_calls =
stats->numDraw;
268 editor_calls = std::min(editor_calls, total_calls);
269 std::uint32_t scene_calls = total_calls - editor_calls;
271 auto format_count = [](std::uint32_t
count) -> std::string
275 return fmt::format(
"{:.1f}M",
static_cast<double>(
count) / 1000000.0);
279 return fmt::format(
"{:.1f}k",
static_cast<double>(
count) / 1000.0);
281 return fmt::format(
"{}",
count);
285 draw_label(
" Triangles");
286 ImGui::TextUnformatted(format_count(scene_primitives).c_str());
288 ImGui::SetItemTooltipEx(
"Total triangle count rendered for the scene\n"
289 "(excludes editor UI). Reducing triangle count\n"
290 "via LODs or culling improves GPU performance.");
293 draw_label(
" Draw Calls");
294 ImGui::Text(
"%u", scene_calls);
296 ImGui::SetItemTooltipEx(
"Number of draw commands sent to the GPU for\n"
297 "scene rendering (excludes editor UI draws).\n"
298 "Fewer draw calls generally means better performance.");
301 draw_label(
" Render Passes");
304 ImGui::SetItemTooltipEx(
"Number of rendering passes executed this frame.\n"
305 "Includes geometry, lighting, shadow, and\n"
306 "post-processing passes.");
310 draw_label(
" Compute Calls");
311 ImGui::Text(
"%u",
stats->numCompute);
313 ImGui::SetItemTooltipEx(
"Number of GPU compute shader dispatches.\n"
314 "Used for GPGPU tasks.");
318 draw_label(
" Blit Calls");
319 ImGui::Text(
"%u",
stats->numBlit);
321 ImGui::SetItemTooltipEx(
"Number of GPU blit (copy/transfer) operations.\n"
322 "Used for copying textures, resolving MSAA,\n"
323 "or transferring between render targets.");
327void draw_memory_section()
336 if(
stats->gpuMemoryUsed > 0)
340 if(
stats->gpuMemoryMax > 0)
343 float pct = (
static_cast<float>(
stats->gpuMemoryUsed) /
344 static_cast<float>(
stats->gpuMemoryMax)) * 100.0f;
345 ImGui::TextColored(color_label,
" GPU Memory");
346 ImGui::SameLine(200.0f);
347 ImGui::TextColored(get_memory_color(pct),
"%s / %s (%.0f%%)",
348 used_str.c_str(), max_str.c_str(),
static_cast<double>(pct));
352 draw_label(
" GPU Memory");
353 ImGui::TextUnformatted(used_str.c_str());
356 ImGui::SetItemTooltipEx(
"Total GPU video memory allocated vs available.\n"
357 "Color-coded by usage percentage:\n"
358 "Green: <60%%, Yellow: 60-80%%, Red: >80%%.\n"
359 "High usage may cause performance degradation\n"
360 "or out-of-memory errors.");
363 if(
stats->textureMemoryUsed > 0)
366 draw_label(
" Texture Mem");
369 ImGui::SetItemTooltipEx(
"GPU memory consumed by texture resources.\n"
370 "Reduce with smaller textures or compressed formats.");
373 if(
stats->rtMemoryUsed > 0)
376 draw_label(
" RT Memory");
379 ImGui::SetItemTooltipEx(
"GPU memory consumed by render targets\n"
380 "(framebuffers). Scales with resolution.");
383 draw_eviction_overlay_stats();
386void draw_pipeline_stats(
const rendering::pipeline_stats& pstats)
389 draw_label(
" Static Models");
390 ImGui::Text(
"%u (%u Meshes)", pstats.drawn_models, pstats.drawn_static_submeshes);
392 ImGui::SetItemTooltipEx(
"Visible static models and submeshes drawn this frame.\n"
393 "The value in parentheses counts individual submeshes\n"
394 "after per-submesh frustum culling.");
397 draw_label(
" Skinned Models");
398 ImGui::Text(
"%u (%u Meshes)", pstats.drawn_skinned_models, pstats.drawn_skinned_submeshes);
400 ImGui::SetItemTooltipEx(
"Visible skinned models and submeshes drawn this frame.\n"
401 "The value in parentheses counts individual submeshes\n"
402 "submitted for GPU skinning.");
405 draw_label(
" Lights");
406 ImGui::Text(
"%u", pstats.drawn_lights);
408 ImGui::SetItemTooltipEx(
"Number of active lights evaluated for the\n"
409 "scene. Each additional light increases shading\n"
410 "cost in deferred and forward rendering.");
413 draw_label(
" Shadow Lights");
414 ImGui::Text(
"%u", pstats.drawn_lights_casting_shadows);
416 ImGui::SetItemTooltipEx(
"Number of lights with shadow casting enabled.\n"
417 "Each shadow light requires one or more extra\n"
418 "render passes to generate shadow maps.");
420 const uint32_t shadow_models = pstats.drawn_models_for_shadows + pstats.drawn_skinned_models_for_shadows;
421 const uint32_t shadow_submeshes = pstats.drawn_submeshes_for_shadows + pstats.drawn_skinned_submeshes_for_shadows;
423 draw_label(
" Shadow Models");
424 ImGui::Text(
"%u (%u meshes)", shadow_models, shadow_submeshes);
426 ImGui::SetItemTooltipEx(
"Models and submeshes rendered into shadow maps.\n"
427 "The mesh count includes cascade and face redraws,\n"
428 "so it can exceed the main-pass submesh count.");
431 draw_label(
" Particles");
432 ImGui::Text(
"%u (%u Batches)", pstats.drawn_particles, pstats.drawn_particles_batches);
434 ImGui::SetItemTooltipEx(
"Active particle emitters and their GPU draw\n"
435 "batches. Particles with different materials\n"
436 "or blend modes produce separate batches.");
438 const auto& batch = pstats.batching_stats;
440 ImGui::TextColored(color_label,
" Batching");
441 ImGui::SetItemTooltipEx(
"Static mesh batching combines multiple meshes\n"
442 "sharing the same material into fewer draw calls,\n"
443 "reducing CPU overhead.");
446 draw_label(
" Batches");
447 ImGui::Text(
"%u (%u Inst)", batch.total_batches, batch.total_instances);
449 ImGui::SetItemTooltipEx(
"Total batches created and total mesh instances\n"
450 "across all batches. A lower batch count relative\n"
451 "to instance count indicates effective batching.");
454 draw_label(
" Efficiency");
455 ImGui::Text(
"%.0f%%",
static_cast<double>(batch.batching_efficiency * 100.0f));
457 ImGui::SetItemTooltipEx(
"Percentage of eligible meshes successfully\n"
458 "combined into batches. 100%% means all eligible\n"
459 "meshes are batched. Low values may indicate\n"
460 "too many unique materials or shader variants.");
463 draw_label(
" Saved");
464 ImGui::Text(
"%u calls", batch.draw_calls_saved);
466 ImGui::SetItemTooltipEx(
"Number of draw calls eliminated by batching.\n"
467 "Higher values mean more CPU time saved from\n"
468 "reduced driver overhead.");
471void draw_pipeline_section(
const rendering::pipeline_stats& pstats)
478 draw_pipeline_stats(pstats);
490 auto* window = ImGui::GetCurrentWindow();
491 if(!window || window->SkipItems)
496 auto content_rect = window->ContentRegionRect;
497 float max_height = content_rect.GetHeight() - 2.0f * overlay_padding;
499 float pos_x = content_rect.Max.x - overlay_width - overlay_padding;
500 float pos_y = content_rect.Min.y + overlay_padding;
502 ImGui::SetCursorScreenPos(ImVec2(pos_x, pos_y));
504 ImGui::PushStyleColor(ImGuiCol_ChildBg, overlay_bg_color);
505 ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0.15f, 0.15f, 0.15f, 0.9f));
506 ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0.22f, 0.22f, 0.22f, 0.9f));
507 ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImVec4(0.18f, 0.18f, 0.18f, 0.9f));
508 ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, overlay_rounding);
509 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8.0f, 6.0f));
510 ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4.0f, 3.0f));
512 ImGuiChildFlags child_flags = ImGuiChildFlags_AlwaysUseWindowPadding | ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysAutoResize;
513 ImGuiWindowFlags window_flags = 0;
515 ImGui::SetNextWindowSizeConstraints(ImVec2(0, 0), ImVec2(overlay_width, max_height));
516 auto child_name = fmt::format(
"##viewport_stats_{}",
id);
517 if(ImGui::BeginChild(child_name.c_str(), ImVec2(overlay_width, 0), child_flags, window_flags))
522 auto header_text_width = ImGui::CalcTextSize(header_text).x;
523 ImGui::AlignedItem(0.5f, overlay_width, header_text_width, [&]() ->
void {
524 ImGui::TextUnformatted(header_text);
529 draw_performance_section();
530 draw_scene_section();
531 draw_memory_section();
532 draw_pipeline_section(pstats);
540 + ImGui::GetStyle().FramePadding.x * 2.0f;
541 ImGui::SetCursorPosX((overlay_width - btn_width) * 0.5f);
550 ImGui::PopStyleVar(3);
551 ImGui::PopStyleColor(4);
556 const float fps = ImGui::GetIO().Framerate;
557 std::array<char, 96> fps_label_buf{};
562 std::snprintf(fps_label_buf.data(),
563 fps_label_buf.size(),
565 static_cast<double>(fps));
566 label = fps_label_buf.data();
569 const auto& style = ImGui::GetStyle();
570 const float item_width = ImGui::CalcTextSize(label).x + style.ItemSpacing.x*2;
574 ImGui::AlignedItem(1.0f,
575 ImGui::GetContentRegionAvail().
x,
582 ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.2f, 0.8f, 0.2f, 1.0f));
584 if(ImGui::MenuItem(label,
"", is_visible))
590 ImGui::PopStyleColor();
593 ImGui::SetItemTooltipEx(
"%s", overlay_state.
is_visible ?
"Hide Statistics" :
"Show Statistics");
#define ICON_MDI_CUBE_OUTLINE
#define ICON_MDI_CHART_LINE
#define ICON_MDI_CHART_BAR
#define ICON_MDI_CHART_LINE_VARIANT
#define ICON_MDI_SWAP_HORIZONTAL
#define ICON_MDI_SPEEDOMETER
void PushFont(Font::Enum _font)
void PopWindowFontScale()
void PushWindowFontScale(float scale)
auto get_stats() -> stats
Snapshot the current statistics.
const stats * get_stats()
void draw(const rendering::pipeline_stats &pstats, state &overlay_state, const char *id)
Draw a statistics overlay child window at the top-right corner of the current ImGui window....
void draw_stats_toggle(state &overlay_state)
Draw a right-aligned "Stats" toggle button for the menu bar. Toggles the overlay visibility on click.
auto format_bytes(std::uint64_t bytes, std::uint8_t num_frac) -> std::string
static auto get_last_frame_max_pass_id() -> gfx::view_id
bool open_profiler_requested