{"record":{"id":"31639cad02ad9148","repo":"BabylonJS/Babylon.js","slug":"reservestreamingpart-capacity-capacity-would-g","errorCode":null,"errorMessage":"reserveStreamingPart: capacity ${capacity} would grow the atlas to ${projectedSplats} splats (plus a sentinel), exceeding the maximum atlas capacity ${maxCapacity}","messagePattern":"reserveStreamingPart: capacity (.+?) would grow the atlas to (.+?) splats \\(plus a sentinel\\), exceeding the maximum atlas capacity (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/dev/core/src/Meshes/GaussianSplatting/gaussianSplattingMesh.pure.ts","lineNumber":2100,"sourceCode":"        // atlas rows via scissor without ever touching a preceding part that shares a row.\r\n        //   - Front alignment: start the usable region on the next row boundary. This only consumes the\r\n        //     preceding parts' already-allocated last-row tail padding, so it costs no extra memory.\r\n        //   - Capacity alignment: pad the region up to a whole number of rows so its end is a row boundary too\r\n        //     (needed when another part follows, e.g. multiple streaming parts).\r\n        const atlasWidth = this._getTextureSize(1).x;\r\n        const startOffset = this._vertexCount; // first atlas index the reserved part occupies\r\n        const alignedBase = Math.ceil(startOffset / atlasWidth) * atlasWidth;\r\n        const frontPad = alignedBase - startOffset; // invisible padding that fills the preceding row\r\n        const alignedCapacity = Math.ceil(capacity / atlasWidth) * atlasWidth;\r\n        const regionSplats = frontPad + alignedCapacity;\r\n\r\n        // Validate the RESULTING atlas, not just `capacity`: existing splats, row-alignment padding, and the trailing\r\n        // empty sentinel slot (`+ 1`, see _getTextureSize) all count against the device's square-texture limit.\r\n        // _getTextureSize would silently clamp and drop the sentinel past the limit, so reject here — before any\r\n        // state is mutated below, so a rejected reservation leaves the compound intact.\r\n        const projectedSplats = startOffset + regionSplats;\r\n        if (projectedSplats + 1 > maxCapacity) {\r\n            throw new Error(\r\n                `reserveStreamingPart: capacity ${capacity} would grow the atlas to ${projectedSplats} splats (plus a sentinel), exceeding the maximum atlas capacity ${maxCapacity}`\r\n            );\r\n        }\r\n\r\n        // Back the atlas with a render-targetable MRT so a streaming engine can GPU-decode into the reserved\r\n        // region. Must be set before _addPartsInternal so the (forced) full rebuild builds MRT attachments and\r\n        // allocates covariance B as RGBA. Harmless/no-op if a streaming part was already reserved.\r\n        this._useMrtAtlas = true;\r\n        this._useRGBACovariants = true;\r\n\r\n        // Higher-order SH: convert the SH textures to render-targetable integer MRTs so the stream can bake SH into\r\n        // its region, and set the compound's SH degree so the draw path lights the decoded splats. Sized for the\r\n        // MAX SH texture count across parts (a later higher-degree part grows it; lower-degree parts neutral-fill).\r\n        if (shTextureCount > 0 && shDegree > 0) {\r\n            this._useShMrtAtlas = true;\r\n            this._shMrtAtlasTextureCount = Math.max(this._shMrtAtlasTextureCount, shTextureCount);\r\n            // Streaming SH degree is tracked separately from _maxShDegree (which folds in static parts too) and is\r\n            // recomputed from the surviving states on removal — so it shrinks correctly. The _addPartsInternal\r","sourceCodeStart":2082,"sourceCodeEnd":2118,"githubUrl":"https://github.com/BabylonJS/Babylon.js/blob/0592b347b8a4ee0236089ea86a749cacfdb266d8/packages/dev/core/src/Meshes/GaussianSplatting/gaussianSplattingMesh.pure.ts#L2082-L2118","documentation":"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.","triggerScenarios":"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).","commonSituations":"Streaming very large splat clouds on mobile GPUs with low maxTextureSize caps; accumulating many streaming parts without ever removing or compacting them.","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"],"exampleFix":"// before\nmesh.reserveStreamingPart(2_000_000); // blows past atlas cap\n// after\nconst cap = engine.getCaps().maxTextureSize ** 2;\nif (existingSplats + 2_000_000 + 1 > cap) {\n    // split into a second mesh or shrink the part\n}","handlingStrategy":"validation","validationCode":"const cap = engine.getCaps().maxTextureSize ** 2;\nif (mesh.getTotalSplatCount?.() + regionSplats + 1 > cap) {\n  throw new RangeError('reservation would exceed atlas capacity');\n}","typeGuard":"null","tryCatchPattern":"try {\n  mesh.reserveStreamingPart(capacity);\n} catch (e) {\n  if (String(e).includes('maximum atlas capacity')) {\n    splitIntoSecondMesh(capacity); // fallback\n  } else throw e;\n}","preventionTips":["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"],"tags":["gaussian-splatting","texture-limits","capacity"],"backgroundTag":"texture-size-limit-exceeded","analyzedSha":"0592b347b8a4ee0236089ea86a749cacfdb266d8","analyzedAt":"2026-08-30T15:11:20.442Z","schemaVersion":2},"datasetVersion":"2026-08-30T18:17:15.746Z"}