Skip to content

Commit da99d17

Browse files
Popov72Copilot
andcommitted
fix: snapshot rendering crash with clustered lighting
clearStorageBuffer() calls _renderEncoder.clearBuffer(), which is a command encoder operation that cannot execute while a render pass is open on the same encoder. In snapshot rendering mode (both STANDARD and FAST), bindFramebuffer() eagerly creates the render pass. The clustered lighting system then fires onClearObservable which calls StorageBuffer.clear(), hitting the locked encoder and producing WebGPU validation errors every frame. Fix: - Move the WebGPU storage buffer clear from onClearObservable (fires after bindFramebuffer) to onBeforeBindObservable (fires before), so it runs while the encoder is free. - Add _endCurrentRenderPass() as a safety guard in clearStorageBuffer() to prevent the same class of bug for any future callers. Fixes https://forum.babylonjs.com/t/63139 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 5b48f65 commit da99d17

2 files changed

Lines changed: 10 additions & 4 deletions

File tree

packages/dev/core/src/Engines/webgpuEngine.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4043,6 +4043,8 @@ export class WebGPUEngine extends ThinWebGPUEngine {
40434043
* @param byteLength the byte length to clear (optional)
40444044
*/
40454045
public clearStorageBuffer(storageBuffer: DataBuffer, byteOffset?: number, byteLength?: number): void {
4046+
// clearBuffer is a command encoder operation and cannot be recorded while a render pass is open on the same encoder.
4047+
this._endCurrentRenderPass();
40464048
this._renderEncoder.clearBuffer(storageBuffer.underlyingResource, byteOffset, byteLength);
40474049
}
40484050

packages/dev/core/src/Lights/Clustered/clusteredLightContainer.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,13 @@ export class ClusteredLightContainer extends Light {
341341
this._tileMaskTexture.onBeforeBindObservable.add(() => {
342342
currentRenderTarget = engine._currentRenderTarget;
343343
this._updateLightData();
344+
// On WebGPU, clear the storage buffer here (before bindFramebuffer) because
345+
// clearBuffer is a command encoder operation that cannot run while a render pass is open.
346+
// In snapshot rendering mode, bindFramebuffer eagerly creates the render pass, so
347+
// clearing must happen before that point.
348+
if (engine.isWebGPU) {
349+
this._tileMaskBuffer?.clear();
350+
}
344351
});
345352

346353
this._tileMaskTexture.onAfterUnbindObservable.add(() => {
@@ -354,10 +361,7 @@ export class ClusteredLightContainer extends Light {
354361
});
355362

356363
this._tileMaskTexture.onClearObservable.add(() => {
357-
if (engine.isWebGPU) {
358-
// Clear the storage buffer for WebGPU
359-
this._tileMaskBuffer?.clear();
360-
} else {
364+
if (!engine.isWebGPU) {
361365
// Only clear the texture on WebGL
362366
engine.clear({ r: 0, g: 0, b: 0, a: 1 }, true, false);
363367
}

0 commit comments

Comments
 (0)