Repository navigation
Cones: stop clipping to the floor every frame - #306
Conversation
On Windows, Flutter draws through Skia without a stencil buffer, so a clip to any shape but a rectangle, a circle or a small convex polygon is a mask drawn on the CPU for every draw under it. Every cone was clipped to the map's whole painted floor (the replay once per frame around all cones; the editor per cone), and editor cones also clipped to their visibility shape. That was most of what cones cost. - The revealed map is clipped to the floor once, in its own repaint boundary (RevealedFloor/ConeCoverageLayer take the floor), and the cone mask is composited over it in a clip layer that saves a layer. A colour filter would group them too, but the engine caches a filter's children as one image, redrawing the map with every mask. - Cones joining a layer that clips the map skip their own floor clip; drag previews, screenshots and the replay's no-worker fallback still clip themselves. - An editor cone without height floors draws its lit area (visibility within reach) as one path made once per painter, with no clip. The only visible change: floor edges inside cones are now antialiased (1 px lines, at most 17/255 on screenshots of a replay). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Warning Review limit reachedYou've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Next included review available in 53 minutes. View limit details
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info
📝 Walkthrough
Merge Risk: ⚪ Minimal · up to
|
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
View cones were the most expensive thing on screen during replay playback and editor page flips. The reason turned out to be specific to Windows: Flutter draws there with Skia and no stencil buffer. So every clip to a complex shape becomes a mask that the CPU draws and uploads. Every cone was clipped to the map's whole painted floor, and editor cones were also clipped to their own visibility shape. This PR removes those clips from the per-frame path without changing what the user sees.
ci: analyze test web
What changed
RevealedFloorandConeCoverageLayertake the painted floor (SvgHeightMapTransform.floorOnScreen). They clip the revealed map to it once, inside the map's own repaint boundary. The replay's cone painter no longer clips at all. Editor cones that join such a layer skip their own floor clip. Drag previews, screenshots and the replay's no-worker fallback still clip themselves.dstIn, inside a clip-rect layer that saves a layer. That isolates the two, sodstIncan't reach the map underneath. A colour-filter layer also groups them, but the engine then caches "map + this frame's mask" as a new image every frame, and the map never gets its own cache.Path.combine, then drawn with no clip. If PathOps ever fails on an outline, that cone falls back to the old clip-and-circle draw, and the failure is remembered.Numbers
Release builds on Windows, 144 Hz (6.9 ms budget per frame). Raster time from
FrameTiming, three interleaved rounds. Replay = 8 s of playback of a 13.08 Summit replay. Page flips = 10 flips between two pages captured from it. Medians:In rounds 1 and 2 this PR's replay ran at 2.5 to 2.6 ms. In round 3 one run hit a load spike on the desktop (5.9 ms, with its page flips slow too). Three earlier series of this design measured 2.5 to 2.9 ms.
How we got here
This was a measurement-driven design loop with a second reviewer (Astra), four rounds:
fAvoidStencilBuffersmakes Skia's clip stack fall back to software masks.Checked
cone_coverage_test.dartcovers the floor contract: a layer given the floor reveals only the floor and tells cones not to clip; without it, cones clip themselves. It is mutation-checked: ignoring the floor, always clipping, and dropping the save layer each fail.🤖 Generated with Claude Code
Summary by CodeRabbit