`PostProcessSequentialGroup` skips earlier post processes when the subtree is rendered more than once

オープン
#4,193 コメント 0 件 リアクション 0 件 担当者 1 名 GitHub で見る

メンテナーはふだん 1 日以内に返信

@ufrshubham がすでに取り組んでいます。

2026年11月1日 から。

  • #4194 @ufrshubham による — オープン

評価

この issue はまだ評価されていません。

説明

bug
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?
  1. Create a PostProcessSequentialGroup with a post process that renders its subtree twice, such as a drop shadow, after another post process, such as a grayscale filter.
  2. Set it as camera.postProcess, with some colored components in the world.
  3. 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

環境構築

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

flame-engine/flame のほかの issue

flame-engine/flame の issue をすべて見る

似ている issue

Dart の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。