`PostProcessSequentialGroup` skips earlier post processes when the subtree is rendered more than once
メンテナーはふだん 1 日以内に返信
評価
この issue はまだ評価されていません。
説明
What happened?
When a post process in a PostProcessSequentialGroup renders its subtree more than once, only the first call runs the post processes before it in the group. Every later call renders the raw tree and silently skips them.
Rendering the subtree more than once is common for multi-pass effects, and the PostProcess docs explicitly allow it. A drop shadow, for example, renders the subtree once as a dark, blurred shadow and then again for the scene itself. In PostProcessSequentialGroup([Grayscale(), DropShadow()]), the grayscale is only applied to the shadow pass, so the scene itself stays in full color and the grayscale effect appears to do nothing. Nothing is thrown.
https://github.com/user-attachments/assets/7315eab9-d002-490a-a954-368d15e5dc82
What do you expect?
Every call to renderSubtree or rasterizeSubtree should run the full chain of post processes before it in the group. In the example below, the whole scene should be gray, with a shadow, the same as nesting the two post processes by hand.
How can we reproduce this?
- Create a
PostProcessSequentialGroupwith a post process that renders its subtree twice, such as a drop shadow, after another post process, such as a grayscale filter. - Set it as
camera.postProcess, with some colored components in the world. - The shadows are drawn, but the components stay in color.
What steps should take to fix this?
PostProcessSequentialGroup.render (post_process.dart#L245-L260) builds a single list and removes post processes from it as it goes:
final stack = postProcesses.reversed.toList();
void runNext(Canvas c) {
if (stack.isEmpty) {
renderTree(c);
return;
}
final postProcess = stack.removeAt(0);
postProcess.render(c, size, runNext, updateContext);
}
The first subtree render empties the list, so the next one finds it empty and goes straight to renderTree.
Do have an example of where the bug occurs?
import 'dart:ui';
import 'package:flame/components.dart';
import 'package:flame/game.dart';
import 'package:flame/post_process.dart';
import 'package:flutter/material.dart' show Colors;
import 'package:flutter/widgets.dart';
void main() => runApp(GameWidget(game: ReproGame()));
class ReproGame extends FlameGame {
@override
Future<void> onLoad() async {
world.addAll([
CircleComponent(
radius: 40,
position: Vector2(-160, -40),
paint: Paint()..color = Colors.red,
),
RectangleComponent(
size: Vector2.all(80),
position: Vector2(-40, -40),
paint: Paint()..color = Colors.green,
),
CircleComponent(
radius: 40,
position: Vector2(80, -40),
paint: Paint()..color = Colors.blue,
),
]);
camera.postProcess = PostProcessSequentialGroup(
// Grayscale is applied first, then DropShadow, so the whole scene
// should be gray, with a shadow.
postProcesses: [Grayscale(), DropShadow()],
);
}
}
/// Renders its subtree in grayscale.
class Grayscale extends PostProcess {
final _paint = Paint()
..colorFilter = const ColorFilter.matrix([
0.2126, 0.7152, 0.0722, 0, 0, //
0.2126, 0.7152, 0.0722, 0, 0, //
0.2126, 0.7152, 0.0722, 0, 0, //
0, 0, 0, 1, 0, //
]);
@override
void postProcess(Vector2 size, Canvas canvas) {
canvas.saveLayer(null, _paint);
renderSubtree(canvas);
canvas.restore();
}
}
/// Renders its subtree as a dark, blurred shadow, then again on top.
class DropShadow extends PostProcess {
final _shadowPaint = Paint()
..colorFilter = const ColorFilter.mode(Color(0xAA000000), BlendMode.srcIn)
..imageFilter = ImageFilter.blur(sigmaX: 3, sigmaY: 3);
@override
void postProcess(Vector2 size, Canvas canvas) {
// First pass: the shadow.
canvas
..save()
..translate(8, 10)
..saveLayer(null, _shadowPaint);
renderSubtree(canvas);
canvas
..restore()
..restore();
// Second pass: the scene itself.
renderSubtree(canvas);
}
}
Expected: gray shapes with shadows. Actual: red, green and blue shapes with shadows. Replacing the group with Grayscale() alone turns the shapes gray, so the filter itself works.
Relevant log output
Nothing is logged or thrown.
Execute in a terminal and put output into the code block below
[√] Flutter (Channel stable, 3.47.8, on Microsoft Windows [Version 10.0.22631.6199], locale en-US)
• Flutter version 3.47.8 on channel stable
• Framework revision 737ba8f885 (8 days ago), 2026-10-22 21:37:31 -0700
• Engine revision cf636b1dfb
• Dart version 3.13.7
• DevTools version 2.60.0
[√] Windows Version (11 Pro 64-bit, 23H2, 2009)
[√] Android toolchain - develop for Android devices (Android SDK version 36.1.0)
[√] Chrome - develop for the web
[√] Visual Studio - develop Windows apps (Visual Studio Community 2026 18.11.2)
[√] Connected device (3 available)
[√] Network resources
• No issues found!
Affected platforms
All
Are you interested in working on a PR for this?
- I want to work on this
- 主要言語
- Dart
- スター
- 10.8k
- フォーク
- 1.1k
- 平均マージ
- 1日 21時間
- マージ済み PR(30日)
- 53
環境構築
- Dockerfile・Docker Compose ファイルなし
- プルリクエストのテンプレートあり
- コントリビューションガイドを読む
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
flame-engine/flame のほかの issue
-
`ComponentPool` returns components to the pool while they are still in use, and loses components removed before mounting対応中かも @ufrshubham が 1 日前に担当しました。 オープンbug
flame-engine/flame#4191 · 担当者 1 名 ·
メンテナーはふだん 1 日以内に返信
-
`PostProcessComponent` throws `StateError` when it has no `PositionComponent` children対応中かも @ufrshubham が 2 日前に担当しました。 オープンbug
flame-engine/flame#4182 · 担当者 1 名 ·
メンテナーはふだん 1 日以内に返信
-
enhancement
難易度 5/5 1週間以上 初心者へのやさしさ 35/100
flame-engine/flame#4176 ·
メンテナーはふだん 1 日以内に返信
-
discussion enhancement
難易度 5/5 1週間以上 初心者へのやさしさ 25/100
flame-engine/flame#4175 ·
メンテナーはふだん 1 日以内に返信
-
enhancement
難易度 5/5 1週間以上 初心者へのやさしさ 35/100
flame-engine/flame#4174 ·
メンテナーはふだん 1 日以内に返信
flame-engine/flame の issue をすべて見る
似ている issue
-
bot-found bug priority: P3
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
madenvel/KalinkaPlayer#498 ·
-
難易度 2/5 1〜3時間 初心者へのやさしさ 70/100
libnativeapi/nativeapi#21 ·
-
bug
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
chriskelly/wish-together#102 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 82/100
TomBursch/kitchenowl#1188 ·
メンテナーはふだん 2 日以内に返信
-
area/adaptivity kind/task priority/p1
難易度 2/5 1〜3時間 初心者へのやさしさ 85/100