[flame_3d] Children added to an Object3D after it was rendered can be culled while on screen
Los mantenedores suelen responder en 1 día
Evaluación
Este issue todavía no se ha evaluado.
Descripción
What happened?
When a child is added to a Component3D whose AABB has already been computed, the parent's cached AABB is not invalidated. The parent's AABB is the hull of its own geometry and the AABBs of its children, so it no longer covers the new child.
This matters when the parent is an Object3D: Object3D.renderTree frustum culls the parent's AABB first and skips the whole subtree when it is outside of the view. A child added inside the view, but outside of the parent's stale AABB, is therefore never rendered.
Component3D.onMount only marks the new child's own world transform as dirty, nothing marks the parent's AABB as dirty. A typical case is a spawner or pool that is an Object3D and adds children to itself at runtime.
What do you expect?
Adding a child to a component should invalidate the cached AABB of that component (and its ancestors), so that the new child is taken into account when culling.
How can we reproduce this?
- Run the
main.dartbelow withflutter run --enable-flutter-gpu(on Windows also add--enable-impeller). - The
Spawneris anObject3Dthat starts with a single (blue) child behind the camera, so its AABB is behind the camera too. After one second it adds a green cube to itself, in front of the camera. - Only the red control cube is ever rendered. The green cube is skipped because the
Spawneris culled using its stale AABB.
What steps should take to fix this?
Mark the parent's AABB dirty when a component is mounted:
@override
void onMount() {
super.onMount();
_markWorldTransformDirty();
if (parent case final Component3D parent) {
parent.markAabbDirty();
}
}
Doing the same in onRemove is optional. Without it the parent's AABB stays bigger than needed after a child is removed. That isn't visible and only makes culling less effective, but it keeps the bounds tight.
Marking is cheap since it stops at the first ancestor that is already dirty, and the parent's AABB is only recomputed lazily on the next read. This is the same cost as a child moving, which already marks its parents dirty.
Do have an example of where the bug occurs?
import 'package:flame/game.dart';
import 'package:flame_3d/camera.dart';
import 'package:flame_3d/components.dart';
import 'package:flame_3d/game.dart';
import 'package:flame_3d/graphics.dart';
import 'package:flame_3d/resources.dart';
import 'package:flutter/widgets.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await GpuBackend.initialize();
runApp(GameWidget(game: ReproGame()));
}
class ReproGame extends FlameGame3D {
ReproGame()
: super(
camera: CameraComponent3D(
position: Vector3(0, 2, -6),
target: Vector3(0, 1, 0),
),
);
@override
Future<void> onLoad() async {
world.addAll([
// Red: not nested, world position (1.5, 1, 0). Rendered.
MeshComponent(
mesh: _cubeMesh(const Color(0xFFFF0000)),
position: Vector3(1.5, 1, 0),
),
// A group with a single child behind the camera. After one second a
// green cube is added to it at world position (-1.5, 1, 0). The green
// cube is never rendered.
Spawner(
initial: MeshComponent(
mesh: _cubeMesh(const Color(0xFF0000FF)),
position: Vector3(0, 1, -20),
),
spawned: MeshComponent(
mesh: _cubeMesh(const Color(0xFF00FF00)),
position: Vector3(-1.5, 1, 0),
),
),
]);
}
Mesh _cubeMesh(Color color) => CuboidMesh(
size: Vector3.all(1),
material: UnlitMaterial(albedoColor: color),
);
}
/// An [Object3D] group that adds [spawned] to itself after one second.
class Spawner extends Object3D {
Spawner({required Component3D initial, required this.spawned})
: super(children: [initial]);
final Component3D spawned;
double _elapsed = 0;
@override
void update(double dt) {
super.update(dt);
_elapsed += dt;
if (_elapsed > 1 && spawned.parent == null) {
add(spawned);
}
}
@override
void draw(RenderContext context) {}
}
Relevant log output
No errors or warnings are logged.
Execute in a terminal and put output into the code block below
Flutter 3.47.8 • channel stable • https://github.com/flutter/flutter.git
Framework • revision 737ba8f885 (6 days ago) • 2026-10-22 21:37:31 -0700
Engine • hash dfd2e6cb7d319db05e3bf1503f3e2ac60f6288a2 (revision cf636b1dfb) (6 days ago) • 2026-10-23 02:13:38.000Z
Tools • Dart 3.13.7 • DevTools 2.60.0
Affected platforms
All
Other information
Reproduced on Windows (Impeller with the OpenGL ES backend), but the culling logic is platform independent.
Related to #4163, which is the other case of cached AABBs not being invalidated (when an ancestor moves). Both touch the same code in Component3D, but they can be reproduced and fixed independently.
If the parent is a plain Component3D the bug isn't visible, since only Object3D culls its subtree.
Are you interested in working on a PR for this?
- I want to work on this
- Lenguaje dominante
- Dart
- Estrellas
- 10.8k
- Forks
- 1.1k
- Merge medio
- 1 d 21 h
- PR fusionados (30 d)
- 53
Preparar el entorno
- Sin Dockerfile ni archivo de Docker Compose
- Tiene una plantilla de pull request
- Leer la guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de flame-engine/flame
-
`PostProcessSequentialGroup` skips earlier post processes when the subtree is rendered more than oncePosiblemente ocupada @ufrshubham la tomó hace 1 día. Abiertobug
flame-engine/flame#4193 · 1 asignado ·
Los mantenedores suelen responder en 1 día
-
`ComponentPool` returns components to the pool while they are still in use, and loses components removed before mountingPosiblemente ocupada @ufrshubham la tomó hace 1 día. Abiertobug
flame-engine/flame#4191 · 1 asignado ·
Los mantenedores suelen responder en 1 día
-
`PostProcessComponent` throws `StateError` when it has no `PositionComponent` childrenPosiblemente ocupada @ufrshubham la tomó hace 2 días. Abiertobug
flame-engine/flame#4182 · 1 asignado ·
Los mantenedores suelen responder en 1 día
-
enhancement
Dificultad 5/5 Más de una semana Aptitud para principiantes 35/100
flame-engine/flame#4176 ·
Los mantenedores suelen responder en 1 día
-
discussion enhancement
Dificultad 5/5 Más de una semana Aptitud para principiantes 25/100
flame-engine/flame#4175 ·
Los mantenedores suelen responder en 1 día
Todos los issues de flame-engine/flame
Issues similares
-
bug
Dificultad 1/5 Menos de una hora Aptitud para principiantes 85/100
lollipopkit/flutter_server_box#1851 · 1 comentario · 1 reacción ·
Los mantenedores suelen responder en 1 día
-
type:feature
Dificultad 2/5 1-3 horas Aptitud para principiantes 65/100
mikejr83/rc-soar-score#724 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 68/100
ccch1mneyyy/dsh-Go#381 ·
Los mantenedores suelen responder en 1 día
-
Broken links in the docsAbierto
Dificultad 1/5 Menos de una hora Aptitud para principiantes 85/100
letsar/flutter_slidable#563 ·
Los mantenedores suelen responder en 1 día
-
bot-found bug priority: P3
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
madenvel/KalinkaPlayer#498 ·