Make the host per-monitor DPI aware and scale the ImGui overlay
The window sized itself to GetSystemMetrics(SM_CXSCREEN/CYSCREEN) but the process was DPI-unaware, so on a scaled display (e.g. 4K @ 150%) Windows handed us a virtualized resolution and bitmap-stretched the whole window up to native -- softening the mirror, which is the tool's entire point. Declare per-monitor-v2 awareness at startup so GetSystemMetrics/GetDpiForWindow report true pixels. That alone would shrink the fixed-13px ImGui overlay to crisp-but-tiny, so pair it with UI scaling: rebuild the default-font atlas at a DPI-scaled SizePixels (crisp at the target size, unlike FontGlobalScale's bitmap stretch) and ScaleAllSizes() the style. Net: same physical size as before, now sharp. - common/include/coop/dpi.hpp: pure DPI->scale math (uses USER_DEFAULT_SCREEN_DPI and a named kBaseFontPx, not bare 96/13 literals), with a zero fallback and clamping. Unit-tested by tests/dpi_test.cpp. - imgui_layer: apply_dpi() at init from GetDpiForWindow; set_dpi() for runtime changes (rebuild atlas + reset/scale style + invalidate the DX11 font texture). - d3d11_window: latch WM_DPICHANGED (honor the suggested rect), expose take_dpi_change(); main loop polls it and calls imgui.set_dpi() between frames. The DPI math is unit-tested; the actual awareness + font rasterization + live WM_DPICHANGED rescale are verified by hand. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
#include <imgui_impl_dx11.h>
|
||||
#include <imgui_impl_win32.h>
|
||||
|
||||
#include "coop/dpi.hpp"
|
||||
#include "coop/tool_paths.hpp"
|
||||
#include "ui/app_chrome.hpp"
|
||||
|
||||
@@ -54,7 +55,6 @@ bool ImGuiLayer::init(HWND hwnd, ID3D11Device* device, ID3D11DeviceContext* cont
|
||||
ini_path_ = to_utf8(ini_w);
|
||||
io.IniFilename = ini_path_.c_str();
|
||||
set_layout_persisted(had_layout);
|
||||
ImGui::StyleColorsDark();
|
||||
|
||||
if (!ImGui_ImplWin32_Init(hwnd))
|
||||
{
|
||||
@@ -65,9 +65,49 @@ bool ImGuiLayer::init(HWND hwnd, ID3D11Device* device, ID3D11DeviceContext* cont
|
||||
return false;
|
||||
}
|
||||
initialized_ = true;
|
||||
|
||||
// Scale the overlay (font + style) to the monitor the window opened on. The process is
|
||||
// per-monitor-DPI-aware (set in wWinMain), so GetDpiForWindow returns that monitor's real DPI.
|
||||
apply_dpi(GetDpiForWindow(hwnd));
|
||||
return true;
|
||||
}
|
||||
|
||||
void ImGuiLayer::apply_dpi(unsigned dpi)
|
||||
{
|
||||
const float scale = dpi_scale_from(dpi);
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
|
||||
// Rebuild the font atlas at the DPI-scaled pixel size so ImGui's built-in default font is
|
||||
// rasterized crisp at the target size, rather than bitmap-stretched the way io.FontGlobalScale
|
||||
// would leave it. scaled_font_px() scales the one named base-size constant.
|
||||
io.Fonts->Clear();
|
||||
ImFontConfig cfg;
|
||||
cfg.SizePixels = scaled_font_px(dpi);
|
||||
io.Fonts->AddFontDefault(&cfg);
|
||||
|
||||
// Reset to the base dark theme, then scale spacing/padding/border sizes to match. Resetting
|
||||
// first keeps repeated DPI changes from compounding (ScaleAllSizes multiplies the style in place).
|
||||
ImGui::StyleColorsDark();
|
||||
ImGui::GetStyle().ScaleAllSizes(scale);
|
||||
|
||||
// Drop the DX11 backend's cached font texture so it rebuilds from the new atlas next frame. Before
|
||||
// the first frame nothing is built yet, so this is a harmless no-op during init().
|
||||
if (initialized_)
|
||||
{
|
||||
ImGui_ImplDX11_InvalidateDeviceObjects();
|
||||
}
|
||||
dpi_scale_ = scale;
|
||||
}
|
||||
|
||||
void ImGuiLayer::set_dpi(unsigned dpi)
|
||||
{
|
||||
if (!initialized_ || dpi_scale_from(dpi) == dpi_scale_)
|
||||
{
|
||||
return; // not up yet, or the scale didn't actually change -- skip a needless atlas rebuild
|
||||
}
|
||||
apply_dpi(dpi);
|
||||
}
|
||||
|
||||
void ImGuiLayer::begin_frame()
|
||||
{
|
||||
ImGui_ImplDX11_NewFrame();
|
||||
|
||||
Reference in New Issue
Block a user