[Impeller] Glyph atlas overflow when repeatedly scaling TextComponent in Flame game
- 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
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