imgui: Infinite loop in TabBar resizing

Version/Branch of Dear ImGui:

Version: “1.89 WIP” e13913ed Branch: master

Back-end/Renderer/Compiler/OS

Back-ends: imgui_impl_vulkan.cpp + imgui_impl_glfw.cpp Compiler: clang Operating System: linux/debian sid

My Issue/Question:

since i updated to a new version of imgui, i’m experiencing random hangs at times. i think i tracked the issue down to commit c4b91017, in particular the excess width handling in the TabBar. what seems to happen when i step through the code in gdb is that

https://github.com/ocornut/imgui/blob/master/imgui_widgets.cpp#L1578 <= this becomes an infinite while loop (in current master same code)

because in the following the line if (items[n].Width + 1.0f <= items[n].InitialWidth) is always unfortunate enough to only contain fractional bits, so the +1.0f case never triggers:

Thread 1 "vkdt" received signal SIGINT, Interrupt.
0x00005555555f527e in ImGui::ShrinkWidths (items=0x55555a389dd0, count=4, width_excess=0.760025024) at ../ext/imgui/imgui_widgets.cpp:1581
1581	            if (items[n].Width + 1.0f <= items[n].InitialWidth)
(gdb) p items[n]
$1 = {Index = 3, Width = 100, InitialWidth = 100.479996}
(gdb) 

today i can always reproduce it with my settings and screen resolution using https://github.com/hanatos/vkdt/commits/master but i’m hoping it would be possible to fix this conceptually so i don’t have to prepare a minimal example. for context, i have four tabs that almost fill the width of the containing window. they all run into this rounding issue:

(gdb) p items[0]
$2 = {Index = 2, Width = 163, InitialWidth = 163.480011}
(gdb) p items[1]
$3 = {Index = 0, Width = 119, InitialWidth = 119.479996}
(gdb) p items[2]
$4 = {Index = 1, Width = 107, InitialWidth = 107.479996}
(gdb) p items[3]
$5 = {Index = 3, Width = 100, InitialWidth = 100.479996}

for now i have commented out the while(width_excess > 0.0f) loop to get back a working version.

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Reactions: 2
  • Comments: 16 (10 by maintainers)

Commits related to this issue

Most upvoted comments

Can you try this version.? Only replacing the final loop from the current commit:

    while (width_excess > 0.0f)
        for (int n = 0; n < count && width_excess > 0.0f; n++)
        {
            float width_to_add = ImMin(items[n].InitialWidth - items[n].Width, 1.0f);
            items[n].Width += width_to_add;
            width_excess -= width_to_add;
        }

The thing I checked are:

  • Using rounded widths, the right most tabs is always at the same pixel distance to the right of the tab bar.
  • Using rounded widths, the relative spacing between each tabs never changes.
  • Can’t get it to infinite loop with non-rounded widths.

Issues (that are not new)

  • Using non-rounded widths result in subtle offset when starting to resize, because we round output at this point (not new), technically easier to fix, could do.
  • Using non-rounded widths still result in subtle wobbling but that’s due to layout system (not new)

It’s not perfect for non-rounded width but nothing in Dear ImGui currently is (*)

* Amusingly, at the time I am typing this into Firefox, the textbox height keeps resizing back-and-worth between +0 and +1 pixels whenever I type… Guess some other code has tricky rounding issue…

so probably what happens is that there is more than one pixel width_excess but all the individual tabs still only receive less than one pixel offset? how about a single-pass floyd-steinberg diffusion instead?

    // Round width and redistribute remainder
    // floyd steinberg diffusion:
    width_excess = 0.0f;
    for(int n = 0; n < count; n++)
    {
        items[n].Width += width_excess;
        float width_rounded = ImFloor(items[n].Width);
        width_excess += items[n].Width - width_rounded;
        items[n].Width = width_rounded;
    }

(patch attached, had to gzip it so github would accept it) 0001-tabbar-propose-to-distribute-widths-via-floyd-steinb.patch.gz

These are two example inputs that lead to inifinite loops for me with the code in its current state:

// GImGui->CurrentWindow->Size = { 1269, 956 }
ImGuiShrinkWidthItem testItemsA[] =
{
	{ 0, 99.0000000f, 99.0000000f },
	{ 1, 110.952225f, 110.952225f },
	{ 2, 84.9522247f, 84.9522247f },
	{ 3, 144.952225f, 144.952225f },
};
ImGui::ShrinkWidths(testItemsA, 4, 1.80895996f);
// GImGui->CurrentWindow->Size = { 1269, 956 }
ImGuiShrinkWidthItem testItemsB[] =
{
	{ 0, 99.0000000f, 99.0000000f },
	{ 1, 110.953949f, 110.953949f },
	{ 2, 84.9539490f, 84.9539490f },
	{ 3, 144.953949f, 144.953949f },
};
ImGui::ShrinkWidths(testItemsB, 4, 1.81582642f);

From my limited testing the single pass floyd steinberg diffusion approach appears to be working quite well here without any obvious issues 👍🏿