From 30b77d1b84804f4497a19b7db3482e8e735635fe Mon Sep 17 00:00:00 2001 From: Raanan Weber Date: Thu, 9 Jul 2026 21:20:05 +0200 Subject: [PATCH 01/10] feat(loader): add EXT_lights_image_based support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the glTF EXT_lights_image_based extension as a self-registering, lazy, side-effect-free GltfFeature module. It installs a document-level image-based light (irradiance SH9 + prefiltered specular mip cubemaps + intensity/rotation) referenced by the active scene and drives PBR IBL through Lite's existing environment path. Reuse (KISS / maximum reuse per GUIDANCE): - Specular mips are RGBD PNGs (same encoding as .env faces) -> shared uploadCubemapRGBD decoder. - BRDF split-sum LUT generated on-GPU via the shared generateBrdfLut. - Final EnvironmentTextures assembled with assembleEnvironmentTextures and wired onto the scene exactly like loadEnvironment (scene._envTextures + registerEnvSceneUniforms + tone-mapping defaults). - glTF irradianceCoefficients -> SphericalPolynomial conversion reproduces Babylon's SH -> intensity -> Lambertian (x1/pi) chain so the shared polynomialToPreScaledHarmonics yields matching coefficients. Wiring: a lazy AssetContainer._sceneSetup closure (invoked by addToScene) installs the environment, keeping the core loader/scene feature-agnostic. Fixes the Khronos "Environment Test" (glTF-IBL) model; parity scene264 renders at MAD 0.461 (limit 0.5), peer-consistent with the existing PBR-IBL scenes. NOTE: reusing the shared IBL modules (env-helpers, hdr-ibl-pipeline, samplers) makes them second consumers in every gltf scene's rollup graph, hoisting those previously-inlined leaves into standalone chunks (+0.5-1.1 KB per gltf scene). This needs a bundle-size ceiling decision — see PR description. No ceilings or golden references changed in this commit. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- lab/lite/babylon-ref-scene264.html | 27 +++ lab/lite/bundle-bjs-scene264.html | 27 +++ lab/lite/bundle-scene264.html | 28 +++ lab/lite/scene264.html | 28 +++ lab/lite/src/bjs/scene264.ts | 45 +++++ lab/lite/src/lite/scene264.ts | 34 ++++ lab/public/bundle/manifest/scene264.json | 19 ++ lab/public/thumbnails/scene264.jpg | Bin 0 -> 32733 bytes packages/babylon-lite/src/asset-container.ts | 8 + .../gltf-ext-lights-image-based.ts | 166 ++++++++++++++++++ .../src/loader-gltf/gltf-feature-registry.ts | 1 + packages/babylon-lite/src/scene/scene-core.ts | 4 + .../babylon-ref-golden.png | Bin 0 -> 56170 bytes scene-config.json | 10 ++ .../scenes/scene264-environment-test.spec.ts | 33 ++++ 15 files changed, 430 insertions(+) create mode 100644 lab/lite/babylon-ref-scene264.html create mode 100644 lab/lite/bundle-bjs-scene264.html create mode 100644 lab/lite/bundle-scene264.html create mode 100644 lab/lite/scene264.html create mode 100644 lab/lite/src/bjs/scene264.ts create mode 100644 lab/lite/src/lite/scene264.ts create mode 100644 lab/public/bundle/manifest/scene264.json create mode 100644 lab/public/thumbnails/scene264.jpg create mode 100644 packages/babylon-lite/src/loader-gltf/gltf-ext-lights-image-based.ts create mode 100644 reference/lite/scene264-environment-test/babylon-ref-golden.png create mode 100644 tests/lite/parity/scenes/scene264-environment-test.spec.ts diff --git a/lab/lite/babylon-ref-scene264.html b/lab/lite/babylon-ref-scene264.html new file mode 100644 index 0000000000..b506451f89 --- /dev/null +++ b/lab/lite/babylon-ref-scene264.html @@ -0,0 +1,27 @@ + + + + + Babylon.js Reference — Scene 264: EnvironmentTest (EXT_lights_image_based) + + + + + + + diff --git a/lab/lite/bundle-bjs-scene264.html b/lab/lite/bundle-bjs-scene264.html new file mode 100644 index 0000000000..3c18778ac7 --- /dev/null +++ b/lab/lite/bundle-bjs-scene264.html @@ -0,0 +1,27 @@ + + + + + Babylon.js Reference — Scene 264: EnvironmentTest (EXT_lights_image_based) (Bundle) + + + + + + + diff --git a/lab/lite/bundle-scene264.html b/lab/lite/bundle-scene264.html new file mode 100644 index 0000000000..44d00a1955 --- /dev/null +++ b/lab/lite/bundle-scene264.html @@ -0,0 +1,28 @@ + + + + + Babylon Lite — Scene 264: EnvironmentTest (EXT_lights_image_based) (Bundle) + + + + + + + + diff --git a/lab/lite/scene264.html b/lab/lite/scene264.html new file mode 100644 index 0000000000..49373edfd8 --- /dev/null +++ b/lab/lite/scene264.html @@ -0,0 +1,28 @@ + + + + + Babylon Lite — Scene 264: EnvironmentTest (EXT_lights_image_based) + + + + + + + + diff --git a/lab/lite/src/bjs/scene264.ts b/lab/lite/src/bjs/scene264.ts new file mode 100644 index 0000000000..a9673bc033 --- /dev/null +++ b/lab/lite/src/bjs/scene264.ts @@ -0,0 +1,45 @@ +import { ArcRotateCamera } from "@babylonjs/core/Cameras/arcRotateCamera"; +import { Color4 } from "@babylonjs/core/Maths/math.color"; +import { WebGPUEngine } from "@babylonjs/core/Engines/webgpuEngine"; +import "@babylonjs/core/Helpers/sceneHelpers"; +import "@babylonjs/core/Loading/loadingScreen"; +import { SceneLoader } from "@babylonjs/core/Loading/sceneLoader"; +import { Scene } from "@babylonjs/core/scene"; +import "@babylonjs/loaders/glTF"; + +const MODEL_URL = "https://cx20.github.io/gltf-test/tutorialModels/EnvironmentTest/glTF-IBL/EnvironmentTest.gltf"; + +(async function () { + const __initStart = performance.now(); + const canvas = document.getElementById("renderCanvas") as HTMLCanvasElement; + const engine = new WebGPUEngine(canvas, { antialias: true, adaptToDeviceRatio: true }); + await engine.initAsync(); + engine.displayLoadingUI = function () {}; + + const scene = new Scene(engine); + scene.clearColor = new Color4(0.2, 0.2, 0.3, 1.0); + + // The glTF's EXT_lights_image_based extension installs scene.environmentTexture. + await SceneLoader.AppendAsync("", MODEL_URL, scene); + + // Match loadEnvironment's image-processing defaults (the Lite extension sets these). + scene.imageProcessingConfiguration.exposure = 0.8; + scene.imageProcessingConfiguration.contrast = 1.2; + scene.imageProcessingConfiguration.toneMappingEnabled = true; + + // Same auto-framing formula as Lite's createDefaultCamera; override angles to match. + scene.createDefaultCamera(true, true, true); + const camera = scene.activeCamera as ArcRotateCamera; + camera.alpha = Math.PI / 2; + camera.beta = Math.PI / 2.5; + + await scene.whenReadyAsync(); + engine.runRenderLoop(() => scene.render()); + window.addEventListener("resize", () => engine.resize()); + + await new Promise((resolve) => scene.onAfterRenderObservable.addOnce(() => resolve())); + canvas.dataset.camAlpha = String(camera.alpha); + canvas.dataset.camRadius = String(camera.radius); + canvas.dataset.initMs = String(performance.now() - __initStart); + canvas.dataset.ready = "true"; +})().catch(console.error); diff --git a/lab/lite/src/lite/scene264.ts b/lab/lite/src/lite/scene264.ts new file mode 100644 index 0000000000..66edb8511a --- /dev/null +++ b/lab/lite/src/lite/scene264.ts @@ -0,0 +1,34 @@ +// Scene 264 — EnvironmentTest (cx20 gltf-test parity). +// Exercises EXT_lights_image_based: the glTF carries its own image-based light +// (irradiance SH9 + prefiltered specular cubemap), so we do NOT call +// loadEnvironment — the extension installs the environment onto the scene. +import { addToScene, startEngine, createEngine, createSceneContext, createDefaultCamera, loadGltf, attachControl, registerScene } from "babylon-lite"; + +const MODEL_URL = "https://cx20.github.io/gltf-test/tutorialModels/EnvironmentTest/glTF-IBL/EnvironmentTest.gltf"; + +async function main(): Promise { + const __initStart = performance.now(); + const canvas = document.getElementById("renderCanvas") as HTMLCanvasElement; + + const engine = await createEngine(canvas); + const scene = createSceneContext(engine); + scene.clearColor = { r: 0.2, g: 0.2, b: 0.3, a: 1.0 }; + + const root = await loadGltf(engine, MODEL_URL); + addToScene(scene, root); + + const cam = createDefaultCamera(scene); + cam.alpha = Math.PI / 2; + cam.beta = Math.PI / 2.5; + attachControl(cam, canvas, scene); + + await registerScene(scene); + await startEngine(engine); + (window as any).__scene = scene; + canvas.dataset.camAlpha = String(cam.alpha); + canvas.dataset.camRadius = String(cam.radius); + canvas.dataset.initMs = String(performance.now() - __initStart); + canvas.dataset.ready = "true"; +} + +main().catch(console.error); diff --git a/lab/public/bundle/manifest/scene264.json b/lab/public/bundle/manifest/scene264.json new file mode 100644 index 0000000000..28f84df779 --- /dev/null +++ b/lab/public/bundle/manifest/scene264.json @@ -0,0 +1,19 @@ +{ + "rawKB": 80.2, + "gzipKB": 33.6, + "ignoredRawKB": 0, + "runtimeChunks": [ + "scene264-env-helpers-23YvOcOj.js", + "scene264-generate-mipmaps-KrqAJzxS.js", + "scene264-gltf-ext-lights-image-based-Bl2jmRGe.js", + "scene264-gltf-feature-registry-RAFggZxh.js", + "scene264-gltf-json-asset-OTC4RQu_.js", + "scene264-hdr-ibl-pipeline-JseuEIIG.js", + "scene264-ibl-fragment-CeRrvT96.js", + "scene264-pbr-renderable-CskWgmPH.js", + "scene264-rgbd-decode-C0Son7dU.js", + "scene264-samplers-DRa_c529.js", + "scene264-scene-ubo-extras-B5B4aSOt.js", + "scene264.js" + ] +} diff --git a/lab/public/thumbnails/scene264.jpg b/lab/public/thumbnails/scene264.jpg new file mode 100644 index 0000000000000000000000000000000000000000..74b6bec7f7872a6ac28c51dadf6c0e3c98146aeb GIT binary patch literal 32733 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 z%0A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

>l()5|PJ1haf{pi$a`Uvr` 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 literal 0 HcmV?d00001 diff --git a/packages/babylon-lite/src/asset-container.ts b/packages/babylon-lite/src/asset-container.ts index d2760e610a..8cbac7271d 100644 --- a/packages/babylon-lite/src/asset-container.ts +++ b/packages/babylon-lite/src/asset-container.ts @@ -30,6 +30,14 @@ export interface AssetContainer { * `enableBoneControl()` was called before loading; otherwise `undefined`. * Drive bones via `getBoneByName()` + the `setBone*` functions. */ skeletons?: Skeleton[]; + /** Deferred scene-wiring hook contributed by a loader feature that needs the + * target `SceneContext` (which the loader itself never sees). `addToScene()` + * invokes it once, synchronously, while processing the container. Used by + * `EXT_lights_image_based` to install its image-based-light environment + * (spherical harmonics + specular cubemap) onto the scene. Lazy features own + * the closure so the core loader/scene stay feature-agnostic. + * @internal */ + _sceneSetup?: (scene: import("./scene/scene-core.js").SceneContext) => void; } /** diff --git a/packages/babylon-lite/src/loader-gltf/gltf-ext-lights-image-based.ts b/packages/babylon-lite/src/loader-gltf/gltf-ext-lights-image-based.ts new file mode 100644 index 0000000000..4be647896d --- /dev/null +++ b/packages/babylon-lite/src/loader-gltf/gltf-ext-lights-image-based.ts @@ -0,0 +1,166 @@ +/** EXT_lights_image_based glTF extension. + * + * Installs a document-level image-based light (diffuse irradiance SH9 + a set of + * prefiltered specular mip cubemaps, plus intensity/rotation) referenced by the + * active scene, driving the PBR IBL through Lite's existing environment path. + * + * Reuse (KISS, maximum reuse per GUIDANCE): + * - Specular mips are RGBD-encoded PNGs (identical encoding to Babylon `.env` + * faces), so they upload through the shared `uploadCubemapRGBD` decoder. + * - The BRDF split-sum LUT is generated procedurally on-GPU (`generateBrdfLut`, + * shared with the HDR loader) — no external asset URL is needed. + * - The final `EnvironmentTextures` are assembled with `assembleEnvironmentTextures` + * and wired onto the scene exactly like `loadEnvironment` / `loadHdrEnvironment` + * (`scene._envTextures` + `registerEnvSceneUniforms` + tone-mapping defaults). + * + * The glTF `irradianceCoefficients` are raw SH9 coefficients. Babylon converts + * them SH → intensity scale → Lambertian radiance (×1/π) → SphericalPolynomial, + * and its shader later expands that polynomial to the pre-scaled harmonics the + * scene UBO consumes. We reproduce the SH→polynomial step here so the shared + * `polynomialToPreScaledHarmonics` (inside `assembleEnvironmentTextures`) yields + * byte-for-byte the same rendering coefficients as Babylon. + */ + +import type { GltfFeature } from "./gltf-feature.js"; +import type { SceneContext } from "../scene/scene-core.js"; + +// NOTE: this module is a lazily-imported feature chunk (see gltf-feature-registry) +// and keeps ZERO static runtime imports — every dependency is pulled in via dynamic +// import inside applyAsset. Scenes that never trigger EXT_lights_image_based never +// fetch this chunk (or its dependencies) at runtime. Its reuse of the shared IBL +// modules (env-helpers, hdr-ibl-pipeline, rgbd-decode, samplers) does, however, +// participate in rollup's static chunk graph for every gltf build, which can hoist +// those shared leaves into standalone chunks — see the PR notes on bundle-size. + +interface GltfImageBasedLight { + name?: string; + intensity?: number; + rotation?: [number, number, number, number]; + irradianceCoefficients?: number[][]; + specularImageSize: number; + specularImages: number[][]; +} + +/** Convert glTF `EXT_lights_image_based` SH9 irradiance coefficients into the + * 27-float spherical-polynomial layout `[x,y,z,xx,yy,zz,yz,zx,xy]` that + * `assembleEnvironmentTextures` → `polynomialToPreScaledHarmonics` expects. + * + * Mirrors Babylon's chain: `SphericalHarmonics.FromArray(coeffs)` → + * `scaleInPlace(intensity)` → `convertIrradianceToLambertianRadiance()` (×1/π) → + * `SphericalPolynomial.FromHarmonics()` (`updateFromHarmonics`, which folds a + * final ×1/π). All band constants are copied from Babylon `sphericalPolynomial`. */ +function irradianceCoefficientsToPolynomial(coeffs: number[][], intensity: number): Float32Array { + // Harmonic scale = intensity (radiance) × 1/π (Lambertian normalisation). + const s = intensity / Math.PI; + const poly = new Float32Array(27); + for (let c = 0; c < 3; c++) { + const l00 = coeffs[0]![c]! * s; + const l1_1 = coeffs[1]![c]! * s; + const l10 = coeffs[2]![c]! * s; + const l11 = coeffs[3]![c]! * s; + const l2_2 = coeffs[4]![c]! * s; + const l2_1 = coeffs[5]![c]! * s; + const l20 = coeffs[6]![c]! * s; + const l21 = coeffs[7]![c]! * s; + const l22 = coeffs[8]![c]! * s; + + // updateFromHarmonics(), then the trailing ×(1/π). + const k = 1 / Math.PI; + poly[0 + c] = -1.02333 * l11 * k; // x + poly[3 + c] = -1.02333 * l1_1 * k; // y + poly[6 + c] = 1.02333 * l10 * k; // z + poly[9 + c] = (0.886277 * l00 - 0.247708 * l20 + 0.429043 * l22) * k; // xx + poly[12 + c] = (0.886277 * l00 - 0.247708 * l20 - 0.429043 * l22) * k; // yy + poly[15 + c] = (0.886277 * l00 + 0.495417 * l20) * k; // zz + poly[18 + c] = -0.858086 * l2_1 * k; // yz + poly[21 + c] = -0.858086 * l21 * k; // zx + poly[24 + c] = 0.858086 * l2_2 * k; // xy + } + return poly; +} + +/** Extract the Y-axis rotation angle (radians) from an image-based-light + * quaternion. The extension's rotations are pure Y-yaw; Lite drives environment + * yaw through `scene.envRotationY`. Babylon inverts the rotation for its + * left-handed target scene, so we negate to match its sampling direction. */ +function envYawFromQuaternion(q: [number, number, number, number]): number { + return -2 * Math.atan2(q[1], q[3]); +} + +const feature: GltfFeature = { + id: "EXT_lights_image_based", + async applyAsset(_meshes, _root, ctx) { + const json = ctx._json; + const lights: GltfImageBasedLight[] | undefined = json.extensions?.EXT_lights_image_based?.lights; + const sceneDef = json.scenes?.[json.scene ?? 0]; + const lightIdx: number | undefined = sceneDef?.extensions?.EXT_lights_image_based?.light; + if (!lights?.length || lightIdx === undefined) { + return {}; + } + const light = lights[lightIdx]; + if (!light?.specularImages?.length || !light.irradianceCoefficients) { + return {}; + } + + const engine = ctx._engine; + const specularImageSize = light.specularImageSize; + const mipCount = light.specularImages.length; + + // Decode every specular face (flat, mip-major face-minor — the order + // uploadCubemapRGBD expects; glTF face order +X,-X,+Y,-Y,+Z,-Z matches + // WebGPU cube layers). + const flatIndices = light.specularImages.flat(); + const { resolveImage } = await import("./gltf-parser.js"); + const faceImages = await Promise.all(flatIndices.map((imgIdx) => resolveImage(json, ctx._binChunk, imgIdx, ctx._baseUrl))); + + const { uploadCubemapRGBD } = await import("../loader-env/rgbd-decode.js"); + const specularCube = uploadCubemapRGBD(engine, faceImages, specularImageSize, mipCount); + for (const img of faceImages) { + img.close(); + } + + const { generateBrdfLut } = await import("../loader-hdr/hdr-ibl-pipeline.js"); + const brdfLut = generateBrdfLut(engine); + + const intensity = light.intensity ?? 1; + const irradianceSH = irradianceCoefficientsToPolynomial(light.irradianceCoefficients, intensity); + // Fit the LOD scale to the available mip count exactly as Babylon does. + const lodGenerationScale = (mipCount - 1) / Math.log2(specularImageSize); + + const { assembleEnvironmentTextures } = await import("../loader-env/env-helpers.js"); + const textures = assembleEnvironmentTextures(specularCube, brdfLut, irradianceSH, lodGenerationScale, engine); + + const envRotationY = light.rotation ? envYawFromQuaternion(light.rotation) : 0; + + // Pull the scene-wiring helpers in now (still lazy — part of this feature + // chunk) so the synchronous _sceneSetup closure can use them directly. + const { acquireGPUTexture, releaseGPUTexture } = await import("../resource/gpu-pool.js"); + const { registerEnvSceneUniforms } = await import("../scene/scene-ubo-extras.js"); + + // Defer the scene wiring: applyAsset has no SceneContext, so hand the core + // loader a closure that addToScene() runs against the real scene. + const _sceneSetup = (scene: SceneContext): void => { + scene._envTextures = textures; + if (envRotationY) { + scene.envRotationY = envRotationY; + } + registerEnvSceneUniforms(scene); + + acquireGPUTexture(specularCube); + acquireGPUTexture(brdfLut); + scene._disposables.push(() => { + releaseGPUTexture(specularCube); + releaseGPUTexture(brdfLut); + }); + + // Match loadEnvironment's image-processing defaults (tone mapping on, + // exposure 0.8, contrast 1.2) so IBL-lit output matches Babylon. + scene.imageProcessing.toneMappingEnabled = true; + scene.imageProcessing.exposure = 0.8; + scene.imageProcessing.contrast = 1.2; + }; + + return { _sceneSetup }; + }, +}; +export default feature; diff --git a/packages/babylon-lite/src/loader-gltf/gltf-feature-registry.ts b/packages/babylon-lite/src/loader-gltf/gltf-feature-registry.ts index 4f0b09dd05..6a4b650518 100644 --- a/packages/babylon-lite/src/loader-gltf/gltf-feature-registry.ts +++ b/packages/babylon-lite/src/loader-gltf/gltf-feature-registry.ts @@ -57,6 +57,7 @@ const _features: [Trigger, Loader][] = [ // Per-asset features [hasGltfExtras, () => import("./gltf-feature-extras.js")], ["KHR_lights_punctual", () => import("./gltf-feature-lights-punctual.js")], + ["EXT_lights_image_based", () => import("./gltf-ext-lights-image-based.js")], [(j) => !!j.animations?.length, () => import("./gltf-feature-animations.js")], // Non-Float32 / normalized animation sampler accessors (e.g. Animation_SamplerType normalized // BYTE/SHORT rotation) need the lazy denorm converter; plain float samplers never load it. diff --git a/packages/babylon-lite/src/scene/scene-core.ts b/packages/babylon-lite/src/scene/scene-core.ts index 1a889c08a2..cc35f7e49f 100644 --- a/packages/babylon-lite/src/scene/scene-core.ts +++ b/packages/babylon-lite/src/scene/scene-core.ts @@ -332,6 +332,10 @@ export function addToScene(scene: SceneContext, entity: Mesh | LightBase | Camer } }); } + // Feature-owned scene wiring (e.g. EXT_lights_image_based installs its IBL + // environment). Runs synchronously so the environment is registered before + // registerScene() builds the scene UBO / PBR renderables. + result._sceneSetup?.(ctx); return; } if ("_gpu" in entity && "material" in entity) { diff --git a/reference/lite/scene264-environment-test/babylon-ref-golden.png b/reference/lite/scene264-environment-test/babylon-ref-golden.png new file mode 100644 index 0000000000000000000000000000000000000000..564e10fe54a52ff8d9edda66f6eeeff8bc190350 GIT binary patch literal 56170 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 { + const browser = page.context().browser()!; + await captureGolden(browser, { sceneId: 264, timeout: 90_000 }); + + await page.goto("/scene264.html"); + await page.waitForFunction(() => document.querySelector("canvas")?.dataset.ready === "true", { timeout: 60_000 }); + await page.waitForTimeout(500); + + const screenshotPath = path.join(REFERENCE_DIR, "test-actual.png"); + await page.locator("canvas").screenshot({ path: screenshotPath }); + + const full = compareImages(screenshotPath, GOLDEN_REF); + await attachCompareArtifacts(testInfo, screenshotPath, GOLDEN_REF, REFERENCE_DIR); + console.log(`Full image MAD: ${full.mad.toFixed(3)} (limit ${sceneConfig.maxMad})`); + expect(full.mad, `Full image MAD should be <= ${sceneConfig.maxMad}`).toBeLessThanOrEqual(sceneConfig.maxMad); +}); From fa60a73483b91114a1d8f4e315020b8ca2c75d02 Mon Sep 17 00:00:00 2001 From: Raanan Weber Date: Thu, 9 Jul 2026 22:41:03 +0200 Subject: [PATCH 02/10] perf(loader): isolate EXT_lights_image_based cubemap uploader Give the EXT_lights_image_based feature a private RGBD cubemap uploader (ibl-cubemap-upload.ts) instead of importing uploadCubemapRGBD from the shared loader-env/rgbd-decode.js. rgbd-decode is a static dependency of the .env loader, so a second (extension) consumer pinned the otherwise tree-shaken cubemap path into every .env/DDS scene's bundle (~1 KB). The private copy is reachable only from this lazily-imported feature chunk, so non-IBL scenes pay zero bytes for it and only EXT_lights_image_based scenes fetch it. scene264 stays under its ceiling (80.1/82 KB); parity MAD 0.461. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- lab/public/bundle/manifest/scene264.json | 10 +- .../gltf-ext-lights-image-based.ts | 12 ++- .../src/loader-gltf/ibl-cubemap-upload.ts | 100 ++++++++++++++++++ 3 files changed, 112 insertions(+), 10 deletions(-) create mode 100644 packages/babylon-lite/src/loader-gltf/ibl-cubemap-upload.ts diff --git a/lab/public/bundle/manifest/scene264.json b/lab/public/bundle/manifest/scene264.json index 28f84df779..cc43bee12e 100644 --- a/lab/public/bundle/manifest/scene264.json +++ b/lab/public/bundle/manifest/scene264.json @@ -1,17 +1,17 @@ { - "rawKB": 80.2, - "gzipKB": 33.6, + "rawKB": 80.1, + "gzipKB": 33.5, "ignoredRawKB": 0, "runtimeChunks": [ "scene264-env-helpers-23YvOcOj.js", "scene264-generate-mipmaps-KrqAJzxS.js", - "scene264-gltf-ext-lights-image-based-Bl2jmRGe.js", - "scene264-gltf-feature-registry-RAFggZxh.js", + "scene264-gltf-ext-lights-image-based-ClJe5F5q.js", + "scene264-gltf-feature-registry-DzmBjAKy.js", "scene264-gltf-json-asset-OTC4RQu_.js", "scene264-hdr-ibl-pipeline-JseuEIIG.js", + "scene264-ibl-cubemap-upload-B9Q6v-do.js", "scene264-ibl-fragment-CeRrvT96.js", "scene264-pbr-renderable-CskWgmPH.js", - "scene264-rgbd-decode-C0Son7dU.js", "scene264-samplers-DRa_c529.js", "scene264-scene-ubo-extras-B5B4aSOt.js", "scene264.js" diff --git a/packages/babylon-lite/src/loader-gltf/gltf-ext-lights-image-based.ts b/packages/babylon-lite/src/loader-gltf/gltf-ext-lights-image-based.ts index 4be647896d..1febfbe697 100644 --- a/packages/babylon-lite/src/loader-gltf/gltf-ext-lights-image-based.ts +++ b/packages/babylon-lite/src/loader-gltf/gltf-ext-lights-image-based.ts @@ -27,10 +27,12 @@ import type { SceneContext } from "../scene/scene-core.js"; // NOTE: this module is a lazily-imported feature chunk (see gltf-feature-registry) // and keeps ZERO static runtime imports — every dependency is pulled in via dynamic // import inside applyAsset. Scenes that never trigger EXT_lights_image_based never -// fetch this chunk (or its dependencies) at runtime. Its reuse of the shared IBL -// modules (env-helpers, hdr-ibl-pipeline, rgbd-decode, samplers) does, however, -// participate in rollup's static chunk graph for every gltf build, which can hoist -// those shared leaves into standalone chunks — see the PR notes on bundle-size. +// fetch this chunk (or its dependencies) at runtime. The cubemap uploader is kept +// private (ibl-cubemap-upload.js) rather than shared with loader-env/rgbd-decode so +// the extension does not pin that decoder into every .env/DDS scene. Its reuse of +// env-helpers / hdr-ibl-pipeline still participates in rollup's static chunk graph +// for every gltf build, which can hoist shared leaves (e.g. samplers) into their own +// chunk — see the PR notes on bundle-size. interface GltfImageBasedLight { name?: string; @@ -113,7 +115,7 @@ const feature: GltfFeature = { const { resolveImage } = await import("./gltf-parser.js"); const faceImages = await Promise.all(flatIndices.map((imgIdx) => resolveImage(json, ctx._binChunk, imgIdx, ctx._baseUrl))); - const { uploadCubemapRGBD } = await import("../loader-env/rgbd-decode.js"); + const { uploadCubemapRGBD } = await import("./ibl-cubemap-upload.js"); const specularCube = uploadCubemapRGBD(engine, faceImages, specularImageSize, mipCount); for (const img of faceImages) { img.close(); diff --git a/packages/babylon-lite/src/loader-gltf/ibl-cubemap-upload.ts b/packages/babylon-lite/src/loader-gltf/ibl-cubemap-upload.ts new file mode 100644 index 0000000000..f3bcc8f401 --- /dev/null +++ b/packages/babylon-lite/src/loader-gltf/ibl-cubemap-upload.ts @@ -0,0 +1,100 @@ +/** RGBD cubemap uploader for EXT_lights_image_based. + * + * A self-contained copy of the RGBD `->` rgba16float cubemap decode used by the + * image-based-lights extension. It is intentionally NOT shared with + * `loader-env/rgbd-decode.ts`: that module is a static dependency of the `.env` + * loader (`load-env.ts`) and is therefore inlined into every environment scene's + * chunk graph. Importing `uploadCubemapRGBD` from it would make the extension a + * second consumer of that shared module, pinning the (otherwise tree-shaken) + * cubemap path into every `.env`/DDS scene's bundle. Keeping a private copy here + * — reachable only from this lazily-imported feature chunk — means non-IBL scenes + * pay zero bytes for it and only `EXT_lights_image_based` scenes fetch it. */ + +import { TU } from "../engine/gpu-flags.js"; +import type { EngineContext } from "../engine/engine.js"; + +const WGSL = `override f:bool=false;@group(0)@binding(0)var t:texture_2d;@group(0)@binding(1)var o:texture_storage_2d;@compute @workgroup_size(8,8)fn main(@builtin(global_invocation_id)g:vec3u){let d=textureDimensions(t);if(any(g.xy>=d)){return;}let c=textureLoad(t,vec2u(g.x,select(g.y,d.y-1u-g.y,f)),0);textureStore(o,g.xy,vec4f(pow(c.rgb,vec3f(2.2))/max(c.a,1.0/255.0),1));}`; + +let _device: GPUDevice | null = null; +let _module: GPUShaderModule | null = null; +let _pipeline: GPUComputePipeline | null = null; + +function getPipeline(device: GPUDevice): GPUComputePipeline { + if (device !== _device) { + _device = device; + _module = device.createShaderModule({ code: WGSL }); + _pipeline = null; + } + if (_pipeline) { + return _pipeline; + } + _pipeline = device.createComputePipeline({ + layout: "auto", + compute: { module: _module!, entryPoint: "main", constants: { f: 1 } }, + }); + return _pipeline; +} + +/** Decode and upload a RGBD cubemap (6 faces × N mips) → rgba16float cube texture. + * Y-flipped on read (BJS uploads cubemap faces with invertY=true). */ +export function uploadCubemapRGBD(engine: EngineContext, images: ImageBitmap[], width: number, mipCount: number): GPUTexture { + const device = engine._device; + const pipeline = getPipeline(device); + + const texture = device.createTexture({ + size: { width, height: width, depthOrArrayLayers: 6 }, + format: "rgba16float", + mipLevelCount: mipCount, + usage: TU.TEXTURE_BINDING | TU.COPY_DST | TU.COPY_SRC | TU.RENDER_ATTACHMENT, + dimension: "2d", + }); + + for (let mip = 0; mip < mipCount; mip++) { + const mipSize = Math.max(1, width >> mip); + + const inputTex = device.createTexture({ + size: { width: mipSize, height: mipSize }, + format: "rgba8unorm", + usage: TU.TEXTURE_BINDING | TU.COPY_DST | TU.RENDER_ATTACHMENT, + }); + + const outputTex = device.createTexture({ + size: { width: mipSize, height: mipSize }, + format: "rgba16float", + usage: TU.STORAGE_BINDING | TU.COPY_SRC, + }); + + const bindGroup = device.createBindGroup({ + layout: pipeline.getBindGroupLayout(0), + entries: [ + { binding: 0, resource: inputTex.createView() }, + { binding: 1, resource: outputTex.createView() }, + ], + }); + + for (let face = 0; face < 6; face++) { + const idx = mip * 6 + face; + if (idx >= images.length) { + break; + } + + device.queue.copyExternalImageToTexture({ source: images[idx]!, flipY: false }, { texture: inputTex, premultipliedAlpha: false }, { width: mipSize, height: mipSize }); + + const encoder = device.createCommandEncoder(); + const pass = encoder.beginComputePass(); + pass.setPipeline(pipeline); + pass.setBindGroup(0, bindGroup); + pass.dispatchWorkgroups(Math.ceil(mipSize / 8), Math.ceil(mipSize / 8)); + pass.end(); + encoder.copyTextureToTexture({ texture: outputTex }, { texture, origin: { x: 0, y: 0, z: face }, mipLevel: mip }, { width: mipSize, height: mipSize }); + + // One submit per face ensures sequential hazards on the reused input/output. + device.queue.submit([encoder.finish()]); + } + + inputTex.destroy(); + outputTex.destroy(); + } + + return texture; +} From b9f3e538d82b5072ebd09331660d20c87f969b04 Mon Sep 17 00:00:00 2001 From: Raanan Weber Date: Fri, 10 Jul 2026 14:33:50 +0200 Subject: [PATCH 03/10] perf(loader): isolate EXT_lights_image_based IBL cost to its own chunk Duplicate the shared IBL helpers (env-texture assembly, BRDF LUT, SH conversion, samplers, resolveImage, env scene-UBO wiring) into a private ibl-env-assembly.ts reachable only from the lazy EXT_lights_image_based feature chunk, and destroy the feature's env textures directly instead of via the gpu-pool refcount path. This stops the feature from becoming a second static consumer of single-consumer leaves, so rollup keeps the 43 non-IBL glTF scenes' inlined copies untouched (zero bundle delta) and the feature's byte cost lands only on the scene that uses it. Deliberate, user-approved exception to the max-reuse mandate; documented in both modules with canonical-source mapping. Bundle result: 209/211 scenes at/under ceiling; only scene41 and scene47 drift +0.1 KB (the irreducible per-scene gltf-feature-registry entry). scene264 (EnvironmentTest) at 79.7 KB, under its 82 KB ceiling. Parity unchanged (scene264 MAD 0.461). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- lab/public/bundle/manifest/scene104.json | 4 +- lab/public/bundle/manifest/scene105.json | 6 +- lab/public/bundle/manifest/scene11.json | 6 +- lab/public/bundle/manifest/scene112.json | 4 +- lab/public/bundle/manifest/scene115.json | 4 +- lab/public/bundle/manifest/scene12.json | 4 +- lab/public/bundle/manifest/scene144.json | 6 +- lab/public/bundle/manifest/scene147.json | 4 +- lab/public/bundle/manifest/scene148.json | 4 +- lab/public/bundle/manifest/scene149.json | 4 +- lab/public/bundle/manifest/scene152.json | 4 +- lab/public/bundle/manifest/scene157.json | 6 +- lab/public/bundle/manifest/scene158.json | 4 +- lab/public/bundle/manifest/scene164.json | 4 +- lab/public/bundle/manifest/scene176.json | 4 +- lab/public/bundle/manifest/scene178.json | 4 +- lab/public/bundle/manifest/scene210.json | 6 +- lab/public/bundle/manifest/scene211.json | 4 +- lab/public/bundle/manifest/scene212.json | 6 +- lab/public/bundle/manifest/scene218.json | 6 +- lab/public/bundle/manifest/scene219.json | 4 +- lab/public/bundle/manifest/scene240.json | 4 +- lab/public/bundle/manifest/scene241.json | 6 +- lab/public/bundle/manifest/scene242.json | 4 +- lab/public/bundle/manifest/scene243.json | 6 +- lab/public/bundle/manifest/scene244.json | 4 +- lab/public/bundle/manifest/scene245.json | 4 +- lab/public/bundle/manifest/scene246.json | 4 +- lab/public/bundle/manifest/scene247.json | 6 +- lab/public/bundle/manifest/scene251.json | 6 +- lab/public/bundle/manifest/scene253.json | 4 +- lab/public/bundle/manifest/scene254.json | 6 +- lab/public/bundle/manifest/scene255.json | 4 +- lab/public/bundle/manifest/scene257.json | 4 +- lab/public/bundle/manifest/scene260.json | 4 +- lab/public/bundle/manifest/scene264.json | 17 +- lab/public/bundle/manifest/scene27.json | 4 +- lab/public/bundle/manifest/scene28.json | 4 +- lab/public/bundle/manifest/scene29.json | 6 +- lab/public/bundle/manifest/scene30.json | 4 +- lab/public/bundle/manifest/scene31.json | 6 +- lab/public/bundle/manifest/scene32.json | 6 +- lab/public/bundle/manifest/scene33.json | 4 +- lab/public/bundle/manifest/scene34.json | 6 +- lab/public/bundle/manifest/scene35.json | 6 +- lab/public/bundle/manifest/scene37.json | 4 +- lab/public/bundle/manifest/scene39.json | 4 +- lab/public/bundle/manifest/scene41.json | 4 +- lab/public/bundle/manifest/scene47.json | 4 +- lab/public/bundle/manifest/scene5.json | 4 +- lab/public/bundle/manifest/scene7.json | 4 +- lab/public/bundle/manifest/scene73.json | 6 +- lab/public/bundle/manifest/scene99.json | 6 +- .../gltf-ext-lights-image-based.ts | 41 ++-- .../src/loader-gltf/ibl-env-assembly.ts | 180 ++++++++++++++++++ 55 files changed, 334 insertions(+), 150 deletions(-) create mode 100644 packages/babylon-lite/src/loader-gltf/ibl-env-assembly.ts diff --git a/lab/public/bundle/manifest/scene104.json b/lab/public/bundle/manifest/scene104.json index 898967446e..f6c1c2bc19 100644 --- a/lab/public/bundle/manifest/scene104.json +++ b/lab/public/bundle/manifest/scene104.json @@ -1,10 +1,10 @@ { - "rawKB": 102, + "rawKB": 102.1, "gzipKB": 39.5, "ignoredRawKB": 0, "runtimeChunks": [ "scene104-generate-mipmaps-BGv6wRC-.js", - "scene104-gltf-feature-registry-DwXBTigv.js", + "scene104-gltf-feature-registry-Cbi5zHyM.js", "scene104-gltf-glb-parser-BJfNq64D.js", "scene104-shader-composer-CgJmQyJj.js", "scene104-standard-renderable-DyCnrjVB.js", diff --git a/lab/public/bundle/manifest/scene105.json b/lab/public/bundle/manifest/scene105.json index 55f9f48b16..53bbc057a2 100644 --- a/lab/public/bundle/manifest/scene105.json +++ b/lab/public/bundle/manifest/scene105.json @@ -1,10 +1,10 @@ { - "rawKB": 102.7, - "gzipKB": 39.7, + "rawKB": 102.8, + "gzipKB": 39.8, "ignoredRawKB": 0, "runtimeChunks": [ "scene105-generate-mipmaps-Bw7xZIhD.js", - "scene105-gltf-feature-registry-CR980RoJ.js", + "scene105-gltf-feature-registry-CygnIXOd.js", "scene105-gltf-glb-parser-BsWC1iTB.js", "scene105-shader-composer-CgJmQyJj.js", "scene105-standard-renderable-SIpbaDAl.js", diff --git a/lab/public/bundle/manifest/scene11.json b/lab/public/bundle/manifest/scene11.json index eb2e8dcb04..2469252573 100644 --- a/lab/public/bundle/manifest/scene11.json +++ b/lab/public/bundle/manifest/scene11.json @@ -1,6 +1,6 @@ { - "rawKB": 88.9, - "gzipKB": 37.3, + "rawKB": 89, + "gzipKB": 37.4, "ignoredRawKB": 0, "runtimeChunks": [ "scene11-create-skeleton-D7GjlnE9.js", @@ -9,7 +9,7 @@ "scene11-gltf-ext-spec-gloss-D60sFZYX.js", "scene11-gltf-feature-animations-Ah3qbbGd.js", "scene11-gltf-feature-extras-VHPteGay.js", - "scene11-gltf-feature-registry-D6AVav_g.js", + "scene11-gltf-feature-registry-ByKwsRHs.js", "scene11-gltf-feature-skeleton-CgO1jgOS.js", "scene11-gltf-glb-parser-B0CzN8nR.js", "scene11-gltf-sampler-desc-DP9TFn1W.js", diff --git a/lab/public/bundle/manifest/scene112.json b/lab/public/bundle/manifest/scene112.json index 326a0400de..fab215828c 100644 --- a/lab/public/bundle/manifest/scene112.json +++ b/lab/public/bundle/manifest/scene112.json @@ -1,5 +1,5 @@ { - "rawKB": 120.6, + "rawKB": 120.7, "gzipKB": 48.9, "ignoredRawKB": 0, "runtimeChunks": [ @@ -9,7 +9,7 @@ "scene112-generate-mipmaps-r2o5vX6L.js", "scene112-gltf-ext-basisu-CRSWpXls.js", "scene112-gltf-ext-dielectric-C6hvXXiF.js", - "scene112-gltf-feature-registry-C0IDwRtI.js", + "scene112-gltf-feature-registry-TY3VrTqV.js", "scene112-gltf-json-asset-CBZ_Aalp.js", "scene112-ibl-fragment-CeRrvT96.js", "scene112-pbr-refraction-UBFpPYrf.js", diff --git a/lab/public/bundle/manifest/scene115.json b/lab/public/bundle/manifest/scene115.json index 8ce02e8d2f..069edf5ae7 100644 --- a/lab/public/bundle/manifest/scene115.json +++ b/lab/public/bundle/manifest/scene115.json @@ -1,5 +1,5 @@ { - "rawKB": 126.2, + "rawKB": 126.3, "gzipKB": 51.9, "ignoredRawKB": 0, "runtimeChunks": [ @@ -10,7 +10,7 @@ "scene115-gltf-color-normalize-4-WGzPL2.js", "scene115-gltf-feature-animations-DAeYetsL.js", "scene115-gltf-feature-morph-DLWJRlR1.js", - "scene115-gltf-feature-registry-CKfMtIY7.js", + "scene115-gltf-feature-registry-nG6paoAl.js", "scene115-gltf-feature-skeleton-DnB7dCA_.js", "scene115-gltf-json-asset-D9q5e9Ta.js", "scene115-morph-fragment-D0omyTLw.js", diff --git a/lab/public/bundle/manifest/scene12.json b/lab/public/bundle/manifest/scene12.json index 58067ea75f..290016d0e5 100644 --- a/lab/public/bundle/manifest/scene12.json +++ b/lab/public/bundle/manifest/scene12.json @@ -1,5 +1,5 @@ { - "rawKB": 103.4, + "rawKB": 103.5, "gzipKB": 42.2, "ignoredRawKB": 0, "runtimeChunks": [ @@ -7,7 +7,7 @@ "scene12-generate-mipmaps-Bsp4UQ3D.js", "scene12-gltf-animation-B52vCe_y.js", "scene12-gltf-feature-animations-mg2hSyya.js", - "scene12-gltf-feature-registry-CwS0X5va.js", + "scene12-gltf-feature-registry-D-RPz0hr.js", "scene12-gltf-feature-skeleton-Buu6DPVS.js", "scene12-gltf-glb-parser-BrsOlymf.js", "scene12-ibl-fragment-CeRrvT96.js", diff --git a/lab/public/bundle/manifest/scene144.json b/lab/public/bundle/manifest/scene144.json index f2ec767e08..12550c4914 100644 --- a/lab/public/bundle/manifest/scene144.json +++ b/lab/public/bundle/manifest/scene144.json @@ -1,6 +1,6 @@ { - "rawKB": 110.4, - "gzipKB": 44.6, + "rawKB": 110.5, + "gzipKB": 44.7, "ignoredRawKB": 0, "runtimeChunks": [ "scene144-create-skeleton-pj3iqxnA.js", @@ -9,7 +9,7 @@ "scene144-gltf-ext-spec-gloss-D60sFZYX.js", "scene144-gltf-feature-animations-Ci3bwd2g.js", "scene144-gltf-feature-extras-VHPteGay.js", - "scene144-gltf-feature-registry-JawfCB1w.js", + "scene144-gltf-feature-registry-DqkX9qmc.js", "scene144-gltf-feature-skeleton-DVin9DRx.js", "scene144-gltf-glb-parser-CXf6hcyj.js", "scene144-gltf-pbr-builder-ext-g9qXikef.js", diff --git a/lab/public/bundle/manifest/scene147.json b/lab/public/bundle/manifest/scene147.json index 40e67a8c2c..debf57c5ba 100644 --- a/lab/public/bundle/manifest/scene147.json +++ b/lab/public/bundle/manifest/scene147.json @@ -1,12 +1,12 @@ { - "rawKB": 103.7, + "rawKB": 103.8, "gzipKB": 41.5, "ignoredRawKB": 0, "runtimeChunks": [ "scene147-directional-light-CwjabfgT.js", "scene147-generate-mipmaps-BfjMA7hX.js", "scene147-gltf-feature-lights-punctual-DdmW_Vp5.js", - "scene147-gltf-feature-registry-CuEQdmGB.js", + "scene147-gltf-feature-registry-DkDsZsOv.js", "scene147-gltf-glb-parser-BPCGAatE.js", "scene147-gltf-light-pointer-state-B7kFTRxK.js", "scene147-multilight-wgsl-CLgjeQ_G.js", diff --git a/lab/public/bundle/manifest/scene148.json b/lab/public/bundle/manifest/scene148.json index 34527a8361..01d7e0034e 100644 --- a/lab/public/bundle/manifest/scene148.json +++ b/lab/public/bundle/manifest/scene148.json @@ -1,12 +1,12 @@ { - "rawKB": 109.6, + "rawKB": 109.7, "gzipKB": 43, "ignoredRawKB": 0, "runtimeChunks": [ "scene148-directional-light-DQDzxnQb.js", "scene148-generate-mipmaps-LgSJe-aH.js", "scene148-gltf-feature-lights-punctual-BLCC9HwV.js", - "scene148-gltf-feature-registry-B0bz4Bga.js", + "scene148-gltf-feature-registry-cyqR5Ryc.js", "scene148-gltf-glb-parser-B33Edg7S.js", "scene148-gltf-light-pointer-state-B7kFTRxK.js", "scene148-pbr-geometry-view-M_f3olN0.js", diff --git a/lab/public/bundle/manifest/scene149.json b/lab/public/bundle/manifest/scene149.json index 0ffb8d8b9a..5f0c29f6e6 100644 --- a/lab/public/bundle/manifest/scene149.json +++ b/lab/public/bundle/manifest/scene149.json @@ -1,5 +1,5 @@ { - "rawKB": 109.1, + "rawKB": 109.2, "gzipKB": 44.6, "ignoredRawKB": 6.8, "runtimeChunks": [ @@ -9,7 +9,7 @@ "scene149-generate-mipmaps-D6ALG8gN.js", "scene149-geometry-texture-output-BfeQftmX.js", "scene149-gltf-feature-lights-punctual-w2wC-7RK.js", - "scene149-gltf-feature-registry-CnklNaiP.js", + "scene149-gltf-feature-registry-BVbo0osC.js", "scene149-gltf-glb-parser-Cwf9MN9N.js", "scene149-gltf-light-pointer-state-B7kFTRxK.js", "scene149-input-block-DAEnTkZz.js", diff --git a/lab/public/bundle/manifest/scene152.json b/lab/public/bundle/manifest/scene152.json index a13ff396f0..d0f38d58a2 100644 --- a/lab/public/bundle/manifest/scene152.json +++ b/lab/public/bundle/manifest/scene152.json @@ -1,5 +1,5 @@ { - "rawKB": 94, + "rawKB": 94.1, "gzipKB": 39.1, "ignoredRawKB": 0, "runtimeChunks": [ @@ -9,7 +9,7 @@ "scene152-gltf-ext-spec-gloss-D60sFZYX.js", "scene152-gltf-feature-animations-D3TKVRLO.js", "scene152-gltf-feature-extras-VHPteGay.js", - "scene152-gltf-feature-registry-B4mQ-NA8.js", + "scene152-gltf-feature-registry-BaN62s2y.js", "scene152-gltf-feature-skeleton-Dycakb4m.js", "scene152-gltf-glb-parser-Dr_B2hXm.js", "scene152-gltf-sampler-desc-D0Q2VM93.js", diff --git a/lab/public/bundle/manifest/scene157.json b/lab/public/bundle/manifest/scene157.json index 7786bbc698..ca546dc226 100644 --- a/lab/public/bundle/manifest/scene157.json +++ b/lab/public/bundle/manifest/scene157.json @@ -1,13 +1,13 @@ { - "rawKB": 95.4, - "gzipKB": 38.8, + "rawKB": 95.5, + "gzipKB": 38.9, "ignoredRawKB": 0, "runtimeChunks": [ "scene157-create-skeleton-CkTxS2Cs.js", "scene157-generate-mipmaps-BYHWBGCp.js", "scene157-gltf-animation-PnQa26Zh.js", "scene157-gltf-feature-animations-CW9BmVG9.js", - "scene157-gltf-feature-registry-GpisHBJE.js", + "scene157-gltf-feature-registry-Ba3aPcJ3.js", "scene157-gltf-feature-skeleton-DBoRQhCC.js", "scene157-gltf-glb-parser-DZ4Xgxsa.js", "scene157-multilight-wgsl-BVR6TJvC.js", diff --git a/lab/public/bundle/manifest/scene158.json b/lab/public/bundle/manifest/scene158.json index 323f223045..9db6cda975 100644 --- a/lab/public/bundle/manifest/scene158.json +++ b/lab/public/bundle/manifest/scene158.json @@ -1,5 +1,5 @@ { - "rawKB": 95.9, + "rawKB": 96, "gzipKB": 39, "ignoredRawKB": 0, "runtimeChunks": [ @@ -7,7 +7,7 @@ "scene158-generate-mipmaps-bNvmU0dI.js", "scene158-gltf-animation-D4bTYTv1.js", "scene158-gltf-feature-animations-D0VA15ba.js", - "scene158-gltf-feature-registry-CL7XMoSu.js", + "scene158-gltf-feature-registry-BVKfNzy9.js", "scene158-gltf-feature-skeleton-BF_AXZXo.js", "scene158-gltf-glb-parser-BhqojsL9.js", "scene158-multilight-wgsl-DtA6qLNj.js", diff --git a/lab/public/bundle/manifest/scene164.json b/lab/public/bundle/manifest/scene164.json index a0b37732ff..9598467231 100644 --- a/lab/public/bundle/manifest/scene164.json +++ b/lab/public/bundle/manifest/scene164.json @@ -1,5 +1,5 @@ { - "rawKB": 98.5, + "rawKB": 98.6, "gzipKB": 40.8, "ignoredRawKB": 0, "runtimeChunks": [ @@ -10,7 +10,7 @@ "scene164-gltf-color-normalize-4-WGzPL2.js", "scene164-gltf-feature-animations-BBCk7Uhp.js", "scene164-gltf-feature-morph-Dcma2yce.js", - "scene164-gltf-feature-registry-ETAD7yc2.js", + "scene164-gltf-feature-registry-R9Cl9Cjm.js", "scene164-gltf-feature-skeleton-B5SJY-Kk.js", "scene164-gltf-json-asset-BCxbuHbB.js", "scene164-morph-fragment-uMd1sOoJ.js", diff --git a/lab/public/bundle/manifest/scene176.json b/lab/public/bundle/manifest/scene176.json index 9953133848..c1f7cf89c5 100644 --- a/lab/public/bundle/manifest/scene176.json +++ b/lab/public/bundle/manifest/scene176.json @@ -1,12 +1,12 @@ { - "rawKB": 103.9, + "rawKB": 104, "gzipKB": 41.8, "ignoredRawKB": 0, "runtimeChunks": [ "scene176-generate-mipmaps-Dse5kXpK.js", "scene176-gltf-ext-dielectric-C6hvXXiF.js", "scene176-gltf-feature-extras-VHPteGay.js", - "scene176-gltf-feature-registry-HsPw4vNE.js", + "scene176-gltf-feature-registry-DxBYyndf.js", "scene176-gltf-json-asset-CVMNQwdq.js", "scene176-ibl-fragment-CeRrvT96.js", "scene176-ibl-skybox-wgsl-CFIBOHHx.js", diff --git a/lab/public/bundle/manifest/scene178.json b/lab/public/bundle/manifest/scene178.json index 7d46bbc265..8ceab576ca 100644 --- a/lab/public/bundle/manifest/scene178.json +++ b/lab/public/bundle/manifest/scene178.json @@ -1,11 +1,11 @@ { - "rawKB": 87.7, + "rawKB": 87.8, "gzipKB": 35.7, "ignoredRawKB": 0, "runtimeChunks": [ "scene178-generate-mipmaps-MoaS2Mzr.js", "scene178-gltf-ext-iridescence-b4LVniAM.js", - "scene178-gltf-feature-registry-BNPKmHxQ.js", + "scene178-gltf-feature-registry-CGPtvGle.js", "scene178-gltf-glb-parser-d5msxooD.js", "scene178-ibl-fragment-CeRrvT96.js", "scene178-ibl-skybox-wgsl-CFIBOHHx.js", diff --git a/lab/public/bundle/manifest/scene210.json b/lab/public/bundle/manifest/scene210.json index 923688dead..7dbf9e6331 100644 --- a/lab/public/bundle/manifest/scene210.json +++ b/lab/public/bundle/manifest/scene210.json @@ -1,10 +1,10 @@ { - "rawKB": 76.8, - "gzipKB": 31.8, + "rawKB": 76.9, + "gzipKB": 31.9, "ignoredRawKB": 0, "runtimeChunks": [ "scene210-generate-mipmaps-BeLXoJVY.js", - "scene210-gltf-feature-registry-BOK-pJDV.js", + "scene210-gltf-feature-registry-qxaa4rPD.js", "scene210-gltf-feature-xmp-S8Tl-XNQ.js", "scene210-gltf-glb-parser-fJOQf1ny.js", "scene210-gltf-interleave-DEtV3KE6.js", diff --git a/lab/public/bundle/manifest/scene211.json b/lab/public/bundle/manifest/scene211.json index 8198bf3915..6e3fea4513 100644 --- a/lab/public/bundle/manifest/scene211.json +++ b/lab/public/bundle/manifest/scene211.json @@ -1,5 +1,5 @@ { - "rawKB": 90.2, + "rawKB": 90.3, "gzipKB": 38, "ignoredRawKB": 0, "runtimeChunks": [ @@ -9,7 +9,7 @@ "scene211-gltf-ext-quantization-Do-ofjB-.js", "scene211-gltf-feature-animations-DFJye6J0.js", "scene211-gltf-feature-meshopt-Dt65KW5n.js", - "scene211-gltf-feature-registry-D_7EzYLo.js", + "scene211-gltf-feature-registry-D9TYLORM.js", "scene211-gltf-feature-skeleton-CvU1ia6O.js", "scene211-gltf-json-asset-_1B5yK0t.js", "scene211-gltf-multi-buffer-Cwm5u1Qq.js", diff --git a/lab/public/bundle/manifest/scene212.json b/lab/public/bundle/manifest/scene212.json index f3c7df588a..f804f3766f 100644 --- a/lab/public/bundle/manifest/scene212.json +++ b/lab/public/bundle/manifest/scene212.json @@ -1,11 +1,11 @@ { - "rawKB": 104.2, - "gzipKB": 41.7, + "rawKB": 104.3, + "gzipKB": 41.8, "ignoredRawKB": 0, "runtimeChunks": [ "scene212-generate-mipmaps-BA4GCk3y.js", "scene212-gltf-ext-dielectric-C6hvXXiF.js", - "scene212-gltf-feature-registry-Ch23mwuV.js", + "scene212-gltf-feature-registry-9KZxt4Ry.js", "scene212-gltf-glb-parser-DpbXuAuC.js", "scene212-ibl-fragment-CeRrvT96.js", "scene212-ibl-skybox-wgsl-CFIBOHHx.js", diff --git a/lab/public/bundle/manifest/scene218.json b/lab/public/bundle/manifest/scene218.json index 42acd497ba..2dae48f217 100644 --- a/lab/public/bundle/manifest/scene218.json +++ b/lab/public/bundle/manifest/scene218.json @@ -1,6 +1,6 @@ { - "rawKB": 92.5, - "gzipKB": 38.5, + "rawKB": 92.6, + "gzipKB": 38.6, "ignoredRawKB": 0, "runtimeChunks": [ "scene218-create-skeleton-Dzggqla-.js", @@ -9,7 +9,7 @@ "scene218-gltf-ext-spec-gloss-D60sFZYX.js", "scene218-gltf-feature-animations-B-zqkkUE.js", "scene218-gltf-feature-extras-VHPteGay.js", - "scene218-gltf-feature-registry-hHAUfgir.js", + "scene218-gltf-feature-registry-BVeb_1XQ.js", "scene218-gltf-feature-skeleton-D09Z9z9B.js", "scene218-gltf-glb-parser-BXHcqEF1.js", "scene218-gltf-sampler-desc-CS2bAJuk.js", diff --git a/lab/public/bundle/manifest/scene219.json b/lab/public/bundle/manifest/scene219.json index 6fc8f06285..1832b61cce 100644 --- a/lab/public/bundle/manifest/scene219.json +++ b/lab/public/bundle/manifest/scene219.json @@ -1,5 +1,5 @@ { - "rawKB": 95, + "rawKB": 95.1, "gzipKB": 39.7, "ignoredRawKB": 0, "runtimeChunks": [ @@ -9,7 +9,7 @@ "scene219-gltf-ext-spec-gloss-D60sFZYX.js", "scene219-gltf-feature-animations-Dyoe_yY7.js", "scene219-gltf-feature-extras-VHPteGay.js", - "scene219-gltf-feature-registry-CWrb_rBs.js", + "scene219-gltf-feature-registry-DRPBY7Th.js", "scene219-gltf-feature-skeleton-D8TxDI88.js", "scene219-gltf-glb-parser-PDyH09Yg.js", "scene219-gltf-sampler-desc-CKWNJQQv.js", diff --git a/lab/public/bundle/manifest/scene240.json b/lab/public/bundle/manifest/scene240.json index ceba26d166..a3d438bf79 100644 --- a/lab/public/bundle/manifest/scene240.json +++ b/lab/public/bundle/manifest/scene240.json @@ -1,5 +1,5 @@ { - "rawKB": 90.8, + "rawKB": 90.9, "gzipKB": 37.9, "ignoredRawKB": 0, "runtimeChunks": [ @@ -7,7 +7,7 @@ "scene240-generate-mipmaps-BQZ9ivTX.js", "scene240-gltf-animation-aBVjSk7N.js", "scene240-gltf-feature-animations-CVhWeNLT.js", - "scene240-gltf-feature-registry-Qez-HwfU.js", + "scene240-gltf-feature-registry-BJPaRqTC.js", "scene240-gltf-json-asset-wZq70j5q.js", "scene240-gltf-multi-buffer-Cwm5u1Qq.js", "scene240-gltf-normals-DYhLwXNX.js", diff --git a/lab/public/bundle/manifest/scene241.json b/lab/public/bundle/manifest/scene241.json index 71e1118ce9..f641afd876 100644 --- a/lab/public/bundle/manifest/scene241.json +++ b/lab/public/bundle/manifest/scene241.json @@ -1,6 +1,6 @@ { - "rawKB": 161.6, - "gzipKB": 66, + "rawKB": 161.7, + "gzipKB": 66.1, "ignoredRawKB": 0, "runtimeChunks": [ "scene241-alpha-test-fragment-BpzeKSEk.js", @@ -21,7 +21,7 @@ "scene241-gltf-feature-animation-pointer-BwPD7ki5.js", "scene241-gltf-feature-animations-BOZCaeEQ.js", "scene241-gltf-feature-lights-punctual-DjeruV0x.js", - "scene241-gltf-feature-registry-BfFM7Bs6.js", + "scene241-gltf-feature-registry-_N02IgMk.js", "scene241-gltf-json-asset-BI5mxnCG.js", "scene241-gltf-light-pointer-state-B7kFTRxK.js", "scene241-gltf-pbr-builder-ext-D5jBhNyS.js", diff --git a/lab/public/bundle/manifest/scene242.json b/lab/public/bundle/manifest/scene242.json index 70c1bc506d..04455aa2cd 100644 --- a/lab/public/bundle/manifest/scene242.json +++ b/lab/public/bundle/manifest/scene242.json @@ -1,5 +1,5 @@ { - "rawKB": 96.9, + "rawKB": 97, "gzipKB": 40.1, "ignoredRawKB": 0, "runtimeChunks": [ @@ -10,7 +10,7 @@ "scene242-gltf-ext-emissive-strength-CButLZnK.js", "scene242-gltf-feature-animation-pointer-DH5WABcF.js", "scene242-gltf-feature-animations-4gglUWxZ.js", - "scene242-gltf-feature-registry-C_ukKuDF.js", + "scene242-gltf-feature-registry-Ce1ySQzN.js", "scene242-gltf-json-asset-CTUWQBxV.js", "scene242-ibl-fragment-CeRrvT96.js", "scene242-pbr-renderable-DWGtqTct.js", diff --git a/lab/public/bundle/manifest/scene243.json b/lab/public/bundle/manifest/scene243.json index 4a16e1a97f..cc6a002f37 100644 --- a/lab/public/bundle/manifest/scene243.json +++ b/lab/public/bundle/manifest/scene243.json @@ -1,6 +1,6 @@ { - "rawKB": 97.2, - "gzipKB": 40.9, + "rawKB": 97.3, + "gzipKB": 41, "ignoredRawKB": 0, "runtimeChunks": [ "scene243-create-morph-targets-C_lcfVOA.js", @@ -9,7 +9,7 @@ "scene243-gltf-feature-animations-C06Hz25F.js", "scene243-gltf-feature-extras-VHPteGay.js", "scene243-gltf-feature-morph-BCUQi4W_.js", - "scene243-gltf-feature-registry-eBJA3kV-.js", + "scene243-gltf-feature-registry-g5PKojC9.js", "scene243-gltf-json-asset-CnDTTIDf.js", "scene243-gltf-pbr-builder-ext-CVrSOAh6.js", "scene243-ibl-fragment-CeRrvT96.js", diff --git a/lab/public/bundle/manifest/scene244.json b/lab/public/bundle/manifest/scene244.json index 90b4b0e305..c3dc84fca5 100644 --- a/lab/public/bundle/manifest/scene244.json +++ b/lab/public/bundle/manifest/scene244.json @@ -1,5 +1,5 @@ { - "rawKB": 133.5, + "rawKB": 133.6, "gzipKB": 54, "ignoredRawKB": 0, "runtimeChunks": [ @@ -12,7 +12,7 @@ "scene244-gltf-ext-uv-transform-YGWG8v_J.js", "scene244-gltf-feature-animation-pointer-DU6kbDPJ.js", "scene244-gltf-feature-animations-B8Qicl0w.js", - "scene244-gltf-feature-registry-DOrTx9lt.js", + "scene244-gltf-feature-registry-DaAQu_Es.js", "scene244-gltf-json-asset-DaKe4-Lc.js", "scene244-gltf-pbr-builder-ext-BI2CisGc.js", "scene244-ibl-fragment-CeRrvT96.js", diff --git a/lab/public/bundle/manifest/scene245.json b/lab/public/bundle/manifest/scene245.json index d111b49d62..77ef1ed023 100644 --- a/lab/public/bundle/manifest/scene245.json +++ b/lab/public/bundle/manifest/scene245.json @@ -1,5 +1,5 @@ { - "rawKB": 100.3, + "rawKB": 100.4, "gzipKB": 42.5, "ignoredRawKB": 0, "runtimeChunks": [ @@ -8,7 +8,7 @@ "scene245-generate-mipmaps-WM6bVf2v.js", "scene245-gltf-animation-l40-wjM0.js", "scene245-gltf-feature-animations-BcEW2aw2.js", - "scene245-gltf-feature-registry-BzxerbPm.js", + "scene245-gltf-feature-registry-D0k6Moff.js", "scene245-gltf-feature-skeleton-BDGOydjm.js", "scene245-gltf-interleave-DmjUidsh.js", "scene245-gltf-json-asset-BtsXM9cU.js", diff --git a/lab/public/bundle/manifest/scene246.json b/lab/public/bundle/manifest/scene246.json index 0d5411d8c0..a7dab0d01a 100644 --- a/lab/public/bundle/manifest/scene246.json +++ b/lab/public/bundle/manifest/scene246.json @@ -1,5 +1,5 @@ { - "rawKB": 99.2, + "rawKB": 99.3, "gzipKB": 42.1, "ignoredRawKB": 0, "runtimeChunks": [ @@ -8,7 +8,7 @@ "scene246-generate-mipmaps-DzKvtUax.js", "scene246-gltf-animation-Bg1KL3K9.js", "scene246-gltf-feature-animations-Ba_0YkMS.js", - "scene246-gltf-feature-registry-LCrk84fF.js", + "scene246-gltf-feature-registry-BR-JbWqQ.js", "scene246-gltf-feature-skeleton-BqFTwPFs.js", "scene246-gltf-interleave-Df62YAf1.js", "scene246-gltf-json-asset-DZ_iAA05.js", diff --git a/lab/public/bundle/manifest/scene247.json b/lab/public/bundle/manifest/scene247.json index ff6fa08954..525beecd01 100644 --- a/lab/public/bundle/manifest/scene247.json +++ b/lab/public/bundle/manifest/scene247.json @@ -1,12 +1,12 @@ { - "rawKB": 83.9, - "gzipKB": 34.8, + "rawKB": 84, + "gzipKB": 34.9, "ignoredRawKB": 0, "runtimeChunks": [ "scene247-generate-mipmaps-Crazqgzv.js", "scene247-gltf-feature-extras-VHPteGay.js", "scene247-gltf-feature-gpu-instancing-BQnnR0-8.js", - "scene247-gltf-feature-registry-DyO0jZaZ.js", + "scene247-gltf-feature-registry-E9tteNHr.js", "scene247-gltf-json-asset-nLezCZiO.js", "scene247-gltf-multi-buffer-Cwm5u1Qq.js", "scene247-ibl-fragment-CeRrvT96.js", diff --git a/lab/public/bundle/manifest/scene251.json b/lab/public/bundle/manifest/scene251.json index b21e92cde4..fd791079c2 100644 --- a/lab/public/bundle/manifest/scene251.json +++ b/lab/public/bundle/manifest/scene251.json @@ -1,13 +1,13 @@ { - "rawKB": 89.6, - "gzipKB": 37, + "rawKB": 89.7, + "gzipKB": 37.1, "ignoredRawKB": 0, "runtimeChunks": [ "scene251-create-skeleton-QaTYEW_C.js", "scene251-generate-mipmaps-QjbHE23R.js", "scene251-gltf-animation-Cn2klEOG.js", "scene251-gltf-feature-animations-Bu_GfCne.js", - "scene251-gltf-feature-registry-BHEwW2jL.js", + "scene251-gltf-feature-registry-C-kk-mMW.js", "scene251-gltf-feature-skeleton-C732lS6K.js", "scene251-gltf-glb-parser-8AD91ACR.js", "scene251-multilight-wgsl-C4_Zdm3l.js", diff --git a/lab/public/bundle/manifest/scene253.json b/lab/public/bundle/manifest/scene253.json index 47b1fabee2..2e1a83c3b6 100644 --- a/lab/public/bundle/manifest/scene253.json +++ b/lab/public/bundle/manifest/scene253.json @@ -1,5 +1,5 @@ { - "rawKB": 152.7, + "rawKB": 152.8, "gzipKB": 64.4, "ignoredRawKB": 0, "runtimeChunks": [ @@ -24,7 +24,7 @@ "scene253-gltf-feature-extras-VHPteGay.js", "scene253-gltf-feature-lights-punctual-B0T8d3mz.js", "scene253-gltf-feature-morph-OgvZWqfo.js", - "scene253-gltf-feature-registry-B0E2pnIh.js", + "scene253-gltf-feature-registry-Dwscbzir.js", "scene253-gltf-feature-skeleton-BA9d6aU5.js", "scene253-gltf-json-asset-CQyVogeZ.js", "scene253-gltf-light-pointer-state-B7kFTRxK.js", diff --git a/lab/public/bundle/manifest/scene254.json b/lab/public/bundle/manifest/scene254.json index 5383717ec5..4d7b6e8734 100644 --- a/lab/public/bundle/manifest/scene254.json +++ b/lab/public/bundle/manifest/scene254.json @@ -1,12 +1,12 @@ { - "rawKB": 92.1, - "gzipKB": 38.2, + "rawKB": 92.3, + "gzipKB": 38.3, "ignoredRawKB": 0, "runtimeChunks": [ "scene254-generate-mipmaps-CYwo5pXb.js", "scene254-gltf-animation-BROLRbEL.js", "scene254-gltf-feature-animations-CrSKPw_V.js", - "scene254-gltf-feature-registry-DeI6546B.js", + "scene254-gltf-feature-registry-B6lfVbhV.js", "scene254-gltf-json-asset-DHCCWzhb.js", "scene254-gltf-sampler-denorm-Bui_WQIw.js", "scene254-ibl-fragment-CeRrvT96.js", diff --git a/lab/public/bundle/manifest/scene255.json b/lab/public/bundle/manifest/scene255.json index 19355f7621..fec33c6329 100644 --- a/lab/public/bundle/manifest/scene255.json +++ b/lab/public/bundle/manifest/scene255.json @@ -1,5 +1,5 @@ { - "rawKB": 93.8, + "rawKB": 93.9, "gzipKB": 39.3, "ignoredRawKB": 0, "runtimeChunks": [ @@ -8,7 +8,7 @@ "scene255-generate-mipmaps-B7LdcXNp.js", "scene255-gltf-animation-BbNxs5BN.js", "scene255-gltf-feature-animations-D2HGW9O0.js", - "scene255-gltf-feature-registry-D_kfDZ8p.js", + "scene255-gltf-feature-registry-Cncbz2fg.js", "scene255-gltf-feature-skeleton-Cn5nmTIx.js", "scene255-gltf-json-asset-Di1rSDsD.js", "scene255-gltf-normals-DYhLwXNX.js", diff --git a/lab/public/bundle/manifest/scene257.json b/lab/public/bundle/manifest/scene257.json index bfb8236f65..e5b7e5e952 100644 --- a/lab/public/bundle/manifest/scene257.json +++ b/lab/public/bundle/manifest/scene257.json @@ -1,12 +1,12 @@ { - "rawKB": 79.8, + "rawKB": 79.9, "gzipKB": 32.9, "ignoredRawKB": 0, "runtimeChunks": [ "scene257-flat-normal-wgsl-Cm9mM4tC.js", "scene257-generate-mipmaps-C2jNwHZ4.js", "scene257-gltf-feature-primitive-BMqT3MMH.js", - "scene257-gltf-feature-registry-ClAPN7-r.js", + "scene257-gltf-feature-registry-Dsvyl-fX.js", "scene257-gltf-json-asset-B1b3YkuB.js", "scene257-gltf-normals-DYhLwXNX.js", "scene257-ibl-fragment-CeRrvT96.js", diff --git a/lab/public/bundle/manifest/scene260.json b/lab/public/bundle/manifest/scene260.json index 54dd13e34c..c20ed0b9b7 100644 --- a/lab/public/bundle/manifest/scene260.json +++ b/lab/public/bundle/manifest/scene260.json @@ -1,12 +1,12 @@ { - "rawKB": 79.7, + "rawKB": 79.8, "gzipKB": 32.9, "ignoredRawKB": 0, "runtimeChunks": [ "scene260-flat-normal-wgsl-Cm9mM4tC.js", "scene260-generate-mipmaps-BDaYF730.js", "scene260-gltf-feature-primitive-BMqT3MMH.js", - "scene260-gltf-feature-registry-B_W0S6Lu.js", + "scene260-gltf-feature-registry-9sjY5vkN.js", "scene260-gltf-json-asset-gSoA2XSV.js", "scene260-gltf-normals-DYhLwXNX.js", "scene260-ibl-fragment-CeRrvT96.js", diff --git a/lab/public/bundle/manifest/scene264.json b/lab/public/bundle/manifest/scene264.json index cc43bee12e..c6ebd9e01a 100644 --- a/lab/public/bundle/manifest/scene264.json +++ b/lab/public/bundle/manifest/scene264.json @@ -1,19 +1,16 @@ { - "rawKB": 80.1, - "gzipKB": 33.5, + "rawKB": 79.7, + "gzipKB": 33.2, "ignoredRawKB": 0, "runtimeChunks": [ - "scene264-env-helpers-23YvOcOj.js", - "scene264-generate-mipmaps-KrqAJzxS.js", - "scene264-gltf-ext-lights-image-based-ClJe5F5q.js", - "scene264-gltf-feature-registry-DzmBjAKy.js", + "scene264-generate-mipmaps-A3KgbDFl.js", + "scene264-gltf-ext-lights-image-based-BNhwTX_c.js", + "scene264-gltf-feature-registry-CTTRYdLW.js", "scene264-gltf-json-asset-OTC4RQu_.js", - "scene264-hdr-ibl-pipeline-JseuEIIG.js", "scene264-ibl-cubemap-upload-B9Q6v-do.js", + "scene264-ibl-env-assembly-DGK6fHLG.js", "scene264-ibl-fragment-CeRrvT96.js", - "scene264-pbr-renderable-CskWgmPH.js", - "scene264-samplers-DRa_c529.js", - "scene264-scene-ubo-extras-B5B4aSOt.js", + "scene264-pbr-renderable-B73M7MfR.js", "scene264.js" ] } diff --git a/lab/public/bundle/manifest/scene27.json b/lab/public/bundle/manifest/scene27.json index fe7438d790..a93a4a3ad2 100644 --- a/lab/public/bundle/manifest/scene27.json +++ b/lab/public/bundle/manifest/scene27.json @@ -1,11 +1,11 @@ { - "rawKB": 82.7, + "rawKB": 82.8, "gzipKB": 33.8, "ignoredRawKB": 0, "runtimeChunks": [ "scene27-generate-mipmaps-CaLzDGPp.js", "scene27-gltf-ext-dielectric-C6hvXXiF.js", - "scene27-gltf-feature-registry-unsG-wQu.js", + "scene27-gltf-feature-registry-CyoXHbRf.js", "scene27-gltf-feature-variants-BeIf4Q9N.js", "scene27-gltf-glb-parser-By8noE3G.js", "scene27-gltf-pbr-builder-ext-OvOYi-mh.js", diff --git a/lab/public/bundle/manifest/scene28.json b/lab/public/bundle/manifest/scene28.json index 93b5aad31e..5c3c81c541 100644 --- a/lab/public/bundle/manifest/scene28.json +++ b/lab/public/bundle/manifest/scene28.json @@ -1,12 +1,12 @@ { - "rawKB": 86.4, + "rawKB": 86.5, "gzipKB": 35.2, "ignoredRawKB": 0, "runtimeChunks": [ "scene28-clearcoat-fragment-AiyWBFX1.js", "scene28-generate-mipmaps-7w1hCgCw.js", "scene28-gltf-ext-clearcoat-BgaLgSgD.js", - "scene28-gltf-feature-registry-CCh7fhoq.js", + "scene28-gltf-feature-registry-DlOmIbig.js", "scene28-gltf-json-asset-DNaJyx0P.js", "scene28-ibl-fragment-CeRrvT96.js", "scene28-pbr-renderable-Db_oA_YL.js", diff --git a/lab/public/bundle/manifest/scene29.json b/lab/public/bundle/manifest/scene29.json index 8954f7d7aa..4b641536db 100644 --- a/lab/public/bundle/manifest/scene29.json +++ b/lab/public/bundle/manifest/scene29.json @@ -1,12 +1,12 @@ { - "rawKB": 90.1, - "gzipKB": 37.1, + "rawKB": 90.2, + "gzipKB": 37.2, "ignoredRawKB": 0, "runtimeChunks": [ "scene29-generate-mipmaps-DJCYSY1_.js", "scene29-gltf-ext-sheen-Dv23b_3T.js", "scene29-gltf-ext-uv-transform-tUSEOqA7.js", - "scene29-gltf-feature-registry-D1t8cGvh.js", + "scene29-gltf-feature-registry-oaAupIBh.js", "scene29-gltf-json-asset-EOtWL8g4.js", "scene29-gltf-pbr-builder-ext-CpjrBrd7.js", "scene29-ibl-fragment-CeRrvT96.js", diff --git a/lab/public/bundle/manifest/scene30.json b/lab/public/bundle/manifest/scene30.json index c0a4f1d378..6b7c239047 100644 --- a/lab/public/bundle/manifest/scene30.json +++ b/lab/public/bundle/manifest/scene30.json @@ -1,5 +1,5 @@ { - "rawKB": 103.8, + "rawKB": 103.9, "gzipKB": 42.8, "ignoredRawKB": 0, "runtimeChunks": [ @@ -8,7 +8,7 @@ "scene30-gltf-ext-dielectric-C6hvXXiF.js", "scene30-gltf-ext-uv-transform-DQN5DAiE.js", "scene30-gltf-feature-draco-BSbw1ZBU.js", - "scene30-gltf-feature-registry-DzLjJi7O.js", + "scene30-gltf-feature-registry-CHHdy50i.js", "scene30-gltf-glb-parser-DJ0cTJ-4.js", "scene30-gltf-pbr-builder-ext-B4o2oNmo.js", "scene30-ibl-fragment-CeRrvT96.js", diff --git a/lab/public/bundle/manifest/scene31.json b/lab/public/bundle/manifest/scene31.json index a317da0ecb..dafc9667f1 100644 --- a/lab/public/bundle/manifest/scene31.json +++ b/lab/public/bundle/manifest/scene31.json @@ -1,12 +1,12 @@ { - "rawKB": 80, - "gzipKB": 32.8, + "rawKB": 80.2, + "gzipKB": 32.9, "ignoredRawKB": 0, "runtimeChunks": [ "scene31-emissive-fragment-C1TNSo0w.js", "scene31-generate-mipmaps-CoCbB-UD.js", "scene31-gltf-ext-emissive-strength-CButLZnK.js", - "scene31-gltf-feature-registry-CAqLESLI.js", + "scene31-gltf-feature-registry-D19L65zH.js", "scene31-gltf-glb-parser-CzRZVo57.js", "scene31-ibl-fragment-CeRrvT96.js", "scene31-pbr-renderable-CTabzZ4N.js", diff --git a/lab/public/bundle/manifest/scene32.json b/lab/public/bundle/manifest/scene32.json index 767f2f4352..43c572eb1b 100644 --- a/lab/public/bundle/manifest/scene32.json +++ b/lab/public/bundle/manifest/scene32.json @@ -1,11 +1,11 @@ { - "rawKB": 79.9, - "gzipKB": 32.8, + "rawKB": 80, + "gzipKB": 32.9, "ignoredRawKB": 0, "runtimeChunks": [ "scene32-generate-mipmaps-DdnhjgK3.js", "scene32-gltf-ext-unlit-1vUYfF7v.js", - "scene32-gltf-feature-registry-DLwLR3I_.js", + "scene32-gltf-feature-registry-BGfQHlt3.js", "scene32-gltf-glb-parser-BX2AS-bF.js", "scene32-ibl-fragment-CeRrvT96.js", "scene32-pbr-renderable-BgtCs3dX.js", diff --git a/lab/public/bundle/manifest/scene33.json b/lab/public/bundle/manifest/scene33.json index ec351b1724..e06a22b7db 100644 --- a/lab/public/bundle/manifest/scene33.json +++ b/lab/public/bundle/manifest/scene33.json @@ -1,12 +1,12 @@ { - "rawKB": 103.8, + "rawKB": 103.9, "gzipKB": 42.6, "ignoredRawKB": 0, "runtimeChunks": [ "scene33-generate-mipmaps-Z_0Gnbub.js", "scene33-gltf-ext-dielectric-C6hvXXiF.js", "scene33-gltf-feature-lights-punctual-B3W8Lfux.js", - "scene33-gltf-feature-registry-DHO_EH3d.js", + "scene33-gltf-feature-registry-CnlQcUYG.js", "scene33-gltf-glb-parser-DMBUHtjv.js", "scene33-gltf-light-pointer-state-B7kFTRxK.js", "scene33-gltf-sampler-desc-ChMUJ3dG.js", diff --git a/lab/public/bundle/manifest/scene34.json b/lab/public/bundle/manifest/scene34.json index 62760a9faa..70a4dd8223 100644 --- a/lab/public/bundle/manifest/scene34.json +++ b/lab/public/bundle/manifest/scene34.json @@ -1,6 +1,6 @@ { - "rawKB": 96.8, - "gzipKB": 39.8, + "rawKB": 96.9, + "gzipKB": 39.9, "ignoredRawKB": 0, "runtimeChunks": [ "scene34-generate-mipmaps-C244DQVV.js", @@ -8,7 +8,7 @@ "scene34-gltf-ext-node-visibility-BITO3iFw.js", "scene34-gltf-feature-animation-pointer-DaFC0phL.js", "scene34-gltf-feature-animations-DyttCede.js", - "scene34-gltf-feature-registry-BdkYMnC6.js", + "scene34-gltf-feature-registry-Bw1WJP4y.js", "scene34-gltf-glb-parser-BxoFTGKn.js", "scene34-gltf-sampler-denorm-C_vs8OEN.js", "scene34-ibl-fragment-CeRrvT96.js", diff --git a/lab/public/bundle/manifest/scene35.json b/lab/public/bundle/manifest/scene35.json index feb0288f0a..787e49be17 100644 --- a/lab/public/bundle/manifest/scene35.json +++ b/lab/public/bundle/manifest/scene35.json @@ -1,11 +1,11 @@ { - "rawKB": 83.2, - "gzipKB": 34.2, + "rawKB": 83.3, + "gzipKB": 34.3, "ignoredRawKB": 0, "runtimeChunks": [ "scene35-generate-mipmaps-Dw2dswCB.js", "scene35-gltf-feature-gpu-instancing-D1uNjxTA.js", - "scene35-gltf-feature-registry-Ch6QZbAD.js", + "scene35-gltf-feature-registry-BwePSyoW.js", "scene35-gltf-glb-parser-BJyme4Vk.js", "scene35-ibl-fragment-CeRrvT96.js", "scene35-pbr-renderable-CQ7zqP7W.js", diff --git a/lab/public/bundle/manifest/scene37.json b/lab/public/bundle/manifest/scene37.json index 4a4521fe5a..46de8674fd 100644 --- a/lab/public/bundle/manifest/scene37.json +++ b/lab/public/bundle/manifest/scene37.json @@ -1,5 +1,5 @@ { - "rawKB": 97, + "rawKB": 97.1, "gzipKB": 39.7, "ignoredRawKB": 0, "runtimeChunks": [ @@ -8,7 +8,7 @@ "scene37-gltf-ext-dielectric-C6hvXXiF.js", "scene37-gltf-ext-sheen-Dv23b_3T.js", "scene37-gltf-ext-uv-transform-hVTzf5Q9.js", - "scene37-gltf-feature-registry-ayxqJZ73.js", + "scene37-gltf-feature-registry-C5Kfyh4Z.js", "scene37-gltf-glb-parser-CkbGcOCI.js", "scene37-gltf-pbr-builder-ext-rpTZqaAk.js", "scene37-ibl-fragment-CeRrvT96.js", diff --git a/lab/public/bundle/manifest/scene39.json b/lab/public/bundle/manifest/scene39.json index 791af63f77..3f82474c43 100644 --- a/lab/public/bundle/manifest/scene39.json +++ b/lab/public/bundle/manifest/scene39.json @@ -1,5 +1,5 @@ { - "rawKB": 108.5, + "rawKB": 108.6, "gzipKB": 45.9, "ignoredRawKB": 0, "runtimeChunks": [ @@ -12,7 +12,7 @@ "scene39-gltf-feature-animation-pointer-g4Ofbzej.js", "scene39-gltf-feature-animations-qIVKY2J4.js", "scene39-gltf-feature-lights-punctual-CM09I-Ul.js", - "scene39-gltf-feature-registry-CFyidLO1.js", + "scene39-gltf-feature-registry-Bm0G1xSz.js", "scene39-gltf-json-asset-CnK_Yal2.js", "scene39-gltf-light-pointer-state-B7kFTRxK.js", "scene39-gltf-pbr-builder-ext-WVY5V9c8.js", diff --git a/lab/public/bundle/manifest/scene41.json b/lab/public/bundle/manifest/scene41.json index e7ebe1e1f9..df2581e2d6 100644 --- a/lab/public/bundle/manifest/scene41.json +++ b/lab/public/bundle/manifest/scene41.json @@ -1,11 +1,11 @@ { - "rawKB": 93.2, + "rawKB": 93.3, "gzipKB": 37.1, "ignoredRawKB": 0, "runtimeChunks": [ "scene41-generate-mipmaps-BWxJCIcc.js", "scene41-gltf-ext-spec-gloss-D60sFZYX.js", - "scene41-gltf-feature-registry-Bo2QTxi1.js", + "scene41-gltf-feature-registry-DXpmB12R.js", "scene41-gltf-glb-parser-CkOP9avo.js", "scene41-gltf-sampler-desc-E_7RaQlV.js", "scene41-point-light-CnkkP9oB.js", diff --git a/lab/public/bundle/manifest/scene47.json b/lab/public/bundle/manifest/scene47.json index f1eaafbdb1..2411dd9785 100644 --- a/lab/public/bundle/manifest/scene47.json +++ b/lab/public/bundle/manifest/scene47.json @@ -1,11 +1,11 @@ { - "rawKB": 92.5, + "rawKB": 92.6, "gzipKB": 37, "ignoredRawKB": 0, "runtimeChunks": [ "scene47-generate-mipmaps-CmIGpFd5.js", "scene47-gltf-ext-spec-gloss-D60sFZYX.js", - "scene47-gltf-feature-registry-DB_FogYz.js", + "scene47-gltf-feature-registry-DupOp5vP.js", "scene47-gltf-glb-parser-6-tsF2C2.js", "scene47-gltf-sampler-desc-BJMmnJDF.js", "scene47-shader-composer-CgJmQyJj.js", diff --git a/lab/public/bundle/manifest/scene5.json b/lab/public/bundle/manifest/scene5.json index 146f1e78e8..2d11bf7910 100644 --- a/lab/public/bundle/manifest/scene5.json +++ b/lab/public/bundle/manifest/scene5.json @@ -1,5 +1,5 @@ { - "rawKB": 91.6, + "rawKB": 91.7, "gzipKB": 38.7, "ignoredRawKB": 0, "runtimeChunks": [ @@ -10,7 +10,7 @@ "scene5-gltf-color-normalize-4-WGzPL2.js", "scene5-gltf-feature-animations-CIs1egTE.js", "scene5-gltf-feature-morph-BNcyLEaQ.js", - "scene5-gltf-feature-registry-BXOUrv1d.js", + "scene5-gltf-feature-registry-BIgnV4Cr.js", "scene5-gltf-feature-skeleton-CD_v1q-S.js", "scene5-gltf-json-asset-DS0i23k1.js", "scene5-morph-fragment-DsC8M531.js", diff --git a/lab/public/bundle/manifest/scene7.json b/lab/public/bundle/manifest/scene7.json index 947dafdca8..5a05ce54fe 100644 --- a/lab/public/bundle/manifest/scene7.json +++ b/lab/public/bundle/manifest/scene7.json @@ -1,5 +1,5 @@ { - "rawKB": 105.7, + "rawKB": 105.8, "gzipKB": 44.7, "ignoredRawKB": 0, "runtimeChunks": [ @@ -10,7 +10,7 @@ "scene7-gltf-animation-RP7A9LpW.js", "scene7-gltf-feature-animations-DAoIJxtt.js", "scene7-gltf-feature-extras-VHPteGay.js", - "scene7-gltf-feature-registry-DCsCAT__.js", + "scene7-gltf-feature-registry-DsKSWko8.js", "scene7-gltf-feature-skeleton-CLGLulj4.js", "scene7-gltf-json-asset-CAp-SN2V.js", "scene7-ibl-fragment-CeRrvT96.js", diff --git a/lab/public/bundle/manifest/scene73.json b/lab/public/bundle/manifest/scene73.json index 94e364be85..30efc1ca3f 100644 --- a/lab/public/bundle/manifest/scene73.json +++ b/lab/public/bundle/manifest/scene73.json @@ -1,6 +1,6 @@ { - "rawKB": 143.7, - "gzipKB": 58, + "rawKB": 143.8, + "gzipKB": 58.1, "ignoredRawKB": 3.3, "runtimeChunks": [ "scene73-_math-factory-DFVyS8gB.js", @@ -10,7 +10,7 @@ "scene73-fragment-output-CfRmfbPv.js", "scene73-generate-mipmaps-DcnO72FN.js", "scene73-gltf-ext-clearcoat-BgaLgSgD.js", - "scene73-gltf-feature-registry-Bx9a6LoG.js", + "scene73-gltf-feature-registry-DyiSampQ.js", "scene73-gltf-glb-parser-Bm1IrZjT.js", "scene73-ibl-fragment-CeRrvT96.js", "scene73-input-block-lrbZBZJO.js", diff --git a/lab/public/bundle/manifest/scene99.json b/lab/public/bundle/manifest/scene99.json index 4b4c028fab..1d3b684139 100644 --- a/lab/public/bundle/manifest/scene99.json +++ b/lab/public/bundle/manifest/scene99.json @@ -1,6 +1,6 @@ { - "rawKB": 91.9, - "gzipKB": 38.1, + "rawKB": 92, + "gzipKB": 38.2, "ignoredRawKB": 0, "runtimeChunks": [ "scene99-animation-group-KASeQgqU.js", @@ -8,7 +8,7 @@ "scene99-generate-mipmaps-BFyY2NRa.js", "scene99-gltf-animation-T1Ovedwq.js", "scene99-gltf-feature-animations-DpNyEUQl.js", - "scene99-gltf-feature-registry-h9KrPdL4.js", + "scene99-gltf-feature-registry-DrSMhVY7.js", "scene99-gltf-feature-skeleton-Bsv0NjIg.js", "scene99-gltf-glb-parser-BtneRupN.js", "scene99-multilight-wgsl-Cni0HYFi.js", diff --git a/packages/babylon-lite/src/loader-gltf/gltf-ext-lights-image-based.ts b/packages/babylon-lite/src/loader-gltf/gltf-ext-lights-image-based.ts index 1febfbe697..ac9855ecd3 100644 --- a/packages/babylon-lite/src/loader-gltf/gltf-ext-lights-image-based.ts +++ b/packages/babylon-lite/src/loader-gltf/gltf-ext-lights-image-based.ts @@ -27,12 +27,15 @@ import type { SceneContext } from "../scene/scene-core.js"; // NOTE: this module is a lazily-imported feature chunk (see gltf-feature-registry) // and keeps ZERO static runtime imports — every dependency is pulled in via dynamic // import inside applyAsset. Scenes that never trigger EXT_lights_image_based never -// fetch this chunk (or its dependencies) at runtime. The cubemap uploader is kept -// private (ibl-cubemap-upload.js) rather than shared with loader-env/rgbd-decode so -// the extension does not pin that decoder into every .env/DDS scene. Its reuse of -// env-helpers / hdr-ibl-pipeline still participates in rollup's static chunk graph -// for every gltf build, which can hoist shared leaves (e.g. samplers) into their own -// chunk — see the PR notes on bundle-size. +// fetch this chunk (or its dependencies) at runtime. The heavy IBL helpers it reuses +// (cubemap upload, env-texture assembly, BRDF LUT, SH conversion, samplers) are kept +// as PRIVATE copies (ibl-cubemap-upload.js + ibl-env-assembly.js) rather than shared +// with loader-env / loader-hdr. This is a deliberate, user-approved exception to the +// max-reuse mandate: those canonical helpers are single-consumer and thus inlined +// into every glTF scene's main chunk; importing them here would make this feature a +// second consumer and force rollup to hoist them into shared chunks that all 44 glTF +// scenes would pay for. The private copies isolate the feature's byte cost to the one +// scene that uses it. See ibl-env-assembly.js for the canonical-source mapping. interface GltfImageBasedLight { name?: string; @@ -112,7 +115,7 @@ const feature: GltfFeature = { // uploadCubemapRGBD expects; glTF face order +X,-X,+Y,-Y,+Z,-Z matches // WebGPU cube layers). const flatIndices = light.specularImages.flat(); - const { resolveImage } = await import("./gltf-parser.js"); + const { resolveImage } = await import("./ibl-env-assembly.js"); const faceImages = await Promise.all(flatIndices.map((imgIdx) => resolveImage(json, ctx._binChunk, imgIdx, ctx._baseUrl))); const { uploadCubemapRGBD } = await import("./ibl-cubemap-upload.js"); @@ -121,7 +124,7 @@ const feature: GltfFeature = { img.close(); } - const { generateBrdfLut } = await import("../loader-hdr/hdr-ibl-pipeline.js"); + const { generateBrdfLut } = await import("./ibl-env-assembly.js"); const brdfLut = generateBrdfLut(engine); const intensity = light.intensity ?? 1; @@ -129,15 +132,16 @@ const feature: GltfFeature = { // Fit the LOD scale to the available mip count exactly as Babylon does. const lodGenerationScale = (mipCount - 1) / Math.log2(specularImageSize); - const { assembleEnvironmentTextures } = await import("../loader-env/env-helpers.js"); + const { assembleEnvironmentTextures } = await import("./ibl-env-assembly.js"); const textures = assembleEnvironmentTextures(specularCube, brdfLut, irradianceSH, lodGenerationScale, engine); const envRotationY = light.rotation ? envYawFromQuaternion(light.rotation) : 0; - // Pull the scene-wiring helpers in now (still lazy — part of this feature - // chunk) so the synchronous _sceneSetup closure can use them directly. - const { acquireGPUTexture, releaseGPUTexture } = await import("../resource/gpu-pool.js"); - const { registerEnvSceneUniforms } = await import("../scene/scene-ubo-extras.js"); + // Pull the scene-wiring helper in now (still lazy — part of this feature + // chunk) so the synchronous _sceneSetup closure can use it directly. + // registerEnvSceneUniforms is a private copy (see ibl-env-assembly) to + // avoid pinning scene-ubo-extras into every glTF scene. + const { registerEnvSceneUniforms } = await import("./ibl-env-assembly.js"); // Defer the scene wiring: applyAsset has no SceneContext, so hand the core // loader a closure that addToScene() runs against the real scene. @@ -148,11 +152,14 @@ const feature: GltfFeature = { } registerEnvSceneUniforms(scene); - acquireGPUTexture(specularCube); - acquireGPUTexture(brdfLut); + // specularCube + brdfLut are created fresh here and owned solely by + // this scene (nothing else acquires them), so a direct destroy on + // scene teardown is equivalent to the gpu-pool refcount path used by + // load-env — and it avoids pinning gpu-pool's exports into every + // glTF scene's main chunk, keeping the 43 non-IBL scenes at baseline. scene._disposables.push(() => { - releaseGPUTexture(specularCube); - releaseGPUTexture(brdfLut); + specularCube.destroy(); + brdfLut.destroy(); }); // Match loadEnvironment's image-processing defaults (tone mapping on, diff --git a/packages/babylon-lite/src/loader-gltf/ibl-env-assembly.ts b/packages/babylon-lite/src/loader-gltf/ibl-env-assembly.ts new file mode 100644 index 0000000000..bacf93a280 --- /dev/null +++ b/packages/babylon-lite/src/loader-gltf/ibl-env-assembly.ts @@ -0,0 +1,180 @@ +/** Private IBL assembly helpers for the EXT_lights_image_based feature. + * + * ⚠️ DELIBERATE DUPLICATION — user-approved exception to the max-reuse mandate. + * + * The functions below are byte-for-byte copies of shared environment/HDR IBL + * helpers whose canonical homes are: + * - sampler descriptors (bilinear/trilinear) → ../resource/samplers.ts + * - polynomialToPreScaledHarmonics → ../loader-env/load-env.ts + * - assembleEnvironmentTextures → ../loader-env/env-helpers.ts + * - generateBrdfLut → ../loader-hdr/hdr-ibl-pipeline.ts + * - resolveImage → ./gltf-parser.ts + * - registerEnvSceneUniforms (+ its writer) → ../scene/scene-ubo-extras.ts + * + * WHY the copy exists: those canonical modules are each SINGLE-consumer on + * master (env-helpers ← load-env, generateBrdfLut ← load-hdr, samplers ← + * generate-mipmaps), so rollup INLINES them into every glTF scene's main chunk. + * If the (lazily-imported) EXT_lights_image_based feature imported them directly + * it would become a SECOND consumer, forcing rollup to hoist each into a shared + * chunk that all 44 glTF scenes then fetch separately (~0.5–1.0 KB of chunk + * boilerplate per scene, ~32 KB total across the suite). By duplicating them + * here — reachable only from this lazy feature chunk — the 43 non-IBL glTF + * scenes keep their original inlined copies untouched (zero bundle delta) and + * the feature's cost is isolated to the single scene that uses it + * (EnvironmentTest / scene264), whose ceiling absorbs the ~few KB. + * + * If the canonical helpers change, update these copies to match (they exist + * purely to keep the feature self-contained, not to diverge in behaviour). + */ + +import { F32 } from "../engine/typed-arrays.js"; +import { TU } from "../engine/gpu-flags.js"; +import brdfLutWGSL from "../../shaders/hdr-brdf-lut.compute.wgsl?raw"; +import type { EngineContext } from "../engine/engine.js"; +import type { EnvironmentTextures } from "../loader-env/load-env.js"; +import type { SceneContext } from "../scene/scene-core.js"; + +// Sampler descriptors copied from resource/samplers.ts. Created directly here +// (device.createSampler) rather than via gpu-pool.getOrCreateSampler so this +// feature does not add getOrCreateSampler to gpu-pool's retained export set in +// every glTF scene. The only cost is two extra (immutable, stateless) sampler +// objects on the single IBL load — negligible and side-effect-free. +const _bilinearDesc: GPUSamplerDescriptor = { magFilter: "linear", minFilter: "linear" }; +const _trilinearDesc: GPUSamplerDescriptor = { magFilter: "linear", minFilter: "linear", mipmapFilter: "linear" }; + +/** Copy of loader-env/load-env.ts `polynomialToPreScaledHarmonics`. */ +function polynomialToPreScaledHarmonics(poly: Float32Array): Float32Array { + const C00xy = 0.3333338747897695; + const C00z = 0.33333298856284405; + const C1 = 1.4999984284682104; + const C2 = 3.999982863580422; + const C20zz = 1.3333326611423701; + const C20xy = 0.6666653397393608; + const C22 = 1.999991431790211; + + const out = new F32(36); + for (let i = 0; i < 3; i++) { + const x = poly[i]!; + const y = poly[3 + i]!; + const z = poly[6 + i]!; + const xx = poly[9 + i]!; + const yy = poly[12 + i]!; + const zz = poly[15 + i]!; + const yz = poly[18 + i]!; + const zx = poly[21 + i]!; + const xy = poly[24 + i]!; + out[i] = (xx + yy) * C00xy + zz * C00z; // L00 + out[4 + i] = y * C1; // L1_1 + out[8 + i] = z * C1; // L10 + out[12 + i] = x * C1; // L11 + out[16 + i] = xy * C2; // L2_2 + out[20 + i] = yz * C2; // L2_1 + out[24 + i] = zz * C20zz - (xx + yy) * C20xy; // L20 + out[28 + i] = zx * C2; // L21 + out[32 + i] = (xx - yy) * C22; // L22 + } + return out; +} + +/** Copy of loader-env/env-helpers.ts `assembleEnvironmentTextures`. */ +export function assembleEnvironmentTextures( + specularCube: GPUTexture, + brdfLut: GPUTexture, + irradianceSH: Float32Array, + lodGenerationScale: number, + engine: EngineContext +): EnvironmentTextures { + return { + specularCube, + specularCubeView: specularCube.createView({ dimension: "cube" }), + brdfLut, + brdfLutView: brdfLut.createView(), + cubeSampler: engine._device.createSampler(_trilinearDesc), + brdfSampler: engine._device.createSampler(_bilinearDesc), + irradianceSH, + sphericalHarmonics: polynomialToPreScaledHarmonics(irradianceSH), + lodGenerationScale, + }; +} + +// Copy of loader-hdr/hdr-ibl-pipeline.ts `generateBrdfLut` (split-sum BRDF LUT). +let _brdfPipeline: GPUComputePipeline | null = null; +let _brdfPipelineDevice: GPUDevice | null = null; + +/** Copy of loader-hdr/hdr-ibl-pipeline.ts `generateBrdfLut`. */ +export function generateBrdfLut(engine: EngineContext): GPUTexture { + const device = engine._device; + if (!_brdfPipeline || _brdfPipelineDevice !== device) { + _brdfPipeline = device.createComputePipeline({ + layout: "auto", + compute: { module: device.createShaderModule({ code: brdfLutWGSL }), entryPoint: "main" }, + }); + _brdfPipelineDevice = device; + } + const size = 256; + const texture = device.createTexture({ + size: { width: size, height: size }, + format: "rgba16float", + usage: TU.TEXTURE_BINDING | TU.STORAGE_BINDING, + }); + const bindGroup = device.createBindGroup({ + layout: _brdfPipeline.getBindGroupLayout(0), + entries: [{ binding: 0, resource: texture.createView() }], + }); + const encoder = device.createCommandEncoder(); + const pass = encoder.beginComputePass(); + pass.setPipeline(_brdfPipeline); + pass.setBindGroup(0, bindGroup); + pass.dispatchWorkgroups(Math.ceil(size / 8), Math.ceil(size / 8)); + pass.end(); + device.queue.submit([encoder.finish()]); + return texture; +} + +/** Copy of gltf-parser.ts `resolveImage` — resolves a glTF image (GLB bufferView + * or external URI) to an ImageBitmap. Kept private so this feature does not pin + * gltf-parser's export surface into every glTF scene's main chunk. */ +export async function resolveImage(json: any, binChunk: DataView, imageIdx: number, baseUrl: string): Promise { + const image = json.images[imageIdx]; + + if (image.bufferView !== undefined) { + const bv = json.bufferViews[image.bufferView]; + const offset = binChunk.byteOffset + (bv.byteOffset ?? 0); + const slice = binChunk.buffer.slice(offset, offset + bv.byteLength); + const blob = new Blob([slice as ArrayBuffer], { type: image.mimeType ?? "image/png" }); + return createImageBitmap(blob, { premultiplyAlpha: "none", colorSpaceConversion: "none" }); + } + + if (image.uri) { + const imageUrl = new URL(image.uri, baseUrl + "x").href; + const response = await fetch(imageUrl); + if (!response.ok) { + throw new Error(`Failed to load image: ${response.status} ${response.statusText}`); + } + const blob = await response.blob(); + return createImageBitmap(blob, { premultiplyAlpha: "none", colorSpaceConversion: "none" }); + } + + throw new Error("Image has neither bufferView nor uri"); +} + +/** Copy of scene-ubo-extras.ts `writeEnvShUbo` — writes the environment + * spherical-harmonics slice (float offsets 40–75) of the SceneUniforms struct. */ +function writeEnvShUbo(data: Float32Array, scene: SceneContext): void { + const sh = scene._envTextures?.sphericalHarmonics; + if (sh) { + data.set(sh, 40); + } +} + +/** Copy of scene-ubo-extras.ts `registerEnvSceneUniforms` (+ its dedup helper) — + * registers the env-SH scene-UBO contributor. The render task invokes whatever + * contributors are on `scene._sceneUboContributors`, so a private copy of the + * writer is functionally identical to the shared one. Kept private so this + * feature does not pin scene-ubo-extras into every glTF scene's main chunk. */ +export function registerEnvSceneUniforms(scene: SceneContext): void { + const list = (scene._sceneUboContributors ??= []); + if (!list.includes(writeEnvShUbo)) { + list.push(writeEnvShUbo); + } +} From 3cd83029806368fde84c4e2026447edd373bfc35 Mon Sep 17 00:00:00 2001 From: Raanan Weber Date: Fri, 10 Jul 2026 15:30:18 +0200 Subject: [PATCH 04/10] build(bundle): bump scene12/41/47 ceilings for combined registry-entry cost The EXT_lights_image_based (PR5) lazy feature adds a ~90-byte entry to every glTF scene's gltf-feature-registry chunk. Three scenes sit at ~zero ceiling headroom, so the exact-byte bundle-size check tips over: - scene12 103.5 -> 103.6 (was over by 7 bytes) - scene41 93.2 -> 93.4 - scene47 92.5 -> 92.7 These are the final COMBINED ceilings covering BOTH the sparse (PR4 #394) and IBL (PR5) registry entries on the same zero-headroom scenes, so once both features coexist no further bump is needed. The ~90-byte registry entry is irreducible (any new lazy glTF feature requires it). scene264 stays under its existing 82 KB ceiling. No golden changes. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- scene-config.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scene-config.json b/scene-config.json index e4fc9eeeff..987b78fc2b 100644 --- a/scene-config.json +++ b/scene-config.json @@ -122,7 +122,7 @@ "slug": "scene12-shader-balls", "name": "Scene 12 — PBR Shader Balls", "maxMad": 0.027, - "maxRawKB": 103.5, + "maxRawKB": 103.6, "description": "3 shader balls with metallic reflectance textures, directional light, env rotation.", "tags": ["pbr", "gltf"], "skipPerf": true @@ -436,7 +436,7 @@ "slug": "scene41-physics-shapes", "name": "Scene 41 — Physics Shape Debug Viewer", "maxMad": 10, - "maxRawKB": 93.2, + "maxRawKB": 93.4, "maxGzipKB": 36, "description": "Havok Physics V2 mesh, convex hull, and aggregate collision shapes with PhysicsViewer wireframes. Loaded skull and seagull port of PG #LKPBW5.", "tags": ["std", "procedural", "physics"], @@ -502,7 +502,7 @@ "slug": "scene47-physics-heightfield", "name": "Scene 47 — Physics Heightfield", "maxMad": 0.05, - "maxRawKB": 92.5, + "maxRawKB": 92.7, "maxGzipKB": 35, "description": "Havok Physics V2 heightfield from heightMap.png with two rows of falling shapes: row 1 PhysicsAggregate all types (box, sphere, capsule, cylinder, plus convex hull and mesh from the seagull glb), row 2 body+shape all types including convex hull and mesh from the seagull glb.", "tags": ["physics", "gltf", "procedural", "std"], From b1e6ab9873a55dac0aa29e8aca1cc7bee36afca7 Mon Sep 17 00:00:00 2001 From: Raanan Weber Date: Sun, 12 Jul 2026 13:40:07 +0200 Subject: [PATCH 05/10] chore(lab): renumber EnvironmentTest scene 264->265 to avoid collision with master NPE scene264 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- ...ef-scene264.html => babylon-ref-scene265.html} | 4 ++-- ...bjs-scene264.html => bundle-bjs-scene265.html} | 4 ++-- ...{bundle-scene264.html => bundle-scene265.html} | 4 ++-- lab/lite/{scene264.html => scene265.html} | 4 ++-- lab/lite/src/bjs/{scene264.ts => scene265.ts} | 0 lab/lite/src/lite/{scene264.ts => scene265.ts} | 2 +- .../manifest/{scene264.json => scene265.json} | 0 .../thumbnails/{scene264.jpg => scene265.jpg} | Bin .../babylon-ref-golden.png | Bin scene-config.json | 6 +++--- ....spec.ts => scene265-environment-test.spec.ts} | 14 +++++++------- 11 files changed, 19 insertions(+), 19 deletions(-) rename lab/lite/{babylon-ref-scene264.html => babylon-ref-scene265.html} (82%) rename lab/lite/{bundle-bjs-scene264.html => bundle-bjs-scene265.html} (82%) rename lab/lite/{bundle-scene264.html => bundle-scene265.html} (83%) rename lab/lite/{scene264.html => scene265.html} (83%) rename lab/lite/src/bjs/{scene264.ts => scene265.ts} (100%) rename lab/lite/src/lite/{scene264.ts => scene265.ts} (95%) rename lab/public/bundle/manifest/{scene264.json => scene265.json} (100%) rename lab/public/thumbnails/{scene264.jpg => scene265.jpg} (100%) rename reference/lite/{scene264-environment-test => scene265-environment-test}/babylon-ref-golden.png (100%) rename tests/lite/parity/scenes/{scene264-environment-test.spec.ts => scene265-environment-test.spec.ts} (78%) diff --git a/lab/lite/babylon-ref-scene264.html b/lab/lite/babylon-ref-scene265.html similarity index 82% rename from lab/lite/babylon-ref-scene264.html rename to lab/lite/babylon-ref-scene265.html index b506451f89..1a50bc9629 100644 --- a/lab/lite/babylon-ref-scene264.html +++ b/lab/lite/babylon-ref-scene265.html @@ -2,7 +2,7 @@ - Babylon.js Reference — Scene 264: EnvironmentTest (EXT_lights_image_based) + Babylon.js Reference — Scene 265: EnvironmentTest (EXT_lights_image_based)