Profiling (Pixel 7 + SM-T819) showed the chart CPU-bound in Skia
analytic-AA path fill plus a full-buffer copy on every frame.
- LineSignalRenderer: draw the signal polyline with anti-aliasing off,
via a cached common Paint + drawIntoCanvas (KMP-safe, no nativeCanvas).
Flips Skia from CPU coverage-mask raster to GPU tessellation;
configured strokeWidth still honored. Removes the old Stroke cache.
- TieredBuffer/CircularBuffer: replace the full ~60k-sample copy+scan in
the draw hot path with an O(log n) bisect window read (copyWindow).
Output byte-identical (equivalence tests incl. ring-wrap + edges).
- RealtimeChart: split the single Canvas into a cold layer (Y-axis line,
grid, labels) and a hot layer (signal, X-axis, crosshair) so per-frame
TextMeasurer layout stops running every frame. Y range stabilized to
the tick grid and shared by both layers (signal never clips).
- minSdk 26 -> 24.
Renderer unit test moved commonTest -> iosTest (Compose Paint delegates
to a non-mockable android.graphics.Paint stub on the JVM host; Skiko
backs it for real). Verified on SM-T819 (release): frame time 150ms ->
48ms, ~10 -> ~31 fps at full 8x200 Hz load.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>