Viewports: 1-frame rogue OS window glitch with BeginPopupModal + AlwaysAutoResize + Pivot + NoAutoMerge #9568

Open
opened 2026-10-01 08:11:43 +00:00 by Solomon0x0 · 0 comments
Solomon0x0 commented 2026-10-01 08:11:43 +00:00 (Migrated from github.com)
### Version/Branch of Dear ImGui:

Version 1.93.0 WIP, Branch: docking

### Back-ends:

imgui_impl_win32.cpp + imgui_impl_dx11.cpp

### Compiler, OS:

Windows 11 + MSVC

### Full config/build information:

```cpp
Dear ImGui 1.93.0 WIP (19297)
--------------------------------
sizeof(size_t): 8, sizeof(ImDrawIdx): 2, sizeof(ImDrawVert): 20
define: __cplusplus=199711
define: _WIN32
define: _WIN64
define: _MSC_VER=1944
define: _MSVC_LANG=201402
define: IMGUI_HAS_VIEWPORT
define: IMGUI_HAS_DOCK
IM_ASSERT: runs expression: OK. expand size: OK
--------------------------------
io.BackendPlatformName: imgui_impl_win32
io.BackendRendererName: imgui_impl_dx11
io.ConfigFlags: 0x00000483
 NavEnableKeyboard
 NavEnableGamepad
 DockingEnable
 ViewportsEnable
io.ConfigDpiScaleFonts
io.ConfigDpiScaleViewports
io.ConfigViewportsNoDecoration
io.ConfigViewportsNoDefaultParent
io.ConfigNavCaptureKeyboard
io.ConfigInputTextCursorBlink
io.ConfigWindowsResizeFromEdges
io.ConfigMemoryCompactTimer = 60.0
io.BackendFlags: 0x00003C1E
 HasMouseCursors
 HasSetMousePos
 PlatformHasViewports
 HasMouseHoveredViewport
 HasParentViewport
 RendererHasVtxOffset
 RendererHasTextures
 RendererHasViewports
--------------------------------
io.Fonts: 1 fonts, Flags: 0x00000000, TexSize: 512,128
io.Fonts->FontLoaderName: stb_truetype
io.DisplaySize: 1264.00,761.00
io.DisplayFramebufferScale: 1.00,1.00
--------------------------------
style.WindowPadding: 8.00,8.00
style.WindowBorderSize: 1.00
style.FramePadding: 4.00,3.00
style.FrameRounding: 0.00
style.FrameBorderSize: 0.00
style.ItemSpacing: 8.00,4.00
style.ItemInnerSpacing: 4.00,4.00

Details:

When a parent window is forced into its own viewport using ImGuiViewportFlags_NoAutoMerge (via ImGuiWindowClass), opening a Modal Popup with ImGuiWindowFlags_AlwaysAutoResize and centering it via SetNextWindowPos (with a pivot) creates a 1-frame rogue OS window outside the parent boundaries.

The root cause (Inheritance & Pivot calculation delay):

  1. The Modal inherits the NoAutoMerge viewport flag from the parent's ImGuiWindowClass.
  2. On the first frame, the modal's size is unknown (0x0) due to AlwaysAutoResize. Thus, window->HiddenFramesCannotSkipItems is set to 1.
  3. Inside imgui.cpp Begin(), the boolean window_pos_with_pivot evaluates to false because of the hidden frames condition.
  4. Because position assignment is completely bypassed for this frame, the modal defaults to a fallback coordinate (e.g., 60, 60).
  5. Since the modal is at a fallback coordinate (outside its parent) AND carries the inherited NoAutoMerge flag, the Viewport system panics and creates a temporary standalone OS window (HWND) with a drop shadow for exactly 1 frame.
  6. On the second frame, the size is known, the pivot is calculated, and the modal snaps correctly into the host window, destroying the rogue viewport.

Proposed Fix:
We can prevent this rogue viewport creation by honoring the target position (without the pivot) for the first measuring frame. This keeps the window safely within the host boundaries until the exact size/pivot is resolved.

In imgui.cpp (inside Begin()):

        const bool window_pos_with_pivot = (window->SetWindowPosVal.x != FLT_MAX && window->HiddenFramesCannotSkipItems == 0);
        if (window_pos_with_pivot)
            SetWindowPos(window, window->SetWindowPosVal - window->Size * window->SetWindowPosPivot, 0); // Position given a pivot (e.g. for centering)
        else if ((flags & ImGuiWindowFlags_ChildMenu) != 0)
            window->Pos = FindBestWindowPosForPopup(window);
        else if ((flags & ImGuiWindowFlags_Popup) != 0 && !window_pos_set_by_api && window_just_appearing_after_hidden_for_resize)
            window->Pos = FindBestWindowPosForPopup(window);
        else if ((flags & ImGuiWindowFlags_Tooltip) != 0 && !window_pos_set_by_api && !window_is_child_tooltip)
            window->Pos = FindBestWindowPosForPopup(window);
+       else if (window->SetWindowPosVal.x != FLT_MAX) 
+           window->Pos = window->SetWindowPosVal - window->Size * window->SetWindowPosPivot; // [Suggested Fix] Prevent rogue viewports by honoring target pos before size/pivot is resolved

Screenshots/Video:

Before: https://github.com/user-attachments/assets/34df4fe8-fb19-4dc2-879d-03026c1950ed
(Attached screenshots from the Metrics/Debugger illustrate Viewport #3 being transiently created on the first frame due to the fallback positioning and later merged successfully)

After: https://github.com/user-attachments/assets/01c9b9e2-311b-4a93-a647-c1f853c2c4fb

Minimal, Complete and Verifiable Example code:

// Ensure Viewports are enabled in io.ConfigFlags

// 1. Force the parent window into its own separate viewport
ImGuiWindowClass window_class;
window_class.ViewportFlagsOverrideSet = ImGuiViewportFlags_NoAutoMerge;
ImGui::SetNextWindowClass(&window_class);

ImGui::Begin("Bugged Parent Window");
if (ImGui::Button("Open Glitchy Modal"))
    ImGui::OpenPopup("TestModal");

// 2. Try to center the modal to the parent using a pivot
ImVec2 parentPos = ImGui::GetWindowPos();
ImVec2 parentSize = ImGui::GetWindowSize();
ImVec2 parentCenter = ImVec2(parentPos.x + parentSize.x * 0.5f, parentPos.y + parentSize.y * 0.5f);

ImGui::SetNextWindowPos(parentCenter, ImGuiCond_Always, ImVec2(0.5f, 0.5f));
if (ImGui::BeginPopupModal("TestModal", NULL, ImGuiWindowFlags_AlwaysAutoResize))
{
    ImGui::Text("This modal creates a 1-frame rogue OS window glitch on its first frame.");
    if (ImGui::Button("Close")) ImGui::CloseCurrentPopup();
    ImGui::EndPopup();
}
ImGui::End();

```markdown ### Version/Branch of Dear ImGui: Version 1.93.0 WIP, Branch: docking ### Back-ends: imgui_impl_win32.cpp + imgui_impl_dx11.cpp ### Compiler, OS: Windows 11 + MSVC ### Full config/build information: ```cpp Dear ImGui 1.93.0 WIP (19297) -------------------------------- sizeof(size_t): 8, sizeof(ImDrawIdx): 2, sizeof(ImDrawVert): 20 define: __cplusplus=199711 define: _WIN32 define: _WIN64 define: _MSC_VER=1944 define: _MSVC_LANG=201402 define: IMGUI_HAS_VIEWPORT define: IMGUI_HAS_DOCK IM_ASSERT: runs expression: OK. expand size: OK -------------------------------- io.BackendPlatformName: imgui_impl_win32 io.BackendRendererName: imgui_impl_dx11 io.ConfigFlags: 0x00000483 NavEnableKeyboard NavEnableGamepad DockingEnable ViewportsEnable io.ConfigDpiScaleFonts io.ConfigDpiScaleViewports io.ConfigViewportsNoDecoration io.ConfigViewportsNoDefaultParent io.ConfigNavCaptureKeyboard io.ConfigInputTextCursorBlink io.ConfigWindowsResizeFromEdges io.ConfigMemoryCompactTimer = 60.0 io.BackendFlags: 0x00003C1E HasMouseCursors HasSetMousePos PlatformHasViewports HasMouseHoveredViewport HasParentViewport RendererHasVtxOffset RendererHasTextures RendererHasViewports -------------------------------- io.Fonts: 1 fonts, Flags: 0x00000000, TexSize: 512,128 io.Fonts->FontLoaderName: stb_truetype io.DisplaySize: 1264.00,761.00 io.DisplayFramebufferScale: 1.00,1.00 -------------------------------- style.WindowPadding: 8.00,8.00 style.WindowBorderSize: 1.00 style.FramePadding: 4.00,3.00 style.FrameRounding: 0.00 style.FrameBorderSize: 0.00 style.ItemSpacing: 8.00,4.00 style.ItemInnerSpacing: 4.00,4.00 ``` ### Details: When a parent window is forced into its own viewport using `ImGuiViewportFlags_NoAutoMerge` (via `ImGuiWindowClass`), opening a Modal Popup with `ImGuiWindowFlags_AlwaysAutoResize` and centering it via `SetNextWindowPos` (with a pivot) creates a 1-frame rogue OS window outside the parent boundaries. **The root cause (Inheritance & Pivot calculation delay):** 1. The Modal inherits the `NoAutoMerge` viewport flag from the parent's `ImGuiWindowClass`. 2. On the first frame, the modal's size is unknown (`0x0`) due to `AlwaysAutoResize`. Thus, `window->HiddenFramesCannotSkipItems` is set to 1. 3. Inside `imgui.cpp` `Begin()`, the boolean `window_pos_with_pivot` evaluates to `false` because of the hidden frames condition. 4. Because position assignment is completely bypassed for this frame, the modal defaults to a fallback coordinate (e.g., `60, 60`). 5. Since the modal is at a fallback coordinate (outside its parent) AND carries the inherited `NoAutoMerge` flag, the Viewport system panics and creates a temporary standalone OS window (HWND) with a drop shadow for exactly 1 frame. 6. On the second frame, the size is known, the pivot is calculated, and the modal snaps correctly into the host window, destroying the rogue viewport. **Proposed Fix:** We can prevent this rogue viewport creation by honoring the target position (without the pivot) for the first measuring frame. This keeps the window safely within the host boundaries until the exact size/pivot is resolved. In `imgui.cpp` (inside `Begin()`): ```diff const bool window_pos_with_pivot = (window->SetWindowPosVal.x != FLT_MAX && window->HiddenFramesCannotSkipItems == 0); if (window_pos_with_pivot) SetWindowPos(window, window->SetWindowPosVal - window->Size * window->SetWindowPosPivot, 0); // Position given a pivot (e.g. for centering) else if ((flags & ImGuiWindowFlags_ChildMenu) != 0) window->Pos = FindBestWindowPosForPopup(window); else if ((flags & ImGuiWindowFlags_Popup) != 0 && !window_pos_set_by_api && window_just_appearing_after_hidden_for_resize) window->Pos = FindBestWindowPosForPopup(window); else if ((flags & ImGuiWindowFlags_Tooltip) != 0 && !window_pos_set_by_api && !window_is_child_tooltip) window->Pos = FindBestWindowPosForPopup(window); + else if (window->SetWindowPosVal.x != FLT_MAX) + window->Pos = window->SetWindowPosVal - window->Size * window->SetWindowPosPivot; // [Suggested Fix] Prevent rogue viewports by honoring target pos before size/pivot is resolved ``` ### Screenshots/Video: Before: https://github.com/user-attachments/assets/34df4fe8-fb19-4dc2-879d-03026c1950ed *(Attached screenshots from the Metrics/Debugger illustrate Viewport #3 being transiently created on the first frame due to the fallback positioning and later merged successfully)* After: https://github.com/user-attachments/assets/01c9b9e2-311b-4a93-a647-c1f853c2c4fb ### Minimal, Complete and Verifiable Example code: ```cpp // Ensure Viewports are enabled in io.ConfigFlags // 1. Force the parent window into its own separate viewport ImGuiWindowClass window_class; window_class.ViewportFlagsOverrideSet = ImGuiViewportFlags_NoAutoMerge; ImGui::SetNextWindowClass(&window_class); ImGui::Begin("Bugged Parent Window"); if (ImGui::Button("Open Glitchy Modal")) ImGui::OpenPopup("TestModal"); // 2. Try to center the modal to the parent using a pivot ImVec2 parentPos = ImGui::GetWindowPos(); ImVec2 parentSize = ImGui::GetWindowSize(); ImVec2 parentCenter = ImVec2(parentPos.x + parentSize.x * 0.5f, parentPos.y + parentSize.y * 0.5f); ImGui::SetNextWindowPos(parentCenter, ImGuiCond_Always, ImVec2(0.5f, 0.5f)); if (ImGui::BeginPopupModal("TestModal", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { ImGui::Text("This modal creates a 1-frame rogue OS window glitch on its first frame."); if (ImGui::Button("Close")) ImGui::CloseCurrentPopup(); ImGui::EndPopup(); } ImGui::End(); ```
Sign in to join this conversation.
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
ocornut/imgui#9568
No description provided.