flutter / flutter/flutter

[Impeller] Glyph atlas overflow when repeatedly scaling TextComponent in Flame game

Open
#183,297 17 comments 0 reactions 0 assignees View on GitHub
a: typography e: impeller engine P1 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 Flame game with a TextComponent.
2. Repeatedly add ScaleEffect to the TextComponent (simulating score updates).
3. After approximately 340+ scale operations, text rendering breaks.

### Expected results

Text should continue rendering normally regardless of how many times it's been scaled.

### Actual results

After ~340 scale operations, the following errors appear and text disappears/breaks:

```
E/flutter: [ERROR:flutter/impeller/typographer/lazy_glyph_atlas.cc(86)] Break on 'ImpellerValidationBreak' to inspect point of failure: Could not create valid atlas.
E/flutter: [ERROR:flutter/impeller/entity/contents/text_contents.cc(234)] Break on 'ImpellerValidationBreak' to inspect point of failure: Cannot render glyphs without prepared atlas.
```
### Code sample

Code sample

```dart
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flame/game.dart';
import 'package:flame/components.dart';
import 'package:flame/effects.dart';
import 'package:flutter/services.dart';

void main() {
WidgetsFlutterBinding.ensureInitialized();
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
]);
runApp(const MyApp());
}

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

@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: GameWidget(game: GlyphAtlasBugGame()),
),
);
}
}

class GlyphAtlasBugGame extends FlameGame {
late TextComponent _scoreDisplay;
int _score = 0;
double _timeSinceLastUpdate = 0;
double _nextUpdateInterval = 0.1;
final Random _random = Random();

@override
Future onLoad() async {
await super.onLoad();

_scoreDisplay = TextComponent(
text: '$_score',
anchor: Anchor.topCenter,
position: Vector2(size.x / 2, 100),
textRenderer: TextPaint(
style: const TextStyle(
color: Colors.white,
fontSize: 50.0,
fontWeight: FontWeight.bold,
shadows: [
Shadow(
color: Colors.black,
offset: Offset(2, 2),
blurRadius: 2,
),
],
),
),
);
add(_scoreDisplay);
_setRandomInterval();
}

void _setRandomInterval() {
final intervals = [0.05, 0.1];
_nextUpdateInterval = intervals[_random.nextInt(intervals.length)];
}

@override
void update(double dt) {
super.update(dt);

_timeSinceLastUpdate += dt;
if (_timeSinceLastUpdate >= _nextUpdateInterval) {
_timeSinceLastUpdate = 0;

final burstCount = _random.nextBool() ? 1 : (_random.nextInt(2) + 1);

for (int i = 0; i < burstCount; i++) {
_updateScore();
}

_setRandomInterval();
}
}

void _updateScore() {
_score++;
_scoreDisplay.text = _score.toString();

final ScaleEffect popEffect = ScaleEffect.to(
Vector2.all(1.2),
EffectController(
duration: 0.1,
alternate: true,
curve: Curves.easeInOut,
),
);
_scoreDisplay.add(popEffect);

if (_score % 50 == 0) {
final effectCount = _scoreDisplay.children.whereType().length;
debugPrint('Score: $_score - Stacked Effects: $effectCount');
}
}
}
```

### Screenshots or Video

Screenshots / Video demonstration

https://www.mediafire.com/file/gcitacmm3nnyfq4/%255BImpeller%255D_Glyph_Atlas_Bug.mp4/file

### Logs

Logs

```console
I/flutter (26806): Effects on scoreDisplay: 0
I/flutter (26806): Effects on scoreDisplay: 1
I/flutter (26806): Effects on scoreDisplay: 1
I/flutter (26806): Effects on scoreDisplay: 1
I/flutter (26806): Effects on scoreDisplay: 0
I/flutter (26806): Effects on scoreDisplay: 0
I/flutter (26806): Effects on scoreDisplay: 1
I/flutter (26806): Effects on scoreDisplay: 1
I/flutter (26806): Effects on scoreDisplay: 1
I/flutter (26806): Effects on scoreDisplay: 1
I/flutter (26806): Effects on scoreDisplay: 1
I/flutter (26806): Effects on scoreDisplay: 0
I/flutter (26806): Effects on scoreDisplay: 1
I/flutter (26806): Effects on scoreDisplay: 0
I/flutter (26806): Effects on scoreDisplay: 1
I/flutter (26806): Effects on scoreDisplay: 0
I/flutter (26806): Effects on scoreDisplay: 1
I/flutter (26806): Effects on scoreDisplay: 1
I/flutter (26806): Effects on scoreDisplay: 1
I/flutter (26806): Effects on scoreDisplay: 1
I/flutter (26806): Effects on scoreDisplay: 3
I/flutter (26806): Effects on scoreDisplay: 3
I/flutter (26806): Effects on scoreDisplay: 3
I/flutter (26806): Effects on scoreDisplay: 3
I/flutter (26806): Effects on scoreDisplay: 3
I/flutter (26806): Effects on scoreDisplay: 2
I/flutter (26806): Effects on scoreDisplay: 1
I/flutter (26806): Effects on scoreDisplay: 2
I/flutter (26806): Effects on scoreDisplay: 0
I/flutter (26806): Effects on scoreDisplay: 1
I/flutter (26806): Effects on scoreDisplay: 1
I/flutter (26806): Effects on scoreDisplay: 1
I/flutter (26806): Effects on scoreDisplay: 1
I/flutter (26806): Effects on scoreDisplay: 1
I/flutter (26806): Effects on scoreDisplay: 2
I/flutter (26806): Effects on scoreDisplay: 2
I/flutter (26806): Effects on scoreDisplay: 1
I/flutter (26806): Effects on scoreDisplay: 1
I/flutter (26806): Effects on scoreDisplay: 0
I/flutter (26806): Effects on scoreDisplay: 0
I/flutter (26806): Effects on scoreDisplay: 1
I/flutter (26806): Effects on scoreDisplay: 0
I/flutter (26806): Effects on scoreDisplay: 0
I/flutter (26806): Effects on scoreDisplay: 1
I/flutter (26806): Effects on scoreDisplay: 1
I/flutter (26806): Effects on scoreDisplay: 2
I/flutter (26806): Effects on scoreDisplay: 1
I/flutter (26806): Effects on scoreDisplay: 1
I/flutter (26806): Effects on scoreDisplay: 2
I/flutter (26806): Effects on scoreDisplay: 2
I/flutter (26806): Effects on scoreDisplay: 2
I/flutter (26806): Effects on scoreDisplay: 2
I/flutter (26806): Effects on scoreDisplay: 2
I/flutter (26806): Effects on scoreDisplay: 0
I/flutter (26806): Effects on scoreDisplay: 1
I/flutter (26806): Effects on scoreDisplay: 2
I/flutter (26806): Effects on scoreDisplay: 1
I/flutter (26806): Effects on scoreDisplay: 1
I/flutter (26806): Effects on scoreDisplay: 1
I/flutter (26806): Effects on scoreDisplay: 1
I/flutter (26806): Effects on scoreDisplay: 3
I/flutter (26806): Effects on scoreDisplay: 4
E/flutter (26806): [ERROR:flutter/impeller/typographer/lazy_glyph_atlas.cc(86)] Break on 'ImpellerValidationBreak' to inspect point of failure: Could not create valid atlas.
E/flutter (26806): [ERROR:flutter/impeller/entity/contents/text_contents.cc(234)] Break on 'ImpellerValidationBreak' to inspect point of failure: Cannot render glyphs without prepared atlas.
I/flutter (26806): Effects on scoreDisplay: 2
I/flutter (26806): Effects on scoreDisplay: 0
I/flutter (26806): Effects on scoreDisplay: 1
I/flutter (26806): Effects on scoreDisplay: 1
I/flutter (26806): Effects on scoreDisplay: 0
I/flutter (26806): Effects on scoreDisplay: 1
I/flutter (26806): Effects on scoreDisplay: 1
I/flutter (26806): Effects on scoreDisplay: 1
I/flutter (26806): Effects on scoreDisplay: 1
I/flutter (26806): Effects on scoreDisplay: 3
I/flutter (26806): Effects on scoreDisplay: 3
I/flutter (26806): Effects on scoreDisplay: 3
I/flutter (26806): Effects on scoreDisplay: 3
I/flutter (26806): Effects on scoreDisplay: 3
I/flutter (26806): Effects on scoreDisplay: 2
I/flutter (26806): Effects on scoreDisplay: 2
I/flutter (26806): Effects on scoreDisplay: 2
```

### Flutter Doctor output

Doctor output

```console
[√] Flutter (Channel stable, 3.38.6, on Microsoft Windows [Version 10.0.26200.7840], locale en-IN) [967ms]
• Flutter version 3.38.6 on channel stable at C:\src\flutter\flutter
• Upstream repository https://github.com/flutter/flutter.git
• Framework revision 8b87286849 (7 weeks ago), 2026-01-08 10:49:17 -0800
• Engine revision 78fc3012e4
• Dart version 3.10.7
• DevTools version 2.51.1
• Feature flags: enable-web, enable-linux-desktop, enable-macos-desktop, enable-windows-desktop, enable-android, enable-ios, cli-animations,
enable-native-assets, omit-legacy-version-file, enable-lldb-debugging

[√] Windows Version (11 Home Single Language 64-bit, 25H2, 2009) [5.1s]

[√] Android toolchain - develop for Android devices (Android SDK version 36.1.0-rc1) [6.5s]
• Android SDK at C:\Users\xxabd\AppData\Local\Android\Sdk
• Emulator version 36.1.9.0 (build_id 13823996) (CL:N/A)
• Platform android-36, build-tools 36.1.0-rc1
• Java binary at: C:\Program Files\Android\Android Studio\jbr\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.8+-14018985-b1038.68)
• All Android licenses accepted.

[√] Chrome - develop for the web [331ms]
• Chrome at C:\Program Files\Google\Chrome\Application\chrome.exe

[√] Visual Studio - develop Windows apps (Visual Studio Build Tools 2019 16.11.50) [327ms]
• Visual Studio at C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools
• Visual Studio Build Tools 2019 version 16.11.36324.18
• Windows 10 SDK version 10.0.19041.0

[√] Connected device (3 available) [658ms]
• Windows (desktop) • windows • windows-x64 • Microsoft Windows [Version 10.0.26200.7840]
• Chrome (web) • chrome • web-javascript • Google Chrome 145.0.7632.160
• Edge (web) • edge • web-javascript • Microsoft Edge 145.0.3800.70

[√] Network resources [1,299ms]
• All expected network resources are available.

• No issues found!
```

Contributor guide

Open the contributing guide

Research direction

Start by reproducing the supplied Flame game and repeated ScaleEffect sequence on the reported Flutter setup. Then read flutter/impeller/typographer/lazy_glyph_atlas.cc and flutter/impeller/entity/contents/text_contents.cc around the logged failures. Done means text continues rendering after more than 340 scale operations without the invalid-atlas errors.

Written by the indexing model from the issue text.

Assessment

Tech stack
dart
Domain
computer-graphics
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.