24#include <tess/diagnostics/diagnostics.h>
26#if defined(TESS_ENABLE_IMGUI) && TESS_DIAGNOSTICS_ENABLED
29#error "tess/debug/imgui/panels.h requires <imgui.h> to be included first"
32#ifndef IMGUI_HAS_TABLE
33#error "tess/debug/imgui/panels.h requires Dear ImGui 1.80 or later"
36#include <tess/diagnostics/export.h>
37#include <tess/diagnostics/trace.h>
45namespace tess::debug::imgui {
48[[nodiscard]]
inline auto to_ull(std::uint64_t value)
noexcept
49 ->
unsigned long long {
50 return static_cast<unsigned long long>(value);
53inline void draw_right_aligned(std::uint64_t value) {
54 std::array<char, std::numeric_limits<std::uint64_t>::digits10 + 2> text{};
56 std::to_chars(text.data(), text.data() + text.size() - 1, value);
57 const auto text_width = ImGui::CalcTextSize(text.data(), result.ptr).x;
58 const auto available_width = ImGui::GetContentRegionAvail().x;
59 ImGui::SetCursorPosX(ImGui::GetCursorPosX() + available_width - text_width);
60 ImGui::TextUnformatted(text.data(), result.ptr);
65[[nodiscard]]
inline auto category_name(
66 diagnostics::TraceCategory category)
noexcept ->
const char* {
68 case diagnostics::TraceCategory::General:
70 case diagnostics::TraceCategory::Path:
72 case diagnostics::TraceCategory::Topology:
74 case diagnostics::TraceCategory::Queued:
76 case diagnostics::TraceCategory::Planner:
78 case diagnostics::TraceCategory::Scheduler:
80 case diagnostics::TraceCategory::Render:
82 case diagnostics::TraceCategory::Count:
90inline void draw_timing_panel(
const diagnostics::TimingSnapshot& timing) {
91 ImGui::TextUnformatted(
"Timing (ns)");
93 constexpr int column_count = 6;
94 constexpr ImGuiTableFlags flags =
95 ImGuiTableFlags_Resizable | ImGuiTableFlags_NoSavedSettings |
96 ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersInnerV |
97 ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_ScrollX |
98 ImGuiTableFlags_ScrollY;
99 const auto unit = ImGui::GetTextLineHeightWithSpacing();
100 const auto visible_rows =
101 static_cast<float>(diagnostics::trace_category_count) + 2.0F;
102 if (!ImGui::BeginTable(
"##tess_timing", column_count, flags,
103 ImVec2{0.0F, unit * visible_rows})) {
106 ImGui::TableSetupColumn(
"Category", ImGuiTableColumnFlags_WidthFixed,
108 ImGui::TableSetupColumn(
"Samples", ImGuiTableColumnFlags_WidthFixed,
110 ImGui::TableSetupColumn(
"Total", ImGuiTableColumnFlags_WidthFixed,
112 ImGui::TableSetupColumn(
"Average", ImGuiTableColumnFlags_WidthFixed,
114 ImGui::TableSetupColumn(
"Minimum", ImGuiTableColumnFlags_WidthFixed,
116 ImGui::TableSetupColumn(
"Maximum", ImGuiTableColumnFlags_WidthFixed,
118 ImGui::TableSetupScrollFreeze(1, 1);
119 ImGui::TableHeadersRow();
120 for (std::size_t index = 0; index < diagnostics::trace_category_count;
122 const auto category =
static_cast<diagnostics::TraceCategory
>(index);
123 const auto& stats = timing.stats(category);
125 stats.samples == 0 ? std::uint64_t{0} : stats.total_ns / stats.samples;
126 ImGui::TableNextRow();
127 ImGui::TableSetColumnIndex(0);
128 ImGui::TextUnformatted(category_name(category));
129 ImGui::TableSetColumnIndex(1);
130 detail::draw_right_aligned(stats.samples);
131 ImGui::TableSetColumnIndex(2);
132 detail::draw_right_aligned(stats.total_ns);
133 ImGui::TableSetColumnIndex(3);
134 detail::draw_right_aligned(average);
135 ImGui::TableSetColumnIndex(4);
136 detail::draw_right_aligned(stats.min_ns);
137 ImGui::TableSetColumnIndex(5);
138 detail::draw_right_aligned(stats.max_ns);
144inline void draw_path_counters_panel(
const diagnostics::PathCounters& path) {
145 ImGui::TextUnformatted(
"Path counters");
147 ImGui::Text(
"heap push / pop: %llu / %llu", detail::to_ull(path.heap_pushes),
148 detail::to_ull(path.heap_pops));
149 ImGui::Text(
"relax ok / attempts: %llu / %llu",
150 detail::to_ull(path.relax_successes),
151 detail::to_ull(path.relax_attempts));
152 ImGui::Text(
"touched nodes: %llu", detail::to_ull(path.touched_nodes));
153 ImGui::Text(
"passability checks: %llu",
154 detail::to_ull(path.passability_checks));
158inline void draw_queued_counters_panel(
159 const diagnostics::QueuedPhaseCounters& queued) {
160 ImGui::TextUnformatted(
"Queued phase counters");
162 ImGui::Text(
"phase calls / ops: %llu / %llu",
163 detail::to_ull(queued.phase_calls),
164 detail::to_ull(queued.phase_operations));
165 ImGui::Text(
"failures: %llu", detail::to_ull(queued.phase_failures));
166 ImGui::Text(
"dirty merged: %llu", detail::to_ull(queued.dirty_chunks_merged));
170inline void draw_allocation_counters_panel(
171 const diagnostics::AllocationCounters& allocation) {
172 ImGui::TextUnformatted(
"Allocation counters");
174 ImGui::Text(
"alloc: %llu (%llu bytes)",
175 detail::to_ull(allocation.allocations),
176 detail::to_ull(allocation.allocation_bytes));
177 ImGui::Text(
"free: %llu (%llu bytes)",
178 detail::to_ull(allocation.deallocations),
179 detail::to_ull(allocation.deallocation_bytes));
180 ImGui::Text(
"live / peak: %llu / %llu bytes",
181 detail::to_ull(allocation.live_bytes),
182 detail::to_ull(allocation.peak_live_bytes));
186inline void draw_recent_timing_spans_panel(
187 const diagnostics::DiagnosticsSnapshot& snapshot) {
188 ImGui::TextUnformatted(
"Recent timed spans");
190 for (std::size_t index = 0; index < snapshot.trace_record_count; ++index) {
191 const auto& record = snapshot.trace_records[index];
192 if (record.kind != diagnostics::TraceRecordKind::Duration) {
195 const auto label_size =
196 record.label.size() >
197 static_cast<std::size_t
>(std::numeric_limits<int>::max())
198 ? std::numeric_limits<int>::max()
199 :
static_cast<int>(record.label.size());
200 const auto milliseconds =
static_cast<double>(record.value) / 1'000'000.0;
201 const auto* label = record.label.empty() ?
"" : record.label.data();
202 ImGui::Text(
"%-10s %.*s: %.3f ms; alloc/free %llu/%llu bytes",
203 category_name(record.category), label_size, label, milliseconds,
204 detail::to_ull(record.allocation_bytes),
205 detail::to_ull(record.deallocation_bytes));
207 if (snapshot.trace_records_dropped != 0) {
208 ImGui::Text(
"dropped: %llu",
209 detail::to_ull(snapshot.trace_records_dropped));
214inline void draw_diagnostics_panel(
215 const diagnostics::DiagnosticsSnapshot& snapshot) {
216 draw_timing_panel(snapshot.timing);
218 draw_recent_timing_spans_panel(snapshot);
220 draw_path_counters_panel(snapshot.path);
222 draw_queued_counters_panel(snapshot.queued);
224 draw_allocation_counters_panel(snapshot.allocation);