flutter / flutter/flutter

[Impeller] Unbounded Vulkan resource growth in embedder compositor path (EmbedderExternalViewEmbedder)

Open
#183,267 2 comments 0 reactions 0 assignees View on GitHub
e: impeller engine has reproducible steps P1 platform-linux team-engine triaged-engine
Dominant language
Dart
Stars
179k
Forks
31.1k
PR merge metrics
PR metrics pending

Description

## Description

The Impeller Vulkan backend caches command pools and descriptor pools in thread-local storage. These caches grow unbounded unless `DisposeThreadLocalCachedResources()` is called each frame.

PRs #182265 and #182402 fixed this for two code paths:
- `GPUSurfaceVulkanImpeller` delegate path (#182265)
- `libImpeller` C API / `ImpellerVulkanSwapchainAcquireNextSurfaceNew` (#182402)

However, the **Flutter Embedder API compositor path** through `EmbedderExternalViewEmbedder` was missed.

## Steps to reproduce

1. Use the Flutter Embedder API with Impeller Vulkan rendering (`FlutterCompositor` / `EmbedderExternalViewEmbedder`)
2. Run any Flutter app
3. Monitor memory usage over time
4. Observe linear memory growth (~20-95KB per frame)

## Root cause

`EmbedderExternalViewEmbedder::SubmitFlutterView()` calls `builder.Render()` which calls `EmbedderExternalView::Render()` which calls the `impeller::RenderToTarget(ContentContext&, ...)` overload at `dl_dispatcher.cc:1360`. This overload does **not** call `DisposeThreadLocalCachedResources()`.

In contrast, the sibling `RenderToOnScreenSurface()` overload at `dl_dispatcher.cc:1300` **does** include the cleanup call.

## What accumulates

Without `DisposeThreadLocalCachedResources()`:
- **VkCommandBuffers**: New allocation every frame, never reused (pool never reset)
- **VkDescriptorPools**: New pool created every ~80 frames, never freed
- **collected_buffers_**: C++ vector grows without bound

## Proposed fix

Add `DisposeThreadLocalCachedResources()` after `builder.Render()` in `SubmitFlutterView`, matching the pattern from #182265 and #182402:

```cpp
builder.Render();

if (aiks_context) {
aiks_context->GetContext()->DisposeThreadLocalCachedResources();
}
```

The call goes through the base `impeller::Context` interface, so it is a no-op on non-Vulkan backends.

## Environment

Found on Linux/Wayland with a custom Vulkan embedder, but affects all embedder API users with Impeller/Vulkan.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.