@driftengine/core 4.9.0 → 4.11.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 (231) hide show
  1. package/CHANGELOG.json +133 -11
  2. package/README.md +43 -38
  3. package/dist/index.d.ts +7 -4
  4. package/dist/index.js +5 -4
  5. package/dist/render/ambientHarmonics.d.ts +1 -1
  6. package/dist/render/ambientHarmonics.js +1 -1
  7. package/dist/render/areaLights.d.ts +4 -4
  8. package/dist/render/areaLights.js +1 -1
  9. package/dist/render/backend/api.d.ts +10 -3
  10. package/dist/render/backend/select.js +11 -5
  11. package/dist/render/backend/webgl2/boneAnimations.d.ts +29 -0
  12. package/dist/render/backend/webgl2/boneAnimations.js +80 -0
  13. package/dist/render/backend/webgl2/instanced.d.ts +5 -1
  14. package/dist/render/backend/webgl2/instanced.js +13 -1
  15. package/dist/render/backend/webgl2/renderer.d.ts +189 -4
  16. package/dist/render/backend/webgl2/renderer.js +614 -66
  17. package/dist/render/backend/webgl2/skinPaletteTexture.d.ts +5 -3
  18. package/dist/render/backend/webgl2/skinPaletteTexture.js +6 -4
  19. package/dist/render/backend/webgpu/boneAnimations.d.ts +34 -0
  20. package/dist/render/backend/webgpu/boneAnimations.js +76 -0
  21. package/dist/render/backend/webgpu/buffers.d.ts +24 -4
  22. package/dist/render/backend/webgpu/buffers.js +116 -59
  23. package/dist/render/backend/webgpu/clothKernels.d.ts +8 -0
  24. package/dist/render/backend/webgpu/clothKernels.js +76 -0
  25. package/dist/render/backend/webgpu/clothOps.d.ts +39 -0
  26. package/dist/render/backend/webgpu/clothOps.js +78 -0
  27. package/dist/render/backend/webgpu/clothSetLayout.d.ts +25 -0
  28. package/dist/render/backend/webgpu/clothSetLayout.js +84 -0
  29. package/dist/render/backend/webgpu/clothSetPack.d.ts +33 -0
  30. package/dist/render/backend/webgpu/clothSetPack.js +144 -0
  31. package/dist/render/backend/webgpu/compressedUpload.d.ts +0 -1
  32. package/dist/render/backend/webgpu/compressedUpload.js +53 -8
  33. package/dist/render/backend/webgpu/depthPass.d.ts +59 -3
  34. package/dist/render/backend/webgpu/depthPass.js +49 -14
  35. package/dist/render/backend/webgpu/drawCommand.d.ts +20 -4
  36. package/dist/render/backend/webgpu/drawCommand.js +11 -3
  37. package/dist/render/backend/webgpu/flatPass.d.ts +85 -42
  38. package/dist/render/backend/webgpu/flatPass.js +137 -20
  39. package/dist/render/backend/webgpu/gpuClothSet.d.ts +80 -0
  40. package/dist/render/backend/webgpu/gpuClothSet.js +462 -0
  41. package/dist/render/backend/webgpu/instanced.d.ts +14 -1
  42. package/dist/render/backend/webgpu/instanced.js +24 -1
  43. package/dist/render/backend/webgpu/latePass.js +1 -0
  44. package/dist/render/backend/webgpu/materialStore.d.ts +101 -0
  45. package/dist/render/backend/webgpu/materialStore.js +265 -0
  46. package/dist/render/backend/webgpu/motionPass.js +15 -6
  47. package/dist/render/backend/webgpu/pipelineCache.d.ts +28 -1
  48. package/dist/render/backend/webgpu/pipelineCache.js +46 -1
  49. package/dist/render/backend/webgpu/postPass.d.ts +1 -1
  50. package/dist/render/backend/webgpu/renderer.d.ts +200 -35
  51. package/dist/render/backend/webgpu/renderer.js +993 -168
  52. package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.d.ts +9 -2
  53. package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.js +121 -47
  54. package/dist/render/backend/webgpu/skinPaletteRing.d.ts +7 -3
  55. package/dist/render/backend/webgpu/skinPaletteRing.js +13 -7
  56. package/dist/render/backend/webgpu/skinPaletteTexture.d.ts +6 -0
  57. package/dist/render/backend/webgpu/skinPaletteTexture.js +7 -1
  58. package/dist/render/backend/webgpu/staticBundles.d.ts +125 -0
  59. package/dist/render/backend/webgpu/staticBundles.js +242 -0
  60. package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +10 -3
  61. package/dist/render/backend/webgpu/surfaceTexturePass.js +20 -2
  62. package/dist/render/backend/webgpu/uniformRing.d.ts +21 -0
  63. package/dist/render/backend/webgpu/uniformRing.js +9 -1
  64. package/dist/render/backend/webgpu/vertexFormats.d.ts +22 -0
  65. package/dist/render/backend/webgpu/vertexFormats.js +97 -0
  66. package/dist/render/bloomChain.d.ts +3 -3
  67. package/dist/render/boneAnimation.d.ts +92 -0
  68. package/dist/render/boneAnimation.js +133 -0
  69. package/dist/render/compressedSource.d.ts +33 -13
  70. package/dist/render/compressedSource.js +70 -25
  71. package/dist/render/glCompressed.js +83 -0
  72. package/dist/render/globalMediumPass.d.ts +2 -0
  73. package/dist/render/globalMediumPass.js +1 -0
  74. package/dist/render/indexWidth.d.ts +17 -0
  75. package/dist/render/indexWidth.js +23 -0
  76. package/dist/render/instances.d.ts +26 -0
  77. package/dist/render/lightBudget.d.ts +10 -0
  78. package/dist/render/lightBudget.js +10 -0
  79. package/dist/render/lightMatrix.d.ts +11 -0
  80. package/dist/render/lightMatrix.js +14 -0
  81. package/dist/render/lightmap.d.ts +1 -1
  82. package/dist/render/mesh.d.ts +14 -0
  83. package/dist/render/mesh.js +44 -14
  84. package/dist/render/pointLightSelection.d.ts +4 -4
  85. package/dist/render/renderQuality.d.ts +30 -0
  86. package/dist/render/renderQuality.js +5 -0
  87. package/dist/render/rendererHarness.d.ts +7 -0
  88. package/dist/render/rendererHarness.js +12 -0
  89. package/dist/render/screenSpaceReflection.d.ts +2 -1
  90. package/dist/render/shader.d.ts +15 -0
  91. package/dist/render/shader.js +41 -0
  92. package/dist/render/shaders/boneAnimation.d.ts +19 -0
  93. package/dist/render/shaders/boneAnimation.js +63 -0
  94. package/dist/render/shaders/depth.d.ts +7 -0
  95. package/dist/render/shaders/depth.js +35 -10
  96. package/dist/render/shaders/flat/directionalShadow.js +84 -10
  97. package/dist/render/shaders/flat/glassTint.d.ts +1 -1
  98. package/dist/render/shaders/flat/glassTint.js +3 -0
  99. package/dist/render/shaders/flat/index.d.ts +21 -0
  100. package/dist/render/shaders/flat/index.js +42 -7
  101. package/dist/render/shaders/flat/layered.d.ts +19 -0
  102. package/dist/render/shaders/flat/layered.js +115 -0
  103. package/dist/render/shaders/flat/main.d.ts +1 -1
  104. package/dist/render/shaders/flat/main.js +58 -21
  105. package/dist/render/shaders/flat/mapReads.d.ts +10 -0
  106. package/dist/render/shaders/flat/mapReads.js +38 -0
  107. package/dist/render/shaders/flat/models.js +1 -1
  108. package/dist/render/shaders/flat/movingSun.d.ts +21 -0
  109. package/dist/render/shaders/flat/movingSun.js +46 -0
  110. package/dist/render/shaders/flat/normalMap.d.ts +6 -2
  111. package/dist/render/shaders/flat/normalMap.js +14 -3
  112. package/dist/render/shaders/flat/overlay.d.ts +3 -2
  113. package/dist/render/shaders/flat/overlay.js +42 -20
  114. package/dist/render/shaders/flat/pointShadow.js +2 -2
  115. package/dist/render/shaders/flat/preamble.js +59 -27
  116. package/dist/render/shaders/flat/worldUv.d.ts +22 -0
  117. package/dist/render/shaders/flat/worldUv.js +113 -0
  118. package/dist/render/shaders/generated/depth.wgsl.d.ts +93 -0
  119. package/dist/render/shaders/generated/depth.wgsl.js +93 -0
  120. package/dist/render/shaders/generated/flat.wgsl.d.ts +3616 -3042
  121. package/dist/render/shaders/generated/flat.wgsl.js +3688 -3113
  122. package/dist/render/shaders/generated/globalMedium.wgsl.d.ts +8 -3
  123. package/dist/render/shaders/generated/globalMedium.wgsl.js +8 -3
  124. package/dist/render/shaders/generated/lightVolume.wgsl.d.ts +15 -10
  125. package/dist/render/shaders/generated/lightVolume.wgsl.js +16 -11
  126. package/dist/render/shaders/globalMedium.js +9 -1
  127. package/dist/render/shaders/lightVolume.js +9 -1
  128. package/dist/render/shaders/outputTransform.d.ts +7 -0
  129. package/dist/render/shaders/outputTransform.js +28 -14
  130. package/dist/render/shaders/tangentFrame.d.ts +1 -1
  131. package/dist/render/shaders/tangentFrame.js +16 -3
  132. package/dist/render/shaders/vertexChannel.d.ts +7 -0
  133. package/dist/render/shaders/vertexChannel.js +26 -0
  134. package/dist/render/skinnedClothRun.d.ts +28 -0
  135. package/dist/render/skinnedClothRun.js +103 -35
  136. package/dist/render/staticDraws.d.ts +47 -0
  137. package/dist/render/staticDraws.js +43 -0
  138. package/dist/render/surfaceLayers.d.ts +39 -0
  139. package/dist/render/surfaceLayers.js +26 -0
  140. package/dist/render/surfaceOverlay.d.ts +16 -3
  141. package/dist/render/surfaceOverlay.js +11 -6
  142. package/dist/render/surfaceProjection.d.ts +34 -0
  143. package/dist/render/surfaceProjection.js +30 -0
  144. package/dist/render/surfaceTexture.d.ts +68 -7
  145. package/dist/render/surfaceTexture.js +56 -17
  146. package/dist/render/vertexPacking.d.ts +48 -0
  147. package/dist/render/vertexPacking.js +68 -0
  148. package/package.json +6 -6
  149. package/src/index.ts +12 -4
  150. package/src/render/ambientHarmonics.ts +1 -1
  151. package/src/render/areaLights.ts +4 -4
  152. package/src/render/backend/api.ts +13 -3
  153. package/src/render/backend/select.ts +10 -5
  154. package/src/render/backend/webgl2/boneAnimations.ts +105 -0
  155. package/src/render/backend/webgl2/instanced.ts +20 -1
  156. package/src/render/backend/webgl2/renderer.ts +723 -79
  157. package/src/render/backend/webgl2/skinPaletteTexture.ts +6 -4
  158. package/src/render/backend/webgpu/boneAnimations.ts +97 -0
  159. package/src/render/backend/webgpu/buffers.ts +195 -70
  160. package/src/render/backend/webgpu/clothKernels.ts +86 -0
  161. package/src/render/backend/webgpu/clothOps.ts +86 -0
  162. package/src/render/backend/webgpu/clothSetLayout.ts +102 -0
  163. package/src/render/backend/webgpu/clothSetPack.ts +197 -0
  164. package/src/render/backend/webgpu/compressedUpload.ts +59 -9
  165. package/src/render/backend/webgpu/depthPass.ts +51 -10
  166. package/src/render/backend/webgpu/drawCommand.ts +25 -4
  167. package/src/render/backend/webgpu/flatPass.ts +212 -41
  168. package/src/render/backend/webgpu/gpuClothSet.ts +564 -0
  169. package/src/render/backend/webgpu/instanced.ts +32 -1
  170. package/src/render/backend/webgpu/latePass.ts +1 -0
  171. package/src/render/backend/webgpu/materialStore.ts +276 -0
  172. package/src/render/backend/webgpu/motionPass.ts +18 -7
  173. package/src/render/backend/webgpu/pipelineCache.ts +53 -7
  174. package/src/render/backend/webgpu/renderer.ts +1201 -202
  175. package/src/render/backend/webgpu/shaders/clothSolve.wgsl.ts +121 -47
  176. package/src/render/backend/webgpu/skinPaletteRing.ts +13 -7
  177. package/src/render/backend/webgpu/skinPaletteTexture.ts +7 -1
  178. package/src/render/backend/webgpu/staticBundles.ts +364 -0
  179. package/src/render/backend/webgpu/surfaceTexturePass.ts +29 -3
  180. package/src/render/backend/webgpu/uniformRing.ts +10 -1
  181. package/src/render/backend/webgpu/vertexFormats.ts +113 -0
  182. package/src/render/bloomChain.ts +3 -3
  183. package/src/render/boneAnimation.ts +223 -0
  184. package/src/render/compressedSource.ts +121 -29
  185. package/src/render/glCompressed.ts +83 -0
  186. package/src/render/globalMediumPass.ts +7 -0
  187. package/src/render/indexWidth.ts +27 -0
  188. package/src/render/instances.ts +27 -0
  189. package/src/render/lightBudget.ts +11 -0
  190. package/src/render/lightMatrix.ts +15 -0
  191. package/src/render/lightmap.ts +1 -1
  192. package/src/render/mesh.ts +72 -14
  193. package/src/render/pointLightSelection.ts +4 -4
  194. package/src/render/renderQuality.ts +38 -0
  195. package/src/render/rendererHarness.ts +15 -0
  196. package/src/render/screenSpaceReflection.ts +2 -1
  197. package/src/render/shader.ts +59 -0
  198. package/src/render/shaders/boneAnimation.ts +63 -0
  199. package/src/render/shaders/depth.ts +45 -10
  200. package/src/render/shaders/flat/directionalShadow.ts +84 -10
  201. package/src/render/shaders/flat/glassTint.ts +7 -1
  202. package/src/render/shaders/flat/index.ts +63 -7
  203. package/src/render/shaders/flat/layered.ts +115 -0
  204. package/src/render/shaders/flat/main.ts +58 -21
  205. package/src/render/shaders/flat/mapReads.ts +38 -0
  206. package/src/render/shaders/flat/models.ts +1 -1
  207. package/src/render/shaders/flat/movingSun.ts +46 -0
  208. package/src/render/shaders/flat/normalMap.ts +14 -3
  209. package/src/render/shaders/flat/overlay.ts +42 -20
  210. package/src/render/shaders/flat/pointShadow.ts +2 -2
  211. package/src/render/shaders/flat/preamble.ts +59 -27
  212. package/src/render/shaders/flat/worldUv.ts +113 -0
  213. package/src/render/shaders/generated/depth.wgsl.ts +95 -0
  214. package/src/render/shaders/generated/flat.wgsl.ts +3688 -3113
  215. package/src/render/shaders/generated/globalMedium.wgsl.ts +8 -3
  216. package/src/render/shaders/generated/lightVolume.wgsl.ts +16 -11
  217. package/src/render/shaders/globalMedium.ts +9 -1
  218. package/src/render/shaders/lightVolume.ts +9 -1
  219. package/src/render/shaders/outputTransform.ts +30 -14
  220. package/src/render/shaders/tangentFrame.ts +16 -3
  221. package/src/render/shaders/vertexChannel.ts +27 -0
  222. package/src/render/skinnedClothRun.ts +138 -38
  223. package/src/render/staticDraws.ts +95 -0
  224. package/src/render/surfaceLayers.ts +57 -0
  225. package/src/render/surfaceOverlay.ts +24 -7
  226. package/src/render/surfaceProjection.ts +57 -0
  227. package/src/render/surfaceTexture.ts +124 -27
  228. package/src/render/vertexPacking.ts +109 -0
  229. package/dist/render/backend/webgpu/gpuSkinnedCloth.d.ts +0 -74
  230. package/dist/render/backend/webgpu/gpuSkinnedCloth.js +0 -452
  231. package/src/render/backend/webgpu/gpuSkinnedCloth.ts +0 -502
@@ -8,8 +8,10 @@
8
8
  * array samples exactly as a 2D texture does. A mesh names its layer per vertex (`MeshData.layers`),
9
9
  * so a merged block wearing forty facades is one draw.
10
10
  */
11
+ import type { SurfaceLayers } from './surfaceLayers.ts';
12
+ import type { SurfaceProjection } from './surfaceProjection.ts';
11
13
  import type { SurfaceModel } from './surfaceModel.ts';
12
- import type { CompressedTextureFormat, SurfaceSource } from './compressedSource.ts';
14
+ import type { CompressedTextureFormat, CompressedTextureSource, SurfaceSource } from './compressedSource.ts';
13
15
  import type { SurfaceLayerEffect } from './surfaceEffects.ts';
14
16
  import type { CutoutMode } from './cutoutDither.ts';
15
17
  import type { LightmapTexels } from './lightmap.ts';
@@ -107,7 +109,7 @@ export interface SurfaceMaterial<Texture = SurfaceTexture> {
107
109
  * which is every light's default too, so a scene that names no channel is lit as it always was.
108
110
  *
109
111
  * A point or spot light shades this surface only where its `PointLightSet.lightChannels` and this
110
- * share a bit — Unreal's lighting channels. A character's own key and rim lights on channel 2,
112
+ * share a bit — lighting channels. A character's own key and rim lights on channel 2,
111
113
  * and the character's materials on 3, light the character and leave the floor around it as the
112
114
  * stage's lights alone have it. **What it does not reach**: the sun, the sky and the probes, area
113
115
  * lights, DriftLight's volume and the GPU-driven pipeline light every surface whatever its mask.
@@ -141,6 +143,22 @@ export interface SurfaceMaterial<Texture = SurfaceTexture> {
141
143
  * which is the opposite of what the caller asked for.
142
144
  */
143
145
  occlusionStrength?: number;
146
+ /**
147
+ * How much of the environment this material's draws mirror, 0 to 1: the number
148
+ * `setSurfaceReflectivity` sets for every draw, stated by the material for its own. Absent, its
149
+ * draws wear the setter's, as every material's did before this existed.
150
+ *
151
+ * **Here so a static list can carry it.** A list keeps each entry's material and none of the
152
+ * renderer's state between draws (`createStaticDraws`), so a stage whose batches each mirror their
153
+ * own share of a baked reflection says so in the material it records. What it gives up is nothing a
154
+ * setter could do: one called after this material is set is ignored for the material's draws.
155
+ */
156
+ reflectivity?: number;
157
+ /**
158
+ * How bright the environment this material's draws reflect is: `setEnvironmentGain`'s number,
159
+ * stated by the material for its own, and absent the setter's. Held non-negative.
160
+ */
161
+ environmentGain?: number;
144
162
  /**
145
163
  * Scales the emissive map, per channel. Default 1.
146
164
  *
@@ -164,14 +182,14 @@ export interface SurfaceMaterial<Texture = SurfaceTexture> {
164
182
  modelMap?: Texture | null;
165
183
  /**
166
184
  * Whether a lamp's and the sun's highlight on this surface is GGX's own, `π · D · Vis · F · N·L`,
167
- * as Unreal and this engine's skin and eye shade it, rather than the engine's lobe scaled to a
168
- * peak of one. **False by default**, every surface as it was.
185
+ * as physically based renderers and this engine's skin and eye shade it, rather than the engine's
186
+ * lobe scaled to a peak of one. **False by default**, every surface as it was.
169
187
  *
170
188
  * The peak-normalised lobe is a look control: the specular attribute says how strong a highlight
171
189
  * is and roughness how wide, apart. Physical, they are entangled as they are in a real surface —
172
190
  * the peak is `1 / (4 α²)` of the light head-on, so a polished surface's highlight is many times
173
191
  * the look's and a rough one's lower and broader — and the specular attribute is read as the
174
- * reflectance at normal incidence, F0: Unreal's `0.08 × Specular`, 0.04 at its default. Fresnel
192
+ * reflectance at normal incidence, F0: `0.08 × Specular`, 0.04 at its default. Fresnel
175
193
  * then brightens every surface toward grazing, not only a metal. A rectangle's highlight is the
176
194
  * lobe integrated over it with the attribute as F0 already, so it is unchanged either way.
177
195
  *
@@ -180,6 +198,34 @@ export interface SurfaceMaterial<Texture = SurfaceTexture> {
180
198
  * and an 8-bit frame clips the brighter peak without `hdrScene`.
181
199
  */
182
200
  physicalSpecular?: boolean;
201
+ /**
202
+ * Texture coordinates from where a point is in the world rather than from the mesh: `'planar'`
203
+ * across the horizontal axes for ground, `'triplanar'` on three planes for walls and rock, at so
204
+ * many repeats a metre. Absent, the mesh's, as every material's were. **A lit switch**, compiled in
205
+ * the first time a material asks, so a scene that never asks pays nothing. See
206
+ * `surfaceProjection.ts` for what it gives up.
207
+ */
208
+ projection?: SurfaceProjection | null;
209
+ /**
210
+ * How much light from behind a thin surface lets through, 0 to 1: a banner lit from behind, a
211
+ * leaf against the sun, a lampshade. The light falling on the far side — the sun, lamps, area
212
+ * lights, DriftLight — reaches the eye through the surface, coloured by its own colour; a metal
213
+ * lets none through. 0, the default, is every material as it was. Two-sided or not: a one-sided
214
+ * surface seen from its front shows the light behind it as well.
215
+ *
216
+ * **What it gives up**: a thin surface, not a volume, so a thick one lets through as much as a
217
+ * sheet; the light behind is not blurred by the surface; and none of it on glass, which lets
218
+ * light through by its own rule (`TranslucentMeshOptions.glass`).
219
+ */
220
+ diffuseTransmission?: number;
221
+ /**
222
+ * Up to five layers blended by a mask, each at its own repeat: `albedo`, `normal` and `orm` arrays
223
+ * whose layers are the material's, and a mask laying each over the ones before it. Absent, the
224
+ * material is one layer, as every material was. **A lit switch**, compiled in the first time a
225
+ * material asks. The mask is read where `modelMap` goes, so a material carries one or the other.
226
+ * See `surfaceLayers.ts` for what it gives up.
227
+ */
228
+ layers?: SurfaceLayers<Texture> | null;
183
229
  }
184
230
  export interface SurfaceTextureOptions {
185
231
  /**
@@ -275,7 +321,9 @@ export declare class SurfaceTexture {
275
321
  /** The per-layer effects table, or null for a texture given none. See `surfaceEffects.ts`. */
276
322
  private effectsTable;
277
323
  /** The block format a compressed texture holds, or null for one uploaded from images. */
278
- private readonly blockFormat;
324
+ private blockFormat;
325
+ /** How the texture is sampled, kept so blocks swapped in by `update` are sampled the same way. */
326
+ private readonly sampling;
279
327
  /** Level 0's size, which `flatLayer` copies; kept as `update` changes it. */
280
328
  private width;
281
329
  private height;
@@ -303,7 +351,20 @@ export declare class SurfaceTexture {
303
351
  * Not a hot path: it re-uploads the whole image and regenerates the chain. A caller
304
352
  * doing this every frame at any size wants to know that it costs what it costs.
305
353
  */
306
- update(gl: WebGL2RenderingContext, source: TexImageSource): void;
354
+ update(gl: WebGL2RenderingContext, source: TexImageSource | CompressedTextureSource, compressed?: readonly CompressedTextureFormat[]): void;
355
+ /**
356
+ * Blocks in place of the image, behind the same handle: **a new GL object** rather than the
357
+ * image's levels respecified, because a chain shorter than the image's would leave the levels
358
+ * past it allocated, and immutable storage could not be respecified at all. Sampled as the image
359
+ * was, and refused before anything changes where the context does not take the format.
360
+ */
361
+ private swapToBlocks;
362
+ /**
363
+ * Wrap, filters and anisotropy on the bound texture. Nearest where the caller said their pixels
364
+ * are the subject; see `filter`. The mip chain is still blended between levels, because that is
365
+ * minification and this option is about magnification.
366
+ */
367
+ private applySampling;
307
368
  /**
308
369
  * Attach the only layer as the bound framebuffer's colour: a scene capture's target, drawn into
309
370
  * by `captureScene`. Engine-internal, as `bind` is.
@@ -1,5 +1,5 @@
1
1
  import { layerSize, refuseArrayUpdate, sourceSize } from './textureSource.js';
2
- import { compressedLayers, planBlocks, refuseBlockUpdate } from './compressedSource.js';
2
+ import { compressedLayers, isCompressedSource, planBlocks, refuseBlockUpdate, } from './compressedSource.js';
3
3
  import { uploadCompressedArray } from './glCompressed.js';
4
4
  import { SURFACE_EFFECT_TEXELS, packSurfaceEffects } from './surfaceEffects.js';
5
5
  import { isLightmapTexels } from './lightmap.js';
@@ -25,6 +25,8 @@ export class SurfaceTexture {
25
25
  effectsTable = null;
26
26
  /** The block format a compressed texture holds, or null for one uploaded from images. */
27
27
  blockFormat;
28
+ /** How the texture is sampled, kept so blocks swapped in by `update` are sampled the same way. */
29
+ sampling;
28
30
  /** Level 0's size, which `flatLayer` copies; kept as `update` changes it. */
29
31
  width = 0;
30
32
  height = 0;
@@ -101,23 +103,14 @@ export class SurfaceTexture {
101
103
  this.storage = this.srgb ? gl.SRGB8_ALPHA8 : gl.RGBA8;
102
104
  uploadImages(gl, sources, this.srgb);
103
105
  }
104
- gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_S, wrap);
105
- gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_T, wrap);
106
- /* Nearest where the caller said their pixels are the subject; see `filter`. The mip chain is
107
- still blended between levels, because that is minification and this option is about
108
- magnification. */
109
- const nearest = options.filter === 'nearest';
110
- gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MAG_FILTER, nearest ? gl.NEAREST : gl.LINEAR);
111
- gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MIN_FILTER, this.mipmapped
112
- ? nearest
113
- ? gl.NEAREST_MIPMAP_LINEAR
114
- : gl.LINEAR_MIPMAP_LINEAR
115
- : nearest
116
- ? gl.NEAREST
117
- : gl.LINEAR);
106
+ this.sampling = {
107
+ wrap,
108
+ nearest: options.filter === 'nearest',
109
+ anisotropy: options.anisotropy ?? DEFAULT_ANISOTROPY,
110
+ };
111
+ this.applySampling(gl);
118
112
  if (this.mipmapped && this.blockFormat === null)
119
113
  gl.generateMipmap(gl.TEXTURE_2D_ARRAY);
120
- applyAnisotropy(gl, options.anisotropy ?? DEFAULT_ANISOTROPY);
121
114
  gl.bindTexture(gl.TEXTURE_2D_ARRAY, null);
122
115
  /* Half floats, nearest, clamped: a table read by texelFetch, whose every whole number is under
123
116
  what a half holds exactly. */
@@ -155,10 +148,14 @@ export class SurfaceTexture {
155
148
  * Not a hot path: it re-uploads the whole image and regenerates the chain. A caller
156
149
  * doing this every frame at any size wants to know that it costs what it costs.
157
150
  */
158
- update(gl, source) {
151
+ update(gl, source, compressed = []) {
159
152
  if (this.texture === null)
160
153
  return;
161
154
  refuseArrayUpdate(this.layers);
155
+ if (isCompressedSource(source)) {
156
+ this.swapToBlocks(gl, source, compressed);
157
+ return;
158
+ }
162
159
  refuseBlockUpdate(this.blockFormat);
163
160
  const { width, height } = sourceSize(source);
164
161
  this.width = width;
@@ -171,6 +168,48 @@ export class SurfaceTexture {
171
168
  gl.generateMipmap(gl.TEXTURE_2D_ARRAY);
172
169
  gl.bindTexture(gl.TEXTURE_2D_ARRAY, null);
173
170
  }
171
+ /**
172
+ * Blocks in place of the image, behind the same handle: **a new GL object** rather than the
173
+ * image's levels respecified, because a chain shorter than the image's would leave the levels
174
+ * past it allocated, and immutable storage could not be respecified at all. Sampled as the image
175
+ * was, and refused before anything changes where the context does not take the format.
176
+ */
177
+ swapToBlocks(gl, source, compressed) {
178
+ const plan = planBlocks([source], this.srgb, compressed);
179
+ const texture = gl.createTexture();
180
+ if (texture === null)
181
+ throw new Error('SurfaceTexture: createTexture failed');
182
+ this.dropFlat(gl);
183
+ if (this.texture !== null)
184
+ gl.deleteTexture(this.texture);
185
+ this.texture = texture;
186
+ this.blockFormat = source.format;
187
+ this.width = source.width;
188
+ this.height = source.height;
189
+ gl.bindTexture(gl.TEXTURE_2D_ARRAY, texture);
190
+ uploadCompressedArray(gl, plan.name, [source], this.mipmapped ? plan.levels : 1);
191
+ this.applySampling(gl);
192
+ gl.bindTexture(gl.TEXTURE_2D_ARRAY, null);
193
+ }
194
+ /**
195
+ * Wrap, filters and anisotropy on the bound texture. Nearest where the caller said their pixels
196
+ * are the subject; see `filter`. The mip chain is still blended between levels, because that is
197
+ * minification and this option is about magnification.
198
+ */
199
+ applySampling(gl) {
200
+ const { wrap, nearest, anisotropy } = this.sampling;
201
+ gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_S, wrap);
202
+ gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_T, wrap);
203
+ gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MAG_FILTER, nearest ? gl.NEAREST : gl.LINEAR);
204
+ gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MIN_FILTER, this.mipmapped
205
+ ? nearest
206
+ ? gl.NEAREST_MIPMAP_LINEAR
207
+ : gl.LINEAR_MIPMAP_LINEAR
208
+ : nearest
209
+ ? gl.NEAREST
210
+ : gl.LINEAR);
211
+ applyAnisotropy(gl, anisotropy);
212
+ }
174
213
  /**
175
214
  * Attach the only layer as the bound framebuffer's colour: a scene capture's target, drawn into
176
215
  * by `captureScene`. Engine-internal, as `bind` is.
@@ -0,0 +1,48 @@
1
+ import type { MeshData } from '@driftengine/drft';
2
+ /**
3
+ * How each of a mesh's vertex attributes travels to the device, decided once from its data and
4
+ * bound by each backend: as floats, as sixteen-bit fixed point, or as one value every vertex
5
+ * shares.
6
+ *
7
+ * **Only what reads back as it went, or near enough that no picture can tell.** One value every
8
+ * vertex shares is exact, and is a constant rather than a stream: a colour or an emissive naming
9
+ * no difference between vertices costs nothing a vertex, where both were always stored per vertex.
10
+ * A unit direction — a normal, a tangent and its handedness — in sixteen-bit signed fixed point is
11
+ * within 1/32767 a component, a few thousandths of a degree: measured against floats, 12 pixels of
12
+ * 921,600 moved in one published scene, by 2 of 255 on average, and none in the other eight.
13
+ * Colours and skin weights inside [0, 1] in sixteen-bit unsigned fixed point are within 1/65535. A
14
+ * textured vertex with a tangent frame, white and not glowing, goes from 64 bytes to 36 on WebGPU.
15
+ *
16
+ * **What it gives up.** Eight-bit normals would save four bytes more and are not taken: a normal in
17
+ * steps of 1/127 can band a highlight. **Texture coordinates stay floats**, in any range: a
18
+ * coordinate on a tile's far edge rounds past it in sixteen-bit fixed point, by 1/131,070, and a
19
+ * pixel there samples the neighbouring tile of an atlas — measured as 29 pixels moved by 62 of 255
20
+ * in the voxel scene, every one at a face's edge — and a half float, which steps by a 2048th near
21
+ * 1.0, is worse. A dynamic mesh's normals stay floats, because each rewrite would have to be packed
22
+ * on the CPU. What would make this wrong is a value outside its range, which the range check sends
23
+ * to floats rather than clamping, or a shader reading an attribute as something other than the
24
+ * value it carries.
25
+ */
26
+ /** Floats; sixteen-bit fixed point, signed or not; or one value for every vertex. */
27
+ export type AttributePacking = 'float' | 'snorm16' | 'unorm16' | 'constant';
28
+ /** The attributes this decides for. Every other one travels as floats. */
29
+ export declare const PACKED_ATTRIBUTES: readonly ['normals', 'tangents', 'colors', 'emissive', 'weights', 'weights2'];
30
+ export type PackedAttribute = (typeof PACKED_ATTRIBUTES)[number];
31
+ export type VertexPacking = Readonly<Record<PackedAttribute, AttributePacking>>;
32
+ /** The choice for this mesh. `dynamic` is a mesh whose positions and normals are rewritten. */
33
+ export declare function vertexPackingOf(data: MeshData, dynamic: boolean): VertexPacking;
34
+ /**
35
+ * What 1 is in each fixed point, the values WebGPU's `snorm16` and `unorm16` and WebGL2's
36
+ * normalised `SHORT` and `UNSIGNED_SHORT` divide by. A value is stored rounded to nearest, so 1, 0
37
+ * and -1 come back exactly.
38
+ */
39
+ export declare const SNORM16_ONE = 32767;
40
+ export declare const UNORM16_ONE = 65535;
41
+ /** One value in the fixed point whose 1 is `one`, rounded to nearest: the only place it is. */
42
+ export declare function toFixed16(value: number, one: number): number;
43
+ /** An attribute's values in its fixed point, tightly, for a backend reading separate arrays. */
44
+ export declare function fixedArray(values: Float32Array, packing: 'snorm16' | 'unorm16'): Int16Array | Uint16Array;
45
+ /** How `name` travels under this packing: floats for an attribute it does not decide for. */
46
+ export declare function packingFor(packing: VertexPacking, name: string): AttributePacking;
47
+ /** The value every vertex shares, for an attribute packed as `'constant'`. */
48
+ export declare function sharedValue(values: Float32Array, width: number): number[];
@@ -0,0 +1,68 @@
1
+ /** The attributes this decides for. Every other one travels as floats. */
2
+ export const PACKED_ATTRIBUTES = [
3
+ 'normals',
4
+ 'tangents',
5
+ 'colors',
6
+ 'emissive',
7
+ 'weights',
8
+ 'weights2',
9
+ ];
10
+ /** The choice for this mesh. `dynamic` is a mesh whose positions and normals are rewritten. */
11
+ export function vertexPackingOf(data, dynamic) {
12
+ return {
13
+ normals: dynamic ? 'float' : fixedOrFloat(data.normals, -1),
14
+ tangents: fixedOrFloat(data.tangents, -1),
15
+ colors: shared(data.colors, 3) ? 'constant' : fixedOrFloat(data.colors, 0),
16
+ emissive: shared(data.emissive, 1) ? 'constant' : 'float',
17
+ weights: fixedOrFloat(data.weights, 0),
18
+ weights2: fixedOrFloat(data.weights2, 0),
19
+ };
20
+ }
21
+ /**
22
+ * What 1 is in each fixed point, the values WebGPU's `snorm16` and `unorm16` and WebGL2's
23
+ * normalised `SHORT` and `UNSIGNED_SHORT` divide by. A value is stored rounded to nearest, so 1, 0
24
+ * and -1 come back exactly.
25
+ */
26
+ export const SNORM16_ONE = 32767;
27
+ export const UNORM16_ONE = 65535;
28
+ /** One value in the fixed point whose 1 is `one`, rounded to nearest: the only place it is. */
29
+ export function toFixed16(value, one) {
30
+ return Math.round(value * one);
31
+ }
32
+ /** An attribute's values in its fixed point, tightly, for a backend reading separate arrays. */
33
+ export function fixedArray(values, packing) {
34
+ const out = packing === 'snorm16' ? new Int16Array(values.length) : new Uint16Array(values.length);
35
+ const one = packing === 'snorm16' ? SNORM16_ONE : UNORM16_ONE;
36
+ for (let at = 0; at < values.length; at++)
37
+ out[at] = toFixed16(values[at], one);
38
+ return out;
39
+ }
40
+ /** How `name` travels under this packing: floats for an attribute it does not decide for. */
41
+ export function packingFor(packing, name) {
42
+ return packing[name] ?? 'float';
43
+ }
44
+ /** The value every vertex shares, for an attribute packed as `'constant'`. */
45
+ export function sharedValue(values, width) {
46
+ return Array.from(values.subarray(0, width));
47
+ }
48
+ /** Fixed point where every value lies in [low, 1]; floats otherwise, and for a NaN. */
49
+ function fixedOrFloat(values, low) {
50
+ if (values === undefined)
51
+ return 'float';
52
+ for (let at = 0; at < values.length; at++) {
53
+ const value = values[at];
54
+ if (!(value >= low && value <= 1))
55
+ return 'float';
56
+ }
57
+ return low < 0 ? 'snorm16' : 'unorm16';
58
+ }
59
+ /** Whether every vertex holds the first vertex's value. Not for a mesh of none. */
60
+ function shared(values, width) {
61
+ if (values.length < width)
62
+ return false;
63
+ for (let at = width; at < values.length; at++) {
64
+ if (values[at] !== values[at % width])
65
+ return false;
66
+ }
67
+ return true;
68
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@driftengine/core",
3
- "version": "4.9.0",
3
+ "version": "4.11.0",
4
4
  "description": "The runtime of DriftEngine, a WebGPU game engine with a WebGL2 fallback: renderer, loop, input, cameras, scene graph and physics",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -29,8 +29,8 @@
29
29
  ],
30
30
  "sideEffects": false,
31
31
  "dependencies": {
32
- "@driftengine/drft": "4.9.0",
33
- "@driftengine/physics": "4.9.0",
32
+ "@driftengine/drft": "4.11.0",
33
+ "@driftengine/physics": "4.11.0",
34
34
  "gl-matrix": "^3.4.3"
35
35
  },
36
36
  "author": "Drift Technologies",
@@ -67,8 +67,8 @@
67
67
  "prepack": "node ../../scripts/build.mjs @driftengine/core"
68
68
  },
69
69
  "devDependencies": {
70
- "@driftengine/nav": "4.9.0",
71
- "@driftengine/network": "4.9.0",
72
- "@driftengine/texture": "4.9.0"
70
+ "@driftengine/nav": "4.11.0",
71
+ "@driftengine/network": "4.11.0",
72
+ "@driftengine/texture": "4.11.0"
73
73
  }
74
74
  }
package/src/index.ts CHANGED
@@ -539,8 +539,8 @@ export type { LightmapPage, LightmapRegion } from './render/lightmap.ts';
539
539
  * A skinned cloth solved where the renderer can: compute on WebGPU, the CPU under WebGL2. A function
540
540
  * rather than a renderer method so only a game that imports it carries the solver.
541
541
  */
542
- export { createSkinnedCloth } from './render/skinnedClothRun.ts';
543
- export type { SkinnedClothSolver } from './render/skinnedClothRun.ts';
542
+ export { createSkinnedCloth, createSkinnedClothSet } from './render/skinnedClothRun.ts';
543
+ export type { SkinnedClothSet, SkinnedClothSolver } from './render/skinnedClothRun.ts';
544
544
 
545
545
  /**
546
546
  * What a frame asked of a backend, against the ceilings that backend imposes.
@@ -568,6 +568,10 @@ export type {
568
568
  ShadowCasterSink,
569
569
  ShadowCasters,
570
570
  } from './render/shadowCasters.ts';
571
+ /* What `createStaticDraws` hands back, so a consumer can hold one in a field it declares. */
572
+ export type { StaticDrawsHandle } from './render/staticDraws.ts';
573
+ /* A bone animation's clip as a caller hands it over, and the handle `createBoneAnimation` returns. */
574
+ export type { BoneAnimationClip, BoneAnimationHandle } from './render/boneAnimation.ts';
571
575
  export type { Atmosphere, UnderwaterAtmosphere } from './render/atmosphere.ts';
572
576
  /*
573
577
  * Reading an image's texels back exactly, which no canvas and no `VideoFrame` will do.
@@ -648,6 +652,7 @@ export type {
648
652
  DirectionalShadowDepthLayers,
649
653
  GlassShadows,
650
654
  OutputTransform,
655
+ PipelineCompile,
651
656
  RenderQuality,
652
657
  RenderQualityOptions,
653
658
  ShadowFilterTaps,
@@ -659,13 +664,16 @@ export { MAX_POINT_LIGHTS, SURFACE_TEXTURE_UNIT } from './render/lightBudget.ts'
659
664
  export { MAX_CLUSTERED_LIGHTS } from './render/clusteredLights.ts';
660
665
  export { SurfaceTexture } from './render/surfaceTexture.ts';
661
666
  export type { SurfaceMaterial, SurfaceTextureOptions } from './render/surfaceTexture.ts';
667
+ export type { SurfaceProjection } from './render/surfaceProjection.ts';
662
668
  /*
663
- * BC compressed textures: the source `createSurfaceTexture` takes as blocks, and the one question to
664
- * ask before handing one over — whether this device takes its format (`renderer.compressedFormats`).
669
+ * Compressed textures — BC, ETC2 and EAC, ASTC: the source `createSurfaceTexture` takes as blocks,
670
+ * the one question to ask before handing one over — whether this device takes its format
671
+ * (`renderer.compressedFormats`) — and the bytes a level of it holds, for a reader checking a file.
665
672
  */
666
673
  export {
667
674
  compressedFormatName,
668
675
  isCompressedSource,
676
+ levelBytes,
669
677
  uploadsCompressed,
670
678
  } from './render/compressedSource.ts';
671
679
  export type {
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * **Why a draw needs its own.** The engine's diffuse ambient is one answer for a whole frame — the
6
6
  * sky-and-ground gradient, or the probe grid's irradiance — so everything standing in one place is
7
- * lit alike. A stage with a baked volume of indirect light (Unreal's volumetric lightmap) lights a
7
+ * lit alike. A stage with a baked volume of indirect light (a volumetric lightmap) lights a
8
8
  * character by sampling that volume where the character stands; a consumer that samples it each
9
9
  * frame hands the result here, around that character's draws, and the character takes the light of
10
10
  * the spot it is in rather than the frame's average.
@@ -94,14 +94,14 @@ export interface AreaLightSource {
94
94
  * others. **Read only where the rectangle is shaded through the froxel table** — the ones past
95
95
  * `maxAreaLights` with `clusteredLights` on; the fixed four light everything they face, as they
96
96
  * always have. Absent, it is where the rectangle's irradiance on its axis falls to a thousandth
97
- * of a scene unit, and never inside the rectangle itself. Unreal's `AttenuationRadius`, in metres.
97
+ * of a scene unit, and never inside the rectangle itself. An attenuation radius, in metres.
98
98
  */
99
99
  range?: number;
100
100
  /**
101
101
  * Barn doors: a flap hinged at each of the rectangle's four edges, standing this many degrees from
102
102
  * its normal. 90, or absent, folds them flat and they hide nothing; smaller closes them, so the
103
103
  * light narrows to the opening they leave — a rectangle at 50° throws a beam rather than filling
104
- * its hemisphere. Unreal's `BarnDoorAngle`, whose default of 88° is nearly flat.
104
+ * its hemisphere. 88° is the usual default, nearly flat.
105
105
  *
106
106
  * What a fragment sees past the doors is the part of the rectangle no door's tip hides, one axis
107
107
  * at a time, and the rectangle then shades as that smaller rectangle, its highlight included.
@@ -111,7 +111,7 @@ export interface AreaLightSource {
111
111
  * rectangle's back face has none.
112
112
  */
113
113
  barnDoorAngle?: number;
114
- /** The barn doors' length in metres. Absent, 0.2: Unreal's default of 20 cm. */
114
+ /** The barn doors' length in metres. Absent, 0.2: the usual default of 20 cm. */
115
115
  barnDoorLength?: number;
116
116
  }
117
117
 
@@ -150,7 +150,7 @@ export function createAreaLightBuffer(capacity: number = MAX_AREA_LIGHTS): AreaL
150
150
  };
151
151
  }
152
152
 
153
- /** Unreal's default barn door length, in metres. See `AreaLightSource.barnDoorLength`. */
153
+ /** The usual default barn door length, in metres. See `AreaLightSource.barnDoorLength`. */
154
154
  export const DEFAULT_BARN_DOOR_LENGTH = 0.2;
155
155
 
156
156
  /**
@@ -8,7 +8,7 @@ import type { Renderer, TranslucentMeshOptions } from './webgl2/renderer.ts';
8
8
  import type { InstancedOptions, MeshInstances } from '../instances.ts';
9
9
  import type { SurfaceMaterial } from '../surfaceTexture.ts';
10
10
  import type { SurfaceOverlay } from '../surfaceOverlay.ts';
11
- import type { SurfaceSource } from '../compressedSource.ts';
11
+ import type { CompressedTextureSource, SurfaceSource } from '../compressedSource.ts';
12
12
  import type { LightmapPage } from '../lightmap.ts';
13
13
  import type { DeviceParticles } from '../particlePool.ts';
14
14
  import type { ClothBindingData } from '../clothBindingData.ts';
@@ -415,12 +415,22 @@ export type RendererApi = Omit<
415
415
  ): boolean;
416
416
 
417
417
  /**
418
- * Replace a texture's pixels, keeping the GPU object and its sampler state.
418
+ * Replace a texture's pixels, keeping the handle and its sampler state.
419
419
  *
420
420
  * The binding a draw loop already holds stays valid, so the swap is a swap rather than a
421
421
  * rebuild. Not a hot path: it re-uploads the whole image and rebuilds the mip chain.
422
+ *
423
+ * **Blocks may replace an image**, at the format's own size and with the chain they carry: the
424
+ * same picture at a fraction of the memory, which is how a loader that first showed a decoded
425
+ * image hands the device the blocks it encoded afterwards. Read in the colour space the texture
426
+ * was made with, and refused as `createSurfaceTexture` refuses them where the device does not take
427
+ * that format (`compressedFormats`). **The way back is refused**: a texture holding blocks takes
428
+ * no image, since nothing could say what its chain should become.
422
429
  */
423
- updateSurfaceTexture(texture: SurfaceTextureHandle, source: TexImageSource): void;
430
+ updateSurfaceTexture(
431
+ texture: SurfaceTextureHandle,
432
+ source: TexImageSource | CompressedTextureSource,
433
+ ): void;
424
434
 
425
435
  disposeSurfaceTexture(texture: SurfaceTextureHandle): void;
426
436
 
@@ -248,12 +248,17 @@ async function acquireWebGpu(
248
248
  features.push('timestamp-query');
249
249
  }
250
250
  /*
251
- * BC compressed textures, on the same terms: asked for wherever the adapter offers it, because
252
- * a device without it still draws — the loader decodes for it — and one with it samples a BC7
253
- * texture at a byte a texel instead of four. See `compressedUpload.ts`.
251
+ * The three block families, on the same terms: each asked for wherever the adapter offers it,
252
+ * because a device without one still draws — the loader decodes, or encodes ETC2, for it — and
253
+ * one with it samples a texture at a byte a texel or half of one instead of four. Desktops
254
+ * offer BC; phones ETC2 and ASTC. See `compressedUpload.ts`.
254
255
  */
255
- if (adapter.features?.has('texture-compression-bc') === true) {
256
- features.push('texture-compression-bc');
256
+ for (const family of [
257
+ 'texture-compression-bc',
258
+ 'texture-compression-etc2',
259
+ 'texture-compression-astc',
260
+ ] as const) {
261
+ if (adapter.features?.has(family) === true) features.push(family);
257
262
  }
258
263
  const descriptor: GPUDeviceDescriptor = {};
259
264
  if (Object.keys(required).length > 0) descriptor.requiredLimits = required;
@@ -0,0 +1,105 @@
1
+ /**
2
+ * A bone animation's textures on this backend, and a batch's clocks: what
3
+ * `shaders/boneAnimation.ts` reads with `texelFetch`, made once and never filtered. The twin of
4
+ * `webgpu/boneAnimations.ts`, at the same formats for the same reason: a turn in half floats jitters
5
+ * a crowd's hands at the frame's rate.
6
+ */
7
+ import {
8
+ CLOCK_TEXTURE_WIDTH,
9
+ type BoneAnimationHandle,
10
+ type BoneAnimationTexels,
11
+ } from '../../boneAnimation.ts';
12
+
13
+ /**
14
+ * A float texture read by index: one level, nearest, clamped. Bound on unit 0, chosen rather than
15
+ * inherited, for the reason `oitPass.ts` gives: `bindTexture` binds to whatever unit is active, and
16
+ * a frame leaves that wherever its last pass finished.
17
+ */
18
+ function floatTexture(
19
+ gl: WebGL2RenderingContext,
20
+ internal: number,
21
+ width: number,
22
+ height: number,
23
+ ): WebGLTexture {
24
+ const texture = gl.createTexture();
25
+ if (texture === null) throw new Error('boneAnimations: createTexture failed');
26
+ gl.activeTexture(gl.TEXTURE0);
27
+ gl.bindTexture(gl.TEXTURE_2D, texture);
28
+ gl.texStorage2D(gl.TEXTURE_2D, 1, internal, width, height);
29
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
30
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
31
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
32
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
33
+ return texture;
34
+ }
35
+
36
+ /** A clip on the device, from `createBoneAnimation`. */
37
+ export class GlBoneAnimation implements BoneAnimationHandle {
38
+ readonly bones: number;
39
+ readonly frames: number;
40
+ readonly framesPerSecond: number;
41
+ readonly boneScale: number;
42
+ readonly places: WebGLTexture;
43
+ readonly turns: WebGLTexture;
44
+ disposed = false;
45
+
46
+ constructor(gl: WebGL2RenderingContext, texels: BoneAnimationTexels) {
47
+ this.bones = texels.bones;
48
+ this.frames = texels.frames;
49
+ this.framesPerSecond = texels.framesPerSecond;
50
+ this.boneScale = texels.boneScale;
51
+ const make = (data: Float32Array): WebGLTexture => {
52
+ const texture = floatTexture(gl, gl.RGBA32F, texels.bones, texels.frames);
53
+ gl.texSubImage2D(
54
+ gl.TEXTURE_2D,
55
+ 0,
56
+ 0,
57
+ 0,
58
+ texels.bones,
59
+ texels.frames,
60
+ gl.RGBA,
61
+ gl.FLOAT,
62
+ data,
63
+ );
64
+ return texture;
65
+ };
66
+ this.places = make(texels.places);
67
+ this.turns = make(texels.turns);
68
+ gl.bindTexture(gl.TEXTURE_2D, null);
69
+ }
70
+
71
+ dispose(gl: WebGL2RenderingContext): void {
72
+ this.disposed = true;
73
+ gl.deleteTexture(this.places);
74
+ gl.deleteTexture(this.turns);
75
+ }
76
+ }
77
+
78
+ /** A batch's clocks: two floats an instance, wrapped onto rows `CLOCK_TEXTURE_WIDTH` wide. */
79
+ export class GlInstanceClocks {
80
+ readonly texture: WebGLTexture;
81
+ readonly staging: Float32Array;
82
+ private readonly width: number;
83
+
84
+ constructor(gl: WebGL2RenderingContext, capacity: number) {
85
+ this.width = Math.max(1, Math.min(capacity, CLOCK_TEXTURE_WIDTH));
86
+ const rows = Math.max(1, Math.ceil(capacity / this.width));
87
+ this.staging = new Float32Array(this.width * rows * 2);
88
+ this.texture = floatTexture(gl, gl.RG32F, this.width, rows);
89
+ gl.bindTexture(gl.TEXTURE_2D, null);
90
+ }
91
+
92
+ /** The rows that hold the first `count` instances, from `staging`. */
93
+ upload(gl: WebGL2RenderingContext, count: number): void {
94
+ if (count <= 0) return;
95
+ const rows = Math.ceil(count / this.width);
96
+ gl.activeTexture(gl.TEXTURE0);
97
+ gl.bindTexture(gl.TEXTURE_2D, this.texture);
98
+ gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, this.width, rows, gl.RG, gl.FLOAT, this.staging);
99
+ gl.bindTexture(gl.TEXTURE_2D, null);
100
+ }
101
+
102
+ dispose(gl: WebGL2RenderingContext): void {
103
+ gl.deleteTexture(this.texture);
104
+ }
105
+ }