bindGroupFor method

GPUBindGroup bindGroupFor(
  1. WebGpuBindingLayouts layouts,
  2. int group,
  3. Map<int, WebGpuSlice>? blocks,
  4. Map<int, GPUTextureView>? views,
  5. Map<int, GPUSampler>? samplers,
)

The bind group for one @group of layouts, assembled from what the pass has bound and cached by what went into it.

A binding the pass never filled gets a neutral resource rather than being left out: WebGPU refuses an incomplete group outright. An unfilled block reads the zeroed buffer and an unfilled sampler a white texel.

And says so, into debugDrainErrors. The contract makes a declared slot left unbound the caller's mistake, and this is the one backend that sees every stage's declarations at the draw. It used to fill the hole in silence, which made it the backend that drew cleanly through the 0.7.2 regression that Metal failed on natively.

Implementation

GPUBindGroup bindGroupFor(
  WebGpuBindingLayouts layouts,
  int group,
  Map<int, WebGpuSlice>? blocks,
  Map<int, GPUTextureView>? views,
  Map<int, GPUSampler>? samplers,
) {
  final shape = layouts.shapes[group];
  final resources = <Object>[];
  final entries = <GPUBindGroupEntry>[];
  for (final bound in shape.blocks) {
    final block = bound.block;
    final filled = blocks?[block.binding]?.buffer;
    if (filled == null && bound.bindable) {
      _unbound('uniform block "${block.name}"');
    }
    final buffer = filled ?? _zeroBlock;
    resources.add(buffer);
    entries.add(
      GPUBindGroupEntry.buffer(
        binding: block.binding,
        // Offset zero and the block's own size: the offset a draw actually
        // wants rides on `setBindGroup` instead, which is what
        // `hasDynamicOffset` bought.
        resource: GPUBufferBinding(
          buffer: buffer,
          offset: 0,
          size: block.sizeInBytes,
        ),
      ),
    );
  }
  for (final bound in shape.samplers) {
    final sampler = bound.sampler;
    final filledView = views?[sampler.textureBinding];
    if (filledView == null && bound.bindable) {
      _unbound('sampler "${sampler.name}"');
    }
    final view = filledView ?? _blankView(sampler.dimension);
    final object =
        samplers?[sampler.samplerBinding] ??
        samplerFor(SamplerOptions.linearRepeat);
    resources
      ..add(view)
      ..add(object);
    entries
      ..add(
        GPUBindGroupEntry.textureView(
          binding: sampler.textureBinding,
          resource: view,
        ),
      )
      ..add(
        GPUBindGroupEntry.sampler(
          binding: sampler.samplerBinding,
          resource: object,
        ),
      );
  }
  return _bindGroups[_BindGroupKey(
    layouts.groups[group],
    resources,
  )] ??= guard(
    'a bind group for group $group',
    () => gpuDevice.createBindGroup(
      GPUBindGroupDescriptor(
        layout: layouts.groups[group],
        entries: entries.toJS,
        label: 'group $group',
      ),
    ),
  );
}