Visitar URL original
WebGPU: invalidate cached draws when vertex buffers change by Popov72 · Pull Request #19051 · BabylonJS/Babylon.js · GitHub
Skip to content

WebGPU: invalidate cached draws when vertex buffers change - #19051

Open
Popov72 wants to merge 3 commits into
BabylonJS:masterfrom
Popov72:popov72-webgpu-stale-render-bundle
Open

Popov72 wants to merge 3 commits into
BabylonJS:masterfrom
Popov72:popov72-webgpu-stale-render-bundle

Conversation

@Popov72

@Popov72 Popov72 commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

🤖 This PR was created by the create-pr skill.

Summary

Invalidate cached WebGPU vertex bindings when a geometry vertex buffer is added, replaced, or removed.

This fixes stale render bundles after thinInstanceSetBuffer() replaces the matrix or custom attribute buffers of a WGSL ShaderMaterial mesh. The old bundle retained the destroyed GPU buffer and subsequent submissions failed with used in submit while destroyed.

Forum report and reproduction: https://forum.babylonjs.com/t/64385

Approach and performance

  • Walk the geometry's existing mesh/submesh/material/draw-wrapper ownership, including draw-wrapper overrides, multi-material submaterials, and inactive render-pass materials. No new dependency cache, buffer-to-bundle tracking, or per-frame/per-draw checks.
  • Invalidate at binding-change time, not in Geometry._notifyUpdate(). Updating an existing GPU buffer's contents continues to reuse its render bundle.
  • Preserve draw wrappers, effects, readiness flags, active uniform bindings/owner reservations, and indirect draw buffers. The WebGPU draw context discards its fastBundle and refreshes vertex-pulling storage bindings on its next draw, without resetting or disposing the context.
  • Cover all cached render passes and WebGPU compatibility-mode transitions. Leave WebGL behavior unchanged.

Only vertex-binding changes pay the ownership traversal and next-bundle-recording cost. Steady-state drawing has no added checks. The geometry update APIs add only an aligned-buffer reference comparison and preserve bundles for genuinely in-place updates.

Review follow-up

The original full draw-cache reset was too broad. The follow-up preserves active contexts when automatic previous-matrix buffers are created between separate culling draws, and preserves readiness for frozen meshes instead of stranding them with replacement wrappers.

Validation

  • Added 25 regression tests covering shared geometry and multiple render passes, matrix/custom/previous-matrix replacement, draw-wrapper overrides, attribute addition/removal, static-buffer recreation, buffer growth, in-place updates, and WebGL.
  • Reproduced both review regressions before the follow-up: automatic previous matrices with separate culling passes threw in setBuffer(), and frozen meshes stopped issuing draws after replacement.
  • Regressions now cover both compatibility modes for frozen meshes, active-context preservation without an explicit previous-matrix buffer, and refreshed vertex-pulling storage bindings without losing uniform bindings.
  • All 69 targeted cache/geometry/thin-instance/uniform-owner lifecycle tests pass.
  • Real WebGPU devhost checks pass for the original WGSL reproduction, separate culling draws with automatic previous matrices, and frozen meshes. Confirmed visible green instances after replacement in both compatibility modes.
  • Non-compatibility-mode validation records exactly one bundle reconstruction for the batched replacements, then resumes bundle reuse, including after an in-place update.
  • Changed-file formatting/lint, core TypeScript checking, and core tree-shaking checks pass.

Temporary devhost changes and this session's validation server have been removed/stopped. Full-repository checks were not run.

Additional review follow-up

Commit c629c60 covers material-owned wrappers (ShaderMaterial(..., false)) and detects aligned-buffer replacement in both geometry update APIs. New tests include base/scene-default/multi-material/pass-owned wrappers, both aligned update paths, and preserved bundles when updates retain the GPU binding. Real WebGPU checks verify all three scenarios with zero errors and green pixels via GPU readback; each GPU-buffer replacement causes one bundle reconstruction, with reuse between replacements.

Copilot AI balanced review requested due to automatic review settings October 25, 2026 16:01
Reset draw caches at geometry vertex-buffer binding changes, without new per-draw checks or dependency tracking. Preserve cache reuse for in-place updates and cover shared geometry, render passes, buffer recreation and growth.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@bjsplat

bjsplat commented Oct 25, 2026

Copy link
Copy Markdown
Collaborator

Please make sure to label your PR with "bug", "new feature" or "breaking change" label(s).
To prevent this PR from going to the changelog marked it with the "skip changelog" label.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Cache invalidation can stop frozen meshes from rendering and dispose an active draw context before a second culling draw.

2 open issues
What changed in this PR

This PR prevents stale WebGPU render bundles after vertex-buffer binding changes by invalidating draw caches for meshes sharing a geometry.

Changes:

  • Reset cached draws when vertex buffers are added, replaced, or removed.
  • Preserve caches for in-place updates and leave WebGL behavior unchanged.
  • Add regression coverage for thin-instance buffers, shared geometry, multiple render passes, and compatibility mode.
File Description
packages/​dev/​core/​test/​unit/​Meshes/​babylon.geometry.drawCache.test.ts Adds draw-cache invalidation and preservation tests.
packages/​dev/​core/​src/​Meshes/​geometry.ts Invalidates WebGPU draw caches on vertex-buffer binding changes.

Review effort: Balanced

Invalidate cached bundles and refresh vertex-pulling bindings without disposing active contexts or clearing frozen-mesh readiness. Add regressions for automatic previous matrices with separate culling passes, frozen meshes across render passes and compatibility modes, and preserved uniform/indirect bindings.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@bjsplat

bjsplat commented Oct 25, 2026

Copy link
Copy Markdown
Collaborator

Please make sure to label your PR with "bug", "new feature" or "breaking change" label(s).
To prevent this PR from going to the changelog marked it with the "skip changelog" label.

@bjsplat

bjsplat commented Oct 25, 2026

Copy link
Copy Markdown
Collaborator

Snapshot stored with reference name:
refs/pull/19051/merge

Test environment:
https://snapshots-cvgtc2eugrd3cgfd.z01.azurefd.net/refs/pull/19051/merge/index.html

To test a playground add it to the URL, for example:

https://snapshots-cvgtc2eugrd3cgfd.z01.azurefd.net/refs/pull/19051/merge/index.html#WGZLGJ#4600

Links to test your changes to core in the published versions of the Babylon tools (does not contain changes you made to the tools themselves):

https://playground.babylonjs.com/?snapshot=refs/pull/19051/merge
https://sandbox.babylonjs.com/?snapshot=refs/pull/19051/merge
https://gui.babylonjs.com/?snapshot=refs/pull/19051/merge
https://nme.babylonjs.com/?snapshot=refs/pull/19051/merge

To test the snapshot in the playground with a playground ID add it after the snapshot query string:

https://playground.babylonjs.com/?snapshot=refs/pull/19051/merge#BCU1XR#0

If you made changes to the sandbox or playground in this PR, additional comments will be generated soon containing links to the dev versions of those tools.

@bjsplat

bjsplat commented Oct 25, 2026

Copy link
Copy Markdown
Collaborator

🟢 Memory Leak Test Results

4 passed, 0 leaked out of 4 scenarios

🟢 All memory leak tests passed — no leaks detected.

Passed Scenarios (4)
Scenario Package
Core Playground #2FDQT5#1508 @babylonjs/core
Core Playground #T90MQ4#14 @babylonjs/core
Core Playground #8EDB5N#2 @babylonjs/core
Core Playground #LL5BIQ#636 @babylonjs/core

@bjsplat

bjsplat commented Oct 25, 2026

Copy link
Copy Markdown
Collaborator

@bjsplat

bjsplat commented Oct 25, 2026

Copy link
Copy Markdown
Collaborator

@bjsplat

bjsplat commented Oct 25, 2026

Copy link
Copy Markdown
Collaborator

@bjsplat

bjsplat commented Oct 25, 2026

Copy link
Copy Markdown
Collaborator

⚡ Performance Test Results

🟢 All performance tests passed — no regressions detected.

@deltakosh deltakosh self-assigned this Oct 26, 2026

@deltakosh deltakosh left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hello @Popov72,

Could you cover two remaining invalidation paths?

  • With ShaderMaterial(..., false), the active wrapper belongs to the material and is missed by the submesh traversal.
  • Updating an unaligned attribute can recreate its aligned GPU buffer, but the update paths never invalidate cached draws.

Both can leave bundles referencing destroyed buffers. Please add regressions for these cases while preserving bundle reuse for genuinely in-place updates.

Thanks!

Invalidate material-owned wrappers, including multi-materials and inactive render-pass overrides. Detect aligned-buffer replacement in geometry update APIs without discarding bundles for genuinely in-place updates. Add regressions preserving active contexts and readiness state.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@Popov72

Popov72 commented Nov 2, 2026

Copy link
Copy Markdown
Contributor Author

[Responded by Copilot on behalf of Popov72]

@deltakosh Both paths are covered in c629c60:

  • Invalidation now includes material-owned wrappers (ShaderMaterial(..., false)), as well as submesh wrappers and overrides. It handles base materials, multi-material submaterials, and inactive render-pass overrides without creating new wrappers or disposing active contexts.
  • Geometry.updateVerticesData() and updateVerticesDataDirectly() now check whether the aligned buffer was replaced during the update. They invalidate cached draws only when that binding changes. Genuinely in-place updates retain their bundles.

Added regressions for both cases and for bundle preservation. All 69 targeted tests pass. Real WebGPU devhost checks also pass for material-owned replacement and both aligned update APIs, with no GPU errors and green output verified by GPU readback. The bundle counters show one reconstruction per GPU-buffer replacement and continued reuse between replacements.

There is no new dependency cache or per-draw tracking. The update APIs add only an aligned-buffer reference comparison; ownership traversal and bundle reconstruction occur only when a binding changes.

@Popov72
Popov72 requested a review from deltakosh November 2, 2026 14:09

@deltakosh deltakosh left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hello @Popov72,

Both remaining invalidation paths are now covered: material-owned draw wrappers (including multi-material and per-render-pass materials) and aligned-buffer replacement in both geometry update APIs. The tests also keep bundle reuse for genuinely in-place updates.

Thanks!

@deltakosh
deltakosh requested a review from sebavan November 2, 2026 15:54

@sebavan sebavan left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The targeted invalidation is substantially better than resetting draw caches: it preserves active contexts, frozen-mesh readiness, uniform ownership and indirect buffers, and adds no steady-state per-draw traversal. I verified that the earlier objections and the material-owned/aligned-buffer follow-ups are addressed.

One closely related invalidation gap remains: ShadowDepthWrapper owns additional draw wrappers that this traversal never reaches. A thin-instance buffer replacement can therefore leave the shadow pass using the same destroyed-buffer bundle this PR fixes for the main pass. Please cover these wrappers and add a shadow-pass regression before merging; details are inline.

This is a source-traced review of the specified commit, not an on-device reproduction. The Format/Lint check is also currently failing and needs to be cleared independently. Geometry swapping has a similar pre-existing cache issue, but I am not treating that separate base behavior as a blocker for this PR.

Comment on lines +115 to +121
if (material && this._isMultiMaterial(material)) {
material = material.getSubMaterial(this.materialIndex);
}
if (material && !material._storeEffectOnSubMeshes) {
material._getDrawWrapper().drawContext?._invalidateVertexBuffers?.();
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[P2] Invalidate the draw wrappers owned by ShadowDepthWrapper too

For a material with a shadowDepthWrapper, the shadow generator renders with wrappers held privately in ShadowDepthWrapper._subMeshToDepthWrapper, rather than the submesh's _drawWrappers or material._getDrawWrapper(). This helper does not reach those draw contexts.

On WebGPU with compatibility mode disabled, render a thin-instanced shadow caster using such a material, then replace its matrix buffer via thinInstanceSetBuffer("matrix", newData) without changing the attribute layout. Since the defines/effect remain the same, the cached shadow fastBundle still references the replaced GPU buffer. If the old buffer was destroyed, shadow submission can fail with used in submit while destroyed; otherwise it keeps stale bindings.

Please add an internal invalidation hook on ShadowDepthWrapper for this submesh, invalidate its per-pass wrappers and main wrapper without disposing their contexts, and invoke it for each resolved material here. A regression with an actual shadow-depth wrapper should accompany the material-owned wrapper tests. This finding is traced from the source paths, not a GPU reproduction.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants