Dedup identical RGBA payloads against the last frame's Arc<RenderImage>

WebSurfaceFrame::from_parts was unconditionally calling
Arc::new(RenderImage::new([image::Frame::new(image_buffer)])) on
every live frame, even when the underlying bytes were
byte-for-byte identical to the previous frame. At 60 fps on a 1080p
canvas that was ~960 MB/s of host-side cloning plus a fresh GPUI
texture allocation on every tick — the bottleneck Linus + Karpathy
+ Jony flagged as the next material step after dropping the
file-system pixel pipe (a80d039).

A thread_local single-slot cache in web_surface_frame.rs now keys
on a 64-bit DefaultHasher of the raw RGBA bytes. On a cache hit
the existing Arc<RenderImage> is reused; on a miss the buffer is
built once, stored, and returned. Steady-state idle pages stop
churning the GPUI texture pool entirely. Hash collisions are
1 in 2^64 — if that ever becomes a real worry, the cache key can
be widened to length + a sample of bytes before paying the full
memcmp; not worth doing today.

The T10 red guard
(identical_live_frames_share_render_image_arc) drops its
#[ignore] attribute outright per the contract it documented.
The T7 red guard (user_click_in_rendered_web_canvas_reaches_input_pipeline)
remains ignored — it's a separate diagnosis tracked under T13.

cargo test --bin ely_app: 113 passed, 0 failed, 1 ignored
(was 112+2, T10 guard went green).
cargo test --bin ely_app -- --ignored: 1 failed (only T7 click
pipeline remains red).

Follow-ups (left for the endgame T10 IOSurface path):
  * a per-tab cache would prevent multi-tab switching from
    thrashing the single slot; defer until a real multi-tab
    scroll benchmark shows it matters.
  * the endgame is OffscreenRenderingContext + IOSurface so the
    GPU texture itself is the source of truth and the host-side
    Vec<u8> + ImageBuffer + RenderImage allocation chain
    disappears entirely.
This commit is contained in:
2026-05-10 19:36:08 -04:00
parent 414ba3d158
commit 7f3b8b42b3
2 changed files with 57 additions and 22 deletions
+6 -13
View File
@@ -271,20 +271,13 @@ async fn baseline_overlay_div_receives_simulated_click(cx: &mut TestAppContext)
/// against the last bytes or switch to `OffscreenRenderingContext` +
/// IOSurface so the GPU texture is the source of truth.
///
/// `#[ignore]` so default `cargo test` stays green; remove the
/// attribute the moment T10 lands so the regression is permanent.
/// Regression guard: with the single-slot `LAST_FRAME_IMAGE` cache in
/// `web_surface_frame.rs`, two `ServoLiveFrame` inputs carrying
/// byte-identical RGBA payloads now share the same `Arc<RenderImage>`.
/// Without this guard a regression that drops the cache silently
/// returns to ~960 MB/s of host-side RGBA cloning + per-frame GPUI
/// texture allocations.
#[test]
#[ignore = "T10 red guard. Failure mode confirmed via `cargo test -- --ignored`: \
two ServoLiveFrames carrying byte-identical RGBA payloads still \
produce distinct Arc<RenderImage> instances, because \
WebSurfaceFrame::from_parts unconditionally calls \
Arc::new(RenderImage::new(...)). At 60 fps 1080p that is \
~960 MB/s of host-side RGBA cloning + GPUI texture allocations \
and is the next bottleneck after the file-system pipe. The \
fix lives in WebSurfaceFrame (interim: dedup upload against \
last frame bytes) or in the rendering pipeline (final: \
OffscreenRenderingContext + IOSurface zero-copy). The fix \
commit must remove this attribute outright — not toggle it."]
fn identical_live_frames_share_render_image_arc() {
let width = 16u32;
let height = 8u32;