@driftengine/core 4.4.1 → 4.4.2

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 (38) hide show
  1. package/CHANGELOG.json +26 -0
  2. package/README.md +1 -1
  3. package/dist/render/backend/webgl2/renderer.d.ts +3 -0
  4. package/dist/render/backend/webgl2/renderer.js +13 -0
  5. package/dist/render/backend/webgpu/postPass.d.ts +19 -3
  6. package/dist/render/backend/webgpu/postPass.js +17 -4
  7. package/dist/render/backend/webgpu/renderer.d.ts +10 -0
  8. package/dist/render/backend/webgpu/renderer.js +67 -4
  9. package/dist/render/flameLight.js +15 -2
  10. package/dist/render/pointShadowBudget.js +51 -41
  11. package/dist/render/recon/disocclusion.d.ts +36 -3
  12. package/dist/render/recon/disocclusion.js +56 -4
  13. package/dist/render/recon/resolve.js +95 -12
  14. package/dist/render/sceneTarget.d.ts +2 -0
  15. package/dist/render/sceneTarget.js +1 -1
  16. package/dist/render/shaders/generated/temporalResolve.wgsl.d.ts +31 -6
  17. package/dist/render/shaders/generated/temporalResolve.wgsl.js +31 -6
  18. package/dist/render/shaders/recon/resolve.wgsl.d.ts +1 -1
  19. package/dist/render/shaders/recon/resolve.wgsl.js +112 -8
  20. package/dist/render/shaders/temporalResolve.js +133 -5
  21. package/dist/render/temporalAa.d.ts +145 -0
  22. package/dist/render/temporalAa.js +190 -0
  23. package/dist/render/temporalPass.d.ts +16 -14
  24. package/dist/render/temporalPass.js +59 -14
  25. package/package.json +6 -6
  26. package/src/render/backend/webgl2/renderer.ts +21 -1
  27. package/src/render/backend/webgpu/postPass.ts +34 -4
  28. package/src/render/backend/webgpu/renderer.ts +70 -4
  29. package/src/render/flameLight.ts +15 -2
  30. package/src/render/pointShadowBudget.ts +49 -39
  31. package/src/render/recon/disocclusion.ts +54 -3
  32. package/src/render/recon/resolve.ts +137 -10
  33. package/src/render/sceneTarget.ts +8 -1
  34. package/src/render/shaders/generated/temporalResolve.wgsl.ts +31 -6
  35. package/src/render/shaders/recon/resolve.wgsl.ts +112 -8
  36. package/src/render/shaders/temporalResolve.ts +141 -5
  37. package/src/render/temporalAa.ts +227 -0
  38. package/src/render/temporalPass.ts +88 -12
@@ -82,6 +82,7 @@ export function flameFrequencyHz(diameterM: number): number {
82
82
  export function createFlame(options: FlameOptions): Flame {
83
83
  const height = HEIGHT_PER_DIAMETER * options.diameterM;
84
84
  const centre: Vec3 = [options.x, options.y + height / 3, options.z];
85
+ const swayM = (options.sway ?? SWAY_PER_HEIGHT) * height;
85
86
  /* A seed spread over the whole turn, so seeds 1 and 2 are not neighbours in phase. */
86
87
  const phase = ((options.seed ?? 0) * 2.399963) % (Math.PI * 2);
87
88
  const light: PointLightSource = {
@@ -94,7 +95,19 @@ export function createFlame(options: FlameOptions): Flame {
94
95
  radius: options.radius,
95
96
  /* Off, because this helper is the flicker; the selection's own would apply a second one. */
96
97
  flicker: 0,
97
- shadowNear: Math.max(0.05, options.diameterM),
98
+ /*
99
+ * **Clear of what the fire burns from wherever the light has wandered**, which is the sway added
100
+ * to the near plane a still flame would want. The shadow map is baked from where the light
101
+ * stood, so the cut moves with it, and each axis moves by at most `swayM`: a near plane of D
102
+ * alone left a brazier's bowl 7 mm inside it at the top of the sway and 13 cm outside at the
103
+ * bottom, and the bowl's shadow on the floor under the brazier came and went with every
104
+ * re-bake — a dark disc on some, none on others.
105
+ *
106
+ * What it gives up is `swayM` of shadow nearest the light, which is inside the fixture. What
107
+ * would make it wrong is a fixture reaching further below the flame than D / 6: this clears
108
+ * that far and no further, so a deep bowl sets its own `shadowNear` from its own depth.
109
+ */
110
+ shadowNear: Math.max(0.05, options.diameterM) + swayM,
98
111
  sourceRadius: options.diameterM / 2,
99
112
  ...(options.castsShadow === false ? { castsShadow: false } : {}),
100
113
  };
@@ -112,7 +125,7 @@ export function createFlame(options: FlameOptions): Flame {
112
125
  color: [options.color[0], options.color[1], options.color[2]],
113
126
  flicker: options.flicker ?? 0.18,
114
127
  phase,
115
- swayM: (options.sway ?? SWAY_PER_HEIGHT) * height,
128
+ swayM,
116
129
  };
117
130
  }
118
131
 
@@ -256,15 +256,15 @@ export function runPointShadowBakes<M extends StaleCheck, L extends BakeableLigh
256
256
  * **The run decays rather than resetting, and it climbs past the threshold before it stops.**
257
257
  *
258
258
  * A light that wanders on a curve is not stale on *every* frame — near the turning points of
259
- * its travel it moves less in a frame than the rebake tolerance, so a run that reset to zero
260
- * lost the tracking allowance there and the map dropped back to two faces a frame. The shadow
261
- * then travelled smoothly through the fast part of the wander and stepped through the slow
262
- * part, which is not motion and not stillness: it reads as a vibration. Reported that way,
263
- * against a brazier, once the tracking allowance was in.
259
+ * its travel it moves less in a frame than the rebake tolerance. When the run decided whether a
260
+ * wanderer got its whole cube, a run that reset to zero dropped it back to two faces a frame
261
+ * there, and the shadow stepped through the slow part of the wander: reported as a vibration
262
+ * against a brazier. Every image-holding map is re-baked whole now, so the run only orders the
263
+ * queue, and the hysteresis keeps a wanderer behind the lights that settle instead of letting
264
+ * it jump ahead at every turning point.
264
265
  *
265
- * So the run is hysteresis. It climbs to twice the threshold, which buys a light that has
266
- * earned the allowance three quiet frames before it loses it, and decays one frame at a time
267
- * so a light that genuinely settles stops re-baking rather than holding a budget for ever.
266
+ * It climbs to twice the threshold, which keeps a light chronic for three quiet frames, and
267
+ * decays one frame at a time so a light that genuinely settles rejoins the ordinary order.
268
268
  */
269
269
  const previous = scratch.staleRun[slot] ?? 0;
270
270
  const run = stale
@@ -285,37 +285,40 @@ export function runPointShadowBakes<M extends StaleCheck, L extends BakeableLigh
285
285
  let faces = facesPerFrame;
286
286
  let urgentSpent = false;
287
287
  /*
288
- * **One wandering light finishes its cube in the frame it started it**, the same allowance a
289
- * light with no image at all gets, and for a reason that turns out to be the same one: a map
290
- * dribbled out at two faces a frame is a map nobody can sample correctly until it lands.
288
+ * **A map that holds an image is re-baked whole or not at all**, and one being shaded may take a
289
+ * whole cube a frame outside the budget to make that possible.
291
290
  *
292
- * A cold light cannot be sampled *at all* until its six faces are in, so it takes the whole
293
- * cube at once. A chronically stale one can be sampled throughout and is wrong in a quieter
294
- * way: `planBake` pins the origin for the whole of a resumed bake, so six faces spread over
295
- * three frames means the published origin steps once every third frame. The shader shoots from
296
- * that origin, so the shadow it draws steps with it — reported on a brazier as motion that was
297
- * "really fast, not smooth, snappy, and unrealistic", which is a 20 Hz sample of a flame that
298
- * wanders about once a second.
291
+ * Faces land in the layer the shader samples as they are drawn, and `planBake` publishes the
292
+ * origin they were drawn from only with the last one. So a re-bake spread over frames is a cube
293
+ * the shader reads from the old origin with part of it drawn from the new one — and a face is a
294
+ * 90 degree frustum, so the part drawn from somewhere else is a straight-edged quadrilateral of
295
+ * wrong shadow on the ground under the lamp. With the budget spent elsewhere it could hold for a
296
+ * second and more. Reported under a brazier as a dark shape that came and went.
299
297
  *
300
- * Given the cube in one frame the origin moves every frame and the shadow travels instead.
298
+ * A map with no image yet is not sampled until its sixth face lands (see
299
+ * `PointShadowSystem.bind`), so it still fills a few faces a frame, and a cold one being shaded
300
+ * still jumps the queue with all six.
301
301
  *
302
- * **Bounded to one light, and it has to be.** Eight braziers each taking six passes is the 48
303
- * passes and 89 ms `pointShadowFacesPerFrame` exists to prevent. One is the light being shaded
304
- * that most recently went stale, which is the one somebody is standing next to; the rest keep
305
- * dribbling under the ordinary budget, and their shadows step rather than travel. That is the
306
- * right way round: a shadow you are looking at moves, and a shadow across the square does not
307
- * cost the frame.
302
+ * The allowance used to be for a *chronically* stale light only — a wandering flame — whose
303
+ * dribbled cube published a new origin once every third frame, so its shadow stepped at 20 Hz
304
+ * where the flame wanders about once a second: reported as motion that was "really fast, not
305
+ * smooth, snappy, and unrealistic". Any shaded light is owed the same now, because a cube read
306
+ * part-way is wrong in the same place for the same reason whether or not it will be stale again.
308
307
  *
309
- * It is spent *after* the cold and settling groups, because `planPointShadowBakes` orders the
310
- * chronic ones last — so a light arriving in range still gets its first image ahead of a flame
311
- * refining one it already has.
308
+ * **Bounded to one light, and it has to be.** Eight braziers each taking six passes is the 48
309
+ * passes and 89 ms `pointShadowFacesPerFrame` exists to prevent. The first in the plan's order
310
+ * takes it, and `planPointShadowBakes` puts the lights that settled ahead of the ones that never
311
+ * do, so a flame refining its image cannot hold a correction hostage. The rest keep sampling a
312
+ * complete image from a few centimetres away until their turn, which is the trade
313
+ * `pointShadowRebakeDistance` already makes. A warm map, held but not shaded, is re-baked when a
314
+ * whole cube fits the budget or once it is shaded, and not before.
312
315
  *
313
- * **What would make it wrong** is a caster set heavy enough that six passes over it does not
314
- * fit the frame. Measured against a game whose whole static world is about 8,500 triangles in
315
- * a handful of draws; a world an order of magnitude heavier wants `pointShadowFacesPerFrame`
316
+ * **What would make it wrong** is a caster set heavy enough that six passes over it does not fit
317
+ * the frame. Measured against a game whose whole static world is about 8,500 triangles in a
318
+ * handful of draws; a world an order of magnitude heavier wants `pointShadowFacesPerFrame`
316
319
  * raised or this given up, and `gpuTiming` is how to tell which.
317
320
  */
318
- let trackingSpent = false;
321
+ let refreshSpent = false;
319
322
  for (let i = 0; i < planned && faces > 0; i++) {
320
323
  const slot = scratch.order[i] ?? -1;
321
324
  /*
@@ -328,13 +331,20 @@ export function runPointShadowBakes<M extends StaleCheck, L extends BakeableLigh
328
331
  if (light === undefined || map === undefined) continue;
329
332
 
330
333
  const sampled = slot < pool.sampledCount;
331
- const cold = sampled && scratch.ready[slot] === 0;
332
- const urgent = cold && !urgentSpent;
333
- /* A light being shaded whose map will be stale again next frame however it is served. */
334
- const tracking = !cold && sampled && scratch.chronic[slot] === 1 && !trackingSpent;
335
- if (urgent) urgentSpent = true;
336
- if (tracking) trackingSpent = true;
337
- faces -= bake(map, light, staticCasters, urgent || tracking ? faceCount : faces);
334
+ if (scratch.ready[slot] !== 1) {
335
+ /* No image to spoil: a few faces a frame, or all six at once for one being shaded. */
336
+ const urgent = sampled && !urgentSpent;
337
+ if (urgent) urgentSpent = true;
338
+ faces -= bake(map, light, staticCasters, urgent ? faceCount : faces);
339
+ continue;
340
+ }
341
+ if (faces >= faceCount) {
342
+ faces -= bake(map, light, staticCasters, faceCount);
343
+ continue;
344
+ }
345
+ if (!sampled || refreshSpent) continue;
346
+ refreshSpent = true;
347
+ faces -= bake(map, light, staticCasters, faceCount);
338
348
  }
339
349
 
340
350
  /*
@@ -12,10 +12,15 @@
12
12
  * a different part of a texel than the one it stands for, which at a grazing angle is a different
13
13
  * depth; the tolerance grows with the motion's length.
14
14
  *
15
+ * **And in proportion to the surface's own slope.** A new jitter samples a surface somewhere else, so
16
+ * on one turned from the eye two frames' samples of it differ by up to a texel's worth of its depth
17
+ * with nothing moved. `surfaceSlope` measures that step on the surface's own side of any silhouette.
18
+ *
15
19
  * **Normals, for the case depth cannot see.** A leaf's two sides are at one depth, and the history of
16
20
  * the front is not the back. The forward frame has no normal buffer, so both normals come from their
17
21
  * frame's depth — `worldPositionFromDepth` and `normalFromPositions` — in world space, where the two
18
- * frames agree.
22
+ * frames agree. **A normal differenced across an edge is not a normal**, so the caller weighs each by
23
+ * `planarity`: at an edge, a sliver, or anything finer than a texel the depth test decides alone.
19
24
  *
20
25
  * **Continuous in every input**, because a step is a line on screen along which the history is kept on
21
26
  * one frame and dropped on the next: every threshold is a smoothstep, never a comparison.
@@ -51,12 +56,57 @@ function smoothstep(edge0: number, edge1: number, x: number): number {
51
56
  return t * t * (3 - 2 * t);
52
57
  }
53
58
 
59
+ /**
60
+ * The band over which three texels stop being one plane: how far their two steps in inverse depth
61
+ * fail to cancel, as a share of the two together. None of it is a plane at any angle; all of it is an
62
+ * edge — one side steps, the other does not — or a sliver, both stepping the same way.
63
+ *
64
+ * **A share and not a size**, which is the lesson of the rods in `resolve.test.ts`: a part a
65
+ * hundredth of the depth in front of a wall passes the depth test, and a normal differenced across it
66
+ * still leans by its height over a texel's width — nearly edge-on where texels are small. What would
67
+ * make this wrong is a curved surface judged at its apex, where both steps lean one way and the
68
+ * normal is sound; there it only gives the normals less say, which the depth test does not need.
69
+ */
70
+ export const PLANAR_FROM = 0.25;
71
+ export const PLANAR_TO = 0.5;
72
+
73
+ /**
74
+ * How sure it is that `centre` and its neighbours either side along one axis lie on one plane, one to
75
+ * zero. Inverse depth is affine across a plane on screen, so a plane's two steps cancel whatever its
76
+ * angle. One where a neighbour is missing — zero, off the picture or undrawn — or where there is no
77
+ * step to compare: nothing to judge by, so a normal keeps the say it always had.
78
+ */
79
+ export function planarity(before: number, centre: number, after: number): number {
80
+ if (!(before > 0) || !(centre > 0) || !(after > 0)) return 1;
81
+ const a = 1 / after - 1 / centre;
82
+ const b = 1 / before - 1 / centre;
83
+ const spread = Math.abs(a) + Math.abs(b);
84
+ if (!(spread > 0)) return 1;
85
+ return 1 - smoothstep(PLANAR_FROM, PLANAR_TO, Math.abs(a + b) / spread);
86
+ }
87
+
88
+ /**
89
+ * How far a surface's depth moves across one texel along an axis, relative to the depth: the smaller
90
+ * of the steps to its two neighbours, because beside a silhouette one of them is the surface behind.
91
+ * Zero at a sliver — nearer or farther than both, so each step is to another surface — and one
92
+ * neighbour's step where only one is there.
93
+ */
94
+ export function surfaceSlope(before: number, centre: number, after: number): number {
95
+ const a = after > 0 ? after - centre : Number.NaN;
96
+ const b = before > 0 ? before - centre : Number.NaN;
97
+ if (Number.isNaN(a)) return Number.isNaN(b) ? 0 : Math.abs(b) / centre;
98
+ if (Number.isNaN(b)) return Math.abs(a) / centre;
99
+ if (a * b > 0) return 0;
100
+ return Math.min(Math.abs(a), Math.abs(b)) / centre;
101
+ }
102
+
54
103
  /**
55
104
  * One where the history is trustworthy, zero where it is not, and continuous between.
56
105
  *
57
106
  * `currDepth` is the view depth this surface should have had last frame; `histDepth` the view depth
58
107
  * last frame held where it lands, or zero where it landed off the screen; `motionLength` the motion's
59
- * length in uv; `normalDot` the cosine between the two frames' world normals there.
108
+ * length in uv; `normalDot` the cosine between the two frames' world normals there; `slope` the
109
+ * surface's `surfaceSlope`, which widens the tolerance one for one.
60
110
  */
61
111
  export function disocclusionWeight(
62
112
  currDepth: number,
@@ -64,12 +114,13 @@ export function disocclusionWeight(
64
114
  motionLength: number,
65
115
  normalDot: number,
66
116
  params: DisocclusionParams,
117
+ slope = 0,
67
118
  ): number {
68
119
  if (!(currDepth > 0) || !(histDepth > 0) || !(motionLength >= 0) || Number.isNaN(normalDot)) {
69
120
  return 0;
70
121
  }
71
122
  const disagreement = Math.abs(currDepth - histDepth) / currDepth;
72
- const tolerance = params.depthTolerance + params.motionScale * motionLength;
123
+ const tolerance = params.depthTolerance + params.motionScale * motionLength + slope;
73
124
  const depth = 1 - smoothstep(tolerance, 2 * tolerance, disagreement);
74
125
  const normal = smoothstep(params.normalFloor, params.normalCeiling, normalDot);
75
126
  return depth * normal;
@@ -21,8 +21,10 @@
21
21
  * previous view depth in its third channel; otherwise it is the camera's, through the surface's
22
22
  * world position.
23
23
  * 4. Where the surface was is read from the history — its colour and the weight it had gathered — and
24
- * last frame's depth there says whether it is the same surface; the two frames' normals, from their
25
- * depths, catch thin geometry. The disocclusion scales the history's weight.
24
+ * last frame's depth says whether it is the same surface: the nearest of the nine texels around
25
+ * where the dilated texel was, forgiven its own slope across a texel. The two frames' normals, from
26
+ * their depths, catch thin geometry, each with the say its planarity gives it. The disocclusion
27
+ * scales the history's weight.
26
28
  * 5. The history is clipped to the box, its weight capped so a tenth of each frame is always new, and
27
29
  * the two are mixed by their weights. Where the pixel has gathered less than one sample's worth,
28
30
  * the render read bicubically fills the gap. The history keeps colour and weight; the picture shown
@@ -33,7 +35,13 @@
33
35
  * buffer before a sample reaches this arithmetic, as the temporal resolve already does.
34
36
  */
35
37
  import { clipToBox, rgbToYCoCg, varianceBox, yCoCgToRgb } from './clamp.ts';
36
- import { disocclusionWeight, normalFromPositions, worldPositionFromDepth } from './disocclusion.ts';
38
+ import {
39
+ disocclusionWeight,
40
+ normalFromPositions,
41
+ planarity,
42
+ surfaceSlope,
43
+ worldPositionFromDepth,
44
+ } from './disocclusion.ts';
37
45
  import type { DisocclusionParams } from './disocclusion.ts';
38
46
  import { sampleBilinear, sampleHistoryBicubic5 } from './reproject.ts';
39
47
 
@@ -209,6 +217,59 @@ function worldAt(
209
217
  return 1 / w;
210
218
  }
211
219
 
220
+ /**
221
+ * A texel's view depth at its unjittered uv, or 0 — `worldAt` without the position, which is all a
222
+ * search over depths needs: one row of the inverse rather than four. Zero off the picture, where
223
+ * `worldAt` would clamp, because a neighbour that is not there is not a step.
224
+ */
225
+ function viewDepthAt(
226
+ depths: ArrayLike<number>,
227
+ inverse: ArrayLike<number>,
228
+ jitter: ArrayLike<number>,
229
+ width: number,
230
+ height: number,
231
+ x: number,
232
+ y: number,
233
+ ): number {
234
+ if (x < 0 || y < 0 || x >= width || y >= height) return 0;
235
+ const u = (x + 0.5 - (jitter[0] as number)) / width;
236
+ const v = (y + 0.5 - (jitter[1] as number)) / height;
237
+ const m = inverse;
238
+ const w =
239
+ (m[3] as number) * (u * 2 - 1) +
240
+ (m[7] as number) * (v * 2 - 1) +
241
+ (m[11] as number) * (depths[y * width + x] as number) +
242
+ (m[15] as number);
243
+ return Math.abs(w) > 1e-12 ? 1 / w : 0;
244
+ }
245
+
246
+ /** What a texel's four neighbours say about its surface: see `planarity` and `surfaceSlope`. */
247
+ const SURFACE = { planar: 1, slope: 0 };
248
+
249
+ function surfaceAt(
250
+ depths: ArrayLike<number>,
251
+ inverse: ArrayLike<number>,
252
+ jitter: ArrayLike<number>,
253
+ width: number,
254
+ height: number,
255
+ x: number,
256
+ y: number,
257
+ centre: number,
258
+ ): void {
259
+ let planar = 1;
260
+ let slope = 0;
261
+ for (let axis = 0; axis < 2; axis += 1) {
262
+ const ax = 1 - axis;
263
+ const ay = axis;
264
+ const after = viewDepthAt(depths, inverse, jitter, width, height, x + ax, y + ay);
265
+ const before = viewDepthAt(depths, inverse, jitter, width, height, x - ax, y - ay);
266
+ planar *= planarity(before, centre, after);
267
+ slope = Math.max(slope, surfaceSlope(before, centre, after));
268
+ }
269
+ SURFACE.planar = planar;
270
+ SURFACE.slope = slope;
271
+ }
272
+
212
273
  /** The world normal at a texel, from its depth and its right and upper neighbours'. */
213
274
  function normalAt(
214
275
  depths: ArrayLike<number>,
@@ -282,20 +343,84 @@ function historyWeight(frame: ResolveFrame, u: number, v: number, dx: number, dy
282
343
  4,
283
344
  HISTORY,
284
345
  );
285
- const px = clampTexel(Math.round(pu * rw + (frame.previousJitter[0] as number) - 0.5), rw);
286
- const py = clampTexel(Math.round(pv * rh + (frame.previousJitter[1] as number) - 0.5), rh);
287
- const held = worldAt(
346
+ /*
347
+ * **Last frame's depth is searched for this surface, around where the dilated texel was.** The
348
+ * surface being judged is the dilated texel's, which beside an edge is a neighbour's, so judging
349
+ * it by the texel under the pixel compares two surfaces: the side of every silhouette away from the
350
+ * eye dropped its history on every frame of a still picture. And a new jitter samples a surface
351
+ * somewhere else, so the nearest of last frame's nine is taken rather than the one the rounding
352
+ * picks. What it gives up is a disocclusion narrower than a texel, which keeps a history that the
353
+ * neighbourhood clip then bounds.
354
+ */
355
+ const su = (dx + 0.5 - (frame.jitter[0] as number)) / rw + mu;
356
+ const sv = (dy + 0.5 - (frame.jitter[1] as number)) / rh + mv;
357
+ const cx = clampTexel(Math.round(su * rw + (frame.previousJitter[0] as number) - 0.5), rw);
358
+ const cy = clampTexel(Math.round(sv * rh + (frame.previousJitter[1] as number) - 0.5), rh);
359
+ /*
360
+ * The first texel looked at answers alone when it agrees within the tolerance, which it does
361
+ * everywhere but at an edge: the depth test trusts it fully whichever texel would have won, so
362
+ * only an edge pays for the other eight.
363
+ */
364
+ let held = viewDepthAt(
288
365
  frame.previousDepth,
289
366
  frame.previousInverseViewProj,
290
367
  frame.previousJitter,
291
368
  rw,
292
369
  rh,
293
- px,
294
- py,
295
- RIGHT,
370
+ cx,
371
+ cy,
296
372
  );
373
+ let px = cx;
374
+ let py = cy;
375
+ if (!(held > 0 && Math.abs(held - expected) <= frame.disocclusion.depthTolerance * expected)) {
376
+ held = 0;
377
+ let nearest = Number.POSITIVE_INFINITY;
378
+ for (let j = -1; j <= 1; j += 1) {
379
+ for (let i = -1; i <= 1; i += 1) {
380
+ const tx = clampTexel(cx + i, rw);
381
+ const ty = clampTexel(cy + j, rh);
382
+ const depth = viewDepthAt(
383
+ frame.previousDepth,
384
+ frame.previousInverseViewProj,
385
+ frame.previousJitter,
386
+ rw,
387
+ rh,
388
+ tx,
389
+ ty,
390
+ );
391
+ if (!(depth > 0)) continue;
392
+ const off = Math.abs(depth - expected);
393
+ if (off < nearest) {
394
+ nearest = off;
395
+ held = depth;
396
+ px = tx;
397
+ py = ty;
398
+ }
399
+ }
400
+ }
401
+ }
402
+
403
+ const here = viewDepthAt(frame.depth, frame.inverseViewProj, frame.jitter, rw, rh, dx, dy);
404
+ surfaceAt(frame.depth, frame.inverseViewProj, frame.jitter, rw, rh, dx, dy, here);
405
+ const slope = SURFACE.slope;
406
+ let planar = SURFACE.planar;
407
+ if (planar > 0) {
408
+ surfaceAt(
409
+ frame.previousDepth,
410
+ frame.previousInverseViewProj,
411
+ frame.previousJitter,
412
+ rw,
413
+ rh,
414
+ px,
415
+ py,
416
+ held,
417
+ );
418
+ planar *= SURFACE.planar;
419
+ }
420
+ /* A normal has the say its planarity gives it; at an edge or a sliver, none, and none is computed. */
297
421
  let normalDot = 1;
298
422
  if (
423
+ planar > 0 &&
299
424
  normalAt(frame.depth, frame.inverseViewProj, frame.jitter, frame.eye, rw, rh, dx, dy, NORMAL) &&
300
425
  normalAt(
301
426
  frame.previousDepth,
@@ -309,10 +434,11 @@ function historyWeight(frame: ResolveFrame, u: number, v: number, dx: number, dy
309
434
  PREVIOUS_NORMAL,
310
435
  )
311
436
  ) {
312
- normalDot =
437
+ const cosine =
313
438
  (NORMAL[0] as number) * (PREVIOUS_NORMAL[0] as number) +
314
439
  (NORMAL[1] as number) * (PREVIOUS_NORMAL[1] as number) +
315
440
  (NORMAL[2] as number) * (PREVIOUS_NORMAL[2] as number);
441
+ normalDot = 1 - planar * (1 - cosine);
316
442
  }
317
443
  const trust = disocclusionWeight(
318
444
  expected,
@@ -320,6 +446,7 @@ function historyWeight(frame: ResolveFrame, u: number, v: number, dx: number, dy
320
446
  Math.hypot(mu, mv),
321
447
  normalDot,
322
448
  frame.disocclusion,
449
+ slope,
323
450
  );
324
451
  return (HISTORY[3] as number) * trust;
325
452
  }
@@ -743,7 +743,12 @@ export class SceneTarget {
743
743
  * `veilColor` and `veilAlpha` are positional defaults and every existing caller passes them
744
744
  * by position.
745
745
  */
746
- temporal?: { readonly reprojection: Float32Array; readonly blend: number },
746
+ temporal?: {
747
+ readonly reprojection: Float32Array;
748
+ readonly blend: number;
749
+ readonly periodStart: boolean;
750
+ readonly depthToViewZ: Float32Array;
751
+ },
747
752
  ): void {
748
753
  const { gl } = this;
749
754
  this.depthWanted =
@@ -883,6 +888,8 @@ export class SceneTarget {
883
888
  this.width,
884
889
  this.height,
885
890
  temporal.blend,
891
+ temporal.periodStart,
892
+ temporal.depthToViewZ,
886
893
  );
887
894
  }
888
895
 
@@ -5,7 +5,7 @@
5
5
  * the next generation, and `npm run wgsl:check` fails the build when this is stale.
6
6
  */
7
7
 
8
- export const TEMPORAL_RESOLVE_FRAG_WGSL = "struct Uniforms {\n uReprojection: mat4x4<f32>,\n uTexel: vec2<f32>,\n uHistoryBlend: f32,\n}\n\n@group(0) @binding(32) \nvar uScene_t: texture_2d<f32>;\n@group(0) @binding(33) \nvar uScene_s: sampler;\nvar<private> vUv_1: vec2<f32>;\n@group(0) @binding(1) \nvar<uniform> unnamed: Uniforms;\n@group(0) @binding(34) \nvar uHistory_t: texture_2d<f32>;\n@group(0) @binding(35) \nvar uHistory_s: sampler;\nvar<private> fragColor: vec4<f32>;\n@group(0) @binding(36) \nvar uDepth_t: texture_2d<f32>;\n@group(0) @binding(37) \nvar uDepth_s: sampler;\n\nfn clipToNeighbourhood_u0028_vf3_u003b_vf3_u003b_vf3_u003b(history: ptr<function, vec3<f32>>, lo: ptr<function, vec3<f32>>, hi: ptr<function, vec3<f32>>) -> vec3<f32> {\n var centre: vec3<f32>;\n var extent: vec3<f32>;\n var offset: vec3<f32>;\n var ratios: vec3<f32>;\n var ratio: f32;\n\n let _e39 = (*lo);\n let _e40 = (*hi);\n centre = ((_e39 + _e40) * 0.5f);\n let _e43 = (*hi);\n let _e44 = (*lo);\n extent = ((_e43 - _e44) * 0.5f);\n let _e47 = (*history);\n let _e48 = centre;\n offset = (_e47 - _e48);\n let _e50 = offset;\n let _e52 = extent;\n ratios = (abs(_e50) / max(_e52, vec3<f32>(0.0000001f, 0.0000001f, 0.0000001f)));\n let _e56 = ratios[0u];\n let _e58 = ratios[1u];\n let _e60 = ratios[2u];\n ratio = max(_e56, max(_e58, _e60));\n let _e63 = ratio;\n if (_e63 <= 1f) {\n let _e65 = (*history);\n return _e65;\n }\n let _e66 = centre;\n let _e67 = offset;\n let _e68 = ratio;\n return (_e66 + (_e67 / vec3(_e68)));\n}\n\nfn historyCatmullRom_u0028_vf2_u003b(uv: ptr<function, vec2<f32>>) -> vec3<f32> {\n var size: vec2<f32>;\n var at: vec2<f32>;\n var base: vec2<f32>;\n var f: vec2<f32>;\n var w0_: vec2<f32>;\n var w1_: vec2<f32>;\n var w2_: vec2<f32>;\n var w3_: vec2<f32>;\n var w12_: vec2<f32>;\n var at0_: vec2<f32>;\n var at3_: vec2<f32>;\n var at12_: vec2<f32>;\n var sum: vec3<f32>;\n var weight: f32;\n\n let _e47 = unnamed.uTexel;\n size = (vec2(1f) / _e47);\n let _e50 = (*uv);\n let _e51 = size;\n at = (_e50 * _e51);\n let _e53 = at;\n base = (floor((_e53 - vec2(0.5f))) + vec2(0.5f));\n let _e59 = at;\n let _e60 = base;\n f = (_e59 - _e60);\n let _e62 = f;\n let _e63 = f;\n let _e64 = f;\n w0_ = (_e62 * (vec2(-0.5f) + (_e63 * (vec2(1f) - (_e64 * 0.5f)))));\n let _e72 = f;\n let _e73 = f;\n let _e75 = f;\n w1_ = (vec2(1f) + ((_e72 * _e73) * (vec2(-2.5f) + (_e75 * 1.5f))));\n let _e82 = f;\n let _e83 = f;\n let _e84 = f;\n w2_ = (_e82 * (vec2(0.5f) + (_e83 * (vec2(2f) - (_e84 * 1.5f)))));\n let _e92 = f;\n let _e93 = f;\n let _e95 = f;\n w3_ = ((_e92 * _e93) * (vec2(-0.5f) + (_e95 * 0.5f)));\n let _e100 = w1_;\n let _e101 = w2_;\n w12_ = (_e100 + _e101);\n let _e103 = base;\n let _e107 = unnamed.uTexel;\n at0_ = ((_e103 - vec2(1f)) * _e107);\n let _e109 = base;\n let _e113 = unnamed.uTexel;\n at3_ = ((_e109 + vec2(2f)) * _e113);\n let _e115 = base;\n let _e116 = w2_;\n let _e117 = w12_;\n let _e121 = unnamed.uTexel;\n at12_ = ((_e115 + (_e116 / _e117)) * _e121);\n let _e124 = at12_[0u];\n let _e126 = at0_[1u];\n let _e128 = textureSampleLevel(uHistory_t, uHistory_s, vec2<f32>(_e124, _e126), 0f);\n let _e131 = w12_[0u];\n let _e133 = w0_[1u];\n let _e137 = at0_[0u];\n let _e139 = at12_[1u];\n let _e141 = textureSampleLevel(uHistory_t, uHistory_s, vec2<f32>(_e137, _e139), 0f);\n let _e144 = w0_[0u];\n let _e146 = w12_[1u];\n let _e150 = at12_;\n let _e151 = textureSampleLevel(uHistory_t, uHistory_s, _e150, 0f);\n let _e154 = w12_[0u];\n let _e156 = w12_[1u];\n let _e161 = at3_[0u];\n let _e163 = at12_[1u];\n let _e165 = textureSampleLevel(uHistory_t, uHistory_s, vec2<f32>(_e161, _e163), 0f);\n let _e168 = w3_[0u];\n let _e170 = w12_[1u];\n let _e175 = at12_[0u];\n let _e177 = at3_[1u];\n let _e179 = textureSampleLevel(uHistory_t, uHistory_s, vec2<f32>(_e175, _e177), 0f);\n let _e182 = w12_[0u];\n let _e184 = w3_[1u];\n sum = (((((_e128.xyz * (_e131 * _e133)) + (_e141.xyz * (_e144 * _e146))) + (_e151.xyz * (_e154 * _e156))) + (_e165.xyz * (_e168 * _e170))) + (_e179.xyz * (_e182 * _e184)));\n let _e189 = w12_[0u];\n let _e191 = w0_[1u];\n let _e194 = w0_[0u];\n let _e196 = w12_[1u];\n let _e200 = w12_[0u];\n let _e202 = w12_[1u];\n let _e206 = w3_[0u];\n let _e208 = w12_[1u];\n let _e212 = w12_[0u];\n let _e214 = w3_[1u];\n weight = (((((_e189 * _e191) + (_e194 * _e196)) + (_e200 * _e202)) + (_e206 * _e208)) + (_e212 * _e214));\n let _e217 = sum;\n let _e218 = weight;\n return max((_e217 / vec3(_e218)), vec3<f32>(0f, 0f, 0f));\n}\n\nfn neighbourhood_u0028_vf3_u003b_vf3_u003b(lo_1: ptr<function, vec3<f32>>, hi_1: ptr<function, vec3<f32>>) {\n var y: i32;\n var x: i32;\n var c: vec3<f32>;\n\n (*lo_1) = vec3<f32>(1000000000f, 1000000000f, 1000000000f);\n (*hi_1) = vec3<f32>(-1000000000f, -1000000000f, -1000000000f);\n y = -1i;\n loop {\n let _e36 = y;\n if (_e36 <= 1i) {\n x = -1i;\n loop {\n let _e38 = x;\n if (_e38 <= 1i) {\n let _e40 = vUv_1;\n let _e41 = x;\n let _e43 = y;\n let _e47 = unnamed.uTexel;\n let _e50 = textureSampleLevel(uScene_t, uScene_s, (_e40 + (vec2<f32>(f32(_e41), f32(_e43)) * _e47)), 0f);\n c = _e50.xyz;\n let _e52 = (*lo_1);\n let _e53 = c;\n (*lo_1) = min(_e52, _e53);\n let _e55 = (*hi_1);\n let _e56 = c;\n (*hi_1) = max(_e55, _e56);\n continue;\n } else {\n break;\n }\n continuing {\n let _e58 = x;\n x = (_e58 + 1i);\n }\n }\n continue;\n } else {\n break;\n }\n continuing {\n let _e60 = y;\n y = (_e60 + 1i);\n }\n }\n return;\n}\n\nfn main_1() {\n var current: vec3<f32>;\n var depth: f32;\n var clip: vec4<f32>;\n var previous: vec4<f32>;\n var wasUv: vec2<f32>;\n var lo_2: vec3<f32>;\n var hi_2: vec3<f32>;\n var param: vec3<f32>;\n var param_1: vec3<f32>;\n var history_1: vec3<f32>;\n var param_2: vec2<f32>;\n var bounded: vec3<f32>;\n var param_3: vec3<f32>;\n var param_4: vec3<f32>;\n var param_5: vec3<f32>;\n var phi_440_: bool;\n var phi_447_: bool;\n var phi_454_: bool;\n\n let _e46 = vUv_1;\n let _e47 = textureSampleLevel(uScene_t, uScene_s, _e46, 0f);\n current = _e47.xyz;\n let _e50 = unnamed.uHistoryBlend;\n if (_e50 <= 0f) {\n let _e52 = current;\n fragColor = vec4<f32>(_e52.x, _e52.y, _e52.z, 1f);\n return;\n }\n let _e57 = vUv_1;\n let _e58 = textureSampleLevel(uDepth_t, uDepth_s, _e57, 0f);\n depth = _e58.x;\n let _e60 = vUv_1;\n let _e63 = ((_e60 * 2f) - vec2(1f));\n let _e64 = depth;\n clip = vec4<f32>(_e63.x, _e63.y, (1f - (_e64 * 2f)), 1f);\n let _e71 = unnamed.uReprojection;\n let _e72 = clip;\n previous = (_e71 * _e72);\n let _e75 = previous[3u];\n if (_e75 <= 0f) {\n let _e77 = current;\n fragColor = vec4<f32>(_e77.x, _e77.y, _e77.z, 1f);\n return;\n }\n let _e82 = previous;\n let _e85 = previous[3u];\n wasUv = (((_e82.xy / vec2(_e85)) * 0.5f) + vec2(0.5f));\n let _e92 = wasUv[0u];\n let _e93 = (_e92 < 0f);\n phi_440_ = _e93;\n if !(_e93) {\n let _e96 = wasUv[0u];\n phi_440_ = (_e96 > 1f);\n }\n let _e99 = phi_440_;\n phi_447_ = _e99;\n if !(_e99) {\n let _e102 = wasUv[1u];\n phi_447_ = (_e102 < 0f);\n }\n let _e105 = phi_447_;\n phi_454_ = _e105;\n if !(_e105) {\n let _e108 = wasUv[1u];\n phi_454_ = (_e108 > 1f);\n }\n let _e111 = phi_454_;\n if _e111 {\n let _e112 = current;\n fragColor = vec4<f32>(_e112.x, _e112.y, _e112.z, 1f);\n return;\n }\n neighbourhood_u0028_vf3_u003b_vf3_u003b((&param), (&param_1));\n let _e117 = param;\n lo_2 = _e117;\n let _e118 = param_1;\n hi_2 = _e118;\n let _e119 = wasUv;\n param_2 = _e119;\n let _e120 = historyCatmullRom_u0028_vf2_u003b((&param_2));\n history_1 = _e120;\n let _e121 = history_1;\n param_3 = _e121;\n let _e122 = lo_2;\n param_4 = _e122;\n let _e123 = hi_2;\n param_5 = _e123;\n let _e124 = clipToNeighbourhood_u0028_vf3_u003b_vf3_u003b_vf3_u003b((&param_3), (&param_4), (&param_5));\n bounded = _e124;\n let _e125 = current;\n let _e126 = bounded;\n let _e128 = unnamed.uHistoryBlend;\n let _e130 = mix(_e125, _e126, vec3(_e128));\n fragColor = vec4<f32>(_e130.x, _e130.y, _e130.z, 1f);\n return;\n}\n\n@fragment \nfn main(@location(0) vUv: vec2<f32>) -> @location(0) vec4<f32> {\n vUv_1 = vUv;\n main_1();\n let _e3 = fragColor;\n return _e3;\n}\n";
8
+ export const TEMPORAL_RESOLVE_FRAG_WGSL = "struct Uniforms {\n uDepthToViewZ: vec4<f32>,\n uAntiFlicker: f32,\n uReprojection: mat4x4<f32>,\n uTexel: vec2<f32>,\n uPeriodStart: f32,\n uHistoryBlend: f32,\n}\n\nstruct FragmentOutput {\n @location(0) member: vec4<f32>,\n @location(1) member_1: vec4<f32>,\n @location(2) member_2: vec4<f32>,\n}\n\n@group(0) @binding(1) \nvar<uniform> unnamed: Uniforms;\n@group(0) @binding(32) \nvar uScene_t: texture_2d<f32>;\n@group(0) @binding(33) \nvar uScene_s: sampler;\nvar<private> vUv_1: vec2<f32>;\n@group(0) @binding(34) \nvar uHistory_t: texture_2d<f32>;\n@group(0) @binding(35) \nvar uHistory_s: sampler;\nvar<private> fragColor: vec4<f32>;\nvar<private> flickerOut: vec4<f32>;\nvar<private> flickerMotionOut: vec4<f32>;\n@group(0) @binding(40) \nvar uDepth_t: texture_2d<f32>;\n@group(0) @binding(41) \nvar uDepth_s: sampler;\n@group(0) @binding(36) \nvar uFlicker_t: texture_2d<f32>;\n@group(0) @binding(38) \nvar uFlickerMotion_t: texture_2d<f32>;\n@group(0) @binding(37) \nvar uFlicker_s: sampler;\n@group(0) @binding(39) \nvar uFlickerMotion_s: sampler;\n\nfn repeats_u0028_f1_u003b_f1_u003b(mean: ptr<function, f32>, previous: ptr<function, f32>) -> f32 {\n var apart: f32;\n\n let _e63 = (*mean);\n let _e64 = (*previous);\n let _e67 = (*mean);\n let _e68 = (*previous);\n apart = (abs((_e63 - _e64)) / max(max(_e67, _e68), 0.003921569f));\n let _e72 = apart;\n return (1f - smoothstep(0.05f, 0.1f, _e72));\n}\n\nfn clipToNeighbourhood_u0028_vf3_u003b_vf3_u003b_vf3_u003b(history: ptr<function, vec3<f32>>, lo: ptr<function, vec3<f32>>, hi: ptr<function, vec3<f32>>) -> vec3<f32> {\n var centre: vec3<f32>;\n var extent: vec3<f32>;\n var offset: vec3<f32>;\n var ratios: vec3<f32>;\n var ratio: f32;\n\n let _e68 = (*lo);\n let _e69 = (*hi);\n centre = ((_e68 + _e69) * 0.5f);\n let _e72 = (*hi);\n let _e73 = (*lo);\n extent = ((_e72 - _e73) * 0.5f);\n let _e76 = (*history);\n let _e77 = centre;\n offset = (_e76 - _e77);\n let _e79 = offset;\n let _e81 = extent;\n ratios = (abs(_e79) / max(_e81, vec3<f32>(0.0000001f, 0.0000001f, 0.0000001f)));\n let _e85 = ratios[0u];\n let _e87 = ratios[1u];\n let _e89 = ratios[2u];\n ratio = max(_e85, max(_e87, _e89));\n let _e92 = ratio;\n if (_e92 <= 1f) {\n let _e94 = (*history);\n return _e94;\n }\n let _e95 = centre;\n let _e96 = offset;\n let _e97 = ratio;\n return (_e95 + (_e96 / vec3(_e97)));\n}\n\nfn luma_u0028_vf3_u003b(c: ptr<function, vec3<f32>>) -> f32 {\n let _e61 = (*c);\n return dot(_e61, vec3<f32>(0.2126f, 0.7152f, 0.0722f));\n}\n\nfn historyCatmullRom_u0028_vf2_u003b(uv: ptr<function, vec2<f32>>) -> vec3<f32> {\n var size: vec2<f32>;\n var at: vec2<f32>;\n var base: vec2<f32>;\n var f: vec2<f32>;\n var w0_: vec2<f32>;\n var w1_: vec2<f32>;\n var w2_: vec2<f32>;\n var w3_: vec2<f32>;\n var w12_: vec2<f32>;\n var at0_: vec2<f32>;\n var at3_: vec2<f32>;\n var at12_: vec2<f32>;\n var sum: vec3<f32>;\n var weight: f32;\n\n let _e76 = unnamed.uTexel;\n size = (vec2(1f) / _e76);\n let _e79 = (*uv);\n let _e80 = size;\n at = (_e79 * _e80);\n let _e82 = at;\n base = (floor((_e82 - vec2(0.5f))) + vec2(0.5f));\n let _e88 = at;\n let _e89 = base;\n f = (_e88 - _e89);\n let _e91 = f;\n let _e92 = f;\n let _e93 = f;\n w0_ = (_e91 * (vec2(-0.5f) + (_e92 * (vec2(1f) - (_e93 * 0.5f)))));\n let _e101 = f;\n let _e102 = f;\n let _e104 = f;\n w1_ = (vec2(1f) + ((_e101 * _e102) * (vec2(-2.5f) + (_e104 * 1.5f))));\n let _e111 = f;\n let _e112 = f;\n let _e113 = f;\n w2_ = (_e111 * (vec2(0.5f) + (_e112 * (vec2(2f) - (_e113 * 1.5f)))));\n let _e121 = f;\n let _e122 = f;\n let _e124 = f;\n w3_ = ((_e121 * _e122) * (vec2(-0.5f) + (_e124 * 0.5f)));\n let _e129 = w1_;\n let _e130 = w2_;\n w12_ = (_e129 + _e130);\n let _e132 = base;\n let _e136 = unnamed.uTexel;\n at0_ = ((_e132 - vec2(1f)) * _e136);\n let _e138 = base;\n let _e142 = unnamed.uTexel;\n at3_ = ((_e138 + vec2(2f)) * _e142);\n let _e144 = base;\n let _e145 = w2_;\n let _e146 = w12_;\n let _e150 = unnamed.uTexel;\n at12_ = ((_e144 + (_e145 / _e146)) * _e150);\n let _e153 = at12_[0u];\n let _e155 = at0_[1u];\n let _e157 = textureSampleLevel(uHistory_t, uHistory_s, vec2<f32>(_e153, _e155), 0f);\n let _e160 = w12_[0u];\n let _e162 = w0_[1u];\n let _e166 = at0_[0u];\n let _e168 = at12_[1u];\n let _e170 = textureSampleLevel(uHistory_t, uHistory_s, vec2<f32>(_e166, _e168), 0f);\n let _e173 = w0_[0u];\n let _e175 = w12_[1u];\n let _e179 = at12_;\n let _e180 = textureSampleLevel(uHistory_t, uHistory_s, _e179, 0f);\n let _e183 = w12_[0u];\n let _e185 = w12_[1u];\n let _e190 = at3_[0u];\n let _e192 = at12_[1u];\n let _e194 = textureSampleLevel(uHistory_t, uHistory_s, vec2<f32>(_e190, _e192), 0f);\n let _e197 = w3_[0u];\n let _e199 = w12_[1u];\n let _e204 = at12_[0u];\n let _e206 = at3_[1u];\n let _e208 = textureSampleLevel(uHistory_t, uHistory_s, vec2<f32>(_e204, _e206), 0f);\n let _e211 = w12_[0u];\n let _e213 = w3_[1u];\n sum = (((((_e157.xyz * (_e160 * _e162)) + (_e170.xyz * (_e173 * _e175))) + (_e180.xyz * (_e183 * _e185))) + (_e194.xyz * (_e197 * _e199))) + (_e208.xyz * (_e211 * _e213)));\n let _e218 = w12_[0u];\n let _e220 = w0_[1u];\n let _e223 = w0_[0u];\n let _e225 = w12_[1u];\n let _e229 = w12_[0u];\n let _e231 = w12_[1u];\n let _e235 = w3_[0u];\n let _e237 = w12_[1u];\n let _e241 = w12_[0u];\n let _e243 = w3_[1u];\n weight = (((((_e218 * _e220) + (_e223 * _e225)) + (_e229 * _e231)) + (_e235 * _e237)) + (_e241 * _e243));\n let _e246 = sum;\n let _e247 = weight;\n return max((_e246 / vec3(_e247)), vec3<f32>(0f, 0f, 0f));\n}\n\nfn neighbourhood_u0028_vf3_u003b_vf3_u003b(lo_1: ptr<function, vec3<f32>>, hi_1: ptr<function, vec3<f32>>) {\n var y: i32;\n var x: i32;\n var c_1: vec3<f32>;\n\n (*lo_1) = vec3<f32>(1000000000f, 1000000000f, 1000000000f);\n (*hi_1) = vec3<f32>(-1000000000f, -1000000000f, -1000000000f);\n y = -1i;\n loop {\n let _e65 = y;\n if (_e65 <= 1i) {\n x = -1i;\n loop {\n let _e67 = x;\n if (_e67 <= 1i) {\n let _e69 = vUv_1;\n let _e70 = x;\n let _e72 = y;\n let _e76 = unnamed.uTexel;\n let _e79 = textureSampleLevel(uScene_t, uScene_s, (_e69 + (vec2<f32>(f32(_e70), f32(_e72)) * _e76)), 0f);\n c_1 = _e79.xyz;\n let _e81 = (*lo_1);\n let _e82 = c_1;\n (*lo_1) = min(_e81, _e82);\n let _e84 = (*hi_1);\n let _e85 = c_1;\n (*hi_1) = max(_e84, _e85);\n continue;\n } else {\n break;\n }\n continuing {\n let _e87 = x;\n x = (_e87 + 1i);\n }\n }\n continue;\n } else {\n break;\n }\n continuing {\n let _e89 = y;\n y = (_e89 + 1i);\n }\n }\n return;\n}\n\nfn viewDepthOf_u0028_f1_u003b(depth: ptr<function, f32>) -> f32 {\n var z: f32;\n\n let _e62 = (*depth);\n z = (1f - (_e62 * 2f));\n let _e67 = unnamed.uDepthToViewZ[0u];\n let _e68 = z;\n let _e72 = unnamed.uDepthToViewZ[1u];\n let _e77 = unnamed.uDepthToViewZ[2u];\n let _e78 = z;\n let _e82 = unnamed.uDepthToViewZ[3u];\n return (-(((_e67 * _e68) + _e72)) / ((_e77 * _e78) + _e82));\n}\n\nfn freshRecord_u0028_vf3_u003b(current: ptr<function, vec3<f32>>) -> vec4<f32> {\n var seen: f32;\n var param: vec3<f32>;\n\n let _e63 = (*current);\n param = _e63;\n let _e64 = luma_u0028_vf3_u003b((&param));\n seen = _e64;\n let _e65 = seen;\n let _e67 = seen;\n return vec4<f32>(0f, (_e65 * 0.125f), _e67, 0f);\n}\n\nfn main_1() {\n var current_1: vec3<f32>;\n var param_1: vec3<f32>;\n var nearestAt: vec2<f32>;\n var depth_1: f32;\n var metres: f32;\n var param_2: f32;\n var y_1: i32;\n var x_1: i32;\n var at_1: vec2<f32>;\n var tap: f32;\n var tapMetres: f32;\n var param_3: f32;\n var clip: vec4<f32>;\n var previous_1: vec4<f32>;\n var param_4: vec3<f32>;\n var wasUv: vec2<f32>;\n var param_5: vec3<f32>;\n var lo_2: vec3<f32>;\n var hi_2: vec3<f32>;\n var param_6: vec3<f32>;\n var param_7: vec3<f32>;\n var history_1: vec3<f32>;\n var param_8: vec2<f32>;\n var size_1: vec2<i32>;\n var was: vec2<i32>;\n var nearest: f32;\n var n: i32;\n var at_2: vec2<i32>;\n var local: vec2<i32>;\n var local_1: vec2<i32>;\n var local_2: vec2<i32>;\n var stood: f32;\n var motionThere: vec4<f32>;\n var missedAll: bool;\n var same: bool;\n var swingOut: f32;\n var local_3: f32;\n var seen_1: f32;\n var param_9: vec3<f32>;\n var held: vec4<f32>;\n var local_4: vec4<f32>;\n var param_10: vec3<f32>;\n var still: f32;\n var apart_1: f32;\n var reach: f32;\n var within: bool;\n var proven: bool;\n var widen: f32;\n var local_5: f32;\n var blend: f32;\n var local_6: f32;\n var bounded: vec3<f32>;\n var param_11: vec3<f32>;\n var param_12: vec3<f32>;\n var param_13: vec3<f32>;\n var sum_1: f32;\n var mean_1: f32;\n var repeated: f32;\n var local_7: f32;\n var param_14: f32;\n var param_15: f32;\n var local_8: f32;\n var phi_589_: bool;\n var phi_596_: bool;\n var phi_603_: bool;\n var phi_740_: bool;\n\n let _e122 = vUv_1;\n let _e123 = textureSampleLevel(uScene_t, uScene_s, _e122, 0f);\n current_1 = _e123.xyz;\n let _e126 = unnamed.uHistoryBlend;\n if (_e126 <= 0f) {\n let _e128 = current_1;\n fragColor = vec4<f32>(_e128.x, _e128.y, _e128.z, 1f);\n let _e133 = current_1;\n param_1 = _e133;\n let _e134 = freshRecord_u0028_vf3_u003b((&param_1));\n flickerOut = _e134;\n flickerMotionOut = vec4<f32>(0f, 0f, 0f, 0f);\n return;\n }\n let _e135 = vUv_1;\n nearestAt = _e135;\n let _e136 = vUv_1;\n let _e137 = textureSampleLevel(uDepth_t, uDepth_s, _e136, 0f);\n depth_1 = _e137.x;\n let _e139 = depth_1;\n param_2 = _e139;\n let _e140 = viewDepthOf_u0028_f1_u003b((&param_2));\n metres = _e140;\n y_1 = -1i;\n loop {\n let _e141 = y_1;\n if (_e141 <= 1i) {\n x_1 = -1i;\n loop {\n let _e143 = x_1;\n if (_e143 <= 1i) {\n let _e145 = vUv_1;\n let _e146 = x_1;\n let _e148 = y_1;\n let _e152 = unnamed.uTexel;\n at_1 = (_e145 + (vec2<f32>(f32(_e146), f32(_e148)) * _e152));\n let _e155 = at_1;\n let _e156 = textureSampleLevel(uDepth_t, uDepth_s, _e155, 0f);\n tap = _e156.x;\n let _e158 = tap;\n param_3 = _e158;\n let _e159 = viewDepthOf_u0028_f1_u003b((&param_3));\n tapMetres = _e159;\n let _e160 = tapMetres;\n let _e161 = metres;\n if (_e160 < _e161) {\n let _e163 = tapMetres;\n metres = _e163;\n let _e164 = tap;\n depth_1 = _e164;\n let _e165 = at_1;\n nearestAt = _e165;\n }\n continue;\n } else {\n break;\n }\n continuing {\n let _e166 = x_1;\n x_1 = (_e166 + 1i);\n }\n }\n continue;\n } else {\n break;\n }\n continuing {\n let _e168 = y_1;\n y_1 = (_e168 + 1i);\n }\n }\n let _e170 = nearestAt;\n let _e173 = ((_e170 * 2f) - vec2(1f));\n let _e174 = depth_1;\n clip = vec4<f32>(_e173.x, _e173.y, (1f - (_e174 * 2f)), 1f);\n let _e181 = unnamed.uReprojection;\n let _e182 = clip;\n previous_1 = (_e181 * _e182);\n let _e185 = previous_1[3u];\n if (_e185 <= 0f) {\n let _e187 = current_1;\n fragColor = vec4<f32>(_e187.x, _e187.y, _e187.z, 1f);\n let _e192 = current_1;\n param_4 = _e192;\n let _e193 = freshRecord_u0028_vf3_u003b((&param_4));\n flickerOut = _e193;\n let _e194 = metres;\n flickerMotionOut = vec4<f32>(_e194, 0f, 0f, 0f);\n return;\n }\n let _e196 = vUv_1;\n let _e197 = previous_1;\n let _e200 = previous_1[3u];\n let _e206 = nearestAt;\n wasUv = (_e196 + ((((_e197.xy / vec2(_e200)) * 0.5f) + vec2(0.5f)) - _e206));\n let _e210 = wasUv[0u];\n let _e211 = (_e210 < 0f);\n phi_589_ = _e211;\n if !(_e211) {\n let _e214 = wasUv[0u];\n phi_589_ = (_e214 > 1f);\n }\n let _e217 = phi_589_;\n phi_596_ = _e217;\n if !(_e217) {\n let _e220 = wasUv[1u];\n phi_596_ = (_e220 < 0f);\n }\n let _e223 = phi_596_;\n phi_603_ = _e223;\n if !(_e223) {\n let _e226 = wasUv[1u];\n phi_603_ = (_e226 > 1f);\n }\n let _e229 = phi_603_;\n if _e229 {\n let _e230 = current_1;\n fragColor = vec4<f32>(_e230.x, _e230.y, _e230.z, 1f);\n let _e235 = current_1;\n param_5 = _e235;\n let _e236 = freshRecord_u0028_vf3_u003b((&param_5));\n flickerOut = _e236;\n let _e237 = metres;\n flickerMotionOut = vec4<f32>(_e237, 0f, 0f, 0f);\n return;\n }\n neighbourhood_u0028_vf3_u003b_vf3_u003b((&param_6), (&param_7));\n let _e239 = param_6;\n lo_2 = _e239;\n let _e240 = param_7;\n hi_2 = _e240;\n let _e241 = wasUv;\n param_8 = _e241;\n let _e242 = historyCatmullRom_u0028_vf2_u003b((&param_8));\n history_1 = _e242;\n let _e243 = textureDimensions(uFlicker_t, 0i);\n size_1 = vec2<i32>(_e243);\n let _e245 = wasUv;\n let _e246 = size_1;\n let _e250 = size_1;\n was = clamp(vec2<i32>((_e245 * vec2<f32>(_e246))), vec2<i32>(0i, 0i), (_e250 - vec2(1i)));\n nearest = 1000000000f;\n n = 0i;\n loop {\n let _e254 = n;\n if (_e254 < 5i) {\n let _e256 = was;\n let _e257 = n;\n if (_e257 == 1i) {\n local = vec2<i32>(1i, 0i);\n } else {\n let _e259 = n;\n if (_e259 == 2i) {\n local_1 = vec2<i32>(-1i, 0i);\n } else {\n let _e261 = n;\n if (_e261 == 3i) {\n local_2 = vec2<i32>(0i, 1i);\n } else {\n let _e263 = n;\n local_2 = select(vec2<i32>(0i, 0i), vec2<i32>(0i, -1i), vec2((_e263 == 4i)));\n }\n let _e267 = local_2;\n local_1 = _e267;\n }\n let _e268 = local_1;\n local = _e268;\n }\n let _e269 = local;\n at_2 = (_e256 + _e269);\n let _e271 = at_2;\n let _e272 = size_1;\n let _e276 = textureLoad(uFlickerMotion_t, clamp(_e271, vec2<i32>(0i, 0i), (_e272 - vec2(1i))), 0i);\n stood = _e276.x;\n let _e278 = stood;\n if (_e278 > 0f) {\n let _e280 = nearest;\n let _e281 = stood;\n let _e283 = previous_1[3u];\n nearest = min(_e280, abs((_e281 - _e283)));\n }\n continue;\n } else {\n break;\n }\n continuing {\n let _e287 = n;\n n = (_e287 + 1i);\n }\n }\n let _e289 = was;\n let _e290 = textureLoad(uFlickerMotion_t, _e289, 0i);\n motionThere = _e290;\n let _e291 = nearest;\n missedAll = (_e291 > 100000000f);\n let _e294 = unnamed.uAntiFlicker;\n let _e296 = missedAll;\n let _e298 = ((_e294 > 0.5f) && !(_e296));\n phi_740_ = _e298;\n if _e298 {\n let _e299 = nearest;\n let _e301 = motionThere[1u];\n let _e304 = previous_1[3u];\n phi_740_ = (_e299 <= ((6f * _e301) + (0.02f * _e304)));\n }\n let _e309 = phi_740_;\n same = _e309;\n let _e310 = missedAll;\n if _e310 {\n local_3 = 0f;\n } else {\n let _e312 = motionThere[1u];\n let _e313 = nearest;\n let _e315 = motionThere[1u];\n local_3 = (_e312 + ((_e313 - _e315) * 0.125f));\n }\n let _e319 = local_3;\n swingOut = _e319;\n let _e320 = current_1;\n param_9 = _e320;\n let _e321 = luma_u0028_vf3_u003b((&param_9));\n seen_1 = _e321;\n let _e322 = same;\n if _e322 {\n let _e323 = was;\n let _e324 = textureLoad(uFlicker_t, _e323, 0i);\n local_4 = _e324;\n } else {\n let _e325 = current_1;\n param_10 = _e325;\n let _e326 = freshRecord_u0028_vf3_u003b((&param_10));\n local_4 = _e326;\n }\n let _e327 = local_4;\n held = _e327;\n let _e328 = wasUv;\n let _e329 = vUv_1;\n let _e332 = unnamed.uTexel;\n still = (1f - smoothstep(0.01f, 0.05f, length(((_e328 - _e329) / _e332))));\n let _e337 = seen_1;\n let _e339 = held[2u];\n apart_1 = abs((_e337 - _e339));\n let _e343 = held[0u];\n reach = ((6f * _e343) + 0.003921569f);\n let _e346 = apart_1;\n let _e347 = reach;\n within = (_e346 <= _e347);\n let _e350 = held[3u];\n let _e352 = within;\n proven = ((_e350 > 0.75f) && _e352);\n let _e354 = proven;\n if _e354 {\n let _e356 = held[0u];\n let _e358 = still;\n local_5 = ((6f * _e356) * _e358);\n } else {\n local_5 = 0f;\n }\n let _e360 = local_5;\n widen = _e360;\n let _e361 = proven;\n if _e361 {\n let _e363 = unnamed.uHistoryBlend;\n let _e364 = still;\n local_6 = mix(_e363, 0.97f, _e364);\n } else {\n let _e367 = unnamed.uHistoryBlend;\n local_6 = _e367;\n }\n let _e368 = local_6;\n blend = _e368;\n let _e369 = lo_2;\n let _e370 = widen;\n let _e373 = hi_2;\n let _e374 = widen;\n let _e377 = history_1;\n param_11 = _e377;\n param_12 = (_e369 - vec3(_e370));\n param_13 = (_e373 + vec3(_e374));\n let _e378 = clipToNeighbourhood_u0028_vf3_u003b_vf3_u003b_vf3_u003b((&param_11), (&param_12), (&param_13));\n bounded = _e378;\n let _e380 = held[1u];\n sum_1 = _e380;\n let _e382 = held[2u];\n mean_1 = _e382;\n let _e383 = within;\n if _e383 {\n let _e385 = held[3u];\n local_7 = _e385;\n } else {\n local_7 = 0f;\n }\n let _e386 = local_7;\n repeated = _e386;\n let _e388 = unnamed.uPeriodStart;\n if (_e388 > 0.5f) {\n let _e390 = sum_1;\n param_14 = _e390;\n let _e391 = mean_1;\n param_15 = _e391;\n let _e392 = repeats_u0028_f1_u003b_f1_u003b((&param_14), (&param_15));\n if (_e392 > 0.5f) {\n let _e394 = repeated;\n local_8 = min(1f, (_e394 + 0.5f));\n } else {\n local_8 = 0f;\n }\n let _e397 = local_8;\n repeated = _e397;\n let _e398 = sum_1;\n mean_1 = _e398;\n sum_1 = 0f;\n }\n let _e400 = held[0u];\n let _e401 = apart_1;\n let _e403 = held[0u];\n let _e407 = sum_1;\n let _e408 = seen_1;\n let _e411 = mean_1;\n let _e412 = repeated;\n let _e413 = still;\n flickerOut = vec4<f32>((_e400 + ((_e401 - _e403) * 0.125f)), (_e407 + (_e408 * 0.125f)), _e411, (_e412 * _e413));\n let _e416 = metres;\n let _e417 = swingOut;\n flickerMotionOut = vec4<f32>(_e416, _e417, 0f, 0f);\n let _e419 = current_1;\n let _e420 = bounded;\n let _e421 = blend;\n let _e423 = mix(_e419, _e420, vec3(_e421));\n fragColor = vec4<f32>(_e423.x, _e423.y, _e423.z, 1f);\n return;\n}\n\n@fragment \nfn main(@location(0) vUv: vec2<f32>) -> FragmentOutput {\n vUv_1 = vUv;\n main_1();\n let _e5 = fragColor;\n let _e6 = flickerOut;\n let _e7 = flickerMotionOut;\n return FragmentOutput(_e5, _e6, _e7);\n}\n";
9
9
 
10
10
  /**
11
11
  * What the transform assigned, so the renderer binds the same numbers.
@@ -15,20 +15,35 @@ export const TEMPORAL_RESOLVE_FRAG_WGSL = "struct Uniforms {\n uReprojection:
15
15
  export const TEMPORALRESOLVE_BINDINGS = {
16
16
  "TEMPORAL_RESOLVE_FRAG": {
17
17
  "uniforms": 1,
18
- "uniformSize": 80,
18
+ "uniformSize": 112,
19
19
  "fields": {
20
- "uReprojection": {
20
+ "uDepthToViewZ": {
21
21
  "offset": 0,
22
+ "size": 16,
23
+ "type": "vec4"
24
+ },
25
+ "uAntiFlicker": {
26
+ "offset": 16,
27
+ "size": 4,
28
+ "type": "float"
29
+ },
30
+ "uReprojection": {
31
+ "offset": 32,
22
32
  "size": 64,
23
33
  "type": "mat4"
24
34
  },
25
35
  "uTexel": {
26
- "offset": 64,
36
+ "offset": 96,
27
37
  "size": 8,
28
38
  "type": "vec2"
29
39
  },
40
+ "uPeriodStart": {
41
+ "offset": 104,
42
+ "size": 4,
43
+ "type": "float"
44
+ },
30
45
  "uHistoryBlend": {
31
- "offset": 72,
46
+ "offset": 108,
32
47
  "size": 4,
33
48
  "type": "float"
34
49
  }
@@ -44,10 +59,20 @@ export const TEMPORALRESOLVE_BINDINGS = {
44
59
  "sampler": 35,
45
60
  "type": "sampler2D"
46
61
  },
47
- "uDepth": {
62
+ "uFlicker": {
48
63
  "texture": 36,
49
64
  "sampler": 37,
50
65
  "type": "sampler2D"
66
+ },
67
+ "uFlickerMotion": {
68
+ "texture": 38,
69
+ "sampler": 39,
70
+ "type": "sampler2D"
71
+ },
72
+ "uDepth": {
73
+ "texture": 40,
74
+ "sampler": 41,
75
+ "type": "sampler2D"
51
76
  }
52
77
  }
53
78
  }