@driftengine/core 4.4.1 → 4.5.0

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 (218) hide show
  1. package/CHANGELOG.json +72 -0
  2. package/README.md +1 -1
  3. package/dist/index.d.ts +5 -1
  4. package/dist/index.js +7 -0
  5. package/dist/render/backend/api.d.ts +17 -11
  6. package/dist/render/backend/webgl2/glassTintPrograms.d.ts +13 -0
  7. package/dist/render/backend/webgl2/glassTintPrograms.js +38 -0
  8. package/dist/render/backend/webgl2/renderer.d.ts +82 -8
  9. package/dist/render/backend/webgl2/renderer.js +373 -105
  10. package/dist/render/backend/webgpu/buffers.d.ts +19 -1
  11. package/dist/render/backend/webgpu/buffers.js +27 -1
  12. package/dist/render/backend/webgpu/depthPass.d.ts +1 -1
  13. package/dist/render/backend/webgpu/depthPass.js +2 -1
  14. package/dist/render/backend/webgpu/flatPass.d.ts +2 -0
  15. package/dist/render/backend/webgpu/flatPass.js +20 -1
  16. package/dist/render/backend/webgpu/glassTintPass.d.ts +28 -0
  17. package/dist/render/backend/webgpu/glassTintPass.js +205 -0
  18. package/dist/render/backend/webgpu/gpuDrivenPass.d.ts +60 -1
  19. package/dist/render/backend/webgpu/gpuDrivenPass.js +305 -5
  20. package/dist/render/backend/webgpu/instanced.d.ts +12 -1
  21. package/dist/render/backend/webgpu/instanced.js +20 -1
  22. package/dist/render/backend/webgpu/latePass.d.ts +26 -0
  23. package/dist/render/backend/webgpu/latePass.js +133 -0
  24. package/dist/render/backend/webgpu/lightVolumePass.js +5 -1
  25. package/dist/render/backend/webgpu/motionPass.d.ts +95 -0
  26. package/dist/render/backend/webgpu/motionPass.js +491 -0
  27. package/dist/render/backend/webgpu/pipelineCache.d.ts +10 -1
  28. package/dist/render/backend/webgpu/pipelineCache.js +11 -1
  29. package/dist/render/backend/webgpu/pointGlassTintPass.d.ts +32 -0
  30. package/dist/render/backend/webgpu/pointGlassTintPass.js +193 -0
  31. package/dist/render/backend/webgpu/pointShadowPass.d.ts +35 -6
  32. package/dist/render/backend/webgpu/pointShadowPass.js +105 -20
  33. package/dist/render/backend/webgpu/postPass.d.ts +21 -15
  34. package/dist/render/backend/webgpu/postPass.js +17 -4
  35. package/dist/render/backend/webgpu/renderer.d.ts +181 -54
  36. package/dist/render/backend/webgpu/renderer.js +1011 -394
  37. package/dist/render/backend/webgpu/scatterPass.js +2 -1
  38. package/dist/render/backend/webgpu/skinPaletteRing.d.ts +4 -0
  39. package/dist/render/backend/webgpu/skinPaletteRing.js +4 -0
  40. package/dist/render/backend/webgpu/snapshotMips.d.ts +29 -0
  41. package/dist/render/backend/webgpu/snapshotMips.js +52 -0
  42. package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +5 -0
  43. package/dist/render/backend/webgpu/surfaceTexturePass.js +1 -1
  44. package/dist/render/flameLight.js +15 -2
  45. package/dist/render/glass.d.ts +45 -0
  46. package/dist/render/glass.js +39 -0
  47. package/dist/render/glassCasters.d.ts +40 -0
  48. package/dist/render/glassCasters.js +84 -0
  49. package/dist/render/glassShadow.d.ts +78 -0
  50. package/dist/render/glassShadow.js +125 -0
  51. package/dist/render/globalMediumPass.d.ts +2 -3
  52. package/dist/render/globalMediumPass.js +2 -8
  53. package/dist/render/gpudriven/lit.d.ts +12 -0
  54. package/dist/render/gpudriven/lit.js +23 -0
  55. package/dist/render/gpudriven/materialTable.d.ts +15 -5
  56. package/dist/render/gpudriven/materialTable.js +26 -6
  57. package/dist/render/gpudriven/pipeline.d.ts +1 -1
  58. package/dist/render/gpudriven/pipeline.js +4 -3
  59. package/dist/render/gpudriven/sunGlass.d.ts +35 -0
  60. package/dist/render/gpudriven/sunGlass.js +89 -0
  61. package/dist/render/lightBudget.d.ts +17 -5
  62. package/dist/render/lightBudget.js +17 -5
  63. package/dist/render/pointGlassTint.d.ts +59 -0
  64. package/dist/render/pointGlassTint.js +198 -0
  65. package/dist/render/pointShadowArray.d.ts +56 -3
  66. package/dist/render/pointShadowArray.js +127 -16
  67. package/dist/render/pointShadowBudget.js +51 -41
  68. package/dist/render/pointShadowMap.d.ts +23 -1
  69. package/dist/render/pointShadowMap.js +27 -1
  70. package/dist/render/recon/changeFrames.d.ts +15 -0
  71. package/dist/render/recon/changeFrames.js +21 -0
  72. package/dist/render/recon/disocclusion.d.ts +36 -3
  73. package/dist/render/recon/disocclusion.js +56 -4
  74. package/dist/render/recon/lateRouting.d.ts +14 -0
  75. package/dist/render/recon/lateRouting.js +18 -0
  76. package/dist/render/recon/mover.d.ts +57 -0
  77. package/dist/render/recon/mover.js +86 -0
  78. package/dist/render/recon/resolve.js +95 -12
  79. package/dist/render/renderQuality.d.ts +35 -0
  80. package/dist/render/renderQuality.js +7 -0
  81. package/dist/render/rendererHarness.js +12 -0
  82. package/dist/render/sceneTarget.d.ts +9 -1
  83. package/dist/render/sceneTarget.js +59 -13
  84. package/dist/render/shaders/depth.d.ts +16 -1
  85. package/dist/render/shaders/depth.js +85 -9
  86. package/dist/render/shaders/flat/directionalShadow.js +91 -6
  87. package/dist/render/shaders/flat/glassTint.d.ts +6 -0
  88. package/dist/render/shaders/flat/glassTint.js +25 -0
  89. package/dist/render/shaders/flat/index.d.ts +13 -0
  90. package/dist/render/shaders/flat/index.js +4 -0
  91. package/dist/render/shaders/flat/lampGlass.d.ts +1 -0
  92. package/dist/render/shaders/flat/lampGlass.js +183 -0
  93. package/dist/render/shaders/flat/main.d.ts +1 -1
  94. package/dist/render/shaders/flat/main.js +115 -15
  95. package/dist/render/shaders/flat/pointShadow.d.ts +1 -1
  96. package/dist/render/shaders/flat/pointShadow.js +2 -0
  97. package/dist/render/shaders/flat/preamble.js +21 -6
  98. package/dist/render/shaders/generated/depth.wgsl.d.ts +227 -4
  99. package/dist/render/shaders/generated/depth.wgsl.js +227 -4
  100. package/dist/render/shaders/generated/flat.wgsl.d.ts +360 -312
  101. package/dist/render/shaders/generated/flat.wgsl.js +376 -328
  102. package/dist/render/shaders/generated/globalMedium.wgsl.d.ts +3 -13
  103. package/dist/render/shaders/generated/globalMedium.wgsl.js +3 -13
  104. package/dist/render/shaders/generated/lightVolume.wgsl.d.ts +3 -13
  105. package/dist/render/shaders/generated/lightVolume.wgsl.js +4 -14
  106. package/dist/render/shaders/generated/octahedralResolve.wgsl.d.ts +29 -0
  107. package/dist/render/shaders/generated/octahedralResolve.wgsl.js +29 -0
  108. package/dist/render/shaders/generated/temporalResolve.wgsl.d.ts +31 -6
  109. package/dist/render/shaders/generated/temporalResolve.wgsl.js +31 -6
  110. package/dist/render/shaders/globalMedium.js +6 -6
  111. package/dist/render/shaders/gpudriven/blendRaster.wgsl.d.ts +7 -0
  112. package/dist/render/shaders/gpudriven/blendRaster.wgsl.js +86 -1
  113. package/dist/render/shaders/gpudriven/cull.wgsl.d.ts +1 -1
  114. package/dist/render/shaders/gpudriven/cull.wgsl.js +11 -2
  115. package/dist/render/shaders/gpudriven/materialTable.wgsl.d.ts +1 -1
  116. package/dist/render/shaders/gpudriven/materialTable.wgsl.js +3 -1
  117. package/dist/render/shaders/gpudriven/shade.wgsl.js +50 -1
  118. package/dist/render/shaders/gpudriven/sunGlass.wgsl.d.ts +9 -0
  119. package/dist/render/shaders/gpudriven/sunGlass.wgsl.js +133 -0
  120. package/dist/render/shaders/lightVolume.js +6 -6
  121. package/dist/render/shaders/octahedralResolve.d.ts +8 -0
  122. package/dist/render/shaders/octahedralResolve.js +48 -17
  123. package/dist/render/shaders/recon/lateDepth.wgsl.d.ts +1 -0
  124. package/dist/render/shaders/recon/lateDepth.wgsl.js +44 -0
  125. package/dist/render/shaders/recon/motion.wgsl.d.ts +1 -1
  126. package/dist/render/shaders/recon/motion.wgsl.js +84 -0
  127. package/dist/render/shaders/recon/resolve.wgsl.d.ts +1 -1
  128. package/dist/render/shaders/recon/resolve.wgsl.js +112 -8
  129. package/dist/render/shaders/temporalResolve.js +133 -5
  130. package/dist/render/shadowCasters.d.ts +20 -1
  131. package/dist/render/shadowCasters.js +15 -1
  132. package/dist/render/shadowMap.d.ts +54 -5
  133. package/dist/render/shadowMap.js +190 -36
  134. package/dist/render/sunGlassTint.d.ts +25 -0
  135. package/dist/render/sunGlassTint.js +79 -0
  136. package/dist/render/temporalAa.d.ts +145 -0
  137. package/dist/render/temporalAa.js +190 -0
  138. package/dist/render/temporalPass.d.ts +16 -14
  139. package/dist/render/temporalPass.js +59 -14
  140. package/dist/render/translucentQueue.d.ts +6 -0
  141. package/dist/render/translucentQueue.js +15 -1
  142. package/package.json +6 -6
  143. package/src/index.ts +10 -0
  144. package/src/render/backend/api.ts +15 -9
  145. package/src/render/backend/webgl2/glassTintPrograms.ts +58 -0
  146. package/src/render/backend/webgl2/renderer.ts +422 -109
  147. package/src/render/backend/webgpu/buffers.ts +50 -2
  148. package/src/render/backend/webgpu/depthPass.ts +2 -1
  149. package/src/render/backend/webgpu/flatPass.ts +22 -1
  150. package/src/render/backend/webgpu/glassTintPass.ts +244 -0
  151. package/src/render/backend/webgpu/gpuDrivenPass.ts +349 -5
  152. package/src/render/backend/webgpu/instanced.ts +21 -1
  153. package/src/render/backend/webgpu/latePass.ts +152 -0
  154. package/src/render/backend/webgpu/lightVolumePass.ts +5 -1
  155. package/src/render/backend/webgpu/motionPass.ts +535 -0
  156. package/src/render/backend/webgpu/pipelineCache.ts +12 -1
  157. package/src/render/backend/webgpu/pointGlassTintPass.ts +233 -0
  158. package/src/render/backend/webgpu/pointShadowPass.ts +158 -26
  159. package/src/render/backend/webgpu/postPass.ts +34 -4
  160. package/src/render/backend/webgpu/renderer.ts +1178 -425
  161. package/src/render/backend/webgpu/scatterPass.ts +2 -1
  162. package/src/render/backend/webgpu/skinPaletteRing.ts +4 -0
  163. package/src/render/backend/webgpu/snapshotMips.ts +80 -0
  164. package/src/render/backend/webgpu/surfaceTexturePass.ts +1 -1
  165. package/src/render/flameLight.ts +15 -2
  166. package/src/render/glass.ts +74 -0
  167. package/src/render/glassCasters.ts +164 -0
  168. package/src/render/glassShadow.ts +153 -0
  169. package/src/render/globalMediumPass.ts +4 -11
  170. package/src/render/gpudriven/lit.ts +31 -0
  171. package/src/render/gpudriven/materialTable.ts +28 -6
  172. package/src/render/gpudriven/pipeline.ts +4 -3
  173. package/src/render/gpudriven/sunGlass.ts +117 -0
  174. package/src/render/lightBudget.ts +19 -5
  175. package/src/render/pointGlassTint.ts +250 -0
  176. package/src/render/pointShadowArray.ts +188 -22
  177. package/src/render/pointShadowBudget.ts +49 -39
  178. package/src/render/pointShadowMap.ts +41 -0
  179. package/src/render/recon/changeFrames.ts +24 -0
  180. package/src/render/recon/disocclusion.ts +54 -3
  181. package/src/render/recon/lateRouting.ts +29 -0
  182. package/src/render/recon/mover.ts +118 -0
  183. package/src/render/recon/resolve.ts +137 -10
  184. package/src/render/renderQuality.ts +47 -0
  185. package/src/render/rendererHarness.ts +12 -0
  186. package/src/render/sceneTarget.ts +66 -12
  187. package/src/render/shaders/depth.ts +109 -9
  188. package/src/render/shaders/flat/directionalShadow.ts +97 -6
  189. package/src/render/shaders/flat/glassTint.ts +25 -0
  190. package/src/render/shaders/flat/index.ts +17 -0
  191. package/src/render/shaders/flat/lampGlass.ts +185 -0
  192. package/src/render/shaders/flat/main.ts +115 -15
  193. package/src/render/shaders/flat/pointShadow.ts +2 -0
  194. package/src/render/shaders/flat/preamble.ts +21 -6
  195. package/src/render/shaders/generated/depth.wgsl.ts +234 -4
  196. package/src/render/shaders/generated/flat.wgsl.ts +376 -328
  197. package/src/render/shaders/generated/globalMedium.wgsl.ts +3 -13
  198. package/src/render/shaders/generated/lightVolume.wgsl.ts +4 -14
  199. package/src/render/shaders/generated/octahedralResolve.wgsl.ts +30 -0
  200. package/src/render/shaders/generated/temporalResolve.wgsl.ts +31 -6
  201. package/src/render/shaders/globalMedium.ts +6 -6
  202. package/src/render/shaders/gpudriven/blendRaster.wgsl.ts +88 -1
  203. package/src/render/shaders/gpudriven/cull.wgsl.ts +11 -2
  204. package/src/render/shaders/gpudriven/materialTable.wgsl.ts +3 -1
  205. package/src/render/shaders/gpudriven/shade.wgsl.ts +50 -1
  206. package/src/render/shaders/gpudriven/sunGlass.wgsl.ts +139 -0
  207. package/src/render/shaders/lightVolume.ts +6 -6
  208. package/src/render/shaders/octahedralResolve.ts +50 -17
  209. package/src/render/shaders/recon/lateDepth.wgsl.ts +47 -0
  210. package/src/render/shaders/recon/motion.wgsl.ts +84 -0
  211. package/src/render/shaders/recon/resolve.wgsl.ts +112 -8
  212. package/src/render/shaders/temporalResolve.ts +141 -5
  213. package/src/render/shadowCasters.ts +28 -1
  214. package/src/render/shadowMap.ts +231 -37
  215. package/src/render/sunGlassTint.ts +86 -0
  216. package/src/render/temporalAa.ts +227 -0
  217. package/src/render/temporalPass.ts +88 -12
  218. package/src/render/translucentQueue.ts +20 -1
@@ -48,6 +48,15 @@ export interface GpuMesh {
48
48
  * its own does.
49
49
  */
50
50
  readonly vertexStride: number;
51
+ /**
52
+ * Byte offsets of the joint indices and weights in the interleaved vertex, or null for a mesh
53
+ * that supplied neither. For a pass that skins through a shader of its own — the motion pass —
54
+ * which has to find them without the `present` map the generated pipelines are keyed by.
55
+ */
56
+ readonly skinOffsets: {
57
+ readonly joints: number;
58
+ readonly weights: number;
59
+ } | null;
51
60
  readonly indexBuffer: GPUBuffer;
52
61
  readonly indexCount: number;
53
62
  /**
@@ -114,7 +123,16 @@ export interface GpuMesh {
114
123
  * one CPU-side copy of the vertex data per dynamic mesh, which is why the flag exists rather
115
124
  * than every mesh paying it.
116
125
  */
117
- readonly update: ((device: GPUDevice, positions: Float32Array, normals?: Float32Array) => void) | null;
126
+ readonly update: ((device: GPUDevice, positions: Float32Array, normals?: Float32Array, previous?: GPUBuffer | null) => void) | null;
127
+ /**
128
+ * A dynamic mesh's motion state, which the renderer owns: a buffer for last frame's interleaved
129
+ * rows, made the first time a reconstructing renderer rewrites the mesh, and the frame of the
130
+ * last rewrite. Null on a mesh created without `{ dynamic: true }`, which never moves a vertex.
131
+ */
132
+ readonly motion: {
133
+ previous: GPUBuffer | null;
134
+ changed: number;
135
+ } | null;
118
136
  dispose(): void;
119
137
  }
120
138
  /** A mesh on its way to the device, and the iterator that gets it there. */
@@ -235,6 +235,7 @@ export function createGpuMeshIncremental(device, data, dynamic = false) {
235
235
  progress,
236
236
  hasTangents: data.tangents !== undefined,
237
237
  isSkinned: data.joints !== undefined,
238
+ skinOffsets: skinOffsetsOf(supplied),
238
239
  hasChannel: data.channel !== undefined,
239
240
  morph: data.morphTargets === undefined || data.morphTargetCount === undefined
240
241
  ? null
@@ -329,12 +330,31 @@ function interleaveField(rows, source, width, step, fieldOffset, from, to) {
329
330
  rows[write] = source[read];
330
331
  }
331
332
  }
333
+ /**
334
+ * Where the joints and weights sit in a vertex of `supplied`, the attributes a mesh interleaves in
335
+ * layout order; null unless it supplied both.
336
+ */
337
+ function skinOffsetsOf(supplied) {
338
+ let floats = 0;
339
+ let joints = -1;
340
+ let weights = -1;
341
+ for (const attribute of supplied) {
342
+ if (attribute.name === 'joints')
343
+ joints = floats * 4;
344
+ if (attribute.name === 'weights')
345
+ weights = floats * 4;
346
+ floats += attribute.components;
347
+ }
348
+ return joints >= 0 && weights >= 0 ? { joints, weights } : null;
349
+ }
332
350
  /** The handle, made where it can see only what it hands out. */
333
351
  function gpuMeshOf(parts) {
352
+ const motion = parts.update === null ? null : { previous: null, changed: -1 };
334
353
  const { vertices, constants, indexBuffer, progress } = parts;
335
354
  return {
336
355
  vertexBuffers: [vertices, constants],
337
356
  vertexStride: parts.stride,
357
+ skinOffsets: parts.skinOffsets,
338
358
  indexBuffer,
339
359
  indexCount: parts.indexCount,
340
360
  bounds: parts.bounds,
@@ -346,7 +366,9 @@ function gpuMeshOf(parts) {
346
366
  hasChannel: parts.hasChannel,
347
367
  morph: parts.morph,
348
368
  update: parts.update,
369
+ motion,
349
370
  dispose() {
371
+ motion?.previous?.destroy();
350
372
  vertices.destroy();
351
373
  constants.destroy();
352
374
  indexBuffer.destroy();
@@ -359,12 +381,16 @@ function gpuMeshOf(parts) {
359
381
  * receive have to still be there.
360
382
  */
361
383
  function dynamicUpdate(interleaved, vertices, bounds, vertexCount, step, positionField, normalField) {
362
- return (target, positions, normals) => {
384
+ return (target, positions, normals, previous = null) => {
363
385
  if (positions.length !== vertexCount * 3) {
364
386
  throw new Error(`this mesh has ${vertexCount} vertices and the update has ${positions.length / 3}. ` +
365
387
  "A mesh's vertex count is fixed at creation: the index buffer, the pipeline and " +
366
388
  'every other attribute are sized against it.');
367
389
  }
390
+ /* The rows as they stand are last frame's, handed over before a position of them changes.
391
+ `writeBuffer` copies at the call, so patching the array afterwards cannot reach this copy. */
392
+ if (previous !== null)
393
+ target.queue.writeBuffer(previous, 0, interleaved);
368
394
  /* Everything that decides whether this is on screen starts from the bounds, so a cloth
369
395
  that blew sideways out of its original box would be culled while still visible. */
370
396
  boundsOfPositions(positions, bounds);
@@ -117,7 +117,7 @@ export declare const DEPTH_FRAG_BINDING: 1;
117
117
  export declare const DEPTH_PREVIOUS_BINDING: {
118
118
  readonly texture: 32;
119
119
  readonly sampler: 33;
120
- readonly type: "sampler2D";
120
+ readonly type: "sampler2DArray";
121
121
  };
122
122
  /** Where the skinned vertex stage reads the joint palette. Generated, not chosen here. */
123
123
  export declare const DEPTH_PALETTE_BINDING: {
@@ -104,7 +104,8 @@ cutout = false) {
104
104
  {
105
105
  binding: PREVIOUS.texture,
106
106
  visibility: VISIBILITY_FRAGMENT,
107
- texture: { sampleType: 'unfilterable-float' },
107
+ /* An array since the sun's maps became one (`shadowMap.ts`): the peel reads its static layer. */
108
+ texture: { sampleType: 'unfilterable-float', viewDimension: '2d-array' },
108
109
  },
109
110
  {
110
111
  binding: PREVIOUS.sampler,
@@ -58,6 +58,8 @@ export interface StageBindings {
58
58
  readonly stride?: number;
59
59
  }>>;
60
60
  readonly textures: Readonly<Record<string, TextureBinding>>;
61
+ /** Each pipeline-overridable constant's id, by the GLSL name it was declared under. */
62
+ readonly overrides?: Readonly<Record<string, number>>;
61
63
  }
62
64
  /** The key for a set of vertex flags: the same rule `variantKey` uses, flags on, sorted, joined. */
63
65
  export declare function flatVertexKey(skinned: boolean, morphed: boolean, instanced?: boolean): string;
@@ -107,6 +107,20 @@ export function flatFragmentBindings(variant) {
107
107
  throw new Error(`flatPass: no bindings for variant "${variant}"`);
108
108
  return found;
109
109
  }
110
+ /**
111
+ * The lit fragment stage's constants, set from the cache: the glass switch, keyed by its **id**.
112
+ *
113
+ * Not by its name, because an override declared with `@id` answers to the id alone — WebGPU's
114
+ * "pipeline-overridable constant identifier string" — and naga writes one on every specialisation
115
+ * constant. Keyed by name, Chrome refused every lit pipeline: *Pipeline overridable constant
116
+ * "GLASS_SHADOWS" not found*, and the frame drew nothing.
117
+ */
118
+ function flatFragmentConstants(variant, cache) {
119
+ const id = flatFragmentBindings(variant).overrides?.['GLASS_SHADOWS'];
120
+ if (id === undefined)
121
+ throw new Error(`flatPass: variant "${variant}" has no glass switch`);
122
+ return { [String(id)]: cache.glassShadows ? 1 : 0 };
123
+ }
110
124
  /** The depth format both backends compare with. */
111
125
  /* Re-exported so the twenty passes that already import it from here do not each learn a new
112
126
  module; `depthConvention.ts` is where the decision lives and why. */
@@ -182,7 +196,10 @@ export function createFlatBindGroupLayout(device, variant, skinned = false, morp
182
196
  const integer = texture.type === 'usampler2D' || texture.type === 'usampler3D';
183
197
  /* DriftLight's brick index and atlas, the lit pass's only volumes. See `driftLight.ts`. */
184
198
  const volume = texture.type === 'sampler3D' || texture.type === 'usampler3D';
185
- const isShadow = integer || (name !== 'uEnvironment' && (name.endsWith('ShadowMap') || cube || array));
199
+ /* Glass tints are colour, mipmapped and filtered: a frosted pane is read at a coarser level. */
200
+ const tint = name.endsWith('GlassTints');
201
+ const isShadow = integer ||
202
+ (!tint && name !== 'uEnvironment' && (name.endsWith('ShadowMap') || cube || array));
186
203
  entries.push({
187
204
  binding: texture.texture,
188
205
  visibility: VISIBILITY_FRAGMENT,
@@ -449,6 +466,8 @@ doubleSided = false) {
449
466
  code: FLAT_FRAG_WGSL[variant] ?? '',
450
467
  }),
451
468
  entryPoint: 'main',
469
+ /* Every variant declares it, so every lit pipeline may set it: see `PipelineCache`. */
470
+ constants: flatFragmentConstants(variant, cache),
452
471
  targets: [
453
472
  oitTarget(oit) ??
454
473
  (translucent
@@ -0,0 +1,28 @@
1
+ import type { ResolvedGlass } from '../../glass.ts';
2
+ import type { PipelineCache } from './pipelineCache.ts';
3
+ import { UniformRing } from './uniformRing.ts';
4
+ export type GlassTintKind = 'rigid' | 'instanced' | 'skinned' | 'cutout' | 'instancedCutout';
5
+ /** The multiplying state: colour times what passes, clarity times clarity, order-free. */
6
+ export declare const GLASS_TINT_BLEND: GPUBlendState;
7
+ export declare class GpuGlassTint {
8
+ readonly frag: UniformRing;
9
+ private readonly layouts;
10
+ private plainGroup;
11
+ private groupVertBuffer;
12
+ private groupFragBuffer;
13
+ private readonly cutoutGroups;
14
+ private readonly skinnedGroups;
15
+ private readonly device;
16
+ constructor(device: GPUDevice);
17
+ /** One pane's block: what it lets through, how clear it is, and the light it is drawn for. */
18
+ writePane(slot: number, glass: ResolvedGlass, light: Float32Array): void;
19
+ /** The plain group, rebuilt only when a ring has grown a new buffer under it. */
20
+ group(vertBuffer: GPUBuffer): GPUBindGroup;
21
+ /** A cutout pane's group, one per map, built the first time that map casts colour. */
22
+ cutoutGroup(vertBuffer: GPUBuffer, view: GPUTextureView, sampler: GPUSampler): GPUBindGroup;
23
+ /** A skinned pane's group, one per palette view. */
24
+ skinnedGroup(vertBuffer: GPUBuffer, palette: GPUTextureView): GPUBindGroup;
25
+ /** The pipeline for one kind of caster, cull mode and vertex layout, cached by key. */
26
+ pipeline(cache: PipelineCache, kind: GlassTintKind, key: string, present: Readonly<Record<string, boolean>>, cullMode: GPUCullMode): GPURenderPipeline;
27
+ dispose(): void;
28
+ }
@@ -0,0 +1,205 @@
1
+ /**
2
+ * What a pane lets through, drawn into a shadow's tint layer on this device: the layouts, the
3
+ * per-draw block, the bind groups and the pipelines for the five tint programs (`depth.ts`).
4
+ *
5
+ * **The vertex half is the depth pass's**, block for block — the tint's vertex stages are the depth
6
+ * ones with the world position handed on — so a draw's vertex slot comes from the shadow pass's own
7
+ * ring and only the pane's 32 bytes are this file's. **The fragment half multiplies**: an
8
+ * `rgba8unorm` target cleared to white, `dst × src` for colour and `dst-alpha × src` for clarity, and
9
+ * no depth, so every pane on a ray counts in any order. Built the first time glass casts, so a scene
10
+ * with no glass builds none of it.
11
+ */
12
+ import { DEPTH_BINDINGS } from '../../shaders/generated/depth.wgsl.js';
13
+ import { GLASS_TINT_CUTOUT_FRAG_WGSL, GLASS_TINT_CUTOUT_VERT_WGSL, GLASS_TINT_FRAG_WGSL, GLASS_TINT_INSTANCED_CUTOUT_VERT_WGSL, GLASS_TINT_INSTANCED_VERT_WGSL, GLASS_TINT_SKINNED_VERT_WGSL, GLASS_TINT_VERT_WGSL, } from '../../shaders/generated/depth.wgsl.js';
14
+ import { vertexBufferLayouts } from './buffers.js';
15
+ import { shaderModule } from './shaderModules.js';
16
+ import { UniformRing } from './uniformRing.js';
17
+ const VISIBILITY_VERTEX = 0x1;
18
+ const VISIBILITY_FRAGMENT = 0x2;
19
+ const USAGE_UNIFORM_DST = 0x40 | 0x8; // UNIFORM | COPY_DST
20
+ const VERT = DEPTH_BINDINGS.GLASS_TINT_VERT;
21
+ const CUTOUT_VERT = DEPTH_BINDINGS.GLASS_TINT_CUTOUT_VERT;
22
+ const FRAG = DEPTH_BINDINGS.GLASS_TINT_FRAG;
23
+ const CUTOUT_MAP = DEPTH_BINDINGS.GLASS_TINT_CUTOUT_FRAG.textures.uCutoutMap;
24
+ const PALETTE = DEPTH_BINDINGS.GLASS_TINT_SKINNED_VERT.textures.uJointPalette;
25
+ const PANE = FRAG.fields.uGlassPane.offset;
26
+ const LIGHT = FRAG.fields.uGlassLight.offset;
27
+ /** Panes a frame can tint: a pane is a draw per shadow it reaches, and panes are few. */
28
+ const MAX_TINT_DRAWS = 512;
29
+ /** The multiplying state: colour times what passes, clarity times clarity, order-free. */
30
+ export const GLASS_TINT_BLEND = {
31
+ color: { operation: 'add', srcFactor: 'dst', dstFactor: 'zero' },
32
+ alpha: { operation: 'add', srcFactor: 'dst-alpha', dstFactor: 'zero' },
33
+ };
34
+ export class GpuGlassTint {
35
+ frag;
36
+ layouts;
37
+ plainGroup = null;
38
+ groupVertBuffer = null;
39
+ groupFragBuffer = null;
40
+ cutoutGroups = new Map();
41
+ skinnedGroups = new Map();
42
+ device;
43
+ constructor(device) {
44
+ this.device = device;
45
+ this.frag = new UniformRing(device, FRAG.uniformSize, MAX_TINT_DRAWS, USAGE_UNIFORM_DST, 'glassTint.ring');
46
+ const vert = (size) => ({
47
+ binding: VERT.uniforms,
48
+ visibility: VISIBILITY_VERTEX,
49
+ buffer: { type: 'uniform', hasDynamicOffset: true, minBindingSize: size },
50
+ });
51
+ const frag = {
52
+ binding: FRAG.uniforms,
53
+ visibility: VISIBILITY_FRAGMENT,
54
+ buffer: { type: 'uniform', hasDynamicOffset: true, minBindingSize: FRAG.uniformSize },
55
+ };
56
+ this.layouts = {
57
+ plain: device.createBindGroupLayout({
58
+ label: 'glassTint.layout',
59
+ entries: [vert(VERT.uniformSize), frag],
60
+ }),
61
+ cutout: device.createBindGroupLayout({
62
+ label: 'glassTint.layout.cutout',
63
+ entries: [
64
+ vert(CUTOUT_VERT.uniformSize),
65
+ frag,
66
+ {
67
+ binding: CUTOUT_MAP.texture,
68
+ visibility: VISIBILITY_FRAGMENT,
69
+ texture: { sampleType: 'float' },
70
+ },
71
+ {
72
+ binding: CUTOUT_MAP.sampler,
73
+ visibility: VISIBILITY_FRAGMENT,
74
+ sampler: { type: 'filtering' },
75
+ },
76
+ ],
77
+ }),
78
+ skinned: device.createBindGroupLayout({
79
+ label: 'glassTint.layout.skinned',
80
+ entries: [
81
+ {
82
+ binding: PALETTE.texture,
83
+ visibility: VISIBILITY_VERTEX,
84
+ texture: { sampleType: 'unfilterable-float' },
85
+ },
86
+ vert(VERT.uniformSize),
87
+ frag,
88
+ ],
89
+ }),
90
+ };
91
+ }
92
+ /** One pane's block: what it lets through, how clear it is, and the light it is drawn for. */
93
+ writePane(slot, glass, light) {
94
+ const pane = paneScratch;
95
+ pane[0] = glass.transmission * glass.tint[0];
96
+ pane[1] = glass.transmission * glass.tint[1];
97
+ pane[2] = glass.transmission * glass.tint[2];
98
+ pane[3] = 1 - glass.frost;
99
+ this.frag.writeFloats(slot, PANE, pane);
100
+ this.frag.writeFloats(slot, LIGHT, light);
101
+ }
102
+ /** The plain group, rebuilt only when a ring has grown a new buffer under it. */
103
+ group(vertBuffer) {
104
+ if (this.plainGroup === null ||
105
+ this.groupVertBuffer !== vertBuffer ||
106
+ this.groupFragBuffer !== this.frag.buffer) {
107
+ this.cutoutGroups.clear();
108
+ this.skinnedGroups.clear();
109
+ this.groupVertBuffer = vertBuffer;
110
+ this.groupFragBuffer = this.frag.buffer;
111
+ this.plainGroup = this.device.createBindGroup({
112
+ label: 'glassTint.group',
113
+ layout: this.layouts.plain,
114
+ entries: [
115
+ { binding: VERT.uniforms, resource: { buffer: vertBuffer, size: VERT.uniformSize } },
116
+ {
117
+ binding: FRAG.uniforms,
118
+ resource: { buffer: this.frag.buffer, size: FRAG.uniformSize },
119
+ },
120
+ ],
121
+ });
122
+ }
123
+ return this.plainGroup;
124
+ }
125
+ /** A cutout pane's group, one per map, built the first time that map casts colour. */
126
+ cutoutGroup(vertBuffer, view, sampler) {
127
+ this.group(vertBuffer);
128
+ const held = this.cutoutGroups.get(view);
129
+ if (held !== undefined)
130
+ return held;
131
+ const made = this.device.createBindGroup({
132
+ label: 'glassTint.group.cutout',
133
+ layout: this.layouts.cutout,
134
+ entries: [
135
+ {
136
+ binding: CUTOUT_VERT.uniforms,
137
+ resource: { buffer: vertBuffer, size: CUTOUT_VERT.uniformSize },
138
+ },
139
+ { binding: FRAG.uniforms, resource: { buffer: this.frag.buffer, size: FRAG.uniformSize } },
140
+ { binding: CUTOUT_MAP.texture, resource: view },
141
+ { binding: CUTOUT_MAP.sampler, resource: sampler },
142
+ ],
143
+ });
144
+ this.cutoutGroups.set(view, made);
145
+ return made;
146
+ }
147
+ /** A skinned pane's group, one per palette view. */
148
+ skinnedGroup(vertBuffer, palette) {
149
+ this.group(vertBuffer);
150
+ const held = this.skinnedGroups.get(palette);
151
+ if (held !== undefined)
152
+ return held;
153
+ const made = this.device.createBindGroup({
154
+ label: 'glassTint.group.skinned',
155
+ layout: this.layouts.skinned,
156
+ entries: [
157
+ { binding: PALETTE.texture, resource: palette },
158
+ { binding: VERT.uniforms, resource: { buffer: vertBuffer, size: VERT.uniformSize } },
159
+ { binding: FRAG.uniforms, resource: { buffer: this.frag.buffer, size: FRAG.uniformSize } },
160
+ ],
161
+ });
162
+ this.skinnedGroups.set(palette, made);
163
+ return made;
164
+ }
165
+ /** The pipeline for one kind of caster, cull mode and vertex layout, cached by key. */
166
+ pipeline(cache, kind, key, present, cullMode) {
167
+ const cutout = kind === 'cutout' || kind === 'instancedCutout';
168
+ const instanced = kind === 'instanced' || kind === 'instancedCutout';
169
+ const layout = kind === 'skinned' ? this.layouts.skinned : cutout ? this.layouts.cutout : this.layouts.plain;
170
+ const vertex = kind === 'rigid'
171
+ ? GLASS_TINT_VERT_WGSL
172
+ : kind === 'instanced'
173
+ ? GLASS_TINT_INSTANCED_VERT_WGSL
174
+ : kind === 'skinned'
175
+ ? GLASS_TINT_SKINNED_VERT_WGSL
176
+ : kind === 'cutout'
177
+ ? GLASS_TINT_CUTOUT_VERT_WGSL
178
+ : GLASS_TINT_INSTANCED_CUTOUT_VERT_WGSL;
179
+ const label = `glass-tint|${kind}|${cullMode}|${key}`;
180
+ return cache.get(label, () => ({
181
+ label,
182
+ layout: this.device.createPipelineLayout({ bindGroupLayouts: [layout] }),
183
+ vertex: {
184
+ module: shaderModule(this.device, { label: `glassTint.${kind}.vert`, code: vertex }),
185
+ entryPoint: 'main',
186
+ buffers: vertexBufferLayouts(present, instanced),
187
+ },
188
+ fragment: {
189
+ module: shaderModule(this.device, {
190
+ label: cutout ? 'glassTint.cutout.frag' : 'glassTint.frag',
191
+ code: cutout ? GLASS_TINT_CUTOUT_FRAG_WGSL : GLASS_TINT_FRAG_WGSL,
192
+ }),
193
+ entryPoint: 'main',
194
+ targets: [{ format: 'rgba8unorm', blend: GLASS_TINT_BLEND }],
195
+ },
196
+ /* The depth pass's primitive state exactly — its winding included, for the reason
197
+ `depthPipeline` gives: this map is sampled, not presented. */
198
+ primitive: { topology: 'triangle-list', cullMode, frontFace: 'cw' },
199
+ }));
200
+ }
201
+ dispose() {
202
+ this.frag.dispose();
203
+ }
204
+ }
205
+ const paneScratch = new Float32Array(4);
@@ -59,6 +59,8 @@
59
59
  * generated, so taking it mirrors the picture — and mirrors the winding with it, which is how it
60
60
  * reads as a culling problem. `PassDevice` carries both matrices and says which is which.
61
61
  */
62
+ import { type GlassOptions } from '../../glass.ts';
63
+ import type { GlassShadows } from '../../renderQuality.ts';
62
64
  import { type FogOptions } from '../../fog.ts';
63
65
  import { StreamingScene } from '../../gpudriven/streamScene.ts';
64
66
  import { type GpuDrivenTextures } from '../../gpudriven/materialTable.ts';
@@ -69,7 +71,7 @@ import type { PassContext, PassDefinition, PassDevice, PrepareContext } from '..
69
71
  * Five blocks since 2026-09-17 — the three texture programs, the UV scale and the map strengths
70
72
  * joined the eight — and `gpudriven/materialTable.ts` writes them.
71
73
  */
72
- export declare const MATERIAL_FLOATS = 24;
74
+ export declare const MATERIAL_FLOATS = 28;
73
75
  /** Floats a vertex in the one interleaved buffer: position, normal, colour, uv. */
74
76
  export declare const VERTEX_FLOATS = 12;
75
77
  /**
@@ -186,6 +188,14 @@ export interface GpuDrivenMaterial {
186
188
  * blend and said nothing else gets — visible, and therefore findable.
187
189
  */
188
190
  readonly opacity?: number;
191
+ /**
192
+ * Glass: how much light passes, how milky it is, and the colour it takes — `GlassOptions`, as the
193
+ * forward path's `drawTranslucentMesh` takes it. Absent, or letting nothing through, is not glass.
194
+ *
195
+ * **A glass material is drawn in the blended half whatever `blend` says**, because a visibility
196
+ * buffer holds one surface a pixel and glass is the pane and what is behind it.
197
+ */
198
+ readonly glass?: GlassOptions;
189
199
  }
190
200
  /** What the pass needs told once a frame, before `beginFrame`. */
191
201
  export interface GpuDrivenView {
@@ -288,6 +298,12 @@ export interface GpuDrivenShadowOptions {
288
298
  * texels, and reaches toward the sun as far as the scene does.
289
299
  */
290
300
  readonly followRadius?: number;
301
+ /**
302
+ * What the sun keeps through glass: `RenderQualityOptions.glassShadows`, which a contributed pass
303
+ * is not handed and so takes here. `'full'` by default, as there; `'half'` halves the tint and
304
+ * `'off'` draws a glass material's shadow as nothing, which is what it cast before.
305
+ */
306
+ readonly glass?: GlassShadows;
291
307
  }
292
308
  export declare class GpuDrivenPass implements PassDefinition {
293
309
  readonly label = "driftengine.gpu-driven";
@@ -410,6 +426,30 @@ export declare class GpuDrivenPass implements PassDefinition {
410
426
  private latentClamp;
411
427
  private latentRepeat;
412
428
  private shadowView;
429
+ /** The nearest pane's depth from the sun: the shadow texture's second layer, where glass casts. */
430
+ private glassDepthView;
431
+ /** What the panes let through, as the sun sees them, with the chain frost reads. */
432
+ private glassTintTexture;
433
+ private glassTintTarget;
434
+ private glassTintSampled;
435
+ private glassTintMips;
436
+ /** One white texel, bound where no glass casts, which the lookup reads as none. */
437
+ private glassTintNone;
438
+ private readonly glassMode;
439
+ /** Whether the sun's glass pass runs at all: a glass material, and glass shadows not off. */
440
+ private readonly castsGlass;
441
+ private glassList;
442
+ private glassArgs;
443
+ private glassLodBuffer;
444
+ private readonly lodParamsGlass;
445
+ private shadowCullSettingsGlass;
446
+ private glassLodGroup;
447
+ private glassCullGroup;
448
+ private glassCompactGroup;
449
+ private glassDepthPipeline;
450
+ private glassDepthGroup;
451
+ private glassTintPipeline;
452
+ private glassTintGroup;
413
453
  /**
414
454
  * What is bound as the probe where the frame has none.
415
455
  *
@@ -521,7 +561,18 @@ export declare class GpuDrivenPass implements PassDefinition {
521
561
  private visibilityTexture;
522
562
  private depthTexture;
523
563
  private colourTexture;
564
+ /** Level 0: what the shading writes, the resolve lays over and the blit presents. */
524
565
  private colourView;
566
+ /** Every level, for glass to read at the level its frost picks. Level 0 alone with no glass. */
567
+ private colourSampledView;
568
+ /** The chain glass reads, filled from the shaded picture each frame; null in a scene with none. */
569
+ private colourMips;
570
+ /** Whether any material is glass, which is what earns the colour its chain: see `buildTargets`. */
571
+ private readonly hasGlass;
572
+ /** The pipelines the chain draws with, one a format. */
573
+ private readonly mipPipelineCache;
574
+ private readonly mipPipelines;
575
+ private glassSampler;
525
576
  private visibility;
526
577
  private hzb;
527
578
  private levelStart;
@@ -794,8 +845,16 @@ export declare class GpuDrivenPass implements PassDefinition {
794
845
  * second render pass where it ended, so `shadow` in the stage times is all of it.
795
846
  */
796
847
  private drawShadow;
848
+ /**
849
+ * The sun's glass, after its opaque map: the glass cut from the light, then the panes drawn
850
+ * twice — where the nearest one is, and what they let through, multiplied — and the tint's
851
+ * chain filled for frost to read. The forward path's `flushSunGlass`, in this pipeline's terms.
852
+ */
853
+ private drawShadowGlass;
797
854
  /** One half of the map: the first clears it, the second draws over it and closes the stage's time. */
798
855
  private drawShadowHalf;
856
+ /** The linear sampler glass reads the colour's and the tint's chains through, made once. */
857
+ private glassSamplerFor;
799
858
  private drawPhase;
800
859
  draw(ctx: PassContext): void;
801
860
  dispose(device: PassDevice): void;