@driftengine/core 4.9.0 → 4.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (231) hide show
  1. package/CHANGELOG.json +133 -11
  2. package/README.md +43 -38
  3. package/dist/index.d.ts +7 -4
  4. package/dist/index.js +5 -4
  5. package/dist/render/ambientHarmonics.d.ts +1 -1
  6. package/dist/render/ambientHarmonics.js +1 -1
  7. package/dist/render/areaLights.d.ts +4 -4
  8. package/dist/render/areaLights.js +1 -1
  9. package/dist/render/backend/api.d.ts +10 -3
  10. package/dist/render/backend/select.js +11 -5
  11. package/dist/render/backend/webgl2/boneAnimations.d.ts +29 -0
  12. package/dist/render/backend/webgl2/boneAnimations.js +80 -0
  13. package/dist/render/backend/webgl2/instanced.d.ts +5 -1
  14. package/dist/render/backend/webgl2/instanced.js +13 -1
  15. package/dist/render/backend/webgl2/renderer.d.ts +189 -4
  16. package/dist/render/backend/webgl2/renderer.js +614 -66
  17. package/dist/render/backend/webgl2/skinPaletteTexture.d.ts +5 -3
  18. package/dist/render/backend/webgl2/skinPaletteTexture.js +6 -4
  19. package/dist/render/backend/webgpu/boneAnimations.d.ts +34 -0
  20. package/dist/render/backend/webgpu/boneAnimations.js +76 -0
  21. package/dist/render/backend/webgpu/buffers.d.ts +24 -4
  22. package/dist/render/backend/webgpu/buffers.js +116 -59
  23. package/dist/render/backend/webgpu/clothKernels.d.ts +8 -0
  24. package/dist/render/backend/webgpu/clothKernels.js +76 -0
  25. package/dist/render/backend/webgpu/clothOps.d.ts +39 -0
  26. package/dist/render/backend/webgpu/clothOps.js +78 -0
  27. package/dist/render/backend/webgpu/clothSetLayout.d.ts +25 -0
  28. package/dist/render/backend/webgpu/clothSetLayout.js +84 -0
  29. package/dist/render/backend/webgpu/clothSetPack.d.ts +33 -0
  30. package/dist/render/backend/webgpu/clothSetPack.js +144 -0
  31. package/dist/render/backend/webgpu/compressedUpload.d.ts +0 -1
  32. package/dist/render/backend/webgpu/compressedUpload.js +53 -8
  33. package/dist/render/backend/webgpu/depthPass.d.ts +59 -3
  34. package/dist/render/backend/webgpu/depthPass.js +49 -14
  35. package/dist/render/backend/webgpu/drawCommand.d.ts +20 -4
  36. package/dist/render/backend/webgpu/drawCommand.js +11 -3
  37. package/dist/render/backend/webgpu/flatPass.d.ts +85 -42
  38. package/dist/render/backend/webgpu/flatPass.js +137 -20
  39. package/dist/render/backend/webgpu/gpuClothSet.d.ts +80 -0
  40. package/dist/render/backend/webgpu/gpuClothSet.js +462 -0
  41. package/dist/render/backend/webgpu/instanced.d.ts +14 -1
  42. package/dist/render/backend/webgpu/instanced.js +24 -1
  43. package/dist/render/backend/webgpu/latePass.js +1 -0
  44. package/dist/render/backend/webgpu/materialStore.d.ts +101 -0
  45. package/dist/render/backend/webgpu/materialStore.js +265 -0
  46. package/dist/render/backend/webgpu/motionPass.js +15 -6
  47. package/dist/render/backend/webgpu/pipelineCache.d.ts +28 -1
  48. package/dist/render/backend/webgpu/pipelineCache.js +46 -1
  49. package/dist/render/backend/webgpu/postPass.d.ts +1 -1
  50. package/dist/render/backend/webgpu/renderer.d.ts +200 -35
  51. package/dist/render/backend/webgpu/renderer.js +993 -168
  52. package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.d.ts +9 -2
  53. package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.js +121 -47
  54. package/dist/render/backend/webgpu/skinPaletteRing.d.ts +7 -3
  55. package/dist/render/backend/webgpu/skinPaletteRing.js +13 -7
  56. package/dist/render/backend/webgpu/skinPaletteTexture.d.ts +6 -0
  57. package/dist/render/backend/webgpu/skinPaletteTexture.js +7 -1
  58. package/dist/render/backend/webgpu/staticBundles.d.ts +125 -0
  59. package/dist/render/backend/webgpu/staticBundles.js +242 -0
  60. package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +10 -3
  61. package/dist/render/backend/webgpu/surfaceTexturePass.js +20 -2
  62. package/dist/render/backend/webgpu/uniformRing.d.ts +21 -0
  63. package/dist/render/backend/webgpu/uniformRing.js +9 -1
  64. package/dist/render/backend/webgpu/vertexFormats.d.ts +22 -0
  65. package/dist/render/backend/webgpu/vertexFormats.js +97 -0
  66. package/dist/render/bloomChain.d.ts +3 -3
  67. package/dist/render/boneAnimation.d.ts +92 -0
  68. package/dist/render/boneAnimation.js +133 -0
  69. package/dist/render/compressedSource.d.ts +33 -13
  70. package/dist/render/compressedSource.js +70 -25
  71. package/dist/render/glCompressed.js +83 -0
  72. package/dist/render/globalMediumPass.d.ts +2 -0
  73. package/dist/render/globalMediumPass.js +1 -0
  74. package/dist/render/indexWidth.d.ts +17 -0
  75. package/dist/render/indexWidth.js +23 -0
  76. package/dist/render/instances.d.ts +26 -0
  77. package/dist/render/lightBudget.d.ts +10 -0
  78. package/dist/render/lightBudget.js +10 -0
  79. package/dist/render/lightMatrix.d.ts +11 -0
  80. package/dist/render/lightMatrix.js +14 -0
  81. package/dist/render/lightmap.d.ts +1 -1
  82. package/dist/render/mesh.d.ts +14 -0
  83. package/dist/render/mesh.js +44 -14
  84. package/dist/render/pointLightSelection.d.ts +4 -4
  85. package/dist/render/renderQuality.d.ts +30 -0
  86. package/dist/render/renderQuality.js +5 -0
  87. package/dist/render/rendererHarness.d.ts +7 -0
  88. package/dist/render/rendererHarness.js +12 -0
  89. package/dist/render/screenSpaceReflection.d.ts +2 -1
  90. package/dist/render/shader.d.ts +15 -0
  91. package/dist/render/shader.js +41 -0
  92. package/dist/render/shaders/boneAnimation.d.ts +19 -0
  93. package/dist/render/shaders/boneAnimation.js +63 -0
  94. package/dist/render/shaders/depth.d.ts +7 -0
  95. package/dist/render/shaders/depth.js +35 -10
  96. package/dist/render/shaders/flat/directionalShadow.js +84 -10
  97. package/dist/render/shaders/flat/glassTint.d.ts +1 -1
  98. package/dist/render/shaders/flat/glassTint.js +3 -0
  99. package/dist/render/shaders/flat/index.d.ts +21 -0
  100. package/dist/render/shaders/flat/index.js +42 -7
  101. package/dist/render/shaders/flat/layered.d.ts +19 -0
  102. package/dist/render/shaders/flat/layered.js +115 -0
  103. package/dist/render/shaders/flat/main.d.ts +1 -1
  104. package/dist/render/shaders/flat/main.js +58 -21
  105. package/dist/render/shaders/flat/mapReads.d.ts +10 -0
  106. package/dist/render/shaders/flat/mapReads.js +38 -0
  107. package/dist/render/shaders/flat/models.js +1 -1
  108. package/dist/render/shaders/flat/movingSun.d.ts +21 -0
  109. package/dist/render/shaders/flat/movingSun.js +46 -0
  110. package/dist/render/shaders/flat/normalMap.d.ts +6 -2
  111. package/dist/render/shaders/flat/normalMap.js +14 -3
  112. package/dist/render/shaders/flat/overlay.d.ts +3 -2
  113. package/dist/render/shaders/flat/overlay.js +42 -20
  114. package/dist/render/shaders/flat/pointShadow.js +2 -2
  115. package/dist/render/shaders/flat/preamble.js +59 -27
  116. package/dist/render/shaders/flat/worldUv.d.ts +22 -0
  117. package/dist/render/shaders/flat/worldUv.js +113 -0
  118. package/dist/render/shaders/generated/depth.wgsl.d.ts +93 -0
  119. package/dist/render/shaders/generated/depth.wgsl.js +93 -0
  120. package/dist/render/shaders/generated/flat.wgsl.d.ts +3616 -3042
  121. package/dist/render/shaders/generated/flat.wgsl.js +3688 -3113
  122. package/dist/render/shaders/generated/globalMedium.wgsl.d.ts +8 -3
  123. package/dist/render/shaders/generated/globalMedium.wgsl.js +8 -3
  124. package/dist/render/shaders/generated/lightVolume.wgsl.d.ts +15 -10
  125. package/dist/render/shaders/generated/lightVolume.wgsl.js +16 -11
  126. package/dist/render/shaders/globalMedium.js +9 -1
  127. package/dist/render/shaders/lightVolume.js +9 -1
  128. package/dist/render/shaders/outputTransform.d.ts +7 -0
  129. package/dist/render/shaders/outputTransform.js +28 -14
  130. package/dist/render/shaders/tangentFrame.d.ts +1 -1
  131. package/dist/render/shaders/tangentFrame.js +16 -3
  132. package/dist/render/shaders/vertexChannel.d.ts +7 -0
  133. package/dist/render/shaders/vertexChannel.js +26 -0
  134. package/dist/render/skinnedClothRun.d.ts +28 -0
  135. package/dist/render/skinnedClothRun.js +103 -35
  136. package/dist/render/staticDraws.d.ts +47 -0
  137. package/dist/render/staticDraws.js +43 -0
  138. package/dist/render/surfaceLayers.d.ts +39 -0
  139. package/dist/render/surfaceLayers.js +26 -0
  140. package/dist/render/surfaceOverlay.d.ts +16 -3
  141. package/dist/render/surfaceOverlay.js +11 -6
  142. package/dist/render/surfaceProjection.d.ts +34 -0
  143. package/dist/render/surfaceProjection.js +30 -0
  144. package/dist/render/surfaceTexture.d.ts +68 -7
  145. package/dist/render/surfaceTexture.js +56 -17
  146. package/dist/render/vertexPacking.d.ts +48 -0
  147. package/dist/render/vertexPacking.js +68 -0
  148. package/package.json +6 -6
  149. package/src/index.ts +12 -4
  150. package/src/render/ambientHarmonics.ts +1 -1
  151. package/src/render/areaLights.ts +4 -4
  152. package/src/render/backend/api.ts +13 -3
  153. package/src/render/backend/select.ts +10 -5
  154. package/src/render/backend/webgl2/boneAnimations.ts +105 -0
  155. package/src/render/backend/webgl2/instanced.ts +20 -1
  156. package/src/render/backend/webgl2/renderer.ts +723 -79
  157. package/src/render/backend/webgl2/skinPaletteTexture.ts +6 -4
  158. package/src/render/backend/webgpu/boneAnimations.ts +97 -0
  159. package/src/render/backend/webgpu/buffers.ts +195 -70
  160. package/src/render/backend/webgpu/clothKernels.ts +86 -0
  161. package/src/render/backend/webgpu/clothOps.ts +86 -0
  162. package/src/render/backend/webgpu/clothSetLayout.ts +102 -0
  163. package/src/render/backend/webgpu/clothSetPack.ts +197 -0
  164. package/src/render/backend/webgpu/compressedUpload.ts +59 -9
  165. package/src/render/backend/webgpu/depthPass.ts +51 -10
  166. package/src/render/backend/webgpu/drawCommand.ts +25 -4
  167. package/src/render/backend/webgpu/flatPass.ts +212 -41
  168. package/src/render/backend/webgpu/gpuClothSet.ts +564 -0
  169. package/src/render/backend/webgpu/instanced.ts +32 -1
  170. package/src/render/backend/webgpu/latePass.ts +1 -0
  171. package/src/render/backend/webgpu/materialStore.ts +276 -0
  172. package/src/render/backend/webgpu/motionPass.ts +18 -7
  173. package/src/render/backend/webgpu/pipelineCache.ts +53 -7
  174. package/src/render/backend/webgpu/renderer.ts +1201 -202
  175. package/src/render/backend/webgpu/shaders/clothSolve.wgsl.ts +121 -47
  176. package/src/render/backend/webgpu/skinPaletteRing.ts +13 -7
  177. package/src/render/backend/webgpu/skinPaletteTexture.ts +7 -1
  178. package/src/render/backend/webgpu/staticBundles.ts +364 -0
  179. package/src/render/backend/webgpu/surfaceTexturePass.ts +29 -3
  180. package/src/render/backend/webgpu/uniformRing.ts +10 -1
  181. package/src/render/backend/webgpu/vertexFormats.ts +113 -0
  182. package/src/render/bloomChain.ts +3 -3
  183. package/src/render/boneAnimation.ts +223 -0
  184. package/src/render/compressedSource.ts +121 -29
  185. package/src/render/glCompressed.ts +83 -0
  186. package/src/render/globalMediumPass.ts +7 -0
  187. package/src/render/indexWidth.ts +27 -0
  188. package/src/render/instances.ts +27 -0
  189. package/src/render/lightBudget.ts +11 -0
  190. package/src/render/lightMatrix.ts +15 -0
  191. package/src/render/lightmap.ts +1 -1
  192. package/src/render/mesh.ts +72 -14
  193. package/src/render/pointLightSelection.ts +4 -4
  194. package/src/render/renderQuality.ts +38 -0
  195. package/src/render/rendererHarness.ts +15 -0
  196. package/src/render/screenSpaceReflection.ts +2 -1
  197. package/src/render/shader.ts +59 -0
  198. package/src/render/shaders/boneAnimation.ts +63 -0
  199. package/src/render/shaders/depth.ts +45 -10
  200. package/src/render/shaders/flat/directionalShadow.ts +84 -10
  201. package/src/render/shaders/flat/glassTint.ts +7 -1
  202. package/src/render/shaders/flat/index.ts +63 -7
  203. package/src/render/shaders/flat/layered.ts +115 -0
  204. package/src/render/shaders/flat/main.ts +58 -21
  205. package/src/render/shaders/flat/mapReads.ts +38 -0
  206. package/src/render/shaders/flat/models.ts +1 -1
  207. package/src/render/shaders/flat/movingSun.ts +46 -0
  208. package/src/render/shaders/flat/normalMap.ts +14 -3
  209. package/src/render/shaders/flat/overlay.ts +42 -20
  210. package/src/render/shaders/flat/pointShadow.ts +2 -2
  211. package/src/render/shaders/flat/preamble.ts +59 -27
  212. package/src/render/shaders/flat/worldUv.ts +113 -0
  213. package/src/render/shaders/generated/depth.wgsl.ts +95 -0
  214. package/src/render/shaders/generated/flat.wgsl.ts +3688 -3113
  215. package/src/render/shaders/generated/globalMedium.wgsl.ts +8 -3
  216. package/src/render/shaders/generated/lightVolume.wgsl.ts +16 -11
  217. package/src/render/shaders/globalMedium.ts +9 -1
  218. package/src/render/shaders/lightVolume.ts +9 -1
  219. package/src/render/shaders/outputTransform.ts +30 -14
  220. package/src/render/shaders/tangentFrame.ts +16 -3
  221. package/src/render/shaders/vertexChannel.ts +27 -0
  222. package/src/render/skinnedClothRun.ts +138 -38
  223. package/src/render/staticDraws.ts +95 -0
  224. package/src/render/surfaceLayers.ts +57 -0
  225. package/src/render/surfaceOverlay.ts +24 -7
  226. package/src/render/surfaceProjection.ts +57 -0
  227. package/src/render/surfaceTexture.ts +124 -27
  228. package/src/render/vertexPacking.ts +109 -0
  229. package/dist/render/backend/webgpu/gpuSkinnedCloth.d.ts +0 -74
  230. package/dist/render/backend/webgpu/gpuSkinnedCloth.js +0 -452
  231. package/src/render/backend/webgpu/gpuSkinnedCloth.ts +0 -502
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Vertex animation from bone textures: a clip that turns and places each bone frame by frame,
3
+ * which every vertex of an instanced batch's mesh follows — one bone a vertex, named by the mesh's
4
+ * second coordinates — at a moment each instance keeps its own clock for.
5
+ *
6
+ * **What a crowd is built from.** A rigged character skinned per draw costs a palette upload and a
7
+ * draw each; a crowd of them, played back from a clip a mesh, costs nothing on the processor once
8
+ * the clip is on the device, and every instance moves at the frame's rate on its own clock rather
9
+ * than in step with the others. What it gives up against skinning is the blend: a vertex follows
10
+ * exactly one bone, so a joint bends hard, which at a crowd's distance is what such clips are made
11
+ * for.
12
+ *
13
+ * **The vertex's bone is its second coordinates' u times `boneScale`**, rounded: a mesh's second
14
+ * coordinates (`MeshData.lightmapUvs`) are free on a crowd, which bakes no lightmap, and the format
15
+ * that writes one bone a sixty-fourth of u is the format this reads with `boneScale: 64`.
16
+ *
17
+ * **A frame's turn and place move the vertex as the bone does**: the turn about the mesh's origin,
18
+ * then the place added — `turn(q, p) + t` — and its normal and tangent turned with it. Between two
19
+ * frames both are blended: the places linearly, the turns normalised along the shorter arc. The
20
+ * moment is `(sceneTime × rate + phase) × framesPerSecond`, looping, where the scene's time is the
21
+ * clock `setAnimationTime` sets, once a frame before the shadows, and each instance's phase and
22
+ * rate are `MeshInstances.clocks`.
23
+ *
24
+ * Backend-neutral: both backends upload what `packBoneAnimation` packs and run
25
+ * `shaders/boneAnimation.ts`, which is `animateBoneVertex` below, line for line.
26
+ */
27
+ import type { MeshInstances } from './instances.ts';
28
+ /** A clip on the device, from `createBoneAnimation`. Opaque but for its size. */
29
+ export interface BoneAnimationHandle {
30
+ readonly bones: number;
31
+ readonly frames: number;
32
+ }
33
+ /** A clip, as a caller hands it to `createBoneAnimation`. */
34
+ export interface BoneAnimationClip {
35
+ /** Bones each frame holds. */
36
+ readonly bones: number;
37
+ /** Frames, played in order and looped. */
38
+ readonly frames: number;
39
+ readonly framesPerSecond: number;
40
+ /** Where each bone puts what it carries: three floats a bone, bone by bone, frame by frame. */
41
+ readonly places: Float32Array;
42
+ /** Each bone's turn: a unit quaternion, x y z w, laid out as `places` is. */
43
+ readonly turns: Float32Array;
44
+ /** How many bones one unit of the second coordinates' u counts: `bones` unless given. */
45
+ readonly boneScale?: number;
46
+ }
47
+ /** A clip as textures hold it: a texel a bone a frame, bones across and frames down. */
48
+ export interface BoneAnimationTexels {
49
+ readonly bones: number;
50
+ readonly frames: number;
51
+ readonly framesPerSecond: number;
52
+ readonly boneScale: number;
53
+ /** RGBA floats: the place, and 1. */
54
+ readonly places: Float32Array;
55
+ /** RGBA floats: the turn, normalised. */
56
+ readonly turns: Float32Array;
57
+ }
58
+ /**
59
+ * The most bones or frames a clip may have: WebGL2's guaranteed texture size. A clip past it is
60
+ * refused rather than cut, since a cut clip is a crowd frozen at its last row.
61
+ */
62
+ export declare const MAX_BONE_TEXTURE_SIZE = 2048;
63
+ /** A clip checked and packed into its two textures' texels. Runs once, when the clip is made. */
64
+ export declare function packBoneAnimation(clip: BoneAnimationClip): BoneAnimationTexels;
65
+ /** How wide a batch's clock texture is: one row up to this many instances, then more rows. */
66
+ export declare const CLOCK_TEXTURE_WIDTH = 1024;
67
+ /**
68
+ * Each instance's clock into `out`, two floats an instance — its phase in seconds and its rate —
69
+ * from `data.clocks`, and `(0, 1)` where it gives none: every instance at the clip's own speed,
70
+ * in step. `out` is the batch's, sized once.
71
+ */
72
+ export declare function packInstanceClocks(data: MeshInstances, out: Float32Array): void;
73
+ /**
74
+ * Which two frames a moment falls between, and how far: what the shader reads. Looped, with a
75
+ * moment before the clip's start counted back from its end. **The first frame is clamped to the
76
+ * last**, with the blend left at whatever reaches the next: a moment a hair short of the loop that
77
+ * float error rounds up to `frames` lands on the last frame blended fully into the first, which is
78
+ * the first frame — the right answer — where an index of `frames` would read past the texture.
79
+ */
80
+ export declare function boneFrame(moment: number, frames: number, out: {
81
+ first: number;
82
+ second: number;
83
+ blend: number;
84
+ }): void;
85
+ /**
86
+ * Where the clip puts a vertex at rest at `position`, following `bone`, at the scene's `time` on an
87
+ * instance clock of `phase` and `rate`, into `out`. The processor's statement of what the shader
88
+ * does, which the tests hold the arithmetic to and a device check holds the shader to.
89
+ */
90
+ export declare function animateBoneVertex(clip: BoneAnimationTexels, bone: number, position: ArrayLike<number>, time: number, phase: number, rate: number, out: Float32Array): void;
91
+ /** The bone a vertex follows, from its second coordinates' u: what the shader rounds. */
92
+ export declare function boneOf(u: number, boneScale: number): number;
@@ -0,0 +1,133 @@
1
+ /**
2
+ * The most bones or frames a clip may have: WebGL2's guaranteed texture size. A clip past it is
3
+ * refused rather than cut, since a cut clip is a crowd frozen at its last row.
4
+ */
5
+ export const MAX_BONE_TEXTURE_SIZE = 2048;
6
+ /** A clip checked and packed into its two textures' texels. Runs once, when the clip is made. */
7
+ export function packBoneAnimation(clip) {
8
+ const { bones, frames, framesPerSecond } = clip;
9
+ for (const [name, value] of [
10
+ ['bones', bones],
11
+ ['frames', frames],
12
+ ]) {
13
+ if (!Number.isInteger(value) || value < 1 || value > MAX_BONE_TEXTURE_SIZE) {
14
+ throw new Error(`createBoneAnimation: ${name} is ${value}; a whole number from 1 to ${MAX_BONE_TEXTURE_SIZE}`);
15
+ }
16
+ }
17
+ if (!(framesPerSecond > 0)) {
18
+ throw new Error(`createBoneAnimation: framesPerSecond is ${framesPerSecond}; above zero`);
19
+ }
20
+ const count = bones * frames;
21
+ if (clip.places.length !== count * 3 || clip.turns.length !== count * 4) {
22
+ throw new Error(`createBoneAnimation: ${bones} bones over ${frames} frames is ${count * 3} places and ` +
23
+ `${count * 4} turn numbers, and was given ${clip.places.length} and ${clip.turns.length}`);
24
+ }
25
+ const boneScale = clip.boneScale ?? bones;
26
+ if (!(boneScale > 0)) {
27
+ throw new Error(`createBoneAnimation: boneScale is ${boneScale}; above zero`);
28
+ }
29
+ const places = new Float32Array(count * 4);
30
+ const turns = new Float32Array(count * 4);
31
+ for (let i = 0; i < count; i++) {
32
+ places[i * 4] = clip.places[i * 3];
33
+ places[i * 4 + 1] = clip.places[i * 3 + 1];
34
+ places[i * 4 + 2] = clip.places[i * 3 + 2];
35
+ places[i * 4 + 3] = 1;
36
+ const x = clip.turns[i * 4];
37
+ const y = clip.turns[i * 4 + 1];
38
+ const z = clip.turns[i * 4 + 2];
39
+ const w = clip.turns[i * 4 + 3];
40
+ const length = Math.hypot(x, y, z, w);
41
+ /* A zero turn is no turn, rather than a NaN through every vertex that reads it. */
42
+ const scale = length > 0 ? 1 / length : 0;
43
+ turns[i * 4] = x * scale;
44
+ turns[i * 4 + 1] = y * scale;
45
+ turns[i * 4 + 2] = z * scale;
46
+ turns[i * 4 + 3] = length > 0 ? w * scale : 1;
47
+ }
48
+ return { bones, frames, framesPerSecond, boneScale, places, turns };
49
+ }
50
+ /** How wide a batch's clock texture is: one row up to this many instances, then more rows. */
51
+ export const CLOCK_TEXTURE_WIDTH = 1024;
52
+ /**
53
+ * Each instance's clock into `out`, two floats an instance — its phase in seconds and its rate —
54
+ * from `data.clocks`, and `(0, 1)` where it gives none: every instance at the clip's own speed,
55
+ * in step. `out` is the batch's, sized once.
56
+ */
57
+ export function packInstanceClocks(data, out) {
58
+ const clocks = data.clocks;
59
+ const count = Math.min(data.count, out.length / 2);
60
+ for (let i = 0; i < count; i++) {
61
+ out[i * 2] = clocks === undefined ? 0 : (clocks[i * 2] ?? 0);
62
+ out[i * 2 + 1] = clocks === undefined ? 1 : (clocks[i * 2 + 1] ?? 1);
63
+ }
64
+ }
65
+ /**
66
+ * Which two frames a moment falls between, and how far: what the shader reads. Looped, with a
67
+ * moment before the clip's start counted back from its end. **The first frame is clamped to the
68
+ * last**, with the blend left at whatever reaches the next: a moment a hair short of the loop that
69
+ * float error rounds up to `frames` lands on the last frame blended fully into the first, which is
70
+ * the first frame — the right answer — where an index of `frames` would read past the texture.
71
+ */
72
+ export function boneFrame(moment, frames, out) {
73
+ const wrapped = moment - frames * Math.floor(moment / frames);
74
+ const first = Math.min(Math.floor(wrapped), frames - 1);
75
+ out.first = first;
76
+ out.second = first + 1 === frames ? 0 : first + 1;
77
+ out.blend = Math.min(Math.max(wrapped - first, 0), 1);
78
+ }
79
+ const scratchFrame = { first: 0, second: 0, blend: 0 };
80
+ const mixed = new Float32Array(4);
81
+ /** `v` turned by the unit quaternion `q`, into `out`. */
82
+ function turn(q, at, v, out) {
83
+ const qx = q[at];
84
+ const qy = q[at + 1];
85
+ const qz = q[at + 2];
86
+ const qw = q[at + 3];
87
+ const vx = v[0];
88
+ const vy = v[1];
89
+ const vz = v[2];
90
+ /* v + 2 q.xyz × (q.xyz × v + q.w v), as the shader writes it. */
91
+ const cx = qy * vz - qz * vy + qw * vx;
92
+ const cy = qz * vx - qx * vz + qw * vy;
93
+ const cz = qx * vy - qy * vx + qw * vz;
94
+ out[0] = vx + 2 * (qy * cz - qz * cy);
95
+ out[1] = vy + 2 * (qz * cx - qx * cz);
96
+ out[2] = vz + 2 * (qx * cy - qy * cx);
97
+ }
98
+ /**
99
+ * Where the clip puts a vertex at rest at `position`, following `bone`, at the scene's `time` on an
100
+ * instance clock of `phase` and `rate`, into `out`. The processor's statement of what the shader
101
+ * does, which the tests hold the arithmetic to and a device check holds the shader to.
102
+ */
103
+ export function animateBoneVertex(clip, bone, position, time, phase, rate, out) {
104
+ boneFrame((time * rate + phase) * clip.framesPerSecond, clip.frames, scratchFrame);
105
+ const { first, second, blend } = scratchFrame;
106
+ const b = Math.min(Math.max(bone, 0), clip.bones - 1);
107
+ const a = (first * clip.bones + b) * 4;
108
+ const c = (second * clip.bones + b) * 4;
109
+ const q = clip.turns;
110
+ /* The shorter arc: a quaternion and its negation are one turn. */
111
+ const sign = q[a] * q[c] +
112
+ q[a + 1] * q[c + 1] +
113
+ q[a + 2] * q[c + 2] +
114
+ q[a + 3] * q[c + 3] <
115
+ 0
116
+ ? -1
117
+ : 1;
118
+ for (let k = 0; k < 4; k++) {
119
+ mixed[k] = q[a + k] * (1 - blend) + sign * q[c + k] * blend;
120
+ }
121
+ const length = Math.hypot(mixed[0], mixed[1], mixed[2], mixed[3]);
122
+ for (let k = 0; k < 4; k++)
123
+ mixed[k] = mixed[k] / length;
124
+ turn(mixed, 0, position, out);
125
+ const p = clip.places;
126
+ for (let k = 0; k < 3; k++) {
127
+ out[k] = out[k] + p[a + k] * (1 - blend) + p[c + k] * blend;
128
+ }
129
+ }
130
+ /** The bone a vertex follows, from its second coordinates' u: what the shader rounds. */
131
+ export function boneOf(u, boneScale) {
132
+ return Math.floor(u * boneScale + 0.5);
133
+ }
@@ -2,22 +2,39 @@
2
2
  * A block-compressed image a caller hands to `createSurfaceTexture`, and the one decision about it
3
3
  * both backends share: whether this device takes it as blocks.
4
4
  *
5
- * **Uploaded as it is, or not at all.** A BC texture costs a byte a texel on the GPU (BC7) or half
6
- * of one (BC1) against four for RGBA, and keeps the mip chain its author built rather than one a box
7
- * filter makes. A device that cannot sample the format — most phones, which have ASTC and ETC2 —
8
- * gets nothing from the blocks, and the engine does not decode them here: core ships no decoder.
9
- * The loader in `@driftengine/assets` asks `uploadsCompressed` first and decodes at load where the
10
- * answer is no; a caller handing a source this device cannot take is refused by name.
5
+ * **Uploaded as it is, or not at all.** A block format costs a byte a texel on the GPU (BC7, ETC2
6
+ * with alpha, ASTC 4x4) or half of one (BC1, ETC2 without) against four for RGBA, and keeps the mip
7
+ * chain its author built rather than one a box filter makes. **Three families, and a device has the
8
+ * ones it has**: desktops sample BC, phones ETC2 and ASTC, and few anything else. A device that
9
+ * cannot sample a format gets nothing from its blocks, and core does not convert them: the loader
10
+ * in `@driftengine/assets` asks `uploadsCompressed` first, and where the answer is no it decodes
11
+ * them — and encodes ETC2 where the device takes that — off the main thread. A caller handing a
12
+ * source this device cannot take is refused by name.
11
13
  *
12
14
  * Backend-neutral, because both backends must agree on what they accept before either touches a
13
- * device. `BlockFormat` is the same union `@driftengine/drft` names `BcFormat`; core imports nothing
15
+ * device. `@driftengine/drft`'s `BcFormat` is the BC part of `BlockFormat`; core imports nothing
14
16
  * from that package, and a format added there without a twin here fails where the loader passes one
15
17
  * to the other, at compile time.
16
18
  */
17
- /** BC1 to BC5 and BC7. BC6H and the signed forms are not among them. */
18
- export type BlockFormat = 'bc1' | 'bc2' | 'bc3' | 'bc4' | 'bc5' | 'bc7';
19
+ /**
20
+ * BC1 to BC5 and BC7; ETC2's three and EAC's two; and ASTC at every block size the LDR profile has.
21
+ * BC6H, ASTC HDR and the signed forms are not among them.
22
+ */
23
+ export type BlockFormat = 'bc1' | 'bc2' | 'bc3' | 'bc4' | 'bc5' | 'bc7' | 'etc2-rgb8' | 'etc2-rgb8a1' | 'etc2-rgba8' | 'eac-r11' | 'eac-rg11' | 'astc-4x4' | 'astc-5x4' | 'astc-5x5' | 'astc-6x5' | 'astc-6x6' | 'astc-8x5' | 'astc-8x6' | 'astc-8x8' | 'astc-10x5' | 'astc-10x6' | 'astc-10x8' | 'astc-10x10' | 'astc-12x10' | 'astc-12x12';
24
+ /** The formats with no sRGB form: one or two channels of data, never colour. */
25
+ type LinearOnly = 'bc4' | 'bc5' | 'eac-r11' | 'eac-rg11';
19
26
  /** A format as a device offers it: the colour space is part of the name, as WebGPU's are. */
20
- export type CompressedTextureFormat = 'bc1' | 'bc1-srgb' | 'bc2' | 'bc2-srgb' | 'bc3' | 'bc3-srgb' | 'bc4' | 'bc5' | 'bc7' | 'bc7-srgb';
27
+ export type CompressedTextureFormat = BlockFormat | `${Exclude<BlockFormat, LinearOnly>}-srgb`;
28
+ /** A format's block: how many texels across and down, and how many bytes it takes. */
29
+ export interface BlockShape {
30
+ readonly width: number;
31
+ readonly height: number;
32
+ readonly bytes: number;
33
+ }
34
+ /** Every format's block. ASTC's are sixteen bytes whatever their size, which is its whole idea. */
35
+ export declare const BLOCK_SHAPES: Readonly<Record<BlockFormat, BlockShape>>;
36
+ /** Every format's family, which is what a device offers or does not. */
37
+ export declare function blockFamily(format: BlockFormat): 'bc' | 'etc2' | 'astc';
21
38
  /** Block-compressed texels with their stored mip chain, level 0 first. */
22
39
  export interface CompressedTextureSource {
23
40
  readonly format: BlockFormat;
@@ -32,7 +49,7 @@ export type SurfaceSource = TexImageSource | CompressedTextureSource;
32
49
  * one GPU texture has one format.
33
50
  */
34
51
  export declare function compressedLayers(sources: readonly SurfaceSource[]): readonly CompressedTextureSource[] | null;
35
- /** Bytes per 4x4 block. */
52
+ /** Bytes per block. See `BLOCK_SHAPES` for the block's size, which is not always 4x4. */
36
53
  export declare const BLOCK_BYTES: Readonly<Record<BlockFormat, number>>;
37
54
  /** Whether `source` is a compressed source rather than an image. */
38
55
  export declare function isCompressedSource(source: unknown): source is CompressedTextureSource;
@@ -40,11 +57,13 @@ export declare function isCompressedSource(source: unknown): source is Compresse
40
57
  export declare function compressedFormatName(format: BlockFormat, srgb: boolean): CompressedTextureFormat | null;
41
58
  /**
42
59
  * Whether a `width` by `height` source in `format` uploads as blocks on a device offering
43
- * `available`. Level 0 must be whole blocks each way: WebGPU refuses any other size for a BC
44
- * texture, and WebGL2's S3TC extension says the same, so a 6x6 image is decoded rather than
60
+ * `available`. Level 0 must be whole blocks each way: WebGPU refuses any other size for a block
61
+ * texture, and WebGL2's extensions say the same, so a 6x6 image in 4x4 blocks is decoded rather than
45
62
  * refused. What would make that wrong is a device that pads; none is relied on here.
46
63
  */
47
64
  export declare function uploadsCompressed(format: BlockFormat, srgb: boolean, width: number, height: number, available: readonly CompressedTextureFormat[]): boolean;
65
+ /** The bytes level `level` of a `width` by `height` image takes: whole blocks, one at least. */
66
+ export declare function levelBytes(format: BlockFormat, width: number, height: number, level: number): number;
48
67
  /**
49
68
  * How many levels the layers carry, after checking each is the size its place requires and every
50
69
  * layer matches the first. Refuses by name: a short level uploaded anyway is a device error at
@@ -64,3 +83,4 @@ export declare function planBlocks(blocks: readonly CompressedTextureSource[], s
64
83
  * quietly reallocated, so the handle a draw holds keeps meaning what it meant.
65
84
  */
66
85
  export declare function refuseBlockUpdate(format: BlockFormat | null): void;
86
+ export {};
@@ -2,18 +2,61 @@
2
2
  * A block-compressed image a caller hands to `createSurfaceTexture`, and the one decision about it
3
3
  * both backends share: whether this device takes it as blocks.
4
4
  *
5
- * **Uploaded as it is, or not at all.** A BC texture costs a byte a texel on the GPU (BC7) or half
6
- * of one (BC1) against four for RGBA, and keeps the mip chain its author built rather than one a box
7
- * filter makes. A device that cannot sample the format — most phones, which have ASTC and ETC2 —
8
- * gets nothing from the blocks, and the engine does not decode them here: core ships no decoder.
9
- * The loader in `@driftengine/assets` asks `uploadsCompressed` first and decodes at load where the
10
- * answer is no; a caller handing a source this device cannot take is refused by name.
5
+ * **Uploaded as it is, or not at all.** A block format costs a byte a texel on the GPU (BC7, ETC2
6
+ * with alpha, ASTC 4x4) or half of one (BC1, ETC2 without) against four for RGBA, and keeps the mip
7
+ * chain its author built rather than one a box filter makes. **Three families, and a device has the
8
+ * ones it has**: desktops sample BC, phones ETC2 and ASTC, and few anything else. A device that
9
+ * cannot sample a format gets nothing from its blocks, and core does not convert them: the loader
10
+ * in `@driftengine/assets` asks `uploadsCompressed` first, and where the answer is no it decodes
11
+ * them — and encodes ETC2 where the device takes that — off the main thread. A caller handing a
12
+ * source this device cannot take is refused by name.
11
13
  *
12
14
  * Backend-neutral, because both backends must agree on what they accept before either touches a
13
- * device. `BlockFormat` is the same union `@driftengine/drft` names `BcFormat`; core imports nothing
15
+ * device. `@driftengine/drft`'s `BcFormat` is the BC part of `BlockFormat`; core imports nothing
14
16
  * from that package, and a format added there without a twin here fails where the loader passes one
15
17
  * to the other, at compile time.
16
18
  */
19
+ const shape = (width, height, bytes) => ({
20
+ width,
21
+ height,
22
+ bytes,
23
+ });
24
+ /** Every format's block. ASTC's are sixteen bytes whatever their size, which is its whole idea. */
25
+ export const BLOCK_SHAPES = {
26
+ bc1: shape(4, 4, 8),
27
+ bc2: shape(4, 4, 16),
28
+ bc3: shape(4, 4, 16),
29
+ bc4: shape(4, 4, 8),
30
+ bc5: shape(4, 4, 16),
31
+ bc7: shape(4, 4, 16),
32
+ 'etc2-rgb8': shape(4, 4, 8),
33
+ 'etc2-rgb8a1': shape(4, 4, 8),
34
+ 'etc2-rgba8': shape(4, 4, 16),
35
+ 'eac-r11': shape(4, 4, 8),
36
+ 'eac-rg11': shape(4, 4, 16),
37
+ 'astc-4x4': shape(4, 4, 16),
38
+ 'astc-5x4': shape(5, 4, 16),
39
+ 'astc-5x5': shape(5, 5, 16),
40
+ 'astc-6x5': shape(6, 5, 16),
41
+ 'astc-6x6': shape(6, 6, 16),
42
+ 'astc-8x5': shape(8, 5, 16),
43
+ 'astc-8x6': shape(8, 6, 16),
44
+ 'astc-8x8': shape(8, 8, 16),
45
+ 'astc-10x5': shape(10, 5, 16),
46
+ 'astc-10x6': shape(10, 6, 16),
47
+ 'astc-10x8': shape(10, 8, 16),
48
+ 'astc-10x10': shape(10, 10, 16),
49
+ 'astc-12x10': shape(12, 10, 16),
50
+ 'astc-12x12': shape(12, 12, 16),
51
+ };
52
+ /** Every format's family, which is what a device offers or does not. */
53
+ export function blockFamily(format) {
54
+ if (format.startsWith('bc'))
55
+ return 'bc';
56
+ if (format.startsWith('astc'))
57
+ return 'astc';
58
+ return 'etc2';
59
+ }
17
60
  /**
18
61
  * The layers as blocks, or null where every one is an image. An array mixing the two is refused:
19
62
  * one GPU texture has one format.
@@ -31,15 +74,8 @@ export function compressedLayers(sources) {
31
74
  }
32
75
  return sources;
33
76
  }
34
- /** Bytes per 4x4 block. */
35
- export const BLOCK_BYTES = {
36
- bc1: 8,
37
- bc2: 16,
38
- bc3: 16,
39
- bc4: 8,
40
- bc5: 16,
41
- bc7: 16,
42
- };
77
+ /** Bytes per block. See `BLOCK_SHAPES` for the block's size, which is not always 4x4. */
78
+ export const BLOCK_BYTES = Object.fromEntries(Object.entries(BLOCK_SHAPES).map(([format, block]) => [format, block.bytes]));
43
79
  /** Whether `source` is a compressed source rather than an image. */
44
80
  export function isCompressedSource(source) {
45
81
  const candidate = source;
@@ -52,23 +88,30 @@ export function isCompressedSource(source) {
52
88
  export function compressedFormatName(format, srgb) {
53
89
  if (!srgb)
54
90
  return format;
55
- return format === 'bc4' || format === 'bc5' ? null : `${format}-srgb`;
91
+ return format === 'bc4' || format === 'bc5' || format === 'eac-r11' || format === 'eac-rg11'
92
+ ? null
93
+ : `${format}-srgb`;
56
94
  }
57
95
  /**
58
96
  * Whether a `width` by `height` source in `format` uploads as blocks on a device offering
59
- * `available`. Level 0 must be whole blocks each way: WebGPU refuses any other size for a BC
60
- * texture, and WebGL2's S3TC extension says the same, so a 6x6 image is decoded rather than
97
+ * `available`. Level 0 must be whole blocks each way: WebGPU refuses any other size for a block
98
+ * texture, and WebGL2's extensions say the same, so a 6x6 image in 4x4 blocks is decoded rather than
61
99
  * refused. What would make that wrong is a device that pads; none is relied on here.
62
100
  */
63
101
  export function uploadsCompressed(format, srgb, width, height, available) {
64
102
  const name = compressedFormatName(format, srgb);
65
- return name !== null && width % 4 === 0 && height % 4 === 0 && available.includes(name);
103
+ const block = BLOCK_SHAPES[format];
104
+ return (name !== null &&
105
+ width % block.width === 0 &&
106
+ height % block.height === 0 &&
107
+ available.includes(name));
66
108
  }
67
109
  /** The bytes level `level` of a `width` by `height` image takes: whole blocks, one at least. */
68
- function levelBytes(format, width, height, level) {
110
+ export function levelBytes(format, width, height, level) {
69
111
  const w = Math.max(1, width >> level);
70
112
  const h = Math.max(1, height >> level);
71
- return Math.ceil(w / 4) * Math.ceil(h / 4) * BLOCK_BYTES[format];
113
+ const block = BLOCK_SHAPES[format];
114
+ return Math.ceil(w / block.width) * Math.ceil(h / block.height) * block.bytes;
72
115
  }
73
116
  /**
74
117
  * How many levels the layers carry, after checking each is the size its place requires and every
@@ -111,10 +154,12 @@ export function compressedLevels(layers) {
111
154
  /** What a refusal names: the format, its colour space, and what the device offers instead. */
112
155
  export function compressedRefusal(format, srgb, width, height, available) {
113
156
  const asked = `${format.toUpperCase()}${srgb ? ' sRGB' : ''} at ${width}x${height}`;
114
- const offered = available.length === 0 ? 'no BC format' : available.join(', ');
157
+ const offered = available.length === 0 ? 'no block format' : available.join(', ');
158
+ const block = BLOCK_SHAPES[format];
115
159
  return (`createSurfaceTexture: this device cannot take ${asked} as blocks — it offers ${offered}, and ` +
116
- 'a BC texture must be whole 4x4 blocks at level 0. Decode it first (decodeBc in ' +
117
- '@driftengine/assets), or ask renderer.compressedFormats before choosing.');
160
+ `the texture must be whole ${block.width}x${block.height} blocks at level 0. Decode it first ` +
161
+ '(decodeBc, or encodeEtc2 for a device that takes ETC2, in @driftengine/assets), or ask ' +
162
+ 'renderer.compressedFormats before choosing.');
118
163
  }
119
164
  /** The format name and level count blocks upload as here, or a refusal naming why they cannot. */
120
165
  export function planBlocks(blocks, srgb, compressed) {
@@ -10,12 +10,95 @@ const INTERNAL = {
10
10
  bc5: 0x8dbd, // COMPRESSED_RED_GREEN_RGTC2_EXT
11
11
  bc7: 0x8e8c, // COMPRESSED_RGBA_BPTC_UNORM_EXT
12
12
  'bc7-srgb': 0x8e8d, // COMPRESSED_SRGB_ALPHA_BPTC_UNORM_EXT
13
+ 'eac-r11': 0x9270, // COMPRESSED_R11_EAC
14
+ 'eac-rg11': 0x9272, // COMPRESSED_RG11_EAC
15
+ 'etc2-rgb8': 0x9274, // COMPRESSED_RGB8_ETC2
16
+ 'etc2-rgb8-srgb': 0x9275, // COMPRESSED_SRGB8_ETC2
17
+ 'etc2-rgb8a1': 0x9276, // COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2
18
+ 'etc2-rgb8a1-srgb': 0x9277, // COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2
19
+ 'etc2-rgba8': 0x9278, // COMPRESSED_RGBA8_ETC2_EAC
20
+ 'etc2-rgba8-srgb': 0x9279, // COMPRESSED_SRGB8_ALPHA8_ETC2_EAC
21
+ 'astc-4x4': 0x93b0, // COMPRESSED_RGBA_ASTC_4x4_KHR
22
+ 'astc-4x4-srgb': 0x93d0, // COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR
23
+ 'astc-5x4': 0x93b1, // COMPRESSED_RGBA_ASTC_5x4_KHR
24
+ 'astc-5x4-srgb': 0x93d1, // COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR
25
+ 'astc-5x5': 0x93b2, // COMPRESSED_RGBA_ASTC_5x5_KHR
26
+ 'astc-5x5-srgb': 0x93d2, // COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR
27
+ 'astc-6x5': 0x93b3, // COMPRESSED_RGBA_ASTC_6x5_KHR
28
+ 'astc-6x5-srgb': 0x93d3, // COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR
29
+ 'astc-6x6': 0x93b4, // COMPRESSED_RGBA_ASTC_6x6_KHR
30
+ 'astc-6x6-srgb': 0x93d4, // COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR
31
+ 'astc-8x5': 0x93b5, // COMPRESSED_RGBA_ASTC_8x5_KHR
32
+ 'astc-8x5-srgb': 0x93d5, // COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR
33
+ 'astc-8x6': 0x93b6, // COMPRESSED_RGBA_ASTC_8x6_KHR
34
+ 'astc-8x6-srgb': 0x93d6, // COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR
35
+ 'astc-8x8': 0x93b7, // COMPRESSED_RGBA_ASTC_8x8_KHR
36
+ 'astc-8x8-srgb': 0x93d7, // COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR
37
+ 'astc-10x5': 0x93b8, // COMPRESSED_RGBA_ASTC_10x5_KHR
38
+ 'astc-10x5-srgb': 0x93d8, // COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR
39
+ 'astc-10x6': 0x93b9, // COMPRESSED_RGBA_ASTC_10x6_KHR
40
+ 'astc-10x6-srgb': 0x93d9, // COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR
41
+ 'astc-10x8': 0x93ba, // COMPRESSED_RGBA_ASTC_10x8_KHR
42
+ 'astc-10x8-srgb': 0x93da, // COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR
43
+ 'astc-10x10': 0x93bb, // COMPRESSED_RGBA_ASTC_10x10_KHR
44
+ 'astc-10x10-srgb': 0x93db, // COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR
45
+ 'astc-12x10': 0x93bc, // COMPRESSED_RGBA_ASTC_12x10_KHR
46
+ 'astc-12x10-srgb': 0x93dc, // COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR
47
+ 'astc-12x12': 0x93bd, // COMPRESSED_RGBA_ASTC_12x12_KHR
48
+ 'astc-12x12-srgb': 0x93dd, // COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR
13
49
  };
14
50
  const BY_EXTENSION = [
15
51
  ['WEBGL_compressed_texture_s3tc', ['bc1', 'bc2', 'bc3']],
16
52
  ['WEBGL_compressed_texture_s3tc_srgb', ['bc1-srgb', 'bc2-srgb', 'bc3-srgb']],
17
53
  ['EXT_texture_compression_rgtc', ['bc4', 'bc5']],
18
54
  ['EXT_texture_compression_bptc', ['bc7', 'bc7-srgb']],
55
+ /* The two families a phone has: every GLES 3 device takes ETC2, and most take ASTC beside it. */
56
+ [
57
+ 'WEBGL_compressed_texture_etc',
58
+ [
59
+ 'etc2-rgb8',
60
+ 'etc2-rgb8-srgb',
61
+ 'etc2-rgb8a1',
62
+ 'etc2-rgb8a1-srgb',
63
+ 'etc2-rgba8',
64
+ 'etc2-rgba8-srgb',
65
+ 'eac-r11',
66
+ 'eac-rg11',
67
+ ],
68
+ ],
69
+ [
70
+ 'WEBGL_compressed_texture_astc',
71
+ [
72
+ 'astc-4x4',
73
+ 'astc-4x4-srgb',
74
+ 'astc-5x4',
75
+ 'astc-5x4-srgb',
76
+ 'astc-5x5',
77
+ 'astc-5x5-srgb',
78
+ 'astc-6x5',
79
+ 'astc-6x5-srgb',
80
+ 'astc-6x6',
81
+ 'astc-6x6-srgb',
82
+ 'astc-8x5',
83
+ 'astc-8x5-srgb',
84
+ 'astc-8x6',
85
+ 'astc-8x6-srgb',
86
+ 'astc-8x8',
87
+ 'astc-8x8-srgb',
88
+ 'astc-10x5',
89
+ 'astc-10x5-srgb',
90
+ 'astc-10x6',
91
+ 'astc-10x6-srgb',
92
+ 'astc-10x8',
93
+ 'astc-10x8-srgb',
94
+ 'astc-10x10',
95
+ 'astc-10x10-srgb',
96
+ 'astc-12x10',
97
+ 'astc-12x10-srgb',
98
+ 'astc-12x12',
99
+ 'astc-12x12-srgb',
100
+ ],
101
+ ],
19
102
  ];
20
103
  /**
21
104
  * The BC formats this context samples, enabling each extension that offers some. Enabling is what
@@ -11,6 +11,8 @@ export interface MediumLight {
11
11
  readonly sunShadow: number;
12
12
  /** World → light clip, **uncorrected**: the shader does its own `* 0.5 + 0.5`. */
13
13
  readonly lightViewProj: ReadonlyMat4;
14
+ /** The same for the sun's moving layer, whose shadow pass may be drawn with its own. */
15
+ readonly movingLightViewProj: ReadonlyMat4;
14
16
  /** The sun's static, moving and peeled maps, as one array. See `shadowMap.ts`. */
15
17
  readonly sunShadows: WebGLTexture;
16
18
  readonly peeledEnabled: boolean;
@@ -173,6 +173,7 @@ export class GlobalMediumPass {
173
173
  gl.uniform1i(u['uSteps'] ?? null, steps);
174
174
  gl.uniform1f(u['uSunShadow'] ?? null, light.sunShadow);
175
175
  gl.uniformMatrix4fv(u['uLightViewProj'] ?? null, false, light.lightViewProj);
176
+ gl.uniformMatrix4fv(u['uMovingLightViewProj'] ?? null, false, light.movingLightViewProj);
176
177
  gl.uniform1i(u['uPeeledShadowEnabled'] ?? null, light.peeledEnabled ? 1 : 0);
177
178
  gl.drawArrays(gl.TRIANGLES, 0, 3);
178
179
  gl.bindVertexArray(null);
@@ -0,0 +1,17 @@
1
+ /**
2
+ * A mesh's indices in the narrowest type that holds them: sixteen bits where every vertex can be
3
+ * named in sixteen, thirty-two otherwise — half the index memory for every mesh of 65,536 vertices
4
+ * or fewer, which is most of them. Lossless because `validateMeshData` has already refused an
5
+ * index past the last vertex.
6
+ *
7
+ * **Padded to an even count**, so the sixteen-bit copy is a whole number of four-byte words: WebGPU
8
+ * writes a buffer only in whole words. The extra index is never drawn, since every draw counts the
9
+ * mesh's own indices. What would make this wrong is a draw that counts the buffer instead.
10
+ *
11
+ * **What it costs** is a copy of the indices while the mesh is built, let go once they are on the
12
+ * device; and every draw of the mesh has to say which width it holds, which is why the width
13
+ * travels with the mesh rather than being worked out where it is drawn.
14
+ */
15
+ /** The most vertices sixteen-bit indices can name: every index below 65,536. */
16
+ export declare const MAX_UINT16_VERTICES = 65536;
17
+ export declare function compactIndices(indices: Uint32Array, vertexCount: number): Uint16Array | Uint32Array;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * A mesh's indices in the narrowest type that holds them: sixteen bits where every vertex can be
3
+ * named in sixteen, thirty-two otherwise — half the index memory for every mesh of 65,536 vertices
4
+ * or fewer, which is most of them. Lossless because `validateMeshData` has already refused an
5
+ * index past the last vertex.
6
+ *
7
+ * **Padded to an even count**, so the sixteen-bit copy is a whole number of four-byte words: WebGPU
8
+ * writes a buffer only in whole words. The extra index is never drawn, since every draw counts the
9
+ * mesh's own indices. What would make this wrong is a draw that counts the buffer instead.
10
+ *
11
+ * **What it costs** is a copy of the indices while the mesh is built, let go once they are on the
12
+ * device; and every draw of the mesh has to say which width it holds, which is why the width
13
+ * travels with the mesh rather than being worked out where it is drawn.
14
+ */
15
+ /** The most vertices sixteen-bit indices can name: every index below 65,536. */
16
+ export const MAX_UINT16_VERTICES = 65536;
17
+ export function compactIndices(indices, vertexCount) {
18
+ if (vertexCount > MAX_UINT16_VERTICES)
19
+ return indices;
20
+ const out = new Uint16Array(indices.length + (indices.length % 2));
21
+ out.set(indices);
22
+ return out;
23
+ }