@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
@@ -0,0 +1,265 @@
1
+ import { DYNAMIC_ALIGNMENT, MAX_RING_BYTES } from './uniformRing.js';
2
+ /**
3
+ * The lit stage's material blocks, each kept in its slot for as long as later draws ask for the
4
+ * same numbers, so a material that has not changed since the last frame costs no upload at all.
5
+ *
6
+ * **Keyed by content, because nothing else names a material.** A material here is whatever the
7
+ * setters left in the block when a draw took it — a consumer never hands over an object that could
8
+ * carry an identity — so the block's own words are the key: hashed to find a bucket, compared word
9
+ * for word to accept a match, and bound by the slot's offset. What it gives up is a hash and a
10
+ * compare of a few hundred bytes per material change, against copying and uploading the whole
11
+ * block every frame, which a stage of four hundred materials measured at megabytes a frame.
12
+ *
13
+ * **A slot the frame has asked for is never given away within it**, for the reason `UniformRing`
14
+ * exists: a queue write does not interleave with recorded draws, so rewriting a slot a recorded draw
15
+ * reads would hand that draw the new numbers. A frame asking for more distinct blocks than there are
16
+ * slots is refused, counted and skipped, as the ring was. Across frames rewriting is safe, because a
17
+ * write issued after a submit is ordered after the commands in it.
18
+ *
19
+ * **What a new block displaces is a slot nobody asked for this frame or the last**, found by a hand
20
+ * that sweeps the slots in turn; only when every slot was asked for recently does it take one the
21
+ * last frame used. What would make that wrong is a scene whose distinct materials a frame approach
22
+ * the store's size: it then rewrites some of its steady materials every frame, which costs their
23
+ * upload and nothing else.
24
+ */
25
+ export class MaterialStore {
26
+ device;
27
+ usage;
28
+ label;
29
+ budget;
30
+ /** Replaced by `growTo`, so every bind group over it is rebuilt when that says so. */
31
+ buffer;
32
+ /** Bytes per slot, rounded up to the alignment a dynamic offset requires. */
33
+ slotSize;
34
+ words;
35
+ stride;
36
+ capacity;
37
+ staging;
38
+ ints;
39
+ /** Per slot: its block's hash, the next slot in its bucket or -1, and whether it holds one. */
40
+ hashes;
41
+ next;
42
+ held;
43
+ /** Per slot: the frame that last asked for it, and whether it was written since the last flush. */
44
+ askedIn;
45
+ pending;
46
+ /** Per slot: how many holders pinned it. A pinned slot is never displaced. */
47
+ pins;
48
+ /**
49
+ * How many times the store has forgotten everything it held, which growth does: a holder of an
50
+ * offset compares this, because an offset from before is a slot of a buffer that is gone.
51
+ */
52
+ epoch = 0;
53
+ /** Per bucket: its first slot, or -1. A power of two at least twice the slots. */
54
+ heads;
55
+ mask;
56
+ lowPending;
57
+ highPending = -1;
58
+ hand = 0;
59
+ /** Starts past zero, so a slot never asked for reads as long unasked. */
60
+ frame = 2;
61
+ constructor(device, bytesPerBlock, slots, usage, label = 'materials.store', budget = null) {
62
+ this.device = device;
63
+ this.usage = usage;
64
+ this.label = label;
65
+ this.budget = budget;
66
+ this.words = Math.ceil(bytesPerBlock / 4);
67
+ this.slotSize = Math.ceil(bytesPerBlock / DYNAMIC_ALIGNMENT) * DYNAMIC_ALIGNMENT;
68
+ this.stride = this.slotSize / 4;
69
+ this.capacity = slots;
70
+ this.lowPending = slots;
71
+ this.staging = new ArrayBuffer(this.slotSize * slots);
72
+ this.ints = new Int32Array(this.staging);
73
+ this.hashes = new Int32Array(slots);
74
+ this.next = new Int32Array(slots);
75
+ this.held = new Uint8Array(slots);
76
+ this.askedIn = new Int32Array(slots);
77
+ this.pending = new Uint8Array(slots);
78
+ this.pins = new Int32Array(slots);
79
+ this.heads = new Int32Array(bucketsFor(slots)).fill(-1);
80
+ this.mask = this.heads.length - 1;
81
+ this.buffer = device.createBuffer({ label, size: this.staging.byteLength, usage });
82
+ }
83
+ /** How many distinct blocks a frame may hold. */
84
+ get slots() {
85
+ return this.capacity;
86
+ }
87
+ /** Start a frame: every slot the last one asked for may be displaced from here on, last. */
88
+ beginFrame() {
89
+ this.frame += 1;
90
+ }
91
+ /**
92
+ * The byte offset of a slot holding exactly `block`, written there if no slot did, or null when
93
+ * every slot is one this frame has asked for. `block` is the whole block, word for word.
94
+ */
95
+ place(block) {
96
+ this.budget?.ask();
97
+ const hash = hashWords(block, this.words);
98
+ const bucket = hash & this.mask;
99
+ for (let slot = this.heads[bucket]; slot >= 0; slot = this.next[slot]) {
100
+ if (this.hashes[slot] === hash && this.holds(slot, block)) {
101
+ this.askedIn[slot] = this.frame;
102
+ return slot * this.slotSize;
103
+ }
104
+ }
105
+ const slot = this.displace();
106
+ if (slot < 0) {
107
+ this.budget?.drop();
108
+ return null;
109
+ }
110
+ if (this.held[slot] === 1)
111
+ this.unlink(slot);
112
+ this.ints.set(block, slot * this.stride);
113
+ this.hashes[slot] = hash;
114
+ this.next[slot] = this.heads[bucket];
115
+ this.heads[bucket] = slot;
116
+ this.held[slot] = 1;
117
+ this.askedIn[slot] = this.frame;
118
+ this.pending[slot] = 1;
119
+ if (slot < this.lowPending)
120
+ this.lowPending = slot;
121
+ if (slot > this.highPending)
122
+ this.highPending = slot;
123
+ return slot * this.slotSize;
124
+ }
125
+ /** Upload every slot written since the last flush, one write per run of neighbouring slots. */
126
+ flush() {
127
+ let slot = this.lowPending;
128
+ while (slot <= this.highPending) {
129
+ if (this.pending[slot] !== 1) {
130
+ slot += 1;
131
+ continue;
132
+ }
133
+ const from = slot;
134
+ while (slot <= this.highPending && this.pending[slot] === 1) {
135
+ this.pending[slot] = 0;
136
+ slot += 1;
137
+ }
138
+ const at = from * this.slotSize;
139
+ this.device.queue.writeBuffer(this.buffer, at, this.staging, at, (slot - from) * this.slotSize);
140
+ }
141
+ this.lowPending = this.capacity;
142
+ this.highPending = -1;
143
+ }
144
+ /**
145
+ * Make room for at least `slots`, forgetting every block held, and say whether the buffer was
146
+ * replaced. Called where `UniformRing.growTo` is, at a frame's start, for the reasons it gives,
147
+ * and refused past the same ceiling.
148
+ */
149
+ growTo(slots) {
150
+ if (slots <= this.capacity)
151
+ return false;
152
+ if (this.slotSize * slots > MAX_RING_BYTES)
153
+ return false;
154
+ this.buffer.destroy();
155
+ this.capacity = slots;
156
+ this.staging = new ArrayBuffer(this.slotSize * slots);
157
+ this.ints = new Int32Array(this.staging);
158
+ this.hashes = new Int32Array(slots);
159
+ this.next = new Int32Array(slots);
160
+ this.held = new Uint8Array(slots);
161
+ this.askedIn = new Int32Array(slots);
162
+ this.pending = new Uint8Array(slots);
163
+ this.pins = new Int32Array(slots);
164
+ this.heads = new Int32Array(bucketsFor(slots)).fill(-1);
165
+ this.mask = this.heads.length - 1;
166
+ this.lowPending = slots;
167
+ this.epoch += 1;
168
+ this.highPending = -1;
169
+ this.hand = 0;
170
+ this.buffer = this.device.createBuffer({
171
+ label: this.label,
172
+ size: this.staging.byteLength,
173
+ usage: this.usage,
174
+ });
175
+ return true;
176
+ }
177
+ /**
178
+ * Keep the block at `offset` where it is until `unpin`: a holder binding it by offset frame after
179
+ * frame — a list recorded once — whether or not anything asks for it. Counted, so two holders of
180
+ * one block each let go of their own pin.
181
+ */
182
+ pin(offset) {
183
+ const slot = offset / this.slotSize;
184
+ this.pins[slot] = this.pins[slot] + 1;
185
+ }
186
+ unpin(offset) {
187
+ const slot = offset / this.slotSize;
188
+ if (this.pins[slot] > 0)
189
+ this.pins[slot] = this.pins[slot] - 1;
190
+ }
191
+ dispose() {
192
+ this.buffer.destroy();
193
+ }
194
+ holds(slot, block) {
195
+ const base = slot * this.stride;
196
+ for (let word = 0; word < this.words; word++) {
197
+ if (this.ints[base + word] !== block[word])
198
+ return false;
199
+ }
200
+ return true;
201
+ }
202
+ /**
203
+ * The slot a new block goes in: the first from the hand nobody asked for this frame or the last,
204
+ * else the first this frame has not asked for, else none. One sweep at most.
205
+ */
206
+ displace() {
207
+ let fallback = -1;
208
+ for (let step = 0; step < this.capacity; step++) {
209
+ const slot = this.hand;
210
+ this.hand = slot + 1 === this.capacity ? 0 : slot + 1;
211
+ if (this.pins[slot] > 0)
212
+ continue;
213
+ const asked = this.askedIn[slot];
214
+ if (asked < this.frame - 1)
215
+ return slot;
216
+ if (fallback < 0 && asked !== this.frame)
217
+ fallback = slot;
218
+ }
219
+ return fallback;
220
+ }
221
+ unlink(slot) {
222
+ const bucket = this.hashes[slot] & this.mask;
223
+ let at = this.heads[bucket];
224
+ if (at === slot) {
225
+ this.heads[bucket] = this.next[slot];
226
+ return;
227
+ }
228
+ while (at >= 0) {
229
+ const after = this.next[at];
230
+ if (after === slot) {
231
+ this.next[at] = this.next[slot];
232
+ return;
233
+ }
234
+ at = after;
235
+ }
236
+ }
237
+ }
238
+ function bucketsFor(slots) {
239
+ let buckets = 16;
240
+ while (buckets < slots * 2)
241
+ buckets *= 2;
242
+ return buckets;
243
+ }
244
+ /**
245
+ * MurmurHash3's 32-bit mix over the block's words. Not FNV, whose multiply carries a difference only
246
+ * upward: the bucket is the hash's low bits, and a material's words are mostly floats, whose low
247
+ * mantissa bits are zero for every value a person types — 0.5, 1, 2 — so blocks differing only in
248
+ * those would all have shared one bucket. The compare that follows a match is what decides.
249
+ */
250
+ export function hashWords(block, words) {
251
+ let hash = 0x9747b28c;
252
+ for (let word = 0; word < words; word++) {
253
+ let k = Math.imul(block[word], 0xcc9e2d51);
254
+ k = (k << 15) | (k >>> 17);
255
+ hash ^= Math.imul(k, 0x1b873593);
256
+ hash = (hash << 13) | (hash >>> 19);
257
+ hash = (Math.imul(hash, 5) + 0xe6546b64) | 0;
258
+ }
259
+ hash ^= words * 4;
260
+ hash ^= hash >>> 16;
261
+ hash = Math.imul(hash, 0x85ebca6b);
262
+ hash ^= hash >>> 13;
263
+ hash = Math.imul(hash, 0xc2b2ae35);
264
+ return hash ^ (hash >>> 16);
265
+ }
@@ -52,6 +52,12 @@ export declare class PipelineCache {
52
52
  readonly glassShadows: boolean;
53
53
  /** How to describe each lit pipeline again, so `enable` can rebuild every one. */
54
54
  private readonly litDescribers;
55
+ /**
56
+ * Bumped when a switch's rebuilt set lands, which replaces pipelines already handed out: a holder
57
+ * of one — a recorded render bundle — compares this and records again rather than drawing on
58
+ * with the pipelines from before the switch.
59
+ */
60
+ version: number;
55
61
  /** Bumped by every switch, so a lit build started before one is not kept after it. */
56
62
  private litGeneration;
57
63
  /** The last rebuild, which the next one waits for so they land in the order they were asked. */
@@ -51,6 +51,12 @@ export class PipelineCache {
51
51
  glassShadows;
52
52
  /** How to describe each lit pipeline again, so `enable` can rebuild every one. */
53
53
  litDescribers = new Map();
54
+ /**
55
+ * Bumped when a switch's rebuilt set lands, which replaces pipelines already handed out: a holder
56
+ * of one — a recorded render bundle — compares this and records again rather than drawing on
57
+ * with the pipelines from before the switch.
58
+ */
59
+ version = 0;
54
60
  /** Bumped by every switch, so a lit build started before one is not kept after it. */
55
61
  litGeneration = 0;
56
62
  /** The last rebuild, which the next one waits for so they land in the order they were asked. */
@@ -201,6 +207,7 @@ export class PipelineCache {
201
207
  if (result.status === 'fulfilled')
202
208
  this.pipelines.set(result.value[0], result.value[1]);
203
209
  }
210
+ this.version += 1;
204
211
  });
205
212
  return this.litRebuild;
206
213
  }
@@ -5,7 +5,7 @@ import { TEMPORAL_RESOLVE_FRAG_WGSL } from '../../shaders/generated/temporalReso
5
5
  import { OIT_RESOLVE_FRAG_WGSL } from '../../shaders/generated/oitResolve.wgsl.ts';
6
6
  import { DECAL_PROJECT_FRAG_WGSL } from '../../shaders/generated/decalProject.wgsl.ts';
7
7
  import { SSR_TRACE_FRAG_WGSL } from '../../shaders/generated/ssrTrace.wgsl.ts';
8
- import { SSR_RESOLVE_FRAG_WGSL } from '../../shaders/generated/ssrResolve.wgsl.ts';
8
+ import { SSR_MATERIAL_RESOLVE_FRAG_WGSL, SSR_RESOLVE_FRAG_WGSL } from '../../shaders/generated/ssrResolve.wgsl.ts';
9
9
  import { MEDIUM_FRAG_WGSL, MEDIUM_UPSAMPLE_FRAG_WGSL } from '../../shaders/generated/globalMedium.wgsl.ts';
10
10
  import type { UniformFields } from './scatterPass.ts';
11
11
  import type { PipelineCache } from './pipelineCache.ts';
@@ -26,7 +26,7 @@ import type { PipelineCache } from './pipelineCache.ts';
26
26
  * float target the generated shaders can sample as an ordinary texture.
27
27
  */
28
28
  export declare const RUSH_FRAG_FIELDS: UniformFields;
29
- export declare const RUSH_FRAG_SIZE: 208;
29
+ export declare const RUSH_FRAG_SIZE: 272;
30
30
  declare const RUSH_UNIFORMS: 1;
31
31
  declare const RUSH_TEXTURES: {
32
32
  readonly uScene: {
@@ -39,29 +39,34 @@ declare const RUSH_TEXTURES: {
39
39
  readonly sampler: 35;
40
40
  readonly type: "sampler2D";
41
41
  };
42
- readonly uAo: {
42
+ readonly uMotion: {
43
43
  readonly texture: 36;
44
44
  readonly sampler: 37;
45
45
  readonly type: "sampler2D";
46
46
  };
47
- readonly uBloom: {
47
+ readonly uAo: {
48
48
  readonly texture: 38;
49
49
  readonly sampler: 39;
50
50
  readonly type: "sampler2D";
51
51
  };
52
- readonly uExposureHeld: {
52
+ readonly uBloom: {
53
53
  readonly texture: 40;
54
54
  readonly sampler: 41;
55
55
  readonly type: "sampler2D";
56
56
  };
57
- readonly uExposureLocal: {
57
+ readonly uExposureHeld: {
58
58
  readonly texture: 42;
59
59
  readonly sampler: 43;
60
60
  readonly type: "sampler2D";
61
61
  };
62
- readonly uGradeLut: {
62
+ readonly uExposureLocal: {
63
63
  readonly texture: 44;
64
64
  readonly sampler: 45;
65
+ readonly type: "sampler2D";
66
+ };
67
+ readonly uGradeLut: {
68
+ readonly texture: 46;
69
+ readonly sampler: 47;
65
70
  readonly type: "sampler3D";
66
71
  };
67
72
  };
@@ -180,6 +185,12 @@ export declare const PREMULTIPLIED_OVER_BLEND: GPUBlendState;
180
185
  * the reflection's own and its alpha is the coverage this resolve reads.
181
186
  */
182
187
  export declare const REFLECTION_OVER_BLEND: GPUBlendState;
188
+ /**
189
+ * The frame's materials' reflection, added: the found colour times the tint, less the probe's share,
190
+ * which is negative where the probe was brighter than what replaced it. A half-float scene takes
191
+ * the negative addend as the subtraction it is. Its alpha kept.
192
+ */
193
+ export declare const FRAME_REFLECTION_BLEND: GPUBlendState;
183
194
  /**
184
195
  * The bloom pyramid's way back up: an octave added onto a level that the blend multiplies by that
185
196
  * level's tint first — the blend constant, which white makes `one, one` exactly. Into the pyramid's
@@ -276,7 +287,7 @@ export declare const DECAL_TEXTURES: {
276
287
  * commands, so one block rewritten between two passes gives both passes the last write.
277
288
  */
278
289
  export declare const SSR_FRAG_FIELDS: UniformFields;
279
- export declare const SSR_FRAG_SIZE: 272;
290
+ export declare const SSR_FRAG_SIZE: 288;
280
291
  export declare const SSR_UNIFORMS: 1;
281
292
  export declare const SSR_TEXTURES: {
282
293
  readonly uSsrDepth: {
@@ -289,6 +300,16 @@ export declare const SSR_TEXTURES: {
289
300
  readonly sampler: 35;
290
301
  readonly type: "sampler2D";
291
302
  };
303
+ readonly uSsrProbeMap: {
304
+ readonly texture: 36;
305
+ readonly sampler: 37;
306
+ readonly type: "sampler2D";
307
+ };
308
+ readonly uSsrTintMap: {
309
+ readonly texture: 38;
310
+ readonly sampler: 39;
311
+ readonly type: "sampler2D";
312
+ };
292
313
  };
293
314
  export declare const SSR_RESOLVE_TEXTURES: {
294
315
  readonly uSsrReflection: {
@@ -297,6 +318,27 @@ export declare const SSR_RESOLVE_TEXTURES: {
297
318
  readonly type: "sampler2D";
298
319
  };
299
320
  };
321
+ /** The material resolve's block — the blur — and its three inputs. See `frameReflections.ts`. */
322
+ export declare const SSR_MATERIAL_FIELDS: UniformFields;
323
+ export declare const SSR_MATERIAL_SIZE: 16;
324
+ export declare const SSR_MATERIAL_UNIFORMS: 1;
325
+ export declare const SSR_MATERIAL_TEXTURES: {
326
+ readonly uSsrReflection: {
327
+ readonly texture: 32;
328
+ readonly sampler: 33;
329
+ readonly type: "sampler2D";
330
+ };
331
+ readonly uSsrProbeMap: {
332
+ readonly texture: 34;
333
+ readonly sampler: 35;
334
+ readonly type: "sampler2D";
335
+ };
336
+ readonly uSsrTintMap: {
337
+ readonly texture: 36;
338
+ readonly sampler: 37;
339
+ readonly type: "sampler2D";
340
+ };
341
+ };
300
342
  /**
301
343
  * The resolve's layout: one texture and no uniform block.
302
344
  *
@@ -341,7 +383,7 @@ export declare const MEDIUM_UPSAMPLE_TEXTURES: {
341
383
  readonly type: "sampler2D";
342
384
  };
343
385
  };
344
- export { RUSH_FRAG_WGSL, AO_FRAG_WGSL, AO_BLUR_FRAG_WGSL, TEMPORAL_RESOLVE_FRAG_WGSL, OIT_RESOLVE_FRAG_WGSL, DECAL_PROJECT_FRAG_WGSL, SSR_TRACE_FRAG_WGSL, SSR_RESOLVE_FRAG_WGSL, };
386
+ export { RUSH_FRAG_WGSL, AO_FRAG_WGSL, AO_BLUR_FRAG_WGSL, TEMPORAL_RESOLVE_FRAG_WGSL, OIT_RESOLVE_FRAG_WGSL, DECAL_PROJECT_FRAG_WGSL, SSR_TRACE_FRAG_WGSL, SSR_RESOLVE_FRAG_WGSL, SSR_MATERIAL_RESOLVE_FRAG_WGSL, };
345
387
  export { BLOOM_PREFILTER_FRAG_WGSL, BLOOM_DOWNSAMPLE_FRAG_WGSL, BLOOM_UPSAMPLE_FRAG_WGSL };
346
388
  export { RUSH_UNIFORMS, RUSH_TEXTURES };
347
389
  export declare const AO_UNIFORMS: 1;
@@ -6,7 +6,7 @@ import { TEMPORALRESOLVE_BINDINGS as TAA_BINDINGS, TEMPORAL_RESOLVE_FRAG_WGSL, }
6
6
  import { OITRESOLVE_BINDINGS as OIT_BINDINGS, OIT_RESOLVE_FRAG_WGSL, } from '../../shaders/generated/oitResolve.wgsl.js';
7
7
  import { DECALPROJECT_BINDINGS as DECAL_BINDINGS, DECAL_PROJECT_FRAG_WGSL, } from '../../shaders/generated/decalProject.wgsl.js';
8
8
  import { SSRTRACE_BINDINGS as SSR_BINDINGS, SSR_TRACE_FRAG_WGSL, } from '../../shaders/generated/ssrTrace.wgsl.js';
9
- import { SSRRESOLVE_BINDINGS as SSR_RESOLVE_BINDINGS, SSR_RESOLVE_FRAG_WGSL, } from '../../shaders/generated/ssrResolve.wgsl.js';
9
+ import { SSRRESOLVE_BINDINGS as SSR_RESOLVE_BINDINGS, SSR_MATERIAL_RESOLVE_FRAG_WGSL, SSR_RESOLVE_FRAG_WGSL, } from '../../shaders/generated/ssrResolve.wgsl.js';
10
10
  import { GLOBALMEDIUM_BINDINGS as MEDIUM_BINDINGS, MEDIUM_FRAG_WGSL, MEDIUM_UPSAMPLE_FRAG_WGSL, } from '../../shaders/generated/globalMedium.wgsl.js';
11
11
  import { shaderModule } from './shaderModules.js';
12
12
  import { SCENE_ALPHA_KEEPS, SCENE_ALPHA_TRANSMITS } from '../../sceneCoverage.js';
@@ -252,6 +252,15 @@ export const REFLECTION_OVER_BLEND = {
252
252
  color: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
253
253
  alpha: SCENE_ALPHA_KEEPS,
254
254
  };
255
+ /**
256
+ * The frame's materials' reflection, added: the found colour times the tint, less the probe's share,
257
+ * which is negative where the probe was brighter than what replaced it. A half-float scene takes
258
+ * the negative addend as the subtraction it is. Its alpha kept.
259
+ */
260
+ export const FRAME_REFLECTION_BLEND = {
261
+ color: { srcFactor: 'one', dstFactor: 'one', operation: 'add' },
262
+ alpha: SCENE_ALPHA_KEEPS,
263
+ };
255
264
  /**
256
265
  * The bloom pyramid's way back up: an octave added onto a level that the blend multiplies by that
257
266
  * level's tint first — the blend constant, which white makes `one, one` exactly. Into the pyramid's
@@ -340,6 +349,11 @@ export const SSR_FRAG_SIZE = SSR_BINDINGS.SSR_TRACE_FRAG.uniformSize;
340
349
  export const SSR_UNIFORMS = SSR_BINDINGS.SSR_TRACE_FRAG.uniforms;
341
350
  export const SSR_TEXTURES = SSR_BINDINGS.SSR_TRACE_FRAG.textures;
342
351
  export const SSR_RESOLVE_TEXTURES = SSR_RESOLVE_BINDINGS.SSR_RESOLVE_FRAG.textures;
352
+ /** The material resolve's block — the blur — and its three inputs. See `frameReflections.ts`. */
353
+ export const SSR_MATERIAL_FIELDS = SSR_RESOLVE_BINDINGS.SSR_MATERIAL_RESOLVE_FRAG.fields;
354
+ export const SSR_MATERIAL_SIZE = SSR_RESOLVE_BINDINGS.SSR_MATERIAL_RESOLVE_FRAG.uniformSize;
355
+ export const SSR_MATERIAL_UNIFORMS = SSR_RESOLVE_BINDINGS.SSR_MATERIAL_RESOLVE_FRAG.uniforms;
356
+ export const SSR_MATERIAL_TEXTURES = SSR_RESOLVE_BINDINGS.SSR_MATERIAL_RESOLVE_FRAG.textures;
343
357
  /**
344
358
  * The resolve's layout: one texture and no uniform block.
345
359
  *
@@ -390,7 +404,7 @@ export const MEDIUM_UNIFORMS = MEDIUM_BINDINGS.MEDIUM_FRAG.uniforms;
390
404
  export const MEDIUM_TEXTURES = MEDIUM_BINDINGS.MEDIUM_FRAG.textures;
391
405
  export const MEDIUM_UPSAMPLE_UNIFORMS = MEDIUM_BINDINGS.MEDIUM_UPSAMPLE_FRAG.uniforms;
392
406
  export const MEDIUM_UPSAMPLE_TEXTURES = MEDIUM_BINDINGS.MEDIUM_UPSAMPLE_FRAG.textures;
393
- export { RUSH_FRAG_WGSL, AO_FRAG_WGSL, AO_BLUR_FRAG_WGSL, TEMPORAL_RESOLVE_FRAG_WGSL, OIT_RESOLVE_FRAG_WGSL, DECAL_PROJECT_FRAG_WGSL, SSR_TRACE_FRAG_WGSL, SSR_RESOLVE_FRAG_WGSL, };
407
+ export { RUSH_FRAG_WGSL, AO_FRAG_WGSL, AO_BLUR_FRAG_WGSL, TEMPORAL_RESOLVE_FRAG_WGSL, OIT_RESOLVE_FRAG_WGSL, DECAL_PROJECT_FRAG_WGSL, SSR_TRACE_FRAG_WGSL, SSR_RESOLVE_FRAG_WGSL, SSR_MATERIAL_RESOLVE_FRAG_WGSL, };
394
408
  export { BLOOM_PREFILTER_FRAG_WGSL, BLOOM_DOWNSAMPLE_FRAG_WGSL, BLOOM_UPSAMPLE_FRAG_WGSL };
395
409
  export { RUSH_UNIFORMS, RUSH_TEXTURES };
396
410
  export const AO_UNIFORMS = AO_BINDINGS.AO_FRAG.uniforms;
@@ -0,0 +1,30 @@
1
+ import type { DrawCommand } from './drawCommand.ts';
2
+ /** Both targets' format: linear light and a tint, which an eight-bit target would clip and band. */
3
+ export declare const REFLECTION_SURFACE_FORMAT: GPUTextureFormat;
4
+ export declare class ReflectionSurfacePass {
5
+ private readonly device;
6
+ private readonly commands;
7
+ private probe;
8
+ private tint;
9
+ /** The environment as the frame shows it, and the roughness in alpha. */
10
+ probeView: GPUTextureView | null;
11
+ /** What a found reflection is multiplied by. */
12
+ tintView: GPUTextureView | null;
13
+ constructor(device: GPUDevice);
14
+ /** A command for one opaque lit draw's surface half, filled by the caller. */
15
+ take(): DrawCommand;
16
+ /** How many draws the frame kept. */
17
+ get kept(): number;
18
+ /** A new frame keeps nothing yet. */
19
+ reset(): void;
20
+ /** The two targets at the frame's size, made again only when it changes. */
21
+ size(width: number, height: number): void;
22
+ /**
23
+ * Every kept draw into the two targets against the frame's depth, which it reads and does not
24
+ * write; both cleared first, so a pixel no lit surface reached reflects nothing. Called with no
25
+ * pass open on `encoder`, and drawn even with nothing kept, since the clear is what the trace
26
+ * reads there.
27
+ */
28
+ draw(encoder: GPUCommandEncoder, depth: GPUTextureView, issue: (pass: GPURenderPassEncoder, command: DrawCommand) => void): void;
29
+ release(): void;
30
+ }
@@ -0,0 +1,100 @@
1
+ /**
2
+ * The surface half of a reflection traced through the frame's materials, on this backend: the
3
+ * frame's opaque lit draws again, against the depth they wrote, into two half-float targets — the
4
+ * environment as the frame shows it with the surface's roughness, and what a reflection found in the
5
+ * frame is multiplied by to land in its place. `shaders/flat/reflectionSurface.ts` is what the two
6
+ * hold and why the swap is exact; this is where they are drawn.
7
+ *
8
+ * **Why a second pass and not a second output of the frame's.** Every pipeline drawn into the
9
+ * frame's pass would have to declare a target it never writes — the sky, water, particles, text and
10
+ * every pass a consumer registers — so the frame keeps its one attachment, and the lit stage's two
11
+ * extra outputs land nowhere there. Here they land: the same pipelines with `REFLECTION_SURFACE` on,
12
+ * which leaves before the lamps, the same bind groups and ring slots, tested `equal` against the
13
+ * frame's depth so only what the frame kept writes.
14
+ *
15
+ * **The draws are commands, kept the way the skin's halves are** (`skinScatterPass.ts`): the frame
16
+ * graph's pool is spent at every flush, and these are replayed after the flush that ends the frame's
17
+ * pass. Each holds the ring slots its frame draw used, which stay valid until the frame's last
18
+ * submit, after this has run.
19
+ *
20
+ * **What it costs**: every opaque lit draw twice, the second without its lamps; two half-float
21
+ * targets the size of the frame. Single-sampled only, as the trace that reads them is.
22
+ */
23
+ import { createCommandPool, resetPool, takeCommand } from './drawCommand.js';
24
+ /** Both targets' format: linear light and a tint, which an eight-bit target would clip and band. */
25
+ export const REFLECTION_SURFACE_FORMAT = 'rgba16float';
26
+ const USAGE_TARGET = 0x10 | 0x4; // RENDER_ATTACHMENT | TEXTURE_BINDING
27
+ export class ReflectionSurfacePass {
28
+ device;
29
+ commands = createCommandPool(256);
30
+ probe = null;
31
+ tint = null;
32
+ /** The environment as the frame shows it, and the roughness in alpha. */
33
+ probeView = null;
34
+ /** What a found reflection is multiplied by. */
35
+ tintView = null;
36
+ constructor(device) {
37
+ this.device = device;
38
+ }
39
+ /** A command for one opaque lit draw's surface half, filled by the caller. */
40
+ take() {
41
+ return this.commands.commands[takeCommand(this.commands)];
42
+ }
43
+ /** How many draws the frame kept. */
44
+ get kept() {
45
+ return this.commands.taken;
46
+ }
47
+ /** A new frame keeps nothing yet. */
48
+ reset() {
49
+ resetPool(this.commands);
50
+ }
51
+ /** The two targets at the frame's size, made again only when it changes. */
52
+ size(width, height) {
53
+ if (this.probe !== null && this.probe.width === width && this.probe.height === height)
54
+ return;
55
+ this.release();
56
+ const make = (label) => this.device.createTexture({
57
+ label,
58
+ size: [width, height],
59
+ format: REFLECTION_SURFACE_FORMAT,
60
+ usage: USAGE_TARGET,
61
+ });
62
+ this.probe = make('reflection.probe');
63
+ this.tint = make('reflection.tint');
64
+ this.probeView = this.probe.createView();
65
+ this.tintView = this.tint.createView();
66
+ }
67
+ /**
68
+ * Every kept draw into the two targets against the frame's depth, which it reads and does not
69
+ * write; both cleared first, so a pixel no lit surface reached reflects nothing. Called with no
70
+ * pass open on `encoder`, and drawn even with nothing kept, since the clear is what the trace
71
+ * reads there.
72
+ */
73
+ draw(encoder, depth, issue) {
74
+ const probe = this.probeView;
75
+ const tint = this.tintView;
76
+ if (probe === null || tint === null)
77
+ return;
78
+ const pass = encoder.beginRenderPass({
79
+ label: 'reflection.surface',
80
+ colorAttachments: [
81
+ null,
82
+ { view: probe, clearValue: [0, 0, 0, 1], loadOp: 'clear', storeOp: 'store' },
83
+ { view: tint, clearValue: [0, 0, 0, 0], loadOp: 'clear', storeOp: 'store' },
84
+ ],
85
+ depthStencilAttachment: { view: depth, depthReadOnly: true },
86
+ });
87
+ for (let i = 0; i < this.commands.taken; i += 1) {
88
+ issue(pass, this.commands.commands[i]);
89
+ }
90
+ pass.end();
91
+ }
92
+ release() {
93
+ this.probe?.destroy();
94
+ this.tint?.destroy();
95
+ this.probe = null;
96
+ this.tint = null;
97
+ this.probeView = null;
98
+ this.tintView = null;
99
+ }
100
+ }