{"record":{"id":"fbd42575c452aeb4","repo":"BabylonJS/Babylon.js","slug":"reservestreamingpart-shtexturecount-shtextureco","errorCode":null,"errorMessage":"reserveStreamingPart: shTextureCount ${shTextureCount} does not match shDegree ${shDegree} (expected ${expectedShTextureCount})","messagePattern":"reserveStreamingPart: shTextureCount (.+?) does not match shDegree (.+?) \\(expected (.+?)\\)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/dev/core/src/Meshes/GaussianSplatting/gaussianSplattingMesh.pure.ts","lineNumber":2074,"sourceCode":"        }\r\n\r\n        // Validate the SH sizing parameters — they drive the SH_DEGREE define and the SH texture allocation, so a\r\n        // fractional/negative/huge value would give an invalid layout or an unbounded allocation. The draw path\r\n        // supports degree 0..4 (shTexture0..4); SH is all-or-nothing; the texture count must match the degree.\r\n        const maxSupportedShDegree = 4;\r\n        if (!Number.isSafeInteger(shDegree) || shDegree < 0 || shDegree > maxSupportedShDegree) {\r\n            throw new Error(`reserveStreamingPart: shDegree must be an integer in [0, ${maxSupportedShDegree}]`);\r\n        }\r\n        if (!Number.isSafeInteger(shTextureCount) || shTextureCount < 0) {\r\n            throw new Error(\"reserveStreamingPart: shTextureCount must be a non-negative integer\");\r\n        }\r\n        if (shDegree > 0 !== shTextureCount > 0) {\r\n            throw new Error(\"reserveStreamingPart: shDegree and shTextureCount must both be positive (SH) or both zero (no SH)\");\r\n        }\r\n        if (shDegree > 0) {\r\n            const expectedShTextureCount = Math.ceil((((shDegree + 1) * (shDegree + 1) - 1) * 3) / _GaussianSplattingBytesPerShTexel);\r\n            if (shTextureCount !== expectedShTextureCount) {\r\n                throw new Error(`reserveStreamingPart: shTextureCount ${shTextureCount} does not match shDegree ${shDegree} (expected ${expectedShTextureCount})`);\r\n            }\r\n        }\r\n\r\n        const maxTextureSize = this._scene.getEngine().getCaps().maxTextureSize;\r\n        const maxCapacity = maxTextureSize * maxTextureSize;\r\n\r\n        // Row-align the region so a later GPU relayout (defrag under a memory budget) can be scoped to whole\r\n        // 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","sourceCodeStart":2056,"sourceCodeEnd":2092,"githubUrl":"https://github.com/BabylonJS/Babylon.js/blob/0592b347b8a4ee0236089ea86a749cacfdb266d8/packages/dev/core/src/Meshes/GaussianSplatting/gaussianSplattingMesh.pure.ts#L2056-L2092","documentation":"GaussianSplattingMesh.reserveStreamingPart validates that the number of SH texels reserved for a streaming part is consistent with the requested spherical-harmonics degree. For degree d, each splat needs ceil((((d+1)^2 - 1) * 3) / bytesPerShTexel) texels, and the library throws when shTextureCount differs from that derived value. This prevents corrupt SH texture layouts in the splat atlas.","triggerScenarios":"Calling reserveStreamingPart with an shTextureCount computed with a different formula, hardcoded count, or one derived from a different bytes-per-texel constant than the one used internally.","commonSituations":"Custom splat converters that precompute SH texture sizes for SH degree 1-3, or code copied from an older Babylon version where the packing constant differed.","solutions":["Recompute shTextureCount as Math.ceil((((shDegree + 1) * (shDegree + 1) - 1) * 3) / _GaussianSplattingBytesPerShTexel) before calling reserveStreamingPart","Pass shTextureCount of 0 together with shDegree 0 if the part has no SH data","Log both values at the call site and compare against the expected count in the message"],"exampleFix":"// before\nmesh.reserveStreamingPart(count, { shDegree: 2, shTextureCount: 45 });\n// after\nconst expected = Math.ceil((((2 + 1) * (2 + 1) - 1) * 3) / bytesPerShTexel);\nmesh.reserveStreamingPart(count, { shDegree: 2, shTextureCount: expected });","handlingStrategy":"validation","validationCode":"const expected = Math.ceil((((shDegree + 1) * (shDegree + 1) - 1) * 3) / bytesPerShTexel);\nif (shDegree > 0 !== shTextureCount > 0 || (shDegree > 0 && shTextureCount !== expected)) {\n    throw new RangeError(`shTextureCount ${shTextureCount} invalid for shDegree ${shDegree}; expected ${expected}`);\n}","typeGuard":"const isValidShPair = (d: number, t: number, b: number) =>\n  d > 0 ? t === Math.ceil((((d + 1) * (d + 1) - 1) * 3) / b) : t === 0;","tryCatchPattern":"try {\n  mesh.reserveStreamingPart(count, opts);\n} catch (e) {\n  if (String(e).includes('shTextureCount')) { opts.shTextureCount = recompute(opts.shDegree); retry(); }\n  else throw e;\n}","preventionTips":["Derive shTextureCount from shDegree with the library's formula, never hardcode it","Keep the SH packing constant in one shared module","Test reserve calls for degrees 0-3 in unit tests"],"tags":["gaussian-splatting","validation","streaming"],"backgroundTag":"splat-sh-texture-count-mismatch","analyzedSha":"0592b347b8a4ee0236089ea86a749cacfdb266d8","analyzedAt":"2026-08-30T15:11:20.442Z","schemaVersion":2},"datasetVersion":"2026-08-30T18:17:15.746Z"}