@driftengine/core 4.8.1 → 4.8.3

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 (159) hide show
  1. package/CHANGELOG.json +142 -0
  2. package/README.md +1 -1
  3. package/dist/index.d.ts +3 -1
  4. package/dist/index.js +8 -0
  5. package/dist/input/actionMap.d.ts +6 -1
  6. package/dist/input/actionMap.js +20 -7
  7. package/dist/input/input.d.ts +28 -0
  8. package/dist/input/input.js +71 -9
  9. package/dist/input/inputHarness.d.ts +9 -0
  10. package/dist/input/inputHarness.js +34 -2
  11. package/dist/render/backend/materialChanges.d.ts +7 -2
  12. package/dist/render/backend/materialChanges.js +8 -3
  13. package/dist/render/backend/webgl2/renderer.d.ts +75 -3
  14. package/dist/render/backend/webgl2/renderer.js +209 -22
  15. package/dist/render/backend/webgpu/buffers.d.ts +2 -0
  16. package/dist/render/backend/webgpu/buffers.js +2 -0
  17. package/dist/render/backend/webgpu/causticsPass.js +2 -1
  18. package/dist/render/backend/webgpu/depthPass.d.ts +13 -3
  19. package/dist/render/backend/webgpu/depthPass.js +4 -3
  20. package/dist/render/backend/webgpu/filmPass.js +2 -1
  21. package/dist/render/backend/webgpu/flatPass.d.ts +5 -0
  22. package/dist/render/backend/webgpu/flatPass.js +18 -8
  23. package/dist/render/backend/webgpu/linePass.js +2 -1
  24. package/dist/render/backend/webgpu/panelPass.js +2 -1
  25. package/dist/render/backend/webgpu/particlePass.js +2 -1
  26. package/dist/render/backend/webgpu/pipelineCache.d.ts +40 -8
  27. package/dist/render/backend/webgpu/pipelineCache.js +76 -9
  28. package/dist/render/backend/webgpu/plumePass.js +2 -1
  29. package/dist/render/backend/webgpu/postPass.d.ts +12 -2
  30. package/dist/render/backend/webgpu/postPass.js +19 -5
  31. package/dist/render/backend/webgpu/renderer.d.ts +66 -2
  32. package/dist/render/backend/webgpu/renderer.js +189 -38
  33. package/dist/render/backend/webgpu/sdfTextPass.js +2 -1
  34. package/dist/render/backend/webgpu/textPass.js +2 -1
  35. package/dist/render/backend/webgpu/waterPass.js +2 -1
  36. package/dist/render/backend/webgpu/windStreakPass.js +2 -1
  37. package/dist/render/boltBatch.js +2 -1
  38. package/dist/render/causticsRenderer.js +2 -1
  39. package/dist/render/cutoutCaster.d.ts +3 -1
  40. package/dist/render/cutoutCaster.js +4 -0
  41. package/dist/render/decalPass.js +2 -1
  42. package/dist/render/globalMediumPass.js +3 -2
  43. package/dist/render/lineBatch.js +5 -1
  44. package/dist/render/mapsWithoutUvs.d.ts +24 -0
  45. package/dist/render/mapsWithoutUvs.js +24 -0
  46. package/dist/render/mesh.d.ts +2 -0
  47. package/dist/render/mesh.js +3 -0
  48. package/dist/render/oitPass.js +2 -1
  49. package/dist/render/particleBatch.js +3 -2
  50. package/dist/render/particlePool.d.ts +21 -2
  51. package/dist/render/particlePool.js +72 -37
  52. package/dist/render/pass.d.ts +12 -0
  53. package/dist/render/plumeRenderer.js +3 -2
  54. package/dist/render/renderQuality.d.ts +16 -8
  55. package/dist/render/sceneCoverage.d.ts +54 -0
  56. package/dist/render/sceneCoverage.js +72 -0
  57. package/dist/render/sdfTextRenderer.js +2 -1
  58. package/dist/render/shaders/depth.js +3 -2
  59. package/dist/render/shaders/flat/glassTint.d.ts +28 -0
  60. package/dist/render/shaders/flat/glassTint.js +31 -0
  61. package/dist/render/shaders/flat/index.d.ts +12 -0
  62. package/dist/render/shaders/flat/index.js +13 -2
  63. package/dist/render/shaders/flat/main.d.ts +1 -1
  64. package/dist/render/shaders/flat/main.js +60 -25
  65. package/dist/render/shaders/flat/surfaceEffects.d.ts +1 -1
  66. package/dist/render/shaders/flat/surfaceEffects.js +6 -2
  67. package/dist/render/shaders/generated/depth.wgsl.d.ts +32 -12
  68. package/dist/render/shaders/generated/depth.wgsl.js +32 -12
  69. package/dist/render/shaders/generated/flat.wgsl.d.ts +89 -0
  70. package/dist/render/shaders/generated/flat.wgsl.js +127 -38
  71. package/dist/render/shaders/generated/line.wgsl.d.ts +1 -1
  72. package/dist/render/shaders/generated/line.wgsl.js +1 -1
  73. package/dist/render/shaders/generated/particle.wgsl.d.ts +3 -3
  74. package/dist/render/shaders/generated/particle.wgsl.js +3 -3
  75. package/dist/render/shaders/generated/rush.wgsl.d.ts +1 -1
  76. package/dist/render/shaders/generated/rush.wgsl.js +1 -1
  77. package/dist/render/shaders/generated/sdfText.wgsl.d.ts +1 -1
  78. package/dist/render/shaders/generated/sdfText.wgsl.js +1 -1
  79. package/dist/render/shaders/generated/temporalResolve.wgsl.d.ts +1 -1
  80. package/dist/render/shaders/generated/temporalResolve.wgsl.js +1 -1
  81. package/dist/render/shaders/line.d.ts +1 -1
  82. package/dist/render/shaders/outputTransform.d.ts +14 -1
  83. package/dist/render/shaders/outputTransform.js +23 -1
  84. package/dist/render/shaders/particle.d.ts +3 -3
  85. package/dist/render/shaders/recon/resolve.wgsl.js +15 -1
  86. package/dist/render/shaders/rush.js +13 -2
  87. package/dist/render/shaders/sdfText.d.ts +1 -1
  88. package/dist/render/shaders/temporalResolve.js +12 -5
  89. package/dist/render/ssrPass.js +2 -1
  90. package/dist/render/surfaceTexture.d.ts +13 -0
  91. package/dist/render/surfaceTexture.js +4 -0
  92. package/dist/render/textRenderer.js +2 -1
  93. package/dist/render/vertexDefaults.js +1 -0
  94. package/dist/render/viewModel.d.ts +29 -0
  95. package/dist/render/viewModel.js +35 -0
  96. package/dist/render/waterRenderer.js +2 -1
  97. package/dist/render/windStreakRenderer.js +2 -1
  98. package/package.json +6 -6
  99. package/src/index.ts +22 -1
  100. package/src/input/actionMap.ts +23 -8
  101. package/src/input/input.ts +79 -9
  102. package/src/input/inputHarness.ts +27 -2
  103. package/src/render/backend/materialChanges.ts +8 -3
  104. package/src/render/backend/webgl2/renderer.ts +223 -29
  105. package/src/render/backend/webgpu/buffers.ts +5 -0
  106. package/src/render/backend/webgpu/causticsPass.ts +2 -1
  107. package/src/render/backend/webgpu/depthPass.ts +4 -3
  108. package/src/render/backend/webgpu/filmPass.ts +2 -1
  109. package/src/render/backend/webgpu/flatPass.ts +43 -26
  110. package/src/render/backend/webgpu/linePass.ts +2 -1
  111. package/src/render/backend/webgpu/panelPass.ts +2 -1
  112. package/src/render/backend/webgpu/particlePass.ts +2 -1
  113. package/src/render/backend/webgpu/pipelineCache.ts +82 -8
  114. package/src/render/backend/webgpu/plumePass.ts +2 -1
  115. package/src/render/backend/webgpu/postPass.ts +20 -5
  116. package/src/render/backend/webgpu/renderer.ts +191 -37
  117. package/src/render/backend/webgpu/sdfTextPass.ts +2 -1
  118. package/src/render/backend/webgpu/textPass.ts +2 -1
  119. package/src/render/backend/webgpu/waterPass.ts +2 -1
  120. package/src/render/backend/webgpu/windStreakPass.ts +2 -1
  121. package/src/render/boltBatch.ts +2 -1
  122. package/src/render/causticsRenderer.ts +2 -1
  123. package/src/render/cutoutCaster.ts +15 -2
  124. package/src/render/decalPass.ts +2 -1
  125. package/src/render/globalMediumPass.ts +3 -2
  126. package/src/render/lineBatch.ts +3 -1
  127. package/src/render/mapsWithoutUvs.ts +43 -0
  128. package/src/render/mesh.ts +3 -0
  129. package/src/render/oitPass.ts +2 -1
  130. package/src/render/particleBatch.ts +3 -2
  131. package/src/render/particlePool.ts +107 -56
  132. package/src/render/pass.ts +12 -0
  133. package/src/render/plumeRenderer.ts +3 -2
  134. package/src/render/renderQuality.ts +16 -8
  135. package/src/render/sceneCoverage.ts +78 -0
  136. package/src/render/sdfTextRenderer.ts +2 -1
  137. package/src/render/shaders/depth.ts +3 -2
  138. package/src/render/shaders/flat/glassTint.ts +48 -0
  139. package/src/render/shaders/flat/index.ts +25 -2
  140. package/src/render/shaders/flat/main.ts +60 -25
  141. package/src/render/shaders/flat/surfaceEffects.ts +6 -2
  142. package/src/render/shaders/generated/depth.wgsl.ts +32 -12
  143. package/src/render/shaders/generated/flat.wgsl.ts +127 -38
  144. package/src/render/shaders/generated/line.wgsl.ts +1 -1
  145. package/src/render/shaders/generated/particle.wgsl.ts +3 -3
  146. package/src/render/shaders/generated/rush.wgsl.ts +1 -1
  147. package/src/render/shaders/generated/sdfText.wgsl.ts +1 -1
  148. package/src/render/shaders/generated/temporalResolve.wgsl.ts +1 -1
  149. package/src/render/shaders/outputTransform.ts +24 -1
  150. package/src/render/shaders/recon/resolve.wgsl.ts +15 -1
  151. package/src/render/shaders/rush.ts +13 -2
  152. package/src/render/shaders/temporalResolve.ts +12 -5
  153. package/src/render/ssrPass.ts +2 -1
  154. package/src/render/surfaceTexture.ts +16 -0
  155. package/src/render/textRenderer.ts +2 -1
  156. package/src/render/vertexDefaults.ts +1 -0
  157. package/src/render/viewModel.ts +41 -0
  158. package/src/render/waterRenderer.ts +2 -1
  159. package/src/render/windStreakRenderer.ts +2 -1
@@ -5,8 +5,9 @@ import {
5
5
  FLAT_VERT_WGSL,
6
6
  } from '../../shaders/generated/flat.wgsl.ts';
7
7
  import { vertexBufferLayouts } from './buffers.ts';
8
- import type { PipelineCache } from './pipelineCache.ts';
8
+ import type { LitSwitch, PipelineCache } from './pipelineCache.ts';
9
9
  import { shaderModule } from './shaderModules.ts';
10
+ import { SCENE_ALPHA_COVERS } from '../../sceneCoverage.ts';
10
11
 
11
12
  /** The material maps the flat pass reads: `2d-array` colour, filtered, not shadow maps. */
12
13
  const SURFACE_MAPS: ReadonlySet<string> = new Set([
@@ -199,17 +200,27 @@ export function flatFragmentBindings(variant: FlatVariant): StageBindings {
199
200
  }
200
201
 
201
202
  /**
202
- * The lit fragment stage's constants, set from the cache: the glass switch, keyed by its **id**.
203
+ * The lit fragment stage's constants, set from the cache's switches, keyed by each one's **id**.
203
204
  *
204
205
  * Not by its name, because an override declared with `@id` answers to the id alone — WebGPU's
205
206
  * "pipeline-overridable constant identifier string" — and naga writes one on every specialisation
206
207
  * constant. Keyed by name, Chrome refused every lit pipeline: *Pipeline overridable constant
207
- * "GLASS_SHADOWS" not found*, and the frame drew nothing.
208
+ * "GLASS_SHADOWS" not found*, and the frame drew nothing. Every override the variant declares is
209
+ * set, so a switch added to the shader and not to the cache is refused here rather than defaulted.
208
210
  */
209
211
  function flatFragmentConstants(variant: FlatVariant, cache: PipelineCache): Record<string, number> {
210
- const id = flatFragmentBindings(variant).overrides?.['GLASS_SHADOWS'];
211
- if (id === undefined) throw new Error(`flatPass: variant "${variant}" has no glass switch`);
212
- return { [String(id)]: cache.glassShadows ? 1 : 0 };
212
+ const overrides = flatFragmentBindings(variant).overrides;
213
+ if (overrides?.['GLASS_SHADOWS'] === undefined) {
214
+ throw new Error(`flatPass: variant "${variant}" has no glass switch`);
215
+ }
216
+ const constants: Record<string, number> = {};
217
+ for (const [name, id] of Object.entries(overrides)) {
218
+ if (!(name in cache.litSwitches)) {
219
+ throw new Error(`flatPass: variant "${variant}" declares a switch "${name}" no cache sets`);
220
+ }
221
+ constants[String(id)] = cache.litSwitches[name as LitSwitch] ? 1 : 0;
222
+ }
223
+ return constants;
213
224
  }
214
225
 
215
226
  /** The depth format both backends compare with. */
@@ -507,23 +518,26 @@ export function flatPipeline(
507
518
  /** Culls nothing, for a surface seen from both faces. The key must carry it: `|2s`. */
508
519
  doubleSided = false,
509
520
  ): GPURenderPipeline {
510
- return cache.get(key, () =>
511
- flatDescriptor(
512
- cache,
513
- device,
514
- layout,
515
- variant,
516
- key,
517
- present,
518
- translucent,
519
- skinned,
520
- morphed,
521
- depthWrite,
522
- depthLayer,
523
- instanced,
524
- oit,
525
- doubleSided,
526
- ),
521
+ return cache.get(
522
+ key,
523
+ () =>
524
+ flatDescriptor(
525
+ cache,
526
+ device,
527
+ layout,
528
+ variant,
529
+ key,
530
+ present,
531
+ translucent,
532
+ skinned,
533
+ morphed,
534
+ depthWrite,
535
+ depthLayer,
536
+ instanced,
537
+ oit,
538
+ doubleSided,
539
+ ),
540
+ true,
527
541
  );
528
542
  }
529
543
 
@@ -549,8 +563,11 @@ export function flatPipelineAsync(
549
563
  /** Whether this pipeline reads morph deltas. See `FlatVertexOptions`. */
550
564
  morphed = false,
551
565
  ): Promise<GPURenderPipeline> {
552
- return cache.getAsync(key, () =>
553
- flatDescriptor(cache, device, layout, variant, key, present, translucent, skinned, morphed),
566
+ return cache.getAsync(
567
+ key,
568
+ () =>
569
+ flatDescriptor(cache, device, layout, variant, key, present, translucent, skinned, morphed),
570
+ true,
554
571
  );
555
572
  }
556
573
 
@@ -665,7 +682,7 @@ function flatDescriptor(
665
682
  dstFactor: 'one-minus-src-alpha',
666
683
  operation: 'add',
667
684
  },
668
- alpha: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
685
+ alpha: SCENE_ALPHA_COVERS,
669
686
  },
670
687
  }
671
688
  : { format: cache.format }),
@@ -9,6 +9,7 @@ import type { UniformFields } from './scatterPass.ts';
9
9
  import { DEPTH_FORMAT } from './flatPass.ts';
10
10
  import type { PipelineCache } from './pipelineCache.ts';
11
11
  import { shaderModule } from './shaderModules.ts';
12
+ import { SCENE_ALPHA_COVERS } from '../../sceneCoverage.ts';
12
13
 
13
14
  /**
14
15
  * The line pass: a polyline with a real width, four vertices a segment.
@@ -222,7 +223,7 @@ export function linePipeline(
222
223
  dstFactor: 'one-minus-src-alpha',
223
224
  operation: 'add',
224
225
  },
225
- alpha: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
226
+ alpha: SCENE_ALPHA_COVERS,
226
227
  },
227
228
  },
228
229
  ],
@@ -7,6 +7,7 @@ import type { UniformFields } from './scatterPass.ts';
7
7
  import { DEPTH_FORMAT } from './flatPass.ts';
8
8
  import type { PipelineCache } from './pipelineCache.ts';
9
9
  import { shaderModule } from './shaderModules.ts';
10
+ import { SCENE_ALPHA_COVERS } from '../../sceneCoverage.ts';
10
11
 
11
12
  /**
12
13
  * The panel pass: two triangles in screen space, one colour, one alpha.
@@ -121,7 +122,7 @@ export function panelPipeline(
121
122
  format: cache.format,
122
123
  blend: {
123
124
  color: { srcFactor: 'src-alpha', dstFactor: 'one-minus-src-alpha', operation: 'add' },
124
- alpha: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
125
+ alpha: SCENE_ALPHA_COVERS,
125
126
  },
126
127
  },
127
128
  ],
@@ -8,6 +8,7 @@ import { DEPTH_FORMAT } from './flatPass.ts';
8
8
  import type { PipelineCache } from './pipelineCache.ts';
9
9
  import type { UniformFields } from './scatterPass.ts';
10
10
  import { shaderModule } from './shaderModules.ts';
11
+ import { SCENE_ALPHA_COVERS } from '../../sceneCoverage.ts';
11
12
 
12
13
  /**
13
14
  * The particle pass: a crossed pair of quads per live particle, rebuilt every frame.
@@ -367,7 +368,7 @@ export function particlePipeline(
367
368
  dstFactor: 'one-minus-src-alpha',
368
369
  operation: 'add',
369
370
  },
370
- alpha: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
371
+ alpha: SCENE_ALPHA_COVERS,
371
372
  },
372
373
  },
373
374
  ],
@@ -1,3 +1,7 @@
1
+ import { noLitSwitches, type LitSwitch } from '../../shaders/flat/glassTint.ts';
2
+
3
+ export type { LitSwitch };
4
+
1
5
  /**
2
6
  * Render pipelines, built once and looked up by name.
3
7
  *
@@ -35,15 +39,30 @@ export class PipelineCache {
35
39
  readonly sampleCount: number;
36
40
 
37
41
  /**
38
- * Whether the lit pipelines here read glass in their shadows: `glassShadows` other than `'off'`.
42
+ * Which of the lit stage's switches the pipelines built now have on: the pipeline-overridable
43
+ * constants the generated lit shader branches on (`GLASS_SHADOWS` and `litSwitchesGlsl`), which
44
+ * the device compiles away where they are off. Read by `flatPass` when it describes a pipeline.
39
45
  *
40
- * **Carried by the cache for the reason the format is**: one value every lit pipeline is built
41
- * with, chosen once by the renderer. It is the `GLASS_SHADOWS` override the generated lit shader
42
- * branches on (see `FlatShaderOptions.glassShadows`), so off specialises the lookups away rather
43
- * than leaving code that returns at its first line and still costs the pass its registers.
46
+ * **Each is code a scene pays for in every lit pixel's registers whether it uses it or not**, so
47
+ * each is off until it is used: glass until the renderer is offered a pane, a fixture until one is
48
+ * loaded, the effects until a material carries a table, DriftLight until a volume is set — and
49
+ * then on for good, through `enable`. Clustering is the profile's, fixed at construction. Every
50
+ * scene paid for all of it from 4.5.0 to 4.8.1, and on a phone that was the lit pass: about 30%
51
+ * of the lit shader's instructions for glass alone, measured on RADV. **What it costs** is the
52
+ * frames between a feature's first use and its recompiled pipelines, which draw without it.
44
53
  */
54
+ readonly litSwitches: Record<LitSwitch, boolean> = noLitSwitches();
55
+
56
+ /** Whether glass may ever be switched on here: `glassShadows` other than `'off'`. */
45
57
  readonly glassShadows: boolean;
46
58
 
59
+ /** How to describe each lit pipeline again, so `enable` can rebuild every one. */
60
+ private readonly litDescribers = new Map<string, () => GPURenderPipelineDescriptor>();
61
+ /** Bumped by every switch, so a lit build started before one is not kept after it. */
62
+ private litGeneration = 0;
63
+ /** The last rebuild, which the next one waits for so they land in the order they were asked. */
64
+ private litRebuild: Promise<void> = Promise.resolve();
65
+
47
66
  private readonly device: GPUDevice;
48
67
  private readonly pipelines = new Map<string, GPURenderPipeline>();
49
68
  /**
@@ -62,11 +81,19 @@ export class PipelineCache {
62
81
  */
63
82
  private readonly compiling = new Map<string, Promise<GPURenderPipeline>>();
64
83
 
65
- constructor(device: GPUDevice, format: GPUTextureFormat, sampleCount = 1, glassShadows = true) {
84
+ constructor(
85
+ device: GPUDevice,
86
+ format: GPUTextureFormat,
87
+ sampleCount = 1,
88
+ glassShadows = true,
89
+ /** The profile's `clusteredLights`, which compiles the clustered arm in or leaves it out. */
90
+ clusteredLights = false,
91
+ ) {
66
92
  this.device = device;
67
93
  this.format = format;
68
94
  this.sampleCount = sampleCount;
69
95
  this.glassShadows = glassShadows;
96
+ this.litSwitches.CLUSTERED_LIGHTS = clusteredLights;
70
97
  }
71
98
 
72
99
  /** How many distinct pipelines have been built. Read by tests and diagnostics. */
@@ -85,11 +112,15 @@ export class PipelineCache {
85
112
  async getAsync(
86
113
  key: string,
87
114
  describe: () => GPURenderPipelineDescriptor,
115
+ /** Whether this is a lit pipeline, which `enable` rebuilds. See `litSwitches`. */
116
+ lit = false,
88
117
  ): Promise<GPURenderPipeline> {
118
+ if (lit) this.litDescribers.set(key, describe);
89
119
  const existing = this.pipelines.get(key);
90
120
  if (existing !== undefined) return existing;
91
121
  const inFlight = this.compiling.get(key);
92
122
  if (inFlight !== undefined) return inFlight;
123
+ const generation = this.litGeneration;
93
124
 
94
125
  /*
95
126
  * **Falls back where the async form is missing.** It is part of the standard, but a device
@@ -103,7 +134,8 @@ export class PipelineCache {
103
134
  ? this.device.createRenderPipelineAsync(describe())
104
135
  : Promise.resolve(this.device.createRenderPipeline(describe()));
105
136
  const promise = build.then((built) => {
106
- this.pipelines.set(key, built);
137
+ /* A lit pipeline started before a switch is not kept: the switch rebuilds it. */
138
+ if (!lit || generation === this.litGeneration) this.pipelines.set(key, built);
107
139
  this.compiling.delete(key);
108
140
  return built;
109
141
  });
@@ -142,7 +174,13 @@ export class PipelineCache {
142
174
  * too**: a nested object with vertex buffer layouts and blend state, constructed to be
143
175
  * thrown away on every hit. Passing a function means a hit costs one `Map` lookup.
144
176
  */
145
- get(key: string, describe: () => GPURenderPipelineDescriptor): GPURenderPipeline {
177
+ get(
178
+ key: string,
179
+ describe: () => GPURenderPipelineDescriptor,
180
+ /** Whether this is a lit pipeline, which `enable` rebuilds. See `litSwitches`. */
181
+ lit = false,
182
+ ): GPURenderPipeline {
183
+ if (lit) this.litDescribers.set(key, describe);
146
184
  const existing = this.pipelines.get(key);
147
185
  if (existing !== undefined) return existing;
148
186
  const built = this.device.createRenderPipeline(describe());
@@ -150,6 +188,42 @@ export class PipelineCache {
150
188
  return built;
151
189
  }
152
190
 
191
+ /**
192
+ * Turn one of the lit stage's switches on, for every lit pipeline built from now on and every one
193
+ * already built. See `litSwitches`.
194
+ *
195
+ * **The rebuilt set lands at once**, when every one of them has compiled, so no frame draws some
196
+ * surfaces with a feature and others without. Until then the pipelines already built go on drawing
197
+ * as they were. Once only: a switch is not turned off again when the last user of it leaves,
198
+ * because a second switch would cost a second compile for a saving the first use showed was not
199
+ * needed. Glass is never turned on where the profile asked for no glass shadows.
200
+ */
201
+ enable(feature: LitSwitch): Promise<void> {
202
+ if (this.litSwitches[feature]) return this.litRebuild;
203
+ if (feature === 'GLASS_SHADOWS' && !this.glassShadows) return this.litRebuild;
204
+ this.litSwitches[feature] = true;
205
+ this.litGeneration += 1;
206
+ const generation = this.litGeneration;
207
+ this.litRebuild = this.litRebuild.then(async () => {
208
+ /* A later switch has its own rebuild, which describes every pipeline with this one on too. */
209
+ if (generation !== this.litGeneration) return;
210
+ const rebuilt = [...this.litDescribers].map(([key, describe]) => {
211
+ const descriptor = describe();
212
+ const build =
213
+ typeof this.device.createRenderPipelineAsync === 'function'
214
+ ? this.device.createRenderPipelineAsync(descriptor)
215
+ : Promise.resolve(this.device.createRenderPipeline(descriptor));
216
+ return build.then((pipeline) => [key, pipeline] as const);
217
+ });
218
+ const results = await Promise.allSettled(rebuilt);
219
+ if (generation !== this.litGeneration) return;
220
+ for (const result of results) {
221
+ if (result.status === 'fulfilled') this.pipelines.set(result.value[0], result.value[1]);
222
+ }
223
+ });
224
+ return this.litRebuild;
225
+ }
226
+
153
227
  /**
154
228
  * Drop every pipeline.
155
229
  *
@@ -9,6 +9,7 @@ import { DEPTH_FORMAT } from './flatPass.ts';
9
9
  import type { PipelineCache } from './pipelineCache.ts';
10
10
  import type { UniformFields } from './scatterPass.ts';
11
11
  import { shaderModule } from './shaderModules.ts';
12
+ import { SCENE_ALPHA_COVERS } from '../../sceneCoverage.ts';
12
13
 
13
14
  /**
14
15
  * The plume pass: crossed quads per plume, displaced and faded in the shaders.
@@ -251,7 +252,7 @@ function blendState(blend: 'additive' | 'alpha'): GPUBlendState {
251
252
  }
252
253
  : {
253
254
  color: { srcFactor: 'src-alpha', dstFactor: 'one-minus-src-alpha', operation: 'add' },
254
- alpha: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
255
+ alpha: SCENE_ALPHA_COVERS,
255
256
  };
256
257
  }
257
258
 
@@ -39,6 +39,7 @@ import {
39
39
  import type { UniformFields } from './scatterPass.ts';
40
40
  import type { PipelineCache } from './pipelineCache.ts';
41
41
  import { shaderModule } from './shaderModules.ts';
42
+ import { SCENE_ALPHA_KEEPS, SCENE_ALPHA_TRANSMITS } from '../../sceneCoverage.ts';
42
43
 
43
44
  /**
44
45
  * The composite: everything that happens to the frame after the world is drawn.
@@ -271,7 +272,7 @@ export function createPostStageLayout(
271
272
  */
272
273
  export const OIT_COMPOSITE_BLEND: GPUBlendState = {
273
274
  color: { srcFactor: 'one-minus-src-alpha', dstFactor: 'src-alpha', operation: 'add' },
274
- alpha: { srcFactor: 'one-minus-src-alpha', dstFactor: 'src-alpha', operation: 'add' },
275
+ alpha: SCENE_ALPHA_TRANSMITS,
275
276
  };
276
277
 
277
278
  /**
@@ -290,17 +291,19 @@ export const OIT_COMPOSITE_BLEND: GPUBlendState = {
290
291
  * exactly the integral — and the composite therefore never samples the colour it is modifying,
291
292
  * which is the read both backends are entitled to call undefined.
292
293
  *
293
- * Alpha is `(zero, one)`: the frame's own alpha is not the medium's to spend. The scene target
294
- * carries it to the canvas, and a medium that wrote there would make a foggy frame translucent.
294
+ * Alpha is the transmittance again, `share * a`: the scene's alpha is how much of the pixel is
295
+ * still the opaque surface, which is what the composite lets ambient occlusion darken, and a
296
+ * medium thick enough to hide a corner hides its occlusion too (`sceneCoverage.ts`). It reaches
297
+ * no canvas: both are opaque, and the composite writes 1.
295
298
  */
296
299
  export const MEDIUM_TRANSMIT_BLEND: GPUBlendState = {
297
300
  color: { srcFactor: 'one', dstFactor: 'src-alpha', operation: 'add' },
298
- alpha: { srcFactor: 'zero', dstFactor: 'one', operation: 'add' },
301
+ alpha: SCENE_ALPHA_TRANSMITS,
299
302
  };
300
303
 
301
304
  export const DECAL_MULTIPLY_BLEND: GPUBlendState = {
302
305
  color: { srcFactor: 'zero', dstFactor: 'src', operation: 'add' },
303
- alpha: { srcFactor: 'zero', dstFactor: 'src', operation: 'add' },
306
+ alpha: SCENE_ALPHA_KEEPS,
304
307
  };
305
308
 
306
309
  /**
@@ -316,6 +319,18 @@ export const PREMULTIPLIED_OVER_BLEND: GPUBlendState = {
316
319
  alpha: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
317
320
  };
318
321
 
322
+ /**
323
+ * The same `over` for the resolve that lands a reflection on the scene, keeping the scene's alpha.
324
+ *
325
+ * A reflection is a layer of the surface it is seen in, so the surface's share of the pixel is
326
+ * unchanged (`sceneCoverage.ts`). The trace keeps `PREMULTIPLIED_OVER_BLEND`, because its target is
327
+ * the reflection's own and its alpha is the coverage this resolve reads.
328
+ */
329
+ export const REFLECTION_OVER_BLEND: GPUBlendState = {
330
+ color: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
331
+ alpha: SCENE_ALPHA_KEEPS,
332
+ };
333
+
319
334
  export const ADDITIVE_BLEND: GPUBlendState = {
320
335
  color: { srcFactor: 'one', dstFactor: 'one', operation: 'add' },
321
336
  alpha: { srcFactor: 'one', dstFactor: 'one', operation: 'add' },