WebGPU failing to render with `SelectionOutlineLayer` (and `scene.enableDepthRenderer()`)

Hi folks! I recently upgraded my project using SelectionOutlineLayer from the standard Engine to WebGPUEngineusing the same options as the Playground does:

const engine = new BABYLON.WebGPUEngine(canvas, {
	enableAllFeatures: true,
	setMaximumLimits: true,
	enableGPUDebugMarkers: true,
});

But it failed to render due to the 2 issues below:


Issue #1: DepthRenderer - “PushDebugGroup called 1 time(s) without a corresponding PopDebugGroup

Initially, it was failing to render with:

babylon.js:1 BJS - [18:22:53]: [Frame 28] WebGPU uncaptured error (1): [object GPUValidationError] - PushDebugGroup called 1 time(s) without a corresponding PopDebugGroup.
 - While encoding [RenderPassEncoder "DepthRenderer - RenderPass"].End().
 - While finishing [CommandEncoder "render"].

Debug group stack:
 > "Render to DepthRenderer (face #0 layer #0)"
 > "Depth renderer"

The mention of DepthRenderermade me look into the source code for SelectionOutlineLayerand indeed it calls scene.enableDepthRenderer():

public constructor(name: string, scene?: Scene, options?: Partial<ISelectionOutlineLayerOptions>) {
	// ...

	this._scene.enableDepthRenderer();
}

To isolate the issue, I temporarily commented out all SelectionOutlineLayercode in my project and ran scene.enableDepthRenderer()just by itself. Indeed, it gave the same error above.

So I asked Claude to find the issue, and it said:

const engine = new BABYLON.WebGPUEngine(canvas, {
	enableAllFeatures: true,
	setMaximumLimits: true,
	enableGPUDebugMarkers: false, // Disable to prevent DepthRenderer debug group imbalance
});

This is the simplest fix - the PushDebugGroup/PopDebugGroup calls only happen when enableGPUDebugMarkers is true. Disabling it stops the error entirely with no other side effects since it’s only used for GPU profiling in browser dev tools.

Indeed, after setting enableGPUDebugMarkers: false, the scene was able to render. I don’t know if I’m doing something very wrong by disabling this, but I moved on and then ran into a 2nd issue:


Issue #2: Error parsing WGSL - “struct member position not found”

With scene.enableDepthRenderer()now seeming to work using the workaround above, I then uncommented all of the SelectionOutlineLayercode in my project.

It failed to render with the error:

babylon.js:1 BJS - [18:37:24]: [Frame 54] WebGPU uncaptured error (1): [object GPUValidationError] - Error while parsing WGSL: :65:28 error: struct member position not found
var positionUpdated: vec3f=input.position;
                           ^^^^^^^^^^^^^^


 - While calling [Device "BabylonWebGPUDevice0"].CreateShaderModule([ShaderModuleDescriptor ""vertex""]).

Again, I asked Claude to resolve the issue, and it said:

The depth renderer uses an internal effect that references input.position, which is invalid WGSL. The WGSL version should use vertexInputs.position instead.

The Fix: Patch the WGSL Shader Before Calling enableDepthRenderer():

for (const [key, shader] of Object.entries(BABYLON.ShaderStore.ShadersStoreWGSL)) {
	if (typeof shader === 'string' && shader.includes('input.position')) {
		console.warn(`Patching broken WGSL shader: ${key}`);
		BABYLON.ShaderStore.ShadersStoreWGSL[key] = shader.replaceAll('input.position', 'vertexInputs.position');
	}
}

Indeed, the scene was able to render after this! :white_check_mark:

The shader files that it modified were:

Patching broken WGSL shader: gaussianSplattingVertexShader
Patching broken WGSL shader: gaussianSplattingDepthVertexShader
Patching broken WGSL shader: pickingVertexShader
Patching broken WGSL shader: glowMapGenerationVertexShader
Patching broken WGSL shader: glowMapMergeVertexShader
Patching broken WGSL shader: layerVertexShader
Patching broken WGSL shader: lensFlareVertexShader
Patching broken WGSL shader: shadowMapVertexShader
Patching broken WGSL shader: backgroundVertexShader
Patching broken WGSL shader: proceduralVertexShader
Patching broken WGSL shader: hdrFilteringVertexShader
Patching broken WGSL shader: hdrIrradianceFilteringVertexShader
Patching broken WGSL shader: colorVertexShader
Patching broken WGSL shader: meshUVSpaceRendererVertexShader
Patching broken WGSL shader: meshUVSpaceRendererFinaliserVertexShader
Patching broken WGSL shader: particlesVertexShader
Patching broken WGSL shader: kernelBlurVertexShader
Patching broken WGSL shader: fxaaVertexShader
Patching broken WGSL shader: fluidRenderingParticleDepthVertexShader
Patching broken WGSL shader: fluidRenderingParticleThicknessVertexShader
Patching broken WGSL shader: fluidRenderingParticleDiffuseVertexShader
Patching broken WGSL shader: depthPixelShader
Patching broken WGSL shader: depthVertexShader
Patching broken WGSL shader: geometryVertexShader
Patching broken WGSL shader: boundingBoxRendererVertexShader
Patching broken WGSL shader: lineVertexShader
Patching broken WGSL shader: iblVoxelSlabDebugVertexShader
Patching broken WGSL shader: spritesVertexShader

Question

Although the scene now renders with SelectionOutlineLayer, I don’t know if it’s wrong to be using these 2 workarounds.

Even more confusing is that SelectionOutlineLayerworks well with WebGPU in the Playground without these 2 workarounds:

I tried my best to match the WebGPU setup between the Playground and my local project, but my local project requires these 2 workarounds while the Playground does not. Is there something I’m missing?

If anyone could please help, I would much appreciate it :smiley: Thank you!!

Regarding issue #1, I’m not sure how it can happen, because the “push” in depthMap.onBeforeBindObservable is balanced with a “pop” in depthMap.onAfterUnbindObservable:

Are you doing anything specific with these observers? If you can set up a live link demonstrating the bug somewhere, that would be great, as I could investigate the issue further.

I would also be very interested in a reproduction of #2, because var positionUpdated: vec3f = input.position; is not a bug in depth.vertex.fx:

input is the correct object with the correct type. vertexInputs is a global variable accessible outside the main function, but when you are inside that function, you can use input and vertexInputs interchangeably.

Please note that Claude’s correction is incorrect! input.position is not the same depending on whether you are in a vertex shader or a fragment shader. If you are in the main function:

  • input.position <=> vertexInputs.position in the vertex shader
  • input.position <=> fragmentInputs.position in the fragment shader
1 Like

Thank you so much Evgeni for helping look into this :smiling_face:

I did not do anything specific with the observers you mentioned.

Please find live links to showcase both issues:

Issue #1: Document

Issue #2: Document

Please let me know if there’s any other information I could help provide, and thank you again!

Thanks for the repro!

I was able to find the bug for #2, I will create a PR shortly.

Regarding #1, are you able to put the babylon.js.map file in the 2/lib/babylon/ directory?

It would help a lot, because the compressed code is quite hard to follow…

Thanks!

[EDIT] Fix for the second issue:

1 Like

Whoa thank you so much for finding the fix, Evgeni!!

I’ve added babylon.js.mapto both live reproduction links and tried my best to turn off minification in the Vite build:

Does pointing index.htmlto https://cdn.babylonjs.com/babylon.js ensure that the source map is loaded? I had do this instead of adding babylon.js.mapto /2/lib/babylon/because Cloudflare rejected the build due to having a file over 25 MB.

Please let me know if I didn’t do this correctly and if there’s anything else I can help with!

Thank you, Evgeni, for all your investigations here :smiling_face:

Yes, that’s ok for issue #1, source code is readable now!

However, I wasn’t able to find the problem…

If possible, can you share the sources of your project (privately if it’s easier for you)?

1 Like

Thank you for looking into this, Evgeni! I’ve added you as a collaborator. Please note that:

  • ./packages/core/browser/src/babylon/create.ts(where WebGPUEngineis created)
  • ./browser/src/visual/outline/singleton.ts(where scene.enableDepthRenderer()is called and the SelectionOutlineLayercode is commented out)

I’m unsure if it will be easy to build the whole project, but you can find all the files for https://dev.regna.io/1/ in ./browser/dist/.

Please let me know if there’s anything else I can do to help!

Thanks for this!

I was able to find the bug, and create a simple repro:

However, I don’t yet know how to solve this problem. The best solution is therefore to disable GPU markers (note that this is a debugging feature that should not be enabled in production code), until we have a proper fix.

1 Like

And here’s the fix for the first bug:

2 Likes

Whoa you’re incredible, Evgeni :exploding_head:

Thank you so much for all of the fixes you made this weekend, including those for several other threads! Absolutely amazing :smiling_face: