flutter / flutter/flutter

[Impeller] Severe rasterizer regression (`CreateGlyphAtlas` pathology) under high distinct dynamic font workload (~600+ fonts)

Open
#190,027 1 comment 1 reaction 0 assignees View on GitHub
e: impeller engine P2 platform-android team-engine triaged-engine
Dominant language
Dart
Stars
179k
Forks
31.1k
PR merge metrics
PR metrics pending

Description

### Steps to reproduce

1. Create a Flutter application that loads a large set of unique dynamic font files (500+ distinct `.ttf`/`.woff2` fonts loaded dynamically per page/view).
2. Render dense, highly diacritic text (e.g., complex Arabic scripts with full tashkeel).
3. Swipe through views rapidly to force dynamic font switching and glyph rasterization.
4. Profile using DevTools / Observatory on an Android device comparing Impeller vs Skia (`--no-enable-impeller`).

### Expected results

Impeller should manage and cache glyph atlas textures efficiently across distinct dynamic font families without triggering expensive GPU thread blocking or dropped UI frames.

The rasterizer thread should process frames synchronously with the UI thread (locked ~1:1 ratio) with worst-case UI frames remaining under the 16.7 ms budget (60 FPS).

### Actual results

Impeller experiences a severe `CreateGlyphAtlas` bottleneck, blocking the GPU rasterization process for **45–57 ms (p50)** per frame transition, causing severe frame drops (jank) and pushing worst-case raster frames up to **143.6 ms**.

The UI thread becomes severely starved, resulting in a **151 : 598 (~1:4) Raster-to-UI frame ratio**, where the UI thread produces frames the rasterizer cannot consume in time.

Reverting to Skia (`--no-enable-impeller`) completely resolves the issue:
* `CreateGlyphAtlas` slices disappear entirely (0 ms).
* Median GPU raster cost drops by **12x** (61.0 ms → 5.0 ms).
* Worst-case GPU raster cost drops by **20x** (143.6 ms → 7.2 ms).
* Worst UI frame drops from 60.7 ms to **15.7 ms** (**Zero Jank**).
* Frame consumption locks back to **272 : 274 (~1:1)**.

#### Summary Table
| Metric | Impeller (Default) | Skia (`--no-enable-impeller`) | Impact / Variance |
| :--- | :--- | :--- | :--- |
| **`CreateGlyphAtlas` (Total/p50)** | **9,156 ms total** (p50: 45–57 ms) | **Absent entirely (0 ms)** | Skia bypasses atlas rebuild bottleneck |
| **`GPURasterizer::Draw` (p50)** | 61.0 ms | **5.0 ms** | **~12x speedup under Skia** |
| **`GPURasterizer::Draw` (worst)** | 143.6 ms | **7.2 ms** | **~20x improvement under Skia** |
| **Worst UI Frame** | 60.7 ms | **15.7 ms** | Below 16.7 ms budget (**Zero Jank**) |
| **Raster : UI Frame Ratio** | 151 : 598 (~1:4 bottleneck) | **272 : 274 (~1:1 locked)** | Impeller UI thread produces unconsumable frames |

### Code sample

``` dart

// Minimal conceptual reproduction for loading distinct dynamic fonts per page view:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

void main() => runApp(const DynamicFontApp());

class DynamicFontApp extends StatelessWidget {
const DynamicFontApp({super.key});

@override
Widget build(BuildContext context) {
return const MaterialApp(
home: DynamicFontPageView(),
);
}
}

class DynamicFontPageView extends StatelessWidget {
const DynamicFontPageView({super.key});

// Simulates switching through views where each page requires a unique dynamic font family
Future _loadFontForPage(int pageIndex) async {
final fontLoader = FontLoader('CustomFont_$pageIndex');
// Load dynamic font asset bytes
final fontData = await rootBundle.load('assets/fonts/font_$pageIndex.ttf');
fontLoader.addFont(Future.value(fontData));
await fontLoader.load();
}

@override
Widget build(BuildContext context) {
return PageView.builder(
itemCount: 600,
itemBuilder: (context, index) {
return FutureBuilder(
future: _loadFontForPage(index),
builder: (context, snapshot) {
if (snapshot.connectionState != ConnectionState.done) {
return const CircularProgressIndicator();
}
return Center(
child: Text(
'سُورَةٌ أَنزَلْنَٰهَا وَفَرَضْنَٰهَا وَأَنزَلْنَا فِيهَا ءَايَٰتٍ بَيِّنَٰتٍ لَّعَلَّكُمْ تَذَكَّرُونَ',
style: TextStyle(
fontFamily: 'CustomFont_$index',
fontSize: 24,
),
textAlign: TextAlign.center,
),
);
},
);
},
);
}
}

```

### Screenshots or Video

Screenshots / Video demonstration

[Upload media here]

### Logs

=== Impeller Run Performance Trace ===
GPURasterizer::Draw p50: 61.0 ms
GPURasterizer::Draw worst: 143.6 ms
CreateGlyphAtlas: 9156 ms total, p50: 57.1 ms
worst UI frame: 60.7 ms
raster : UI frames: 151 : 598

=== Skia Run (--no-enable-impeller) Performance Trace ===
GPURasterizer::Draw p50: 5.0 ms
GPURasterizer::Draw worst: 7.2 ms
CreateGlyphAtlas: slice absent entirely
BulkUpdateAtlasBitmap: 4215 ms
worst UI frame: 15.7 ms
raster : UI frames: 284 : 283

### Flutter Doctor output

Doctor output

```console
~ % flutter doctor -v
[✓] Flutter (Channel stable, 3.44.8, on macOS 26.5.2 25F84 darwin-arm64, locale en-SA) [652ms]
• Flutter version 3.44.8 on channel stable at /Users//Documents/flutter
• Upstream repository https://github.com/flutter/flutter.git
• Framework revision 058e0af2c2 (2 days ago), 2026-07-23 10:56:21 -0700
• Engine revision 0cd610717b
• Dart version 3.12.2
• DevTools version 2.57.0
• Feature flags: enable-web, enable-linux-desktop, enable-macos-desktop, enable-windows-desktop, enable-android,
enable-ios, cli-animations, enable-native-assets, enable-swift-package-manager, omit-legacy-version-file,
enable-lldb-debugging, enable-uiscene-migration

[✓] Android toolchain - develop for Android devices (Android SDK version 37.0.0-rc1) [3.2s]
• Android SDK at /Users//Library/Android/sdk
• Emulator version 36.6.11.0 (build_id 15507667) (CL:N/A)
• Platform android-CinnamonBun, build-tools 37.0.0-rc1
• Java binary at: /Applications/Android Studio.app/Contents/jbr/Contents/Home/bin/java
This is the JDK bundled with the latest Android Studio installation on this machine.
To manually set the JDK path, use: `flutter config --jdk-dir="path/to/jdk"`.
• Java version OpenJDK Runtime Environment (build 21.0.10+-117844308-b1163.108)
• All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS (Xcode 26.6) [1,654ms]
• Xcode at /Applications/Xcode.app/Contents/Developer
• Build 17F113
• CocoaPods version 1.16.2

[✓] Chrome - develop for the web [6ms]
• Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome

[✓] Connected device (2 available) [10.1s]
• macOS (desktop) • macos • darwin-arm64 • macOS 26.5.2 25F84 darwin-arm64
• Chrome (web) • chrome • web-javascript • Google Chrome 150.0.7871.182
! Error: Browsing on the local area network for . Ensure the device is unlocked and attached with a
cable or associated with the same local area network as this Mac.
The device must be opted into Developer Mode to connect wirelessly. (code -27)

[✓] Network resources [2.3s]
• All expected network resources are available.
```

Contributor guide

Open the contributing guide

Research direction

Start by reproducing the dynamic-font workload on Android and capture DevTools or Observatory traces with Impeller and with --no-enable-impeller. Investigate the CreateGlyphAtlas and GPURasterizer::Draw paths using the reported frame timings as a baseline. Done means glyph-atlas work no longer causes the reported raster stalls and frame drops under the reproduction workload.

Written by the indexing model from the issue text.

Assessment

Tech stack
android, dart
Domain
computer-graphics, mobile-dev, performance
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Needs clarification
Newbie friendliness
30/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.