@voluma/vlam 0.2.4 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/README.md +3 -4
  2. package/dist/chunk-loader-DlNwggfG.js +1859 -0
  3. package/dist/chunk-loader-DlNwggfG.js.map +1 -0
  4. package/dist/{compute-sorter.d.ts → core/compute-sorter.d.ts} +1 -1
  5. package/dist/core/index.d.ts +45 -0
  6. package/dist/{splat-scene.d.ts → core/merged-splat-mesh.d.ts} +14 -15
  7. package/dist/{orientation.d.ts → core/orientation.d.ts} +7 -7
  8. package/dist/{relighting.d.ts → core/relighting.d.ts} +1 -1
  9. package/dist/{source-transform.d.ts → core/source-transform.d.ts} +4 -4
  10. package/dist/{splat-budget.d.ts → core/splat-budget.d.ts} +2 -2
  11. package/dist/{splat-data.d.ts → core/splat-data.d.ts} +4 -4
  12. package/dist/{splat-mesh-material.d.ts → core/splat-mesh-material.d.ts} +2 -2
  13. package/dist/{splat-mesh-types.d.ts → core/splat-mesh-types.d.ts} +19 -10
  14. package/dist/{splat-mesh.d.ts → core/splat-mesh.d.ts} +4 -4
  15. package/dist/{splat-modifier.d.ts → core/splat-modifier.d.ts} +5 -5
  16. package/dist/{webgpu-limits.d.ts → core/webgpu-limits.d.ts} +0 -59
  17. package/dist/{effects.d.ts → effects/index.d.ts} +14 -13
  18. package/dist/effects.js.map +1 -1
  19. package/dist/formats/ksplat/index.d.ts +1 -1
  20. package/dist/formats/ksplat/parse-ksplat.d.ts +1 -1
  21. package/dist/formats/ksplat.js.map +1 -1
  22. package/dist/formats/lcc/collision-mesh.d.ts +3 -3
  23. package/dist/formats/lcc/collision-partition.d.ts +1 -1
  24. package/dist/formats/lcc/lcc.d.ts +3 -3
  25. package/dist/formats/lcc/lcc2.d.ts +2 -2
  26. package/dist/formats/lcc/parse-lcc.d.ts +1 -1
  27. package/dist/formats/lcc.js +17 -16
  28. package/dist/formats/lcc.js.map +1 -1
  29. package/dist/formats/ply/index.d.ts +1 -1
  30. package/dist/formats/ply/parse-compressed-ply.d.ts +2 -2
  31. package/dist/formats/ply/parse-splat-ply.d.ts +4 -4
  32. package/dist/formats/ply.js +1 -1
  33. package/dist/formats/ply.js.map +1 -1
  34. package/dist/formats/rad/frontier-worker-protocol.d.ts +8 -1
  35. package/dist/formats/rad/index.d.ts +2 -2
  36. package/dist/formats/rad/parse-rad.d.ts +2 -2
  37. package/dist/formats/rad/rad-foveated-source.d.ts +3 -3
  38. package/dist/formats/rad/rad.d.ts +7 -7
  39. package/dist/formats/rad.js +3 -3
  40. package/dist/formats/rad.js.map +1 -1
  41. package/dist/formats/sog/index.d.ts +1 -1
  42. package/dist/formats/sog/parse-sog.d.ts +6 -6
  43. package/dist/formats/sog.js.map +1 -1
  44. package/dist/formats/splat/index.d.ts +1 -1
  45. package/dist/formats/splat/parse-splat.d.ts +1 -1
  46. package/dist/formats/splat.js.map +1 -1
  47. package/dist/formats/spz/index.d.ts +1 -1
  48. package/dist/formats/spz/parse-spz.d.ts +1 -1
  49. package/dist/formats/spz.js.map +1 -1
  50. package/dist/half-float-Cs4FDq8Q.js.map +1 -1
  51. package/dist/index.js +253 -9047
  52. package/dist/index.js.map +1 -1
  53. package/dist/{chunk-loader.d.ts → loaders/chunk-loader.d.ts} +4 -4
  54. package/dist/loaders/index.d.ts +15 -0
  55. package/dist/{load-scene.d.ts → loaders/load-splat-data.d.ts} +16 -14
  56. package/dist/{load-worker-protocol.d.ts → loaders/load-worker-protocol.d.ts} +2 -2
  57. package/dist/{loading.d.ts → loaders/loading.d.ts} +8 -8
  58. package/dist/loaders.js +26 -0
  59. package/dist/loaders.js.map +1 -0
  60. package/dist/loading-wLo8vRbA.js.map +1 -1
  61. package/dist/{splat-mesh-types-CTN_duw2.js → lod-scheduler-B0a_uBlv.js} +133 -140
  62. package/dist/lod-scheduler-B0a_uBlv.js.map +1 -0
  63. package/dist/logging-BfPdd7NJ.js.map +1 -1
  64. package/dist/{ply-header-BwgExCn-.js → ply-header-B9WllbPg.js} +3 -3
  65. package/dist/ply-header-B9WllbPg.js.map +1 -0
  66. package/dist/radix-sorter-BrbUg_CV.js +178 -0
  67. package/dist/radix-sorter-BrbUg_CV.js.map +1 -0
  68. package/dist/relighting-Tiwep8yd.js +977 -0
  69. package/dist/relighting-Tiwep8yd.js.map +1 -0
  70. package/dist/selection/index.d.ts +13 -0
  71. package/dist/{selection-volume.d.ts → selection/selection-volume.d.ts} +1 -1
  72. package/dist/{splat-partition.d.ts → selection/splat-partition.d.ts} +2 -2
  73. package/dist/selection.js +137 -0
  74. package/dist/selection.js.map +1 -0
  75. package/dist/sh-pack-D5wAe5gg.js.map +1 -1
  76. package/dist/{splat-budget-DXew_86c.js → splat-budget-DuVZspPQ.js} +7 -7
  77. package/dist/splat-budget-DuVZspPQ.js.map +1 -0
  78. package/dist/splat-data-BZM5dhhx.js.map +1 -1
  79. package/dist/splat-mesh-CsLOQb08.js +1942 -0
  80. package/dist/splat-mesh-CsLOQb08.js.map +1 -0
  81. package/dist/splat-mesh-types-BZIko-_9.js +18 -0
  82. package/dist/splat-mesh-types-BZIko-_9.js.map +1 -0
  83. package/dist/static-lod/index.d.ts +12 -0
  84. package/dist/{static-lod-splat-mesh.d.ts → static-lod/static-lod-splat-mesh.d.ts} +3 -3
  85. package/dist/{static-lod-worker-protocol.d.ts → static-lod/static-lod-worker-protocol.d.ts} +1 -1
  86. package/dist/{static-lod.d.ts → static-lod/static-lod.d.ts} +1 -1
  87. package/dist/{static-lod-worker-D2fyqQZO.js → static-lod-worker-DktKnJkR.js} +46 -46
  88. package/dist/{static-lod-worker-D2fyqQZO.js.map → static-lod-worker-DktKnJkR.js.map} +1 -1
  89. package/dist/static-lod.js +148 -0
  90. package/dist/static-lod.js.map +1 -0
  91. package/dist/{budget-governor.d.ts → streaming/budget-governor.d.ts} +4 -4
  92. package/dist/{camera-budget-governor.d.ts → streaming/camera-budget-governor.d.ts} +11 -11
  93. package/dist/{chunk-cache-budget.d.ts → streaming/chunk-cache-budget.d.ts} +5 -5
  94. package/dist/{chunk-fetch-scheduler.d.ts → streaming/chunk-fetch-scheduler.d.ts} +4 -4
  95. package/dist/{dataset-source.d.ts → streaming/dataset-source.d.ts} +1 -1
  96. package/dist/streaming/index.d.ts +23 -0
  97. package/dist/{lod-scheduler.d.ts → streaming/lod-scheduler.d.ts} +1 -1
  98. package/dist/{lod-source.d.ts → streaming/lod-source.d.ts} +9 -9
  99. package/dist/{streamed-splat-mesh-utils.d.ts → streaming/streamed-splat-mesh-utils.d.ts} +2 -2
  100. package/dist/{streamed-splat-mesh.d.ts → streaming/streamed-splat-mesh.d.ts} +66 -32
  101. package/dist/streaming.js +2911 -0
  102. package/dist/streaming.js.map +1 -0
  103. package/dist/unified/index.d.ts +11 -0
  104. package/dist/{unified-splat-renderer.d.ts → unified/unified-splat-mesh.d.ts} +23 -16
  105. package/dist/unified/unified-work-buffer.d.ts +66 -0
  106. package/dist/{work-buffer-gather.d.ts → unified/work-buffer-gather.d.ts} +6 -6
  107. package/dist/{work-buffer-material.d.ts → unified/work-buffer-material.d.ts} +1 -1
  108. package/dist/unified.js +746 -0
  109. package/dist/unified.js.map +1 -0
  110. package/dist/webgpu-limits-Y3CI_6_6.js +43 -0
  111. package/dist/webgpu-limits-Y3CI_6_6.js.map +1 -0
  112. package/package.json +27 -7
  113. package/dist/index.d.ts +0 -54
  114. package/dist/ply-header-BwgExCn-.js.map +0 -1
  115. package/dist/splat-budget-DXew_86c.js.map +0 -1
  116. package/dist/splat-mesh-types-CTN_duw2.js.map +0 -1
  117. /package/dist/{create-splat-renderer.d.ts → core/create-splat-renderer.d.ts} +0 -0
  118. /package/dist/{depth-of-field.d.ts → core/depth-of-field.d.ts} +0 -0
  119. /package/dist/{half-float.d.ts → core/half-float.d.ts} +0 -0
  120. /package/dist/{logging.d.ts → core/logging.d.ts} +0 -0
  121. /package/dist/{radix-sort.d.ts → core/radix-sort.d.ts} +0 -0
  122. /package/dist/{radix-sorter.d.ts → core/radix-sorter.d.ts} +0 -0
  123. /package/dist/{relighting-pass.d.ts → core/relighting-pass.d.ts} +0 -0
  124. /package/dist/{sh-pack.d.ts → core/sh-pack.d.ts} +0 -0
  125. /package/dist/{sort-scheduler.d.ts → core/sort-scheduler.d.ts} +0 -0
  126. /package/dist/{sort-worker-protocol.d.ts → core/sort-worker-protocol.d.ts} +0 -0
  127. /package/dist/{sorter.d.ts → core/sorter.d.ts} +0 -0
  128. /package/dist/{splat-depth-pack.d.ts → core/splat-depth-pack.d.ts} +0 -0
  129. /package/dist/{splat-mesh-picking.d.ts → core/splat-mesh-picking.d.ts} +0 -0
  130. /package/dist/{splat-mesh-pool.d.ts → core/splat-mesh-pool.d.ts} +0 -0
  131. /package/dist/{splat-modifier-stack.d.ts → core/splat-modifier-stack.d.ts} +0 -0
  132. /package/dist/{splat-query.d.ts → core/splat-query.d.ts} +0 -0
  133. /package/dist/{splat-sort-bounds.d.ts → core/splat-sort-bounds.d.ts} +0 -0
  134. /package/dist/{storage-attribute-mirror.d.ts → core/storage-attribute-mirror.d.ts} +0 -0
  135. /package/dist/{worker-sorter.d.ts → core/worker-sorter.d.ts} +0 -0
  136. /package/dist/{xr-view.d.ts → core/xr-view.d.ts} +0 -0
  137. /package/dist/{ply-header.d.ts → loaders/ply-header.d.ts} +0 -0
  138. /package/dist/{lod-manifest.d.ts → streaming/lod-manifest.d.ts} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unified.js","sources":["../src/lib/unified/work-buffer-gather.ts","../src/lib/unified/work-buffer-material.ts","../src/lib/unified/unified-work-buffer.ts","../src/lib/unified/unified-splat-mesh.ts"],"sourcesContent":["import * as THREE from 'three/webgpu';\nimport {\n Fn,\n If,\n bool,\n float,\n int,\n instanceIndex,\n ivec2,\n mat3,\n storage,\n textureLoad,\n uniform,\n vec3,\n vec4,\n} from 'three/tsl';\nimport {\n evaluateSplatSh,\n foldSplatModifierStack,\n asNode,\n applyIsotropicCovarianceOverride,\n DEFAULT_ISOTROPIC_VARIANCE_SCALE,\n type SplatShInputs,\n type Vec3Uniform,\n} from '../core/splat-mesh-material';\nimport { colorSpaceToWorking } from 'three/tsl';\nimport type { SplatModifier } from '../core/splat-modifier';\nimport { StorageMirrorReleaser } from '../core/storage-attribute-mirror';\n\n/**\n * GPU gather pass for Phase 0 of the unified renderer. It resolves a source\n * pool's active local centers into a shared world-space storage buffer. The\n * work-buffer sorter consumes this buffer directly, so source-local pools can\n * later be heterogeneous (including streamed LOD pools).\n *\n * Internal while M15.4's draw path and public source API are completed.\n */\n/** Shared GPU storage for every gathered source in one unified frame. */\nexport class WorkBuffer {\n /** World-space center xyz; w is display opacity (`visibility × source opacity ×` RAD modifier ratio). */\n readonly centers: THREE.StorageBufferAttribute;\n /** Resolved color and opacity per work slot. */\n readonly colors: THREE.StorageBufferAttribute;\n /** First packed covariance row per work slot. */\n readonly covarianceA: THREE.StorageBufferAttribute;\n /** Second packed covariance row per work slot. */\n readonly covarianceB: THREE.StorageBufferAttribute;\n /** Per-slot isotropic covariance mix (0 = keep Σ, 1 = round dot). */\n readonly isotropicMix: THREE.StorageBufferAttribute;\n /** Per-slot isotropic target sigma radius in screen px (0 = disabled). */\n readonly isotropicScreenRadius: THREE.StorageBufferAttribute;\n\n /**\n * Slots past {@link capacity}, reserved so a pipeline warm-up has somewhere\n * harmless to write. See {@link WorkBufferGather.warmUp}: compiling a compute\n * pipeline requires actually dispatching it, and dispatching zero workgroups\n * - while it does compile - makes WebGPU log an \"unusual workgroup count of 0\"\n * warning for every source. One workgroup aimed at this tail compiles just the\n * same, silently, and cannot disturb a drawn slot: admission never allocates\n * past `capacity` and the draw's `instanceCount` never covers it.\n */\n static readonly SCRATCH_SLOTS = 256;\n\n constructor(readonly capacity: number) {\n const slots = capacity + WorkBuffer.SCRATCH_SLOTS;\n this.centers = new THREE.StorageBufferAttribute(new Float32Array(slots * 4), 4);\n this.colors = new THREE.StorageBufferAttribute(new Float32Array(slots * 4), 4);\n this.covarianceA = new THREE.StorageBufferAttribute(new Float32Array(slots * 4), 4);\n this.covarianceB = new THREE.StorageBufferAttribute(new Float32Array(slots * 4), 4);\n this.isotropicMix = new THREE.StorageBufferAttribute(new Float32Array(slots), 1);\n this.isotropicScreenRadius = new THREE.StorageBufferAttribute(new Float32Array(slots), 1);\n this.mirrors = new StorageMirrorReleaser([\n this.centers,\n this.colors,\n this.covarianceA,\n this.covarianceB,\n this.isotropicMix,\n this.isotropicScreenRadius,\n ]);\n }\n\n /**\n * Drops the JS-heap copies three keeps behind these storage buffers.\n *\n * Every one of them is written only by the gather compute pass and read only\n * by the draw material - the CPU side is zeroes that no code ever reads, and\n * three's WebGPU backend never frees it on its own. At 72 B/slot that mirror\n * is ~720 MB of JS heap on a 10M-slot scene. See `storage-attribute-mirror`.\n *\n * Safe to call after any dispatch that bound this buffer; a no-op once\n * settled, and on WebGL2 or a renderer double that has no WebGPU backend.\n */\n releaseCpuMirrors(renderer: THREE.WebGPURenderer): void {\n if (this.mirrors.settled) return;\n this.mirrors.release(renderer);\n }\n\n private readonly mirrors: StorageMirrorReleaser;\n}\n\nexport class WorkBufferGather {\n /** World-space `xyzw` center buffer, one element per unified work slot. */\n readonly centers: THREE.StorageBufferAttribute;\n /** Resolved source RGBA values; production draw consumes this next. */\n readonly colors: THREE.StorageBufferAttribute;\n /** Packed symmetric world-space covariance rows. */\n readonly covarianceA: THREE.StorageBufferAttribute;\n readonly covarianceB: THREE.StorageBufferAttribute;\n readonly isotropicMix: THREE.StorageBufferAttribute;\n readonly isotropicScreenRadius: THREE.StorageBufferAttribute;\n /** Shared target for this source's gathered range. */\n readonly workBuffer: WorkBuffer;\n private readonly capacity: number;\n private readonly activeCount = uniform(0);\n private readonly targetOffset = uniform(0);\n private readonly sourceMatrix = uniform(new THREE.Matrix4());\n private readonly cameraViewMatrix = uniform(new THREE.Matrix4());\n private readonly opacity = uniform(1);\n private readonly pass: THREE.ComputeNode;\n\n constructor(options: {\n capacity: number;\n sourceCapacity: number;\n sourceIndex: THREE.StorageBufferAttribute;\n centersTexture: THREE.DataTexture;\n colorsTexture: THREE.DataTexture;\n covarianceATexture: THREE.DataTexture;\n covarianceBTexture: THREE.DataTexture;\n dataTextureWidth: number;\n sh?: SplatShInputs | null;\n modifiers?: readonly SplatModifier[];\n channels?: ReadonlyMap<string, { texture: THREE.DataTexture }>;\n localCameraPosition?: Vec3Uniform;\n srgbOutput?: boolean;\n /**\n * Source stores Spark LOD alpha (`alpha ÷ 2`, `.rad`). Recover the full\n * `alpha ∈ [0,2]` into `colors.a` from the *original* texture channel so the\n * shared draw material can classify merged vs leaf without seeing fades.\n */\n lodAlpha?: boolean;\n /** Reuse one target across all source gather passes. */\n workBuffer?: WorkBuffer;\n }) {\n const {\n capacity,\n sourceCapacity,\n sourceIndex,\n centersTexture,\n colorsTexture,\n covarianceATexture,\n covarianceBTexture,\n dataTextureWidth,\n } = options;\n const sh = options.sh ?? null;\n const modifiers = options.modifiers ?? [];\n const channels = options.channels ?? new Map<string, { texture: THREE.DataTexture }>();\n const localCameraPosition = options.localCameraPosition ?? uniform(new THREE.Vector3());\n this.workBuffer = options.workBuffer ?? new WorkBuffer(capacity);\n this.capacity = this.workBuffer.capacity;\n this.centers = this.workBuffer.centers;\n this.colors = this.workBuffer.colors;\n this.covarianceA = this.workBuffer.covarianceA;\n this.covarianceB = this.workBuffer.covarianceB;\n this.isotropicMix = this.workBuffer.isotropicMix;\n this.isotropicScreenRadius = this.workBuffer.isotropicScreenRadius;\n const source = storage(sourceIndex, 'uint', sourceCapacity);\n const output = storage(this.centers, 'vec4', capacity);\n const outputColor = storage(this.colors, 'vec4', capacity);\n const outputCovA = storage(this.covarianceA, 'vec4', capacity);\n const outputCovB = storage(this.covarianceB, 'vec4', capacity);\n const outputIsotropicMix = storage(this.isotropicMix, 'float', capacity);\n const outputIsotropicScreenRadius = storage(this.isotropicScreenRadius, 'float', capacity);\n this.pass = Fn(() => {\n If(float(instanceIndex).lessThan(this.activeCount), () => {\n const poolIndex = int(source.element(instanceIndex));\n const texel = ivec2(\n poolIndex.mod(int(dataTextureWidth)),\n poolIndex.div(int(dataTextureWidth)),\n );\n const localCenter = textureLoad(centersTexture, texel).xyz;\n const target = instanceIndex.add(this.targetOffset.toUint());\n const inputA = textureLoad(covarianceATexture, texel);\n const inputB = textureLoad(covarianceBTexture, texel);\n const covarianceBase = mat3(\n vec3(inputA.x, inputA.y, inputA.z),\n vec3(inputA.y, inputA.w, inputB.x),\n vec3(inputA.z, inputB.x, inputB.y),\n );\n const baseColor = textureLoad(colorsTexture, texel);\n const colorAfterSh =\n sh === null\n ? baseColor\n : vec4(\n baseColor.rgb\n .add(\n evaluateSplatSh(\n sh,\n { covarianceBTexture },\n texel,\n localCenter.sub(localCameraPosition).normalize(),\n ),\n )\n .clamp(0.0, 1.0),\n baseColor.a,\n );\n const worldCenter = this.sourceMatrix.mul(vec4(localCenter, 1.0)).xyz;\n const stack = foldSplatModifierStack(modifiers, localCameraPosition, {\n index: poolIndex,\n localCenter,\n color: colorAfterSh,\n makeWorldCenter: () => worldCenter,\n makeViewCenter: () => this.cameraViewMatrix.mul(vec4(worldCenter, 1.0)).xyz,\n makeNormal: () => {\n const inverse = covarianceBase.inverse();\n const toCamera = localCameraPosition.sub(localCenter);\n const axis = inverse.mul(inverse.mul(toCamera)).normalize();\n return axis.mul(axis.dot(toCamera).sign());\n },\n makeChannel: (name) => {\n const channel = channels.get(name);\n if (!channel) {\n throw new Error(\n `UnifiedSplatMesh: source modifier reads undefined channel \"${name}\".`,\n );\n }\n return textureLoad(channel.texture, texel).r;\n },\n });\n const center = stack.offset === null ? localCenter : localCenter.add(stack.offset);\n const visible = stack.visible === null ? bool(true) : stack.visible;\n // Stable work slots: keep hidden splats in the gather/sort range.\n // `centers.w` is display opacity: `visibility × source opacity`, and for\n // `.rad` sources the modifier-to-original alpha ratio as well. The\n // draw material treats `w <= 0` as non-drawable. Compacting hidden\n // splats was rejected (ROADMAP L3): the hidden set exists only on the\n // GPU, so compaction needs a count readback before the CPU-sized sort.\n const gatheredCenter = this.sourceMatrix.mul(vec4(center, 1.0)).xyz;\n const encodedOriginal = asNode<'float'>(colorAfterSh.a);\n const encodedResolved = asNode<'float'>(stack.color.a);\n const visibility = visible.select(float(1), float(0));\n const resolvedColor = options.srgbOutput\n ? stack.color\n : asNode<'vec4'>(colorSpaceToWorking(stack.color, THREE.SRGBColorSpace));\n if (options.lodAlpha) {\n // Store decoded RAD alpha in `colors.a` so merged vs leaf is decided\n // from the unmodified encoding. Visual fades never enter that channel.\n const modifierOpacity = encodedOriginal\n .greaterThan(0)\n .select(encodedResolved.div(encodedOriginal.max(1e-8)), float(1));\n output\n .element(target)\n .assign(vec4(gatheredCenter, visibility.mul(this.opacity).mul(modifierOpacity)));\n outputColor.element(target).assign(vec4(resolvedColor.rgb, encodedOriginal.mul(2.0)));\n } else {\n output.element(target).assign(vec4(gatheredCenter, visibility.mul(this.opacity)));\n outputColor.element(target).assign(vec4(resolvedColor.rgb, resolvedColor.a));\n }\n const covariance =\n stack.rotation === null\n ? covarianceBase\n : stack.rotation.mul(covarianceBase).mul(stack.rotation.transpose());\n // Collapse in mesh-local space (Spark's min(scale) is local), then lift\n // to world. Applying isotropic on world Σ with a local normal inflated\n // λ and produced large soft blobs under mesh transforms.\n let localCovariance: THREE.Node<'mat3'> = asNode<'mat3'>(covariance);\n if (stack.isotropicCovarianceMix !== null) {\n const varianceScale =\n stack.isotropicVarianceScale ?? float(DEFAULT_ISOTROPIC_VARIANCE_SCALE);\n localCovariance = applyIsotropicCovarianceOverride(\n localCovariance,\n vec3(0, 0, 1),\n stack.isotropicCovarianceMix,\n varianceScale,\n );\n }\n const linear = this.sourceMatrix.toMat3();\n const worldCovarianceBase = linear.mul(localCovariance).mul(linear.transpose());\n const worldCovariance =\n stack.scaleSquared === null\n ? worldCovarianceBase\n : worldCovarianceBase.mul(stack.scaleSquared);\n const c0 = worldCovariance.mul(vec3(1, 0, 0));\n const c1 = worldCovariance.mul(vec3(0, 1, 0));\n const c2 = worldCovariance.mul(vec3(0, 0, 1));\n outputCovA.element(target).assign(vec4(c0.x, c0.y, c0.z, c1.y));\n outputCovB.element(target).assign(vec4(c1.z, c2.z, 0, 0));\n outputIsotropicMix\n .element(target)\n .assign(stack.isotropicCovarianceMix === null ? float(0) : stack.isotropicCovarianceMix);\n outputIsotropicScreenRadius\n .element(target)\n .assign(\n stack.isotropicCovarianceMix === null\n ? float(0)\n : (stack.isotropicScreenRadiusPx ?? float(0)),\n );\n });\n })().compute(sourceCapacity, [256]);\n }\n\n /** Gathers a source's active pool slots into a contiguous work-buffer range. */\n gather(\n renderer: THREE.WebGPURenderer,\n activeCount: number,\n targetOffset: number,\n sourceMatrix: THREE.Matrix4,\n cameraViewMatrixOrOpacity: THREE.Matrix4 | number = new THREE.Matrix4(),\n opacity = 1,\n ): void {\n if (activeCount < 0 || targetOffset < 0 || targetOffset + activeCount > this.capacity) {\n throw new Error('WorkBufferGather: requested range exceeds work-buffer capacity.');\n }\n this.activeCount.value = activeCount;\n this.targetOffset.value = targetOffset;\n this.sourceMatrix.value.copy(sourceMatrix);\n const cameraViewMatrix =\n typeof cameraViewMatrixOrOpacity === 'number'\n ? new THREE.Matrix4()\n : cameraViewMatrixOrOpacity;\n this.cameraViewMatrix.value.copy(cameraViewMatrix);\n this.opacity.value = Math.max(\n 0,\n typeof cameraViewMatrixOrOpacity === 'number' ? cameraViewMatrixOrOpacity : opacity,\n );\n this.pass.count = activeCount;\n renderer.compute(this.pass);\n // The dispatch just built this pass's bind group, which is what makes three\n // create the storage buffers - synchronously. Nothing reads their CPU side\n // back, so drop it. A `WorkBuffer` shared by several gathers releases on\n // whichever dispatches first; the rest find it settled.\n this.workBuffer.releaseCpuMirrors(renderer);\n }\n\n /**\n * Compiles this gather's compute pipeline without gathering anything.\n *\n * WebGPU compiles a compute pipeline lazily, at its first dispatch - not when\n * the node graph is built. Measured on a 13-mesh scene, a source's *first*\n * `gather` took up to 2.0s while every later dispatch of the same pipeline\n * took under 0.4ms, and because `gather` runs inside `update` that compile\n * landed whole in one render frame. Running it through the async path here\n * compiles off the critical frame, so the first real gather finds it ready.\n *\n * The dispatch targets {@link WorkBuffer.SCRATCH_SLOTS} past the drawable\n * capacity rather than dispatching zero workgroups: zero also compiles, but\n * WebGPU warns about it once per source, and writing into a drawn slot could\n * leave junk behind when a cached gather is reused instead of re-run.\n *\n * Best-effort: a failure here costs only the stall it was meant to avoid, so\n * callers may ignore it.\n */\n async warmUp(renderer: THREE.WebGPURenderer): Promise<void> {\n this.activeCount.value = 1;\n this.targetOffset.value = this.capacity;\n this.pass.count = 1;\n await renderer.computeAsync(this.pass);\n // Leave nothing that a later reuse check could mistake for real state.\n this.activeCount.value = 0;\n this.targetOffset.value = 0;\n // Warm-up is often the first dispatch of all, so this is usually where the\n // mirrors actually go.\n this.workBuffer.releaseCpuMirrors(renderer);\n }\n\n /** Releases the gather pipeline. The owner releases the storage attribute. */\n dispose(): void {\n this.pass.dispose();\n }\n}\n","import * as THREE from 'three/webgpu';\nimport type { FloatUniform, Vec2Uniform } from '../core/splat-mesh-material';\nimport {\n capProjectedEigenvaluesToScreenRadius,\n equalizeProjectedEigenvalues,\n} from '../core/splat-mesh-material';\nimport { MAX_DOF_VARIANCE } from '../core/depth-of-field';\nimport {\n Discard,\n Fn,\n cameraProjectionMatrix,\n float,\n instanceIndex,\n mat3,\n mix,\n modelViewMatrix,\n positionGeometry,\n screenUV,\n storage,\n texture as tslTexture,\n varying,\n vec2,\n vec3,\n vec4,\n} from 'three/tsl';\n\n/**\n * Creates the unified EWA draw material for gathered world-space splats.\n *\n * Its vertex stage reads both the sorter-written order and the gather-written\n * centers, covariance, and resolved color from storage buffers. It keeps the\n * verified ±3σ / exp(-4.5·|q|²) convention used by {@link SplatMesh}.\n */\nexport function createWorkBufferMaterial(options: {\n capacity: number;\n centers: THREE.StorageBufferAttribute;\n colors: THREE.StorageBufferAttribute;\n covarianceA: THREE.StorageBufferAttribute;\n covarianceB: THREE.StorageBufferAttribute;\n isotropicMix: THREE.StorageBufferAttribute;\n isotropicScreenRadius: THREE.StorageBufferAttribute;\n order: THREE.StorageInstancedBufferAttribute;\n focal: Vec2Uniform;\n viewport: Vec2Uniform;\n maxStdDev: FloatUniform;\n /** Screen-space minimum splat radius in px; `0` disables. */\n minSplatSizePx: FloatUniform;\n antialias: FloatUniform;\n /** Format-selected projected low-pass variance, in px². */\n projectedLowPassVariance: FloatUniform;\n /** LCC always preserves the low-pass integral; standard follows antialias. */\n compensateProjectedLowPass: FloatUniform;\n /** Core projected-2D DoF focus plane. Live; `0` aperture disables. */\n dofFocusDistance: FloatUniform;\n dofAperture: FloatUniform;\n /**\n * Proxy-mesh relight map (RGB = lit, A = coverage). Display only.\n * Host swaps the texture object and rebuilds when enabling/changing map.\n */\n relightMap: THREE.Texture;\n relightBlend: FloatUniform;\n relightBrightness: FloatUniform;\n relightBackground: FloatUniform;\n /** Coverage soft edge in screen pixels; `0` = hard mask. */\n relightSoftness: FloatUniform;\n}): THREE.NodeMaterial {\n const material = new THREE.NodeMaterial();\n const centers = storage(options.centers, 'vec4', options.capacity);\n const order = storage(options.order, 'float', options.capacity);\n const colors = storage(options.colors, 'vec4', options.capacity);\n const covarianceA = storage(options.covarianceA, 'vec4', options.capacity);\n const covarianceB = storage(options.covarianceB, 'vec4', options.capacity);\n const isotropicMix = storage(options.isotropicMix, 'float', options.capacity);\n const isotropicScreenRadius = storage(options.isotropicScreenRadius, 'float', options.capacity);\n const workColor = varying(vec4(1, 1, 1, 1), 'vWorkColor');\n const quadPosition = varying(positionGeometry.xy, 'vWorkQuadPosition');\n const opacityCompensation = varying(float(1), 'vWorkOpacityCompensation');\n const displayOpacity = varying(float(1), 'vWorkDisplayOpacity');\n // Spark LOD alpha, matching `SplatMesh`'s display graph. The gather has\n // already recovered `alpha ∈ [0,2]` for `.rad` sources, so `alpha > 1`\n // identifies a merged node here without a per-source shader variant: a\n // non-`.rad` source can only reach `alpha ≤ 1`, for which the branches below\n // collapse to the plain Gaussian this material has always drawn.\n const adjustedStdDev = varying(float(0), 'vWorkAdjustedStdDev');\n material.vertexNode = Fn(() => {\n const workIndex = order.element(instanceIndex).toInt();\n const centerSample = centers.element(workIndex);\n const center = centerSample.xyz;\n // Gather stamps display opacity into center.w. `w <= 0` is non-drawable\n // (hidden, fully faded, or a zero source). Fractional fades still draw.\n const drawable = centerSample.w.greaterThan(0);\n displayOpacity.assign(centerSample.w);\n workColor.assign(colors.element(workIndex));\n const viewCenter = modelViewMatrix.mul(vec4(center, 1.0)).toVar();\n const clipCenter = cameraProjectionMatrix.mul(viewCenter).toVar();\n const covA = covarianceA.element(workIndex);\n const covB = covarianceB.element(workIndex);\n const covariance = mat3(\n vec3(covA.x, covA.y, covA.z),\n vec3(covA.y, covA.w, covB.x),\n vec3(covA.z, covB.x, covB.y),\n );\n const invZ = float(1).div(viewCenter.z);\n const invZ2 = invZ.mul(invZ);\n const j1 = vec3(\n options.focal.x.mul(invZ),\n 0,\n options.focal.x.negate().mul(viewCenter.x).mul(invZ2),\n );\n const j2 = vec3(\n 0,\n options.focal.y.mul(invZ),\n options.focal.y.negate().mul(viewCenter.y).mul(invZ2),\n );\n const viewRotationT = modelViewMatrix.toMat3().transpose();\n const u1 = viewRotationT.mul(j1);\n const u2 = viewRotationT.mul(j2);\n const aRaw = u1.dot(covariance.mul(u1));\n const dRaw = u2.dot(covariance.mul(u2));\n const bRaw = u1.dot(covariance.mul(u2));\n\n const depth = viewCenter.z.negate().max(1e-4);\n const focus = options.dofFocusDistance.max(1e-4);\n // Aperture maps through the live focus plane (Spark host-helper parity), so\n // a near focus plane widens the angle and softens the whole scene.\n const halfApertureAngle = options.dofAperture.max(0).mul(0.5).div(focus).atan();\n // Spark's falloff (`/depth`). Unbounded near the camera; MAX_DOF_VARIANCE\n // below is the fill-rate guard, exactly as Spark's maxPixelRadius is.\n const focusBlur = depth.sub(focus).abs().div(depth);\n const apertureRadius = options.focal.x.mul(halfApertureAngle.tan());\n const cocRadiusPx = focusBlur.mul(apertureRadius);\n const cocVar = cocRadiusPx.mul(cocRadiusPx).min(float(MAX_DOF_VARIANCE));\n\n const a = aRaw.add(options.projectedLowPassVariance).add(cocVar);\n const d = dRaw.add(options.projectedLowPassVariance).add(cocVar);\n const b = bRaw;\n const isoMix = isotropicMix.element(workIndex);\n const detBlur = a.mul(d).sub(b.mul(b)).max(1e-9);\n // Classic LCC always compensates its 0.1 px² low-pass. Standard sources\n // retain their antialias-controlled 0.3 px² compatibility behavior.\n const detRaw = aRaw.mul(dRaw).sub(bRaw.mul(bRaw)).max(0);\n const detBase = aRaw\n .add(options.projectedLowPassVariance)\n .mul(dRaw.add(options.projectedLowPassVariance))\n .sub(bRaw.mul(bRaw))\n .max(1e-9);\n const mipFade = detRaw.div(detBlur).sqrt();\n const dofOnlyFade = detBase.div(detBlur).sqrt();\n const compensate = options.antialias.max(options.compensateProjectedLowPass);\n const fade = mix(dofOnlyFade, mipFade, compensate);\n // Screen-capped isotropic points must not inherit the mip-filter opacity\n // fade - it was derived from the pre-cap footprint and washes out dots.\n opacityCompensation.assign(mix(fade, float(1), isoMix));\n const mid = a.add(d).mul(0.5);\n const radius = vec2(a.sub(d).mul(0.5), b).length();\n let lambda1 = mid.add(radius);\n let lambda2 = mid.sub(radius).max(0);\n const equalized = equalizeProjectedEigenvalues(lambda1, lambda2, isoMix);\n lambda1 = equalized.lambda1;\n lambda2 = equalized.lambda2;\n const capped = capProjectedEigenvaluesToScreenRadius(\n lambda1,\n lambda2,\n isoMix,\n isotropicScreenRadius.element(workIndex),\n options.maxStdDev,\n );\n lambda1 = capped.lambda1;\n lambda2 = capped.lambda2;\n // A merged node (`alpha > 1`) grows the σ-cutoff `maxStdDev + 0.7·(remap−1)`\n // so one coarse splat covers the subtree it stands in for; the covariance is\n // untouched. A leaf keeps the base cutoff. `SplatMesh` does the same at\n // `applySplatMaterialGraph`'s `lodAlpha` branch.\n const remap = workColor.a.mul(4).sub(3).min(5);\n const stdDev = workColor.a\n .greaterThan(1)\n .select(options.maxStdDev.add(remap.sub(1).mul(0.7)), options.maxStdDev);\n adjustedStdDev.assign(stdDev);\n const eigenvector = vec2(b, lambda1.sub(a)).add(vec2(1e-6, 0)).normalize();\n // Screen-space minimum on each axis: a splat below the floor grows to it so\n // its Gaussian tiles with neighbours instead of leaving dark gaps between\n // sparse zoomed-out splats; already-large splats are untouched. Mirrors\n // `applySplatMaterialGraph`. `.max` after `.min(1024)` keeps the order valid.\n const minSplat = options.minSplatSizePx;\n const major = eigenvector.mul(lambda1.sqrt().mul(stdDev).min(1024).max(minSplat));\n const minor = vec2(eigenvector.y, eigenvector.x.negate()).mul(\n lambda2.sqrt().mul(stdDev).min(1024).max(minSplat),\n );\n const pixelOffset = major.mul(positionGeometry.x).add(minor.mul(positionGeometry.y));\n const ndcCenter = clipCenter.xy.div(clipCenter.w);\n const clipPosition = vec4(\n ndcCenter.add(pixelOffset.mul(2).div(options.viewport)),\n clipCenter.z.div(clipCenter.w),\n 1,\n );\n // Match SplatMesh's conservative center-frustum rejection. Without this,\n // a center behind the camera can produce a mirrored, screen-filling quad.\n // Modifier-hidden / zero-opacity entries (displayOpacity=0) share the\n // clipped destination so they generate no fragments while keeping a stable sort slot.\n const margin = clipCenter.w.mul(1.2);\n const inFrustum = clipCenter.z\n .greaterThan(margin.negate())\n .and(clipCenter.x.abs().lessThan(margin))\n .and(clipCenter.y.abs().lessThan(margin));\n return inFrustum.and(drawable).select(clipPosition, vec4(0, 0, 2, 1));\n })();\n material.fragmentNode = Fn(() => {\n const squaredDistance = quadPosition.dot(quadPosition);\n Discard(squaredDistance.greaterThan(1));\n // Spark's LOD falloff, mirroring `SplatMesh`'s display fragment.\n // `g = exp(-½·adjustedStdDev²·|q|²)` is the Gaussian at this fragment. A leaf\n // (`alpha ≤ 1`, and every non-`.rad` source) composites `g · alpha`. A merged\n // node uses the super-Gaussian plateau `1 − (1 − g)^a`, `a = exp((remap²−1)/e)`,\n // so a coarse splat fills its subtree's footprint without inflating Σ.\n const g = squaredDistance.mul(adjustedStdDev.mul(adjustedStdDev).mul(-0.5)).exp();\n const remap = workColor.a.mul(4).sub(3).min(5);\n const aExp = remap\n .mul(remap)\n .sub(1)\n .mul(1 / Math.E)\n .exp();\n const merged = g.oneMinus().pow(aExp).oneMinus();\n const opacity = workColor.a.greaterThan(1).select(merged, g.mul(workColor.a));\n const alpha = opacity.mul(opacityCompensation).mul(displayOpacity);\n const litCenter = tslTexture(options.relightMap, screenUV);\n const ox = options.relightSoftness.div(options.viewport.x.max(1));\n const oy = options.relightSoftness.div(options.viewport.y.max(1));\n const s0 = litCenter;\n const s1 = tslTexture(options.relightMap, screenUV.add(vec2(ox, 0)));\n const s2 = tslTexture(options.relightMap, screenUV.add(vec2(ox.negate(), 0)));\n const s3 = tslTexture(options.relightMap, screenUV.add(vec2(0, oy)));\n const s4 = tslTexture(options.relightMap, screenUV.add(vec2(0, oy.negate())));\n const wSum = s0.a.add(s1.a).add(s2.a).add(s3.a).add(s4.a).max(1e-4);\n const rgbSoft = s0.rgb\n .mul(s0.a)\n .add(s1.rgb.mul(s1.a))\n .add(s2.rgb.mul(s2.a))\n .add(s3.rgb.mul(s3.a))\n .add(s4.rgb.mul(s4.a))\n .div(wSum);\n const aSoft = wSum.mul(0.2);\n const litSoft = vec4(rgbSoft, aSoft);\n const lit = options.relightSoftness.greaterThan(0.5).select(litSoft, litCenter);\n const factor = mix(\n vec3(options.relightBackground),\n lit.rgb.mul(options.relightBrightness),\n lit.a,\n );\n const rgb = mix(workColor.rgb, workColor.rgb.mul(factor), options.relightBlend);\n return vec4(rgb.mul(alpha), alpha);\n })();\n material.transparent = true;\n material.depthTest = true;\n material.depthWrite = false;\n material.side = THREE.DoubleSide;\n material.blending = THREE.CustomBlending;\n material.blendSrc = THREE.OneFactor;\n material.blendDst = THREE.OneMinusSrcAlphaFactor;\n material.blendSrcAlpha = THREE.OneFactor;\n material.blendDstAlpha = THREE.OneMinusSrcAlphaFactor;\n material.toneMapped = false;\n return material;\n}\n","/**\n * Byte costs of {@link UnifiedSplatMesh}'s GPU work buffer.\n *\n * Kept off the core entry so hosts that never composite sources do not pay\n * for the accounting, and so the numbers cannot drift from the renderer\n * that allocates those buffers.\n */\n\n/** Bytes per splat in the largest unified work-buffer attribute (RGBA32F centers). */\nexport const WORK_BUFFER_CENTERS_BYTES_PER_SPLAT = 16;\n\n/**\n * Bytes per work-buffer slot across *all* of its attributes: centers, colors,\n * covarianceA and covarianceB at RGBA32F (16 each), plus the isotropic mix and\n * screen-radius scalars (4 each).\n */\nexport const WORK_BUFFER_BYTES_PER_SLOT = 16 * 4 + 4 * 2;\n\nfunction assertNonNegativeCapacity(capacity: number): void {\n if (!Number.isFinite(capacity) || capacity < 0) {\n throw new RangeError('Splat capacity must be a non-negative finite number.');\n }\n}\n\n/**\n * Byte size of the largest single storage-buffer bind VLAM allocates for a\n * given splat capacity (unified work-buffer centers: `capacity × 16`).\n *\n * Use with adapter/device limits to decide whether to raise `requiredLimits`\n * or lower the budget before constructing {@link UnifiedSplatMesh}.\n */\nexport function estimateLargestStorageBufferBytes(capacity: number): number {\n assertNonNegativeCapacity(capacity);\n return Math.floor(capacity) * WORK_BUFFER_CENTERS_BYTES_PER_SPLAT;\n}\n\n/**\n * Estimates what a {@link UnifiedSplatMesh}'s work buffer costs, in bytes,\n * once the scene has rendered a frame.\n *\n * This used to be double the figure below, and the doubling was not a detail.\n * Every `THREE.StorageBufferAttribute` is constructed around a JS typed array,\n * and three's WebGPU backend never releases it: the\n * `attribute.onUploadCallback()` call that would is missing from the backend's\n * buffer upload path. So a work buffer of N slots cost\n * N × {@link WORK_BUFFER_BYTES_PER_SLOT} on the GPU *and* the same again on the\n * JS heap, permanently, even though nothing ever reads the CPU copy back - the\n * gather pass writes these buffers entirely on the GPU.\n *\n * `WorkBuffer.releaseCpuMirrors` now drops those mirrors after the first\n * dispatch that uploads them (see `storage-attribute-mirror`), so steady state\n * is the GPU side alone. The *peak* is still double - the array has to exist\n * for the upload copy - which is what\n * {@link estimateUnifiedWorkBufferPeakBytes} reports.\n *\n * Either figure is invisible to `estimateSplatPoolBytes`, which prices the\n * splat *pool* and knows nothing about the unified renderer's scratch space. A\n * host sizing a scene for a memory-constrained device wants both.\n *\n * @param capacity - Work-buffer capacity in splats.\n * @returns Estimated steady-state bytes.\n * @throws {RangeError} if `capacity` is not a non-negative finite number.\n */\nexport function estimateUnifiedWorkBufferBytes(capacity: number): number {\n assertNonNegativeCapacity(capacity);\n return Math.floor(capacity) * WORK_BUFFER_BYTES_PER_SLOT;\n}\n\n/**\n * Peak bytes a {@link UnifiedSplatMesh}'s work buffer occupies: the window\n * between construction and the first dispatch, while the GPU buffer and the JS\n * array three copied it from are both alive.\n *\n * Ask this one when the question is \"will allocating this scene succeed\" - an\n * out-of-memory failure happens against the peak. Ask\n * {@link estimateUnifiedWorkBufferBytes} when the question is \"what will this\n * scene hold once it is up\".\n *\n * @param capacity - Work-buffer capacity in splats.\n * @returns Estimated bytes, GPU plus the not-yet-released CPU mirror.\n * @throws {RangeError} if `capacity` is not a non-negative finite number.\n */\nexport function estimateUnifiedWorkBufferPeakBytes(capacity: number): number {\n return estimateUnifiedWorkBufferBytes(capacity) * 2;\n}\n","import * as THREE from 'three/webgpu';\nimport { uniform } from 'three/tsl';\nimport { ComputeSorter, releaseRendererAttributes } from '../core/compute-sorter';\nimport { RadixSorter } from '../core/radix-sorter';\nimport { clampDepthOfFieldSettings, type DepthOfFieldSettings } from '../core/depth-of-field';\nimport {\n clampRelightingSettings,\n createPlaceholderRelightTexture,\n DEFAULT_RELIGHT_BACKGROUND,\n DEFAULT_RELIGHT_BRIGHTNESS,\n DEFAULT_RELIGHT_SOFTNESS,\n type RelightingSettings,\n type RelightingUniforms,\n} from '../core/relighting';\nimport { SplatMesh } from '../core/splat-mesh';\nimport type { SplatPickOptions, SplatPickResult, UnifiedSourceView } from '../core/splat-mesh';\nimport { isFillConstrainedSplatDevice } from '../core/splat-budget';\nimport { WebGpuSortScheduler } from '../core/sort-scheduler';\nimport { WorkBuffer, WorkBufferGather } from './work-buffer-gather';\nimport { createWorkBufferMaterial } from './work-buffer-material';\nimport type { FloatUniform, Vec2Uniform } from '../core/splat-mesh-material';\nimport { assertStorageBufferFitsDevice } from '../core/webgpu-limits';\nimport { estimateLargestStorageBufferBytes } from './unified-work-buffer';\nimport { resolveXrView } from '../core/xr-view';\nimport { StorageMirrorReleaser } from '../core/storage-attribute-mirror';\nimport type { SplatSorter } from '../core/sorter';\n\ninterface SourceRecord {\n source: SplatMesh;\n gather: WorkBufferGather;\n graphRevision: number;\n priority: number;\n opacity: number;\n visible: boolean;\n /** Whether modifier uniforms are host-invalidated instead of assumed live. */\n cacheModifiers: boolean;\n originalVisible: boolean;\n lastGather: {\n activeCount: number;\n contentRevision: number;\n offset: number;\n opacity: number;\n matrixWorld: THREE.Matrix4;\n /** Local camera position used by view-dependent SH evaluation. */\n localCameraPosition: THREE.Vector3;\n } | null;\n /** Per-prepare scratch: the source view snapshot for the current frame. */\n view: UnifiedSourceView | null;\n /** Per-prepare scratch: registration index for stable priority ordering. */\n registrationOrder: number;\n /** Per-prepare scratch: this frame's admitted work-buffer offset. */\n offset: number;\n}\n\n/** One admitted source's work-buffer ownership from a prepared frame. */\ninterface LayoutEntry {\n source: SplatMesh;\n offset: number;\n activeCount: number;\n}\n\n/** Registration settings for one source in a {@link UnifiedSplatMesh}. */\nexport interface UnifiedSplatSourceOptions {\n /** Whole-source display opacity applied after gather falloff. Defaults to `1`.\n * Does not change RAD LOD classification or splat shape. */\n opacity?: number;\n /** Whether this source contributes to the unified draw. Defaults to `true`. */\n visible?: boolean;\n /** Higher values survive a fixed work-buffer overflow first. Defaults to `0`. */\n priority?: number;\n /**\n * Cache a modifier-bearing source until {@link UnifiedSplatMesh.invalidateSource}\n * is called. Defaults to `false`, which safely re-gathers live modifier\n * uniforms every frame.\n *\n * Use this for static modifiers, or when the host has a natural dirty signal\n * for every uniform/channel mutation. Forgetting to invalidate leaves the\n * unified work-buffer output stale.\n */\n cacheModifiers?: boolean;\n}\n\n/**\n * Result of a successful {@link UnifiedSplatMesh.pick}: the frontmost hit\n * across every visible registered source, tagged with the source it landed on.\n */\nexport interface UnifiedSplatPickResult extends SplatPickResult {\n /** The registered source mesh whose splat produced the winning hit. */\n readonly source: SplatMesh;\n}\n\n/**\n * Construction settings shared by every source in a unified renderer.\n *\n * @experimental May change in a minor release.\n */\nexport interface UnifiedSplatMeshOptions {\n /** Composite source colors in display (sRGB) space. Defaults to `false`. */\n srgbOutput?: boolean;\n /**\n * Global depth-sort strategy. `'exact'` preserves every Float32 depth bit\n * through a stable radix sort of gathered world-space centers. `'counting'`\n * remains the lower-cost default for existing callers.\n *\n * @experimental Exact sorting trades two additional radix passes for better\n * ordering in large scenes with dense foliage or overlapping surfaces.\n */\n sortStrategy?: 'counting' | 'exact';\n}\n\n/**\n * Returns true when `renderer` can drive {@link UnifiedSplatMesh}.\n * Heterogeneous gather/sort/draw is WebGPU-only; WebGL2 hosts keep standalone\n * `SplatMesh` draws or static {@link MergedSplatMesh}.\n */\nexport function supportsUnifiedSplatMesh(renderer: object): boolean {\n return (\n (renderer as { backend?: { isWebGPUBackend?: boolean } }).backend?.isWebGPUBackend === true\n );\n}\n\n/**\n * One gather → global-sort → draw path for static and streamed splat sources.\n * Sources retain their own residency and update lifecycle; this mesh gathers\n * their current active pool ranges into one shared work buffer per frame.\n *\n * The unified mesh stays at identity (`matrixAutoUpdate` is off). Pose each\n * registered source mesh instead - sorter bounds and the draw material both\n * consume world-space centers written by gather.\n *\n * WebGPU only. Prefer {@link supportsUnifiedSplatMesh} before construction.\n *\n * @experimental May change in a minor release.\n */\nexport class UnifiedSplatMesh extends THREE.Mesh {\n private readonly workBuffer: WorkBuffer;\n private readonly workSourceIndex: THREE.StorageBufferAttribute;\n /** The sorted draw-order buffer; lives outside the geometry, freed on dispose. */\n private readonly orderAttribute: THREE.StorageInstancedBufferAttribute;\n /**\n * Frees the JS mirrors three keeps behind `workSourceIndex` and `order`.\n *\n * `workSourceIndex` is an identity ramp written once at construction; `order`\n * is written only by the sorter, on the GPU. Neither is ever read back, so\n * both mirrors are 4 B/slot of dead JS heap once uploaded.\n */\n private readonly mirrors: StorageMirrorReleaser;\n private readonly sorter: SplatSorter;\n private readonly renderer: THREE.WebGPURenderer;\n private readonly sources: SourceRecord[] = [];\n private previousLayout: LayoutEntry[] = [];\n /** Previous layout keyed by source; kept in step with {@link previousLayout}. */\n private readonly previousLayoutBySource = new Map<SplatMesh, LayoutEntry>();\n /** Per-prepare scratch lists, reused so a steady frame allocates nothing. */\n private readonly candidateScratch: SourceRecord[] = [];\n private readonly admittedScratch: SourceRecord[] = [];\n private readonly drawingBufferSize = new THREE.Vector2();\n /** Reused target dimensions for secondary views; avoids per-view garbage. */\n private readonly secondaryViewSize = new THREE.Vector2();\n /**\n * Camera gating for the global sorter, mirroring the standalone\n * `SplatMesh.requestSortIfNeeded` path: an unchanged work buffer under a\n * stationary camera skips the whole-buffer re-sort. Content changes\n * (regather, layout change) force the next sort through the scheduler.\n */\n private readonly sortScheduler: WebGpuSortScheduler;\n /** Model-view of the last accepted sort; starts unmatchable (zero scale). */\n private readonly lastSortedModelView = new THREE.Matrix4().makeScale(0, 0, 0);\n /**\n * Total admitted splats in the last prepared frame. `removeSource` filters\n * {@link previousLayout} directly, so a tail source's removal can leave the\n * remaining entries identical - only this total betrays the shrink, and a\n * shrunk draw range must re-sort (the old permutation indexes past it).\n */\n private previousAdmittedTotal = 0;\n private readonly bounds = new THREE.Sphere();\n private readonly focal: Vec2Uniform;\n private readonly viewport: Vec2Uniform;\n private readonly maxStdDev: FloatUniform;\n private readonly minSplatSizePx: FloatUniform;\n private readonly antialias: FloatUniform;\n private readonly projectedLowPassVariance: FloatUniform;\n private readonly compensateProjectedLowPass: FloatUniform;\n private readonly dofFocusDistance: FloatUniform;\n private readonly dofAperture: FloatUniform;\n private readonly relightPlaceholder: THREE.DataTexture;\n private relightMap: THREE.Texture;\n private readonly relightBlend: FloatUniform;\n private readonly relightBrightness: FloatUniform;\n private readonly relightBackground: FloatUniform;\n private readonly relightSoftness: FloatUniform;\n private readonly srgbOutput: boolean;\n private sourceMaxStdDev: number | null = null;\n private sourceAntialias: boolean | null = null;\n private sourceProjectedFilterProfile: 'default' | 'lcc' | null = null;\n private overflowedSourceCount = 0;\n private overflowedSplatCount = 0;\n private disposed = false;\n\n constructor(\n renderer: THREE.WebGPURenderer,\n capacity: number,\n options: UnifiedSplatMeshOptions = {},\n ) {\n if (!supportsUnifiedSplatMesh(renderer)) {\n throw new Error(\n 'UnifiedSplatMesh requires a WebGPU backend (renderer.backend.isWebGPUBackend). ' +\n 'On WebGL2 use standalone SplatMesh draws or static MergedSplatMesh.',\n );\n }\n // Fail before allocating StorageBufferAttributes that would trip a cryptic\n // CreateBindGroup validation error when the host left the 128 MiB default.\n assertStorageBufferFitsDevice(renderer, estimateLargestStorageBufferBytes(capacity), capacity);\n const workBuffer = new WorkBuffer(capacity);\n const focal = uniform(new THREE.Vector2());\n const viewport = uniform(new THREE.Vector2());\n const maxStdDev = uniform(3);\n const minSplatSizePx = uniform(0);\n const antialias = uniform(0);\n const projectedLowPassVariance = uniform(0.3);\n const compensateProjectedLowPass = uniform(0);\n const dofFocusDistance = uniform(10);\n const dofAperture = uniform(0);\n const relightPlaceholder = createPlaceholderRelightTexture();\n const relightBlend = uniform(0);\n const relightBrightness = uniform(DEFAULT_RELIGHT_BRIGHTNESS);\n const relightBackground = uniform(DEFAULT_RELIGHT_BACKGROUND);\n const relightSoftness = uniform(DEFAULT_RELIGHT_SOFTNESS);\n const order = new THREE.StorageInstancedBufferAttribute(new Float32Array(capacity), 1);\n const geometry = new THREE.InstancedBufferGeometry();\n geometry.setIndex([0, 1, 2, 0, 2, 3]);\n geometry.setAttribute(\n 'position',\n new THREE.BufferAttribute(new Float32Array([-1, -1, 0, 1, -1, 0, 1, 1, 0, -1, 1, 0]), 3),\n );\n geometry.instanceCount = 0;\n super(\n geometry,\n createWorkBufferMaterial({\n capacity,\n centers: workBuffer.centers,\n colors: workBuffer.colors,\n covarianceA: workBuffer.covarianceA,\n covarianceB: workBuffer.covarianceB,\n isotropicMix: workBuffer.isotropicMix,\n isotropicScreenRadius: workBuffer.isotropicScreenRadius,\n order,\n focal,\n viewport,\n maxStdDev,\n minSplatSizePx,\n antialias,\n projectedLowPassVariance,\n compensateProjectedLowPass,\n dofFocusDistance,\n dofAperture,\n relightMap: relightPlaceholder,\n relightBlend,\n relightBrightness,\n relightBackground,\n relightSoftness,\n }),\n );\n const indices = new Uint32Array(capacity);\n for (let i = 0; i < capacity; i++) indices[i] = i;\n this.workBuffer = workBuffer;\n this.focal = focal;\n this.viewport = viewport;\n this.maxStdDev = maxStdDev;\n this.minSplatSizePx = minSplatSizePx;\n this.antialias = antialias;\n this.projectedLowPassVariance = projectedLowPassVariance;\n this.compensateProjectedLowPass = compensateProjectedLowPass;\n this.dofFocusDistance = dofFocusDistance;\n this.dofAperture = dofAperture;\n this.relightPlaceholder = relightPlaceholder;\n this.relightMap = relightPlaceholder;\n this.relightBlend = relightBlend;\n this.relightBrightness = relightBrightness;\n this.relightBackground = relightBackground;\n this.relightSoftness = relightSoftness;\n this.srgbOutput = options.srgbOutput ?? false;\n this.sortScheduler = new WebGpuSortScheduler(undefined, isFillConstrainedSplatDevice());\n this.orderAttribute = order;\n this.workSourceIndex = new THREE.StorageBufferAttribute(indices, 1);\n this.mirrors = new StorageMirrorReleaser([this.workSourceIndex, this.orderAttribute]);\n this.renderer = renderer;\n const sortInputs = {\n renderer,\n capacity,\n centersBuffer: workBuffer.centers,\n splatIndexAttribute: order,\n sourceIndexAttribute: this.workSourceIndex,\n };\n this.sorter =\n options.sortStrategy === 'exact'\n ? new RadixSorter({ ...sortInputs, exactDepth: true })\n : new ComputeSorter(sortInputs);\n this.frustumCulled = false;\n this.matrixAutoUpdate = false;\n this.matrix.identity();\n this.matrixWorld.identity();\n }\n\n /** Fixed work-buffer splat capacity chosen at construction. */\n get capacity(): number {\n return this.workBuffer.capacity;\n }\n\n /** Registers a source. The caller keeps ownership and may still query it. */\n addSource(source: SplatMesh, options: UnifiedSplatSourceOptions = {}): void {\n this.assertNotDisposed('addSource');\n if (this.sources.some((record) => record.source === source)) return;\n const view = source.getUnifiedSourceView();\n const resolvedVisible = options.visible ?? source.visible;\n if (view.hasSourcePlacement) {\n // The gather pass resolves one matrix per source (the mesh's\n // `matrixWorld`); it knows nothing about a pool's *inner* per-source\n // placement, so a nested scene would draw every one of its sources at\n // its pool-local position. Reject rather than draw it wrong.\n throw new Error(\n 'UnifiedSplatMesh: a MergedSplatMesh is already a unified pool with its own global ' +\n 'sort; add its sources individually, or draw the mesh directly.',\n );\n }\n if (view.srgbOutput !== this.srgbOutput) {\n throw new Error(\"UnifiedSplatMesh: every source must use the renderer's srgbOutput setting.\");\n }\n if (this.sourceMaxStdDev === null) {\n this.sourceMaxStdDev = view.maxStdDev;\n this.maxStdDev.value = view.maxStdDev;\n this.minSplatSizePx.value = view.minSplatSizePx;\n } else if (this.sourceMaxStdDev !== view.maxStdDev) {\n throw new Error('UnifiedSplatMesh: every source must use the same maxStdDev setting.');\n } else if (this.minSplatSizePx.value !== view.minSplatSizePx) {\n throw new Error('UnifiedSplatMesh: every source must use the same minSplatSizePx setting.');\n }\n if (this.sourceAntialias === null) {\n this.sourceAntialias = view.antialias;\n this.antialias.value = view.antialias ? 1 : 0;\n } else if (this.sourceAntialias !== view.antialias) {\n throw new Error('UnifiedSplatMesh: every source must use the same antialias setting.');\n }\n if (this.sourceProjectedFilterProfile === null) {\n this.sourceProjectedFilterProfile = view.projectedFilterProfile;\n const lccProfile = view.projectedFilterProfile === 'lcc';\n this.projectedLowPassVariance.value = lccProfile ? 0.1 : 0.3;\n this.compensateProjectedLowPass.value = lccProfile ? 1 : 0;\n } else if (this.sourceProjectedFilterProfile !== view.projectedFilterProfile) {\n throw new Error('UnifiedSplatMesh: every source must use the same projected filter profile.');\n }\n this.sources.push({\n source,\n gather: this.createGather(view),\n graphRevision: view.graphRevision,\n priority: options.priority ?? 0,\n opacity: options.opacity ?? 1,\n visible: resolvedVisible,\n cacheModifiers: options.cacheModifiers ?? false,\n originalVisible: source.visible,\n lastGather: null,\n view: null,\n registrationOrder: 0,\n offset: 0,\n });\n source.visible = false;\n source.setUnifiedPickVisibility(resolvedVisible);\n }\n\n /**\n * Invalidates one registered source's cached gather output.\n *\n * Required after changing modifier uniforms or channels when that source was\n * registered with `cacheModifiers: true`. The following {@link update}\n * re-gathers the source and re-sorts because modifiers may move splats.\n * Returns `false` when the source is not registered.\n */\n invalidateSource(source: SplatMesh): boolean {\n this.assertNotDisposed('invalidateSource');\n const record = this.sources.find((candidate) => candidate.source === source);\n if (!record) return false;\n record.lastGather = null;\n return true;\n }\n\n /** Number of whole sources omitted in the most recent update due to capacity. */\n get droppedSourceCount(): number {\n return this.overflowedSourceCount;\n }\n\n /** Number of active splats omitted in the most recent update due to capacity. */\n get droppedSplatCount(): number {\n return this.overflowedSplatCount;\n }\n\n /**\n * Sets whole-source display opacity without rebuilding the gather pipeline.\n *\n * This scales the completed fragment after RAD merged-vs-leaf classification\n * and falloff. It must never alter LOD selection or splat shape; hosts such as\n * marker crossfades rely on that. Values `<= 0` skip drawing the source.\n */\n setSourceOpacity(source: SplatMesh, opacity: number): void {\n this.assertNotDisposed('setSourceOpacity');\n const record = this.sources.find((entry) => entry.source === source);\n if (!record) throw new Error('UnifiedSplatMesh: source is not registered.');\n record.opacity = opacity;\n }\n\n /** Shows or hides a source without changing its residency. */\n setSourceVisible(source: SplatMesh, visible: boolean): void {\n this.assertNotDisposed('setSourceVisible');\n const record = this.sources.find((entry) => entry.source === source);\n if (!record) throw new Error('UnifiedSplatMesh: source is not registered.');\n record.visible = visible;\n record.source.setUnifiedPickVisibility(visible);\n }\n\n /**\n * Core projected-2D depth of field on the unified draw pass (not gather).\n * Live uniforms - no gather-cache invalidation. Pass `aperture: 0` to disable.\n */\n setDepthOfField(settings: Partial<DepthOfFieldSettings>): void {\n this.assertNotDisposed('setDepthOfField');\n const next = clampDepthOfFieldSettings(settings, this.getDepthOfField());\n this.dofFocusDistance.value = next.focusDistance;\n this.dofAperture.value = next.aperture;\n }\n\n /** Current core DoF uniforms (`aperture === 0` means off). */\n getDepthOfField(): DepthOfFieldSettings {\n return {\n focusDistance: this.dofFocusDistance.value,\n aperture: this.dofAperture.value,\n };\n }\n\n /**\n * PlayCanvas-style proxy-mesh relighting on the unified draw pass (not gather).\n * Live blend/brightness/background; map identity change rebuilds the draw\n * material. Does not invalidate gather caches. Pass `null` to disable.\n */\n setRelighting(options: RelightingSettings | null): void {\n this.assertNotDisposed('setRelighting');\n if (options === null) {\n this.relightBlend.value = 0;\n if (this.relightMap !== this.relightPlaceholder) {\n this.relightMap = this.relightPlaceholder;\n this.rebuildDrawMaterial();\n }\n return;\n }\n const next = clampRelightingSettings(options, this.getRelighting());\n this.relightBlend.value = next.blend;\n this.relightBrightness.value = next.brightness;\n this.relightBackground.value = next.background;\n this.relightSoftness.value = next.softness;\n if (options.map !== this.relightMap) {\n this.relightMap = options.map;\n this.rebuildDrawMaterial();\n }\n }\n\n /** Current relight numeric uniforms (`blend === 0` means off). */\n getRelighting(): RelightingUniforms {\n return {\n blend: this.relightBlend.value,\n brightness: this.relightBrightness.value,\n background: this.relightBackground.value,\n softness: this.relightSoftness.value,\n };\n }\n\n /**\n * Asynchronously picks the frontmost visible splat under an NDC coordinate\n * across every registered source.\n *\n * Semantics: each source that is\n * currently visible in this unified renderer runs its own depth-tested GPU\n * pick pass; the hit nearest the camera wins and the result names its source\n * mesh. Hidden sources (`setSourceVisible(source, false)`) never hit. A hit\n * whose source was removed, hidden, or disposed while its readback was in\n * flight is dropped rather than misattributed. Resolves `null` for misses,\n * an empty source list, and picks pending across {@link dispose}.\n *\n * Uses the construction renderer. Concurrent calls are safe: each source\n * serializes its own picks and shared renderer state is restored\n * synchronously before any await.\n */\n async pick(\n ndc: THREE.Vector2,\n camera: THREE.Camera,\n options?: SplatPickOptions,\n ): Promise<UnifiedSplatPickResult | null> {\n if (this.disposed) return null;\n const candidates = this.sources\n .filter((record) => record.visible)\n .map((record) => record.source);\n if (candidates.length === 0) return null;\n const hits = await Promise.all(\n candidates.map(async (source) => {\n const hit = await source.pick(ndc, camera, this.renderer, options);\n return hit === null ? null : { source, point: hit.point, distance: hit.distance };\n }),\n );\n if (this.disposed) return null;\n let best: UnifiedSplatPickResult | null = null;\n for (const hit of hits) {\n if (hit === null) continue;\n // Registration may have changed while the readbacks were in flight;\n // never attribute a hit to a source no longer picked by this renderer.\n const record = this.sources.find((entry) => entry.source === hit.source);\n if (record === undefined || !record.visible) continue;\n if (best === null || hit.distance < best.distance) best = hit;\n }\n return best;\n }\n\n /** Unregisters a source and restores its standalone draw visibility. */\n removeSource(source: SplatMesh): boolean {\n this.assertNotDisposed('removeSource');\n const index = this.sources.findIndex((entry) => entry.source === source);\n if (index < 0) return false;\n const [record] = this.sources.splice(index, 1);\n if (record) {\n record.gather.dispose();\n record.source.visible = record.originalVisible;\n record.source.setUnifiedPickVisibility(null);\n record.lastGather = null;\n }\n this.previousLayout = this.previousLayout.filter((entry) => entry.source !== source);\n this.previousLayoutBySource.delete(source);\n if (this.sources.length === 0) {\n this.sourceMaxStdDev = null;\n this.sourceAntialias = null;\n this.sourceProjectedFilterProfile = null;\n this.previousLayout = [];\n this.previousLayoutBySource.clear();\n }\n return true;\n }\n\n /** Updates source LODs, gathers their active splats, then globally sorts them. */\n update(camera: THREE.PerspectiveCamera): void {\n if (this.disposed) return;\n this.prepare(camera);\n }\n\n /**\n * Re-sorts and draws this unified source list for a secondary camera.\n * Mirrors and portals use this instead of rendering the hidden source meshes\n * individually, so their reflection keeps one global transparent order.\n */\n renderView(\n camera: THREE.PerspectiveCamera,\n renderer: THREE.WebGPURenderer,\n target: THREE.RenderTarget | null = null,\n ): void {\n if (this.disposed) return;\n if (renderer !== this.renderer) {\n throw new Error('UnifiedSplatMesh: renderView must use its construction renderer.');\n }\n if (!supportsUnifiedSplatMesh(renderer)) {\n throw new Error(\n 'UnifiedSplatMesh requires a WebGPU backend (renderer.backend.isWebGPUBackend).',\n );\n }\n // A secondary view must sort for its own camera regardless of cadence, and\n // it leaves a foreign order in the shared buffer - invalidate afterwards so\n // the next primary update re-sorts even if its camera has not moved.\n const targetSize = target ? this.secondaryViewSize.set(target.width, target.height) : undefined;\n this.prepare(camera, targetSize, true);\n const previousTarget = renderer.getRenderTarget();\n try {\n renderer.setRenderTarget(target);\n renderer.render(this, camera);\n } finally {\n renderer.setRenderTarget(previousTarget);\n // `prepare` left the shared order buffer sorted for the secondary view.\n // Invalidate even when its draw throws, or a stationary primary view can\n // render through that foreign order after the host recovers.\n this.sortScheduler.invalidate();\n }\n }\n\n private prepare(\n camera: THREE.PerspectiveCamera,\n targetSize?: THREE.Vector2,\n forceSort = false,\n ): void {\n if (!supportsUnifiedSplatMesh(this.renderer)) {\n throw new Error(\n 'UnifiedSplatMesh requires a WebGPU backend (renderer.backend.isWebGPUBackend).',\n );\n }\n // Hosts must not pose this mesh; world centers already include source transforms.\n this.matrix.identity();\n this.matrixWorld.identity();\n // A secondary view (`renderView`) states its own size and camera; the\n // primary one follows the renderer, which in XR means a *per-eye* viewport\n // and a cyclopean head pose. Taking the drawing buffer there would be twice\n // the true width and halve every splat's horizontal size, and sorting from\n // the application camera would order the scene from wherever that camera\n // was left standing.\n const xrView = targetSize ? null : resolveXrView(camera, this.renderer);\n const projectionCamera: THREE.Camera = xrView?.eye ?? camera;\n // One global sort from the head serves both eyes (see `xr-view.ts`).\n const viewCamera: THREE.Camera = xrView?.head ?? camera;\n const viewport = this.drawingBufferSize;\n if (targetSize) viewport.copy(targetSize);\n else if (xrView) viewport.set(xrView.width, xrView.height);\n else this.renderer.getDrawingBufferSize(viewport);\n const projection = projectionCamera.projectionMatrix.elements;\n this.viewport.value.copy(viewport);\n const focalX = projection[0] ?? 0;\n const focalY = projection[5] ?? 0;\n this.focal.value.set((focalX * viewport.x) / 2, (focalY * viewport.y) / 2);\n const updated = this.candidateScratch;\n updated.length = 0;\n for (let i = 0; i < this.sources.length; i++) {\n const record = this.sources[i] as SourceRecord;\n // Children take the application camera: they resolve the XR view for\n // themselves, and a streamed source needs it for LOD scheduling.\n record.source.update(camera, this.renderer, { sort: false });\n record.view = record.source.getUnifiedSourceView();\n record.registrationOrder = i;\n updated.push(record);\n }\n // Overflow is deterministic and region-safe: preserve whole sources rather\n // than gathering a prefix of a streamed cut. Higher priority wins; matching\n // priorities retain registration order.\n updated.sort((a, b) => b.priority - a.priority || a.registrationOrder - b.registrationOrder);\n\n this.overflowedSourceCount = 0;\n this.overflowedSplatCount = 0;\n this.bounds.makeEmpty();\n\n const admitted = this.admittedScratch;\n admitted.length = 0;\n let offset = 0;\n for (const record of updated) {\n const view = record.view as UnifiedSourceView;\n if (record.graphRevision !== view.graphRevision) {\n // Build the replacement before disposing the old pipeline: a modifier\n // stack that throws at graph-build time must leave the record holding\n // a live gather (and an unchanged revision) so the next update can\n // retry after the caller fixes the stack.\n const rebuilt = this.createGather(view);\n record.gather.dispose();\n record.gather = rebuilt;\n record.graphRevision = view.graphRevision;\n record.lastGather = null;\n }\n if (!record.visible) {\n record.lastGather = null;\n continue;\n }\n if (view.activeCount === 0) {\n // An empty cut (e.g. a streamed source before its first chunk lands)\n // owns no work slice: skip the zero-splat gather dispatch and keep the\n // sorter bounds limited to sources that contribute splats.\n record.lastGather = null;\n continue;\n }\n if (offset + view.activeCount > this.workBuffer.capacity) {\n this.overflowedSourceCount++;\n this.overflowedSplatCount += view.activeCount;\n record.lastGather = null;\n continue;\n }\n record.offset = offset;\n admitted.push(record);\n this.bounds.union(view.worldBounds);\n offset += view.activeCount;\n }\n\n // Geometry under the draw order changed (layout, active cut, transform,\n // opacity, content revision). Camera-only SH / modifier color refreshes\n // re-gather the *same* world centers and must not force a sort.\n let geometryInvalidated = false;\n for (const record of admitted) {\n const view = record.view as UnifiedSourceView;\n const sliceOffset = record.offset;\n const previous = this.previousLayoutBySource.get(record.source);\n const ownedSameSlice =\n previous !== undefined &&\n previous.offset === sliceOffset &&\n previous.activeCount === view.activeCount;\n const last = record.lastGather;\n const geometryMatches =\n last !== null &&\n ownedSameSlice &&\n last.activeCount === view.activeCount &&\n last.contentRevision === view.contentRevision &&\n last.offset === sliceOffset &&\n last.opacity === record.opacity &&\n last.matrixWorld.equals(view.matrixWorld);\n const reusable =\n geometryMatches &&\n // SH depends on camera position, not orientation. A stationary camera\n // can reuse the resolved colors instead of re-running a full-source\n // gather every frame; camera motion invalidates only SH-bearing slices.\n (view.sh === null || last.localCameraPosition.equals(view.localCameraPosition.value)) &&\n (view.modifiers.length === 0 || record.cacheModifiers) &&\n // Belt-and-braces: a placed pool's splats move without its own\n // `matrixWorld` changing, so a cached gather would go stale silently.\n // `addSource` already rejects these.\n !view.hasSourcePlacement;\n if (!reusable) {\n record.gather.gather(\n this.renderer,\n view.activeCount,\n sliceOffset,\n view.matrixWorld,\n viewCamera.matrixWorldInverse,\n record.opacity,\n );\n if (!geometryMatches) geometryInvalidated = true;\n if (record.lastGather === null) {\n record.lastGather = {\n activeCount: view.activeCount,\n contentRevision: view.contentRevision,\n offset: sliceOffset,\n opacity: record.opacity,\n matrixWorld: view.matrixWorld.clone(),\n localCameraPosition: view.localCameraPosition.value.clone(),\n };\n } else {\n record.lastGather.activeCount = view.activeCount;\n record.lastGather.contentRevision = view.contentRevision;\n record.lastGather.offset = sliceOffset;\n record.lastGather.opacity = record.opacity;\n record.lastGather.matrixWorld.copy(view.matrixWorld);\n record.lastGather.localCameraPosition.copy(view.localCameraPosition.value);\n }\n }\n }\n\n // Commit ownership only after every gather for this preparation has run.\n let layoutChanged =\n admitted.length !== this.previousLayout.length || offset !== this.previousAdmittedTotal;\n this.previousAdmittedTotal = offset;\n if (!layoutChanged) {\n for (let i = 0; i < admitted.length; i++) {\n const record = admitted[i] as SourceRecord;\n const entry = this.previousLayout[i] as LayoutEntry;\n if (\n entry.source !== record.source ||\n entry.offset !== record.offset ||\n entry.activeCount !== (record.view as UnifiedSourceView).activeCount\n ) {\n layoutChanged = true;\n break;\n }\n }\n }\n if (layoutChanged) {\n this.previousLayout = admitted.map((record) => ({\n source: record.source,\n offset: record.offset,\n activeCount: (record.view as UnifiedSourceView).activeCount,\n }));\n this.previousLayoutBySource.clear();\n for (const entry of this.previousLayout) this.previousLayoutBySource.set(entry.source, entry);\n }\n\n // Work-buffer *geometry* moved underneath the current draw order: the next\n // sort must run whatever the camera did. Camera-driven SH/modifier color\n // refreshes re-gather without geometryInvalidated and deliberately skip\n // this. DoF is a live draw uniform and reaches neither branch.\n if (geometryInvalidated || layoutChanged) this.sortScheduler.invalidateContent();\n\n if (offset > 0) {\n const now = performance.now();\n const viewInverse = viewCamera.matrixWorldInverse;\n if (\n forceSort ||\n this.sortScheduler.shouldSubmit(viewInverse, this.lastSortedModelView, offset, now)\n ) {\n if (this.sorter.sort(viewInverse, offset, this.bounds)) {\n this.lastSortedModelView.copy(viewInverse);\n this.sortScheduler.markAccepted(now);\n }\n }\n }\n (this.geometry as THREE.InstancedBufferGeometry).instanceCount = offset;\n // Both are uploaded by the first frame's gather/sort dispatches, and neither\n // is ever read back - see the field comment. The work buffer's own mirrors\n // are released by `WorkBufferGather.gather`.\n if (!this.mirrors.settled) this.mirrors.release(this.renderer);\n }\n\n dispose(): void {\n if (this.disposed) return;\n this.disposed = true;\n for (const record of this.sources) {\n record.gather.dispose();\n record.source.visible = record.originalVisible;\n record.source.setUnifiedPickVisibility(null);\n record.lastGather = null;\n }\n this.sources.length = 0;\n this.previousLayout = [];\n this.previousLayoutBySource.clear();\n this.candidateScratch.length = 0;\n this.admittedScratch.length = 0;\n this.sourceMaxStdDev = null;\n this.sourceAntialias = null;\n this.sourceProjectedFilterProfile = null;\n this.sorter.dispose();\n this.geometry.dispose();\n (this.material as THREE.Material).dispose();\n this.relightPlaceholder.dispose();\n // The work buffer, its source-index list and the draw-order buffer are\n // storage attributes outside the geometry, so nothing above frees their\n // GPU buffers - ~72 B per work slot leaked per scene swap without this.\n releaseRendererAttributes(this.renderer, [\n this.workBuffer.centers,\n this.workBuffer.colors,\n this.workBuffer.covarianceA,\n this.workBuffer.covarianceB,\n this.workBuffer.isotropicMix,\n this.workBuffer.isotropicScreenRadius,\n this.workSourceIndex,\n this.orderAttribute,\n ]);\n }\n\n private assertNotDisposed(operation: string): void {\n if (this.disposed) {\n throw new Error(`UnifiedSplatMesh: ${operation} called after dispose.`);\n }\n }\n\n /** Rebuilds the EWA draw material when the relight map identity changes. */\n private rebuildDrawMaterial(): void {\n const previous = this.material as THREE.Material;\n this.material = createWorkBufferMaterial({\n capacity: this.workBuffer.capacity,\n centers: this.workBuffer.centers,\n colors: this.workBuffer.colors,\n covarianceA: this.workBuffer.covarianceA,\n covarianceB: this.workBuffer.covarianceB,\n isotropicMix: this.workBuffer.isotropicMix,\n isotropicScreenRadius: this.workBuffer.isotropicScreenRadius,\n order: this.orderAttribute,\n focal: this.focal,\n viewport: this.viewport,\n maxStdDev: this.maxStdDev,\n minSplatSizePx: this.minSplatSizePx,\n antialias: this.antialias,\n projectedLowPassVariance: this.projectedLowPassVariance,\n compensateProjectedLowPass: this.compensateProjectedLowPass,\n dofFocusDistance: this.dofFocusDistance,\n dofAperture: this.dofAperture,\n relightMap: this.relightMap,\n relightBlend: this.relightBlend,\n relightBrightness: this.relightBrightness,\n relightBackground: this.relightBackground,\n relightSoftness: this.relightSoftness,\n });\n previous.dispose();\n }\n\n private createGather(view: ReturnType<SplatMesh['getUnifiedSourceView']>): WorkBufferGather {\n const gather = this.buildGather(view);\n // Compile off the critical frame. WebGPU defers compute-pipeline compilation\n // to the first dispatch, and that dispatch happens inside `update` - a\n // measured 2.0s frame on a multi-mesh scene. Warming here is best-effort:\n // if it fails or has not finished by the first real gather, the only cost is\n // the stall this avoids, so nothing waits on it.\n void gather.warmUp(this.renderer).catch(() => undefined);\n return gather;\n }\n\n private buildGather(view: ReturnType<SplatMesh['getUnifiedSourceView']>): WorkBufferGather {\n return new WorkBufferGather({\n capacity: this.workBuffer.capacity,\n sourceCapacity: view.capacity,\n sourceIndex: view.sourceIndex,\n centersTexture: view.centersTexture,\n colorsTexture: view.colorsTexture,\n covarianceATexture: view.covarianceATexture,\n covarianceBTexture: view.covarianceBTexture,\n dataTextureWidth: view.dataTextureWidth,\n sh: view.sh,\n modifiers: view.modifiers,\n channels: view.channels,\n localCameraPosition: view.localCameraPosition,\n srgbOutput: this.srgbOutput,\n // Per source, not a compatibility field: the gather normalizes `.rad`\n // half-alpha here so one shared draw material serves mixed scenes.\n lodAlpha: view.lodAlpha,\n workBuffer: this.workBuffer,\n });\n }\n}\n"],"names":["_WorkBuffer","capacity","__publicField","slots","THREE","StorageMirrorReleaser","renderer","WorkBuffer","WorkBufferGather","options","uniform","sourceCapacity","sourceIndex","centersTexture","colorsTexture","covarianceATexture","covarianceBTexture","dataTextureWidth","sh","modifiers","channels","localCameraPosition","source","storage","output","outputColor","outputCovA","outputCovB","outputIsotropicMix","outputIsotropicScreenRadius","Fn","If","float","instanceIndex","poolIndex","int","texel","ivec2","localCenter","textureLoad","target","inputA","inputB","covarianceBase","mat3","vec3","baseColor","colorAfterSh","vec4","evaluateSplatSh","worldCenter","stack","foldSplatModifierStack","inverse","toCamera","axis","name","channel","center","visible","bool","gatheredCenter","encodedOriginal","asNode","encodedResolved","visibility","resolvedColor","colorSpaceToWorking","modifierOpacity","covariance","localCovariance","varianceScale","DEFAULT_ISOTROPIC_VARIANCE_SCALE","applyIsotropicCovarianceOverride","linear","worldCovarianceBase","worldCovariance","c0","c1","c2","activeCount","targetOffset","sourceMatrix","cameraViewMatrixOrOpacity","opacity","cameraViewMatrix","createWorkBufferMaterial","material","centers","order","colors","covarianceA","covarianceB","isotropicMix","isotropicScreenRadius","workColor","varying","quadPosition","positionGeometry","opacityCompensation","displayOpacity","adjustedStdDev","workIndex","centerSample","drawable","viewCenter","modelViewMatrix","clipCenter","cameraProjectionMatrix","covA","covB","invZ","invZ2","j1","j2","viewRotationT","u1","u2","aRaw","dRaw","bRaw","depth","focus","halfApertureAngle","focusBlur","apertureRadius","cocRadiusPx","cocVar","MAX_DOF_VARIANCE","a","d","b","isoMix","detBlur","detRaw","detBase","mipFade","dofOnlyFade","compensate","fade","mix","mid","radius","vec2","lambda1","lambda2","equalized","equalizeProjectedEigenvalues","capped","capProjectedEigenvaluesToScreenRadius","remap","stdDev","eigenvector","minSplat","major","minor","pixelOffset","ndcCenter","clipPosition","margin","squaredDistance","Discard","g","aExp","merged","alpha","litCenter","tslTexture","screenUV","ox","oy","s0","s1","s2","s3","s4","wSum","rgbSoft","aSoft","litSoft","lit","factor","rgb","WORK_BUFFER_CENTERS_BYTES_PER_SPLAT","WORK_BUFFER_BYTES_PER_SLOT","assertNonNegativeCapacity","estimateLargestStorageBufferBytes","estimateUnifiedWorkBufferBytes","estimateUnifiedWorkBufferPeakBytes","supportsUnifiedSplatMesh","_a","UnifiedSplatMesh","assertStorageBufferFitsDevice","workBuffer","focal","viewport","maxStdDev","minSplatSizePx","antialias","projectedLowPassVariance","compensateProjectedLowPass","dofFocusDistance","dofAperture","relightPlaceholder","createPlaceholderRelightTexture","relightBlend","relightBrightness","DEFAULT_RELIGHT_BRIGHTNESS","relightBackground","DEFAULT_RELIGHT_BACKGROUND","relightSoftness","DEFAULT_RELIGHT_SOFTNESS","geometry","indices","i","WebGpuSortScheduler","isFillConstrainedSplatDevice","sortInputs","RadixSorter","ComputeSorter","record","view","resolvedVisible","lccProfile","candidate","entry","settings","next","clampDepthOfFieldSettings","clampRelightingSettings","ndc","camera","candidates","hits","hit","best","index","targetSize","previousTarget","forceSort","xrView","resolveXrView","projectionCamera","viewCamera","projection","focalX","focalY","updated","admitted","offset","rebuilt","geometryInvalidated","sliceOffset","previous","ownedSameSlice","last","geometryMatches","layoutChanged","now","viewInverse","releaseRendererAttributes","operation","gather"],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ const a = 134217728;
2
+ function d(e) {
3
+ return {
4
+ maxStorageBufferBindingSize: e.limits.maxStorageBufferBindingSize,
5
+ maxBufferSize: e.limits.maxBufferSize,
6
+ // Spec minimum, so an adapter that somehow reports nothing still requests a
7
+ // legal value rather than `undefined` (which requestDevice rejects).
8
+ maxTextureDimension2D: e.limits.maxTextureDimension2D ?? 8192
9
+ };
10
+ }
11
+ function u(e = typeof navigator < "u" ? navigator : void 0) {
12
+ var t;
13
+ if (!e) return !0;
14
+ const i = (t = e.userAgentData) == null ? void 0 : t.platform;
15
+ return typeof i == "string" && i.length > 0 ? i !== "Windows" : /^Win/i.test(e.platform ?? "") ? !1 : !/Windows/i.test(e.userAgent ?? "");
16
+ }
17
+ function m(e = "high-performance", i) {
18
+ return u(i) ? { powerPreference: e } : {};
19
+ }
20
+ function f(e) {
21
+ var r, n;
22
+ const i = e.backend;
23
+ if ((i == null ? void 0 : i.isWebGPUBackend) !== !0) return 0;
24
+ const t = (n = (r = i.device) == null ? void 0 : r.limits) == null ? void 0 : n.maxStorageBufferBindingSize;
25
+ return typeof t == "number" && t > 0 ? t : 0;
26
+ }
27
+ function B(e, i, t) {
28
+ const r = f(e);
29
+ if (r <= 0 || i <= r) return;
30
+ const n = (i / (1024 * 1024)).toFixed(1), o = (r / (1024 * 1024)).toFixed(1), s = (134217728 / (1024 * 1024)).toFixed(0);
31
+ throw new Error(
32
+ `vlam: storage buffer needs ${n} MiB for ${t.toLocaleString("en-US")} splats, but device maxStorageBufferBindingSize is ${o} MiB (WebGPU default is ${s}). Pass recommendedWebGpuRequiredLimits(adapter) to WebGPURenderer, or lower the splat budget.`
33
+ );
34
+ }
35
+ export {
36
+ a as W,
37
+ B as a,
38
+ f as d,
39
+ d as r,
40
+ u as s,
41
+ m as w
42
+ };
43
+ //# sourceMappingURL=webgpu-limits-Y3CI_6_6.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webgpu-limits-Y3CI_6_6.js","sources":["../src/lib/core/webgpu-limits.ts"],"sourcesContent":["import type { WebGPURenderer } from 'three/webgpu';\n\n/**\n * WebGPU device limits hosts should request for VLAM beyond the spec defaults.\n *\n * The WebGPU default `maxStorageBufferBindingSize` is 128 MiB. Unified work\n * buffers and large streamed pools allocate storage attributes past that\n * (16 bytes per splat for an RGBA32F centers buffer). Desktop adapters often\n * advertise ~2 GiB, but only when those values are passed as `requiredLimits`\n * to `adapter.requestDevice()` / Three.js `WebGPURenderer`.\n *\n * `maxTextureDimension2D` is here for the same reason: the default is 8192,\n * which caps a pool at 8192 rows of {@link SPLAT_DATA_TEXTURE_WIDTH} (≈16.7 M\n * splats of *capacity*, so ≈11.2 M resident at the usual 1.5 slack). Desktop\n * adapters commonly advertise 16384 and double that ceiling for free.\n */\nexport interface WebGpuRequiredLimits {\n maxStorageBufferBindingSize: number;\n maxBufferSize: number;\n maxTextureDimension2D: number;\n // Assignable to the `Record<string, number>` Three.js WebGPURenderer takes\n // as `requiredLimits`, and open to further WebGPU limit names.\n [limit: string]: number;\n}\n\n/**\n * WebGPU's default `maxStorageBufferBindingSize` when the host does not raise\n * it via `requiredLimits` (128 MiB).\n */\nexport const WEBGPU_DEFAULT_MAX_STORAGE_BUFFER_BINDING_SIZE = 134_217_728;\n\n/**\n * Copies the adapter's advertised buffer maxima for use as\n * `WebGPURenderer({ requiredLimits })`.\n *\n * Does not call `requestDevice` - the host owns device creation.\n */\nexport function recommendedWebGpuRequiredLimits(adapter: {\n limits: {\n maxStorageBufferBindingSize: number;\n maxBufferSize: number;\n maxTextureDimension2D?: number;\n };\n}): WebGpuRequiredLimits {\n return {\n maxStorageBufferBindingSize: adapter.limits.maxStorageBufferBindingSize,\n maxBufferSize: adapter.limits.maxBufferSize,\n // Spec minimum, so an adapter that somehow reports nothing still requests a\n // legal value rather than `undefined` (which requestDevice rejects).\n maxTextureDimension2D: adapter.limits.maxTextureDimension2D ?? 8192,\n };\n}\n\n/** Mirrors WebGPU `GPUPowerPreference` without requiring `@webgpu/types`. */\nexport type WebGpuPowerPreference = 'high-performance' | 'low-power';\n\ntype NavigatorPlatformProbe = Pick<Navigator, 'platform' | 'userAgent'> & {\n userAgentData?: { platform?: string };\n};\n\n/**\n * Whether the host should pass `powerPreference` to `navigator.gpu.requestAdapter`\n * / Three.js `WebGPURenderer`.\n *\n * Chrome on Windows ignores the option and logs a console warning when it is\n * present ([crbug.com/369219127](https://crbug.com/369219127)). Prefer\n * {@link webGpuPowerPreferenceOptions} so Windows hosts omit it automatically.\n */\nexport function supportsWebGpuPowerPreference(\n nav: NavigatorPlatformProbe | undefined = typeof navigator !== 'undefined'\n ? navigator\n : undefined,\n): boolean {\n if (!nav) return true;\n\n const uaDataPlatform = nav.userAgentData?.platform;\n if (typeof uaDataPlatform === 'string' && uaDataPlatform.length > 0) {\n return uaDataPlatform !== 'Windows';\n }\n\n // `navigator.platform` is \"Win32\" on both 32- and 64-bit Windows browsers.\n if (/^Win/i.test(nav.platform ?? '')) return false;\n return !/Windows/i.test(nav.userAgent ?? '');\n}\n\n/**\n * Options to spread into `requestAdapter(...)` and `WebGPURenderer({...})`.\n * Empty on Windows so Chrome does not warn about an ignored `powerPreference`.\n */\nexport function webGpuPowerPreferenceOptions(\n powerPreference: WebGpuPowerPreference = 'high-performance',\n nav?: NavigatorPlatformProbe,\n): { powerPreference: WebGpuPowerPreference } | Record<string, never> {\n return supportsWebGpuPowerPreference(nav) ? { powerPreference } : {};\n}\n\n/**\n * The renderer's WebGPU `maxStorageBufferBindingSize`, or 0 when it cannot be\n * read (WebGL2 fallback, or device not yet available). Callers skip the check\n * when 0 rather than falsely reject.\n */\nexport function deviceMaxStorageBufferBindingSize(renderer: WebGPURenderer): number {\n const backend = renderer.backend as\n | {\n isWebGPUBackend?: boolean;\n device?: { limits?: { maxStorageBufferBindingSize?: number } };\n }\n | undefined;\n if (backend?.isWebGPUBackend !== true) return 0;\n const limit = backend.device?.limits?.maxStorageBufferBindingSize;\n return typeof limit === 'number' && limit > 0 ? limit : 0;\n}\n\n/**\n * Throws when a planned storage buffer would exceed the device bind limit.\n * No-ops when the limit cannot be read (WebGL2 / uninitialized backend).\n */\nexport function assertStorageBufferFitsDevice(\n renderer: WebGPURenderer,\n byteSize: number,\n capacity: number,\n): void {\n const limit = deviceMaxStorageBufferBindingSize(renderer);\n if (limit <= 0 || byteSize <= limit) return;\n\n const needMiB = (byteSize / (1024 * 1024)).toFixed(1);\n const limitMiB = (limit / (1024 * 1024)).toFixed(1);\n const defaultMiB = (WEBGPU_DEFAULT_MAX_STORAGE_BUFFER_BINDING_SIZE / (1024 * 1024)).toFixed(0);\n throw new Error(\n `vlam: storage buffer needs ${needMiB} MiB for ${capacity.toLocaleString('en-US')} splats, ` +\n `but device maxStorageBufferBindingSize is ${limitMiB} MiB ` +\n `(WebGPU default is ${defaultMiB}). Pass recommendedWebGpuRequiredLimits(adapter) to ` +\n `WebGPURenderer, or lower the splat budget.`,\n );\n}\n"],"names":["WEBGPU_DEFAULT_MAX_STORAGE_BUFFER_BINDING_SIZE","recommendedWebGpuRequiredLimits","adapter","supportsWebGpuPowerPreference","nav","_a","uaDataPlatform","webGpuPowerPreferenceOptions","powerPreference","deviceMaxStorageBufferBindingSize","renderer","_b","backend","limit","assertStorageBufferFitsDevice","byteSize","capacity","needMiB","limitMiB","defaultMiB"],"mappings":"AA6BO,MAAMA,IAAiD;AAQvD,SAASC,EAAgCC,GAMvB;AACvB,SAAO;AAAA,IACL,6BAA6BA,EAAQ,OAAO;AAAA,IAC5C,eAAeA,EAAQ,OAAO;AAAA;AAAA;AAAA,IAG9B,uBAAuBA,EAAQ,OAAO,yBAAyB;AAAA,EAAA;AAEnE;AAiBO,SAASC,EACdC,IAA0C,OAAO,YAAc,MAC3D,YACA,QACK;AA3CJ,MAAAC;AA4CL,MAAI,CAACD,EAAK,QAAO;AAEjB,QAAME,KAAiBD,IAAAD,EAAI,kBAAJ,gBAAAC,EAAmB;AAC1C,SAAI,OAAOC,KAAmB,YAAYA,EAAe,SAAS,IACzDA,MAAmB,YAIxB,QAAQ,KAAKF,EAAI,YAAY,EAAE,IAAU,KACtC,CAAC,WAAW,KAAKA,EAAI,aAAa,EAAE;AAC7C;AAMO,SAASG,EACdC,IAAyC,oBACzCJ,GACoE;AACpE,SAAOD,EAA8BC,CAAG,IAAI,EAAE,iBAAAI,EAAA,IAAoB,CAAA;AACpE;AAOO,SAASC,EAAkCC,GAAkC;AAxE7E,MAAAL,GAAAM;AAyEL,QAAMC,IAAUF,EAAS;AAMzB,OAAIE,KAAA,gBAAAA,EAAS,qBAAoB,GAAM,QAAO;AAC9C,QAAMC,KAAQF,KAAAN,IAAAO,EAAQ,WAAR,gBAAAP,EAAgB,WAAhB,gBAAAM,EAAwB;AACtC,SAAO,OAAOE,KAAU,YAAYA,IAAQ,IAAIA,IAAQ;AAC1D;AAMO,SAASC,EACdJ,GACAK,GACAC,GACM;AACN,QAAMH,IAAQJ,EAAkCC,CAAQ;AACxD,MAAIG,KAAS,KAAKE,KAAYF,EAAO;AAErC,QAAMI,KAAWF,KAAY,OAAO,OAAO,QAAQ,CAAC,GAC9CG,KAAYL,KAAS,OAAO,OAAO,QAAQ,CAAC,GAC5CM,KAAc,aAAkD,OAAO,OAAO,QAAQ,CAAC;AAC7F,QAAM,IAAI;AAAA,IACR,8BAA8BF,CAAO,YAAYD,EAAS,eAAe,OAAO,CAAC,sDAClCE,CAAQ,2BAC/BC,CAAU;AAAA,EAAA;AAGtC;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voluma/vlam",
3
- "version": "0.2.4",
3
+ "version": "0.3.2",
4
4
  "description": "VLAM! - A lightweight WebGPU Gaussian splat viewer for three.js, built for high performance, streaming LOD, and fully customizable rendering through an open shader pipeline.",
5
5
  "license": "MIT",
6
6
  "author": "Voluma",
@@ -37,14 +37,34 @@
37
37
  },
38
38
  "main": "./dist/index.js",
39
39
  "module": "./dist/index.js",
40
- "types": "./dist/index.d.ts",
40
+ "types": "./dist/core/index.d.ts",
41
41
  "exports": {
42
42
  ".": {
43
- "types": "./dist/index.d.ts",
43
+ "types": "./dist/core/index.d.ts",
44
44
  "import": "./dist/index.js"
45
45
  },
46
+ "./loaders": {
47
+ "types": "./dist/loaders/index.d.ts",
48
+ "import": "./dist/loaders.js"
49
+ },
50
+ "./static-lod": {
51
+ "types": "./dist/static-lod/index.d.ts",
52
+ "import": "./dist/static-lod.js"
53
+ },
54
+ "./streaming": {
55
+ "types": "./dist/streaming/index.d.ts",
56
+ "import": "./dist/streaming.js"
57
+ },
58
+ "./unified": {
59
+ "types": "./dist/unified/index.d.ts",
60
+ "import": "./dist/unified.js"
61
+ },
62
+ "./selection": {
63
+ "types": "./dist/selection/index.d.ts",
64
+ "import": "./dist/selection.js"
65
+ },
46
66
  "./effects": {
47
- "types": "./dist/effects.d.ts",
67
+ "types": "./dist/effects/index.d.ts",
48
68
  "import": "./dist/effects.js"
49
69
  },
50
70
  "./formats/ply": {
@@ -79,9 +99,9 @@
79
99
  },
80
100
  "scripts": {
81
101
  "dev": "npm run docs:api:site && vitepress dev site",
82
- "docs:api:site": "typedoc --options typedoc.site.json",
102
+ "docs:api:site": "typedoc --options site/typedoc.site.json",
83
103
  "build:site": "npm run typecheck && node scripts/build-site.mjs",
84
- "build:lib": "vite build --config vite.config.lib.ts && tsc -p tsconfig.lib.json",
104
+ "build:lib": "vite build --config scripts/vite.config.lib.ts && tsc -p tsconfig.lib.json",
85
105
  "build": "npm run build:site && npm run build:lib",
86
106
  "prepack": "npm run build:lib",
87
107
  "typecheck": "tsc --noEmit",
@@ -92,7 +112,7 @@
92
112
  "test:coverage": "vitest run --coverage",
93
113
  "docs:check": "node scripts/docs-check.mjs",
94
114
  "docs:samples": "tsc --noEmit -p docs/guide/samples/tsconfig.json && tsc --noEmit -p docs/examples/samples/tsconfig.json",
95
- "docs:api": "typedoc",
115
+ "docs:api": "typedoc --options site/typedoc.json",
96
116
  "size:check": "node scripts/check-bundle-size.mjs",
97
117
  "deploy": "npm run build:site && wrangler deploy"
98
118
  },
package/dist/index.d.ts DELETED
@@ -1,54 +0,0 @@
1
- /**
2
- * VLAM! | a WebGPU-first 3D Gaussian Splatting renderer for three.js.
3
- *
4
- * Public API surface. Everything else under `src/lib/` is internal and may
5
- * change without notice.
6
- *
7
- * Format-specific parsers live on optional subpaths:
8
- * - `@voluma/vlam/formats/ply` - 3DGS `.ply`, raw and compressed
9
- * - `@voluma/vlam/formats/sog` - PlayCanvas SOG, bundled and unbundled
10
- * - `@voluma/vlam/formats/rad` - Spark `.rad`/`.radc`
11
- * - `@voluma/vlam/formats/lcc` - XGRIDS LCC / `.lcc2` + collision helpers
12
- * - `@voluma/vlam/formats/spz` - Niantic `.spz`
13
- * - `@voluma/vlam/formats/splat` - antimatter15 `.splat`
14
- * - `@voluma/vlam/formats/ksplat` - mkkellogg `.ksplat`
15
- *
16
- * {@link loadScene} / {@link StreamedSplatMesh.load} still accept those formats
17
- * without importing a subpath; the library loads format code on demand.
18
- *
19
- * @module core
20
- */
21
- export { SplatMesh, MAX_SH_BANDS, DEFAULT_FOVEATION_TARGET_PX, DEFAULT_FOVEATION_DRAW_BUDGET, resolveSplatPerformanceProfile, type UnifiedSourceView, type SplatMeshOptions, type SplatUpdateOptions, type SplatSortStrategy, type SplatPerformanceProfile, type SplatRange, type SplatPickOptions, type SplatPickResult, type SplatNearestResult, type SplatRayResult, type SplatHeightResult, type SplatChannelType, type SplatChannelOptions, } from './splat-mesh';
22
- export { SplatPool, type SplatPoolOptions } from './splat-mesh-pool';
23
- export { SPLAT_DATA_TEXTURE_WIDTH, deviceMaxTextureSize, assertPoolRowsFitDevice, } from './splat-mesh-pool';
24
- export { MAX_DOF_RADIUS_PX, MAX_DOF_VARIANCE, apertureAngleFromSize, computeDofCocVariancePx2, computeDofOpacityFade, clampDepthOfFieldSettings, type DepthOfFieldSettings, } from './depth-of-field';
25
- export { DEFAULT_RELIGHT_BLEND, DEFAULT_RELIGHT_BRIGHTNESS, DEFAULT_RELIGHT_BACKGROUND, DEFAULT_RELIGHT_SOFTNESS, clampRelightingSettings, createPlaceholderRelightTexture, type RelightingSettings, type RelightingUniforms, } from './relighting';
26
- export { type SplatOrientation, type OrientableFormat, createYUpTransform, yUpTransformForFormat, } from './orientation';
27
- export { StreamedSplatMesh, type StreamedSplatMeshOptions, type InitialRevealState, type PersistentChannelOptions, type StreamedSplatPerformanceEvent, } from './streamed-splat-mesh';
28
- export { StaticLodSplatMesh, type StaticLodSplatMeshOptions, type StaticLodSplatMeshLoadOptions, type StaticLodLoadProgress, } from './static-lod-splat-mesh';
29
- export type { StaticLodBuildProgress } from './static-lod';
30
- export { SplatScene, type SplatSceneOptions, type AddSourceOptions } from './splat-scene';
31
- export { MAX_SOURCES } from './source-transform';
32
- export type { SplatShInputs, Vec3Uniform } from './splat-mesh-material';
33
- export { UnifiedSplatRenderer, supportsUnifiedSplatRenderer, type UnifiedSplatPickResult, type UnifiedSplatRendererOptions, type UnifiedSplatSourceOptions, } from './unified-splat-renderer';
34
- export { resolveSplatBudget, detectSplatDeviceProfile, classifySplatGpuClass, probeSplatGpuClass, isFillConstrainedSplatDevice, recommendedMaxPixelRatio, recommendedRadMaxStdDev, recommendedXrFramebufferScale, resolveXrSplatBudget, resolveCpuCacheBytes, suggestAdaptivePixelRatio, ADAPTIVE_PIXEL_RATIO_WARMUP_FRAMES, estimateSplatPoolBytes, type SplatBudgetOptions, type SplatCostClass, type SplatDeviceProfile, type SplatGpuClass, type SplatGpuAdapterInfo, type AdaptivePixelRatioInput, type AdaptivePixelRatioResult, type SplatPoolBytesOptions, } from './splat-budget';
35
- export { xrSessionInit } from './xr-view';
36
- export { BudgetGovernor, type BudgetGovernorOptions, type BudgetGovernedMember, } from './budget-governor';
37
- export { CameraBudgetGovernor, type CameraBudgetGovernorOptions, type CameraBudgetMember, type CameraBudgetMemberOptions, } from './camera-budget-governor';
38
- export { createSplatRenderer, type CreateSplatRendererOptions, type SplatRendererGpu, type SplatRendererGpuAdapter, type SplatRendererGpuDevice, } from './create-splat-renderer';
39
- export { recommendedWebGpuRequiredLimits, supportsWebGpuPowerPreference, webGpuPowerPreferenceOptions, estimateLargestStorageBufferBytes, estimateUnifiedWorkBufferBytes, estimateUnifiedWorkBufferPeakBytes, deviceMaxStorageBufferBindingSize, assertStorageBufferFitsDevice, WORK_BUFFER_CENTERS_BYTES_PER_SPLAT, WORK_BUFFER_BYTES_PER_SLOT, WEBGPU_DEFAULT_MAX_STORAGE_BUFFER_BINDING_SIZE, type WebGpuRequiredLimits, type WebGpuPowerPreference, } from './webgpu-limits';
40
- export { loadScene, loadSceneFile, type SplatLoadOptions, type SplatFileLoadOptions, } from './load-scene';
41
- export { ChunkLoader } from './chunk-loader';
42
- export { ChunkFetchScheduler, type ChunkFetchSchedulerOptions, type ChunkFetchClient, type ChunkFetchHandle, type ChunkFetchKind, } from './chunk-fetch-scheduler';
43
- export { ChunkCacheBudget, type ChunkCacheBudgetOptions, type ChunkCacheClient, type ChunkCacheHandle, } from './chunk-cache-budget';
44
- export { SplatLoadError, isAbortError, type SplatProgressCallback, type SplatFormat, type SplatSourceFormat, type StreamedSplatFormat, type SplatRequestOptions, type SplatLoadPhase, type SplatInputOptions, type ChunkFileFormat, } from './loading';
45
- export type { CollisionMeshTile } from './formats/lcc/collision-mesh';
46
- export type { SceneCollision, CollisionMeshDescriptor, EnvironmentTile } from './lod-source';
47
- export { httpDatasetSource, createLocalDataset } from './dataset-source';
48
- export type { SplatDatasetSource, LocalDataset } from './dataset-source';
49
- export type { SplatData, SplatShData, SplatPackedShData } from './splat-data';
50
- export { createSelectionVolume, selectInData, countInData, type SelectionVolume, type SelectionVolumeKind, type SelectionVolumeOptions, } from './selection-volume';
51
- export { partitionSplatData, type SplatPartition } from './splat-partition';
52
- export type { SplatModifier, SplatContext, SplatOutputs, ModifierStackTarget, } from './splat-modifier';
53
- export { ModifierSlots } from './splat-modifier';
54
- export { setVlamLogHandler, type VlamLogHandler, type VlamLogLevel } from './logging';
@@ -1 +0,0 @@
1
- {"version":3,"file":"ply-header-BwgExCn-.js","sources":["../src/lib/ply-header.ts"],"sourcesContent":["/**\n * Shared PLY header reader.\n *\n * PLY is a container, not a format: the same `.ply` extension carries the\n * uncompressed \"INRIA flavor\" 3DGS export (see `parse-splat-ply.ts`) and\n * SuperSplat/PlayCanvas's compressed flavor (`parse-compressed-ply.ts`).\n * Both need the same thing first - the element list, their property layouts,\n * and where each element's records start - so that lives here.\n */\n\n/** Byte size of each scalar type PLY can declare. */\nconst PROPERTY_SIZES: Record<string, number> = {\n float: 4,\n float32: 4,\n double: 8,\n float64: 8,\n int: 4,\n int32: 4,\n uint: 4,\n uint32: 4,\n short: 2,\n int16: 2,\n ushort: 2,\n uint16: 2,\n char: 1,\n int8: 1,\n uchar: 1,\n uint8: 1,\n};\n\n/** One scalar property of an element. */\nexport interface PlyProperty {\n readonly type: string;\n /** Byte offset within one record of the element. */\n readonly offset: number;\n}\n\n/**\n * One list property (`property list <countType> <itemType> <name>`), such as a\n * mesh's `face` indices. Only the types are recorded - the data is variable-size\n * and must be walked record by record (see `parse-mesh-ply.ts`).\n */\nexport interface PlyListProperty {\n /** Type of the per-record item count, e.g. `uchar`. */\n readonly countType: string;\n /** Type of each item, e.g. `int`. */\n readonly itemType: string;\n}\n\n/** One PLY element (`element <name> <count>`) and its record layout. */\nexport interface PlyElement {\n readonly name: string;\n readonly count: number;\n readonly properties: ReadonlyMap<string, PlyProperty>;\n /** Property names in declaration order. */\n readonly propertyNames: readonly string[];\n /** List properties by name; empty unless {@link hasListProperty}. */\n readonly listProperties: ReadonlyMap<string, PlyListProperty>;\n /**\n * Bytes per record - meaningless when {@link hasListProperty}, since a list\n * makes each record's size depend on its contents.\n */\n readonly stride: number;\n /**\n * Byte offset of this element's first record within the file, or -1 when a\n * preceding element has a variable-size record and no arithmetic can find it.\n */\n readonly offset: number;\n /** A list property makes this element's records variable-size. */\n readonly hasListProperty: boolean;\n}\n\n/** A parsed PLY header. */\nexport interface PlyHeader {\n readonly elements: readonly PlyElement[];\n /** The element of that name, or undefined. */\n element(name: string): PlyElement | undefined;\n}\n\n/**\n * Reads a binary little-endian PLY header and resolves every element's byte\n * offset by walking the elements in declaration order.\n *\n * Whether the declared records actually fit in the buffer is deliberately not\n * checked here - call {@link assertPlyElementFits} once the caller has\n * validated the properties it needs, so a wrong property type is reported as\n * such rather than as the truncation it happens to imply.\n *\n * @throws {Error} if the file is not a binary little-endian PLY, or declares a\n * property type this reader cannot size.\n */\nexport function parsePlyHeader(buffer: ArrayBuffer): PlyHeader {\n const headerBytes = new Uint8Array(buffer, 0, Math.min(buffer.byteLength, 64 * 1024));\n const headerText = new TextDecoder('ascii').decode(headerBytes);\n const endMatch = /end_header\\r?\\n/.exec(headerText);\n if (!/^ply\\r?\\n/.test(headerText)) {\n throw new Error('Not a PLY file (missing \"ply\" magic).');\n }\n if (!endMatch) {\n throw new Error(\n 'Unsupported PLY file: no \"end_header\" within the first 64 KiB - the header exceeds the ' +\n 'supported window, or the file is truncated.',\n );\n }\n if (!headerText.includes('format binary_little_endian 1.0')) {\n throw new Error('Unsupported PLY format; expected binary_little_endian 1.0.');\n }\n\n interface Building {\n name: string;\n count: number;\n properties: Map<string, PlyProperty>;\n propertyNames: string[];\n listProperties: Map<string, PlyListProperty>;\n stride: number;\n hasListProperty: boolean;\n }\n const building: Building[] = [];\n\n for (const line of headerText.slice(0, endMatch.index).split('\\n')) {\n const tokens = line.trim().split(/\\s+/);\n if (tokens[0] === 'element') {\n const name = tokens[1];\n const count = Number(tokens[2]);\n if (name === undefined) throw new Error(`Malformed PLY element: \"${line.trim()}\"`);\n if (!Number.isSafeInteger(count) || count < 0) {\n throw new Error(`Invalid PLY ${name} count: ${tokens[2]}.`);\n }\n building.push({\n name,\n count,\n properties: new Map(),\n propertyNames: [],\n listProperties: new Map(),\n stride: 0,\n hasListProperty: false,\n });\n } else if (tokens[0] === 'property') {\n const element = building[building.length - 1];\n if (!element) throw new Error(`PLY property declared before any element: \"${line.trim()}\"`);\n if (tokens[1] === 'list') {\n // A list makes this element's records variable-size. That is fine for\n // the element itself (a trailing `face` list is common) - it only\n // costs later elements their computable offset. The types are recorded\n // so a reader that does want the data can walk it record by record.\n const countType = tokens[2] ?? '';\n const itemType = tokens[3] ?? '';\n const listName = tokens[4];\n if (\n PROPERTY_SIZES[countType] === undefined ||\n PROPERTY_SIZES[itemType] === undefined ||\n listName === undefined\n ) {\n throw new Error(`Unsupported PLY property: \"${line.trim()}\"`);\n }\n if (element.properties.has(listName) || element.listProperties.has(listName)) {\n throw new Error(`Duplicate PLY property \"${listName}\" in element \"${element.name}\".`);\n }\n element.listProperties.set(listName, { countType, itemType });\n element.hasListProperty = true;\n continue;\n }\n const type = tokens[1] ?? '';\n const size = PROPERTY_SIZES[type];\n const name = tokens[2];\n if (size === undefined || name === undefined) {\n throw new Error(`Unsupported PLY property: \"${line.trim()}\"`);\n }\n if (element.properties.has(name) || element.listProperties.has(name)) {\n // A duplicate name would let the later offset overwrite the earlier\n // one while the stride counts both - every read after it lands on\n // the wrong bytes.\n throw new Error(`Duplicate PLY property \"${name}\" in element \"${element.name}\".`);\n }\n element.properties.set(name, { type, offset: element.stride });\n element.propertyNames.push(name);\n element.stride += size;\n }\n }\n\n // Records are laid out back to back in element order, starting right after\n // the header - so each element begins where the previous one ended, until a\n // variable-size element makes the rest unreachable by arithmetic.\n let offset = endMatch.index + endMatch[0].length;\n let resolved = true;\n const elements: PlyElement[] = [];\n for (const element of building) {\n elements.push({\n name: element.name,\n count: element.count,\n properties: element.properties,\n propertyNames: element.propertyNames,\n listProperties: element.listProperties,\n stride: element.stride,\n offset: resolved ? offset : -1,\n hasListProperty: element.hasListProperty,\n });\n if (element.hasListProperty) resolved = false;\n else offset += element.count * element.stride;\n }\n\n return {\n elements,\n element: (name: string) => elements.find((candidate) => candidate.name === name),\n };\n}\n\n/**\n * Asserts an element's records are actually present in a file of `totalBytes`.\n *\n * Takes a size rather than a buffer so it works for a streamed parse, which\n * only ever holds a window of the file.\n *\n * @throws {Error} if the element sits behind a variable-size one, or its\n * records run past the end of the file.\n */\nexport function assertPlyElementFits(element: PlyElement, totalBytes: number): void {\n if (element.offset < 0) {\n throw new Error(\n `Unsupported PLY layout: element \"${element.name}\" follows a list property, so its ` +\n 'position cannot be determined.',\n );\n }\n const end = element.offset + element.count * element.stride;\n if (end > totalBytes) {\n throw new Error(\n `Truncated PLY file: element \"${element.name}\" needs ${end} bytes but the file is ` +\n `${totalBytes}.`,\n );\n }\n}\n\n/**\n * Byte size of a PLY scalar type.\n *\n * @throws {Error} if the type is not one PLY can declare.\n */\nexport function plyTypeSize(type: string): number {\n const size = PROPERTY_SIZES[type];\n if (size === undefined) throw new Error(`Unsupported PLY property type: \"${type}\".`);\n return size;\n}\n\n/** The byte offset of `property` within `element`'s record. */\nexport function plyPropertyOffset(element: PlyElement, property: string): number {\n const found = element.properties.get(property);\n if (!found) {\n throw new Error(`PLY element \"${element.name}\" is missing property \"${property}\".`);\n }\n return found.offset;\n}\n\n/** Asserts every named property exists on `element` with the given type. */\nexport function assertPlyPropertyTypes(\n element: PlyElement,\n properties: readonly string[],\n types: readonly string[],\n what: string,\n): void {\n for (const name of properties) {\n const property = element.properties.get(name);\n if (!property) {\n throw new Error(`${what}: missing property \"${name}\".`);\n }\n if (!types.includes(property.type)) {\n throw new Error(\n `Unsupported PLY property type for \"${name}\": expected ${types.join(' or ')}, ` +\n `got ${property.type}.`,\n );\n }\n }\n}\n"],"names":["PROPERTY_SIZES","parsePlyHeader","buffer","headerBytes","headerText","endMatch","building","line","tokens","name","count","element","countType","itemType","listName","type","size","offset","resolved","elements","candidate","assertPlyElementFits","totalBytes","end","plyTypeSize","plyPropertyOffset","property","found","assertPlyPropertyTypes","properties","types","what"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"splat-budget-DXew_86c.js","sources":["../src/lib/splat-budget.ts"],"sourcesContent":["/**\n * Device profiling and the default active-splat budget.\n *\n * Streaming is splat-count budgeted rather than byte budgeted: every cost in\n * the renderer (pool textures, sort buffers) scales with the number of\n * resident splats - roughly 64 bytes of GPU memory per splat (three RGBA32F\n * data textures + one RGBA8, plus the sort index/order/bucket buffers), or\n * about 48 bytes when `poolFloatTextures: 'float16'` halves centers and\n * covarianceA; plus about 52 more bytes of CPU-side backing per splat of\n * pool capacity. Capping the active splat count therefore caps memory\n * directly, which is what matters on mobile GPUs.\n */\n\nimport type { StreamedSplatFormat } from './loading';\nimport { shCoefficientCount } from './sh-pack';\n\n/**\n * Desktop GPU capability class for budget / quality defaults.\n *\n * Mobile still keys off {@link SplatDeviceProfile.isMobile}; this splits\n * non-mobile machines that would otherwise share the ~8M workstation path.\n * Unset means \"unknown\" - keep the pre-existing desktop defaults (fail open).\n */\nexport type SplatGpuClass = 'discrete' | 'integrated' | 'fallback';\n\n/** Browser/device signals used for deterministic budget selection. */\nexport interface SplatDeviceProfile {\n /** Coarse memory estimate in GiB, when exposed by the browser. */\n deviceMemoryGb?: number;\n /** Whether the runtime is an iOS/iPadOS-class device. */\n isIOS?: boolean;\n /** Whether the runtime is a phone/tablet-class device (includes iOS). */\n isMobile?: boolean;\n /**\n * Whether the runtime is a standalone XR headset browser (Quest, Pico, …).\n * Headsets are mobile-class GPUs asked to fill a stereo framebuffer larger\n * than a 4K desktop, so they get their own, tighter defaults.\n */\n isHeadset?: boolean;\n /**\n * Whether this is a *budget* phone rather than a flagship one.\n *\n * `isMobile` cannot tell a Galaxy A51 from an iPhone 15 Pro, and the memory\n * signal cannot either - Chrome privacy-caps `deviceMemory` at 8 GiB, so the\n * flagship and the mid-ranger both land on the same ceiling despite an order\n * of magnitude between their GPUs. A coarse signal, and a *downward* one: a\n * reading of ≤4 is trustworthy because the cap only ever lowers it, while a\n * reading of 8 says nothing. {@link hasWebGpu} is the sharper signal where it\n * applies, since it names a difference in how the sort runs rather than\n * guessing capability from RAM.\n */\n isLowPower?: boolean;\n /**\n * Whether the runtime exposes WebGPU at all.\n *\n * `false` means the renderer will take the WebGL2 fallback, where the depth\n * sort runs on the CPU rather than as a GPU compute pass - a different cost\n * curve, not just a slower one. See {@link resolveSplatBudget}.\n *\n * Detected from the presence of `navigator.gpu`, which is a *browser support*\n * signal rather than an adapter one: a runtime that exposes the API but whose\n * `requestAdapter` fails (driver blocklist) still reports `true` here and will\n * fall back to WebGL2 anyway. A host that knows its renderer's real backend\n * can correct this by passing its own profile.\n */\n hasWebGpu?: boolean;\n /**\n * Desktop GPU class from {@link probeSplatGpuClass} / {@link classifySplatGpuClass}.\n * Omit when unknown so workstations without adapter info keep the 8M path.\n */\n gpuClass?: SplatGpuClass;\n}\n\n/** Adapter identifying fields used by {@link classifySplatGpuClass}. */\nexport interface SplatGpuAdapterInfo {\n vendor?: string;\n architecture?: string;\n device?: string;\n description?: string;\n isFallbackAdapter?: boolean;\n}\n\n/**\n * How expensive a format's splats are *per splat*, which is not the same across\n * formats and is the reason one mobile budget cannot serve them all.\n *\n * - `'sampled'` - coarser LOD levels are subsamples of the same surface, so a\n * splat costs about the same however deep the cut is. Streamed SOG.\n * - `'lcc'` - XGRIDS' levels are decimated *alternatives* whose splats merge\n * into wide flat discs (see {@link liftBudgetToFinestLevel}). Cost per splat\n * therefore *rises* as the budget falls, because a tighter budget serves\n * coarser levels and each of their splats covers more pixels. `.rad`, `.lcc`,\n * `.lcc2`.\n *\n * Measured on one iPhone 15 Pro, which is what the numbers below are worth:\n * `oldtimers-route` (`.rad`) ran 31-39 fps at 750k and 45-50 at 600k, while\n * `sandwijck` (streamed SOG) held 52-57 fps at 675k resident. Two streamed\n * scenes, near-identical splat counts, ~20 fps apart - splat count is a poor\n * proxy for cost, and the format is the only better signal available for free\n * before a single byte is fetched.\n */\nexport type SplatCostClass = 'lcc' | 'sampled';\n\n/** The mobile ceilings, per {@link SplatCostClass}. */\ninterface MobileClassBudget {\n /**\n * The most a mobile GPU is asked to keep resident, whatever its RAM says.\n *\n * `deviceMemory` is a *memory* signal, but a mobile GPU runs out of fill rate\n * and bandwidth long before memory: every splat is an alpha-blended quad, and\n * a busy view stacks hundreds of them per pixel. Chrome also privacy-caps\n * `deviceMemory` at 8 GiB, so every Android flagship reports 8 and would\n * otherwise land on the 8M desktop ceiling - measured at 5-24 fps on an\n * Adreno 750 (S24 Ultra), against 45 fps for the same scene on an iPhone at\n * 1M. Peer viewers land in the same band: SuperSplat budgets mobile at 1-2M,\n * Spark targets 1M on Android / 1.5M on iOS.\n */\n ceiling: number;\n /**\n * The budget for a mobile device that exposes no `deviceMemory` at all -\n * which is every iPhone, since mobile Safari does not implement it. This is\n * the branch the measurements above were taken on, so it is a separate number\n * rather than a fraction of {@link ceiling}.\n */\n withoutMemorySignal: number;\n}\n\nconst MOBILE_BUDGETS: Readonly<Record<SplatCostClass, MobileClassBudget>> = {\n // Both measured: 600k held 45-50 fps where 750k managed 31-39. The ceiling\n // matches the default because the evidence is about the class, not the RAM -\n // an Android flagship reporting 8 GiB has no more fill rate for wide discs\n // than an iPhone that reports nothing.\n lcc: { ceiling: 600_000, withoutMemorySignal: 600_000 },\n // Today's numbers, unchanged. `sandwijck` on iPhone had visible headroom\n // (52-57 fps while losing centre detail). Galaxy S24 Ultra SD at ~827k was still\n // streaming (164k holes); do not raise this ceiling from that capture.\n sampled: { ceiling: 1_000_000, withoutMemorySignal: 750_000 },\n};\n\n/**\n * Desktop *integrated* / unified GPU ceilings (laptop iGPU, Apple Silicon).\n *\n * These machines report as desktop in the UA and often claim 8 GiB\n * `deviceMemory`, so without a GPU class they inherit the workstation 8M path.\n * LCC stays at 1M to match the demo performance-mode ceiling; streamed SOG can\n * hold a bit more because sampled levels do not inflate per-splat fill the way\n * XGRIDS coarse discs do.\n */\nconst INTEGRATED_BUDGETS: Readonly<Record<SplatCostClass, number>> = {\n lcc: 1_000_000,\n sampled: 2_000_000,\n};\n\n/**\n * Classifies a WebGPU adapter from its identifying strings.\n *\n * Heuristics only - browsers may blank fields for privacy. Prefer\n * {@link probeSplatGpuClass} when both power-preference adapters are available.\n */\nexport function classifySplatGpuClass(info: SplatGpuAdapterInfo | undefined): SplatGpuClass {\n if (info?.isFallbackAdapter === true) return 'fallback';\n const vendor = (info?.vendor ?? '').toLowerCase();\n const architecture = (info?.architecture ?? '').toLowerCase();\n const device = (info?.device ?? '').toLowerCase();\n const description = (info?.description ?? '').toLowerCase();\n const blob = `${vendor} ${architecture} ${device} ${description}`;\n\n // Apple Silicon is always unified memory / shared GPU.\n if (vendor === 'apple' || blob.includes('apple')) return 'integrated';\n\n // Intel integrated families commonly exposed by Chrome's adapter.info.\n if (\n vendor === 'intel' ||\n architecture.startsWith('gen-') ||\n /iris|uhd|xe-lp|xe-hpg|alderlake|tigerlake|meteorlake|arrowlake/.test(blob)\n ) {\n // Discrete Intel Arc often carries \"arc\" or dg2-class strings; treat those\n // as discrete when clearly named, otherwise Intel desktop WebGPU is usually iGPU.\n if (/\\barc\\b|dg2|battlemage|xe2-hpg/.test(blob) && !/iris|uhd|xe-lp/.test(blob)) {\n return 'discrete';\n }\n return 'integrated';\n }\n\n // AMD APUs / explicit iGPU architecture tokens.\n if (vendor === 'amd' || vendor.includes('amd')) {\n if (/igpu|vega-igpu|gfx1\\d-igpu|radeon\\s*graphics(?!\\s*pro)/.test(blob)) {\n return 'integrated';\n }\n // Bare \"amd\" without a discrete cue still often means an APU on laptops;\n // Radeon RX / Radeon Pro name the discrete cards.\n if (/radeon\\s*rx|radeon\\s*pro|navi|rdna/.test(blob)) return 'discrete';\n }\n\n return 'discrete';\n}\n\n/** Minimal `navigator.gpu` surface for {@link probeSplatGpuClass}. */\nexport interface SplatGpuProbeEntry {\n requestAdapter(options?: {\n powerPreference?: 'low-power' | 'high-performance';\n }): Promise<SplatGpuProbeAdapter | null>;\n}\n\n/** Adapter handle with optional `.info` (WebGPU GPUAdapterInfo). */\nexport interface SplatGpuProbeAdapter {\n readonly info?: SplatGpuAdapterInfo;\n}\n\nfunction ambientProbeGpu(): SplatGpuProbeEntry | null {\n const nav = typeof navigator !== 'undefined' ? (navigator as { gpu?: unknown }) : undefined;\n const gpu = nav?.gpu;\n return gpu ? (gpu as SplatGpuProbeEntry) : null;\n}\n\nfunction adapterIdentityKey(info: SplatGpuAdapterInfo | undefined): string {\n return [\n info?.vendor ?? '',\n info?.architecture ?? '',\n info?.device ?? '',\n info?.description ?? '',\n ].join('\\0');\n}\n\n/**\n * Probes WebGPU adapters to choose a {@link SplatGpuClass}.\n *\n * Requests both `low-power` and `high-performance` adapters when possible. A\n * hybrid laptop that exposes two different devices is `discrete`; a single\n * shared / unified GPU (Apple Silicon, many iGPU-only machines) is classified\n * from {@link classifySplatGpuClass}. Returns `undefined` when WebGPU is absent\n * or the probe fails, so budget resolution keeps the legacy desktop path.\n */\nexport async function probeSplatGpuClass(\n gpu: SplatGpuProbeEntry | null | undefined = typeof navigator !== 'undefined'\n ? ambientProbeGpu()\n : null,\n): Promise<SplatGpuClass | undefined> {\n if (!gpu) return undefined;\n let low: SplatGpuProbeAdapter | null;\n let high: SplatGpuProbeAdapter | null;\n try {\n const adapters = await Promise.all([\n gpu.requestAdapter({ powerPreference: 'low-power' }),\n gpu.requestAdapter({ powerPreference: 'high-performance' }),\n ]);\n low = adapters[0] ?? null;\n high = adapters[1] ?? null;\n } catch {\n return undefined;\n }\n if (!low && !high) return undefined;\n\n const lowInfo = low?.info;\n const highInfo = high?.info;\n if (lowInfo?.isFallbackAdapter || highInfo?.isFallbackAdapter) return 'fallback';\n\n const lowKey = adapterIdentityKey(lowInfo);\n const highKey = adapterIdentityKey(highInfo);\n const both = low != null && high != null;\n // Distinct non-empty identities ⇒ hybrid dGPU available.\n if (both && lowKey.length > 3 && highKey.length > 3 && lowKey !== highKey) {\n return 'discrete';\n }\n\n const info = highInfo ?? lowInfo;\n return classifySplatGpuClass(info);\n}\n\n/**\n * Whether fill-rate / laptop-class defaults apply (mobile, or desktop\n * integrated / software fallback). Exported so hosts and the demo share one\n * predicate for performance-mode defaults and adaptive DPR.\n */\nexport function isFillConstrainedSplatDevice(\n profile: SplatDeviceProfile | undefined = detectSplatDeviceProfile(),\n): boolean {\n return (\n profile?.isMobile === true ||\n profile?.gpuClass === 'integrated' ||\n profile?.gpuClass === 'fallback'\n );\n}\n\n/**\n * The cost class of a streamed format. Unknown and absent formats read as\n * `'sampled'`, which is the pre-existing behaviour for every caller that does\n * not name one.\n */\nfunction splatCostClass(format?: StreamedSplatFormat): SplatCostClass {\n return format === 'rad' || format === 'lcc' || format === 'lcc2' ? 'lcc' : 'sampled';\n}\n\n/**\n * The headset ceiling, below the phone one.\n *\n * A headset is a phone-class GPU asked to fill a stereo framebuffer larger\n * than a 4K desktop (~1680×1760 *per eye* on a Quest 3), sharing the SoC with\n * a compositor that must hit 72 Hz. Every splat is drawn twice, so the\n * fill-rate wall that sets {@link MOBILE_BUDGET_MAX} arrives proportionally\n * sooner. This is a *cap*, not a floor: a genuinely small device still scales\n * below it on its memory signal.\n */\nconst HEADSET_BUDGET_MAX = 600_000;\n\n/**\n * The ceiling for a budget phone, below the flagship one.\n *\n * A mid-range mobile GPU is not a slightly slower flagship: a Mali-G72 MP3\n * (Galaxy A51) has a small fraction of an Adreno 750's fill rate, while both\n * report the same privacy-capped 8 GiB `deviceMemory` and the same `isMobile`.\n * Starting such a device at the flagship ceiling means it thrashes from the\n * first frame. This is a *cap*: a device whose memory signal is lower still\n * scales below it.\n */\nconst LOW_POWER_BUDGET_MAX = 500_000;\n\n/**\n * The ceiling for a mobile device with no WebGPU at all.\n *\n * This is the tightest tier, and the signal behind it is the most direct: with\n * no WebGPU there is no compute path, so the depth sort runs on the CPU in a\n * worker, and that cost scales with the splat count far worse than a GPU radix\n * sort does. A phone still on the WebGL2 fallback is also, in practice, old -\n * WebGPU has shipped on Android 12+ and iOS 18 - so the two costs arrive\n * together.\n *\n * Measured on a Galaxy S7 (2016, Mali-T880, WebGL2) at **750k**, which was the\n * low-power tier at the time: **5 fps, 194 ms frames**. That device is out of\n * scope as a target, but it is evidence that the memory-derived tier alone lands\n * far too high once the sort is on the CPU. The tiers have since been lowered\n * across the board, so the figure is the measurement's, not this constant's.\n *\n * Desktop is deliberately exempt: a desktop that falls back to WebGL2 is\n * sorting on a far stronger CPU, and capping it here would punish a\n * driver-blocklist fallback on capable hardware.\n */\nconst NO_WEBGPU_BUDGET_MAX = 400_000;\n\n/** Reads the available browser device signals without requiring a DOM runtime. */\nexport function detectSplatDeviceProfile(): SplatDeviceProfile | undefined {\n if (typeof navigator === 'undefined') return undefined;\n const nav = navigator as Navigator & { deviceMemory?: number };\n const isIOS =\n /iPhone|iPad|iPod/.test(nav.userAgent) ||\n (nav.platform === 'MacIntel' && nav.maxTouchPoints > 1);\n // UA first (it names the platform outright); the coarse-pointer probe then\n // catches mobile browsers that hide or reword their UA.\n const isMobile =\n isIOS ||\n /Android/i.test(nav.userAgent) ||\n (nav.maxTouchPoints > 0 &&\n typeof matchMedia === 'function' &&\n matchMedia('(pointer: coarse)').matches);\n // Best-effort *hint* only - see `resolveXrSplatBudget`, which is what\n // actually sizes an XR session. Most standalone headsets are Android under\n // the hood (Quest, Pico, Vive, Android XR), so `isMobile` already catches\n // them even when this misses; the named flag just lets first paint start at\n // a headset-appropriate size instead of resizing after the session opens.\n //\n // It cannot be relied on. visionOS Safari presents as *desktop* Safari, so\n // Apple Vision Pro matches neither this nor `isMobile` and would take the\n // desktop budget; and every new headset misses until its string is added.\n // That is the whole reason budgets key off presentation state instead.\n const isHeadset = /OculusBrowser|Quest|Pico(?: Neo)?[ /]|Wolvic|VRBrowser|Vive|XRBrowser/i.test(\n nav.userAgent,\n );\n // A budget phone, best-effort. `deviceMemory` is privacy-capped at 8 GiB but\n // *not* raised, so a device reporting ≤4 really does have ≤4 - that makes a\n // low reading trustworthy even though a high one says nothing. Deliberately\n // not `hardwareConcurrency`: it would get this exactly backwards, since the\n // Galaxy A51 this exists for is octa-core while an iPhone 15 Pro reports 6.\n const isLowPower =\n (isMobile || isHeadset) && nav.deviceMemory !== undefined && nav.deviceMemory <= 4;\n return {\n ...(nav.deviceMemory === undefined ? {} : { deviceMemoryGb: nav.deviceMemory }),\n isIOS,\n isMobile: isMobile || isHeadset,\n isHeadset,\n isLowPower,\n hasWebGpu: 'gpu' in nav,\n };\n}\n\n/** Scene-side signals for {@link resolveSplatBudget}. */\nexport interface SplatBudgetOptions {\n /**\n * The format about to be loaded, which selects the {@link SplatCostClass}\n * whose mobile tier applies. Omit when it is not yet known - the `'sampled'`\n * numbers are what every caller resolved before cost classes existed.\n */\n format?: StreamedSplatFormat;\n /**\n * A ceiling on the resolved default, for callers that want to tighten without\n * overriding what this function knows.\n *\n * `override` is absolute: it wins over the device tier, the cost class and\n * everything else, because a caller who names a number has said they know\n * better. That is the right contract and the wrong tool for \"the same as\n * usual, but no more than N\" - which is what a performance toggle or a host\n * default actually means. Pinning a number there has twice shipped as a bug:\n * a demo performance mode that *raised* the load on the weakest device tested,\n * and a host default that bypassed every device tier.\n *\n * Applied only when `override` is omitted, and only downward.\n */\n cap?: number;\n}\n\n/**\n * Chooses a default active-splat budget for the current device.\n *\n * The default is derived from the coarsest device-memory signal the platform\n * exposes; callers who know better (or want a hard cap for a mobile test on\n * desktop) should pass an explicit override.\n *\n * @param override - Explicit budget in splats; wins over device detection when\n * positive. Throws `RangeError` if it is not a positive finite number.\n * @param profile - Device signals to decide from; defaults to detecting them.\n * @param options - Scene signals. `format` selects the {@link SplatCostClass}\n * whose mobile tier applies; omitting it keeps the `'sampled'` numbers, which\n * are what every caller resolved before cost classes existed. `cap` is a\n * ceiling on the *resolved default*, for callers who want to tighten without\n * overriding - see {@link SplatBudgetOptions.cap}.\n * @returns A splat-count budget, clamped to a sensible range.\n */\nexport function resolveSplatBudget(\n override?: number,\n profile: SplatDeviceProfile | undefined = detectSplatDeviceProfile(),\n options: SplatBudgetOptions = {},\n): number {\n const cap = options.cap;\n if (cap !== undefined && (!Number.isFinite(cap) || cap <= 0)) {\n throw new RangeError('Splat budget cap must be a positive finite number.');\n }\n if (override !== undefined) {\n if (!Number.isFinite(override) || override <= 0) {\n throw new RangeError('Splat budget must be a positive finite number.');\n }\n // Deliberately *not* capped: `override` is the caller saying they know\n // better than every default, and `cap` is a ceiling on a default. Applying\n // it here would make it a second, quieter override.\n return Math.floor(override);\n }\n const capped = (budget: number): number =>\n cap === undefined ? budget : Math.min(budget, Math.floor(cap));\n\n // The device ceiling, tightest signal first. Expressed as a cap rather than\n // a flat headset default so a low-memory headset still scales *below* it\n // instead of being pinned up to it - and so the memory validation below\n // still runs for headsets.\n // Every applicable cap, then the tightest of them. A ternary chain would make\n // this order-dependent, and the order is not obvious - a device can be mobile\n // *and* low-power *and* on the WebGL2 fallback at once, and the answer must be\n // the smallest cap regardless of which test happens to run first.\n //\n // Only the mobile cap is per-format. The headset, low-power and no-WebGPU\n // tiers were each measured on their own hardware against a limit the format\n // cannot move - stereo fill, a mid-range GPU, a CPU-side sort - so they stay\n // absolute, and a raised `'sampled'` tier never reaches those devices.\n const mobile = MOBILE_BUDGETS[splatCostClass(options.format)];\n const costClass = splatCostClass(options.format);\n const caps: number[] = [];\n if (profile?.isHeadset) caps.push(HEADSET_BUDGET_MAX);\n if (profile?.isLowPower) caps.push(LOW_POWER_BUDGET_MAX);\n if (profile?.isMobile) caps.push(mobile.ceiling);\n // Mobile only: a desktop on the WebGL2 fallback sorts on a far stronger CPU.\n if (profile?.isMobile && profile.hasWebGpu === false) caps.push(NO_WEBGPU_BUDGET_MAX);\n // Desktop GPU class: laptop / unified / software must not inherit the 8M path.\n if (!profile?.isMobile && profile?.gpuClass === 'integrated') {\n caps.push(INTEGRATED_BUDGETS[costClass]);\n }\n if (!profile?.isMobile && profile?.gpuClass === 'fallback') {\n caps.push(NO_WEBGPU_BUDGET_MAX);\n }\n const ceiling = caps.length === 0 ? Infinity : Math.min(...caps);\n\n // `deviceMemory` (Chrome/Edge/Android) reports GiB, spec-capped at 8.\n const deviceMemoryGb = profile?.deviceMemoryGb;\n if (deviceMemoryGb !== undefined) {\n if (!Number.isFinite(deviceMemoryGb) || deviceMemoryGb <= 0) {\n throw new RangeError('Device memory must be a positive finite number.');\n }\n // 1M splats per reported GiB → Chrome's 8 GiB privacy cap yields the 8M\n // desktop default. Mobile/headset/integrated caps above still win when tighter.\n const scaled = clamp(Math.round(deviceMemoryGb * 1_000_000), 500_000, 8_000_000);\n return capped(Math.min(scaled, ceiling));\n }\n\n // No `deviceMemory`: mobile Safari (iOS) and privacy-restricted mobile\n // browsers land here, and must not fall through to the desktop default.\n if (profile?.isMobile) return capped(Math.min(mobile.withoutMemorySignal, ceiling));\n\n // Integrated / fallback desktop without a memory signal still needs the class\n // ceiling (Apple Silicon Safari often omits deviceMemory entirely).\n if (profile?.gpuClass === 'integrated') {\n return capped(Math.min(INTEGRATED_BUDGETS[costClass], ceiling));\n }\n if (profile?.gpuClass === 'fallback') {\n return capped(Math.min(NO_WEBGPU_BUDGET_MAX, ceiling));\n }\n\n // An absent profile can mean SSR, Node tooling, or a privacy-restricted\n // browser. Known non-iOS desktop without a memory signal still gets the\n // 8M default; everything else stays on the conservative portable floor.\n return capped(profile?.isIOS === false ? 8_000_000 : 1_000_000);\n}\n\n/**\n * The budget to run at **while an immersive session presents**, given whatever\n * budget the page is using outside one.\n *\n * Stereo is the cost, and stereo is a property of the *session*, not of the\n * device: every splat is drawn twice, into two eye viewports that together\n * exceed a 4K desktop, on a GPU that must hold 72–90 Hz or the compositor\n * reprojects. That is true of a standalone headset and equally true of a\n * desktop machine driving a tethered one - which is exactly the case device\n * sniffing cannot see, since such a machine is a desktop right up until the\n * moment it is not.\n *\n * Keying off presentation instead of identity is also the only approach that\n * survives new hardware. `detectSplatDeviceProfile`'s `isHeadset` is a\n * user-agent guess: it misses Apple Vision Pro outright (visionOS Safari\n * presents as desktop Safari, so a headset would otherwise take the multi-\n * million desktop budget) and misses every headset released after it is\n * written. This function needs to know none of that.\n *\n * Apply it on `sessionstart` and restore the original on `sessionend` - via\n * `BudgetGovernor.setBudget` when several meshes share a pool, or a streamed\n * mesh's own budget setter otherwise.\n *\n * @param pageBudget - The budget in use outside the session, typically from\n * {@link resolveSplatBudget}. Throws `RangeError` if not a positive finite\n * number.\n * @returns `pageBudget` lowered to the stereo ceiling; never raised - a device\n * already rendering below the ceiling stays where it is.\n */\nexport function resolveXrSplatBudget(pageBudget: number): number {\n if (!Number.isFinite(pageBudget) || pageBudget <= 0) {\n throw new RangeError('Splat budget must be a positive finite number.');\n }\n return Math.min(Math.floor(pageBudget), HEADSET_BUDGET_MAX);\n}\n\n/**\n * The largest finest-level scene taken whole rather than streamed. At ~64 B\n * of GPU pool per splat this is ~380 MB - affordable on a desktop, and far\n * cheaper than it looks next to the alternative, since a scene held whole\n * never swaps, never compacts and never re-fetches.\n */\nconst FINEST_LEVEL_BUDGET_MAX = 8_000_000;\n\n/**\n * Raises `budget` far enough to hold a scene's finest level in full, when\n * that level is small enough to be worth taking whole.\n *\n * The LOD budget assumes coarser levels are cheap *approximations* of the\n * same surface, so trading detail for memory is nearly free. XGRIDS' LCC\n * LCC breaks that assumption: its five levels are decimated alternatives\n * whose splats merge into wide flat discs (correct at distance, streaked up\n * close), so any budget shortfall costs visible quality - sub-chunking gives\n * the scheduler ~128k-splat granularity to thin cells with, but a thinned\n * fine level is still sparser than the capture intends. A capture whose\n * finest level fits under the desktop ceiling is better shown whole: Casino's\n * level 0 is 4.74M splats and sits under the 8M default.\n *\n * Mobile and fill-constrained desktops (integrated / fallback GPU class) are\n * exempt: their cap is a fill-rate limit, not a sizing accident.\n *\n * @param budget - The resolved device budget.\n * @param finestLevelSplats - Splats in the scene's finest level.\n * @returns `budget`, or the finest level's size when that is the better fit.\n */\nexport function liftBudgetToFinestLevel(\n budget: number,\n finestLevelSplats: number,\n profile: SplatDeviceProfile | undefined = detectSplatDeviceProfile(),\n): number {\n if (isFillConstrainedSplatDevice(profile)) return budget;\n if (!Number.isFinite(finestLevelSplats) || finestLevelSplats <= budget) return budget;\n if (finestLevelSplats > FINEST_LEVEL_BUDGET_MAX) return budget;\n return Math.floor(finestLevelSplats);\n}\n\n/** Options for {@link estimateSplatPoolBytes}. */\nexport interface SplatPoolBytesOptions {\n /**\n * Pool texture precision, matching `SplatMeshOptions.poolFloatTextures`.\n * `'float16'` halves centers and covarianceA. Default `'float32'`.\n */\n floatTextures?: 'float32' | 'float16';\n /** Per-splat SH bands the pool allocates for. Default `0`. */\n shBands?: 0 | 1 | 2 | 3;\n /**\n * Pool capacity as a multiple of `splats`. `StreamedSplatMesh` allocates\n * 1.5× (the default here) so per-run row alignment and the\n * append-before-remove window during LOD swaps have somewhere to go; pass\n * `1.4` to model `experimentalStagedSwaps: false`, or `1` for a static mesh.\n */\n capacityFactor?: number;\n /**\n * Whether to include the CPU-side backing arrays. Default `true` - they are\n * real host memory a device has to find, so the honest answer to \"can I\n * afford this ceiling\" includes them.\n */\n includeCpuBacking?: boolean;\n}\n\n/**\n * Estimates the memory a splat pool of `splats` costs.\n *\n * This exists to make a streamed mesh's `maxBudget` a computation rather than a\n * guess. A pool is allocated **once, from the ceiling** and never\n * grows, so several governed meshes cost the sum of their ceilings whatever the\n * shared budget is set to - a `BudgetGovernor` redistributes *sharpness* within\n * that envelope, it does not shrink it. Price the ceilings before choosing them:\n *\n * ```js\n * // 1 main + 4 markers, each able to reach 1.5M splats\n * const bytes = estimateSplatPoolBytes(4_000_000) + 4 * estimateSplatPoolBytes(1_500_000);\n * ```\n *\n * Per splat of *capacity* (`splats × capacityFactor`), counted from what the\n * constructor actually allocates:\n *\n * - **Pool textures** 52 B - centers RGBA32F 16, colors RGBA8 4,\n * covarianceA RGBA32F 16, covarianceB RGBA32F 16. `'float16'` drops centers\n * and covarianceA to 8 each (36 B); covarianceB stays float32 because it\n * packs integer IDs.\n * - **Packed SH** 16 B per `RGBA32UI` texture, `ceil(coefficients / 4)` of them\n * - so 16 / 32 / 64 B at 1 / 2 / 3 bands.\n * - **Sort storage** 16 B - the radix sorter's ping-pong key/value buffers.\n * - **CPU backing** 68 B - the float32/uint8 arrays kept for partial uploads\n * (always full precision, even under `'float16'`): centers 16, colors 4,\n * covarianceA 16, covarianceB 16, plus four u32-per-splat arrays - draw-order\n * indices, the active-list source index, the pool-slot map, and the picker's\n * pool-index template (allocated lazily, counted because a picked scene pays\n * it). Under `'float16'` add a further 16 B: the half-encoded texture images\n * are held alongside the float32 backing, not instead of it, so that\n * precision saves GPU bytes only. Included unless `includeCpuBacking` is\n * `false`.\n *\n * The module header's \"roughly 64 bytes per splat\" rounds the GPU side of this.\n *\n * @param splats - The ceiling in splats (e.g. a mesh's `maxBudget`).\n * @returns Estimated bytes. Indicative, not a device-memory guarantee: driver\n * texture padding, staging allocations and the decoded-chunk CPU cache\n * ({@link resolveCpuCacheBytes}) sit outside it.\n * @throws {RangeError} if `splats` is not a positive finite number, or\n * `capacityFactor` is not a finite number `>= 1`.\n */\nexport function estimateSplatPoolBytes(\n splats: number,\n options: SplatPoolBytesOptions = {},\n): number {\n if (!Number.isFinite(splats) || splats <= 0) {\n throw new RangeError('Splat count must be a positive finite number.');\n }\n const capacityFactor = options.capacityFactor ?? 1.5;\n if (!Number.isFinite(capacityFactor) || capacityFactor < 1) {\n throw new RangeError('Pool capacityFactor must be a finite number >= 1.');\n }\n const float16 = options.floatTextures === 'float16';\n const shTextures = Math.ceil(shCoefficientCount(options.shBands ?? 0) / 4);\n const shBytes = shTextures * 16;\n\n const centers = float16 ? 8 : 16;\n const covarianceA = float16 ? 8 : 16;\n const poolTextures = centers + 4 /* colors */ + covarianceA + 16 /* covarianceB */ + shBytes;\n const sortBuffers = 16; // radix ping-pong: keys A/B + values A/B, u32 each\n // Backing arrays are float32/uint32 regardless of the texture precision, and\n // under 'float16' the half-encoded texture images are held *in addition* to\n // them (the constructor keeps both), so that precision saves GPU bytes only.\n const halfImages = float16 ? 8 + 8 : 0; /* centersImage + covarianceAImage */\n const cpuBacking =\n options.includeCpuBacking === false\n ? 0\n : 16 /* centers */ +\n 4 /* colors */ +\n 16 /* covarianceA */ +\n 16 /* covarianceB */ +\n shBytes +\n 4 /* draw order (splatIndexes) */ +\n 4 /* active-list source index */ +\n 4 /* activeSlotByPoolIndex */ +\n 4 /* picker pool-index template */ +\n halfImages;\n\n const capacity = Math.ceil(splats * capacityFactor);\n return capacity * (poolTextures + sortBuffers + cpuBacking);\n}\n\n/**\n * Suggested ceiling for the renderer's pixel ratio on this device.\n *\n * Splat rendering is fragment-bound, so render resolution is one of the\n * largest costs on a high-DPI phone: a 2.6x display renders ~7x the pixels of\n * a 1x one, and every one of them blends the full depth-sorted splat stack.\n * The library cannot apply this itself (it draws into a renderer the app\n * owns), so it exports the policy - pass it to `renderer.setPixelRatio`:\n *\n * ```js\n * renderer.setPixelRatio(Math.min(window.devicePixelRatio, recommendedMaxPixelRatio()));\n * ```\n *\n * This is the *quality* ceiling. Viewers that offer a performance mode\n * (SuperSplat halves mobile resolution in its own) should go lower still when\n * it is on.\n */\nexport function recommendedMaxPixelRatio(\n profile: SplatDeviceProfile | undefined = detectSplatDeviceProfile(),\n): number {\n return isFillConstrainedSplatDevice(profile) ? 1.5 : 2;\n}\n\n/**\n * The Gaussian cutoff a `.rad` renders at, in standard deviations, or\n * `undefined` to accept the device-wide default.\n *\n * Spark hard-codes `sqrt(8)` (≈2.83σ) for every device, and matching it is what\n * makes a `.rad` look like Spark's render on a desktop. On a phone it is the\n * wrong trade: rendering is fill-bound on mobile, so a format override bypasses\n * the device policy that every other format accepts.\n *\n * Returning `undefined` on mobile lets `SplatMesh` apply its own 3σ cutoff and\n * undersized-splat floor; `.rad` must not escape that mobile policy.\n *\n * @returns √8 on discrete desktop; `undefined` on fill-constrained devices,\n * meaning \"no format override\".\n */\nexport function recommendedRadMaxStdDev(\n profile: SplatDeviceProfile | undefined = detectSplatDeviceProfile(),\n): number | undefined {\n return isFillConstrainedSplatDevice(profile) ? undefined : Math.SQRT2 * 2;\n}\n\n/**\n * Suggested WebXR framebuffer scale for this device. On three 0.185.x this is\n * a **WebGL XR** policy: apply it once before the session starts with\n * `renderer.xr.setFramebufferScaleFactor`. three's WebGPU XR path creates its\n * `XRGPUBinding` projection layer at native scale and does not consume that\n * setting, so WebGPU hosts should leave the native scale and use the presenting\n * splat budget plus fixed foveation for runtime headroom instead.\n *\n * Splat rendering is fill-bound, and a headset's default framebuffer is\n * already supersampled past its panels (~1680×1760 per eye on Quest 3):\n * 0.8 cuts fragment work ~36% for a barely visible softening. Non-headset\n * XR (desktop-tethered) keeps the native 1.0.\n */\nexport function recommendedXrFramebufferScale(\n profile: SplatDeviceProfile | undefined = detectSplatDeviceProfile(),\n): number {\n return profile?.isHeadset ? 0.8 : 1;\n}\n\n/**\n * Host-threaded samples to ignore before the EMA starts.\n *\n * First `renderer.compute` and first `copyTextureToTexture` each compile a\n * WebGPU pipeline (~200 ms). Seeding the EMA from those drops the ratio to\n * the floor, and a 60 Hz display's 16.7 ms vsync never beats the raise bar\n * (`targetFrameMs * 0.85` ≈ 15.3 ms), so the drop is permanent. Pass the\n * returned {@link AdaptivePixelRatioResult.warmupRemaining} back each frame.\n */\nexport const ADAPTIVE_PIXEL_RATIO_WARMUP_FRAMES = 5;\n\n/** Inputs for {@link suggestAdaptivePixelRatio}. */\nexport interface AdaptivePixelRatioInput {\n /** Latest wall-frame time in milliseconds. */\n frameMs: number;\n /** Currently applied pixel ratio. */\n current: number;\n /** Quality ceiling (typically {@link recommendedMaxPixelRatio}). */\n max: number;\n /** Floor; defaults to `1`. */\n min?: number;\n /** EMA of frame time from the previous call; omit on the first sample. */\n emaMs?: number;\n /**\n * Samples still ignored before the EMA starts. Omit or `0` to start\n * immediately. Thread the result's `warmupRemaining` when using\n * {@link ADAPTIVE_PIXEL_RATIO_WARMUP_FRAMES}.\n */\n warmupRemaining?: number;\n /**\n * Comfortable frame time (ms). Below this (with headroom) the helper may\n * step the ratio up. Default `18` (~55 fps).\n */\n targetFrameMs?: number;\n /**\n * Sustained frame time (ms) that triggers a step down. Default `22`\n * (~45 fps) so brief spikes do not thrash the canvas size.\n */\n pressureFrameMs?: number;\n}\n\n/** Result of {@link suggestAdaptivePixelRatio}. */\nexport interface AdaptivePixelRatioResult {\n /** Suggested pixel ratio after hysteresis (quarter steps). */\n pixelRatio: number;\n /** Updated EMA to pass back on the next call. Unset while warming up. */\n emaMs: number | undefined;\n /** Remaining warmup samples; pass back as `warmupRemaining`. */\n warmupRemaining: number;\n}\n\n/**\n * Suggests a pixel ratio under frame-time pressure.\n *\n * The library cannot call `renderer.setPixelRatio` (the host owns the\n * renderer); this is a pure policy helper. Pass the previous result's\n * `emaMs` and `warmupRemaining` each frame, and only re-size the canvas when\n * `pixelRatio` changes.\n *\n * Steps are quarter-units with asymmetric thresholds (pressure to lower,\n * comfortable headroom to raise) so the ratio does not oscillate. One-off\n * hitches (pipeline compiles, tab resume) that are several times the EMA do\n * not count as pressure.\n */\nexport function suggestAdaptivePixelRatio(\n input: AdaptivePixelRatioInput,\n): AdaptivePixelRatioResult {\n const min = input.min ?? 1;\n const max = Math.max(min, input.max);\n const targetFrameMs = input.targetFrameMs ?? 18;\n const pressureFrameMs = input.pressureFrameMs ?? 22;\n const frameMs = Number.isFinite(input.frameMs) ? Math.max(0, input.frameMs) : targetFrameMs;\n const pixelRatio = clamp(input.current, min, max);\n const warmupRaw = input.warmupRemaining;\n const warmupRemaining =\n warmupRaw !== undefined && Number.isFinite(warmupRaw) && warmupRaw > 0\n ? Math.floor(warmupRaw)\n : 0;\n if (warmupRemaining > 0) {\n return { pixelRatio, emaMs: input.emaMs, warmupRemaining: warmupRemaining - 1 };\n }\n if (\n input.emaMs !== undefined &&\n Number.isFinite(input.emaMs) &&\n frameMs > Math.max(input.emaMs * 4, pressureFrameMs * 4)\n ) {\n return { pixelRatio, emaMs: input.emaMs, warmupRemaining: 0 };\n }\n const alpha = 0.15;\n const emaMs = input.emaMs === undefined ? frameMs : input.emaMs * (1 - alpha) + frameMs * alpha;\n\n let next = pixelRatio;\n if (emaMs > pressureFrameMs && next > min) {\n next = Math.max(min, roundPixelRatio(next - 0.25));\n } else if (emaMs < targetFrameMs * 0.85 && next < max) {\n next = Math.min(max, roundPixelRatio(next + 0.25));\n }\n return { pixelRatio: clamp(next, min, max), emaMs, warmupRemaining: 0 };\n}\n\n/** Quarter-step pixel ratios (1, 1.25, 1.5, …) keep canvas resizes coarse. */\nfunction roundPixelRatio(value: number): number {\n return Math.round(value * 4) / 4;\n}\n\n/**\n * Chooses a decoded-chunk CPU cache cap from the same safe device profile.\n *\n * **The unknown-memory fallback must not read as \"tiny\".** iOS Safari does not\n * implement `navigator.deviceMemory` at all, so every iPhone lands on this\n * branch - and an earlier version resolved it to 1 GiB, i.e. the 32 MiB floor,\n * on hardware with 8 GiB. That is not a conservative guess, it is a wrong one,\n * and it is invisible on `.rad`, whose page-table cache has its own floor.\n *\n * On streamed SOG there is no such floor and the cost is immediate: measured on\n * an iPhone 15 Pro against `sandwijck`, the scheduler asked for 539,734 splats\n * and the mesh could only hold 466,499 of them across 3 chunk files (desktop:\n * 5), evicting continuously. The finest level is wanted *nearest the camera* and\n * lives in the largest files, so those are what fail to stay resident - the\n * middle of the view drops out and the scene renders as a donut.\n *\n * A device that declines to report its memory is far likelier to be a modern\n * phone withholding a fingerprinting signal than an actual 1 GiB device, so the\n * fallback assumes 4 GiB (128 MiB of cache). The floor stays for the genuinely\n * profile-less case - SSR and Node tooling, which never stream a scene anyway.\n */\nexport function resolveCpuCacheBytes(\n profile: SplatDeviceProfile | undefined = detectSplatDeviceProfile(),\n): number {\n const gb = profile === undefined ? 1 : (profile.deviceMemoryGb ?? 4);\n const mib = 1024 * 1024;\n return Math.min(256 * mib, Math.max(32 * mib, gb * 32 * mib));\n}\n\nfunction clamp(value: number, min: number, max: number): number {\n return Math.min(max, Math.max(min, value));\n}\n"],"names":["MOBILE_BUDGETS","INTEGRATED_BUDGETS","classifySplatGpuClass","info","vendor","architecture","device","description","blob","ambientProbeGpu","nav","gpu","adapterIdentityKey","probeSplatGpuClass","low","high","adapters","lowInfo","highInfo","lowKey","highKey","isFillConstrainedSplatDevice","profile","detectSplatDeviceProfile","splatCostClass","format","HEADSET_BUDGET_MAX","LOW_POWER_BUDGET_MAX","NO_WEBGPU_BUDGET_MAX","isIOS","isMobile","isHeadset","isLowPower","resolveSplatBudget","override","options","cap","capped","budget","mobile","costClass","caps","ceiling","deviceMemoryGb","scaled","clamp","resolveXrSplatBudget","pageBudget","FINEST_LEVEL_BUDGET_MAX","liftBudgetToFinestLevel","finestLevelSplats","estimateSplatPoolBytes","splats","capacityFactor","float16","shBytes","shCoefficientCount","centers","covarianceA","poolTextures","sortBuffers","halfImages","cpuBacking","recommendedMaxPixelRatio","recommendedRadMaxStdDev","recommendedXrFramebufferScale","ADAPTIVE_PIXEL_RATIO_WARMUP_FRAMES","suggestAdaptivePixelRatio","input","min","max","targetFrameMs","pressureFrameMs","frameMs","pixelRatio","warmupRaw","warmupRemaining","alpha","emaMs","next","roundPixelRatio","value","resolveCpuCacheBytes","gb","mib"],"mappings":"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"}