@driftengine/core 4.8.6 → 4.9.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 (253) hide show
  1. package/CHANGELOG.json +132 -0
  2. package/README.md +1 -1
  3. package/dist/index.d.ts +7 -2
  4. package/dist/index.js +8 -1
  5. package/dist/render/anisotropicLobe.d.ts +10 -0
  6. package/dist/render/anisotropicLobe.js +19 -0
  7. package/dist/render/areaLights.d.ts +35 -0
  8. package/dist/render/areaLights.js +35 -0
  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 +41 -3
  12. package/dist/render/backend/createRenderer.d.ts +6 -0
  13. package/dist/render/backend/createRenderer.js +4 -1
  14. package/dist/render/backend/webgl2/renderer.d.ts +140 -16
  15. package/dist/render/backend/webgl2/renderer.js +436 -44
  16. package/dist/render/backend/webgl2/sceneCaptureTarget.d.ts +29 -0
  17. package/dist/render/backend/webgl2/sceneCaptureTarget.js +96 -0
  18. package/dist/render/backend/webgpu/device.d.ts +7 -1
  19. package/dist/render/backend/webgpu/device.js +31 -3
  20. package/dist/render/backend/webgpu/flatPass.d.ts +16 -1
  21. package/dist/render/backend/webgpu/flatPass.js +58 -26
  22. package/dist/render/backend/webgpu/particlePass.js +2 -1
  23. package/dist/render/backend/webgpu/postPass.d.ts +51 -9
  24. package/dist/render/backend/webgpu/postPass.js +16 -2
  25. package/dist/render/backend/webgpu/reflectionSurfacePass.d.ts +30 -0
  26. package/dist/render/backend/webgpu/reflectionSurfacePass.js +100 -0
  27. package/dist/render/backend/webgpu/renderer.d.ts +166 -3
  28. package/dist/render/backend/webgpu/renderer.js +738 -83
  29. package/dist/render/backend/webgpu/sceneCaptureTarget.d.ts +24 -0
  30. package/dist/render/backend/webgpu/sceneCaptureTarget.js +59 -0
  31. package/dist/render/backend/webgpu/shaders/clusterBin.wgsl.d.ts +1 -1
  32. package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +4 -1
  33. package/dist/render/backend/webgpu/surfaceTexturePass.js +34 -8
  34. package/dist/render/backend/webgpu/waterPass.d.ts +1 -1
  35. package/dist/render/clusteredLights.d.ts +23 -5
  36. package/dist/render/clusteredLights.js +35 -8
  37. package/dist/render/displayRange.d.ts +31 -0
  38. package/dist/render/displayRange.js +47 -0
  39. package/dist/render/driftLight/bake.d.ts +2 -0
  40. package/dist/render/driftLight/bake.js +1 -1
  41. package/dist/render/driftLight/falloff.d.ts +4 -2
  42. package/dist/render/driftLight/falloff.js +8 -2
  43. package/dist/render/filmicCurve.d.ts +74 -0
  44. package/dist/render/filmicCurve.js +203 -0
  45. package/dist/render/fog.d.ts +4 -1
  46. package/dist/render/fog.js +12 -2
  47. package/dist/render/frame/resources.d.ts +2 -2
  48. package/dist/render/frame/resources.js +6 -0
  49. package/dist/render/frameReflections.d.ts +40 -0
  50. package/dist/render/frameReflections.js +48 -0
  51. package/dist/render/gi/bounceLights.d.ts +3 -2
  52. package/dist/render/gi/bounceLights.js +8 -3
  53. package/dist/render/instanceCull.js +5 -0
  54. package/dist/render/instances.d.ts +19 -5
  55. package/dist/render/instances.js +9 -3
  56. package/dist/render/lightBudget.d.ts +12 -1
  57. package/dist/render/lightBudget.js +23 -3
  58. package/dist/render/lightmap.d.ts +23 -9
  59. package/dist/render/lightmap.js +40 -18
  60. package/dist/render/particlePool.d.ts +21 -0
  61. package/dist/render/particlePool.js +7 -0
  62. package/dist/render/pass.d.ts +49 -0
  63. package/dist/render/pointLightSelection.d.ts +13 -0
  64. package/dist/render/pointLightSelection.js +3 -1
  65. package/dist/render/reflectionSurfaceTargets.d.ts +28 -0
  66. package/dist/render/reflectionSurfaceTargets.js +99 -0
  67. package/dist/render/renderQuality.d.ts +47 -15
  68. package/dist/render/renderQuality.js +4 -0
  69. package/dist/render/rendererHarness.js +3 -0
  70. package/dist/render/rgb9e5.d.ts +22 -0
  71. package/dist/render/rgb9e5.js +47 -0
  72. package/dist/render/sceneCapture.d.ts +29 -0
  73. package/dist/render/sceneCapture.js +36 -0
  74. package/dist/render/sceneTarget.d.ts +5 -0
  75. package/dist/render/sceneTarget.js +24 -1
  76. package/dist/render/screenSpaceReflection.d.ts +32 -1
  77. package/dist/render/screenSpaceReflection.js +14 -0
  78. package/dist/render/shaders/arcane.d.ts +1 -1
  79. package/dist/render/shaders/bolt.d.ts +1 -1
  80. package/dist/render/shaders/bolt.js +7 -1
  81. package/dist/render/shaders/caustics.d.ts +1 -1
  82. package/dist/render/shaders/depth.js +11 -0
  83. package/dist/render/shaders/film.d.ts +1 -1
  84. package/dist/render/shaders/filmLook.js +2 -1
  85. package/dist/render/shaders/fire.d.ts +1 -1
  86. package/dist/render/shaders/flat/anisotropic.d.ts +1 -1
  87. package/dist/render/shaders/flat/anisotropic.js +37 -3
  88. package/dist/render/shaders/flat/areaLight.d.ts +1 -1
  89. package/dist/render/shaders/flat/areaLight.js +46 -0
  90. package/dist/render/shaders/flat/glassTint.d.ts +3 -2
  91. package/dist/render/shaders/flat/glassTint.js +5 -1
  92. package/dist/render/shaders/flat/index.d.ts +15 -0
  93. package/dist/render/shaders/flat/index.js +22 -2
  94. package/dist/render/shaders/flat/lobes.d.ts +6 -0
  95. package/dist/render/shaders/flat/lobes.js +49 -43
  96. package/dist/render/shaders/flat/main.d.ts +1 -1
  97. package/dist/render/shaders/flat/main.js +85 -22
  98. package/dist/render/shaders/flat/models.d.ts +5 -2
  99. package/dist/render/shaders/flat/models.js +100 -16
  100. package/dist/render/shaders/flat/overlay.d.ts +16 -0
  101. package/dist/render/shaders/flat/overlay.js +117 -0
  102. package/dist/render/shaders/flat/pointShadow.js +1 -1
  103. package/dist/render/shaders/flat/preamble.js +10 -3
  104. package/dist/render/shaders/flat/reflectionSurface.d.ts +21 -0
  105. package/dist/render/shaders/flat/reflectionSurface.js +61 -0
  106. package/dist/render/shaders/fog.d.ts +1 -1
  107. package/dist/render/shaders/fog.js +9 -2
  108. package/dist/render/shaders/fringe.d.ts +29 -0
  109. package/dist/render/shaders/fringe.js +55 -0
  110. package/dist/render/shaders/generated/arcane.wgsl.d.ts +1 -1
  111. package/dist/render/shaders/generated/arcane.wgsl.js +1 -1
  112. package/dist/render/shaders/generated/bolt.wgsl.d.ts +1 -1
  113. package/dist/render/shaders/generated/bolt.wgsl.js +1 -1
  114. package/dist/render/shaders/generated/caustics.wgsl.d.ts +1 -1
  115. package/dist/render/shaders/generated/caustics.wgsl.js +1 -1
  116. package/dist/render/shaders/generated/depth.wgsl.d.ts +4 -4
  117. package/dist/render/shaders/generated/depth.wgsl.js +4 -4
  118. package/dist/render/shaders/generated/film.wgsl.d.ts +1 -1
  119. package/dist/render/shaders/generated/film.wgsl.js +1 -1
  120. package/dist/render/shaders/generated/fire.wgsl.d.ts +1 -1
  121. package/dist/render/shaders/generated/fire.wgsl.js +1 -1
  122. package/dist/render/shaders/generated/flat.wgsl.d.ts +192 -16
  123. package/dist/render/shaders/generated/flat.wgsl.js +230 -54
  124. package/dist/render/shaders/generated/line.wgsl.d.ts +1 -1
  125. package/dist/render/shaders/generated/line.wgsl.js +1 -1
  126. package/dist/render/shaders/generated/particle.wgsl.d.ts +4 -4
  127. package/dist/render/shaders/generated/particle.wgsl.js +4 -4
  128. package/dist/render/shaders/generated/rush.wgsl.d.ts +56 -26
  129. package/dist/render/shaders/generated/rush.wgsl.js +56 -26
  130. package/dist/render/shaders/generated/scatter.wgsl.d.ts +1 -1
  131. package/dist/render/shaders/generated/scatter.wgsl.js +1 -1
  132. package/dist/render/shaders/generated/smoke.wgsl.d.ts +1 -1
  133. package/dist/render/shaders/generated/smoke.wgsl.js +1 -1
  134. package/dist/render/shaders/generated/ssrResolve.wgsl.d.ts +29 -0
  135. package/dist/render/shaders/generated/ssrResolve.wgsl.js +29 -0
  136. package/dist/render/shaders/generated/ssrTrace.wgsl.d.ts +32 -7
  137. package/dist/render/shaders/generated/ssrTrace.wgsl.js +32 -7
  138. package/dist/render/shaders/generated/water.wgsl.d.ts +23 -16
  139. package/dist/render/shaders/generated/water.wgsl.js +23 -16
  140. package/dist/render/shaders/gi/probeBake.wgsl.js +9 -5
  141. package/dist/render/shaders/gpudriven/fog.wgsl.d.ts +2 -2
  142. package/dist/render/shaders/gpudriven/fog.wgsl.js +11 -3
  143. package/dist/render/shaders/line.d.ts +1 -1
  144. package/dist/render/shaders/particle.d.ts +3 -3
  145. package/dist/render/shaders/recon/motion.wgsl.d.ts +1 -1
  146. package/dist/render/shaders/recon/motion.wgsl.js +13 -2
  147. package/dist/render/shaders/rush.js +54 -11
  148. package/dist/render/shaders/scatter.d.ts +1 -1
  149. package/dist/render/shaders/smoke.d.ts +1 -1
  150. package/dist/render/shaders/ssrResolve.d.ts +17 -0
  151. package/dist/render/shaders/ssrResolve.js +51 -0
  152. package/dist/render/shaders/ssrTrace.js +46 -1
  153. package/dist/render/shaders/water.d.ts +1 -1
  154. package/dist/render/shaders/water.js +12 -3
  155. package/dist/render/skinScatter.d.ts +3 -1
  156. package/dist/render/skinScatter.js +3 -1
  157. package/dist/render/ssrPass.d.ts +16 -0
  158. package/dist/render/ssrPass.js +107 -1
  159. package/dist/render/surfaceModel.d.ts +29 -8
  160. package/dist/render/surfaceModel.js +33 -9
  161. package/dist/render/surfaceOverlay.d.ts +132 -0
  162. package/dist/render/surfaceOverlay.js +85 -0
  163. package/dist/render/surfaceTexture.d.ts +44 -1
  164. package/dist/render/surfaceTexture.js +96 -5
  165. package/dist/render/temporalAa.d.ts +14 -0
  166. package/dist/render/temporalAa.js +20 -0
  167. package/dist/render/vertexDefaults.d.ts +17 -0
  168. package/dist/render/vertexDefaults.js +24 -0
  169. package/package.json +6 -6
  170. package/src/index.ts +22 -2
  171. package/src/render/anisotropicLobe.ts +29 -0
  172. package/src/render/areaLights.ts +58 -0
  173. package/src/render/atmosphere.ts +22 -1
  174. package/src/render/backend/api.ts +58 -2
  175. package/src/render/backend/createRenderer.ts +18 -1
  176. package/src/render/backend/webgl2/renderer.ts +548 -50
  177. package/src/render/backend/webgl2/sceneCaptureTarget.ts +130 -0
  178. package/src/render/backend/webgpu/device.ts +42 -3
  179. package/src/render/backend/webgpu/flatPass.ts +63 -26
  180. package/src/render/backend/webgpu/particlePass.ts +2 -1
  181. package/src/render/backend/webgpu/postPass.ts +18 -0
  182. package/src/render/backend/webgpu/reflectionSurfacePass.ts +114 -0
  183. package/src/render/backend/webgpu/renderer.ts +927 -100
  184. package/src/render/backend/webgpu/sceneCaptureTarget.ts +63 -0
  185. package/src/render/backend/webgpu/surfaceTexturePass.ts +44 -14
  186. package/src/render/clusteredLights.ts +41 -8
  187. package/src/render/displayRange.ts +61 -0
  188. package/src/render/driftLight/bake.ts +3 -1
  189. package/src/render/driftLight/falloff.ts +13 -2
  190. package/src/render/filmicCurve.ts +229 -0
  191. package/src/render/fog.ts +19 -3
  192. package/src/render/frame/resources.ts +6 -0
  193. package/src/render/frameReflections.ts +72 -0
  194. package/src/render/gi/bounceLights.ts +12 -3
  195. package/src/render/instanceCull.ts +4 -0
  196. package/src/render/instances.ts +26 -6
  197. package/src/render/lightBudget.ts +40 -2
  198. package/src/render/lightmap.ts +64 -23
  199. package/src/render/particlePool.ts +23 -0
  200. package/src/render/pass.ts +53 -1
  201. package/src/render/pointLightSelection.ts +20 -1
  202. package/src/render/reflectionSurfaceTargets.ts +105 -0
  203. package/src/render/renderQuality.ts +52 -15
  204. package/src/render/rendererHarness.ts +3 -0
  205. package/src/render/rgb9e5.ts +52 -0
  206. package/src/render/sceneCapture.ts +56 -0
  207. package/src/render/sceneTarget.ts +30 -2
  208. package/src/render/screenSpaceReflection.ts +38 -1
  209. package/src/render/shaders/bolt.ts +7 -1
  210. package/src/render/shaders/depth.ts +11 -0
  211. package/src/render/shaders/filmLook.ts +2 -1
  212. package/src/render/shaders/flat/anisotropic.ts +37 -3
  213. package/src/render/shaders/flat/areaLight.ts +46 -0
  214. package/src/render/shaders/flat/glassTint.ts +13 -2
  215. package/src/render/shaders/flat/index.ts +37 -2
  216. package/src/render/shaders/flat/lobes.ts +50 -43
  217. package/src/render/shaders/flat/main.ts +85 -22
  218. package/src/render/shaders/flat/models.ts +100 -16
  219. package/src/render/shaders/flat/overlay.ts +117 -0
  220. package/src/render/shaders/flat/pointShadow.ts +1 -1
  221. package/src/render/shaders/flat/preamble.ts +10 -3
  222. package/src/render/shaders/flat/reflectionSurface.ts +61 -0
  223. package/src/render/shaders/fog.ts +9 -2
  224. package/src/render/shaders/fringe.ts +59 -0
  225. package/src/render/shaders/generated/arcane.wgsl.ts +1 -1
  226. package/src/render/shaders/generated/bolt.wgsl.ts +1 -1
  227. package/src/render/shaders/generated/caustics.wgsl.ts +1 -1
  228. package/src/render/shaders/generated/depth.wgsl.ts +4 -4
  229. package/src/render/shaders/generated/film.wgsl.ts +1 -1
  230. package/src/render/shaders/generated/fire.wgsl.ts +1 -1
  231. package/src/render/shaders/generated/flat.wgsl.ts +230 -54
  232. package/src/render/shaders/generated/line.wgsl.ts +1 -1
  233. package/src/render/shaders/generated/particle.wgsl.ts +4 -4
  234. package/src/render/shaders/generated/rush.wgsl.ts +56 -26
  235. package/src/render/shaders/generated/scatter.wgsl.ts +1 -1
  236. package/src/render/shaders/generated/smoke.wgsl.ts +1 -1
  237. package/src/render/shaders/generated/ssrResolve.wgsl.ts +30 -0
  238. package/src/render/shaders/generated/ssrTrace.wgsl.ts +32 -7
  239. package/src/render/shaders/generated/water.wgsl.ts +23 -16
  240. package/src/render/shaders/gi/probeBake.wgsl.ts +9 -5
  241. package/src/render/shaders/gpudriven/fog.wgsl.ts +11 -3
  242. package/src/render/shaders/recon/motion.wgsl.ts +13 -2
  243. package/src/render/shaders/rush.ts +54 -11
  244. package/src/render/shaders/ssrResolve.ts +52 -0
  245. package/src/render/shaders/ssrTrace.ts +46 -1
  246. package/src/render/shaders/water.ts +12 -3
  247. package/src/render/skinScatter.ts +3 -1
  248. package/src/render/ssrPass.ts +139 -1
  249. package/src/render/surfaceModel.ts +47 -10
  250. package/src/render/surfaceOverlay.ts +195 -0
  251. package/src/render/surfaceTexture.ts +136 -9
  252. package/src/render/temporalAa.ts +20 -0
  253. package/src/render/vertexDefaults.ts +28 -0
@@ -23,6 +23,8 @@ import type { SurfaceLayerEffect } from './surfaceEffects.ts';
23
23
  import type { CutoutMode } from './cutoutDither.ts';
24
24
  import { isLightmapTexels } from './lightmap.ts';
25
25
  import type { LightmapTexels } from './lightmap.ts';
26
+ import { isSceneCaptureTexels } from './sceneCapture.ts';
27
+ import type { SceneCaptureTexels } from './sceneCapture.ts';
26
28
 
27
29
  /**
28
30
  * How a texture behaves past its edges and between its texels.
@@ -172,6 +174,24 @@ export interface SurfaceMaterial<Texture = SurfaceTexture> {
172
174
  * that is not a colour.
173
175
  */
174
176
  modelMap?: Texture | null;
177
+ /**
178
+ * Whether a lamp's and the sun's highlight on this surface is GGX's own, `π · D · Vis · F · N·L`,
179
+ * as Unreal and this engine's skin and eye shade it, rather than the engine's lobe scaled to a
180
+ * peak of one. **False by default**, every surface as it was.
181
+ *
182
+ * The peak-normalised lobe is a look control: the specular attribute says how strong a highlight
183
+ * is and roughness how wide, apart. Physical, they are entangled as they are in a real surface —
184
+ * the peak is `1 / (4 α²)` of the light head-on, so a polished surface's highlight is many times
185
+ * the look's and a rough one's lower and broader — and the specular attribute is read as the
186
+ * reflectance at normal incidence, F0: Unreal's `0.08 × Specular`, 0.04 at its default. Fresnel
187
+ * then brightens every surface toward grazing, not only a metal. A rectangle's highlight is the
188
+ * lobe integrated over it with the attribute as F0 already, so it is unchanged either way.
189
+ *
190
+ * On the standard, lightmap and anisotropic models. Skin and the eye are physical already; hair
191
+ * keeps its own three lobes. **What it gives up**: the look's independence of strength and width,
192
+ * and an 8-bit frame clips the brighter peak without `hdrScene`.
193
+ */
194
+ physicalSpecular?: boolean;
175
195
  }
176
196
 
177
197
  export interface SurfaceTextureOptions {
@@ -273,6 +293,13 @@ export class SurfaceTexture {
273
293
  private effectsTable: WebGLTexture | null = null;
274
294
  /** The block format a compressed texture holds, or null for one uploaded from images. */
275
295
  private readonly blockFormat: BlockFormat | null;
296
+ /** Level 0's size, which `flatLayer` copies; kept as `update` changes it. */
297
+ private width = 0;
298
+ private height = 0;
299
+ /** The texture's storage format, which `flatLayer`'s copy matches so the copy converts nothing. */
300
+ private readonly storage: number;
301
+ /** Layer 0 as a plain 2D texture, made by `flatLayer` and dropped when the pixels change. */
302
+ private flat: WebGLTexture | null = null;
276
303
 
277
304
  /**
278
305
  * One image, or an array of images that must share one size (`layerSize` refuses otherwise).
@@ -281,14 +308,16 @@ export class SurfaceTexture {
281
308
  */
282
309
  constructor(
283
310
  gl: WebGL2RenderingContext,
284
- source: SurfaceSource | readonly SurfaceSource[] | LightmapTexels,
311
+ source: SurfaceSource | readonly SurfaceSource[] | LightmapTexels | SceneCaptureTexels,
285
312
  options: SurfaceTextureOptions = {},
286
313
  compressed: readonly CompressedTextureFormat[] = [],
287
314
  ) {
288
- /* A lightmap's page: half floats, uploaded as they are. See `lightmap.ts`. */
315
+ /* A scene capture's target: one empty layer the renderer draws into. See `sceneCapture.ts`. */
316
+ const capture = isSceneCaptureTexels(source) ? source : null;
317
+ /* A lightmap's page: two layers of bytes, uploaded as they are. See `lightmap.ts`. */
289
318
  const baked = isLightmapTexels(source) ? source : null;
290
319
  const listed = (
291
- baked !== null ? [] : Array.isArray(source) ? source : [source]
320
+ baked !== null || capture !== null ? [] : Array.isArray(source) ? source : [source]
292
321
  ) as readonly SurfaceSource[];
293
322
  const blocks = compressedLayers(listed);
294
323
  this.srgb = (options.colorSpace ?? 'linear') === 'srgb';
@@ -300,31 +329,68 @@ export class SurfaceTexture {
300
329
  const wrap = (options.wrap ?? 'repeat') === 'repeat' ? gl.REPEAT : gl.CLAMP_TO_EDGE;
301
330
  gl.bindTexture(gl.TEXTURE_2D_ARRAY, texture);
302
331
 
303
- if (baked !== null) {
332
+ if (capture !== null) {
333
+ /* Immutable storage, one level: a framebuffer attachment, and radiance read linear. */
334
+ this.layers = 1;
335
+ this.blockFormat = null;
336
+ this.mipmapped = false;
337
+ this.width = capture.width;
338
+ this.height = capture.height;
339
+ this.storage = capture.float ? gl.RGBA16F : gl.RGBA8;
340
+ gl.texStorage3D(
341
+ gl.TEXTURE_2D_ARRAY,
342
+ 1,
343
+ capture.float ? gl.RGBA16F : gl.RGBA8,
344
+ capture.width,
345
+ capture.height,
346
+ 1,
347
+ );
348
+ } else if (baked !== null) {
304
349
  /* No chain: a page is read at its own resolution, and a level of it would blend regions. */
305
- this.layers = baked.layers;
350
+ this.layers = baked.layers.length;
306
351
  this.blockFormat = null;
307
352
  this.mipmapped = false;
353
+ this.width = baked.width;
354
+ this.height = baked.height;
355
+ this.storage = gl.RGBA8;
308
356
  /* A typed array refuses either flag set, so both are put down here rather than assumed. */
309
357
  gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
310
358
  gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
359
+ /* Bytes, and read by `texelFetch` alone: the shader decodes and filters. See `lightmap.ts`. */
311
360
  gl.texImage3D(
312
361
  gl.TEXTURE_2D_ARRAY,
313
362
  0,
314
- gl.RGBA16F,
363
+ gl.RGBA8,
315
364
  baked.width,
316
365
  baked.height,
317
- baked.layers,
366
+ this.layers,
318
367
  0,
319
368
  gl.RGBA,
320
- gl.HALF_FLOAT,
321
- baked.texels,
369
+ gl.UNSIGNED_BYTE,
370
+ null,
322
371
  );
372
+ /* A layer at a time, so a page's arrays are uploaded where they lie rather than joined. */
373
+ for (let layer = 0; layer < this.layers; layer++) {
374
+ gl.texSubImage3D(
375
+ gl.TEXTURE_2D_ARRAY,
376
+ 0,
377
+ 0,
378
+ 0,
379
+ layer,
380
+ baked.width,
381
+ baked.height,
382
+ 1,
383
+ gl.RGBA,
384
+ gl.UNSIGNED_BYTE,
385
+ baked.layers[layer] as Uint8Array,
386
+ );
387
+ }
323
388
  } else if (blocks !== null && plan !== null) {
324
389
  /* The stored chain, or level 0 alone where no chain was asked for. Nothing is generated:
325
390
  `generateMipmap` cannot write a compressed format. */
326
391
  this.layers = blocks.length;
327
392
  this.blockFormat = (blocks[0] as CompressedTextureSource).format;
393
+ this.storage = gl.NONE;
328
394
  this.mipmapped = (options.mipmap ?? true) && plan.levels > 1;
329
395
  uploadCompressedArray(gl, plan.name, blocks, this.mipmapped ? plan.levels : 1);
330
396
  } else {
@@ -332,6 +398,10 @@ export class SurfaceTexture {
332
398
  this.layers = sources.length;
333
399
  this.blockFormat = null;
334
400
  this.mipmapped = options.mipmap ?? true;
401
+ const { width, height } = layerSize(sources);
402
+ this.width = width;
403
+ this.height = height;
404
+ this.storage = this.srgb ? gl.SRGB8_ALPHA8 : gl.RGBA8;
335
405
  uploadImages(gl, sources, this.srgb);
336
406
  }
337
407
  gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_S, wrap);
@@ -410,6 +480,9 @@ export class SurfaceTexture {
410
480
  refuseArrayUpdate(this.layers);
411
481
  refuseBlockUpdate(this.blockFormat);
412
482
  const { width, height } = sourceSize(source);
483
+ this.width = width;
484
+ this.height = height;
485
+ this.dropFlat(gl);
413
486
  gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.texture);
414
487
  /* Unflipped, matching the constructor. See it for why, and for what it cost. */
415
488
  gl.texImage3D(
@@ -428,14 +501,68 @@ export class SurfaceTexture {
428
501
  gl.bindTexture(gl.TEXTURE_2D_ARRAY, null);
429
502
  }
430
503
 
504
+ /**
505
+ * Attach the only layer as the bound framebuffer's colour: a scene capture's target, drawn into
506
+ * by `captureScene`. Engine-internal, as `bind` is.
507
+ */
508
+ attachColor(gl: WebGL2RenderingContext): void {
509
+ gl.framebufferTextureLayer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, this.texture, 0, 0);
510
+ }
511
+
431
512
  /** Bind to a unit for sampling. Engine-internal: the renderer owns unit assignment. */
432
513
  bind(gl: WebGL2RenderingContext, unit: number): void {
433
514
  gl.activeTexture(gl.TEXTURE0 + unit);
434
515
  gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.texture);
435
516
  }
436
517
 
518
+ /**
519
+ * Layer 0 as a plain 2D texture: what a `sampler2D` can be handed, which an array cannot. Copied
520
+ * the first time it is asked for, by a framebuffer blit into storage of the same format, and kept
521
+ * until `update` or `dispose`. Engine-internal: a surface overlay's atlas is bound where the
522
+ * lit stage's one 2D image slot is (`surfaceOverlay.ts`).
523
+ *
524
+ * **Null for blocks**, which no framebuffer can read; the caller says so. What it costs is a copy
525
+ * of level 0 in memory beside the array, and the bindings of both framebuffer targets, which are
526
+ * read back before the blit and put back after it — a sync query, paid once per image.
527
+ */
528
+ flatLayer(gl: WebGL2RenderingContext): WebGLTexture | null {
529
+ if (this.texture === null || this.blockFormat !== null) return null;
530
+ if (this.flat !== null) return this.flat;
531
+ const flat = gl.createTexture();
532
+ const read = gl.createFramebuffer();
533
+ const draw = gl.createFramebuffer();
534
+ if (flat === null || read === null || draw === null) return null;
535
+ gl.bindTexture(gl.TEXTURE_2D, flat);
536
+ gl.texStorage2D(gl.TEXTURE_2D, 1, this.storage, this.width, this.height);
537
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
538
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
539
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
540
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
541
+ gl.bindTexture(gl.TEXTURE_2D, null);
542
+ const heldRead = gl.getParameter(gl.READ_FRAMEBUFFER_BINDING) as WebGLFramebuffer | null;
543
+ const heldDraw = gl.getParameter(gl.DRAW_FRAMEBUFFER_BINDING) as WebGLFramebuffer | null;
544
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, read);
545
+ gl.framebufferTextureLayer(gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, this.texture, 0, 0);
546
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, draw);
547
+ gl.framebufferTexture2D(gl.DRAW_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, flat, 0);
548
+ const [w, h] = [this.width, this.height];
549
+ gl.blitFramebuffer(0, 0, w, h, 0, 0, w, h, gl.COLOR_BUFFER_BIT, gl.NEAREST);
550
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, heldRead);
551
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, heldDraw);
552
+ gl.deleteFramebuffer(read);
553
+ gl.deleteFramebuffer(draw);
554
+ this.flat = flat;
555
+ return flat;
556
+ }
557
+
558
+ private dropFlat(gl: WebGL2RenderingContext): void {
559
+ if (this.flat !== null) gl.deleteTexture(this.flat);
560
+ this.flat = null;
561
+ }
562
+
437
563
  dispose(gl: WebGL2RenderingContext): void {
438
564
  if (this.texture === null) return;
565
+ this.dropFlat(gl);
439
566
  gl.deleteTexture(this.texture);
440
567
  this.texture = null;
441
568
  if (this.effectsTable !== null) gl.deleteTexture(this.effectsTable);
@@ -451,6 +451,17 @@ export class TemporalHistory {
451
451
  private picture = false;
452
452
  private width = 0;
453
453
  private height = 0;
454
+ /** Whether this frame of the renderer's has opened one of the sequence, and what it answered. */
455
+ private opened = false;
456
+ private answer = false;
457
+
458
+ /**
459
+ * A frame of the renderer's began: the next `openFrame` opens a frame of the sequence. Called at
460
+ * `beginFrame`, by both backends.
461
+ */
462
+ nextFrame(): void {
463
+ this.opened = false;
464
+ }
454
465
 
455
466
  /**
456
467
  * Open a frame at this target size, and answer whether the history may be sampled.
@@ -458,13 +469,22 @@ export class TemporalHistory {
458
469
  * **The jitter advances whether or not it may be.** Holding the sequence still on the frames a
459
470
  * resize or a cut discarded would sample the same position twice running, which is one sample
460
471
  * and not two.
472
+ *
473
+ * **Once a frame, however many times it is asked.** A renderer asks where the scene's camera
474
+ * arrives, and a frame binds that camera again after a scene capture or anything else that bound
475
+ * another; each asking opened a frame of its own, so the sequence stepped twice in such a frame
476
+ * and the edges of the picture swam. Asked again, it gives the answer it gave. What it gives up:
477
+ * a size changed between two asks in one frame is seen at the next frame, not at once.
461
478
  */
462
479
  openFrame(width: number, height: number): boolean {
480
+ if (this.opened) return this.answer;
481
+ this.opened = true;
463
482
  this.frameIndex++;
464
483
  const usable = this.picture && width === this.width && height === this.height;
465
484
  if (!usable) this.picture = false;
466
485
  this.width = width;
467
486
  this.height = height;
487
+ this.answer = usable;
468
488
  return usable;
469
489
  }
470
490
 
@@ -113,8 +113,18 @@ export const OUTPUT_TRANSFORM_CODE: Readonly<Record<string, number>> = {
113
113
  srgb: 1,
114
114
  aces: 2,
115
115
  shoulder: 3,
116
+ filmic: 4,
116
117
  };
117
118
 
119
+ /**
120
+ * The code a **forward** pass grades with: the transform's own, except `filmic`, which only the
121
+ * composite carries the constants for, and which a pass grading itself therefore draws as `aces`.
122
+ * Both backends' `gradeCode` read this, so neither can hand a forward stage a code it lacks.
123
+ */
124
+ export function forwardTransformCode(transform: string): number {
125
+ return transform === 'filmic' ? 2 : (OUTPUT_TRANSFORM_CODE[transform] ?? 0);
126
+ }
127
+
118
128
  /**
119
129
  * How far the speed rush reaches, and how far a motion-blurred pixel may travel, both in UV.
120
130
  *
@@ -123,3 +133,21 @@ export const OUTPUT_TRANSFORM_CODE: Readonly<Record<string, number>> = {
123
133
  */
124
134
  export const RUSH_REACH_UV = 0.012;
125
135
  export const MOTION_BLUR_MAX_UV = 0.03;
136
+
137
+ /**
138
+ * A longest smear as `setCameraMotionBlur` takes it: a share of the frame, above zero and at most
139
+ * half, and the default where the number is not one. Shared, so both backends cap one way.
140
+ */
141
+ export function clampMotionShare(share: number): number {
142
+ return Number.isFinite(share) && share > 0 ? Math.min(share, 0.5) : MOTION_BLUR_MAX_UV;
143
+ }
144
+
145
+ /**
146
+ * Said once where a draw names where it was last frame and the frame blurs by motion, but keeps no
147
+ * motion target to blur that draw by: each object's own motion is drawn only under a
148
+ * reconstruction on WebGPU, and anywhere else the camera's motion alone smears the frame.
149
+ */
150
+ export const OBJECT_BLUR_UNKEPT =
151
+ "[driftengine] motion blur follows each draw's own motion only under a reconstruction " +
152
+ "(DriftTR, WebGPU), which draws the motion target it reads; here the camera's motion alone " +
153
+ 'blurs the frame, and a moving object is smeared only as the camera moved past it.';