@driftengine/core 4.8.7 → 4.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (263) hide show
  1. package/CHANGELOG.json +123 -11
  2. package/README.md +43 -38
  3. package/dist/index.d.ts +8 -1
  4. package/dist/index.js +11 -3
  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/atmosphere.d.ts +13 -0
  10. package/dist/render/atmosphere.js +9 -1
  11. package/dist/render/backend/api.d.ts +19 -4
  12. package/dist/render/backend/createRenderer.d.ts +6 -0
  13. package/dist/render/backend/createRenderer.js +4 -1
  14. package/dist/render/backend/select.js +11 -5
  15. package/dist/render/backend/webgl2/boneAnimations.d.ts +29 -0
  16. package/dist/render/backend/webgl2/boneAnimations.js +80 -0
  17. package/dist/render/backend/webgl2/instanced.d.ts +5 -1
  18. package/dist/render/backend/webgl2/instanced.js +13 -1
  19. package/dist/render/backend/webgl2/renderer.d.ts +180 -4
  20. package/dist/render/backend/webgl2/renderer.js +550 -40
  21. package/dist/render/backend/webgl2/skinPaletteTexture.d.ts +5 -3
  22. package/dist/render/backend/webgl2/skinPaletteTexture.js +6 -4
  23. package/dist/render/backend/webgpu/boneAnimations.d.ts +34 -0
  24. package/dist/render/backend/webgpu/boneAnimations.js +76 -0
  25. package/dist/render/backend/webgpu/buffers.d.ts +19 -4
  26. package/dist/render/backend/webgpu/buffers.js +18 -11
  27. package/dist/render/backend/webgpu/compressedUpload.d.ts +0 -1
  28. package/dist/render/backend/webgpu/compressedUpload.js +53 -8
  29. package/dist/render/backend/webgpu/depthPass.d.ts +59 -3
  30. package/dist/render/backend/webgpu/depthPass.js +49 -14
  31. package/dist/render/backend/webgpu/device.d.ts +7 -1
  32. package/dist/render/backend/webgpu/device.js +31 -3
  33. package/dist/render/backend/webgpu/drawCommand.d.ts +18 -4
  34. package/dist/render/backend/webgpu/drawCommand.js +10 -3
  35. package/dist/render/backend/webgpu/flatPass.d.ts +100 -42
  36. package/dist/render/backend/webgpu/flatPass.js +191 -43
  37. package/dist/render/backend/webgpu/instanced.d.ts +14 -1
  38. package/dist/render/backend/webgpu/instanced.js +24 -1
  39. package/dist/render/backend/webgpu/materialStore.d.ts +101 -0
  40. package/dist/render/backend/webgpu/materialStore.js +265 -0
  41. package/dist/render/backend/webgpu/pipelineCache.d.ts +6 -0
  42. package/dist/render/backend/webgpu/pipelineCache.js +7 -0
  43. package/dist/render/backend/webgpu/postPass.d.ts +51 -9
  44. package/dist/render/backend/webgpu/postPass.js +16 -2
  45. package/dist/render/backend/webgpu/reflectionSurfacePass.d.ts +30 -0
  46. package/dist/render/backend/webgpu/reflectionSurfacePass.js +100 -0
  47. package/dist/render/backend/webgpu/renderer.d.ts +283 -37
  48. package/dist/render/backend/webgpu/renderer.js +1363 -202
  49. package/dist/render/backend/webgpu/skinPaletteRing.d.ts +7 -3
  50. package/dist/render/backend/webgpu/skinPaletteRing.js +13 -7
  51. package/dist/render/backend/webgpu/skinPaletteTexture.d.ts +6 -0
  52. package/dist/render/backend/webgpu/skinPaletteTexture.js +7 -1
  53. package/dist/render/backend/webgpu/staticBundles.d.ts +125 -0
  54. package/dist/render/backend/webgpu/staticBundles.js +241 -0
  55. package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +10 -3
  56. package/dist/render/backend/webgpu/surfaceTexturePass.js +20 -2
  57. package/dist/render/backend/webgpu/uniformRing.d.ts +21 -0
  58. package/dist/render/backend/webgpu/uniformRing.js +9 -1
  59. package/dist/render/bloomChain.d.ts +3 -3
  60. package/dist/render/boneAnimation.d.ts +92 -0
  61. package/dist/render/boneAnimation.js +133 -0
  62. package/dist/render/compressedSource.d.ts +33 -13
  63. package/dist/render/compressedSource.js +70 -25
  64. package/dist/render/displayRange.d.ts +31 -0
  65. package/dist/render/displayRange.js +47 -0
  66. package/dist/render/filmicCurve.d.ts +74 -0
  67. package/dist/render/filmicCurve.js +203 -0
  68. package/dist/render/fog.d.ts +4 -1
  69. package/dist/render/fog.js +12 -2
  70. package/dist/render/frame/resources.d.ts +2 -2
  71. package/dist/render/frame/resources.js +6 -0
  72. package/dist/render/frameReflections.d.ts +40 -0
  73. package/dist/render/frameReflections.js +48 -0
  74. package/dist/render/glCompressed.js +83 -0
  75. package/dist/render/instances.d.ts +26 -0
  76. package/dist/render/lightBudget.d.ts +10 -0
  77. package/dist/render/lightBudget.js +10 -0
  78. package/dist/render/lightmap.d.ts +1 -1
  79. package/dist/render/mesh.d.ts +5 -0
  80. package/dist/render/mesh.js +6 -0
  81. package/dist/render/pass.d.ts +49 -0
  82. package/dist/render/pointLightSelection.d.ts +4 -4
  83. package/dist/render/reflectionSurfaceTargets.d.ts +28 -0
  84. package/dist/render/reflectionSurfaceTargets.js +99 -0
  85. package/dist/render/renderQuality.d.ts +39 -8
  86. package/dist/render/renderQuality.js +4 -0
  87. package/dist/render/sceneTarget.d.ts +5 -0
  88. package/dist/render/sceneTarget.js +24 -1
  89. package/dist/render/screenSpaceReflection.d.ts +7 -1
  90. package/dist/render/screenSpaceReflection.js +5 -0
  91. package/dist/render/shaders/arcane.d.ts +1 -1
  92. package/dist/render/shaders/bolt.d.ts +1 -1
  93. package/dist/render/shaders/boneAnimation.d.ts +19 -0
  94. package/dist/render/shaders/boneAnimation.js +63 -0
  95. package/dist/render/shaders/caustics.d.ts +1 -1
  96. package/dist/render/shaders/depth.d.ts +7 -0
  97. package/dist/render/shaders/depth.js +35 -10
  98. package/dist/render/shaders/film.d.ts +1 -1
  99. package/dist/render/shaders/filmLook.js +2 -1
  100. package/dist/render/shaders/fire.d.ts +1 -1
  101. package/dist/render/shaders/flat/glassTint.d.ts +1 -1
  102. package/dist/render/shaders/flat/glassTint.js +2 -0
  103. package/dist/render/shaders/flat/index.d.ts +16 -0
  104. package/dist/render/shaders/flat/index.js +33 -5
  105. package/dist/render/shaders/flat/main.d.ts +1 -1
  106. package/dist/render/shaders/flat/main.js +43 -5
  107. package/dist/render/shaders/flat/models.js +1 -1
  108. package/dist/render/shaders/flat/overlay.d.ts +16 -0
  109. package/dist/render/shaders/flat/overlay.js +117 -0
  110. package/dist/render/shaders/flat/pointShadow.js +3 -3
  111. package/dist/render/shaders/flat/preamble.js +38 -24
  112. package/dist/render/shaders/flat/reflectionSurface.d.ts +21 -0
  113. package/dist/render/shaders/flat/reflectionSurface.js +61 -0
  114. package/dist/render/shaders/fog.d.ts +1 -1
  115. package/dist/render/shaders/fog.js +9 -2
  116. package/dist/render/shaders/fringe.d.ts +29 -0
  117. package/dist/render/shaders/fringe.js +55 -0
  118. package/dist/render/shaders/generated/arcane.wgsl.d.ts +1 -1
  119. package/dist/render/shaders/generated/arcane.wgsl.js +1 -1
  120. package/dist/render/shaders/generated/bolt.wgsl.d.ts +1 -1
  121. package/dist/render/shaders/generated/bolt.wgsl.js +1 -1
  122. package/dist/render/shaders/generated/caustics.wgsl.d.ts +1 -1
  123. package/dist/render/shaders/generated/caustics.wgsl.js +1 -1
  124. package/dist/render/shaders/generated/depth.wgsl.d.ts +93 -0
  125. package/dist/render/shaders/generated/depth.wgsl.js +93 -0
  126. package/dist/render/shaders/generated/film.wgsl.d.ts +1 -1
  127. package/dist/render/shaders/generated/film.wgsl.js +1 -1
  128. package/dist/render/shaders/generated/fire.wgsl.d.ts +1 -1
  129. package/dist/render/shaders/generated/fire.wgsl.js +1 -1
  130. package/dist/render/shaders/generated/flat.wgsl.d.ts +3254 -2920
  131. package/dist/render/shaders/generated/flat.wgsl.js +3292 -2957
  132. package/dist/render/shaders/generated/line.wgsl.d.ts +1 -1
  133. package/dist/render/shaders/generated/line.wgsl.js +1 -1
  134. package/dist/render/shaders/generated/particle.wgsl.d.ts +4 -4
  135. package/dist/render/shaders/generated/particle.wgsl.js +4 -4
  136. package/dist/render/shaders/generated/rush.wgsl.d.ts +56 -26
  137. package/dist/render/shaders/generated/rush.wgsl.js +56 -26
  138. package/dist/render/shaders/generated/scatter.wgsl.d.ts +1 -1
  139. package/dist/render/shaders/generated/scatter.wgsl.js +1 -1
  140. package/dist/render/shaders/generated/smoke.wgsl.d.ts +1 -1
  141. package/dist/render/shaders/generated/smoke.wgsl.js +1 -1
  142. package/dist/render/shaders/generated/ssrResolve.wgsl.d.ts +29 -0
  143. package/dist/render/shaders/generated/ssrResolve.wgsl.js +29 -0
  144. package/dist/render/shaders/generated/ssrTrace.wgsl.d.ts +22 -2
  145. package/dist/render/shaders/generated/ssrTrace.wgsl.js +22 -2
  146. package/dist/render/shaders/generated/water.wgsl.d.ts +1 -1
  147. package/dist/render/shaders/generated/water.wgsl.js +1 -1
  148. package/dist/render/shaders/gpudriven/fog.wgsl.d.ts +2 -2
  149. package/dist/render/shaders/gpudriven/fog.wgsl.js +11 -3
  150. package/dist/render/shaders/line.d.ts +1 -1
  151. package/dist/render/shaders/outputTransform.d.ts +7 -0
  152. package/dist/render/shaders/outputTransform.js +28 -14
  153. package/dist/render/shaders/particle.d.ts +3 -3
  154. package/dist/render/shaders/rush.js +54 -11
  155. package/dist/render/shaders/scatter.d.ts +1 -1
  156. package/dist/render/shaders/smoke.d.ts +1 -1
  157. package/dist/render/shaders/ssrResolve.d.ts +17 -0
  158. package/dist/render/shaders/ssrResolve.js +51 -0
  159. package/dist/render/shaders/ssrTrace.js +26 -0
  160. package/dist/render/shaders/vertexChannel.d.ts +7 -0
  161. package/dist/render/shaders/vertexChannel.js +26 -0
  162. package/dist/render/shaders/water.d.ts +1 -1
  163. package/dist/render/ssrPass.d.ts +16 -0
  164. package/dist/render/ssrPass.js +106 -1
  165. package/dist/render/staticDraws.d.ts +47 -0
  166. package/dist/render/staticDraws.js +43 -0
  167. package/dist/render/surfaceOverlay.d.ts +132 -0
  168. package/dist/render/surfaceOverlay.js +85 -0
  169. package/dist/render/surfaceTexture.d.ts +57 -7
  170. package/dist/render/surfaceTexture.js +125 -17
  171. package/dist/render/temporalAa.d.ts +14 -0
  172. package/dist/render/temporalAa.js +20 -0
  173. package/dist/render/vertexDefaults.d.ts +17 -0
  174. package/dist/render/vertexDefaults.js +24 -0
  175. package/package.json +6 -6
  176. package/src/index.ts +24 -2
  177. package/src/render/ambientHarmonics.ts +1 -1
  178. package/src/render/areaLights.ts +4 -4
  179. package/src/render/atmosphere.ts +22 -1
  180. package/src/render/backend/api.ts +22 -3
  181. package/src/render/backend/createRenderer.ts +18 -1
  182. package/src/render/backend/select.ts +10 -5
  183. package/src/render/backend/webgl2/boneAnimations.ts +105 -0
  184. package/src/render/backend/webgl2/instanced.ts +20 -1
  185. package/src/render/backend/webgl2/renderer.ts +672 -37
  186. package/src/render/backend/webgl2/skinPaletteTexture.ts +6 -4
  187. package/src/render/backend/webgpu/boneAnimations.ts +97 -0
  188. package/src/render/backend/webgpu/buffers.ts +42 -16
  189. package/src/render/backend/webgpu/compressedUpload.ts +59 -9
  190. package/src/render/backend/webgpu/depthPass.ts +51 -10
  191. package/src/render/backend/webgpu/device.ts +42 -3
  192. package/src/render/backend/webgpu/drawCommand.ts +22 -4
  193. package/src/render/backend/webgpu/flatPass.ts +250 -40
  194. package/src/render/backend/webgpu/instanced.ts +32 -1
  195. package/src/render/backend/webgpu/materialStore.ts +276 -0
  196. package/src/render/backend/webgpu/pipelineCache.ts +7 -0
  197. package/src/render/backend/webgpu/postPass.ts +18 -0
  198. package/src/render/backend/webgpu/reflectionSurfacePass.ts +114 -0
  199. package/src/render/backend/webgpu/renderer.ts +1655 -230
  200. package/src/render/backend/webgpu/skinPaletteRing.ts +13 -7
  201. package/src/render/backend/webgpu/skinPaletteTexture.ts +7 -1
  202. package/src/render/backend/webgpu/staticBundles.ts +363 -0
  203. package/src/render/backend/webgpu/surfaceTexturePass.ts +29 -3
  204. package/src/render/backend/webgpu/uniformRing.ts +10 -1
  205. package/src/render/bloomChain.ts +3 -3
  206. package/src/render/boneAnimation.ts +223 -0
  207. package/src/render/compressedSource.ts +121 -29
  208. package/src/render/displayRange.ts +61 -0
  209. package/src/render/filmicCurve.ts +229 -0
  210. package/src/render/fog.ts +19 -3
  211. package/src/render/frame/resources.ts +6 -0
  212. package/src/render/frameReflections.ts +72 -0
  213. package/src/render/glCompressed.ts +83 -0
  214. package/src/render/instances.ts +27 -0
  215. package/src/render/lightBudget.ts +11 -0
  216. package/src/render/lightmap.ts +1 -1
  217. package/src/render/mesh.ts +6 -0
  218. package/src/render/pass.ts +53 -1
  219. package/src/render/pointLightSelection.ts +4 -4
  220. package/src/render/reflectionSurfaceTargets.ts +105 -0
  221. package/src/render/renderQuality.ts +44 -8
  222. package/src/render/sceneTarget.ts +30 -2
  223. package/src/render/screenSpaceReflection.ts +7 -1
  224. package/src/render/shaders/boneAnimation.ts +63 -0
  225. package/src/render/shaders/depth.ts +45 -10
  226. package/src/render/shaders/filmLook.ts +2 -1
  227. package/src/render/shaders/flat/glassTint.ts +5 -1
  228. package/src/render/shaders/flat/index.ts +49 -5
  229. package/src/render/shaders/flat/main.ts +43 -5
  230. package/src/render/shaders/flat/models.ts +1 -1
  231. package/src/render/shaders/flat/overlay.ts +117 -0
  232. package/src/render/shaders/flat/pointShadow.ts +3 -3
  233. package/src/render/shaders/flat/preamble.ts +38 -24
  234. package/src/render/shaders/flat/reflectionSurface.ts +61 -0
  235. package/src/render/shaders/fog.ts +9 -2
  236. package/src/render/shaders/fringe.ts +59 -0
  237. package/src/render/shaders/generated/arcane.wgsl.ts +1 -1
  238. package/src/render/shaders/generated/bolt.wgsl.ts +1 -1
  239. package/src/render/shaders/generated/caustics.wgsl.ts +1 -1
  240. package/src/render/shaders/generated/depth.wgsl.ts +95 -0
  241. package/src/render/shaders/generated/film.wgsl.ts +1 -1
  242. package/src/render/shaders/generated/fire.wgsl.ts +1 -1
  243. package/src/render/shaders/generated/flat.wgsl.ts +3292 -2957
  244. package/src/render/shaders/generated/line.wgsl.ts +1 -1
  245. package/src/render/shaders/generated/particle.wgsl.ts +4 -4
  246. package/src/render/shaders/generated/rush.wgsl.ts +56 -26
  247. package/src/render/shaders/generated/scatter.wgsl.ts +1 -1
  248. package/src/render/shaders/generated/smoke.wgsl.ts +1 -1
  249. package/src/render/shaders/generated/ssrResolve.wgsl.ts +30 -0
  250. package/src/render/shaders/generated/ssrTrace.wgsl.ts +22 -2
  251. package/src/render/shaders/generated/water.wgsl.ts +1 -1
  252. package/src/render/shaders/gpudriven/fog.wgsl.ts +11 -3
  253. package/src/render/shaders/outputTransform.ts +30 -14
  254. package/src/render/shaders/rush.ts +54 -11
  255. package/src/render/shaders/ssrResolve.ts +52 -0
  256. package/src/render/shaders/ssrTrace.ts +26 -0
  257. package/src/render/shaders/vertexChannel.ts +27 -0
  258. package/src/render/ssrPass.ts +138 -1
  259. package/src/render/staticDraws.ts +95 -0
  260. package/src/render/surfaceOverlay.ts +195 -0
  261. package/src/render/surfaceTexture.ts +161 -27
  262. package/src/render/temporalAa.ts +20 -0
  263. package/src/render/vertexDefaults.ts +28 -0
@@ -9,7 +9,7 @@
9
9
  * so a merged block wearing forty facades is one draw.
10
10
  */
11
11
  import type { SurfaceModel } from './surfaceModel.ts';
12
- import type { CompressedTextureFormat, SurfaceSource } from './compressedSource.ts';
12
+ import type { CompressedTextureFormat, CompressedTextureSource, SurfaceSource } from './compressedSource.ts';
13
13
  import type { SurfaceLayerEffect } from './surfaceEffects.ts';
14
14
  import type { CutoutMode } from './cutoutDither.ts';
15
15
  import type { LightmapTexels } from './lightmap.ts';
@@ -107,7 +107,7 @@ export interface SurfaceMaterial<Texture = SurfaceTexture> {
107
107
  * which is every light's default too, so a scene that names no channel is lit as it always was.
108
108
  *
109
109
  * A point or spot light shades this surface only where its `PointLightSet.lightChannels` and this
110
- * share a bit — Unreal's lighting channels. A character's own key and rim lights on channel 2,
110
+ * share a bit — lighting channels. A character's own key and rim lights on channel 2,
111
111
  * and the character's materials on 3, light the character and leave the floor around it as the
112
112
  * stage's lights alone have it. **What it does not reach**: the sun, the sky and the probes, area
113
113
  * lights, DriftLight's volume and the GPU-driven pipeline light every surface whatever its mask.
@@ -141,6 +141,22 @@ export interface SurfaceMaterial<Texture = SurfaceTexture> {
141
141
  * which is the opposite of what the caller asked for.
142
142
  */
143
143
  occlusionStrength?: number;
144
+ /**
145
+ * How much of the environment this material's draws mirror, 0 to 1: the number
146
+ * `setSurfaceReflectivity` sets for every draw, stated by the material for its own. Absent, its
147
+ * draws wear the setter's, as every material's did before this existed.
148
+ *
149
+ * **Here so a static list can carry it.** A list keeps each entry's material and none of the
150
+ * renderer's state between draws (`createStaticDraws`), so a stage whose batches each mirror their
151
+ * own share of a baked reflection says so in the material it records. What it gives up is nothing a
152
+ * setter could do: one called after this material is set is ignored for the material's draws.
153
+ */
154
+ reflectivity?: number;
155
+ /**
156
+ * How bright the environment this material's draws reflect is: `setEnvironmentGain`'s number,
157
+ * stated by the material for its own, and absent the setter's. Held non-negative.
158
+ */
159
+ environmentGain?: number;
144
160
  /**
145
161
  * Scales the emissive map, per channel. Default 1.
146
162
  *
@@ -164,14 +180,14 @@ export interface SurfaceMaterial<Texture = SurfaceTexture> {
164
180
  modelMap?: Texture | null;
165
181
  /**
166
182
  * Whether a lamp's and the sun's highlight on this surface is GGX's own, `π · D · Vis · F · N·L`,
167
- * as Unreal and this engine's skin and eye shade it, rather than the engine's lobe scaled to a
168
- * peak of one. **False by default**, every surface as it was.
183
+ * as physically based renderers and this engine's skin and eye shade it, rather than the engine's
184
+ * lobe scaled to a peak of one. **False by default**, every surface as it was.
169
185
  *
170
186
  * The peak-normalised lobe is a look control: the specular attribute says how strong a highlight
171
187
  * is and roughness how wide, apart. Physical, they are entangled as they are in a real surface —
172
188
  * the peak is `1 / (4 α²)` of the light head-on, so a polished surface's highlight is many times
173
189
  * the look's and a rough one's lower and broader — and the specular attribute is read as the
174
- * reflectance at normal incidence, F0: Unreal's `0.08 × Specular`, 0.04 at its default. Fresnel
190
+ * reflectance at normal incidence, F0: `0.08 × Specular`, 0.04 at its default. Fresnel
175
191
  * then brightens every surface toward grazing, not only a metal. A rectangle's highlight is the
176
192
  * lobe integrated over it with the attribute as F0 already, so it is unchanged either way.
177
193
  *
@@ -275,7 +291,16 @@ export declare class SurfaceTexture {
275
291
  /** The per-layer effects table, or null for a texture given none. See `surfaceEffects.ts`. */
276
292
  private effectsTable;
277
293
  /** The block format a compressed texture holds, or null for one uploaded from images. */
278
- private readonly blockFormat;
294
+ private blockFormat;
295
+ /** How the texture is sampled, kept so blocks swapped in by `update` are sampled the same way. */
296
+ private readonly sampling;
297
+ /** Level 0's size, which `flatLayer` copies; kept as `update` changes it. */
298
+ private width;
299
+ private height;
300
+ /** The texture's storage format, which `flatLayer`'s copy matches so the copy converts nothing. */
301
+ private readonly storage;
302
+ /** Layer 0 as a plain 2D texture, made by `flatLayer` and dropped when the pixels change. */
303
+ private flat;
279
304
  /**
280
305
  * One image, or an array of images that must share one size (`layerSize` refuses otherwise).
281
306
  * Uploaded unflipped either way; see the constructor body for why. Or blocks, uploaded as they are
@@ -296,7 +321,20 @@ export declare class SurfaceTexture {
296
321
  * Not a hot path: it re-uploads the whole image and regenerates the chain. A caller
297
322
  * doing this every frame at any size wants to know that it costs what it costs.
298
323
  */
299
- update(gl: WebGL2RenderingContext, source: TexImageSource): void;
324
+ update(gl: WebGL2RenderingContext, source: TexImageSource | CompressedTextureSource, compressed?: readonly CompressedTextureFormat[]): void;
325
+ /**
326
+ * Blocks in place of the image, behind the same handle: **a new GL object** rather than the
327
+ * image's levels respecified, because a chain shorter than the image's would leave the levels
328
+ * past it allocated, and immutable storage could not be respecified at all. Sampled as the image
329
+ * was, and refused before anything changes where the context does not take the format.
330
+ */
331
+ private swapToBlocks;
332
+ /**
333
+ * Wrap, filters and anisotropy on the bound texture. Nearest where the caller said their pixels
334
+ * are the subject; see `filter`. The mip chain is still blended between levels, because that is
335
+ * minification and this option is about magnification.
336
+ */
337
+ private applySampling;
300
338
  /**
301
339
  * Attach the only layer as the bound framebuffer's colour: a scene capture's target, drawn into
302
340
  * by `captureScene`. Engine-internal, as `bind` is.
@@ -304,5 +342,17 @@ export declare class SurfaceTexture {
304
342
  attachColor(gl: WebGL2RenderingContext): void;
305
343
  /** Bind to a unit for sampling. Engine-internal: the renderer owns unit assignment. */
306
344
  bind(gl: WebGL2RenderingContext, unit: number): void;
345
+ /**
346
+ * Layer 0 as a plain 2D texture: what a `sampler2D` can be handed, which an array cannot. Copied
347
+ * the first time it is asked for, by a framebuffer blit into storage of the same format, and kept
348
+ * until `update` or `dispose`. Engine-internal: a surface overlay's atlas is bound where the
349
+ * lit stage's one 2D image slot is (`surfaceOverlay.ts`).
350
+ *
351
+ * **Null for blocks**, which no framebuffer can read; the caller says so. What it costs is a copy
352
+ * of level 0 in memory beside the array, and the bindings of both framebuffer targets, which are
353
+ * read back before the blit and put back after it — a sync query, paid once per image.
354
+ */
355
+ flatLayer(gl: WebGL2RenderingContext): WebGLTexture | null;
356
+ private dropFlat;
307
357
  dispose(gl: WebGL2RenderingContext): void;
308
358
  }
@@ -1,5 +1,5 @@
1
1
  import { layerSize, refuseArrayUpdate, sourceSize } from './textureSource.js';
2
- import { compressedLayers, planBlocks, refuseBlockUpdate } from './compressedSource.js';
2
+ import { compressedLayers, isCompressedSource, planBlocks, refuseBlockUpdate, } from './compressedSource.js';
3
3
  import { uploadCompressedArray } from './glCompressed.js';
4
4
  import { SURFACE_EFFECT_TEXELS, packSurfaceEffects } from './surfaceEffects.js';
5
5
  import { isLightmapTexels } from './lightmap.js';
@@ -25,6 +25,15 @@ export class SurfaceTexture {
25
25
  effectsTable = null;
26
26
  /** The block format a compressed texture holds, or null for one uploaded from images. */
27
27
  blockFormat;
28
+ /** How the texture is sampled, kept so blocks swapped in by `update` are sampled the same way. */
29
+ sampling;
30
+ /** Level 0's size, which `flatLayer` copies; kept as `update` changes it. */
31
+ width = 0;
32
+ height = 0;
33
+ /** The texture's storage format, which `flatLayer`'s copy matches so the copy converts nothing. */
34
+ storage;
35
+ /** Layer 0 as a plain 2D texture, made by `flatLayer` and dropped when the pixels change. */
36
+ flat = null;
28
37
  /**
29
38
  * One image, or an array of images that must share one size (`layerSize` refuses otherwise).
30
39
  * Uploaded unflipped either way; see the constructor body for why. Or blocks, uploaded as they are
@@ -51,6 +60,9 @@ export class SurfaceTexture {
51
60
  this.layers = 1;
52
61
  this.blockFormat = null;
53
62
  this.mipmapped = false;
63
+ this.width = capture.width;
64
+ this.height = capture.height;
65
+ this.storage = capture.float ? gl.RGBA16F : gl.RGBA8;
54
66
  gl.texStorage3D(gl.TEXTURE_2D_ARRAY, 1, capture.float ? gl.RGBA16F : gl.RGBA8, capture.width, capture.height, 1);
55
67
  }
56
68
  else if (baked !== null) {
@@ -58,6 +70,9 @@ export class SurfaceTexture {
58
70
  this.layers = baked.layers.length;
59
71
  this.blockFormat = null;
60
72
  this.mipmapped = false;
73
+ this.width = baked.width;
74
+ this.height = baked.height;
75
+ this.storage = gl.RGBA8;
61
76
  /* A typed array refuses either flag set, so both are put down here rather than assumed. */
62
77
  gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
63
78
  gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
@@ -73,6 +88,7 @@ export class SurfaceTexture {
73
88
  `generateMipmap` cannot write a compressed format. */
74
89
  this.layers = blocks.length;
75
90
  this.blockFormat = blocks[0].format;
91
+ this.storage = gl.NONE;
76
92
  this.mipmapped = (options.mipmap ?? true) && plan.levels > 1;
77
93
  uploadCompressedArray(gl, plan.name, blocks, this.mipmapped ? plan.levels : 1);
78
94
  }
@@ -81,25 +97,20 @@ export class SurfaceTexture {
81
97
  this.layers = sources.length;
82
98
  this.blockFormat = null;
83
99
  this.mipmapped = options.mipmap ?? true;
100
+ const { width, height } = layerSize(sources);
101
+ this.width = width;
102
+ this.height = height;
103
+ this.storage = this.srgb ? gl.SRGB8_ALPHA8 : gl.RGBA8;
84
104
  uploadImages(gl, sources, this.srgb);
85
105
  }
86
- gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_S, wrap);
87
- gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_T, wrap);
88
- /* Nearest where the caller said their pixels are the subject; see `filter`. The mip chain is
89
- still blended between levels, because that is minification and this option is about
90
- magnification. */
91
- const nearest = options.filter === 'nearest';
92
- gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MAG_FILTER, nearest ? gl.NEAREST : gl.LINEAR);
93
- gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MIN_FILTER, this.mipmapped
94
- ? nearest
95
- ? gl.NEAREST_MIPMAP_LINEAR
96
- : gl.LINEAR_MIPMAP_LINEAR
97
- : nearest
98
- ? gl.NEAREST
99
- : gl.LINEAR);
106
+ this.sampling = {
107
+ wrap,
108
+ nearest: options.filter === 'nearest',
109
+ anisotropy: options.anisotropy ?? DEFAULT_ANISOTROPY,
110
+ };
111
+ this.applySampling(gl);
100
112
  if (this.mipmapped && this.blockFormat === null)
101
113
  gl.generateMipmap(gl.TEXTURE_2D_ARRAY);
102
- applyAnisotropy(gl, options.anisotropy ?? DEFAULT_ANISOTROPY);
103
114
  gl.bindTexture(gl.TEXTURE_2D_ARRAY, null);
104
115
  /* Half floats, nearest, clamped: a table read by texelFetch, whose every whole number is under
105
116
  what a half holds exactly. */
@@ -137,12 +148,19 @@ export class SurfaceTexture {
137
148
  * Not a hot path: it re-uploads the whole image and regenerates the chain. A caller
138
149
  * doing this every frame at any size wants to know that it costs what it costs.
139
150
  */
140
- update(gl, source) {
151
+ update(gl, source, compressed = []) {
141
152
  if (this.texture === null)
142
153
  return;
143
154
  refuseArrayUpdate(this.layers);
155
+ if (isCompressedSource(source)) {
156
+ this.swapToBlocks(gl, source, compressed);
157
+ return;
158
+ }
144
159
  refuseBlockUpdate(this.blockFormat);
145
160
  const { width, height } = sourceSize(source);
161
+ this.width = width;
162
+ this.height = height;
163
+ this.dropFlat(gl);
146
164
  gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.texture);
147
165
  /* Unflipped, matching the constructor. See it for why, and for what it cost. */
148
166
  gl.texImage3D(gl.TEXTURE_2D_ARRAY, 0, this.srgb ? gl.SRGB8_ALPHA8 : gl.RGBA, width, height, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, source);
@@ -150,6 +168,48 @@ export class SurfaceTexture {
150
168
  gl.generateMipmap(gl.TEXTURE_2D_ARRAY);
151
169
  gl.bindTexture(gl.TEXTURE_2D_ARRAY, null);
152
170
  }
171
+ /**
172
+ * Blocks in place of the image, behind the same handle: **a new GL object** rather than the
173
+ * image's levels respecified, because a chain shorter than the image's would leave the levels
174
+ * past it allocated, and immutable storage could not be respecified at all. Sampled as the image
175
+ * was, and refused before anything changes where the context does not take the format.
176
+ */
177
+ swapToBlocks(gl, source, compressed) {
178
+ const plan = planBlocks([source], this.srgb, compressed);
179
+ const texture = gl.createTexture();
180
+ if (texture === null)
181
+ throw new Error('SurfaceTexture: createTexture failed');
182
+ this.dropFlat(gl);
183
+ if (this.texture !== null)
184
+ gl.deleteTexture(this.texture);
185
+ this.texture = texture;
186
+ this.blockFormat = source.format;
187
+ this.width = source.width;
188
+ this.height = source.height;
189
+ gl.bindTexture(gl.TEXTURE_2D_ARRAY, texture);
190
+ uploadCompressedArray(gl, plan.name, [source], this.mipmapped ? plan.levels : 1);
191
+ this.applySampling(gl);
192
+ gl.bindTexture(gl.TEXTURE_2D_ARRAY, null);
193
+ }
194
+ /**
195
+ * Wrap, filters and anisotropy on the bound texture. Nearest where the caller said their pixels
196
+ * are the subject; see `filter`. The mip chain is still blended between levels, because that is
197
+ * minification and this option is about magnification.
198
+ */
199
+ applySampling(gl) {
200
+ const { wrap, nearest, anisotropy } = this.sampling;
201
+ gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_S, wrap);
202
+ gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_T, wrap);
203
+ gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MAG_FILTER, nearest ? gl.NEAREST : gl.LINEAR);
204
+ gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MIN_FILTER, this.mipmapped
205
+ ? nearest
206
+ ? gl.NEAREST_MIPMAP_LINEAR
207
+ : gl.LINEAR_MIPMAP_LINEAR
208
+ : nearest
209
+ ? gl.NEAREST
210
+ : gl.LINEAR);
211
+ applyAnisotropy(gl, anisotropy);
212
+ }
153
213
  /**
154
214
  * Attach the only layer as the bound framebuffer's colour: a scene capture's target, drawn into
155
215
  * by `captureScene`. Engine-internal, as `bind` is.
@@ -162,9 +222,57 @@ export class SurfaceTexture {
162
222
  gl.activeTexture(gl.TEXTURE0 + unit);
163
223
  gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.texture);
164
224
  }
225
+ /**
226
+ * Layer 0 as a plain 2D texture: what a `sampler2D` can be handed, which an array cannot. Copied
227
+ * the first time it is asked for, by a framebuffer blit into storage of the same format, and kept
228
+ * until `update` or `dispose`. Engine-internal: a surface overlay's atlas is bound where the
229
+ * lit stage's one 2D image slot is (`surfaceOverlay.ts`).
230
+ *
231
+ * **Null for blocks**, which no framebuffer can read; the caller says so. What it costs is a copy
232
+ * of level 0 in memory beside the array, and the bindings of both framebuffer targets, which are
233
+ * read back before the blit and put back after it — a sync query, paid once per image.
234
+ */
235
+ flatLayer(gl) {
236
+ if (this.texture === null || this.blockFormat !== null)
237
+ return null;
238
+ if (this.flat !== null)
239
+ return this.flat;
240
+ const flat = gl.createTexture();
241
+ const read = gl.createFramebuffer();
242
+ const draw = gl.createFramebuffer();
243
+ if (flat === null || read === null || draw === null)
244
+ return null;
245
+ gl.bindTexture(gl.TEXTURE_2D, flat);
246
+ gl.texStorage2D(gl.TEXTURE_2D, 1, this.storage, this.width, this.height);
247
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
248
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
249
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
250
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
251
+ gl.bindTexture(gl.TEXTURE_2D, null);
252
+ const heldRead = gl.getParameter(gl.READ_FRAMEBUFFER_BINDING);
253
+ const heldDraw = gl.getParameter(gl.DRAW_FRAMEBUFFER_BINDING);
254
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, read);
255
+ gl.framebufferTextureLayer(gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, this.texture, 0, 0);
256
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, draw);
257
+ gl.framebufferTexture2D(gl.DRAW_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, flat, 0);
258
+ const [w, h] = [this.width, this.height];
259
+ gl.blitFramebuffer(0, 0, w, h, 0, 0, w, h, gl.COLOR_BUFFER_BIT, gl.NEAREST);
260
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, heldRead);
261
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, heldDraw);
262
+ gl.deleteFramebuffer(read);
263
+ gl.deleteFramebuffer(draw);
264
+ this.flat = flat;
265
+ return flat;
266
+ }
267
+ dropFlat(gl) {
268
+ if (this.flat !== null)
269
+ gl.deleteTexture(this.flat);
270
+ this.flat = null;
271
+ }
165
272
  dispose(gl) {
166
273
  if (this.texture === null)
167
274
  return;
275
+ this.dropFlat(gl);
168
276
  gl.deleteTexture(this.texture);
169
277
  this.texture = null;
170
278
  if (this.effectsTable !== null)
@@ -248,12 +248,26 @@ export declare class TemporalHistory {
248
248
  private picture;
249
249
  private width;
250
250
  private height;
251
+ /** Whether this frame of the renderer's has opened one of the sequence, and what it answered. */
252
+ private opened;
253
+ private answer;
254
+ /**
255
+ * A frame of the renderer's began: the next `openFrame` opens a frame of the sequence. Called at
256
+ * `beginFrame`, by both backends.
257
+ */
258
+ nextFrame(): void;
251
259
  /**
252
260
  * Open a frame at this target size, and answer whether the history may be sampled.
253
261
  *
254
262
  * **The jitter advances whether or not it may be.** Holding the sequence still on the frames a
255
263
  * resize or a cut discarded would sample the same position twice running, which is one sample
256
264
  * and not two.
265
+ *
266
+ * **Once a frame, however many times it is asked.** A renderer asks where the scene's camera
267
+ * arrives, and a frame binds that camera again after a scene capture or anything else that bound
268
+ * another; each asking opened a frame of its own, so the sequence stepped twice in such a frame
269
+ * and the edges of the picture swam. Asked again, it gives the answer it gave. What it gives up:
270
+ * a size changed between two asks in one frame is seen at the next frame, not at once.
257
271
  */
258
272
  openFrame(width: number, height: number): boolean;
259
273
  /**
@@ -355,20 +355,40 @@ export class TemporalHistory {
355
355
  picture = false;
356
356
  width = 0;
357
357
  height = 0;
358
+ /** Whether this frame of the renderer's has opened one of the sequence, and what it answered. */
359
+ opened = false;
360
+ answer = false;
361
+ /**
362
+ * A frame of the renderer's began: the next `openFrame` opens a frame of the sequence. Called at
363
+ * `beginFrame`, by both backends.
364
+ */
365
+ nextFrame() {
366
+ this.opened = false;
367
+ }
358
368
  /**
359
369
  * Open a frame at this target size, and answer whether the history may be sampled.
360
370
  *
361
371
  * **The jitter advances whether or not it may be.** Holding the sequence still on the frames a
362
372
  * resize or a cut discarded would sample the same position twice running, which is one sample
363
373
  * and not two.
374
+ *
375
+ * **Once a frame, however many times it is asked.** A renderer asks where the scene's camera
376
+ * arrives, and a frame binds that camera again after a scene capture or anything else that bound
377
+ * another; each asking opened a frame of its own, so the sequence stepped twice in such a frame
378
+ * and the edges of the picture swam. Asked again, it gives the answer it gave. What it gives up:
379
+ * a size changed between two asks in one frame is seen at the next frame, not at once.
364
380
  */
365
381
  openFrame(width, height) {
382
+ if (this.opened)
383
+ return this.answer;
384
+ this.opened = true;
366
385
  this.frameIndex++;
367
386
  const usable = this.picture && width === this.width && height === this.height;
368
387
  if (!usable)
369
388
  this.picture = false;
370
389
  this.width = width;
371
390
  this.height = height;
391
+ this.answer = usable;
372
392
  return usable;
373
393
  }
374
394
  /**
@@ -38,6 +38,12 @@ export declare const ABSENT_ATTRIBUTE: Readonly<Record<string, readonly number[]
38
38
  * value that produces a picture. `flat.ts` reads `0` as none, `1` as sRGB and `2` as ACES.
39
39
  */
40
40
  export declare const OUTPUT_TRANSFORM_CODE: Readonly<Record<string, number>>;
41
+ /**
42
+ * The code a **forward** pass grades with: the transform's own, except `filmic`, which only the
43
+ * composite carries the constants for, and which a pass grading itself therefore draws as `aces`.
44
+ * Both backends' `gradeCode` read this, so neither can hand a forward stage a code it lacks.
45
+ */
46
+ export declare function forwardTransformCode(transform: string): number;
41
47
  /**
42
48
  * How far the speed rush reaches, and how far a motion-blurred pixel may travel, both in UV.
43
49
  *
@@ -46,3 +52,14 @@ export declare const OUTPUT_TRANSFORM_CODE: Readonly<Record<string, number>>;
46
52
  */
47
53
  export declare const RUSH_REACH_UV = 0.012;
48
54
  export declare const MOTION_BLUR_MAX_UV = 0.03;
55
+ /**
56
+ * A longest smear as `setCameraMotionBlur` takes it: a share of the frame, above zero and at most
57
+ * half, and the default where the number is not one. Shared, so both backends cap one way.
58
+ */
59
+ export declare function clampMotionShare(share: number): number;
60
+ /**
61
+ * Said once where a draw names where it was last frame and the frame blurs by motion, but keeps no
62
+ * motion target to blur that draw by: each object's own motion is drawn only under a
63
+ * reconstruction on WebGPU, and anywhere else the camera's motion alone smears the frame.
64
+ */
65
+ export declare const OBJECT_BLUR_UNKEPT: string;
@@ -110,7 +110,16 @@ export const OUTPUT_TRANSFORM_CODE = {
110
110
  srgb: 1,
111
111
  aces: 2,
112
112
  shoulder: 3,
113
+ filmic: 4,
113
114
  };
115
+ /**
116
+ * The code a **forward** pass grades with: the transform's own, except `filmic`, which only the
117
+ * composite carries the constants for, and which a pass grading itself therefore draws as `aces`.
118
+ * Both backends' `gradeCode` read this, so neither can hand a forward stage a code it lacks.
119
+ */
120
+ export function forwardTransformCode(transform) {
121
+ return transform === 'filmic' ? 2 : (OUTPUT_TRANSFORM_CODE[transform] ?? 0);
122
+ }
114
123
  /**
115
124
  * How far the speed rush reaches, and how far a motion-blurred pixel may travel, both in UV.
116
125
  *
@@ -119,3 +128,18 @@ export const OUTPUT_TRANSFORM_CODE = {
119
128
  */
120
129
  export const RUSH_REACH_UV = 0.012;
121
130
  export const MOTION_BLUR_MAX_UV = 0.03;
131
+ /**
132
+ * A longest smear as `setCameraMotionBlur` takes it: a share of the frame, above zero and at most
133
+ * half, and the default where the number is not one. Shared, so both backends cap one way.
134
+ */
135
+ export function clampMotionShare(share) {
136
+ return Number.isFinite(share) && share > 0 ? Math.min(share, 0.5) : MOTION_BLUR_MAX_UV;
137
+ }
138
+ /**
139
+ * Said once where a draw names where it was last frame and the frame blurs by motion, but keeps no
140
+ * motion target to blur that draw by: each object's own motion is drawn only under a
141
+ * reconstruction on WebGPU, and anywhere else the camera's motion alone smears the frame.
142
+ */
143
+ export const OBJECT_BLUR_UNKEPT = "[driftengine] motion blur follows each draw's own motion only under a reconstruction " +
144
+ "(DriftTR, WebGPU), which draws the motion target it reads; here the camera's motion alone " +
145
+ 'blurs the frame, and a moving object is smeared only as the camera moved past it.';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@driftengine/core",
3
- "version": "4.8.7",
3
+ "version": "4.10.0",
4
4
  "description": "The runtime of DriftEngine, a WebGPU game engine with a WebGL2 fallback: renderer, loop, input, cameras, scene graph and physics",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -29,8 +29,8 @@
29
29
  ],
30
30
  "sideEffects": false,
31
31
  "dependencies": {
32
- "@driftengine/drft": "4.8.7",
33
- "@driftengine/physics": "4.8.7",
32
+ "@driftengine/drft": "4.10.0",
33
+ "@driftengine/physics": "4.10.0",
34
34
  "gl-matrix": "^3.4.3"
35
35
  },
36
36
  "author": "Drift Technologies",
@@ -67,8 +67,8 @@
67
67
  "prepack": "node ../../scripts/build.mjs @driftengine/core"
68
68
  },
69
69
  "devDependencies": {
70
- "@driftengine/nav": "4.8.7",
71
- "@driftengine/network": "4.8.7",
72
- "@driftengine/texture": "4.8.7"
70
+ "@driftengine/nav": "4.10.0",
71
+ "@driftengine/network": "4.10.0",
72
+ "@driftengine/texture": "4.10.0"
73
73
  }
74
74
  }
package/src/index.ts CHANGED
@@ -454,6 +454,8 @@ export type { ReflectionOptions, ReflectiveSample } from './render/gi/reflection
454
454
  */
455
455
  export { GRADE_PLACEHOLDER_SIZE, MAX_GRADE_SIZE, identityGradeLut } from './render/colourGrade.ts';
456
456
  export type { ColourGradeLut } from './render/colourGrade.ts';
457
+ export { DEFAULT_FILMIC_CURVE } from './render/filmicCurve.ts';
458
+ export type { FilmicCurve } from './render/filmicCurve.ts';
457
459
  /**
458
460
  * How bloom answers past its threshold — a ramp in place of the subtraction, and a tint for each
459
461
  * of the pyramid's `BLOOM_LEVELS` octaves — the third argument of `setBloom`.
@@ -513,6 +515,20 @@ export type {
513
515
  SurfaceModel,
514
516
  SurfaceModelKind,
515
517
  } from './render/surfaceModel.ts';
518
+ /*
519
+ * What a draw lays over its surface beyond its material — a rim of light, a dissolve, wrinkle
520
+ * normals by region — set per draw with `setSurfaceOverlay`, its images regions of one atlas.
521
+ */
522
+ /* Every opaque lit surface reflecting the frame by its own material: `screenSpaceReflections`. */
523
+ export { DEFAULT_FRAME_REFLECTIONS } from './render/frameReflections.ts';
524
+ export type { FrameReflections } from './render/frameReflections.ts';
525
+ export type {
526
+ OverlayRegion,
527
+ SurfaceDissolve,
528
+ SurfaceOverlay,
529
+ SurfaceRim,
530
+ SurfaceWrinkle,
531
+ } from './render/surfaceOverlay.ts';
516
532
  /*
517
533
  * A baked lightmap: the page a consumer decoded, which `createLightmap` uploads as a lightmapped
518
534
  * material's `modelMap`, and the region of it a material or an instance reads.
@@ -552,6 +568,10 @@ export type {
552
568
  ShadowCasterSink,
553
569
  ShadowCasters,
554
570
  } from './render/shadowCasters.ts';
571
+ /* What `createStaticDraws` hands back, so a consumer can hold one in a field it declares. */
572
+ export type { StaticDrawsHandle } from './render/staticDraws.ts';
573
+ /* A bone animation's clip as a caller hands it over, and the handle `createBoneAnimation` returns. */
574
+ export type { BoneAnimationClip, BoneAnimationHandle } from './render/boneAnimation.ts';
555
575
  export type { Atmosphere, UnderwaterAtmosphere } from './render/atmosphere.ts';
556
576
  /*
557
577
  * Reading an image's texels back exactly, which no canvas and no `VideoFrame` will do.
@@ -644,12 +664,14 @@ export { MAX_CLUSTERED_LIGHTS } from './render/clusteredLights.ts';
644
664
  export { SurfaceTexture } from './render/surfaceTexture.ts';
645
665
  export type { SurfaceMaterial, SurfaceTextureOptions } from './render/surfaceTexture.ts';
646
666
  /*
647
- * BC compressed textures: the source `createSurfaceTexture` takes as blocks, and the one question to
648
- * ask before handing one over — whether this device takes its format (`renderer.compressedFormats`).
667
+ * Compressed textures — BC, ETC2 and EAC, ASTC: the source `createSurfaceTexture` takes as blocks,
668
+ * the one question to ask before handing one over — whether this device takes its format
669
+ * (`renderer.compressedFormats`) — and the bytes a level of it holds, for a reader checking a file.
649
670
  */
650
671
  export {
651
672
  compressedFormatName,
652
673
  isCompressedSource,
674
+ levelBytes,
653
675
  uploadsCompressed,
654
676
  } from './render/compressedSource.ts';
655
677
  export type {
@@ -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.
@@ -94,14 +94,14 @@ export interface AreaLightSource {
94
94
  * others. **Read only where the rectangle is shaded through the froxel table** — the ones past
95
95
  * `maxAreaLights` with `clusteredLights` on; the fixed four light everything they face, as they
96
96
  * always have. Absent, it is where the rectangle's irradiance on its axis falls to a thousandth
97
- * of a scene unit, and never inside the rectangle itself. Unreal's `AttenuationRadius`, in metres.
97
+ * of a scene unit, and never inside the rectangle itself. An attenuation radius, in metres.
98
98
  */
99
99
  range?: number;
100
100
  /**
101
101
  * Barn doors: a flap hinged at each of the rectangle's four edges, standing this many degrees from
102
102
  * its normal. 90, or absent, folds them flat and they hide nothing; smaller closes them, so the
103
103
  * light narrows to the opening they leave — a rectangle at 50° throws a beam rather than filling
104
- * its hemisphere. Unreal's `BarnDoorAngle`, whose default of 88° is nearly flat.
104
+ * its hemisphere. 88° is the usual default, nearly flat.
105
105
  *
106
106
  * What a fragment sees past the doors is the part of the rectangle no door's tip hides, one axis
107
107
  * at a time, and the rectangle then shades as that smaller rectangle, its highlight included.
@@ -111,7 +111,7 @@ export interface AreaLightSource {
111
111
  * rectangle's back face has none.
112
112
  */
113
113
  barnDoorAngle?: number;
114
- /** The barn doors' length in metres. Absent, 0.2: Unreal's default of 20 cm. */
114
+ /** The barn doors' length in metres. Absent, 0.2: the usual default of 20 cm. */
115
115
  barnDoorLength?: number;
116
116
  }
117
117
 
@@ -150,7 +150,7 @@ export function createAreaLightBuffer(capacity: number = MAX_AREA_LIGHTS): AreaL
150
150
  };
151
151
  }
152
152
 
153
- /** Unreal's default barn door length, in metres. See `AreaLightSource.barnDoorLength`. */
153
+ /** The usual default barn door length, in metres. See `AreaLightSource.barnDoorLength`. */
154
154
  export const DEFAULT_BARN_DOOR_LENGTH = 0.2;
155
155
 
156
156
  /**
@@ -47,6 +47,19 @@ export interface Atmosphere {
47
47
  /** Linear mode only: where haze begins, and where it is total. */
48
48
  fogNear?: number;
49
49
  fogFar?: number;
50
+ /**
51
+ * The medium only: how far along the view ray it begins, in metres. Absent or 0 is at the
52
+ * camera, as every world before it was drawn.
53
+ *
54
+ * The air nearer than this is perfectly clear, and what lies beyond is fogged by the air past
55
+ * this point alone — so a faint, bright haze can lie over a distant crowd while the figures a
56
+ * few metres from the camera stay sharp, which no density can do when the medium starts at the
57
+ * eye: there it veils the whole frame, lifting the shadows and draining the colour before it
58
+ * reaches anything far enough to want it. Unlike `linear`'s `fogNear`, nothing past it is a
59
+ * ramp: it is the same extinction, integrated from further out. **What it gives up** is the air
60
+ * between the camera and the start, which a real medium has; that is the point of it.
61
+ */
62
+ fogStart?: number;
50
63
  underwater: UnderwaterAtmosphere | null;
51
64
  }
52
65
 
@@ -132,7 +145,15 @@ export function resolveAtmosphere(
132
145
  out.fogHeightFalloff = atmosphere.fogHeightFalloff;
133
146
  out.fogEyeY = cameraY;
134
147
  out.fogMode = atmosphere.fogMode === 'linear' ? 1 : 0;
135
- out.fogNear = atmosphere.fogNear ?? 0;
148
+ /*
149
+ * **One slot, two meanings, and never both**: the ramp's near distance in linear mode, and in
150
+ * the medium where it begins (`fogStart`), which the medium never read before — so a world that
151
+ * left a `fogNear` on an exponential fog starts nothing, and draws exactly as it did.
152
+ */
153
+ out.fogNear =
154
+ atmosphere.fogMode === 'linear'
155
+ ? (atmosphere.fogNear ?? 0)
156
+ : Math.max(0, Number.isFinite(atmosphere.fogStart) ? (atmosphere.fogStart ?? 0) : 0);
136
157
  out.fogFar = atmosphere.fogFar ?? 1;
137
158
  /*
138
159
  * The air medium stands in when nothing is submerged. The shaders multiply by the factor