@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
@@ -87,13 +87,18 @@ export declare const DEPTH_CUTOUT_VERT_FIELDS: {
87
87
  readonly size: 8;
88
88
  readonly type: "vec2";
89
89
  };
90
- readonly uAlphaCutout: {
90
+ readonly uUvOffset: {
91
91
  readonly offset: 168;
92
+ readonly size: 8;
93
+ readonly type: "vec2";
94
+ };
95
+ readonly uAlphaCutout: {
96
+ readonly offset: 176;
92
97
  readonly size: 4;
93
98
  readonly type: "float";
94
99
  };
95
100
  };
96
- export declare const DEPTH_CUTOUT_VERT_SIZE: 176;
101
+ export declare const DEPTH_CUTOUT_VERT_SIZE: 192;
97
102
  export declare const DEPTH_INSTANCED_CUTOUT_VERT_FIELDS: {
98
103
  readonly uLightViewProj: {
99
104
  readonly offset: 0;
@@ -105,8 +110,13 @@ export declare const DEPTH_INSTANCED_CUTOUT_VERT_FIELDS: {
105
110
  readonly size: 8;
106
111
  readonly type: "vec2";
107
112
  };
108
- readonly uAlphaCutout: {
113
+ readonly uUvOffset: {
109
114
  readonly offset: 72;
115
+ readonly size: 8;
116
+ readonly type: "vec2";
117
+ };
118
+ readonly uAlphaCutout: {
119
+ readonly offset: 80;
110
120
  readonly size: 4;
111
121
  readonly type: "float";
112
122
  };
@@ -12,9 +12,10 @@ export const DEPTH_VERT_FIELDS = DEPTH_BINDINGS.DEPTH_VERT.fields;
12
12
  export const DEPTH_VERT_SIZE = DEPTH_BINDINGS.DEPTH_VERT.uniformSize;
13
13
  export const DEPTH_FRAG_SIZE = DEPTH_BINDINGS.DEPTH_FRAG.uniformSize;
14
14
  /*
15
- * The cutout variants' vertex blocks. The rigid one is the plain block with `uUvScale` and
16
- * `uAlphaCutout` after every other field, so a slot written for it is written with the plain
17
- * offsets plus these two; the instanced one has no model and no wind, so its two sit earlier.
15
+ * The cutout variants' vertex blocks. The rigid one is the plain block with `uUvScale`,
16
+ * `uUvOffset` and `uAlphaCutout` after every other field, so a slot written for it is written with
17
+ * the plain offsets plus these three; the instanced one has no model and no wind, so its three sit
18
+ * earlier.
18
19
  */
19
20
  export const DEPTH_CUTOUT_VERT_FIELDS = DEPTH_BINDINGS.DEPTH_CUTOUT_VERT.fields;
20
21
  export const DEPTH_CUTOUT_VERT_SIZE = DEPTH_BINDINGS.DEPTH_CUTOUT_VERT.uniformSize;
@@ -3,6 +3,7 @@ import { FILM_BINDINGS, FILM_FRAG_WGSL, FILM_VERT_WGSL, } from '../../shaders/ge
3
3
  import { vertexBufferLayouts } from './buffers.js';
4
4
  import { DEPTH_FORMAT } from './flatPass.js';
5
5
  import { shaderModule } from './shaderModules.js';
6
+ import { SCENE_ALPHA_KEEPS } from '../../sceneCoverage.js';
6
7
  /**
7
8
  * The film pass: a thin wet layer — an oil slick, a puddle — lying over the world.
8
9
  *
@@ -85,7 +86,7 @@ export function filmPipeline(cache, device, layout, key, present) {
85
86
  format: cache.format,
86
87
  blend: {
87
88
  color: { srcFactor: 'src-alpha', dstFactor: 'one-minus-src-alpha', operation: 'add' },
88
- alpha: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
89
+ alpha: SCENE_ALPHA_KEEPS,
89
90
  },
90
91
  },
91
92
  ],
@@ -86,6 +86,11 @@ export declare const FLAT_VERT_FIELDS: {
86
86
  readonly size: 8;
87
87
  readonly type: "vec2";
88
88
  };
89
+ readonly uUvOffset: {
90
+ readonly offset: 152;
91
+ readonly size: 8;
92
+ readonly type: "vec2";
93
+ };
89
94
  readonly uModel: {
90
95
  readonly offset: 160;
91
96
  readonly size: 64;
@@ -2,6 +2,7 @@ import { DEPTH_COMPARE, DEPTH_FORMAT, depthOffsetForLayer } from '../../depthCon
2
2
  import { FLAT_BINDINGS, FLAT_FRAG_WGSL, FLAT_VERT_WGSL, } from '../../shaders/generated/flat.wgsl.js';
3
3
  import { vertexBufferLayouts } from './buffers.js';
4
4
  import { shaderModule } from './shaderModules.js';
5
+ import { SCENE_ALPHA_COVERS } from '../../sceneCoverage.js';
5
6
  /** The material maps the flat pass reads: `2d-array` colour, filtered, not shadow maps. */
6
7
  const SURFACE_MAPS = new Set([
7
8
  'uAlbedo',
@@ -115,18 +116,27 @@ export function flatFragmentBindings(variant) {
115
116
  return found;
116
117
  }
117
118
  /**
118
- * The lit fragment stage's constants, set from the cache: the glass switch, keyed by its **id**.
119
+ * The lit fragment stage's constants, set from the cache's switches, keyed by each one's **id**.
119
120
  *
120
121
  * Not by its name, because an override declared with `@id` answers to the id alone — WebGPU's
121
122
  * "pipeline-overridable constant identifier string" — and naga writes one on every specialisation
122
123
  * constant. Keyed by name, Chrome refused every lit pipeline: *Pipeline overridable constant
123
- * "GLASS_SHADOWS" not found*, and the frame drew nothing.
124
+ * "GLASS_SHADOWS" not found*, and the frame drew nothing. Every override the variant declares is
125
+ * set, so a switch added to the shader and not to the cache is refused here rather than defaulted.
124
126
  */
125
127
  function flatFragmentConstants(variant, cache) {
126
- const id = flatFragmentBindings(variant).overrides?.['GLASS_SHADOWS'];
127
- if (id === undefined)
128
+ const overrides = flatFragmentBindings(variant).overrides;
129
+ if (overrides?.['GLASS_SHADOWS'] === undefined) {
128
130
  throw new Error(`flatPass: variant "${variant}" has no glass switch`);
129
- return { [String(id)]: cache.glassShadows ? 1 : 0 };
131
+ }
132
+ const constants = {};
133
+ for (const [name, id] of Object.entries(overrides)) {
134
+ if (!(name in cache.litSwitches)) {
135
+ throw new Error(`flatPass: variant "${variant}" declares a switch "${name}" no cache sets`);
136
+ }
137
+ constants[String(id)] = cache.litSwitches[name] ? 1 : 0;
138
+ }
139
+ return constants;
130
140
  }
131
141
  /** The depth format both backends compare with. */
132
142
  /* Re-exported so the twenty passes that already import it from here do not each learn a new
@@ -391,7 +401,7 @@ instanced = false,
391
401
  oit = 'none',
392
402
  /** Culls nothing, for a surface seen from both faces. The key must carry it: `|2s`. */
393
403
  doubleSided = false) {
394
- return cache.get(key, () => flatDescriptor(cache, device, layout, variant, key, present, translucent, skinned, morphed, depthWrite, depthLayer, instanced, oit, doubleSided));
404
+ return cache.get(key, () => flatDescriptor(cache, device, layout, variant, key, present, translucent, skinned, morphed, depthWrite, depthLayer, instanced, oit, doubleSided), true);
395
405
  }
396
406
  /**
397
407
  * The same pipeline, compiled off the main thread.
@@ -407,7 +417,7 @@ export function flatPipelineAsync(cache, device, layout, variant, key, present,
407
417
  skinned = false,
408
418
  /** Whether this pipeline reads morph deltas. See `FlatVertexOptions`. */
409
419
  morphed = false) {
410
- return cache.getAsync(key, () => flatDescriptor(cache, device, layout, variant, key, present, translucent, skinned, morphed));
420
+ return cache.getAsync(key, () => flatDescriptor(cache, device, layout, variant, key, present, translucent, skinned, morphed), true);
411
421
  }
412
422
  /**
413
423
  * The colour target for an order-independent pass, or null where this is an ordinary draw.
@@ -508,7 +518,7 @@ doubleSided = false) {
508
518
  dstFactor: 'one-minus-src-alpha',
509
519
  operation: 'add',
510
520
  },
511
- alpha: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
521
+ alpha: SCENE_ALPHA_COVERS,
512
522
  },
513
523
  }
514
524
  : { format: cache.format }),
@@ -3,6 +3,7 @@ import { LINE_BINDINGS, LINE_FRAG_WGSL, LINE_VERT_WGSL, } from '../../shaders/ge
3
3
  import { buildSegmentQuads } from '../../segmentQuads.js';
4
4
  import { DEPTH_FORMAT } from './flatPass.js';
5
5
  import { shaderModule } from './shaderModules.js';
6
+ import { SCENE_ALPHA_COVERS } from '../../sceneCoverage.js';
6
7
  /**
7
8
  * The line pass: a polyline with a real width, four vertices a segment.
8
9
  *
@@ -179,7 +180,7 @@ additive = false) {
179
180
  dstFactor: 'one-minus-src-alpha',
180
181
  operation: 'add',
181
182
  },
182
- alpha: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
183
+ alpha: SCENE_ALPHA_COVERS,
183
184
  },
184
185
  },
185
186
  ],
@@ -1,6 +1,7 @@
1
1
  import { PANEL_BINDINGS, PANEL_FRAG_WGSL, PANEL_VERT_WGSL, } from '../../shaders/generated/panel.wgsl.js';
2
2
  import { DEPTH_FORMAT } from './flatPass.js';
3
3
  import { shaderModule } from './shaderModules.js';
4
+ import { SCENE_ALPHA_COVERS } from '../../sceneCoverage.js';
4
5
  /**
5
6
  * The panel pass: two triangles in screen space, one colour, one alpha.
6
7
  *
@@ -97,7 +98,7 @@ export function panelPipeline(cache, device, layout) {
97
98
  format: cache.format,
98
99
  blend: {
99
100
  color: { srcFactor: 'src-alpha', dstFactor: 'one-minus-src-alpha', operation: 'add' },
100
- alpha: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
101
+ alpha: SCENE_ALPHA_COVERS,
101
102
  },
102
103
  },
103
104
  ],
@@ -4,6 +4,7 @@ import { PARTICLE_BINDINGS } from '../../shaders/generated/particle.wgsl.js';
4
4
  import { PARTICLE_BLADES, PARTICLE_INDICES, PARTICLE_VERTS } from '../../shaders/particle.js';
5
5
  import { DEPTH_FORMAT } from './flatPass.js';
6
6
  import { shaderModule } from './shaderModules.js';
7
+ import { SCENE_ALPHA_COVERS } from '../../sceneCoverage.js';
7
8
  /**
8
9
  * The particle pass: a crossed pair of quads per live particle, rebuilt every frame.
9
10
  *
@@ -269,7 +270,7 @@ export function particlePipeline(cache, device, layout, material, blend) {
269
270
  dstFactor: 'one-minus-src-alpha',
270
271
  operation: 'add',
271
272
  },
272
- alpha: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
273
+ alpha: SCENE_ALPHA_COVERS,
273
274
  },
274
275
  },
275
276
  ],
@@ -1,3 +1,5 @@
1
+ import { type LitSwitch } from '../../shaders/flat/glassTint.ts';
2
+ export type { LitSwitch };
1
3
  /**
2
4
  * Render pipelines, built once and looked up by name.
3
5
  *
@@ -33,14 +35,27 @@ export declare class PipelineCache {
33
35
  */
34
36
  readonly sampleCount: number;
35
37
  /**
36
- * Whether the lit pipelines here read glass in their shadows: `glassShadows` other than `'off'`.
38
+ * Which of the lit stage's switches the pipelines built now have on: the pipeline-overridable
39
+ * constants the generated lit shader branches on (`GLASS_SHADOWS` and `litSwitchesGlsl`), which
40
+ * the device compiles away where they are off. Read by `flatPass` when it describes a pipeline.
37
41
  *
38
- * **Carried by the cache for the reason the format is**: one value every lit pipeline is built
39
- * with, chosen once by the renderer. It is the `GLASS_SHADOWS` override the generated lit shader
40
- * branches on (see `FlatShaderOptions.glassShadows`), so off specialises the lookups away rather
41
- * than leaving code that returns at its first line and still costs the pass its registers.
42
+ * **Each is code a scene pays for in every lit pixel's registers whether it uses it or not**, so
43
+ * each is off until it is used: glass until the renderer is offered a pane, a fixture until one is
44
+ * loaded, the effects until a material carries a table, DriftLight until a volume is set — and
45
+ * then on for good, through `enable`. Clustering is the profile's, fixed at construction. Every
46
+ * 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%
47
+ * of the lit shader's instructions for glass alone, measured on RADV. **What it costs** is the
48
+ * frames between a feature's first use and its recompiled pipelines, which draw without it.
42
49
  */
50
+ readonly litSwitches: Record<LitSwitch, boolean>;
51
+ /** Whether glass may ever be switched on here: `glassShadows` other than `'off'`. */
43
52
  readonly glassShadows: boolean;
53
+ /** How to describe each lit pipeline again, so `enable` can rebuild every one. */
54
+ private readonly litDescribers;
55
+ /** Bumped by every switch, so a lit build started before one is not kept after it. */
56
+ private litGeneration;
57
+ /** The last rebuild, which the next one waits for so they land in the order they were asked. */
58
+ private litRebuild;
44
59
  private readonly device;
45
60
  private readonly pipelines;
46
61
  /**
@@ -58,7 +73,9 @@ export declare class PipelineCache {
58
73
  * wait for readiness rather than discovering it mid-frame.
59
74
  */
60
75
  private readonly compiling;
61
- constructor(device: GPUDevice, format: GPUTextureFormat, sampleCount?: number, glassShadows?: boolean);
76
+ constructor(device: GPUDevice, format: GPUTextureFormat, sampleCount?: number, glassShadows?: boolean,
77
+ /** The profile's `clusteredLights`, which compiles the clustered arm in or leaves it out. */
78
+ clusteredLights?: boolean);
62
79
  /** How many distinct pipelines have been built. Read by tests and diagnostics. */
63
80
  get size(): number;
64
81
  /**
@@ -69,7 +86,9 @@ export declare class PipelineCache {
69
86
  * difference is only *when* the driver does the work, and that difference is the whole bug
70
87
  * this exists for.
71
88
  */
72
- getAsync(key: string, describe: () => GPURenderPipelineDescriptor): Promise<GPURenderPipeline>;
89
+ getAsync(key: string, describe: () => GPURenderPipelineDescriptor,
90
+ /** Whether this is a lit pipeline, which `enable` rebuilds. See `litSwitches`. */
91
+ lit?: boolean): Promise<GPURenderPipeline>;
73
92
  /**
74
93
  * Resolve once every pipeline asked for so far has finished compiling.
75
94
  *
@@ -90,7 +109,20 @@ export declare class PipelineCache {
90
109
  * too**: a nested object with vertex buffer layouts and blend state, constructed to be
91
110
  * thrown away on every hit. Passing a function means a hit costs one `Map` lookup.
92
111
  */
93
- get(key: string, describe: () => GPURenderPipelineDescriptor): GPURenderPipeline;
112
+ get(key: string, describe: () => GPURenderPipelineDescriptor,
113
+ /** Whether this is a lit pipeline, which `enable` rebuilds. See `litSwitches`. */
114
+ lit?: boolean): GPURenderPipeline;
115
+ /**
116
+ * Turn one of the lit stage's switches on, for every lit pipeline built from now on and every one
117
+ * already built. See `litSwitches`.
118
+ *
119
+ * **The rebuilt set lands at once**, when every one of them has compiled, so no frame draws some
120
+ * surfaces with a feature and others without. Until then the pipelines already built go on drawing
121
+ * as they were. Once only: a switch is not turned off again when the last user of it leaves,
122
+ * because a second switch would cost a second compile for a saving the first use showed was not
123
+ * needed. Glass is never turned on where the profile asked for no glass shadows.
124
+ */
125
+ enable(feature: LitSwitch): Promise<void>;
94
126
  /**
95
127
  * Drop every pipeline.
96
128
  *
@@ -1,3 +1,4 @@
1
+ import { noLitSwitches } from '../../shaders/flat/glassTint.js';
1
2
  /**
2
3
  * Render pipelines, built once and looked up by name.
3
4
  *
@@ -33,14 +34,27 @@ export class PipelineCache {
33
34
  */
34
35
  sampleCount;
35
36
  /**
36
- * Whether the lit pipelines here read glass in their shadows: `glassShadows` other than `'off'`.
37
+ * Which of the lit stage's switches the pipelines built now have on: the pipeline-overridable
38
+ * constants the generated lit shader branches on (`GLASS_SHADOWS` and `litSwitchesGlsl`), which
39
+ * the device compiles away where they are off. Read by `flatPass` when it describes a pipeline.
37
40
  *
38
- * **Carried by the cache for the reason the format is**: one value every lit pipeline is built
39
- * with, chosen once by the renderer. It is the `GLASS_SHADOWS` override the generated lit shader
40
- * branches on (see `FlatShaderOptions.glassShadows`), so off specialises the lookups away rather
41
- * than leaving code that returns at its first line and still costs the pass its registers.
41
+ * **Each is code a scene pays for in every lit pixel's registers whether it uses it or not**, so
42
+ * each is off until it is used: glass until the renderer is offered a pane, a fixture until one is
43
+ * loaded, the effects until a material carries a table, DriftLight until a volume is set — and
44
+ * then on for good, through `enable`. Clustering is the profile's, fixed at construction. Every
45
+ * 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%
46
+ * of the lit shader's instructions for glass alone, measured on RADV. **What it costs** is the
47
+ * frames between a feature's first use and its recompiled pipelines, which draw without it.
42
48
  */
49
+ litSwitches = noLitSwitches();
50
+ /** Whether glass may ever be switched on here: `glassShadows` other than `'off'`. */
43
51
  glassShadows;
52
+ /** How to describe each lit pipeline again, so `enable` can rebuild every one. */
53
+ litDescribers = new Map();
54
+ /** Bumped by every switch, so a lit build started before one is not kept after it. */
55
+ litGeneration = 0;
56
+ /** The last rebuild, which the next one waits for so they land in the order they were asked. */
57
+ litRebuild = Promise.resolve();
44
58
  device;
45
59
  pipelines = new Map();
46
60
  /**
@@ -58,11 +72,14 @@ export class PipelineCache {
58
72
  * wait for readiness rather than discovering it mid-frame.
59
73
  */
60
74
  compiling = new Map();
61
- constructor(device, format, sampleCount = 1, glassShadows = true) {
75
+ constructor(device, format, sampleCount = 1, glassShadows = true,
76
+ /** The profile's `clusteredLights`, which compiles the clustered arm in or leaves it out. */
77
+ clusteredLights = false) {
62
78
  this.device = device;
63
79
  this.format = format;
64
80
  this.sampleCount = sampleCount;
65
81
  this.glassShadows = glassShadows;
82
+ this.litSwitches.CLUSTERED_LIGHTS = clusteredLights;
66
83
  }
67
84
  /** How many distinct pipelines have been built. Read by tests and diagnostics. */
68
85
  get size() {
@@ -76,13 +93,18 @@ export class PipelineCache {
76
93
  * difference is only *when* the driver does the work, and that difference is the whole bug
77
94
  * this exists for.
78
95
  */
79
- async getAsync(key, describe) {
96
+ async getAsync(key, describe,
97
+ /** Whether this is a lit pipeline, which `enable` rebuilds. See `litSwitches`. */
98
+ lit = false) {
99
+ if (lit)
100
+ this.litDescribers.set(key, describe);
80
101
  const existing = this.pipelines.get(key);
81
102
  if (existing !== undefined)
82
103
  return existing;
83
104
  const inFlight = this.compiling.get(key);
84
105
  if (inFlight !== undefined)
85
106
  return inFlight;
107
+ const generation = this.litGeneration;
86
108
  /*
87
109
  * **Falls back where the async form is missing.** It is part of the standard, but a device
88
110
  * can be a test double, a polyfill or an older implementation, and a renderer that throws
@@ -94,7 +116,9 @@ export class PipelineCache {
94
116
  ? this.device.createRenderPipelineAsync(describe())
95
117
  : Promise.resolve(this.device.createRenderPipeline(describe()));
96
118
  const promise = build.then((built) => {
97
- this.pipelines.set(key, built);
119
+ /* A lit pipeline started before a switch is not kept: the switch rebuilds it. */
120
+ if (!lit || generation === this.litGeneration)
121
+ this.pipelines.set(key, built);
98
122
  this.compiling.delete(key);
99
123
  return built;
100
124
  });
@@ -129,7 +153,11 @@ export class PipelineCache {
129
153
  * too**: a nested object with vertex buffer layouts and blend state, constructed to be
130
154
  * thrown away on every hit. Passing a function means a hit costs one `Map` lookup.
131
155
  */
132
- get(key, describe) {
156
+ get(key, describe,
157
+ /** Whether this is a lit pipeline, which `enable` rebuilds. See `litSwitches`. */
158
+ lit = false) {
159
+ if (lit)
160
+ this.litDescribers.set(key, describe);
133
161
  const existing = this.pipelines.get(key);
134
162
  if (existing !== undefined)
135
163
  return existing;
@@ -137,6 +165,45 @@ export class PipelineCache {
137
165
  this.pipelines.set(key, built);
138
166
  return built;
139
167
  }
168
+ /**
169
+ * Turn one of the lit stage's switches on, for every lit pipeline built from now on and every one
170
+ * already built. See `litSwitches`.
171
+ *
172
+ * **The rebuilt set lands at once**, when every one of them has compiled, so no frame draws some
173
+ * surfaces with a feature and others without. Until then the pipelines already built go on drawing
174
+ * as they were. Once only: a switch is not turned off again when the last user of it leaves,
175
+ * because a second switch would cost a second compile for a saving the first use showed was not
176
+ * needed. Glass is never turned on where the profile asked for no glass shadows.
177
+ */
178
+ enable(feature) {
179
+ if (this.litSwitches[feature])
180
+ return this.litRebuild;
181
+ if (feature === 'GLASS_SHADOWS' && !this.glassShadows)
182
+ return this.litRebuild;
183
+ this.litSwitches[feature] = true;
184
+ this.litGeneration += 1;
185
+ const generation = this.litGeneration;
186
+ this.litRebuild = this.litRebuild.then(async () => {
187
+ /* A later switch has its own rebuild, which describes every pipeline with this one on too. */
188
+ if (generation !== this.litGeneration)
189
+ return;
190
+ const rebuilt = [...this.litDescribers].map(([key, describe]) => {
191
+ const descriptor = describe();
192
+ const build = typeof this.device.createRenderPipelineAsync === 'function'
193
+ ? this.device.createRenderPipelineAsync(descriptor)
194
+ : Promise.resolve(this.device.createRenderPipeline(descriptor));
195
+ return build.then((pipeline) => [key, pipeline]);
196
+ });
197
+ const results = await Promise.allSettled(rebuilt);
198
+ if (generation !== this.litGeneration)
199
+ return;
200
+ for (const result of results) {
201
+ if (result.status === 'fulfilled')
202
+ this.pipelines.set(result.value[0], result.value[1]);
203
+ }
204
+ });
205
+ return this.litRebuild;
206
+ }
140
207
  /**
141
208
  * Drop every pipeline.
142
209
  *
@@ -6,6 +6,7 @@ import { FIRE_BINDINGS } from '../../shaders/generated/fire.wgsl.js';
6
6
  import { SMOKE_BINDINGS } from '../../shaders/generated/smoke.wgsl.js';
7
7
  import { DEPTH_FORMAT } from './flatPass.js';
8
8
  import { shaderModule } from './shaderModules.js';
9
+ import { SCENE_ALPHA_COVERS } from '../../sceneCoverage.js';
9
10
  /**
10
11
  * The plume pass: crossed quads per plume, displaced and faded in the shaders.
11
12
  *
@@ -179,7 +180,7 @@ function blendState(blend) {
179
180
  }
180
181
  : {
181
182
  color: { srcFactor: 'src-alpha', dstFactor: 'one-minus-src-alpha', operation: 'add' },
182
- alpha: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
183
+ alpha: SCENE_ALPHA_COVERS,
183
184
  };
184
185
  }
185
186
  /**
@@ -156,8 +156,10 @@ export declare const OIT_COMPOSITE_BLEND: GPUBlendState;
156
156
  * exactly the integral — and the composite therefore never samples the colour it is modifying,
157
157
  * which is the read both backends are entitled to call undefined.
158
158
  *
159
- * Alpha is `(zero, one)`: the frame's own alpha is not the medium's to spend. The scene target
160
- * carries it to the canvas, and a medium that wrote there would make a foggy frame translucent.
159
+ * Alpha is the transmittance again, `share * a`: the scene's alpha is how much of the pixel is
160
+ * still the opaque surface, which is what the composite lets ambient occlusion darken, and a
161
+ * medium thick enough to hide a corner hides its occlusion too (`sceneCoverage.ts`). It reaches
162
+ * no canvas: both are opaque, and the composite writes 1.
161
163
  */
162
164
  export declare const MEDIUM_TRANSMIT_BLEND: GPUBlendState;
163
165
  export declare const DECAL_MULTIPLY_BLEND: GPUBlendState;
@@ -170,6 +172,14 @@ export declare const DECAL_MULTIPLY_BLEND: GPUBlendState;
170
172
  * sample the colour it is compositing onto.
171
173
  */
172
174
  export declare const PREMULTIPLIED_OVER_BLEND: GPUBlendState;
175
+ /**
176
+ * The same `over` for the resolve that lands a reflection on the scene, keeping the scene's alpha.
177
+ *
178
+ * A reflection is a layer of the surface it is seen in, so the surface's share of the pixel is
179
+ * unchanged (`sceneCoverage.ts`). The trace keeps `PREMULTIPLIED_OVER_BLEND`, because its target is
180
+ * the reflection's own and its alpha is the coverage this resolve reads.
181
+ */
182
+ export declare const REFLECTION_OVER_BLEND: GPUBlendState;
173
183
  export declare const ADDITIVE_BLEND: GPUBlendState;
174
184
  /** A fullscreen pass over one or more inputs, writing to one colour target. */
175
185
  export declare function postPipeline(cache: PipelineCache, device: GPUDevice, layout: GPUBindGroupLayout, key: string, fragment: string, format: GPUTextureFormat, blend?: GPUBlendState): GPURenderPipeline;
@@ -9,6 +9,7 @@ import { SSRTRACE_BINDINGS as SSR_BINDINGS, SSR_TRACE_FRAG_WGSL, } from '../../s
9
9
  import { SSRRESOLVE_BINDINGS as SSR_RESOLVE_BINDINGS, 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
+ import { SCENE_ALPHA_KEEPS, SCENE_ALPHA_TRANSMITS } from '../../sceneCoverage.js';
12
13
  /**
13
14
  * The composite: everything that happens to the frame after the world is drawn.
14
15
  *
@@ -197,7 +198,7 @@ export function createPostStageLayout(device, uniformBinding, uniformSize, textu
197
198
  */
198
199
  export const OIT_COMPOSITE_BLEND = {
199
200
  color: { srcFactor: 'one-minus-src-alpha', dstFactor: 'src-alpha', operation: 'add' },
200
- alpha: { srcFactor: 'one-minus-src-alpha', dstFactor: 'src-alpha', operation: 'add' },
201
+ alpha: SCENE_ALPHA_TRANSMITS,
201
202
  };
202
203
  /**
203
204
  * What a drawn decal does to the pixel under it: `dst * src`.
@@ -215,16 +216,18 @@ export const OIT_COMPOSITE_BLEND = {
215
216
  * exactly the integral — and the composite therefore never samples the colour it is modifying,
216
217
  * which is the read both backends are entitled to call undefined.
217
218
  *
218
- * Alpha is `(zero, one)`: the frame's own alpha is not the medium's to spend. The scene target
219
- * carries it to the canvas, and a medium that wrote there would make a foggy frame translucent.
219
+ * Alpha is the transmittance again, `share * a`: the scene's alpha is how much of the pixel is
220
+ * still the opaque surface, which is what the composite lets ambient occlusion darken, and a
221
+ * medium thick enough to hide a corner hides its occlusion too (`sceneCoverage.ts`). It reaches
222
+ * no canvas: both are opaque, and the composite writes 1.
220
223
  */
221
224
  export const MEDIUM_TRANSMIT_BLEND = {
222
225
  color: { srcFactor: 'one', dstFactor: 'src-alpha', operation: 'add' },
223
- alpha: { srcFactor: 'zero', dstFactor: 'one', operation: 'add' },
226
+ alpha: SCENE_ALPHA_TRANSMITS,
224
227
  };
225
228
  export const DECAL_MULTIPLY_BLEND = {
226
229
  color: { srcFactor: 'zero', dstFactor: 'src', operation: 'add' },
227
- alpha: { srcFactor: 'zero', dstFactor: 'src', operation: 'add' },
230
+ alpha: SCENE_ALPHA_KEEPS,
228
231
  };
229
232
  /**
230
233
  * `over`, against colour a pass has already multiplied by its own coverage.
@@ -238,6 +241,17 @@ export const PREMULTIPLIED_OVER_BLEND = {
238
241
  color: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
239
242
  alpha: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
240
243
  };
244
+ /**
245
+ * The same `over` for the resolve that lands a reflection on the scene, keeping the scene's alpha.
246
+ *
247
+ * A reflection is a layer of the surface it is seen in, so the surface's share of the pixel is
248
+ * unchanged (`sceneCoverage.ts`). The trace keeps `PREMULTIPLIED_OVER_BLEND`, because its target is
249
+ * the reflection's own and its alpha is the coverage this resolve reads.
250
+ */
251
+ export const REFLECTION_OVER_BLEND = {
252
+ color: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
253
+ alpha: SCENE_ALPHA_KEEPS,
254
+ };
241
255
  export const ADDITIVE_BLEND = {
242
256
  color: { srcFactor: 'one', dstFactor: 'one', operation: 'add' },
243
257
  alpha: { srcFactor: 'one', dstFactor: 'one', operation: 'add' },