Visitar URL original
fix: Keep the before-passes added to a RenderPassForward by mvaligursky · Pull Request #9781 · playcanvas/engine · GitHub
Skip to content

fix: Keep the before-passes added to a RenderPassForward - #9781

Merged
mvaligursky merged 1 commit into
mainfrom
mv-internal-before-passes
Oct 16, 2026
Merged

mvaligursky merged 1 commit into
mainfrom
mv-internal-before-passes

Conversation

@mvaligursky

Copy link
Copy Markdown
Contributor

Before-passes added to a RenderPassForward, such as one built by hand and assigned to the frame passes of a camera, now execute. Before, they were silently dropped every frame.

Changes:

  • RenderPassForward collected the passes it depends on in the frame, its directional shadow passes and the before-passes of its cameras, into its own beforePasses, and cleared that list each time it was scheduled. Anything else in the list was cleared too, so it never executed.
  • These passes now go to a separate engine-owned list, which the frame graph schedules ahead of the before-passes. The engine no longer changes the beforePasses of a pass, so they execute every frame the pass does.
  • Shadow and camera before-passes still execute before a pass's own before-passes, which can depend on them.

RenderPassForward collected the passes it depends on in the frame, its
directional shadow passes and the before-passes of its cameras, into its
beforePasses, clearing them each time it was scheduled. Before-passes added to
it by the user were cleared with them, and never executed.

These passes now go to a separate internal list, which the frame graph
schedules ahead of the before-passes, so the engine no longer changes the
beforePasses of a pass.
@github-actions

Copy link
Copy Markdown

Build size report

This PR changes the size of the minified bundles.

Bundle Minified Gzip Brotli
playcanvas.min.js 2664.6 KB (+172 B, +0.01%) 690.9 KB (+37 B, +0.01%) 535.8 KB (−39 B, −0.01%)
playcanvas.min.mjs 2661.4 KB (+172 B, +0.01%) 689.6 KB (+43 B, +0.01%) 535.4 KB (+151 B, +0.03%)

@mvaligursky mvaligursky left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated PR review by Codex (OpenAI AI agent), posted through the account holder's authorized automated review process.

Reviewed aedcb6d against 27ffbde. One P2 correctness issue remains in the newly enabled nested-forward-pass path: a user before-pass rendering a second camera can overwrite the parent's directional shadow map before the parent draws. See the inline reproduction.

The intended fix works for ordinary before-passes: the list stays intact, the user pass executes once on each of three consecutive frames, camera dependencies run first, disabling the parent suppresses the subtree, and re-enabling restores it without accumulation. Existing nested camera event ordering remains correct.

Validation:

  • Full Node suite: 4,364 passing, 43 pending.
  • Full Dawn/Metal WebGPU suite: 4,363 passing, 39 pending.
  • All changed files lint clean; declaration build and consumer type tests pass. GitHub CI is green.
  • Runtime-inspection probes reproduce the shadow problem on both WebGL2 and WebGPU: nesting camera B's forward pass under camera A changes 1,550 of 4,096 pixels in A's output compared with the equivalent flat sequence [passB, passA]. Turning shadows off removes the difference entirely. The base does not show the shadow mismatch, although it incorrectly drops B's before-pass.
  • Pixel-verification checks of unaffected standard and CameraFrame rendering at two camera poses each remain byte-identical to the base: 0 of 16,384 pixels differ per backend, with stable repeated baseline captures and no browser errors.

No other actionable findings.

Comment thread src/scene/frame-graph.js
Comment on lines +128 to +133
// the passes the engine added for the frame execute first, as the passes set up by the user
// can depend on them
const internalBeforePasses = renderPass.internalBeforePasses;
for (let i = 0; i < internalBeforePasses.length; i++) {
this.addRenderPass(internalBeforePasses[i]);
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Restore parent shadows after a before-pass renders another camera

With two cameras sharing a shadow-casting directional light, put B's offscreen RenderPassForward in A's forward pass's beforePasses. This ordering now produces shadow A → shadow B → draw B → draw A. Directional cameras share the light's shadow-map texture, so B overwrites it and A renders against the wrong shadow contents; the parent's shadow dependency is never refreshed after the nested pass. The equivalent flat sequence [passB, passA] correctly schedules shadow B → draw B → shadow A → draw A. I reproduced a difference in 1,550/4,096 pixels of A's output on both WebGL2 and WebGPU, with zero difference when shadows are disabled. Please revalidate/restore the parent camera's directional shadow state after nested user passes that can change it (or preserve separate per-camera resources), and cover this two-camera case in the regression tests. Keeping all internal dependencies ahead of the user before-passes is not sufficient for this newly supported configuration.

Automated review by Codex (OpenAI AI agent).

@mvaligursky
mvaligursky merged commit 8f36823 into main Oct 16, 2026
11 checks passed
@mvaligursky
mvaligursky deleted the mv-internal-before-passes branch October 16, 2026 14:37

This branch was successfully deployed

2 active deployments
Preview – engine — aedcb6da Deployed Oct 16, 2026 by vercel[bot]
Preview – engine-api-docs — aedcb6da Deployed Oct 16, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant