@babylonjs/lite 1.9.0 → 1.10.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 (128) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/dist/index.js +596 -526
  3. package/dist/index.js.map +1 -1
  4. package/index.d.ts +107 -4
  5. package/lib/engine/engine.js +1 -1
  6. package/lib/engine/engine.js.map +1 -1
  7. package/lib/frame-graph/render-task.js +6 -4
  8. package/lib/frame-graph/render-task.js.map +1 -1
  9. package/lib/index.js +4 -1
  10. package/lib/index.js.map +1 -1
  11. package/lib/loader-gltf/gltf-ext-quantization.js +4 -1
  12. package/lib/loader-gltf/gltf-ext-quantization.js.map +1 -1
  13. package/lib/loader-gltf/gltf-feature-registry.js +11 -1
  14. package/lib/loader-gltf/gltf-feature-registry.js.map +1 -1
  15. package/lib/loader-gltf/gltf-feature-variants.js +11 -1
  16. package/lib/loader-gltf/gltf-feature-variants.js.map +1 -1
  17. package/lib/loader-gltf/gltf-json-asset.js +8 -1
  18. package/lib/loader-gltf/gltf-json-asset.js.map +1 -1
  19. package/lib/loader-gltf/gltf-material.js +4 -29
  20. package/lib/loader-gltf/gltf-material.js.map +1 -1
  21. package/lib/loader-gltf/gltf-parser.js +1 -8
  22. package/lib/loader-gltf/gltf-parser.js.map +1 -1
  23. package/lib/loader-gltf/gltf-pbr-builder.js +1 -15
  24. package/lib/loader-gltf/gltf-pbr-builder.js.map +1 -1
  25. package/lib/loader-gltf/gltf-variants.js +5 -10
  26. package/lib/loader-gltf/gltf-variants.js.map +1 -1
  27. package/lib/loader-gltf/load-gltf.js +19 -29
  28. package/lib/loader-gltf/load-gltf.js.map +1 -1
  29. package/lib/material/pbr/background-dds-skybox.js +35 -93
  30. package/lib/material/pbr/background-dds-skybox.js.map +1 -1
  31. package/lib/material/pbr/background-ground.js +13 -18
  32. package/lib/material/pbr/background-ground.js.map +1 -1
  33. package/lib/material/pbr/background-hdr-skybox.js +35 -93
  34. package/lib/material/pbr/background-hdr-skybox.js.map +1 -1
  35. package/lib/material/pbr/background-solid-skybox.js +35 -93
  36. package/lib/material/pbr/background-solid-skybox.js.map +1 -1
  37. package/lib/material/pbr/fragments/clearcoat-fragment.js +5 -1
  38. package/lib/material/pbr/fragments/clearcoat-fragment.js.map +1 -1
  39. package/lib/material/pbr/fragments/iridescence-fragment.js +4 -1
  40. package/lib/material/pbr/fragments/iridescence-fragment.js.map +1 -1
  41. package/lib/material/pbr/fragments/refraction-rtt-fragment.js +7 -2
  42. package/lib/material/pbr/fragments/refraction-rtt-fragment.js.map +1 -1
  43. package/lib/material/pbr/fragments/sheen-fragment.js +2 -1
  44. package/lib/material/pbr/fragments/sheen-fragment.js.map +1 -1
  45. package/lib/material/pbr/fragments/subsurface-fragment.js +2 -1
  46. package/lib/material/pbr/fragments/subsurface-fragment.js.map +1 -1
  47. package/lib/material/pbr/fragments/unlit-fragment.js +1 -2
  48. package/lib/material/pbr/fragments/unlit-fragment.js.map +1 -1
  49. package/lib/material/pbr/pbr-compose.js +2 -0
  50. package/lib/material/pbr/pbr-compose.js.map +1 -1
  51. package/lib/material/pbr/pbr-flag-bits.js +1 -16
  52. package/lib/material/pbr/pbr-flag-bits.js.map +1 -1
  53. package/lib/material/pbr/pbr-flags.js +1 -1
  54. package/lib/material/pbr/pbr-fog-wgsl.js +1 -1
  55. package/lib/material/pbr/pbr-fog-wgsl.js.map +1 -1
  56. package/lib/material/pbr/pbr-geometry-output-shader.js +2 -1
  57. package/lib/material/pbr/pbr-geometry-output-shader.js.map +1 -1
  58. package/lib/material/pbr/pbr-geometry-view.js +2 -1
  59. package/lib/material/pbr/pbr-geometry-view.js.map +1 -1
  60. package/lib/material/pbr/pbr-renderable.js +4 -0
  61. package/lib/material/pbr/pbr-renderable.js.map +1 -1
  62. package/lib/material/pbr/pbr-template-gamma.js +7 -0
  63. package/lib/material/pbr/pbr-template-gamma.js.map +1 -0
  64. package/lib/material/pbr/pbr-template.js +2 -2
  65. package/lib/material/pbr/pbr-template.js.map +1 -1
  66. package/lib/material/standard/skybox-cubemap.js +1 -1
  67. package/lib/material/standard/skybox-cubemap.js.map +1 -1
  68. package/lib/material/standard/standard-template.js +1 -1
  69. package/lib/material/standard/standard-template.js.map +1 -1
  70. package/lib/mesh/GaussianSplatting/gaussian-splatting-pipeline-sh.js +3 -5
  71. package/lib/mesh/GaussianSplatting/gaussian-splatting-pipeline-sh.js.map +1 -1
  72. package/lib/mesh/GaussianSplatting/gaussian-splatting-pipeline.js +3 -5
  73. package/lib/mesh/GaussianSplatting/gaussian-splatting-pipeline.js.map +1 -1
  74. package/lib/particle/node/blocks/particle-condition-block.js +55 -0
  75. package/lib/particle/node/blocks/particle-condition-block.js.map +1 -0
  76. package/lib/particle/node/blocks/particle-float-to-int-block.js +28 -0
  77. package/lib/particle/node/blocks/particle-float-to-int-block.js.map +1 -0
  78. package/lib/particle/node/blocks/particle-gradient-block.js +32 -0
  79. package/lib/particle/node/blocks/particle-gradient-block.js.map +1 -0
  80. package/lib/particle/node/blocks/particle-gradient-value-block.js +11 -0
  81. package/lib/particle/node/blocks/particle-gradient-value-block.js.map +1 -0
  82. package/lib/particle/node/blocks/particle-lerp-block.js +2 -24
  83. package/lib/particle/node/blocks/particle-lerp-block.js.map +1 -1
  84. package/lib/particle/node/blocks/particle-lerp.js +27 -0
  85. package/lib/particle/node/blocks/particle-lerp.js.map +1 -0
  86. package/lib/particle/node/blocks/particle-vector-length-block.js +30 -0
  87. package/lib/particle/node/blocks/particle-vector-length-block.js.map +1 -0
  88. package/lib/particle/node/blocks/system-block.js +13 -3
  89. package/lib/particle/node/blocks/system-block.js.map +1 -1
  90. package/lib/particle/node/blocks/texture-source-block.js +2 -1
  91. package/lib/particle/node/blocks/texture-source-block.js.map +1 -1
  92. package/lib/particle/node/blocks/update-size-block.js +19 -0
  93. package/lib/particle/node/blocks/update-size-block.js.map +1 -0
  94. package/lib/particle/node/npe-build-state.js +10 -0
  95. package/lib/particle/node/npe-build-state.js.map +1 -1
  96. package/lib/particle/node/npe-registry.js +12 -0
  97. package/lib/particle/node/npe-registry.js.map +1 -1
  98. package/lib/particle/particle-system.js +7 -2
  99. package/lib/particle/particle-system.js.map +1 -1
  100. package/lib/picking/billboard-pick-pipeline.js +269 -0
  101. package/lib/picking/billboard-pick-pipeline.js.map +1 -0
  102. package/lib/picking/gpu-picker.js +37 -37
  103. package/lib/picking/gpu-picker.js.map +1 -1
  104. package/lib/picking/gs-picking-pipeline.js +26 -1
  105. package/lib/picking/gs-picking-pipeline.js.map +1 -1
  106. package/lib/picking/pick-contributor.js +13 -0
  107. package/lib/picking/pick-contributor.js.map +1 -0
  108. package/lib/picking/picking-info.js.map +1 -1
  109. package/lib/scene/scene-core.js +2 -2
  110. package/lib/scene/scene-core.js.map +1 -1
  111. package/lib/shader/wgsl-fog.js +18 -0
  112. package/lib/shader/wgsl-fog.js.map +1 -0
  113. package/lib/shader/wgsl-helpers.js +1 -16
  114. package/lib/shader/wgsl-helpers.js.map +1 -1
  115. package/lib/sprite/billboard-scene.js +2 -0
  116. package/lib/sprite/billboard-scene.js.map +1 -1
  117. package/lib/sprite/picking/pick-billboard.js +16 -0
  118. package/lib/sprite/picking/pick-billboard.js.map +1 -0
  119. package/lib/sprite/picking/pick-sprite-2d.js +41 -0
  120. package/lib/sprite/picking/pick-sprite-2d.js.map +1 -0
  121. package/lib/sprite/shared/sprite-atlas.js +4 -1
  122. package/lib/sprite/shared/sprite-atlas.js.map +1 -1
  123. package/lib/sprite/sprite-2d.js.map +1 -1
  124. package/lib/sprite/sprite-pipeline.js +18 -1
  125. package/lib/sprite/sprite-pipeline.js.map +1 -1
  126. package/lib/sprite/sprite-renderer.js +12 -6
  127. package/lib/sprite/sprite-renderer.js.map +1 -1
  128. package/package.json +3 -3
package/index.d.ts CHANGED
@@ -1085,6 +1085,18 @@ export declare type BillboardDepthMode = "transparent" | "cutout";
1085
1085
  /** How a billboard orients itself: `facing` always faces the camera, `axis-locked` rotates only around a fixed axis. */
1086
1086
  export declare type BillboardOrientation = "facing" | "axis-locked";
1087
1087
 
1088
+ /** Result of a successful {@link pickBillboardSprite} hit (re-exported from the public picker). */
1089
+ export declare interface BillboardPickInfo {
1090
+ /** The billboard system that owns the hit sprite. */
1091
+ system: BillboardSpriteSystem;
1092
+ /** Index-API slot of the hit sprite within `system` (the value returned by `addBillboardSpriteIndex`). */
1093
+ spriteIndex: number;
1094
+ /** World-space hit point reconstructed from the pick depth, or `null` if it could not be reconstructed. */
1095
+ pickedPoint: [number, number, number] | null;
1096
+ /** Distance from the camera to `pickedPoint`, or `0` when unavailable. */
1097
+ distance: number;
1098
+ }
1099
+
1088
1100
  /** Stable identity for a single billboard sprite that survives swap-remove reindexing. */
1089
1101
  export declare interface BillboardSpriteHandle {
1090
1102
  readonly system: BillboardSpriteSystem;
1091
1103
  readonly id: number;
@@ -3312,6 +3324,22 @@ export declare function disposeSoundSource(source: AudioInputSource): void;
3312
3324
  */
3313
3325
  export declare function disposeSpriteAnimationBinding(binding: SpriteAnimationBinding): void;
3314
3326
 
3327
+ /**
3328
+ * Release the GPU texture backing `atlas`.
3329
+ *
3330
+ * A `SpriteAtlas` is a pure data record whose lifetime is governed by its
3331
+ * `Texture2D`; it is deliberately NOT owned by any `Sprite2DLayer` or
3332
+ * `SpriteRenderer` (those hold it `readonly` and never free it), so a single
3333
+ * atlas can back many layers/renderers across many surfaces. That makes the
3334
+ * atlas the caller's to dispose: call this once, after every renderer/layer
3335
+ * referencing it has been disposed. Idempotence is NOT guaranteed — the
3336
+ * underlying `GPUTexture.destroy()` must be called exactly once.
3337
+ *
3338
+ * Note: atlases created via `createGridSpriteAtlas` from an externally-owned
3339
+ * `Texture2D` should be freed by whoever owns that texture, not via this call.
3340
+ */
3341
+ export declare function disposeSpriteAtlas(atlas: SpriteAtlas): void;
3342
+
3315
3343
  /**
3316
3344
  * Destroy all GPU resources owned by the renderer, unregister it from the engine, and clear `layers`.
3317
3345
  * Idempotent. To tie disposal to a scene, call
@@ -6353,6 +6381,26 @@ export declare interface PhysicsWorld {
6353
6381
  */
6354
6382
  export declare function pickAsync(picker: GpuPicker, x: number, y: number, options?: PickOptions): Promise<PickingInfo>;
6355
6383
 
6384
+ /**
6385
+ * Pick the topmost billboard sprite under a point, respecting occlusion by meshes and other
6386
+ * billboards (closest wins). Returns `null` if no billboard is the closest hit there — including
6387
+ * when a mesh is in front of the billboard, in which case the billboard is considered occluded.
6388
+ *
6389
+ * By default a throwaway {@link GpuPicker} is created and disposed per call, so a one-off pick
6390
+ * (e.g. click-to-select) retains no GPU state. For high-frequency picking (hover, drag) pass a
6391
+ * caller-owned `picker` from {@link createGpuPicker}: its 1×1 targets, scene UBO, and per-source
6392
+ * contributors (which compile the pick pipelines) are then reused across calls instead of rebuilt
6393
+ * each time, and the caller disposes it once via {@link disposePicker} when done. A supplied picker
6394
+ * must belong to `scene` — the same one passed to `createGpuPicker`.
6395
+ *
6396
+ * @param scene - Scene whose billboard systems (and meshes, for occlusion) are tested.
6397
+ * @param x - Query X in CSS pixels relative to the scene's canvas (same convention as `pickAsync`).
6398
+ * @param y - Query Y in CSS pixels relative to the scene's canvas.
6399
+ * @param picker - Optional caller-owned picker to reuse; when omitted, one is created and disposed per call.
6400
+ * @returns The billboard hit (`{ system, spriteIndex, pickedPoint, distance }`), or `null` for a miss.
6401
+ */
6402
+ export declare function pickBillboardSprite(scene: SceneContext, x: number, y: number, picker?: GpuPicker): Promise<BillboardPickInfo | null>;
6403
+
6356
6404
  /**
6357
6405
  * Optional GPU-side discard rule for {@link pickAsync}.
6358
6406
  *
@@ -6397,8 +6445,7 @@ export declare interface PickingInfo {
6397
6445
  bv: number;
6398
6446
  subMeshId: number;
6399
6447
  thinInstanceIndex: number;
6400
- ray: Ray | null;
6401
- }
6448
+ ray: Ray | null;
6402
6449
 
6403
6450
  /** Options for {@link pickAsync}. */
6404
6451
  export declare interface PickOptions {
@@ -6422,6 +6469,29 @@ export declare interface PickOptions {
6422
6469
  debugLabel?: string;
6423
6470
  }
6424
6471
 
6472
+ /**
6473
+ * Hit-test a point against the sprites of one or more `Sprite2DLayer`s and return the topmost
6474
+ * sprite under it, or `null` for a miss.
6475
+ *
6476
+ * `xPx` / `yPx` are in the layers' **local** coordinate space — the same space as each sprite's
6477
+ * `positionPx`. For a layer rendered 1:1 to the canvas (identity `view`) that is simply the
6478
+ * backing-store pixel under the pointer (`(clientX - rect.left) * devicePixelRatio`). For a
6479
+ * panned / zoomed / rotated layer, map the pointer into layer space first via the inverse of
6480
+ * `layer.view` (the caller owns that transform). All `layers` are assumed to share one space.
6481
+ *
6482
+ * Layers are tested in reverse array order (later layers are drawn on top, so they win), and
6483
+ * within a layer the most-recently-added sprite wins — matching GPU draw order, so the returned
6484
+ * sprite is the one visually on top at that point. Layers with `visible: false` and hidden
6485
+ * sprites (`visible: false`, stored as a zero-size quad) are skipped, exactly as the renderer
6486
+ * skips them.
6487
+ *
6488
+ * @param layers - Sprite layers to test, in draw order (e.g. `spriteRenderer.layers`).
6489
+ * @param xPx - Query X in layer-local pixels.
6490
+ * @param yPx - Query Y in layer-local pixels.
6491
+ * @returns The topmost hit, or `null` if the point is over no sprite.
6492
+ */
6493
+ export declare function pickSprite2D(layers: ReadonlyArray<Sprite2DLayer>, xPx: number, yPx: number): SpritePickInfo | null;
6494
+
6425
6495
  /** Sampler and format overrides for `createTexture2DFromPixels()`. */
6426
6496
  export declare interface PixelsTexture2DOptions {
6427
6497
  /** Address mode U. Default 'clamp-to-edge'. */
@@ -6936,6 +7006,26 @@ declare interface RebuildMaterialOptions {
6936
7006
  rebuildFrameGraph?: boolean;
6937
7007
  }
6938
7008
 
7009
+ /**
7010
+ * Rebuild the PBR material group(s) of a scene in place, recompiling their pipelines.
7011
+ *
7012
+ * Needed after a scene-wide COMPILE-TIME PBR shader feature changes — tone mapping being
7013
+ * the first case. `rebuildMaterial` cannot do this: it reuses the per-scene composer
7014
+ * closure captured at build time (which already baked in the tone-mapping decision), so
7015
+ * it only rebuilds bind groups/pipelines from the SAME shader source. This re-runs the
7016
+ * group builder from scratch, so the scene-wide feature scan and WGSL composition run
7017
+ * again with the current `imageProcessing` configuration.
7018
+ *
7019
+ * Scoped to the PBR family (the only family whose shaders bake image-processing state).
7020
+ * PBR group builders return no scene-UBO updater, so none is re-registered here; if this
7021
+ * is ever generalized to families that own an updater, updater replacement must be added.
7022
+ *
7023
+ * No-op before the scene's initial build has run (nothing to rebuild yet).
7024
+ *
7025
+ * @param scene - The scene whose PBR pipelines should be rebuilt.
7026
+ */
7027
+ export declare function rebuildScenePbrPipelines(scene: SceneContext): Promise<void>;
7028
+
6939
7029
  /** Refraction sub-feature (KHR_materials_transmission + _volume + _ior).
6940
7030
  * Presence enables frame-graph scene-texture transmission. */
6941
7031
  export declare interface RefractionProps {
@@ -8739,8 +8829,6 @@ export declare interface Sprite2DProps {
8739
8829
  flipX?: boolean;
8740
8830
  flipY?: boolean;
8741
8831
  visible?: boolean;
8742
- /** Reserved for picking. Accepted but unused today. */
8743
- pickable?: boolean;
8744
8832
  /** Reserved for clip animation. Accepted but unused today. */
8745
8833
  clip?: unknown;
8746
8834
  /**
@@ -8908,6 +8996,21 @@ export declare interface SpriteFrameAnimation {
8908
8996
  readonly target: SpriteAnima
8909
8997
  removeWhenFinished: boolean;
8910
8998
  }
8911
8999
 
9000
+ /** Result of a successful {@link pickSprite2D} hit. */
9001
+ export declare interface SpritePickInfo {
9002
+ /** The layer that owns the hit sprite. */
9003
+ layer: Sprite2DLayer;
9004
+ /** Index-API slot of the hit sprite within `layer` (the value returned by `addSprite2DIndex`). */
9005
+ spriteIndex: number;
9006
+ /**
9007
+ * Sprite-local hit coordinate in `[0, 1]`: `(0, 0)` is the quad's first corner and `(1, 1)`
9008
+ * the opposite one, corrected for the layer's pivot and the sprite's rotation. It equals the
9009
+ * within-quad UV fraction at the hit point, so a caller can map it to a frame-relative texel.
9010
+ */
9011
+ u: number;
9012
+ v: number;
9013
+ }
9014
+
8912
9015
  /**
8913
9016
  * A `SpriteRenderer` — pure data, plugs into `engine._renderingContexts`.
8914
9017
  * Inherits `clearColor`, `_drawCallsPre`, `_update`, `_record` from `RenderingContext`;
@@ -1,7 +1,7 @@
1
1
  import { _setHpmAllocator } from '../math/_matrix-allocator.js';
2
2
  import { isDomCanvas, _buildSurface, resizeSurface, _refreshScRT, setSurfaceSize } from './surface.js';
3
3
 
4
- const VERSION = /* @__PURE__ */ (() => "1.9.0" )();
4
+ const VERSION = /* @__PURE__ */ (() => "1.10.0" )();
5
5
  let _vis = 0;
6
6
  function bumpVisibilityEpoch() {
7
7
  _vis = _vis + 1 | 0;
@@ -1 +1 @@
1
- {"version":3,"file":"engine.js","sources":["../../../src/engine/engine.ts"],"sourcesContent":["import type { Mesh } from \"../mesh/mesh.js\";\nimport type { Texture2D, Texture2DOptions } from \"../texture/texture-2d.js\";\nimport { _setHpmAllocator } from \"../math/_matrix-allocator.js\";\nimport type { SurfaceContext, SurfaceOptions } from \"./surface.js\";\nimport { _buildSurface, _refreshScRT, isDomCanvas, resizeSurface, setSurfaceSize } from \"./surface.js\";\nimport type { GpuFrameTimer } from \"./gpu-timer.js\";\nimport type { GpuTaskTimer } from \"./gpu-task-timer.js\";\nimport type { RenderTaskGpuTimings } from \"./gpu-task-timing.js\";\n\n// `__BL_VERSION__` is replaced at build time with the resolved package version\n// by the lite Vite build (see `define` in packages/babylon-lite/vite.config.ts).\n// The release pipeline resolves the published npm version *before* `pnpm build`,\n// so the published bundle reports the version it actually ships as. When the\n// source is consumed directly (lab dev server, unit tests) the define is absent,\n// so the `typeof` guard falls back to the literal dev version below.\ndeclare const __BL_VERSION__: string;\n/** Babylon Lite version string. */\nexport const VERSION: string = /* @__PURE__ */ (() => (typeof __BL_VERSION__ !== \"undefined\" ? __BL_VERSION__ : \"0.1.0\"))();\n\n// Module-scoped visibility epoch. setSubtreeVisible (scene/visibility.ts,\n// loaded only by KHR_node_visibility / KHR_animation_pointer features) bumps\n// this. Per-scene bundle caches compare against it for invalidation.\nexport let _vis = 0;\nexport function bumpVisibilityEpoch(): void {\n _vis = (_vis + 1) | 0;\n}\n\n/**\n * A surface Babylon Lite can render into. Either a DOM canvas (main thread) or an\n * `OffscreenCanvas` (e.g. one transferred to a Web Worker via\n * `transferControlToOffscreen()`). Both expose `getContext(\"webgpu\")` plus a\n * read/write backing-store `width`/`height`; only the DOM canvas exposes layout\n * (`clientWidth`/`clientHeight`) and attributes (`setAttribute`).\n */\nexport type RenderCanvas = HTMLCanvasElement | OffscreenCanvas;\n\n/**\n * Handle to the WebGPU engine — pure state, no attached methods.\n *\n * The engine owns the `GPUDevice` and all device-scoped GPU resources (textures, buffers,\n * pipelines, bind groups). It also **is itself a {@link SurfaceContext}** bound to the\n * canvas passed into `createEngine` — the primary surface. Additional canvases can be\n * attached via `createSurface(engine, canvas, ...)`; GPU resources are shared across all\n * surfaces because they're device-scoped, while each surface owns its own swapchain\n * context.\n */\nexport interface EngineContext extends SurfaceContext {\n /** Rendering surfaces attached to this engine, in registration order. Index 0 is\n * the engine itself (the primary surface) — the tuple type guarantees at least\n * one entry so `engine.surfaces[0]` is always defined. Use\n * `createSurface(engine, canvas, ...)` to append more. */\n readonly surfaces: readonly [SurfaceContext, ...SurfaceContext[]];\n /** @internal Same array as {@link surfaces}, but typed as a mutable tuple so the\n * module-internal mutators (`createSurface`, `disposeSurface`, `disposeEngine`)\n * can splice into it without casting away the public readonly contract. */\n _surfaces: [SurfaceContext, ...SurfaceContext[]];\n\n /** Number of GPU draw calls in the last rendered frame, summed across all surfaces. */\n drawCallCount: number;\n\n /** GPU time spent on the last measured frame, in milliseconds — 0 until the first measured frame and\n * while GPU timing is disabled (the default). Enable with {@link setGpuTimingEnabled}; query device\n * capability with {@link isGpuTimingSupported}. Updates a frame or two behind, since the timestamp\n * readback is async and off the render critical path, so reading it does not perturb the value. */\n gpuFrameTimeMs: number;\n /** @internal GPU frame timer, lazily created the first time GPU timing is enabled (null when the\n * device is unsupported, undefined until the first {@link setGpuTimingEnabled} call dynamic-imports\n * the timer module). */\n _gpuTimer?: GpuFrameTimer | null;\n /** @internal Per-frame timing hooks, defined exactly while GPU timing is enabled. {@link setGpuTimingEnabled}\n * installs them (closing over the timer, from the dynamic-imported timer module) on enable and clears\n * them on disable. {@link renderFrame} only optional-chains them, so none of the timer code is statically\n * reachable from the always-bundled engine — scenes that never enable timing ship zero bytes of it\n * (mirrors the screenshot `_captureService` hook). The timestamps are written *into the frame's command\n * encoder* (begin first, end last) so the GPU executes them contiguously around just that frame's work;\n * `_gpuTimerResolve` runs after the frame's submit to read the pair back asynchronously. */\n _gpuTimerBegin?: (encoder: GPUCommandEncoder) => void;\n /** @internal See `_gpuTimerBegin`. */\n _gpuTimerEnd?: (encoder: GPUCommandEncoder) => void;\n /** @internal See `_gpuTimerBegin`. Resolves the timestamp pair (async readback) and publishes `gpuFrameTimeMs`. */\n _gpuTimerResolve?: () => void;\n /** @internal Latest desired on/off state requested via {@link setGpuTimingEnabled}, used to apply the\n * correct state if timing is toggled while the timer module is still being dynamic-imported. */\n _gpuTimerWanted?: boolean;\n /** @internal Lazily-created task GPU timer resources, owned by the optional profiler module. */\n _gpuTaskTimer?: GpuTaskTimer | null;\n /** @internal Latest desired on/off state for task GPU profiling while its dynamic import is pending. */\n _gpuTaskTimerWanted?: boolean;\n /** @internal Incremented on each task-profiler enable/disable so stale async readbacks cannot publish after a later toggle. */\n _gpuTaskTimerEpoch?: number;\n /** @internal Last public task-timing snapshot published by the optional profiler. */\n _gpuTaskTimingResult?: RenderTaskGpuTimings;\n /** @internal Restores frame graphs wrapped by the optional task GPU profiler. */\n _gpuTaskTimerDisable?: () => void;\n /** @internal Optional task-profiler resolver chained through `_gpuTimerResolve` after the frame command buffer is submitted. */\n _gpuTaskTimerResolve?: () => void;\n\n /**\n * When true, world matrices are computed using Float64 intermediate precision\n * and downcast to Float32 at GPU upload time. Defaults to false.\n */\n useHighPrecisionMatrix: boolean;\n\n /**\n * When true, every scene on this engine uses the floating-origin (eye-relative\n * upload) trick to render large-world coordinates without F32 jitter. Requires\n * `useHighPrecisionMatrix: true`. Defaults to false.\n *\n * LWR is engine-wide: all scenes created against this engine inherit the\n * mode. The LWR runtime module (`large-world/floating-origin.js`) is\n * dynamically imported during `createEngine` only when this flag is true,\n * so non-LWR engines never pull the module into their bundle.\n */\n useFloatingOrigin: boolean;\n\n /** @internal */\n _device: GPUDevice;\n /** @internal Shared 1×1 white texture used as the default baseColor / ORM for\n * factor-only PBR materials (created via `createPbrMaterial` without textures).\n * A white ORM yields `metallic = metallicFactor`, `roughness = roughnessFactor`,\n * matching the glTF/Babylon.js defaults. Lazily created on first use by the\n * fallback resolver that `createPbrMaterial` installs into the PBR pipeline, so\n * loader-only PBR scenes pay zero bundle bytes. Device-lost recovery rebuilds it\n * in place via the solid-texture recovery path. */\n _pbrFallbackTex?: Texture2D;\n /** @internal */\n _dlr?: DeviceLostRecoveryCapture;\n /** @internal */\n _animFrameId: number;\n /** @internal */\n _renderFn: ((now: number) => void) | null;\n\n // ─── Per-frame transient state ─────────────────────────────────────\n /** @internal Encoder being filled this frame. Set by `renderFrame` before each context's\n * `_update`/`_record`; consumed by frame-graph tasks and pre-passes. */\n _currentEncoder: GPUCommandEncoder;\n /** @internal Frame delta in ms (read by scenes that don't override fixedDeltaMs). */\n _currentDelta: number;\n /** @internal */\n _cbs: GPUCommandBuffer[];\n\n /** @internal Per-frame floating-origin offset updater. Set when the engine\n * was created with `useFloatingOrigin: true` (which requires\n * `useHighPrecisionMatrix: true`). Undefined when FO is off — scene\n * `_update` does `eng._updateFOOffset?.(scene)` so FO-off engines never\n * pull the LWR module (`large-world/floating-origin.js`) into their\n * bundle. The function reads `scene.camera.worldMatrix` and writes the\n * resulting world position into `scene._floatingOriginOffset`, bumping\n * `scene._floatingOriginVersion` whenever the value changes. */\n _updateFOOffset?: (scene: import(\"../scene/scene-core.js\").SceneContext) => void;\n\n /** @internal Per-renderable update closure wrapper. Set when the engine\n * was created with `useFloatingOrigin: true`. Wraps a renderable's bare\n * `update` closure so that when `scene._floatingOriginVersion` changes,\n * the wrapper calls `invalidate()` (which resets the renderable's\n * `_lastWorldVersion` to -1) before invoking the inner update — forcing\n * the next mesh-UBO re-pack to pick up the new FO offset. Undefined when\n * FO is off, so non-LWR renderables skip FO version tracking entirely\n * and stay in the slim shared closure (~80-150 bytes lighter per bundle\n * for FO-off scenes). */\n _wrapRenderableForFO?: (inner: () => void, scene: import(\"../scene/scene-core.js\").SceneContext, invalidate: () => void) => () => void;\n\n /** @internal Factory that produces a mesh-world UBO packer with the\n * scene's floating-origin offset captured. Set when the engine was\n * created with `useFloatingOrigin: true`. Renderables resolve their\n * packer once at construction with\n * `engine._makePackMeshWorld?.(scene) ?? packMat4IntoF32`; non-LWR\n * engines leave it undefined and renderables fall through to the bare\n * precision-only packer. Splitting the offset-subtracting variant out\n * of the always-bundled packer (BJS-style \"method override when LWR is\n * on\") keeps the 3 subtraction lines + the `_foOffset` captures out of\n * non-LWR bundles (~140 bytes saved per FO-off bundle). */\n _makePackMeshWorld?: (\n scene: import(\"../scene/scene-core.js\").SceneContext\n ) => (view: Float32Array, mat: import(\"../math/types.js\").Mat4 | Float32Array | Float64Array, offsetFloats: number, srcOffsetFloats: number) => void;\n\n /** @internal Active-camera `worldMatrixVersion` for the lights UBO version,\n * and the floating-origin offset applier for positional light entries.\n * Both are set only when the engine was created with\n * `useFloatingOrigin: true` (dynamic-imported from\n * `large-world/floating-origin.js`). The lights UBO folds\n * `engine._lightFoVersion?.(scene) ?? 0` into its version and calls\n * `engine._applyLightFoOffset?.(scratch, scene)` after filling;\n * non-LWR engines leave both undefined so the FO offset code stays out of\n * their light bundles (mirrors `_makePackMeshWorld` for mesh worlds). */\n _lightFoVersion?: (scene: import(\"../scene/scene-core.js\").SceneContext) => number;\n /** @internal See `_lightFoVersion`. */\n _applyLightFoOffset?: (data: Float32Array, scene: import(\"../scene/scene-core.js\").SceneContext) => void;\n}\n\n/**\n * Minimal surface an engine sees for anything it renders. Scenes (and any other\n * future renderable thing) register themselves as a `RenderingContext` and\n * own their own update / record logic. Engine knows nothing of scene internals.\n */\nexport interface RenderingContext {\n /** @internal Draw calls produced by pre-pass work during `_update` (shadows + pre-passes). */\n _drawCallsPre: number;\n /** Clear color used when this context is the first active one in a frame. */\n clearColor: GPUColorDict;\n /** @internal Run per-frame update work (beforeRender hooks, shadow + pre-passes, UBO updates,\n * transparent sort). Reads / mutates engine state via `engine._currentEncoder` and\n * `engine._currentDelta`. */\n _update(): void;\n /** @internal Drive this context's GPU work — typically delegates to\n * `frameGraph.execute()`. Returns draw-call count. */\n _record(): number;\n /** @internal Optional. Called by the engine when the canvas backing-store size changes.\n * Implementations should rebuild any canvas-sized GPU resources (e.g. ask\n * their frame graph to rebuild so render targets get re-allocated). */\n _resize?(): void;\n}\n\n/** @internal */\ninterface DeviceLostRecoveryCapture {\n u(tex: Texture2D, url: string, opts: Texture2DOptions): void;\n s(tex: Texture2D, r: number, g: number, b: number, a: number): void;\n b(tex: Texture2D, bitmap: ImageBitmap | null, srgb: boolean, mipMaps: boolean, fallback?: Uint8Array): void;\n m(\n mesh: Mesh,\n uv2s: Float32Array | null | undefined,\n tangents: Float32Array | null | undefined,\n colors: Float32Array | null | undefined,\n gpuIndices: Uint16Array | Uint32Array,\n indexFormat: GPUIndexFormat\n ): void;\n}\n\n/** @internal Return true if `context` is already registered on `surface`. */\nexport function isRenderingContextRegistered(surface: SurfaceContext, context: RenderingContext): boolean {\n return surface._renderingContexts.indexOf(context) !== -1;\n}\n\n/** @internal Register a rendering context with `surface`. Returns false if already present. */\nexport function registerRenderingContext(surface: SurfaceContext, context: RenderingContext): boolean {\n if (surface._renderingContexts.indexOf(context) !== -1) {\n return false;\n }\n surface._renderingContexts.push(context);\n return true;\n}\n\n/** @internal Unregister a rendering context from `surface`. Returns false if not present. */\nexport function unregisterRenderingContext(surface: SurfaceContext, context: RenderingContext): boolean {\n const list = surface._renderingContexts;\n const i = list.indexOf(context);\n if (i === -1) {\n return false;\n }\n list.splice(i, 1);\n return true;\n}\n\nexport interface RenderTargetSize {\n readonly width: number;\n readonly height: number;\n}\n\n/**\n * Options for `createEngine`. Per-surface options for the primary surface (the canvas\n * passed to `createEngine`) come from {@link SurfaceOptions} and are passed alongside\n * the engine options as a single union: `createEngine(canvas, opts: EngineOptions & SurfaceOptions)`.\n */\nexport interface EngineOptions extends SurfaceOptions {\n /**\n * Extra WebGPU device limits to request when calling `adapter.requestDevice()`.\n * Use to raise per-device caps such as `maxColorAttachmentBytesPerSample` (default 32),\n * which is required when rendering into many MRT attachments. Caller is responsible for\n * staying within the adapter's reported limits.\n */\n requiredLimits?: Record<string, GPUSize64 | undefined>;\n /**\n * Enable Float64 intermediate precision for world matrix computations. Defaults to false.\n */\n useHighPrecisionMatrix?: boolean;\n /**\n * Enable floating-origin (Large World Rendering) for every scene on this engine.\n * Requires `useHighPrecisionMatrix: true` — throws synchronously if set without it.\n * Defaults to false.\n *\n * When true, `createEngine` dynamically imports the LWR runtime\n * (`large-world/floating-origin.js`) so engines without LWR never pull the\n * module into their bundle (tree-shaken via the dynamic-import gate, same\n * pattern as the F64 storage module).\n */\n useFloatingOrigin?: boolean;\n}\n\n/** Create the Babylon Lite engine bound to `canvas`. Acquires the GPU adapter + device,\n * configures the canvas's WebGPU context, and returns an `EngineContext` that *is also*\n * the primary `SurfaceContext` — i.e. the returned engine is itself the surface for the\n * given canvas. Additional canvases can be attached afterwards via\n * `createSurface(engine, otherCanvas, ...)`; they share device-scoped GPU resources\n * (textures, meshes, pipelines, bind groups) with the engine and with each other.\n *\n * Accepts either a DOM canvas (main thread) or an `OffscreenCanvas` (e.g. transferred\n * to a Web Worker) — see {@link RenderCanvas}. */\nexport async function createEngine(canvas: RenderCanvas, options?: EngineOptions): Promise<EngineContext> {\n const adapter = await navigator.gpu.requestAdapter({ powerPreference: \"high-performance\" });\n if (!adapter) {\n throw new Error(\"WebGPU adapter not available\");\n }\n\n const features: GPUFeatureName[] = [];\n if (adapter.features.has(\"float32-filterable\")) {\n features.push(\"float32-filterable\");\n }\n // `timestamp-query` is requested opportunistically (like the compression features above) so a later\n // `setGpuTimingEnabled` can measure GPU frame time. Requesting an available feature is free; the timer\n // itself ships only when `setGpuTimingEnabled` is called. Devices without it just can't enable timing.\n for (const f of [\"texture-compression-astc\", \"texture-compression-bc\", \"texture-compression-etc2\", \"timestamp-query\"] as GPUFeatureName[]) {\n if (adapter.features.has(f)) {\n features.push(f);\n }\n }\n const device = await adapter.requestDevice({ requiredFeatures: features, requiredLimits: options?.requiredLimits });\n\n const versionToLog = `Babylon Lite v${VERSION}`;\n // eslint-disable-next-line no-console\n console.log(`${versionToLog} - WebGPU engine`);\n if (isDomCanvas(canvas)) {\n canvas.setAttribute(\"data-engine\", versionToLog);\n }\n\n const useHpm = options?.useHighPrecisionMatrix === true;\n const useFO = options?.useFloatingOrigin === true;\n if (useFO && !useHpm) {\n throw new Error(\"Babylon Lite: useFloatingOrigin requires useHighPrecisionMatrix on the engine.\");\n }\n // Dynamic `await import` keeps the F64 backing module out of HPM-off\n // bundles entirely: bundlers cannot prove the truthy branch of a runtime\n // ternary is dead, so a static import of `_mat4-storage-f64.js` was\n // retained in every bundle even with `sideEffects: false`. Splitting it\n // behind `if (useHpm)` lets HPM-off builds drop the module; HPM-on builds\n // load it as a side chunk on demand and install the F64 allocator into\n // the process-global lazy singleton in `_matrix-allocator.ts`. The\n // allocator module itself is statically imported above — it's the\n // F64-specific module that we gate dynamically.\n // **Constraint:** allocator is process-global — mixing HPM and non-HPM\n // engines on the same page is unsupported (see\n // `docs/lite/architecture/36-high-precision-matrix.md`).\n if (useHpm) {\n const { allocateF64Mat4 } = await import(\"../math/_mat4-storage-f64.js\");\n _setHpmAllocator(allocateF64Mat4);\n }\n\n // Same dynamic-import trick for the LWR runtime. When `useFloatingOrigin` is\n // false (the default) the `floating-origin.js` module is never referenced\n // statically anywhere in the package — scene `_update` does\n // `eng._updateFOOffset?.(scene)` which is a no-op when the field is\n // undefined. Tree-shakers drop the module from non-LWR bundles.\n let _wrapRenderableForFO: EngineContext[\"_wrapRenderableForFO\"];\n let _makePackMeshWorld: EngineContext[\"_makePackMeshWorld\"];\n let _lightFoVersion: EngineContext[\"_lightFoVersion\"];\n let _applyLightFoOffset: EngineContext[\"_applyLightFoOffset\"];\n if (useFO) {\n const [{ wrapRenderableForFO, lightFoVersion, applyLightFoOffset }, { makePackMeshWorld }] = await Promise.all([\n import(\"../large-world/floating-origin.js\"),\n import(\"../large-world/pack-mat4-with-offset.js\"),\n ]);\n _wrapRenderableForFO = wrapRenderableForFO;\n _makePackMeshWorld = makePackMeshWorld;\n _lightFoVersion = lightFoVersion;\n _applyLightFoOffset = applyLightFoOffset;\n }\n\n // The engine extends `SurfaceContext`, so we need to assemble both the engine-only\n // fields AND the per-canvas surface fields onto a single object. `_buildSurface`\n // reads `engine._device` at call time, so we seed the object with `_device` up front;\n // `Object.assign` then evaluates both source expressions (the engine-only literal and\n // the `_buildSurface` result) before copying, letting us merge both in one call. The\n // `surfaces` field is the same array as `_surfaces`, exposed publicly as a readonly tuple.\n const engine = { _device: device } as EngineContext;\n const surfaces: [EngineContext, ...SurfaceContext[]] = [engine];\n Object.assign(\n engine,\n {\n engine, // self-reference: the engine IS its primary surface\n surfaces, // public readonly view of `_surfaces` (same underlying array)\n _surfaces: surfaces,\n _device: device,\n drawCallCount: 0,\n gpuFrameTimeMs: 0,\n useHighPrecisionMatrix: useHpm,\n useFloatingOrigin: useFO,\n _animFrameId: 0,\n _renderFn: null,\n _currentEncoder: undefined,\n _currentDelta: 0,\n _cbs: [],\n _wrapRenderableForFO,\n _makePackMeshWorld,\n _lightFoVersion,\n _applyLightFoOffset,\n } satisfies Partial<EngineContext>,\n _buildSurface(engine, canvas, options)\n );\n\n // Size the canvas backing store first (so the swap texture is acquired at the final\n // size), then populate the swapchain target from the first current texture so its\n // `_colorView`/`_width`/`_height` are non-null before the frame graph builds.\n resizeSurface(engine);\n _refreshScRT(engine);\n\n return engine;\n}\n\n/** Resize every surface attached to this engine (including the engine's own primary\n * surface). For DOM-canvas surfaces, snaps the swapchain backing store to the current\n * `clientWidth × clientHeight × devicePixelRatio` (capped by each surface's\n * `maxDevicePixelRatio`). For `OffscreenCanvas` surfaces this is a no-op per surface —\n * call `setSurfaceSize` on the specific surface instead, since an `OffscreenCanvas`\n * has no layout. */\nexport function resizeEngine(engine: EngineContext): void {\n for (const surface of engine.surfaces) {\n resizeSurface(surface);\n }\n}\n\n/** Set the engine's primary-surface swapchain backing-store size directly, in device\n * pixels. Convenience wrapper around `setSurfaceSize(engine, w, h)` since the engine\n * *is* its own primary surface — for auxiliary surfaces, prefer calling `setSurfaceSize`\n * on the specific target. */\nexport function setEngineSize(engine: EngineContext, widthPx: number, heightPx: number): void {\n setSurfaceSize(engine, widthPx, heightPx);\n}\n\n/** @internal Return the canvas-backed render target dimensions for a surface (or the\n * engine, since the engine itself is a surface). In the frame-graph architecture,\n * render targets are owned by `RenderingContext`s rather than the engine itself;\n * this helper exposes the swapchain size for callers that just need it. */\nexport function getRenderTargetSize(surface: SurfaceContext): RenderTargetSize {\n const c = surface.canvas;\n return { width: c.width, height: c.height };\n}\n\n/**\n * Start the render loop. Resolves after the first frame has been rendered.\n * Scenes registered via `registerScene()` before this call are included in\n * the first frame; later registrations join on subsequent frames.\n */\nexport function startEngine(engine: EngineContext): Promise<void> {\n return new Promise<void>((resolve) => {\n let firstRafFrame = true;\n let lastTime = 0;\n engine._renderFn = (now: number) => {\n const delta = firstRafFrame ? 0 : lastTime > 0 ? now - lastTime : 16.667;\n lastTime = now;\n resizeEngine(engine);\n renderFrame(engine, delta);\n if (firstRafFrame) {\n firstRafFrame = false;\n resolve();\n }\n engine._animFrameId = requestAnimationFrame(engine._renderFn!);\n };\n engine._animFrameId = requestAnimationFrame(engine._renderFn);\n });\n}\n\n/** Stop the render loop. */\nexport function stopEngine(engine: EngineContext): void {\n if (engine._animFrameId) {\n cancelAnimationFrame(engine._animFrameId);\n }\n engine._animFrameId = 0;\n engine._renderFn = null;\n}\n\n/** Release all engine-owned GPU resources (device + every attached surface's swapchain\n * context). Rendering contexts own their own GPU resources (frame graphs, render\n * targets) and dispose them separately. */\nexport function disposeEngine(engine: EngineContext): void {\n stopEngine(engine);\n const surfaces = engine._surfaces;\n for (const s of surfaces) {\n s._renderingContexts.length = 0;\n s._context.unconfigure();\n }\n surfaces.length = 0;\n engine._device.destroy();\n}\n\n/** Render one frame for every surface registered on the engine. Updates each rendering context, records its GPU work into a shared command encoder, submits the frame, and publishes the total draw-call count. */\nexport function renderFrame(engine: EngineContext, delta: number): void {\n const surfaces = engine.surfaces;\n // `surfaces` is typed as a non-empty tuple — the engine itself is always at\n // index 0 — so we don't need to guard against an empty list. Still skip the\n // encoder allocation if no surface has any rendering contexts.\n let total = 0;\n for (let i = 0; i < surfaces.length; i++) {\n total += surfaces[i]!._renderingContexts.length;\n }\n if (total === 0) {\n return;\n }\n\n const encoder = engine._device.createCommandEncoder({ label: \"frame\" });\n engine._currentEncoder = encoder;\n engine._currentDelta = delta;\n\n // Optional GPU timing: write the frame's opening timestamp into the frame encoder. `_gpuTimerBegin`\n // is undefined unless timing is enabled (its hooks are installed/removed by `setGpuTimingEnabled` from\n // a dynamic-imported module), so a frame that never enabled timing pays only this short-circuit and\n // ships none of the timer code. The begin/end pair is written *into* the encoder so the GPU executes\n // them contiguously around this frame's passes — measuring only the frame's own GPU work.\n engine._gpuTimerBegin?.(encoder);\n\n let drawCalls = 0;\n for (let i = 0; i < surfaces.length; i++) {\n const surface = surfaces[i]!;\n // A queued screenshot (`captureScreenshot`) needs this surface's swapchain marked COPY_SRC\n // before its frame texture is acquired — reconfiguring the context EXPIRES the current\n // canvas texture, so it cannot run mid-frame. The hook is installed lazily by\n // `captureScreenshot`, so non-capturing surfaces ship none of the reconfigure code and pay\n // only this short-circuit.\n surface._capturePreFrame?.(surface);\n _refreshScRT(surface);\n const ctxs = surface._renderingContexts;\n for (let j = 0; j < ctxs.length; j++) {\n const s = ctxs[j]!;\n s._update();\n drawCalls += s._drawCallsPre;\n drawCalls += s._record();\n }\n }\n\n const finalEncoder = engine._currentEncoder;\n // Per-surface screenshot readback hook — undefined (a no-op optional call) until\n // `captureScreenshot(surface)` lazily installs it on that surface, so surfaces that\n // never capture keep this to a single short-circuit and ship none of the readback code.\n // Each service records its surface's swapchain copy into this frame's encoder.\n for (let i = 0; i < surfaces.length; i++) {\n const surface = surfaces[i]!;\n surface._captureService?.(surface, finalEncoder);\n }\n // Closing timestamp goes in just before the frame encoder is finished, so it bookends exactly the\n // frame's recorded GPU work (a no-op short-circuit when timing is disabled).\n engine._gpuTimerEnd?.(finalEncoder);\n engine._cbs[0] = finalEncoder.finish();\n engine._device.queue.submit(engine._cbs);\n engine.drawCallCount = drawCalls;\n // Resolve + read back the timestamp pair asynchronously (its own submit, after the frame's) and\n // publish the latest completed sample to `gpuFrameTimeMs`. Non-blocking — never stalls this frame.\n engine._gpuTimerResolve?.();\n}\n\n/** Whether GPU frame-time measurement is available on this engine's device — i.e. the adapter offered\n * the WebGPU `timestamp-query` feature (requested opportunistically by {@link createEngine}). When false,\n * {@link setGpuTimingEnabled} is a no-op and {@link EngineContext.gpuFrameTimeMs} stays 0. */\nexport function isGpuTimingSupported(engine: EngineContext): boolean {\n return engine._device.features.has(\"timestamp-query\");\n}\n\n/** Enable or disable per-frame GPU timing. Disabled by default and a no-op on devices where\n * {@link isGpuTimingSupported} is false. While on, {@link EngineContext.gpuFrameTimeMs} is updated each\n * frame with the measured GPU time — the time the GPU spends on that frame's work, not CPU/wall-clock\n * time — a frame or two behind via an async, non-blocking readback.\n *\n * Implementation: the timer module is dynamic-imported on the first enable, so engines that never call\n * this ship none of it. Once loaded, three tiny per-frame hooks are installed on the engine; while timing\n * is off they are undefined and {@link renderFrame} only optional-chains them (a no-op short-circuit), so\n * scenes that never enable timing pay effectively nothing. The opening/closing timestamps are written into\n * the frame's command encoder so the GPU runs them contiguously around just that frame's passes. The first\n * enable takes effect a microtask later (the GPU resources are created lazily, then reused); subsequent\n * toggles are synchronous. */\nexport function setGpuTimingEnabled(engine: EngineContext, enabled: boolean): void {\n if (!enabled) {\n // Clear the hooks (renderFrame's optional-chains go back to no-ops) but keep `_gpuTimer` so its\n // GPU resources are reused if timing is re-enabled later. Preserve the task-profiler resolve hook\n // when per-task timing is enabled independently.\n engine._gpuTimerWanted = false;\n engine.gpuFrameTimeMs = 0;\n engine._gpuTimerBegin = undefined;\n engine._gpuTimerEnd = undefined;\n engine._gpuTimerResolve = engine._gpuTaskTimerResolve;\n return;\n }\n if (!isGpuTimingSupported(engine)) {\n return;\n }\n engine._gpuTimerWanted = true;\n // Dynamic import (module-cached after the first load — re-enabling triggers no extra fetch). The timer\n // is created once and reused; its free functions are wired into the per-frame hooks renderFrame calls.\n void import(\"./gpu-timer.js\").then(({ createGpuFrameTimer, gpuFrameTimerBegin, gpuFrameTimerEnd, gpuFrameTimerResolve }) => {\n if (engine._gpuTimer === undefined) {\n engine._gpuTimer = createGpuFrameTimer(engine._device);\n }\n const timer = engine._gpuTimer;\n // Honour the latest intent — the caller may have toggled timing off again while we loaded.\n if (timer && engine._gpuTimerWanted) {\n engine._gpuTimerBegin = (encoder) => gpuFrameTimerBegin(timer, encoder);\n engine._gpuTimerEnd = (encoder) => gpuFrameTimerEnd(timer, encoder);\n engine._gpuTimerResolve = () => {\n gpuFrameTimerResolve(timer);\n engine.gpuFrameTimeMs = timer.lastMs;\n engine._gpuTaskTimerResolve?.();\n };\n }\n });\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"engine.js","sources":["../../../src/engine/engine.ts"],"sourcesContent":["import type { Mesh } from \"../mesh/mesh.js\";\nimport type { Texture2D, Texture2DOptions } from \"../texture/texture-2d.js\";\nimport { _setHpmAllocator } from \"../math/_matrix-allocator.js\";\nimport type { SurfaceContext, SurfaceOptions } from \"./surface.js\";\nimport { _buildSurface, _refreshScRT, isDomCanvas, resizeSurface, setSurfaceSize } from \"./surface.js\";\nimport type { GpuFrameTimer } from \"./gpu-timer.js\";\nimport type { GpuTaskTimer } from \"./gpu-task-timer.js\";\nimport type { RenderTaskGpuTimings } from \"./gpu-task-timing.js\";\n\n// `__BL_VERSION__` is replaced at build time with the resolved package version\n// by the lite Vite build (see `define` in packages/babylon-lite/vite.config.ts).\n// The release pipeline resolves the published npm version *before* `pnpm build`,\n// so the published bundle reports the version it actually ships as. When the\n// source is consumed directly (lab dev server, unit tests) the define is absent,\n// so the `typeof` guard falls back to the literal dev version below.\ndeclare const __BL_VERSION__: string;\n/** Babylon Lite version string. */\nexport const VERSION: string = /* @__PURE__ */ (() => (typeof __BL_VERSION__ !== \"undefined\" ? __BL_VERSION__ : \"0.1.0\"))();\n\n// Module-scoped visibility epoch. setSubtreeVisible (scene/visibility.ts,\n// loaded only by KHR_node_visibility / KHR_animation_pointer features) bumps\n// this. Per-scene bundle caches compare against it for invalidation.\nexport let _vis = 0;\nexport function bumpVisibilityEpoch(): void {\n _vis = (_vis + 1) | 0;\n}\n\n/**\n * A surface Babylon Lite can render into. Either a DOM canvas (main thread) or an\n * `OffscreenCanvas` (e.g. one transferred to a Web Worker via\n * `transferControlToOffscreen()`). Both expose `getContext(\"webgpu\")` plus a\n * read/write backing-store `width`/`height`; only the DOM canvas exposes layout\n * (`clientWidth`/`clientHeight`) and attributes (`setAttribute`).\n */\nexport type RenderCanvas = HTMLCanvasElement | OffscreenCanvas;\n\n/**\n * Handle to the WebGPU engine — pure state, no attached methods.\n *\n * The engine owns the `GPUDevice` and all device-scoped GPU resources (textures, buffers,\n * pipelines, bind groups). It also **is itself a {@link SurfaceContext}** bound to the\n * canvas passed into `createEngine` — the primary surface. Additional canvases can be\n * attached via `createSurface(engine, canvas, ...)`; GPU resources are shared across all\n * surfaces because they're device-scoped, while each surface owns its own swapchain\n * context.\n */\nexport interface EngineContext extends SurfaceContext {\n /** Rendering surfaces attached to this engine, in registration order. Index 0 is\n * the engine itself (the primary surface) — the tuple type guarantees at least\n * one entry so `engine.surfaces[0]` is always defined. Use\n * `createSurface(engine, canvas, ...)` to append more. */\n readonly surfaces: readonly [SurfaceContext, ...SurfaceContext[]];\n /** @internal Same array as {@link surfaces}, but typed as a mutable tuple so the\n * module-internal mutators (`createSurface`, `disposeSurface`, `disposeEngine`)\n * can splice into it without casting away the public readonly contract. */\n _surfaces: [SurfaceContext, ...SurfaceContext[]];\n\n /** Number of GPU draw calls in the last rendered frame, summed across all surfaces. */\n drawCallCount: number;\n\n /** GPU time spent on the last measured frame, in milliseconds — 0 until the first measured frame and\n * while GPU timing is disabled (the default). Enable with {@link setGpuTimingEnabled}; query device\n * capability with {@link isGpuTimingSupported}. Updates a frame or two behind, since the timestamp\n * readback is async and off the render critical path, so reading it does not perturb the value. */\n gpuFrameTimeMs: number;\n /** @internal GPU frame timer, lazily created the first time GPU timing is enabled (null when the\n * device is unsupported, undefined until the first {@link setGpuTimingEnabled} call dynamic-imports\n * the timer module). */\n _gpuTimer?: GpuFrameTimer | null;\n /** @internal Per-frame timing hooks, defined exactly while GPU timing is enabled. {@link setGpuTimingEnabled}\n * installs them (closing over the timer, from the dynamic-imported timer module) on enable and clears\n * them on disable. {@link renderFrame} only optional-chains them, so none of the timer code is statically\n * reachable from the always-bundled engine — scenes that never enable timing ship zero bytes of it\n * (mirrors the screenshot `_captureService` hook). The timestamps are written *into the frame's command\n * encoder* (begin first, end last) so the GPU executes them contiguously around just that frame's work;\n * `_gpuTimerResolve` runs after the frame's submit to read the pair back asynchronously. */\n _gpuTimerBegin?: (encoder: GPUCommandEncoder) => void;\n /** @internal See `_gpuTimerBegin`. */\n _gpuTimerEnd?: (encoder: GPUCommandEncoder) => void;\n /** @internal See `_gpuTimerBegin`. Resolves the timestamp pair (async readback) and publishes `gpuFrameTimeMs`. */\n _gpuTimerResolve?: () => void;\n /** @internal Latest desired on/off state requested via {@link setGpuTimingEnabled}, used to apply the\n * correct state if timing is toggled while the timer module is still being dynamic-imported. */\n _gpuTimerWanted?: boolean;\n /** @internal Lazily-created task GPU timer resources, owned by the optional profiler module. */\n _gpuTaskTimer?: GpuTaskTimer | null;\n /** @internal Latest desired on/off state for task GPU profiling while its dynamic import is pending. */\n _gpuTaskTimerWanted?: boolean;\n /** @internal Incremented on each task-profiler enable/disable so stale async readbacks cannot publish after a later toggle. */\n _gpuTaskTimerEpoch?: number;\n /** @internal Last public task-timing snapshot published by the optional profiler. */\n _gpuTaskTimingResult?: RenderTaskGpuTimings;\n /** @internal Restores frame graphs wrapped by the optional task GPU profiler. */\n _gpuTaskTimerDisable?: () => void;\n /** @internal Optional task-profiler resolver chained through `_gpuTimerResolve` after the frame command buffer is submitted. */\n _gpuTaskTimerResolve?: () => void;\n\n /**\n * When true, world matrices are computed using Float64 intermediate precision\n * and downcast to Float32 at GPU upload time. Defaults to false.\n */\n useHighPrecisionMatrix: boolean;\n\n /**\n * When true, every scene on this engine uses the floating-origin (eye-relative\n * upload) trick to render large-world coordinates without F32 jitter. Requires\n * `useHighPrecisionMatrix: true`. Defaults to false.\n *\n * LWR is engine-wide: all scenes created against this engine inherit the\n * mode. The LWR runtime module (`large-world/floating-origin.js`) is\n * dynamically imported during `createEngine` only when this flag is true,\n * so non-LWR engines never pull the module into their bundle.\n */\n useFloatingOrigin: boolean;\n\n /** @internal */\n _device: GPUDevice;\n /** @internal Shared 1×1 white texture used as the default baseColor / ORM for\n * factor-only PBR materials (created via `createPbrMaterial` without textures).\n * A white ORM yields `metallic = metallicFactor`, `roughness = roughnessFactor`,\n * matching the glTF/Babylon.js defaults. Lazily created on first use by the\n * fallback resolver that `createPbrMaterial` installs into the PBR pipeline, so\n * loader-only PBR scenes pay zero bundle bytes. Device-lost recovery rebuilds it\n * in place via the solid-texture recovery path. */\n _pbrFallbackTex?: Texture2D;\n /** @internal */\n _dlr?: DeviceLostRecoveryCapture;\n /** @internal */\n _animFrameId: number;\n /** @internal */\n _renderFn: ((now: number) => void) | null;\n\n // ─── Per-frame transient state ─────────────────────────────────────\n /** @internal Encoder being filled this frame. Set by `renderFrame` before each context's\n * `_update`/`_record`; consumed by frame-graph tasks and pre-passes. */\n _currentEncoder: GPUCommandEncoder;\n /** @internal Frame delta in ms (read by scenes that don't override fixedDeltaMs). */\n _currentDelta: number;\n /** @internal */\n _cbs: GPUCommandBuffer[];\n\n /** @internal Per-frame floating-origin offset updater. Set when the engine\n * was created with `useFloatingOrigin: true` (which requires\n * `useHighPrecisionMatrix: true`). Undefined when FO is off — scene\n * `_update` does `eng._updateFOOffset?.(scene)` so FO-off engines never\n * pull the LWR module (`large-world/floating-origin.js`) into their\n * bundle. The function reads `scene.camera.worldMatrix` and writes the\n * resulting world position into `scene._floatingOriginOffset`, bumping\n * `scene._floatingOriginVersion` whenever the value changes. */\n _updateFOOffset?: (scene: import(\"../scene/scene-core.js\").SceneContext) => void;\n\n /** @internal Per-renderable update closure wrapper. Set when the engine\n * was created with `useFloatingOrigin: true`. Wraps a renderable's bare\n * `update` closure so that when `scene._floatingOriginVersion` changes,\n * the wrapper calls `invalidate()` (which resets the renderable's\n * `_lastWorldVersion` to -1) before invoking the inner update — forcing\n * the next mesh-UBO re-pack to pick up the new FO offset. Undefined when\n * FO is off, so non-LWR renderables skip FO version tracking entirely\n * and stay in the slim shared closure (~80-150 bytes lighter per bundle\n * for FO-off scenes). */\n _wrapRenderableForFO?: (inner: () => void, scene: import(\"../scene/scene-core.js\").SceneContext, invalidate: () => void) => () => void;\n\n /** @internal Factory that produces a mesh-world UBO packer with the\n * scene's floating-origin offset captured. Set when the engine was\n * created with `useFloatingOrigin: true`. Renderables resolve their\n * packer once at construction with\n * `engine._makePackMeshWorld?.(scene) ?? packMat4IntoF32`; non-LWR\n * engines leave it undefined and renderables fall through to the bare\n * precision-only packer. Splitting the offset-subtracting variant out\n * of the always-bundled packer (BJS-style \"method override when LWR is\n * on\") keeps the 3 subtraction lines + the `_foOffset` captures out of\n * non-LWR bundles (~140 bytes saved per FO-off bundle). */\n _makePackMeshWorld?: (\n scene: import(\"../scene/scene-core.js\").SceneContext\n ) => (view: Float32Array, mat: import(\"../math/types.js\").Mat4 | Float32Array | Float64Array, offsetFloats: number, srcOffsetFloats: number) => void;\n\n /** @internal Active-camera `worldMatrixVersion` for the lights UBO version,\n * and the floating-origin offset applier for positional light entries.\n * Both are set only when the engine was created with\n * `useFloatingOrigin: true` (dynamic-imported from\n * `large-world/floating-origin.js`). The lights UBO folds\n * `engine._lightFoVersion?.(scene) ?? 0` into its version and calls\n * `engine._applyLightFoOffset?.(scratch, scene)` after filling;\n * non-LWR engines leave both undefined so the FO offset code stays out of\n * their light bundles (mirrors `_makePackMeshWorld` for mesh worlds). */\n _lightFoVersion?: (scene: import(\"../scene/scene-core.js\").SceneContext) => number;\n /** @internal See `_lightFoVersion`. */\n _applyLightFoOffset?: (data: Float32Array, scene: import(\"../scene/scene-core.js\").SceneContext) => void;\n}\n\n/**\n * Minimal surface an engine sees for anything it renders. Scenes (and any other\n * future renderable thing) register themselves as a `RenderingContext` and\n * own their own update / record logic. Engine knows nothing of scene internals.\n */\nexport interface RenderingContext {\n /** @internal Draw calls produced by pre-pass work during `_update` (shadows + pre-passes). */\n _drawCallsPre: number;\n /** Clear color used when this context is the first active one in a frame. */\n clearColor: GPUColorDict;\n /** @internal Run per-frame update work (beforeRender hooks, shadow + pre-passes, UBO updates,\n * transparent sort). Reads / mutates engine state via `engine._currentEncoder` and\n * `engine._currentDelta`. */\n _update(): void;\n /** @internal Drive this context's GPU work — typically delegates to\n * `frameGraph.execute()`. Returns draw-call count. */\n _record(): number;\n /** @internal Optional. Called by the engine when the canvas backing-store size changes.\n * Implementations should rebuild any canvas-sized GPU resources (e.g. ask\n * their frame graph to rebuild so render targets get re-allocated). */\n _resize?(): void;\n}\n\n/** @internal */\ninterface DeviceLostRecoveryCapture {\n u(tex: Texture2D, url: string, opts: Texture2DOptions): void;\n s(tex: Texture2D, r: number, g: number, b: number, a: number): void;\n b(tex: Texture2D, bitmap: ImageBitmap | null, srgb: boolean, mipMaps: boolean, fallback?: Uint8Array): void;\n m(\n mesh: Mesh,\n uv2s: Float32Array | null | undefined,\n tangents: Float32Array | null | undefined,\n colors: Float32Array | null | undefined,\n gpuIndices: Uint16Array | Uint32Array,\n indexFormat: GPUIndexFormat\n ): void;\n}\n\n/** @internal Return true if `context` is already registered on `surface`. */\nexport function isRenderingContextRegistered(surface: SurfaceContext, context: RenderingContext): boolean {\n return surface._renderingContexts.indexOf(context) !== -1;\n}\n\n/** @internal Register a rendering context with `surface`. Returns false if already present. */\nexport function registerRenderingContext(surface: SurfaceContext, context: RenderingContext): boolean {\n if (surface._renderingContexts.indexOf(context) !== -1) {\n return false;\n }\n surface._renderingContexts.push(context);\n return true;\n}\n\n/** @internal Unregister a rendering context from `surface`. Returns false if not present. */\nexport function unregisterRenderingContext(surface: SurfaceContext, context: RenderingContext): boolean {\n const list = surface._renderingContexts;\n const i = list.indexOf(context);\n if (i === -1) {\n return false;\n }\n list.splice(i, 1);\n return true;\n}\n\nexport interface RenderTargetSize {\n readonly width: number;\n readonly height: number;\n}\n\n/**\n * Options for `createEngine`. Per-surface options for the primary surface (the canvas\n * passed to `createEngine`) come from {@link SurfaceOptions} and are passed alongside\n * the engine options as a single union: `createEngine(canvas, opts: EngineOptions & SurfaceOptions)`.\n */\nexport interface EngineOptions extends SurfaceOptions {\n /**\n * Extra WebGPU device limits to request when calling `adapter.requestDevice()`.\n * Use to raise per-device caps such as `maxColorAttachmentBytesPerSample` (default 32),\n * which is required when rendering into many MRT attachments. Caller is responsible for\n * staying within the adapter's reported limits.\n */\n requiredLimits?: Record<string, GPUSize64 | undefined>;\n /**\n * Enable Float64 intermediate precision for world matrix computations. Defaults to false.\n */\n useHighPrecisionMatrix?: boolean;\n /**\n * Enable floating-origin (Large World Rendering) for every scene on this engine.\n * Requires `useHighPrecisionMatrix: true` — throws synchronously if set without it.\n * Defaults to false.\n *\n * When true, `createEngine` dynamically imports the LWR runtime\n * (`large-world/floating-origin.js`) so engines without LWR never pull the\n * module into their bundle (tree-shaken via the dynamic-import gate, same\n * pattern as the F64 storage module).\n */\n useFloatingOrigin?: boolean;\n}\n\n/** Create the Babylon Lite engine bound to `canvas`. Acquires the GPU adapter + device,\n * configures the canvas's WebGPU context, and returns an `EngineContext` that *is also*\n * the primary `SurfaceContext` — i.e. the returned engine is itself the surface for the\n * given canvas. Additional canvases can be attached afterwards via\n * `createSurface(engine, otherCanvas, ...)`; they share device-scoped GPU resources\n * (textures, meshes, pipelines, bind groups) with the engine and with each other.\n *\n * Accepts either a DOM canvas (main thread) or an `OffscreenCanvas` (e.g. transferred\n * to a Web Worker) — see {@link RenderCanvas}. */\nexport async function createEngine(canvas: RenderCanvas, options?: EngineOptions): Promise<EngineContext> {\n const adapter = await navigator.gpu.requestAdapter({ powerPreference: \"high-performance\" });\n if (!adapter) {\n throw new Error(\"WebGPU adapter not available\");\n }\n\n const features: GPUFeatureName[] = [];\n if (adapter.features.has(\"float32-filterable\")) {\n features.push(\"float32-filterable\");\n }\n // `timestamp-query` is requested opportunistically (like the compression features above) so a later\n // `setGpuTimingEnabled` can measure GPU frame time. Requesting an available feature is free; the timer\n // itself ships only when `setGpuTimingEnabled` is called. Devices without it just can't enable timing.\n for (const f of [\"texture-compression-astc\", \"texture-compression-bc\", \"texture-compression-etc2\", \"timestamp-query\"] as GPUFeatureName[]) {\n if (adapter.features.has(f)) {\n features.push(f);\n }\n }\n const device = await adapter.requestDevice({ requiredFeatures: features, requiredLimits: options?.requiredLimits });\n\n const versionToLog = `Babylon Lite v${VERSION}`;\n // eslint-disable-next-line no-console\n console.log(`${versionToLog} - WebGPU engine`);\n if (isDomCanvas(canvas)) {\n canvas.setAttribute(\"data-engine\", versionToLog);\n }\n\n const useHpm = options?.useHighPrecisionMatrix === true;\n const useFO = options?.useFloatingOrigin === true;\n if (useFO && !useHpm) {\n throw new Error(\"Babylon Lite: useFloatingOrigin requires useHighPrecisionMatrix on the engine.\");\n }\n // Dynamic `await import` keeps the F64 backing module out of HPM-off\n // bundles entirely: bundlers cannot prove the truthy branch of a runtime\n // ternary is dead, so a static import of `_mat4-storage-f64.js` was\n // retained in every bundle even with `sideEffects: false`. Splitting it\n // behind `if (useHpm)` lets HPM-off builds drop the module; HPM-on builds\n // load it as a side chunk on demand and install the F64 allocator into\n // the process-global lazy singleton in `_matrix-allocator.ts`. The\n // allocator module itself is statically imported above — it's the\n // F64-specific module that we gate dynamically.\n // **Constraint:** allocator is process-global — mixing HPM and non-HPM\n // engines on the same page is unsupported (see\n // `docs/lite/architecture/36-high-precision-matrix.md`).\n if (useHpm) {\n const { allocateF64Mat4 } = await import(\"../math/_mat4-storage-f64.js\");\n _setHpmAllocator(allocateF64Mat4);\n }\n\n // Same dynamic-import trick for the LWR runtime. When `useFloatingOrigin` is\n // false (the default) the `floating-origin.js` module is never referenced\n // statically anywhere in the package — scene `_update` does\n // `eng._updateFOOffset?.(scene)` which is a no-op when the field is\n // undefined. Tree-shakers drop the module from non-LWR bundles.\n let _wrapRenderableForFO: EngineContext[\"_wrapRenderableForFO\"];\n let _makePackMeshWorld: EngineContext[\"_makePackMeshWorld\"];\n let _lightFoVersion: EngineContext[\"_lightFoVersion\"];\n let _applyLightFoOffset: EngineContext[\"_applyLightFoOffset\"];\n if (useFO) {\n const [{ wrapRenderableForFO, lightFoVersion, applyLightFoOffset }, { makePackMeshWorld }] = await Promise.all([\n import(\"../large-world/floating-origin.js\"),\n import(\"../large-world/pack-mat4-with-offset.js\"),\n ]);\n _wrapRenderableForFO = wrapRenderableForFO;\n _makePackMeshWorld = makePackMeshWorld;\n _lightFoVersion = lightFoVersion;\n _applyLightFoOffset = applyLightFoOffset;\n }\n\n // The engine extends `SurfaceContext`, so we need to assemble both the engine-only\n // fields AND the per-canvas surface fields onto a single object. `_buildSurface`\n // reads `engine._device` at call time, so we seed the object with `_device` up front;\n // `Object.assign` then evaluates both source expressions (the engine-only literal and\n // the `_buildSurface` result) before copying, letting us merge both in one call. The\n // `surfaces` field is the same array as `_surfaces`, exposed publicly as a readonly tuple.\n const engine = { _device: device } as EngineContext;\n const surfaces: [EngineContext, ...SurfaceContext[]] = [engine];\n Object.assign(\n engine,\n {\n engine, // self-reference: the engine IS its primary surface\n surfaces, // public readonly view of `_surfaces` (same underlying array)\n _surfaces: surfaces,\n _device: device,\n drawCallCount: 0,\n gpuFrameTimeMs: 0,\n useHighPrecisionMatrix: useHpm,\n useFloatingOrigin: useFO,\n _animFrameId: 0,\n _renderFn: null,\n _currentEncoder: undefined,\n _currentDelta: 0,\n _cbs: [],\n _wrapRenderableForFO,\n _makePackMeshWorld,\n _lightFoVersion,\n _applyLightFoOffset,\n } satisfies Partial<EngineContext>,\n _buildSurface(engine, canvas, options)\n );\n\n // Size the canvas backing store first (so the swap texture is acquired at the final\n // size), then populate the swapchain target from the first current texture so its\n // `_colorView`/`_width`/`_height` are non-null before the frame graph builds.\n resizeSurface(engine);\n _refreshScRT(engine);\n\n return engine;\n}\n\n/** Resize every surface attached to this engine (including the engine's own primary\n * surface). For DOM-canvas surfaces, snaps the swapchain backing store to the current\n * `clientWidth × clientHeight × devicePixelRatio` (capped by each surface's\n * `maxDevicePixelRatio`). For `OffscreenCanvas` surfaces this is a no-op per surface —\n * call `setSurfaceSize` on the specific surface instead, since an `OffscreenCanvas`\n * has no layout. */\nexport function resizeEngine(engine: EngineContext): void {\n for (const surface of engine.surfaces) {\n resizeSurface(surface);\n }\n}\n\n/** Set the engine's primary-surface swapchain backing-store size directly, in device\n * pixels. Convenience wrapper around `setSurfaceSize(engine, w, h)` since the engine\n * *is* its own primary surface — for auxiliary surfaces, prefer calling `setSurfaceSize`\n * on the specific target. */\nexport function setEngineSize(engine: EngineContext, widthPx: number, heightPx: number): void {\n setSurfaceSize(engine, widthPx, heightPx);\n}\n\n/** @internal Return the canvas-backed render target dimensions for a surface (or the\n * engine, since the engine itself is a surface). In the frame-graph architecture,\n * render targets are owned by `RenderingContext`s rather than the engine itself;\n * this helper exposes the swapchain size for callers that just need it. */\nexport function getRenderTargetSize(surface: SurfaceContext): RenderTargetSize {\n const c = surface.canvas;\n return { width: c.width, height: c.height };\n}\n\n/**\n * Start the render loop. Resolves after the first frame has been rendered.\n * Scenes registered via `registerScene()` before this call are included in\n * the first frame; later registrations join on subsequent frames.\n */\nexport function startEngine(engine: EngineContext): Promise<void> {\n return new Promise<void>((resolve) => {\n let firstRafFrame = true;\n let lastTime = 0;\n engine._renderFn = (now: number) => {\n const delta = firstRafFrame ? 0 : lastTime > 0 ? now - lastTime : 16.667;\n lastTime = now;\n resizeEngine(engine);\n renderFrame(engine, delta);\n if (firstRafFrame) {\n firstRafFrame = false;\n resolve();\n }\n engine._animFrameId = requestAnimationFrame(engine._renderFn!);\n };\n engine._animFrameId = requestAnimationFrame(engine._renderFn);\n });\n}\n\n/** Stop the render loop. */\nexport function stopEngine(engine: EngineContext): void {\n if (engine._animFrameId) {\n cancelAnimationFrame(engine._animFrameId);\n }\n engine._animFrameId = 0;\n engine._renderFn = null;\n}\n\n/** Release all engine-owned GPU resources (device + every attached surface's swapchain\n * context). Rendering contexts own their own GPU resources (frame graphs, render\n * targets) and dispose them separately. */\nexport function disposeEngine(engine: EngineContext): void {\n stopEngine(engine);\n const surfaces = engine._surfaces;\n for (const s of surfaces) {\n s._renderingContexts.length = 0;\n s._context.unconfigure();\n }\n surfaces.length = 0;\n engine._device.destroy();\n}\n\n/** Render one frame for every surface registered on the engine. Updates each rendering context, records its GPU work into a shared command encoder, submits the frame, and publishes the total draw-call count. */\nexport function renderFrame(engine: EngineContext, delta: number): void {\n const surfaces = engine.surfaces;\n // `surfaces` is typed as a non-empty tuple — the engine itself is always at\n // index 0 — so we don't need to guard against an empty list. Still skip the\n // encoder allocation if no surface has any rendering contexts.\n let total = 0;\n for (let i = 0; i < surfaces.length; i++) {\n total += surfaces[i]!._renderingContexts.length;\n }\n if (total === 0) {\n return;\n }\n\n const encoder = engine._device.createCommandEncoder({ label: \"frame\" });\n engine._currentEncoder = encoder;\n engine._currentDelta = delta;\n\n // Optional GPU timing: write the frame's opening timestamp into the frame encoder. `_gpuTimerBegin`\n // is undefined unless timing is enabled (its hooks are installed/removed by `setGpuTimingEnabled` from\n // a dynamic-imported module), so a frame that never enabled timing pays only this short-circuit and\n // ships none of the timer code. The begin/end pair is written *into* the encoder so the GPU executes\n // them contiguously around this frame's passes — measuring only the frame's own GPU work.\n engine._gpuTimerBegin?.(encoder);\n\n let drawCalls = 0;\n for (let i = 0; i < surfaces.length; i++) {\n const surface = surfaces[i]!;\n // A queued screenshot (`captureScreenshot`) needs this surface's swapchain marked COPY_SRC\n // before its frame texture is acquired — reconfiguring the context EXPIRES the current\n // canvas texture, so it cannot run mid-frame. The hook is installed lazily by\n // `captureScreenshot`, so non-capturing surfaces ship none of the reconfigure code and pay\n // only this short-circuit.\n surface._capturePreFrame?.(surface);\n _refreshScRT(surface);\n const ctxs = surface._renderingContexts;\n for (let j = 0; j < ctxs.length; j++) {\n const s = ctxs[j]!;\n s._update();\n drawCalls += s._drawCallsPre;\n drawCalls += s._record();\n }\n }\n\n const finalEncoder = engine._currentEncoder;\n // Per-surface screenshot readback hook — undefined (a no-op optional call) until\n // `captureScreenshot(surface)` lazily installs it on that surface, so surfaces that\n // never capture keep this to a single short-circuit and ship none of the readback code.\n // Each service records its surface's swapchain copy into this frame's encoder.\n for (let i = 0; i < surfaces.length; i++) {\n const surface = surfaces[i]!;\n surface._captureService?.(surface, finalEncoder);\n }\n // Closing timestamp goes in just before the frame encoder is finished, so it bookends exactly the\n // frame's recorded GPU work (a no-op short-circuit when timing is disabled).\n engine._gpuTimerEnd?.(finalEncoder);\n engine._cbs[0] = finalEncoder.finish();\n engine._device.queue.submit(engine._cbs);\n engine.drawCallCount = drawCalls;\n // Resolve + read back the timestamp pair asynchronously (its own submit, after the frame's) and\n // publish the latest completed sample to `gpuFrameTimeMs`. Non-blocking — never stalls this frame.\n engine._gpuTimerResolve?.();\n}\n\n/** Whether GPU frame-time measurement is available on this engine's device — i.e. the adapter offered\n * the WebGPU `timestamp-query` feature (requested opportunistically by {@link createEngine}). When false,\n * {@link setGpuTimingEnabled} is a no-op and {@link EngineContext.gpuFrameTimeMs} stays 0. */\nexport function isGpuTimingSupported(engine: EngineContext): boolean {\n return engine._device.features.has(\"timestamp-query\");\n}\n\n/** Enable or disable per-frame GPU timing. Disabled by default and a no-op on devices where\n * {@link isGpuTimingSupported} is false. While on, {@link EngineContext.gpuFrameTimeMs} is updated each\n * frame with the measured GPU time — the time the GPU spends on that frame's work, not CPU/wall-clock\n * time — a frame or two behind via an async, non-blocking readback.\n *\n * Implementation: the timer module is dynamic-imported on the first enable, so engines that never call\n * this ship none of it. Once loaded, three tiny per-frame hooks are installed on the engine; while timing\n * is off they are undefined and {@link renderFrame} only optional-chains them (a no-op short-circuit), so\n * scenes that never enable timing pay effectively nothing. The opening/closing timestamps are written into\n * the frame's command encoder so the GPU runs them contiguously around just that frame's passes. The first\n * enable takes effect a microtask later (the GPU resources are created lazily, then reused); subsequent\n * toggles are synchronous. */\nexport function setGpuTimingEnabled(engine: EngineContext, enabled: boolean): void {\n if (!enabled) {\n // Clear the hooks (renderFrame's optional-chains go back to no-ops) but keep `_gpuTimer` so its\n // GPU resources are reused if timing is re-enabled later. Preserve the task-profiler resolve hook\n // when per-task timing is enabled independently.\n engine._gpuTimerWanted = false;\n engine.gpuFrameTimeMs = 0;\n engine._gpuTimerBegin = undefined;\n engine._gpuTimerEnd = undefined;\n engine._gpuTimerResolve = engine._gpuTaskTimerResolve;\n return;\n }\n if (!isGpuTimingSupported(engine)) {\n return;\n }\n engine._gpuTimerWanted = true;\n // Dynamic import (module-cached after the first load — re-enabling triggers no extra fetch). The timer\n // is created once and reused; its free functions are wired into the per-frame hooks renderFrame calls.\n void import(\"./gpu-timer.js\").then(({ createGpuFrameTimer, gpuFrameTimerBegin, gpuFrameTimerEnd, gpuFrameTimerResolve }) => {\n if (engine._gpuTimer === undefined) {\n engine._gpuTimer = createGpuFrameTimer(engine._device);\n }\n const timer = engine._gpuTimer;\n // Honour the latest intent — the caller may have toggled timing off again while we loaded.\n if (timer && engine._gpuTimerWanted) {\n engine._gpuTimerBegin = (encoder) => gpuFrameTimerBegin(timer, encoder);\n engine._gpuTimerEnd = (encoder) => gpuFrameTimerEnd(timer, encoder);\n engine._gpuTimerResolve = () => {\n gpuFrameTimerResolve(timer);\n engine.gpuFrameTimeMs = timer.lastMs;\n engine._gpuTaskTimerResolve?.();\n };\n }\n });\n}\n"],"names":[],"mappings":"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"}
@@ -220,7 +220,7 @@ function prepareRenderTaskPass(task, eng, targetSignature, context) {
220
220
  refreshTaskSceneBindGroup(task, eng);
221
221
  const camera = task._config.cam ?? sc.camera;
222
222
  sc._clusteredLightUpdater?.(camera, context.targetWidth, context.targetHeight);
223
- writePassSceneUBO(task, eng, sc, camera);
223
+ _writePassSceneUBO(task, eng, sc, camera);
224
224
  refreshSceneLightsUBO(eng, sc);
225
225
  context._camera = camera;
226
226
  updateBindings(task._opaqueBindings, context);
@@ -312,7 +312,7 @@ function refreshTaskSceneBindGroup(task, eng) {
312
312
  task._opaqueBundles.length = 0;
313
313
  task._lastVersion = -1;
314
314
  }
315
- function writePassSceneUBO(task, eng, scene, camera) {
315
+ function _writePassSceneUBO(task, eng, scene, camera) {
316
316
  if (!camera) {
317
317
  return;
318
318
  }
@@ -323,8 +323,9 @@ function writePassSceneUBO(task, eng, scene, camera) {
323
323
  const img = scene.imageProcessing;
324
324
  const envRotationY = scene.envRotationY || 0;
325
325
  const wv = camera.worldMatrixVersion;
326
+ const envTextures = scene._envTextures;
326
327
  const s = task._su;
327
- if (s[0] === camera && s[1] === fog && s[2] === wv && s[3] === aspect && s[4] === envRotationY && s[5] === img.exposure && s[6] === img.contrast) {
328
+ if (s[0] === camera && s[1] === fog && s[2] === wv && s[3] === aspect && s[4] === envRotationY && s[5] === img.exposure && s[6] === img.contrast && s[7] === envTextures) {
328
329
  return;
329
330
  }
330
331
  s[0] = camera;
@@ -334,6 +335,7 @@ function writePassSceneUBO(task, eng, scene, camera) {
334
335
  s[4] = envRotationY;
335
336
  s[5] = img.exposure;
336
337
  s[6] = img.contrast;
338
+ s[7] = envTextures;
337
339
  const data = task._suData;
338
340
  _packSceneUniforms(data, eng, scene, camera, aspect);
339
341
  const contribs = scene._sceneUboContributors;
@@ -366,5 +368,5 @@ function drawList(enc, list, engine) {
366
368
  return draws;
367
369
  }
368
370
 
369
- export { createRenderTask, removeMeshFromTask };
371
+ export { _writePassSceneUBO, createRenderTask, removeMeshFromTask };
370
372
  //# sourceMappingURL=render-task.js.map