BabylonJS/Babylon.js · error
reserveStreamingPart: capacity ${capacity} would grow the at
Error message
reserveStreamingPart: capacity ${capacity} would grow the atlas to ${projectedSplats} splats (plus a sentinel), exceeding the maximum atlas capacity ${maxCapacity} What it means
reserveStreamingPart projects the resulting atlas size (existing splats + new region + one sentinel texel row) against the device's maxTextureSize^2 limit and throws if it would exceed it. It rejects before mutating any state so a failed reservation leaves the compound mesh intact. The sentinel slot is required by _getTextureSize and is not optional.
Source
Thrown at packages/dev/core/src/Meshes/GaussianSplatting/gaussianSplattingMesh.pure.ts:2100
// atlas rows via scissor without ever touching a preceding part that shares a row.
// - Front alignment: start the usable region on the next row boundary. This only consumes the
// preceding parts' already-allocated last-row tail padding, so it costs no extra memory.
// - Capacity alignment: pad the region up to a whole number of rows so its end is a row boundary too
// (needed when another part follows, e.g. multiple streaming parts).
const atlasWidth = this._getTextureSize(1).x;
const startOffset = this._vertexCount; // first atlas index the reserved part occupies
const alignedBase = Math.ceil(startOffset / atlasWidth) * atlasWidth;
const frontPad = alignedBase - startOffset; // invisible padding that fills the preceding row
const alignedCapacity = Math.ceil(capacity / atlasWidth) * atlasWidth;
const regionSplats = frontPad + alignedCapacity;
// Validate the RESULTING atlas, not just `capacity`: existing splats, row-alignment padding, and the trailing
// empty sentinel slot (`+ 1`, see _getTextureSize) all count against the device's square-texture limit.
// _getTextureSize would silently clamp and drop the sentinel past the limit, so reject here — before any
// state is mutated below, so a rejected reservation leaves the compound intact.
const projectedSplats = startOffset + regionSplats;
if (projectedSplats + 1 > maxCapacity) {
throw new Error(
`reserveStreamingPart: capacity ${capacity} would grow the atlas to ${projectedSplats} splats (plus a sentinel), exceeding the maximum atlas capacity ${maxCapacity}`
);
}
// Back the atlas with a render-targetable MRT so a streaming engine can GPU-decode into the reserved
// region. Must be set before _addPartsInternal so the (forced) full rebuild builds MRT attachments and
// allocates covariance B as RGBA. Harmless/no-op if a streaming part was already reserved.
this._useMrtAtlas = true;
this._useRGBACovariants = true;
// Higher-order SH: convert the SH textures to render-targetable integer MRTs so the stream can bake SH into
// its region, and set the compound's SH degree so the draw path lights the decoded splats. Sized for the
// MAX SH texture count across parts (a later higher-degree part grows it; lower-degree parts neutral-fill).
if (shTextureCount > 0 && shDegree > 0) {
this._useShMrtAtlas = true;
this._shMrtAtlasTextureCount = Math.max(this._shMrtAtlasTextureCount, shTextureCount);
// Streaming SH degree is tracked separately from _maxShDegree (which folds in static parts too) and is
// recomputed from the surviving states on removal — so it shrinks correctly. The _addPartsInternal
View on GitHub (pinned to 0592b347b8)
Solutions
- Split the splat cloud across more GaussianSplattingMesh instances instead of one atlas
- Remove unused streaming parts (freeing their regions) before reserving new ones
- Check this.scene.getEngine().getCaps().maxTextureSize ahead of time and clamp total splat count to maxTextureSize^2 - 1 minus existing splats
Example fix
// before
mesh.reserveStreamingPart(2_000_000); // blows past atlas cap
// after
const cap = engine.getCaps().maxTextureSize ** 2;
if (existingSplats + 2_000_000 + 1 > cap) {
// split into a second mesh or shrink the part
} Defensive patterns
Strategy: validation
Validate before calling
const cap = engine.getCaps().maxTextureSize ** 2;
if (mesh.getTotalSplatCount?.() + regionSplats + 1 > cap) {
throw new RangeError('reservation would exceed atlas capacity');
} Type guard
null
Try / catch
try {
mesh.reserveStreamingPart(capacity);
} catch (e) {
if (String(e).includes('maximum atlas capacity')) {
splitIntoSecondMesh(capacity); // fallback
} else throw e;
} Prevention
- Read engine.getCaps().maxTextureSize before designing streaming batch sizes
- Budget total splats as maxTextureSize^2 - 1 per compound mesh
- Remove stale streaming parts before reserving new capacity
When it happens
Trigger: Reserving a streaming part whose startOffset + regionSplats + 1 exceeds maxTextureSize * maxTextureSize; e.g. adding parts to an already near-full atlas on a GPU with a small max texture size (4096 or 8192).
Common situations: Streaming very large splat clouds on mobile GPUs with low maxTextureSize caps; accumulating many streaming parts without ever removing or compacting them.
Related errors
- GaussianSplattingDebugger.addMesh: mesh must have a Gaussian
- To call addPart()/addParts(), each source mesh must be fully
- To call addPart()/addParts(), each source mesh must not be a
- Cannot add part, as the maximum part count (${maxPartCount})
- Cannot rebuild compound part "${proxy.name}": the retained c
AI-assisted analysis of BabylonJS/Babylon.js@0592b347b8 (2026-08-30).
Data as JSON: /api/errors/31639cad02ad9148.
Report an issue: GitHub.