@driftengine/core 4.5.0 → 4.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (209) hide show
  1. package/CHANGELOG.json +62 -0
  2. package/README.md +1 -1
  3. package/dist/geometry/boxCsg.d.ts +28 -0
  4. package/dist/geometry/boxCsg.js +139 -0
  5. package/dist/geometry/csg.d.ts +36 -0
  6. package/dist/geometry/csg.js +329 -0
  7. package/dist/geometry/meshBuilder.js +13 -0
  8. package/dist/geometry/meshConcat.js +1 -0
  9. package/dist/geometry/pixelFont.js +2 -0
  10. package/dist/geometry/solid.d.ts +61 -0
  11. package/dist/geometry/solid.js +152 -0
  12. package/dist/geometry/solidFlat.d.ts +35 -0
  13. package/dist/geometry/solidFlat.js +428 -0
  14. package/dist/geometry/solidHarness.d.ts +19 -0
  15. package/dist/geometry/solidHarness.js +66 -0
  16. package/dist/geometry/solidNormals.d.ts +17 -0
  17. package/dist/geometry/solidNormals.js +86 -0
  18. package/dist/geometry/solidRound.d.ts +41 -0
  19. package/dist/geometry/solidRound.js +323 -0
  20. package/dist/geometry/solidWriter.d.ts +16 -0
  21. package/dist/geometry/solidWriter.js +42 -0
  22. package/dist/index.d.ts +23 -2
  23. package/dist/index.js +13 -1
  24. package/dist/render/backend/api.d.ts +17 -3
  25. package/dist/render/backend/drawFog.d.ts +26 -0
  26. package/dist/render/backend/drawFog.js +30 -0
  27. package/dist/render/backend/materialChanges.d.ts +2 -1
  28. package/dist/render/backend/materialChanges.js +6 -1
  29. package/dist/render/backend/webgl2/instanced.d.ts +24 -5
  30. package/dist/render/backend/webgl2/instanced.js +72 -15
  31. package/dist/render/backend/webgl2/renderer.d.ts +122 -8
  32. package/dist/render/backend/webgl2/renderer.js +249 -128
  33. package/dist/render/backend/webgpu/buffers.d.ts +17 -0
  34. package/dist/render/backend/webgpu/buffers.js +34 -3
  35. package/dist/render/backend/webgpu/depthPass.js +5 -1
  36. package/dist/render/backend/webgpu/drawCommand.d.ts +5 -0
  37. package/dist/render/backend/webgpu/drawCommand.js +1 -0
  38. package/dist/render/backend/webgpu/flatPass.d.ts +4 -1
  39. package/dist/render/backend/webgpu/flatPass.js +38 -9
  40. package/dist/render/backend/webgpu/glassTintPass.js +2 -1
  41. package/dist/render/backend/webgpu/instanceCullPass.d.ts +34 -0
  42. package/dist/render/backend/webgpu/instanceCullPass.js +144 -0
  43. package/dist/render/backend/webgpu/instanced.d.ts +11 -1
  44. package/dist/render/backend/webgpu/instanced.js +21 -3
  45. package/dist/render/backend/webgpu/latePass.d.ts +6 -2
  46. package/dist/render/backend/webgpu/latePass.js +11 -4
  47. package/dist/render/backend/webgpu/motionPass.js +2 -1
  48. package/dist/render/backend/webgpu/postPass.d.ts +1 -1
  49. package/dist/render/backend/webgpu/renderer.d.ts +100 -10
  50. package/dist/render/backend/webgpu/renderer.js +478 -214
  51. package/dist/render/backend/webgpu/sdfTextPass.js +6 -1
  52. package/dist/render/backend/webgpu/shaders/instanceCull.wgsl.d.ts +18 -0
  53. package/dist/render/backend/webgpu/shaders/instanceCull.wgsl.js +59 -0
  54. package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +17 -3
  55. package/dist/render/backend/webgpu/surfaceTexturePass.js +77 -38
  56. package/dist/render/driftLight/bake.d.ts +7 -0
  57. package/dist/render/driftLight/bake.js +61 -56
  58. package/dist/render/driftLight/denseField.d.ts +46 -0
  59. package/dist/render/driftLight/denseField.js +109 -0
  60. package/dist/render/driftLight/lightField.d.ts +10 -5
  61. package/dist/render/driftLight/lightField.js +25 -19
  62. package/dist/render/driftLight/presence.d.ts +50 -0
  63. package/dist/render/driftLight/presence.js +43 -0
  64. package/dist/render/driftLight/uniforms.d.ts +3 -3
  65. package/dist/render/driftLight/uniforms.js +5 -6
  66. package/dist/render/driftLight/worldLightField.d.ts +41 -0
  67. package/dist/render/driftLight/worldLightField.js +66 -0
  68. package/dist/render/fixtureAtlas.d.ts +20 -0
  69. package/dist/render/fixtureAtlas.js +49 -0
  70. package/dist/render/instanceCull.d.ts +63 -0
  71. package/dist/render/instanceCull.js +160 -0
  72. package/dist/render/instances.d.ts +15 -0
  73. package/dist/render/lightBudget.d.ts +22 -33
  74. package/dist/render/lightBudget.js +24 -35
  75. package/dist/render/lightGrid.d.ts +42 -0
  76. package/dist/render/lightGrid.js +87 -0
  77. package/dist/render/mesh.d.ts +23 -24
  78. package/dist/render/mesh.js +56 -68
  79. package/dist/render/occlusion.d.ts +10 -0
  80. package/dist/render/occlusion.js +36 -7
  81. package/dist/render/pointLightSelection.d.ts +7 -0
  82. package/dist/render/pointLightSelection.js +29 -4
  83. package/dist/render/rainField.d.ts +13 -0
  84. package/dist/render/rainField.js +27 -5
  85. package/dist/render/recon/lateDepth.d.ts +11 -0
  86. package/dist/render/recon/lateDepth.js +70 -0
  87. package/dist/render/recon/resolve.d.ts +34 -4
  88. package/dist/render/recon/resolve.js +161 -20
  89. package/dist/render/rendererHarness.js +4 -0
  90. package/dist/render/sceneTarget.d.ts +2 -0
  91. package/dist/render/sceneTarget.js +2 -0
  92. package/dist/render/shaders/depth.js +12 -9
  93. package/dist/render/shaders/flat/driftLight.d.ts +1 -1
  94. package/dist/render/shaders/flat/driftLight.js +32 -7
  95. package/dist/render/shaders/flat/index.js +5 -3
  96. package/dist/render/shaders/flat/main.d.ts +1 -1
  97. package/dist/render/shaders/flat/main.js +75 -23
  98. package/dist/render/shaders/flat/preamble.js +75 -20
  99. package/dist/render/shaders/flat/surfaceEffects.d.ts +29 -0
  100. package/dist/render/shaders/flat/surfaceEffects.js +152 -0
  101. package/dist/render/shaders/generated/depth.wgsl.d.ts +8 -8
  102. package/dist/render/shaders/generated/depth.wgsl.js +8 -8
  103. package/dist/render/shaders/generated/flat.wgsl.d.ts +1320 -1320
  104. package/dist/render/shaders/generated/flat.wgsl.js +1359 -1345
  105. package/dist/render/shaders/generated/lightVolume.wgsl.js +18 -4
  106. package/dist/render/shaders/generated/rush.wgsl.d.ts +21 -16
  107. package/dist/render/shaders/generated/rush.wgsl.js +21 -16
  108. package/dist/render/shaders/generated/sdfText.wgsl.d.ts +2 -2
  109. package/dist/render/shaders/generated/sdfText.wgsl.js +2 -2
  110. package/dist/render/shaders/generated/text.wgsl.d.ts +1 -1
  111. package/dist/render/shaders/generated/text.wgsl.js +1 -1
  112. package/dist/render/shaders/gi/driftLight.wgsl.d.ts +1 -1
  113. package/dist/render/shaders/gi/driftLight.wgsl.js +27 -4
  114. package/dist/render/shaders/recon/lateDepth.wgsl.js +38 -13
  115. package/dist/render/shaders/recon/resolve.wgsl.d.ts +8 -7
  116. package/dist/render/shaders/recon/resolve.wgsl.js +161 -27
  117. package/dist/render/shaders/rush.js +11 -1
  118. package/dist/render/shaders/sdfText.d.ts +1 -1
  119. package/dist/render/shaders/sdfText.js +3 -2
  120. package/dist/render/shaders/text.d.ts +1 -1
  121. package/dist/render/shaders/text.js +18 -9
  122. package/dist/render/surfaceEffects.d.ts +83 -0
  123. package/dist/render/surfaceEffects.js +75 -0
  124. package/dist/render/surfaceScene.d.ts +8 -0
  125. package/dist/render/surfaceScene.js +8 -0
  126. package/dist/render/surfaceTexture.d.ts +18 -2
  127. package/dist/render/surfaceTexture.js +68 -15
  128. package/dist/render/temporalAa.d.ts +25 -6
  129. package/dist/render/temporalAa.js +45 -15
  130. package/dist/render/textureSource.d.ts +30 -0
  131. package/dist/render/textureSource.js +53 -0
  132. package/dist/render/vertexDefaults.js +2 -0
  133. package/dist/world/hlod.d.ts +104 -0
  134. package/dist/world/hlod.js +214 -0
  135. package/package.json +6 -6
  136. package/src/geometry/boxCsg.ts +186 -0
  137. package/src/geometry/csg.ts +384 -0
  138. package/src/geometry/meshBuilder.ts +15 -0
  139. package/src/geometry/meshConcat.ts +1 -0
  140. package/src/geometry/pixelFont.ts +2 -0
  141. package/src/geometry/solid.ts +189 -0
  142. package/src/geometry/solidFlat.ts +522 -0
  143. package/src/geometry/solidHarness.ts +83 -0
  144. package/src/geometry/solidNormals.ts +103 -0
  145. package/src/geometry/solidRound.ts +443 -0
  146. package/src/geometry/solidWriter.ts +58 -0
  147. package/src/index.ts +51 -1
  148. package/src/render/backend/api.ts +21 -2
  149. package/src/render/backend/drawFog.ts +31 -0
  150. package/src/render/backend/materialChanges.ts +9 -2
  151. package/src/render/backend/webgl2/instanced.ts +80 -14
  152. package/src/render/backend/webgl2/renderer.ts +311 -150
  153. package/src/render/backend/webgpu/buffers.ts +49 -3
  154. package/src/render/backend/webgpu/depthPass.ts +5 -1
  155. package/src/render/backend/webgpu/drawCommand.ts +6 -0
  156. package/src/render/backend/webgpu/flatPass.ts +41 -11
  157. package/src/render/backend/webgpu/glassTintPass.ts +2 -1
  158. package/src/render/backend/webgpu/instanceCullPass.ts +165 -0
  159. package/src/render/backend/webgpu/instanced.ts +21 -3
  160. package/src/render/backend/webgpu/latePass.ts +12 -3
  161. package/src/render/backend/webgpu/motionPass.ts +2 -1
  162. package/src/render/backend/webgpu/renderer.ts +556 -239
  163. package/src/render/backend/webgpu/sdfTextPass.ts +6 -1
  164. package/src/render/backend/webgpu/shaders/instanceCull.wgsl.ts +61 -0
  165. package/src/render/backend/webgpu/surfaceTexturePass.ts +90 -50
  166. package/src/render/driftLight/bake.ts +85 -62
  167. package/src/render/driftLight/denseField.ts +149 -0
  168. package/src/render/driftLight/lightField.ts +33 -18
  169. package/src/render/driftLight/presence.ts +69 -0
  170. package/src/render/driftLight/uniforms.ts +8 -9
  171. package/src/render/driftLight/worldLightField.ts +83 -0
  172. package/src/render/fixtureAtlas.ts +63 -0
  173. package/src/render/instanceCull.ts +205 -0
  174. package/src/render/instances.ts +16 -0
  175. package/src/render/lightBudget.ts +25 -36
  176. package/src/render/lightGrid.ts +130 -0
  177. package/src/render/mesh.ts +64 -72
  178. package/src/render/occlusion.ts +51 -7
  179. package/src/render/pointLightSelection.ts +72 -4
  180. package/src/render/rainField.ts +39 -5
  181. package/src/render/recon/lateDepth.ts +83 -0
  182. package/src/render/recon/resolve.ts +206 -20
  183. package/src/render/rendererHarness.ts +4 -0
  184. package/src/render/sceneTarget.ts +4 -0
  185. package/src/render/shaders/depth.ts +12 -9
  186. package/src/render/shaders/flat/driftLight.ts +32 -7
  187. package/src/render/shaders/flat/index.ts +5 -3
  188. package/src/render/shaders/flat/main.ts +75 -23
  189. package/src/render/shaders/flat/preamble.ts +75 -20
  190. package/src/render/shaders/flat/surfaceEffects.ts +152 -0
  191. package/src/render/shaders/generated/depth.wgsl.ts +8 -8
  192. package/src/render/shaders/generated/flat.wgsl.ts +1361 -1345
  193. package/src/render/shaders/generated/lightVolume.wgsl.ts +20 -4
  194. package/src/render/shaders/generated/rush.wgsl.ts +21 -16
  195. package/src/render/shaders/generated/sdfText.wgsl.ts +2 -2
  196. package/src/render/shaders/generated/text.wgsl.ts +1 -1
  197. package/src/render/shaders/gi/driftLight.wgsl.ts +27 -4
  198. package/src/render/shaders/recon/lateDepth.wgsl.ts +38 -13
  199. package/src/render/shaders/recon/resolve.wgsl.ts +161 -27
  200. package/src/render/shaders/rush.ts +11 -1
  201. package/src/render/shaders/sdfText.ts +3 -2
  202. package/src/render/shaders/text.ts +18 -9
  203. package/src/render/surfaceEffects.ts +132 -0
  204. package/src/render/surfaceScene.ts +16 -0
  205. package/src/render/surfaceTexture.ts +110 -19
  206. package/src/render/temporalAa.ts +78 -14
  207. package/src/render/textureSource.ts +72 -0
  208. package/src/render/vertexDefaults.ts +2 -0
  209. package/src/world/hlod.ts +290 -0
@@ -1,4 +1,4 @@
1
- /** A GPU image the caller supplies, sampled by the flat shader as surface colour. */
1
+ import type { SurfaceLayerEffect } from './surfaceEffects.ts';
2
2
  /**
3
3
  * How a texture behaves past its edges and between its texels.
4
4
  *
@@ -101,6 +101,12 @@ export interface SurfaceMaterial<Texture = SurfaceTexture> {
101
101
  emissiveScale?: readonly [number, number, number];
102
102
  }
103
103
  export interface SurfaceTextureOptions {
104
+ /**
105
+ * What each layer does beyond its picture — rooms behind windows, windows lit by night, wear,
106
+ * animation, staying dry — one entry a layer, `undefined` for a layer with none. Read only where
107
+ * this texture is a material's albedo. See `surfaceEffects.ts`.
108
+ */
109
+ effects?: readonly (SurfaceLayerEffect | undefined)[];
104
110
  /**
105
111
  * `repeat` tiles the image, `clamp` stretches its edge texels.
106
112
  *
@@ -183,7 +189,17 @@ export declare class SurfaceTexture {
183
189
  private readonly mipmapped;
184
190
  /** Whether the sampler decodes sRGB on fetch. See `colorSpace`. */
185
191
  private readonly srgb;
186
- constructor(gl: WebGL2RenderingContext, source: TexImageSource, options?: SurfaceTextureOptions);
192
+ /** How many images the array holds: 1 for `createSurfaceTexture`, the list's length otherwise. */
193
+ readonly layers: number;
194
+ /** The per-layer effects table, or null for a texture given none. See `surfaceEffects.ts`. */
195
+ private effectsTable;
196
+ /**
197
+ * One image, or an array of images that must share one size (`layerSize` refuses otherwise).
198
+ * Uploaded unflipped either way; see the constructor body for why.
199
+ */
200
+ constructor(gl: WebGL2RenderingContext, source: TexImageSource | readonly TexImageSource[], options?: SurfaceTextureOptions);
201
+ /** Bind the effects table to `unit`, or `fallback` for a texture given none. */
202
+ bindEffects(gl: WebGL2RenderingContext, unit: number, fallback: WebGLTexture): void;
187
203
  /**
188
204
  * Replace the pixels, keeping the GPU object and its sampler state.
189
205
  *
@@ -1,4 +1,15 @@
1
- /** A GPU image the caller supplies, sampled by the flat shader as surface colour. */
1
+ /**
2
+ * A GPU image the caller supplies, sampled by the flat shader as surface colour — or several, as
3
+ * the layers of one array.
4
+ *
5
+ * **Every surface texture is an array; a plain image is an array of one.** The lit pass has three of
6
+ * WebGL2's guaranteed sixteen texture units free, so a second set of array samplers beside the 2D
7
+ * ones was never affordable. Making the one set of samplers arrays costs no unit, and a one-layer
8
+ * array samples exactly as a 2D texture does. A mesh names its layer per vertex (`MeshData.layers`),
9
+ * so a merged block wearing forty facades is one draw.
10
+ */
11
+ import { isSourceList, layerSize, refuseArrayUpdate, sourceSize } from './textureSource.js';
12
+ import { SURFACE_EFFECT_TEXELS, packSurfaceEffects } from './surfaceEffects.js';
2
13
  const DEFAULT_ANISOTROPY = 4;
3
14
  /**
4
15
  * An image uploaded once and bound per draw.
@@ -14,7 +25,18 @@ export class SurfaceTexture {
14
25
  mipmapped;
15
26
  /** Whether the sampler decodes sRGB on fetch. See `colorSpace`. */
16
27
  srgb;
28
+ /** How many images the array holds: 1 for `createSurfaceTexture`, the list's length otherwise. */
29
+ layers;
30
+ /** The per-layer effects table, or null for a texture given none. See `surfaceEffects.ts`. */
31
+ effectsTable = null;
32
+ /**
33
+ * One image, or an array of images that must share one size (`layerSize` refuses otherwise).
34
+ * Uploaded unflipped either way; see the constructor body for why.
35
+ */
17
36
  constructor(gl, source, options = {}) {
37
+ const sources = isSourceList(source) ? source : [source];
38
+ const { width, height } = layerSize(sources);
39
+ this.layers = sources.length;
18
40
  const texture = gl.createTexture();
19
41
  if (texture === null)
20
42
  throw new Error('SurfaceTexture: createTexture failed');
@@ -22,7 +44,7 @@ export class SurfaceTexture {
22
44
  this.mipmapped = options.mipmap ?? true;
23
45
  this.srgb = (options.colorSpace ?? 'linear') === 'srgb';
24
46
  const wrap = (options.wrap ?? 'repeat') === 'repeat' ? gl.REPEAT : gl.CLAMP_TO_EDGE;
25
- gl.bindTexture(gl.TEXTURE_2D, texture);
47
+ gl.bindTexture(gl.TEXTURE_2D_ARRAY, texture);
26
48
  /*
27
49
  * **Not flipped, and this used to be, which is the bug it is written down for.**
28
50
  *
@@ -44,15 +66,20 @@ export class SurfaceTexture {
44
66
  *
45
67
  * A caller that was mirroring its own canvas to cancel this must stop.
46
68
  */
47
- gl.texImage2D(gl.TEXTURE_2D, 0, this.srgb ? gl.SRGB8_ALPHA8 : gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, source);
48
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, wrap);
49
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, wrap);
69
+ /* Allocated mutable rather than with texStorage3D, so `update` can hand a one-layer texture a
70
+ new size, as texImage2D always could. Each layer is then written in place. */
71
+ gl.texImage3D(gl.TEXTURE_2D_ARRAY, 0, this.srgb ? gl.SRGB8_ALPHA8 : gl.RGBA, width, height, sources.length, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
72
+ for (let layer = 0; layer < sources.length; layer++) {
73
+ gl.texSubImage3D(gl.TEXTURE_2D_ARRAY, 0, 0, 0, layer, width, height, 1, gl.RGBA, gl.UNSIGNED_BYTE, sources[layer]);
74
+ }
75
+ gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_S, wrap);
76
+ gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_T, wrap);
50
77
  /* Nearest where the caller said their pixels are the subject; see `filter`. The mip chain is
51
78
  still blended between levels, because that is minification and this option is about
52
79
  magnification. */
53
80
  const nearest = options.filter === 'nearest';
54
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, nearest ? gl.NEAREST : gl.LINEAR);
55
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, this.mipmapped
81
+ gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MAG_FILTER, nearest ? gl.NEAREST : gl.LINEAR);
82
+ gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MIN_FILTER, this.mipmapped
56
83
  ? nearest
57
84
  ? gl.NEAREST_MIPMAP_LINEAR
58
85
  : gl.LINEAR_MIPMAP_LINEAR
@@ -60,9 +87,30 @@ export class SurfaceTexture {
60
87
  ? gl.NEAREST
61
88
  : gl.LINEAR);
62
89
  if (this.mipmapped)
63
- gl.generateMipmap(gl.TEXTURE_2D);
90
+ gl.generateMipmap(gl.TEXTURE_2D_ARRAY);
64
91
  applyAnisotropy(gl, options.anisotropy ?? DEFAULT_ANISOTROPY);
65
- gl.bindTexture(gl.TEXTURE_2D, null);
92
+ gl.bindTexture(gl.TEXTURE_2D_ARRAY, null);
93
+ /* Half floats, nearest, clamped: a table read by texelFetch, whose every whole number is under
94
+ what a half holds exactly. */
95
+ const effects = options.effects ?? [];
96
+ if (effects.length > 0) {
97
+ const table = gl.createTexture();
98
+ if (table === null)
99
+ throw new Error('SurfaceTexture: createTexture failed');
100
+ gl.bindTexture(gl.TEXTURE_2D, table);
101
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA16F, SURFACE_EFFECT_TEXELS, effects.length, 0, gl.RGBA, gl.FLOAT, packSurfaceEffects(effects));
102
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
103
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
104
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
105
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
106
+ gl.bindTexture(gl.TEXTURE_2D, null);
107
+ this.effectsTable = table;
108
+ }
109
+ }
110
+ /** Bind the effects table to `unit`, or `fallback` for a texture given none. */
111
+ bindEffects(gl, unit, fallback) {
112
+ gl.activeTexture(gl.TEXTURE0 + unit);
113
+ gl.bindTexture(gl.TEXTURE_2D, this.effectsTable ?? fallback);
66
114
  }
67
115
  /**
68
116
  * Replace the pixels, keeping the GPU object and its sampler state.
@@ -78,23 +126,28 @@ export class SurfaceTexture {
78
126
  update(gl, source) {
79
127
  if (this.texture === null)
80
128
  return;
81
- gl.bindTexture(gl.TEXTURE_2D, this.texture);
129
+ refuseArrayUpdate(this.layers);
130
+ const { width, height } = sourceSize(source);
131
+ gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.texture);
82
132
  /* Unflipped, matching the constructor. See it for why, and for what it cost. */
83
- gl.texImage2D(gl.TEXTURE_2D, 0, this.srgb ? gl.SRGB8_ALPHA8 : gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, source);
133
+ gl.texImage3D(gl.TEXTURE_2D_ARRAY, 0, this.srgb ? gl.SRGB8_ALPHA8 : gl.RGBA, width, height, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, source);
84
134
  if (this.mipmapped)
85
- gl.generateMipmap(gl.TEXTURE_2D);
86
- gl.bindTexture(gl.TEXTURE_2D, null);
135
+ gl.generateMipmap(gl.TEXTURE_2D_ARRAY);
136
+ gl.bindTexture(gl.TEXTURE_2D_ARRAY, null);
87
137
  }
88
138
  /** Bind to a unit for sampling. Engine-internal: the renderer owns unit assignment. */
89
139
  bind(gl, unit) {
90
140
  gl.activeTexture(gl.TEXTURE0 + unit);
91
- gl.bindTexture(gl.TEXTURE_2D, this.texture);
141
+ gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.texture);
92
142
  }
93
143
  dispose(gl) {
94
144
  if (this.texture === null)
95
145
  return;
96
146
  gl.deleteTexture(this.texture);
97
147
  this.texture = null;
148
+ if (this.effectsTable !== null)
149
+ gl.deleteTexture(this.effectsTable);
150
+ this.effectsTable = null;
98
151
  }
99
152
  }
100
153
  /**
@@ -112,5 +165,5 @@ function applyAnisotropy(gl, requested) {
112
165
  if (ext === null)
113
166
  return;
114
167
  const max = gl.getParameter(ext.MAX_TEXTURE_MAX_ANISOTROPY_EXT);
115
- gl.texParameterf(gl.TEXTURE_2D, ext.TEXTURE_MAX_ANISOTROPY_EXT, Math.min(requested, max));
168
+ gl.texParameterf(gl.TEXTURE_2D_ARRAY, ext.TEXTURE_MAX_ANISOTROPY_EXT, Math.min(requested, max));
116
169
  }
@@ -123,6 +123,24 @@ export declare const FLICKER_MEMORY: number;
123
123
  * Threads caught in a share nearer a half spread more and need fewer.
124
124
  */
125
125
  export declare const FLICKER_WIDTH = 6;
126
+ /**
127
+ * The two numbers the proof takes from the jitter's period: how much of a period one frame is, and
128
+ * how many spreads a proven pixel's box widens by. This resolve's are `FLICKER_MEMORY` and
129
+ * `FLICKER_WIDTH`; a reconstruction jitters along a longer sequence and needs its own.
130
+ */
131
+ export interface FlickerRule {
132
+ readonly memory: number;
133
+ readonly width: number;
134
+ }
135
+ /** The temporal resolve's own: eight phases. */
136
+ export declare const TEMPORAL_FLICKER: FlickerRule;
137
+ /**
138
+ * The rule for a jitter of `phases` positions. **The width keeps the room `FLICKER_WIDTH` leaves over
139
+ * what eight phases need** — 4.8 — in proportion: a longer period wears the spread down further
140
+ * between catches, so over eighteen phases the hardest thread needs eleven spreads, and a box that
141
+ * reached six reset the count of exactly the pixels this is for.
142
+ */
143
+ export declare function flickerRule(phases: number): FlickerRule;
126
144
  /**
127
145
  * How far apart two periods' means may be, relative to the larger, and still repeat: fully below
128
146
  * this, not at all at twice it. Above an eight-bit record's rounding and a lamp's slow breathing; far
@@ -192,7 +210,7 @@ export interface Flicker {
192
210
  * record of another surface is no record**, so the resolve starts one over wherever last frame's
193
211
  * depth says the surface is not the one it followed, before this frame widens by anything.
194
212
  */
195
- export declare function freshFlicker(out: Flicker, sample: number): Flicker;
213
+ export declare function freshFlicker(out: Flicker, sample: number, rule?: FlickerRule): Flicker;
196
214
  /** Rec. 709 luma, which is what the record measures in. */
197
215
  export declare function luma(c: readonly [number, number, number]): number;
198
216
  /** One for a pixel whose reprojection did not move, falling to none at a twentieth of a pixel. */
@@ -202,20 +220,21 @@ export declare function stillness(motionPixels: number): number;
202
220
  * lands within the reach of the box it would widen to. A sample beyond that reach is something
203
221
  * arriving, and it disqualifies the frame it lands in as well as the count it carries out.
204
222
  */
205
- export declare function proven(record: Flicker, sample: number): boolean;
223
+ export declare function proven(record: Flicker, sample: number, rule?: FlickerRule): boolean;
206
224
  /** How far a pixel's box widens this frame: by its spread, once proven, and only while still. */
207
- export declare function flickerWidening(record: Flicker, sample: number, still: number): number;
225
+ export declare function flickerWidening(record: Flicker, sample: number, still: number, rule?: FlickerRule): number;
208
226
  /** How much of the history this frame keeps: the base blend, or more once proven. */
209
- export declare function flickerBlend(base: number, record: Flicker, sample: number, still: number): number;
227
+ export declare function flickerBlend(base: number, record: Flicker, sample: number, still: number, rule?: FlickerRule): number;
210
228
  /** Whether two periods' means are the same, one to zero. */
211
229
  export declare function repeats(mean: number, previous: number): number;
212
230
  /**
213
231
  * The record carried out, from the one carried in and this frame's sample in luma. `periodStart`
214
232
  * is the first frame of the jitter's period, where the period just gathered is compared with the
215
233
  * one before. Forgotten in proportion to motion — a moving pixel's record would be a record of
216
- * somewhere else.
234
+ * somewhere else. `rule` is this resolve's eight phases unless a caller jitters along another
235
+ * sequence, as a reconstruction does.
217
236
  */
218
- export declare function nextFlicker(out: Flicker, previous: Flicker, sample: number, still: number, periodStart: boolean): Flicker;
237
+ export declare function nextFlicker(out: Flicker, previous: Flicker, sample: number, still: number, periodStart: boolean, rule?: FlickerRule): Flicker;
219
238
  /**
220
239
  * Whether the last frame left a picture this one may sample, and where the jitter has got to.
221
240
  *
@@ -176,6 +176,35 @@ export const FLICKER_MEMORY = 1 / JITTER_PERIOD;
176
176
  * Threads caught in a share nearer a half spread more and need fewer.
177
177
  */
178
178
  export const FLICKER_WIDTH = 6;
179
+ /** The temporal resolve's own: eight phases. */
180
+ export const TEMPORAL_FLICKER = { memory: FLICKER_MEMORY, width: FLICKER_WIDTH };
181
+ /**
182
+ * The running spread of the hardest thread — one caught in a single phase of `phases` — just before
183
+ * its catch, as a share of its contrast: the steady state `FLICKER_WIDTH`'s comment solves for eight
184
+ * phases, solved for any. With m one over the period and r = (1 − m)^(p − 1), the seven, or p − 1,
185
+ * misses that wear it down: a = (m + ((p − 1)m² − m)·r) / (1 − (1 − m)·r).
186
+ */
187
+ function hardestSpread(phases) {
188
+ const m = 1 / phases;
189
+ const r = (1 - m) ** (phases - 1);
190
+ return (m + ((phases - 1) * m * m - m) * r) / (1 - (1 - m) * r);
191
+ }
192
+ /** How many of those spreads the hardest thread's catch lands from its period's mean. */
193
+ function widthNeeded(phases) {
194
+ return (1 - 1 / phases) / hardestSpread(phases);
195
+ }
196
+ /**
197
+ * The rule for a jitter of `phases` positions. **The width keeps the room `FLICKER_WIDTH` leaves over
198
+ * what eight phases need** — 4.8 — in proportion: a longer period wears the spread down further
199
+ * between catches, so over eighteen phases the hardest thread needs eleven spreads, and a box that
200
+ * reached six reset the count of exactly the pixels this is for.
201
+ */
202
+ export function flickerRule(phases) {
203
+ return {
204
+ memory: 1 / phases,
205
+ width: (FLICKER_WIDTH * widthNeeded(phases)) / widthNeeded(JITTER_PERIOD),
206
+ };
207
+ }
179
208
  /**
180
209
  * How far apart two periods' means may be, relative to the larger, and still repeat: fully below
181
210
  * this, not at all at twice it. Above an eight-bit record's rounding and a lamp's slow breathing; far
@@ -246,9 +275,9 @@ export const STILL_TO = 0.05;
246
275
  * record of another surface is no record**, so the resolve starts one over wherever last frame's
247
276
  * depth says the surface is not the one it followed, before this frame widens by anything.
248
277
  */
249
- export function freshFlicker(out, sample) {
278
+ export function freshFlicker(out, sample, rule = TEMPORAL_FLICKER) {
250
279
  out.spread = 0;
251
- out.sum = sample * FLICKER_MEMORY;
280
+ out.sum = sample * rule.memory;
252
281
  out.mean = sample;
253
282
  out.repeated = 0;
254
283
  return out;
@@ -270,20 +299,20 @@ export function stillness(motionPixels) {
270
299
  * lands within the reach of the box it would widen to. A sample beyond that reach is something
271
300
  * arriving, and it disqualifies the frame it lands in as well as the count it carries out.
272
301
  */
273
- export function proven(record, sample) {
274
- return record.repeated > 0.75 && within(record, sample);
302
+ export function proven(record, sample, rule = TEMPORAL_FLICKER) {
303
+ return record.repeated > 0.75 && within(record, sample, rule);
275
304
  }
276
305
  /** Whether this frame's sample lands inside the reach of the box the record would widen to. */
277
- function within(record, sample) {
278
- return Math.abs(sample - record.mean) <= FLICKER_WIDTH * record.spread + 1 / 255;
306
+ function within(record, sample, rule) {
307
+ return Math.abs(sample - record.mean) <= rule.width * record.spread + 1 / 255;
279
308
  }
280
309
  /** How far a pixel's box widens this frame: by its spread, once proven, and only while still. */
281
- export function flickerWidening(record, sample, still) {
282
- return proven(record, sample) ? FLICKER_WIDTH * record.spread * still : 0;
310
+ export function flickerWidening(record, sample, still, rule = TEMPORAL_FLICKER) {
311
+ return proven(record, sample, rule) ? rule.width * record.spread * still : 0;
283
312
  }
284
313
  /** How much of the history this frame keeps: the base blend, or more once proven. */
285
- export function flickerBlend(base, record, sample, still) {
286
- return proven(record, sample) ? base + (FLICKER_BLEND - base) * still : base;
314
+ export function flickerBlend(base, record, sample, still, rule = TEMPORAL_FLICKER) {
315
+ return proven(record, sample, rule) ? base + (FLICKER_BLEND - base) * still : base;
287
316
  }
288
317
  /** Whether two periods' means are the same, one to zero. */
289
318
  export function repeats(mean, previous) {
@@ -294,20 +323,21 @@ export function repeats(mean, previous) {
294
323
  * The record carried out, from the one carried in and this frame's sample in luma. `periodStart`
295
324
  * is the first frame of the jitter's period, where the period just gathered is compared with the
296
325
  * one before. Forgotten in proportion to motion — a moving pixel's record would be a record of
297
- * somewhere else.
326
+ * somewhere else. `rule` is this resolve's eight phases unless a caller jitters along another
327
+ * sequence, as a reconstruction does.
298
328
  */
299
- export function nextFlicker(out, previous, sample, still, periodStart) {
329
+ export function nextFlicker(out, previous, sample, still, periodStart, rule = TEMPORAL_FLICKER) {
300
330
  const apart = Math.abs(sample - previous.mean);
301
331
  let { sum, mean, repeated } = previous;
302
- if (apart > FLICKER_WIDTH * previous.spread + 1 / 255)
332
+ if (apart > rule.width * previous.spread + 1 / 255)
303
333
  repeated = 0;
304
334
  if (periodStart) {
305
335
  repeated = repeats(sum, mean) > 0.5 ? Math.min(1, repeated + 0.5) : 0;
306
336
  mean = sum;
307
337
  sum = 0;
308
338
  }
309
- out.spread = previous.spread + (apart - previous.spread) * FLICKER_MEMORY;
310
- out.sum = sum + sample * FLICKER_MEMORY;
339
+ out.spread = previous.spread + (apart - previous.spread) * rule.memory;
340
+ out.sum = sum + sample * rule.memory;
311
341
  out.mean = mean;
312
342
  out.repeated = repeated * still;
313
343
  return out;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * The size of an image a caller hands over, and the one rule a texture array adds to it: every
3
+ * layer the same size.
4
+ *
5
+ * Backend-neutral, because both backends upload the same `TexImageSource` and must agree on its
6
+ * size, and on refusing an array whose layers disagree, before either touches a device.
7
+ */
8
+ export interface SourceSize {
9
+ readonly width: number;
10
+ readonly height: number;
11
+ }
12
+ /** Width and height of any `TexImageSource`: a video by its frame, everything else by its own size. */
13
+ export declare function sourceSize(source: TexImageSource): SourceSize;
14
+ /**
15
+ * The one size every layer of an array shares, or a refusal naming the first that differs.
16
+ *
17
+ * **Refused rather than resized**, because an array layer has no size of its own — the device
18
+ * allocates one size for all of them — and quietly scaling a 128-pixel sign up to a 512 array, or a
19
+ * facade down, is a decision about quality the caller should make where they can see it. Sorting
20
+ * images into arrays by size is theirs; this makes sure they did.
21
+ */
22
+ export declare function layerSize(sources: readonly TexImageSource[]): SourceSize;
23
+ /** Whether a constructor argument is a list of images rather than one. */
24
+ export declare function isSourceList(source: TexImageSource | readonly TexImageSource[]): source is readonly TexImageSource[];
25
+ /**
26
+ * `update` replaces the one image of a plain texture. An array's layers are its identity — a
27
+ * block's facades by index — and replacing "the image" of a forty-layer array has no single
28
+ * meaning, so it is refused rather than guessed at.
29
+ */
30
+ export declare function refuseArrayUpdate(layers: number): void;
@@ -0,0 +1,53 @@
1
+ /**
2
+ * The size of an image a caller hands over, and the one rule a texture array adds to it: every
3
+ * layer the same size.
4
+ *
5
+ * Backend-neutral, because both backends upload the same `TexImageSource` and must agree on its
6
+ * size, and on refusing an array whose layers disagree, before either touches a device.
7
+ */
8
+ /** Width and height of any `TexImageSource`: a video by its frame, everything else by its own size. */
9
+ export function sourceSize(source) {
10
+ const candidate = source;
11
+ const width = candidate.videoWidth ?? candidate.width ?? 0;
12
+ const height = candidate.videoHeight ?? candidate.height ?? 0;
13
+ return { width: Math.max(1, width), height: Math.max(1, height) };
14
+ }
15
+ /**
16
+ * The one size every layer of an array shares, or a refusal naming the first that differs.
17
+ *
18
+ * **Refused rather than resized**, because an array layer has no size of its own — the device
19
+ * allocates one size for all of them — and quietly scaling a 128-pixel sign up to a 512 array, or a
20
+ * facade down, is a decision about quality the caller should make where they can see it. Sorting
21
+ * images into arrays by size is theirs; this makes sure they did.
22
+ */
23
+ export function layerSize(sources) {
24
+ const first = sources[0];
25
+ if (first === undefined) {
26
+ throw new Error('createSurfaceTextureArray: an array needs at least one image');
27
+ }
28
+ const size = sourceSize(first);
29
+ for (let layer = 1; layer < sources.length; layer++) {
30
+ const next = sourceSize(sources[layer]);
31
+ if (next.width !== size.width || next.height !== size.height) {
32
+ throw new Error(`createSurfaceTextureArray: layer ${layer} is ${next.width}×${next.height} and layer 0 is ` +
33
+ `${size.width}×${size.height}. Every layer of an array is one size on the device; group ` +
34
+ 'images into arrays by size, or scale them to one, before building the array.');
35
+ }
36
+ }
37
+ return size;
38
+ }
39
+ /** Whether a constructor argument is a list of images rather than one. */
40
+ export function isSourceList(source) {
41
+ return Array.isArray(source);
42
+ }
43
+ /**
44
+ * `update` replaces the one image of a plain texture. An array's layers are its identity — a
45
+ * block's facades by index — and replacing "the image" of a forty-layer array has no single
46
+ * meaning, so it is refused rather than guessed at.
47
+ */
48
+ export function refuseArrayUpdate(layers) {
49
+ if (layers === 1)
50
+ return;
51
+ throw new Error(`updateSurfaceTexture: this texture is an array of ${layers} layers, and update replaces the ` +
52
+ 'one image of a plain texture. Build a new array for new layers.');
53
+ }
@@ -88,6 +88,8 @@ export const ABSENT_ATTRIBUTE = {
88
88
  * the thing being measured.
89
89
  */
90
90
  channel: [0, 1, 1, 1],
91
+ /* Layer 0: the only image a plain texture has, and the first of an array. */
92
+ layers: [0],
91
93
  };
92
94
  /**
93
95
  * What `uOutputTransform` means, as the shader's own integers.
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Which level of detail each region of a world is drawn at, and the crossfade between two.
3
+ *
4
+ * **A region is a box and one geometric error per level**, finest first: how far, in metres, that
5
+ * level's surface may stand from the real one. A level is good enough once its error projects under
6
+ * `pixelTolerance` pixels, so the distance it takes over at is `error · projectionScale / tolerance`
7
+ * — the rule 3D Tiles uses, and the reason the file carries errors rather than distances: a
8
+ * distance is a decision about one screen, and an error is a fact about the geometry.
9
+ *
10
+ * **A band around every switch, so a still eye can never flip.** A region coarsens only once the
11
+ * eye is `1 + hysteresis` past the level's distance and refines only once it is `1 − hysteresis`
12
+ * inside it, so an eye standing on a threshold — or wobbling across it by less than the band —
13
+ * keeps the level it has. Distance is to the region's **box**, so the region under the eye is
14
+ * always at its finest, whatever its size.
15
+ *
16
+ * **A change crossfades rather than pops.** Each draw carries a dither for `setDitherFade`: the
17
+ * level arriving at `+t`, the one leaving at `−t`, which cover every pixel exactly once. `t` runs
18
+ * over `fadeSec` of the caller's `dtSec`, never a clock — so **a held clock never finishes a fade**,
19
+ * and a capture wants `fadeSec: 0`. A region nobody can see finishes any fade at once, and a region
20
+ * just added fades in from nothing.
21
+ *
22
+ * What it gives up: a region is one decision, so a long one seen end-on is drawn at the level its
23
+ * nearest corner asks for all the way along. Smaller regions are the lever.
24
+ *
25
+ * Nothing here allocates after construction except `add`, which is not per frame.
26
+ */
27
+ import type { FrustumPlanes } from '../math/frustum.ts';
28
+ import type { BoxOccluder } from '../render/instanceCull.ts';
29
+ /** The level of a region drawn at none of its own: past `farDistance`, or not arrived yet. */
30
+ export declare const HLOD_NONE = 255;
31
+ export interface HlodOptions {
32
+ /** The most regions the set holds at once. */
33
+ readonly capacity: number;
34
+ /** The most levels a region may carry. 4 unless stated. */
35
+ readonly maxLevels?: number;
36
+ /** How many pixels of geometric error a level may show before a finer one is drawn. 1.5 unless stated. */
37
+ readonly pixelTolerance?: number;
38
+ /** The band around each switch, as a fraction of its distance. 0.15 unless stated. */
39
+ readonly hysteresis?: number;
40
+ /** Seconds a crossfade takes. 0.4 unless stated; 0 switches at once. */
41
+ readonly fadeSec?: number;
42
+ /** Beyond this, a region is drawn at no level at all. Unbounded unless stated. */
43
+ readonly farDistance?: number;
44
+ }
45
+ /** What `select` hands back: one entry per draw, a crossfading region taking two. */
46
+ export interface HlodDraws {
47
+ count: number;
48
+ /** The region's id, as it was added. */
49
+ readonly region: Uint32Array;
50
+ /** Which of its levels to draw. */
51
+ readonly level: Uint8Array;
52
+ /** What to pass `setDitherFade` for this draw: 0 whole, `+t` arriving, `−t` leaving. */
53
+ readonly dither: Float32Array;
54
+ }
55
+ /** Room for every region of a set of this capacity, each crossfading at once. */
56
+ export declare function createHlodDraws(capacity: number): HlodDraws;
57
+ /** Pixels a metre spans at one metre's distance: the screen's half of the switch distance. */
58
+ export declare function projectionScaleOf(fovYDeg: number, heightPx: number): number;
59
+ export declare class HlodSet {
60
+ readonly capacity: number;
61
+ readonly maxLevels: number;
62
+ private readonly tolerance;
63
+ private readonly band;
64
+ private readonly fadeSec;
65
+ private readonly far;
66
+ private readonly slots;
67
+ private readonly ids;
68
+ private readonly boxes;
69
+ private readonly errors;
70
+ private readonly levels;
71
+ /** The level each region is at, or arriving at. */
72
+ private readonly level;
73
+ /** The level a crossfade is leaving. */
74
+ private readonly from;
75
+ /** How far through its crossfade each region is; 1 is settled. */
76
+ private readonly fade;
77
+ private readonly box;
78
+ private count;
79
+ constructor(options: HlodOptions);
80
+ get size(): number;
81
+ has(id: number): boolean;
82
+ /** The level a region is at or arriving at, or `HLOD_NONE`. -1 for a region not in the set. */
83
+ levelOf(id: number): number;
84
+ /**
85
+ * Add a region: its box as min then max, and one geometric error per level, finest first and
86
+ * never shrinking. It arrives at no level and fades into the one the next `select` picks.
87
+ */
88
+ add(id: number, box: ArrayLike<number>, errors: ArrayLike<number>): void;
89
+ /** Remove a region; nothing if it is not there. The last region takes its slot. */
90
+ remove(id: number): void;
91
+ /**
92
+ * Decide every region's level for this view and write what to draw into `out`, returning how
93
+ * many draws that is. `projectionScale` is `projectionScaleOf(fov, height)`; `occluder` may be
94
+ * the renderer, asked after this frame's occluders are declared.
95
+ */
96
+ select(eye: ArrayLike<number>, projectionScale: number, frustum: FrustumPlanes, occluder: BoxOccluder | null, dtSec: number, out: HlodDraws): number;
97
+ /** The level a region at distance `d` should be at, from where it is, through the bands. */
98
+ private levelFor;
99
+ /** The distance level `index` takes over at; the level past the coarsest is the far limit. */
100
+ private switchAt;
101
+ /** Begin, reverse or redirect a crossfade toward `target`. */
102
+ private move;
103
+ private emit;
104
+ }