setCustomAttribute method

void setCustomAttribute(
  1. String name,
  2. Float32List data, {
  3. required int components,
})

Attaches a custom per-vertex attribute stream named name, matching a material's attributes entry (and the generated shader's in <name>).

data is a tightly packed run of components-component float vectors, one per vertex (so its length is vertexCount * components). The stream is uploaded to its own buffer and bound after the geometry's built-in attributes in the color pass; the depth-style passes fetch only position, so an attribute-driven vertex displacement is not reflected in shadows. Re-attaching the same name replaces it. Attaching one to a skinned mesh switches it to a described layout, since reflection cannot know which slot the stream was bound to.

Implementation

void setCustomAttribute(
  String name,
  Float32List data, {
  required int components,
}) {
  if (components < 1 || components > 4) {
    throw ArgumentError.value(
      components,
      'components',
      'must be between 1 and 4',
    );
  }
  // _vertexCount is 0 until the first vertex upload, so a mismatch can only
  // be judged once the count is known. Setting the attribute first is a
  // legitimate ordering, covered by the message clause below.
  if (_vertexCount > 0 && data.length != _vertexCount * components) {
    throw ArgumentError(
      'Custom attribute "$name" has ${data.length} floats, but this geometry '
      'has $_vertexCount vertices at $components components each, so it needs '
      '${_vertexCount * components}. Set the attribute after uploading '
      'vertices, and re-set it after any rebuild that changes the vertex '
      'count.',
    );
  }
  final bytes = ByteData.sublistView(data);
  final buffer = gpu.gpuContext.createDeviceBuffer(
    gpu.StorageMode.hostVisible,
    bytes.lengthInBytes,
  );
  buffer.overwrite(bytes);
  _customAttributes[name] = (
    format: _vertexFormatForComponents(components),
    view: gpu.BufferView(
      buffer,
      offsetInBytes: 0,
      lengthInBytes: bytes.lengthInBytes,
    ),
    data: data,
    components: components,
  );
}