-
-
Notifications
You must be signed in to change notification settings - Fork 11.4k
Description
Version/Branch of Dear ImGui:
Dear ImGui 1.92.5 WIP (19246) (docking)
Back-ends:
imgui_impl_glfw.cpp + imgui_impl_vulkan.cpp
Compiler, OS:
Windows 11
Full config/build information:
Dear ImGui 1.92.5 WIP (19246)
--------------------------------
sizeof(size_t): 8, sizeof(ImDrawIdx): 2, sizeof(ImDrawVert): 20
define: __cplusplus=199711
define: IMGUI_DISABLE_OBSOLETE_FUNCTIONS
define: _WIN32
define: _WIN64
define: _MSC_VER=1944
define: _MSVC_LANG=202002
define: IMGUI_HAS_VIEWPORT
define: IMGUI_HAS_DOCK
IM_ASSERT: runs expression: OK. expand size: OK
--------------------------------
io.BackendPlatformName: imgui_impl_glfw (3500)
io.BackendRendererName: imgui_impl_vulkan
io.ConfigFlags: 0x00000481
NavEnableKeyboard
DockingEnable
ViewportsEnable
io.ConfigViewportsNoDecoration
io.ConfigViewportsNoDefaultParent
io.ConfigNavCaptureKeyboard
io.ConfigInputTextCursorBlink
io.ConfigWindowsResizeFromEdges
io.ConfigWindowsMoveFromTitleBarOnly
io.ConfigMemoryCompactTimer = 60.0
io.BackendFlags: 0x00001C1E
HasMouseCursors
HasSetMousePos
PlatformHasViewports
HasMouseHoveredViewport
RendererHasVtxOffset
RendererHasTextures
RendererHasViewports
--------------------------------
io.Fonts: 5 fonts, Flags: 0x00000000, TexSize: 512,128
io.Fonts->FontLoaderName: stb_truetype
io.DisplaySize: 2560.00,1392.00
io.DisplayFramebufferScale: 1.00,1.00
--------------------------------
style.WindowPadding: 8.00,8.00
style.WindowBorderSize: 0.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.00Details:
Higher GPU usage when ImGUI is hidden or overlapped with another window.
Below will be a LLM summary of the issue and changes in code that help fix this issue.
From 50% of 3D GPU use to normal. If this fix is wrong, will be glad to know a better solution.
Background
Applications built with GLFW + Vulkan + Dear ImGui typically render continuously. When the main window is hidden, minimized, occluded by another window, or sits in the background, continuing to render at full rate wastes GPU and power. The official ImGui demo tries to mitigate some of these cases, but it does not address every situation out of the box.
This document explains:
- What the official demo does and why GPU use can still be high when occluded.
- Practical fixes you can apply to your GLFW/Vulkan loop to reduce background/hidden GPU usage.
- Vulkan present mode guidance and multi-viewport caveats.
What the official ImGui GLFW/Vulkan demo does
- Initializes with a vsync-friendly present mode by default (FIFO). An optional macro can enable unlimited frame rate (MAILBOX/IMMEDIATE) for testing.
- In the main loop:
- Checks if the framebuffer size is zero or the window is iconified and, if so, sleeps briefly and skips rendering that frame.
- Otherwise renders normally every frame.
This already prevents busy rendering when minimized or sized to zero, but it does not explicitly throttle when the window is simply occluded by another window. On many systems, an occluded but non-iconified window continues to render at full rate.
Why high GPU usage still happens
- Rendering continues at the normal cadence while the window is visible but not foreground or is fully covered by other windows.
- If multi-viewport is enabled, platform windows are also updated every frame.
- If a non-FIFO present mode is selected (MAILBOX or IMMEDIATE), the swapchain can present at high rates even when not visible.
Recommended fixes
Apply the following incremental mitigations to your main loop. They are safe with the current ImGui backends and mirror/improve upon the demo behavior.
1) Skip when minimized or framebuffer is zero
Mirrors the official demo and avoids work when minimized/hidden.
// After glfwPollEvents()
int fb_w = 0, fb_h = 0;
glfwGetFramebufferSize(window, &fb_w, &fb_h);
if (fb_w == 0 || fb_h == 0 || glfwGetWindowAttrib(window, GLFW_ICONIFIED)) {
// Sleep a little to avoid a busy loop
glfwWaitEventsTimeout(0.010);
continue;
}2) Throttle when not in the foreground
Occlusion is hard to detect portably. A pragmatic approach is to reduce effective FPS when the app is not foreground/focused.
// Reduce GPU while not in foreground
static auto lastBackgroundFrame = std::chrono::steady_clock::now();
const bool isFocused = glfwGetWindowAttrib(window, GLFW_FOCUSED) != 0;
if (!isFocused) {
constexpr auto kBackgroundFrameInterval = std::chrono::milliseconds(200); // ~5 FPS
auto now = std::chrono::steady_clock::now();
if (now - lastBackgroundFrame < kBackgroundFrameInterval) {
glfwWaitEventsTimeout(0.050);
continue; // Skip full frame
}
lastBackgroundFrame = now;
}Windows-specific enhancement:
// On Win32, you can be stricter by checking foreground window
HWND hwnd = glfwGetWin32Window(window);
bool isForeground = (GetForegroundWindow() == hwnd);
// Use isForeground instead of/is addition to isFocusedThis keeps the app responsive while dramatically cutting background GPU usage.
3) Keep multi-viewport backend invariant
When ImGuiConfigFlags_ViewportsEnable is set, the backend requires that you always call:
ImGui::UpdatePlatformWindows();
ImGui::RenderPlatformWindowsDefault();after ImGui::Render() on frames that you do render. Do not conditionally skip these calls based on visibility/foreground; doing so can violate Dear ImGui internal invariants and cause assertions. Throttle earlier in the loop (as shown above) so that any frame you do render also updates platform windows.
4) Use FIFO present mode (vsync) in production
Ensure your swapchain present mode prefers FIFO (vsync) in production builds. Only enable MAILBOX/IMMEDIATE for testing or profiling.
Typical selection logic:
#ifdef APP_USE_UNLIMITED_FRAME_RATE
VkPresentModeKHR present_modes[] = {
VK_PRESENT_MODE_MAILBOX_KHR,
VK_PRESENT_MODE_IMMEDIATE_KHR,
VK_PRESENT_MODE_FIFO_KHR
};
#else
VkPresentModeKHR present_modes[] = {
VK_PRESENT_MODE_FIFO_KHR
};
#endif
wd->PresentMode = ImGui_ImplVulkanH_SelectPresentMode(physicalDevice, surface,
present_modes,
IM_ARRAYSIZE(present_modes));With FIFO, the driver synchronizes presentation to the display refresh, capping effective frame rate and reducing waste in the background.
Putting it together (GLFW/Vulkan loop outline)
glfwPollEvents();
// 1) Minimized or zero-size: skip
int fb_w = 0, fb_h = 0;
glfwGetFramebufferSize(window, &fb_w, &fb_h);
if (fb_w == 0 || fb_h == 0 || glfwGetWindowAttrib(window, GLFW_ICONIFIED)) {
glfwWaitEventsTimeout(0.010);
continue;
}
// 2) Background throttle
static auto lastBackgroundFrame = std::chrono::steady_clock::now();
bool isFocused = glfwGetWindowAttrib(window, GLFW_FOCUSED) != 0;
if (!isFocused) {
constexpr auto kBackgroundFrameInterval = std::chrono::milliseconds(200);
auto now = std::chrono::steady_clock::now();
if (now - lastBackgroundFrame < kBackgroundFrameInterval) {
glfwWaitEventsTimeout(0.050);
continue;
}
lastBackgroundFrame = now;
}
// New ImGui frame
ImGui_ImplVulkan_NewFrame();
ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame();
// ... build UI ...
ImGui::Render();
ImDrawData* draw_data = ImGui::GetDrawData();
// Record/submit/present...
// 3) Multi-viewport platform windows (if enabled)
if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) {
ImGui::UpdatePlatformWindows();
ImGui::RenderPlatformWindowsDefault();
}Additional considerations
- Do not spin a busy loop when throttling. Always use a timed wait to allow the OS to deliver events efficiently.
- If you implement your own frame pacing or job system, coordinate throttling with those systems to avoid conflicts.
- For laptops or power-sensitive deployments, consider making the background FPS cap configurable.
Checklist
- Window minimized or size zero: skip and timed wait.
- Background/unfocused: throttle to a low FPS (for example 5-15 FPS).
- Multi-viewport: always call platform windows update/render on rendered frames.
- Present mode: use FIFO by default; avoid MAILBOX/IMMEDIATE in production.
These measures align with Dear ImGui backend expectations while significantly reducing GPU usage when the window is hidden, occluded, or in the background.