// Closed-loop simulation of the guest vblank lock with the runtime's own // RefreshFit and VblankLock (video/refresh_fit.cpp, video/vblank_lock.cpp). // From the repo root: // clang++ -std=c++20 -O2 -Iruntime tests/vblank_lock_sim.cpp runtime/video/refresh_fit.cpp \ // runtime/video/vblank_lock.cpp -o build/vblank_lock_sim // build/vblank_lock_sim [--multiples] // (--multiples: only the displays at a whole multiple of 60 Hz, and the // planted faults; that part is the ctest. The 60 Hz tables print as in v0.1.6.) // // Model (times in ns): // display refreshes every P (true rate), phase random; a present made // before R_k - latch shows at R_k (FIFO, in order); its // completion is seen at R_k + notify (0.2-0.6 ms, sometimes late). // guest each vblank (VblankLock::Next, or free-running 16.667 ms) // releases one game frame, which reaches the presenter x later // (x drawn per scene). The presenter keeps only the newest // pending frame (older ones are dropped), picks it up ~0.5 ms // later, and presents at once when a swapchain image is free // (3 images: one on screen, at most 2 queued), else waits for // the next flip holding it. // variable (the second table) no grid: a present shows as soon as it is // latched, no sooner than 1/144 s after the last refresh, and // the display refreshes on its own after 1/48 s without one. // Counted from the 60th second (the lock has converged by then): dropped // frames, repeats (a refresh with no new frame while frames flow), slips, // the median vblank -> on screen latency, and the guest vblank's rate. // suspended (the last tables) the game is suspended (iOS: Control Center, // the app switcher): no vblank fires and its frames stand still, // the display shows what was queued, and the next vblank comes // exactly the time suspended later, then VblankLock::Resumed // (gpu/command_processor.cpp's Vsync thread). // multiple (--multiples) a display at 2-4 times 60 Hz (120, 180, 240) with // its multiple told to RefreshFit (the display mode's rate: video/ // refresh_multiple.h). A present still shows on the first refresh // after it (the display has spare refreshes, so nothing queues); // the presenter holds a new frame until VblankLock::PresentAt. // Counted per new frame: refreshes on screen against the vblanks' // (uneven: not `multiple` a vblank), and frames that couldn't make // their refresh (ready after the latch of the refresh `multiple` // after the one before their vblank, the 60 Hz frame's budget): // each such miss may cost two uneven frames (one long, the next // short), anything more is the cadence's own fault. Optionally the // vblank thread wakes late (Windows: 1.26 +- 0.45 ms), Frame Rate // 30 (two lock periods a vblank), and planted faults. #include