#479

perf(macos): reliable native scrolling, display-rate pacing, and GPU compositing for the canvas presenter

Openrdevwants to merge intofrom

Summary

  • Scroller and content stay in step. Clip-view notification suppression was shared across all scroll drivers, so a user scroll that settled while another region was being written was dropped. It is now per driver. Each push also compares the clip view's real origin with what the engine last published and reports any drift, so one lost report can no longer leave the scroller and content apart for good.
  • Delivery stays live during AppKit tracking. Scroll-driver offsets, pointer motion, and frame events queued with dispatch_after stalled inside nested tracking loops. They now use common-mode CFRunLoop timers. The driver view opts out of responsive scrolling, which latched gestures away from the router.
  • Registered image pixels hash once, at registration, instead of on every frame plan.
  • Display-rate pacing. Frame timers use the screen's maximum frame rate, clamped to 30–120 Hz, instead of a fixed 60 Hz, so ProMotion displays animate and scroll at full rate.
  • Retained scroll rendering. A cached command raster is retargeted when the new command equals the cached one under a single translation, instead of re-rasterizing through CoreGraphics on every wheel event. Quads draw 1:1 into device pixels through a nearest sampler. Independent cache misses build concurrently and publish in display order, and the per-entry cache ceiling rises to 16 MiB.
  • Layer runs. Overlapping consecutive commands composite through one layer, so shared antialiased edges resolve at full precision and match the CPU path. Disjoint commands keep their own cached rasters.
  • GPU quads. Registered images under an axis-aligned transform draw as GPU-sampled quads. Solid and rounded rectangles and rectangle strokes draw as analytic coverage quads, including under rounded clips. The raster cache evicts in one LRU pass, and text widths and ink extents are cached.

Notes for review

  • Debug traces: NATIVE_SDK_SCROLL_TRACE=1 and NATIVE_SDK_RASTER_CACHE_TRACE=1.
  • scripts/test-macos-scroll-host.sh runs the real AppKit host's delivery and raster-cache contracts headlessly. zig build test-macos-scroll-host runs it, and zig build test includes it on macOS.

Verification

  • zig build test on macOS with Zig 0.16.0: passed, including the scroll host harness.
  • scripts/gate.sh fast: every step passed except examples-mobile, which needs the Android NDK. The NDK isn't installed here, and upstream main fails the same way.
  • New tests:
    • a driver report the engine could not apply still arms the next correction
    • registered images keep a cached content fingerprint through registry compaction
    • registered image cache retains identical content and uploads pixel or dimension changes
    • Scroll host harness checks for tracking-mode delivery, and byte-for-byte equality between a retargeted raster and a fresh one, including rounded and surface clips.