@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
package/CHANGELOG.json CHANGED
@@ -1,4 +1,126 @@
1
1
  [
2
+ {
3
+ "version": "4.11.0",
4
+ "date": "2026-10-10",
5
+ "entries": [
6
+ {
7
+ "kind": "added",
8
+ "short": "`SurfaceRim.mode: 'blend'` pulls a surface's colour toward the rim colour at its edges before it is lit, where a rim used to add glow over it.",
9
+ "text": "A surface overlay's rim takes `mode: 'blend'`: the base colour is mixed toward the rim colour by the edge's weight, raised to its contrast and scaled by its alpha, before lighting, so the edge is lit, shadowed and fogged as the rest of the surface is rather than glowing over it, and a colour channel weighted above 1 is held at 1. `'add'`, the default, is the rim as it was. On `demo/dev/surfaceOverlay.html?rim=blend` an edge out of the light moved by −1.9 in luminance where adding moved it by +41, and the two backends draw the same picture."
10
+ },
11
+ {
12
+ "kind": "added",
13
+ "short": "`setDitherOpacity(true)` spends each opaque draw's own opacity, an instance's alpha included, on a screen door, so an instance fades without blending or a pop.",
14
+ "text": "With `setDitherOpacity(true)` the draws that follow keep a pixel only where the opacity it carries (a mesh's own, or `MeshInstances.alphas` for an instance of a batch) lies above an 8x8 ordered threshold, the pattern `setDitherFade` uses, so an opaque instance fades in or out over a few frames instead of popping, still writes depth and needs no sorting; two instances crossfading cover each pixel once. On `demo/dev/hlodFade.html?opacity=` the share of pixels kept matches the opacity to 1/64 on both backends. What it gives up: a screen door shows as a pattern up close, which the temporal resolve smooths where it runs."
15
+ },
16
+ {
17
+ "kind": "added",
18
+ "short": "`createSkinnedClothSet` solves every garment together, one dispatch per batch of constraints across all of them, and `maxSteps` caps the steps one frame runs.",
19
+ "text": "`createSkinnedClothSet(renderer, setups)` solves several garments together: on WebGPU one pass a step, each constraint batch of every garment merged by colour into one dispatch, where each garment had its own encoder, pass and submit. Garments are named by their index, in `setPose(g, …)`, `setWind(g, …)`, `reset(g)` and `particles[g]` for `setCloth`, and `step(dt)` runs them all; `createSkinnedCloth` is a set of one, and a backend without compute runs the set on the CPU. Garments in a set share `substeps`, `iterations` and `maxSteps`, and are refused by the first they disagree on. `SkinnedClothParameters.maxSteps` caps the whole steps one `advance` runs and drops the time past it, so a slow frame no longer hands the next one more steps; unset, it is unbounded as before. On `demo/dev/skinnedCloth.html?bench=1&garments=15`, fifteen garments step in 0.793 ms as one set against 8.903 ms as fifteen solvers, and a set agrees with separate solvers within the spread of a nudged control."
20
+ },
21
+ {
22
+ "kind": "added",
23
+ "short": "`RenderQuality.pipelineCompile: 'skip'` leaves a draw out while its new pipeline compiles off the main thread, rather than holding the frame for the compile.",
24
+ "text": "A lit pipeline first needed inside a frame, for a material's surface model or a lit switch met mid-scene, was compiled where it was asked for, which holds the GPU process for the compile, reported at 130 to 400 ms on a phone. With `pipelineCompile: 'skip'` the compile starts off the main thread and the draw is left out until it lands; `'wait'`, the default, is as before. `ready()` waits for every compile started either way. On WebGL2 a model program compiles through `KHR_parallel_shader_compile` where the context offers it and waits where it does not. On `demo/dev/pipelineCompile.html`, eight new variants at once held a WebGPU frame 700 to 3,700 ms waiting and 17 ms skipping, the draws landing 21 to 26 frames later; through ANGLE on OpenGL the longest WebGL2 frame went from 820–1,120 ms to 630–700 ms, the driver finishing its own compile at the program's first draw. What it gives up is the draw, for the frames its compile takes."
25
+ },
26
+ {
27
+ "kind": "added",
28
+ "short": "`SurfaceMaterial.projection` places a material's maps by the world, planar across the ground or triplanar on three planes, at so many repeats a metre.",
29
+ "text": "`projection: { kind: 'planar' | 'triplanar', scale, sharpness? }` reads a material's maps at a point's place in the world rather than at the mesh's texture coordinates: `'planar'` across the two horizontal axes, `'triplanar'` on three planes blended by how squarely the surface faces each, with the normal map turned into the world by the whiteout blend, so no tangent frame is needed. A lit switch, compiled in the first time a material asks. On `demo/dev/worldUv.html` a triplanar cube matches the same map read through each face's own coordinates and tangents, on both backends. What it gives up: a projection belongs to the world, so a mesh that moves slides under it; triplanar reads each map three times; a texture is mirrored on a plane's far side; and a cutout material's shadow is still cut by the mesh's coordinates."
30
+ },
31
+ {
32
+ "kind": "added",
33
+ "short": "The sun's moving shadow layer is read through the matrix its own pass was drawn with, so what moves can have a tight, sharp square inside the static layer's wide one.",
34
+ "text": "A frame's `beginShadowPass(matrix, 'dynamic')` drawn with a matrix other than the environment's `lightViewProj` is now read through that matrix: the static layer keeps one square over the whole of what stands still, and the moving layer a tight one around what moves, at its own texel size, filter and depth span. The lit stage, the global medium and light volumes all read it, behind a lit switch that turns on the first frame a dynamic pass's matrix differs. On `demo/dev/sunLayers.html?moving=tight` a moving caster's shadow core went from 144 to 202 pixels and its penumbra from 886 to 739, and everything outside the tight square was unchanged to the pixel, on both backends. What it gives up: a receiver outside the static layer's square takes no sun shadow at all, so the tight square belongs inside the wide one."
35
+ },
36
+ {
37
+ "kind": "added",
38
+ "short": "`SurfaceMaterial.diffuseTransmission` lets light from behind a thin surface through, coloured by it: a banner, a leaf or a lampshade lit from behind.",
39
+ "text": "`diffuseTransmission`, 0 to 1, brings the light falling on a surface's far side (the sun, lamps, area lights and DriftLight) to the eye through it, coloured by the surface's own colour; a metal lets none through, and 0, the default, is every material as it was. A lamp's shadow is read from the side the lamp is on, so a thin surface does not stand in its own shadow. On `demo/dev/thinLight.html` a sheet lit only from behind went from 16 to 163 in luminance under the sun and to 104 under a lamp, with no light behind it unchanged, on both backends. What it gives up: a thin surface rather than a volume, so a thick one lets as much through as a sheet; the light behind is not blurred; and glass lets light through by its own rule."
40
+ },
41
+ {
42
+ "kind": "added",
43
+ "short": "`SurfaceMaterial.layers` blends up to five layers by a mask, each at its own repeat, colour, normal and roughness alike, out of one texture array per map.",
44
+ "text": "`layers: { mask, repeats, emissiveLayer? }` lays up to four layers over a base by an RGBA mask read at the mesh's coordinates, red laying layer 1 through alpha laying layer 4, each over every one before it. Layer `i` is layer `i` of the material's albedo, normal and ORM arrays, read at the coordinates times its own repeat, and the tangent-space normals are blended before the frame turns them into the world; `emissiveLayer` makes the emissive map glow only where that layer shows. A lit switch, compiled in the first time a material asks, and the mask is read where `modelMap` goes, so it takes no texture binding of its own. On `demo/dev/layers.html` three layers at three repeats draw the same on both backends, and a material without layers draws as before. What it gives up: every layer is read at every pixel whatever its share; a material carries a mask or a surface model's map, not both."
45
+ },
46
+ {
47
+ "kind": "changed",
48
+ "short": "A mesh's indices are sixteen bits where its vertices fit, and its normals, tangents, colours and weights sixteen-bit fixed point: 29% less mesh memory in the published scenes.",
49
+ "text": "Every attribute of a mesh travelled as 32-bit floats and every index as 32 bits. Indices are 16 bits now wherever a mesh has 65,536 vertices or fewer; normals and tangents travel as 16-bit signed fixed point, colours and skin weights inside [0, 1] as unsigned; and a colour or an emissive every vertex shares is a constant rather than a stream. Both backends decide it the same way, from the data, when a mesh is made, and positions and texture coordinates stay floats. Measured on WebGPU across the eight published scenes, mesh vertex and index buffers went from 210 to 150 MB: the voxel sandbox's vertices from 38.4 to 28.5 MB and its indices from 4.8 to 2.4, the showroom's from 113.8 to 78.2 and from 34.9 to 25.2. Eight of the nine scenes are the same to the pixel on WebGPU, and one moves 12 pixels by 2 of 255 on average, which is normals at 1/32767. What it gives up: texture coordinates stay floats because sixteen bits move a pixel on an atlas tile's edge onto the next tile, measured as 29 pixels in the voxel sandbox; a dynamic mesh's normals stay floats; and an attribute outside its range stays a float rather than being clamped."
50
+ },
51
+ {
52
+ "kind": "fixed",
53
+ "short": "A normal map on a mesh with no tangents and a mirrored mapping is no longer turned half a circle by the frame derived for it, which lit it from the far side.",
54
+ "text": "Where a mesh carries no tangents, the frame its normal map is read in is derived from screen-space derivatives, and its handedness was taken from the texture coordinates' determinant alone, which cannot see a mapping mirrored about the normal: such a surface had its bumps lit from the side opposite the light. The handedness is the surface's orientation on screen against its normal now, which is the determinant's sign wherever a mapping is not mirrored. On `demo/dev/worldUv.html` a mirrored ground read 74.8 derived against 110.8 through its tangents, and 110.8 both ways after; every published scene is unchanged to the pixel."
55
+ },
56
+ {
57
+ "kind": "fixed",
58
+ "short": "On WebGPU a lit surface reads the sun's static layer through the environment's light matrix, not through whichever matrix the last shadow pass used.",
59
+ "text": "The WebGPU lit vertex stage projected a receiver into the sun's map through the matrix of the frame's last shadow pass. Where every pass shared one matrix nothing showed; where a dynamic pass was drawn with a tighter one, the static layer was read through it and its shadows landed in the wrong place. It projects through `env.lightViewProj` now, as WebGL2 always did."
60
+ },
61
+ {
62
+ "kind": "fixed",
63
+ "short": "`ready()` no longer waits for ever on a pipeline whose compile off the main thread failed, and it waits for a lit switch's rebuild as well.",
64
+ "text": "A pipeline whose `createRenderPipelineAsync` rejected stayed among the compiles `ready()` waits on, so a boot awaiting it never resolved; it is taken out, and built where it is next asked for so the device says why. And `ready()` now waits for a lit switch's rebuild, so the first frame after it has every pipeline with the feature that turned the switch on, where a capture taken then photographed the frames before."
65
+ },
66
+ {
67
+ "kind": "fixed",
68
+ "short": "On WebGL2 a surface model's program first made in the middle of a mesh pass keeps the pass's dither fade, where its draws used to ignore it.",
69
+ "text": "A program for a surface model first made between `bindMeshPass` and the draws after it was handed the pass's state with the dither fade cleared, so the draws through it ignored `setDitherFade` until the next pass. WebGL2 only: WebGPU carries the fade in each draw's block."
70
+ }
71
+ ]
72
+ },
73
+ {
74
+ "version": "4.10.0",
75
+ "date": "2026-10-07",
76
+ "entries": [
77
+ {
78
+ "kind": "added",
79
+ "short": "`createStaticDraws` keeps a world's still draws and `drawStaticDraws` replays them: on WebGPU one render bundle a view, recorded once, where a frame encoded every draw.",
80
+ "text": "`renderer.createStaticDraws(casters)` takes the enumeration `drawSceneCasters` takes — rigid meshes and instanced batches, each with its material — once, and keeps it; `drawStaticDraws(list)` draws it into the open mesh pass under whatever camera, lights and fog `bindMeshPass` set, the frame's, a mirror's or a capture's, and `disposeStaticDraws` releases it. On WebGPU a list is recorded into a render bundle for each view and replayed with one call, and recorded again only when something it was recorded against changes; WebGL2 has no bundles and replays the entries every frame, drawing the same picture. A skinned mesh or a scatter batch is refused by name, since both change every frame, and a list draws whole in every view, so a world split into lists by region culls by list. On `demo/dev/staticDraws.html`, 412 draws cost the main thread 0.650 ms a frame drawn one by one and 0.127 ms as a list — 1.646 and 0.202 with a scene capture beside the frame — and the two pictures are the same to the pixel on both backends."
81
+ },
82
+ {
83
+ "kind": "added",
84
+ "short": "`createBoneAnimation` plays a crowd in the instanced vertex stage: each vertex follows the bone its second coordinates name, each instance on its own clock, shadows included.",
85
+ "text": "`renderer.createBoneAnimation(clip)` takes a clip as a turn and a place for each bone at each of its frames, and `InstancedOptions.animation` plays it on every instance of a batch: each vertex follows the one bone its second coordinates' u names, at the instance's own moment of the clip. `MeshInstances.clocks` holds a phase and a rate an instance, and `setAnimationTime(seconds)` the clock they share, set once a frame before the shadows. Every instance moves at the frame's rate and out of step with the next, in the colour pass and in every shadow, with nothing done on the processor once the clip is made; `disposeBoneAnimation` releases it. Both backends run the same arithmetic: on `demo/dev/boneCrowd.html` twenty-five instances played by the vertex stage and posed on the processor by that arithmetic differ by 0 pixels on WebGPU and 2 on WebGL2, shadows included. What it gives up: a vertex follows one bone; an animated batch is culled whole, never instance by instance; and under reconstruction or the temporal resolve a crowd's own movement is not in the frame's motion."
86
+ },
87
+ {
88
+ "kind": "added",
89
+ "short": "Surface textures take ETC2, EAC and ASTC blocks beside BC, uploaded as they are wherever `compressedFormats` says the device samples them, on both backends.",
90
+ "text": "`createSurfaceTexture` takes ETC2 (`etc2-rgb8`, `etc2-rgb8a1`, `etc2-rgba8`), EAC (`eac-r11`, `eac-rg11`) and every LDR ASTC block size from 4x4 to 12x12, each with its sRGB twin where it has one, beside the BC formats it already took. WebGPU asks for `texture-compression-etc2` and `texture-compression-astc` wherever the adapter offers them, WebGL2 reads `WEBGL_compressed_texture_etc` and `WEBGL_compressed_texture_astc`, and `compressedFormats` lists what the device samples — so a phone's textures go up at a byte a texel or less, where RGBA takes four. `updateSurfaceTexture` takes blocks too: they replace an image behind the handle every draw already holds, read in the colour space the texture was made in and refused where the device does not take them, while an image in place of blocks is still refused. `levelBytes` gives the bytes a level of any block format holds. On `demo/dev/phoneTextures.html` an ETC2 chain, an EAC normal map and an ASTC block draw exactly the texels they decode to, on both backends."
91
+ },
92
+ {
93
+ "kind": "added",
94
+ "short": "On a device that samples ETC2 and not BC, `DrftLoader` re-encodes BC textures as ETC2 in a worker and swaps them in; `readKtx2` takes a KTX2 file as its blocks.",
95
+ "text": "A `DrftLoader` given `bcWorker` re-encodes each BC texture as ETC2 or EAC where the device samples those and not BC, which is a phone. The texture is shown decoded first, exactly as before, and its chain then replaces it behind the handle it was drawn with, from a second worker of the same factory, one texture at a time: `etc2-rgb8`, or `etc2-rgba8` where the decoded alpha is less than opaque, `eac-r11` for BC4 and `eac-rg11` for BC5, every level averaged in light where the slot reads colour. Nothing arrives later for it, and the texture ends at half a byte a texel, or a byte with alpha, where it held four; the encode costs about 0.7 s for a 2048² image on a desktop processor, on a core of its own. Without a worker nothing is encoded. `readKtx2` reads an uncompressed KTX2 file of ASTC, ETC2, EAC or BC as the blocks it carries, refusing a supercompressed file, a cube or an array by name, and `encodeEtc2` and `encodeEtc2Chain` encode pixels a caller holds. On `demo/dev/phoneLoad.html` the loader's swapped texture and the same chain uploaded directly draw the same picture on both backends."
96
+ },
97
+ {
98
+ "kind": "added",
99
+ "short": "`SurfaceMaterial.reflectivity` and `environmentGain` state a material's own reflection for its draws, so a static list keeps each batch's reflectivity and gain.",
100
+ "text": "A `SurfaceMaterial` may state `reflectivity`, 0 to 1, and `environmentGain`, held non-negative: the numbers `setSurfaceReflectivity` and `setEnvironmentGain` set for every draw, stated by the material for its own. A draw under a material that states one wears it whatever the setters say, and a material stating neither leaves its draws to the setters, as every material did before; each falls back on its own. They are what lets a static list carry a stage whose batches each mirror their own share of a baked reflection at their own brightness, since a list keeps each entry's material and none of the renderer's state between draws. On both backends."
101
+ },
102
+ {
103
+ "kind": "changed",
104
+ "short": "WebGPU keeps a draw's material across frames: the lit block is a pass's and a material's, and a material seen before is bound again rather than uploaded again.",
105
+ "text": "The lit stage's uniforms on WebGPU are three blocks where they were one: the pass's, written once a pass; the material's, kept in a store by its contents and bound by offset, so a material a frame has drawn before costs no upload; and, for the vertex stage, the camera, light matrix and wind, so a draw's own slot no longer changes when the camera does. A frame that draws what the one before it drew uploads no material bytes, where the dev pages measured 14 to 29 KB a frame, and every published scene draws the same to the pixel on both backends. The cutout dither's frame moved into the pass's block with it, since a number that changes every frame made every cutout material new every frame. The frame's statistics still count material changes, as WebGL2's do."
106
+ },
107
+ {
108
+ "kind": "changed",
109
+ "short": "A skinned draw keeps its bind group per material, and a skin palette slot keeps its largest texture, so mixed rigs no longer make textures and bind groups every frame.",
110
+ "text": "On WebGPU a skin palette slot keeps the largest texture it has needed rather than replacing it whenever a rig of another joint count takes the slot, and a skinned or morphed draw's bind group is kept per slot and per material rather than built again; WebGL2's palette only grows as well. A frame whose skinned meshes and characters are culled differently from the frame before no longer creates and destroys textures and bind groups to draw them."
111
+ },
112
+ {
113
+ "kind": "changed",
114
+ "short": "`updateMesh` under reconstruction records the previous positions' copy into the frame's own work rather than submitting a command buffer of its own each time.",
115
+ "text": "On WebGPU, rewriting a mesh's positions while DriftTR keeps its previous positions for motion copied the old ones in a command buffer submitted on the spot, one submit a rewrite. The copy is recorded into the frame's own work now, so a mesh rewritten every frame costs its upload and nothing more, and every submit the renderer makes goes through one place."
116
+ },
117
+ {
118
+ "kind": "fixed",
119
+ "short": "Refraction bends by the surface's normal as the eye sees it, so a card facing the camera shimmers by its normal map instead of bending as one lens.",
120
+ "text": "A refracting draw bent the frame behind it by the world x and y of its normal, which on a quad facing the camera is the quad's lean against the world's axes: one offset across the whole quad, a lens, with a normal map's tilt a small change on top. It bends by the normal in view space now, the map's tilt included, so a distortion card shimmers by its map and a pane bends by how it leans toward the eye wherever the eye stands, the same on both backends."
121
+ }
122
+ ]
123
+ },
2
124
  {
3
125
  "version": "4.9.0",
4
126
  "date": "2026-10-07",
@@ -76,18 +198,18 @@
76
198
  },
77
199
  {
78
200
  "kind": "added",
79
- "short": "`PointLightSource.falloffExponent` lights by `(1 - (d/R)^2)^n` with no distance term, Unreal's falloff for a light without inverse square, in both arms and on water, DriftLight and DriftRay.",
80
- "text": "A point or spot light may name its own falloff: `(1 - (d/R)^2)^n` with `R` its radius and `n` the exponent, and no inverse square, which is how Unreal lights a light that does not use inverse-square falloff. A rig authored there keeps its shape: a fill meant to light a whole hall across a hundred metres does, where the frame's falloff had it gone a few metres out. 0, or absent, is the frame's falloff as before. The selection carries it in `PointLightBuffer.falloffExponents` and the environment in `lightFalloffExponents`; the fixed arm reads it in the third lane of the cone uniform, which a two-float array already paid a whole row for, so the eight-light build spends no uniform row on it, and the froxel table's record grows to six texels. Water, DriftLight's volume and DriftRay's bounce fall off by it too, so a light lands the same way on every surface it reaches. On `demo/dev/clustered.html` one light drawn by the fixed arm and by the table is 0 pixels apart on both backends, and an exponent of 8 moves 42,123 pixels against none."
201
+ "short": "`PointLightSource.falloffExponent` lights by `(1 - (d/R)^2)^n` with no distance term, the usual falloff for a light without inverse square, in both arms and on water, DriftLight and DriftRay.",
202
+ "text": "A point or spot light may name its own falloff: `(1 - (d/R)^2)^n` with `R` its radius and `n` the exponent, and no inverse square, which is how lighting tools commonly light a light that does not use inverse-square falloff. A rig authored that way keeps its shape: a fill meant to light a whole hall across a hundred metres does, where the frame's falloff had it gone a few metres out. 0, or absent, is the frame's falloff as before. The selection carries it in `PointLightBuffer.falloffExponents` and the environment in `lightFalloffExponents`; the fixed arm reads it in the third lane of the cone uniform, which a two-float array already paid a whole row for, so the eight-light build spends no uniform row on it, and the froxel table's record grows to six texels. Water, DriftLight's volume and DriftRay's bounce fall off by it too, so a light lands the same way on every surface it reaches. On `demo/dev/clustered.html` one light drawn by the fixed arm and by the table is 0 pixels apart on both backends, and an exponent of 8 moves 42,123 pixels against none."
81
203
  },
82
204
  {
83
205
  "kind": "added",
84
206
  "short": "`AreaLightSource.barnDoorAngle` and `barnDoorLength` hang barn doors on a rectangle: each fragment is lit by the part the doors leave in view, its highlight included, in both arms.",
85
- "text": "A rectangle may carry barn doors, a flap hinged at each edge standing `barnDoorAngle` degrees from its normal and `barnDoorLength` metres long, 20 cm when absent, as Unreal's rect lights do. At 90 they fold flat and hide nothing; smaller narrows the light to the opening they leave. Each fragment shades the part of the rectangle no door's tip hides, one axis at a time, so the form factor and the integrated highlight both come from that smaller rectangle, in the fixed arm, which reads the doors in the size uniform's two spare lanes, and in the froxel table's record. Each door is taken as endless along its hinge, so where two meet the corner they hide is square rather than mitred, and a shadowed rectangle's map is still baked from its whole face. Measured from straight above a 1.2 by 0.5 m panel 1.2 m over a floor with 20 degree, 30 cm doors: nothing changes within the 1.04 by 0.69 m box the door tips leave whole, nothing is lit past 4.94 and 2.32 m where they hide all of it, the fixed and clustered arms are 0 pixels apart, and doors at 90 degrees are 0 pixels from none, on both backends."
207
+ "text": "A rectangle may carry barn doors, a flap hinged at each edge standing `barnDoorAngle` degrees from its normal and `barnDoorLength` metres long, 20 cm when absent, the usual default for a rectangle light. At 90 they fold flat and hide nothing; smaller narrows the light to the opening they leave. Each fragment shades the part of the rectangle no door's tip hides, one axis at a time, so the form factor and the integrated highlight both come from that smaller rectangle, in the fixed arm, which reads the doors in the size uniform's two spare lanes, and in the froxel table's record. Each door is taken as endless along its hinge, so where two meet the corner they hide is square rather than mitred, and a shadowed rectangle's map is still baked from its whole face. Measured from straight above a 1.2 by 0.5 m panel 1.2 m over a floor with 20 degree, 30 cm doors: nothing changes within the 1.04 by 0.69 m box the door tips leave whole, nothing is lit past 4.94 and 2.32 m where they hide all of it, the fixed and clustered arms are 0 pixels apart, and doors at 90 degrees are 0 pixels from none, on both backends."
86
208
  },
87
209
  {
88
210
  "kind": "added",
89
211
  "short": "`SurfaceMaterial.physicalSpecular` shades a lamp's and the sun's highlight as GGX's own `pi D Vis F N.L` with the specular attribute as F0, on the standard, lightmap and anisotropic models.",
90
- "text": "The engine's highlight is a look control: it peaks at the specular attribute times one however rough the surface, so strength and width are set apart. A material with `physicalSpecular: true` takes GGX's own term instead, `pi * D * Vis * F * N.L` with Smith's height-correlated masking, as Unreal shades and as this engine's skin and eye already did. Its peak is `1 / (4 alpha^2)` of the light head-on, so a polished surface's highlight is several times the look's and a rough one's lower and wider; the specular attribute is read as the reflectance at normal incidence, Unreal's `0.08 * Specular`, and Fresnel brightens every surface toward grazing rather than a metal alone. Lamps, widened by their size as before, and the sun take it, on the standard, lightmap and anisotropic models; a rectangle's highlight was already the lobe integrated over it with that F0. The code is a lit switch, compiled into the lit programs the first time a material asks, so a scene that never does carries none of it. Measured with a lamp and an eye mirrored about a floor at roughness 0.5, where the term works out by hand to 4.4376: the physical highlight at 1/4.4377 of the light reads the look's middle pixel to within a level on both backends, from a lamp and from the sun, and the anisotropic model's physical lobe at strength 0 is the standard one to within a level everywhere."
212
+ "text": "The engine's highlight is a look control: it peaks at the specular attribute times one however rough the surface, so strength and width are set apart. A material with `physicalSpecular: true` takes GGX's own term instead, `pi * D * Vis * F * N.L` with Smith's height-correlated masking, as this engine's skin and eye already did. Its peak is `1 / (4 alpha^2)` of the light head-on, so a polished surface's highlight is several times the look's and a rough one's lower and wider; the specular attribute is read as the reflectance at normal incidence, `0.08 * Specular`, and Fresnel brightens every surface toward grazing rather than a metal alone. Lamps, widened by their size as before, and the sun take it, on the standard, lightmap and anisotropic models; a rectangle's highlight was already the lobe integrated over it with that F0. The code is a lit switch, compiled into the lit programs the first time a material asks, so a scene that never does carries none of it. Measured with a lamp and an eye mirrored about a floor at roughness 0.5, where the term works out by hand to 4.4376: the physical highlight at 1/4.4377 of the light reads the look's middle pixel to within a level on both backends, from a lamp and from the sun, and the anisotropic model's physical lobe at strength 0 is the standard one to within a level everywhere."
91
213
  },
92
214
  {
93
215
  "kind": "added",
@@ -138,7 +260,7 @@
138
260
  {
139
261
  "kind": "added",
140
262
  "short": "Baked lightmaps: `createLightmap` uploads a page of irradiance and direction, and a `lightmapModel` material reads its region of it at a mesh's `lightmapUvs`, or an instance's region in a batch.",
141
- "text": "`createLightmap(page)` uploads a baked page, linear irradiance as three floats a texel and the direction it arrives from as a first-order spherical harmonic in four bytes, as two half-float layers, and returns a texture that a material made with `lightmapModel({ region })` takes as its `modelMap`. A surface adds `albedo * (1 - metal) * irradiance * max(0, dot(d.xyz, n) + d.w)` to its diffuse, Unreal's high-quality lightmap read, with `n` the shading normal and the page read at the mesh's second coordinates, `MeshData.lightmapUvs`, scaled and offset by the region. An instanced batch carries a region an instance in `MeshInstances.lightmapRegions`, applied before the material's, so instances of one mesh sit in different parts of one page. The lightmap is a shading model, a pipeline constant of its own, so nothing that does not use one pays for it, and every published scene moves 0 pixels on WebGPU. What it takes is spent elsewhere: the coordinates ride the grain and relief attributes, stored below zero so that the same mesh drawn without its page has neither, and an instance's region rides its tint and opacity. A mesh naming lightmap coordinates beside grain or relief that is not zero is refused, and a batch and a material that disagree about regions are said once on the console. Checked on a room baked on the CPU with a box's shadow in it (`demo/dev/lightmap.html`): a page of one value adds exactly what the same ambient raised by `setAmbientSH` adds, 0 pixels apart on both backends, sixteen instanced tiles are within one level of the same tiles drawn one at a time, and the two backends are within one level of each other. The GPU-driven pipeline reads no page."
263
+ "text": "`createLightmap(page)` uploads a baked page, linear irradiance as three floats a texel and the direction it arrives from as a first-order spherical harmonic in four bytes, as two half-float layers, and returns a texture that a material made with `lightmapModel({ region })` takes as its `modelMap`. A surface adds `albedo * (1 - metal) * irradiance * max(0, dot(d.xyz, n) + d.w)` to its diffuse, a directional lightmap read, with `n` the shading normal and the page read at the mesh's second coordinates, `MeshData.lightmapUvs`, scaled and offset by the region. An instanced batch carries a region an instance in `MeshInstances.lightmapRegions`, applied before the material's, so instances of one mesh sit in different parts of one page. The lightmap is a shading model, a pipeline constant of its own, so nothing that does not use one pays for it, and every published scene moves 0 pixels on WebGPU. What it takes is spent elsewhere: the coordinates ride the grain and relief attributes, stored below zero so that the same mesh drawn without its page has neither, and an instance's region rides its tint and opacity. A mesh naming lightmap coordinates beside grain or relief that is not zero is refused, and a batch and a material that disagree about regions are said once on the console. Checked on a room baked on the CPU with a box's shadow in it (`demo/dev/lightmap.html`): a page of one value adds exactly what the same ambient raised by `setAmbientSH` adds, 0 pixels apart on both backends, sixteen instanced tiles are within one level of the same tiles drawn one at a time, and the two backends are within one level of each other. The GPU-driven pipeline reads no page."
142
264
  },
143
265
  {
144
266
  "kind": "added",
@@ -148,17 +270,17 @@
148
270
  {
149
271
  "kind": "added",
150
272
  "short": "`setAmbientSH(coefficients)` gives the draws that follow their own ambient, nine RGB spherical-harmonic coefficients in place of the frame's and the probes', and `null` gives the frame's back.",
151
- "text": "`setAmbientSH(coefficients)` takes the 27 numbers of a second-order spherical harmonic, nine coefficients of radiance in three channels, and lights the draws that follow by its cosine-convolved irradiance in place of the frame's ambient and the probe grid's, until `null` or the next `bindMeshPass` gives the frame's back. A character moving through a baked irradiance volume samples it where it stands each frame and sets it around its own draws, which is Unreal's per-object volumetric lightmap. It is material state, so on WebGPU a change takes a material slot. The lit shader had no uniform row to spare on a 256-row phone, so scalar uniforms were packed into vectors they now share, and the eight-light build fits the 256 rows it fitted before. Checked on both backends: the frame's own sky-and-ground gradient handed back as coefficients draws 0 pixels from the frame without them."
273
+ "text": "`setAmbientSH(coefficients)` takes the 27 numbers of a second-order spherical harmonic, nine coefficients of radiance in three channels, and lights the draws that follow by its cosine-convolved irradiance in place of the frame's ambient and the probe grid's, until `null` or the next `bindMeshPass` gives the frame's back. A character moving through a baked irradiance volume samples it where it stands each frame and sets it around its own draws, a per-object volumetric lightmap. It is material state, so on WebGPU a change takes a material slot. The lit shader had no uniform row to spare on a 256-row phone, so scalar uniforms were packed into vectors they now share, and the eight-light build fits the 256 rows it fitted before. Checked on both backends: the frame's own sky-and-ground gradient handed back as coefficients draws 0 pixels from the frame without them."
152
274
  },
153
275
  {
154
276
  "kind": "added",
155
277
  "short": "Lighting channels: a point or spot light shades only surfaces whose `SurfaceMaterial.lightChannels` mask shares a bit with its own, so a character's key and rim lights leave the stage alone.",
156
- "text": "Each point or spot light carries a channel mask, `Environment.lightChannels` beside `lightColors` (and `PointLightSet.lightChannels` where a consumer fills the set itself), and each material one, `SurfaceMaterial.lightChannels`, both whole masks from 1 to 255 and 1 by default, so a scene that names none is lit exactly as it was. A light shades a surface only where the two share a bit, which is Unreal's lighting channels: a fighting game lights its characters with key and rim lights that the stage never sees. The fixed loop and the froxel table both honour it on both backends, the mask riding a spare lane of a light's profile axis in the one and the light's record in the other. The sun, the sky, the probes, area lights, DriftLight and the GPU-driven pipeline light every surface whatever its mask."
278
+ "text": "Each point or spot light carries a channel mask, `Environment.lightChannels` beside `lightColors` (and `PointLightSet.lightChannels` where a consumer fills the set itself), and each material one, `SurfaceMaterial.lightChannels`, both whole masks from 1 to 255 and 1 by default, so a scene that names none is lit exactly as it was. A light shades a surface only where the two share a bit: a game lights its characters with key and rim lights that the stage never sees. The fixed loop and the froxel table both honour it on both backends, the mask riding a spare lane of a light's profile axis in the one and the light's record in the other. The sun, the sky, the probes, area lights, DriftLight and the GPU-driven pipeline light every surface whatever its mask."
157
279
  },
158
280
  {
159
281
  "kind": "added",
160
- "short": "`setBloom` takes a response: a ramp in place of subtracting the threshold, and a tint for each of the six levels, so a stage authored against Unreal's bloom blooms as it was lit.",
161
- "text": "`setBloom(scale, threshold, response)` takes a `BloomResponse`: `ramp`, the scene units over which a colour comes in from none of it at the threshold to all of it, which is Unreal's `saturate((L - threshold) / 2)` where the old subtraction stays the default; and `tints`, a colour for each of the six levels from the finest halo to a twentieth of the frame, white by default and not clamped, so a tint carries a strength. One band over the colour less a low threshold washed a stage authored with a threshold of 0.1 white. Both backends weigh each level by a blend constant as it is added back, so the response costs no pass and no texture, and a frame that names none draws what it drew: the published scenes move 0 pixels on WebGPU. `BLOOM_LEVELS` is exported."
282
+ "short": "`setBloom` takes a response: a ramp in place of subtracting the threshold, and a tint for each of the six levels, so a stage authored against a ramped bloom blooms as it was lit.",
283
+ "text": "`setBloom(scale, threshold, response)` takes a `BloomResponse`: `ramp`, the scene units over which a colour comes in from none of it at the threshold to all of it, `saturate((L - threshold) / 2)` where the old subtraction stays the default; and `tints`, a colour for each of the six levels from the finest halo to a twentieth of the frame, white by default and not clamped, so a tint carries a strength. One band over the colour less a low threshold washed a stage authored with a threshold of 0.1 white. Both backends weigh each level by a blend constant as it is added back, so the response costs no pass and no texture, and a frame that names none draws what it drew: the published scenes move 0 pixels on WebGPU. `BLOOM_LEVELS` is exported."
162
284
  },
163
285
  {
164
286
  "kind": "added",
@@ -173,7 +295,7 @@
173
295
  {
174
296
  "kind": "added",
175
297
  "short": "With `clusteredLights` on, rectangle lights past `maxAreaLights` are shaded through the froxel table, unshadowed and windowed by a `range`, so a stage's strips and panels all light it.",
176
- "text": "Rectangles past the fixed `maxAreaLights` are binned into the froxel table beside the point lights and shaded by the same function the fixed loop uses, integrated specular and all, so one rectangle drawn either way is 0 pixels apart on both backends. They take no shadow, and are windowed to zero at `AreaLightSource.range`, Unreal's attenuation radius in metres, which a rectangle that names none derives from where its light on its own axis falls to a thousandth of a scene unit. A stage with 163 rectangle lights had been drawing them as spots. Without the table, rectangles past the fixed ones are said once on the console rather than dropped quietly."
298
+ "text": "Rectangles past the fixed `maxAreaLights` are binned into the froxel table beside the point lights and shaded by the same function the fixed loop uses, integrated specular and all, so one rectangle drawn either way is 0 pixels apart on both backends. They take no shadow, and are windowed to zero at `AreaLightSource.range`, an attenuation radius in metres, which a rectangle that names none derives from where its light on its own axis falls to a thousandth of a scene unit. A stage with 163 rectangle lights had been drawing them as spots. Without the table, rectangles past the fixed ones are said once on the console rather than dropped quietly."
177
299
  },
178
300
  {
179
301
  "kind": "changed",
@@ -188,7 +310,7 @@
188
310
  {
189
311
  "kind": "fixed",
190
312
  "short": "On WebGPU a crowd of skinned characters no longer stands in its bind pose past the 128th palette of a frame: the ring holds one a draw, and a rig drawn by five passes spends one slot.",
191
- "text": "The WebGPU joint-palette ring was capped at 128 palettes a frame and declined every skinned draw past it, and a rig drawn in five passes (the main pass, a reflection and three shadow layers) uploaded the same matrices five times, so three dozen idling extras beside two fighters filled it in one frame and the consumer stood its crowd in the bind pose. The ring holds as many palettes as a frame holds draws, `RenderQuality.drawsPerFrame`, and a palette set again with the same numbers is the slot it already has, compared by value, since a caller may pose every rig into one scratch array. A slot still allocates its texture only when a frame first reaches it. WebGL2 never had the limit."
313
+ "text": "The WebGPU joint-palette ring was capped at 128 palettes a frame and declined every skinned draw past it, and a rig drawn in five passes (the main pass, a reflection and three shadow layers) uploaded the same matrices five times, so three dozen idling extras beside two characters filled it in one frame and the consumer stood its crowd in the bind pose. The ring holds as many palettes as a frame holds draws, `RenderQuality.drawsPerFrame`, and a palette set again with the same numbers is the slot it already has, compared by value, since a caller may pose every rig into one scratch array. A slot still allocates its texture only when a frame first reaches it. WebGL2 never had the limit."
192
314
  },
193
315
  {
194
316
  "kind": "fixed",
package/README.md CHANGED
@@ -1,17 +1,17 @@
1
1
  # @driftengine/core
2
2
 
3
- The runtime of DriftEngine, a WebGPU game engine for the web and for native apps. It draws through
4
- WebGPU where the browser has a device and through WebGL2 everywhere else, behind one API, and it
5
- carries the loop, the renderer, cameras, input, the scene graph, geometry, text
6
- and the whole of [`@driftengine/physics`](https://driftengine.dev/docs/simulation/rigid-bodies),
7
- which it re-exports. The rest of the engine is separate packages you add when you need them.
3
+ The runtime of [DriftEngine](https://github.com/drftrun/driftengine), a 3D game engine for the web
4
+ and for installed applications. It draws through WebGPU where the browser has a device and through
5
+ WebGL2 everywhere else, behind one API, and carries the fixed-step loop, the renderer, cameras,
6
+ input, the scene graph, geometry, text and the whole of `@driftengine/physics`, which it re-exports.
7
+ Everything else is a separate package a game adds when it reaches for it.
8
8
 
9
- **Cost: 858.8 KB gzipped, importing `createRenderer`.** Measured by `scripts/size-gate.test.mjs`,
9
+ **Cost: 898.2 KB gzipped, importing `createRenderer`.** Measured by `scripts/size-gate.test.mjs`,
10
10
  which fails the build if the figure drifts by more than 3%.
11
11
 
12
12
  The manual is at **[driftengine.dev/docs](https://driftengine.dev/docs)**, with an example running
13
- beside every chapter, and the reference for every export at
14
- [driftengine.dev/docs/api](https://driftengine.dev/docs/api).
13
+ beside every chapter, and every export is in the
14
+ [reference](https://driftengine.dev/docs/api).
15
15
 
16
16
  ## Install
17
17
 
@@ -19,7 +19,8 @@ beside every chapter, and the reference for every export at
19
19
  npm install @driftengine/core
20
20
  ```
21
21
 
22
- It needs a browser with WebGL2, which is every current one, and an ES module bundler such as Vite.
22
+ It resolves to compiled JavaScript, so any ES module bundler takes it as it is; Vite is what the
23
+ examples use. A browser needs WebGL2 at least, which every current one has.
23
24
  [Installation](https://driftengine.dev/docs/start/installation) covers the page, TypeScript and the
24
25
  backends.
25
26
 
@@ -170,42 +171,46 @@ This is the engine's `examples/starter`, and a test keeps the two the same.
170
171
 
171
172
  ## What is in it
172
173
 
173
- | | |
174
- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
175
- | The frame | A fixed-step [loop](https://driftengine.dev/docs/start/the-loop) with interpolation, [two backends](https://driftengine.dev/docs/concepts/backends) behind one API, [quality profiles](https://driftengine.dev/docs/concepts/quality), and a [frame](https://driftengine.dev/docs/concepts/the-frame) whose order you can read. |
176
- | Drawing | [Meshes](https://driftengine.dev/docs/rendering/meshes), [materials](https://driftengine.dev/docs/rendering/materials), [instancing](https://driftengine.dev/docs/rendering/instancing), [lights](https://driftengine.dev/docs/rendering/lights) and [shadows](https://driftengine.dev/docs/rendering/shadows), [the sky](https://driftengine.dev/docs/rendering/sky-and-atmosphere), [fog and weather](https://driftengine.dev/docs/rendering/fog-and-weather), [water](https://driftengine.dev/docs/rendering/water), [reflections](https://driftengine.dev/docs/rendering/reflections), [post-processing](https://driftengine.dev/docs/rendering/post-processing), [particles](https://driftengine.dev/docs/rendering/particles), [text](https://driftengine.dev/docs/rendering/text-and-overlays) and [passes of your own](https://driftengine.dev/docs/rendering/custom-passes). |
177
- | Light at scale | [DriftLight](https://driftengine.dev/docs/rendering/driftlight) for hundreds of fixed lights, [DriftRay](https://driftengine.dev/docs/rendering/driftray) for bounced light traced every frame, [DriftTR](https://driftengine.dev/docs/rendering/drifttr) for temporal reconstruction, and the [GPU-driven pipeline](https://driftengine.dev/docs/rendering/gpu-driven). |
178
- | Worlds | [Worlds bigger than a float](https://driftengine.dev/docs/worlds/large-worlds), streamed in cells, with [hierarchical detail](https://driftengine.dev/docs/worlds/hierarchical-detail) and [worlds built from a kit](https://driftengine.dev/docs/worlds/worlds-from-a-kit). |
179
- | Physics | Deterministic [rigid bodies](https://driftengine.dev/docs/simulation/rigid-bodies), [joints](https://driftengine.dev/docs/simulation/joints), [queries](https://driftengine.dev/docs/simulation/queries), [characters](https://driftengine.dev/docs/simulation/characters), [vehicles](https://driftengine.dev/docs/simulation/vehicles), and [ragdolls and cloth](https://driftengine.dev/docs/simulation/ragdolls-and-cloth). |
180
- | Play | [Input](https://driftengine.dev/docs/interface/input) from every device through one action map, [cameras that cut](https://driftengine.dev/docs/interface/cameras), [picking](https://driftengine.dev/docs/rendering/picking) and the [scene graph](https://driftengine.dev/docs/concepts/scene-graph). |
174
+ | | |
175
+ | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
176
+ | The frame | A fixed-step [loop](https://driftengine.dev/docs/start/the-loop) with interpolation, [two backends](https://driftengine.dev/docs/concepts/backends) behind one API, construction-time [quality profiles](https://driftengine.dev/docs/concepts/quality), and a [frame](https://driftengine.dev/docs/concepts/the-frame) whose order you can read. |
177
+ | Drawing | [Meshes](https://driftengine.dev/docs/rendering/meshes), [materials](https://driftengine.dev/docs/rendering/materials), [instancing](https://driftengine.dev/docs/rendering/instancing), [lights](https://driftengine.dev/docs/rendering/lights) and [shadows](https://driftengine.dev/docs/rendering/shadows), [the sky](https://driftengine.dev/docs/rendering/sky-and-atmosphere), [fog and weather](https://driftengine.dev/docs/rendering/fog-and-weather), [water](https://driftengine.dev/docs/rendering/water), [reflections](https://driftengine.dev/docs/rendering/reflections), [post-processing](https://driftengine.dev/docs/rendering/post-processing), [particles](https://driftengine.dev/docs/rendering/particles), [text](https://driftengine.dev/docs/rendering/text-and-overlays) and [passes of your own](https://driftengine.dev/docs/rendering/custom-passes). |
178
+ | Light at scale | [DriftLight](https://driftengine.dev/docs/rendering/driftlight) for thousands of fixed lights, [DriftRay](https://driftengine.dev/docs/rendering/driftray) for bounced light traced every frame, [DriftTR](https://driftengine.dev/docs/rendering/drifttr) for temporal reconstruction, and the [GPU-driven pipeline](https://driftengine.dev/docs/rendering/gpu-driven). |
179
+ | Many things | Instanced batches that cull, crowds animated from bone textures in the vertex stage (`createBoneAnimation`), and static draws recorded once and replayed every frame (`createStaticDraws`). |
180
+ | Textures | Images and compressed blocks through one call: BC for desktops, ETC2, EAC and ASTC for phones, each uploaded as it is where `compressedFormats` says the device samples it. `updateSurfaceTexture` can replace an image with blocks behind the handle a draw already holds. |
181
+ | Worlds | [Worlds bigger than a float](https://driftengine.dev/docs/worlds/large-worlds), streamed in cells, with [hierarchical detail](https://driftengine.dev/docs/worlds/hierarchical-detail) and [worlds built from a kit](https://driftengine.dev/docs/worlds/worlds-from-a-kit). |
182
+ | Physics | Deterministic [rigid bodies](https://driftengine.dev/docs/simulation/rigid-bodies), [joints](https://driftengine.dev/docs/simulation/joints), [queries](https://driftengine.dev/docs/simulation/queries), [characters](https://driftengine.dev/docs/simulation/characters), [vehicles](https://driftengine.dev/docs/simulation/vehicles), and [ragdolls and cloth](https://driftengine.dev/docs/simulation/ragdolls-and-cloth). |
183
+ | Play | [Input](https://driftengine.dev/docs/interface/input) from every device through one action map, [cameras that cut](https://driftengine.dev/docs/interface/cameras), [picking](https://driftengine.dev/docs/rendering/picking) and the [scene graph](https://driftengine.dev/docs/concepts/scene-graph). |
184
+ | Around the game | Saves and preferences through a store the game supplies, a host seam for windows, displays and files, and the visual-gate scripts — `@driftengine/core/scripts/browser.mjs` and its siblings — that photograph a page on the hardware GPU. |
181
185
 
182
186
  ## The other packages
183
187
 
184
188
  Each is added when a game reaches for it, and a game that never imports one carries none of it.
185
189
 
186
- | Package | What it adds |
187
- | ----------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
188
- | `@driftengine/animation` | Skeletons, clips, blend trees and IK. [Guide](https://driftengine.dev/docs/simulation/animation) |
189
- | `@driftengine/nav` | Navigation meshes and routes. [Guide](https://driftengine.dev/docs/simulation/navigation) |
190
- | `@driftengine/entities` | An entity component system with prefabs and rewind. [Guide](https://driftengine.dev/docs/simulation/entities) |
191
- | `@driftengine/chemistry` | Heat, burning, phase changes and smoke. [Guide](https://driftengine.dev/docs/simulation/chemistry) |
192
- | `@driftengine/ai` | Behaviour trees and agents. [Guide](https://driftengine.dev/docs/simulation/agents) |
193
- | `@driftengine/audio` | A mix of buses, placed sounds, rooms and rhythm. [Guide](https://driftengine.dev/docs/content/audio) |
194
- | `@driftengine/assets` and `@driftengine/drft` | Model importers and the streaming `.drft` container. [Guide](https://driftengine.dev/docs/content/importing-models) |
195
- | `@driftengine/terrain` | Terrain drawn as a clipmap. [Guide](https://driftengine.dev/docs/worlds/terrain) |
196
- | `@driftengine/splats` | Gaussian splat captures among meshes. [Guide](https://driftengine.dev/docs/worlds/splats) |
197
- | `@driftengine/texture` | Materials as programs decoded on the device. [Guide](https://driftengine.dev/docs/worlds/drifttexture) |
198
- | `@driftengine/capture` | A video clip turned into a surface, a collider and entities. [Guide](https://driftengine.dev/docs/worlds/driftcapture) |
199
- | `@driftengine/ui2d` | Sprites, tilemaps and interfaces. [Guide](https://driftengine.dev/docs/interface/sprites) |
200
- | `@driftengine/script` | DriftScript, the engine's scripting language, with hot reload. [Reference](https://driftengine.dev/docs/api/script) |
201
- | `@driftengine/network` | Rollback netcode. [Reference](https://driftengine.dev/docs/api/network) |
202
- | `@driftengine/xr` | WebXR sessions. [Reference](https://driftengine.dev/docs/api/xr) |
203
- | `@driftengine/media` | Recording and exporting clips. [Reference](https://driftengine.dev/docs/api/media) |
204
- | `@driftengine/editor` and `@driftengine/tools` | The editor and in-game tools. [Reference](https://driftengine.dev/docs/api/editor) |
205
- | `@driftengine/package` and `@driftengine/native-host` | Installable apps built from the same game. [Reference](https://driftengine.dev/docs/api/package) |
190
+ | Package | What it adds |
191
+ | ----------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
192
+ | `@driftengine/animation` | Skeletons, clips, blend trees and IK. [Guide](https://driftengine.dev/docs/simulation/animation) |
193
+ | `@driftengine/nav` | Navigation meshes and routes. [Guide](https://driftengine.dev/docs/simulation/navigation) |
194
+ | `@driftengine/entities` | Entities, components, systems and prefabs, with rewind. [Guide](https://driftengine.dev/docs/simulation/entities) |
195
+ | `@driftengine/chemistry` | Heat, burning, phase changes and smoke. [Guide](https://driftengine.dev/docs/simulation/chemistry) |
196
+ | `@driftengine/ai` | Behaviour trees and agents. [Guide](https://driftengine.dev/docs/simulation/agents) |
197
+ | `@driftengine/audio` | A mix of buses, placed sounds, rooms and rhythm. [Guide](https://driftengine.dev/docs/content/audio) |
198
+ | `@driftengine/assets` and `@driftengine/drft` | Model importers, KTX2, and the streaming `.drft` container. [Guide](https://driftengine.dev/docs/content/importing-models) |
199
+ | `@driftengine/terrain` | Terrain drawn as a clipmap. [Guide](https://driftengine.dev/docs/worlds/terrain) |
200
+ | `@driftengine/splats` | Gaussian splat captures among meshes. [Guide](https://driftengine.dev/docs/worlds/splats) |
201
+ | `@driftengine/texture` | Materials as programs decoded on the device. [Guide](https://driftengine.dev/docs/worlds/drifttexture) |
202
+ | `@driftengine/capture` | A video clip turned into a surface, a collider and entities. [Guide](https://driftengine.dev/docs/worlds/driftcapture) |
203
+ | `@driftengine/ui2d` | Sprites, tilemaps and interfaces. [Guide](https://driftengine.dev/docs/interface/sprites) |
204
+ | `@driftengine/script` | DriftScript, the engine's scripting language, with hot reload. [Reference](https://driftengine.dev/docs/api/script) |
205
+ | `@driftengine/network` | Lockstep and rollback netcode. [Reference](https://driftengine.dev/docs/api/network) |
206
+ | `@driftengine/xr` | WebXR sessions. [Reference](https://driftengine.dev/docs/api/xr) |
207
+ | `@driftengine/media` | Recording and exporting clips. [Reference](https://driftengine.dev/docs/api/media) |
208
+ | `@driftengine/editor` and `@driftengine/tools` | The editor and in-game tools. [Reference](https://driftengine.dev/docs/api/editor) |
209
+ | `@driftengine/package` and `@driftengine/native-host` | Installable apps built from the same game. [Reference](https://driftengine.dev/docs/api/package) |
206
210
 
207
211
  ## Licence
208
212
 
209
- Apache-2.0. Part of [DriftEngine](https://github.com/drftrun/driftengine); see
213
+ Apache-2.0. Distributing it means reproducing its `NOTICE`, which every bundle of it already
214
+ carries. Part of [DriftEngine](https://github.com/drftrun/driftengine); see
210
215
  [`docs/ARCHITECTURE.md`](https://github.com/drftrun/driftengine/blob/main/docs/ARCHITECTURE.md) for
211
216
  how the packages divide.
package/dist/index.d.ts CHANGED
@@ -164,8 +164,8 @@ export type { FrameReflections } from './render/frameReflections.ts';
164
164
  export type { OverlayRegion, SurfaceDissolve, SurfaceOverlay, SurfaceRim, SurfaceWrinkle, } from './render/surfaceOverlay.ts';
165
165
  export { WHOLE_PAGE } from './render/lightmap.ts';
166
166
  export type { LightmapPage, LightmapRegion } from './render/lightmap.ts';
167
- export { createSkinnedCloth } from './render/skinnedClothRun.ts';
168
- export type { SkinnedClothSolver } from './render/skinnedClothRun.ts';
167
+ export { createSkinnedCloth, createSkinnedClothSet } from './render/skinnedClothRun.ts';
168
+ export type { SkinnedClothSet, SkinnedClothSolver } from './render/skinnedClothRun.ts';
169
169
  /**
170
170
  * What a frame asked of a backend, against the ceilings that backend imposes.
171
171
  *
@@ -175,6 +175,8 @@ export type { SkinnedClothSolver } from './render/skinnedClothRun.ts';
175
175
  export type { BudgetLine, FrameBudget } from './render/backend/budget.ts';
176
176
  export type { FrameTimer } from './render/backend/timer.ts';
177
177
  export type { SceneCasterMaterial, SceneCasters, ShadowCasterCloth, ShadowCasterSink, ShadowCasters, } from './render/shadowCasters.ts';
178
+ export type { StaticDrawsHandle } from './render/staticDraws.ts';
179
+ export type { BoneAnimationClip, BoneAnimationHandle } from './render/boneAnimation.ts';
178
180
  export type { Atmosphere, UnderwaterAtmosphere } from './render/atmosphere.ts';
179
181
  export { createImageTexelDecoder } from './render/imageTexels.ts';
180
182
  export type { ImageTexelDecoder, ImageTexels } from './render/imageTexels.ts';
@@ -221,12 +223,13 @@ export type { PrefilterQuality } from './render/prefilterEnvMap.ts';
221
223
  * `ProbeBakeOptions`, which is the whole of the reasoning.
222
224
  */
223
225
  export type { ProbeBakeOptions } from './render/reflectionProbe.ts';
224
- export type { DirectionalShadowDepthLayers, GlassShadows, OutputTransform, RenderQuality, RenderQualityOptions, ShadowFilterTaps, SkinScattering, WaterReflectionFilterTaps, } from './render/renderQuality.ts';
226
+ export type { DirectionalShadowDepthLayers, GlassShadows, OutputTransform, PipelineCompile, RenderQuality, RenderQualityOptions, ShadowFilterTaps, SkinScattering, WaterReflectionFilterTaps, } from './render/renderQuality.ts';
225
227
  export { MAX_POINT_LIGHTS, SURFACE_TEXTURE_UNIT } from './render/lightBudget.ts';
226
228
  export { MAX_CLUSTERED_LIGHTS } from './render/clusteredLights.ts';
227
229
  export { SurfaceTexture } from './render/surfaceTexture.ts';
228
230
  export type { SurfaceMaterial, SurfaceTextureOptions } from './render/surfaceTexture.ts';
229
- export { compressedFormatName, isCompressedSource, uploadsCompressed, } from './render/compressedSource.ts';
231
+ export type { SurfaceProjection } from './render/surfaceProjection.ts';
232
+ export { compressedFormatName, isCompressedSource, levelBytes, uploadsCompressed, } from './render/compressedSource.ts';
230
233
  export type { BlockFormat, CompressedTextureFormat, CompressedTextureSource, SurfaceSource, } from './render/compressedSource.ts';
231
234
  export { DEFAULT_GOVERNOR_LIMITS, ResolutionGovernor } from './render/resolutionGovernor.ts';
232
235
  export type { GovernorLimits } from './render/resolutionGovernor.ts';
package/dist/index.js CHANGED
@@ -237,7 +237,7 @@ export { WHOLE_PAGE } from './render/lightmap.js';
237
237
  * A skinned cloth solved where the renderer can: compute on WebGPU, the CPU under WebGL2. A function
238
238
  * rather than a renderer method so only a game that imports it carries the solver.
239
239
  */
240
- export { createSkinnedCloth } from './render/skinnedClothRun.js';
240
+ export { createSkinnedCloth, createSkinnedClothSet } from './render/skinnedClothRun.js';
241
241
  /*
242
242
  * Reading an image's texels back exactly, which no canvas and no `VideoFrame` will do.
243
243
  *
@@ -288,10 +288,11 @@ export { MAX_POINT_LIGHTS, SURFACE_TEXTURE_UNIT } from './render/lightBudget.js'
288
288
  export { MAX_CLUSTERED_LIGHTS } from './render/clusteredLights.js';
289
289
  export { SurfaceTexture } from './render/surfaceTexture.js';
290
290
  /*
291
- * BC compressed textures: the source `createSurfaceTexture` takes as blocks, and the one question to
292
- * ask before handing one over — whether this device takes its format (`renderer.compressedFormats`).
291
+ * Compressed textures — BC, ETC2 and EAC, ASTC: the source `createSurfaceTexture` takes as blocks,
292
+ * the one question to ask before handing one over — whether this device takes its format
293
+ * (`renderer.compressedFormats`) — and the bytes a level of it holds, for a reader checking a file.
293
294
  */
294
- export { compressedFormatName, isCompressedSource, uploadsCompressed, } from './render/compressedSource.js';
295
+ export { compressedFormatName, isCompressedSource, levelBytes, uploadsCompressed, } from './render/compressedSource.js';
295
296
  export { DEFAULT_GOVERNOR_LIMITS, ResolutionGovernor } from './render/resolutionGovernor.js';
296
297
  export { isWeakGpuFamily } from './render/gpuCapability.js';
297
298
  /**
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * **Why a draw needs its own.** The engine's diffuse ambient is one answer for a whole frame — the
6
6
  * sky-and-ground gradient, or the probe grid's irradiance — so everything standing in one place is
7
- * lit alike. A stage with a baked volume of indirect light (Unreal's volumetric lightmap) lights a
7
+ * lit alike. A stage with a baked volume of indirect light (a volumetric lightmap) lights a
8
8
  * character by sampling that volume where the character stands; a consumer that samples it each
9
9
  * frame hands the result here, around that character's draws, and the character takes the light of
10
10
  * the spot it is in rather than the frame's average.
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * **Why a draw needs its own.** The engine's diffuse ambient is one answer for a whole frame — the
6
6
  * sky-and-ground gradient, or the probe grid's irradiance — so everything standing in one place is
7
- * lit alike. A stage with a baked volume of indirect light (Unreal's volumetric lightmap) lights a
7
+ * lit alike. A stage with a baked volume of indirect light (a volumetric lightmap) lights a
8
8
  * character by sampling that volume where the character stands; a consumer that samples it each
9
9
  * frame hands the result here, around that character's draws, and the character takes the light of
10
10
  * the spot it is in rather than the frame's average.
@@ -92,14 +92,14 @@ export interface AreaLightSource {
92
92
  * others. **Read only where the rectangle is shaded through the froxel table** — the ones past
93
93
  * `maxAreaLights` with `clusteredLights` on; the fixed four light everything they face, as they
94
94
  * always have. Absent, it is where the rectangle's irradiance on its axis falls to a thousandth
95
- * of a scene unit, and never inside the rectangle itself. Unreal's `AttenuationRadius`, in metres.
95
+ * of a scene unit, and never inside the rectangle itself. An attenuation radius, in metres.
96
96
  */
97
97
  range?: number;
98
98
  /**
99
99
  * Barn doors: a flap hinged at each of the rectangle's four edges, standing this many degrees from
100
100
  * its normal. 90, or absent, folds them flat and they hide nothing; smaller closes them, so the
101
101
  * light narrows to the opening they leave — a rectangle at 50° throws a beam rather than filling
102
- * its hemisphere. Unreal's `BarnDoorAngle`, whose default of 88° is nearly flat.
102
+ * its hemisphere. 88° is the usual default, nearly flat.
103
103
  *
104
104
  * What a fragment sees past the doors is the part of the rectangle no door's tip hides, one axis
105
105
  * at a time, and the rectangle then shades as that smaller rectangle, its highlight included.
@@ -109,7 +109,7 @@ export interface AreaLightSource {
109
109
  * rectangle's back face has none.
110
110
  */
111
111
  barnDoorAngle?: number;
112
- /** The barn doors' length in metres. Absent, 0.2: Unreal's default of 20 cm. */
112
+ /** The barn doors' length in metres. Absent, 0.2: the usual default of 20 cm. */
113
113
  barnDoorLength?: number;
114
114
  }
115
115
  /** The irradiance a derived `range` ends at, in scene units. See `AreaLightSource.range`. */
@@ -132,7 +132,7 @@ export interface AreaLightBuffer {
132
132
  readonly barnDoors?: Float32Array;
133
133
  }
134
134
  export declare function createAreaLightBuffer(capacity?: number): AreaLightBuffer;
135
- /** Unreal's default barn door length, in metres. See `AreaLightSource.barnDoorLength`. */
135
+ /** The usual default barn door length, in metres. See `AreaLightSource.barnDoorLength`. */
136
136
  export declare const DEFAULT_BARN_DOOR_LENGTH = 0.2;
137
137
  /**
138
138
  * A rectangle's barn doors as the shader reads them, into `out[at]` and `out[at + 1]`: the cosine of
@@ -23,7 +23,7 @@ export function createAreaLightBuffer(capacity = MAX_AREA_LIGHTS) {
23
23
  barnDoors: new Float32Array(capacity * 2),
24
24
  };
25
25
  }
26
- /** Unreal's default barn door length, in metres. See `AreaLightSource.barnDoorLength`. */
26
+ /** The usual default barn door length, in metres. See `AreaLightSource.barnDoorLength`. */
27
27
  export const DEFAULT_BARN_DOOR_LENGTH = 0.2;
28
28
  /**
29
29
  * A rectangle's barn doors as the shader reads them, into `out[at]` and `out[at + 1]`: the cosine of
@@ -8,7 +8,7 @@ import type { Renderer, TranslucentMeshOptions } from './webgl2/renderer.ts';
8
8
  import type { InstancedOptions, MeshInstances } from '../instances.ts';
9
9
  import type { SurfaceMaterial } from '../surfaceTexture.ts';
10
10
  import type { SurfaceOverlay } from '../surfaceOverlay.ts';
11
- import type { SurfaceSource } from '../compressedSource.ts';
11
+ import type { CompressedTextureSource, SurfaceSource } from '../compressedSource.ts';
12
12
  import type { LightmapPage } from '../lightmap.ts';
13
13
  import type { DeviceParticles } from '../particlePool.ts';
14
14
  import type { ClothBindingData } from '../clothBindingData.ts';
@@ -270,12 +270,19 @@ export type RendererApi = Omit<{
270
270
  */
271
271
  captureScene(capture: SurfaceTextureHandle, camera: Camera, clearColor: Vec3, draw: (camera: Camera) => void): boolean;
272
272
  /**
273
- * Replace a texture's pixels, keeping the GPU object and its sampler state.
273
+ * Replace a texture's pixels, keeping the handle and its sampler state.
274
274
  *
275
275
  * The binding a draw loop already holds stays valid, so the swap is a swap rather than a
276
276
  * rebuild. Not a hot path: it re-uploads the whole image and rebuilds the mip chain.
277
+ *
278
+ * **Blocks may replace an image**, at the format's own size and with the chain they carry: the
279
+ * same picture at a fraction of the memory, which is how a loader that first showed a decoded
280
+ * image hands the device the blocks it encoded afterwards. Read in the colour space the texture
281
+ * was made with, and refused as `createSurfaceTexture` refuses them where the device does not take
282
+ * that format (`compressedFormats`). **The way back is refused**: a texture holding blocks takes
283
+ * no image, since nothing could say what its chain should become.
277
284
  */
278
- updateSurfaceTexture(texture: SurfaceTextureHandle, source: TexImageSource): void;
285
+ updateSurfaceTexture(texture: SurfaceTextureHandle, source: TexImageSource | CompressedTextureSource): void;
279
286
  disposeSurfaceTexture(texture: SurfaceTextureHandle): void;
280
287
  /**
281
288
  * Choose the texture the following `drawMesh` calls sample, or null for none.