chore: sync from fn-registry agent
This commit is contained in:
@@ -0,0 +1,6 @@
|
|||||||
|
# Standalone smoke for the AltSnap viewport-jitter regression. No registry
|
||||||
|
# functions linked: this is a pure framework test. Built always so it gets
|
||||||
|
# cross-compiled to Windows alongside the rest of the apps.
|
||||||
|
add_imgui_app(altsnap_jitter_test
|
||||||
|
main.cpp
|
||||||
|
)
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
---
|
||||||
|
name: altsnap_jitter_test
|
||||||
|
lang: cpp
|
||||||
|
domain: tools
|
||||||
|
description: "Regression test for multi-viewport window jitter triggered by external window movers (AltSnap on Windows, tiling WMs). Drives glfwSetWindowPos every frame and asserts ImGui viewport tracks the OS pos within 1px."
|
||||||
|
tags: [imgui, test, regression, headless]
|
||||||
|
uses_functions: []
|
||||||
|
uses_types: []
|
||||||
|
framework: "imgui"
|
||||||
|
entry_point: "main.cpp"
|
||||||
|
dir_path: "cpp/apps/altsnap_jitter_test"
|
||||||
|
repo_url: ""
|
||||||
|
---
|
||||||
|
|
||||||
|
# altsnap_jitter_test
|
||||||
|
|
||||||
|
Headless C++ harness para validar el fix de jitter de multi-viewport (AltSnap, tiling WMs).
|
||||||
|
|
||||||
|
## Que hace
|
||||||
|
|
||||||
|
1. Arranca `fn::run_app` con viewports ON (config exacta que reproducia el bug).
|
||||||
|
2. Tras 8 frames de warmup, mueve la ventana via `glfwSetWindowPos` un step por frame durante 60 frames.
|
||||||
|
3. Cada frame muestrea pos OS (`glfwGetWindowPos`) y pos ImGui (`GetMainViewport()->Pos`).
|
||||||
|
4. Cuenta divergencias > 1px entre ambos. Cero divergencias = PASS, exit 0.
|
||||||
|
5. Reporta tambien clamp del WM (cuando el compositor rechaza la pos pedida).
|
||||||
|
|
||||||
|
## Run
|
||||||
|
|
||||||
|
WSL/Linux:
|
||||||
|
```bash
|
||||||
|
cd cpp && cmake -B build/linux -DFN_BUILD_TESTS=OFF
|
||||||
|
cmake --build build/linux --target altsnap_jitter_test -j4
|
||||||
|
xvfb-run -a -s "-screen 0 1280x800x24" \
|
||||||
|
env LIBGL_ALWAYS_SOFTWARE=1 GALLIUM_DRIVER=llvmpipe \
|
||||||
|
build/linux/apps/altsnap_jitter_test/altsnap_jitter_test
|
||||||
|
echo "EXIT: $?"
|
||||||
|
```
|
||||||
|
|
||||||
|
Windows (cross-compile + Desktop deploy):
|
||||||
|
```bash
|
||||||
|
source bash/functions/infra/e2e_run_cpp_windows.sh
|
||||||
|
e2e_run_cpp_windows altsnap_jitter_test
|
||||||
|
```
|
||||||
|
|
||||||
|
## Output esperado (PASS)
|
||||||
|
|
||||||
|
```
|
||||||
|
[altsnap_jitter] f=0 target=(200,200) actual=(200,200) vp=(200.0,200.0) sync_d=0 clamp_d=0
|
||||||
|
[altsnap_jitter] f=10 target=(240,200) actual=(240,200) vp=(240.0,200.0) sync_d=0 clamp_d=0
|
||||||
|
...
|
||||||
|
[altsnap_jitter] DONE frames=60 max_sync_divergence=0px max_clamp_divergence=0px bad_sync=0 bad_clamp=0
|
||||||
|
[altsnap_jitter] PASS
|
||||||
|
```
|
||||||
|
|
||||||
|
## Criterio de fallo
|
||||||
|
|
||||||
|
`bad_sync > 0` significa que ImGui viewport->Pos quedo fuera de sincronia con la pos real OS, exactamente la condicion que produce el visible "temblor" cuando AltSnap arrastra la ventana. Ese feedback loop es lo que arregla la patch en `cpp/framework/app_base.cpp` (callback GLFW + per-frame sync de viewports).
|
||||||
@@ -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