@driftengine/core 4.8.4 → 4.8.6

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 (138) hide show
  1. package/CHANGELOG.json +4332 -4250
  2. package/dist/build/shaderComments.js +0 -0
  3. package/dist/index.d.ts +10 -2
  4. package/dist/index.js +11 -1
  5. package/dist/render/ambientHarmonics.d.ts +46 -0
  6. package/dist/render/ambientHarmonics.js +80 -0
  7. package/dist/render/areaLights.d.ts +19 -0
  8. package/dist/render/areaLights.js +20 -0
  9. package/dist/render/backend/api.d.ts +13 -2
  10. package/dist/render/backend/webgl2/instanced.d.ts +2 -0
  11. package/dist/render/backend/webgl2/instanced.js +11 -2
  12. package/dist/render/backend/webgl2/renderer.d.ts +74 -1
  13. package/dist/render/backend/webgl2/renderer.js +229 -37
  14. package/dist/render/backend/webgl2/skinScatter.d.ts +14 -0
  15. package/dist/render/backend/webgl2/skinScatter.js +88 -26
  16. package/dist/render/backend/webgpu/buffers.js +2 -1
  17. package/dist/render/backend/webgpu/clusterBinner.js +3 -2
  18. package/dist/render/backend/webgpu/flatPass.d.ts +7 -5
  19. package/dist/render/backend/webgpu/flatPass.js +15 -13
  20. package/dist/render/backend/webgpu/particlePass.d.ts +37 -4
  21. package/dist/render/backend/webgpu/particlePass.js +80 -12
  22. package/dist/render/backend/webgpu/postPass.d.ts +6 -1
  23. package/dist/render/backend/webgpu/postPass.js +7 -2
  24. package/dist/render/backend/webgpu/renderer.d.ts +58 -7
  25. package/dist/render/backend/webgpu/renderer.js +329 -78
  26. package/dist/render/backend/webgpu/shaders/clusterBin.wgsl.d.ts +1 -1
  27. package/dist/render/backend/webgpu/shaders/clusterBin.wgsl.js +41 -1
  28. package/dist/render/backend/webgpu/skinPaletteRing.d.ts +40 -28
  29. package/dist/render/backend/webgpu/skinPaletteRing.js +69 -32
  30. package/dist/render/backend/webgpu/skinScatterPass.d.ts +10 -2
  31. package/dist/render/backend/webgpu/skinScatterPass.js +59 -9
  32. package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +2 -1
  33. package/dist/render/backend/webgpu/surfaceTexturePass.js +18 -4
  34. package/dist/render/bloomChain.d.ts +44 -0
  35. package/dist/render/bloomChain.js +28 -0
  36. package/dist/render/bloomPass.d.ts +3 -1
  37. package/dist/render/bloomPass.js +16 -2
  38. package/dist/render/clusteredLights.d.ts +55 -1
  39. package/dist/render/clusteredLights.js +182 -8
  40. package/dist/render/fixtureAtlas.js +1 -1
  41. package/dist/render/instanceCull.js +7 -0
  42. package/dist/render/instances.d.ts +32 -10
  43. package/dist/render/instances.js +17 -8
  44. package/dist/render/lightBudget.d.ts +26 -1
  45. package/dist/render/lightBudget.js +26 -3
  46. package/dist/render/lightmap.d.ts +102 -0
  47. package/dist/render/lightmap.js +118 -0
  48. package/dist/render/mesh.js +2 -1
  49. package/dist/render/particleBatch.d.ts +59 -3
  50. package/dist/render/particleBatch.js +53 -2
  51. package/dist/render/particleMaterial.d.ts +6 -1
  52. package/dist/render/particleMaterial.js +10 -2
  53. package/dist/render/particlePool.d.ts +11 -0
  54. package/dist/render/particlePool.js +2 -0
  55. package/dist/render/particleSort.d.ts +34 -0
  56. package/dist/render/particleSort.js +96 -0
  57. package/dist/render/sceneTarget.d.ts +5 -0
  58. package/dist/render/sceneTarget.js +8 -1
  59. package/dist/render/shaders/bloom.d.ts +8 -4
  60. package/dist/render/shaders/bloom.js +20 -3
  61. package/dist/render/shaders/flat/areaLight.d.ts +10 -0
  62. package/dist/render/shaders/flat/areaLight.js +96 -0
  63. package/dist/render/shaders/flat/index.js +30 -4
  64. package/dist/render/shaders/flat/main.d.ts +1 -1
  65. package/dist/render/shaders/flat/main.js +107 -105
  66. package/dist/render/shaders/flat/models.d.ts +16 -4
  67. package/dist/render/shaders/flat/models.js +64 -4
  68. package/dist/render/shaders/flat/normalMap.d.ts +10 -0
  69. package/dist/render/shaders/flat/normalMap.js +25 -0
  70. package/dist/render/shaders/flat/preamble.js +74 -37
  71. package/dist/render/shaders/flat/skin.js +6 -5
  72. package/dist/render/shaders/generated/bloom.wgsl.d.ts +13 -3
  73. package/dist/render/shaders/generated/bloom.wgsl.js +13 -3
  74. package/dist/render/shaders/generated/flat.wgsl.d.ts +1631 -2044
  75. package/dist/render/shaders/generated/flat.wgsl.js +1669 -2082
  76. package/dist/render/shaders/generated/particle.wgsl.d.ts +115 -1
  77. package/dist/render/shaders/generated/particle.wgsl.js +115 -1
  78. package/dist/render/shaders/generated/skinBlur.wgsl.d.ts +12 -2
  79. package/dist/render/shaders/generated/skinBlur.wgsl.js +12 -2
  80. package/dist/render/shaders/particle.d.ts +17 -1
  81. package/dist/render/shaders/particle.js +133 -1
  82. package/dist/render/shaders/skinBlur.js +25 -3
  83. package/dist/render/skinBlur.d.ts +8 -5
  84. package/dist/render/skinBlur.js +8 -5
  85. package/dist/render/surfaceModel.d.ts +21 -1
  86. package/dist/render/surfaceModel.js +22 -0
  87. package/dist/render/surfaceTexture.d.ts +14 -1
  88. package/dist/render/surfaceTexture.js +15 -2
  89. package/package.json +6 -6
  90. package/src/build/shaderComments.ts +0 -0
  91. package/src/index.ts +20 -1
  92. package/src/render/ambientHarmonics.ts +92 -0
  93. package/src/render/areaLights.ts +39 -0
  94. package/src/render/backend/api.ts +14 -1
  95. package/src/render/backend/webgl2/instanced.ts +12 -2
  96. package/src/render/backend/webgl2/renderer.ts +292 -35
  97. package/src/render/backend/webgl2/skinScatter.ts +120 -43
  98. package/src/render/backend/webgpu/buffers.ts +2 -1
  99. package/src/render/backend/webgpu/clusterBinner.ts +3 -1
  100. package/src/render/backend/webgpu/flatPass.ts +18 -13
  101. package/src/render/backend/webgpu/particlePass.ts +118 -12
  102. package/src/render/backend/webgpu/postPass.ts +7 -2
  103. package/src/render/backend/webgpu/renderer.ts +396 -95
  104. package/src/render/backend/webgpu/shaders/clusterBin.wgsl.ts +41 -1
  105. package/src/render/backend/webgpu/skinPaletteRing.ts +65 -33
  106. package/src/render/backend/webgpu/skinScatterPass.ts +59 -9
  107. package/src/render/backend/webgpu/surfaceTexturePass.ts +26 -5
  108. package/src/render/bloomChain.ts +69 -0
  109. package/src/render/bloomPass.ts +26 -2
  110. package/src/render/clusteredLights.ts +221 -7
  111. package/src/render/fixtureAtlas.ts +1 -1
  112. package/src/render/instanceCull.ts +5 -0
  113. package/src/render/instances.ts +40 -11
  114. package/src/render/lightBudget.ts +48 -2
  115. package/src/render/lightmap.ts +202 -0
  116. package/src/render/mesh.ts +2 -1
  117. package/src/render/particleBatch.ts +95 -4
  118. package/src/render/particleMaterial.ts +11 -1
  119. package/src/render/particlePool.ts +13 -0
  120. package/src/render/particleSort.ts +133 -0
  121. package/src/render/sceneTarget.ts +18 -1
  122. package/src/render/shaders/bloom.ts +20 -3
  123. package/src/render/shaders/flat/areaLight.ts +96 -0
  124. package/src/render/shaders/flat/index.ts +30 -4
  125. package/src/render/shaders/flat/main.ts +107 -105
  126. package/src/render/shaders/flat/models.ts +73 -8
  127. package/src/render/shaders/flat/normalMap.ts +25 -0
  128. package/src/render/shaders/flat/preamble.ts +74 -37
  129. package/src/render/shaders/flat/skin.ts +6 -5
  130. package/src/render/shaders/generated/bloom.wgsl.ts +13 -3
  131. package/src/render/shaders/generated/flat.wgsl.ts +1669 -2082
  132. package/src/render/shaders/generated/particle.wgsl.ts +116 -1
  133. package/src/render/shaders/generated/skinBlur.wgsl.ts +12 -2
  134. package/src/render/shaders/particle.ts +134 -1
  135. package/src/render/shaders/skinBlur.ts +25 -3
  136. package/src/render/skinBlur.ts +8 -5
  137. package/src/render/surfaceModel.ts +36 -1
  138. package/src/render/surfaceTexture.ts +41 -3
@@ -5,14 +5,15 @@
5
5
  *
6
6
  * **Drawn as it goes, not kept**: this backend issues a draw when it is asked for, and the state a
7
7
  * skin draw reads — its joint palette, its uniforms — is rewritten by the draw after it. So the
8
- * renderer draws each skin twice in place, the frame's half and then at once its diffuse half into
9
- * this target, which shares the frame's depth and tests for equality against it.
8
+ * renderer draws each skin three times in place: the frame's half, then at once the diffuse's light
9
+ * alone into this target and its colour alone into the other, both sharing the frame's depth and
10
+ * testing for equality against it. The blur spreads the light and multiplies it by the colour.
10
11
  *
11
12
  * **The units it reads are its own** — `SKIN_BLUR_TEXTURE_UNIT` and the one after — past every
12
13
  * unit a lit program binds, because it runs in the middle of a frame and the draws after it must
13
14
  * find their textures where they left them.
14
15
  */
15
- import { SKIN_BLUR_DEPTH_UNIT, SKIN_BLUR_TEXTURE_UNIT } from '../../lightBudget.js';
16
+ import { SKIN_BLUR_ALBEDO_UNIT, SKIN_BLUR_DEPTH_UNIT, SKIN_BLUR_TEXTURE_UNIT, } from '../../lightBudget.js';
16
17
  import { blendKeeping } from '../../sceneCoverage.js';
17
18
  import { compileProgram, uniformLocations } from '../../shader.js';
18
19
  import { FULLSCREEN_VERT } from '../../shaders/fullscreen.js';
@@ -30,9 +31,13 @@ export class GlSkinScatter {
30
31
  depthToViewZ = new Float32Array(4);
31
32
  diffuse = null;
32
33
  across = null;
34
+ albedo = null;
33
35
  multisampled = null;
36
+ albedoMultisampled = null;
34
37
  drawFramebuffer = null;
35
38
  resolveFramebuffer = null;
39
+ albedoFramebuffer = null;
40
+ albedoResolveFramebuffer = null;
36
41
  acrossFramebuffer = null;
37
42
  width = 0;
38
43
  height = 0;
@@ -41,6 +46,7 @@ export class GlSkinScatter {
41
46
  /** Refused once and for good: a driver that will not render to half floats. */
42
47
  unavailable = false;
43
48
  cleared = false;
49
+ albedoCleared = false;
44
50
  drawn = false;
45
51
  constructor(gl) {
46
52
  this.gl = gl;
@@ -102,6 +108,16 @@ export class GlSkinScatter {
102
108
  }
103
109
  this.drawn = true;
104
110
  }
111
+ /** Bind the target for the same skin's colour half, cleared the first time a frame does. */
112
+ beginAlbedo() {
113
+ const { gl } = this;
114
+ gl.bindFramebuffer(gl.FRAMEBUFFER, this.albedoFramebuffer);
115
+ gl.viewport(0, 0, this.width, this.height);
116
+ if (!this.albedoCleared) {
117
+ gl.clearBufferfv(gl.COLOR, 0, CLEAR);
118
+ this.albedoCleared = true;
119
+ }
120
+ }
105
121
  /**
106
122
  * Across into the second target, then down and added into the frame, which `scene` binds. `depth`
107
123
  * is the frame's depth as a single-sampled texture, or null where none could be had — and then
@@ -110,9 +126,8 @@ export class GlSkinScatter {
110
126
  spread(scene, depth, empty, projection, inverse) {
111
127
  const { gl } = this;
112
128
  if (this.multisampled !== null) {
113
- gl.bindFramebuffer(gl.READ_FRAMEBUFFER, this.drawFramebuffer);
114
- gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, this.resolveFramebuffer);
115
- gl.blitFramebuffer(0, 0, this.width, this.height, 0, 0, this.width, this.height, gl.COLOR_BUFFER_BIT, gl.NEAREST);
129
+ this.resolve(this.drawFramebuffer, this.resolveFramebuffer);
130
+ this.resolve(this.albedoFramebuffer, this.albedoResolveFramebuffer);
116
131
  }
117
132
  gl.disable(gl.DEPTH_TEST);
118
133
  gl.disable(gl.CULL_FACE);
@@ -123,6 +138,9 @@ export class GlSkinScatter {
123
138
  const u = this.uniforms;
124
139
  gl.uniform1i(u['uSkin'] ?? null, SKIN_BLUR_TEXTURE_UNIT);
125
140
  gl.uniform1i(u['uDepth'] ?? null, SKIN_BLUR_DEPTH_UNIT);
141
+ gl.uniform1i(u['uAlbedo'] ?? null, SKIN_BLUR_ALBEDO_UNIT);
142
+ gl.activeTexture(gl.TEXTURE0 + SKIN_BLUR_ALBEDO_UNIT);
143
+ gl.bindTexture(gl.TEXTURE_2D, this.albedo ?? empty);
126
144
  /* The four terms that carry a depth back to view-space metres: the occlusion blur's own. */
127
145
  this.depthToViewZ[0] = inverse[10] ?? 0;
128
146
  this.depthToViewZ[1] = inverse[14] ?? 0;
@@ -141,12 +159,15 @@ export class GlSkinScatter {
141
159
  this.step[0] = 1 / this.width;
142
160
  this.step[1] = 0;
143
161
  gl.uniform2fv(u['uStep'] ?? null, this.step);
162
+ /* The light spreads on both axes; the colour is applied once, on the one adding into the frame. */
163
+ gl.uniform1f(u['uApplyAlbedo'] ?? null, 0);
144
164
  gl.drawArrays(gl.TRIANGLES, 0, 3);
145
165
  scene.bind();
146
166
  gl.bindTexture(gl.TEXTURE_2D, this.across);
147
167
  this.step[0] = 0;
148
168
  this.step[1] = 1 / this.height;
149
169
  gl.uniform2fv(u['uStep'] ?? null, this.step);
170
+ gl.uniform1f(u['uApplyAlbedo'] ?? null, 1);
150
171
  /* Added: its colour onto the frame's, the frame's alpha kept. */
151
172
  gl.enable(gl.BLEND);
152
173
  gl.blendEquation(gl.FUNC_ADD);
@@ -162,8 +183,16 @@ export class GlSkinScatter {
162
183
  /** Spent at the frame's start: what was drawn into the target was last frame's. */
163
184
  reset() {
164
185
  this.cleared = false;
186
+ this.albedoCleared = false;
165
187
  this.drawn = false;
166
188
  }
189
+ /** A multisampled target into its single-sampled texture. */
190
+ resolve(from, to) {
191
+ const { gl } = this;
192
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, from);
193
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, to);
194
+ gl.blitFramebuffer(0, 0, this.width, this.height, 0, 0, this.width, this.height, gl.COLOR_BUFFER_BIT, gl.NEAREST);
195
+ }
167
196
  dispose() {
168
197
  const { gl } = this;
169
198
  this.release();
@@ -181,36 +210,56 @@ export class GlSkinScatter {
181
210
  this.sharedDepth = depthRenderbuffer ?? depthTexture;
182
211
  this.diffuse = this.halfFloatTexture(width, height);
183
212
  this.across = this.halfFloatTexture(width, height);
184
- this.drawFramebuffer = gl.createFramebuffer();
185
- gl.bindFramebuffer(gl.FRAMEBUFFER, this.drawFramebuffer);
186
- if (samples > 1 && depthRenderbuffer !== null) {
187
- this.multisampled = gl.createRenderbuffer();
188
- gl.bindRenderbuffer(gl.RENDERBUFFER, this.multisampled);
189
- gl.renderbufferStorageMultisample(gl.RENDERBUFFER, samples, gl.RGBA16F, width, height);
190
- gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, this.multisampled);
191
- gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthRenderbuffer);
192
- }
193
- else {
194
- gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.diffuse, 0);
195
- gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, depthTexture, 0);
196
- }
197
- const drawReady = gl.checkFramebufferStatus(gl.FRAMEBUFFER) === gl.FRAMEBUFFER_COMPLETE;
198
- if (this.multisampled !== null) {
199
- this.resolveFramebuffer = gl.createFramebuffer();
200
- gl.bindFramebuffer(gl.FRAMEBUFFER, this.resolveFramebuffer);
201
- gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.diffuse, 0);
202
- }
213
+ this.albedo = this.halfFloatTexture(width, height);
214
+ const multisampled = samples > 1 && depthRenderbuffer !== null;
215
+ const diffuse = this.drawTarget(this.diffuse, multisampled, depthTexture, depthRenderbuffer);
216
+ this.drawFramebuffer = diffuse.framebuffer;
217
+ this.multisampled = diffuse.multisampled;
218
+ this.resolveFramebuffer = diffuse.resolve;
219
+ const albedo = this.drawTarget(this.albedo, multisampled, depthTexture, depthRenderbuffer);
220
+ this.albedoFramebuffer = albedo.framebuffer;
221
+ this.albedoMultisampled = albedo.multisampled;
222
+ this.albedoResolveFramebuffer = albedo.resolve;
203
223
  this.acrossFramebuffer = gl.createFramebuffer();
204
224
  gl.bindFramebuffer(gl.FRAMEBUFFER, this.acrossFramebuffer);
205
225
  gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.across, 0);
206
226
  const acrossReady = gl.checkFramebufferStatus(gl.FRAMEBUFFER) === gl.FRAMEBUFFER_COMPLETE;
207
227
  gl.bindRenderbuffer(gl.RENDERBUFFER, null);
208
- if (drawReady && acrossReady)
228
+ if (diffuse.ready && albedo.ready && acrossReady)
209
229
  return true;
210
230
  this.release();
211
231
  this.refuse();
212
232
  return false;
213
233
  }
234
+ /**
235
+ * A framebuffer a skin half draws into against the frame's depth: the texture itself, or a
236
+ * multisampled renderbuffer and the framebuffer that resolves it into the texture.
237
+ */
238
+ drawTarget(texture, multisampled, depthTexture, depthRenderbuffer) {
239
+ const { gl } = this;
240
+ const framebuffer = gl.createFramebuffer();
241
+ gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
242
+ let samples = null;
243
+ if (multisampled && depthRenderbuffer !== null) {
244
+ samples = gl.createRenderbuffer();
245
+ gl.bindRenderbuffer(gl.RENDERBUFFER, samples);
246
+ gl.renderbufferStorageMultisample(gl.RENDERBUFFER, this.samples, gl.RGBA16F, this.width, this.height);
247
+ gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, samples);
248
+ gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthRenderbuffer);
249
+ }
250
+ else {
251
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
252
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, depthTexture, 0);
253
+ }
254
+ const ready = gl.checkFramebufferStatus(gl.FRAMEBUFFER) === gl.FRAMEBUFFER_COMPLETE;
255
+ let resolve = null;
256
+ if (samples !== null) {
257
+ resolve = gl.createFramebuffer();
258
+ gl.bindFramebuffer(gl.FRAMEBUFFER, resolve);
259
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
260
+ }
261
+ return { framebuffer, multisampled: samples, resolve, ready };
262
+ }
214
263
  refuse() {
215
264
  if (this.unavailable)
216
265
  return;
@@ -237,20 +286,33 @@ export class GlSkinScatter {
237
286
  gl.deleteFramebuffer(this.drawFramebuffer);
238
287
  if (this.resolveFramebuffer !== null)
239
288
  gl.deleteFramebuffer(this.resolveFramebuffer);
289
+ if (this.albedoFramebuffer !== null)
290
+ gl.deleteFramebuffer(this.albedoFramebuffer);
291
+ if (this.albedoResolveFramebuffer !== null) {
292
+ gl.deleteFramebuffer(this.albedoResolveFramebuffer);
293
+ }
240
294
  if (this.acrossFramebuffer !== null)
241
295
  gl.deleteFramebuffer(this.acrossFramebuffer);
242
296
  if (this.multisampled !== null)
243
297
  gl.deleteRenderbuffer(this.multisampled);
298
+ if (this.albedoMultisampled !== null)
299
+ gl.deleteRenderbuffer(this.albedoMultisampled);
244
300
  if (this.diffuse !== null)
245
301
  gl.deleteTexture(this.diffuse);
246
302
  if (this.across !== null)
247
303
  gl.deleteTexture(this.across);
304
+ if (this.albedo !== null)
305
+ gl.deleteTexture(this.albedo);
248
306
  this.drawFramebuffer = null;
249
307
  this.resolveFramebuffer = null;
308
+ this.albedoFramebuffer = null;
309
+ this.albedoResolveFramebuffer = null;
250
310
  this.acrossFramebuffer = null;
251
311
  this.multisampled = null;
312
+ this.albedoMultisampled = null;
252
313
  this.diffuse = null;
253
314
  this.across = null;
315
+ this.albedo = null;
254
316
  this.sharedDepth = null;
255
317
  }
256
318
  }
@@ -501,7 +501,8 @@ const INSTANCE_ATTRIBUTES = [
501
501
  { shaderLocation: 12, offset: 16, format: 'float32x4' },
502
502
  { shaderLocation: 13, offset: 32, format: 'float32x4' },
503
503
  { shaderLocation: 14, offset: 48, format: 'float32x4' },
504
- { shaderLocation: 15, offset: 64, format: 'float32x3' },
504
+ /* The tint and, in w, the opacity. See `INSTANCE_FLOATS`. */
505
+ { shaderLocation: 15, offset: 64, format: 'float32x4' },
505
506
  ];
506
507
  /** The locations above, as the set the base layout must stand clear of on an instanced draw. */
507
508
  const INSTANCE_LOCATIONS = new Set(INSTANCE_ATTRIBUTES.map((attribute) => attribute.shaderLocation));
@@ -1,4 +1,4 @@
1
- import { CLUSTER_COUNT, LIGHT_TEXELS, MAX_CLUSTERED_LIGHTS, TABLE_HEIGHT, TABLE_WIDTH, writeLightRecord, } from '../../clusteredLights.js';
1
+ import { CLUSTER_COUNT, LIGHT_TEXELS, MAX_CLUSTERED_LIGHTS, TABLE_HEIGHT, TABLE_WIDTH, clusteredLightTotal, writeLightRecord, } from '../../clusteredLights.js';
2
2
  import { MAX_POINT_LIGHTS } from '../../lightBudget.js';
3
3
  import { CLUSTER_BIN_WGSL } from './shaders/clusterBin.wgsl.js';
4
4
  /**
@@ -129,7 +129,8 @@ export class ClusterBinner {
129
129
  return;
130
130
  if (params === null || lightBuffer === null)
131
131
  return;
132
- const count = Math.min(frame.lights.count, MAX_CLUSTERED_LIGHTS);
132
+ /* The lamps and the clustered rectangles after them. See `ClusterLightSet.areas`. */
133
+ const count = clusteredLightTotal(frame.lights);
133
134
  /*
134
135
  * The records, by the function `buildLightClusters` writes them with, because the shader
135
136
  * copies them into the table verbatim and the lit pass reads one layout on both backends.
@@ -1,5 +1,5 @@
1
1
  import type { SurfaceModelKind } from '../../surfaceModel.ts';
2
- import type { SkinHalf } from '../../shaders/flat/models.ts';
2
+ import { type SkinHalf } from '../../shaders/flat/models.ts';
3
3
  import type { PipelineCache } from './pipelineCache.ts';
4
4
  import type { ClothViews } from './clothTextures.ts';
5
5
  /**
@@ -69,12 +69,14 @@ export declare function flatVertexKey(skinned: boolean, morphed: boolean, instan
69
69
  /** Everything the generator recorded about one variant of the vertex stage. */
70
70
  export declare function flatVertexBindings(skinned: boolean, morphed?: boolean, instanced?: boolean): StageBindings;
71
71
  /**
72
- * A vertex stage's constants, keyed by each override's **id** as the fragment's are: `SKIN_EIGHT` in
73
- * a skinned stage, nothing in any other. Every override a stage declares is set, and one this does
72
+ * A vertex stage's constants, keyed by each override's **id** as the fragment's are: `SKIN_EIGHT` and
73
+ * `CLOTH_BOUND` in a skinned stage, `LIGHTMAP_REGIONS` in an instanced one, nothing in any other. Every override a stage declares is set, and one this does
74
74
  * not know is refused here rather than defaulted. Shared with the shadow and glass-tint casters,
75
75
  * whose skinned stages declare the same switch.
76
76
  */
77
- export declare function vertexConstants(bindings: StageBindings, skinEight: boolean, cloth?: boolean): Record<string, number>;
77
+ export declare function vertexConstants(bindings: StageBindings, skinEight: boolean, cloth?: boolean,
78
+ /** Whether an instanced stage reads its tint lane as a lightmap region. See `lightmap.ts`. */
79
+ lightmapRegions?: boolean): Record<string, number>;
78
80
  export declare const FLAT_VERT_FIELDS: {
79
81
  readonly uViewProj: {
80
82
  readonly offset: 0;
@@ -233,7 +235,7 @@ skinEight?: boolean,
233
235
  cloth?: boolean,
234
236
  /** Which shading model its fragment stage shades by, null the standard. The key must carry it. */
235
237
  model?: SurfaceModelKind | null,
236
- /** Which half of a skin it draws. The key must carry it: `|ss` or `|sd`. See `SkinHalf`. */
238
+ /** Which half of a skin it draws. The key must carry it: `|ss`, `|sd` or `|sa`. See `SkinHalf`. */
237
239
  skinPass?: SkinHalf): GPURenderPipeline;
238
240
  /**
239
241
  * The same pipeline, compiled off the main thread.
@@ -1,4 +1,5 @@
1
1
  import { SURFACE_MODEL_SWITCH } from '../../surfaceModel.js';
2
+ import { SKIN_HALF_SWITCH } from '../../shaders/flat/models.js';
2
3
  import { DEPTH_COMPARE, DEPTH_FORMAT, depthOffsetForLayer } from '../../depthConvention.js';
3
4
  import { FLAT_BINDINGS, FLAT_FRAG_WGSL, FLAT_VERT_WGSL, } from '../../shaders/generated/flat.wgsl.js';
4
5
  import { vertexBufferLayouts } from './buffers.js';
@@ -71,18 +72,22 @@ export function flatVertexBindings(skinned, morphed = false, instanced = false)
71
72
  }
72
73
  const PLAIN_VERTEX = flatVertexBindings(false);
73
74
  /**
74
- * A vertex stage's constants, keyed by each override's **id** as the fragment's are: `SKIN_EIGHT` in
75
- * a skinned stage, nothing in any other. Every override a stage declares is set, and one this does
75
+ * A vertex stage's constants, keyed by each override's **id** as the fragment's are: `SKIN_EIGHT` and
76
+ * `CLOTH_BOUND` in a skinned stage, `LIGHTMAP_REGIONS` in an instanced one, nothing in any other. Every override a stage declares is set, and one this does
76
77
  * not know is refused here rather than defaulted. Shared with the shadow and glass-tint casters,
77
78
  * whose skinned stages declare the same switch.
78
79
  */
79
- export function vertexConstants(bindings, skinEight, cloth = false) {
80
+ export function vertexConstants(bindings, skinEight, cloth = false,
81
+ /** Whether an instanced stage reads its tint lane as a lightmap region. See `lightmap.ts`. */
82
+ lightmapRegions = false) {
80
83
  const constants = {};
81
84
  for (const [name, id] of Object.entries(bindings.overrides ?? {})) {
82
85
  if (name === 'SKIN_EIGHT')
83
86
  constants[String(id)] = skinEight ? 1 : 0;
84
87
  else if (name === 'CLOTH_BOUND')
85
88
  constants[String(id)] = cloth ? 1 : 0;
89
+ else if (name === 'LIGHTMAP_REGIONS')
90
+ constants[String(id)] = lightmapRegions ? 1 : 0;
86
91
  else
87
92
  throw new Error(`flatPass: a vertex stage declares a switch "${name}" nothing sets`);
88
93
  }
@@ -168,13 +173,9 @@ function flatFragmentConstants(variant, cache, model, skinPass = 'whole') {
168
173
  constants[String(id)] = name === modelSwitch ? 1 : 0;
169
174
  continue;
170
175
  }
171
- /* Skin's two halves under the screen-space blur, set from the key as a model's switch is. */
172
- if (name === 'SKIN_SCREEN' || name === 'SKIN_DIFFUSE') {
173
- constants[String(id)] =
174
- (name === 'SKIN_SCREEN' && skinPass === 'scene') ||
175
- (name === 'SKIN_DIFFUSE' && skinPass === 'diffuse')
176
- ? 1
177
- : 0;
176
+ /* Skin's halves under the screen-space blur, set from the key as a model's switch is. */
177
+ if (name === 'SKIN_SCREEN' || name === 'SKIN_DIFFUSE' || name === 'SKIN_ALBEDO') {
178
+ constants[String(id)] = skinPass !== 'whole' && name === SKIN_HALF_SWITCH[skinPass] ? 1 : 0;
178
179
  continue;
179
180
  }
180
181
  if (!(name in cache.litSwitches)) {
@@ -479,7 +480,7 @@ skinEight = false,
479
480
  cloth = false,
480
481
  /** Which shading model its fragment stage shades by, null the standard. The key must carry it. */
481
482
  model = null,
482
- /** Which half of a skin it draws. The key must carry it: `|ss` or `|sd`. See `SkinHalf`. */
483
+ /** Which half of a skin it draws. The key must carry it: `|ss`, `|sd` or `|sa`. See `SkinHalf`. */
483
484
  skinPass = 'whole') {
484
485
  return cache.get(key, () => flatDescriptor(cache, device, layout, variant, key, present, translucent, skinned, morphed, depthWrite, depthLayer, instanced, oit, doubleSided, alphaToCoverage, skinEight, cloth, model, skinPass), true);
485
486
  }
@@ -563,7 +564,8 @@ doubleSided = false, alphaToCoverage = false, skinEight = false,
563
564
  /** Whether its vertex stage places vertices by a cloth binding. See `clothBinding.ts`. */
564
565
  cloth = false, model = null, skinPass = 'whole') {
565
566
  const offset = depthOffsetForLayer(depthLayer);
566
- const diffuseAlone = skinPass === 'diffuse';
567
+ /* The diffuse and colour halves are the same surface again, into the blur's targets. */
568
+ const diffuseAlone = skinPass === 'diffuse' || skinPass === 'albedo';
567
569
  return {
568
570
  label: key,
569
571
  layout: device.createPipelineLayout({ bindGroupLayouts: [layout] }),
@@ -579,7 +581,7 @@ cloth = false, model = null, skinPass = 'whole') {
579
581
  }),
580
582
  entryPoint: 'main',
581
583
  buffers: vertexBufferLayouts(present, instanced),
582
- constants: vertexConstants(flatVertexBindings(skinned, morphed, instanced), skinEight, cloth),
584
+ constants: vertexConstants(flatVertexBindings(skinned, morphed, instanced), skinEight, cloth, model === 'lightmap'),
583
585
  },
584
586
  fragment: {
585
587
  module: shaderModule(device, {
@@ -1,6 +1,7 @@
1
1
  import { type ParticleMaterial } from '../../particleMaterial.ts';
2
2
  import type { ParticleInstances } from '../../particlePool.ts';
3
- import type { ParticleBlend, ParticleFacing } from '../../particleBatch.ts';
3
+ import { type ParticleBlend, type ParticleFacing } from '../../particleBatch.ts';
4
+ import { type ParticleSort } from '../../particleSort.ts';
4
5
  import type { PipelineCache } from './pipelineCache.ts';
5
6
  import type { UniformFields } from './scatterPass.ts';
6
7
  /**
@@ -33,8 +34,22 @@ export interface GpuParticles {
33
34
  readonly coreGain: number;
34
35
  /** Whether the mote fragment stage mixes toward the medium. Ignored by the other materials. */
35
36
  readonly fog: boolean;
37
+ /** A sprite's image view, or null for a procedural material. See `ParticleBatchOptions`. */
38
+ readonly texture: GPUTextureView | null;
39
+ readonly cells: readonly [number, number];
40
+ readonly blendCells: boolean;
41
+ readonly softDepth: number;
42
+ readonly cameraFade: number;
43
+ readonly cameraOffset: number;
44
+ /** Room to draw farthest first, where the batch asked. See `particleSort.ts`. */
45
+ readonly order: ParticleSort | null;
46
+ /** A sprite's bind group, which holds the frame's depth copy and is rebuilt when that moves. */
47
+ spriteGroup: GPUBindGroup | null;
48
+ spriteGroupDepth: GPUTextureView | null;
36
49
  /** Reused every frame; see `packInstances`. */
37
50
  readonly staging: Float32Array;
51
+ /** The sprite stream, two floats a particle, before it is woven into `staging`. */
52
+ readonly spriteStaging: Float32Array;
38
53
  /**
39
54
  * This batch's own uniform buffers, staging and bind group — never shared with another
40
55
  * batch of the same material. See the note on `createGpuParticles` for why: two pools of one
@@ -49,7 +64,8 @@ export interface GpuParticles {
49
64
  readonly fragFloats: Float32Array;
50
65
  readonly fragInts: Int32Array;
51
66
  readonly fields: UniformFields;
52
- readonly bindGroup: GPUBindGroup;
67
+ /** The procedural materials' group; a sprite's holds the depth copy, so it is `spriteGroup`. */
68
+ readonly bindGroup: GPUBindGroup | null;
53
69
  /**
54
70
  * The encoder this batch was last drawn into, by the renderer's count, or -1.
55
71
  *
@@ -77,7 +93,7 @@ export interface GpuParticles {
77
93
  * written last. `layout` still comes from the caller and is shared per material: it is
78
94
  * structural, not data, so nothing about sharing it can go stale mid-frame.
79
95
  */
80
- export declare function createGpuParticles(device: GPUDevice, capacity: number, material: ParticleMaterial, blend: ParticleBlend, stretchSec: number, facing: ParticleFacing, erosion: number, coreGain: number, fog: boolean, layout: GPUBindGroupLayout, fragFields: UniformFields, fragSize: number): GpuParticles;
96
+ export declare function createGpuParticles(device: GPUDevice, capacity: number, material: ParticleMaterial, blend: ParticleBlend, stretchSec: number, facing: ParticleFacing, erosion: number, coreGain: number, fog: boolean, layout: GPUBindGroupLayout, fragFields: UniformFields, fragSize: number, sprite?: SpriteOptions | null): GpuParticles;
81
97
  /**
82
98
  * Interleave the live prefix of eight streams into the batch's own staging array.
83
99
  *
@@ -85,7 +101,24 @@ export declare function createGpuParticles(device: GPUDevice, capacity: number,
85
101
  * into an array allocated with the batch — the frame loop may not allocate.
86
102
  */
87
103
  export declare function packInstances(batch: GpuParticles, data: ParticleInstances): number;
88
- export declare function createParticleBindGroupLayout(device: GPUDevice, fragSize: number): GPUBindGroupLayout;
104
+ /** The floats `packInstances` writes per particle, for the caller's upload. */
105
+ export declare const PARTICLE_INSTANCE_FLOATS = 16;
106
+ /** What a `'sprite'` batch carries beyond a procedural one. See `ParticleBatchOptions`. */
107
+ export interface SpriteOptions {
108
+ readonly texture: GPUTextureView | null;
109
+ readonly cells: readonly [number, number];
110
+ readonly blendCells: boolean;
111
+ readonly softDepth: number;
112
+ readonly cameraFade: number;
113
+ readonly cameraOffset: number;
114
+ readonly sort: boolean;
115
+ }
116
+ export declare function createParticleBindGroupLayout(device: GPUDevice, fragSize: number, sprite?: boolean): GPUBindGroupLayout;
117
+ /**
118
+ * A sprite batch's bind group: its uniforms, its image, and the frame's depth copy — or any
119
+ * single-sample float texture standing in, where the batch asks for no soft edge.
120
+ */
121
+ export declare function createSpriteBindGroup(device: GPUDevice, layout: GPUBindGroupLayout, batch: GpuParticles, fragSize: number, depth: GPUTextureView, linear: GPUSampler, nearest: GPUSampler): GPUBindGroup;
89
122
  export declare function createParticleBindGroup(device: GPUDevice, layout: GPUBindGroupLayout, perDraw: GPUBuffer, perFrame: GPUBuffer, fragSize: number): GPUBindGroup;
90
123
  /**
91
124
  * The pipeline for one material and blend.
@@ -2,6 +2,8 @@ import { DEPTH_COMPARE } from '../../depthConvention.js';
2
2
  import { particleShaders } from '../../particleMaterial.js';
3
3
  import { PARTICLE_BINDINGS } from '../../shaders/generated/particle.wgsl.js';
4
4
  import { PARTICLE_BLADES, PARTICLE_INDICES, PARTICLE_VERTS } from '../../shaders/particle.js';
5
+ import { packSpriteStream } from '../../particleBatch.js';
6
+ import { createParticleSort } from '../../particleSort.js';
5
7
  import { DEPTH_FORMAT } from './flatPass.js';
6
8
  import { shaderModule } from './shaderModules.js';
7
9
  import { SCENE_ALPHA_COVERS } from '../../sceneCoverage.js';
@@ -22,7 +24,9 @@ export function particleFragBindings(material) {
22
24
  ? PARTICLE_BINDINGS.PARTICLE_SPARK_FRAG
23
25
  : material === 'mote'
24
26
  ? PARTICLE_BINDINGS.PARTICLE_MOTE_FRAG
25
- : PARTICLE_BINDINGS.PARTICLE_SMOKE_FRAG;
27
+ : material === 'sprite'
28
+ ? PARTICLE_BINDINGS.PARTICLE_SPRITE_FRAG
29
+ : PARTICLE_BINDINGS.PARTICLE_SMOKE_FRAG;
26
30
  return { fields: stage.fields, size: stage.uniformSize };
27
31
  }
28
32
  const VISIBILITY_VERTEX = 0x1;
@@ -31,17 +35,17 @@ const USAGE_VERTEX = 0x0020 | 0x0008;
31
35
  const USAGE_INDEX = 0x0010 | 0x0008;
32
36
  const USAGE_UNIFORM_DST = 0x0040 | 0x0008;
33
37
  /**
34
- * Ten attributes and eight slots, so the per-instance eight share one buffer.
38
+ * Eleven attributes and eight slots, so the per-instance nine share one buffer.
35
39
  *
36
- * **`maxVertexBuffers` is eight and this shader declares ten inputs**, so something has to
37
- * interleave. The per-instance streams are the ones that do: the engine holds them as eight
38
- * separate arrays and they are packed into one interleaved buffer per frame, which is a copy
39
- * the upload was already paying per stream rather than new work.
40
+ * **`maxVertexBuffers` is eight and this shader declares eleven inputs**, so something has to
41
+ * interleave. The per-instance streams are the ones that do: the engine holds them as separate
42
+ * arrays and they are packed into one interleaved buffer per frame, which is a copy the upload
43
+ * was already paying per stream rather than new work.
40
44
  *
41
45
  * Floats per instance, in the order they are packed: position 3, size 1, spin 1, colour 3,
42
- * alpha 1, age 1, seed 1, velocity 3.
46
+ * alpha 1, age 1, seed 1, velocity 3, and a sprite's frame and half-height 2.
43
47
  */
44
- const INSTANCE_FLOATS = 14;
48
+ const INSTANCE_FLOATS = 16;
45
49
  const INSTANCE_ATTRIBUTES = [
46
50
  { shaderLocation: 2, offset: 0, format: 'float32x3' },
47
51
  { shaderLocation: 3, offset: 12, format: 'float32' },
@@ -51,6 +55,7 @@ const INSTANCE_ATTRIBUTES = [
51
55
  { shaderLocation: 7, offset: 36, format: 'float32' },
52
56
  { shaderLocation: 8, offset: 40, format: 'float32' },
53
57
  { shaderLocation: 9, offset: 44, format: 'float32x3' },
58
+ { shaderLocation: 10, offset: 56, format: 'float32x2' },
54
59
  ];
55
60
  export function particleVertexLayouts() {
56
61
  return [
@@ -87,7 +92,7 @@ export function particleVertexLayouts() {
87
92
  * written last. `layout` still comes from the caller and is shared per material: it is
88
93
  * structural, not data, so nothing about sharing it can go stale mid-frame.
89
94
  */
90
- export function createGpuParticles(device, capacity, material, blend, stretchSec, facing, erosion, coreGain, fog, layout, fragFields, fragSize) {
95
+ export function createGpuParticles(device, capacity, material, blend, stretchSec, facing, erosion, coreGain, fog, layout, fragFields, fragSize, sprite = null) {
91
96
  /* The cross, built once: two blades of four corners, interleaved with the blade index. */
92
97
  const corners = new Float32Array(PARTICLE_VERTS * 3);
93
98
  const CORNERS = [
@@ -158,7 +163,17 @@ export function createGpuParticles(device, capacity, material, blend, stretchSec
158
163
  erosion,
159
164
  coreGain,
160
165
  fog,
166
+ texture: sprite?.texture ?? null,
167
+ cells: sprite?.cells ?? [1, 1],
168
+ blendCells: sprite?.blendCells ?? false,
169
+ softDepth: sprite?.softDepth ?? 0,
170
+ cameraFade: sprite?.cameraFade ?? 0,
171
+ cameraOffset: sprite?.cameraOffset ?? 0,
172
+ order: sprite?.sort === true ? createParticleSort(capacity) : null,
173
+ spriteGroup: null,
174
+ spriteGroupDepth: null,
161
175
  staging: new Float32Array(capacity * INSTANCE_FLOATS),
176
+ spriteStaging: new Float32Array(capacity * 2),
162
177
  vertUniforms,
163
178
  fragUniforms,
164
179
  vertStaging,
@@ -167,7 +182,9 @@ export function createGpuParticles(device, capacity, material, blend, stretchSec
167
182
  fragFloats: new Float32Array(fragStaging),
168
183
  fragInts: new Int32Array(fragStaging),
169
184
  fields: fragFields,
170
- bindGroup: createParticleBindGroup(device, layout, vertUniforms, fragUniforms, fragSize),
185
+ bindGroup: material === 'sprite'
186
+ ? null
187
+ : createParticleBindGroup(device, layout, vertUniforms, fragUniforms, fragSize),
171
188
  dispose() {
172
189
  cornerBuffer.destroy();
173
190
  instances.destroy();
@@ -186,6 +203,8 @@ export function createGpuParticles(device, capacity, material, blend, stretchSec
186
203
  export function packInstances(batch, data) {
187
204
  const n = Math.min(data.count, batch.capacity);
188
205
  const out = batch.staging;
206
+ /* The sprite's frame and half-height, two floats a particle, woven in below. */
207
+ packSpriteStream(data, n, batch.spriteStaging);
189
208
  for (let i = 0; i < n; i++) {
190
209
  const o = i * INSTANCE_FLOATS;
191
210
  const p = i * 3;
@@ -203,12 +222,17 @@ export function packInstances(batch, data) {
203
222
  out[o + 11] = data.velocities[p];
204
223
  out[o + 12] = data.velocities[p + 1];
205
224
  out[o + 13] = data.velocities[p + 2];
225
+ out[o + 14] = batch.spriteStaging[i * 2];
226
+ out[o + 15] = batch.spriteStaging[i * 2 + 1];
206
227
  }
207
228
  return n;
208
229
  }
209
- export function createParticleBindGroupLayout(device, fragSize) {
230
+ /** The floats `packInstances` writes per particle, for the caller's upload. */
231
+ export const PARTICLE_INSTANCE_FLOATS = INSTANCE_FLOATS;
232
+ export function createParticleBindGroupLayout(device, fragSize, sprite = false) {
233
+ const textures = PARTICLE_BINDINGS.PARTICLE_SPRITE_FRAG.textures;
210
234
  return device.createBindGroupLayout({
211
- label: 'particle.layout',
235
+ label: sprite ? 'particle.layout:sprite' : 'particle.layout',
212
236
  entries: [
213
237
  {
214
238
  binding: VERT_BINDING,
@@ -220,6 +244,50 @@ export function createParticleBindGroupLayout(device, fragSize) {
220
244
  visibility: VISIBILITY_FRAGMENT,
221
245
  buffer: { type: 'uniform', minBindingSize: fragSize },
222
246
  },
247
+ /* The image, filtered, and the frame's depth copy, read texel for texel. */
248
+ ...(sprite
249
+ ? [
250
+ {
251
+ binding: textures.uSprite.texture,
252
+ visibility: VISIBILITY_FRAGMENT,
253
+ texture: { sampleType: 'float', viewDimension: '2d-array' },
254
+ },
255
+ {
256
+ binding: textures.uSprite.sampler,
257
+ visibility: VISIBILITY_FRAGMENT,
258
+ sampler: { type: 'filtering' },
259
+ },
260
+ {
261
+ binding: textures.uDepth.texture,
262
+ visibility: VISIBILITY_FRAGMENT,
263
+ texture: { sampleType: 'unfilterable-float' },
264
+ },
265
+ {
266
+ binding: textures.uDepth.sampler,
267
+ visibility: VISIBILITY_FRAGMENT,
268
+ sampler: { type: 'non-filtering' },
269
+ },
270
+ ]
271
+ : []),
272
+ ],
273
+ });
274
+ }
275
+ /**
276
+ * A sprite batch's bind group: its uniforms, its image, and the frame's depth copy — or any
277
+ * single-sample float texture standing in, where the batch asks for no soft edge.
278
+ */
279
+ export function createSpriteBindGroup(device, layout, batch, fragSize, depth, linear, nearest) {
280
+ const textures = PARTICLE_BINDINGS.PARTICLE_SPRITE_FRAG.textures;
281
+ return device.createBindGroup({
282
+ label: 'particle.bindGroup:sprite',
283
+ layout,
284
+ entries: [
285
+ { binding: VERT_BINDING, resource: { buffer: batch.vertUniforms, size: PARTICLE_VERT_SIZE } },
286
+ { binding: FRAG_BINDING, resource: { buffer: batch.fragUniforms, size: fragSize } },
287
+ { binding: textures.uSprite.texture, resource: batch.texture },
288
+ { binding: textures.uSprite.sampler, resource: linear },
289
+ { binding: textures.uDepth.texture, resource: depth },
290
+ { binding: textures.uDepth.sampler, resource: nearest },
223
291
  ],
224
292
  });
225
293
  }
@@ -180,7 +180,12 @@ export declare const PREMULTIPLIED_OVER_BLEND: GPUBlendState;
180
180
  * the reflection's own and its alpha is the coverage this resolve reads.
181
181
  */
182
182
  export declare const REFLECTION_OVER_BLEND: GPUBlendState;
183
- export declare const ADDITIVE_BLEND: GPUBlendState;
183
+ /**
184
+ * The bloom pyramid's way back up: an octave added onto a level that the blend multiplies by that
185
+ * level's tint first — the blend constant, which white makes `one, one` exactly. Into the pyramid's
186
+ * own levels, never the scene. See `BloomResponse`.
187
+ */
188
+ export declare const TINTED_ADD_BLEND: GPUBlendState;
184
189
  /** A fullscreen pass over one or more inputs, writing to one colour target. */
185
190
  export declare function postPipeline(cache: PipelineCache, device: GPUDevice, layout: GPUBindGroupLayout, key: string, fragment: string, format: GPUTextureFormat, blend?: GPUBlendState): GPURenderPipeline;
186
191
  /**
@@ -252,8 +252,13 @@ export const REFLECTION_OVER_BLEND = {
252
252
  color: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
253
253
  alpha: SCENE_ALPHA_KEEPS,
254
254
  };
255
- export const ADDITIVE_BLEND = {
256
- color: { srcFactor: 'one', dstFactor: 'one', operation: 'add' },
255
+ /**
256
+ * The bloom pyramid's way back up: an octave added onto a level that the blend multiplies by that
257
+ * level's tint first — the blend constant, which white makes `one, one` exactly. Into the pyramid's
258
+ * own levels, never the scene. See `BloomResponse`.
259
+ */
260
+ export const TINTED_ADD_BLEND = {
261
+ color: { srcFactor: 'one', dstFactor: 'constant', operation: 'add' },
257
262
  alpha: { srcFactor: 'one', dstFactor: 'one', operation: 'add' },
258
263
  };
259
264
  /** A fullscreen pass over one or more inputs, writing to one colour target. */