BabylonJS/Babylon.js · error
_writeStreamingSplats: splatsData has ${uBuffer.length} byte
Error message
_writeStreamingSplats: splatsData has ${uBuffer.length} bytes, need ${count * 32} for ${count} splats (stride 32) What it means
_writeStreamingSplats requires the input buffer to contain at least count * 32 bytes (the 32-byte stride per splat: position, scale, rotation, color/covariance data). It throws when splatsData is shorter than that, preventing reads past the end of the supplied buffer.
Source
Thrown at packages/dev/core/src/Meshes/GaussianSplatting/gaussianSplattingMeshBase.pure.ts:3171
* @param count number of splats to write
* @param splatsData raw `.splat` bytes for `count` splats (stride 32)
* @param min optional running min accumulator for the written centers
* @param max optional running max accumulator for the written centers
*/
protected _writeStreamingSplats(globalOffset: number, count: number, splatsData: ArrayBuffer | ArrayBufferView, min?: Vector3, max?: Vector3): void {
if (!this._splatPositions) {
return;
}
// Validate the range against the atlas and the input length (independent of GPU state): an out-of-range
// offset/count would write over another part's texels or allocate a huge transient (the CPU arrays are
// atlas-indexed up to `end`); a short input would read past its end.
const capacity = this._splatPositions.length / 4;
const uBuffer = GaussianSplattingMeshBase._GetSplatDataBytes(splatsData);
if (!Number.isInteger(globalOffset) || !Number.isInteger(count) || globalOffset < 0 || count < 0 || globalOffset + count > capacity) {
throw new Error(`_writeStreamingSplats: range [${globalOffset}, ${globalOffset + count}) is outside the atlas bounds [0, ${capacity})`);
}
if (uBuffer.length < count * 32) {
throw new Error(`_writeStreamingSplats: splatsData has ${uBuffer.length} bytes, need ${count * 32} for ${count} splats (stride 32)`);
}
if (count === 0 || !this._covariancesATexture) {
return;
}
const textureSize = this._getTextureSize(this._vertexCount);
const width = textureSize.x;
const covBSItemSize = this._useRGBACovariants ? 4 : 2;
const end = globalOffset + count;
const fBuffer = GaussianSplattingMeshBase._GetSplatDataFloats(splatsData);
// Transients are sized to `count`, not `end` (globalOffset + count): sizing by atlas position would allocate
// tens of MB for a small write near a large atlas's tail. dstIndex still addresses the atlas _splatPositions.
const covA = new Uint16Array(count * 4);
const covB = new Uint16Array(count * covBSItemSize);
const colorArray = new Uint8Array(count * 4);
const localMin = min ?? new Vector3(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE);
const localMax = max ?? new Vector3(-Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE);View on GitHub (pinned to 0592b347b8)
Solutions
- Ensure splatsData.byteLength >= count * 32 before the call, slicing from a larger source buffer as needed
- Reduce count to Math.floor(uBuffer.length / 32) if the buffer is the limiting factor
- Verify the source data stride is 32 bytes and re-pack it if not
Example fix
// before base._writeStreamingSplats(0, count, smallBuffer); // after const usable = Math.min(count, Math.floor(smallBuffer.byteLength / 32)); base._writeStreamingSplats(0, usable, smallBuffer);
Defensive patterns
Strategy: validation
Validate before calling
if (splatsData.byteLength < count * 32) {
count = Math.floor(splatsData.byteLength / 32); // or abort
} Type guard
const hasEnoughSplats = (buf: ArrayBuffer | ArrayBufferView, count: number) => (ArrayBuffer.isView(buf) ? buf.byteLength : buf.byteLength) >= count * 32;
Try / catch
try {
base._writeStreamingSplats(offset, count, data);
} catch (e) {
if (String(e).includes('stride 32')) { count = Math.floor(u8.byteLength / 32); retry(); }
else throw e;
} Prevention
- Fix the per-splat stride at 32 bytes across your pipeline
- Slice source buffers with count * 32 math, not assumed strides
- Unit-test buffer sizing for boundary counts
When it happens
Trigger: Passing a packed subset buffer whose length is less than count*32 while claiming to write count splats; mismatched stride assumptions (e.g. 20-byte .splat rows vs 32-byte expected).
Common situations: Converting between splat file formats with different strides; slicing a buffer with wrong byte math (count * stride mismatch).
Related errors
- Gaussian splat data byte length (${bytes.byteLength}) is not
- Loading from ArrayBufferView can not be used with plugins th
- GaussianSplattingDebugger.addMesh: mesh must have a Gaussian
- To call addPart()/addParts(), each source mesh must not be a
- reserveStreamingPart: shTextureCount ${shTextureCount} does
AI-assisted analysis of BabylonJS/Babylon.js@0592b347b8 (2026-08-30).
Data as JSON: /api/errors/5c42f66268946235.
Report an issue: GitHub.