Skip to content

Track Vello memory footprint and Fission fork decision #81

Description

@zcourts

Summary

Fission's native GPU path inherited a very high fixed memory floor from Vello's dynamic GPU buffers. On a simple 800x600 Fission screen, release builds were using hundreds of MiB before scene complexity justified it.

This issue records the investigation, the fork we are carrying for the 0.5.1 release path, and why we are not planning to PR this exact fix upstream at this point.

Evidence

Measured on macOS / Apple M1 Pro with release builds and vmmap -summary <pid> physical footprint:

Case Renderer / mode Physical footprint Notes
examples/inbox default Metal/Vello 214.4 MiB Simple inbox screen, 800x600 default window.
examples/inbox FISSION_RENDERER=software 57.5 MiB Same app without Vello GPU path.
examples/counter default Metal/Vello 247.3 MiB Minimal app still paid the Vello fixed dynamic-buffer cost.
examples/motion-memory-repro default Metal/Vello 262.9 MiB Plain/default repro path.
Minimal Vello two-rect probe after renderer creation ~17 MiB Vello initialized, no render submitted.
Minimal Vello two-rect probe after first render_to_texture ~192 MiB Large allocation comes from Vello render-time buffers, not Fission display-list complexity.

The root cause we found is Vello's fixed bump-allocated dynamic buffer sizing in vello_encoding/src/config.rs::BufferSizes::new. The fixed dynamic working set was about 165 MiB before ordinary scene complexity mattered, with large allocations for tiles, lines, segment counts, path segments, blend spill, and ptcl.

External context reviewed

We looked at the existing Vello/Xilem memory discussions and adjacent renderer work:

Vello issue 366 is the key reason we should not try to upstream this exact Fission fork as-is. The fork uses the broad direction of profiling, validation, and retry/readback. That is one of the solution shapes Vello has explicitly debated and not adopted as its preferred production strategy because of the tradeoffs around readback, async behavior, and robust dynamic memory.

So for now we should treat this as a Fission product decision: the memory difference is large enough to justify carrying a fork, but we should not spend time trying to convince upstream to accept this exact design.

What we tried

1. Software renderer comparison

The software path brought examples/inbox down to ~57.5 MiB, proving the app/display-list itself was not inherently 200+ MiB.

2. Minimal Vello probe

A minimal two-rectangle Vello render jumped from ~17 MiB after renderer creation to ~192 MiB after first render. This isolated the problem to Vello render-time GPU buffers rather than Fission widgets, text, image cache, retained display list, or app state.

3. Initial Worka/Fission Vello fork

Fork: https://github.com/worka-ai/vello
Branch: worka/dynamic-gpu-buffers

The first experiment replaced the fixed dynamic buffer constants with scene/viewport-derived initial sizing. It proved the source of the memory floor:

Case Physical footprint Peak Notes
examples/inbox 65.2 MiB 79.0 MiB 800x600 launch.
examples/counter 52.5 MiB 66.1 MiB 800x600 launch.
examples/motion-memory-repro 88.3 MiB 106.5 MiB 800x600 launch.

This was not complete because it had no grow/retry validation.

4. Profiled dynamic-buffer fork

The current fork adds:

  • RenderWorkloadProfile / DynamicBufferPolicy so callers can provide target size, tile coverage, scene complexity, and sizing policy.
  • Vello grow/retry validation by reading GPU bump counters after the coarse pass.
  • Bounds checks for ptcl and segments writes in the coarse shader so failed allocations are reported instead of producing undefined output.
  • Retry-counter sanitising so counters from stages after an earlier failed stage are not blindly trusted.
  • Fission integration that calculates the workload profile while walking the retained display list and passes it to Vello per render.

Measured result:

Case Physical footprint Peak Notes
examples/inbox 50.0 MiB 64.6 MiB 800x600 launch.
examples/counter 42.4 MiB 56.5 MiB 800x600 launch.
examples/motion-memory-repro 74.1 MiB 88.7 MiB 800x600 launch.

Inbox stability over 60s stayed around 47.6-49.3 MiB physical footprint with a 63.6 MiB peak.

Resize screenshots after the final profile/grow path:

Logical viewport Physical footprint after screenshot Peak
800x600 61.3 MiB 66.4 MiB
1200x900 95.8 MiB 104.1 MiB
1600x1200 113.9 MiB 125.3 MiB

Screenshots were non-flat and manually inspected; the UI rendered correctly rather than a grey/blank frame.

5. wgpu::MemoryHints::MemoryUsage

We also set wgpu::MemoryHints::MemoryUsage in Vello's RenderContext device creation path.

On the tested macOS/Metal host, this did not materially reduce memory compared with the profiled buffer fork:

Case Physical footprint Peak
examples/inbox 62.5 MiB 88.6 MiB
examples/counter 52.5 MiB 81.2 MiB
examples/motion-memory-repro 78.9 MiB 90.6 MiB

It may still matter on Vulkan/DX12 backends where WGPU allocator chunk sizing is the larger contributor.

6. Direct WGPU2D prototype

We prototyped a selectable fission-render-wgpu2d path. It currently renders solid rectangles/strokes and placeholders for text, images, paths, SVG, surfaces, gradients, clips, transforms, and opacity.

It is useful as an isolation/prototype path but not a release-quality alternative yet:

Case Physical footprint Peak Notes
examples/inbox 84.8 MiB 98.1 MiB Placeholder-heavy renderer.
examples/counter 57.0 MiB 86.7 MiB Placeholder-heavy renderer.
examples/motion-memory-repro 111.0 MiB 163.3 MiB Placeholder-heavy renderer.

We have parked this prototype for later and are moving forward with the Vello fork for now.

Current decision

For the 0.5.1 path we should publish and depend on Fission-owned Vello fork packages:

  • fission-vello-encoding
  • fission-vello-shaders
  • fission-vello

Fission can depend on fission-vello using Cargo package rename so the code can continue importing vello::....

Risk / known state

No obvious correctness regression has been seen in normal tested app/viewports, and the memory improvement is large enough to justify the fork.

There is still a high-DPI/very-large-render-target stress case around 2400x1800 logical on a 2x display. The fork reports allocation failure instead of silently going grey or allocating without bound, but the shell still expects render success. That should be tracked as follow-up hardening, not as a blocker for adopting the fork for normal app sizes.

Acceptance criteria

  • Fission 0.5.1 uses the Fission-owned Vello fork packages instead of crates.io vello directly.
  • The memory profile doc remains updated with pre/post measurements.
  • examples/inbox, examples/counter, and examples/motion-memory-repro stay within the measured post-fork range on the same harness.
  • Android/winit upgrade work continues separately so the 0.5.1 release also avoids android-activity 0.5.2 startup crashes.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions