// altsnap_jitter_test — automated regression test for the multi-viewport // jitter that AltSnap (and other Win32 window movers) trigger on the C++ // app shell. The test spawns the standard fn::run_app with viewports ON, // then drives the main GLFW window with glfwSetWindowPos every frame to // emulate an external mover (AltSnap, tiling WM, snap-assist, ...). For // each frame it samples the actual OS window position and ImGui's main // viewport->Pos, and asserts both stay in sync within a 1-pixel tolerance // after a 1-frame settle window. Without the anti-jitter patch in // app_base.cpp the two diverge and the test exits 1. // // Linux/WSL: runs the same loop. With xvfb the window manager honors // glfwSetWindowPos, so the test is meaningful in CI. On a real WSL X // display some compositors clamp positions; the test logs and accepts // "OS clamped" frames as long as ImGui tracks the clamped value. // // Windows (target): cross-compiled via build_cpp_windows, deployed to // the Desktop apps dir, launched via cmd.exe; this is where the original // AltSnap symptom lives, so any divergence here is the real regression. #include "app_base.h" #include "imgui.h" #include #define GLFW_EXPOSE_NATIVE_WIN32 #ifdef _WIN32 #include #endif #include #include #include #include namespace { struct Sample { int frame = 0; int target_x = 0; int target_y = 0; int actual_x = 0; int actual_y = 0; float vp_x = 0.0f; float vp_y = 0.0f; }; constexpr int kWarmupFrames = 8; // wait for the platform to settle constexpr int kTestFrames = 60; // measured frames constexpr int kStepPx = 4; // px / frame constexpr int kBaseX = 200; constexpr int kBaseY = 200; constexpr float kSyncTolerance = 1.0f; // ImGui viewport vs OS pos constexpr int kClampTolerance = 6; // OS-applied vs target (compositor clamp ok up to this) GLFWwindow* g_window = nullptr; int g_frame = 0; int g_max_sync = 0; int g_max_clamp = 0; int g_bad_sync = 0; // frames where vp diverges from OS pos > tolerance int g_bad_clamp = 0; // frames where OS pos diverges from target > clamp tolerance bool g_done = false; void render() { // Locate the main window once. ImGui_ImplGlfw stashes it on the main // viewport's PlatformHandle right after init. if (!g_window) { if (ImGuiViewport* vp = ImGui::GetMainViewport()) { g_window = (GLFWwindow*)vp->PlatformHandle; } } if (!g_window) return; if (g_frame < kWarmupFrames) { ++g_frame; return; } int rel = g_frame - kWarmupFrames; if (rel < kTestFrames) { // Drive the OS window from inside the render loop. Mimics what an // external mover (AltSnap, tiling WM) would do: a fresh SetWindowPos // every tick. With the fix in place ImGui's main viewport->Pos must // follow within the same frame because of the GLFW pos callback. int tx = kBaseX + rel * kStepPx; int ty = kBaseY; glfwSetWindowPos(g_window, tx, ty); // Sample AFTER the SetWindowPos. The callback fires synchronously on // most platforms; if not, the per-frame sync pass in app_base.cpp // covers it before this render() call on the next frame. We measure // both here for diagnostics. int ax = 0, ay = 0; glfwGetWindowPos(g_window, &ax, &ay); ImVec2 vp = ImGui::GetMainViewport()->Pos; int sync_dx = (int)std::abs(vp.x - (float)ax); int sync_dy = (int)std::abs(vp.y - (float)ay); int clamp_dx = std::abs(ax - tx); int clamp_dy = std::abs(ay - ty); int sync_d = std::max(sync_dx, sync_dy); int clamp_d = std::max(clamp_dx, clamp_dy); if ((float)sync_d > kSyncTolerance) ++g_bad_sync; if (clamp_d > kClampTolerance) ++g_bad_clamp; g_max_sync = std::max(g_max_sync, sync_d); g_max_clamp = std::max(g_max_clamp, clamp_d); // Verbose first/last frame for log readability. if (rel == 0 || rel == kTestFrames - 1 || (rel % 10 == 0)) { std::fprintf(stdout, "[altsnap_jitter] f=%d target=(%d,%d) actual=(%d,%d) vp=(%.1f,%.1f) sync_d=%d clamp_d=%d\n", rel, tx, ty, ax, ay, vp.x, vp.y, sync_d, clamp_d); std::fflush(stdout); } ++g_frame; return; } if (!g_done) { std::fprintf(stdout, "[altsnap_jitter] DONE frames=%d max_sync_divergence=%dpx max_clamp_divergence=%dpx bad_sync=%d bad_clamp=%d\n", kTestFrames, g_max_sync, g_max_clamp, g_bad_sync, g_bad_clamp); std::fflush(stdout); g_done = true; } glfwSetWindowShouldClose(g_window, GLFW_TRUE); } } // namespace int main(int argc, char** argv) { (void)argc; (void)argv; fn::AppConfig cfg; cfg.title = "altsnap_jitter_test"; cfg.width = 800; cfg.height = 600; cfg.vsync = false; // run as fast as possible cfg.viewports = true; // exact config that exhibits the bug cfg.init_gl_loader = false; int rc = fn::run_app(cfg, render); if (rc != 0) { std::fprintf(stderr, "[altsnap_jitter] run_app returned %d\n", rc); return rc; } // Pass criterion: every measured frame must have ImGui viewport tracking // the OS-reported window pos within 1px. A frame where the OS clamped // the position (compositor) is fine as long as ImGui follows the clamp. bool pass = (g_bad_sync == 0); std::fprintf(stdout, "[altsnap_jitter] %s\n", pass ? "PASS" : "FAIL"); return pass ? 0 : 1; }