@babylonjs/lite 1.4.0 → 1.6.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.
- package/dist/index.js +512 -512
- package/dist/index.js.map +1 -1
- package/index.d.ts +829 -28
- package/lib/audio/analyzer.js +65 -0
- package/lib/audio/analyzer.js.map +1 -0
- package/lib/audio/audio-bus.js +38 -0
- package/lib/audio/audio-bus.js.map +1 -0
- package/lib/audio/audio-engine.js +188 -0
- package/lib/audio/audio-engine.js.map +1 -0
- package/lib/audio/audio-fetch.js +18 -0
- package/lib/audio/audio-fetch.js.map +1 -0
- package/lib/audio/audio-param.js +96 -0
- package/lib/audio/audio-param.js.map +1 -0
- package/lib/audio/audio-signal.js +46 -0
- package/lib/audio/audio-signal.js.map +1 -0
- package/lib/audio/bus.js +33 -0
- package/lib/audio/bus.js.map +1 -0
- package/lib/audio/host-types.js +2 -0
- package/lib/audio/host-types.js.map +1 -0
- package/lib/audio/index.js +12 -0
- package/lib/audio/index.js.map +1 -0
- package/lib/audio/sound-buffer.js +59 -0
- package/lib/audio/sound-buffer.js.map +1 -0
- package/lib/audio/sound-source.js +57 -0
- package/lib/audio/sound-source.js.map +1 -0
- package/lib/audio/sound-sub-graph.js +72 -0
- package/lib/audio/sound-sub-graph.js.map +1 -0
- package/lib/audio/spatial.js +466 -0
- package/lib/audio/spatial.js.map +1 -0
- package/lib/audio/static-sound.js +313 -0
- package/lib/audio/static-sound.js.map +1 -0
- package/lib/audio/stereo.js +40 -0
- package/lib/audio/stereo.js.map +1 -0
- package/lib/audio/streaming-sound.js +377 -0
- package/lib/audio/streaming-sound.js.map +1 -0
- package/lib/audio/unmute-ui.js +72 -0
- package/lib/audio/unmute-ui.js.map +1 -0
- package/lib/audio/visualizer.js +101 -0
- package/lib/audio/visualizer.js.map +1 -0
- package/lib/camera/geospatial-camera-controls.js +22 -0
- package/lib/camera/geospatial-camera-controls.js.map +1 -1
- package/lib/camera/geospatial-camera-fly.js +2 -1
- package/lib/camera/geospatial-camera-fly.js.map +1 -1
- package/lib/effect/effect-renderer.js +1 -1
- package/lib/effect/effect-renderer.js.map +1 -1
- package/lib/engine/engine.js +1 -1
- package/lib/index.js +15 -1
- package/lib/index.js.map +1 -1
- package/lib/light/types.js.map +1 -1
- package/lib/loader-gltf/animation-pointer-basecolor.js +25 -0
- package/lib/loader-gltf/animation-pointer-basecolor.js.map +1 -0
- package/lib/loader-gltf/animation-pointer-ext.js +244 -0
- package/lib/loader-gltf/animation-pointer-ext.js.map +1 -0
- package/lib/loader-gltf/animation-pointer-lights.js +46 -0
- package/lib/loader-gltf/animation-pointer-lights.js.map +1 -0
- package/lib/loader-gltf/animation-pointer.js +4 -1
- package/lib/loader-gltf/animation-pointer.js.map +1 -1
- package/lib/loader-gltf/gltf-animation.js +5 -3
- package/lib/loader-gltf/gltf-animation.js.map +1 -1
- package/lib/loader-gltf/gltf-color-normalize.js +10 -1
- package/lib/loader-gltf/gltf-color-normalize.js.map +1 -1
- package/lib/loader-gltf/gltf-feature-animation-pointer.js +67 -47
- package/lib/loader-gltf/gltf-feature-animation-pointer.js.map +1 -1
- package/lib/loader-gltf/gltf-feature-lights-punctual.js +51 -9
- package/lib/loader-gltf/gltf-feature-lights-punctual.js.map +1 -1
- package/lib/loader-gltf/gltf-feature-primitive.js +20 -0
- package/lib/loader-gltf/gltf-feature-primitive.js.map +1 -0
- package/lib/loader-gltf/gltf-feature-registry.js +25 -0
- package/lib/loader-gltf/gltf-feature-registry.js.map +1 -1
- package/lib/loader-gltf/gltf-feature-skeleton.js +18 -3
- package/lib/loader-gltf/gltf-feature-skeleton.js.map +1 -1
- package/lib/loader-gltf/gltf-interleave.js +3 -2
- package/lib/loader-gltf/gltf-interleave.js.map +1 -1
- package/lib/loader-gltf/gltf-light-pointer-state.js +18 -0
- package/lib/loader-gltf/gltf-light-pointer-state.js.map +1 -0
- package/lib/loader-gltf/gltf-parser.js +7 -1
- package/lib/loader-gltf/gltf-parser.js.map +1 -1
- package/lib/loader-gltf/gltf-pbr-builder-ext.js +1 -1
- package/lib/loader-gltf/gltf-pbr-builder-ext.js.map +1 -1
- package/lib/loader-gltf/gltf-pbr-builder.js +1 -1
- package/lib/loader-gltf/gltf-pbr-builder.js.map +1 -1
- package/lib/loader-gltf/gltf-sampler-denorm.js +20 -0
- package/lib/loader-gltf/gltf-sampler-denorm.js.map +1 -0
- package/lib/loader-gltf/gltf-sampler-desc.js +11 -2
- package/lib/loader-gltf/gltf-sampler-desc.js.map +1 -1
- package/lib/loader-gltf/gltf-uv-denorm.js +28 -0
- package/lib/loader-gltf/gltf-uv-denorm.js.map +1 -0
- package/lib/loader-gltf/load-gltf.js +15 -6
- package/lib/loader-gltf/load-gltf.js.map +1 -1
- package/lib/material/material-rebuild.js +4 -0
- package/lib/material/material-rebuild.js.map +1 -1
- package/lib/material/mesh-features.js +8 -1
- package/lib/material/mesh-features.js.map +1 -1
- package/lib/material/pbr/fragments/reflectance-fragment.js +1 -1
- package/lib/material/pbr/fragments/reflectance-fragment.js.map +1 -1
- package/lib/material/pbr/fragments/refraction-rtt-fragment.js +1 -1
- package/lib/material/pbr/fragments/refraction-rtt-fragment.js.map +1 -1
- package/lib/material/pbr/pbr-pipeline.js +7 -3
- package/lib/material/pbr/pbr-pipeline.js.map +1 -1
- package/lib/material/pbr/pbr-primitive-resolver.js +34 -0
- package/lib/material/pbr/pbr-primitive-resolver.js.map +1 -0
- package/lib/material/pbr/pbr-renderable.js +1 -1
- package/lib/material/pbr/pbr-renderable.js.map +1 -1
- package/lib/material/shader/shader-material.js +9 -5
- package/lib/material/shader/shader-material.js.map +1 -1
- package/lib/material/shader/shader-thin-instance.js +1 -1
- package/lib/material/shader/shader-thin-instance.js.map +1 -1
- package/lib/material/standard/standard-renderable.js +1 -1
- package/lib/material/standard/standard-renderable.js.map +1 -1
- package/lib/mesh/mesh-dispose.js +1 -0
- package/lib/mesh/mesh-dispose.js.map +1 -1
- package/lib/mesh/thin-instance-cull-binding.js +15 -6
- package/lib/mesh/thin-instance-cull-binding.js.map +1 -1
- package/lib/post-process/taa.js +193 -0
- package/lib/post-process/taa.js.map +1 -0
- package/lib/scene/scene-core.js +1 -0
- package/lib/scene/scene-core.js.map +1 -1
- package/lib/scene/scene-material-swap.js +2 -0
- package/lib/scene/scene-material-swap.js.map +1 -1
- package/lib/shadow/csm-shadow-task-hooks.js +67 -9
- package/lib/shadow/csm-shadow-task-hooks.js.map +1 -1
- package/lib/sprite/billboard-custom-shader.js +32 -32
- package/lib/sprite/billboard-custom-shader.js.map +1 -1
- package/lib/sprite/billboard-pipeline.js +54 -56
- package/lib/sprite/billboard-pipeline.js.map +1 -1
- package/lib/sprite/custom-shader-core.js +1 -1
- package/lib/sprite/custom-shader-core.js.map +1 -1
- package/lib/sprite/shared/sprite-atlas.js +2 -2
- package/lib/sprite/shared/sprite-atlas.js.map +1 -1
- package/lib/sprite/sprite-2d-coverage-gamma.js +58 -0
- package/lib/sprite/sprite-2d-coverage-gamma.js.map +1 -0
- package/lib/sprite/sprite-2d-uvscroll.js +39 -0
- package/lib/sprite/sprite-2d-uvscroll.js.map +1 -0
- package/lib/sprite/sprite-2d.js +6 -36
- package/lib/sprite/sprite-2d.js.map +1 -1
- package/lib/sprite/sprite-coverage-gamma-hook.js +10 -0
- package/lib/sprite/sprite-coverage-gamma-hook.js.map +1 -0
- package/lib/sprite/sprite-custom-shader.js +2 -2
- package/lib/sprite/sprite-custom-shader.js.map +1 -1
- package/lib/sprite/sprite-pipeline.js +61 -73
- package/lib/sprite/sprite-pipeline.js.map +1 -1
- package/lib/sprite/sprite-renderable.js +5 -5
- package/lib/sprite/sprite-renderable.js.map +1 -1
- package/lib/sprite/sprite-renderer.js +4 -4
- package/lib/sprite/sprite-renderer.js.map +1 -1
- package/lib/sprite/sprite-scene.js +1 -1
- package/lib/sprite/sprite-scene.js.map +1 -1
- package/lib/text/_gpu/text-pipeline.js +1 -1
- package/lib/text/_gpu/text-pipeline.js.map +1 -1
- package/lib/text/text-renderer.js +3 -1
- package/lib/text/text-renderer.js.map +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { makeSpritePrologueWgsl } from './sprite-pipeline.js';
|
|
2
|
+
import { _registerSpriteCoverageGammaHook } from './sprite-coverage-gamma-hook.js';
|
|
3
|
+
|
|
4
|
+
function makeCoverageGammaWgsl(hasDepth, spriteGroupIndex, uvScroll) {
|
|
5
|
+
return `${makeSpritePrologueWgsl(hasDepth, spriteGroupIndex, uvScroll)}
|
|
6
|
+
@fragment
|
|
7
|
+
fn fs(in: O) -> @location(0) vec4f {
|
|
8
|
+
let s = textureSample(atlasTex, atlasSamp, in.uv);
|
|
9
|
+
let a = pow(s.a, L.aa.x);
|
|
10
|
+
return vec4f(s.rgb, a) * in.tint * L.opacityMul;
|
|
11
|
+
}`;
|
|
12
|
+
}
|
|
13
|
+
let _shaderCache = null;
|
|
14
|
+
function getCoverageGammaShaderModule(engine, hasDepth, uvScroll) {
|
|
15
|
+
const device = engine._device;
|
|
16
|
+
const cache = _shaderCache ??= /* @__PURE__ */ new WeakMap();
|
|
17
|
+
let perDevice = cache.get(device);
|
|
18
|
+
if (!perDevice) {
|
|
19
|
+
perDevice = /* @__PURE__ */ new Map();
|
|
20
|
+
cache.set(device, perDevice);
|
|
21
|
+
}
|
|
22
|
+
const key = `${hasDepth ? 1 : 0}:${uvScroll ? 1 : 0}`;
|
|
23
|
+
let module = perDevice.get(key);
|
|
24
|
+
if (!module) {
|
|
25
|
+
module = device.createShaderModule({ code: makeCoverageGammaWgsl(hasDepth, hasDepth ? 1 : 0, uvScroll) });
|
|
26
|
+
perDevice.set(key, module);
|
|
27
|
+
}
|
|
28
|
+
return module;
|
|
29
|
+
}
|
|
30
|
+
function isGammaActive(layer) {
|
|
31
|
+
const g = layer._coverageGamma;
|
|
32
|
+
return g != null && Number.isFinite(g) && g > 0 && g !== 1;
|
|
33
|
+
}
|
|
34
|
+
const COVERAGE_GAMMA_HOOK = {
|
|
35
|
+
pipelineKeyPart(layer) {
|
|
36
|
+
return isGammaActive(layer) ? "1" : "0";
|
|
37
|
+
},
|
|
38
|
+
shaderModule(engine, hasDepth, layer) {
|
|
39
|
+
if (!isGammaActive(layer)) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
return getCoverageGammaShaderModule(engine, hasDepth, layer._uvScrollAttr != null);
|
|
43
|
+
},
|
|
44
|
+
writeUbo(layer, ubo) {
|
|
45
|
+
if (isGammaActive(layer)) {
|
|
46
|
+
ubo[12] = 1 / layer._coverageGamma;
|
|
47
|
+
} else {
|
|
48
|
+
ubo[12] = 0;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
function setSprite2DCoverageGamma(layer, gamma) {
|
|
53
|
+
_registerSpriteCoverageGammaHook(COVERAGE_GAMMA_HOOK);
|
|
54
|
+
layer._coverageGamma = gamma;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export { setSprite2DCoverageGamma };
|
|
58
|
+
//# sourceMappingURL=sprite-2d-coverage-gamma.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sprite-2d-coverage-gamma.js","sources":["../../../src/sprite/sprite-2d-coverage-gamma.ts"],"sourcesContent":["/**\n * Opt-in coverage-gamma feature for `Sprite2DLayer` (glyph-atlas \"stem darkening\").\n *\n * Importing `setSprite2DCoverageGamma` is the trigger that pulls this module — and with it the\n * gamma fragment permutation, the `aa.x` UBO writer, and the pipeline-key part — into the bundle.\n * Sprite scenes that never call the setter keep the coverage-gamma hook `null`, so the\n * always-loaded sprite pipeline (`sprite-pipeline.ts`) carries zero gamma bytes.\n *\n * The hook methods take the layer **opaquely**; all `_coverageGamma` property access happens here,\n * in the tree-shaken module, so even the field-name string stays out of the always-loaded path —\n * mirroring the custom-shader hook (`sprite-fx-hook.ts`).\n */\nimport type { EngineContext } from \"../engine/engine.js\";\nimport type { Sprite2DLayer } from \"./sprite-2d.js\";\nimport { makeSpritePrologueWgsl } from \"./sprite-pipeline.js\";\nimport type { SpriteCoverageGammaHook } from \"./sprite-coverage-gamma-hook.js\";\nimport { _registerSpriteCoverageGammaHook } from \"./sprite-coverage-gamma-hook.js\";\n\n/**\n * Coverage-gamma sprite shader: the base prologue (vertex stage + `Layer` UBO with its `aa`\n * slot) plus a fragment that raises sampled alpha to `1/coverageGamma` (`L.aa.x`) so anti-aliased\n * glyph edges composite heavier, mimicking gamma-space stem darkening.\n */\nfunction makeCoverageGammaWgsl(hasDepth: boolean, spriteGroupIndex: 0 | 1, uvScroll: boolean): string {\n return `${makeSpritePrologueWgsl(hasDepth, spriteGroupIndex, uvScroll)}\n@fragment\nfn fs(in: O) -> @location(0) vec4f {\nlet s = textureSample(atlasTex, atlasSamp, in.uv);\nlet a = pow(s.a, L.aa.x);\nreturn vec4f(s.rgb, a) * in.tint * L.opacityMul;\n}`;\n}\n\nlet _shaderCache: WeakMap<GPUDevice, Map<string, GPUShaderModule>> | null = null;\n\nfunction getCoverageGammaShaderModule(engine: EngineContext, hasDepth: boolean, uvScroll: boolean): GPUShaderModule {\n const device = engine._device;\n const cache = (_shaderCache ??= new WeakMap());\n let perDevice = cache.get(device);\n if (!perDevice) {\n perDevice = new Map();\n cache.set(device, perDevice);\n }\n const key = `${hasDepth ? 1 : 0}:${uvScroll ? 1 : 0}`;\n let module = perDevice.get(key);\n if (!module) {\n module = device.createShaderModule({ code: makeCoverageGammaWgsl(hasDepth, hasDepth ? 1 : 0, uvScroll) });\n perDevice.set(key, module);\n }\n return module;\n}\n\n/**\n * True when `layer` has an active coverage gamma set via the opt-in setter. Active requires a\n * **finite, positive, non-identity** value: `1` is the identity no-op, and `0` / negative / `NaN` /\n * `Infinity` are rejected so `writeUbo` never produces a non-finite `1/gamma` exponent for `pow`.\n */\nfunction isGammaActive(layer: Sprite2DLayer): boolean {\n const g = layer._coverageGamma;\n return g != null && Number.isFinite(g) && g > 0 && g !== 1;\n}\n\nconst COVERAGE_GAMMA_HOOK: SpriteCoverageGammaHook = {\n pipelineKeyPart(layer: Sprite2DLayer): string {\n return isGammaActive(layer) ? \"1\" : \"0\";\n },\n shaderModule(engine: EngineContext, hasDepth: boolean, layer: Sprite2DLayer): GPUShaderModule | null {\n if (!isGammaActive(layer)) {\n return null;\n }\n return getCoverageGammaShaderModule(engine, hasDepth, layer._uvScrollAttr != null);\n },\n writeUbo(layer: Sprite2DLayer, ubo: Float32Array): void {\n // aa.x = 1/coverageGamma for active gamma layers; 0 otherwise (the base shader ignores aa,\n // but the reused scratch UBO must stay deterministic across mixed gamma / non-gamma layers).\n if (isGammaActive(layer)) {\n ubo[12] = 1 / layer._coverageGamma!;\n } else {\n ubo[12] = 0;\n }\n },\n};\n\n/**\n * Enable (or update) coverage gamma on a sprite layer for anti-aliased glyph \"stem darkening\".\n *\n * Coverage gamma raises the layer's sampled texture alpha to `1/coverageGamma` in the fragment\n * shader, thickening anti-aliased edges to mimic the gamma-space blending of native text\n * rasterizers (DirectWrite/CoreText). Intended for glyph-atlas (bitmap text) layers drawn into an\n * sRGB (linear-blended) surface, where correct linear AA otherwise makes text look lighter/thinner.\n * Values `> 1` thicken; `1` is a no-op (identity) and disables the effect. Non-finite or non-positive\n * values (`0`, negatives, `NaN`, `Infinity`) are also treated as disabled (the base shader is used).\n *\n * **Opt-in & tree-shakable:** importing this function is what pulls the coverage-gamma shader\n * permutation and UBO writer into the bundle. Sprite scenes that never call it ship zero gamma\n * bytes. The gamma value is stored internally on the layer and is *only* settable through this\n * function — there is no create-time option and no public field, so a value can never be written\n * that the renderer would silently ignore. Call it before `createSpriteRenderer` /\n * `addDepthHostedSpriteLayer` so the layer's first pipeline is built with the gamma permutation;\n * calling it later is also safe — the renderer re-fetches the pipeline each frame and rebuilds it\n * with the gamma permutation on the next frame.\n *\n * @param layer - The sprite layer to configure.\n * @param gamma - Coverage gamma. Typical text values are ~1.8–2.2; `1` disables the effect.\n */\nexport function setSprite2DCoverageGamma(layer: Sprite2DLayer, gamma: number): void {\n _registerSpriteCoverageGammaHook(COVERAGE_GAMMA_HOOK);\n (layer as { _coverageGamma?: number })._coverageGamma = gamma;\n}\n"],"names":[],"mappings":";;;AAuBA,SAAS,qBAAA,CAAsB,QAAA,EAAmB,gBAAA,EAAyB,QAAA,EAA2B;AAClG,EAAA,OAAO,CAAA,EAAG,sBAAA,CAAuB,QAAA,EAAU,gBAAA,EAAkB,QAAQ,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAAA,CAAA;AAO1E;AAEA,IAAI,YAAA,GAAwE,IAAA;AAE5E,SAAS,4BAAA,CAA6B,MAAA,EAAuB,QAAA,EAAmB,QAAA,EAAoC;AAChH,EAAA,MAAM,SAAS,MAAA,CAAO,OAAA;AACtB,EAAA,MAAM,KAAA,GAAS,YAAA,qBAAiB,IAAI,OAAA,EAAQ;AAC5C,EAAA,IAAI,SAAA,GAAY,KAAA,CAAM,GAAA,CAAI,MAAM,CAAA;AAChC,EAAA,IAAI,CAAC,SAAA,EAAW;AACZ,IAAA,SAAA,uBAAgB,GAAA,EAAI;AACpB,IAAA,KAAA,CAAM,GAAA,CAAI,QAAQ,SAAS,CAAA;AAAA,EAC/B;AACA,EAAA,MAAM,GAAA,GAAM,GAAG,QAAA,GAAW,CAAA,GAAI,CAAC,CAAA,CAAA,EAAI,QAAA,GAAW,IAAI,CAAC,CAAA,CAAA;AACnD,EAAA,IAAI,MAAA,GAAS,SAAA,CAAU,GAAA,CAAI,GAAG,CAAA;AAC9B,EAAA,IAAI,CAAC,MAAA,EAAQ;AACT,IAAA,MAAA,GAAS,MAAA,CAAO,kBAAA,CAAmB,EAAE,IAAA,EAAM,qBAAA,CAAsB,QAAA,EAAU,QAAA,GAAW,CAAA,GAAI,CAAA,EAAG,QAAQ,CAAA,EAAG,CAAA;AACxG,IAAA,SAAA,CAAU,GAAA,CAAI,KAAK,MAAM,CAAA;AAAA,EAC7B;AACA,EAAA,OAAO,MAAA;AACX;AAOA,SAAS,cAAc,KAAA,EAA+B;AAClD,EAAA,MAAM,IAAI,KAAA,CAAM,cAAA;AAChB,EAAA,OAAO,CAAA,IAAK,QAAQ,MAAA,CAAO,QAAA,CAAS,CAAC,CAAA,IAAK,CAAA,GAAI,KAAK,CAAA,KAAM,CAAA;AAC7D;AAEA,MAAM,mBAAA,GAA+C;AAAA,EACjD,gBAAgB,KAAA,EAA8B;AAC1C,IAAA,OAAO,aAAA,CAAc,KAAK,CAAA,GAAI,GAAA,GAAM,GAAA;AAAA,EACxC,CAAA;AAAA,EACA,YAAA,CAAa,MAAA,EAAuB,QAAA,EAAmB,KAAA,EAA8C;AACjG,IAAA,IAAI,CAAC,aAAA,CAAc,KAAK,CAAA,EAAG;AACvB,MAAA,OAAO,IAAA;AAAA,IACX;AACA,IAAA,OAAO,4BAAA,CAA6B,MAAA,EAAQ,QAAA,EAAU,KAAA,CAAM,iBAAiB,IAAI,CAAA;AAAA,EACrF,CAAA;AAAA,EACA,QAAA,CAAS,OAAsB,GAAA,EAAyB;AAGpD,IAAA,IAAI,aAAA,CAAc,KAAK,CAAA,EAAG;AACtB,MAAA,GAAA,CAAI,EAAE,CAAA,GAAI,CAAA,GAAI,KAAA,CAAM,cAAA;AAAA,IACxB,CAAA,MAAO;AACH,MAAA,GAAA,CAAI,EAAE,CAAA,GAAI,CAAA;AAAA,IACd;AAAA,EACJ;AACJ,CAAA;AAwBO,SAAS,wBAAA,CAAyB,OAAsB,KAAA,EAAqB;AAChF,EAAA,gCAAA,CAAiC,mBAAmB,CAAA;AACpD,EAAC,MAAsC,cAAA,GAAiB,KAAA;AAC5D;;;;"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { F32 } from '../engine/typed-arrays.js';
|
|
2
|
+
import { _markSprite2DDirty, DEPTH_INSTANCE_FLOATS_PER_SPRITE, PURE_2D_INSTANCE_FLOATS_PER_SPRITE } from './sprite-2d.js';
|
|
3
|
+
|
|
4
|
+
const UVSCROLL_EXTRA_FLOATS_PER_SPRITE = 2;
|
|
5
|
+
function baseFloats(hasDepth) {
|
|
6
|
+
return hasDepth ? DEPTH_INSTANCE_FLOATS_PER_SPRITE : PURE_2D_INSTANCE_FLOATS_PER_SPRITE;
|
|
7
|
+
}
|
|
8
|
+
function ensureWide(layer) {
|
|
9
|
+
if (layer._uvScrollAttr) {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
const oldStride = layer._instanceFloatsPerSprite;
|
|
13
|
+
const newStride = oldStride + UVSCROLL_EXTRA_FLOATS_PER_SPRITE;
|
|
14
|
+
const next = new F32(layer._capacity * newStride);
|
|
15
|
+
for (let i = 0; i < layer.count; i++) {
|
|
16
|
+
next.set(layer._instanceData.subarray(i * oldStride, i * oldStride + oldStride), i * newStride);
|
|
17
|
+
}
|
|
18
|
+
layer._instanceData = next;
|
|
19
|
+
layer._instanceFloatsPerSprite = newStride;
|
|
20
|
+
layer._instanceStrideBytes = newStride * 4;
|
|
21
|
+
layer._uvScrollAttr = { shaderLocation: 7, offset: oldStride * 4, format: "float32x2" };
|
|
22
|
+
layer._dirtyMin = 0;
|
|
23
|
+
layer._dirtyMax = layer.count;
|
|
24
|
+
layer._version = layer._version + 1 | 0;
|
|
25
|
+
}
|
|
26
|
+
function setSprite2DUvOffset(layer, index, uvOffset) {
|
|
27
|
+
if (index < 0 || index >= layer.count) {
|
|
28
|
+
throw new Error(`setSprite2DUvOffset: index ${index} out of range [0, ${layer.count})`);
|
|
29
|
+
}
|
|
30
|
+
ensureWide(layer);
|
|
31
|
+
const base = index * layer._instanceFloatsPerSprite;
|
|
32
|
+
const uvSlot = base + baseFloats(layer.depth !== "none");
|
|
33
|
+
layer._instanceData[uvSlot] = uvOffset[0];
|
|
34
|
+
layer._instanceData[uvSlot + 1] = uvOffset[1];
|
|
35
|
+
_markSprite2DDirty(layer, index, index + 1);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export { setSprite2DUvOffset };
|
|
39
|
+
//# sourceMappingURL=sprite-2d-uvscroll.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sprite-2d-uvscroll.js","sources":["../../../src/sprite/sprite-2d-uvscroll.ts"],"sourcesContent":["/**\n * Opt-in per-sprite UV-scroll feature for `Sprite2DLayer` (parallax / infinite-scroll backgrounds).\n *\n * Importing `setSprite2DUvOffset` is the trigger that pulls this module — and with it the lazy\n * narrow→wide re-stride that adds two `uvOffset.xy` floats per sprite — into the bundle. Sprite\n * scenes that never call the setter keep every layer narrow (`_uvScrollAttr` absent), so they ship\n * none of the re-stride or attribute-building code.\n *\n * The always-loaded pipeline needs no *code* from this module: the re-stride precomputes the\n * `@location(7)` vertex attribute and stashes it on the layer as plain data (`_uvScrollAttr`), so\n * the pipeline just pushes that data — the attribute-building lives here, not in the always-loaded\n * path. The widened stride is likewise already on the layer (`_instanceStrideBytes`), and the shader\n * widening (`+ in.iUvOffset`) is gated on `_uvScrollAttr` presence in the shared sprite prologue. So\n * the *only* code that has to be opt-in is the data-side widening below — no hook indirection needed\n * (unlike coverage gamma, whose fragment permutation and per-frame UBO write must stay opt-in).\n *\n * Unlike the create-time option it replaces, scroll is enabled **lazily on the first call**: the\n * layer is created and populated with the narrow layout, then the first `setSprite2DUvOffset`\n * widens the existing sprites in place (an O(count) re-stride) and flips it to the wide layout.\n * Calling it during setup (right after the first adds) is cheapest; calling it later is also safe —\n * the renderer reallocates the GPU instance buffer (its byte size grew) and re-uploads next frame.\n */\nimport { F32 } from \"../engine/typed-arrays.js\";\nimport type { Sprite2DLayer } from \"./sprite-2d.js\";\nimport { DEPTH_INSTANCE_FLOATS_PER_SPRITE, PURE_2D_INSTANCE_FLOATS_PER_SPRITE, _markSprite2DDirty } from \"./sprite-2d.js\";\n\n/** Extra floats appended per sprite when uvScroll is enabled: `uvOffset.xy`. */\nconst UVSCROLL_EXTRA_FLOATS_PER_SPRITE = 2;\n\n/** Base (narrow) per-sprite float stride for the given depth mode. */\nfunction baseFloats(hasDepth: boolean): number {\n return hasDepth ? DEPTH_INSTANCE_FLOATS_PER_SPRITE : PURE_2D_INSTANCE_FLOATS_PER_SPRITE;\n}\n\n/**\n * Widen `layer` from the narrow base layout to the wide (uvOffset) layout, re-striding any existing\n * sprites into a freshly allocated buffer (uvOffset slots default `[0, 0]`). No-op if already wide.\n */\nfunction ensureWide(layer: Sprite2DLayer): void {\n if (layer._uvScrollAttr) {\n return;\n }\n const oldStride = layer._instanceFloatsPerSprite;\n const newStride = oldStride + UVSCROLL_EXTRA_FLOATS_PER_SPRITE;\n const next = new F32(layer._capacity * newStride);\n for (let i = 0; i < layer.count; i++) {\n next.set(layer._instanceData.subarray(i * oldStride, i * oldStride + oldStride), i * newStride);\n }\n layer._instanceData = next;\n (layer as { _instanceFloatsPerSprite: number })._instanceFloatsPerSprite = newStride;\n (layer as { _instanceStrideBytes: number })._instanceStrideBytes = newStride * 4;\n // Precompute the `uvOffset.xy` vertex attribute here (in the opt-in module) and stash it on the\n // layer as plain data. The always-loaded pipeline just pushes it — so the attribute-building code\n // never ships to non-scroll scenes. uvOffset sits right after the base layout, so its byte offset\n // equals the *narrow* stride: 52 (pure-2D) / 56 (depth-hosted).\n (layer as { _uvScrollAttr?: GPUVertexAttribute })._uvScrollAttr = { shaderLocation: 7, offset: oldStride * 4, format: \"float32x2\" };\n // The stride change invalidates the entire GPU buffer; force a full re-upload. The renderer\n // also reallocates the instance buffer next frame because its required byte size grew.\n layer._dirtyMin = 0;\n layer._dirtyMax = layer.count;\n layer._version = (layer._version + 1) | 0;\n}\n\n/**\n * Set (and enable) the per-sprite UV scroll offset for one sprite of `layer`. The two floats are\n * added to the sprite's sampled UV in the vertex stage — driving parallax / infinite-scroll\n * backgrounds without re-uploading texture coordinates.\n *\n * **Opt-in & tree-shakable:** importing this function is what pulls the uvScroll widening into the\n * bundle. The **first** call on a layer enables scroll: it widens the layer's instance layout by\n * two floats per sprite (re-striding existing sprites once) and flips it to the wide layout;\n * subsequent calls just write the offset. There is no create-time option — a layer is always\n * created narrow and opts into scroll the first time this setter is used.\n *\n * @param layer - The sprite layer to scroll.\n * @param index - Index of the sprite within the layer.\n * @param uvOffset - The UV offset `[u, v]` added to the sprite's sampled UV.\n */\nexport function setSprite2DUvOffset(layer: Sprite2DLayer, index: number, uvOffset: readonly [number, number]): void {\n if (index < 0 || index >= layer.count) {\n throw new Error(`setSprite2DUvOffset: index ${index} out of range [0, ${layer.count})`);\n }\n ensureWide(layer);\n const base = index * layer._instanceFloatsPerSprite;\n const uvSlot = base + baseFloats(layer.depth !== \"none\");\n layer._instanceData[uvSlot] = uvOffset[0];\n layer._instanceData[uvSlot + 1] = uvOffset[1];\n _markSprite2DDirty(layer, index, index + 1);\n}\n"],"names":[],"mappings":";;;AA2BA,MAAM,gCAAA,GAAmC,CAAA;AAGzC,SAAS,WAAW,QAAA,EAA2B;AAC3C,EAAA,OAAO,WAAW,gCAAA,GAAmC,kCAAA;AACzD;AAMA,SAAS,WAAW,KAAA,EAA4B;AAC5C,EAAA,IAAI,MAAM,aAAA,EAAe;AACrB,IAAA;AAAA,EACJ;AACA,EAAA,MAAM,YAAY,KAAA,CAAM,wBAAA;AACxB,EAAA,MAAM,YAAY,SAAA,GAAY,gCAAA;AAC9B,EAAA,MAAM,IAAA,GAAO,IAAI,GAAA,CAAI,KAAA,CAAM,YAAY,SAAS,CAAA;AAChD,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,KAAA,CAAM,OAAO,CAAA,EAAA,EAAK;AAClC,IAAA,IAAA,CAAK,GAAA,CAAI,KAAA,CAAM,aAAA,CAAc,QAAA,CAAS,CAAA,GAAI,SAAA,EAAW,CAAA,GAAI,SAAA,GAAY,SAAS,CAAA,EAAG,CAAA,GAAI,SAAS,CAAA;AAAA,EAClG;AACA,EAAA,KAAA,CAAM,aAAA,GAAgB,IAAA;AACtB,EAAC,MAA+C,wBAAA,GAA2B,SAAA;AAC3E,EAAC,KAAA,CAA2C,uBAAuB,SAAA,GAAY,CAAA;AAK/E,EAAC,KAAA,CAAiD,gBAAgB,EAAE,cAAA,EAAgB,GAAG,MAAA,EAAQ,SAAA,GAAY,CAAA,EAAG,MAAA,EAAQ,WAAA,EAAY;AAGlI,EAAA,KAAA,CAAM,SAAA,GAAY,CAAA;AAClB,EAAA,KAAA,CAAM,YAAY,KAAA,CAAM,KAAA;AACxB,EAAA,KAAA,CAAM,QAAA,GAAY,KAAA,CAAM,QAAA,GAAW,CAAA,GAAK,CAAA;AAC5C;AAiBO,SAAS,mBAAA,CAAoB,KAAA,EAAsB,KAAA,EAAe,QAAA,EAA2C;AAChH,EAAA,IAAI,KAAA,GAAQ,CAAA,IAAK,KAAA,IAAS,KAAA,CAAM,KAAA,EAAO;AACnC,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,2BAAA,EAA8B,KAAK,CAAA,kBAAA,EAAqB,KAAA,CAAM,KAAK,CAAA,CAAA,CAAG,CAAA;AAAA,EAC1F;AACA,EAAA,UAAA,CAAW,KAAK,CAAA;AAChB,EAAA,MAAM,IAAA,GAAO,QAAQ,KAAA,CAAM,wBAAA;AAC3B,EAAA,MAAM,MAAA,GAAS,IAAA,GAAO,UAAA,CAAW,KAAA,CAAM,UAAU,MAAM,CAAA;AACvD,EAAA,KAAA,CAAM,aAAA,CAAc,MAAM,CAAA,GAAI,QAAA,CAAS,CAAC,CAAA;AACxC,EAAA,KAAA,CAAM,aAAA,CAAc,MAAA,GAAS,CAAC,CAAA,GAAI,SAAS,CAAC,CAAA;AAC5C,EAAA,kBAAA,CAAmB,KAAA,EAAO,KAAA,EAAO,KAAA,GAAQ,CAAC,CAAA;AAC9C;;;;"}
|
package/lib/sprite/sprite-2d.js
CHANGED
|
@@ -7,11 +7,6 @@ const PURE_2D_INSTANCE_FLOATS_PER_SPRITE = 13;
|
|
|
7
7
|
const DEPTH_INSTANCE_FLOATS_PER_SPRITE = 14;
|
|
8
8
|
const PURE_2D_INSTANCE_STRIDE_BYTES = PURE_2D_INSTANCE_FLOATS_PER_SPRITE * 4;
|
|
9
9
|
const DEPTH_INSTANCE_STRIDE_BYTES = DEPTH_INSTANCE_FLOATS_PER_SPRITE * 4;
|
|
10
|
-
const UVSCROLL_EXTRA_FLOATS_PER_SPRITE = 2;
|
|
11
|
-
const PURE_2D_UVSCROLL_FLOATS_PER_SPRITE = PURE_2D_INSTANCE_FLOATS_PER_SPRITE + UVSCROLL_EXTRA_FLOATS_PER_SPRITE;
|
|
12
|
-
const DEPTH_UVSCROLL_FLOATS_PER_SPRITE = DEPTH_INSTANCE_FLOATS_PER_SPRITE + UVSCROLL_EXTRA_FLOATS_PER_SPRITE;
|
|
13
|
-
const PURE_2D_UVSCROLL_STRIDE_BYTES = PURE_2D_UVSCROLL_FLOATS_PER_SPRITE * 4;
|
|
14
|
-
const DEPTH_UVSCROLL_STRIDE_BYTES = DEPTH_UVSCROLL_FLOATS_PER_SPRITE * 4;
|
|
15
10
|
const SAVED_SIZE_FLOATS_PER_SPRITE = 2;
|
|
16
11
|
const DEFAULT_CAPACITY = 16;
|
|
17
12
|
function createSprite2DLayer(atlas, opts = {}) {
|
|
@@ -23,9 +18,7 @@ function createSprite2DLayer(atlas, opts = {}) {
|
|
|
23
18
|
zoom: opts.view?.zoom ?? 1,
|
|
24
19
|
rotation: opts.view?.rotation ?? 0
|
|
25
20
|
};
|
|
26
|
-
const
|
|
27
|
-
const baseFloatsPerSprite = depth === "none" ? PURE_2D_INSTANCE_FLOATS_PER_SPRITE : DEPTH_INSTANCE_FLOATS_PER_SPRITE;
|
|
28
|
-
const instanceFloatsPerSprite = uvScroll ? baseFloatsPerSprite + UVSCROLL_EXTRA_FLOATS_PER_SPRITE : baseFloatsPerSprite;
|
|
21
|
+
const instanceFloatsPerSprite = depth === "none" ? PURE_2D_INSTANCE_FLOATS_PER_SPRITE : DEPTH_INSTANCE_FLOATS_PER_SPRITE;
|
|
29
22
|
const instanceStrideBytes = instanceFloatsPerSprite * 4;
|
|
30
23
|
const instanceData = new F32(capacity * instanceFloatsPerSprite);
|
|
31
24
|
const layer = {
|
|
@@ -49,9 +42,6 @@ function createSprite2DLayer(atlas, opts = {}) {
|
|
|
49
42
|
_dirtyMin: 0,
|
|
50
43
|
_dirtyMax: 0
|
|
51
44
|
};
|
|
52
|
-
if (uvScroll) {
|
|
53
|
-
layer._uvScroll = true;
|
|
54
|
-
}
|
|
55
45
|
_getSpriteFxHook()?.initLayer(layer, opts);
|
|
56
46
|
return layer;
|
|
57
47
|
}
|
|
@@ -62,19 +52,6 @@ function setSprite2DShaderParams(layer, params) {
|
|
|
62
52
|
target[2] = params[2];
|
|
63
53
|
target[3] = params[3];
|
|
64
54
|
}
|
|
65
|
-
function setSprite2DUvOffset(layer, index, uvOffset) {
|
|
66
|
-
if (!layer._uvScroll) {
|
|
67
|
-
throw new Error("setSprite2DUvOffset: layer was not created with uvScroll: true.");
|
|
68
|
-
}
|
|
69
|
-
if (index < 0 || index >= layer.count) {
|
|
70
|
-
throw new Error(`setSprite2DUvOffset: index ${index} out of range [0, ${layer.count})`);
|
|
71
|
-
}
|
|
72
|
-
const base = index * layer._instanceFloatsPerSprite;
|
|
73
|
-
const uvSlot = base + (layer.depth !== "none" ? 14 : 13);
|
|
74
|
-
layer._instanceData[uvSlot] = uvOffset[0];
|
|
75
|
-
layer._instanceData[uvSlot + 1] = uvOffset[1];
|
|
76
|
-
markDirty(layer, index, index + 1);
|
|
77
|
-
}
|
|
78
55
|
function growCapacity(layer, minCapacity) {
|
|
79
56
|
let cap = layer._capacity;
|
|
80
57
|
while (cap < minCapacity) {
|
|
@@ -180,16 +157,6 @@ function writeInstance(layer, slotIndex, props, prev) {
|
|
|
180
157
|
if (hasDepthSlot) {
|
|
181
158
|
data[base + 13] = z;
|
|
182
159
|
}
|
|
183
|
-
if (layer._uvScroll) {
|
|
184
|
-
const uvSlot = base + (hasDepthSlot ? 14 : 13);
|
|
185
|
-
if (props.uvOffset) {
|
|
186
|
-
data[uvSlot] = props.uvOffset[0];
|
|
187
|
-
data[uvSlot + 1] = props.uvOffset[1];
|
|
188
|
-
} else if (isAdd) {
|
|
189
|
-
data[uvSlot] = 0;
|
|
190
|
-
data[uvSlot + 1] = 0;
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
160
|
}
|
|
194
161
|
function markDirty(layer, lo, hi) {
|
|
195
162
|
if (layer._dirtyMin >= layer._dirtyMax) {
|
|
@@ -205,9 +172,12 @@ function markDirty(layer, lo, hi) {
|
|
|
205
172
|
}
|
|
206
173
|
layer._version = layer._version + 1 | 0;
|
|
207
174
|
}
|
|
175
|
+
function _markSprite2DDirty(layer, lo, hi) {
|
|
176
|
+
markDirty(layer, lo, hi);
|
|
177
|
+
}
|
|
208
178
|
function addSprite2DIndex(layer, props) {
|
|
209
179
|
if (props.positionPx === void 0) {
|
|
210
|
-
throw new Error("addSprite2DIndex:
|
|
180
|
+
throw new Error("addSprite2DIndex: positionPx required.");
|
|
211
181
|
}
|
|
212
182
|
const idx = layer.count;
|
|
213
183
|
if (idx >= layer._capacity) {
|
|
@@ -270,5 +240,5 @@ function setSprite2DFrameIndex(layer, index, frame) {
|
|
|
270
240
|
markDirty(layer, index, index + 1);
|
|
271
241
|
}
|
|
272
242
|
|
|
273
|
-
export { DEPTH_INSTANCE_FLOATS_PER_SPRITE, DEPTH_INSTANCE_STRIDE_BYTES,
|
|
243
|
+
export { DEPTH_INSTANCE_FLOATS_PER_SPRITE, DEPTH_INSTANCE_STRIDE_BYTES, PURE_2D_INSTANCE_FLOATS_PER_SPRITE, PURE_2D_INSTANCE_STRIDE_BYTES, SAVED_SIZE_FLOATS_PER_SPRITE, _markSprite2DDirty, addSprite2DIndex, clearSprite2DLayer, createSprite2DLayer, removeSprite2DIndex, setSprite2DFrameIndex, setSprite2DShaderParams, updateSprite2DIndex };
|
|
274
244
|
//# sourceMappingURL=sprite-2d.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sprite-2d.js","sources":["../../../src/sprite/sprite-2d.ts"],"sourcesContent":["/**\n * `Sprite2DLayer` — pixel-coordinate sprite layer. Pure-data interface +\n * standalone Index API for add / update / remove / setFrame. The layer is\n * owned by a `SpriteRenderer` (pure-2D / HUD `depth: \"none\"` path) or by a\n * scene renderable added through `addDepthHostedSpriteLayer`\n * (`depth: \"test\" | \"test-write\"`).\n *\n * The Index API stays the storage foundation. The optional Handle API lives in\n * `sprite-2d-handle.ts` and lazily installs hooks only when handles are used.\n */\nimport { F32 } from \"../engine/typed-arrays.js\";\nimport type { SpriteAtlas } from \"./shared/sprite-atlas.js\";\nimport { resolveSpriteFrame } from \"./shared/sprite-atlas.js\";\nimport type { Sprite2DCustomShader } from \"./sprite-custom-shader.js\";\nimport { _getSpriteFxHook } from \"./sprite-fx-hook.js\";\nimport type { SpriteBlendDescriptor } from \"./sprite-blend.js\";\nimport { spriteBlendAlpha } from \"./sprite-blend.js\";\n\n/**\n * Output blend mode for a sprite layer — a pure-data descriptor value. Import one of\n * `spriteBlendAlpha` (default), `spriteBlendPremultiplied`, or `spriteBlendAdditive` and pass\n * it as `Sprite2DLayerOptions.blendMode`. (Type alias of {@link SpriteBlendDescriptor}.)\n */\nexport type SpriteBlendMode = SpriteBlendDescriptor;\n\n/** Depth participation. `\"none\"` uses `SpriteRenderer`; depth-enabled modes use `addToScene`. */\nexport type Sprite2DDepthMode = \"none\" | \"test\" | \"test-write\";\n\n/** Per-layer 2D camera (pan / zoom / rotation). Identity = pixel-perfect HUD. */\nexport interface Sprite2DView {\n positionPx: [number, number];\n zoom: number;\n rotation: number;\n}\n\n/** Options accepted by `createSprite2DLayer`. */\nexport interface Sprite2DLayerOptions {\n capacity?: number;\n blendMode?: SpriteBlendMode;\n opacity?: number;\n visible?: boolean;\n order?: number;\n view?: Partial<Sprite2DView>;\n depth?: Sprite2DDepthMode;\n /**\n * Layer-wide rotation / scaling pivot in normalised sprite-local space\n * (`[0,0]` = top-left, `[0.5, 0.5]` = center, `[1,1]` = bottom-right).\n * The pivot point of every sprite in the layer lands at its `positionPx`\n * and is the center of `rotation`. Defaults to `[0.5, 0.5]` (center) to\n * match Babylon.js sprite behavior. Per-sprite / per-frame pivot is a\n * future PR — most 2D HUD layers want one uniform pivot anyway.\n */\n pivot?: [number, number];\n /**\n * Opt-in per-layer custom fragment shader (see `createSprite2DCustomShader`). Works on both\n * pure-2D (`depth: \"none\"`) layers drawn by a `SpriteRenderer` and depth-hosted\n * (`depth: \"test\" | \"test-write\"`) layers. Drives procedural effects (animated sky, clouds,\n * water / heat shimmer, twinkle, vignette) from a built-in `fx.time` clock plus an optional\n * `fx.params` vec4 set via `setSprite2DShaderParams`.\n */\n customShader?: Sprite2DCustomShader;\n /**\n * Default NDC depth (`0` = near, `1` = far) for sprites added to this layer when their\n * `Sprite2DProps.z` is omitted. Only meaningful for `depth: \"test\" | \"test-write\"` layers\n * (depth-hosted sprites added to a `SceneContext` via `addDepthHostedSpriteLayer`).\n *\n * Depth-hosted layers store one Z per sprite (slot [13] of their 14-float instance buffer),\n * so a single layer can mix sprites at different depths — e.g. one in front of a box, one\n * behind it. Pure-2D layers (`depth: \"none\"`) use the 13-float HUD layout and carry no Z slot.\n * Defaults to `0.5`. Mutating `layer.layerZ` after sprites have been added does **not**\n * retroactively change them; it only affects sprites added afterwards. To move an existing\n * depth-hosted sprite, call `updateSprite2DIndex(layer, idx, { z: … })`.\n */\n layerZ?: number;\n /**\n * Opt-in per-sprite UV scroll offset. When `true`, every sprite gains two extra instance\n * floats (`uvOffset.xy`) added to its sampled UV in the vertex stage — enabling parallax /\n * infinite-scroll backgrounds without re-uploading texture coordinates. Set the offset per\n * sprite via `Sprite2DProps.uvOffset` (on add) or `setSprite2DUvOffset` (live).\n *\n * **Zero cross-scene cost:** layers created without `uvScroll` keep the narrow 13/14-float\n * layout, the base vertex attributes, and the base WGSL — they ship none of the uvScroll\n * widening. The wider stride, the extra `@location(7)` attribute, and the `+ iUvOffset` WGSL\n * are gated directly on this per-layer flag. Defaults to `false`.\n *\n * Pairs naturally with a tileable atlas texture sampled in `repeat` wrap mode\n * (`loadSpriteAtlas(..., { textureOptions: { addressModeU: \"repeat\", addressModeV: \"repeat\" } })`).\n */\n uvScroll?: boolean;\n}\n\n/** A `Sprite2DLayer` — pure data, no methods. */\nexport interface Sprite2DLayer {\n /** @internal */\n readonly _entityType: \"sprite-2d-layer\";\n readonly atlas: SpriteAtlas;\n readonly depth: Sprite2DDepthMode;\n readonly blendMode: SpriteBlendMode;\n opacity: number;\n visible: boolean;\n order: number;\n view: Sprite2DView;\n /** Layer-wide pivot in normalised sprite-local space; see `Sprite2DLayerOptions.pivot`. */\n pivot: [number, number];\n /**\n * Opt-in custom fragment shader for this layer; see `Sprite2DLayerOptions.customShader`.\n * **Absent** (not `null`) on plain layers — never default-initialized, so the always-loaded\n * path carries zero custom-shader bytes (see `sprite-fx-hook.ts`). Present only when the\n * layer was created with a `customShader`.\n */\n readonly customShader?: Sprite2DCustomShader;\n /**\n * User `fx.params` vec4 fed to a custom shader each frame; mutate via `setSprite2DShaderParams`.\n * **Absent** on plain layers (only allocated for custom-shader layers, or lazily by the setter).\n */\n shaderParams?: [number, number, number, number];\n /**\n * @internal Opt-in per-sprite UV-scroll flag; see `Sprite2DLayerOptions.uvScroll`. **Absent** (not `false`)\n * on plain layers — never default-initialized, so the always-loaded path and every non-scroll\n * sprite scene keep the narrow layout and base shader. Present (`true`) only when the layer was\n * created with `uvScroll: true`, which widens the instance stride by two floats (`uvOffset.xy`).\n */\n readonly _uvScroll?: boolean;\n /** Default NDC depth for newly added sprites; see `Sprite2DLayerOptions.layerZ`. */\n layerZ: number;\n readonly count: number;\n\n /** @internal Capacity of the per-instance buffer (in sprites). */\n _capacity: number;\n /** @internal Per-instance stride in floats; 13 for pure-2D, 14 for depth-hosted. */\n readonly _instanceFloatsPerSprite: number;\n /** @internal Per-instance stride in bytes; 52 for pure-2D, 56 for depth-hosted. */\n readonly _instanceStrideBytes: number;\n /** @internal Per-instance CPU staging buffer; layout = `_instanceFloatsPerSprite` per sprite. */\n _instanceData: Float32Array;\n /**\n * @internal CPU-only side buffer holding the **true** (un-hidden) size of every sprite,\n * laid out as `[w0, h0, w1, h1, …]` (`SAVED_SIZE_FLOATS_PER_SPRITE` = 2 floats per sprite).\n *\n * **Invariant:** this buffer always holds the sprite's real size, regardless of visibility.\n * It exists because `visible: false` is implemented by zeroing the GPU-side size slots\n * (degenerate quad → free rasterizer cull) — a free hide on the GPU at the cost of 8 B per\n * sprite on the CPU. Without this shadow, a `visible: true` patch that omits `sizePx`\n * would have no way to recover the original size. Grown in lockstep with `_instanceData`.\n */\n _savedSize: Float32Array;\n /** @internal Bumped on any structural / per-instance edit; renderer compares. */\n _version: number;\n /** @internal Min dirty index inclusive (for partial uploads). */\n _dirtyMin: number;\n /** @internal Max dirty index exclusive. */\n _dirtyMax: number;\n /** @internal Optional hooks installed by the opt-in handle module. */\n _handleHooks?: Sprite2DIndexHandleHooks;\n}\n\n/** @internal Lazy hooks used by the opt-in Handle API to track swap-removes. */\nexport interface Sprite2DIndexHandleHooks {\n readonly removeIndex: (index: number, last: number) => void;\n readonly clear: () => void;\n}\n\n/** Per-sprite init record passed to `addSprite2DIndex` / `updateSprite2DIndex`. */\nexport interface Sprite2DProps {\n positionPx: [number, number];\n sizePx?: [number, number];\n frame?: number;\n rotation?: number;\n color?: [number, number, number, number];\n flipX?: boolean;\n flipY?: boolean;\n visible?: boolean;\n /** Reserved for picking. Accepted but unused today. */\n pickable?: boolean;\n /** Reserved for clip animation. Accepted but unused today. */\n clip?: unknown;\n /**\n * Per-sprite NDC depth (`0` = near, `1` = far). Only stored and consumed by depth-hosted\n * layers (`depth: \"test\" | \"test-write\"`); pure-2D HUD layers use a 13-float layout and\n * do not allocate a Z slot. When omitted on add for a depth-hosted layer, defaults to the\n * **owning layer's** `layerZ` at the moment of insertion. When omitted on update, the\n * sprite's existing Z is preserved. Mutate freely — the next binding update will re-upload\n * only the dirty range.\n */\n z?: number;\n /**\n * Per-sprite UV scroll offset added to the sampled UV in the vertex stage. Only stored and\n * consumed by `uvScroll` layers (created with `Sprite2DLayerOptions.uvScroll: true`); non-scroll\n * layers use the narrow 13/14-float layout and do not allocate a uvOffset slot. When omitted on\n * add for a uvScroll layer, defaults to `[0, 0]`. When omitted on update, the sprite's existing\n * offset is preserved. Live updates: `setSprite2DUvOffset`.\n */\n uvOffset?: [number, number];\n}\n\n/**\n * Pure-2D per-instance vertex layout (13 floats = 52 bytes, `depth: \"none\"`):\n * ```\n * [0..1] positionPx.xy (float32x2 @ offset 0)\n * [2..3] sizePx.xy (float32x2 @ offset 8)\n * [4..5] uvMin.xy (float32x2 @ offset 16)\n * [6..7] uvMax.xy (float32x2 @ offset 24)\n * [8] rotation (float32 @ offset 32)\n * [9..12] colorRGBA (float32x4 @ offset 36)\n * ```\n *\n * Depth-hosted layers (`depth: \"test\" | \"test-write\"`) extend this to 14 floats = 56 bytes:\n * ```\n * [13] z (NDC depth) (float32 @ offset 52, consumed only by depth-hosted pipelines)\n * ```\n *\n * `uvScroll` layers (created with `Sprite2DLayerOptions.uvScroll: true`) append two more floats\n * (`uvOffset.xy`) *after* the base layout, orthogonally to depth:\n * ```\n * pure-2D + uvScroll (15 floats = 60 bytes): [13..14] uvOffset.xy (float32x2 @ offset 52)\n * depth + uvScroll (16 floats = 64 bytes): [14..15] uvOffset.xy (float32x2 @ offset 56)\n * ```\n * The depth Z stays at slot [13]; uvOffset is appended after it. Non-scroll layers carry no\n * uvOffset slot and ship none of the widening (see `Sprite2DLayerOptions.uvScroll`).\n *\n * Visibility (`visible: false`) is implemented by zeroing slots [2..3]; the sprite's true\n * size lives in `layer._savedSize` so a later `visible: true` (without re-supplying\n * `sizePx`) can restore it. See `_savedSize` for the invariant.\n */\nexport const PURE_2D_INSTANCE_FLOATS_PER_SPRITE = 13;\nexport const DEPTH_INSTANCE_FLOATS_PER_SPRITE = 14;\n/** @internal Pure-2D per-sprite stride in bytes. */\nexport const PURE_2D_INSTANCE_STRIDE_BYTES = PURE_2D_INSTANCE_FLOATS_PER_SPRITE * 4;\n/** @internal Depth-hosted per-sprite stride in bytes. */\nexport const DEPTH_INSTANCE_STRIDE_BYTES = DEPTH_INSTANCE_FLOATS_PER_SPRITE * 4;\n/** @internal Extra floats appended per sprite when `uvScroll` is enabled: `uvOffset.xy`. */\nexport const UVSCROLL_EXTRA_FLOATS_PER_SPRITE = 2;\n/** @internal Pure-2D + uvScroll per-sprite stride in floats (13 + 2). */\nexport const PURE_2D_UVSCROLL_FLOATS_PER_SPRITE = PURE_2D_INSTANCE_FLOATS_PER_SPRITE + UVSCROLL_EXTRA_FLOATS_PER_SPRITE;\n/** @internal Depth-hosted + uvScroll per-sprite stride in floats (14 + 2). */\nexport const DEPTH_UVSCROLL_FLOATS_PER_SPRITE = DEPTH_INSTANCE_FLOATS_PER_SPRITE + UVSCROLL_EXTRA_FLOATS_PER_SPRITE;\n/** @internal Pure-2D + uvScroll per-sprite stride in bytes (60). */\nexport const PURE_2D_UVSCROLL_STRIDE_BYTES = PURE_2D_UVSCROLL_FLOATS_PER_SPRITE * 4;\n/** @internal Depth-hosted + uvScroll per-sprite stride in bytes (64). */\nexport const DEPTH_UVSCROLL_STRIDE_BYTES = DEPTH_UVSCROLL_FLOATS_PER_SPRITE * 4;\n/** @internal Per-sprite stride (in floats) of the `_savedSize` shadow buffer: `[w, h]`. */\nexport const SAVED_SIZE_FLOATS_PER_SPRITE = 2;\n\nconst DEFAULT_CAPACITY = 16;\n\n/** Create a new (empty) `Sprite2DLayer` backed by `atlas`. */\nexport function createSprite2DLayer(atlas: SpriteAtlas, opts: Sprite2DLayerOptions = {}): Sprite2DLayer {\n const depth = opts.depth ?? \"none\";\n const blendMode = opts.blendMode ?? spriteBlendAlpha;\n\n const capacity = Math.max(1, opts.capacity ?? DEFAULT_CAPACITY);\n const view: Sprite2DView = {\n positionPx: [opts.view?.positionPx?.[0] ?? 0, opts.view?.positionPx?.[1] ?? 0],\n zoom: opts.view?.zoom ?? 1,\n rotation: opts.view?.rotation ?? 0,\n };\n\n const uvScroll = opts.uvScroll === true;\n const baseFloatsPerSprite = depth === \"none\" ? PURE_2D_INSTANCE_FLOATS_PER_SPRITE : DEPTH_INSTANCE_FLOATS_PER_SPRITE;\n const instanceFloatsPerSprite = uvScroll ? baseFloatsPerSprite + UVSCROLL_EXTRA_FLOATS_PER_SPRITE : baseFloatsPerSprite;\n const instanceStrideBytes = instanceFloatsPerSprite * 4;\n const instanceData = new F32(capacity * instanceFloatsPerSprite);\n const layer: Sprite2DLayer = {\n _entityType: \"sprite-2d-layer\",\n atlas,\n depth,\n blendMode,\n opacity: opts.opacity ?? 1,\n visible: opts.visible ?? true,\n order: opts.order ?? 0,\n view,\n pivot: [opts.pivot?.[0] ?? 0.5, opts.pivot?.[1] ?? 0.5],\n layerZ: opts.layerZ ?? 0.5,\n count: 0,\n _capacity: capacity,\n _instanceFloatsPerSprite: instanceFloatsPerSprite,\n _instanceStrideBytes: instanceStrideBytes,\n _instanceData: instanceData,\n _savedSize: new F32(capacity * SAVED_SIZE_FLOATS_PER_SPRITE),\n _version: 0,\n _dirtyMin: 0,\n _dirtyMax: 0,\n };\n // Zero-default-init discipline: the base layer never names `_uvScroll`. Set it only when the\n // caller opted in, so plain layers keep the field off the always-loaded path and read as narrow.\n if (uvScroll) {\n (layer as { _uvScroll?: boolean })._uvScroll = true;\n }\n // Zero-default-init discipline: the base layer never names `customShader` / `shaderParams`.\n // When (and only when) a custom shader was supplied, the registered hook copies it on — the\n // impl lives in the tree-shaken `sprite-custom-shader` module, so plain scenes ship none of it.\n _getSpriteFxHook()?.initLayer(layer, opts);\n return layer;\n}\n\n/**\n * Set the user `fx.params` vec4 fed to this layer's custom shader (`createSprite2DCustomShader`)\n * each frame. No visual effect unless the layer was created with a `customShader`. Read in WGSL\n * as `fx.params`. Mutates in place; the renderer re-uploads the small FX UBO next frame.\n */\nexport function setSprite2DShaderParams(layer: Sprite2DLayer, params: readonly [number, number, number, number]): void {\n // Lazy-allocate: the base layer never names `shaderParams` (the custom-shader hook sets it only\n // when `opts.customShader` is present), so a plain layer keeps the field off the always-loaded path.\n const target = (layer.shaderParams ??= [0, 0, 0, 0]);\n target[0] = params[0];\n target[1] = params[1];\n target[2] = params[2];\n target[3] = params[3];\n}\n\n/**\n * Set the per-sprite UV scroll offset for one sprite of a `uvScroll` layer (live). The two floats\n * are added to the sprite's sampled UV in the vertex stage — driving parallax / infinite-scroll\n * backgrounds without re-uploading texture coordinates. Marks only this sprite's range dirty.\n *\n * Throws if the layer was not created with `Sprite2DLayerOptions.uvScroll: true` (non-scroll layers\n * carry no uvOffset slot) or if `index` is out of range.\n */\nexport function setSprite2DUvOffset(layer: Sprite2DLayer, index: number, uvOffset: readonly [number, number]): void {\n if (!layer._uvScroll) {\n throw new Error(\"setSprite2DUvOffset: layer was not created with uvScroll: true.\");\n }\n if (index < 0 || index >= layer.count) {\n throw new Error(`setSprite2DUvOffset: index ${index} out of range [0, ${layer.count})`);\n }\n const base = index * layer._instanceFloatsPerSprite;\n const uvSlot = base + (layer.depth !== \"none\" ? 14 : 13);\n layer._instanceData[uvSlot] = uvOffset[0];\n layer._instanceData[uvSlot + 1] = uvOffset[1];\n markDirty(layer, index, index + 1);\n}\n\nfunction growCapacity(layer: Sprite2DLayer, minCapacity: number): void {\n let cap = layer._capacity;\n while (cap < minCapacity) {\n cap *= 2;\n }\n const next = new F32(cap * layer._instanceFloatsPerSprite);\n next.set(layer._instanceData);\n layer._instanceData = next;\n const nextSaved = new F32(cap * SAVED_SIZE_FLOATS_PER_SPRITE);\n nextSaved.set(layer._savedSize);\n layer._savedSize = nextSaved;\n layer._capacity = cap;\n}\n\nfunction setSprite2DCount(layer: Sprite2DLayer, count: number): void {\n (layer as { count: number }).count = count;\n}\n\n/**\n * Write one sprite's instance data into `layer._instanceData[base..base+layer._instanceFloatsPerSprite]`.\n *\n * Two call sites with different shapes:\n * - **add**: `prev === null`. `props` is a full `Sprite2DProps` (positionPx required).\n * Unspecified fields take their documented defaults (size=frame.sourceSizePx or 0,\n * UVs=[0,0,1,1], rotation=0, color=opaque white, visible=true).\n * - **update**: `prev` is the existing per-layer instance slice. Unspecified fields are preserved.\n *\n * Resolution rules (per field): `props` value if given, else (on add) the default, else `prev`.\n * `frame` is a higher-level intent: when supplied it stomps the four UV slots from the atlas\n * (then `flipX`/`flipY` swap them). It does **not** by itself imply a size change — `sizePx`\n * remains independent — but on add, a missing `sizePx` falls back to `frame.sourceSizePx`.\n *\n * **Visibility model (the part that needs explaining):**\n * - `_savedSize[slot]` always stores the sprite's *true* size (unaffected by visibility).\n * - `data[base+2..+3]` (the GPU-visible size) is `_savedSize` when visible, else `(0, 0)`.\n * - We detect previous visibility by checking `prev[2]==0 && prev[3]==0` (only hidden sprites\n * have zeroed GPU size). The CPU shadow gives us back the true size for free.\n */\nfunction writeInstance(layer: Sprite2DLayer, slotIndex: number, props: Partial<Sprite2DProps>, prev: Float32Array | null): void {\n const data = layer._instanceData;\n const base = slotIndex * layer._instanceFloatsPerSprite;\n const savedBase = slotIndex * SAVED_SIZE_FLOATS_PER_SPRITE; // [w, h] per sprite\n const isAdd = prev === null;\n\n // Optional frame lookup (used for UV stomp + size default on add).\n const frame = props.frame !== undefined ? layer.atlas.frames[resolveSpriteFrame(layer.atlas, props.frame)]! : null;\n\n // ── Position (required on add; preserved on update if omitted) ──────────────────────\n const posX = props.positionPx ? props.positionPx[0] : prev![0]!;\n const posY = props.positionPx ? props.positionPx[1] : prev![1]!;\n\n // ── True size (props.sizePx → frame default → previous true size) ───────────────────\n // The shadow buffer makes \"previous true size\" cheap and unambiguous regardless of visibility.\n let trueW: number;\n let trueH: number;\n if (props.sizePx) {\n trueW = props.sizePx[0];\n trueH = props.sizePx[1];\n } else if (frame) {\n trueW = frame.sourceSizePx[0];\n trueH = frame.sourceSizePx[1];\n } else if (isAdd) {\n trueW = 0;\n trueH = 0;\n } else {\n trueW = layer._savedSize[savedBase]!;\n trueH = layer._savedSize[savedBase + 1]!;\n }\n layer._savedSize[savedBase] = trueW;\n layer._savedSize[savedBase + 1] = trueH;\n\n // ── Visibility (props.visible → preserved → default true on add) ────────────────────\n let visible: boolean;\n if (props.visible !== undefined) {\n visible = props.visible;\n } else if (isAdd) {\n visible = true;\n } else {\n // Previous sprite was hidden iff its GPU size was zeroed.\n visible = prev![2]! !== 0 || prev![3]! !== 0;\n }\n\n // ── UVs (frame stomps; else preserved; else default [0,0,1,1] on add) ───────────────\n // flipX/flipY apply on top, by swapping the U/V endpoints.\n let uMin: number;\n let vMin: number;\n let uMax: number;\n let vMax: number;\n if (frame) {\n uMin = frame.uvMin[0];\n vMin = frame.uvMin[1];\n uMax = frame.uvMax[0];\n vMax = frame.uvMax[1];\n } else if (isAdd) {\n uMin = 0;\n vMin = 0;\n uMax = 1;\n vMax = 1;\n } else {\n uMin = prev![4]!;\n vMin = prev![5]!;\n uMax = prev![6]!;\n vMax = prev![7]!;\n }\n if (props.flipX === true) {\n const t = uMin;\n uMin = uMax;\n uMax = t;\n }\n if (props.flipY === true) {\n const t = vMin;\n vMin = vMax;\n vMax = t;\n }\n\n // ── Rotation ────────────────────────────────────────────────────────────────────────\n const rotation = props.rotation ?? (prev ? prev[8]! : 0);\n\n // ── Per-instance Z (depth-hosted only) ──────────────────────────────────────────────\n // Pure-2D layers intentionally have no slot [13]; `z` is accepted by the public API but\n // not allocated, uploaded, declared, or fetched by HUD pipelines.\n const hasDepthSlot = layer.depth !== \"none\";\n const z = hasDepthSlot ? (props.z ?? (prev ? prev[13]! : layer.layerZ)) : 0;\n\n // ── Write the float slots ──────────────────────────────────────────────────────────\n data[base + 0] = posX;\n data[base + 1] = posY;\n data[base + 2] = visible ? trueW : 0;\n data[base + 3] = visible ? trueH : 0;\n data[base + 4] = uMin;\n data[base + 5] = vMin;\n data[base + 6] = uMax;\n data[base + 7] = vMax;\n data[base + 8] = rotation;\n\n // ── Color (float32x4 to match Babylon.js SpriteRenderer's color precision) ─────────\n if (props.color) {\n data[base + 9] = props.color[0];\n data[base + 10] = props.color[1];\n data[base + 11] = props.color[2];\n data[base + 12] = props.color[3];\n } else if (isAdd) {\n data[base + 9] = 1;\n data[base + 10] = 1;\n data[base + 11] = 1;\n data[base + 12] = 1;\n }\n // else: previous color floats are already in place — nothing to write.\n\n // ── Per-instance Z (slot [13], depth-hosted layout only) ───────────────────────────\n if (hasDepthSlot) {\n data[base + 13] = z;\n }\n\n // ── Per-sprite uvOffset (uvScroll layout only) ─────────────────────────────────────\n // Appended after the base layout: slot [13] for pure-2D, slot [14] for depth-hosted\n // (the depth Z keeps slot [13]). props → preserved → default [0,0] on add.\n if (layer._uvScroll) {\n const uvSlot = base + (hasDepthSlot ? 14 : 13);\n if (props.uvOffset) {\n data[uvSlot] = props.uvOffset[0];\n data[uvSlot + 1] = props.uvOffset[1];\n } else if (isAdd) {\n data[uvSlot] = 0;\n data[uvSlot + 1] = 0;\n }\n // else: previous uvOffset floats are already in place — nothing to write.\n }\n}\n\nfunction markDirty(layer: Sprite2DLayer, lo: number, hi: number): void {\n if (layer._dirtyMin >= layer._dirtyMax) {\n layer._dirtyMin = lo;\n layer._dirtyMax = hi;\n } else {\n if (lo < layer._dirtyMin) {\n layer._dirtyMin = lo;\n }\n if (hi > layer._dirtyMax) {\n layer._dirtyMax = hi;\n }\n }\n layer._version = (layer._version + 1) | 0;\n}\n\n/** Add one sprite. Returns its index. Grows capacity as needed. */\nexport function addSprite2DIndex(layer: Sprite2DLayer, props: Sprite2DProps): number {\n if (props.positionPx === undefined) {\n throw new Error(\"addSprite2DIndex: props.positionPx is required.\");\n }\n const idx = layer.count;\n if (idx >= layer._capacity) {\n growCapacity(layer, idx + 1);\n }\n writeInstance(layer, idx, props, null);\n setSprite2DCount(layer, layer.count + 1);\n markDirty(layer, idx, idx + 1);\n return idx;\n}\n\n/** Patch one sprite. Unspecified fields are preserved. */\nexport function updateSprite2DIndex(layer: Sprite2DLayer, index: number, patch: Partial<Sprite2DProps>): void {\n if (index < 0 || index >= layer.count) {\n throw new Error(`updateSprite2DIndex: index ${index} out of range [0, ${layer.count})`);\n }\n const base = index * layer._instanceFloatsPerSprite;\n const prev = layer._instanceData.subarray(base, base + layer._instanceFloatsPerSprite);\n writeInstance(layer, index, patch, prev);\n markDirty(layer, index, index + 1);\n}\n\n/** Swap-remove a sprite. The last sprite (if any) takes its slot. */\nexport function removeSprite2DIndex(layer: Sprite2DLayer, index: number): void {\n if (index < 0 || index >= layer.count) {\n throw new Error(`removeSprite2DIndex: index ${index} out of range [0, ${layer.count})`);\n }\n const last = layer.count - 1;\n layer._handleHooks?.removeIndex(index, last);\n if (index !== last) {\n layer._instanceData.copyWithin(index * layer._instanceFloatsPerSprite, last * layer._instanceFloatsPerSprite, (last + 1) * layer._instanceFloatsPerSprite);\n // Carry the swapped sprite's saved-size shadow with it (`[w, h]` per sprite).\n layer._savedSize.copyWithin(index * SAVED_SIZE_FLOATS_PER_SPRITE, last * SAVED_SIZE_FLOATS_PER_SPRITE, (last + 1) * SAVED_SIZE_FLOATS_PER_SPRITE);\n }\n // Clear the now-unused tail saved-size slot so a future re-add starts clean.\n layer._savedSize[last * SAVED_SIZE_FLOATS_PER_SPRITE] = 0;\n layer._savedSize[last * SAVED_SIZE_FLOATS_PER_SPRITE + 1] = 0;\n setSprite2DCount(layer, last);\n markDirty(layer, index, index + 1);\n}\n\n/** Clear all sprites from a layer while preserving allocated capacity. */\nexport function clearSprite2DLayer(layer: Sprite2DLayer): void {\n const count = layer.count;\n layer._dirtyMin = 0;\n layer._dirtyMax = 0;\n layer._handleHooks?.clear();\n if (count === 0) {\n return;\n }\n layer._savedSize.fill(0, 0, count * SAVED_SIZE_FLOATS_PER_SPRITE);\n setSprite2DCount(layer, 0);\n layer._version = (layer._version + 1) | 0;\n}\n\n/**\n * Update only the frame UVs for one sprite.\n *\n * The sprite keeps its explicit `sizePx`/saved size. For atlas-driven size\n * changes, call `updateSprite2DIndex(layer, index, { frame, sizePx })`.\n * Existing flip state is preserved for non-degenerate UV ranges.\n */\nexport function setSprite2DFrameIndex(layer: Sprite2DLayer, index: number, frame: number): void {\n if (index < 0 || index >= layer.count) {\n throw new Error(`setSprite2DFrameIndex: index ${index} out of range [0, ${layer.count})`);\n }\n const frameIdx = resolveSpriteFrame(layer.atlas, frame);\n const f = layer.atlas.frames[frameIdx]!;\n const base = index * layer._instanceFloatsPerSprite;\n const flipX = layer._instanceData[base + 4]! > layer._instanceData[base + 6]!;\n const flipY = layer._instanceData[base + 5]! > layer._instanceData[base + 7]!;\n layer._instanceData[base + 4] = flipX ? f.uvMax[0] : f.uvMin[0];\n layer._instanceData[base + 5] = flipY ? f.uvMax[1] : f.uvMin[1];\n layer._instanceData[base + 6] = flipX ? f.uvMin[0] : f.uvMax[0];\n layer._instanceData[base + 7] = flipY ? f.uvMin[1] : f.uvMax[1];\n markDirty(layer, index, index + 1);\n}\n"],"names":[],"mappings":";;;;;AAgOO,MAAM,kCAAA,GAAqC;AAC3C,MAAM,gCAAA,GAAmC;AAEzC,MAAM,gCAAgC,kCAAA,GAAqC;AAE3E,MAAM,8BAA8B,gCAAA,GAAmC;AAEvE,MAAM,gCAAA,GAAmC;AAEzC,MAAM,qCAAqC,kCAAA,GAAqC;AAEhF,MAAM,mCAAmC,gCAAA,GAAmC;AAE5E,MAAM,gCAAgC,kCAAA,GAAqC;AAE3E,MAAM,8BAA8B,gCAAA,GAAmC;AAEvE,MAAM,4BAAA,GAA+B;AAE5C,MAAM,gBAAA,GAAmB,EAAA;AAGlB,SAAS,mBAAA,CAAoB,KAAA,EAAoB,IAAA,GAA6B,EAAC,EAAkB;AACpG,EAAA,MAAM,KAAA,GAAQ,KAAK,KAAA,IAAS,MAAA;AAC5B,EAAA,MAAM,SAAA,GAAY,KAAK,SAAA,IAAa,gBAAA;AAEpC,EAAA,MAAM,WAAW,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,IAAA,CAAK,YAAY,gBAAgB,CAAA;AAC9D,EAAA,MAAM,IAAA,GAAqB;AAAA,IACvB,UAAA,EAAY,CAAC,IAAA,CAAK,IAAA,EAAM,UAAA,GAAa,CAAC,CAAA,IAAK,CAAA,EAAG,IAAA,CAAK,IAAA,EAAM,UAAA,GAAa,CAAC,KAAK,CAAC,CAAA;AAAA,IAC7E,IAAA,EAAM,IAAA,CAAK,IAAA,EAAM,IAAA,IAAQ,CAAA;AAAA,IACzB,QAAA,EAAU,IAAA,CAAK,IAAA,EAAM,QAAA,IAAY;AAAA,GACrC;AAEA,EAAA,MAAM,QAAA,GAAW,KAAK,QAAA,KAAa,IAAA;AACnC,EAAA,MAAM,mBAAA,GAAsB,KAAA,KAAU,MAAA,GAAS,kCAAA,GAAqC,gCAAA;AACpF,EAAA,MAAM,uBAAA,GAA0B,QAAA,GAAW,mBAAA,GAAsB,gCAAA,GAAmC,mBAAA;AACpG,EAAA,MAAM,sBAAsB,uBAAA,GAA0B,CAAA;AACtD,EAAA,MAAM,YAAA,GAAe,IAAI,GAAA,CAAI,QAAA,GAAW,uBAAuB,CAAA;AAC/D,EAAA,MAAM,KAAA,GAAuB;AAAA,IACzB,WAAA,EAAa,iBAAA;AAAA,IACb,KAAA;AAAA,IACA,KAAA;AAAA,IACA,SAAA;AAAA,IACA,OAAA,EAAS,KAAK,OAAA,IAAW,CAAA;AAAA,IACzB,OAAA,EAAS,KAAK,OAAA,IAAW,IAAA;AAAA,IACzB,KAAA,EAAO,KAAK,KAAA,IAAS,CAAA;AAAA,IACrB,IAAA;AAAA,IACA,KAAA,EAAO,CAAC,IAAA,CAAK,KAAA,GAAQ,CAAC,CAAA,IAAK,GAAA,EAAK,IAAA,CAAK,KAAA,GAAQ,CAAC,CAAA,IAAK,GAAG,CAAA;AAAA,IACtD,MAAA,EAAQ,KAAK,MAAA,IAAU,GAAA;AAAA,IACvB,KAAA,EAAO,CAAA;AAAA,IACP,SAAA,EAAW,QAAA;AAAA,IACX,wBAAA,EAA0B,uBAAA;AAAA,IAC1B,oBAAA,EAAsB,mBAAA;AAAA,IACtB,aAAA,EAAe,YAAA;AAAA,IACf,UAAA,EAAY,IAAI,GAAA,CAAI,QAAA,GAAW,4BAA4B,CAAA;AAAA,IAC3D,QAAA,EAAU,CAAA;AAAA,IACV,SAAA,EAAW,CAAA;AAAA,IACX,SAAA,EAAW;AAAA,GACf;AAGA,EAAA,IAAI,QAAA,EAAU;AACV,IAAC,MAAkC,SAAA,GAAY,IAAA;AAAA,EACnD;AAIA,EAAA,gBAAA,EAAiB,EAAG,SAAA,CAAU,KAAA,EAAO,IAAI,CAAA;AACzC,EAAA,OAAO,KAAA;AACX;AAOO,SAAS,uBAAA,CAAwB,OAAsB,MAAA,EAAyD;AAGnH,EAAA,MAAM,SAAU,KAAA,CAAM,YAAA,KAAiB,CAAC,CAAA,EAAG,CAAA,EAAG,GAAG,CAAC,CAAA;AAClD,EAAA,MAAA,CAAO,CAAC,CAAA,GAAI,MAAA,CAAO,CAAC,CAAA;AACpB,EAAA,MAAA,CAAO,CAAC,CAAA,GAAI,MAAA,CAAO,CAAC,CAAA;AACpB,EAAA,MAAA,CAAO,CAAC,CAAA,GAAI,MAAA,CAAO,CAAC,CAAA;AACpB,EAAA,MAAA,CAAO,CAAC,CAAA,GAAI,MAAA,CAAO,CAAC,CAAA;AACxB;AAUO,SAAS,mBAAA,CAAoB,KAAA,EAAsB,KAAA,EAAe,QAAA,EAA2C;AAChH,EAAA,IAAI,CAAC,MAAM,SAAA,EAAW;AAClB,IAAA,MAAM,IAAI,MAAM,iEAAiE,CAAA;AAAA,EACrF;AACA,EAAA,IAAI,KAAA,GAAQ,CAAA,IAAK,KAAA,IAAS,KAAA,CAAM,KAAA,EAAO;AACnC,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,2BAAA,EAA8B,KAAK,CAAA,kBAAA,EAAqB,KAAA,CAAM,KAAK,CAAA,CAAA,CAAG,CAAA;AAAA,EAC1F;AACA,EAAA,MAAM,IAAA,GAAO,QAAQ,KAAA,CAAM,wBAAA;AAC3B,EAAA,MAAM,MAAA,GAAS,IAAA,IAAQ,KAAA,CAAM,KAAA,KAAU,SAAS,EAAA,GAAK,EAAA,CAAA;AACrD,EAAA,KAAA,CAAM,aAAA,CAAc,MAAM,CAAA,GAAI,QAAA,CAAS,CAAC,CAAA;AACxC,EAAA,KAAA,CAAM,aAAA,CAAc,MAAA,GAAS,CAAC,CAAA,GAAI,SAAS,CAAC,CAAA;AAC5C,EAAA,SAAA,CAAU,KAAA,EAAO,KAAA,EAAO,KAAA,GAAQ,CAAC,CAAA;AACrC;AAEA,SAAS,YAAA,CAAa,OAAsB,WAAA,EAA2B;AACnE,EAAA,IAAI,MAAM,KAAA,CAAM,SAAA;AAChB,EAAA,OAAO,MAAM,WAAA,EAAa;AACtB,IAAA,GAAA,IAAO,CAAA;AAAA,EACX;AACA,EAAA,MAAM,IAAA,GAAO,IAAI,GAAA,CAAI,GAAA,GAAM,MAAM,wBAAwB,CAAA;AACzD,EAAA,IAAA,CAAK,GAAA,CAAI,MAAM,aAAa,CAAA;AAC5B,EAAA,KAAA,CAAM,aAAA,GAAgB,IAAA;AACtB,EAAA,MAAM,SAAA,GAAY,IAAI,GAAA,CAAI,GAAA,GAAM,4BAA4B,CAAA;AAC5D,EAAA,SAAA,CAAU,GAAA,CAAI,MAAM,UAAU,CAAA;AAC9B,EAAA,KAAA,CAAM,UAAA,GAAa,SAAA;AACnB,EAAA,KAAA,CAAM,SAAA,GAAY,GAAA;AACtB;AAEA,SAAS,gBAAA,CAAiB,OAAsB,KAAA,EAAqB;AACjE,EAAC,MAA4B,KAAA,GAAQ,KAAA;AACzC;AAsBA,SAAS,aAAA,CAAc,KAAA,EAAsB,SAAA,EAAmB,KAAA,EAA+B,IAAA,EAAiC;AAC5H,EAAA,MAAM,OAAO,KAAA,CAAM,aAAA;AACnB,EAAA,MAAM,IAAA,GAAO,YAAY,KAAA,CAAM,wBAAA;AAC/B,EAAA,MAAM,YAAY,SAAA,GAAY,4BAAA;AAC9B,EAAA,MAAM,QAAQ,IAAA,KAAS,IAAA;AAGvB,EAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,KAAA,KAAU,MAAA,GAAY,KAAA,CAAM,KAAA,CAAM,MAAA,CAAO,kBAAA,CAAmB,KAAA,CAAM,KAAA,EAAO,KAAA,CAAM,KAAK,CAAC,CAAA,GAAK,IAAA;AAG9G,EAAA,MAAM,IAAA,GAAO,MAAM,UAAA,GAAa,KAAA,CAAM,WAAW,CAAC,CAAA,GAAI,KAAM,CAAC,CAAA;AAC7D,EAAA,MAAM,IAAA,GAAO,MAAM,UAAA,GAAa,KAAA,CAAM,WAAW,CAAC,CAAA,GAAI,KAAM,CAAC,CAAA;AAI7D,EAAA,IAAI,KAAA;AACJ,EAAA,IAAI,KAAA;AACJ,EAAA,IAAI,MAAM,MAAA,EAAQ;AACd,IAAA,KAAA,GAAQ,KAAA,CAAM,OAAO,CAAC,CAAA;AACtB,IAAA,KAAA,GAAQ,KAAA,CAAM,OAAO,CAAC,CAAA;AAAA,EAC1B,WAAW,KAAA,EAAO;AACd,IAAA,KAAA,GAAQ,KAAA,CAAM,aAAa,CAAC,CAAA;AAC5B,IAAA,KAAA,GAAQ,KAAA,CAAM,aAAa,CAAC,CAAA;AAAA,EAChC,WAAW,KAAA,EAAO;AACd,IAAA,KAAA,GAAQ,CAAA;AACR,IAAA,KAAA,GAAQ,CAAA;AAAA,EACZ,CAAA,MAAO;AACH,IAAA,KAAA,GAAQ,KAAA,CAAM,WAAW,SAAS,CAAA;AAClC,IAAA,KAAA,GAAQ,KAAA,CAAM,UAAA,CAAW,SAAA,GAAY,CAAC,CAAA;AAAA,EAC1C;AACA,EAAA,KAAA,CAAM,UAAA,CAAW,SAAS,CAAA,GAAI,KAAA;AAC9B,EAAA,KAAA,CAAM,UAAA,CAAW,SAAA,GAAY,CAAC,CAAA,GAAI,KAAA;AAGlC,EAAA,IAAI,OAAA;AACJ,EAAA,IAAI,KAAA,CAAM,YAAY,MAAA,EAAW;AAC7B,IAAA,OAAA,GAAU,KAAA,CAAM,OAAA;AAAA,EACpB,WAAW,KAAA,EAAO;AACd,IAAA,OAAA,GAAU,IAAA;AAAA,EACd,CAAA,MAAO;AAEH,IAAA,OAAA,GAAU,KAAM,CAAC,CAAA,KAAO,CAAA,IAAK,IAAA,CAAM,CAAC,CAAA,KAAO,CAAA;AAAA,EAC/C;AAIA,EAAA,IAAI,IAAA;AACJ,EAAA,IAAI,IAAA;AACJ,EAAA,IAAI,IAAA;AACJ,EAAA,IAAI,IAAA;AACJ,EAAA,IAAI,KAAA,EAAO;AACP,IAAA,IAAA,GAAO,KAAA,CAAM,MAAM,CAAC,CAAA;AACpB,IAAA,IAAA,GAAO,KAAA,CAAM,MAAM,CAAC,CAAA;AACpB,IAAA,IAAA,GAAO,KAAA,CAAM,MAAM,CAAC,CAAA;AACpB,IAAA,IAAA,GAAO,KAAA,CAAM,MAAM,CAAC,CAAA;AAAA,EACxB,WAAW,KAAA,EAAO;AACd,IAAA,IAAA,GAAO,CAAA;AACP,IAAA,IAAA,GAAO,CAAA;AACP,IAAA,IAAA,GAAO,CAAA;AACP,IAAA,IAAA,GAAO,CAAA;AAAA,EACX,CAAA,MAAO;AACH,IAAA,IAAA,GAAO,KAAM,CAAC,CAAA;AACd,IAAA,IAAA,GAAO,KAAM,CAAC,CAAA;AACd,IAAA,IAAA,GAAO,KAAM,CAAC,CAAA;AACd,IAAA,IAAA,GAAO,KAAM,CAAC,CAAA;AAAA,EAClB;AACA,EAAA,IAAI,KAAA,CAAM,UAAU,IAAA,EAAM;AACtB,IAAA,MAAM,CAAA,GAAI,IAAA;AACV,IAAA,IAAA,GAAO,IAAA;AACP,IAAA,IAAA,GAAO,CAAA;AAAA,EACX;AACA,EAAA,IAAI,KAAA,CAAM,UAAU,IAAA,EAAM;AACtB,IAAA,MAAM,CAAA,GAAI,IAAA;AACV,IAAA,IAAA,GAAO,IAAA;AACP,IAAA,IAAA,GAAO,CAAA;AAAA,EACX;AAGA,EAAA,MAAM,WAAW,KAAA,CAAM,QAAA,KAAa,IAAA,GAAO,IAAA,CAAK,CAAC,CAAA,GAAK,CAAA,CAAA;AAKtD,EAAA,MAAM,YAAA,GAAe,MAAM,KAAA,KAAU,MAAA;AACrC,EAAA,MAAM,CAAA,GAAI,eAAgB,KAAA,CAAM,CAAA,KAAM,OAAO,IAAA,CAAK,EAAE,CAAA,GAAK,KAAA,CAAM,MAAA,CAAA,GAAW,CAAA;AAG1E,EAAA,IAAA,CAAK,IAAA,GAAO,CAAC,CAAA,GAAI,IAAA;AACjB,EAAA,IAAA,CAAK,IAAA,GAAO,CAAC,CAAA,GAAI,IAAA;AACjB,EAAA,IAAA,CAAK,IAAA,GAAO,CAAC,CAAA,GAAI,OAAA,GAAU,KAAA,GAAQ,CAAA;AACnC,EAAA,IAAA,CAAK,IAAA,GAAO,CAAC,CAAA,GAAI,OAAA,GAAU,KAAA,GAAQ,CAAA;AACnC,EAAA,IAAA,CAAK,IAAA,GAAO,CAAC,CAAA,GAAI,IAAA;AACjB,EAAA,IAAA,CAAK,IAAA,GAAO,CAAC,CAAA,GAAI,IAAA;AACjB,EAAA,IAAA,CAAK,IAAA,GAAO,CAAC,CAAA,GAAI,IAAA;AACjB,EAAA,IAAA,CAAK,IAAA,GAAO,CAAC,CAAA,GAAI,IAAA;AACjB,EAAA,IAAA,CAAK,IAAA,GAAO,CAAC,CAAA,GAAI,QAAA;AAGjB,EAAA,IAAI,MAAM,KAAA,EAAO;AACb,IAAA,IAAA,CAAK,IAAA,GAAO,CAAC,CAAA,GAAI,KAAA,CAAM,MAAM,CAAC,CAAA;AAC9B,IAAA,IAAA,CAAK,IAAA,GAAO,EAAE,CAAA,GAAI,KAAA,CAAM,MAAM,CAAC,CAAA;AAC/B,IAAA,IAAA,CAAK,IAAA,GAAO,EAAE,CAAA,GAAI,KAAA,CAAM,MAAM,CAAC,CAAA;AAC/B,IAAA,IAAA,CAAK,IAAA,GAAO,EAAE,CAAA,GAAI,KAAA,CAAM,MAAM,CAAC,CAAA;AAAA,EACnC,WAAW,KAAA,EAAO;AACd,IAAA,IAAA,CAAK,IAAA,GAAO,CAAC,CAAA,GAAI,CAAA;AACjB,IAAA,IAAA,CAAK,IAAA,GAAO,EAAE,CAAA,GAAI,CAAA;AAClB,IAAA,IAAA,CAAK,IAAA,GAAO,EAAE,CAAA,GAAI,CAAA;AAClB,IAAA,IAAA,CAAK,IAAA,GAAO,EAAE,CAAA,GAAI,CAAA;AAAA,EACtB;AAIA,EAAA,IAAI,YAAA,EAAc;AACd,IAAA,IAAA,CAAK,IAAA,GAAO,EAAE,CAAA,GAAI,CAAA;AAAA,EACtB;AAKA,EAAA,IAAI,MAAM,SAAA,EAAW;AACjB,IAAA,MAAM,MAAA,GAAS,IAAA,IAAQ,YAAA,GAAe,EAAA,GAAK,EAAA,CAAA;AAC3C,IAAA,IAAI,MAAM,QAAA,EAAU;AAChB,MAAA,IAAA,CAAK,MAAM,CAAA,GAAI,KAAA,CAAM,QAAA,CAAS,CAAC,CAAA;AAC/B,MAAA,IAAA,CAAK,MAAA,GAAS,CAAC,CAAA,GAAI,KAAA,CAAM,SAAS,CAAC,CAAA;AAAA,IACvC,WAAW,KAAA,EAAO;AACd,MAAA,IAAA,CAAK,MAAM,CAAA,GAAI,CAAA;AACf,MAAA,IAAA,CAAK,MAAA,GAAS,CAAC,CAAA,GAAI,CAAA;AAAA,IACvB;AAAA,EAEJ;AACJ;AAEA,SAAS,SAAA,CAAU,KAAA,EAAsB,EAAA,EAAY,EAAA,EAAkB;AACnE,EAAA,IAAI,KAAA,CAAM,SAAA,IAAa,KAAA,CAAM,SAAA,EAAW;AACpC,IAAA,KAAA,CAAM,SAAA,GAAY,EAAA;AAClB,IAAA,KAAA,CAAM,SAAA,GAAY,EAAA;AAAA,EACtB,CAAA,MAAO;AACH,IAAA,IAAI,EAAA,GAAK,MAAM,SAAA,EAAW;AACtB,MAAA,KAAA,CAAM,SAAA,GAAY,EAAA;AAAA,IACtB;AACA,IAAA,IAAI,EAAA,GAAK,MAAM,SAAA,EAAW;AACtB,MAAA,KAAA,CAAM,SAAA,GAAY,EAAA;AAAA,IACtB;AAAA,EACJ;AACA,EAAA,KAAA,CAAM,QAAA,GAAY,KAAA,CAAM,QAAA,GAAW,CAAA,GAAK,CAAA;AAC5C;AAGO,SAAS,gBAAA,CAAiB,OAAsB,KAAA,EAA8B;AACjF,EAAA,IAAI,KAAA,CAAM,eAAe,MAAA,EAAW;AAChC,IAAA,MAAM,IAAI,MAAM,iDAAiD,CAAA;AAAA,EACrE;AACA,EAAA,MAAM,MAAM,KAAA,CAAM,KAAA;AAClB,EAAA,IAAI,GAAA,IAAO,MAAM,SAAA,EAAW;AACxB,IAAA,YAAA,CAAa,KAAA,EAAO,MAAM,CAAC,CAAA;AAAA,EAC/B;AACA,EAAA,aAAA,CAAc,KAAA,EAAO,GAAA,EAAK,KAAA,EAAO,IAAI,CAAA;AACrC,EAAA,gBAAA,CAAiB,KAAA,EAAO,KAAA,CAAM,KAAA,GAAQ,CAAC,CAAA;AACvC,EAAA,SAAA,CAAU,KAAA,EAAO,GAAA,EAAK,GAAA,GAAM,CAAC,CAAA;AAC7B,EAAA,OAAO,GAAA;AACX;AAGO,SAAS,mBAAA,CAAoB,KAAA,EAAsB,KAAA,EAAe,KAAA,EAAqC;AAC1G,EAAA,IAAI,KAAA,GAAQ,CAAA,IAAK,KAAA,IAAS,KAAA,CAAM,KAAA,EAAO;AACnC,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,2BAAA,EAA8B,KAAK,CAAA,kBAAA,EAAqB,KAAA,CAAM,KAAK,CAAA,CAAA,CAAG,CAAA;AAAA,EAC1F;AACA,EAAA,MAAM,IAAA,GAAO,QAAQ,KAAA,CAAM,wBAAA;AAC3B,EAAA,MAAM,OAAO,KAAA,CAAM,aAAA,CAAc,SAAS,IAAA,EAAM,IAAA,GAAO,MAAM,wBAAwB,CAAA;AACrF,EAAA,aAAA,CAAc,KAAA,EAAO,KAAA,EAAO,KAAA,EAAO,IAAI,CAAA;AACvC,EAAA,SAAA,CAAU,KAAA,EAAO,KAAA,EAAO,KAAA,GAAQ,CAAC,CAAA;AACrC;AAGO,SAAS,mBAAA,CAAoB,OAAsB,KAAA,EAAqB;AAC3E,EAAA,IAAI,KAAA,GAAQ,CAAA,IAAK,KAAA,IAAS,KAAA,CAAM,KAAA,EAAO;AACnC,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,2BAAA,EAA8B,KAAK,CAAA,kBAAA,EAAqB,KAAA,CAAM,KAAK,CAAA,CAAA,CAAG,CAAA;AAAA,EAC1F;AACA,EAAA,MAAM,IAAA,GAAO,MAAM,KAAA,GAAQ,CAAA;AAC3B,EAAA,KAAA,CAAM,YAAA,EAAc,WAAA,CAAY,KAAA,EAAO,IAAI,CAAA;AAC3C,EAAA,IAAI,UAAU,IAAA,EAAM;AAChB,IAAA,KAAA,CAAM,aAAA,CAAc,UAAA,CAAW,KAAA,GAAQ,KAAA,CAAM,wBAAA,EAA0B,IAAA,GAAO,KAAA,CAAM,wBAAA,EAAA,CAA2B,IAAA,GAAO,CAAA,IAAK,KAAA,CAAM,wBAAwB,CAAA;AAEzJ,IAAA,KAAA,CAAM,UAAA,CAAW,WAAW,KAAA,GAAQ,4BAAA,EAA8B,OAAO,4BAAA,EAAA,CAA+B,IAAA,GAAO,KAAK,4BAA4B,CAAA;AAAA,EACpJ;AAEA,EAAA,KAAA,CAAM,UAAA,CAAW,IAAA,GAAO,4BAA4B,CAAA,GAAI,CAAA;AACxD,EAAA,KAAA,CAAM,UAAA,CAAW,IAAA,GAAO,4BAAA,GAA+B,CAAC,CAAA,GAAI,CAAA;AAC5D,EAAA,gBAAA,CAAiB,OAAO,IAAI,CAAA;AAC5B,EAAA,SAAA,CAAU,KAAA,EAAO,KAAA,EAAO,KAAA,GAAQ,CAAC,CAAA;AACrC;AAGO,SAAS,mBAAmB,KAAA,EAA4B;AAC3D,EAAA,MAAM,QAAQ,KAAA,CAAM,KAAA;AACpB,EAAA,KAAA,CAAM,SAAA,GAAY,CAAA;AAClB,EAAA,KAAA,CAAM,SAAA,GAAY,CAAA;AAClB,EAAA,KAAA,CAAM,cAAc,KAAA,EAAM;AAC1B,EAAA,IAAI,UAAU,CAAA,EAAG;AACb,IAAA;AAAA,EACJ;AACA,EAAA,KAAA,CAAM,UAAA,CAAW,IAAA,CAAK,CAAA,EAAG,CAAA,EAAG,QAAQ,4BAA4B,CAAA;AAChE,EAAA,gBAAA,CAAiB,OAAO,CAAC,CAAA;AACzB,EAAA,KAAA,CAAM,QAAA,GAAY,KAAA,CAAM,QAAA,GAAW,CAAA,GAAK,CAAA;AAC5C;AASO,SAAS,qBAAA,CAAsB,KAAA,EAAsB,KAAA,EAAe,KAAA,EAAqB;AAC5F,EAAA,IAAI,KAAA,GAAQ,CAAA,IAAK,KAAA,IAAS,KAAA,CAAM,KAAA,EAAO;AACnC,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,6BAAA,EAAgC,KAAK,CAAA,kBAAA,EAAqB,KAAA,CAAM,KAAK,CAAA,CAAA,CAAG,CAAA;AAAA,EAC5F;AACA,EAAA,MAAM,QAAA,GAAW,kBAAA,CAAmB,KAAA,CAAM,KAAA,EAAO,KAAK,CAAA;AACtD,EAAA,MAAM,CAAA,GAAI,KAAA,CAAM,KAAA,CAAM,MAAA,CAAO,QAAQ,CAAA;AACrC,EAAA,MAAM,IAAA,GAAO,QAAQ,KAAA,CAAM,wBAAA;AAC3B,EAAA,MAAM,KAAA,GAAQ,MAAM,aAAA,CAAc,IAAA,GAAO,CAAC,CAAA,GAAK,KAAA,CAAM,aAAA,CAAc,IAAA,GAAO,CAAC,CAAA;AAC3E,EAAA,MAAM,KAAA,GAAQ,MAAM,aAAA,CAAc,IAAA,GAAO,CAAC,CAAA,GAAK,KAAA,CAAM,aAAA,CAAc,IAAA,GAAO,CAAC,CAAA;AAC3E,EAAA,KAAA,CAAM,aAAA,CAAc,IAAA,GAAO,CAAC,CAAA,GAAI,KAAA,GAAQ,CAAA,CAAE,KAAA,CAAM,CAAC,CAAA,GAAI,CAAA,CAAE,KAAA,CAAM,CAAC,CAAA;AAC9D,EAAA,KAAA,CAAM,aAAA,CAAc,IAAA,GAAO,CAAC,CAAA,GAAI,KAAA,GAAQ,CAAA,CAAE,KAAA,CAAM,CAAC,CAAA,GAAI,CAAA,CAAE,KAAA,CAAM,CAAC,CAAA;AAC9D,EAAA,KAAA,CAAM,aAAA,CAAc,IAAA,GAAO,CAAC,CAAA,GAAI,KAAA,GAAQ,CAAA,CAAE,KAAA,CAAM,CAAC,CAAA,GAAI,CAAA,CAAE,KAAA,CAAM,CAAC,CAAA;AAC9D,EAAA,KAAA,CAAM,aAAA,CAAc,IAAA,GAAO,CAAC,CAAA,GAAI,KAAA,GAAQ,CAAA,CAAE,KAAA,CAAM,CAAC,CAAA,GAAI,CAAA,CAAE,KAAA,CAAM,CAAC,CAAA;AAC9D,EAAA,SAAA,CAAU,KAAA,EAAO,KAAA,EAAO,KAAA,GAAQ,CAAC,CAAA;AACrC;;;;"}
|
|
1
|
+
{"version":3,"file":"sprite-2d.js","sources":["../../../src/sprite/sprite-2d.ts"],"sourcesContent":["/**\n * `Sprite2DLayer` — pixel-coordinate sprite layer. Pure-data interface +\n * standalone Index API for add / update / remove / setFrame. The layer is\n * owned by a `SpriteRenderer` (pure-2D / HUD `depth: \"none\"` path) or by a\n * scene renderable added through `addDepthHostedSpriteLayer`\n * (`depth: \"test\" | \"test-write\"`).\n *\n * The Index API stays the storage foundation. The optional Handle API lives in\n * `sprite-2d-handle.ts` and lazily installs hooks only when handles are used.\n */\nimport { F32 } from \"../engine/typed-arrays.js\";\nimport type { SpriteAtlas } from \"./shared/sprite-atlas.js\";\nimport { resolveSpriteFrame } from \"./shared/sprite-atlas.js\";\nimport type { Sprite2DCustomShader } from \"./sprite-custom-shader.js\";\nimport { _getSpriteFxHook } from \"./sprite-fx-hook.js\";\nimport type { SpriteBlendDescriptor } from \"./sprite-blend.js\";\nimport { spriteBlendAlpha } from \"./sprite-blend.js\";\n\n/**\n * Output blend mode for a sprite layer — a pure-data descriptor value. Import one of\n * `spriteBlendAlpha` (default), `spriteBlendPremultiplied`, or `spriteBlendAdditive` and pass\n * it as `Sprite2DLayerOptions.blendMode`. (Type alias of {@link SpriteBlendDescriptor}.)\n */\nexport type SpriteBlendMode = SpriteBlendDescriptor;\n\n/** Depth participation. `\"none\"` uses `SpriteRenderer`; depth-enabled modes use `addToScene`. */\nexport type Sprite2DDepthMode = \"none\" | \"test\" | \"test-write\";\n\n/** Per-layer 2D camera (pan / zoom / rotation). Identity = pixel-perfect HUD. */\nexport interface Sprite2DView {\n positionPx: [number, number];\n zoom: number;\n rotation: number;\n}\n\n/** Options accepted by `createSprite2DLayer`. */\nexport interface Sprite2DLayerOptions {\n capacity?: number;\n blendMode?: SpriteBlendMode;\n opacity?: number;\n visible?: boolean;\n order?: number;\n view?: Partial<Sprite2DView>;\n depth?: Sprite2DDepthMode;\n /**\n * Layer-wide rotation / scaling pivot in normalised sprite-local space\n * (`[0,0]` = top-left, `[0.5, 0.5]` = center, `[1,1]` = bottom-right).\n * The pivot point of every sprite in the layer lands at its `positionPx`\n * and is the center of `rotation`. Defaults to `[0.5, 0.5]` (center) to\n * match Babylon.js sprite behavior. Per-sprite / per-frame pivot is a\n * future PR — most 2D HUD layers want one uniform pivot anyway.\n */\n pivot?: [number, number];\n /**\n * Opt-in per-layer custom fragment shader (see `createSprite2DCustomShader`). Works on both\n * pure-2D (`depth: \"none\"`) layers drawn by a `SpriteRenderer` and depth-hosted\n * (`depth: \"test\" | \"test-write\"`) layers. Drives procedural effects (animated sky, clouds,\n * water / heat shimmer, twinkle, vignette) from a built-in `fx.time` clock plus an optional\n * `fx.params` vec4 set via `setSprite2DShaderParams`.\n */\n customShader?: Sprite2DCustomShader;\n /**\n * Default NDC depth (`0` = near, `1` = far) for sprites added to this layer when their\n * `Sprite2DProps.z` is omitted. Only meaningful for `depth: \"test\" | \"test-write\"` layers\n * (depth-hosted sprites added to a `SceneContext` via `addDepthHostedSpriteLayer`).\n *\n * Depth-hosted layers store one Z per sprite (slot [13] of their 14-float instance buffer),\n * so a single layer can mix sprites at different depths — e.g. one in front of a box, one\n * behind it. Pure-2D layers (`depth: \"none\"`) use the 13-float HUD layout and carry no Z slot.\n * Defaults to `0.5`. Mutating `layer.layerZ` after sprites have been added does **not**\n * retroactively change them; it only affects sprites added afterwards. To move an existing\n * depth-hosted sprite, call `updateSprite2DIndex(layer, idx, { z: … })`.\n */\n layerZ?: number;\n}\n\n/** A `Sprite2DLayer` — pure data, no methods. */\nexport interface Sprite2DLayer {\n /** @internal */\n readonly _entityType: \"sprite-2d-layer\";\n readonly atlas: SpriteAtlas;\n readonly depth: Sprite2DDepthMode;\n readonly blendMode: SpriteBlendMode;\n opacity: number;\n visible: boolean;\n order: number;\n view: Sprite2DView;\n /** Layer-wide pivot in normalised sprite-local space; see `Sprite2DLayerOptions.pivot`. */\n pivot: [number, number];\n /**\n * Opt-in custom fragment shader for this layer; see `Sprite2DLayerOptions.customShader`.\n * **Absent** (not `null`) on plain layers — never default-initialized, so the always-loaded\n * path carries zero custom-shader bytes (see `sprite-fx-hook.ts`). Present only when the\n * layer was created with a `customShader`.\n */\n readonly customShader?: Sprite2DCustomShader;\n /**\n * User `fx.params` vec4 fed to a custom shader each frame; mutate via `setSprite2DShaderParams`.\n * **Absent** on plain layers (only allocated for custom-shader layers, or lazily by the setter).\n */\n shaderParams?: [number, number, number, number];\n /**\n * @internal Opt-in per-sprite UV-scroll vertex attribute (`uvOffset.xy` at `@location(7)`);\n * enabled via {@link setSprite2DUvOffset}. **Absent** (`undefined`) on plain layers — never\n * default-initialized, so the always-loaded path and every non-scroll sprite scene keep the\n * narrow layout and base shader. The first `setSprite2DUvOffset` call widens the instance stride\n * by two floats and stashes the precomputed attribute here, so the always-loaded pipeline just\n * *consumes* it as data (`instanceAttributes.push(layer._uvScrollAttr)`) — the attribute-building\n * code lives entirely in the opt-in `sprite-2d-uvscroll.ts`. Presence also serves as the\n * \"is wide\" flag (drives the shader permutation key + the prologue `+ in.iUvOffset`).\n */\n _uvScrollAttr?: GPUVertexAttribute;\n /**\n * @internal Opt-in coverage-gamma value (glyph \"stem darkening\"); set only via\n * {@link setSprite2DCoverageGamma}. **Absent** (`undefined`, treated as `1` = no-op) on plain\n * layers — never default-initialized, so the always-loaded path keeps the base shader and\n * non-gamma scenes ship zero gamma bytes. A value `!= null && !== 1` activates the gamma shader\n * permutation (the per-fragment `pow`) and is read each frame into the layer UBO; it stays\n * live-mutable through the setter. Internal so callers can't write a value that the always-loaded\n * path would silently ignore — enabling is exclusively the setter's job.\n */\n _coverageGamma?: number;\n /** Default NDC depth for newly added sprites; see `Sprite2DLayerOptions.layerZ`. */\n layerZ: number;\n readonly count: number;\n\n /** @internal Capacity of the per-instance buffer (in sprites). */\n _capacity: number;\n /** @internal Per-instance stride in floats; 13 for pure-2D, 14 for depth-hosted. */\n readonly _instanceFloatsPerSprite: number;\n /** @internal Per-instance stride in bytes; 52 for pure-2D, 56 for depth-hosted. */\n readonly _instanceStrideBytes: number;\n /** @internal Per-instance CPU staging buffer; layout = `_instanceFloatsPerSprite` per sprite. */\n _instanceData: Float32Array;\n /**\n * @internal CPU-only side buffer holding the **true** (un-hidden) size of every sprite,\n * laid out as `[w0, h0, w1, h1, …]` (`SAVED_SIZE_FLOATS_PER_SPRITE` = 2 floats per sprite).\n *\n * **Invariant:** this buffer always holds the sprite's real size, regardless of visibility.\n * It exists because `visible: false` is implemented by zeroing the GPU-side size slots\n * (degenerate quad → free rasterizer cull) — a free hide on the GPU at the cost of 8 B per\n * sprite on the CPU. Without this shadow, a `visible: true` patch that omits `sizePx`\n * would have no way to recover the original size. Grown in lockstep with `_instanceData`.\n */\n _savedSize: Float32Array;\n /** @internal Bumped on any structural / per-instance edit; renderer compares. */\n _version: number;\n /** @internal Min dirty index inclusive (for partial uploads). */\n _dirtyMin: number;\n /** @internal Max dirty index exclusive. */\n _dirtyMax: number;\n /** @internal Optional hooks installed by the opt-in handle module. */\n _handleHooks?: Sprite2DIndexHandleHooks;\n}\n\n/** @internal Lazy hooks used by the opt-in Handle API to track swap-removes. */\nexport interface Sprite2DIndexHandleHooks {\n readonly removeIndex: (index: number, last: number) => void;\n readonly clear: () => void;\n}\n\n/** Per-sprite init record passed to `addSprite2DIndex` / `updateSprite2DIndex`. */\nexport interface Sprite2DProps {\n positionPx: [number, number];\n sizePx?: [number, number];\n frame?: number;\n rotation?: number;\n color?: [number, number, number, number];\n flipX?: boolean;\n flipY?: boolean;\n visible?: boolean;\n /** Reserved for picking. Accepted but unused today. */\n pickable?: boolean;\n /** Reserved for clip animation. Accepted but unused today. */\n clip?: unknown;\n /**\n * Per-sprite NDC depth (`0` = near, `1` = far). Only stored and consumed by depth-hosted\n * layers (`depth: \"test\" | \"test-write\"`); pure-2D HUD layers use a 13-float layout and\n * do not allocate a Z slot. When omitted on add for a depth-hosted layer, defaults to the\n * **owning layer's** `layerZ` at the moment of insertion. When omitted on update, the\n * sprite's existing Z is preserved. Mutate freely — the next binding update will re-upload\n * only the dirty range.\n */\n z?: number;\n}\n\n/**\n * Pure-2D per-instance vertex layout (13 floats = 52 bytes, `depth: \"none\"`):\n * ```\n * [0..1] positionPx.xy (float32x2 @ offset 0)\n * [2..3] sizePx.xy (float32x2 @ offset 8)\n * [4..5] uvMin.xy (float32x2 @ offset 16)\n * [6..7] uvMax.xy (float32x2 @ offset 24)\n * [8] rotation (float32 @ offset 32)\n * [9..12] colorRGBA (float32x4 @ offset 36)\n * ```\n *\n * Depth-hosted layers (`depth: \"test\" | \"test-write\"`) extend this to 14 floats = 56 bytes:\n * ```\n * [13] z (NDC depth) (float32 @ offset 52, consumed only by depth-hosted pipelines)\n * ```\n *\n * The opt-in per-sprite UV-scroll feature (`setSprite2DUvOffset`, see `sprite-2d-uvscroll.ts`)\n * appends two more floats (`uvOffset.xy`) *after* the base layout, orthogonally to depth:\n * ```\n * pure-2D + uvScroll (15 floats = 60 bytes): [13..14] uvOffset.xy (float32x2 @ offset 52)\n * depth + uvScroll (16 floats = 64 bytes): [14..15] uvOffset.xy (float32x2 @ offset 56)\n * ```\n * The depth Z stays at slot [13]; uvOffset is appended after it. Non-scroll layers carry no\n * uvOffset slot and ship none of the widening (the wide layout lives entirely in the opt-in module).\n *\n * Visibility (`visible: false`) is implemented by zeroing slots [2..3]; the sprite's true\n * size lives in `layer._savedSize` so a later `visible: true` (without re-supplying\n * `sizePx`) can restore it. See `_savedSize` for the invariant.\n */\nexport const PURE_2D_INSTANCE_FLOATS_PER_SPRITE = 13;\nexport const DEPTH_INSTANCE_FLOATS_PER_SPRITE = 14;\n/** @internal Pure-2D per-sprite stride in bytes. */\nexport const PURE_2D_INSTANCE_STRIDE_BYTES = PURE_2D_INSTANCE_FLOATS_PER_SPRITE * 4;\n/** @internal Depth-hosted per-sprite stride in bytes. */\nexport const DEPTH_INSTANCE_STRIDE_BYTES = DEPTH_INSTANCE_FLOATS_PER_SPRITE * 4;\n/** @internal Per-sprite stride (in floats) of the `_savedSize` shadow buffer: `[w, h]`. */\nexport const SAVED_SIZE_FLOATS_PER_SPRITE = 2;\n\nconst DEFAULT_CAPACITY = 16;\n\n/** Create a new (empty) `Sprite2DLayer` backed by `atlas`. */\nexport function createSprite2DLayer(atlas: SpriteAtlas, opts: Sprite2DLayerOptions = {}): Sprite2DLayer {\n const depth = opts.depth ?? \"none\";\n const blendMode = opts.blendMode ?? spriteBlendAlpha;\n\n const capacity = Math.max(1, opts.capacity ?? DEFAULT_CAPACITY);\n const view: Sprite2DView = {\n positionPx: [opts.view?.positionPx?.[0] ?? 0, opts.view?.positionPx?.[1] ?? 0],\n zoom: opts.view?.zoom ?? 1,\n rotation: opts.view?.rotation ?? 0,\n };\n\n const instanceFloatsPerSprite = depth === \"none\" ? PURE_2D_INSTANCE_FLOATS_PER_SPRITE : DEPTH_INSTANCE_FLOATS_PER_SPRITE;\n const instanceStrideBytes = instanceFloatsPerSprite * 4;\n const instanceData = new F32(capacity * instanceFloatsPerSprite);\n const layer: Sprite2DLayer = {\n _entityType: \"sprite-2d-layer\",\n atlas,\n depth,\n blendMode,\n opacity: opts.opacity ?? 1,\n visible: opts.visible ?? true,\n order: opts.order ?? 0,\n view,\n pivot: [opts.pivot?.[0] ?? 0.5, opts.pivot?.[1] ?? 0.5],\n layerZ: opts.layerZ ?? 0.5,\n count: 0,\n _capacity: capacity,\n _instanceFloatsPerSprite: instanceFloatsPerSprite,\n _instanceStrideBytes: instanceStrideBytes,\n _instanceData: instanceData,\n _savedSize: new F32(capacity * SAVED_SIZE_FLOATS_PER_SPRITE),\n _version: 0,\n _dirtyMin: 0,\n _dirtyMax: 0,\n };\n // Zero-default-init discipline: the base layer never names `customShader` / `shaderParams`.\n // When (and only when) a custom shader was supplied, the registered hook copies it on — the\n // impl lives in the tree-shaken `sprite-custom-shader` module, so plain scenes ship none of it.\n _getSpriteFxHook()?.initLayer(layer, opts);\n return layer;\n}\n\n/**\n * Set the user `fx.params` vec4 fed to this layer's custom shader (`createSprite2DCustomShader`)\n * each frame. No visual effect unless the layer was created with a `customShader`. Read in WGSL\n * as `fx.params`. Mutates in place; the renderer re-uploads the small FX UBO next frame.\n */\nexport function setSprite2DShaderParams(layer: Sprite2DLayer, params: readonly [number, number, number, number]): void {\n // Lazy-allocate: the base layer never names `shaderParams` (the custom-shader hook sets it only\n // when `opts.customShader` is present), so a plain layer keeps the field off the always-loaded path.\n const target = (layer.shaderParams ??= [0, 0, 0, 0]);\n target[0] = params[0];\n target[1] = params[1];\n target[2] = params[2];\n target[3] = params[3];\n}\n\nfunction growCapacity(layer: Sprite2DLayer, minCapacity: number): void {\n let cap = layer._capacity;\n while (cap < minCapacity) {\n cap *= 2;\n }\n const next = new F32(cap * layer._instanceFloatsPerSprite);\n next.set(layer._instanceData);\n layer._instanceData = next;\n const nextSaved = new F32(cap * SAVED_SIZE_FLOATS_PER_SPRITE);\n nextSaved.set(layer._savedSize);\n layer._savedSize = nextSaved;\n layer._capacity = cap;\n}\n\nfunction setSprite2DCount(layer: Sprite2DLayer, count: number): void {\n (layer as { count: number }).count = count;\n}\n\n/**\n * Write one sprite's instance data into `layer._instanceData[base..base+layer._instanceFloatsPerSprite]`.\n *\n * Two call sites with different shapes:\n * - **add**: `prev === null`. `props` is a full `Sprite2DProps` (positionPx required).\n * Unspecified fields take their documented defaults (size=frame.sourceSizePx or 0,\n * UVs=[0,0,1,1], rotation=0, color=opaque white, visible=true).\n * - **update**: `prev` is the existing per-layer instance slice. Unspecified fields are preserved.\n *\n * Resolution rules (per field): `props` value if given, else (on add) the default, else `prev`.\n * `frame` is a higher-level intent: when supplied it stomps the four UV slots from the atlas\n * (then `flipX`/`flipY` swap them). It does **not** by itself imply a size change — `sizePx`\n * remains independent — but on add, a missing `sizePx` falls back to `frame.sourceSizePx`.\n *\n * **Visibility model (the part that needs explaining):**\n * - `_savedSize[slot]` always stores the sprite's *true* size (unaffected by visibility).\n * - `data[base+2..+3]` (the GPU-visible size) is `_savedSize` when visible, else `(0, 0)`.\n * - We detect previous visibility by checking `prev[2]==0 && prev[3]==0` (only hidden sprites\n * have zeroed GPU size). The CPU shadow gives us back the true size for free.\n */\nfunction writeInstance(layer: Sprite2DLayer, slotIndex: number, props: Partial<Sprite2DProps>, prev: Float32Array | null): void {\n const data = layer._instanceData;\n const base = slotIndex * layer._instanceFloatsPerSprite;\n const savedBase = slotIndex * SAVED_SIZE_FLOATS_PER_SPRITE; // [w, h] per sprite\n const isAdd = prev === null;\n\n // Optional frame lookup (used for UV stomp + size default on add).\n const frame = props.frame !== undefined ? layer.atlas.frames[resolveSpriteFrame(layer.atlas, props.frame)]! : null;\n\n // ── Position (required on add; preserved on update if omitted) ──────────────────────\n const posX = props.positionPx ? props.positionPx[0] : prev![0]!;\n const posY = props.positionPx ? props.positionPx[1] : prev![1]!;\n\n // ── True size (props.sizePx → frame default → previous true size) ───────────────────\n // The shadow buffer makes \"previous true size\" cheap and unambiguous regardless of visibility.\n let trueW: number;\n let trueH: number;\n if (props.sizePx) {\n trueW = props.sizePx[0];\n trueH = props.sizePx[1];\n } else if (frame) {\n trueW = frame.sourceSizePx[0];\n trueH = frame.sourceSizePx[1];\n } else if (isAdd) {\n trueW = 0;\n trueH = 0;\n } else {\n trueW = layer._savedSize[savedBase]!;\n trueH = layer._savedSize[savedBase + 1]!;\n }\n layer._savedSize[savedBase] = trueW;\n layer._savedSize[savedBase + 1] = trueH;\n\n // ── Visibility (props.visible → preserved → default true on add) ────────────────────\n let visible: boolean;\n if (props.visible !== undefined) {\n visible = props.visible;\n } else if (isAdd) {\n visible = true;\n } else {\n // Previous sprite was hidden iff its GPU size was zeroed.\n visible = prev![2]! !== 0 || prev![3]! !== 0;\n }\n\n // ── UVs (frame stomps; else preserved; else default [0,0,1,1] on add) ───────────────\n // flipX/flipY apply on top, by swapping the U/V endpoints.\n let uMin: number;\n let vMin: number;\n let uMax: number;\n let vMax: number;\n if (frame) {\n uMin = frame.uvMin[0];\n vMin = frame.uvMin[1];\n uMax = frame.uvMax[0];\n vMax = frame.uvMax[1];\n } else if (isAdd) {\n uMin = 0;\n vMin = 0;\n uMax = 1;\n vMax = 1;\n } else {\n uMin = prev![4]!;\n vMin = prev![5]!;\n uMax = prev![6]!;\n vMax = prev![7]!;\n }\n if (props.flipX === true) {\n const t = uMin;\n uMin = uMax;\n uMax = t;\n }\n if (props.flipY === true) {\n const t = vMin;\n vMin = vMax;\n vMax = t;\n }\n\n // ── Rotation ────────────────────────────────────────────────────────────────────────\n const rotation = props.rotation ?? (prev ? prev[8]! : 0);\n\n // ── Per-instance Z (depth-hosted only) ──────────────────────────────────────────────\n // Pure-2D layers intentionally have no slot [13]; `z` is accepted by the public API but\n // not allocated, uploaded, declared, or fetched by HUD pipelines.\n const hasDepthSlot = layer.depth !== \"none\";\n const z = hasDepthSlot ? (props.z ?? (prev ? prev[13]! : layer.layerZ)) : 0;\n\n // ── Write the float slots ──────────────────────────────────────────────────────────\n data[base + 0] = posX;\n data[base + 1] = posY;\n data[base + 2] = visible ? trueW : 0;\n data[base + 3] = visible ? trueH : 0;\n data[base + 4] = uMin;\n data[base + 5] = vMin;\n data[base + 6] = uMax;\n data[base + 7] = vMax;\n data[base + 8] = rotation;\n\n // ── Color (float32x4 to match Babylon.js SpriteRenderer's color precision) ─────────\n if (props.color) {\n data[base + 9] = props.color[0];\n data[base + 10] = props.color[1];\n data[base + 11] = props.color[2];\n data[base + 12] = props.color[3];\n } else if (isAdd) {\n data[base + 9] = 1;\n data[base + 10] = 1;\n data[base + 11] = 1;\n data[base + 12] = 1;\n }\n // else: previous color floats are already in place — nothing to write.\n\n // ── Per-instance Z (slot [13], depth-hosted layout only) ───────────────────────────\n if (hasDepthSlot) {\n data[base + 13] = z;\n }\n // Per-sprite uvOffset (wide layout) is written only via the opt-in `setSprite2DUvOffset`\n // (see `sprite-2d-uvscroll.ts`); `writeInstance` touches only base slots, so a wide layer's\n // uvOffset floats are preserved across add / update.\n}\n\nfunction markDirty(layer: Sprite2DLayer, lo: number, hi: number): void {\n if (layer._dirtyMin >= layer._dirtyMax) {\n layer._dirtyMin = lo;\n layer._dirtyMax = hi;\n } else {\n if (lo < layer._dirtyMin) {\n layer._dirtyMin = lo;\n }\n if (hi > layer._dirtyMax) {\n layer._dirtyMax = hi;\n }\n }\n layer._version = (layer._version + 1) | 0;\n}\n\n/** @internal Mark a dirty sprite range + bump version. Exposed for the opt-in uvScroll module. */\nexport function _markSprite2DDirty(layer: Sprite2DLayer, lo: number, hi: number): void {\n markDirty(layer, lo, hi);\n}\n\n/** Add one sprite. Returns its index. Grows capacity as needed. */\nexport function addSprite2DIndex(layer: Sprite2DLayer, props: Sprite2DProps): number {\n if (props.positionPx === undefined) {\n throw new Error(\"addSprite2DIndex: positionPx required.\");\n }\n const idx = layer.count;\n if (idx >= layer._capacity) {\n growCapacity(layer, idx + 1);\n }\n writeInstance(layer, idx, props, null);\n setSprite2DCount(layer, layer.count + 1);\n markDirty(layer, idx, idx + 1);\n return idx;\n}\n\n/** Patch one sprite. Unspecified fields are preserved. */\nexport function updateSprite2DIndex(layer: Sprite2DLayer, index: number, patch: Partial<Sprite2DProps>): void {\n if (index < 0 || index >= layer.count) {\n throw new Error(`updateSprite2DIndex: index ${index} out of range [0, ${layer.count})`);\n }\n const base = index * layer._instanceFloatsPerSprite;\n const prev = layer._instanceData.subarray(base, base + layer._instanceFloatsPerSprite);\n writeInstance(layer, index, patch, prev);\n markDirty(layer, index, index + 1);\n}\n\n/** Swap-remove a sprite. The last sprite (if any) takes its slot. */\nexport function removeSprite2DIndex(layer: Sprite2DLayer, index: number): void {\n if (index < 0 || index >= layer.count) {\n throw new Error(`removeSprite2DIndex: index ${index} out of range [0, ${layer.count})`);\n }\n const last = layer.count - 1;\n layer._handleHooks?.removeIndex(index, last);\n if (index !== last) {\n layer._instanceData.copyWithin(index * layer._instanceFloatsPerSprite, last * layer._instanceFloatsPerSprite, (last + 1) * layer._instanceFloatsPerSprite);\n // Carry the swapped sprite's saved-size shadow with it (`[w, h]` per sprite).\n layer._savedSize.copyWithin(index * SAVED_SIZE_FLOATS_PER_SPRITE, last * SAVED_SIZE_FLOATS_PER_SPRITE, (last + 1) * SAVED_SIZE_FLOATS_PER_SPRITE);\n }\n // Clear the now-unused tail saved-size slot so a future re-add starts clean.\n layer._savedSize[last * SAVED_SIZE_FLOATS_PER_SPRITE] = 0;\n layer._savedSize[last * SAVED_SIZE_FLOATS_PER_SPRITE + 1] = 0;\n setSprite2DCount(layer, last);\n markDirty(layer, index, index + 1);\n}\n\n/** Clear all sprites from a layer while preserving allocated capacity. */\nexport function clearSprite2DLayer(layer: Sprite2DLayer): void {\n const count = layer.count;\n layer._dirtyMin = 0;\n layer._dirtyMax = 0;\n layer._handleHooks?.clear();\n if (count === 0) {\n return;\n }\n layer._savedSize.fill(0, 0, count * SAVED_SIZE_FLOATS_PER_SPRITE);\n setSprite2DCount(layer, 0);\n layer._version = (layer._version + 1) | 0;\n}\n\n/**\n * Update only the frame UVs for one sprite.\n *\n * The sprite keeps its explicit `sizePx`/saved size. For atlas-driven size\n * changes, call `updateSprite2DIndex(layer, index, { frame, sizePx })`.\n * Existing flip state is preserved for non-degenerate UV ranges.\n */\nexport function setSprite2DFrameIndex(layer: Sprite2DLayer, index: number, frame: number): void {\n if (index < 0 || index >= layer.count) {\n throw new Error(`setSprite2DFrameIndex: index ${index} out of range [0, ${layer.count})`);\n }\n const frameIdx = resolveSpriteFrame(layer.atlas, frame);\n const f = layer.atlas.frames[frameIdx]!;\n const base = index * layer._instanceFloatsPerSprite;\n const flipX = layer._instanceData[base + 4]! > layer._instanceData[base + 6]!;\n const flipY = layer._instanceData[base + 5]! > layer._instanceData[base + 7]!;\n layer._instanceData[base + 4] = flipX ? f.uvMax[0] : f.uvMin[0];\n layer._instanceData[base + 5] = flipY ? f.uvMax[1] : f.uvMin[1];\n layer._instanceData[base + 6] = flipX ? f.uvMin[0] : f.uvMax[0];\n layer._instanceData[base + 7] = flipY ? f.uvMin[1] : f.uvMax[1];\n markDirty(layer, index, index + 1);\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
let _coverageGammaHook = null;
|
|
2
|
+
function _registerSpriteCoverageGammaHook(hook) {
|
|
3
|
+
_coverageGammaHook = hook;
|
|
4
|
+
}
|
|
5
|
+
function _getSpriteCoverageGammaHook() {
|
|
6
|
+
return _coverageGammaHook;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export { _getSpriteCoverageGammaHook, _registerSpriteCoverageGammaHook };
|
|
10
|
+
//# sourceMappingURL=sprite-coverage-gamma-hook.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sprite-coverage-gamma-hook.js","sources":["../../../src/sprite/sprite-coverage-gamma-hook.ts"],"sourcesContent":["/**\n * Lazy, null-by-default registry hook for the opt-in sprite coverage-gamma feature.\n *\n * Coverage gamma raises a glyph-atlas layer's sampled alpha to `1/coverageGamma` in the\n * fragment stage (text \"stem darkening\"). It is a value-driven feature, so — unlike the\n * custom-shader hook whose impl module is pulled in by importing `createSprite2DCustomShader`\n * — its trigger is `setSprite2DCoverageGamma`. Importing that setter registers this hook and\n * drags in the gamma shader permutation, the `aa.x` UBO write, and the pipeline-key part.\n *\n * The always-loaded sprite pipeline reaches the feature exclusively through these hook methods\n * and never names the layer's internal `_coverageGamma` value, so a sprite scene that never imports\n * the setter keeps the hook `null` and carries zero gamma bytes — mirroring `sprite-fx-hook.ts`.\n *\n * No module-level side effects: the hook slot is a plain nullable `let`; registration is explicit.\n */\nimport type { EngineContext } from \"../engine/engine.js\";\nimport type { Sprite2DLayer } from \"./sprite-2d.js\";\n\n/** @internal Opt-in sprite coverage-gamma hook. The impl reads the layer's internal `_coverageGamma` value. */\nexport interface SpriteCoverageGammaHook {\n /** Pipeline-cache key part for `layer` (`\"1\"` for a gamma layer, `\"0\"` otherwise). */\n pipelineKeyPart(layer: Sprite2DLayer): string;\n /** Coverage-gamma sprite shader module for `layer`, or `null` to fall back to the base shader. */\n shaderModule(engine: EngineContext, hasDepth: boolean, layer: Sprite2DLayer): GPUShaderModule | null;\n /** Write the gamma slice (`aa.x = 1/coverageGamma`) of the layer UBO. Deterministic for every layer. */\n writeUbo(layer: Sprite2DLayer, ubo: Float32Array): void;\n}\n\nlet _coverageGammaHook: SpriteCoverageGammaHook | null = null;\n\n/** @internal Register the sprite coverage-gamma hook. Idempotent; called by `setSprite2DCoverageGamma`. */\nexport function _registerSpriteCoverageGammaHook(hook: SpriteCoverageGammaHook): void {\n _coverageGammaHook = hook;\n}\n\n/** @internal The registered sprite coverage-gamma hook, or `null` when no gamma layer exists. */\nexport function _getSpriteCoverageGammaHook(): SpriteCoverageGammaHook | null {\n return _coverageGammaHook;\n}\n"],"names":[],"mappings":"AA4BA,IAAI,kBAAA,GAAqD,IAAA;AAGlD,SAAS,iCAAiC,IAAA,EAAqC;AAClF,EAAA,kBAAA,GAAqB,IAAA;AACzB;AAGO,SAAS,2BAAA,GAA8D;AAC1E,EAAA,OAAO,kBAAA;AACX;;;;"}
|
|
@@ -7,7 +7,7 @@ function makeCustomSpriteWgsl(hasDepth, spriteGroupIndex, extraTextures, fragmen
|
|
|
7
7
|
return `${makeSpritePrologueWgsl(hasDepth, spriteGroupIndex, uvScroll)}
|
|
8
8
|
${makeExtraBindingsWgsl(spriteGroupIndex, 3, extraTextures)}${makeFxStructWgsl(spriteGroupIndex, fxBinding)}
|
|
9
9
|
@fragment
|
|
10
|
-
fn fs(in:
|
|
10
|
+
fn fs(in: O) -> @location(0) vec4f {
|
|
11
11
|
${fragment}
|
|
12
12
|
}`;
|
|
13
13
|
}
|
|
@@ -23,7 +23,7 @@ const SPRITE_FX_HOOK = {
|
|
|
23
23
|
return layer.customShader?._key ?? "";
|
|
24
24
|
},
|
|
25
25
|
shaderModule(engine, hasDepth, layer) {
|
|
26
|
-
return layer.customShader?._getShaderModule(engine, hasDepth, layer.
|
|
26
|
+
return layer.customShader?._getShaderModule(engine, hasDepth, layer._uvScrollAttr != null) ?? null;
|
|
27
27
|
},
|
|
28
28
|
layoutEntries(layer, startBinding) {
|
|
29
29
|
return layer.customShader?._layoutEntries(startBinding) ?? null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sprite-custom-shader.js","sources":["../../../src/sprite/sprite-custom-shader.ts"],"sourcesContent":["/**\n * Optional, tree-shakable custom-shader hook for `Sprite2DLayer` (the engine owns the layer\n * transform, instancing, sorting, and depth; the caller supplies only a WGSL **fragment body**\n * plus optional extra textures).\n *\n * Works on both pure-2D HUD layers (`depth: \"none\"`, drawn by a `SpriteRenderer`) and\n * depth-hosted 2.5D layers (`depth: \"test\"` / `\"test-write\"`, drawn as scene `Renderable`s).\n * The 2D and billboard composers share their *mechanics* via `custom-shader-core.ts`\n * (extra-texture bindings, name validation, the `SpriteFx` UBO, key allocation) but keep their\n * own vertex stage and varying contract, which genuinely differ: billboards transform in world\n * space and expose `viewDist`/`worldPos`; a 2D layer transforms in pixel space and exposes only\n * `uv`/`tint`.\n *\n * Tree-shaking contract: the default sprite path never imports this module. `sprite-pipeline.ts`\n * only reaches the custom composer through the opaque object a caller builds here via\n * `createSprite2DCustomShader`, so a layer that uses the stock shader pays zero bytes for it.\n *\n * WGSL contract for the supplied `fragment` body:\n * - Receives `in: VOut` with `uv: vec2<f32>` and `tint: vec4<f32>` (the per-sprite `color`).\n * - Has access to `atlasTex` / `atlasSamp` (the layer atlas at bindings 1/2), each extra\n * texture as `<name>Tex` / `<name>Samp`, the `fx` UBO (`fx.time`, `fx.params`), and the\n * `L` layer UBO (e.g. `L.opacityMul`).\n * - Must `return vec4<f32>(...)` (and may `discard`). The body owns all alpha handling; no\n * per-layer opacity is applied automatically.\n */\nimport type { CustomShaderTexture, SpriteLayerFx } from \"./custom-shader-core.js\";\nimport {\n createSpriteLayerFx,\n EMPTY_PARAMS,\n makeCustomShaderLayoutEntries,\n makeExtraBindingsWgsl,\n makeFxStructWgsl,\n makeShaderModuleCache,\n nextCustomShaderKey,\n validateExtraTextureNames,\n} from \"./custom-shader-core.js\";\nimport type { EngineContext } from \"../engine/engine.js\";\nimport { makeSpritePrologueWgsl } from \"./sprite-pipeline.js\";\nimport type { SpriteFxHook } from \"./sprite-fx-hook.js\";\nimport { _registerSpriteFxHook } from \"./sprite-fx-hook.js\";\n\n/** One extra texture bound after the atlas. In WGSL it becomes `<name>Tex` + `<name>Samp`. */\nexport type Sprite2DCustomTexture = CustomShaderTexture;\n\n/** Options for {@link createSprite2DCustomShader}. */\nexport interface Sprite2DCustomShaderOptions {\n /** WGSL fragment body. See the module docs for the in-scope identifiers. */\n readonly fragment: string;\n /** Extra textures, in binding order. Each contributes a `texture_2d` + `sampler`. */\n readonly extraTextures?: readonly Sprite2DCustomTexture[];\n}\n\n/** A compiled-on-demand custom sprite shader. Pure data; pass as `customShader` to `createSprite2DLayer`. */\nexport interface Sprite2DCustomShader {\n /** @internal */\n readonly _entityType: \"sprite-2d-custom-shader\";\n /** @internal Extra textures bound after the atlas. */\n readonly _extraTextures: readonly Sprite2DCustomTexture[];\n /** @internal Stable identity used to key pipeline + shader-module caches. */\n readonly _key: string;\n /** @internal Compose the full WGSL module for a given layout (`hasDepth` → group index). */\n readonly _composeWgsl: (hasDepth: boolean, spriteGroupIndex: 0 | 1, uvScroll: boolean) => string;\n /** @internal Compile + cache the `GPUShaderModule` for a layout (owns its per-device cache). */\n readonly _getShaderModule: (engine: EngineContext, hasDepth: boolean, uvScroll: boolean) => GPUShaderModule;\n /** @internal Extra-texture + fx UBO bind-group **layout** entries, starting at `startBinding` (3). */\n readonly _layoutEntries: (startBinding: number) => GPUBindGroupLayoutEntry[];\n /** @internal Build the opaque per-layer fx attachment (owns the `SpriteFx` UBO, scratch, and elapsed time). */\n readonly _createLayerFx: (engine: EngineContext, label: string) => SpriteLayerFx;\n}\n\nfunction makeCustomSpriteWgsl(hasDepth: boolean, spriteGroupIndex: 0 | 1, extraTextures: readonly Sprite2DCustomTexture[], fragment: string, uvScroll: boolean): string {\n const fxBinding = 3 + extraTextures.length * 2;\n return `${makeSpritePrologueWgsl(hasDepth, spriteGroupIndex, uvScroll)}\n${makeExtraBindingsWgsl(spriteGroupIndex, 3, extraTextures)}${makeFxStructWgsl(spriteGroupIndex, fxBinding)}\n@fragment\nfn fs(in: VOut) -> @location(0) vec4<f32> {\n${fragment}\n}`;\n}\n\n/**\n * The 2D-sprite custom-shader hook implementation. Lives only in this (tree-shaken) module, so the\n * always-loaded sprite path never names `customShader` / `shaderParams`. Reads both off the opaque\n * `layer` and delegates to the descriptor's underscore-prefixed (mangled) methods.\n */\nconst SPRITE_FX_HOOK: SpriteFxHook = {\n initLayer(layer, opts) {\n const customShader = opts.customShader;\n if (customShader) {\n (layer as { customShader?: Sprite2DCustomShader }).customShader = customShader;\n layer.shaderParams = [0, 0, 0, 0];\n }\n },\n pipelineKeyPart(layer) {\n return layer.customShader?._key ?? \"\";\n },\n shaderModule(engine, hasDepth, layer) {\n return layer.customShader?._getShaderModule(engine, hasDepth, layer._uvScroll === true) ?? null;\n },\n layoutEntries(layer, startBinding) {\n return layer.customShader?._layoutEntries(startBinding) ?? null;\n },\n createLayerFx(engine, label, layer) {\n return layer.customShader?._createLayerFx(engine, label) ?? null;\n },\n updateFx(fx, layer, deltaMs) {\n fx.update(layer.shaderParams ?? EMPTY_PARAMS, deltaMs);\n },\n bindEntries(fx, startBinding) {\n return fx.bindEntries(startBinding);\n },\n disposeFx(fx) {\n fx.destroy();\n },\n};\n\n/**\n * Create a custom fragment shader for a sprite layer. Pass the result as the `customShader`\n * option of `createSprite2DLayer`. See the module-level docs for the WGSL contract.\n */\nexport function createSprite2DCustomShader(options: Sprite2DCustomShaderOptions): Sprite2DCustomShader {\n _registerSpriteFxHook(SPRITE_FX_HOOK);\n const fragment = options.fragment;\n if (typeof fragment !== \"string\" || fragment.trim().length === 0) {\n throw new Error(\"createSprite2DCustomShader: `fragment` must be a non-empty WGSL string.\");\n }\n const extraTextures = options.extraTextures ?? [];\n validateExtraTextureNames(\"createSprite2DCustomShader\", extraTextures);\n const moduleCache = makeShaderModuleCache();\n return {\n _entityType: \"sprite-2d-custom-shader\",\n _extraTextures: extraTextures,\n _key: nextCustomShaderKey(\"s\"),\n _composeWgsl: (hasDepth, spriteGroupIndex, uvScroll) => makeCustomSpriteWgsl(hasDepth, spriteGroupIndex, extraTextures, fragment, uvScroll),\n // `spriteGroupIndex = hasDepth ? 1 : 0`: depth-hosted layers put the scene UBO at group 0, so the\n // sprite resources (atlas, extras, fx) shift to group 1; pure-2D layers keep them at group 0.\n _getShaderModule: (engine, hasDepth, uvScroll) =>\n moduleCache(engine, `${hasDepth ? \"1\" : \"0\"}:${uvScroll ? \"u\" : \"-\"}`, () => makeCustomSpriteWgsl(hasDepth, hasDepth ? 1 : 0, extraTextures, fragment, uvScroll)),\n _layoutEntries: (startBinding) => makeCustomShaderLayoutEntries(extraTextures, startBinding),\n _createLayerFx: (engine, label) => createSpriteLayerFx(engine, label, extraTextures),\n };\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"sprite-custom-shader.js","sources":["../../../src/sprite/sprite-custom-shader.ts"],"sourcesContent":["/**\n * Optional, tree-shakable custom-shader hook for `Sprite2DLayer` (the engine owns the layer\n * transform, instancing, sorting, and depth; the caller supplies only a WGSL **fragment body**\n * plus optional extra textures).\n *\n * Works on both pure-2D HUD layers (`depth: \"none\"`, drawn by a `SpriteRenderer`) and\n * depth-hosted 2.5D layers (`depth: \"test\"` / `\"test-write\"`, drawn as scene `Renderable`s).\n * The 2D and billboard composers share their *mechanics* via `custom-shader-core.ts`\n * (extra-texture bindings, name validation, the `SpriteFx` UBO, key allocation) but keep their\n * own vertex stage and varying contract, which genuinely differ: billboards transform in world\n * space and expose `viewDist`/`worldPos`; a 2D layer transforms in pixel space and exposes only\n * `uv`/`tint`.\n *\n * Tree-shaking contract: the default sprite path never imports this module. `sprite-pipeline.ts`\n * only reaches the custom composer through the opaque object a caller builds here via\n * `createSprite2DCustomShader`, so a layer that uses the stock shader pays zero bytes for it.\n *\n * WGSL contract for the supplied `fragment` body:\n * - Receives `in: VOut` with `uv: vec2<f32>` and `tint: vec4<f32>` (the per-sprite `color`).\n * - Has access to `atlasTex` / `atlasSamp` (the layer atlas at bindings 1/2), each extra\n * texture as `<name>Tex` / `<name>Samp`, the `fx` UBO (`fx.time`, `fx.params`), and the\n * `L` layer UBO (e.g. `L.opacityMul`).\n * - Must `return vec4<f32>(...)` (and may `discard`). The body owns all alpha handling; no\n * per-layer opacity is applied automatically.\n */\nimport type { CustomShaderTexture, SpriteLayerFx } from \"./custom-shader-core.js\";\nimport {\n createSpriteLayerFx,\n EMPTY_PARAMS,\n makeCustomShaderLayoutEntries,\n makeExtraBindingsWgsl,\n makeFxStructWgsl,\n makeShaderModuleCache,\n nextCustomShaderKey,\n validateExtraTextureNames,\n} from \"./custom-shader-core.js\";\nimport type { EngineContext } from \"../engine/engine.js\";\nimport { makeSpritePrologueWgsl } from \"./sprite-pipeline.js\";\nimport type { SpriteFxHook } from \"./sprite-fx-hook.js\";\nimport { _registerSpriteFxHook } from \"./sprite-fx-hook.js\";\n\n/** One extra texture bound after the atlas. In WGSL it becomes `<name>Tex` + `<name>Samp`. */\nexport type Sprite2DCustomTexture = CustomShaderTexture;\n\n/** Options for {@link createSprite2DCustomShader}. */\nexport interface Sprite2DCustomShaderOptions {\n /** WGSL fragment body. See the module docs for the in-scope identifiers. */\n readonly fragment: string;\n /** Extra textures, in binding order. Each contributes a `texture_2d` + `sampler`. */\n readonly extraTextures?: readonly Sprite2DCustomTexture[];\n}\n\n/** A compiled-on-demand custom sprite shader. Pure data; pass as `customShader` to `createSprite2DLayer`. */\nexport interface Sprite2DCustomShader {\n /** @internal */\n readonly _entityType: \"sprite-2d-custom-shader\";\n /** @internal Extra textures bound after the atlas. */\n readonly _extraTextures: readonly Sprite2DCustomTexture[];\n /** @internal Stable identity used to key pipeline + shader-module caches. */\n readonly _key: string;\n /** @internal Compose the full WGSL module for a given layout (`hasDepth` → group index). */\n readonly _composeWgsl: (hasDepth: boolean, spriteGroupIndex: 0 | 1, uvScroll: boolean) => string;\n /** @internal Compile + cache the `GPUShaderModule` for a layout (owns its per-device cache). */\n readonly _getShaderModule: (engine: EngineContext, hasDepth: boolean, uvScroll: boolean) => GPUShaderModule;\n /** @internal Extra-texture + fx UBO bind-group **layout** entries, starting at `startBinding` (3). */\n readonly _layoutEntries: (startBinding: number) => GPUBindGroupLayoutEntry[];\n /** @internal Build the opaque per-layer fx attachment (owns the `SpriteFx` UBO, scratch, and elapsed time). */\n readonly _createLayerFx: (engine: EngineContext, label: string) => SpriteLayerFx;\n}\n\nfunction makeCustomSpriteWgsl(hasDepth: boolean, spriteGroupIndex: 0 | 1, extraTextures: readonly Sprite2DCustomTexture[], fragment: string, uvScroll: boolean): string {\n const fxBinding = 3 + extraTextures.length * 2;\n return `${makeSpritePrologueWgsl(hasDepth, spriteGroupIndex, uvScroll)}\n${makeExtraBindingsWgsl(spriteGroupIndex, 3, extraTextures)}${makeFxStructWgsl(spriteGroupIndex, fxBinding)}\n@fragment\nfn fs(in: O) -> @location(0) vec4f {\n${fragment}\n}`;\n}\n\n/**\n * The 2D-sprite custom-shader hook implementation. Lives only in this (tree-shaken) module, so the\n * always-loaded sprite path never names `customShader` / `shaderParams`. Reads both off the opaque\n * `layer` and delegates to the descriptor's underscore-prefixed (mangled) methods.\n */\nconst SPRITE_FX_HOOK: SpriteFxHook = {\n initLayer(layer, opts) {\n const customShader = opts.customShader;\n if (customShader) {\n (layer as { customShader?: Sprite2DCustomShader }).customShader = customShader;\n layer.shaderParams = [0, 0, 0, 0];\n }\n },\n pipelineKeyPart(layer) {\n return layer.customShader?._key ?? \"\";\n },\n shaderModule(engine, hasDepth, layer) {\n return layer.customShader?._getShaderModule(engine, hasDepth, layer._uvScrollAttr != null) ?? null;\n },\n layoutEntries(layer, startBinding) {\n return layer.customShader?._layoutEntries(startBinding) ?? null;\n },\n createLayerFx(engine, label, layer) {\n return layer.customShader?._createLayerFx(engine, label) ?? null;\n },\n updateFx(fx, layer, deltaMs) {\n fx.update(layer.shaderParams ?? EMPTY_PARAMS, deltaMs);\n },\n bindEntries(fx, startBinding) {\n return fx.bindEntries(startBinding);\n },\n disposeFx(fx) {\n fx.destroy();\n },\n};\n\n/**\n * Create a custom fragment shader for a sprite layer. Pass the result as the `customShader`\n * option of `createSprite2DLayer`. See the module-level docs for the WGSL contract.\n */\nexport function createSprite2DCustomShader(options: Sprite2DCustomShaderOptions): Sprite2DCustomShader {\n _registerSpriteFxHook(SPRITE_FX_HOOK);\n const fragment = options.fragment;\n if (typeof fragment !== \"string\" || fragment.trim().length === 0) {\n throw new Error(\"createSprite2DCustomShader: `fragment` must be a non-empty WGSL string.\");\n }\n const extraTextures = options.extraTextures ?? [];\n validateExtraTextureNames(\"createSprite2DCustomShader\", extraTextures);\n const moduleCache = makeShaderModuleCache();\n return {\n _entityType: \"sprite-2d-custom-shader\",\n _extraTextures: extraTextures,\n _key: nextCustomShaderKey(\"s\"),\n _composeWgsl: (hasDepth, spriteGroupIndex, uvScroll) => makeCustomSpriteWgsl(hasDepth, spriteGroupIndex, extraTextures, fragment, uvScroll),\n // `spriteGroupIndex = hasDepth ? 1 : 0`: depth-hosted layers put the scene UBO at group 0, so the\n // sprite resources (atlas, extras, fx) shift to group 1; pure-2D layers keep them at group 0.\n _getShaderModule: (engine, hasDepth, uvScroll) =>\n moduleCache(engine, `${hasDepth ? \"1\" : \"0\"}:${uvScroll ? \"u\" : \"-\"}`, () => makeCustomSpriteWgsl(hasDepth, hasDepth ? 1 : 0, extraTextures, fragment, uvScroll)),\n _layoutEntries: (startBinding) => makeCustomShaderLayoutEntries(extraTextures, startBinding),\n _createLayerFx: (engine, label) => createSpriteLayerFx(engine, label, extraTextures),\n };\n}\n"],"names":[],"mappings":"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"}
|