From 51a1ce7d0f99b11c24232e8346dd068d6f834086 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 12 Nov 2025 15:48:15 -0500 Subject: [PATCH] xo-imgui: imgui_ex4: prep gc history bars for rich tooltips --- xo-imgui/example/ex4/DrawState.cpp | 238 ++++++++++++++------------- xo-imgui/example/ex4/DrawState.hpp | 18 +- xo-imgui/example/ex4/imgui_ex4.cpp | 14 +- xo-imgui/include/xo/imgui/ImRect.hpp | 2 + xo-imgui/src/imgui/ImRect.cpp | 4 +- 5 files changed, 151 insertions(+), 125 deletions(-) diff --git a/xo-imgui/example/ex4/DrawState.cpp b/xo-imgui/example/ex4/DrawState.cpp index 72e00202..67de25ba 100644 --- a/xo-imgui/example/ex4/DrawState.cpp +++ b/xo-imgui/example/ex4/DrawState.cpp @@ -244,82 +244,92 @@ DrawState::draw_tenured(const GcStateDescription & gcstate, *p_layout = layout; } -auto -DrawState::write_gc_history_tooltip(gc_history_headline headline, - const GcStatisticsHistoryItem & stats) - -> TooltipText +#ifdef nope + ImRect::draw_filled_rect + (tt.c_str(), + ImRect::from_xy_span(x_span, ImVec2(ypsz_lo, y_zero)), + persist_color, + draw_list); +#endif + +void +DrawState::write_gc_history_bar(const char * idname, + gc_history_headline headline, + const GcStatisticsHistoryItem & stats, + const ImRect & bar_rect, + ImU32 fill_color, + ImDrawList * draw_list) { - xo::flatstring<512> retval; + draw_list->AddRectFilled(bar_rect.top_left(), bar_rect.bottom_right(), fill_color); - xo::flatstring<256> headline_str; - switch (headline) { - case gc_history_headline::survive: - snprintf(headline_str.data(), headline_str.capacity(), - "survive: %lu: bytes surviving 1st GC after allocation", - stats.survive_z_); - break; - case gc_history_headline::promote: - snprintf(headline_str.data(), headline_str.capacity(), - "promote: %lu: bytes surviving 2nd GC; if nursery promote to tenured", - stats.promote_z_); - break; - case gc_history_headline::persist: - snprintf(headline_str.data(), headline_str.capacity(), - "persist: %lu: bytes surviving 3+ GCs. Only non-zero for full collections", - stats.persist_z_); - break; - case gc_history_headline::garbage0: - snprintf(headline_str.data(), headline_str.capacity(), - "garbage\u2080: %lu: bytes collected on 1st GC after allocation", - stats.garbage0_z_); - break; - case gc_history_headline::garbage1: - snprintf(headline_str.data(), headline_str.capacity(), - "garbage\u2081: %lu: bytes collected on 2nd GC after allocation", - stats.garbage1_z_); - break; - case gc_history_headline::garbageN: - snprintf(headline_str.data(), headline_str.capacity(), - "garbage\u2099: %lu: bytes collected on 3rd or later GC after allocation", - stats.garbageN_z_); - break; - case gc_history_headline::N: - assert(false); - break; + if (bar_rect.is_nonempty()) { + ImGui::SetCursorScreenPos(bar_rect.top_left()); + ImGui::InvisibleButton(idname, ImVec2(bar_rect.width(), bar_rect.height())); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + + /* choose text here */ + switch(headline) { + case gc_history_headline::survive: + ImGui::Text("survive: %lu: bytes surviving 1st GC after allocation", + stats.survive_z_); + break; + case gc_history_headline::promote: + ImGui::Text("promote: %lu: bytes surviving 2nd GC; if nursery promote to tenured", + stats.promote_z_); + break; + case gc_history_headline::persist: + ImGui::Text("persist: %lu: bytes surviving 3+ GCs. Only non-zero for full collections", + stats.persist_z_); + break; + case gc_history_headline::garbage0: + ImGui::Text("garbage\u2080: %lu: bytes collected on 1st GC after allocation", + stats.garbage0_z_); + break; + case gc_history_headline::garbage1: + ImGui::Text("garbage\u2081: %lu: bytes collected on 2nd GC after allocation", + stats.garbage1_z_); + break; + case gc_history_headline::garbageN: + ImGui::Text("garbage\u2099: %lu: bytes collected on 3rd or later GC after allocation", + stats.garbageN_z_); + break; + case gc_history_headline::N: + assert(false); + break; + } + + ImGui::Text("\n" + " gcseq: %lu\n" + " type: %s\n" + " alloc: %lu\n" + " survive: %lu\n" + " promote: %lu\n" + " persist: %lu\n" + " garbage\u2080: %lu\n" /*garbage0*/ + " garbage\u2081: %lu\n" /*garbage1*/ + " garbage\u2099: %lu\n" /*garbageN*/ + " effort: %lu dt: %.1lfus\n" + " copy efficiency: %.1lf%% collection rate: %.0lf bytes/sec", + stats.gc_seq_, + (stats.upto_ == generation::nursery) ? "incremental" : "FULL", + stats.new_alloc_z_, + stats.survive_z_, + stats.promote_z_, + stats.persist_z_, + stats.garbage0_z_, + stats.garbage1_z_, + stats.garbageN_z_, + stats.effort_z_, + 1e-3 * stats.dt_.scale(), + 100.0 * stats.efficiency(), + stats.collection_rate() + ); + + ImGui::EndTooltip(); + } } - - snprintf(retval.data(), retval.capacity(), - "%.*s\n" - "\n" - " gcseq: %lu\n" - " type: %s\n" - " alloc: %lu\n" - " survive: %lu\n" - " promote: %lu\n" - " persist: %lu\n" - " garbage\u2080: %lu\n" /*garbage0*/ - " garbage\u2081: %lu\n" /*garbage1*/ - " garbage\u2099: %lu\n" /*garbageN*/ - " effort: %lu dt: %.1lfus\n" - " copy efficiency: %.1lf%% collection rate: %.0lf bytes/sec", - static_cast(headline_str.capacity()), headline_str.c_str(), - stats.gc_seq_, - (stats.upto_ == generation::nursery) ? "incremental" : "FULL", - stats.new_alloc_z_, - stats.survive_z_, - stats.promote_z_, - stats.persist_z_, - stats.garbage0_z_, - stats.garbage1_z_, - stats.garbageN_z_, - stats.effort_z_, - 1e-3 * stats.dt_.scale(), - 100.0 * stats.efficiency(), - stats.collection_rate() - ); - - return retval.ensure_final_null(); -} /*write_gc_history_tooltip*/ +} /*write_gc_history_bar*/ /** stacked bar chart * @@ -384,6 +394,8 @@ DrawState::draw_gc_history(const GcStateDescription & gcstate, std::size_t i = 0; for (const GcStatisticsHistoryItem & stats : gc_history) { + ImGui::PushID(i); + if ((gen == stats.upto_) || (gen == generation::tenured)) { /* @@ -424,39 +436,36 @@ DrawState::draw_gc_history(const GcStateDescription & gcstate, float ypsz_lo = (y_zero - (display_h * stats.persist_z_ / y_scale)); { - xo::flatstring<512> tt = write_gc_history_tooltip(gc_history_headline::persist, stats); - - ImRect::draw_filled_rect - (tt.c_str(), - ImRect::from_xy_span(x_span, ImVec2(ypsz_lo, y_zero)), - persist_color, - draw_list); + write_gc_history_bar("##persist", + gc_history_headline::persist, + stats, + ImRect::from_xy_span(x_span, ImVec2(ypsz_lo, y_zero)), + persist_color, + draw_list); } /* y-coordinates of promote bar (survived 2nd GC) */ float yp_hi = ypsz_lo; float yp_lo = (yp_hi - (display_h * stats.promote_z_ / y_scale)); { - xo::flatstring<512> tt = write_gc_history_tooltip(gc_history_headline::promote, stats); - - ImRect::draw_filled_rect - (tt.c_str(), - ImRect::from_xy_span(x_span, ImVec2(yp_lo, yp_hi)), - promote_color, - draw_list); + write_gc_history_bar("##promote", + gc_history_headline::promote, + stats, + ImRect::from_xy_span(x_span, ImVec2(yp_lo, yp_hi)), + promote_color, + draw_list); } /* y-coordinates of survivor bar (survived 1st GC) */ float ys_hi = yp_lo; float ys_lo = (ys_hi - (display_h * stats.survive_z_ / y_scale)); { - xo::flatstring<512> tt = write_gc_history_tooltip(gc_history_headline::survive, stats); - - ImRect::draw_filled_rect - (tt.c_str(), - ImRect::from_xy_span(x_span, ImVec2(ys_lo, ys_hi)), - survive_color, - draw_list); + write_gc_history_bar("##survivor", + gc_history_headline::survive, + stats, + ImRect::from_xy_span(x_span, ImVec2(ys_lo, ys_hi)), + survive_color, + draw_list); } // ----------------------------------------------------------- @@ -466,13 +475,12 @@ DrawState::draw_gc_history(const GcStateDescription & gcstate, float ygN_hi = (y_zero + (display_h * stats.garbageN_z_ / y_scale)); { - xo::flatstring<512> tt = write_gc_history_tooltip(gc_history_headline::garbageN, stats); - - ImRect::draw_filled_rect - (tt.c_str(), - ImRect::from_xy_span(x_span, ImVec2(ygN_lo, ygN_hi)), - garbageN_color, - draw_list); + write_gc_history_bar("##garbageN", + gc_history_headline::garbageN, + stats, + ImRect::from_xy_span(x_span, ImVec2(ygN_lo, ygN_hi)), + garbageN_color, + draw_list); } /* y-coordinates of garbage1 bar (killed on 2nd GC) */ @@ -480,13 +488,12 @@ DrawState::draw_gc_history(const GcStateDescription & gcstate, float yg1_hi = (yg1_lo + (display_h * stats.garbage1_z_ / y_scale)); { - TooltipText tt = write_gc_history_tooltip(gc_history_headline::garbage1, stats); - - ImRect::draw_filled_rect - (tt.c_str(), - ImRect(ImVec2(x_lo, yg1_lo), ImVec2(x_hi, yg1_hi)), - garbage1_color, - draw_list); + write_gc_history_bar("##garbage1", + gc_history_headline::garbage1, + stats, + ImRect(ImVec2(x_lo, yg1_lo), ImVec2(x_hi, yg1_hi)), + garbage1_color, + draw_list); } /* y-coordinates of garbage0 bar (killed on 1st GC) */ @@ -494,13 +501,12 @@ DrawState::draw_gc_history(const GcStateDescription & gcstate, float yg0_hi = (yg0_lo + (display_h * stats.garbage0_z_ / y_scale)); { - TooltipText tt = write_gc_history_tooltip(gc_history_headline::garbage0, stats); - - ImRect::draw_filled_rect - (tt.c_str(), - ImRect(ImVec2(x_lo, yg0_lo), ImVec2(x_hi, yg0_hi)), - garbage0_color, - draw_list); + write_gc_history_bar("##garbage0", + gc_history_headline::garbage0, + stats, + ImRect(ImVec2(x_lo, yg0_lo), ImVec2(x_hi, yg0_hi)), + garbage0_color, + draw_list); } } else { /* draw nothing */ @@ -508,6 +514,8 @@ DrawState::draw_gc_history(const GcStateDescription & gcstate, } ++i; + + ImGui::PopID(); } log && log(xtag("i", i)); diff --git a/xo-imgui/example/ex4/DrawState.hpp b/xo-imgui/example/ex4/DrawState.hpp index cfdd6545..ab591da8 100644 --- a/xo-imgui/example/ex4/DrawState.hpp +++ b/xo-imgui/example/ex4/DrawState.hpp @@ -47,8 +47,22 @@ struct DrawState { const ImRect & rect, ImDrawList * draw_list, GenerationLayout * p_layout); - static TooltipText write_gc_history_tooltip(gc_history_headline headline, - const GcStatisticsHistoryItem & stats); + /** + * display one bar in bar strip chart displaying gc statistics + * + * @p idname : name for invisible button widget (expected to be unique) + * @p headline : purpose of rectangle + * @p stats : gc statistics for this bar. + * @p bar_rect : rectangle representing quantity in bytes + * @p fill_color : rectangle fill color + * @p draw_list : draw list for current frame + **/ + static void write_gc_history_bar(const char * idname, + gc_history_headline headline, + const GcStatisticsHistoryItem & stats, + const ImRect & bar_rect, + ImU32 fill_color, + ImDrawList * draw_list); static void draw_gc_history(const GcStateDescription & gcstate, generation gen, const GcStatisticsHistory & gc_history, diff --git a/xo-imgui/example/ex4/imgui_ex4.cpp b/xo-imgui/example/ex4/imgui_ex4.cpp index 38afebf9..03e558c6 100644 --- a/xo-imgui/example/ex4/imgui_ex4.cpp +++ b/xo-imgui/example/ex4/imgui_ex4.cpp @@ -75,12 +75,11 @@ namespace { VulkanApp::ImguiDrawFn make_imgui_draw_frame(AppState * p_app_state, DrawState * p_draw_state, - float * p_f, int * p_counter) + int * p_counter) { - *p_f = 0.0f; *p_counter = 0; - return [p_app_state, p_draw_state, p_f, p_counter](VulkanApp * vulkan_app, ImGuiContext * imgui_cx) + return [p_app_state, p_draw_state, p_counter](VulkanApp * vulkan_app, ImGuiContext * imgui_cx) { scope log(XO_DEBUG(false)); @@ -123,7 +122,7 @@ namespace { // 1. create a simple ImGui window ImGui::Begin("Hello, Vulkan + SDL2!"); - ImGui::Text("This is a minimal ImGui + Vulkan + SDL2 example!"); + ImGui::Text("Incremental GC demo, using ImGui + Vulkan + SDL2"); ImGui::Text("appl average %.3f ms/frame (%.1f fps)", 1000.0f / io.Framerate, io.Framerate); @@ -133,7 +132,9 @@ namespace { ImGui::SliderInt("copy animation budget", &p_draw_state->animate_copy_budget_ms_, 10, 10000); ImGui::NewLine(); - /* N\u2080 = N0, N\u2081 = N1 */ + /* N\u2080 -> N0, but with the 0 subscripted, + * N\u2081 -> N1, but with the 1 subscripted + */ ImGui::Text("alloc [%lu] avail [%lu] ", p_draw_state->gcstate_.gc_allocated_, p_draw_state->gcstate_.gc_available_); @@ -292,10 +293,9 @@ int main() { DrawState draw_state; draw_state.gcstate_ = app_state.snapshot_gc_state(); - float f = 0.0; int counter = 0; VulkanApp::ImguiDrawFn draw_fn - = make_imgui_draw_frame(&app_state, &draw_state, &f, &counter); + = make_imgui_draw_frame(&app_state, &draw_state, &counter); VulkanApp vk_app(draw_fn); vk_app.setup(app_imgui_load_fonts); diff --git a/xo-imgui/include/xo/imgui/ImRect.hpp b/xo-imgui/include/xo/imgui/ImRect.hpp index a9de4afb..4b4eb6fe 100644 --- a/xo-imgui/include/xo/imgui/ImRect.hpp +++ b/xo-imgui/include/xo/imgui/ImRect.hpp @@ -51,6 +51,8 @@ struct ImRect { ImVec2 bottom_left() const { return ImVec2(x_lo(), y_hi()); } ImVec2 top_right() const { return ImVec2(x_hi(), y_lo()); } + bool is_nonempty() const { return (width() > 0.0) && (height() > 0.0); } + ImRect with_x_span(float x0, float x1) const { return ImRect(ImVec2(x0, top_left_.y), ImVec2(x1, bottom_right_.y)); } diff --git a/xo-imgui/src/imgui/ImRect.cpp b/xo-imgui/src/imgui/ImRect.cpp index b2506f5f..6943c704 100644 --- a/xo-imgui/src/imgui/ImRect.cpp +++ b/xo-imgui/src/imgui/ImRect.cpp @@ -18,7 +18,9 @@ ImRect::draw_filled_rect_with_label(const char * text, ImGui::SetCursorScreenPos(rect.top_left()); ImGui::InvisibleButton("ttbutton", ImVec2(rect.width(), rect.height())); if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("%s", tooltip); + ImGui::BeginTooltip(); + ImGui::Text("%s", tooltip); + ImGui::EndTooltip(); } }