Skip to content

feat: add texture pooling on Darwin - #199

Draft
nmfisher wants to merge 7 commits into
developfrom
wip/darwin-texture-pool
Draft

feat: add texture pooling on Darwin#199
nmfisher wants to merge 7 commits into
developfrom
wip/darwin-texture-pool

Conversation

@nmfisher

@nmfisher nmfisher commented Jul 30, 2026

Copy link
Copy Markdown
Owner

The pool reuses one released macOS Flutter texture registration (and its MetalTextureWrapper) for matching widget dimensions across mount/unmount, instead of registering/allocating a fresh producer each time.

It is not required for correctness#202 fixes the leak on its own. The pool is an optimization:

  • avoids re-registering a Flutter texture and reallocating the CV/Metal stack on every mount/unmount
  • cuts remount latency for the common navigate-into/out-of-a-3D-screen pattern

Not merge-ready / open items

nmfisher and others added 5 commits July 30, 2026 11:52
…ace leak (#178)

MetalTextureWrapper created a fresh CVMetalTextureCache per texture and dropped
it on destroy. CVMetalTextureCache is designed to be a long-lived, per-device
object; releasing a cache does not synchronously free the IOSurfaces it has
cached, so CVMetalTextureCacheCreateTextureFromImage pinned ~one IOSurface per
allocation that was never returned.

Reuse one process-wide cache (lazily created per MTLDevice) and flush it on
teardown (MaximumTextureAge: 0). Root cause isolated and the fix verified in a
standalone Flutter reproducer (no thermion dependency):

  plain / shared cache + flush : flat (~0 MB/cycle)
  per-instance cache           : ~1 surface/cycle leak

In thermion's own leak probe, the per-cycle drift on a fresh wrapper dropped
from ~2.15 MB/cycle (~1 IOSurface) to ~0.43 MB/cycle (the Flutter-compositor
floor). Filament import/destroy is unaffected (still clean).

The cache access is not safe for concurrent use; thermion already serializes
texture allocation/teardown on its texture-mutation path (documented inline).

See docs/DARWIN_IOSURFACE_LEAK_ROOT_CAUSE.md for the full investigation.

Co-Authored-By: Claude <noreply@anthropic.com>
Adds an isolation probe that builds an imported color Texture + a depth
Texture + a RenderTarget each cycle and destroys RT -> color -> depth, with
no Flutter widget and no rendering. Pins that the Filament render-target
rebuild is NOT a churn source: measured 0.58 MB drift over 16 cycles (flat),
vs 0.08 MB for texture-import-only. This rules out the RT/depth rebuild as
the source of the mount/unmount residual and obviates any "retain the RT
across mounts" fix.

Co-Authored-By: Claude <noreply@anthropic.com>
Adds a probe that builds color+depth+RT and renders 5 frames into it each
cycle (no Flutter widget). Result: ~0.55 MB/cycle climb with a -4.48 MB
burst reclaim at cycle 9 -> 2.88 MB net over 16 cycles. That burst-reclaim
shape is churn (Metal per-frame command-buffer/encoder pools), not a leak.

This localizes the mount/unmount residual: texture/RT create-destroy is
balanced (filament-rt = 0.58 MB/16); the residual is Metal frame-pool churn
during live rendering, which is bounded and self-reclaiming. No Filament GC
API is relevant because Filament is not the holder.

Co-Authored-By: Claude <noreply@anthropic.com>
…econdary (#178)

The created-instance counters (MetalTextureWrapper / FlutterMetalTextureAdapter)
are now documented as the authoritative leak check: they are flat across
mount/unmount sessions and catch any producer leak unconditionally.

phys_footprint drift is demoted to a loose secondary guard with a per-session
budget (~1 surface/session) instead of one surface total. This sits above the
~0.8 MB/session Metal render-churn floor (per the filament-rt-render probe)
but well below the one-surface-per-session leak the test targets. The old
single-surface total budget false-positived on that churn.

Main regression now passes on macOS while still guarding the real leak.

Co-Authored-By: Claude <noreply@anthropic.com>
@nmfisher nmfisher changed the title WIP: bound macOS texture surface allocations Fix Darwin memory leak from texture lifecycle Jul 30, 2026
@nmfisher nmfisher changed the title Fix Darwin memory leak from texture lifecycle feat(darwin): reuse one registered texture across mount/unmount (pool) Jul 30, 2026
@nmfisher nmfisher changed the title feat(darwin): reuse one registered texture across mount/unmount (pool) feat: add texture pooling on Darwin Jul 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant