@babylonjs/lite 1.4.0 → 1.5.0

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 (122) hide show
  1. package/dist/index.js +381 -375
  2. package/dist/index.js.map +1 -1
  3. package/index.d.ts +757 -0
  4. package/lib/audio/analyzer.js +65 -0
  5. package/lib/audio/analyzer.js.map +1 -0
  6. package/lib/audio/audio-bus.js +38 -0
  7. package/lib/audio/audio-bus.js.map +1 -0
  8. package/lib/audio/audio-engine.js +188 -0
  9. package/lib/audio/audio-engine.js.map +1 -0
  10. package/lib/audio/audio-fetch.js +18 -0
  11. package/lib/audio/audio-fetch.js.map +1 -0
  12. package/lib/audio/audio-param.js +96 -0
  13. package/lib/audio/audio-param.js.map +1 -0
  14. package/lib/audio/audio-signal.js +46 -0
  15. package/lib/audio/audio-signal.js.map +1 -0
  16. package/lib/audio/bus.js +33 -0
  17. package/lib/audio/bus.js.map +1 -0
  18. package/lib/audio/host-types.js +2 -0
  19. package/lib/audio/host-types.js.map +1 -0
  20. package/lib/audio/index.js +12 -0
  21. package/lib/audio/index.js.map +1 -0
  22. package/lib/audio/sound-buffer.js +59 -0
  23. package/lib/audio/sound-buffer.js.map +1 -0
  24. package/lib/audio/sound-source.js +57 -0
  25. package/lib/audio/sound-source.js.map +1 -0
  26. package/lib/audio/sound-sub-graph.js +72 -0
  27. package/lib/audio/sound-sub-graph.js.map +1 -0
  28. package/lib/audio/spatial.js +466 -0
  29. package/lib/audio/spatial.js.map +1 -0
  30. package/lib/audio/static-sound.js +313 -0
  31. package/lib/audio/static-sound.js.map +1 -0
  32. package/lib/audio/stereo.js +40 -0
  33. package/lib/audio/stereo.js.map +1 -0
  34. package/lib/audio/streaming-sound.js +377 -0
  35. package/lib/audio/streaming-sound.js.map +1 -0
  36. package/lib/audio/unmute-ui.js +72 -0
  37. package/lib/audio/unmute-ui.js.map +1 -0
  38. package/lib/audio/visualizer.js +101 -0
  39. package/lib/audio/visualizer.js.map +1 -0
  40. package/lib/engine/engine.js +1 -1
  41. package/lib/index.js +11 -0
  42. package/lib/index.js.map +1 -1
  43. package/lib/light/types.js.map +1 -1
  44. package/lib/loader-gltf/animation-pointer-basecolor.js +25 -0
  45. package/lib/loader-gltf/animation-pointer-basecolor.js.map +1 -0
  46. package/lib/loader-gltf/animation-pointer-ext.js +244 -0
  47. package/lib/loader-gltf/animation-pointer-ext.js.map +1 -0
  48. package/lib/loader-gltf/animation-pointer-lights.js +46 -0
  49. package/lib/loader-gltf/animation-pointer-lights.js.map +1 -0
  50. package/lib/loader-gltf/animation-pointer.js +4 -1
  51. package/lib/loader-gltf/animation-pointer.js.map +1 -1
  52. package/lib/loader-gltf/gltf-animation.js +5 -3
  53. package/lib/loader-gltf/gltf-animation.js.map +1 -1
  54. package/lib/loader-gltf/gltf-color-normalize.js +10 -1
  55. package/lib/loader-gltf/gltf-color-normalize.js.map +1 -1
  56. package/lib/loader-gltf/gltf-feature-animation-pointer.js +67 -47
  57. package/lib/loader-gltf/gltf-feature-animation-pointer.js.map +1 -1
  58. package/lib/loader-gltf/gltf-feature-lights-punctual.js +51 -9
  59. package/lib/loader-gltf/gltf-feature-lights-punctual.js.map +1 -1
  60. package/lib/loader-gltf/gltf-feature-primitive.js +20 -0
  61. package/lib/loader-gltf/gltf-feature-primitive.js.map +1 -0
  62. package/lib/loader-gltf/gltf-feature-registry.js +25 -0
  63. package/lib/loader-gltf/gltf-feature-registry.js.map +1 -1
  64. package/lib/loader-gltf/gltf-feature-skeleton.js +18 -3
  65. package/lib/loader-gltf/gltf-feature-skeleton.js.map +1 -1
  66. package/lib/loader-gltf/gltf-interleave.js +3 -2
  67. package/lib/loader-gltf/gltf-interleave.js.map +1 -1
  68. package/lib/loader-gltf/gltf-light-pointer-state.js +18 -0
  69. package/lib/loader-gltf/gltf-light-pointer-state.js.map +1 -0
  70. package/lib/loader-gltf/gltf-parser.js +7 -1
  71. package/lib/loader-gltf/gltf-parser.js.map +1 -1
  72. package/lib/loader-gltf/gltf-pbr-builder-ext.js +1 -1
  73. package/lib/loader-gltf/gltf-pbr-builder-ext.js.map +1 -1
  74. package/lib/loader-gltf/gltf-pbr-builder.js +1 -1
  75. package/lib/loader-gltf/gltf-pbr-builder.js.map +1 -1
  76. package/lib/loader-gltf/gltf-sampler-denorm.js +20 -0
  77. package/lib/loader-gltf/gltf-sampler-denorm.js.map +1 -0
  78. package/lib/loader-gltf/gltf-sampler-desc.js +11 -2
  79. package/lib/loader-gltf/gltf-sampler-desc.js.map +1 -1
  80. package/lib/loader-gltf/gltf-uv-denorm.js +28 -0
  81. package/lib/loader-gltf/gltf-uv-denorm.js.map +1 -0
  82. package/lib/loader-gltf/load-gltf.js +15 -6
  83. package/lib/loader-gltf/load-gltf.js.map +1 -1
  84. package/lib/material/material-rebuild.js +4 -0
  85. package/lib/material/material-rebuild.js.map +1 -1
  86. package/lib/material/mesh-features.js +8 -1
  87. package/lib/material/mesh-features.js.map +1 -1
  88. package/lib/material/pbr/fragments/reflectance-fragment.js +1 -1
  89. package/lib/material/pbr/fragments/reflectance-fragment.js.map +1 -1
  90. package/lib/material/pbr/fragments/refraction-rtt-fragment.js +1 -1
  91. package/lib/material/pbr/fragments/refraction-rtt-fragment.js.map +1 -1
  92. package/lib/material/pbr/pbr-pipeline.js +7 -3
  93. package/lib/material/pbr/pbr-pipeline.js.map +1 -1
  94. package/lib/material/pbr/pbr-primitive-resolver.js +34 -0
  95. package/lib/material/pbr/pbr-primitive-resolver.js.map +1 -0
  96. package/lib/material/pbr/pbr-renderable.js +1 -1
  97. package/lib/material/pbr/pbr-renderable.js.map +1 -1
  98. package/lib/material/shader/shader-material.js +9 -5
  99. package/lib/material/shader/shader-material.js.map +1 -1
  100. package/lib/material/shader/shader-thin-instance.js +1 -1
  101. package/lib/material/shader/shader-thin-instance.js.map +1 -1
  102. package/lib/material/standard/standard-renderable.js +1 -1
  103. package/lib/material/standard/standard-renderable.js.map +1 -1
  104. package/lib/mesh/mesh-dispose.js +1 -0
  105. package/lib/mesh/mesh-dispose.js.map +1 -1
  106. package/lib/mesh/thin-instance-cull-binding.js +15 -6
  107. package/lib/mesh/thin-instance-cull-binding.js.map +1 -1
  108. package/lib/scene/scene-core.js +1 -0
  109. package/lib/scene/scene-core.js.map +1 -1
  110. package/lib/scene/scene-material-swap.js +2 -0
  111. package/lib/scene/scene-material-swap.js.map +1 -1
  112. package/lib/shadow/csm-shadow-task-hooks.js +67 -9
  113. package/lib/shadow/csm-shadow-task-hooks.js.map +1 -1
  114. package/lib/sprite/sprite-2d.js +4 -0
  115. package/lib/sprite/sprite-2d.js.map +1 -1
  116. package/lib/sprite/sprite-pipeline.js +25 -22
  117. package/lib/sprite/sprite-pipeline.js.map +1 -1
  118. package/lib/text/_gpu/text-pipeline.js +1 -1
  119. package/lib/text/_gpu/text-pipeline.js.map +1 -1
  120. package/lib/text/text-renderer.js +3 -1
  121. package/lib/text/text-renderer.js.map +1 -1
  122. package/package.json +3 -3
@@ -1 +1 @@
1
- {"version":3,"file":"standard-renderable.js","sources":["../../../../src/material/standard/standard-renderable.ts"],"sourcesContent":["/** Standard mesh renderable — builds Renderables from Mesh + StandardMaterial.\n *\n * `buildStandardMeshRenderables` does shared per-scene setup, then delegates\n * per-mesh work to `buildSingleStandardRenderable`. The same single-mesh\n * function is reused by the material-swap path. */\n\nimport { F32 } from \"../../engine/typed-arrays.js\";\nimport type { EngineContext } from \"../../engine/engine.js\";\nimport type { SceneContext } from \"../../scene/scene.js\";\nimport type { Mesh } from \"../../mesh/mesh.js\";\nimport type { Renderable, MeshGroupBuildResult } from \"../../render/renderable.js\";\nimport { collectStdBoundTextures } from \"./collect-std-bound-textures.js\";\nimport type { StandardMaterialProps } from \"./standard-material.js\";\nimport { _computeStandardMaterialFeatures, _standardShaderVariantKey } from \"./standard-material.js\";\nimport { acquireTexture, releaseTexture, clearSamplerCache } from \"../../resource/gpu-pool.js\";\nimport { createUniformBuffer } from \"../../resource/gpu-buffers.js\";\nimport { getOrCreateStandardBindings, getOrCreateStandardPipeline, createStandardMeshBindGroup, clearStandardPipelineCache, writeStdMaterialData } from \"./standard-pipeline.js\";\nimport { ESM_SHADOW_OUTPUT, NO_COLOR_OUTPUT, NEEDS_UV, NEEDS_UV2, HAS_OPACITY_TEXTURE, _getStdExts } from \"./standard-flags.js\";\nimport type { ShaderFragment } from \"../../shader/fragment-types.js\";\nimport type { ShadowGenerator } from \"../../shadow/shadow-generator.js\";\nimport { writeMeshLightSelection } from \"../../render/lights-ubo.js\";\nimport type { Material, MaterialRenderFeatures } from \"../material.js\";\nimport { _computeMeshFeatures, MSH_HAS_INSTANCE_COLOR, MSH_HAS_MORPH_TARGETS, MSH_HAS_THIN_INSTANCES, MSH_RECEIVE_SHADOWS } from \"../mesh-features.js\";\nimport { packMat4IntoF32 } from \"../../math/pack-mat4-into-f32.js\";\n\n/** Scratch buffer for material UBO writes (24 floats = 96 bytes). Reused across\n * every Standard renderable since binding updates are single-threaded per frame. */\nconst _stdMatScratch = new F32(24);\n\n/** Thin instance GPU sync callback type — loaded dynamically only when needed. */\ntype ThinInstanceSync = (\n engine: EngineContext,\n ti: any,\n pass: GPURenderPassEncoder | GPURenderBundleEncoder,\n slot: number,\n hasColor: boolean,\n drawBuffers?: import(\"../../mesh/thin-instance-gpu.js\").ThinInstanceDrawBuffers | null\n) => number;\n\n/** Fragment factories passed from the async group builder. */\nexport interface StdFragmentFactories {\n tiSync?: ThinInstanceSync;\n tiFragment?: (hasColor: boolean) => ShaderFragment;\n shadowFragment?: (shadowLights: import(\"./fragments/std-shadow-fragment.js\").ShadowLightSlot[]) => ShaderFragment;\n /** Present only when at least one mesh in the build has morph targets. */\n morphFragment?: () => ShaderFragment;\n /** Present only when the scene has at least one culling-enabled thin-instance mesh. */\n cull?: typeof import(\"../../mesh/thin-instance-cull-binding.js\");\n}\n\n/** Build Renderable(s) + a SceneUniformUpdater for a set of standard meshes.\n * The `rebuildSingle` closure is reused later (via `_rebuildSingle` on the group\n * builder) for material swaps + per-pass material overrides. */\nexport function buildStandardMeshRenderables(scene: SceneContext, meshes: Mesh[], factories: StdFragmentFactories): MeshGroupBuildResult {\n const engine = scene.surface.engine;\n const device = engine._device;\n const { tiSync, tiFragment, shadowFragment, cull, morphFragment } = factories;\n\n // Collect per-light shadow info.\n const shadowLights: { lightIndex: number; shadowType: \"esm\" | \"pcf\" | \"csm\"; gen: ShadowGenerator }[] = [];\n for (let i = 0; i < scene.lights.length; i++) {\n const sg = scene.lights[i]!.shadowGenerator;\n if (sg) {\n shadowLights.push({ lightIndex: i, shadowType: sg._shadowType, gen: sg });\n }\n }\n const hasSomeShadows = shadowLights.length > 0;\n\n // All receiving meshes in this build share the same shadow generators,\n // so keying the shadow BG by `bindings._shadowBGL` alone is correct.\n const shadowBGCache = new Map<GPUBindGroupLayout, GPUBindGroup>();\n // Closure used both for the initial per-mesh build below AND for later\n // material-swap / per-pass-override rebuilds (set on standardGroupBuilder._rebuildSingle).\n const rebuildSingle = (s: SceneContext, mesh: Mesh, materialOverride?: Material): Renderable => {\n const mat = (materialOverride ?? mesh.material) as StandardMaterialProps;\n const renderFeatures = (mat._renderFeatures ??= { features: _computeStandardMaterialFeatures(mat) }) as MaterialRenderFeatures;\n const isOverride = materialOverride != null;\n const features = renderFeatures.features;\n const shadowOutput = (features & (NO_COLOR_OUTPUT | ESM_SHADOW_OUTPUT)) !== 0;\n const receiveShadows = !shadowOutput && mesh.receiveShadows && hasSomeShadows;\n const meshFeatures = _computeMeshFeatures(mesh, receiveShadows);\n // Build per-feature fragment list (deduped via pipeline cache).\n const frags: ShaderFragment[] = [];\n // Keep morph first: composeStandardShader uses the first fragment's patch\n // to switch the placeholder morph bindings to storage buffers.\n if (meshFeatures & MSH_HAS_MORPH_TARGETS && morphFragment) {\n frags.push(morphFragment());\n }\n for (const ext of _getStdExts().values()) {\n if (features & ext._feature) {\n const f = ext._frag(features);\n if (f) {\n frags.push(f);\n }\n }\n }\n let shaderKey = \"\";\n if (meshFeatures & MSH_RECEIVE_SHADOWS && shadowFragment) {\n const slots = shadowLights.map((sl) => ({ lightIndex: sl.lightIndex, shadowType: sl.shadowType }));\n shaderKey = _standardShaderVariantKey(slots);\n frags.push(shadowFragment(slots));\n }\n if (meshFeatures & MSH_HAS_THIN_INSTANCES && tiFragment) {\n const hasColor = !!(meshFeatures & MSH_HAS_INSTANCE_COLOR);\n const tiFrag = tiFragment(hasColor);\n if (hasColor) {\n // Standard applies instance color to final color (BC), not to baseColor (AT) like PBR.\n const { _fragmentSlots: _fragmentSlots, ...rest } = tiFrag;\n frags.push({\n ...rest,\n _fragmentSlots: {\n BC: `color = vec4<f32>(color.rgb * input.vInstanceColor.rgb, color.a * input.vInstanceColor.a);`,\n },\n });\n } else {\n frags.push(tiFrag);\n }\n }\n const esmShadowDepthCode = (features & ESM_SHADOW_OUTPUT) !== 0 ? (mat as StandardMaterialProps & { readonly _esmShadowDepthCode: string })._esmShadowDepthCode : \"\";\n const bindings = getOrCreateStandardBindings(engine, features, meshFeatures, frags, shaderKey, esmShadowDepthCode, (mat as StandardMaterialProps).stencil ?? null);\n\n const meshShadowGens = receiveShadows ? shadowLights.map((sl) => sl.gen) : [];\n\n const meshUboData = new F32(bindings._composed._meshUboSpec._totalBytes / 4);\n const _packMeshWorld = engine._makePackMeshWorld?.(s as SceneContext) ?? packMat4IntoF32;\n _packMeshWorld(meshUboData, mesh.worldMatrix, 0, 0);\n writeMeshLightSelection(mesh, s.lights, meshUboData);\n const meshUBO = createUniformBuffer(engine, meshUboData);\n const textureLevel = (features & NEEDS_UV) !== 0 ? 1.0 : 0;\n const matData = new F32(24);\n writeStdMaterialData(matData, mat, textureLevel);\n const materialUBO = createUniformBuffer(engine, matData);\n const meshBindGroup = createStandardMeshBindGroup(engine, bindings, meshUBO, materialUBO, mat, mesh.morphTargets ?? null);\n\n // Shadow bind group (group 2) — shared across receiving meshes via shadowBGCache.\n let shadowBindGroup: GPUBindGroup | null = null;\n if (meshShadowGens.length > 0 && bindings._shadowBGL) {\n let cached = shadowBGCache.get(bindings._shadowBGL);\n if (!cached) {\n const entries: GPUBindGroupEntry[] = [];\n let b = 0;\n for (const sg of meshShadowGens) {\n entries.push({ binding: b++, resource: sg._depthTexture.createView() });\n entries.push({ binding: b++, resource: sg._depthSampler });\n entries.push({ binding: b++, resource: { buffer: sg._shadowUBO } });\n }\n cached = device.createBindGroup({ layout: bindings._shadowBGL, entries });\n shadowBGCache.set(bindings._shadowBGL, cached);\n }\n shadowBindGroup = cached;\n }\n\n const needsUV = (features & NEEDS_UV) !== 0;\n const needsUV2 = (features & NEEDS_UV2) !== 0;\n const hasThinInstances = (meshFeatures & MSH_HAS_THIN_INSTANCES) !== 0;\n const hasInstanceColor = (meshFeatures & MSH_HAS_INSTANCE_COLOR) !== 0;\n const isTransparent = !shadowOutput && ((features & HAS_OPACITY_TEXTURE) !== 0 || mat.alpha < 1);\n\n const boundTextures = collectStdBoundTextures(mat);\n for (const t of boundTextures) {\n acquireTexture(t);\n }\n s._meshDisposables.set(mesh, [\n () => {\n for (const t of boundTextures) {\n releaseTexture(t);\n }\n },\n ]);\n\n let _lastWorldVersion = mesh.worldMatrixVersion;\n let _lastLightsCount = s.lights.length;\n const sortCenter = [mesh.worldMatrix[12]!, mesh.worldMatrix[13]!, mesh.worldMatrix[14]!] as [number, number, number];\n const _baseUpdate = (): void => {\n const worldVersion = mesh.worldMatrixVersion;\n if (worldVersion !== _lastWorldVersion || s.lights.length !== _lastLightsCount) {\n sortCenter[0] = mesh.worldMatrix[12]!;\n sortCenter[1] = mesh.worldMatrix[13]!;\n sortCenter[2] = mesh.worldMatrix[14]!;\n _packMeshWorld(meshUboData, mesh.worldMatrix, 0, 0);\n writeMeshLightSelection(mesh, s.lights, meshUboData);\n device.queue.writeBuffer(meshUBO, 0, meshUboData as Float32Array<ArrayBuffer>);\n _lastWorldVersion = worldVersion;\n _lastLightsCount = s.lights.length;\n }\n const uboVersion = mat._uboVersion;\n if (uboVersion !== _lastUboVersion) {\n _lastUboVersion = uboVersion;\n _stdMatScratch.fill(0);\n writeStdMaterialData(_stdMatScratch, mat, textureLevel);\n device.queue.writeBuffer(materialUBO, 0, _stdMatScratch.buffer, 0, 96);\n }\n };\n // FO-version wrapper applied only when the engine has floating-origin\n // on. The wrapper lives in the dynamic-imported `floating-origin.ts`\n // module and is the sole owner of `_lastFoVersion` tracking. For\n // non-LWR engines `_wrapRenderableForFO` is undefined and `update`\n // is the bare closure — no FO bytes in the closure body.\n const _invalidate = (): void => {\n _lastWorldVersion = -1;\n };\n const update = engine._wrapRenderableForFO?.(_baseUpdate, s as SceneContext, _invalidate) ?? _baseUpdate;\n\n const draw = (pass: GPURenderPassEncoder | GPURenderBundleEncoder, cullBinding?: import(\"../../mesh/thin-instance-cull-binding.js\").TiCullBinding): number => {\n // For per-pass material overrides, skip the mesh.material === mat guard\n // because the override material is intentionally not the mesh's current one.\n if (!isOverride && mesh.material !== mat) {\n return 0;\n }\n const g = mesh._gpu;\n let slot = 0;\n const vb = g._vbLayout;\n pass.setVertexBuffer(slot++, g.positionBuffer, vb?._p?._offset);\n pass.setVertexBuffer(slot++, g.normalBuffer, vb?._n?._offset);\n if (needsUV) {\n pass.setVertexBuffer(slot++, g.uvBuffer, vb?._u?._offset);\n }\n if (needsUV2 && g.uv2Buffer) {\n pass.setVertexBuffer(slot++, g.uv2Buffer, vb?._u2?._offset);\n }\n\n const ti = hasThinInstances ? mesh.thinInstances : null;\n if (ti && tiSync) {\n slot = tiSync(engine, ti, pass, slot, hasInstanceColor, cullBinding?.cullDrawBufs);\n }\n\n pass.setIndexBuffer(g.indexBuffer, g.indexFormat);\n pass.setBindGroup(1, meshBindGroup);\n if (receiveShadows && shadowBindGroup) {\n pass.setBindGroup(2, shadowBindGroup);\n }\n if (cullBinding) {\n cullBinding.draw(pass, g.indexCount, ti!.count);\n } else if (ti && ti.count > 0) {\n pass.drawIndexed(g.indexCount, ti.count);\n } else {\n pass.drawIndexed(g.indexCount);\n }\n return 1;\n };\n\n const r: Renderable = {\n order: mesh.renderOrder ?? (isTransparent ? 200 : 100),\n isTransparent,\n mesh,\n bind(eng, sig) {\n const pipeline = getOrCreateStandardPipeline(eng as EngineContext, sig, bindings);\n // Opaque-only GPU culling (opt-in): tryBind gates on opt-in + transparency, returns the per-binding cull lifecycle.\n const cb = cull?.tryBind(r, s, mesh, engine, hasInstanceColor, isTransparent, update);\n return {\n renderable: r,\n pipeline,\n update: cb ? cb.update : update,\n draw: (pass) => draw(pass, cb),\n };\n },\n };\n r._worldCenter = sortCenter;\n let _lastUboVersion = mat._uboVersion;\n return r;\n };\n\n const renderables = meshes.map((m) => rebuildSingle(scene, m));\n\n scene._disposables.push(\n () => clearStandardPipelineCache(),\n () => clearSamplerCache(engine)\n );\n\n return { renderables, rebuildSingle };\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"standard-renderable.js","sources":["../../../../src/material/standard/standard-renderable.ts"],"sourcesContent":["/** Standard mesh renderable — builds Renderables from Mesh + StandardMaterial.\n *\n * `buildStandardMeshRenderables` does shared per-scene setup, then delegates\n * per-mesh work to `buildSingleStandardRenderable`. The same single-mesh\n * function is reused by the material-swap path. */\n\nimport { F32 } from \"../../engine/typed-arrays.js\";\nimport type { EngineContext } from \"../../engine/engine.js\";\nimport type { SceneContext } from \"../../scene/scene.js\";\nimport type { Mesh } from \"../../mesh/mesh.js\";\nimport type { Renderable, MeshGroupBuildResult } from \"../../render/renderable.js\";\nimport { collectStdBoundTextures } from \"./collect-std-bound-textures.js\";\nimport type { StandardMaterialProps } from \"./standard-material.js\";\nimport { _computeStandardMaterialFeatures, _standardShaderVariantKey } from \"./standard-material.js\";\nimport { acquireTexture, releaseTexture, clearSamplerCache } from \"../../resource/gpu-pool.js\";\nimport { createUniformBuffer } from \"../../resource/gpu-buffers.js\";\nimport { getOrCreateStandardBindings, getOrCreateStandardPipeline, createStandardMeshBindGroup, clearStandardPipelineCache, writeStdMaterialData } from \"./standard-pipeline.js\";\nimport { ESM_SHADOW_OUTPUT, NO_COLOR_OUTPUT, NEEDS_UV, NEEDS_UV2, HAS_OPACITY_TEXTURE, _getStdExts } from \"./standard-flags.js\";\nimport type { ShaderFragment } from \"../../shader/fragment-types.js\";\nimport type { ShadowGenerator } from \"../../shadow/shadow-generator.js\";\nimport { writeMeshLightSelection } from \"../../render/lights-ubo.js\";\nimport type { Material, MaterialRenderFeatures } from \"../material.js\";\nimport { _computeMeshFeatures, MSH_HAS_INSTANCE_COLOR, MSH_HAS_MORPH_TARGETS, MSH_HAS_THIN_INSTANCES, MSH_RECEIVE_SHADOWS } from \"../mesh-features.js\";\nimport { packMat4IntoF32 } from \"../../math/pack-mat4-into-f32.js\";\n\n/** Scratch buffer for material UBO writes (24 floats = 96 bytes). Reused across\n * every Standard renderable since binding updates are single-threaded per frame. */\nconst _stdMatScratch = new F32(24);\n\n/** Thin instance GPU sync callback type — loaded dynamically only when needed. */\ntype ThinInstanceSync = (\n engine: EngineContext,\n ti: any,\n pass: GPURenderPassEncoder | GPURenderBundleEncoder,\n slot: number,\n hasColor: boolean,\n drawBuffers?: import(\"../../mesh/thin-instance-gpu.js\").ThinInstanceDrawBuffers | null\n) => number;\n\n/** Fragment factories passed from the async group builder. */\nexport interface StdFragmentFactories {\n tiSync?: ThinInstanceSync;\n tiFragment?: (hasColor: boolean) => ShaderFragment;\n shadowFragment?: (shadowLights: import(\"./fragments/std-shadow-fragment.js\").ShadowLightSlot[]) => ShaderFragment;\n /** Present only when at least one mesh in the build has morph targets. */\n morphFragment?: () => ShaderFragment;\n /** Present only when the scene has at least one culling-enabled thin-instance mesh. */\n cull?: typeof import(\"../../mesh/thin-instance-cull-binding.js\");\n}\n\n/** Build Renderable(s) + a SceneUniformUpdater for a set of standard meshes.\n * The `rebuildSingle` closure is reused later (via `_rebuildSingle` on the group\n * builder) for material swaps + per-pass material overrides. */\nexport function buildStandardMeshRenderables(scene: SceneContext, meshes: Mesh[], factories: StdFragmentFactories): MeshGroupBuildResult {\n const engine = scene.surface.engine;\n const device = engine._device;\n const { tiSync, tiFragment, shadowFragment, cull, morphFragment } = factories;\n\n // Collect per-light shadow info.\n const shadowLights: { lightIndex: number; shadowType: \"esm\" | \"pcf\" | \"csm\"; gen: ShadowGenerator }[] = [];\n for (let i = 0; i < scene.lights.length; i++) {\n const sg = scene.lights[i]!.shadowGenerator;\n if (sg) {\n shadowLights.push({ lightIndex: i, shadowType: sg._shadowType, gen: sg });\n }\n }\n const hasSomeShadows = shadowLights.length > 0;\n\n // All receiving meshes in this build share the same shadow generators,\n // so keying the shadow BG by `bindings._shadowBGL` alone is correct.\n const shadowBGCache = new Map<GPUBindGroupLayout, GPUBindGroup>();\n // Closure used both for the initial per-mesh build below AND for later\n // material-swap / per-pass-override rebuilds (set on standardGroupBuilder._rebuildSingle).\n const rebuildSingle = (s: SceneContext, mesh: Mesh, materialOverride?: Material): Renderable => {\n const mat = (materialOverride ?? mesh.material) as StandardMaterialProps;\n const renderFeatures = (mat._renderFeatures ??= { features: _computeStandardMaterialFeatures(mat) }) as MaterialRenderFeatures;\n const isOverride = materialOverride != null;\n const features = renderFeatures.features;\n const shadowOutput = (features & (NO_COLOR_OUTPUT | ESM_SHADOW_OUTPUT)) !== 0;\n const receiveShadows = !shadowOutput && mesh.receiveShadows && hasSomeShadows;\n const meshFeatures = _computeMeshFeatures(mesh, receiveShadows);\n // Build per-feature fragment list (deduped via pipeline cache).\n const frags: ShaderFragment[] = [];\n // Keep morph first: composeStandardShader uses the first fragment's patch\n // to switch the placeholder morph bindings to storage buffers.\n if (meshFeatures & MSH_HAS_MORPH_TARGETS && morphFragment) {\n frags.push(morphFragment());\n }\n for (const ext of _getStdExts().values()) {\n if (features & ext._feature) {\n const f = ext._frag(features);\n if (f) {\n frags.push(f);\n }\n }\n }\n let shaderKey = \"\";\n if (meshFeatures & MSH_RECEIVE_SHADOWS && shadowFragment) {\n const slots = shadowLights.map((sl) => ({ lightIndex: sl.lightIndex, shadowType: sl.shadowType }));\n shaderKey = _standardShaderVariantKey(slots);\n frags.push(shadowFragment(slots));\n }\n if (meshFeatures & MSH_HAS_THIN_INSTANCES && tiFragment) {\n const hasColor = !!(meshFeatures & MSH_HAS_INSTANCE_COLOR);\n const tiFrag = tiFragment(hasColor);\n if (hasColor) {\n // Standard applies instance color to final color (BC), not to baseColor (AT) like PBR.\n const { _fragmentSlots: _fragmentSlots, ...rest } = tiFrag;\n frags.push({\n ...rest,\n _fragmentSlots: {\n BC: `color = vec4<f32>(color.rgb * input.vInstanceColor.rgb, color.a * input.vInstanceColor.a);`,\n },\n });\n } else {\n frags.push(tiFrag);\n }\n }\n const esmShadowDepthCode = (features & ESM_SHADOW_OUTPUT) !== 0 ? (mat as StandardMaterialProps & { readonly _esmShadowDepthCode: string })._esmShadowDepthCode : \"\";\n const bindings = getOrCreateStandardBindings(engine, features, meshFeatures, frags, shaderKey, esmShadowDepthCode, (mat as StandardMaterialProps).stencil ?? null);\n\n const meshShadowGens = receiveShadows ? shadowLights.map((sl) => sl.gen) : [];\n\n const meshUboData = new F32(bindings._composed._meshUboSpec._totalBytes / 4);\n const _packMeshWorld = engine._makePackMeshWorld?.(s as SceneContext) ?? packMat4IntoF32;\n _packMeshWorld(meshUboData, mesh.worldMatrix, 0, 0);\n writeMeshLightSelection(mesh, s.lights, meshUboData);\n const meshUBO = createUniformBuffer(engine, meshUboData);\n const textureLevel = (features & NEEDS_UV) !== 0 ? 1.0 : 0;\n const matData = new F32(24);\n writeStdMaterialData(matData, mat, textureLevel);\n const materialUBO = createUniformBuffer(engine, matData);\n const meshBindGroup = createStandardMeshBindGroup(engine, bindings, meshUBO, materialUBO, mat, mesh.morphTargets ?? null);\n\n // Shadow bind group (group 2) — shared across receiving meshes via shadowBGCache.\n let shadowBindGroup: GPUBindGroup | null = null;\n if (meshShadowGens.length > 0 && bindings._shadowBGL) {\n let cached = shadowBGCache.get(bindings._shadowBGL);\n if (!cached) {\n const entries: GPUBindGroupEntry[] = [];\n let b = 0;\n for (const sg of meshShadowGens) {\n entries.push({ binding: b++, resource: sg._depthTexture.createView() });\n entries.push({ binding: b++, resource: sg._depthSampler });\n entries.push({ binding: b++, resource: { buffer: sg._shadowUBO } });\n }\n cached = device.createBindGroup({ layout: bindings._shadowBGL, entries });\n shadowBGCache.set(bindings._shadowBGL, cached);\n }\n shadowBindGroup = cached;\n }\n\n const needsUV = (features & NEEDS_UV) !== 0;\n const needsUV2 = (features & NEEDS_UV2) !== 0;\n const hasThinInstances = (meshFeatures & MSH_HAS_THIN_INSTANCES) !== 0;\n const hasInstanceColor = (meshFeatures & MSH_HAS_INSTANCE_COLOR) !== 0;\n const isTransparent = !shadowOutput && ((features & HAS_OPACITY_TEXTURE) !== 0 || mat.alpha < 1);\n\n const boundTextures = collectStdBoundTextures(mat);\n for (const t of boundTextures) {\n acquireTexture(t);\n }\n s._meshDisposables.set(mesh, [\n () => {\n for (const t of boundTextures) {\n releaseTexture(t);\n }\n },\n ]);\n\n let _lastWorldVersion = mesh.worldMatrixVersion;\n let _lastLightsCount = s.lights.length;\n const sortCenter = [mesh.worldMatrix[12]!, mesh.worldMatrix[13]!, mesh.worldMatrix[14]!] as [number, number, number];\n const _baseUpdate = (): void => {\n const worldVersion = mesh.worldMatrixVersion;\n if (worldVersion !== _lastWorldVersion || s.lights.length !== _lastLightsCount) {\n sortCenter[0] = mesh.worldMatrix[12]!;\n sortCenter[1] = mesh.worldMatrix[13]!;\n sortCenter[2] = mesh.worldMatrix[14]!;\n _packMeshWorld(meshUboData, mesh.worldMatrix, 0, 0);\n writeMeshLightSelection(mesh, s.lights, meshUboData);\n device.queue.writeBuffer(meshUBO, 0, meshUboData as Float32Array<ArrayBuffer>);\n _lastWorldVersion = worldVersion;\n _lastLightsCount = s.lights.length;\n }\n const uboVersion = mat._uboVersion;\n if (uboVersion !== _lastUboVersion) {\n _lastUboVersion = uboVersion;\n _stdMatScratch.fill(0);\n writeStdMaterialData(_stdMatScratch, mat, textureLevel);\n device.queue.writeBuffer(materialUBO, 0, _stdMatScratch.buffer, 0, 96);\n }\n };\n // FO-version wrapper applied only when the engine has floating-origin\n // on. The wrapper lives in the dynamic-imported `floating-origin.ts`\n // module and is the sole owner of `_lastFoVersion` tracking. For\n // non-LWR engines `_wrapRenderableForFO` is undefined and `update`\n // is the bare closure — no FO bytes in the closure body.\n const _invalidate = (): void => {\n _lastWorldVersion = -1;\n };\n const update = engine._wrapRenderableForFO?.(_baseUpdate, s as SceneContext, _invalidate) ?? _baseUpdate;\n\n const draw = (pass: GPURenderPassEncoder | GPURenderBundleEncoder, cullBinding?: import(\"../../mesh/thin-instance-cull-binding.js\").TiCullBinding): number => {\n // For per-pass material overrides, skip the mesh.material === mat guard\n // because the override material is intentionally not the mesh's current one.\n if (!isOverride && mesh.material !== mat) {\n return 0;\n }\n const g = mesh._gpu;\n let slot = 0;\n const vb = g._vbLayout;\n pass.setVertexBuffer(slot++, g.positionBuffer, vb?._p?._offset);\n pass.setVertexBuffer(slot++, g.normalBuffer, vb?._n?._offset);\n if (needsUV) {\n pass.setVertexBuffer(slot++, g.uvBuffer, vb?._u?._offset);\n }\n if (needsUV2 && g.uv2Buffer) {\n pass.setVertexBuffer(slot++, g.uv2Buffer, vb?._u2?._offset);\n }\n\n const ti = hasThinInstances ? mesh.thinInstances : null;\n if (ti && tiSync) {\n slot = tiSync(engine, ti, pass, slot, hasInstanceColor, cullBinding?.cullDrawBufs);\n }\n\n pass.setIndexBuffer(g.indexBuffer, g.indexFormat);\n pass.setBindGroup(1, meshBindGroup);\n if (receiveShadows && shadowBindGroup) {\n pass.setBindGroup(2, shadowBindGroup);\n }\n if (cullBinding) {\n cullBinding.draw(pass, g.indexCount, ti!.count);\n } else if (ti && ti.count > 0) {\n pass.drawIndexed(g.indexCount, ti.count);\n } else {\n pass.drawIndexed(g.indexCount);\n }\n return 1;\n };\n\n const r: Renderable = {\n order: mesh.renderOrder ?? (isTransparent ? 200 : 100),\n isTransparent,\n mesh,\n bind(eng, sig) {\n const pipeline = getOrCreateStandardPipeline(eng as EngineContext, sig, bindings);\n // Opaque-only GPU culling (opt-in): tryBind gates on opt-in + transparency, returns the per-binding cull lifecycle.\n const cb = cull?.tryBind(r, s, mesh, engine, hasInstanceColor, isTransparent, update, sig);\n return {\n renderable: r,\n pipeline,\n update: cb ? cb.update : update,\n draw: (pass) => draw(pass, cb),\n };\n },\n };\n r._worldCenter = sortCenter;\n let _lastUboVersion = mat._uboVersion;\n return r;\n };\n\n const renderables = meshes.map((m) => rebuildSingle(scene, m));\n\n scene._disposables.push(\n () => clearStandardPipelineCache(),\n () => clearSamplerCache(engine)\n );\n\n return { renderables, rebuildSingle };\n}\n"],"names":[],"mappings":"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"}
@@ -6,6 +6,7 @@ function disposeMeshGpu(mesh) {
6
6
  g.indexBuffer.destroy();
7
7
  g.tangentBuffer?.destroy();
8
8
  g.uv2Buffer?.destroy();
9
+ g.colorBuffer?.destroy();
9
10
  mesh.thinInstances?._gpuBuffer?.destroy();
10
11
  mesh.thinInstances?._colorGpuBuffer?.destroy();
11
12
  const sk = mesh.skeleton;
@@ -1 +1 @@
1
- {"version":3,"file":"mesh-dispose.js","sources":["../../../src/mesh/mesh-dispose.ts"],"sourcesContent":["import type { Mesh } from \"./mesh.js\";\n\n/** Destroy all GPU resources owned by a mesh (vertex buffers, skeleton, morph targets). */\nexport function disposeMeshGpu(mesh: Mesh): void {\n const g = mesh._gpu;\n g.positionBuffer.destroy();\n g.normalBuffer.destroy();\n g.uvBuffer.destroy();\n g.indexBuffer.destroy();\n g.tangentBuffer?.destroy();\n g.uv2Buffer?.destroy();\n mesh.thinInstances?._gpuBuffer?.destroy();\n mesh.thinInstances?._colorGpuBuffer?.destroy();\n const sk = mesh.skeleton;\n if (sk) {\n sk.boneTexture.destroy();\n sk.jointsBuffer.destroy();\n sk.weightsBuffer.destroy();\n sk.joints1Buffer?.destroy();\n sk.weights1Buffer?.destroy();\n }\n if (mesh.morphTargets) {\n mesh.morphTargets.deltasBuffer.destroy();\n mesh.morphTargets.weightsBuffer.destroy();\n }\n}\n"],"names":[],"mappings":"AAGO,SAAS,eAAe,IAAA,EAAkB;AAC7C,EAAA,MAAM,IAAI,IAAA,CAAK,IAAA;AACf,EAAA,CAAA,CAAE,eAAe,OAAA,EAAQ;AACzB,EAAA,CAAA,CAAE,aAAa,OAAA,EAAQ;AACvB,EAAA,CAAA,CAAE,SAAS,OAAA,EAAQ;AACnB,EAAA,CAAA,CAAE,YAAY,OAAA,EAAQ;AACtB,EAAA,CAAA,CAAE,eAAe,OAAA,EAAQ;AACzB,EAAA,CAAA,CAAE,WAAW,OAAA,EAAQ;AACrB,EAAA,IAAA,CAAK,aAAA,EAAe,YAAY,OAAA,EAAQ;AACxC,EAAA,IAAA,CAAK,aAAA,EAAe,iBAAiB,OAAA,EAAQ;AAC7C,EAAA,MAAM,KAAK,IAAA,CAAK,QAAA;AAChB,EAAA,IAAI,EAAA,EAAI;AACJ,IAAA,EAAA,CAAG,YAAY,OAAA,EAAQ;AACvB,IAAA,EAAA,CAAG,aAAa,OAAA,EAAQ;AACxB,IAAA,EAAA,CAAG,cAAc,OAAA,EAAQ;AACzB,IAAA,EAAA,CAAG,eAAe,OAAA,EAAQ;AAC1B,IAAA,EAAA,CAAG,gBAAgB,OAAA,EAAQ;AAAA,EAC/B;AACA,EAAA,IAAI,KAAK,YAAA,EAAc;AACnB,IAAA,IAAA,CAAK,YAAA,CAAa,aAAa,OAAA,EAAQ;AACvC,IAAA,IAAA,CAAK,YAAA,CAAa,cAAc,OAAA,EAAQ;AAAA,EAC5C;AACJ;;;;"}
1
+ {"version":3,"file":"mesh-dispose.js","sources":["../../../src/mesh/mesh-dispose.ts"],"sourcesContent":["import type { Mesh } from \"./mesh.js\";\n\n/** Destroy all GPU resources owned by a mesh (vertex buffers, skeleton, morph targets). */\nexport function disposeMeshGpu(mesh: Mesh): void {\n const g = mesh._gpu;\n g.positionBuffer.destroy();\n g.normalBuffer.destroy();\n g.uvBuffer.destroy();\n g.indexBuffer.destroy();\n g.tangentBuffer?.destroy();\n g.uv2Buffer?.destroy();\n g.colorBuffer?.destroy();\n mesh.thinInstances?._gpuBuffer?.destroy();\n mesh.thinInstances?._colorGpuBuffer?.destroy();\n const sk = mesh.skeleton;\n if (sk) {\n sk.boneTexture.destroy();\n sk.jointsBuffer.destroy();\n sk.weightsBuffer.destroy();\n sk.joints1Buffer?.destroy();\n sk.weights1Buffer?.destroy();\n }\n if (mesh.morphTargets) {\n mesh.morphTargets.deltasBuffer.destroy();\n mesh.morphTargets.weightsBuffer.destroy();\n }\n}\n"],"names":[],"mappings":"AAGO,SAAS,eAAe,IAAA,EAAkB;AAC7C,EAAA,MAAM,IAAI,IAAA,CAAK,IAAA;AACf,EAAA,CAAA,CAAE,eAAe,OAAA,EAAQ;AACzB,EAAA,CAAA,CAAE,aAAa,OAAA,EAAQ;AACvB,EAAA,CAAA,CAAE,SAAS,OAAA,EAAQ;AACnB,EAAA,CAAA,CAAE,YAAY,OAAA,EAAQ;AACtB,EAAA,CAAA,CAAE,eAAe,OAAA,EAAQ;AACzB,EAAA,CAAA,CAAE,WAAW,OAAA,EAAQ;AACrB,EAAA,CAAA,CAAE,aAAa,OAAA,EAAQ;AACvB,EAAA,IAAA,CAAK,aAAA,EAAe,YAAY,OAAA,EAAQ;AACxC,EAAA,IAAA,CAAK,aAAA,EAAe,iBAAiB,OAAA,EAAQ;AAC7C,EAAA,MAAM,KAAK,IAAA,CAAK,QAAA;AAChB,EAAA,IAAI,EAAA,EAAI;AACJ,IAAA,EAAA,CAAG,YAAY,OAAA,EAAQ;AACvB,IAAA,EAAA,CAAG,aAAa,OAAA,EAAQ;AACxB,IAAA,EAAA,CAAG,cAAc,OAAA,EAAQ;AACzB,IAAA,EAAA,CAAG,eAAe,OAAA,EAAQ;AAC1B,IAAA,EAAA,CAAG,gBAAgB,OAAA,EAAQ;AAAA,EAC/B;AACA,EAAA,IAAI,KAAK,YAAA,EAAc;AACnB,IAAA,IAAA,CAAK,YAAA,CAAa,aAAa,OAAA,EAAQ;AACvC,IAAA,IAAA,CAAK,YAAA,CAAa,cAAc,OAAA,EAAQ;AAAA,EAC5C;AACJ;;;;"}
@@ -1,15 +1,24 @@
1
- import { destroyTiCullState, prepareTiCull, createTiCullState } from './thin-instance-gpu-culling.js';
1
+ import { createTiCullState, destroyTiCullState, prepareTiCull } from './thin-instance-gpu-culling.js';
2
2
 
3
- function tryBind(renderable, scene, mesh, engine, hasColor, excluded, baseUpdate) {
3
+ function tryBind(renderable, scene, mesh, engine, hasColor, excluded, baseUpdate, signature) {
4
4
  const ti = mesh.thinInstances;
5
5
  if (excluded || !ti?._gpuCullingEnabled) {
6
6
  return void 0;
7
7
  }
8
8
  renderable._direct = true;
9
- const state = createTiCullState();
10
- scene._meshDisposables.get(mesh)?.push(() => {
11
- destroyTiCullState(state);
12
- });
9
+ const holder = renderable;
10
+ const cache = holder._tiCullStates ??= /* @__PURE__ */ new WeakMap();
11
+ let state = cache.get(signature);
12
+ if (!state) {
13
+ state = createTiCullState();
14
+ cache.set(signature, state);
15
+ const owned = state;
16
+ scene._meshDisposables.get(mesh)?.push(() => {
17
+ destroyTiCullState(owned);
18
+ });
19
+ } else {
20
+ state._localSphereReady = false;
21
+ }
13
22
  const binding = {
14
23
  cullDrawBufs: null,
15
24
  _args: null,
@@ -1 +1 @@
1
- {"version":3,"file":"thin-instance-cull-binding.js","sources":["../../../src/mesh/thin-instance-cull-binding.ts"],"sourcesContent":["/** Shared per-binding GPU frustum-culling lifecycle for thin-instanced renderables.\n *\n * Dynamically imported only when a scene enables thin-instance GPU culling, and\n * it statically pulls in the compute-cull module — so non-culling scenes fetch\n * neither this helper nor `thin-instance-gpu-culling.ts`.\n *\n * Factored here so Standard, PBR, and ShaderMaterial renderables share one\n * implementation of the cull lifecycle instead of copy-pasting it three times.\n * `tryBind` is the single seam a renderable's `bind()` calls: it does the\n * opaque-only gate + per-mesh `_gpuCullingEnabled` check, marks the renderable\n * `_direct` (read by the render task's buildBindings right after `bind()`\n * returns), and creates the per-binding state. The renderable then reads\n * `cullDrawBufs` for the compacted instance source and calls `binding.draw(...)`\n * for the indirect-vs-fallback draw call. Keeping these few seams tiny is what\n * lets non-culling scenes — which still fetch the per-material renderable\n * chunks — stay within their bundle-size ceilings. */\n\nimport type { EngineContext } from \"../engine/engine.js\";\nimport type { SceneContext } from \"../scene/scene.js\";\nimport type { DrawUpdateContext, Renderable } from \"../render/renderable.js\";\nimport type { Mesh } from \"./mesh.js\";\nimport type { ThinInstanceDrawBuffers } from \"./thin-instance-gpu.js\";\nimport { createTiCullState, destroyTiCullState, prepareTiCull } from \"./thin-instance-gpu-culling.js\";\n\n/** Per-binding cull lifecycle. The renderable's `bind()` obtains one from\n * `tryBind`, uses `update` as the binding's update, reads `cullDrawBufs` (the\n * compacted instance source) and calls `draw()` for the final draw call. */\nexport interface TiCullBinding {\n /** Run the binding's base update, then dispatch the compute cull pass and stash the result. */\n update(context: DrawUpdateContext): void;\n /** Compacted visible-instance buffers, or null to fall back to a full instanced draw. */\n cullDrawBufs: ThinInstanceDrawBuffers | null;\n /** @internal Indirect draw-args buffer (null until/unless culling ran this frame). */\n _args: GPUBuffer | null;\n /** Issue the indirect (culled) draw when visible instances were compacted, else a full instanced draw. */\n draw(pass: GPURenderPassEncoder | GPURenderBundleEncoder, indexCount: number, instanceCount: number): void;\n}\n\n/** Create a per-binding cull lifecycle for one thin-instanced renderable binding,\n * iff the mesh opts in and is not excluded (transparent / transmissive — v1 is\n * opaque-only). Marks the renderable `_direct` so it leaves the cached opaque\n * bundle; this is safe to do during `bind()` because buildBindings reads\n * `_direct` only after `bind()` returns. Returns undefined when culling does not\n * apply, so the caller falls back to a normal instanced draw. */\nexport function tryBind(\n renderable: Renderable,\n scene: SceneContext,\n mesh: Mesh,\n engine: EngineContext,\n hasColor: boolean,\n excluded: boolean,\n baseUpdate: ((context: DrawUpdateContext) => void) | undefined\n): TiCullBinding | undefined {\n const ti = mesh.thinInstances;\n if (excluded || !ti?._gpuCullingEnabled) {\n return undefined;\n }\n (renderable as { _direct?: boolean })._direct = true;\n const state = createTiCullState();\n scene._meshDisposables.get(mesh)?.push(() => {\n destroyTiCullState(state);\n });\n const binding: TiCullBinding = {\n cullDrawBufs: null,\n _args: null,\n update(context: DrawUpdateContext): void {\n baseUpdate?.(context);\n const res = prepareTiCull(engine, state, mesh, mesh._gpu, ti, hasColor, context);\n binding.cullDrawBufs = res?.drawBuffers ?? null;\n binding._args = res?.argsBuffer ?? null;\n },\n draw(pass: GPURenderPassEncoder | GPURenderBundleEncoder, indexCount: number, instanceCount: number): void {\n if (binding._args) {\n pass.drawIndexedIndirect(binding._args, 0);\n } else {\n pass.drawIndexed(indexCount, instanceCount);\n }\n },\n };\n return binding;\n}\n"],"names":[],"mappings":";;AA4CO,SAAS,QACZ,UAAA,EACA,KAAA,EACA,MACA,MAAA,EACA,QAAA,EACA,UACA,UAAA,EACyB;AACzB,EAAA,MAAM,KAAK,IAAA,CAAK,aAAA;AAChB,EAAA,IAAI,QAAA,IAAY,CAAC,EAAA,EAAI,kBAAA,EAAoB;AACrC,IAAA,OAAO,MAAA;AAAA,EACX;AACA,EAAC,WAAqC,OAAA,GAAU,IAAA;AAChD,EAAA,MAAM,QAAQ,iBAAA,EAAkB;AAChC,EAAA,KAAA,CAAM,gBAAA,CAAiB,GAAA,CAAI,IAAI,CAAA,EAAG,KAAK,MAAM;AACzC,IAAA,kBAAA,CAAmB,KAAK,CAAA;AAAA,EAC5B,CAAC,CAAA;AACD,EAAA,MAAM,OAAA,GAAyB;AAAA,IAC3B,YAAA,EAAc,IAAA;AAAA,IACd,KAAA,EAAO,IAAA;AAAA,IACP,OAAO,OAAA,EAAkC;AACrC,MAAA,UAAA,GAAa,OAAO,CAAA;AACpB,MAAA,MAAM,GAAA,GAAM,cAAc,MAAA,EAAQ,KAAA,EAAO,MAAM,IAAA,CAAK,IAAA,EAAM,EAAA,EAAI,QAAA,EAAU,OAAO,CAAA;AAC/E,MAAA,OAAA,CAAQ,YAAA,GAAe,KAAK,WAAA,IAAe,IAAA;AAC3C,MAAA,OAAA,CAAQ,KAAA,GAAQ,KAAK,UAAA,IAAc,IAAA;AAAA,IACvC,CAAA;AAAA,IACA,IAAA,CAAK,IAAA,EAAqD,UAAA,EAAoB,aAAA,EAA6B;AACvG,MAAA,IAAI,QAAQ,KAAA,EAAO;AACf,QAAA,IAAA,CAAK,mBAAA,CAAoB,OAAA,CAAQ,KAAA,EAAO,CAAC,CAAA;AAAA,MAC7C,CAAA,MAAO;AACH,QAAA,IAAA,CAAK,WAAA,CAAY,YAAY,aAAa,CAAA;AAAA,MAC9C;AAAA,IACJ;AAAA,GACJ;AACA,EAAA,OAAO,OAAA;AACX;;;;"}
1
+ {"version":3,"file":"thin-instance-cull-binding.js","sources":["../../../src/mesh/thin-instance-cull-binding.ts"],"sourcesContent":["/** Shared per-binding GPU frustum-culling lifecycle for thin-instanced renderables.\n *\n * Dynamically imported only when a scene enables thin-instance GPU culling, and\n * it statically pulls in the compute-cull module — so non-culling scenes fetch\n * neither this helper nor `thin-instance-gpu-culling.ts`.\n *\n * Factored here so Standard, PBR, and ShaderMaterial renderables share one\n * implementation of the cull lifecycle instead of copy-pasting it three times.\n * `tryBind` is the single seam a renderable's `bind()` calls: it does the\n * opaque-only gate + per-mesh `_gpuCullingEnabled` check, marks the renderable\n * `_direct` (read by the render task's buildBindings right after `bind()`\n * returns), and creates the per-binding state. The renderable then reads\n * `cullDrawBufs` for the compacted instance source and calls `binding.draw(...)`\n * for the indirect-vs-fallback draw call. Keeping these few seams tiny is what\n * lets non-culling scenes — which still fetch the per-material renderable\n * chunks — stay within their bundle-size ceilings. */\n\nimport type { EngineContext } from \"../engine/engine.js\";\nimport type { RenderTargetSignature } from \"../engine/render-target.js\";\nimport type { SceneContext } from \"../scene/scene.js\";\nimport type { DrawUpdateContext, Renderable } from \"../render/renderable.js\";\nimport type { Mesh } from \"./mesh.js\";\nimport type { ThinInstanceDrawBuffers } from \"./thin-instance-gpu.js\";\nimport { createTiCullState, destroyTiCullState, prepareTiCull, type ThinInstanceGpuCullState } from \"./thin-instance-gpu-culling.js\";\n\n/** Per-binding cull lifecycle. The renderable's `bind()` obtains one from\n * `tryBind`, uses `update` as the binding's update, reads `cullDrawBufs` (the\n * compacted instance source) and calls `draw()` for the final draw call. */\nexport interface TiCullBinding {\n /** Run the binding's base update, then dispatch the compute cull pass and stash the result. */\n update(context: DrawUpdateContext): void;\n /** Compacted visible-instance buffers, or null to fall back to a full instanced draw. */\n cullDrawBufs: ThinInstanceDrawBuffers | null;\n /** @internal Indirect draw-args buffer (null until/unless culling ran this frame). */\n _args: GPUBuffer | null;\n /** Issue the indirect (culled) draw when visible instances were compacted, else a full instanced draw. */\n draw(pass: GPURenderPassEncoder | GPURenderBundleEncoder, indexCount: number, instanceCount: number): void;\n}\n\n/** @internal Renderable augmented with its per-signature cull-state cache (see `tryBind`). */\ntype CullCachingRenderable = Renderable & { _tiCullStates?: WeakMap<RenderTargetSignature, ThinInstanceGpuCullState> };\n\n/** Create a per-binding cull lifecycle for one thin-instanced renderable binding,\n * iff the mesh opts in and is not excluded (transparent / transmissive — v1 is\n * opaque-only). Marks the renderable `_direct` so it leaves the cached opaque\n * bundle; this is safe to do during `bind()` because buildBindings reads\n * `_direct` only after `bind()` returns. Returns undefined when culling does not\n * apply, so the caller falls back to a normal instanced draw.\n *\n * The cull STATE (visible/args/params GPU buffers) is REUSED across re-binds: it\n * is cached on the renderable, keyed by the pass's render-target signature.\n * `buildBindings` re-binds every renderable on each `_renderableVersion` bump\n * (i.e. on ANY geometry edit anywhere in the scene), so allocating a fresh state\n * here each time both leaked the previous state's buffers (freed only on mesh\n * dispose) AND churned Dawn's allocator by reallocating these buffers every edit\n * — multi-MB per edit. Reusing keeps `ensureCullBuffers` a no-op when the\n * instance capacity is unchanged. Keying by signature keeps a renderable drawn\n * in several passes (e.g. main + shadow) on an independent cull state per pass.\n * Each cached state is freed once, on mesh disposal. */\nexport function tryBind(\n renderable: Renderable,\n scene: SceneContext,\n mesh: Mesh,\n engine: EngineContext,\n hasColor: boolean,\n excluded: boolean,\n baseUpdate: ((context: DrawUpdateContext) => void) | undefined,\n signature: RenderTargetSignature\n): TiCullBinding | undefined {\n const ti = mesh.thinInstances;\n if (excluded || !ti?._gpuCullingEnabled) {\n return undefined;\n }\n (renderable as { _direct?: boolean })._direct = true;\n const holder = renderable as CullCachingRenderable;\n const cache = (holder._tiCullStates ??= new WeakMap());\n let state = cache.get(signature);\n if (!state) {\n state = createTiCullState();\n cache.set(signature, state);\n const owned = state;\n scene._meshDisposables.get(mesh)?.push(() => {\n destroyTiCullState(owned);\n });\n } else {\n // The mesh geometry may have been resized since the last bind (resizeMeshGeometry bumps the renderable\n // version, which re-binds us); recompute the local bounding sphere so culling stays accurate.\n state._localSphereReady = false;\n }\n const binding: TiCullBinding = {\n cullDrawBufs: null,\n _args: null,\n update(context: DrawUpdateContext): void {\n baseUpdate?.(context);\n const res = prepareTiCull(engine, state, mesh, mesh._gpu, ti, hasColor, context);\n binding.cullDrawBufs = res?.drawBuffers ?? null;\n binding._args = res?.argsBuffer ?? null;\n },\n draw(pass: GPURenderPassEncoder | GPURenderBundleEncoder, indexCount: number, instanceCount: number): void {\n if (binding._args) {\n pass.drawIndexedIndirect(binding._args, 0);\n } else {\n pass.drawIndexed(indexCount, instanceCount);\n }\n },\n };\n return binding;\n}\n"],"names":[],"mappings":";;AA2DO,SAAS,OAAA,CACZ,YACA,KAAA,EACA,IAAA,EACA,QACA,QAAA,EACA,QAAA,EACA,YACA,SAAA,EACyB;AACzB,EAAA,MAAM,KAAK,IAAA,CAAK,aAAA;AAChB,EAAA,IAAI,QAAA,IAAY,CAAC,EAAA,EAAI,kBAAA,EAAoB;AACrC,IAAA,OAAO,MAAA;AAAA,EACX;AACA,EAAC,WAAqC,OAAA,GAAU,IAAA;AAChD,EAAA,MAAM,MAAA,GAAS,UAAA;AACf,EAAA,MAAM,KAAA,GAAS,MAAA,CAAO,aAAA,qBAAkB,IAAI,OAAA,EAAQ;AACpD,EAAA,IAAI,KAAA,GAAQ,KAAA,CAAM,GAAA,CAAI,SAAS,CAAA;AAC/B,EAAA,IAAI,CAAC,KAAA,EAAO;AACR,IAAA,KAAA,GAAQ,iBAAA,EAAkB;AAC1B,IAAA,KAAA,CAAM,GAAA,CAAI,WAAW,KAAK,CAAA;AAC1B,IAAA,MAAM,KAAA,GAAQ,KAAA;AACd,IAAA,KAAA,CAAM,gBAAA,CAAiB,GAAA,CAAI,IAAI,CAAA,EAAG,KAAK,MAAM;AACzC,MAAA,kBAAA,CAAmB,KAAK,CAAA;AAAA,IAC5B,CAAC,CAAA;AAAA,EACL,CAAA,MAAO;AAGH,IAAA,KAAA,CAAM,iBAAA,GAAoB,KAAA;AAAA,EAC9B;AACA,EAAA,MAAM,OAAA,GAAyB;AAAA,IAC3B,YAAA,EAAc,IAAA;AAAA,IACd,KAAA,EAAO,IAAA;AAAA,IACP,OAAO,OAAA,EAAkC;AACrC,MAAA,UAAA,GAAa,OAAO,CAAA;AACpB,MAAA,MAAM,GAAA,GAAM,cAAc,MAAA,EAAQ,KAAA,EAAO,MAAM,IAAA,CAAK,IAAA,EAAM,EAAA,EAAI,QAAA,EAAU,OAAO,CAAA;AAC/E,MAAA,OAAA,CAAQ,YAAA,GAAe,KAAK,WAAA,IAAe,IAAA;AAC3C,MAAA,OAAA,CAAQ,KAAA,GAAQ,KAAK,UAAA,IAAc,IAAA;AAAA,IACvC,CAAA;AAAA,IACA,IAAA,CAAK,IAAA,EAAqD,UAAA,EAAoB,aAAA,EAA6B;AACvG,MAAA,IAAI,QAAQ,KAAA,EAAO;AACf,QAAA,IAAA,CAAK,mBAAA,CAAoB,OAAA,CAAQ,KAAA,EAAO,CAAC,CAAA;AAAA,MAC7C,CAAA,MAAO;AACH,QAAA,IAAA,CAAK,WAAA,CAAY,YAAY,aAAa,CAAA;AAAA,MAC9C;AAAA,IACJ;AAAA,GACJ;AACA,EAAA,OAAO,OAAA;AACX;;;;"}
@@ -31,6 +31,7 @@ function createSceneContext(surface, options) {
31
31
  _meshDisposables: /* @__PURE__ */ new Map(),
32
32
  _materialSwapQueue: [],
33
33
  _renderableVersion: 0,
34
+ _materialEpoch: 0,
34
35
  _built: false,
35
36
  _drawCallsPre: 0,
36
37
  _update() {
@@ -1 +1 @@
1
- {"version":3,"file":"scene-core.js","sources":["../../../src/scene/scene-core.ts"],"sourcesContent":["import type { EngineContext, RenderingContext } from \"../engine/engine.js\";\nimport { _vis, isRenderingContextRegistered, registerRenderingContext, unregisterRenderingContext } from \"../engine/engine.js\";\nimport type { SurfaceContext } from \"../engine/surface.js\";\nimport type { Camera } from \"../camera/camera.js\";\nimport type { LightBase } from \"../light/types.js\";\nimport type { Mesh } from \"../mesh/mesh.js\";\nimport { disposeMeshGpu } from \"../mesh/mesh-dispose.js\";\nimport { registerMeshScene, unregisterMeshScene, enqueueMaterialSwap } from \"./mesh-scene-registry.js\";\nimport { processMaterialSwaps } from \"./scene-material-swap.js\";\nimport type { AnimationGroup } from \"../animation/animation-group.js\";\nimport type { ShadowGenerator } from \"../shadow/shadow-generator.js\";\nimport type { FogConfig } from \"../material/standard/standard-material.js\";\nimport type { Renderable, PrePassRenderable, SceneUniformUpdater, MeshGroupBuilder } from \"../render/renderable.js\";\nimport type { TransformNode } from \"./transform-node.js\";\nimport type { SceneNode } from \"./scene-node.js\";\nimport type { EnvironmentTextures } from \"../loader-env/load-env.js\";\nimport type { FrameGraph } from \"../frame-graph/frame-graph.js\";\nimport { createFrameGraph, _appendTask } from \"../frame-graph/frame-graph.js\";\nimport { createRenderTask } from \"../frame-graph/render-task.js\";\nimport { createRenderTarget } from \"../engine/render-target.js\";\nimport type { AssetContainer } from \"../asset-container.js\";\nimport type { SceneLightGpuState } from \"../render/lights-ubo.js\";\nimport type { ClusteredLightContainer } from \"../light/clustered.js\";\nimport type { GaussianSplattingMesh } from \"../mesh/GaussianSplatting/gaussian-splatting-mesh.js\";\n\n/** Image processing configuration. */\nexport interface ImageProcessingConfig {\n exposure: number;\n contrast: number;\n toneMappingEnabled: boolean;\n /** \"standard\" (BJS TONEMAPPING_STANDARD, default) or \"aces\" (BJS TONEMAPPING_ACES). */\n toneMappingType?: \"standard\" | \"aces\";\n}\n\n/** A clipping plane expressed as the coefficients `[a, b, c, d]` of `a·x + b·y + c·z + d`. */\nexport type ClipPlane = readonly [number, number, number, number];\n\n/** Top-level scene context — pure state, no attached methods. */\nexport interface SceneContext extends RenderingContext {\n /** Surface this scene renders into. Set at scene-creation time and immutable\n * afterwards — the default render task is sized and MSAA-matched to this surface,\n * and `registerScene` attaches the scene to it. For the engine's primary surface\n * (the common single-canvas case) this is the engine itself. The owning engine is\n * reachable via `scene.surface.engine`. */\n readonly surface: SurfaceContext;\n clearColor: GPUColorDict;\n camera: Camera | null;\n lights: LightBase[];\n imageProcessing: ImageProcessingConfig;\n\n /** All meshes added to the scene (standard + PBR). */\n meshes: Mesh[];\n\n /** Animation groups loaded from glTF or created manually. */\n animationGroups: AnimationGroup[];\n\n /** Fog configuration. Null = no fog. */\n fog: FogConfig | null;\n\n /** Scene clip plane as (normal.x, normal.y, normal.z, d). Matches Babylon.js Plane `dot(worldPosition, plane) > 0` discard semantics. */\n clipPlane: ClipPlane | null;\n\n /** Shadow generators registered on this scene. */\n shadowGenerators: ShadowGenerator[];\n\n /** Background material primaryColor (linear RGB). Default from Babylon createDefaultEnvironment. */\n environmentPrimaryColor?: [number, number, number];\n\n /** Environment cubemap Y rotation in radians. */\n envRotationY?: number;\n\n /** Fixed delta time in ms for deterministic animation. 0 = use real rAF delta. */\n fixedDeltaMs: number;\n\n /** All renderables in this scene. The active frame-graph tasks bucket them\n * (opaque / direct / transparent) at bind time based on `isTransparent`, `_direct`, and `_transmissive`. */\n /** @internal */\n _renderables: Renderable[];\n /** @internal Pre-pass work (shadow maps, compute, etc.). */\n _prePasses: PrePassRenderable[];\n /** GaussianSplatting meshes attached to this scene. Populated by\n * `attachGaussianSplattingMesh`. Scene-core stays GS-agnostic apart from\n * this opaque registry (used by `gpu-picker` to iterate GS meshes without\n * scanning `_renderables`). */\n /** @internal */\n _gsMeshes: GaussianSplattingMesh[];\n /** @internal Scene uniform updaters (one per shared UBO). */\n _uniformUpdaters: SceneUniformUpdater[];\n /** @internal Opt-in feature writers for the SceneUniforms UBO (fog, clip plane, env SH).\n * Populated lazily via the scene-ubo-extras seam; run by the render task. */\n _sceneUboContributors?: ((data: Float32Array, scene: SceneContext) => void)[];\n /** @internal Per-frame callbacks run before rendering (animation, physics, etc.). */\n _beforeRender: ((deltaMs: number) => void)[];\n /** @internal Deferred builders — registered by loaders/factories, run once at startEngine(). */\n _deferredBuilders: (() => void | Promise<void>)[];\n /** @internal Mesh group registry — maps builder to its mesh list (internal bookkeeping). */\n _groups: Map<MeshGroupBuilder, Mesh[]>;\n\n // ─── Dispose infrastructure ────────────────────────────────\n /** @internal Shared cleanup callbacks (scene UBOs, lights UBOs, etc.). Registered by builders. */\n _disposables: (() => void)[];\n /** @internal Per-mesh cleanup callbacks (mesh UBOs, bind groups). For material swap + dispose. */\n _meshDisposables: Map<Mesh, (() => void)[]>;\n /** @internal Meshes whose material was changed via setter — drained before each render frame. */\n _materialSwapQueue: Mesh[];\n /** @internal Monotonic counter bumped when the renderable list changes (add/remove/rebuild). */\n _renderableVersion: number;\n /** True once the initial deferred build (buildScene) has run. Meshes added after\n * this point are materialized via the per-frame swap drain rather than the\n * boot-only deferred-builder path. */\n /** @internal */\n _built: boolean;\n\n // ─── Stashed internal state (typed to avoid `as any` casts) ────\n /** @internal */\n _envTextures?: EnvironmentTextures;\n /** @internal Scene-owned shared LightsUniforms UBO state (group 0 binding 1). */\n _lightGpuState?: SceneLightGpuState;\n\n /** Frame graph driving this scene's rendering. Created eagerly by\n * `createSceneContext` with a default `RenderTask` that mirrors\n * `_renderables` into the swapchain. User code may add additional tasks\n * (offscreen RTTs, post-FX, UI overlays, etc.). */\n /** @internal */\n _frameGraph: FrameGraph;\n\n /** @internal Optional clustered point-light container. Only populated by the clustered-light extension API. */\n _clusteredLightContainer?: ClusteredLightContainer;\n /** @internal Updates clustered light cells for the camera used by the current render pass. */\n _clusteredLightUpdater?: (camera: Camera | null | undefined, targetWidth: number, targetHeight: number) => void;\n}\n\n/** Options passed to the scene-context factory. */\nexport interface SceneContextOptions {\n defaultRenderTask?: boolean;\n}\n\n/** Create an empty scene context bound to the given `surface`. The default render task\n * is built against the surface's format, MSAA configuration, and swapchain RT — the\n * scene is permanently bound to that surface. Pass `engine` directly (since\n * `EngineContext extends SurfaceContext`) for the common single-canvas case, or pass\n * an auxiliary surface created via `createSurface`. */\nexport function createSceneContext(surface: SurfaceContext, options?: SceneContextOptions): SceneContext {\n const eng = surface.engine;\n\n // Closures below capture `ctx` by-reference via this object.\n const ctxLocal: Omit<SceneContext, \"_frameGraph\"> = {\n surface,\n clearColor: { r: 0.2, g: 0.2, b: 0.3, a: 1.0 },\n camera: null,\n lights: [],\n meshes: [],\n animationGroups: [],\n fog: null,\n clipPlane: null,\n shadowGenerators: [],\n imageProcessing: { exposure: 1.0, contrast: 1.0, toneMappingEnabled: false },\n _renderables: [],\n _prePasses: [],\n _gsMeshes: [],\n _uniformUpdaters: [],\n fixedDeltaMs: 0,\n _beforeRender: [],\n _deferredBuilders: [],\n _groups: new Map(),\n _disposables: [],\n _meshDisposables: new Map(),\n _materialSwapQueue: [],\n _renderableVersion: 0,\n _built: false,\n _drawCallsPre: 0,\n\n _update(): void {\n // When the engine was created with `useFloatingOrigin: true`, mark\n // the active camera so `getViewMatrix` knows to zero its\n // translation column (the GPU view × world product is then the\n // eye-relative result the LWR offset trick produces). For non-LWR\n // engines `eng.useFloatingOrigin` is false and this is a single\n // boolean check per frame — the inner branch is dead.\n if (eng.useFloatingOrigin && ctx.camera && !ctx.camera._useFloatingOrigin) {\n ctx.camera._useFloatingOrigin = true;\n ctx.camera._viewVer = -1;\n ctx.camera._vpVer = -1;\n }\n const d = ctx.fixedDeltaMs > 0 ? ctx.fixedDeltaMs : eng._currentDelta;\n const encoder = eng._currentEncoder;\n let draws = 0;\n for (const cb of ctx._beforeRender) {\n cb(d);\n }\n if (ctx._materialSwapQueue.length > 0) {\n processMaterialSwaps(ctx);\n }\n for (const pp of ctx._prePasses) {\n draws += pp.execute(encoder, eng);\n }\n for (const u of ctx._uniformUpdaters) {\n u.update(eng);\n }\n ctx._drawCallsPre = draws;\n },\n _record(): number {\n return ctx._frameGraph.execute();\n },\n _resize(): void {\n // Canvas backing-store changed: rebuild the frame graph so canvas-sized\n // render targets get re-allocated at the new pixel size before the next record.\n ctx._frameGraph.build();\n },\n };\n\n const ctx = ctxLocal as SceneContext;\n // Eagerly attach the frame graph + a default swapchain render-pass task. The\n // graph drives all GPU work for this scene; user code can add more tasks\n // (offscreen RTTs, post-FX, UI overlays) before/after.\n const fg = createFrameGraph(eng);\n ctx._frameGraph = fg;\n if (options?.defaultRenderTask !== false) {\n // MSAA: render into an MSAA colour RT (which owns depth) and resolve into the\n // single-sample scRT. No MSAA: render straight into the colour-only\n // scRT with a task-owned single-sample depth buffer it builds/clears/frees.\n // All three reads (format / msaaSamples / scRT) come from the bound `surface`.\n const msaa = surface.msaaSamples > 1;\n const rt = msaa\n ? createRenderTarget({ lbl: \"scene-color\", format: surface.format, dFormat: \"depth24plus-stencil8\", samples: surface.msaaSamples, size: surface })\n : surface.scRT;\n const depth = msaa ? undefined : createRenderTarget({ lbl: \"scene-depth\", dFormat: \"depth24plus-stencil8\", samples: 1, size: surface });\n _appendTask(fg, createRenderTask({ name: \"scene\", rt, rst: msaa ? surface.scRT : undefined, depth, clrColor: ctx.clearColor }, eng, ctx));\n }\n ctx._disposables.push(() => fg.dispose());\n return ctx;\n}\n\n/** Register a callback to run before each rendered frame. */\nexport function onBeforeRender(scene: SceneContext, cb: (deltaMs: number) => void): void {\n (scene as SceneContext)._beforeRender.unshift(cb);\n}\n\n/** Register a callback to run when `disposeScene` is called. Used to tie\n * user-owned GPU resources (e.g. a `SpriteRenderer`) to the scene's lifetime. */\nexport function onSceneDispose(scene: SceneContext, cb: () => void): void {\n (scene as SceneContext)._disposables.push(cb);\n}\n\n/** Get the scene's frame graph. Always non-null — created in `createSceneContext`. */\nexport function getFrameGraph(scene: SceneContext): FrameGraph {\n return (scene as SceneContext)._frameGraph;\n}\n\nexport interface DeferredSceneRenderables {\n renderables: readonly Renderable[];\n dispose?: () => void;\n}\n\n/** @internal Register optional scene-hosted render work without teaching `addToScene` about the feature. */\nexport function addDeferredSceneRenderables(\n scene: SceneContext,\n build: (engine: EngineContext, scene: SceneContext) => DeferredSceneRenderables | Promise<DeferredSceneRenderables>\n): void {\n const ctx = scene as SceneContext;\n ctx._deferredBuilders.push(async () => {\n const built = await build(ctx.surface.engine, ctx);\n ctx._renderables.push(...built.renderables);\n if (built.dispose) {\n ctx._disposables.push(built.dispose);\n }\n });\n}\n\n/**\n * Adds an entity (mesh, light, camera, transform node, shadow generator, or asset container)\n * to the scene, dispatching on its type. Asset containers are unpacked and each contained\n * entity added recursively. Optional scene-hosted systems such as depth-hosted sprites\n * expose their own opt-in add functions so mesh-only scenes do not pay feature-specific\n * routing bytes here.\n * @param scene - The owning scene (pillar 4b: entities never reference the scene themselves).\n * @param entity - The entity (or asset container) to add.\n */\nexport function addToScene(scene: SceneContext, entity: Mesh | LightBase | Camera | ShadowGenerator | TransformNode | AssetContainer): void {\n const ctx = scene as SceneContext;\n // AssetContainer from loadGltf / loadBabylon — process each field present\n if (\"entities\" in entity) {\n const result = entity as AssetContainer;\n for (const e of result.entities) {\n addToScene(scene, e);\n }\n if (result.clearColor) {\n ctx.clearColor = result.clearColor;\n }\n if (result.camera && !ctx.camera) {\n ctx.camera = result.camera;\n }\n if (result.animationGroups?.length) {\n const engine = ctx.surface.engine;\n const groups = result.animationGroups;\n ctx.animationGroups.push(...groups);\n ctx._beforeRender.push((deltaMs: number) => {\n for (const g of groups) {\n if (!g._stopped && g._ctrl) {\n g._ctrl.tick(deltaMs, engine);\n }\n }\n });\n }\n return;\n }\n if (\"_gpu\" in entity && \"material\" in entity) {\n const mesh = entity as unknown as Mesh;\n ctx.meshes.push(mesh);\n registerMeshScene(ctx, mesh);\n const build = mesh.material ? (mesh.material as unknown as { _buildGroup?: MeshGroupBuilder })._buildGroup : undefined;\n if (build) {\n let group = ctx._groups.get(build);\n if (!group) {\n group = [];\n ctx._groups.set(build, group);\n ctx._deferredBuilders.push(async () => {\n const result = await build(ctx, group!);\n ctx._renderables.push(...result.renderables);\n if (result.updater) {\n ctx._uniformUpdaters.push(result.updater);\n }\n });\n }\n group.push(mesh);\n // Added after the initial build: the deferred builder for this group has\n // already run (and only runs at boot), so materialize this mesh's renderable\n // through the per-frame material-swap drain instead.\n if (ctx._built) {\n enqueueMaterialSwap(ctx, mesh);\n }\n }\n } else if (\"lightType\" in entity) {\n ctx.lights.push(entity as LightBase);\n }\n // Recurse into children of meshes, lights, cameras — set parent links\n const kids = (entity as unknown as SceneNode).children;\n if (kids?.length) {\n for (const child of kids) {\n (child as unknown as SceneNode).parent = entity as unknown as SceneNode;\n addToScene(scene, child);\n }\n }\n}\n\n/** Release all GPU resources owned by this scene. */\nexport function disposeScene(scene: SceneContext): void {\n const ctx = scene as SceneContext;\n unregisterRenderingContext(ctx.surface, ctx);\n for (const fn of ctx._disposables) {\n fn();\n }\n for (const fns of ctx._meshDisposables.values()) {\n for (const fn of fns) {\n fn();\n }\n }\n ctx._meshDisposables.clear();\n for (const mesh of ctx.meshes) {\n // Free the mesh's shared GPU buffers only when this was its LAST owning scene.\n if (unregisterMeshScene(ctx, mesh)) {\n disposeMeshGpu(mesh);\n }\n }\n ctx.meshes.length = 0;\n ctx._renderables.length = 0;\n ctx._prePasses.length = 0;\n ctx._gsMeshes.length = 0;\n ctx._uniformUpdaters.length = 0;\n ctx._beforeRender.length = 0;\n ctx._deferredBuilders.length = 0;\n ctx._disposables.length = 0;\n ctx._materialSwapQueue.length = 0;\n ctx.lights.length = 0;\n ctx.animationGroups.length = 0;\n ctx.shadowGenerators.length = 0;\n ctx.camera = null;\n}\n\n/** @internal Run all deferred builders (called by registerScene's boot step before the first frame). */\nexport async function buildScene(scene: SceneContext): Promise<void> {\n const ctx = scene as SceneContext;\n while (ctx._deferredBuilders.length > 0) {\n const builders = [...ctx._deferredBuilders];\n ctx._deferredBuilders = [];\n await Promise.all(builders.map(async (b) => b()));\n }\n ctx._materialSwapQueue.length = 0;\n ctx._renderableVersion++;\n ctx._built = true;\n}\n\n/**\n * Register a scene with the engine. Builds deferred work, sorts renderables by order,\n * and adds the scene to its bound surface's render list in overlay order. The scene is\n * always attached to `scene.surface` (which equals the engine itself in the\n * single-canvas case).\n */\nexport async function registerScene(scene: SceneContext): Promise<void> {\n const ctx = scene;\n const surface = ctx.surface;\n if (isRenderingContextRegistered(surface, ctx)) {\n return;\n }\n await buildScene(scene);\n ctx._renderables.sort(byOrder);\n await Promise.all(ctx._frameGraph._tasks.map((task) => task._preload?.()).filter((preload): preload is Promise<void> => preload !== undefined));\n ctx._frameGraph.build();\n if (surface._renderingContexts.length > 0) {\n const overlay = await import(\"./swapchain-overlay.js\");\n overlay.configureSwapchainOverlayScene(surface, ctx);\n }\n registerRenderingContext(surface, ctx);\n}\n\n/**\n * Register a scene with the engine and install the scene-owned shadow frame-graph task.\n * Use only for scenes that generate shadow maps. Like {@link registerScene}, the scene\n * is attached to `scene.surface` (and its owning engine is `scene.surface.engine`).\n */\nexport async function registerSceneWithShadowSupport(scene: SceneContext): Promise<void> {\n const ctx = scene as SceneContext;\n const surface = ctx.surface;\n if (isRenderingContextRegistered(surface, ctx)) {\n return;\n }\n await buildScene(scene);\n ctx._renderables.sort(byOrder);\n await ensureShadowTask(surface.engine, ctx);\n await Promise.all(ctx._frameGraph._tasks.map((task) => task._preload?.()).filter((preload): preload is Promise<void> => preload !== undefined));\n ctx._frameGraph.build();\n if (surface._renderingContexts.length > 0) {\n const overlay = await import(\"./swapchain-overlay.js\");\n overlay.configureSwapchainOverlayScene(surface, ctx);\n }\n registerRenderingContext(surface, ctx);\n}\n\nconst byOrder = (a: Renderable, b: Renderable): number => a.order - b.order;\n\nasync function ensureShadowTask(engine: EngineContext, scene: SceneContext): Promise<void> {\n const { createShadowTask } = await import(\"../frame-graph/shadow-task.js\");\n scene._frameGraph._tasks.unshift(createShadowTask(engine, scene));\n}\n\n/** Remove a previously-registered scene. Idempotent. Does not dispose scene resources.\n * The scene is always removed from `scene.surface`. */\nexport function unregisterScene(scene: SceneContext): void {\n unregisterRenderingContext(scene.surface, scene as SceneContext);\n}\n"],"names":[],"mappings":";;;;;;;;AA8IO,SAAS,kBAAA,CAAmB,SAAyB,OAAA,EAA6C;AACrG,EAAA,MAAM,MAAM,OAAA,CAAQ,MAAA;AAGpB,EAAA,MAAM,QAAA,GAA8C;AAAA,IAChD,OAAA;AAAA,IACA,UAAA,EAAY,EAAE,CAAA,EAAG,GAAA,EAAK,GAAG,GAAA,EAAK,CAAA,EAAG,GAAA,EAAK,CAAA,EAAG,CAAA,EAAI;AAAA,IAC7C,MAAA,EAAQ,IAAA;AAAA,IACR,QAAQ,EAAC;AAAA,IACT,QAAQ,EAAC;AAAA,IACT,iBAAiB,EAAC;AAAA,IAClB,GAAA,EAAK,IAAA;AAAA,IACL,SAAA,EAAW,IAAA;AAAA,IACX,kBAAkB,EAAC;AAAA,IACnB,iBAAiB,EAAE,QAAA,EAAU,GAAK,QAAA,EAAU,CAAA,EAAK,oBAAoB,KAAA,EAAM;AAAA,IAC3E,cAAc,EAAC;AAAA,IACf,YAAY,EAAC;AAAA,IACb,WAAW,EAAC;AAAA,IACZ,kBAAkB,EAAC;AAAA,IACnB,YAAA,EAAc,CAAA;AAAA,IACd,eAAe,EAAC;AAAA,IAChB,mBAAmB,EAAC;AAAA,IACpB,OAAA,sBAAa,GAAA,EAAI;AAAA,IACjB,cAAc,EAAC;AAAA,IACf,gBAAA,sBAAsB,GAAA,EAAI;AAAA,IAC1B,oBAAoB,EAAC;AAAA,IACrB,kBAAA,EAAoB,CAAA;AAAA,IACpB,MAAA,EAAQ,KAAA;AAAA,IACR,aAAA,EAAe,CAAA;AAAA,IAEf,OAAA,GAAgB;AAOZ,MAAA,IAAI,IAAI,iBAAA,IAAqB,GAAA,CAAI,UAAU,CAAC,GAAA,CAAI,OAAO,kBAAA,EAAoB;AACvE,QAAA,GAAA,CAAI,OAAO,kBAAA,GAAqB,IAAA;AAChC,QAAA,GAAA,CAAI,OAAO,QAAA,GAAW,EAAA;AACtB,QAAA,GAAA,CAAI,OAAO,MAAA,GAAS,EAAA;AAAA,MACxB;AACA,MAAA,MAAM,IAAI,GAAA,CAAI,YAAA,GAAe,CAAA,GAAI,GAAA,CAAI,eAAe,GAAA,CAAI,aAAA;AACxD,MAAA,MAAM,UAAU,GAAA,CAAI,eAAA;AACpB,MAAA,IAAI,KAAA,GAAQ,CAAA;AACZ,MAAA,KAAA,MAAW,EAAA,IAAM,IAAI,aAAA,EAAe;AAChC,QAAA,EAAA,CAAG,CAAC,CAAA;AAAA,MACR;AACA,MAAA,IAAI,GAAA,CAAI,kBAAA,CAAmB,MAAA,GAAS,CAAA,EAAG;AACnC,QAAA,oBAAA,CAAqB,GAAG,CAAA;AAAA,MAC5B;AACA,MAAA,KAAA,MAAW,EAAA,IAAM,IAAI,UAAA,EAAY;AAC7B,QAAA,KAAA,IAAS,EAAA,CAAG,OAAA,CAAQ,OAAA,EAAS,GAAG,CAAA;AAAA,MACpC;AACA,MAAA,KAAA,MAAW,CAAA,IAAK,IAAI,gBAAA,EAAkB;AAClC,QAAA,CAAA,CAAE,OAAO,GAAG,CAAA;AAAA,MAChB;AACA,MAAA,GAAA,CAAI,aAAA,GAAgB,KAAA;AAAA,IACxB,CAAA;AAAA,IACA,OAAA,GAAkB;AACd,MAAA,OAAO,GAAA,CAAI,YAAY,OAAA,EAAQ;AAAA,IACnC,CAAA;AAAA,IACA,OAAA,GAAgB;AAGZ,MAAA,GAAA,CAAI,YAAY,KAAA,EAAM;AAAA,IAC1B;AAAA,GACJ;AAEA,EAAA,MAAM,GAAA,GAAM,QAAA;AAIZ,EAAA,MAAM,EAAA,GAAK,iBAAoB,CAAA;AAC/B,EAAA,GAAA,CAAI,WAAA,GAAc,EAAA;AAClB,EAAA,IAAI,OAAA,EAAS,sBAAsB,KAAA,EAAO;AAKtC,IAAA,MAAM,IAAA,GAAO,QAAQ,WAAA,GAAc,CAAA;AACnC,IAAA,MAAM,KAAK,IAAA,GACL,kBAAA,CAAmB,EAAE,GAAA,EAAK,aAAA,EAAe,QAAQ,OAAA,CAAQ,MAAA,EAAQ,OAAA,EAAS,sBAAA,EAAwB,SAAS,OAAA,CAAQ,WAAA,EAAa,MAAM,OAAA,EAAS,IAC/I,OAAA,CAAQ,IAAA;AACd,IAAA,MAAM,KAAA,GAAQ,IAAA,GAAO,MAAA,GAAY,kBAAA,CAAmB,EAAE,GAAA,EAAK,aAAA,EAAe,OAAA,EAAS,sBAAA,EAAwB,OAAA,EAAS,CAAA,EAAG,IAAA,EAAM,SAAS,CAAA;AACtI,IAAA,WAAA,CAAY,IAAI,gBAAA,CAAiB,EAAE,MAAM,OAAA,EAAS,EAAA,EAAI,KAAK,IAAA,GAAO,OAAA,CAAQ,IAAA,GAAO,MAAA,EAAW,OAAO,QAAA,EAAU,GAAA,CAAI,YAAW,EAAG,GAAA,EAAK,GAAG,CAAC,CAAA;AAAA,EAC5I;AACA,EAAA,GAAA,CAAI,YAAA,CAAa,IAAA,CAAK,MAAM,EAAA,CAAG,SAAS,CAAA;AACxC,EAAA,OAAO,GAAA;AACX;AAGO,SAAS,cAAA,CAAe,OAAqB,EAAA,EAAqC;AACrF,EAAC,KAAA,CAAuB,aAAA,CAAc,OAAA,CAAQ,EAAE,CAAA;AACpD;AAIO,SAAS,cAAA,CAAe,OAAqB,EAAA,EAAsB;AACtE,EAAC,KAAA,CAAuB,YAAA,CAAa,IAAA,CAAK,EAAE,CAAA;AAChD;AAGO,SAAS,cAAc,KAAA,EAAiC;AAC3D,EAAA,OAAQ,KAAA,CAAuB,WAAA;AACnC;AAQO,SAAS,2BAAA,CACZ,OACA,KAAA,EACI;AACJ,EAAA,MAAM,GAAA,GAAM,KAAA;AACZ,EAAA,GAAA,CAAI,iBAAA,CAAkB,KAAK,YAAY;AACnC,IAAA,MAAM,QAAQ,MAAM,KAAA,CAAM,GAAA,CAAI,OAAA,CAAQ,QAAQ,GAAG,CAAA;AACjD,IAAA,GAAA,CAAI,YAAA,CAAa,IAAA,CAAK,GAAG,KAAA,CAAM,WAAW,CAAA;AAC1C,IAAA,IAAI,MAAM,OAAA,EAAS;AACf,MAAA,GAAA,CAAI,YAAA,CAAa,IAAA,CAAK,KAAA,CAAM,OAAO,CAAA;AAAA,IACvC;AAAA,EACJ,CAAC,CAAA;AACL;AAWO,SAAS,UAAA,CAAW,OAAqB,MAAA,EAA4F;AACxI,EAAA,MAAM,GAAA,GAAM,KAAA;AAEZ,EAAA,IAAI,cAAc,MAAA,EAAQ;AACtB,IAAA,MAAM,MAAA,GAAS,MAAA;AACf,IAAA,KAAA,MAAW,CAAA,IAAK,OAAO,QAAA,EAAU;AAC7B,MAAA,UAAA,CAAW,OAAO,CAAC,CAAA;AAAA,IACvB;AACA,IAAA,IAAI,OAAO,UAAA,EAAY;AACnB,MAAA,GAAA,CAAI,aAAa,MAAA,CAAO,UAAA;AAAA,IAC5B;AACA,IAAA,IAAI,MAAA,CAAO,MAAA,IAAU,CAAC,GAAA,CAAI,MAAA,EAAQ;AAC9B,MAAA,GAAA,CAAI,SAAS,MAAA,CAAO,MAAA;AAAA,IACxB;AACA,IAAA,IAAI,MAAA,CAAO,iBAAiB,MAAA,EAAQ;AAChC,MAAA,MAAM,MAAA,GAAS,IAAI,OAAA,CAAQ,MAAA;AAC3B,MAAA,MAAM,SAAS,MAAA,CAAO,eAAA;AACtB,MAAA,GAAA,CAAI,eAAA,CAAgB,IAAA,CAAK,GAAG,MAAM,CAAA;AAClC,MAAA,GAAA,CAAI,aAAA,CAAc,IAAA,CAAK,CAAC,OAAA,KAAoB;AACxC,QAAA,KAAA,MAAW,KAAK,MAAA,EAAQ;AACpB,UAAA,IAAI,CAAC,CAAA,CAAE,QAAA,IAAY,CAAA,CAAE,KAAA,EAAO;AACxB,YAAA,CAAA,CAAE,KAAA,CAAM,IAAA,CAAK,OAAA,EAAS,MAAM,CAAA;AAAA,UAChC;AAAA,QACJ;AAAA,MACJ,CAAC,CAAA;AAAA,IACL;AACA,IAAA;AAAA,EACJ;AACA,EAAA,IAAI,MAAA,IAAU,MAAA,IAAU,UAAA,IAAc,MAAA,EAAQ;AAC1C,IAAA,MAAM,IAAA,GAAO,MAAA;AACb,IAAA,GAAA,CAAI,MAAA,CAAO,KAAK,IAAI,CAAA;AACpB,IAAA,iBAAA,CAAkB,KAAK,IAAI,CAAA;AAC3B,IAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,QAAA,GAAY,IAAA,CAAK,SAA2D,WAAA,GAAc,MAAA;AAC7G,IAAA,IAAI,KAAA,EAAO;AACP,MAAA,IAAI,KAAA,GAAQ,GAAA,CAAI,OAAA,CAAQ,GAAA,CAAI,KAAK,CAAA;AACjC,MAAA,IAAI,CAAC,KAAA,EAAO;AACR,QAAA,KAAA,GAAQ,EAAC;AACT,QAAA,GAAA,CAAI,OAAA,CAAQ,GAAA,CAAI,KAAA,EAAO,KAAK,CAAA;AAC5B,QAAA,GAAA,CAAI,iBAAA,CAAkB,KAAK,YAAY;AACnC,UAAA,MAAM,MAAA,GAAS,MAAM,KAAA,CAAM,GAAA,EAAK,KAAM,CAAA;AACtC,UAAA,GAAA,CAAI,YAAA,CAAa,IAAA,CAAK,GAAG,MAAA,CAAO,WAAW,CAAA;AAC3C,UAAA,IAAI,OAAO,OAAA,EAAS;AAChB,YAAA,GAAA,CAAI,gBAAA,CAAiB,IAAA,CAAK,MAAA,CAAO,OAAO,CAAA;AAAA,UAC5C;AAAA,QACJ,CAAC,CAAA;AAAA,MACL;AACA,MAAA,KAAA,CAAM,KAAK,IAAI,CAAA;AAIf,MAAA,IAAI,IAAI,MAAA,EAAQ;AACZ,QAAA,mBAAA,CAAoB,KAAK,IAAI,CAAA;AAAA,MACjC;AAAA,IACJ;AAAA,EACJ,CAAA,MAAA,IAAW,eAAe,MAAA,EAAQ;AAC9B,IAAA,GAAA,CAAI,MAAA,CAAO,KAAK,MAAmB,CAAA;AAAA,EACvC;AAEA,EAAA,MAAM,OAAQ,MAAA,CAAgC,QAAA;AAC9C,EAAA,IAAI,MAAM,MAAA,EAAQ;AACd,IAAA,KAAA,MAAW,SAAS,IAAA,EAAM;AACtB,MAAC,MAA+B,MAAA,GAAS,MAAA;AACzC,MAAA,UAAA,CAAW,OAAO,KAAK,CAAA;AAAA,IAC3B;AAAA,EACJ;AACJ;AAGO,SAAS,aAAa,KAAA,EAA2B;AACpD,EAAA,MAAM,GAAA,GAAM,KAAA;AACZ,EAAA,0BAAA,CAA2B,GAAA,CAAI,SAAS,GAAG,CAAA;AAC3C,EAAA,KAAA,MAAW,EAAA,IAAM,IAAI,YAAA,EAAc;AAC/B,IAAA,EAAA,EAAG;AAAA,EACP;AACA,EAAA,KAAA,MAAW,GAAA,IAAO,GAAA,CAAI,gBAAA,CAAiB,MAAA,EAAO,EAAG;AAC7C,IAAA,KAAA,MAAW,MAAM,GAAA,EAAK;AAClB,MAAA,EAAA,EAAG;AAAA,IACP;AAAA,EACJ;AACA,EAAA,GAAA,CAAI,iBAAiB,KAAA,EAAM;AAC3B,EAAA,KAAA,MAAW,IAAA,IAAQ,IAAI,MAAA,EAAQ;AAE3B,IAAA,IAAI,mBAAA,CAAoB,GAAA,EAAK,IAAI,CAAA,EAAG;AAChC,MAAA,cAAA,CAAe,IAAI,CAAA;AAAA,IACvB;AAAA,EACJ;AACA,EAAA,GAAA,CAAI,OAAO,MAAA,GAAS,CAAA;AACpB,EAAA,GAAA,CAAI,aAAa,MAAA,GAAS,CAAA;AAC1B,EAAA,GAAA,CAAI,WAAW,MAAA,GAAS,CAAA;AACxB,EAAA,GAAA,CAAI,UAAU,MAAA,GAAS,CAAA;AACvB,EAAA,GAAA,CAAI,iBAAiB,MAAA,GAAS,CAAA;AAC9B,EAAA,GAAA,CAAI,cAAc,MAAA,GAAS,CAAA;AAC3B,EAAA,GAAA,CAAI,kBAAkB,MAAA,GAAS,CAAA;AAC/B,EAAA,GAAA,CAAI,aAAa,MAAA,GAAS,CAAA;AAC1B,EAAA,GAAA,CAAI,mBAAmB,MAAA,GAAS,CAAA;AAChC,EAAA,GAAA,CAAI,OAAO,MAAA,GAAS,CAAA;AACpB,EAAA,GAAA,CAAI,gBAAgB,MAAA,GAAS,CAAA;AAC7B,EAAA,GAAA,CAAI,iBAAiB,MAAA,GAAS,CAAA;AAC9B,EAAA,GAAA,CAAI,MAAA,GAAS,IAAA;AACjB;AAGA,eAAsB,WAAW,KAAA,EAAoC;AACjE,EAAA,MAAM,GAAA,GAAM,KAAA;AACZ,EAAA,OAAO,GAAA,CAAI,iBAAA,CAAkB,MAAA,GAAS,CAAA,EAAG;AACrC,IAAA,MAAM,QAAA,GAAW,CAAC,GAAG,GAAA,CAAI,iBAAiB,CAAA;AAC1C,IAAA,GAAA,CAAI,oBAAoB,EAAC;AACzB,IAAA,MAAM,OAAA,CAAQ,IAAI,QAAA,CAAS,GAAA,CAAI,OAAO,CAAA,KAAM,CAAA,EAAG,CAAC,CAAA;AAAA,EACpD;AACA,EAAA,GAAA,CAAI,mBAAmB,MAAA,GAAS,CAAA;AAChC,EAAA,GAAA,CAAI,kBAAA,EAAA;AACJ,EAAA,GAAA,CAAI,MAAA,GAAS,IAAA;AACjB;AAQA,eAAsB,cAAc,KAAA,EAAoC;AACpE,EAAA,MAAM,GAAA,GAAM,KAAA;AACZ,EAAA,MAAM,UAAU,GAAA,CAAI,OAAA;AACpB,EAAA,IAAI,4BAAA,CAA6B,OAAA,EAAS,GAAG,CAAA,EAAG;AAC5C,IAAA;AAAA,EACJ;AACA,EAAA,MAAM,WAAW,KAAK,CAAA;AACtB,EAAA,GAAA,CAAI,YAAA,CAAa,KAAK,OAAO,CAAA;AAC7B,EAAA,MAAM,QAAQ,GAAA,CAAI,GAAA,CAAI,WAAA,CAAY,MAAA,CAAO,IAAI,CAAC,IAAA,KAAS,IAAA,CAAK,QAAA,IAAY,CAAA,CAAE,MAAA,CAAO,CAAC,OAAA,KAAsC,OAAA,KAAY,MAAS,CAAC,CAAA;AAC9I,EAAA,GAAA,CAAI,YAAY,KAAA,EAAM;AACtB,EAAA,IAAI,OAAA,CAAQ,kBAAA,CAAmB,MAAA,GAAS,CAAA,EAAG;AACvC,IAAA,MAAM,OAAA,GAAU,MAAM,OAAO,wBAAwB,CAAA;AACrD,IAAA,OAAA,CAAQ,8BAAA,CAA+B,SAAS,GAAG,CAAA;AAAA,EACvD;AACA,EAAA,wBAAA,CAAyB,SAAS,GAAG,CAAA;AACzC;AAOA,eAAsB,+BAA+B,KAAA,EAAoC;AACrF,EAAA,MAAM,GAAA,GAAM,KAAA;AACZ,EAAA,MAAM,UAAU,GAAA,CAAI,OAAA;AACpB,EAAA,IAAI,4BAAA,CAA6B,OAAA,EAAS,GAAG,CAAA,EAAG;AAC5C,IAAA;AAAA,EACJ;AACA,EAAA,MAAM,WAAW,KAAK,CAAA;AACtB,EAAA,GAAA,CAAI,YAAA,CAAa,KAAK,OAAO,CAAA;AAC7B,EAAA,MAAM,gBAAA,CAAiB,OAAA,CAAQ,MAAA,EAAQ,GAAG,CAAA;AAC1C,EAAA,MAAM,QAAQ,GAAA,CAAI,GAAA,CAAI,WAAA,CAAY,MAAA,CAAO,IAAI,CAAC,IAAA,KAAS,IAAA,CAAK,QAAA,IAAY,CAAA,CAAE,MAAA,CAAO,CAAC,OAAA,KAAsC,OAAA,KAAY,MAAS,CAAC,CAAA;AAC9I,EAAA,GAAA,CAAI,YAAY,KAAA,EAAM;AACtB,EAAA,IAAI,OAAA,CAAQ,kBAAA,CAAmB,MAAA,GAAS,CAAA,EAAG;AACvC,IAAA,MAAM,OAAA,GAAU,MAAM,OAAO,wBAAwB,CAAA;AACrD,IAAA,OAAA,CAAQ,8BAAA,CAA+B,SAAS,GAAG,CAAA;AAAA,EACvD;AACA,EAAA,wBAAA,CAAyB,SAAS,GAAG,CAAA;AACzC;AAEA,MAAM,UAAU,CAAC,CAAA,EAAe,CAAA,KAA0B,CAAA,CAAE,QAAQ,CAAA,CAAE,KAAA;AAEtE,eAAe,gBAAA,CAAiB,QAAuB,KAAA,EAAoC;AACvF,EAAA,MAAM,EAAE,gBAAA,EAAiB,GAAI,MAAM,OAAO,+BAA+B,CAAA;AACzE,EAAA,KAAA,CAAM,YAAY,MAAA,CAAO,OAAA,CAAQ,gBAAA,CAAiB,MAAA,EAAQ,KAAK,CAAC,CAAA;AACpE;AAIO,SAAS,gBAAgB,KAAA,EAA2B;AACvD,EAAA,0BAAA,CAA2B,KAAA,CAAM,SAAS,KAAqB,CAAA;AACnE;;;;"}
1
+ {"version":3,"file":"scene-core.js","sources":["../../../src/scene/scene-core.ts"],"sourcesContent":["import type { EngineContext, RenderingContext } from \"../engine/engine.js\";\nimport { _vis, isRenderingContextRegistered, registerRenderingContext, unregisterRenderingContext } from \"../engine/engine.js\";\nimport type { SurfaceContext } from \"../engine/surface.js\";\nimport type { Camera } from \"../camera/camera.js\";\nimport type { LightBase } from \"../light/types.js\";\nimport type { Mesh } from \"../mesh/mesh.js\";\nimport { disposeMeshGpu } from \"../mesh/mesh-dispose.js\";\nimport { registerMeshScene, unregisterMeshScene, enqueueMaterialSwap } from \"./mesh-scene-registry.js\";\nimport { processMaterialSwaps } from \"./scene-material-swap.js\";\nimport type { AnimationGroup } from \"../animation/animation-group.js\";\nimport type { ShadowGenerator } from \"../shadow/shadow-generator.js\";\nimport type { FogConfig } from \"../material/standard/standard-material.js\";\nimport type { Renderable, PrePassRenderable, SceneUniformUpdater, MeshGroupBuilder } from \"../render/renderable.js\";\nimport type { TransformNode } from \"./transform-node.js\";\nimport type { SceneNode } from \"./scene-node.js\";\nimport type { EnvironmentTextures } from \"../loader-env/load-env.js\";\nimport type { FrameGraph } from \"../frame-graph/frame-graph.js\";\nimport { createFrameGraph, _appendTask } from \"../frame-graph/frame-graph.js\";\nimport { createRenderTask } from \"../frame-graph/render-task.js\";\nimport { createRenderTarget } from \"../engine/render-target.js\";\nimport type { AssetContainer } from \"../asset-container.js\";\nimport type { SceneLightGpuState } from \"../render/lights-ubo.js\";\nimport type { ClusteredLightContainer } from \"../light/clustered.js\";\nimport type { GaussianSplattingMesh } from \"../mesh/GaussianSplatting/gaussian-splatting-mesh.js\";\n\n/** Image processing configuration. */\nexport interface ImageProcessingConfig {\n exposure: number;\n contrast: number;\n toneMappingEnabled: boolean;\n /** \"standard\" (BJS TONEMAPPING_STANDARD, default) or \"aces\" (BJS TONEMAPPING_ACES). */\n toneMappingType?: \"standard\" | \"aces\";\n}\n\n/** A clipping plane expressed as the coefficients `[a, b, c, d]` of `a·x + b·y + c·z + d`. */\nexport type ClipPlane = readonly [number, number, number, number];\n\n/** Top-level scene context — pure state, no attached methods. */\nexport interface SceneContext extends RenderingContext {\n /** Surface this scene renders into. Set at scene-creation time and immutable\n * afterwards — the default render task is sized and MSAA-matched to this surface,\n * and `registerScene` attaches the scene to it. For the engine's primary surface\n * (the common single-canvas case) this is the engine itself. The owning engine is\n * reachable via `scene.surface.engine`. */\n readonly surface: SurfaceContext;\n clearColor: GPUColorDict;\n camera: Camera | null;\n lights: LightBase[];\n imageProcessing: ImageProcessingConfig;\n\n /** All meshes added to the scene (standard + PBR). */\n meshes: Mesh[];\n\n /** Animation groups loaded from glTF or created manually. */\n animationGroups: AnimationGroup[];\n\n /** Fog configuration. Null = no fog. */\n fog: FogConfig | null;\n\n /** Scene clip plane as (normal.x, normal.y, normal.z, d). Matches Babylon.js Plane `dot(worldPosition, plane) > 0` discard semantics. */\n clipPlane: ClipPlane | null;\n\n /** Shadow generators registered on this scene. */\n shadowGenerators: ShadowGenerator[];\n\n /** Background material primaryColor (linear RGB). Default from Babylon createDefaultEnvironment. */\n environmentPrimaryColor?: [number, number, number];\n\n /** Environment cubemap Y rotation in radians. */\n envRotationY?: number;\n\n /** Fixed delta time in ms for deterministic animation. 0 = use real rAF delta. */\n fixedDeltaMs: number;\n\n /** All renderables in this scene. The active frame-graph tasks bucket them\n * (opaque / direct / transparent) at bind time based on `isTransparent`, `_direct`, and `_transmissive`. */\n /** @internal */\n _renderables: Renderable[];\n /** @internal Pre-pass work (shadow maps, compute, etc.). */\n _prePasses: PrePassRenderable[];\n /** GaussianSplatting meshes attached to this scene. Populated by\n * `attachGaussianSplattingMesh`. Scene-core stays GS-agnostic apart from\n * this opaque registry (used by `gpu-picker` to iterate GS meshes without\n * scanning `_renderables`). */\n /** @internal */\n _gsMeshes: GaussianSplattingMesh[];\n /** @internal Scene uniform updaters (one per shared UBO). */\n _uniformUpdaters: SceneUniformUpdater[];\n /** @internal Opt-in feature writers for the SceneUniforms UBO (fog, clip plane, env SH).\n * Populated lazily via the scene-ubo-extras seam; run by the render task. */\n _sceneUboContributors?: ((data: Float32Array, scene: SceneContext) => void)[];\n /** @internal Per-frame callbacks run before rendering (animation, physics, etc.). */\n _beforeRender: ((deltaMs: number) => void)[];\n /** @internal Deferred builders — registered by loaders/factories, run once at startEngine(). */\n _deferredBuilders: (() => void | Promise<void>)[];\n /** @internal Mesh group registry — maps builder to its mesh list (internal bookkeeping). */\n _groups: Map<MeshGroupBuilder, Mesh[]>;\n\n // ─── Dispose infrastructure ────────────────────────────────\n /** @internal Shared cleanup callbacks (scene UBOs, lights UBOs, etc.). Registered by builders. */\n _disposables: (() => void)[];\n /** @internal Per-mesh cleanup callbacks (mesh UBOs, bind groups). For material swap + dispose. */\n _meshDisposables: Map<Mesh, (() => void)[]>;\n /** @internal Meshes whose material was changed via setter — drained before each render frame. */\n _materialSwapQueue: Mesh[];\n /** @internal Monotonic counter bumped when the renderable list changes (add/remove/rebuild). */\n _renderableVersion: number;\n /** @internal Monotonic counter bumped ONLY when a material's renderables are rebuilt/swapped (material\n * swap drain or `rebuildMaterial`) — NOT on a geometry resize (which bumps `_renderableVersion` alone).\n * Lets consumers that cache material-view-derived GPU state (e.g. the CSM shadow tasks' no-color material\n * views) cheaply re-record on a geometry-only edit and only fully rebuild when a caster's material UBOs\n * were actually destroyed/recreated (which would otherwise leave their cached views dangling). */\n _materialEpoch: number;\n /** True once the initial deferred build (buildScene) has run. Meshes added after\n * this point are materialized via the per-frame swap drain rather than the\n * boot-only deferred-builder path. */\n /** @internal */\n _built: boolean;\n\n // ─── Stashed internal state (typed to avoid `as any` casts) ────\n /** @internal */\n _envTextures?: EnvironmentTextures;\n /** @internal Scene-owned shared LightsUniforms UBO state (group 0 binding 1). */\n _lightGpuState?: SceneLightGpuState;\n\n /** Frame graph driving this scene's rendering. Created eagerly by\n * `createSceneContext` with a default `RenderTask` that mirrors\n * `_renderables` into the swapchain. User code may add additional tasks\n * (offscreen RTTs, post-FX, UI overlays, etc.). */\n /** @internal */\n _frameGraph: FrameGraph;\n\n /** @internal Optional clustered point-light container. Only populated by the clustered-light extension API. */\n _clusteredLightContainer?: ClusteredLightContainer;\n /** @internal Updates clustered light cells for the camera used by the current render pass. */\n _clusteredLightUpdater?: (camera: Camera | null | undefined, targetWidth: number, targetHeight: number) => void;\n}\n\n/** Options passed to the scene-context factory. */\nexport interface SceneContextOptions {\n defaultRenderTask?: boolean;\n}\n\n/** Create an empty scene context bound to the given `surface`. The default render task\n * is built against the surface's format, MSAA configuration, and swapchain RT — the\n * scene is permanently bound to that surface. Pass `engine` directly (since\n * `EngineContext extends SurfaceContext`) for the common single-canvas case, or pass\n * an auxiliary surface created via `createSurface`. */\nexport function createSceneContext(surface: SurfaceContext, options?: SceneContextOptions): SceneContext {\n const eng = surface.engine;\n\n // Closures below capture `ctx` by-reference via this object.\n const ctxLocal: Omit<SceneContext, \"_frameGraph\"> = {\n surface,\n clearColor: { r: 0.2, g: 0.2, b: 0.3, a: 1.0 },\n camera: null,\n lights: [],\n meshes: [],\n animationGroups: [],\n fog: null,\n clipPlane: null,\n shadowGenerators: [],\n imageProcessing: { exposure: 1.0, contrast: 1.0, toneMappingEnabled: false },\n _renderables: [],\n _prePasses: [],\n _gsMeshes: [],\n _uniformUpdaters: [],\n fixedDeltaMs: 0,\n _beforeRender: [],\n _deferredBuilders: [],\n _groups: new Map(),\n _disposables: [],\n _meshDisposables: new Map(),\n _materialSwapQueue: [],\n _renderableVersion: 0,\n _materialEpoch: 0,\n _built: false,\n _drawCallsPre: 0,\n\n _update(): void {\n // When the engine was created with `useFloatingOrigin: true`, mark\n // the active camera so `getViewMatrix` knows to zero its\n // translation column (the GPU view × world product is then the\n // eye-relative result the LWR offset trick produces). For non-LWR\n // engines `eng.useFloatingOrigin` is false and this is a single\n // boolean check per frame — the inner branch is dead.\n if (eng.useFloatingOrigin && ctx.camera && !ctx.camera._useFloatingOrigin) {\n ctx.camera._useFloatingOrigin = true;\n ctx.camera._viewVer = -1;\n ctx.camera._vpVer = -1;\n }\n const d = ctx.fixedDeltaMs > 0 ? ctx.fixedDeltaMs : eng._currentDelta;\n const encoder = eng._currentEncoder;\n let draws = 0;\n for (const cb of ctx._beforeRender) {\n cb(d);\n }\n if (ctx._materialSwapQueue.length > 0) {\n processMaterialSwaps(ctx);\n }\n for (const pp of ctx._prePasses) {\n draws += pp.execute(encoder, eng);\n }\n for (const u of ctx._uniformUpdaters) {\n u.update(eng);\n }\n ctx._drawCallsPre = draws;\n },\n _record(): number {\n return ctx._frameGraph.execute();\n },\n _resize(): void {\n // Canvas backing-store changed: rebuild the frame graph so canvas-sized\n // render targets get re-allocated at the new pixel size before the next record.\n ctx._frameGraph.build();\n },\n };\n\n const ctx = ctxLocal as SceneContext;\n // Eagerly attach the frame graph + a default swapchain render-pass task. The\n // graph drives all GPU work for this scene; user code can add more tasks\n // (offscreen RTTs, post-FX, UI overlays) before/after.\n const fg = createFrameGraph(eng);\n ctx._frameGraph = fg;\n if (options?.defaultRenderTask !== false) {\n // MSAA: render into an MSAA colour RT (which owns depth) and resolve into the\n // single-sample scRT. No MSAA: render straight into the colour-only\n // scRT with a task-owned single-sample depth buffer it builds/clears/frees.\n // All three reads (format / msaaSamples / scRT) come from the bound `surface`.\n const msaa = surface.msaaSamples > 1;\n const rt = msaa\n ? createRenderTarget({ lbl: \"scene-color\", format: surface.format, dFormat: \"depth24plus-stencil8\", samples: surface.msaaSamples, size: surface })\n : surface.scRT;\n const depth = msaa ? undefined : createRenderTarget({ lbl: \"scene-depth\", dFormat: \"depth24plus-stencil8\", samples: 1, size: surface });\n _appendTask(fg, createRenderTask({ name: \"scene\", rt, rst: msaa ? surface.scRT : undefined, depth, clrColor: ctx.clearColor }, eng, ctx));\n }\n ctx._disposables.push(() => fg.dispose());\n return ctx;\n}\n\n/** Register a callback to run before each rendered frame. */\nexport function onBeforeRender(scene: SceneContext, cb: (deltaMs: number) => void): void {\n (scene as SceneContext)._beforeRender.unshift(cb);\n}\n\n/** Register a callback to run when `disposeScene` is called. Used to tie\n * user-owned GPU resources (e.g. a `SpriteRenderer`) to the scene's lifetime. */\nexport function onSceneDispose(scene: SceneContext, cb: () => void): void {\n (scene as SceneContext)._disposables.push(cb);\n}\n\n/** Get the scene's frame graph. Always non-null — created in `createSceneContext`. */\nexport function getFrameGraph(scene: SceneContext): FrameGraph {\n return (scene as SceneContext)._frameGraph;\n}\n\nexport interface DeferredSceneRenderables {\n renderables: readonly Renderable[];\n dispose?: () => void;\n}\n\n/** @internal Register optional scene-hosted render work without teaching `addToScene` about the feature. */\nexport function addDeferredSceneRenderables(\n scene: SceneContext,\n build: (engine: EngineContext, scene: SceneContext) => DeferredSceneRenderables | Promise<DeferredSceneRenderables>\n): void {\n const ctx = scene as SceneContext;\n ctx._deferredBuilders.push(async () => {\n const built = await build(ctx.surface.engine, ctx);\n ctx._renderables.push(...built.renderables);\n if (built.dispose) {\n ctx._disposables.push(built.dispose);\n }\n });\n}\n\n/**\n * Adds an entity (mesh, light, camera, transform node, shadow generator, or asset container)\n * to the scene, dispatching on its type. Asset containers are unpacked and each contained\n * entity added recursively. Optional scene-hosted systems such as depth-hosted sprites\n * expose their own opt-in add functions so mesh-only scenes do not pay feature-specific\n * routing bytes here.\n * @param scene - The owning scene (pillar 4b: entities never reference the scene themselves).\n * @param entity - The entity (or asset container) to add.\n */\nexport function addToScene(scene: SceneContext, entity: Mesh | LightBase | Camera | ShadowGenerator | TransformNode | AssetContainer): void {\n const ctx = scene as SceneContext;\n // AssetContainer from loadGltf / loadBabylon — process each field present\n if (\"entities\" in entity) {\n const result = entity as AssetContainer;\n for (const e of result.entities) {\n addToScene(scene, e);\n }\n if (result.clearColor) {\n ctx.clearColor = result.clearColor;\n }\n if (result.camera && !ctx.camera) {\n ctx.camera = result.camera;\n }\n if (result.animationGroups?.length) {\n const engine = ctx.surface.engine;\n const groups = result.animationGroups;\n ctx.animationGroups.push(...groups);\n ctx._beforeRender.push((deltaMs: number) => {\n for (const g of groups) {\n if (!g._stopped && g._ctrl) {\n g._ctrl.tick(deltaMs, engine);\n }\n }\n });\n }\n return;\n }\n if (\"_gpu\" in entity && \"material\" in entity) {\n const mesh = entity as unknown as Mesh;\n ctx.meshes.push(mesh);\n registerMeshScene(ctx, mesh);\n const build = mesh.material ? (mesh.material as unknown as { _buildGroup?: MeshGroupBuilder })._buildGroup : undefined;\n if (build) {\n let group = ctx._groups.get(build);\n if (!group) {\n group = [];\n ctx._groups.set(build, group);\n ctx._deferredBuilders.push(async () => {\n const result = await build(ctx, group!);\n ctx._renderables.push(...result.renderables);\n if (result.updater) {\n ctx._uniformUpdaters.push(result.updater);\n }\n });\n }\n group.push(mesh);\n // Added after the initial build: the deferred builder for this group has\n // already run (and only runs at boot), so materialize this mesh's renderable\n // through the per-frame material-swap drain instead.\n if (ctx._built) {\n enqueueMaterialSwap(ctx, mesh);\n }\n }\n } else if (\"lightType\" in entity) {\n ctx.lights.push(entity as LightBase);\n }\n // Recurse into children of meshes, lights, cameras — set parent links\n const kids = (entity as unknown as SceneNode).children;\n if (kids?.length) {\n for (const child of kids) {\n (child as unknown as SceneNode).parent = entity as unknown as SceneNode;\n addToScene(scene, child);\n }\n }\n}\n\n/** Release all GPU resources owned by this scene. */\nexport function disposeScene(scene: SceneContext): void {\n const ctx = scene as SceneContext;\n unregisterRenderingContext(ctx.surface, ctx);\n for (const fn of ctx._disposables) {\n fn();\n }\n for (const fns of ctx._meshDisposables.values()) {\n for (const fn of fns) {\n fn();\n }\n }\n ctx._meshDisposables.clear();\n for (const mesh of ctx.meshes) {\n // Free the mesh's shared GPU buffers only when this was its LAST owning scene.\n if (unregisterMeshScene(ctx, mesh)) {\n disposeMeshGpu(mesh);\n }\n }\n ctx.meshes.length = 0;\n ctx._renderables.length = 0;\n ctx._prePasses.length = 0;\n ctx._gsMeshes.length = 0;\n ctx._uniformUpdaters.length = 0;\n ctx._beforeRender.length = 0;\n ctx._deferredBuilders.length = 0;\n ctx._disposables.length = 0;\n ctx._materialSwapQueue.length = 0;\n ctx.lights.length = 0;\n ctx.animationGroups.length = 0;\n ctx.shadowGenerators.length = 0;\n ctx.camera = null;\n}\n\n/** @internal Run all deferred builders (called by registerScene's boot step before the first frame). */\nexport async function buildScene(scene: SceneContext): Promise<void> {\n const ctx = scene as SceneContext;\n while (ctx._deferredBuilders.length > 0) {\n const builders = [...ctx._deferredBuilders];\n ctx._deferredBuilders = [];\n await Promise.all(builders.map(async (b) => b()));\n }\n ctx._materialSwapQueue.length = 0;\n ctx._renderableVersion++;\n ctx._built = true;\n}\n\n/**\n * Register a scene with the engine. Builds deferred work, sorts renderables by order,\n * and adds the scene to its bound surface's render list in overlay order. The scene is\n * always attached to `scene.surface` (which equals the engine itself in the\n * single-canvas case).\n */\nexport async function registerScene(scene: SceneContext): Promise<void> {\n const ctx = scene;\n const surface = ctx.surface;\n if (isRenderingContextRegistered(surface, ctx)) {\n return;\n }\n await buildScene(scene);\n ctx._renderables.sort(byOrder);\n await Promise.all(ctx._frameGraph._tasks.map((task) => task._preload?.()).filter((preload): preload is Promise<void> => preload !== undefined));\n ctx._frameGraph.build();\n if (surface._renderingContexts.length > 0) {\n const overlay = await import(\"./swapchain-overlay.js\");\n overlay.configureSwapchainOverlayScene(surface, ctx);\n }\n registerRenderingContext(surface, ctx);\n}\n\n/**\n * Register a scene with the engine and install the scene-owned shadow frame-graph task.\n * Use only for scenes that generate shadow maps. Like {@link registerScene}, the scene\n * is attached to `scene.surface` (and its owning engine is `scene.surface.engine`).\n */\nexport async function registerSceneWithShadowSupport(scene: SceneContext): Promise<void> {\n const ctx = scene as SceneContext;\n const surface = ctx.surface;\n if (isRenderingContextRegistered(surface, ctx)) {\n return;\n }\n await buildScene(scene);\n ctx._renderables.sort(byOrder);\n await ensureShadowTask(surface.engine, ctx);\n await Promise.all(ctx._frameGraph._tasks.map((task) => task._preload?.()).filter((preload): preload is Promise<void> => preload !== undefined));\n ctx._frameGraph.build();\n if (surface._renderingContexts.length > 0) {\n const overlay = await import(\"./swapchain-overlay.js\");\n overlay.configureSwapchainOverlayScene(surface, ctx);\n }\n registerRenderingContext(surface, ctx);\n}\n\nconst byOrder = (a: Renderable, b: Renderable): number => a.order - b.order;\n\nasync function ensureShadowTask(engine: EngineContext, scene: SceneContext): Promise<void> {\n const { createShadowTask } = await import(\"../frame-graph/shadow-task.js\");\n scene._frameGraph._tasks.unshift(createShadowTask(engine, scene));\n}\n\n/** Remove a previously-registered scene. Idempotent. Does not dispose scene resources.\n * The scene is always removed from `scene.surface`. */\nexport function unregisterScene(scene: SceneContext): void {\n unregisterRenderingContext(scene.surface, scene as SceneContext);\n}\n"],"names":[],"mappings":"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"}
@@ -27,6 +27,7 @@ function processMaterialSwaps(scene) {
27
27
  if (!rebuild) {
28
28
  continue;
29
29
  }
30
+ mat._csmGen = (mat._csmGen ?? 0) + 1;
30
31
  const renderable = rebuild(scene, mesh);
31
32
  let i = scene._renderables.length;
32
33
  while (i > 0 && scene._renderables[i - 1].order > renderable.order) {
@@ -36,6 +37,7 @@ function processMaterialSwaps(scene) {
36
37
  }
37
38
  q.length = 0;
38
39
  scene._renderableVersion++;
40
+ scene._materialEpoch++;
39
41
  }
40
42
 
41
43
  export { processMaterialSwaps };
@@ -1 +1 @@
1
- {"version":3,"file":"scene-material-swap.js","sources":["../../../src/scene/scene-material-swap.ts"],"sourcesContent":["import type { SceneContext } from \"./scene-core.js\";\n\n/** @internal Drain _materialSwapQueue: dispose old resources and rebuild renderables. */\nexport function processMaterialSwaps(scene: SceneContext): void {\n const q = scene._materialSwapQueue;\n if (q.length === 0) {\n return;\n }\n const device = scene.surface.engine._device;\n for (const mesh of q) {\n const old = scene._meshDisposables.get(mesh);\n if (old) {\n scene._meshDisposables.delete(mesh);\n // These disposables free the OLD renderable's GPU resources (per-mesh/material UBOs, the\n // GPU-cull state buffers, texture releases). They must NOT run synchronously: the old buffers\n // may still be referenced by a frame already submitted to the GPU this tick, and destroying\n // them now hits the validation error \"Buffer used in submit while destroyed\" (seen when a\n // plugin / shadow-receiver variant change swaps a planted mesh's material — e.g. planting a\n // fern or agave). The new renderable is rebuilt below and replaces the old one, so nothing\n // records the old resources again; defer the teardown until the GPU has drained the\n // currently-submitted work (onSubmittedWorkDone). Mirrors resizeMeshGeometry.\n void device.queue\n .onSubmittedWorkDone()\n .then(() => {\n try {\n for (const fn of old) {\n fn();\n }\n } catch {\n // Device may have been lost/disposed before the deferred teardown ran.\n }\n })\n .catch(() => {});\n }\n\n const mat = mesh.material;\n const builder = mat?._buildGroup;\n if (!builder) {\n continue;\n }\n const rebuild = builder._rebuildSingle;\n if (!rebuild) {\n continue;\n }\n const renderable = rebuild(scene, mesh);\n // Insert by `order` so the renderable list stays sorted (frame-graph\n // tasks bucket opaque/direct/transparent at bind time).\n let i = scene._renderables.length;\n while (i > 0 && scene._renderables[i - 1]!.order > renderable.order) {\n i--;\n }\n scene._renderables.splice(i, 0, renderable);\n }\n q.length = 0;\n scene._renderableVersion++;\n}\n"],"names":[],"mappings":"AAGO,SAAS,qBAAqB,KAAA,EAA2B;AAC5D,EAAA,MAAM,IAAI,KAAA,CAAM,kBAAA;AAChB,EAAA,IAAI,CAAA,CAAE,WAAW,CAAA,EAAG;AAChB,IAAA;AAAA,EACJ;AACA,EAAA,MAAM,MAAA,GAAS,KAAA,CAAM,OAAA,CAAQ,MAAA,CAAO,OAAA;AACpC,EAAA,KAAA,MAAW,QAAQ,CAAA,EAAG;AAClB,IAAA,MAAM,GAAA,GAAM,KAAA,CAAM,gBAAA,CAAiB,GAAA,CAAI,IAAI,CAAA;AAC3C,IAAA,IAAI,GAAA,EAAK;AACL,MAAA,KAAA,CAAM,gBAAA,CAAiB,OAAO,IAAI,CAAA;AASlC,MAAA,KAAK,MAAA,CAAO,KAAA,CACP,mBAAA,EAAoB,CACpB,KAAK,MAAM;AACR,QAAA,IAAI;AACA,UAAA,KAAA,MAAW,MAAM,GAAA,EAAK;AAClB,YAAA,EAAA,EAAG;AAAA,UACP;AAAA,QACJ,CAAA,CAAA,MAAQ;AAAA,QAER;AAAA,MACJ,CAAC,CAAA,CACA,KAAA,CAAM,MAAM;AAAA,MAAC,CAAC,CAAA;AAAA,IACvB;AAEA,IAAA,MAAM,MAAM,IAAA,CAAK,QAAA;AACjB,IAAA,MAAM,UAAU,GAAA,EAAK,WAAA;AACrB,IAAA,IAAI,CAAC,OAAA,EAAS;AACV,MAAA;AAAA,IACJ;AACA,IAAA,MAAM,UAAU,OAAA,CAAQ,cAAA;AACxB,IAAA,IAAI,CAAC,OAAA,EAAS;AACV,MAAA;AAAA,IACJ;AACA,IAAA,MAAM,UAAA,GAAa,OAAA,CAAQ,KAAA,EAAO,IAAI,CAAA;AAGtC,IAAA,IAAI,CAAA,GAAI,MAAM,YAAA,CAAa,MAAA;AAC3B,IAAA,OAAO,CAAA,GAAI,KAAK,KAAA,CAAM,YAAA,CAAa,IAAI,CAAC,CAAA,CAAG,KAAA,GAAQ,UAAA,CAAW,KAAA,EAAO;AACjE,MAAA,CAAA,EAAA;AAAA,IACJ;AACA,IAAA,KAAA,CAAM,YAAA,CAAa,MAAA,CAAO,CAAA,EAAG,CAAA,EAAG,UAAU,CAAA;AAAA,EAC9C;AACA,EAAA,CAAA,CAAE,MAAA,GAAS,CAAA;AACX,EAAA,KAAA,CAAM,kBAAA,EAAA;AACV;;;;"}
1
+ {"version":3,"file":"scene-material-swap.js","sources":["../../../src/scene/scene-material-swap.ts"],"sourcesContent":["import type { SceneContext } from \"./scene-core.js\";\n\n/** @internal Drain _materialSwapQueue: dispose old resources and rebuild renderables. */\nexport function processMaterialSwaps(scene: SceneContext): void {\n const q = scene._materialSwapQueue;\n if (q.length === 0) {\n return;\n }\n const device = scene.surface.engine._device;\n for (const mesh of q) {\n const old = scene._meshDisposables.get(mesh);\n if (old) {\n scene._meshDisposables.delete(mesh);\n // These disposables free the OLD renderable's GPU resources (per-mesh/material UBOs, the\n // GPU-cull state buffers, texture releases). They must NOT run synchronously: the old buffers\n // may still be referenced by a frame already submitted to the GPU this tick, and destroying\n // them now hits the validation error \"Buffer used in submit while destroyed\" (seen when a\n // plugin / shadow-receiver variant change swaps a planted mesh's material — e.g. planting a\n // fern or agave). The new renderable is rebuilt below and replaces the old one, so nothing\n // records the old resources again; defer the teardown until the GPU has drained the\n // currently-submitted work (onSubmittedWorkDone). Mirrors resizeMeshGeometry.\n void device.queue\n .onSubmittedWorkDone()\n .then(() => {\n try {\n for (const fn of old) {\n fn();\n }\n } catch {\n // Device may have been lost/disposed before the deferred teardown ran.\n }\n })\n .catch(() => {});\n }\n\n const mat = mesh.material;\n const builder = mat?._buildGroup;\n if (!builder) {\n continue;\n }\n const rebuild = builder._rebuildSingle;\n if (!rebuild) {\n continue;\n }\n // Per-material generation: the CSM caster-view cache keys off THIS (which material was rebuilt), not the\n // global _materialEpoch (which also bumps when an unrelated material is swapped), so swapping a non-caster\n // material doesn't force a full shadow rebuild. See ensureCsmShadowTaskState.\n (mat as { _csmGen?: number })._csmGen = ((mat as { _csmGen?: number })._csmGen ?? 0) + 1;\n const renderable = rebuild(scene, mesh);\n // Insert by `order` so the renderable list stays sorted (frame-graph\n // tasks bucket opaque/direct/transparent at bind time).\n let i = scene._renderables.length;\n while (i > 0 && scene._renderables[i - 1]!.order > renderable.order) {\n i--;\n }\n scene._renderables.splice(i, 0, renderable);\n }\n q.length = 0;\n scene._renderableVersion++;\n scene._materialEpoch++; // a caster's material UBOs were rebuilt → CSM-style view caches must fully rebuild\n}\n"],"names":[],"mappings":"AAGO,SAAS,qBAAqB,KAAA,EAA2B;AAC5D,EAAA,MAAM,IAAI,KAAA,CAAM,kBAAA;AAChB,EAAA,IAAI,CAAA,CAAE,WAAW,CAAA,EAAG;AAChB,IAAA;AAAA,EACJ;AACA,EAAA,MAAM,MAAA,GAAS,KAAA,CAAM,OAAA,CAAQ,MAAA,CAAO,OAAA;AACpC,EAAA,KAAA,MAAW,QAAQ,CAAA,EAAG;AAClB,IAAA,MAAM,GAAA,GAAM,KAAA,CAAM,gBAAA,CAAiB,GAAA,CAAI,IAAI,CAAA;AAC3C,IAAA,IAAI,GAAA,EAAK;AACL,MAAA,KAAA,CAAM,gBAAA,CAAiB,OAAO,IAAI,CAAA;AASlC,MAAA,KAAK,MAAA,CAAO,KAAA,CACP,mBAAA,EAAoB,CACpB,KAAK,MAAM;AACR,QAAA,IAAI;AACA,UAAA,KAAA,MAAW,MAAM,GAAA,EAAK;AAClB,YAAA,EAAA,EAAG;AAAA,UACP;AAAA,QACJ,CAAA,CAAA,MAAQ;AAAA,QAER;AAAA,MACJ,CAAC,CAAA,CACA,KAAA,CAAM,MAAM;AAAA,MAAC,CAAC,CAAA;AAAA,IACvB;AAEA,IAAA,MAAM,MAAM,IAAA,CAAK,QAAA;AACjB,IAAA,MAAM,UAAU,GAAA,EAAK,WAAA;AACrB,IAAA,IAAI,CAAC,OAAA,EAAS;AACV,MAAA;AAAA,IACJ;AACA,IAAA,MAAM,UAAU,OAAA,CAAQ,cAAA;AACxB,IAAA,IAAI,CAAC,OAAA,EAAS;AACV,MAAA;AAAA,IACJ;AAIA,IAAC,GAAA,CAA6B,OAAA,GAAA,CAAY,GAAA,CAA6B,OAAA,IAAW,CAAA,IAAK,CAAA;AACvF,IAAA,MAAM,UAAA,GAAa,OAAA,CAAQ,KAAA,EAAO,IAAI,CAAA;AAGtC,IAAA,IAAI,CAAA,GAAI,MAAM,YAAA,CAAa,MAAA;AAC3B,IAAA,OAAO,CAAA,GAAI,KAAK,KAAA,CAAM,YAAA,CAAa,IAAI,CAAC,CAAA,CAAG,KAAA,GAAQ,UAAA,CAAW,KAAA,EAAO;AACjE,MAAA,CAAA,EAAA;AAAA,IACJ;AACA,IAAA,KAAA,CAAM,YAAA,CAAa,MAAA,CAAO,CAAA,EAAG,CAAA,EAAG,UAAU,CAAA;AAAA,EAC9C;AACA,EAAA,CAAA,CAAE,MAAA,GAAS,CAAA;AACX,EAAA,KAAA,CAAM,kBAAA,EAAA;AACN,EAAA,KAAA,CAAM,cAAA,EAAA;AACV;;;;"}
@@ -1,4 +1,4 @@
1
- import { createRenderTask } from '../frame-graph/render-task.js';
1
+ import { removeMeshFromTask, createRenderTask } from '../frame-graph/render-task.js';
2
2
  import { getViewProjectionMatrix } from '../camera/camera.js';
3
3
  import { mat4Invert } from '../math/mat4-invert.js';
4
4
  import { createShadowCamera, casterVersionSum, updateShadowCameraBase, buildLightViewMatrix, multiply4x4 } from './shadow-base.js';
@@ -11,6 +11,50 @@ function ensureCsmShadowTaskState(engine, scene, sg, cfg, casterMeshes, existing
11
11
  if (existing._casterMeshes === casterMeshes && existing._renderableVersion === scene._renderableVersion) {
12
12
  return existing;
13
13
  }
14
+ if (existing._casterMeshes === casterMeshes && existing._materialEpoch === scene._materialEpoch) {
15
+ existing._renderableVersion = scene._renderableVersion;
16
+ return existing;
17
+ }
18
+ let casterMatChanged = false;
19
+ for (const m of casterMeshes) {
20
+ const mat = m.material;
21
+ if (!mat) {
22
+ continue;
23
+ }
24
+ const stored = existing._casterMatGens.get(mat);
25
+ if (stored !== void 0 && stored !== (mat._csmGen ?? 0)) {
26
+ casterMatChanged = true;
27
+ break;
28
+ }
29
+ }
30
+ if (!casterMatChanged) {
31
+ const prevSet = new Set(existing._casterMeshes);
32
+ const nextSet = new Set(casterMeshes);
33
+ const views = existing._materialViews;
34
+ const gens = existing._casterMatGens;
35
+ for (const m of existing._casterMeshes) {
36
+ if (!nextSet.has(m)) {
37
+ for (const t of existing._tasks) {
38
+ removeMeshFromTask(t, m);
39
+ }
40
+ }
41
+ }
42
+ for (const m of casterMeshes) {
43
+ if (!prevSet.has(m) && m.material) {
44
+ const view = getNoColorView(m.material, views);
45
+ for (const t of existing._tasks) {
46
+ t.addMesh(m, { material: view });
47
+ }
48
+ gens.set(m.material, m.material._csmGen ?? 0);
49
+ }
50
+ }
51
+ for (const t of existing._tasks) {
52
+ t._lastVersion = -1;
53
+ }
54
+ existing._casterMeshes = casterMeshes;
55
+ existing._renderableVersion = scene._renderableVersion;
56
+ return existing;
57
+ }
14
58
  const old = existing._task;
15
59
  void engine._device.queue.onSubmittedWorkDone().then(() => {
16
60
  try {
@@ -74,6 +118,12 @@ function ensureCsmShadowTaskState(engine, scene, sg, cfg, casterMeshes, existing
74
118
  }
75
119
  }
76
120
  };
121
+ const casterMatGens = /* @__PURE__ */ new Map();
122
+ for (const m of casterMeshes) {
123
+ if (m.material) {
124
+ casterMatGens.set(m.material, m.material._csmGen ?? 0);
125
+ }
126
+ }
77
127
  return {
78
128
  _task: compositeTask,
79
129
  _tasks: tasks,
@@ -85,7 +135,10 @@ function ensureCsmShadowTaskState(engine, scene, sg, cfg, casterMeshes, existing
85
135
  _lastCamVersion: -1,
86
136
  _uboData: new Float32Array(80),
87
137
  _casterMeshes: casterMeshes,
88
- _renderableVersion: scene._renderableVersion
138
+ _renderableVersion: scene._renderableVersion,
139
+ _materialEpoch: scene._materialEpoch,
140
+ _materialViews: materialViews,
141
+ _casterMatGens: casterMatGens
89
142
  };
90
143
  }
91
144
  function renderCsmShadowMap(engine, sg, state, cfg) {
@@ -265,16 +318,21 @@ function _computeCsmCascades(engine, camera, light, cfg, casterMeshes) {
265
318
  viewMinZ = Math.min(viewMinZ, cMinZ);
266
319
  viewMaxZ = Math.min(viewMaxZ, cMaxZ);
267
320
  }
268
- if (cfg._stabilizeCascades && stableRadius > 0) {
269
- const zq = Math.max(0.5, stableRadius / 128);
270
- viewMinZ = Math.floor(viewMinZ / zq) * zq;
271
- viewMaxZ = Math.ceil(viewMaxZ / zq) * zq;
272
- }
273
321
  }
274
322
  const proj0 = orthoOffCenterLH(minX, maxX, minY, maxY, viewMinZ, viewMaxZ);
275
323
  let transform = multiply4x4(proj0, view);
276
- const ox = transform[12] * (cfg._mapSize / 2);
277
- const oy = transform[13] * (cfg._mapSize / 2);
324
+ let aClipX = transform[12];
325
+ let aClipY = transform[13];
326
+ if (cfg._stabilizeCascades && stableRadius > 0) {
327
+ const texelWorld = 2 * stableRadius / cfg._mapSize;
328
+ const ax = Math.round(cx / texelWorld) * texelWorld;
329
+ const ay = Math.round(cy / texelWorld) * texelWorld;
330
+ const az = Math.round(cz / texelWorld) * texelWorld;
331
+ aClipX = transform[0] * ax + transform[4] * ay + transform[8] * az + transform[12];
332
+ aClipY = transform[1] * ax + transform[5] * ay + transform[9] * az + transform[13];
333
+ }
334
+ const ox = aClipX * (cfg._mapSize / 2);
335
+ const oy = aClipY * (cfg._mapSize / 2);
278
336
  const offX = (Math.round(ox) - ox) * (2 / cfg._mapSize);
279
337
  const offY = (Math.round(oy) - oy) * (2 / cfg._mapSize);
280
338
  const snap = new Float32Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, offX, offY, 0, 1]);