chore(window): add [WINLOG] window-dimension tracing for the multi-monitor bug

Always-on stderr tracing (visible in release builds via the Windows
dragonx-debug.log redirect, unlike DRAGONX_DEBUG-gated DEBUG_LOGF) to capture
exactly how the window size / display-scale / minimum change when dragging
between monitors of different DPI. Logs:
- every WINDOW_RESIZED: size, actual vs stored scale, dpiPending/settle/retries,
  and the DPI-vs-user classification (IGNORED/RECORDED);
- every DISPLAY_SCALE_CHANGED + the full handleDisplayScaleChange trace
  (saved-under-old-scale, restore/proportional, min-clamp, display-clamp, the
  SetWindowMinimumSize+SetWindowSize, and the resulting window state);
- each deferred-resize retry;
- monitor crossings (WINDOW_MOVED, throttled to display changes);
- startup and shutdown size.

Low volume — only fires on window events. Temporary diagnostic; remove once the
multi-monitor sizing bug is fixed. Grep the log for "[WINLOG]".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-10 00:49:59 -05:00
parent 8cbd0cab9d
commit eb08b81706

View File

@@ -71,6 +71,7 @@ extern "C" HRESULT __stdcall SHGetPropertyStoreForWindow(HWND hwnd, REFIID riid,
#endif
#include <cstdio>
#include <cstdarg>
#include <cstdlib>
#include <cmath>
#include <cstdint>
@@ -531,6 +532,32 @@ static void drawWindowBackdrop(dragonx::App& app, ImDrawList* bgDL, ImVec2 p0, I
}
}
// ── Window-dimension diagnostic tracer ──────────────────────────────────────────────────────
// Always-on (writes to stderr → dragonx-debug.log on Windows) so it's visible in release builds,
// unlike DRAGONX_DEBUG-gated DEBUG_LOGF. Grep the log for "[WINLOG]" to trace how the window size
// changes when dragging between monitors of different DPI. Low volume — only fires on window
// resize / move / DPI-scale events. TODO: remove once the multi-monitor sizing bug is resolved.
static void winlog(const char* fmt, ...)
{
char buf[1024];
va_list a; va_start(a, fmt); vsnprintf(buf, sizeof(buf), fmt, a); va_end(a);
fprintf(stderr, "[WINLOG] %s\n", buf);
fflush(stderr);
}
// Dump the full current window state with a short tag.
static void winlogState(SDL_Window* w, const char* tag)
{
int sw = 0, sh = 0, pw = 0, ph = 0, mw = 0, mh = 0;
SDL_GetWindowSize(w, &sw, &sh);
SDL_GetWindowSizeInPixels(w, &pw, &ph);
SDL_GetWindowMinimumSize(w, &mw, &mh);
float ds = SDL_GetWindowDisplayScale(w);
float st = dragonx::ui::material::Typography::instance().getDpiScale();
SDL_DisplayID did = SDL_GetDisplayForWindow(w);
winlog("%-20s size=%dx%d px=%dx%d min=%dx%d dispScale=%.3f storedScale=%.3f display=%u",
tag, sw, sh, pw, ph, mw, mh, ds, st, (unsigned)did);
}
// Handle SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED: save the old-scale size,
// compute + clamp the new physical size, arm the deferred resize retry,
// and rebuild fonts / reset ImGui style at the new DPI scale. newScale
@@ -548,6 +575,9 @@ static void handleDisplayScaleChange(SDL_Window* window, float newScale,
float ratio = newScale / oldScale;
DEBUG_LOGF("Display scale changed: %.2f -> %.2f (ratio %.2f)\n",
oldScale, newScale, ratio);
winlog("======== DPI CHANGE storedScale %.3f -> newScale %.3f (ratio %.3f) ========",
oldScale, newScale, ratio);
winlogState(window, " entry");
ImGuiIO& io = ImGui::GetIO();
#ifdef DRAGONX_USE_DX11
@@ -563,6 +593,7 @@ static void handleDisplayScaleChange(SDL_Window* window, float newScale,
savedSizeForScale[oldPct] = {lastKnownW, lastKnownH};
DEBUG_LOGF(" Saved %dx%d for scale %d%%\n",
lastKnownW, lastKnownH, oldPct);
winlog(" saved lastKnown %dx%d under old scale %d%%", lastKnownW, lastKnownH, oldPct);
}
// Compute new physical size: if we've been to this
@@ -574,12 +605,15 @@ static void handleDisplayScaleChange(SDL_Window* window, float newScale,
if (it != savedSizeForScale.end()) {
newW = it->second.first;
newH = it->second.second;
winlog(" restore saved %dx%d for scale %d%%", newW, newH, newPct);
} else {
// Use lastKnownW/H (pre-auto-resize) instead of
// SDL_GetWindowSize() which already reflects the
// WM_DPICHANGED auto-resize.
newW = (int)lroundf((float)lastKnownW * newScale / oldScale);
newH = (int)lroundf((float)lastKnownH * newScale / oldScale);
winlog(" proportional %dx%d = lastKnown %dx%d * %.3f/%.3f",
newW, newH, lastKnownW, lastKnownH, newScale, oldScale);
}
// Never restore/scale below the new scale's minimum — guarantees the window lands exactly
@@ -587,6 +621,7 @@ static void handleDisplayScaleChange(SDL_Window* window, float newScale,
const int minW = (int)(1024 * newScale), minH = (int)(720 * newScale);
newW = std::max(newW, minW);
newH = std::max(newH, minH);
winlog(" after min-clamp (min %dx%d): %dx%d", minW, minH, newW, newH);
// Clamp to the target display's work area so the
// window doesn't overflow a smaller/higher-density monitor.
@@ -596,6 +631,7 @@ static void handleDisplayScaleChange(SDL_Window* window, float newScale,
if (SDL_GetDisplayUsableBounds(did, &usable)) {
newW = std::min(newW, usable.w);
newH = std::min(newH, usable.h);
winlog(" after display-clamp (usable %dx%d): %dx%d", usable.w, usable.h, newW, newH);
}
}
@@ -619,6 +655,9 @@ static void handleDisplayScaleChange(SDL_Window* window, float newScale,
dpiSettleFrames = 40; // ignore resize-recording until the transition settles
DEBUG_LOGF(" Window resized: %dx%d (scale %.2f, pct %d)\n",
newW, newH, newScale, newPct);
winlog(" SetWindowMinimumSize(%dx%d) then SetWindowSize(%dx%d); armed retries=30 settle=40",
minW, minH, newW, newH);
winlogState(window, " after set");
// 2) Rebuild font atlas at the new DPI scale
typo.reload(io, newScale);
@@ -1110,6 +1149,8 @@ int main(int argc, char* argv[])
}
}
#endif
winlog("STARTUP savedSize=%dx%d currentDpiScale=%.3f", savedWinW, savedWinH, currentDpiScale);
winlogState(window, " startup");
// Create application instance
dragonx::App app;
@@ -1293,6 +1334,7 @@ int main(int argc, char* argv[])
// Frames to keep ignoring resize-recording after a DPI transition (settle window) so transient
// WINDOW_RESIZED events during the transition can't corrupt savedSizeForScale / lastKnownW/H.
int dpiSettleFrames = 0;
SDL_DisplayID lastLoggedDisplay = 0; // [WINLOG] throttle: log MOVED only when the display changes
{
float s = dragonx::ui::material::Typography::instance().getDpiScale();
int w = 0, h = 0;
@@ -1315,8 +1357,11 @@ int main(int argc, char* argv[])
SDL_SetWindowSize(window, dpiTargetW, dpiTargetH);
DEBUG_LOGF(" Deferred resize retry %d: %dx%d -> %dx%d\n",
dpiResizeRetries, curW, curH, dpiTargetW, dpiTargetH);
winlog("RETRY %d: cur %dx%d != target %dx%d -> SetWindowSize(target)",
dpiResizeRetries, curW, curH, dpiTargetW, dpiTargetH);
} else {
// Size matches — done
winlog("RETRY done: reached target %dx%d", dpiTargetW, dpiTargetH);
dpiResizeRetries = 0;
}
--dpiResizeRetries;
@@ -1357,6 +1402,10 @@ int main(int argc, char* argv[])
#endif
bool isDpiResize = dpiSettleFrames > 0 || dpiResizePending ||
std::abs(actualScale - storedScale) > 0.01f;
winlog("RESIZED(wait) evt=%dx%d actualScale=%.3f storedScale=%.3f dpiPending=%d settle=%d retries=%d -> %s",
waitEvent.window.data1, waitEvent.window.data2, actualScale, storedScale,
(int)dpiResizePending, dpiSettleFrames, dpiResizeRetries,
isDpiResize ? "IGNORED(dpi)" : "RECORDED(user)");
if (dpiResizePending) dpiResizePending = false;
if (!isDpiResize) {
int pct = (int)lroundf(storedScale * 100.0f);
@@ -1378,6 +1427,7 @@ int main(int argc, char* argv[])
#ifdef __APPLE__
newScale = 1.0f; // macOS handles Retina via DisplayFramebufferScale
#endif
winlog("SCALE_CHANGED(wait) reported newScale=%.3f", newScale);
handleDisplayScaleChange(window, newScale, savedSizeForScale,
lastKnownW, lastKnownH, dpiTargetW, dpiTargetH,
dpiResizeRetries, dpiResizePending, dpiSettleFrames);
@@ -1437,10 +1487,22 @@ int main(int argc, char* argv[])
if (event.type == SDL_EVENT_QUIT) {
app.beginShutdown();
}
if (event.type == SDL_EVENT_WINDOW_CLOSE_REQUESTED &&
if (event.type == SDL_EVENT_WINDOW_CLOSE_REQUESTED &&
event.window.windowID == SDL_GetWindowID(window)) {
app.beginShutdown();
}
// [WINLOG] Log a window MOVE only when it crosses to a different display (avoids drag spam).
if (event.type == SDL_EVENT_WINDOW_MOVED &&
event.window.windowID == SDL_GetWindowID(window)) {
SDL_DisplayID d = SDL_GetDisplayForWindow(window);
if (d != lastLoggedDisplay) {
lastLoggedDisplay = d;
winlog("MOVED to display %u pos=%d,%d dispScale=%.3f",
(unsigned)d, event.window.data1, event.window.data2,
SDL_GetWindowDisplayScale(window));
winlogState(window, " on-cross");
}
}
#ifdef DRAGONX_USE_DX11
// Handle window resize for DX11 swap chain
if (event.type == SDL_EVENT_WINDOW_RESIZED &&
@@ -1460,6 +1522,10 @@ int main(int argc, char* argv[])
#endif
bool isDpiResize = dpiSettleFrames > 0 || dpiResizePending ||
std::abs(actualScale - storedScale) > 0.01f;
winlog("RESIZED(poll) evt=%dx%d actualScale=%.3f storedScale=%.3f dpiPending=%d settle=%d retries=%d fsPending=%d -> %s",
event.window.data1, event.window.data2, actualScale, storedScale,
(int)dpiResizePending, dpiSettleFrames, dpiResizeRetries, (int)fontScaleResizePending,
isDpiResize ? "IGNORED(dpi)" : "RECORDED(user)");
if (dpiResizePending) dpiResizePending = false;
bool isFSResize = fontScaleResizePending;
if (fontScaleResizePending) fontScaleResizePending = false;
@@ -1491,6 +1557,7 @@ int main(int argc, char* argv[])
#ifdef __APPLE__
newScale = 1.0f; // macOS handles Retina via DisplayFramebufferScale
#endif
winlog("SCALE_CHANGED(poll) reported newScale=%.3f", newScale);
handleDisplayScaleChange(window, newScale, savedSizeForScale,
lastKnownW, lastKnownH, dpiTargetW, dpiTargetH,
dpiResizeRetries, dpiResizePending, dpiSettleFrames);
@@ -1879,6 +1946,7 @@ int main(int argc, char* argv[])
float s = dragonx::ui::material::Typography::instance().getDpiScale();
int logW = (int)lroundf((float)curW / s);
int logH = (int)lroundf((float)curH / s);
winlog("SHUTDOWN save: size=%dx%d storedScale=%.3f -> logical=%dx%d", curW, curH, s, logW, logH);
if (logW >= 1024 && logH >= 720) {
app.settings()->setWindowSize(logW, logH);
app.settings()->save();