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

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.