@driftengine/core 4.4.2 → 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 (193) hide show
  1. package/CHANGELOG.json +46 -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 +79 -8
  9. package/dist/render/backend/webgl2/renderer.js +360 -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 +2 -12
  34. package/dist/render/backend/webgpu/renderer.d.ts +171 -54
  35. package/dist/render/backend/webgpu/renderer.js +944 -390
  36. package/dist/render/backend/webgpu/scatterPass.js +2 -1
  37. package/dist/render/backend/webgpu/skinPaletteRing.d.ts +4 -0
  38. package/dist/render/backend/webgpu/skinPaletteRing.js +4 -0
  39. package/dist/render/backend/webgpu/snapshotMips.d.ts +29 -0
  40. package/dist/render/backend/webgpu/snapshotMips.js +52 -0
  41. package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +5 -0
  42. package/dist/render/backend/webgpu/surfaceTexturePass.js +1 -1
  43. package/dist/render/glass.d.ts +45 -0
  44. package/dist/render/glass.js +39 -0
  45. package/dist/render/glassCasters.d.ts +40 -0
  46. package/dist/render/glassCasters.js +84 -0
  47. package/dist/render/glassShadow.d.ts +78 -0
  48. package/dist/render/glassShadow.js +125 -0
  49. package/dist/render/globalMediumPass.d.ts +2 -3
  50. package/dist/render/globalMediumPass.js +2 -8
  51. package/dist/render/gpudriven/lit.d.ts +12 -0
  52. package/dist/render/gpudriven/lit.js +23 -0
  53. package/dist/render/gpudriven/materialTable.d.ts +15 -5
  54. package/dist/render/gpudriven/materialTable.js +26 -6
  55. package/dist/render/gpudriven/pipeline.d.ts +1 -1
  56. package/dist/render/gpudriven/pipeline.js +4 -3
  57. package/dist/render/gpudriven/sunGlass.d.ts +35 -0
  58. package/dist/render/gpudriven/sunGlass.js +89 -0
  59. package/dist/render/lightBudget.d.ts +17 -5
  60. package/dist/render/lightBudget.js +17 -5
  61. package/dist/render/pointGlassTint.d.ts +59 -0
  62. package/dist/render/pointGlassTint.js +198 -0
  63. package/dist/render/pointShadowArray.d.ts +56 -3
  64. package/dist/render/pointShadowArray.js +127 -16
  65. package/dist/render/pointShadowMap.d.ts +23 -1
  66. package/dist/render/pointShadowMap.js +27 -1
  67. package/dist/render/recon/changeFrames.d.ts +15 -0
  68. package/dist/render/recon/changeFrames.js +21 -0
  69. package/dist/render/recon/lateRouting.d.ts +14 -0
  70. package/dist/render/recon/lateRouting.js +18 -0
  71. package/dist/render/recon/mover.d.ts +57 -0
  72. package/dist/render/recon/mover.js +86 -0
  73. package/dist/render/renderQuality.d.ts +35 -0
  74. package/dist/render/renderQuality.js +7 -0
  75. package/dist/render/rendererHarness.js +12 -0
  76. package/dist/render/sceneTarget.d.ts +7 -1
  77. package/dist/render/sceneTarget.js +58 -12
  78. package/dist/render/shaders/depth.d.ts +16 -1
  79. package/dist/render/shaders/depth.js +85 -9
  80. package/dist/render/shaders/flat/directionalShadow.js +91 -6
  81. package/dist/render/shaders/flat/glassTint.d.ts +6 -0
  82. package/dist/render/shaders/flat/glassTint.js +25 -0
  83. package/dist/render/shaders/flat/index.d.ts +13 -0
  84. package/dist/render/shaders/flat/index.js +4 -0
  85. package/dist/render/shaders/flat/lampGlass.d.ts +1 -0
  86. package/dist/render/shaders/flat/lampGlass.js +183 -0
  87. package/dist/render/shaders/flat/main.d.ts +1 -1
  88. package/dist/render/shaders/flat/main.js +115 -15
  89. package/dist/render/shaders/flat/pointShadow.d.ts +1 -1
  90. package/dist/render/shaders/flat/pointShadow.js +2 -0
  91. package/dist/render/shaders/flat/preamble.js +21 -6
  92. package/dist/render/shaders/generated/depth.wgsl.d.ts +227 -4
  93. package/dist/render/shaders/generated/depth.wgsl.js +227 -4
  94. package/dist/render/shaders/generated/flat.wgsl.d.ts +360 -312
  95. package/dist/render/shaders/generated/flat.wgsl.js +376 -328
  96. package/dist/render/shaders/generated/globalMedium.wgsl.d.ts +3 -13
  97. package/dist/render/shaders/generated/globalMedium.wgsl.js +3 -13
  98. package/dist/render/shaders/generated/lightVolume.wgsl.d.ts +3 -13
  99. package/dist/render/shaders/generated/lightVolume.wgsl.js +4 -14
  100. package/dist/render/shaders/generated/octahedralResolve.wgsl.d.ts +29 -0
  101. package/dist/render/shaders/generated/octahedralResolve.wgsl.js +29 -0
  102. package/dist/render/shaders/globalMedium.js +6 -6
  103. package/dist/render/shaders/gpudriven/blendRaster.wgsl.d.ts +7 -0
  104. package/dist/render/shaders/gpudriven/blendRaster.wgsl.js +86 -1
  105. package/dist/render/shaders/gpudriven/cull.wgsl.d.ts +1 -1
  106. package/dist/render/shaders/gpudriven/cull.wgsl.js +11 -2
  107. package/dist/render/shaders/gpudriven/materialTable.wgsl.d.ts +1 -1
  108. package/dist/render/shaders/gpudriven/materialTable.wgsl.js +3 -1
  109. package/dist/render/shaders/gpudriven/shade.wgsl.js +50 -1
  110. package/dist/render/shaders/gpudriven/sunGlass.wgsl.d.ts +9 -0
  111. package/dist/render/shaders/gpudriven/sunGlass.wgsl.js +133 -0
  112. package/dist/render/shaders/lightVolume.js +6 -6
  113. package/dist/render/shaders/octahedralResolve.d.ts +8 -0
  114. package/dist/render/shaders/octahedralResolve.js +48 -17
  115. package/dist/render/shaders/recon/lateDepth.wgsl.d.ts +1 -0
  116. package/dist/render/shaders/recon/lateDepth.wgsl.js +44 -0
  117. package/dist/render/shaders/recon/motion.wgsl.d.ts +1 -1
  118. package/dist/render/shaders/recon/motion.wgsl.js +84 -0
  119. package/dist/render/shadowCasters.d.ts +20 -1
  120. package/dist/render/shadowCasters.js +15 -1
  121. package/dist/render/shadowMap.d.ts +54 -5
  122. package/dist/render/shadowMap.js +190 -36
  123. package/dist/render/sunGlassTint.d.ts +25 -0
  124. package/dist/render/sunGlassTint.js +79 -0
  125. package/dist/render/translucentQueue.d.ts +6 -0
  126. package/dist/render/translucentQueue.js +15 -1
  127. package/package.json +6 -6
  128. package/src/index.ts +10 -0
  129. package/src/render/backend/api.ts +15 -9
  130. package/src/render/backend/webgl2/glassTintPrograms.ts +58 -0
  131. package/src/render/backend/webgl2/renderer.ts +401 -108
  132. package/src/render/backend/webgpu/buffers.ts +50 -2
  133. package/src/render/backend/webgpu/depthPass.ts +2 -1
  134. package/src/render/backend/webgpu/flatPass.ts +22 -1
  135. package/src/render/backend/webgpu/glassTintPass.ts +244 -0
  136. package/src/render/backend/webgpu/gpuDrivenPass.ts +349 -5
  137. package/src/render/backend/webgpu/instanced.ts +21 -1
  138. package/src/render/backend/webgpu/latePass.ts +152 -0
  139. package/src/render/backend/webgpu/lightVolumePass.ts +5 -1
  140. package/src/render/backend/webgpu/motionPass.ts +535 -0
  141. package/src/render/backend/webgpu/pipelineCache.ts +12 -1
  142. package/src/render/backend/webgpu/pointGlassTintPass.ts +233 -0
  143. package/src/render/backend/webgpu/pointShadowPass.ts +158 -26
  144. package/src/render/backend/webgpu/renderer.ts +1108 -421
  145. package/src/render/backend/webgpu/scatterPass.ts +2 -1
  146. package/src/render/backend/webgpu/skinPaletteRing.ts +4 -0
  147. package/src/render/backend/webgpu/snapshotMips.ts +80 -0
  148. package/src/render/backend/webgpu/surfaceTexturePass.ts +1 -1
  149. package/src/render/glass.ts +74 -0
  150. package/src/render/glassCasters.ts +164 -0
  151. package/src/render/glassShadow.ts +153 -0
  152. package/src/render/globalMediumPass.ts +4 -11
  153. package/src/render/gpudriven/lit.ts +31 -0
  154. package/src/render/gpudriven/materialTable.ts +28 -6
  155. package/src/render/gpudriven/pipeline.ts +4 -3
  156. package/src/render/gpudriven/sunGlass.ts +117 -0
  157. package/src/render/lightBudget.ts +19 -5
  158. package/src/render/pointGlassTint.ts +250 -0
  159. package/src/render/pointShadowArray.ts +188 -22
  160. package/src/render/pointShadowMap.ts +41 -0
  161. package/src/render/recon/changeFrames.ts +24 -0
  162. package/src/render/recon/lateRouting.ts +29 -0
  163. package/src/render/recon/mover.ts +118 -0
  164. package/src/render/renderQuality.ts +47 -0
  165. package/src/render/rendererHarness.ts +12 -0
  166. package/src/render/sceneTarget.ts +58 -11
  167. package/src/render/shaders/depth.ts +109 -9
  168. package/src/render/shaders/flat/directionalShadow.ts +97 -6
  169. package/src/render/shaders/flat/glassTint.ts +25 -0
  170. package/src/render/shaders/flat/index.ts +17 -0
  171. package/src/render/shaders/flat/lampGlass.ts +185 -0
  172. package/src/render/shaders/flat/main.ts +115 -15
  173. package/src/render/shaders/flat/pointShadow.ts +2 -0
  174. package/src/render/shaders/flat/preamble.ts +21 -6
  175. package/src/render/shaders/generated/depth.wgsl.ts +234 -4
  176. package/src/render/shaders/generated/flat.wgsl.ts +376 -328
  177. package/src/render/shaders/generated/globalMedium.wgsl.ts +3 -13
  178. package/src/render/shaders/generated/lightVolume.wgsl.ts +4 -14
  179. package/src/render/shaders/generated/octahedralResolve.wgsl.ts +30 -0
  180. package/src/render/shaders/globalMedium.ts +6 -6
  181. package/src/render/shaders/gpudriven/blendRaster.wgsl.ts +88 -1
  182. package/src/render/shaders/gpudriven/cull.wgsl.ts +11 -2
  183. package/src/render/shaders/gpudriven/materialTable.wgsl.ts +3 -1
  184. package/src/render/shaders/gpudriven/shade.wgsl.ts +50 -1
  185. package/src/render/shaders/gpudriven/sunGlass.wgsl.ts +139 -0
  186. package/src/render/shaders/lightVolume.ts +6 -6
  187. package/src/render/shaders/octahedralResolve.ts +50 -17
  188. package/src/render/shaders/recon/lateDepth.wgsl.ts +47 -0
  189. package/src/render/shaders/recon/motion.wgsl.ts +84 -0
  190. package/src/render/shadowCasters.ts +28 -1
  191. package/src/render/shadowMap.ts +231 -37
  192. package/src/render/sunGlassTint.ts +86 -0
  193. package/src/render/translucentQueue.ts +20 -1
@@ -1,4 +1,4 @@
1
- /** A GPU-driven material as the shading pass reads it: five blocks, one of them integers. */
1
+ /** A GPU-driven material as the shading pass reads it: seven blocks, one of them integers. */
2
2
  import type { GpuDrivenMaterial } from '../backend/webgpu/gpuDrivenPass.ts';
3
3
  import type { GpuDrivenProgram } from './decodeTables.ts';
4
4
  /**
@@ -30,17 +30,18 @@ export interface GpuDrivenTextures {
30
30
  }
31
31
  /**
32
32
  * Words a material entry: tint and emissive, the surface scalars, four program indices, the UV
33
- * scale and two strengths, the two ORM scales with the cutout and blend flags, and
34
- * the opacity with three spare.
33
+ * scale and two strengths, the two ORM scales with the cutout and blend flags, the opacity with the
34
+ * glass's transmission and frost and one spare, and the glass's tint with one spare.
35
35
  *
36
36
  * **It was eight until 2026-09-17**, and metalness took its last spare lane the same day, so the
37
37
  * programs and the scales needed three more blocks rather than squeezing into what was left. It
38
38
  * became twenty-four on 2026-09-18, when transparency arrived and there was nowhere left to put an
39
39
  * opacity: the cutout took lane 18 and the blend flag took 19, and lane 11 is a reserved program
40
40
  * slot rather than a gap. A sixteen-byte block is 1 kB across the whole table, against the 64 kB a
41
- * uniform buffer may hold.
41
+ * uniform buffer may hold. **Twenty-eight on 2026-09-29**, for glass: its transmission and frost
42
+ * took two of the opacity block's spares and its tint needed a block of its own.
42
43
  */
43
- export declare const GPU_DRIVEN_MATERIAL_FLOATS = 24;
44
+ export declare const GPU_DRIVEN_MATERIAL_FLOATS = 28;
44
45
  /**
45
46
  * Below this base-colour alpha the raster discards a fragment. Zero means no test.
46
47
  *
@@ -66,6 +67,15 @@ export declare const MATERIAL_BLEND = 19;
66
67
  * all. A material that does not blend never reads this.
67
68
  */
68
69
  export declare const MATERIAL_OPACITY = 20;
70
+ /**
71
+ * Glass (`glass.ts`): how much light passes, and how milky it is. Zero transmission is not glass,
72
+ * and a material that is glass is drawn in the blended half whatever `blend` says — a visibility
73
+ * buffer holds one surface a pixel, and glass is two.
74
+ */
75
+ export declare const MATERIAL_GLASS_TRANSMISSION = 21;
76
+ export declare const MATERIAL_GLASS_FROST = 22;
77
+ /** The colour light takes through the glass: three lanes, white where the material is not glass. */
78
+ export declare const MATERIAL_GLASS_TINT = 24;
69
79
  /**
70
80
  * Where a material's emissive program is: the fourth program lane, which the table reserved and
71
81
  * initialised to none before anything used it.
@@ -1,18 +1,20 @@
1
- /** A GPU-driven material as the shading pass reads it: five blocks, one of them integers. */
1
+ /** A GPU-driven material as the shading pass reads it: seven blocks, one of them integers. */
2
+ import { resolveGlass } from '../glass.js';
2
3
  import { DECODE_NO_PROGRAM } from '../shaders/gpudriven/decode.wgsl.js';
3
4
  /**
4
5
  * Words a material entry: tint and emissive, the surface scalars, four program indices, the UV
5
- * scale and two strengths, the two ORM scales with the cutout and blend flags, and
6
- * the opacity with three spare.
6
+ * scale and two strengths, the two ORM scales with the cutout and blend flags, the opacity with the
7
+ * glass's transmission and frost and one spare, and the glass's tint with one spare.
7
8
  *
8
9
  * **It was eight until 2026-09-17**, and metalness took its last spare lane the same day, so the
9
10
  * programs and the scales needed three more blocks rather than squeezing into what was left. It
10
11
  * became twenty-four on 2026-09-18, when transparency arrived and there was nowhere left to put an
11
12
  * opacity: the cutout took lane 18 and the blend flag took 19, and lane 11 is a reserved program
12
13
  * slot rather than a gap. A sixteen-byte block is 1 kB across the whole table, against the 64 kB a
13
- * uniform buffer may hold.
14
+ * uniform buffer may hold. **Twenty-eight on 2026-09-29**, for glass: its transmission and frost
15
+ * took two of the opacity block's spares and its tint needed a block of its own.
14
16
  */
15
- export const GPU_DRIVEN_MATERIAL_FLOATS = 24;
17
+ export const GPU_DRIVEN_MATERIAL_FLOATS = 28;
16
18
  /**
17
19
  * Below this base-colour alpha the raster discards a fragment. Zero means no test.
18
20
  *
@@ -38,6 +40,15 @@ export const MATERIAL_BLEND = 19;
38
40
  * all. A material that does not blend never reads this.
39
41
  */
40
42
  export const MATERIAL_OPACITY = 20;
43
+ /**
44
+ * Glass (`glass.ts`): how much light passes, and how milky it is. Zero transmission is not glass,
45
+ * and a material that is glass is drawn in the blended half whatever `blend` says — a visibility
46
+ * buffer holds one surface a pixel, and glass is two.
47
+ */
48
+ export const MATERIAL_GLASS_TRANSMISSION = 21;
49
+ export const MATERIAL_GLASS_FROST = 22;
50
+ /** The colour light takes through the glass: three lanes, white where the material is not glass. */
51
+ export const MATERIAL_GLASS_TINT = 24;
41
52
  /**
42
53
  * Where a material's emissive program is: the fourth program lane, which the table reserved and
43
54
  * initialised to none before anything used it.
@@ -106,10 +117,19 @@ export function writeMaterialTable(materials, indices, slots) {
106
117
  float(m, 17, textures?.metallicScale ?? 1);
107
118
  /* Zero is off, so a material that says nothing about cutout writes the table it wrote before. */
108
119
  float(m, MATERIAL_ALPHA_CUTOFF, material.alphaCutoff ?? 0);
120
+ /* Glass A's clamps and its test of what is glass at all, so both pipelines agree on it. */
121
+ const glass = resolveGlass(material.glass, glassScratch);
109
122
  /* Zero is opaque, so a material that says nothing about blending stays in the opaque half. */
110
- float(m, MATERIAL_BLEND, material.blend === true ? 1 : 0);
123
+ float(m, MATERIAL_BLEND, material.blend === true || glass ? 1 : 0);
111
124
  /* One, so a material that asked to blend and said nothing else is as solid as an opaque one. */
112
125
  float(m, MATERIAL_OPACITY, material.opacity ?? 1);
126
+ float(m, MATERIAL_GLASS_TRANSMISSION, glassScratch.transmission);
127
+ float(m, MATERIAL_GLASS_FROST, glassScratch.frost);
128
+ float(m, MATERIAL_GLASS_TINT, glassScratch.tint[0]);
129
+ float(m, MATERIAL_GLASS_TINT + 1, glassScratch.tint[1]);
130
+ float(m, MATERIAL_GLASS_TINT + 2, glassScratch.tint[2]);
113
131
  });
114
132
  return buffer;
115
133
  }
134
+ /** One resolution every material's glass is read through. */
135
+ const glassScratch = { transmission: 0, frost: 0, tint: [1, 1, 1] };
@@ -83,7 +83,7 @@ export declare const GPU_DRIVEN_RESOURCES: GpuDrivenResources;
83
83
  * What each stage is called, in the order they are recorded — one render or compute pass each, or
84
84
  * one copy between them.
85
85
  */
86
- export declare const GPU_DRIVEN_PASSES: readonly ['clearColour', 'shadow', 'instanceCull', 'cut', 'phaseOneDraw', 'pyramid', 'phaseTwoCull', 'phaseTwoDraw', 'blendCull', 'blendDraw', 'visibilityCopy', 'bin', 'cursorCopy', 'shade', 'blendResolve'];
86
+ export declare const GPU_DRIVEN_PASSES: readonly ['clearColour', 'shadow', 'instanceCull', 'cut', 'phaseOneDraw', 'pyramid', 'phaseTwoCull', 'phaseTwoDraw', 'blendCull', 'visibilityCopy', 'bin', 'cursorCopy', 'shade', 'blendDraw', 'blendResolve'];
87
87
  export type GpuDrivenPassName = (typeof GPU_DRIVEN_PASSES)[number];
88
88
  /**
89
89
  * **Every stage is ordered, so none merges with a neighbour.** Two adjacent compute stages could
@@ -82,11 +82,11 @@ export const GPU_DRIVEN_PASSES = [
82
82
  'phaseTwoCull',
83
83
  'phaseTwoDraw',
84
84
  'blendCull',
85
- 'blendDraw',
86
85
  'visibilityCopy',
87
86
  'bin',
88
87
  'cursorCopy',
89
88
  'shade',
89
+ 'blendDraw',
90
90
  'blendResolve',
91
91
  ];
92
92
  /**
@@ -148,10 +148,11 @@ function edgesFor(res) {
148
148
  /*
149
149
  * **It reads the depth the opaque half wrote and does not write it.** A pane behind a wall is
150
150
  * rejected at the wall; nothing here writes depth, so two panes do not hide each other and
151
- * there is something left to blend.
151
+ * there is something left to blend. **And it reads the shaded colour**, because glass shows the
152
+ * opaque picture behind it — which is why it is recorded after `shade` rather than before.
152
153
  */
153
154
  blendDraw: [
154
- [res.blendList, res.clusters, res.depth],
155
+ [res.blendList, res.clusters, res.depth, res.colour],
155
156
  [res.oitAccum, res.oitReveal],
156
157
  ],
157
158
  visibilityCopy: [[res.visibility], [res.visibilityCopy]],
@@ -0,0 +1,35 @@
1
+ /**
2
+ * The second pipeline's sunlight through glass, as a reference: `glassTintAt` over the glass depth
3
+ * and tint maps the GPU-driven pass draws from the sun, for `shade.wgsl.ts`'s lookup to be read
4
+ * against.
5
+ *
6
+ * **The forward path's rule, not a second one** (`glassShadow.ts`, and `sunGlassLookup` in
7
+ * `flat/directionalShadow.ts`): the outline from taps at the base filter radius that count a pane
8
+ * only where the receiver is behind it, and the colour from spread taps read coarse, unmixed from
9
+ * the ground around the pane by its clarity and weighed by its share. Frost widens only the
10
+ * colour's taps, so the light a frosted pane passes is the light a clear one does.
11
+ *
12
+ * **What differs is where two numbers come from.** A compute invocation has no derivatives, so the
13
+ * map's scale in metres is the caller's (`uvPerMetre`, which an orthographic light matrix states
14
+ * outright); and a coarse level here is a box average over the spread's footprint, which is what a
15
+ * mip level approximates on the device. `scripts/gpu-parity.mjs` holds the device's per-tap
16
+ * arithmetic to `glassShadow.ts`; the sampling is what the glass page measures.
17
+ */
18
+ import { type GlassTexel } from '../glassShadow.ts';
19
+ /** The two maps the sun's glass pass draws, square, row-major from v = 0. */
20
+ export interface SunGlassMaps {
21
+ readonly size: number;
22
+ /** The nearest pane's depth a texel, 1 where no pane was drawn. */
23
+ readonly glassDepth: Float32Array;
24
+ /** What the panes on a texel's ray let through, and their clarity: four floats a texel. */
25
+ readonly tint: Float32Array;
26
+ }
27
+ export interface SunGlassSettings {
28
+ /** Metres the light's depth range spans, which turns a depth difference into a distance. */
29
+ readonly depthSpan: number;
30
+ /** Map units a metre across the light, which turns a spread in metres into one on the map. */
31
+ readonly uvPerMetre: number;
32
+ readonly taps: number;
33
+ }
34
+ /** What the sun keeps through glass at a receiver at map position (u, v) and light depth `depth`. */
35
+ export declare function glassTintAt(u: number, v: number, depth: number, maps: SunGlassMaps, settings: SunGlassSettings, out: GlassTexel): GlassTexel;
@@ -0,0 +1,89 @@
1
+ /**
2
+ * The second pipeline's sunlight through glass, as a reference: `glassTintAt` over the glass depth
3
+ * and tint maps the GPU-driven pass draws from the sun, for `shade.wgsl.ts`'s lookup to be read
4
+ * against.
5
+ *
6
+ * **The forward path's rule, not a second one** (`glassShadow.ts`, and `sunGlassLookup` in
7
+ * `flat/directionalShadow.ts`): the outline from taps at the base filter radius that count a pane
8
+ * only where the receiver is behind it, and the colour from spread taps read coarse, unmixed from
9
+ * the ground around the pane by its clarity and weighed by its share. Frost widens only the
10
+ * colour's taps, so the light a frosted pane passes is the light a clear one does.
11
+ *
12
+ * **What differs is where two numbers come from.** A compute invocation has no derivatives, so the
13
+ * map's scale in metres is the caller's (`uvPerMetre`, which an orthographic light matrix states
14
+ * outright); and a coarse level here is a box average over the spread's footprint, which is what a
15
+ * mip level approximates on the device. `scripts/gpu-parity.mjs` holds the device's per-tap
16
+ * arithmetic to `glassShadow.ts`; the sampling is what the glass page measures.
17
+ */
18
+ import { frostRadius, spreadTint, unmixTint } from '../glassShadow.js';
19
+ import { SHADOW_DEPTH_BIAS, SHADOW_PCF_OFFSETS, SHADOW_TEXEL_SCALE } from './shadow.js';
20
+ const centre = { r: 1, g: 1, b: 1, clarity: 1 };
21
+ const coarse = { r: 1, g: 1, b: 1, clarity: 1 };
22
+ const pane = { r: 1, g: 1, b: 1, clarity: 1 };
23
+ const found = { r: 0, g: 0, b: 0, clarity: 0 };
24
+ function index(maps, u, v) {
25
+ const x = Math.min(maps.size - 1, Math.max(0, Math.floor(u * maps.size)));
26
+ const y = Math.min(maps.size - 1, Math.max(0, Math.floor(v * maps.size)));
27
+ return y * maps.size + x;
28
+ }
29
+ /** The average of every texel within `radius` of (u, v): a mip level's footprint, in the large. */
30
+ function coarseAt(maps, u, v, radius, out) {
31
+ const reach = radius * maps.size;
32
+ const x0 = Math.max(0, Math.floor(u * maps.size - reach));
33
+ const x1 = Math.min(maps.size - 1, Math.floor(u * maps.size + reach));
34
+ const y0 = Math.max(0, Math.floor(v * maps.size - reach));
35
+ const y1 = Math.min(maps.size - 1, Math.floor(v * maps.size + reach));
36
+ out.r = 0;
37
+ out.g = 0;
38
+ out.b = 0;
39
+ out.clarity = 0;
40
+ let n = 0;
41
+ for (let y = y0; y <= y1; y++) {
42
+ for (let x = x0; x <= x1; x++) {
43
+ const at = (y * maps.size + x) * 4;
44
+ out.r += maps.tint[at];
45
+ out.g += maps.tint[at + 1];
46
+ out.b += maps.tint[at + 2];
47
+ out.clarity += maps.tint[at + 3];
48
+ n++;
49
+ }
50
+ }
51
+ out.r /= n;
52
+ out.g /= n;
53
+ out.b /= n;
54
+ out.clarity /= n;
55
+ }
56
+ /** What the sun keeps through glass at a receiver at map position (u, v) and light depth `depth`. */
57
+ export function glassTintAt(u, v, depth, maps, settings, out) {
58
+ out.r = 1;
59
+ out.g = 1;
60
+ out.b = 1;
61
+ out.clarity = 1;
62
+ if (u < 0 || u > 1 || v < 0 || v > 1)
63
+ return out;
64
+ const texel = SHADOW_TEXEL_SCALE / maps.size;
65
+ const bias = SHADOW_DEPTH_BIAS / settings.depthSpan;
66
+ const taps = Math.min(settings.taps, SHADOW_PCF_OFFSETS.length);
67
+ const at = index(maps, u, v);
68
+ centre.clarity = maps.tint[at * 4 + 3];
69
+ const behind = Math.max(depth - maps.glassDepth[at], 0) * settings.depthSpan;
70
+ const radius = frostRadius(centre.clarity, behind * settings.uvPerMetre, texel);
71
+ let cover = 0;
72
+ found.r = 0;
73
+ found.g = 0;
74
+ found.b = 0;
75
+ let shares = 0;
76
+ for (let i = 0; i < taps; i++) {
77
+ const offset = SHADOW_PCF_OFFSETS[i];
78
+ const nearest = maps.glassDepth[index(maps, u + offset[0] * texel, v + offset[1] * texel)];
79
+ if (depth > nearest + bias)
80
+ cover++;
81
+ coarseAt(maps, u + offset[0] * radius, v + offset[1] * radius, radius, coarse);
82
+ const share = unmixTint(coarse, centre.clarity, pane);
83
+ found.r += pane.r * share;
84
+ found.g += pane.g * share;
85
+ found.b += pane.b * share;
86
+ shares += share;
87
+ }
88
+ return spreadTint(cover / Math.max(taps, 1), found, shares, out);
89
+ }
@@ -34,13 +34,14 @@ export declare const MAX_POINT_LIGHTS = 16;
34
34
  /** Two live maps make changing the caster's owning light a crossfade, not a pop. */
35
35
  export declare const LIVE_POINT_SHADOW_MAPS = 2;
36
36
  /**
37
- * Texture units the directional cascade binds: static, its depth peel, and dynamic.
37
+ * Texture units the sun's shadow binds: one, for its static, moving and peeled layers.
38
38
  *
39
- * Named because the shading pass's total unit count has to be compared against what a
40
- * device offers, and a budget assembled from three literals scattered across two files
41
- * is a budget nobody can check. See the guard in `Renderer`'s constructor.
39
+ * **It was three**, a `sampler2D` per map, until 4.5.0 folded them into one `sampler2DArray`
40
+ * (`shadowMap.ts`) the way the lamps' twelve cubemaps became one — which is where the two tints
41
+ * glass casts with found their units. Named because the shading pass's total has to be compared
42
+ * against what a device offers; see the guard in `Renderer`'s constructor.
42
43
  */
43
- export declare const DIRECTIONAL_SHADOW_UNITS = 3;
44
+ export declare const DIRECTIONAL_SHADOW_UNITS = 1;
44
45
  /**
45
46
  * Texture units every point light's shadow binds: one, for all of them.
46
47
  *
@@ -212,6 +213,17 @@ export declare const REFRACT_SCENE_TEXTURE_UNIT: number;
212
213
  */
213
214
  export declare const DRIFT_LIGHT_INDEX_TEXTURE_UNIT: number;
214
215
  export declare const DRIFT_LIGHT_ATLAS_TEXTURE_UNIT: number;
216
+ /**
217
+ * What the sun keeps through glass (`sunGlassTint.ts`), the lit program's fourteenth fragment
218
+ * sampler: the unit the sun's three maps gave back when they became one array.
219
+ */
220
+ export declare const SUN_GLASS_TINT_TEXTURE_UNIT: number;
221
+ /**
222
+ * What each lamp keeps through glass (`pointGlassTint.ts`), the lit program's fifteenth fragment
223
+ * sampler and the last one that leaves a sixteenth spare. Unit sixteen of the combined pool, which
224
+ * WebGL2 guarantees thirty-two of; what a stage may declare is the sixteen, and this is fifteen.
225
+ */
226
+ export declare const POINT_GLASS_TINT_TEXTURE_UNIT: number;
215
227
  /**
216
228
  * A cookie's tile, in texels a side.
217
229
  *
@@ -32,13 +32,14 @@ export const MAX_POINT_LIGHTS = 16;
32
32
  /** Two live maps make changing the caster's owning light a crossfade, not a pop. */
33
33
  export const LIVE_POINT_SHADOW_MAPS = 2;
34
34
  /**
35
- * Texture units the directional cascade binds: static, its depth peel, and dynamic.
35
+ * Texture units the sun's shadow binds: one, for its static, moving and peeled layers.
36
36
  *
37
- * Named because the shading pass's total unit count has to be compared against what a
38
- * device offers, and a budget assembled from three literals scattered across two files
39
- * is a budget nobody can check. See the guard in `Renderer`'s constructor.
37
+ * **It was three**, a `sampler2D` per map, until 4.5.0 folded them into one `sampler2DArray`
38
+ * (`shadowMap.ts`) the way the lamps' twelve cubemaps became one — which is where the two tints
39
+ * glass casts with found their units. Named because the shading pass's total has to be compared
40
+ * against what a device offers; see the guard in `Renderer`'s constructor.
40
41
  */
41
- export const DIRECTIONAL_SHADOW_UNITS = 3;
42
+ export const DIRECTIONAL_SHADOW_UNITS = 1;
42
43
  /**
43
44
  * Texture units every point light's shadow binds: one, for all of them.
44
45
  *
@@ -210,6 +211,17 @@ export const REFRACT_SCENE_TEXTURE_UNIT = COOKIE_ATLAS_TEXTURE_UNIT + 1;
210
211
  */
211
212
  export const DRIFT_LIGHT_INDEX_TEXTURE_UNIT = REFRACT_SCENE_TEXTURE_UNIT + 1;
212
213
  export const DRIFT_LIGHT_ATLAS_TEXTURE_UNIT = DRIFT_LIGHT_INDEX_TEXTURE_UNIT + 1;
214
+ /**
215
+ * What the sun keeps through glass (`sunGlassTint.ts`), the lit program's fourteenth fragment
216
+ * sampler: the unit the sun's three maps gave back when they became one array.
217
+ */
218
+ export const SUN_GLASS_TINT_TEXTURE_UNIT = DRIFT_LIGHT_ATLAS_TEXTURE_UNIT + 1;
219
+ /**
220
+ * What each lamp keeps through glass (`pointGlassTint.ts`), the lit program's fifteenth fragment
221
+ * sampler and the last one that leaves a sixteenth spare. Unit sixteen of the combined pool, which
222
+ * WebGL2 guarantees thirty-two of; what a stage may declare is the sixteen, and this is fifteen.
223
+ */
224
+ export const POINT_GLASS_TINT_TEXTURE_UNIT = SUN_GLASS_TINT_TEXTURE_UNIT + 1;
213
225
  /**
214
226
  * A cookie's tile, in texels a side.
215
227
  *
@@ -0,0 +1,59 @@
1
+ /**
2
+ * What a lamp's light keeps through glass: an RGBA8 array of one layer a light, parallel to the
3
+ * point shadow array and indexed by the light, not by the interleaved depth layer. RGB is the
4
+ * product of every pane's `transmission × (1 − F) × tint`, A the product of their clarities
5
+ * (`glassShadow.ts`).
6
+ *
7
+ * **Drawn a face at a time, as the depth is**: into a face-sized colour scratch cleared to white,
8
+ * with a multiplying blend and no depth test, then resolved into the face's region of the layer by
9
+ * `OCTAHEDRAL_RESOLVE_TINT_FRAG`. One scratch for the whole engine, for the reason
10
+ * `PointShadowArray` gives for its own.
11
+ *
12
+ * **Which faces hold glass is remembered, six bits a layer**, so a face with no glass and none to
13
+ * forget costs nothing, and a face that inherited a previous owner's glass is cleared. **The chain
14
+ * frost reads is rebuilt once a round**, for the layers the round touched, through a 2D staging
15
+ * chain: `generateMipmap` on an array rebuilds every layer, which is the whole pool's texels for one
16
+ * lamp's glass. What that costs is one layer of memory with its chain — 5.6 MB at 1024, 1.4 MB at
17
+ * `'half'` — and a copy out and back per touched layer.
18
+ */
19
+ export declare class PointGlassTint {
20
+ readonly texture: WebGLTexture;
21
+ readonly edge: number;
22
+ private readonly layers;
23
+ private readonly levels;
24
+ private readonly faceSize;
25
+ private readonly scratch;
26
+ private readonly scratchFramebuffer;
27
+ private readonly staging;
28
+ private readonly framebuffer;
29
+ private readonly readFramebuffer;
30
+ /** Parked on the resolve's unit while the scratch is a render target; see `beginFace`. */
31
+ private readonly parked;
32
+ private readonly program;
33
+ private readonly uniforms;
34
+ private readonly vao;
35
+ /** Six bits a layer: which faces' regions hold glass. */
36
+ private readonly heldFaces;
37
+ /** Layers whose level 0 changed since the chain was last rebuilt. */
38
+ private readonly dirty;
39
+ constructor(gl: WebGL2RenderingContext, faceSize: number, edge: number, layers: number);
40
+ /** Whether `face` of `layer` holds glass: a face that does must be cleared before it is reused. */
41
+ held(layer: number, face: number): boolean;
42
+ /**
43
+ * Bind the colour scratch, clear it to white, and set the multiplying state.
44
+ *
45
+ * The previous resolve left the scratch bound for sampling on unit 0, and it is about to be the
46
+ * target: parked first, for the reason `PointShadowArray.beginFace` gives.
47
+ */
48
+ beginFace(gl: WebGL2RenderingContext): void;
49
+ /**
50
+ * Copy the scratch into `face`'s region of `layer`, and remember whether it held glass.
51
+ *
52
+ * Leaves depth testing and writing on, which is what the next face's casters expect: the depth
53
+ * resolve that runs before them turns both on, and this one ran after it and turned them off.
54
+ */
55
+ resolveFace(gl: WebGL2RenderingContext, layer: number, face: number, drawn: boolean): void;
56
+ /** Rebuild the chain of every layer touched since the last call, one layer at a time. */
57
+ fillMips(gl: WebGL2RenderingContext): void;
58
+ dispose(gl: WebGL2RenderingContext): void;
59
+ }
@@ -0,0 +1,198 @@
1
+ import { mat3 } from 'gl-matrix';
2
+ import { createEmptyTexture2D } from './emptyTexture.js';
3
+ import { pointShadowFaceRotation } from './pointShadowImage.js';
4
+ import { compileProgram, uniformLocations } from './shader.js';
5
+ import { OCTAHEDRAL_RESOLVE_TINT_FRAG, OCTAHEDRAL_RESOLVE_VERT, } from './shaders/octahedralResolve.js';
6
+ /** Filled per resolved face, at module scope, because a bake runs inside the frame's budget. */
7
+ const faceRotation = mat3.create();
8
+ const CLEAR = new Float32Array([1, 1, 1, 1]);
9
+ /**
10
+ * What a lamp's light keeps through glass: an RGBA8 array of one layer a light, parallel to the
11
+ * point shadow array and indexed by the light, not by the interleaved depth layer. RGB is the
12
+ * product of every pane's `transmission × (1 − F) × tint`, A the product of their clarities
13
+ * (`glassShadow.ts`).
14
+ *
15
+ * **Drawn a face at a time, as the depth is**: into a face-sized colour scratch cleared to white,
16
+ * with a multiplying blend and no depth test, then resolved into the face's region of the layer by
17
+ * `OCTAHEDRAL_RESOLVE_TINT_FRAG`. One scratch for the whole engine, for the reason
18
+ * `PointShadowArray` gives for its own.
19
+ *
20
+ * **Which faces hold glass is remembered, six bits a layer**, so a face with no glass and none to
21
+ * forget costs nothing, and a face that inherited a previous owner's glass is cleared. **The chain
22
+ * frost reads is rebuilt once a round**, for the layers the round touched, through a 2D staging
23
+ * chain: `generateMipmap` on an array rebuilds every layer, which is the whole pool's texels for one
24
+ * lamp's glass. What that costs is one layer of memory with its chain — 5.6 MB at 1024, 1.4 MB at
25
+ * `'half'` — and a copy out and back per touched layer.
26
+ */
27
+ export class PointGlassTint {
28
+ texture;
29
+ edge;
30
+ layers;
31
+ levels;
32
+ faceSize;
33
+ scratch;
34
+ scratchFramebuffer;
35
+ staging;
36
+ framebuffer;
37
+ readFramebuffer;
38
+ /** Parked on the resolve's unit while the scratch is a render target; see `beginFace`. */
39
+ parked;
40
+ program;
41
+ uniforms;
42
+ vao;
43
+ /** Six bits a layer: which faces' regions hold glass. */
44
+ heldFaces;
45
+ /** Layers whose level 0 changed since the chain was last rebuilt. */
46
+ dirty;
47
+ constructor(gl, faceSize, edge, layers) {
48
+ this.edge = edge;
49
+ this.layers = layers;
50
+ this.faceSize = faceSize;
51
+ this.levels = Math.floor(Math.log2(Math.max(1, edge))) + 1;
52
+ this.heldFaces = new Uint8Array(layers);
53
+ this.dirty = new Uint8Array(layers);
54
+ this.texture = colourTexture(gl, gl.TEXTURE_2D_ARRAY, this.levels, edge, layers);
55
+ this.scratch = colourTexture(gl, gl.TEXTURE_2D, 1, faceSize, 1);
56
+ this.staging = colourTexture(gl, gl.TEXTURE_2D, this.levels, edge, 1);
57
+ this.parked = createEmptyTexture2D(gl);
58
+ this.scratchFramebuffer = framebufferOf(gl);
59
+ this.framebuffer = framebufferOf(gl);
60
+ this.readFramebuffer = framebufferOf(gl);
61
+ gl.bindFramebuffer(gl.FRAMEBUFFER, this.scratchFramebuffer);
62
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.scratch, 0);
63
+ this.program = compileProgram(gl, OCTAHEDRAL_RESOLVE_VERT, OCTAHEDRAL_RESOLVE_TINT_FRAG, 'octahedralResolveTint');
64
+ this.uniforms = uniformLocations(gl, this.program, 'octahedralResolveTint');
65
+ const vao = gl.createVertexArray();
66
+ if (vao === null)
67
+ throw new Error('PointGlassTint: createVertexArray failed');
68
+ this.vao = vao;
69
+ /* Every layer born clear: a light no pane has reached lets everything through. */
70
+ gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
71
+ for (let layer = 0; layer < layers; layer++) {
72
+ gl.framebufferTextureLayer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, this.texture, 0, layer);
73
+ gl.clearBufferfv(gl.COLOR, 0, CLEAR);
74
+ }
75
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
76
+ /* Once, for every layer: white all the way down. */
77
+ gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.texture);
78
+ gl.generateMipmap(gl.TEXTURE_2D_ARRAY);
79
+ gl.bindTexture(gl.TEXTURE_2D_ARRAY, null);
80
+ }
81
+ /** Whether `face` of `layer` holds glass: a face that does must be cleared before it is reused. */
82
+ held(layer, face) {
83
+ return (((this.heldFaces[layer] ?? 0) >> face) & 1) === 1;
84
+ }
85
+ /**
86
+ * Bind the colour scratch, clear it to white, and set the multiplying state.
87
+ *
88
+ * The previous resolve left the scratch bound for sampling on unit 0, and it is about to be the
89
+ * target: parked first, for the reason `PointShadowArray.beginFace` gives.
90
+ */
91
+ beginFace(gl) {
92
+ gl.activeTexture(gl.TEXTURE0);
93
+ gl.bindTexture(gl.TEXTURE_2D, this.parked);
94
+ gl.bindFramebuffer(gl.FRAMEBUFFER, this.scratchFramebuffer);
95
+ gl.viewport(0, 0, this.faceSize, this.faceSize);
96
+ gl.clearBufferfv(gl.COLOR, 0, CLEAR);
97
+ gl.disable(gl.DEPTH_TEST);
98
+ gl.depthMask(false);
99
+ gl.enable(gl.BLEND);
100
+ gl.blendEquation(gl.FUNC_ADD);
101
+ gl.blendFuncSeparate(gl.DST_COLOR, gl.ZERO, gl.DST_ALPHA, gl.ZERO);
102
+ }
103
+ /**
104
+ * Copy the scratch into `face`'s region of `layer`, and remember whether it held glass.
105
+ *
106
+ * Leaves depth testing and writing on, which is what the next face's casters expect: the depth
107
+ * resolve that runs before them turns both on, and this one ran after it and turned them off.
108
+ */
109
+ resolveFace(gl, layer, face, drawn) {
110
+ gl.disable(gl.BLEND);
111
+ gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
112
+ gl.framebufferTextureLayer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, this.texture, 0, layer);
113
+ gl.viewport(0, 0, this.edge, this.edge);
114
+ gl.useProgram(this.program);
115
+ gl.bindVertexArray(this.vao);
116
+ gl.activeTexture(gl.TEXTURE0);
117
+ gl.bindTexture(gl.TEXTURE_2D, this.scratch);
118
+ gl.uniform1i(this.uniforms['uFace'] ?? null, 0);
119
+ gl.uniform1i(this.uniforms['uFaceIndex'] ?? null, face);
120
+ gl.uniformMatrix3fv(this.uniforms['uFaceRotation'] ?? null, false, pointShadowFaceRotation(face, faceRotation));
121
+ gl.uniform1f(this.uniforms['uEdge'] ?? null, this.edge);
122
+ gl.drawArrays(gl.TRIANGLES, 0, 3);
123
+ gl.bindVertexArray(null);
124
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
125
+ gl.enable(gl.DEPTH_TEST);
126
+ gl.depthMask(true);
127
+ const bit = 1 << face;
128
+ const was = this.heldFaces[layer] ?? 0;
129
+ this.heldFaces[layer] = drawn ? was | bit : was & ~bit;
130
+ this.dirty[layer] = 1;
131
+ }
132
+ /** Rebuild the chain of every layer touched since the last call, one layer at a time. */
133
+ fillMips(gl) {
134
+ for (let layer = 0; layer < this.layers; layer++) {
135
+ if (this.dirty[layer] !== 1)
136
+ continue;
137
+ this.dirty[layer] = 0;
138
+ /* Level 0 out into the staging chain, which mipmaps as one 2D texture. */
139
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, this.readFramebuffer);
140
+ gl.framebufferTextureLayer(gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, this.texture, 0, layer);
141
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, this.framebuffer);
142
+ gl.framebufferTexture2D(gl.DRAW_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.staging, 0);
143
+ blit(gl, this.edge, this.edge);
144
+ gl.bindTexture(gl.TEXTURE_2D, this.staging);
145
+ gl.generateMipmap(gl.TEXTURE_2D);
146
+ gl.bindTexture(gl.TEXTURE_2D, null);
147
+ /* And every level after it back into the layer. */
148
+ for (let level = 1; level < this.levels; level++) {
149
+ const size = Math.max(1, this.edge >> level);
150
+ gl.framebufferTexture2D(gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.staging, level);
151
+ gl.framebufferTextureLayer(gl.DRAW_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, this.texture, level, layer);
152
+ blit(gl, size, size);
153
+ }
154
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, null);
155
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null);
156
+ }
157
+ }
158
+ dispose(gl) {
159
+ gl.deleteFramebuffer(this.scratchFramebuffer);
160
+ gl.deleteFramebuffer(this.framebuffer);
161
+ gl.deleteFramebuffer(this.readFramebuffer);
162
+ gl.deleteTexture(this.texture);
163
+ gl.deleteTexture(this.scratch);
164
+ gl.deleteTexture(this.staging);
165
+ gl.deleteTexture(this.parked);
166
+ gl.deleteProgram(this.program);
167
+ gl.deleteVertexArray(this.vao);
168
+ }
169
+ }
170
+ /** A colour texture with `levels` levels: nearest for a single level, trilinear for a chain. */
171
+ function colourTexture(gl, target, levels, edge, layers) {
172
+ const texture = gl.createTexture();
173
+ if (texture === null)
174
+ throw new Error('PointGlassTint: createTexture failed');
175
+ gl.bindTexture(target, texture);
176
+ if (target === gl.TEXTURE_2D_ARRAY) {
177
+ gl.texStorage3D(target, levels, gl.RGBA8, edge, edge, layers);
178
+ }
179
+ else {
180
+ gl.texStorage2D(target, levels, gl.RGBA8, edge, edge);
181
+ }
182
+ gl.texParameteri(target, gl.TEXTURE_MIN_FILTER, levels > 1 ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST);
183
+ gl.texParameteri(target, gl.TEXTURE_MAG_FILTER, levels > 1 ? gl.LINEAR : gl.NEAREST);
184
+ gl.texParameteri(target, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
185
+ gl.texParameteri(target, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
186
+ gl.bindTexture(target, null);
187
+ return texture;
188
+ }
189
+ function framebufferOf(gl) {
190
+ const framebuffer = gl.createFramebuffer();
191
+ if (framebuffer === null)
192
+ throw new Error('PointGlassTint: createFramebuffer failed');
193
+ return framebuffer;
194
+ }
195
+ /** The bound read framebuffer's colour into the bound draw framebuffer's, texel for texel. */
196
+ function blit(gl, width, height) {
197
+ gl.blitFramebuffer(0, 0, width, height, 0, 0, width, height, gl.COLOR_BUFFER_BIT, gl.NEAREST);
198
+ }