setCustomAttribute method
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,
);
}