From eb08b81706ce91cbd2d76400756fce5b4108462d Mon Sep 17 00:00:00 2001 From: DanS Date: Fri, 10 Jul 2026 00:49:59 -0500 Subject: [PATCH] chore(window): add [WINLOG] window-dimension tracing for the multi-monitor bug MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- src/main.cpp | 70 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index 38481cc..23065d2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -71,6 +71,7 @@ extern "C" HRESULT __stdcall SHGetPropertyStoreForWindow(HWND hwnd, REFIID riid, #endif #include +#include #include #include #include @@ -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();