Quick answer: The BindGroupLayout entries must match the WGSL shader’s @group/@binding declarations exactly — binding number, resource type, and visibility stage all have to agree.
A WebGPU renderer’s createRenderPipeline throws a validation error about the bind group layout. The layout and the shader disagree somewhere.
Layout Mirrors the Shader
// WGSL
@group(0) @binding(0) var<uniform> camera: Camera;
@group(0) @binding(1) var albedoTex: texture_2d<f32>;
@group(0) @binding(2) var albedoSampler: sampler;
// JS layout — must match entry-for-entry
device.createBindGroupLayout({ entries: [
{ binding: 0, visibility: GPUShaderStage.VERTEX, buffer: { type: 'uniform' } },
{ binding: 1, visibility: GPUShaderStage.FRAGMENT, texture: {} },
{ binding: 2, visibility: GPUShaderStage.FRAGMENT, sampler: {} },
]});
What Has to Match
- Binding number: same index as
@binding(N). - Resource type:
buffer/texture/sampler/storageTexture— matching the WGSL declaration. - Visibility: which shader stages use it. If the fragment shader samples a texture, its entry needs
FRAGMENTvisibility.
Read the Validation Message
WebGPU’s errors are specific — they name the binding index and what mismatched. Don’t guess; the message points straight at the offending entry.
Auto Layout for Prototyping
Pass layout: 'auto' to createRenderPipeline and let WebGPU derive the layout from the shader. Great for prototyping; switch to an explicit layout when you need to share it across pipelines.
Verifying
The pipeline creates without validation errors. Bind groups built from this layout bind successfully, and the shader reads the resources correctly.
“The bind group layout is a contract with the shader. Binding, type, and visibility must all match.”
Generate the layout from the shader source where you can — hand-maintaining two parallel definitions is a constant drift risk.