mrdoob/three.js · error · Error
THREE.WebGPUAttributeUtils: Bad vertex format item size.
Error message
THREE.WebGPUAttributeUtils: Bad vertex format item size.
What it means
Thrown by WebGPUAttributeUtils._getVertexFormat while deriving a WebGPU vertex format string ('prefix x N') for a BufferAttribute with itemSize > 1. It computes bytesPerUnit = elementByteSize * itemSize, rounds it up to a 4-byte WebGPU alignment boundary (paddedBytesPerUnit), then requires paddedBytesPerUnit / ArrayType.BYTES_PER_ELEMENT to be a whole number. If that quotient is fractional, no valid WebGPU vertex format can describe the attribute, so construction is rejected. With the standard typed arrays registered in typedArraysToVertexFormatPrefix (element sizes 1, 2, or 4 bytes), the padded value is always divisible, so this guard is a defensive invariant that fires only for unusual element sizes or a custom BufferAttribute subclass whose backing array does not evenly divide a 4-byte boundary.
Source
Thrown at src/renderers/webgpu/utils/WebGPUAttributeUtils.js:535
if ( itemSize === 1 ) {
format = typeArraysToVertexFormatPrefixForItemSize1.get( ArrayType );
} else {
const prefixOptions = typedAttributeToVertexFormatPrefix.get( AttributeType ) || typedArraysToVertexFormatPrefix.get( ArrayType );
const prefix = prefixOptions[ normalized ? 1 : 0 ];
if ( prefix ) {
const bytesPerUnit = ArrayType.BYTES_PER_ELEMENT * itemSize;
const paddedBytesPerUnit = Math.floor( ( bytesPerUnit + 3 ) / 4 ) * 4;
const paddedItemSize = paddedBytesPerUnit / ArrayType.BYTES_PER_ELEMENT;
if ( paddedItemSize % 1 ) {
throw new Error( 'THREE.WebGPUAttributeUtils: Bad vertex format item size.' );
}
format = `${prefix}x${paddedItemSize}`;
}
}
if ( ! format ) {
error( 'WebGPUAttributeUtils: Vertex format not supported yet.' );
}
return format;
}View on GitHub (pinned to da05705fa3)
Solutions
- Back the attribute with one of the supported standard typed arrays (Int8Array, Uint8Array, Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array, Float16Array) so paddedBytesPerUnit is always divisible.
- Use an itemSize in {1,2,3,4} which is what the WebGPU vertex-format table covers for these element sizes.
- If a non-standard packed layout is genuinely required, interleave the data into a Float32Array/Uint8Array attribute with a matching itemSize and decode in the shader instead.
- Remove any custom entry you added to typedAttributeToVertexFormatPrefix/typedArraysToVertexFormatPrefix unless the backing array reports a BYTES_PER_ELEMENT in {1,2,4}.
Example fix
// before: custom typed array with non power-of-two friendly element size
class WeirdArray extends Array { static get BYTES_PER_ELEMENT(){ return 3; } }
geo.setAttribute( 'position', new THREE.BufferAttribute( new WeirdArray( data ), 2 ) ); // throws
// after: use a standard typed array sized to the 4-byte WebGPU vertex alignment
geo.setAttribute( 'position', new THREE.BufferAttribute( new Float32Array( data ), 3 ) ); Defensive patterns
Strategy: validation
Validate before calling
const SUPPORTED_VERTEX_ARRAY_TYPES = new Set( [
Int8Array, Uint8Array, Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array,
...( typeof Float16Array !== 'undefined' ? [ Float16Array ] : [] ),
] );
// Reject before attaching an attribute whose WebGPU vertex format cannot be derived.
function isWebGPUVertexAttributeOk( attribute ) {
const { itemSize, array } = attribute;
if ( ! SUPPORTED_VERTEX_ARRAY_TYPES.has( array.constructor ) ) return false;
if ( itemSize < 1 || itemSize > 4 ) return false;
const bytesPerUnit = array.constructor.BYTES_PER_ELEMENT * itemSize;
const padded = Math.floor( ( bytesPerUnit + 3 ) / 4 ) * 4;
return Number.isInteger( padded / array.constructor.BYTES_PER_ELEMENT );
}
if ( ! isWebGPUVertexAttributeOk( attr ) ) {
throw new Error( 'Attribute would produce a bad WebGPU vertex format' );
} Type guard
function isStandardVertexAttribute( attribute ) {
const ArrayCtor = attribute?.array?.constructor;
const supported = [ Int8Array, Uint8Array, Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array ];
if ( typeof Float16Array !== 'undefined' ) supported.push( Float16Array );
return supported.includes( ArrayCtor ) && [ 1, 2, 3, 4 ].includes( attribute.itemSize );
} Prevention
- Only attach BufferAttributes backed by standard 1/2/4-byte typed arrays to geometries rendered with WebGPU.
- Keep itemSize in the 1-4 range that maps onto the WebGPU vertex-format table.
- Do not extend typedArraysToVertexFormatPrefix / typedAttributeToVertexFormatPrefix with arrays whose BYTES_PER_ELEMENT is not 1, 2, or 4.
- Validate attributes once at scene-load time rather than discovering the failure during rendering.
When it happens
Trigger: Registering a custom BufferAttribute subclass in typedAttributeToVertexFormatPrefix backed by a non-standard typed array (element size not in {1,2,4}) combined with an itemSize that makes the 4-byte-padded byte count indivisible by the element size. Monkey-patching or extending BufferAttribute with an array whose .constructor reports a typed array whose BYTES_PER_ELEMENT does not divide 4. Theoretically also with engineered itemSize values, though standard 1/2/4-byte arrays make the division always integral.
Common situations: Using a custom or polyfilled typed array (e.g. BigInt64Array/BigUint64Array with 8-byte elements, or a shim reporting a non-standard byte size) for a vertex attribute. Porting WebGL code that used exotic packed formats. Library upgrades where a previously-tolerated custom attribute type is now routed through _getVertexFormat via the prefix maps.
Related errors
- THREE.WebGLBackend: Unsupported buffer data format:
- THREE.WebGLAttributes: Unsupported buffer data format:
- THREE.WebGLAttributes: The size of the buffer attribute's ar
- THREE.CubeCamera.updateCoordinateSystem(): Invalid coordinat
- THREE.BufferAttribute: array should be a Typed Array.
AI-assisted analysis of mrdoob/three.js@da05705fa3 (2026-08-12).
Data as JSON: /api/errors/2409544f80610010.
Report an issue: GitHub.