chore: sync from fn-registry agent
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
// 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 <GLFW/glfw3.h>
|
||||
#define GLFW_EXPOSE_NATIVE_WIN32
|
||||
#ifdef _WIN32
|
||||
#include <GLFW/glfw3native.h>
|
||||
#endif
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user