@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
@@ -34,6 +34,16 @@ export class PipelineCache {
34
34
  */
35
35
  readonly sampleCount: number;
36
36
 
37
+ /**
38
+ * Whether the lit pipelines here read glass in their shadows: `glassShadows` other than `'off'`.
39
+ *
40
+ * **Carried by the cache for the reason the format is**: one value every lit pipeline is built
41
+ * with, chosen once by the renderer. It is the `GLASS_SHADOWS` override the generated lit shader
42
+ * branches on (see `FlatShaderOptions.glassShadows`), so off specialises the lookups away rather
43
+ * than leaving code that returns at its first line and still costs the pass its registers.
44
+ */
45
+ readonly glassShadows: boolean;
46
+
37
47
  private readonly device: GPUDevice;
38
48
  private readonly pipelines = new Map<string, GPURenderPipeline>();
39
49
  /**
@@ -52,10 +62,11 @@ export class PipelineCache {
52
62
  */
53
63
  private readonly compiling = new Map<string, Promise<GPURenderPipeline>>();
54
64
 
55
- constructor(device: GPUDevice, format: GPUTextureFormat, sampleCount = 1) {
65
+ constructor(device: GPUDevice, format: GPUTextureFormat, sampleCount = 1, glassShadows = true) {
56
66
  this.device = device;
57
67
  this.format = format;
58
68
  this.sampleCount = sampleCount;
69
+ this.glassShadows = glassShadows;
59
70
  }
60
71
 
61
72
  /** How many distinct pipelines have been built. Read by tests and diagnostics. */
@@ -0,0 +1,233 @@
1
+ /**
2
+ * What a lamp's light keeps through glass, on this device: an `rgba8unorm` array of one layer a
3
+ * light, the face-sized colour scratch a face's glass is multiplied into, and the resolve that
4
+ * copies a face into its octahedral region. The twin of `pointGlassTint.ts`, which says what the
5
+ * channels hold and why the chain exists.
6
+ *
7
+ * **The chain is a `SnapshotMips` per layer**, made the first time that layer is touched and
8
+ * recorded once a round for the layers the round touched — a layer's levels are its own
9
+ * subresources here, so nothing is staged the way the other backend must.
10
+ */
11
+ import { mat3 } from 'gl-matrix';
12
+
13
+ import { pointShadowFaceRotation } from '../../pointShadowImage.ts';
14
+ import {
15
+ OCTAHEDRAL_RESOLVE_TINT_FRAG_WGSL,
16
+ OCTAHEDRAL_RESOLVE_VERT_WGSL,
17
+ OCTAHEDRALRESOLVE_BINDINGS,
18
+ } from '../../shaders/generated/octahedralResolve.wgsl.ts';
19
+ import { SnapshotMips } from './snapshotMips.ts';
20
+ import type { MipPipelines } from './surfaceTexturePass.ts';
21
+ import { UniformRing } from './uniformRing.ts';
22
+
23
+ const USAGE_RENDER_ATTACHMENT = 0x10;
24
+ const USAGE_TEXTURE_BINDING = 0x4;
25
+ const USAGE_UNIFORM_DST = 0x40 | 0x8; // UNIFORM | COPY_DST
26
+ const VISIBILITY_FRAGMENT = 0x2;
27
+ const TINT = OCTAHEDRALRESOLVE_BINDINGS.OCTAHEDRAL_RESOLVE_TINT_FRAG;
28
+
29
+ const faceRotation = mat3.create();
30
+ const rotationColumn = new Float32Array(3);
31
+
32
+ export class GpuPointGlassTint {
33
+ readonly texture: GPUTexture;
34
+ /** The whole array, for the lit pass. */
35
+ readonly view: GPUTextureView;
36
+ /** The colour scratch a face's glass is multiplied into. */
37
+ readonly scratchView: GPUTextureView;
38
+ readonly edge: number;
39
+
40
+ private readonly device: GPUDevice;
41
+ private readonly pipelines: MipPipelines;
42
+ private readonly scratch: GPUTexture;
43
+ private readonly layerViews: GPUTextureView[] = [];
44
+ private readonly mips: (SnapshotMips | undefined)[] = [];
45
+ private readonly pipeline: GPURenderPipeline;
46
+ private readonly bindGroup: GPUBindGroup;
47
+ private readonly ring: UniformRing;
48
+ private readonly heldFaces: Uint8Array;
49
+ private readonly dirty: Uint8Array;
50
+
51
+ constructor(
52
+ device: GPUDevice,
53
+ pipelines: MipPipelines,
54
+ faceSize: number,
55
+ edge: number,
56
+ layers: number,
57
+ /** Resolves a frame can make: the array's own figure, since every face resolves once. */
58
+ slots: number,
59
+ encoder: GPUCommandEncoder,
60
+ ) {
61
+ this.device = device;
62
+ this.pipelines = pipelines;
63
+ this.edge = edge;
64
+ this.heldFaces = new Uint8Array(layers);
65
+ /* Every layer's chain is filled once at birth, so a level nothing has touched is white too. */
66
+ this.dirty = new Uint8Array(layers).fill(1);
67
+ this.texture = device.createTexture({
68
+ label: 'pointShadow.tint',
69
+ size: [edge, edge, layers],
70
+ format: 'rgba8unorm',
71
+ mipLevelCount: Math.floor(Math.log2(Math.max(1, edge))) + 1,
72
+ usage: USAGE_RENDER_ATTACHMENT | USAGE_TEXTURE_BINDING,
73
+ });
74
+ this.view = this.texture.createView({ dimension: '2d-array' });
75
+ for (let layer = 0; layer < layers; layer++) {
76
+ this.layerViews.push(
77
+ this.texture.createView({
78
+ label: `pointShadow.tint.layer${layer}`,
79
+ dimension: '2d',
80
+ baseArrayLayer: layer,
81
+ arrayLayerCount: 1,
82
+ baseMipLevel: 0,
83
+ mipLevelCount: 1,
84
+ }),
85
+ );
86
+ }
87
+ this.scratch = device.createTexture({
88
+ label: 'pointShadow.tint.scratch',
89
+ size: [faceSize, faceSize],
90
+ format: 'rgba8unorm',
91
+ usage: USAGE_RENDER_ATTACHMENT | USAGE_TEXTURE_BINDING,
92
+ });
93
+ this.scratchView = this.scratch.createView();
94
+
95
+ this.ring = new UniformRing(
96
+ device,
97
+ TINT.uniformSize,
98
+ slots,
99
+ USAGE_UNIFORM_DST,
100
+ 'pointShadow.tint.uniforms',
101
+ );
102
+ const layout = device.createBindGroupLayout({
103
+ label: 'pointShadow.tint.resolve.layout',
104
+ entries: [
105
+ {
106
+ binding: TINT.uniforms,
107
+ visibility: VISIBILITY_FRAGMENT,
108
+ buffer: { type: 'uniform', hasDynamicOffset: true, minBindingSize: TINT.uniformSize },
109
+ },
110
+ {
111
+ binding: TINT.textures.uFace.texture,
112
+ visibility: VISIBILITY_FRAGMENT,
113
+ texture: { sampleType: 'float' },
114
+ },
115
+ {
116
+ binding: TINT.textures.uFace.sampler,
117
+ visibility: VISIBILITY_FRAGMENT,
118
+ sampler: { type: 'non-filtering' },
119
+ },
120
+ ],
121
+ });
122
+ this.bindGroup = device.createBindGroup({
123
+ label: 'pointShadow.tint.resolve',
124
+ layout,
125
+ entries: [
126
+ { binding: TINT.uniforms, resource: { buffer: this.ring.buffer, size: TINT.uniformSize } },
127
+ { binding: TINT.textures.uFace.texture, resource: this.scratchView },
128
+ {
129
+ binding: TINT.textures.uFace.sampler,
130
+ resource: device.createSampler({ label: 'pointShadow.tint.resolve.sampler' }),
131
+ },
132
+ ],
133
+ });
134
+ this.pipeline = device.createRenderPipeline({
135
+ label: 'pointShadow.tint.resolve',
136
+ layout: device.createPipelineLayout({ bindGroupLayouts: [layout] }),
137
+ vertex: {
138
+ module: device.createShaderModule({ code: OCTAHEDRAL_RESOLVE_VERT_WGSL }),
139
+ entryPoint: 'main',
140
+ },
141
+ fragment: {
142
+ module: device.createShaderModule({ code: OCTAHEDRAL_RESOLVE_TINT_FRAG_WGSL }),
143
+ entryPoint: 'main',
144
+ targets: [{ format: 'rgba8unorm' }],
145
+ },
146
+ primitive: { topology: 'triangle-list' },
147
+ });
148
+
149
+ /* Every layer born clear: a light no pane has reached lets everything through. */
150
+ for (const view of this.layerViews) {
151
+ encoder
152
+ .beginRenderPass({
153
+ label: 'pointShadow.tint.clear',
154
+ colorAttachments: [{ view, loadOp: 'clear', clearValue: [1, 1, 1, 1], storeOp: 'store' }],
155
+ })
156
+ .end();
157
+ }
158
+ }
159
+
160
+ /** Whether `face` of `layer` holds glass: a face that does must be cleared before it is reused. */
161
+ held(layer: number, face: number): boolean {
162
+ return (((this.heldFaces[layer] ?? 0) >> face) & 1) === 1;
163
+ }
164
+
165
+ /** Copy the colour scratch into `face`'s region of `layer`, and remember whether it held glass. */
166
+ resolve(encoder: GPUCommandEncoder, layer: number, face: number, drawn: boolean): void {
167
+ const view = this.layerViews[layer];
168
+ if (view === undefined) return;
169
+ const slot = this.ring.allocate();
170
+ if (slot === null) return;
171
+ pointShadowFaceRotation(face, faceRotation);
172
+ /* WGSL pads a mat3x3 to three four-float columns; the generated struct expects that. */
173
+ const rotation = TINT.fields.uFaceRotation.offset;
174
+ for (let column = 0; column < 3; column++) {
175
+ rotationColumn[0] = faceRotation[column * 3] as number;
176
+ rotationColumn[1] = faceRotation[column * 3 + 1] as number;
177
+ rotationColumn[2] = faceRotation[column * 3 + 2] as number;
178
+ this.ring.writeFloats(slot, rotation + column * 16, rotationColumn);
179
+ }
180
+ this.ring.writeInt(slot, TINT.fields.uFaceIndex.offset, face);
181
+ this.ring.writeFloat(slot, TINT.fields.uEdge.offset, this.edge);
182
+
183
+ const pass = encoder.beginRenderPass({
184
+ label: 'pointShadow.tint.resolve',
185
+ colorAttachments: [{ view, loadOp: 'load', storeOp: 'store' }],
186
+ });
187
+ pass.setPipeline(this.pipeline);
188
+ pass.setBindGroup(0, this.bindGroup, [slot]);
189
+ pass.draw(3);
190
+ pass.end();
191
+
192
+ const bit = 1 << face;
193
+ const was = this.heldFaces[layer] ?? 0;
194
+ this.heldFaces[layer] = drawn ? was | bit : was & ~bit;
195
+ this.dirty[layer] = 1;
196
+ }
197
+
198
+ /** The chain frost reads, for every layer touched since the last call. */
199
+ recordMips(encoder: GPUCommandEncoder): void {
200
+ for (let layer = 0; layer < this.dirty.length; layer++) {
201
+ if (this.dirty[layer] !== 1) continue;
202
+ this.dirty[layer] = 0;
203
+ let chain = this.mips[layer];
204
+ if (chain === undefined) {
205
+ chain = new SnapshotMips(
206
+ this.device,
207
+ this.pipelines,
208
+ this.texture,
209
+ 'rgba8unorm',
210
+ layer,
211
+ 'pointShadow.tint.mips',
212
+ );
213
+ this.mips[layer] = chain;
214
+ }
215
+ chain.record(encoder);
216
+ }
217
+ }
218
+
219
+ beginFrame(): void {
220
+ this.ring.reset();
221
+ }
222
+
223
+ /** Before the round's submit: the slots are written and must be up before any pass reads. */
224
+ flush(): void {
225
+ this.ring.flush();
226
+ }
227
+
228
+ dispose(): void {
229
+ this.texture.destroy();
230
+ this.scratch.destroy();
231
+ this.ring.dispose();
232
+ }
233
+ }
@@ -18,6 +18,8 @@ import {
18
18
  OCTAHEDRAL_RESOLVE_VERT_WGSL,
19
19
  } from '../../shaders/generated/octahedralResolve.wgsl.ts';
20
20
  import { SHADOW_FORMAT } from './depthPass.ts';
21
+ import { GpuPointGlassTint } from './pointGlassTintPass.ts';
22
+ import type { MipPipelines } from './surfaceTexturePass.ts';
21
23
  import { UniformRing } from './uniformRing.ts';
22
24
 
23
25
  /**
@@ -47,6 +49,9 @@ const VISIBILITY_FRAGMENT = 0x2;
47
49
  * silently not landing in its layer.
48
50
  */
49
51
  const RESOLVE_SLOTS = (POINT_SHADOW_POOL + LIVE_POINT_SHADOW_MAPS) * FACE_COUNT;
52
+ /** Twice once glass arrives: a face resolves its opaque depth and then its glass. */
53
+ const DEPTH_RESOLVE_SLOTS = 2 * RESOLVE_SLOTS;
54
+ const USAGE_COPY = 0x01 | 0x02; // COPY_SRC | COPY_DST
50
55
 
51
56
  /** `mat3x3<f32>` then four scalars, at std140's sixteen-byte columns: see the generated struct. */
52
57
  const RESOLVE_UNIFORM_SIZE = 64;
@@ -66,15 +71,19 @@ const rotationColumn = new Float32Array(3);
66
71
  * both say the resolve reads the scratch.
67
72
  */
68
73
  export class GpuPointShadowArray {
69
- /** The array, as the shader samples it. */
70
- readonly view: GPUTextureView;
74
+ /** Lights this array holds; the storage doubles under it once glass arrives. */
71
75
  readonly layers: number;
72
76
  /** Where a face is rendered before it is resolved. One, shared by every light. */
73
77
  readonly scratchView: GPUTextureView;
74
78
 
75
- private readonly texture: GPUTexture;
79
+ /** Replaced once, when glass first casts: see `growForGlass`. */
80
+ private texture: GPUTexture;
81
+ private arrayView: GPUTextureView;
82
+ private tint: GpuPointGlassTint | null = null;
83
+ private readonly device: GPUDevice;
84
+ private readonly faceSize: number;
76
85
  private readonly scratch: GPUTexture;
77
- private readonly layerViews: GPUTextureView[] = [];
86
+ private layerViews: GPUTextureView[] = [];
78
87
  private readonly pipeline: GPURenderPipeline;
79
88
  private readonly bindGroup: GPUBindGroup;
80
89
  /**
@@ -102,24 +111,12 @@ export class GpuPointShadowArray {
102
111
  ) {
103
112
  this.layers =
104
113
  Math.min(lightCount, POINT_SHADOW_POOL) + LIVE_POINT_SHADOW_MAPS + Math.max(0, extraLayers);
114
+ this.device = device;
115
+ this.faceSize = faceSize;
105
116
 
106
- this.texture = device.createTexture({
107
- label: 'pointShadow.array',
108
- size: [OCTAHEDRAL_EDGE, OCTAHEDRAL_EDGE, this.layers],
109
- format: SHADOW_FORMAT,
110
- usage: USAGE_RENDER_ATTACHMENT | USAGE_TEXTURE_BINDING,
111
- });
112
- this.view = this.texture.createView({ dimension: '2d-array' });
113
- for (let layer = 0; layer < this.layers; layer++) {
114
- this.layerViews.push(
115
- this.texture.createView({
116
- label: `pointShadow.layer${layer}`,
117
- dimension: '2d',
118
- baseArrayLayer: layer,
119
- arrayLayerCount: 1,
120
- }),
121
- );
122
- }
117
+ this.texture = depthArray(device, this.layers);
118
+ this.arrayView = this.texture.createView({ dimension: '2d-array' });
119
+ this.layerViews = layerViewsOf(this.texture, this.layers);
123
120
 
124
121
  this.scratch = device.createTexture({
125
122
  label: 'pointShadow.scratch',
@@ -132,7 +129,7 @@ export class GpuPointShadowArray {
132
129
  this.ring = new UniformRing(
133
130
  device,
134
131
  RESOLVE_UNIFORM_SIZE,
135
- RESOLVE_SLOTS,
132
+ DEPTH_RESOLVE_SLOTS,
136
133
  USAGE_UNIFORM_DST,
137
134
  'pointShadow.resolve.uniforms',
138
135
  );
@@ -206,7 +203,102 @@ export class GpuPointShadowArray {
206
203
  far: number,
207
204
  near: number,
208
205
  ): void {
209
- const view = this.layerViews[layer];
206
+ this.resolveInto(encoder, layer * this.stride, face, far, near);
207
+ }
208
+
209
+ /** The face's glass depth into light `layer`'s glass layer, 2L + 1. */
210
+ resolveGlass(
211
+ encoder: GPUCommandEncoder,
212
+ layer: number,
213
+ face: number,
214
+ far: number,
215
+ near: number,
216
+ ): void {
217
+ if (this.tint === null) return;
218
+ this.resolveInto(encoder, 2 * layer + 1, face, far, near);
219
+ }
220
+
221
+ /** The array, as the shader samples it. Replaced once, when glass first casts. */
222
+ get view(): GPUTextureView {
223
+ return this.arrayView;
224
+ }
225
+
226
+ get hasGlass(): boolean {
227
+ return this.tint !== null;
228
+ }
229
+
230
+ /** Physical layers a light: 1, or 2 once glass has interleaved the array. */
231
+ get stride(): number {
232
+ return this.tint === null ? 1 : 2;
233
+ }
234
+
235
+ /** The lights' glass tint, once glass has cast. */
236
+ get glassTint(): GpuPointGlassTint | null {
237
+ return this.tint;
238
+ }
239
+
240
+ /**
241
+ * Interleave the array for glass, on `encoder`: light L's depth moves to 2L and its glass takes
242
+ * 2L + 1, every glass layer born at the far plane, and the tint made. The reasoning, and why no
243
+ * map re-bakes, is `PointShadowArray.growForGlass`'s.
244
+ *
245
+ * Returns the texture it replaced, which the caller destroys once `encoder` is submitted — the
246
+ * copies out of it are recorded there — or null when the array had already grown.
247
+ */
248
+ growForGlass(
249
+ encoder: GPUCommandEncoder,
250
+ tintEdge: number,
251
+ pipelines: MipPipelines,
252
+ ): GPUTexture | null {
253
+ if (this.tint !== null) return null;
254
+ const old = this.texture;
255
+ const texture = depthArray(this.device, this.layers * 2);
256
+ for (let layer = 0; layer < this.layers; layer++) {
257
+ encoder.copyTextureToTexture(
258
+ { texture: old, origin: { x: 0, y: 0, z: layer } },
259
+ { texture, origin: { x: 0, y: 0, z: 2 * layer } },
260
+ [OCTAHEDRAL_EDGE, OCTAHEDRAL_EDGE, 1],
261
+ );
262
+ }
263
+ this.texture = texture;
264
+ this.arrayView = texture.createView({ dimension: '2d-array' });
265
+ this.layerViews = layerViewsOf(texture, this.layers * 2);
266
+ /* Nothing is nearer a lamp than a pane that has not been drawn. */
267
+ for (let layer = 0; layer < this.layers; layer++) {
268
+ encoder
269
+ .beginRenderPass({
270
+ label: 'pointShadow.glass.clear',
271
+ colorAttachments: [],
272
+ depthStencilAttachment: {
273
+ view: this.layerViews[2 * layer + 1] as GPUTextureView,
274
+ depthClearValue: 1,
275
+ depthLoadOp: 'clear',
276
+ depthStoreOp: 'store',
277
+ },
278
+ })
279
+ .end();
280
+ }
281
+ this.tint = new GpuPointGlassTint(
282
+ this.device,
283
+ pipelines,
284
+ this.faceSize,
285
+ tintEdge,
286
+ this.layers,
287
+ RESOLVE_SLOTS,
288
+ encoder,
289
+ );
290
+ return old;
291
+ }
292
+
293
+ /** The scratch into physical layer `physical`'s region for `face`. */
294
+ private resolveInto(
295
+ encoder: GPUCommandEncoder,
296
+ physical: number,
297
+ face: number,
298
+ far: number,
299
+ near: number,
300
+ ): void {
301
+ const view = this.layerViews[physical];
210
302
  if (view === undefined) return;
211
303
  const slot = this.ring.allocate();
212
304
  if (slot === null) return;
@@ -242,25 +334,39 @@ export class GpuPointShadowArray {
242
334
  /** Start a frame's bakes. See the ring: slots are reused and nothing is freed. */
243
335
  beginFrame(): void {
244
336
  this.ring.reset();
337
+ this.tint?.beginFrame();
245
338
  }
246
339
 
247
340
  /** Upload every slot this frame's resolves took, in one write, before the encoder is submitted. */
248
341
  flush(): void {
249
342
  this.ring.flush();
343
+ this.tint?.flush();
250
344
  }
251
345
 
252
346
  dispose(): void {
253
347
  this.texture.destroy();
254
348
  this.scratch.destroy();
255
349
  this.ring.dispose();
350
+ this.tint?.dispose();
256
351
  }
257
352
  }
258
353
 
259
354
  /** What a face's pass needs from the caller: somewhere to record, and the casters to record. */
260
355
  export type PointShadowFacePass = (view: GPUTextureView, viewProj: mat4) => void;
261
356
 
262
- /** And what turns that face into its region of the light's octahedral layer. */
263
- export type PointShadowResolve = (layer: number, face: number, far: number, near: number) => void;
357
+ /**
358
+ * And what turns that face into its region of the light's octahedral layer — and, where the face
359
+ * offered glass, draws and resolves that too, lit from the bake origin `x`, `y`, `z`.
360
+ */
361
+ export type PointShadowResolve = (
362
+ layer: number,
363
+ face: number,
364
+ far: number,
365
+ near: number,
366
+ x: number,
367
+ y: number,
368
+ z: number,
369
+ ) => void;
264
370
 
265
371
  export class GpuPointShadowMap implements PointShadowSource {
266
372
  readonly size: number;
@@ -369,7 +475,7 @@ export class GpuPointShadowMap implements PointShadowSource {
369
475
  array.scratchView,
370
476
  pointShadowFaceViewProj(face, plan.x, plan.y, plan.z, plan.near, plan.range, scratch),
371
477
  );
372
- resolveFace(this.layer, face, plan.range, plan.near);
478
+ resolveFace(this.layer, face, plan.range, plan.near, plan.x, plan.y, plan.z);
373
479
  }
374
480
 
375
481
  this.image.completeBake(last, plan.x, plan.y, plan.z, plan.range, plan.near, plan.sourceRadius);
@@ -379,3 +485,29 @@ export class GpuPointShadowMap implements PointShadowSource {
379
485
  /** Nothing to free: every GPU object belongs to `GpuPointShadowArray` now. */
380
486
  dispose(): void {}
381
487
  }
488
+
489
+ /** The depth array, copyable so glass can interleave it without a re-bake. */
490
+ function depthArray(device: GPUDevice, layers: number): GPUTexture {
491
+ return device.createTexture({
492
+ label: 'pointShadow.array',
493
+ size: [OCTAHEDRAL_EDGE, OCTAHEDRAL_EDGE, layers],
494
+ format: SHADOW_FORMAT,
495
+ usage: USAGE_RENDER_ATTACHMENT | USAGE_TEXTURE_BINDING | USAGE_COPY,
496
+ });
497
+ }
498
+
499
+ /** One single-layer view per physical layer: a depth attachment takes exactly one. */
500
+ function layerViewsOf(texture: GPUTexture, layers: number): GPUTextureView[] {
501
+ const views: GPUTextureView[] = [];
502
+ for (let layer = 0; layer < layers; layer++) {
503
+ views.push(
504
+ texture.createView({
505
+ label: `pointShadow.layer${layer}`,
506
+ dimension: '2d',
507
+ baseArrayLayer: layer,
508
+ arrayLayerCount: 1,
509
+ }),
510
+ );
511
+ }
512
+ return views;
513
+ }
@@ -331,7 +331,37 @@ export function postPipeline(
331
331
  format: GPUTextureFormat,
332
332
  blend?: GPUBlendState,
333
333
  ): GPURenderPipeline {
334
- return cache.get(key, () => ({
334
+ return cache.get(key, () =>
335
+ describePost(device, layout, key, fragment, [
336
+ blend === undefined ? { format } : { format, blend },
337
+ ]),
338
+ );
339
+ }
340
+
341
+ /**
342
+ * The same over several colour targets, in the fragment's output order: the temporal resolve
343
+ * writes its picture and each pixel's flicker record in one pass. `targets` is the caller's, held
344
+ * rather than built per call, since this is asked for every frame.
345
+ */
346
+ export function postPipelineTargets(
347
+ cache: PipelineCache,
348
+ device: GPUDevice,
349
+ layout: GPUBindGroupLayout,
350
+ key: string,
351
+ fragment: string,
352
+ targets: GPUColorTargetState[],
353
+ ): GPURenderPipeline {
354
+ return cache.get(key, () => describePost(device, layout, key, fragment, targets));
355
+ }
356
+
357
+ function describePost(
358
+ device: GPUDevice,
359
+ layout: GPUBindGroupLayout,
360
+ key: string,
361
+ fragment: string,
362
+ targets: GPUColorTargetState[],
363
+ ): GPURenderPipelineDescriptor {
364
+ return {
335
365
  label: key,
336
366
  layout: device.createPipelineLayout({ bindGroupLayouts: [layout] }),
337
367
  vertex: {
@@ -341,7 +371,7 @@ export function postPipeline(
341
371
  fragment: {
342
372
  module: shaderModule(device, { label: key, code: fragment }),
343
373
  entryPoint: 'main',
344
- targets: [blend === undefined ? { format } : { format, blend }],
374
+ targets,
345
375
  },
346
376
  /*
347
377
  * No depth at all, which is what makes this different from every other pipeline here: the
@@ -349,11 +379,11 @@ export function postPipeline(
349
379
  * disables the test, the write and culling by hand for the same draw.
350
380
  */
351
381
  primitive: { topology: 'triangle-list' as const, cullMode: 'none' as const },
352
- }));
382
+ };
353
383
  }
354
384
 
355
385
  /**
356
- * The temporal resolve's block and its three inputs.
386
+ * The temporal resolve's block and its four inputs.
357
387
  *
358
388
  * Named like the occlusion's beside it, and for the same reason `BLOOM_DOWNSAMPLE_FIELDS` is: a
359
389
  * block read by assumption rather than by name is a uniform written to the wrong offset, which