Skip to content

Rebuild bind groups when a bound texture's GPU resource is recreated#8982

Merged
mvaligursky merged 1 commit into
playcanvas:mainfrom
slimbuck:bind-fix
Jun 29, 2026
Merged

Rebuild bind groups when a bound texture's GPU resource is recreated#8982
mvaligursky merged 1 commit into
playcanvas:mainfrom
slimbuck:bind-fix

Conversation

@slimbuck

Copy link
Copy Markdown
Member

Summary

A BindGroup rebuilds its underlying GPU bind group when a bound texture changes, but the dirty check missed the case where a texture's GPU resource is recreated in place (same Texture JS object, new impl) during a render. When this happens, the bind group keeps a view of the old — now destroyed — GPU texture, which surfaces under WebGPU as Destroyed texture [...] used in a submit validation errors and visible corruption (e.g. black frames).

The existing texture dirty check in setTexture/setStorageTexture is:

if (this.textures[index] !== value) {
    this.dirty = true;                                  // a different texture was bound
} else if (this.renderVersionUpdated < texture.renderVersionDirty) {
    this.dirty = true;                                  // texture properties changed
}

renderVersionDirty is stamped with device.renderVersion, which only increments once per rendered frame (GraphicsDevice.frameStart). So when a texture is resized mid-render — in the same render version the consuming bind group was last built — renderVersionUpdated < renderVersionDirty is false and the rebuild is skipped, leaving a stale view of the destroyed GPU texture.

Repro context

Hit by unified gsplat LOD streaming with app.autoRender = false: the work-buffer textures (Texture#resize) grow mid-render as splats stream in, in the same render version as the renderers that bind them. Texture.resize already defers GPU destruction until after submit, but the deferred destroy still fires while a bind group holds a view of the old impl, producing Destroyed texture [... R32Uint / RGBA32Uint ...] used in a submit.

Fix

Track the GPU impl object each texture/storage-texture slot was last built against, and mark the bind group dirty when it changes. impl identity changes exactly when the GPU resource is recreated (resize, lose-context, format change), so this catches mid-render recreation regardless of render-version timing, and adds no rebuilds in steady state (impl is otherwise stable across frames).

Notes

  • Not gsplat-specific — applies to any texture resized mid-render in the same render version as a consumer bind group.
  • No API or behavior change beyond the added rebuild; steady-state cost is one identity comparison and array write per bound texture per update().

@slimbuck slimbuck requested review from Copilot and mvaligursky June 29, 2026 13:44
@slimbuck slimbuck self-assigned this Jun 29, 2026
@slimbuck slimbuck added the area: graphics Graphics related issue label Jun 29, 2026

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.

Pull request overview

This PR fixes a WebGPU correctness issue where BindGroup instances could keep referencing destroyed GPU texture resources when a Texture recreates its GPU impl in-place mid-render (e.g. during Texture.resize), by detecting impl identity changes and forcing a bind group rebuild.

Changes:

  • Track the last-seen texture.impl per bind-group texture slot and mark the bind group dirty when it changes.
  • Apply the same impl tracking to storage texture slots to avoid stale GPU texture views there as well.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

* For each texture / storage-texture slot, the GPU implementation object the slot was last
* built against. A texture's `impl` is replaced when its GPU resource is recreated (e.g.
* {@link Texture#resize}), which can happen mid-render in the same render version the bind
* group was last built — so the {@link renderVersionDirty} check alone misses it and the bind

@mvaligursky mvaligursky 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.

Great change!

@mvaligursky mvaligursky merged commit 48819ac into playcanvas:main Jun 29, 2026
8 of 10 checks passed
mvaligursky pushed a commit that referenced this pull request Jun 30, 2026
@slimbuck slimbuck deleted the bind-fix branch July 1, 2026 11:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: graphics Graphics related issue

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants