@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
@@ -3,6 +3,7 @@ import { compressedLayers, planBlocks, refuseBlockUpdate } from './compressedSou
3
3
  import { uploadCompressedArray } from './glCompressed.js';
4
4
  import { SURFACE_EFFECT_TEXELS, packSurfaceEffects } from './surfaceEffects.js';
5
5
  import { isLightmapTexels } from './lightmap.js';
6
+ import { isSceneCaptureTexels } from './sceneCapture.js';
6
7
  const DEFAULT_ANISOTROPY = 4;
7
8
  /**
8
9
  * An image uploaded once and bound per draw.
@@ -24,15 +25,24 @@ export class SurfaceTexture {
24
25
  effectsTable = null;
25
26
  /** The block format a compressed texture holds, or null for one uploaded from images. */
26
27
  blockFormat;
28
+ /** Level 0's size, which `flatLayer` copies; kept as `update` changes it. */
29
+ width = 0;
30
+ height = 0;
31
+ /** The texture's storage format, which `flatLayer`'s copy matches so the copy converts nothing. */
32
+ storage;
33
+ /** Layer 0 as a plain 2D texture, made by `flatLayer` and dropped when the pixels change. */
34
+ flat = null;
27
35
  /**
28
36
  * One image, or an array of images that must share one size (`layerSize` refuses otherwise).
29
37
  * Uploaded unflipped either way; see the constructor body for why. Or blocks, uploaded as they are
30
38
  * where `compressed` — the formats this context samples — has theirs; see `compressedSource.ts`.
31
39
  */
32
40
  constructor(gl, source, options = {}, compressed = []) {
33
- /* A lightmap's page: half floats, uploaded as they are. See `lightmap.ts`. */
41
+ /* A scene capture's target: one empty layer the renderer draws into. See `sceneCapture.ts`. */
42
+ const capture = isSceneCaptureTexels(source) ? source : null;
43
+ /* A lightmap's page: two layers of bytes, uploaded as they are. See `lightmap.ts`. */
34
44
  const baked = isLightmapTexels(source) ? source : null;
35
- const listed = (baked !== null ? [] : Array.isArray(source) ? source : [source]);
45
+ const listed = (baked !== null || capture !== null ? [] : Array.isArray(source) ? source : [source]);
36
46
  const blocks = compressedLayers(listed);
37
47
  this.srgb = (options.colorSpace ?? 'linear') === 'srgb';
38
48
  /* Refused before anything is allocated, so a refusal leaves no texture behind. */
@@ -43,21 +53,40 @@ export class SurfaceTexture {
43
53
  this.texture = texture;
44
54
  const wrap = (options.wrap ?? 'repeat') === 'repeat' ? gl.REPEAT : gl.CLAMP_TO_EDGE;
45
55
  gl.bindTexture(gl.TEXTURE_2D_ARRAY, texture);
46
- if (baked !== null) {
56
+ if (capture !== null) {
57
+ /* Immutable storage, one level: a framebuffer attachment, and radiance read linear. */
58
+ this.layers = 1;
59
+ this.blockFormat = null;
60
+ this.mipmapped = false;
61
+ this.width = capture.width;
62
+ this.height = capture.height;
63
+ this.storage = capture.float ? gl.RGBA16F : gl.RGBA8;
64
+ gl.texStorage3D(gl.TEXTURE_2D_ARRAY, 1, capture.float ? gl.RGBA16F : gl.RGBA8, capture.width, capture.height, 1);
65
+ }
66
+ else if (baked !== null) {
47
67
  /* No chain: a page is read at its own resolution, and a level of it would blend regions. */
48
- this.layers = baked.layers;
68
+ this.layers = baked.layers.length;
49
69
  this.blockFormat = null;
50
70
  this.mipmapped = false;
71
+ this.width = baked.width;
72
+ this.height = baked.height;
73
+ this.storage = gl.RGBA8;
51
74
  /* A typed array refuses either flag set, so both are put down here rather than assumed. */
52
75
  gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
53
76
  gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
54
- gl.texImage3D(gl.TEXTURE_2D_ARRAY, 0, gl.RGBA16F, baked.width, baked.height, baked.layers, 0, gl.RGBA, gl.HALF_FLOAT, baked.texels);
77
+ /* Bytes, and read by `texelFetch` alone: the shader decodes and filters. See `lightmap.ts`. */
78
+ gl.texImage3D(gl.TEXTURE_2D_ARRAY, 0, gl.RGBA8, baked.width, baked.height, this.layers, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
79
+ /* A layer at a time, so a page's arrays are uploaded where they lie rather than joined. */
80
+ for (let layer = 0; layer < this.layers; layer++) {
81
+ gl.texSubImage3D(gl.TEXTURE_2D_ARRAY, 0, 0, 0, layer, baked.width, baked.height, 1, gl.RGBA, gl.UNSIGNED_BYTE, baked.layers[layer]);
82
+ }
55
83
  }
56
84
  else if (blocks !== null && plan !== null) {
57
85
  /* The stored chain, or level 0 alone where no chain was asked for. Nothing is generated:
58
86
  `generateMipmap` cannot write a compressed format. */
59
87
  this.layers = blocks.length;
60
88
  this.blockFormat = blocks[0].format;
89
+ this.storage = gl.NONE;
61
90
  this.mipmapped = (options.mipmap ?? true) && plan.levels > 1;
62
91
  uploadCompressedArray(gl, plan.name, blocks, this.mipmapped ? plan.levels : 1);
63
92
  }
@@ -66,6 +95,10 @@ export class SurfaceTexture {
66
95
  this.layers = sources.length;
67
96
  this.blockFormat = null;
68
97
  this.mipmapped = options.mipmap ?? true;
98
+ const { width, height } = layerSize(sources);
99
+ this.width = width;
100
+ this.height = height;
101
+ this.storage = this.srgb ? gl.SRGB8_ALPHA8 : gl.RGBA8;
69
102
  uploadImages(gl, sources, this.srgb);
70
103
  }
71
104
  gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_S, wrap);
@@ -128,6 +161,9 @@ export class SurfaceTexture {
128
161
  refuseArrayUpdate(this.layers);
129
162
  refuseBlockUpdate(this.blockFormat);
130
163
  const { width, height } = sourceSize(source);
164
+ this.width = width;
165
+ this.height = height;
166
+ this.dropFlat(gl);
131
167
  gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.texture);
132
168
  /* Unflipped, matching the constructor. See it for why, and for what it cost. */
133
169
  gl.texImage3D(gl.TEXTURE_2D_ARRAY, 0, this.srgb ? gl.SRGB8_ALPHA8 : gl.RGBA, width, height, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, source);
@@ -135,14 +171,69 @@ export class SurfaceTexture {
135
171
  gl.generateMipmap(gl.TEXTURE_2D_ARRAY);
136
172
  gl.bindTexture(gl.TEXTURE_2D_ARRAY, null);
137
173
  }
174
+ /**
175
+ * Attach the only layer as the bound framebuffer's colour: a scene capture's target, drawn into
176
+ * by `captureScene`. Engine-internal, as `bind` is.
177
+ */
178
+ attachColor(gl) {
179
+ gl.framebufferTextureLayer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, this.texture, 0, 0);
180
+ }
138
181
  /** Bind to a unit for sampling. Engine-internal: the renderer owns unit assignment. */
139
182
  bind(gl, unit) {
140
183
  gl.activeTexture(gl.TEXTURE0 + unit);
141
184
  gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.texture);
142
185
  }
186
+ /**
187
+ * Layer 0 as a plain 2D texture: what a `sampler2D` can be handed, which an array cannot. Copied
188
+ * the first time it is asked for, by a framebuffer blit into storage of the same format, and kept
189
+ * until `update` or `dispose`. Engine-internal: a surface overlay's atlas is bound where the
190
+ * lit stage's one 2D image slot is (`surfaceOverlay.ts`).
191
+ *
192
+ * **Null for blocks**, which no framebuffer can read; the caller says so. What it costs is a copy
193
+ * of level 0 in memory beside the array, and the bindings of both framebuffer targets, which are
194
+ * read back before the blit and put back after it — a sync query, paid once per image.
195
+ */
196
+ flatLayer(gl) {
197
+ if (this.texture === null || this.blockFormat !== null)
198
+ return null;
199
+ if (this.flat !== null)
200
+ return this.flat;
201
+ const flat = gl.createTexture();
202
+ const read = gl.createFramebuffer();
203
+ const draw = gl.createFramebuffer();
204
+ if (flat === null || read === null || draw === null)
205
+ return null;
206
+ gl.bindTexture(gl.TEXTURE_2D, flat);
207
+ gl.texStorage2D(gl.TEXTURE_2D, 1, this.storage, this.width, this.height);
208
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
209
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
210
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
211
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
212
+ gl.bindTexture(gl.TEXTURE_2D, null);
213
+ const heldRead = gl.getParameter(gl.READ_FRAMEBUFFER_BINDING);
214
+ const heldDraw = gl.getParameter(gl.DRAW_FRAMEBUFFER_BINDING);
215
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, read);
216
+ gl.framebufferTextureLayer(gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, this.texture, 0, 0);
217
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, draw);
218
+ gl.framebufferTexture2D(gl.DRAW_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, flat, 0);
219
+ const [w, h] = [this.width, this.height];
220
+ gl.blitFramebuffer(0, 0, w, h, 0, 0, w, h, gl.COLOR_BUFFER_BIT, gl.NEAREST);
221
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, heldRead);
222
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, heldDraw);
223
+ gl.deleteFramebuffer(read);
224
+ gl.deleteFramebuffer(draw);
225
+ this.flat = flat;
226
+ return flat;
227
+ }
228
+ dropFlat(gl) {
229
+ if (this.flat !== null)
230
+ gl.deleteTexture(this.flat);
231
+ this.flat = null;
232
+ }
143
233
  dispose(gl) {
144
234
  if (this.texture === null)
145
235
  return;
236
+ this.dropFlat(gl);
146
237
  gl.deleteTexture(this.texture);
147
238
  this.texture = null;
148
239
  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.6",
3
+ "version": "4.9.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.6",
33
- "@driftengine/physics": "4.8.6",
32
+ "@driftengine/drft": "4.9.0",
33
+ "@driftengine/physics": "4.9.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.6",
71
- "@driftengine/network": "4.8.6",
72
- "@driftengine/texture": "4.8.6"
70
+ "@driftengine/nav": "4.9.0",
71
+ "@driftengine/network": "4.9.0",
72
+ "@driftengine/texture": "4.9.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.
@@ -1065,8 +1081,12 @@ export {
1065
1081
  INSTANCE_FLOATS,
1066
1082
  INSTANCE_STRIDE,
1067
1083
  } from './render/instances.ts';
1068
- export { ParticlePool } from './render/particlePool.ts';
1069
- export type { ParticlePoolOptions, ParticleInstances } from './render/particlePool.ts';
1084
+ export { ParticlePool, DEVICE_PARTICLE_FLOATS } from './render/particlePool.ts';
1085
+ export type {
1086
+ DeviceParticles,
1087
+ ParticlePoolOptions,
1088
+ ParticleInstances,
1089
+ } from './render/particlePool.ts';
1070
1090
  export { ParticleBatch } from './render/particleBatch.ts';
1071
1091
  export type {
1072
1092
  ParticleBatchOptions,
@@ -39,3 +39,32 @@ export function anisotropicLobe(
39
39
  const d = x * x + y * y + nh * nh;
40
40
  return 1 / Math.max(d * d, 1e-8);
41
41
  }
42
+
43
+ /** Three components in the lobe's frame: along the direction, across it, along the normal. */
44
+ export type FrameVector = readonly [number, number, number];
45
+
46
+ /**
47
+ * The physical form of the same lobe, `π · D · Vis · N·L`, for a surface whose highlight is GGX's
48
+ * own (`SurfaceMaterial.physicalSpecular`): the stretched distribution normalised as a BRDF's, and
49
+ * Smith's height-correlated masking stretched with it (Heitz 2014, as Filament writes it), Fresnel
50
+ * left to the caller. The half vector, the light and the view are each in the lobe's frame. At
51
+ * strength 0 it is isotropic GGX, the term skin's and the eye's highlights are.
52
+ */
53
+ export function anisotropicPhysicalLobe(
54
+ half: FrameVector,
55
+ light: FrameVector,
56
+ view: FrameVector,
57
+ alphaT: number,
58
+ alphaB: number,
59
+ ): number {
60
+ const x = half[0] / alphaT;
61
+ const y = half[1] / alphaB;
62
+ const d = x * x + y * y + half[2] * half[2];
63
+ const distribution = 1 / (Math.PI * alphaT * alphaB * d * d);
64
+ const ndl = light[2];
65
+ const ndv = view[2];
66
+ const lambdaView = ndl * Math.hypot(alphaT * view[0], alphaB * view[1], ndv);
67
+ const lambdaLight = ndv * Math.hypot(alphaT * light[0], alphaB * light[1], ndl);
68
+ const visibility = 0.5 / Math.max(lambdaView + lambdaLight, 1e-5);
69
+ return Math.PI * distribution * visibility * ndl;
70
+ }
@@ -97,6 +97,22 @@ export interface AreaLightSource {
97
97
  * of a scene unit, and never inside the rectangle itself. Unreal's `AttenuationRadius`, in metres.
98
98
  */
99
99
  range?: number;
100
+ /**
101
+ * Barn doors: a flap hinged at each of the rectangle's four edges, standing this many degrees from
102
+ * its normal. 90, or absent, folds them flat and they hide nothing; smaller closes them, so the
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.
105
+ *
106
+ * What a fragment sees past the doors is the part of the rectangle no door's tip hides, one axis
107
+ * at a time, and the rectangle then shades as that smaller rectangle, its highlight included.
108
+ * Its shadow map is still baked from the whole rectangle's centre. **What it gives up**: each
109
+ * door is taken as endless along its hinge, so the two axes clip apart and the corner where two
110
+ * doors meet hides a square rather than the mitre two real flaps make. One-sided: a two-sided
111
+ * rectangle's back face has none.
112
+ */
113
+ barnDoorAngle?: number;
114
+ /** The barn doors' length in metres. Absent, 0.2: Unreal's default of 20 cm. */
115
+ barnDoorLength?: number;
100
116
  }
101
117
 
102
118
  /** The irradiance a derived `range` ends at, in scene units. See `AreaLightSource.range`. */
@@ -113,6 +129,11 @@ export interface AreaLightBuffer {
113
129
  readonly twoSided: Float32Array;
114
130
  /** One a rectangle: how far its light reaches. See `AreaLightSource.range`. */
115
131
  readonly ranges?: Float32Array;
132
+ /**
133
+ * Two a rectangle: the cosine of its barn doors' angle from its normal, then their length in
134
+ * metres — 0 and 0 for none. See `AreaLightSource.barnDoorAngle`.
135
+ */
136
+ readonly barnDoors?: Float32Array;
116
137
  }
117
138
 
118
139
  export function createAreaLightBuffer(capacity: number = MAX_AREA_LIGHTS): AreaLightBuffer {
@@ -125,9 +146,45 @@ export function createAreaLightBuffer(capacity: number = MAX_AREA_LIGHTS): AreaL
125
146
  sizes: new Float32Array(capacity * 2),
126
147
  twoSided: new Float32Array(capacity),
127
148
  ranges: new Float32Array(capacity),
149
+ barnDoors: new Float32Array(capacity * 2),
128
150
  };
129
151
  }
130
152
 
153
+ /** Unreal's default barn door length, in metres. See `AreaLightSource.barnDoorLength`. */
154
+ export const DEFAULT_BARN_DOOR_LENGTH = 0.2;
155
+
156
+ /**
157
+ * A rectangle's barn doors as the shader reads them, into `out[at]` and `out[at + 1]`: the cosine of
158
+ * their angle and their length. An angle not between 0 and 90 degrees, or a length not above zero,
159
+ * is no doors at all, written as 0 and 0 — which the shader reads as "nothing hidden".
160
+ */
161
+ export function barnDoorsOf(light: AreaLightSource, out: Float32Array, at: number): void {
162
+ const angle = light.barnDoorAngle;
163
+ const length = light.barnDoorLength ?? DEFAULT_BARN_DOOR_LENGTH;
164
+ if (angle === undefined || !(angle >= 0 && angle < 90) || !(length > 0)) {
165
+ out[at] = 0;
166
+ out[at + 1] = 0;
167
+ return;
168
+ }
169
+ out[at] = Math.cos((angle * Math.PI) / 180);
170
+ out[at + 1] = length;
171
+ }
172
+
173
+ /**
174
+ * The fixed arm's half extents and barn doors, four a rectangle as `uAreaLightSize` holds them: the
175
+ * two half extents, then the doors' cosine and length. Into a target the binder owns, for the first
176
+ * `MAX_AREA_LIGHTS` alone, which are all the fixed arm reads.
177
+ */
178
+ export function packAreaSizesAndDoors(lights: AreaLightBuffer, out: Float32Array): void {
179
+ const doors = lights.barnDoors;
180
+ for (let a = 0; a < MAX_AREA_LIGHTS; a++) {
181
+ out[a * 4] = lights.sizes[a * 2] ?? 0;
182
+ out[a * 4 + 1] = lights.sizes[a * 2 + 1] ?? 0;
183
+ out[a * 4 + 2] = doors?.[a * 2] ?? 0;
184
+ out[a * 4 + 3] = doors?.[a * 2 + 1] ?? 0;
185
+ }
186
+ }
187
+
131
188
  /**
132
189
  * How far a rectangle's light reaches when it names no range: where its irradiance on its own axis,
133
190
  * `L · A / d²` for a rectangle of radiance `L` and area `A` seen from far, falls to
@@ -227,6 +284,7 @@ export function selectAreaLights(
227
284
  ? light.range
228
285
  : areaRange(light.r, light.g, light.b, light.halfWidth, light.halfHeight);
229
286
  }
287
+ if (out.barnDoors !== undefined) barnDoorsOf(light, out.barnDoors, slot * 2);
230
288
  }
231
289
 
232
290
  return out;
@@ -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
@@ -1,13 +1,16 @@
1
1
  import type { Bounds } from '../../math/bounds.ts';
2
2
  import type { Vec3 } from '../../math/color.ts';
3
+ import type { Camera } from '../camera.ts';
3
4
  import type { ReadonlyMat4 } from 'gl-matrix';
4
5
  import type { MeshData } from '../mesh.ts';
5
6
  import type { ProbeBakeOptions } from '../reflectionProbe.ts';
6
7
  import type { Renderer, TranslucentMeshOptions } from './webgl2/renderer.ts';
7
8
  import type { InstancedOptions, MeshInstances } from '../instances.ts';
8
9
  import type { SurfaceMaterial } from '../surfaceTexture.ts';
10
+ import type { SurfaceOverlay } from '../surfaceOverlay.ts';
9
11
  import type { SurfaceSource } from '../compressedSource.ts';
10
12
  import type { LightmapPage } from '../lightmap.ts';
13
+ import type { DeviceParticles } from '../particlePool.ts';
11
14
  import type { ClothBindingData } from '../clothBindingData.ts';
12
15
  import type { TextStyle } from '../textLayout.ts';
13
16
  import type { SdfFont } from '../sdfFont.ts';
@@ -276,6 +279,7 @@ export type RendererApi = Omit<
276
279
  | 'disposeCaustics'
277
280
  | 'createParticles'
278
281
  | 'drawParticles'
282
+ | 'drawDeviceParticles'
279
283
  | 'disposeParticles'
280
284
  | 'createWindStreaks'
281
285
  | 'drawWindStreaks'
@@ -302,10 +306,13 @@ export type RendererApi = Omit<
302
306
  | 'createSurfaceTexture'
303
307
  | 'createSurfaceTextureArray'
304
308
  | 'createLightmap'
309
+ | 'createSceneCapture'
310
+ | 'captureScene'
305
311
  | 'updateSurfaceTexture'
306
312
  | 'disposeSurfaceTexture'
307
313
  | 'setSurfaceTexture'
308
314
  | 'setMaterial'
315
+ | 'setSurfaceOverlay'
309
316
  | 'createClothBinding'
310
317
  | 'createClothParticles'
311
318
  | 'updateClothParticles'
@@ -336,6 +343,13 @@ export type RendererApi = Omit<
336
343
  setCloth(binding: ClothBindingHandle | null, particles?: ClothParticlesHandle | null): void;
337
344
  disposeClothBinding(binding: ClothBindingHandle): void;
338
345
  disposeClothParticles(particles: ClothParticlesHandle): void;
346
+ /**
347
+ * What the following draws lay over their surfaces beyond their materials — a rim of light, a
348
+ * dissolve with a glowing edge, wrinkle normals by region — or null for none, which every pass
349
+ * starts with. Per draw, so one character's whole set of materials changes together. Its images
350
+ * are regions of one atlas (`SurfaceOverlay.maps`). See `surfaceOverlay.ts`.
351
+ */
352
+ setSurfaceOverlay(overlay: SurfaceOverlay | null): void;
339
353
  /**
340
354
  * Upload a caller's image, or a BC image's blocks with their stored chain where
341
355
  * `compressedFormats` has the format. See `SurfaceTextureHandle` for the boundary this guards.
@@ -365,13 +379,41 @@ export type RendererApi = Omit<
365
379
 
366
380
  /**
367
381
  * A baked lightmap page on the device, as the texture a `lightmapModel` material takes for its
368
- * `modelMap`: two layers of half floats, the irradiance and the direction, filtered linearly and
369
- * clamped at its edges, with no mip chain. Disposed as any texture is, with
382
+ * `modelMap`: two layers of four bytes a texel, the irradiance as an rgb9e5 word and the
383
+ * direction, which the lit stage decodes and filters itself, clamped at the page's edges, with no
384
+ * mip chain. A page whose irradiance arrives packed uploads where it lies. Disposed as any texture is, with
370
385
  * `disposeSurfaceTexture`; `updateSurfaceTexture` refuses it, as it refuses any array. See
371
386
  * `lightmap.ts` for what a surface does with it.
372
387
  */
373
388
  createLightmap(page: LightmapPage): SurfaceTextureHandle;
374
389
 
390
+ /**
391
+ * A texture the scene can be drawn into from a camera of the caller's (`captureScene`), which a
392
+ * material then shows like any image — most often as its `emissive`, for a screen in the world
393
+ * showing what a camera elsewhere sees. `width` and `height` in pixels, 1 to 4096. It holds
394
+ * radiance, at the colour format a probe takes: half floats under `hdrScene`, bytes otherwise.
395
+ * Disposed as any texture is; `updateSurfaceTexture` refuses it. See `sceneCapture.ts`.
396
+ */
397
+ createSceneCapture(width: number, height: number): SurfaceTextureHandle;
398
+
399
+ /**
400
+ * Draw the scene into a capture from `camera`, at the moment a caller says, usually once a frame
401
+ * before the frame's own world. `draw` is handed the camera, its matrices updated for the
402
+ * capture's shape, and submits the scene exactly as it would to the screen — `bindMeshPass`,
403
+ * `drawMesh` and the rest; `bindMeshPass` the frame's own camera again afterwards. The output
404
+ * transform is held off, so a screen showing it is graded once, with the frame.
405
+ *
406
+ * **The mesh pass alone**, as a probe bake is: what a scene draws after its world is in the
407
+ * capture only if `draw` draws it, and no temporal resolve or effect after the world runs on it.
408
+ * Returns whether it drew: false for a texture that is not a capture.
409
+ */
410
+ captureScene(
411
+ capture: SurfaceTextureHandle,
412
+ camera: Camera,
413
+ clearColor: Vec3,
414
+ draw: (camera: Camera) => void,
415
+ ): boolean;
416
+
375
417
  /**
376
418
  * Replace a texture's pixels, keeping the GPU object and its sampler state.
377
419
  *
@@ -735,6 +777,20 @@ export type RendererApi = Omit<
735
777
  timeSeconds: number,
736
778
  ): void;
737
779
 
780
+ /**
781
+ * Draw a pool's particles from a buffer a caller's own compute wrote (`registerCompute`), where
782
+ * they lie: the pool's material, blend, flipbook and fades, its instance stream the caller's
783
+ * buffer. WebGPU only; WebGL2 says so once and draws nothing. Not sorted. See `DeviceParticles`
784
+ * for the layout.
785
+ */
786
+ drawDeviceParticles(
787
+ batch: ParticleHandle,
788
+ particles: DeviceParticles,
789
+ camera: Parameters<Renderer['drawParticles']>[2],
790
+ env: Parameters<Renderer['drawParticles']>[3],
791
+ timeSeconds: number,
792
+ ): void;
793
+
738
794
  /** Release a particle pool. */
739
795
  disposeParticles(batch: ParticleHandle): void;
740
796