@driftengine/core 4.5.0 → 4.6.1

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 (209) hide show
  1. package/CHANGELOG.json +62 -0
  2. package/README.md +1 -1
  3. package/dist/geometry/boxCsg.d.ts +28 -0
  4. package/dist/geometry/boxCsg.js +139 -0
  5. package/dist/geometry/csg.d.ts +36 -0
  6. package/dist/geometry/csg.js +329 -0
  7. package/dist/geometry/meshBuilder.js +13 -0
  8. package/dist/geometry/meshConcat.js +1 -0
  9. package/dist/geometry/pixelFont.js +2 -0
  10. package/dist/geometry/solid.d.ts +61 -0
  11. package/dist/geometry/solid.js +152 -0
  12. package/dist/geometry/solidFlat.d.ts +35 -0
  13. package/dist/geometry/solidFlat.js +428 -0
  14. package/dist/geometry/solidHarness.d.ts +19 -0
  15. package/dist/geometry/solidHarness.js +66 -0
  16. package/dist/geometry/solidNormals.d.ts +17 -0
  17. package/dist/geometry/solidNormals.js +86 -0
  18. package/dist/geometry/solidRound.d.ts +41 -0
  19. package/dist/geometry/solidRound.js +323 -0
  20. package/dist/geometry/solidWriter.d.ts +16 -0
  21. package/dist/geometry/solidWriter.js +42 -0
  22. package/dist/index.d.ts +23 -2
  23. package/dist/index.js +13 -1
  24. package/dist/render/backend/api.d.ts +17 -3
  25. package/dist/render/backend/drawFog.d.ts +26 -0
  26. package/dist/render/backend/drawFog.js +30 -0
  27. package/dist/render/backend/materialChanges.d.ts +2 -1
  28. package/dist/render/backend/materialChanges.js +6 -1
  29. package/dist/render/backend/webgl2/instanced.d.ts +24 -5
  30. package/dist/render/backend/webgl2/instanced.js +72 -15
  31. package/dist/render/backend/webgl2/renderer.d.ts +122 -8
  32. package/dist/render/backend/webgl2/renderer.js +249 -128
  33. package/dist/render/backend/webgpu/buffers.d.ts +17 -0
  34. package/dist/render/backend/webgpu/buffers.js +34 -3
  35. package/dist/render/backend/webgpu/depthPass.js +5 -1
  36. package/dist/render/backend/webgpu/drawCommand.d.ts +5 -0
  37. package/dist/render/backend/webgpu/drawCommand.js +1 -0
  38. package/dist/render/backend/webgpu/flatPass.d.ts +4 -1
  39. package/dist/render/backend/webgpu/flatPass.js +38 -9
  40. package/dist/render/backend/webgpu/glassTintPass.js +2 -1
  41. package/dist/render/backend/webgpu/instanceCullPass.d.ts +34 -0
  42. package/dist/render/backend/webgpu/instanceCullPass.js +144 -0
  43. package/dist/render/backend/webgpu/instanced.d.ts +11 -1
  44. package/dist/render/backend/webgpu/instanced.js +21 -3
  45. package/dist/render/backend/webgpu/latePass.d.ts +6 -2
  46. package/dist/render/backend/webgpu/latePass.js +11 -4
  47. package/dist/render/backend/webgpu/motionPass.js +2 -1
  48. package/dist/render/backend/webgpu/postPass.d.ts +1 -1
  49. package/dist/render/backend/webgpu/renderer.d.ts +100 -10
  50. package/dist/render/backend/webgpu/renderer.js +478 -214
  51. package/dist/render/backend/webgpu/sdfTextPass.js +6 -1
  52. package/dist/render/backend/webgpu/shaders/instanceCull.wgsl.d.ts +18 -0
  53. package/dist/render/backend/webgpu/shaders/instanceCull.wgsl.js +59 -0
  54. package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +17 -3
  55. package/dist/render/backend/webgpu/surfaceTexturePass.js +77 -38
  56. package/dist/render/driftLight/bake.d.ts +7 -0
  57. package/dist/render/driftLight/bake.js +61 -56
  58. package/dist/render/driftLight/denseField.d.ts +46 -0
  59. package/dist/render/driftLight/denseField.js +109 -0
  60. package/dist/render/driftLight/lightField.d.ts +10 -5
  61. package/dist/render/driftLight/lightField.js +25 -19
  62. package/dist/render/driftLight/presence.d.ts +50 -0
  63. package/dist/render/driftLight/presence.js +43 -0
  64. package/dist/render/driftLight/uniforms.d.ts +3 -3
  65. package/dist/render/driftLight/uniforms.js +5 -6
  66. package/dist/render/driftLight/worldLightField.d.ts +41 -0
  67. package/dist/render/driftLight/worldLightField.js +66 -0
  68. package/dist/render/fixtureAtlas.d.ts +20 -0
  69. package/dist/render/fixtureAtlas.js +49 -0
  70. package/dist/render/instanceCull.d.ts +63 -0
  71. package/dist/render/instanceCull.js +160 -0
  72. package/dist/render/instances.d.ts +15 -0
  73. package/dist/render/lightBudget.d.ts +22 -33
  74. package/dist/render/lightBudget.js +24 -35
  75. package/dist/render/lightGrid.d.ts +42 -0
  76. package/dist/render/lightGrid.js +87 -0
  77. package/dist/render/mesh.d.ts +23 -24
  78. package/dist/render/mesh.js +56 -68
  79. package/dist/render/occlusion.d.ts +10 -0
  80. package/dist/render/occlusion.js +36 -7
  81. package/dist/render/pointLightSelection.d.ts +7 -0
  82. package/dist/render/pointLightSelection.js +29 -4
  83. package/dist/render/rainField.d.ts +13 -0
  84. package/dist/render/rainField.js +27 -5
  85. package/dist/render/recon/lateDepth.d.ts +11 -0
  86. package/dist/render/recon/lateDepth.js +70 -0
  87. package/dist/render/recon/resolve.d.ts +34 -4
  88. package/dist/render/recon/resolve.js +161 -20
  89. package/dist/render/rendererHarness.js +4 -0
  90. package/dist/render/sceneTarget.d.ts +2 -0
  91. package/dist/render/sceneTarget.js +2 -0
  92. package/dist/render/shaders/depth.js +12 -9
  93. package/dist/render/shaders/flat/driftLight.d.ts +1 -1
  94. package/dist/render/shaders/flat/driftLight.js +32 -7
  95. package/dist/render/shaders/flat/index.js +5 -3
  96. package/dist/render/shaders/flat/main.d.ts +1 -1
  97. package/dist/render/shaders/flat/main.js +75 -23
  98. package/dist/render/shaders/flat/preamble.js +75 -20
  99. package/dist/render/shaders/flat/surfaceEffects.d.ts +29 -0
  100. package/dist/render/shaders/flat/surfaceEffects.js +152 -0
  101. package/dist/render/shaders/generated/depth.wgsl.d.ts +8 -8
  102. package/dist/render/shaders/generated/depth.wgsl.js +8 -8
  103. package/dist/render/shaders/generated/flat.wgsl.d.ts +1320 -1320
  104. package/dist/render/shaders/generated/flat.wgsl.js +1359 -1345
  105. package/dist/render/shaders/generated/lightVolume.wgsl.js +18 -4
  106. package/dist/render/shaders/generated/rush.wgsl.d.ts +21 -16
  107. package/dist/render/shaders/generated/rush.wgsl.js +21 -16
  108. package/dist/render/shaders/generated/sdfText.wgsl.d.ts +2 -2
  109. package/dist/render/shaders/generated/sdfText.wgsl.js +2 -2
  110. package/dist/render/shaders/generated/text.wgsl.d.ts +1 -1
  111. package/dist/render/shaders/generated/text.wgsl.js +1 -1
  112. package/dist/render/shaders/gi/driftLight.wgsl.d.ts +1 -1
  113. package/dist/render/shaders/gi/driftLight.wgsl.js +27 -4
  114. package/dist/render/shaders/recon/lateDepth.wgsl.js +38 -13
  115. package/dist/render/shaders/recon/resolve.wgsl.d.ts +8 -7
  116. package/dist/render/shaders/recon/resolve.wgsl.js +161 -27
  117. package/dist/render/shaders/rush.js +11 -1
  118. package/dist/render/shaders/sdfText.d.ts +1 -1
  119. package/dist/render/shaders/sdfText.js +3 -2
  120. package/dist/render/shaders/text.d.ts +1 -1
  121. package/dist/render/shaders/text.js +18 -9
  122. package/dist/render/surfaceEffects.d.ts +83 -0
  123. package/dist/render/surfaceEffects.js +75 -0
  124. package/dist/render/surfaceScene.d.ts +8 -0
  125. package/dist/render/surfaceScene.js +8 -0
  126. package/dist/render/surfaceTexture.d.ts +18 -2
  127. package/dist/render/surfaceTexture.js +68 -15
  128. package/dist/render/temporalAa.d.ts +25 -6
  129. package/dist/render/temporalAa.js +45 -15
  130. package/dist/render/textureSource.d.ts +30 -0
  131. package/dist/render/textureSource.js +53 -0
  132. package/dist/render/vertexDefaults.js +2 -0
  133. package/dist/world/hlod.d.ts +104 -0
  134. package/dist/world/hlod.js +214 -0
  135. package/package.json +6 -6
  136. package/src/geometry/boxCsg.ts +186 -0
  137. package/src/geometry/csg.ts +384 -0
  138. package/src/geometry/meshBuilder.ts +15 -0
  139. package/src/geometry/meshConcat.ts +1 -0
  140. package/src/geometry/pixelFont.ts +2 -0
  141. package/src/geometry/solid.ts +189 -0
  142. package/src/geometry/solidFlat.ts +522 -0
  143. package/src/geometry/solidHarness.ts +83 -0
  144. package/src/geometry/solidNormals.ts +103 -0
  145. package/src/geometry/solidRound.ts +443 -0
  146. package/src/geometry/solidWriter.ts +58 -0
  147. package/src/index.ts +51 -1
  148. package/src/render/backend/api.ts +21 -2
  149. package/src/render/backend/drawFog.ts +31 -0
  150. package/src/render/backend/materialChanges.ts +9 -2
  151. package/src/render/backend/webgl2/instanced.ts +80 -14
  152. package/src/render/backend/webgl2/renderer.ts +311 -150
  153. package/src/render/backend/webgpu/buffers.ts +49 -3
  154. package/src/render/backend/webgpu/depthPass.ts +5 -1
  155. package/src/render/backend/webgpu/drawCommand.ts +6 -0
  156. package/src/render/backend/webgpu/flatPass.ts +41 -11
  157. package/src/render/backend/webgpu/glassTintPass.ts +2 -1
  158. package/src/render/backend/webgpu/instanceCullPass.ts +165 -0
  159. package/src/render/backend/webgpu/instanced.ts +21 -3
  160. package/src/render/backend/webgpu/latePass.ts +12 -3
  161. package/src/render/backend/webgpu/motionPass.ts +2 -1
  162. package/src/render/backend/webgpu/renderer.ts +556 -239
  163. package/src/render/backend/webgpu/sdfTextPass.ts +6 -1
  164. package/src/render/backend/webgpu/shaders/instanceCull.wgsl.ts +61 -0
  165. package/src/render/backend/webgpu/surfaceTexturePass.ts +90 -50
  166. package/src/render/driftLight/bake.ts +85 -62
  167. package/src/render/driftLight/denseField.ts +149 -0
  168. package/src/render/driftLight/lightField.ts +33 -18
  169. package/src/render/driftLight/presence.ts +69 -0
  170. package/src/render/driftLight/uniforms.ts +8 -9
  171. package/src/render/driftLight/worldLightField.ts +83 -0
  172. package/src/render/fixtureAtlas.ts +63 -0
  173. package/src/render/instanceCull.ts +205 -0
  174. package/src/render/instances.ts +16 -0
  175. package/src/render/lightBudget.ts +25 -36
  176. package/src/render/lightGrid.ts +130 -0
  177. package/src/render/mesh.ts +64 -72
  178. package/src/render/occlusion.ts +51 -7
  179. package/src/render/pointLightSelection.ts +72 -4
  180. package/src/render/rainField.ts +39 -5
  181. package/src/render/recon/lateDepth.ts +83 -0
  182. package/src/render/recon/resolve.ts +206 -20
  183. package/src/render/rendererHarness.ts +4 -0
  184. package/src/render/sceneTarget.ts +4 -0
  185. package/src/render/shaders/depth.ts +12 -9
  186. package/src/render/shaders/flat/driftLight.ts +32 -7
  187. package/src/render/shaders/flat/index.ts +5 -3
  188. package/src/render/shaders/flat/main.ts +75 -23
  189. package/src/render/shaders/flat/preamble.ts +75 -20
  190. package/src/render/shaders/flat/surfaceEffects.ts +152 -0
  191. package/src/render/shaders/generated/depth.wgsl.ts +8 -8
  192. package/src/render/shaders/generated/flat.wgsl.ts +1361 -1345
  193. package/src/render/shaders/generated/lightVolume.wgsl.ts +20 -4
  194. package/src/render/shaders/generated/rush.wgsl.ts +21 -16
  195. package/src/render/shaders/generated/sdfText.wgsl.ts +2 -2
  196. package/src/render/shaders/generated/text.wgsl.ts +1 -1
  197. package/src/render/shaders/gi/driftLight.wgsl.ts +27 -4
  198. package/src/render/shaders/recon/lateDepth.wgsl.ts +38 -13
  199. package/src/render/shaders/recon/resolve.wgsl.ts +161 -27
  200. package/src/render/shaders/rush.ts +11 -1
  201. package/src/render/shaders/sdfText.ts +3 -2
  202. package/src/render/shaders/text.ts +18 -9
  203. package/src/render/surfaceEffects.ts +132 -0
  204. package/src/render/surfaceScene.ts +16 -0
  205. package/src/render/surfaceTexture.ts +110 -19
  206. package/src/render/temporalAa.ts +78 -14
  207. package/src/render/textureSource.ts +72 -0
  208. package/src/render/vertexDefaults.ts +2 -0
  209. package/src/world/hlod.ts +290 -0
@@ -26,6 +26,16 @@ export interface VertexAttribute {
26
26
  */
27
27
  export declare const VERTEX_LAYOUT: readonly VertexAttribute[];
28
28
  /** Geometry on the device, with the buffers bound in layout order. */
29
+ /**
30
+ * The texture coordinates of a mesh that names texture-array layers: (u, v, layer), three floats.
31
+ *
32
+ * **Three only where layers exist.** Every location is spent, so the layer rides in this attribute,
33
+ * and widening it for every textured mesh would add four bytes to each of its vertices for nothing.
34
+ * A mesh without layers keeps two, and the shader's `vec3` input reads z as 0 — WebGPU fills a
35
+ * component the format does not carry with 0 (and w with 1) — which is layer 0. The pipeline key
36
+ * carries `:layers`, so the two vertex layouts never share a pipeline.
37
+ */
38
+ export declare const LAYERED_UVS: VertexAttribute;
29
39
  export interface GpuMesh {
30
40
  /**
31
41
  * The buffers a draw binds, in the order `vertexBufferLayouts` describes them.
@@ -58,6 +68,13 @@ export interface GpuMesh {
58
68
  readonly weights: number;
59
69
  } | null;
60
70
  readonly indexBuffer: GPUBuffer;
71
+ /**
72
+ * How many indices the mesh draws. **Zero is a mesh with nothing to draw, and no pass draws it**:
73
+ * a level can hold none of some kind of geometry, and a draw of zero indices does nothing but
74
+ * earn a warning from the device in every pass that issues it. Every place a mesh enters a pass
75
+ * checks this beside `complete` or its vertex buffers, and WebGL2 skips the same meshes, so the
76
+ * two backends count the same draws.
77
+ */
61
78
  readonly indexCount: number;
62
79
  /**
63
80
  * How big this mesh is, in its own space.
@@ -84,6 +84,27 @@ export const VERTEX_LAYOUT = [
84
84
  */
85
85
  { name: 'channel', shaderLocation: 13, format: 'float32x4', components: 4, optional: true },
86
86
  ];
87
+ /** Geometry on the device, with the buffers bound in layout order. */
88
+ /**
89
+ * The texture coordinates of a mesh that names texture-array layers: (u, v, layer), three floats.
90
+ *
91
+ * **Three only where layers exist.** Every location is spent, so the layer rides in this attribute,
92
+ * and widening it for every textured mesh would add four bytes to each of its vertices for nothing.
93
+ * A mesh without layers keeps two, and the shader's `vec3` input reads z as 0 — WebGPU fills a
94
+ * component the format does not carry with 0 (and w with 1) — which is layer 0. The pipeline key
95
+ * carries `:layers`, so the two vertex layouts never share a pipeline.
96
+ */
97
+ export const LAYERED_UVS = {
98
+ name: 'uvs',
99
+ shaderLocation: 5,
100
+ format: 'float32x3',
101
+ components: 3,
102
+ optional: true,
103
+ };
104
+ /** The attribute as this mesh lays it out: the UV field widens where the mesh names layers. */
105
+ function laidOut(attribute, layered) {
106
+ return layered && attribute.name === 'uvs' ? LAYERED_UVS : attribute;
107
+ }
87
108
  /**
88
109
  * Upload a mesh, all of it, now.
89
110
  *
@@ -130,7 +151,8 @@ export function createGpuMeshIncremental(device, data, dynamic = false) {
130
151
  validateMeshData(data);
131
152
  const bounds = boundsOfPositions(data.positions, createBounds());
132
153
  const vertexCount = data.positions.length / 3;
133
- const supplied = VERTEX_LAYOUT.filter((attribute) => !attribute.optional || data[attribute.name] !== undefined);
154
+ const layered = data.layers !== undefined;
155
+ const supplied = VERTEX_LAYOUT.filter((attribute) => !attribute.optional || data[attribute.name] !== undefined).map((attribute) => laidOut(attribute, layered));
134
156
  /* Interleaved: one stride holding every attribute the mesh actually supplied. */
135
157
  const stride = supplied.reduce((sum, attribute) => sum + attribute.components * 4, 0);
136
158
  const step = stride / 4;
@@ -271,7 +293,14 @@ function* uploadSteps(job) {
271
293
  let fieldOffset = 0;
272
294
  for (const attribute of supplied) {
273
295
  const source = data[attribute.name];
274
- interleaveField(interleaved, source, attribute.components, step, fieldOffset, from, to);
296
+ if (attribute === LAYERED_UVS) {
297
+ /* (u, v) from the coordinates and the layer after them, one field of three. */
298
+ interleaveField(interleaved, source, 2, step, fieldOffset, from, to);
299
+ interleaveField(interleaved, data.layers, 1, step, fieldOffset + 2, from, to);
300
+ }
301
+ else {
302
+ interleaveField(interleaved, source, attribute.components, step, fieldOffset, from, to);
303
+ }
275
304
  fieldOffset += attribute.components;
276
305
  }
277
306
  device.queue.writeBuffer(vertices, from * stride, interleaved, from * step, (to - from) * step);
@@ -449,7 +478,9 @@ instanced = false) {
449
478
  const interleaved = [];
450
479
  const constants = [];
451
480
  let offset = 0;
452
- VERTEX_LAYOUT.forEach((attribute, index) => {
481
+ const layered = present['layers'] === true;
482
+ VERTEX_LAYOUT.forEach((declared, index) => {
483
+ const attribute = laidOut(declared, layered);
453
484
  /*
454
485
  * **The instanced pipeline reclaims the two skinning locations, and it is allowed to because
455
486
  * it cannot skin.** Locations 11 and 12 are the joint indices and weights; the INSTANCED
@@ -119,7 +119,11 @@ cutout = false) {
119
119
  {
120
120
  binding: CUTOUT_MAP.texture,
121
121
  visibility: VISIBILITY_FRAGMENT,
122
- texture: { sampleType: 'float' },
122
+ /* A surface texture, and every surface texture is a `2d-array` view. */
123
+ texture: {
124
+ sampleType: 'float',
125
+ viewDimension: '2d-array',
126
+ },
123
127
  },
124
128
  {
125
129
  binding: CUTOUT_MAP.sampler,
@@ -24,6 +24,11 @@ export interface DrawCommand {
24
24
  indexed: boolean;
25
25
  count: number;
26
26
  instances: number;
27
+ /**
28
+ * A `drawIndexedIndirect` record to draw from instead of `count` and `instances`: an instance
29
+ * cull's survivors, counted on the device. Null for every other draw.
30
+ */
31
+ indirect: GPUBuffer | null;
27
32
  }
28
33
  export interface CommandPool {
29
34
  commands: DrawCommand[];
@@ -25,6 +25,7 @@ function emptyCommand() {
25
25
  indexed: false,
26
26
  count: 0,
27
27
  instances: 1,
28
+ indirect: null,
28
29
  };
29
30
  }
30
31
  export function createCommandPool(capacity) {
@@ -178,8 +178,11 @@ export declare function flatPipeline(cache: PipelineCache, device: GPUDevice, la
178
178
  * `depthMask` alone and only turns blending on. A translucent part of a loaded model is a
179
179
  * pane of glass in a solid object, not a particle, and it has to occlude what is behind it
180
180
  * for the rest of the model to sort correctly.
181
+ *
182
+ * `'additive'` adds instead: colour times alpha onto the target, its alpha kept. The key must
183
+ * carry it (`|add`).
181
184
  */
182
- translucent?: boolean,
185
+ translucent?: boolean | 'additive',
183
186
  /** Whether this pipeline reads a joint palette. See `FlatVertexOptions`. */
184
187
  skinned?: boolean,
185
188
  /** Whether this pipeline reads morph deltas. See `FlatVertexOptions`. */
@@ -2,6 +2,13 @@ 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
+ /** The material maps the flat pass reads: `2d-array` colour, filtered, not shadow maps. */
6
+ const SURFACE_MAPS = new Set([
7
+ 'uAlbedo',
8
+ 'uNormalMap',
9
+ 'uOrmMap',
10
+ 'uEmissiveMap',
11
+ ]);
5
12
  /**
6
13
  * The key for a set of flags.
7
14
  *
@@ -198,8 +205,19 @@ export function createFlatBindGroupLayout(device, variant, skinned = false, morp
198
205
  const volume = texture.type === 'sampler3D' || texture.type === 'usampler3D';
199
206
  /* Glass tints are colour, mipmapped and filtered: a frosted pane is read at a coarser level. */
200
207
  const tint = name.endsWith('GlassTints');
208
+ /*
209
+ * **The four material maps are arrays too, and they are colour.** Every surface texture is a
210
+ * `2d-array` view since texture arrays landed, so the rule below — an array is a shadow map —
211
+ * would declare albedo unfilterable and bind a non-filtering sampler to it, which reads as
212
+ * every textured surface going nearest-neighbour and losing its mip chain. Named, as
213
+ * `uEnvironment` is, because a pattern wide enough to catch them catches the shadow arrays.
214
+ */
215
+ const surface = SURFACE_MAPS.has(name);
201
216
  const isShadow = integer ||
202
- (!tint && name !== 'uEnvironment' && (name.endsWith('ShadowMap') || cube || array));
217
+ (!tint &&
218
+ !surface &&
219
+ name !== 'uEnvironment' &&
220
+ (name.endsWith('ShadowMap') || cube || array));
203
221
  entries.push({
204
222
  binding: texture.texture,
205
223
  visibility: VISIBILITY_FRAGMENT,
@@ -349,6 +367,9 @@ export function flatPipeline(cache, device, layout, variant, key, present,
349
367
  * `depthMask` alone and only turns blending on. A translucent part of a loaded model is a
350
368
  * pane of glass in a solid object, not a particle, and it has to occlude what is behind it
351
369
  * for the rest of the model to sort correctly.
370
+ *
371
+ * `'additive'` adds instead: colour times alpha onto the target, its alpha kept. The key must
372
+ * carry it (`|add`).
352
373
  */
353
374
  translucent = false,
354
375
  /** Whether this pipeline reads a joint palette. See `FlatVertexOptions`. */
@@ -470,19 +491,27 @@ doubleSided = false) {
470
491
  constants: flatFragmentConstants(variant, cache),
471
492
  targets: [
472
493
  oitTarget(oit) ??
473
- (translucent
494
+ (translucent === 'additive'
474
495
  ? {
475
496
  format: cache.format,
476
497
  blend: {
477
- color: {
478
- srcFactor: 'src-alpha',
479
- dstFactor: 'one-minus-src-alpha',
480
- operation: 'add',
481
- },
482
- alpha: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
498
+ color: { srcFactor: 'src-alpha', dstFactor: 'one', operation: 'add' },
499
+ alpha: { srcFactor: 'zero', dstFactor: 'one', operation: 'add' },
483
500
  },
484
501
  }
485
- : { format: cache.format }),
502
+ : translucent
503
+ ? {
504
+ format: cache.format,
505
+ blend: {
506
+ color: {
507
+ srcFactor: 'src-alpha',
508
+ dstFactor: 'one-minus-src-alpha',
509
+ operation: 'add',
510
+ },
511
+ alpha: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
512
+ },
513
+ }
514
+ : { format: cache.format }),
486
515
  ],
487
516
  },
488
517
  primitive: { topology: 'triangle-list', cullMode: doubleSided ? 'none' : 'back' },
@@ -66,7 +66,8 @@ export class GpuGlassTint {
66
66
  {
67
67
  binding: CUTOUT_MAP.texture,
68
68
  visibility: VISIBILITY_FRAGMENT,
69
- texture: { sampleType: 'float' },
69
+ /* A surface texture, and every surface texture is a `2d-array` view. */
70
+ texture: { sampleType: 'float', viewDimension: '2d-array' },
70
71
  },
71
72
  {
72
73
  binding: CUTOUT_MAP.sampler,
@@ -0,0 +1,34 @@
1
+ /** Views a batch can be culled for in one frame. See the header. */
2
+ export declare const MAX_CULL_SLOTS = 4;
3
+ /** One set of survivors: the compacted instances, the indirect record, and what the cull reads. */
4
+ export interface CullSlot {
5
+ readonly instances: GPUBuffer;
6
+ readonly args: GPUBuffer;
7
+ readonly params: GPUBuffer;
8
+ readonly group: GPUBindGroup;
9
+ /** How many instances the last queued job tested, which sizes its dispatch. */
10
+ count: number;
11
+ }
12
+ export declare class InstanceCullPass {
13
+ private readonly device;
14
+ private readonly pipeline;
15
+ private readonly layout;
16
+ private readonly jobs;
17
+ private queued;
18
+ private readonly paramsStaging;
19
+ private readonly paramFloats;
20
+ private readonly paramWords;
21
+ private readonly argsStaging;
22
+ constructor(device: GPUDevice);
23
+ /** A slot reading `source`, a batch's full instance buffer of `capacity` instances. */
24
+ createSlot(source: GPUBuffer, capacity: number, label: string): CullSlot;
25
+ /**
26
+ * Queue a cull of `count` instances against six `planes`, the mesh's local sphere being
27
+ * (centre, radius). Returns false when the frame's job ring is full, and the caller draws uncut.
28
+ */
29
+ queue(slot: CullSlot, planes: Float32Array, centre: ArrayLike<number>, radius: number, count: number, indexCount: number): boolean;
30
+ /** Dispatch everything queued, in one compute pass and one submit. Nothing queued is nothing done. */
31
+ flush(): void;
32
+ }
33
+ /** Every buffer a slot owns, destroyed with its batch. */
34
+ export declare function destroySlot(slot: CullSlot): void;
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Instance culls on the device: a batch's instances tested against a view and compacted into an
3
+ * indirect draw, many batches in one compute submit.
4
+ *
5
+ * **Queued during the frame, dispatched in one submit from `flushRings`.** Every submit that can carry
6
+ * a culled draw — the frame, a mirror, a probe face — calls `flushRings` first, so the dispatch that
7
+ * fills a slot is always on the queue ahead of the pass that draws from it. The parameters and the
8
+ * zeroed instance count are written with `queue.writeBuffer` when the job is queued, which the queue
9
+ * orders ahead of both submits.
10
+ *
11
+ * **A slot is one (batch, view) pair in one frame.** A batch drawn from the camera, a mirror and a
12
+ * probe face needs three sets of survivors, so a batch holds up to `MAX_CULL_SLOTS`, taken in order
13
+ * each frame; past that the renderer draws every instance, which is right and merely slower. Each
14
+ * slot owns its buffers and a bind group built once, so queueing allocates nothing.
15
+ */
16
+ import { INSTANCE_STRIDE } from '../../instances.js';
17
+ import { INSTANCE_CULL_WGSL, INSTANCE_CULL_WORKGROUP } from './shaders/instanceCull.wgsl.js';
18
+ import { shaderModule } from './shaderModules.js';
19
+ /** Views a batch can be culled for in one frame. See the header. */
20
+ export const MAX_CULL_SLOTS = 4;
21
+ /** Jobs one frame may queue before the rest draw uncut. A ring size, not an opinion. */
22
+ const MAX_JOBS = 4096;
23
+ /** Bytes of `Params`: six planes, the mesh's sphere, and the instance count padded to 16. */
24
+ const PARAMS_BYTES = 6 * 16 + 16 + 16;
25
+ const USAGE = {
26
+ VERTEX: 0x0020,
27
+ UNIFORM: 0x0040,
28
+ STORAGE: 0x0080,
29
+ INDIRECT: 0x0100,
30
+ COPY_DST: 0x0008,
31
+ };
32
+ export class InstanceCullPass {
33
+ device;
34
+ pipeline;
35
+ layout;
36
+ jobs = new Array(MAX_JOBS).fill(null);
37
+ queued = 0;
38
+ paramsStaging = new ArrayBuffer(PARAMS_BYTES);
39
+ paramFloats = new Float32Array(this.paramsStaging);
40
+ paramWords = new Uint32Array(this.paramsStaging);
41
+ argsStaging = new Uint32Array(5);
42
+ constructor(device) {
43
+ this.device = device;
44
+ this.layout = device.createBindGroupLayout({
45
+ label: 'instances.cull',
46
+ entries: [
47
+ { binding: 0, visibility: 0x4, buffer: { type: 'uniform' } },
48
+ { binding: 1, visibility: 0x4, buffer: { type: 'read-only-storage' } },
49
+ { binding: 2, visibility: 0x4, buffer: { type: 'storage' } },
50
+ { binding: 3, visibility: 0x4, buffer: { type: 'storage' } },
51
+ ],
52
+ });
53
+ this.pipeline = device.createComputePipeline({
54
+ label: 'instances.cull',
55
+ layout: device.createPipelineLayout({ bindGroupLayouts: [this.layout] }),
56
+ compute: {
57
+ module: shaderModule(device, { label: 'instances.cull', code: INSTANCE_CULL_WGSL }),
58
+ entryPoint: 'main',
59
+ },
60
+ });
61
+ }
62
+ /** A slot reading `source`, a batch's full instance buffer of `capacity` instances. */
63
+ createSlot(source, capacity, label) {
64
+ const size = Math.max(1, capacity) * INSTANCE_STRIDE;
65
+ const instances = this.device.createBuffer({
66
+ label: `${label}.kept`,
67
+ size,
68
+ usage: USAGE.VERTEX | USAGE.STORAGE,
69
+ });
70
+ const args = this.device.createBuffer({
71
+ label: `${label}.args`,
72
+ size: 20,
73
+ usage: USAGE.INDIRECT | USAGE.STORAGE | USAGE.COPY_DST,
74
+ });
75
+ const params = this.device.createBuffer({
76
+ label: `${label}.params`,
77
+ size: PARAMS_BYTES,
78
+ usage: USAGE.UNIFORM | USAGE.COPY_DST,
79
+ });
80
+ const group = this.device.createBindGroup({
81
+ label: `${label}.cull`,
82
+ layout: this.layout,
83
+ entries: [
84
+ { binding: 0, resource: { buffer: params } },
85
+ { binding: 1, resource: { buffer: source, size } },
86
+ { binding: 2, resource: { buffer: instances } },
87
+ { binding: 3, resource: { buffer: args } },
88
+ ],
89
+ });
90
+ return { instances, args, params, group, count: 0 };
91
+ }
92
+ /**
93
+ * Queue a cull of `count` instances against six `planes`, the mesh's local sphere being
94
+ * (centre, radius). Returns false when the frame's job ring is full, and the caller draws uncut.
95
+ */
96
+ queue(slot, planes, centre, radius, count, indexCount) {
97
+ if (this.queued >= MAX_JOBS)
98
+ return false;
99
+ const floats = this.paramFloats;
100
+ for (let i = 0; i < 24; i++)
101
+ floats[i] = planes[i];
102
+ floats[24] = centre[0] ?? 0;
103
+ floats[25] = centre[1] ?? 0;
104
+ floats[26] = centre[2] ?? 0;
105
+ floats[27] = radius;
106
+ this.paramWords[28] = count;
107
+ this.device.queue.writeBuffer(slot.params, 0, this.paramsStaging);
108
+ const args = this.argsStaging;
109
+ args[0] = indexCount;
110
+ args[1] = 0;
111
+ args[2] = 0;
112
+ args[3] = 0;
113
+ args[4] = 0;
114
+ this.device.queue.writeBuffer(slot.args, 0, args);
115
+ slot.count = count;
116
+ this.jobs[this.queued++] = slot;
117
+ return true;
118
+ }
119
+ /** Dispatch everything queued, in one compute pass and one submit. Nothing queued is nothing done. */
120
+ flush() {
121
+ if (this.queued === 0)
122
+ return;
123
+ const encoder = this.device.createCommandEncoder({ label: 'instances.cull' });
124
+ const pass = encoder.beginComputePass({ label: 'instances.cull' });
125
+ pass.setPipeline(this.pipeline);
126
+ for (let i = 0; i < this.queued; i++) {
127
+ const slot = this.jobs[i];
128
+ if (slot === null || slot === undefined)
129
+ continue;
130
+ pass.setBindGroup(0, slot.group);
131
+ pass.dispatchWorkgroups(Math.ceil(slot.count / INSTANCE_CULL_WORKGROUP));
132
+ this.jobs[i] = null;
133
+ }
134
+ pass.end();
135
+ this.device.queue.submit([encoder.finish()]);
136
+ this.queued = 0;
137
+ }
138
+ }
139
+ /** Every buffer a slot owns, destroyed with its batch. */
140
+ export function destroySlot(slot) {
141
+ slot.instances.destroy();
142
+ slot.args.destroy();
143
+ slot.params.destroy();
144
+ }
@@ -1,3 +1,4 @@
1
+ import type { CullSlot } from './instanceCullPass.ts';
1
2
  import type { MeshInstances } from '../../instances.ts';
2
3
  import type { GpuMesh } from './buffers.ts';
3
4
  /**
@@ -15,7 +16,16 @@ export declare class GpuInstancedBatch {
15
16
  readonly mesh: GpuMesh;
16
17
  readonly capacity: number;
17
18
  private readonly staging;
18
- constructor(device: GPUDevice, mesh: GpuMesh, capacity: number, label: string);
19
+ /** Whether the camera draw culls this batch. See `InstancedOptions.cull`. */
20
+ readonly cull: boolean;
21
+ /** The world box around every live instance, fitted at upload. See `instancesBox`. */
22
+ readonly box: Float32Array<ArrayBuffer>;
23
+ /** One set of survivors per view drawn this frame. See `InstanceCullPass`. */
24
+ readonly slots: CullSlot[];
25
+ /** The frame the slots were last taken in, and how many were taken. */
26
+ slotFrame: number;
27
+ slotsTaken: number;
28
+ constructor(device: GPUDevice, mesh: GpuMesh, capacity: number, label: string, cull?: boolean);
19
29
  /** Last frame's packed slots, for a reconstruction; made on first use by the renderer. */
20
30
  previousBuffer: GPUBuffer | null;
21
31
  /** How many slots the upload before the last one held, and how many the last one holds. */
@@ -1,6 +1,10 @@
1
+ import { instancesBox } from '../../instanceCull.js';
1
2
  import { INSTANCE_FLOATS, INSTANCE_STRIDE, packInstances } from '../../instances.js';
3
+ import { destroySlot } from './instanceCullPass.js';
2
4
  /** VERTEX | COPY_DST, spelled the way every other pass here spells it. */
3
5
  const USAGE_VERTEX = 0x0020 | 0x0008;
6
+ /** A culling batch's source is read by the cull as storage too. */
7
+ const USAGE_STORAGE = 0x0080;
4
8
  /**
5
9
  * One mesh's per-instance buffer on the device.
6
10
  *
@@ -16,9 +20,19 @@ export class GpuInstancedBatch {
16
20
  mesh;
17
21
  capacity;
18
22
  staging;
19
- constructor(device, mesh, capacity, label) {
23
+ /** Whether the camera draw culls this batch. See `InstancedOptions.cull`. */
24
+ cull;
25
+ /** The world box around every live instance, fitted at upload. See `instancesBox`. */
26
+ box = new Float32Array([1, 1, 1, -1, -1, -1]);
27
+ /** One set of survivors per view drawn this frame. See `InstanceCullPass`. */
28
+ slots = [];
29
+ /** The frame the slots were last taken in, and how many were taken. */
30
+ slotFrame = -1;
31
+ slotsTaken = 0;
32
+ constructor(device, mesh, capacity, label, cull = false) {
20
33
  this.mesh = mesh;
21
34
  this.capacity = capacity;
35
+ this.cull = cull;
22
36
  this.staging = new Float32Array(capacity * INSTANCE_FLOATS);
23
37
  this.buffer = device.createBuffer({
24
38
  /*
@@ -26,8 +40,8 @@ export class GpuInstancedBatch {
26
40
  * naming the resource it was about, and an unlabelled buffer names nothing.
27
41
  */
28
42
  label,
29
- size: capacity * INSTANCE_STRIDE,
30
- usage: USAGE_VERTEX,
43
+ size: Math.max(1, capacity) * INSTANCE_STRIDE,
44
+ usage: USAGE_VERTEX | (cull ? USAGE_STORAGE : 0),
31
45
  });
32
46
  }
33
47
  /** Last frame's packed slots, for a reconstruction; made on first use by the renderer. */
@@ -48,6 +62,8 @@ export class GpuInstancedBatch {
48
62
  * `writeBuffer` copies at the call, so packing afterwards cannot reach that copy.
49
63
  */
50
64
  upload(queue, data, previous = null) {
65
+ if (this.cull)
66
+ instancesBox(data, this.mesh.bounds, this.box);
51
67
  const count = Math.min(data.count, this.capacity);
52
68
  if (previous !== null) {
53
69
  if (this.liveCount > 0) {
@@ -64,5 +80,7 @@ export class GpuInstancedBatch {
64
80
  dispose() {
65
81
  this.previousBuffer?.destroy();
66
82
  this.buffer.destroy();
83
+ for (const slot of this.slots)
84
+ destroySlot(slot);
67
85
  }
68
86
  }
@@ -7,14 +7,18 @@ export declare class LatePass {
7
7
  private readonly layout;
8
8
  private readonly pipeline;
9
9
  private readonly sizes;
10
+ /** The render size and the output size, then the frame's jitter in render texels. */
10
11
  private readonly sizeStaging;
11
12
  private group;
12
13
  private groupSource;
13
14
  constructor(device: GPUDevice);
14
15
  /** The output-size depth, (re)made when the output changes size. */
15
16
  depthView(width: number, height: number): GPUTextureView;
16
- /** Fill the output-size depth from `source`, the render's single-sample depth. */
17
- upscale(encoder: GPUCommandEncoder, source: GPUTextureView, renderWidth: number, renderHeight: number, outputWidth: number, outputHeight: number): void;
17
+ /**
18
+ * Fill the output-size depth from `source`, the render's single-sample depth, drawn jittered by
19
+ * (`jitterX`, `jitterY`) render texels — which the fill takes back out (`recon/lateDepth.ts`).
20
+ */
21
+ upscale(encoder: GPUCommandEncoder, source: GPUTextureView, renderWidth: number, renderHeight: number, outputWidth: number, outputHeight: number, jitterX: number, jitterY: number): void;
18
22
  /** A command slot for one late draw. Every field a caller uses it fills; see `takeCommand`. */
19
23
  take(): DrawCommand;
20
24
  /** Late draws recorded this frame. */
@@ -23,7 +23,8 @@ export class LatePass {
23
23
  layout;
24
24
  pipeline;
25
25
  sizes;
26
- sizeStaging = new Float32Array(4);
26
+ /** The render size and the output size, then the frame's jitter in render texels. */
27
+ sizeStaging = new Float32Array(8);
27
28
  group = null;
28
29
  groupSource = null;
29
30
  constructor(device) {
@@ -46,7 +47,7 @@ export class LatePass {
46
47
  });
47
48
  this.sizes = device.createBuffer({
48
49
  label: 'recon.lateDepth.sizes',
49
- size: 16,
50
+ size: 32,
50
51
  usage: 0x40 | 0x8, // UNIFORM | COPY_DST
51
52
  });
52
53
  }
@@ -64,13 +65,18 @@ export class LatePass {
64
65
  }
65
66
  return this.depthTextureView;
66
67
  }
67
- /** Fill the output-size depth from `source`, the render's single-sample depth. */
68
- upscale(encoder, source, renderWidth, renderHeight, outputWidth, outputHeight) {
68
+ /**
69
+ * Fill the output-size depth from `source`, the render's single-sample depth, drawn jittered by
70
+ * (`jitterX`, `jitterY`) render texels — which the fill takes back out (`recon/lateDepth.ts`).
71
+ */
72
+ upscale(encoder, source, renderWidth, renderHeight, outputWidth, outputHeight, jitterX, jitterY) {
69
73
  const target = this.depthView(outputWidth, outputHeight);
70
74
  this.sizeStaging[0] = renderWidth;
71
75
  this.sizeStaging[1] = renderHeight;
72
76
  this.sizeStaging[2] = outputWidth;
73
77
  this.sizeStaging[3] = outputHeight;
78
+ this.sizeStaging[4] = jitterX;
79
+ this.sizeStaging[5] = jitterY;
74
80
  this.device.queue.writeBuffer(this.sizes, 0, this.sizeStaging);
75
81
  if (this.group === null || this.groupSource !== source) {
76
82
  this.group = this.device.createBindGroup({
@@ -107,6 +113,7 @@ export class LatePass {
107
113
  command.indexBuffer = null;
108
114
  command.indexed = false;
109
115
  command.instances = 1;
116
+ command.indirect = null;
110
117
  return command;
111
118
  }
112
119
  /** Late draws recorded this frame. */
@@ -465,7 +465,8 @@ export class MotionPass {
465
465
  else {
466
466
  pipeline = this.pipelineFor(mesh.vertexStride);
467
467
  }
468
- if (pipeline === null)
468
+ /* A mesh with nothing in it draws nothing: see `GpuMesh.indexCount`. */
469
+ if (pipeline === null || mesh.indexCount === 0)
469
470
  continue;
470
471
  if (pipeline !== bound) {
471
472
  pass.setPipeline(pipeline);
@@ -26,7 +26,7 @@ import type { PipelineCache } from './pipelineCache.ts';
26
26
  * float target the generated shaders can sample as an ordinary texture.
27
27
  */
28
28
  export declare const RUSH_FRAG_FIELDS: UniformFields;
29
- export declare const RUSH_FRAG_SIZE: 192;
29
+ export declare const RUSH_FRAG_SIZE: 208;
30
30
  declare const RUSH_UNIFORMS: 1;
31
31
  declare const RUSH_TEXTURES: {
32
32
  readonly uScene: {