@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
@@ -70,6 +70,9 @@ import { FRUSTUM_FLOATS, frustumPlanes } from '../../gpudriven/frustum.js';
70
70
  import { VIS_EMPTY } from '../../gpudriven/visbuffer.js';
71
71
  import { jitterClip } from '../../recon/jitter.js';
72
72
  import { GPU_DRIVEN_ORDERED, GPU_DRIVEN_PASSES, GPU_DRIVEN_RESOURCES, gpuDrivenLive, gpuDrivenRefusal, gpuDrivenSupported, recordGpuDrivenStages, } from '../../gpudriven/pipeline.js';
73
+ import { resolveGlass } from '../../glass.js';
74
+ import { GLASS_TINT_BLEND } from './glassTintPass.js';
75
+ import { SnapshotMips } from './snapshotMips.js';
73
76
  import { createDeps, resetDeps } from '../../frame/deps.js';
74
77
  import { createGraphPasses, createGraphScratch, scheduleGraph } from '../../frame/graphSchedule.js';
75
78
  import { hzbMipCount, hzbMipSize } from '../../gpudriven/hzb.js';
@@ -384,6 +387,30 @@ export class GpuDrivenPass {
384
387
  latentClamp = null;
385
388
  latentRepeat = null;
386
389
  shadowView = null;
390
+ /** The nearest pane's depth from the sun: the shadow texture's second layer, where glass casts. */
391
+ glassDepthView = null;
392
+ /** What the panes let through, as the sun sees them, with the chain frost reads. */
393
+ glassTintTexture = null;
394
+ glassTintTarget = null;
395
+ glassTintSampled = null;
396
+ glassTintMips = null;
397
+ /** One white texel, bound where no glass casts, which the lookup reads as none. */
398
+ glassTintNone = null;
399
+ glassMode;
400
+ /** Whether the sun's glass pass runs at all: a glass material, and glass shadows not off. */
401
+ castsGlass;
402
+ glassList = null;
403
+ glassArgs = null;
404
+ glassLodBuffer = null;
405
+ lodParamsGlass = new Float32Array(8);
406
+ shadowCullSettingsGlass = null;
407
+ glassLodGroup = null;
408
+ glassCullGroup = null;
409
+ glassCompactGroup = null;
410
+ glassDepthPipeline = null;
411
+ glassDepthGroup = null;
412
+ glassTintPipeline = null;
413
+ glassTintGroup = null;
387
414
  /**
388
415
  * What is bound as the probe where the frame has none.
389
416
  *
@@ -497,7 +524,27 @@ export class GpuDrivenPass {
497
524
  visibilityTexture = null;
498
525
  depthTexture = null;
499
526
  colourTexture = null;
527
+ /** Level 0: what the shading writes, the resolve lays over and the blit presents. */
500
528
  colourView = null;
529
+ /** Every level, for glass to read at the level its frost picks. Level 0 alone with no glass. */
530
+ colourSampledView = null;
531
+ /** The chain glass reads, filled from the shaded picture each frame; null in a scene with none. */
532
+ colourMips = null;
533
+ /** Whether any material is glass, which is what earns the colour its chain: see `buildTargets`. */
534
+ hasGlass;
535
+ /** The pipelines the chain draws with, one a format. */
536
+ mipPipelineCache = new Map();
537
+ mipPipelines = {
538
+ get: (key, describe) => {
539
+ const held = this.mipPipelineCache.get(key);
540
+ if (held !== undefined)
541
+ return held;
542
+ const made = this.device.createRenderPipeline(describe());
543
+ this.mipPipelineCache.set(key, made);
544
+ return made;
545
+ },
546
+ };
547
+ glassSampler = null;
501
548
  visibility = null;
502
549
  hzb = null;
503
550
  levelStart = null;
@@ -613,7 +660,10 @@ export class GpuDrivenPass {
613
660
  this.decode = packDecodeTables(this.programs.programs);
614
661
  this.scene = scene;
615
662
  this.materials = materials;
663
+ this.hasGlass = materials.some((material) => resolveGlass(material.glass, glassProbe));
616
664
  this.shadowSize = shadow.mapSize ?? DEFAULT_RENDER_QUALITY.directionalShadowMapSize;
665
+ this.glassMode = shadow.glass ?? DEFAULT_RENDER_QUALITY.glassShadows;
666
+ this.castsGlass = this.hasGlass && this.glassMode !== 'off';
617
667
  this.shadowMaxDistance =
618
668
  shadow.maxDistance ?? DEFAULT_RENDER_QUALITY.directionalShadowMaxDistance;
619
669
  this.shadowMaxSlope = shadow.maxSlope ?? DEFAULT_RENDER_QUALITY.directionalShadowMaxSlope;
@@ -840,6 +890,10 @@ export class GpuDrivenPass {
840
890
  this.shadowArgs = this.storage(device, 'draw shadow', shadowArgs, INDIRECT | COPY_SRC);
841
891
  this.shadowArgsTwo = this.storage(device, 'draw shadow two', shadowArgs, INDIRECT | COPY_SRC);
842
892
  this.shadowSelected = this.empty(device, 'shadow selected', count * 4);
893
+ /* The sun's glass: its own list and arguments, over the light's selection once the map is done. */
894
+ this.glassList = this.empty(device, 'shadow glass list', count * 4);
895
+ this.glassArgs = this.storage(device, 'draw shadow glass', shadowArgs, INDIRECT | COPY_SRC);
896
+ this.glassLodBuffer = this.empty(device, 'lod params glass', 8 * 4);
843
897
  this.shadowKeep = this.empty(device, 'shadow keep', count * 4);
844
898
  this.shadowPlanes = this.empty(device, 'shadow planes', FRUSTUM_FLOATS * 4);
845
899
  this.shadowHistories = [
@@ -862,6 +916,8 @@ export class GpuDrivenPass {
862
916
  this.shadowCullValues[CULL_CONES] = 1;
863
917
  this.shadowCullSettings = this.storage(device, 'cull settings, shadow', this.shadowCullValues);
864
918
  this.shadowCullSettingsTwo = this.storage(device, 'cull settings, shadow two', this.shadowCullValues);
919
+ /* The glass pass's: no pyramid and no cones, since a pane casts from either side. */
920
+ this.shadowCullSettingsGlass = this.storage(device, 'cull settings, shadow glass', this.shadowCullValues);
865
921
  this.shadowCompactSettings = this.storage(device, 'phase shadow', Uint32Array.from([PHASE_ONE, count]));
866
922
  this.shadowCompactSettingsTwo = this.storage(device, 'phase shadow two', Uint32Array.from([PHASE_TWO, count]));
867
923
  /* Nonzero is hidden, and the light hides nothing: see `CULL_INSTANCES_WGSL`. */
@@ -875,11 +931,47 @@ export class GpuDrivenPass {
875
931
  */
876
932
  this.shadowTexture = device.createTexture({
877
933
  label: 'gpu-driven shadow',
878
- size: { width: this.shadowSize, height: this.shadowSize },
934
+ /* A second layer for the nearest pane where a material is glass, and none where none is. */
935
+ size: {
936
+ width: this.shadowSize,
937
+ height: this.shadowSize,
938
+ depthOrArrayLayers: this.castsGlass ? 2 : 1,
939
+ },
879
940
  format: SHADOW_FORMAT,
880
941
  usage: RENDER_ATTACHMENT | TEXTURE_BINDING,
881
942
  });
882
- this.shadowView = this.shadowTexture.createView();
943
+ const layer = (at) => this.shadowTexture.createView({
944
+ dimension: '2d',
945
+ baseArrayLayer: at,
946
+ arrayLayerCount: 1,
947
+ });
948
+ this.shadowView = layer(0);
949
+ this.glassDepthView = this.castsGlass ? layer(1) : null;
950
+ const tintSize = this.glassMode === 'half' ? Math.max(1, this.shadowSize >> 1) : this.shadowSize;
951
+ this.glassTintTexture = this.castsGlass
952
+ ? device.createTexture({
953
+ label: 'gpu-driven glass tint',
954
+ size: { width: tintSize, height: tintSize },
955
+ format: 'rgba8unorm',
956
+ mipLevelCount: Math.floor(Math.log2(tintSize)) + 1,
957
+ usage: RENDER_ATTACHMENT | TEXTURE_BINDING,
958
+ })
959
+ : null;
960
+ if (this.glassTintTexture !== null) {
961
+ this.glassTintTarget = this.glassTintTexture.createView({
962
+ baseMipLevel: 0,
963
+ mipLevelCount: 1,
964
+ });
965
+ this.glassTintSampled = this.glassTintTexture.createView();
966
+ this.glassTintMips = new SnapshotMips(device, this.mipPipelines, this.glassTintTexture, 'rgba8unorm', null, 'gpu-driven glass tint mips');
967
+ }
968
+ this.glassTintNone = device.createTexture({
969
+ label: 'gpu-driven no glass',
970
+ size: { width: 1, height: 1 },
971
+ format: 'rgba8unorm',
972
+ usage: TEXTURE_BINDING | TEXTURE_COPY_DST,
973
+ });
974
+ device.queue.writeTexture({ texture: this.glassTintNone }, new Uint8Array([255, 255, 255, 255]), { bytesPerRow: 4 }, { width: 1, height: 1 });
883
975
  this.emptyEnvironment = device.createTexture({
884
976
  label: 'gpu-driven no environment',
885
977
  size: { width: 1, height: 1, depthOrArrayLayers: 1 },
@@ -1171,6 +1263,37 @@ export class GpuDrivenPass {
1171
1263
  });
1172
1264
  this.shadowGroup = shadowRasterFor(this.shadowList);
1173
1265
  this.shadowGroupTwo = shadowRasterFor(this.shadowListTwo);
1266
+ /*
1267
+ * **The nearest pane, from the sun: the same raster, culling nothing**, since a pane casts from
1268
+ * either side; into the shadow texture's second layer. Built only where glass casts.
1269
+ */
1270
+ if (this.castsGlass) {
1271
+ this.glassDepthPipeline = device.createRenderPipeline({
1272
+ label: 'gpu-driven shadow glass',
1273
+ layout: 'auto',
1274
+ vertex: { module: raster, entryPoint: 'vertexMain' },
1275
+ primitive: { topology: 'triangle-list', cullMode: 'none', frontFace: 'cw' },
1276
+ depthStencil: {
1277
+ format: SHADOW_FORMAT,
1278
+ depthWriteEnabled: true,
1279
+ depthCompare: 'less',
1280
+ depthBias: 4,
1281
+ depthBiasSlopeScale: 1.1,
1282
+ },
1283
+ });
1284
+ this.glassDepthGroup = device.createBindGroup({
1285
+ layout: this.glassDepthPipeline.getBindGroupLayout(0),
1286
+ entries: [
1287
+ { binding: 0, resource: { buffer: this.shadowMatrix } },
1288
+ { binding: 1, resource: { buffer: this.glassList } },
1289
+ { binding: 2, resource: { buffer: this.clusterMeta } },
1290
+ { binding: 3, resource: { buffer: this.indices } },
1291
+ { binding: 4, resource: { buffer: this.vertices } },
1292
+ { binding: 5, resource: { buffer: this.transformBuffer } },
1293
+ { binding: 6, resource: { buffer: this.materialOf } },
1294
+ ],
1295
+ });
1296
+ }
1174
1297
  /*
1175
1298
  * **The transparent half's raster: one indirect draw into two targets at once.**
1176
1299
  *
@@ -1236,6 +1359,38 @@ export class GpuDrivenPass {
1236
1359
  },
1237
1360
  });
1238
1361
  this.watchBlendPipeline(device);
1362
+ /*
1363
+ * **What a pane lets through, from the sun**: the blended raster's vertex stage handed the
1364
+ * light's matrix and the glass list, and \`glassTintFrag\` for the colour — white target, a
1365
+ * multiplying blend, no depth, so every pane on a ray counts in any order.
1366
+ */
1367
+ if (this.castsGlass) {
1368
+ this.glassTintPipeline = device.createRenderPipeline({
1369
+ label: 'gpu-driven shadow tint',
1370
+ layout: 'auto',
1371
+ vertex: { module: blend, entryPoint: 'blendVert' },
1372
+ fragment: {
1373
+ module: blend,
1374
+ entryPoint: 'glassTintFrag',
1375
+ targets: [{ format: 'rgba8unorm', blend: GLASS_TINT_BLEND }],
1376
+ },
1377
+ primitive: { topology: 'triangle-list', cullMode: 'none' },
1378
+ });
1379
+ this.glassTintGroup = device.createBindGroup({
1380
+ layout: this.glassTintPipeline.getBindGroupLayout(0),
1381
+ entries: [
1382
+ { binding: 0, resource: { buffer: this.shadowMatrix } },
1383
+ { binding: 1, resource: { buffer: this.glassList } },
1384
+ { binding: 2, resource: { buffer: this.clusterMeta } },
1385
+ { binding: 3, resource: { buffer: this.indices } },
1386
+ { binding: 4, resource: { buffer: this.vertices } },
1387
+ { binding: 5, resource: { buffer: this.transformBuffer } },
1388
+ { binding: 6, resource: { buffer: this.materialOf } },
1389
+ { binding: 7, resource: { buffer: this.materialTable } },
1390
+ { binding: 8, resource: { buffer: this.frameBuffer } },
1391
+ ],
1392
+ });
1393
+ }
1239
1394
  /*
1240
1395
  * **Premultiplied over, where `renderer.ts`'s composite is a lerp**, and `blendRaster.wgsl.ts`
1241
1396
  * carries why: this pipeline's colour target has a coverage mask in its alpha, which the blit
@@ -1402,6 +1557,39 @@ export class GpuDrivenPass {
1402
1557
  shadowCullFor(this.shadowCullSettings),
1403
1558
  shadowCullFor(this.shadowCullSettingsTwo),
1404
1559
  ];
1560
+ /*
1561
+ * **The sun's glass cut: the light's selection again, of the glass alone**, once the map is
1562
+ * drawn and those buffers are free. The camera's level of detail, as the map's; every pane in
1563
+ * the light's frustum kept, as the blended half keeps every pane — so compacted as phase two
1564
+ * against a history of zeros, exactly as that half is.
1565
+ */
1566
+ if (this.castsGlass) {
1567
+ this.glassLodGroup = device.createBindGroup({
1568
+ layout: this.lodPipeline.getBindGroupLayout(0),
1569
+ entries: [
1570
+ { binding: 0, resource: { buffer: this.glassLodBuffer } },
1571
+ { binding: 1, resource: { buffer: this.clusterLod } },
1572
+ { binding: 2, resource: { buffer: this.shadowSelected } },
1573
+ { binding: 3, resource: { buffer: this.clusterMeta } },
1574
+ { binding: 4, resource: { buffer: this.instanceNone } },
1575
+ { binding: 5, resource: { buffer: this.materialOf } },
1576
+ { binding: 6, resource: { buffer: this.materialTable } },
1577
+ ],
1578
+ });
1579
+ this.glassCullGroup = shadowCullFor(this.shadowCullSettingsGlass);
1580
+ this.glassCompactGroup = device.createBindGroup({
1581
+ layout: this.compactPipeline.getBindGroupLayout(0),
1582
+ entries: [
1583
+ { binding: 0, resource: { buffer: this.blendCompactSettings } },
1584
+ { binding: 1, resource: { buffer: this.shadowSelected } },
1585
+ { binding: 2, resource: { buffer: this.shadowKeep } },
1586
+ { binding: 3, resource: { buffer: this.zeroHistory } },
1587
+ { binding: 4, resource: { buffer: this.glassList } },
1588
+ { binding: 5, resource: { buffer: this.historySink } },
1589
+ { binding: 6, resource: { buffer: this.glassArgs } },
1590
+ ],
1591
+ });
1592
+ }
1405
1593
  this.shadowSeedGroup = device.createBindGroup({
1406
1594
  layout: this.shadowSeedPipeline.getBindGroupLayout(0),
1407
1595
  entries: [
@@ -1582,11 +1770,22 @@ export class GpuDrivenPass {
1582
1770
  label: 'gpu-driven colour',
1583
1771
  size: { width: stride, height },
1584
1772
  format: 'rgba16float',
1773
+ /*
1774
+ * **A chain only where a material is glass**, which a frosted pane reads a coarser level of,
1775
+ * as the forward path's refraction copy does. A scene with no glass allocates exactly the one
1776
+ * level it always did.
1777
+ */
1778
+ mipLevelCount: this.hasGlass ? Math.floor(Math.log2(Math.max(stride, height))) + 1 : 1,
1585
1779
  /* `RENDER_ATTACHMENT` only so a clear-only render pass can empty it each frame: a storage
1586
1780
  texture has no clear, and the alternative is a dispatch over every pixel. */
1587
1781
  usage: STORAGE_BINDING | TEXTURE_BINDING | RENDER_ATTACHMENT,
1588
1782
  });
1589
- this.colourView = this.colourTexture.createView();
1783
+ /* A storage binding takes one level, so everything that writes or presents reads level 0. */
1784
+ this.colourView = this.colourTexture.createView({ baseMipLevel: 0, mipLevelCount: 1 });
1785
+ this.colourSampledView = this.colourTexture.createView();
1786
+ this.colourMips = this.hasGlass
1787
+ ? new SnapshotMips(device, this.mipPipelines, this.colourTexture, 'rgba16float', null, 'gpu-driven colour mips')
1788
+ : null;
1590
1789
  this.oitAccum = device.createTexture({
1591
1790
  label: 'gpu-driven oit accum',
1592
1791
  size: { width: stride, height },
@@ -1726,12 +1925,22 @@ export class GpuDrivenPass {
1726
1925
  { binding: 16, resource: this.latentRepeat },
1727
1926
  { binding: 17, resource: { buffer: this.decodeNodes } },
1728
1927
  { binding: 18, resource: { buffer: this.decodeWeights } },
1928
+ /* The sun's glass, or the opaque map and a white texel where none casts. */
1929
+ {
1930
+ binding: 19,
1931
+ resource: this.glassDepthView ?? this.shadowView,
1932
+ },
1933
+ {
1934
+ binding: 20,
1935
+ resource: this.glassTintSampled ?? this.glassTintNone.createView(),
1936
+ },
1937
+ { binding: 21, resource: this.glassSamplerFor(device) },
1729
1938
  ],
1730
1939
  });
1731
1940
  this.blitGroup = device.createBindGroup({
1732
1941
  layout: this.blitPipeline.getBindGroupLayout(0),
1733
1942
  entries: [
1734
- { binding: 0, resource: this.colourTexture.createView() },
1943
+ { binding: 0, resource: this.colourView },
1735
1944
  /* The depth the raster wrote, sampled so the blit can hand it to the frame. */
1736
1945
  ...(this.presentDepth
1737
1946
  ? [
@@ -1774,6 +1983,12 @@ export class GpuDrivenPass {
1774
1983
  { binding: 13, resource: this.latentRepeat },
1775
1984
  { binding: 14, resource: { buffer: this.decodeNodes } },
1776
1985
  { binding: 15, resource: { buffer: this.decodeWeights } },
1986
+ /* What the opaque half drew, for glass to show: `blendRaster.wgsl.ts`. */
1987
+ { binding: 16, resource: this.colourSampledView },
1988
+ {
1989
+ binding: 17,
1990
+ resource: this.glassSamplerFor(device),
1991
+ },
1777
1992
  ],
1778
1993
  });
1779
1994
  /*
@@ -1979,6 +2194,16 @@ export class GpuDrivenPass {
1979
2194
  queue.writeBuffer(this.shadowCullSettings, 0, this.shadowCullValues);
1980
2195
  this.shadowCullValues[3] = this.occlusion ? 1 : 0;
1981
2196
  queue.writeBuffer(this.shadowCullSettingsTwo, 0, this.shadowCullValues);
2197
+ if (this.castsGlass) {
2198
+ /* No pyramid and no cones, and the cones put back for the next frame's first write. */
2199
+ this.shadowCullValues[3] = 0;
2200
+ this.shadowCullValues[CULL_CONES] = 0;
2201
+ queue.writeBuffer(this.shadowCullSettingsGlass, 0, this.shadowCullValues);
2202
+ this.shadowCullValues[CULL_CONES] = 1;
2203
+ this.lodParamsGlass.set(this.lodParamsBlend);
2204
+ this.lodParamsGlass[7] = 1;
2205
+ queue.writeBuffer(this.glassLodBuffer, 0, this.lodParamsGlass);
2206
+ }
1982
2207
  queue.writeBuffer(this.planesBuffer, 0, this.planes);
1983
2208
  queue.writeBuffer(this.frameBuffer, 0, params);
1984
2209
  queue.writeBuffer(this.lodBuffer, 0, this.lodParams);
@@ -2038,7 +2263,9 @@ export class GpuDrivenPass {
2038
2263
  * one: the table is written when the pass is built. A scene with nothing blended schedules the
2039
2264
  * three transparent stages away entirely and encodes the frame it encoded before they existed.
2040
2265
  */
2041
- this.shape.blended = this.materials.some((material) => material.blend === true);
2266
+ /* Glass is the blended half's whatever its flag says: a visibility buffer holds one surface. */
2267
+ this.shape.blended =
2268
+ this.hasGlass || this.materials.some((material) => material.blend === true);
2042
2269
  /* `instanceCount` is the one word the compaction touches; the other three never change. */
2043
2270
  encoder.clearBuffer(argsOne, 4, 4);
2044
2271
  encoder.clearBuffer(argsTwo, 4, 4);
@@ -2212,6 +2439,8 @@ export class GpuDrivenPass {
2212
2439
  */
2213
2440
  if (!this.blendUsable || this.blendPipeline === null || this.blendRasterGroup === null)
2214
2441
  return;
2442
+ /* The shaded picture's chain, which glass reads: after `shade`, before the panes. */
2443
+ this.colourMips?.record(encoder);
2215
2444
  const pass = encoder.beginRenderPass({
2216
2445
  label: 'gpu-driven blend draw',
2217
2446
  colorAttachments: [
@@ -2519,6 +2748,57 @@ export class GpuDrivenPass {
2519
2748
  second.end();
2520
2749
  this.drawShadowHalf(encoder, 1, writes);
2521
2750
  this.shadowFlipped = !this.shadowFlipped;
2751
+ if (this.castsGlass && this.blendUsable)
2752
+ this.drawShadowGlass(encoder, groups);
2753
+ }
2754
+ /**
2755
+ * The sun's glass, after its opaque map: the glass cut from the light, then the panes drawn
2756
+ * twice — where the nearest one is, and what they let through, multiplied — and the tint's
2757
+ * chain filled for frost to read. The forward path's `flushSunGlass`, in this pipeline's terms.
2758
+ */
2759
+ drawShadowGlass(encoder, groups) {
2760
+ encoder.clearBuffer(this.glassArgs, 4, 4);
2761
+ const cut = encoder.beginComputePass({ label: 'gpu-driven shadow glass cut' });
2762
+ cut.setPipeline(this.lodPipeline);
2763
+ cut.setBindGroup(0, this.glassLodGroup);
2764
+ cut.dispatchWorkgroups(groups);
2765
+ cut.setPipeline(this.cullPipeline);
2766
+ cut.setBindGroup(0, this.glassCullGroup);
2767
+ cut.dispatchWorkgroups(groups);
2768
+ cut.setPipeline(this.compactPipeline);
2769
+ cut.setBindGroup(0, this.glassCompactGroup);
2770
+ cut.dispatchWorkgroups(groups);
2771
+ cut.end();
2772
+ const depth = encoder.beginRenderPass({
2773
+ label: 'gpu-driven shadow glass',
2774
+ colorAttachments: [],
2775
+ depthStencilAttachment: {
2776
+ view: this.glassDepthView,
2777
+ depthClearValue: SHADOW_DEPTH_CLEAR,
2778
+ depthLoadOp: 'clear',
2779
+ depthStoreOp: 'store',
2780
+ },
2781
+ });
2782
+ depth.setPipeline(this.glassDepthPipeline);
2783
+ depth.setBindGroup(0, this.glassDepthGroup);
2784
+ depth.drawIndirect(this.glassArgs, 0);
2785
+ depth.end();
2786
+ const tint = encoder.beginRenderPass({
2787
+ label: 'gpu-driven shadow tint',
2788
+ colorAttachments: [
2789
+ {
2790
+ view: this.glassTintTarget,
2791
+ clearValue: { r: 1, g: 1, b: 1, a: 1 },
2792
+ loadOp: 'clear',
2793
+ storeOp: 'store',
2794
+ },
2795
+ ],
2796
+ });
2797
+ tint.setPipeline(this.glassTintPipeline);
2798
+ tint.setBindGroup(0, this.glassTintGroup);
2799
+ tint.drawIndirect(this.glassArgs, 0);
2800
+ tint.end();
2801
+ this.glassTintMips?.record(encoder);
2522
2802
  }
2523
2803
  /** One half of the map: the first clears it, the second draws over it and closes the stage's time. */
2524
2804
  drawShadowHalf(encoder, half, writes) {
@@ -2540,6 +2820,18 @@ export class GpuDrivenPass {
2540
2820
  pass.drawIndirect((half === 0 ? this.shadowArgs : this.shadowArgsTwo), 0);
2541
2821
  pass.end();
2542
2822
  }
2823
+ /** The linear sampler glass reads the colour's and the tint's chains through, made once. */
2824
+ glassSamplerFor(device) {
2825
+ this.glassSampler ??= device.createSampler({
2826
+ label: 'gpu-driven glass',
2827
+ magFilter: 'linear',
2828
+ minFilter: 'linear',
2829
+ mipmapFilter: 'linear',
2830
+ addressModeU: 'clamp-to-edge',
2831
+ addressModeV: 'clamp-to-edge',
2832
+ });
2833
+ return this.glassSampler;
2834
+ }
2543
2835
  drawPhase(encoder, phase, clear) {
2544
2836
  const pass = encoder.beginRenderPass({
2545
2837
  label: `gpu-driven phase ${phase + 1}`,
@@ -2597,6 +2889,8 @@ export class GpuDrivenPass {
2597
2889
  return;
2598
2890
  this.disposeTargets();
2599
2891
  this.shadowTexture?.destroy();
2892
+ this.glassTintTexture?.destroy();
2893
+ this.glassTintNone?.destroy();
2600
2894
  this.emptyEnvironment?.destroy();
2601
2895
  this.latents?.destroy();
2602
2896
  this.queries?.destroy();
@@ -2635,6 +2929,10 @@ export class GpuDrivenPass {
2635
2929
  this.shadowListTwo,
2636
2930
  this.shadowSelected,
2637
2931
  this.shadowKeep,
2932
+ this.glassList,
2933
+ this.glassArgs,
2934
+ this.glassLodBuffer,
2935
+ this.shadowCullSettingsGlass,
2638
2936
  this.shadowPlanes,
2639
2937
  this.shadowCullSettings,
2640
2938
  this.shadowCullSettingsTwo,
@@ -2678,3 +2976,5 @@ function multiply(out, a, b) {
2678
2976
  }
2679
2977
  }
2680
2978
  }
2979
+ /** Where `hasGlass` reads each material's glass through; only the answer is kept. */
2980
+ const glassProbe = { transmission: 0, frost: 0, tint: [1, 1, 1] };
@@ -16,12 +16,23 @@ export declare class GpuInstancedBatch {
16
16
  readonly capacity: number;
17
17
  private readonly staging;
18
18
  constructor(device: GPUDevice, mesh: GpuMesh, capacity: number, label: string);
19
+ /** Last frame's packed slots, for a reconstruction; made on first use by the renderer. */
20
+ previousBuffer: GPUBuffer | null;
21
+ /** How many slots the upload before the last one held, and how many the last one holds. */
22
+ previousCount: number;
23
+ liveCount: number;
24
+ /** The frame of the last upload. See `changeFrames.ts`. */
25
+ changed: number;
19
26
  /**
20
27
  * Pack and push `data.count` instances, in one write.
21
28
  *
22
29
  * Only the live prefix, for the reason `uploadScatter` gives: the rest is not drawn and
23
30
  * uploading it would be per-frame waste with nothing reading it.
31
+ *
32
+ * **Given `previous`, the placement still in the staging array — the last upload, which is last
33
+ * frame's — is written there first**, so a reconstruction can pair each slot with where it was.
34
+ * `writeBuffer` copies at the call, so packing afterwards cannot reach that copy.
24
35
  */
25
- upload(queue: GPUQueue, data: MeshInstances): void;
36
+ upload(queue: GPUQueue, data: MeshInstances, previous?: GPUBuffer | null): void;
26
37
  dispose(): void;
27
38
  }
@@ -30,20 +30,39 @@ export class GpuInstancedBatch {
30
30
  usage: USAGE_VERTEX,
31
31
  });
32
32
  }
33
+ /** Last frame's packed slots, for a reconstruction; made on first use by the renderer. */
34
+ previousBuffer = null;
35
+ /** How many slots the upload before the last one held, and how many the last one holds. */
36
+ previousCount = 0;
37
+ liveCount = 0;
38
+ /** The frame of the last upload. See `changeFrames.ts`. */
39
+ changed = -1;
33
40
  /**
34
41
  * Pack and push `data.count` instances, in one write.
35
42
  *
36
43
  * Only the live prefix, for the reason `uploadScatter` gives: the rest is not drawn and
37
44
  * uploading it would be per-frame waste with nothing reading it.
45
+ *
46
+ * **Given `previous`, the placement still in the staging array — the last upload, which is last
47
+ * frame's — is written there first**, so a reconstruction can pair each slot with where it was.
48
+ * `writeBuffer` copies at the call, so packing afterwards cannot reach that copy.
38
49
  */
39
- upload(queue, data) {
50
+ upload(queue, data, previous = null) {
40
51
  const count = Math.min(data.count, this.capacity);
52
+ if (previous !== null) {
53
+ if (this.liveCount > 0) {
54
+ queue.writeBuffer(previous, 0, this.staging, 0, this.liveCount * INSTANCE_FLOATS);
55
+ }
56
+ this.previousCount = this.liveCount;
57
+ }
58
+ this.liveCount = count;
41
59
  if (count === 0)
42
60
  return;
43
61
  packInstances(data, this.staging);
44
62
  queue.writeBuffer(this.buffer, 0, this.staging, 0, count * INSTANCE_FLOATS);
45
63
  }
46
64
  dispose() {
65
+ this.previousBuffer?.destroy();
47
66
  this.buffer.destroy();
48
67
  }
49
68
  }
@@ -0,0 +1,26 @@
1
+ import type { DrawCommand } from './drawCommand.ts';
2
+ export declare class LatePass {
3
+ private readonly device;
4
+ private readonly commands;
5
+ private depth;
6
+ private depthTextureView;
7
+ private readonly layout;
8
+ private readonly pipeline;
9
+ private readonly sizes;
10
+ private readonly sizeStaging;
11
+ private group;
12
+ private groupSource;
13
+ constructor(device: GPUDevice);
14
+ /** The output-size depth, (re)made when the output changes size. */
15
+ 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;
18
+ /** A command slot for one late draw. Every field a caller uses it fills; see `takeCommand`. */
19
+ take(): DrawCommand;
20
+ /** Late draws recorded this frame. */
21
+ get pending(): number;
22
+ /** Every late draw, in the order it was recorded, through the renderer's own issue. */
23
+ replay(pass: GPURenderPassEncoder, issue: (pass: GPURenderPassEncoder, command: DrawCommand) => void): void;
24
+ reset(): void;
25
+ dispose(): void;
26
+ }