@driftengine/core 4.9.0 → 4.10.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 (135) hide show
  1. package/CHANGELOG.json +62 -11
  2. package/README.md +43 -38
  3. package/dist/index.d.ts +3 -1
  4. package/dist/index.js +4 -3
  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 +90 -2
  16. package/dist/render/backend/webgl2/renderer.js +253 -24
  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 +19 -4
  22. package/dist/render/backend/webgpu/buffers.js +18 -11
  23. package/dist/render/backend/webgpu/compressedUpload.d.ts +0 -1
  24. package/dist/render/backend/webgpu/compressedUpload.js +53 -8
  25. package/dist/render/backend/webgpu/depthPass.d.ts +59 -3
  26. package/dist/render/backend/webgpu/depthPass.js +49 -14
  27. package/dist/render/backend/webgpu/drawCommand.d.ts +18 -4
  28. package/dist/render/backend/webgpu/drawCommand.js +10 -3
  29. package/dist/render/backend/webgpu/flatPass.d.ts +85 -42
  30. package/dist/render/backend/webgpu/flatPass.js +136 -20
  31. package/dist/render/backend/webgpu/instanced.d.ts +14 -1
  32. package/dist/render/backend/webgpu/instanced.js +24 -1
  33. package/dist/render/backend/webgpu/materialStore.d.ts +101 -0
  34. package/dist/render/backend/webgpu/materialStore.js +265 -0
  35. package/dist/render/backend/webgpu/pipelineCache.d.ts +6 -0
  36. package/dist/render/backend/webgpu/pipelineCache.js +7 -0
  37. package/dist/render/backend/webgpu/renderer.d.ts +152 -35
  38. package/dist/render/backend/webgpu/renderer.js +787 -142
  39. package/dist/render/backend/webgpu/skinPaletteRing.d.ts +7 -3
  40. package/dist/render/backend/webgpu/skinPaletteRing.js +13 -7
  41. package/dist/render/backend/webgpu/skinPaletteTexture.d.ts +6 -0
  42. package/dist/render/backend/webgpu/skinPaletteTexture.js +7 -1
  43. package/dist/render/backend/webgpu/staticBundles.d.ts +125 -0
  44. package/dist/render/backend/webgpu/staticBundles.js +241 -0
  45. package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +10 -3
  46. package/dist/render/backend/webgpu/surfaceTexturePass.js +20 -2
  47. package/dist/render/backend/webgpu/uniformRing.d.ts +21 -0
  48. package/dist/render/backend/webgpu/uniformRing.js +9 -1
  49. package/dist/render/bloomChain.d.ts +3 -3
  50. package/dist/render/boneAnimation.d.ts +92 -0
  51. package/dist/render/boneAnimation.js +133 -0
  52. package/dist/render/compressedSource.d.ts +33 -13
  53. package/dist/render/compressedSource.js +70 -25
  54. package/dist/render/glCompressed.js +83 -0
  55. package/dist/render/instances.d.ts +26 -0
  56. package/dist/render/lightBudget.d.ts +10 -0
  57. package/dist/render/lightBudget.js +10 -0
  58. package/dist/render/lightmap.d.ts +1 -1
  59. package/dist/render/mesh.d.ts +5 -0
  60. package/dist/render/mesh.js +6 -0
  61. package/dist/render/pointLightSelection.d.ts +4 -4
  62. package/dist/render/screenSpaceReflection.d.ts +2 -1
  63. package/dist/render/shaders/boneAnimation.d.ts +19 -0
  64. package/dist/render/shaders/boneAnimation.js +63 -0
  65. package/dist/render/shaders/depth.d.ts +7 -0
  66. package/dist/render/shaders/depth.js +35 -10
  67. package/dist/render/shaders/flat/index.d.ts +6 -0
  68. package/dist/render/shaders/flat/index.js +27 -5
  69. package/dist/render/shaders/flat/main.d.ts +1 -1
  70. package/dist/render/shaders/flat/main.js +15 -5
  71. package/dist/render/shaders/flat/models.js +1 -1
  72. package/dist/render/shaders/flat/overlay.js +1 -1
  73. package/dist/render/shaders/flat/pointShadow.js +2 -2
  74. package/dist/render/shaders/flat/preamble.js +38 -24
  75. package/dist/render/shaders/generated/depth.wgsl.d.ts +93 -0
  76. package/dist/render/shaders/generated/depth.wgsl.js +93 -0
  77. package/dist/render/shaders/generated/flat.wgsl.d.ts +3077 -2903
  78. package/dist/render/shaders/generated/flat.wgsl.js +3100 -2925
  79. package/dist/render/shaders/outputTransform.d.ts +7 -0
  80. package/dist/render/shaders/outputTransform.js +28 -14
  81. package/dist/render/shaders/vertexChannel.d.ts +7 -0
  82. package/dist/render/shaders/vertexChannel.js +26 -0
  83. package/dist/render/staticDraws.d.ts +47 -0
  84. package/dist/render/staticDraws.js +43 -0
  85. package/dist/render/surfaceTexture.d.ts +38 -7
  86. package/dist/render/surfaceTexture.js +56 -17
  87. package/package.json +6 -6
  88. package/src/index.ts +8 -2
  89. package/src/render/ambientHarmonics.ts +1 -1
  90. package/src/render/areaLights.ts +4 -4
  91. package/src/render/backend/api.ts +13 -3
  92. package/src/render/backend/select.ts +10 -5
  93. package/src/render/backend/webgl2/boneAnimations.ts +105 -0
  94. package/src/render/backend/webgl2/instanced.ts +20 -1
  95. package/src/render/backend/webgl2/renderer.ts +315 -24
  96. package/src/render/backend/webgl2/skinPaletteTexture.ts +6 -4
  97. package/src/render/backend/webgpu/boneAnimations.ts +97 -0
  98. package/src/render/backend/webgpu/buffers.ts +42 -16
  99. package/src/render/backend/webgpu/compressedUpload.ts +59 -9
  100. package/src/render/backend/webgpu/depthPass.ts +51 -10
  101. package/src/render/backend/webgpu/drawCommand.ts +22 -4
  102. package/src/render/backend/webgpu/flatPass.ts +187 -14
  103. package/src/render/backend/webgpu/instanced.ts +32 -1
  104. package/src/render/backend/webgpu/materialStore.ts +276 -0
  105. package/src/render/backend/webgpu/pipelineCache.ts +7 -0
  106. package/src/render/backend/webgpu/renderer.ts +928 -155
  107. package/src/render/backend/webgpu/skinPaletteRing.ts +13 -7
  108. package/src/render/backend/webgpu/skinPaletteTexture.ts +7 -1
  109. package/src/render/backend/webgpu/staticBundles.ts +363 -0
  110. package/src/render/backend/webgpu/surfaceTexturePass.ts +29 -3
  111. package/src/render/backend/webgpu/uniformRing.ts +10 -1
  112. package/src/render/bloomChain.ts +3 -3
  113. package/src/render/boneAnimation.ts +223 -0
  114. package/src/render/compressedSource.ts +121 -29
  115. package/src/render/glCompressed.ts +83 -0
  116. package/src/render/instances.ts +27 -0
  117. package/src/render/lightBudget.ts +11 -0
  118. package/src/render/lightmap.ts +1 -1
  119. package/src/render/mesh.ts +6 -0
  120. package/src/render/pointLightSelection.ts +4 -4
  121. package/src/render/screenSpaceReflection.ts +2 -1
  122. package/src/render/shaders/boneAnimation.ts +63 -0
  123. package/src/render/shaders/depth.ts +45 -10
  124. package/src/render/shaders/flat/index.ts +33 -5
  125. package/src/render/shaders/flat/main.ts +15 -5
  126. package/src/render/shaders/flat/models.ts +1 -1
  127. package/src/render/shaders/flat/overlay.ts +1 -1
  128. package/src/render/shaders/flat/pointShadow.ts +2 -2
  129. package/src/render/shaders/flat/preamble.ts +38 -24
  130. package/src/render/shaders/generated/depth.wgsl.ts +95 -0
  131. package/src/render/shaders/generated/flat.wgsl.ts +3100 -2925
  132. package/src/render/shaders/outputTransform.ts +30 -14
  133. package/src/render/shaders/vertexChannel.ts +27 -0
  134. package/src/render/staticDraws.ts +95 -0
  135. package/src/render/surfaceTexture.ts +94 -27
@@ -77,7 +77,7 @@ import { MAX_AREA_LIGHTS } from '../../areaLights.js';
77
77
  import { areaShadowLayerCount, castingRange, createResolvedAreaShadows, firstAreaShadowLayer, AreaShadowSet, } from '../../areaShadowSet.js';
78
78
  import { equirectToCubeFaces } from '../../equirectToCube.js';
79
79
  import { SKY_FRAG, skyVertFor } from '../../shaders/sky.js';
80
- import { DEPTH_CUTOUT_FRAG, DEPTH_CUTOUT_VERT, DEPTH_FRAG, DEPTH_INSTANCED_CUTOUT_VERT, DEPTH_INSTANCED_VERT, DEPTH_SKINNED_VERT, DEPTH_VERT, } from '../../shaders/depth.js';
80
+ import { DEPTH_CUTOUT_FRAG, DEPTH_CUTOUT_VERT, DEPTH_FRAG, DEPTH_INSTANCED_ANIMATED_CUTOUT_VERT, DEPTH_INSTANCED_ANIMATED_VERT, DEPTH_INSTANCED_CUTOUT_VERT, DEPTH_INSTANCED_VERT, DEPTH_SKINNED_VERT, DEPTH_VERT, } from '../../shaders/depth.js';
81
81
  import { OCTAHEDRAL_EDGE, PointShadowArray, strideLayers } from '../../pointShadowArray.js';
82
82
  import { SunShadowArray } from '../../shadowMap.js';
83
83
  import { POINT_SHADOW_SAMPLER, PointShadowSystem } from '../../pointShadowSystem.js';
@@ -85,6 +85,7 @@ import { castsDepth, glassOf } from '../../shadowCasters.js';
85
85
  import { GlassCasterList } from '../../glassCasters.js';
86
86
  import { SunGlassTint } from '../../sunGlassTint.js';
87
87
  import { GlassTintPrograms } from './glassTintPrograms.js';
88
+ import { captureStaticDraws, replayStaticDraws } from '../../staticDraws.js';
88
89
  import { FACE_COUNT, PointShadowMap } from '../../pointShadowMap.js';
89
90
  import { createResolvedPointShadows, DEFAULT_SOURCE_RADIUS } from '../../pointShadowImage.js';
90
91
  import { createBakeScratch, runPointShadowBakes, selectCastingLights, } from '../../pointShadowBudget.js';
@@ -117,6 +118,9 @@ import { DEFAULT_FILMIC_CURVE, FILMIC_CLAMPED, FILMIC_CONSTANTS, FILMIC_WITHOUT_
117
118
  import { deviceSnappedCellSize } from '../../textLayout.js';
118
119
  import { WindStreakRenderer } from '../../windStreakRenderer.js';
119
120
  import { InstancedBatch } from './instanced.js';
121
+ import { GlBoneAnimation } from './boneAnimations.js';
122
+ import { packBoneAnimation } from '../../boneAnimation.js';
123
+ import { BONE_PLACES_TEXTURE_UNIT, BONE_TURNS_TEXTURE_UNIT, INSTANCE_CLOCKS_TEXTURE_UNIT, } from '../../lightBudget.js';
120
124
  import { cutoutOf } from '../../cutoutCaster.js';
121
125
  import { CUTOUT_RESOLVE_CODE, resolveCutout } from '../../cutoutDither.js';
122
126
  import { materialHasMaps, noteMapsWithoutUvs } from '../../mapsWithoutUvs.js';
@@ -802,6 +806,11 @@ export class WebGL2Renderer {
802
806
  depthCutoutUniforms;
803
807
  depthInstancedCutoutProgram;
804
808
  depthInstancedCutoutUniforms;
809
+ /** A crowd's casters: the instanced depth programs that play a bone animation. */
810
+ depthAnimatedProgram;
811
+ depthAnimatedUniforms;
812
+ depthAnimatedCutoutProgram;
813
+ depthAnimatedCutoutUniforms;
805
814
  scatterDepthProgram;
806
815
  scatterDepthUniforms;
807
816
  /** The sun's static, moving and peeled maps, as layers of one array. See `shadowMap.ts`. */
@@ -1205,14 +1214,21 @@ export class WebGL2Renderer {
1205
1214
  const count = Math.min(data.count, gpuBatch.capacity);
1206
1215
  if (count === 0)
1207
1216
  return;
1217
+ const clip = gpuBatch.animation;
1218
+ if (clip !== null && clip.disposed)
1219
+ return;
1208
1220
  this.shadowDrawBudget.ask();
1209
1221
  const twoSided = material?.doubleSided === true;
1210
1222
  if (twoSided)
1211
1223
  gl.disable(gl.CULL_FACE);
1212
1224
  const cutout = cutoutOf(material);
1213
1225
  if (cutout !== null) {
1214
- gl.useProgram(this.depthInstancedCutoutProgram);
1215
- this.bindCutoutDepth(this.depthInstancedCutoutUniforms, cutout);
1226
+ /* A crowd casts where its clip has moved it, through the animated variant. */
1227
+ const uniforms = clip === null ? this.depthInstancedCutoutUniforms : this.depthAnimatedCutoutUniforms;
1228
+ gl.useProgram(clip === null ? this.depthInstancedCutoutProgram : this.depthAnimatedCutoutProgram);
1229
+ this.bindCutoutDepth(uniforms, cutout);
1230
+ if (clip !== null)
1231
+ this.bindAnimation(uniforms, gpuBatch);
1216
1232
  gpuBatch.draw(gl, count);
1217
1233
  if (twoSided && this.depthPassCullsFaces)
1218
1234
  gl.enable(gl.CULL_FACE);
@@ -1220,12 +1236,14 @@ export class WebGL2Renderer {
1220
1236
  this.restoreSurfaceTextureUnit();
1221
1237
  return;
1222
1238
  }
1223
- const u = this.depthInstancedUniforms;
1224
- gl.useProgram(this.depthInstancedProgram);
1239
+ const u = clip === null ? this.depthInstancedUniforms : this.depthAnimatedUniforms;
1240
+ gl.useProgram(clip === null ? this.depthInstancedProgram : this.depthAnimatedProgram);
1225
1241
  gl.uniformMatrix4fv(u['uLightViewProj'] ?? null, false, this.activeDepthViewProj);
1226
1242
  gl.uniform1i(u['uPeelShadowLayer'] ?? null, this.activeDepthPeel ? 1 : 0);
1227
1243
  if (this.activeDepthPeel)
1228
1244
  gl.uniform1i(u['uPreviousShadowMap'] ?? null, 0);
1245
+ if (clip !== null)
1246
+ this.bindAnimation(u, gpuBatch);
1229
1247
  gpuBatch.draw(gl, count);
1230
1248
  if (twoSided && this.depthPassCullsFaces)
1231
1249
  gl.enable(gl.CULL_FACE);
@@ -2230,10 +2248,15 @@ export class WebGL2Renderer {
2230
2248
  this.depthCutoutUniforms = uniformLocations(gl, this.depthCutoutProgram, 'depth-cutout');
2231
2249
  this.depthInstancedCutoutProgram = compileProgram(gl, DEPTH_INSTANCED_CUTOUT_VERT, DEPTH_CUTOUT_FRAG, 'depth-instanced-cutout');
2232
2250
  this.depthInstancedCutoutUniforms = uniformLocations(gl, this.depthInstancedCutoutProgram, 'depth-instanced-cutout');
2251
+ this.depthAnimatedProgram = compileProgram(gl, DEPTH_INSTANCED_ANIMATED_VERT, DEPTH_FRAG, 'depth-instanced-animated');
2252
+ this.depthAnimatedUniforms = uniformLocations(gl, this.depthAnimatedProgram, 'depth-instanced-animated');
2253
+ this.depthAnimatedCutoutProgram = compileProgram(gl, DEPTH_INSTANCED_ANIMATED_CUTOUT_VERT, DEPTH_CUTOUT_FRAG, 'depth-instanced-animated-cutout');
2254
+ this.depthAnimatedCutoutUniforms = uniformLocations(gl, this.depthAnimatedCutoutProgram, 'depth-instanced-animated-cutout');
2233
2255
  /* The cutout map is always read from the surface unit, which the draw borrows and gives back. */
2234
2256
  for (const [program, uniforms] of [
2235
2257
  [this.depthCutoutProgram, this.depthCutoutUniforms],
2236
2258
  [this.depthInstancedCutoutProgram, this.depthInstancedCutoutUniforms],
2259
+ [this.depthAnimatedCutoutProgram, this.depthAnimatedCutoutUniforms],
2237
2260
  ]) {
2238
2261
  gl.useProgram(program);
2239
2262
  gl.uniform1i(uniforms['uCutoutMap'] ?? null, SURFACE_TEXTURE_UNIT);
@@ -2401,6 +2424,8 @@ export class WebGL2Renderer {
2401
2424
  this.lightVolumeProgram,
2402
2425
  this.depthCutoutProgram,
2403
2426
  this.depthInstancedCutoutProgram,
2427
+ this.depthAnimatedProgram,
2428
+ this.depthAnimatedCutoutProgram,
2404
2429
  ]) {
2405
2430
  gl.deleteProgram(program);
2406
2431
  }
@@ -2718,7 +2743,7 @@ export class WebGL2Renderer {
2718
2743
  updateSurfaceTexture(texture, source) {
2719
2744
  if (this.contextLost)
2720
2745
  return;
2721
- texture.update(this.gl, source);
2746
+ texture.update(this.gl, source, this.compressedFormats);
2722
2747
  /* New pixels are a new copy for the overlay's slot: see `SurfaceTexture.flatLayer`. */
2723
2748
  if (texture === this.overlayMaps)
2724
2749
  this.bindOverlayMaps(texture);
@@ -2929,6 +2954,13 @@ export class WebGL2Renderer {
2929
2954
  this.materialDoubleSided = material?.doubleSided === true;
2930
2955
  this.materialHasMaps = materialHasMaps(material);
2931
2956
  this.currentMaterial = material;
2957
+ const reflectivity = material?.reflectivity;
2958
+ const environmentGain = material?.environmentGain;
2959
+ this.materialReflectivity =
2960
+ reflectivity === undefined ? null : Math.min(1, Math.max(0, reflectivity));
2961
+ this.materialEnvironmentGain =
2962
+ environmentGain === undefined ? null : Math.max(0, environmentGain);
2963
+ this.writeEnvironmentDials();
2932
2964
  /* What the blur spreads this skin by: the last material to name a profile says. */
2933
2965
  const model = material?.model;
2934
2966
  warnUnpagedLightmap(model, material?.modelMap);
@@ -2966,7 +2998,8 @@ export class WebGL2Renderer {
2966
2998
  const v = this.cutoutVector;
2967
2999
  v[0] = material?.cutout ?? 0;
2968
3000
  v[1] = CUTOUT_RESOLVE_CODE[this.cutoutResolveNow(material, translucent)];
2969
- v[2] = this.cutoutFrame;
3001
+ /* z unused: the dither's frame is the pass's, in `uHighlightMin.w`. See `uCutout`. */
3002
+ v[2] = 0;
2970
3003
  /* The material's lighting channels, which the lamps' are compared with. See `uCutout`. */
2971
3004
  v[3] = lightChannelsOf(material?.lightChannels);
2972
3005
  return v;
@@ -3157,8 +3190,22 @@ export class WebGL2Renderer {
3157
3190
  setSurfaceReflectivity(amount) {
3158
3191
  if (this.contextLost)
3159
3192
  return;
3193
+ this.passReflectivity = Math.min(1, Math.max(0, amount));
3194
+ this.writeEnvironmentDials();
3195
+ }
3196
+ /**
3197
+ * The reflectivity and environment gain the setters gave the pass, and the ones the material
3198
+ * set now states, or null where it states none (`SurfaceMaterial.reflectivity`). A draw wears the
3199
+ * material's where it has one and the pass's otherwise.
3200
+ */
3201
+ passReflectivity = 0;
3202
+ passEnvironmentGain = 1;
3203
+ materialReflectivity = null;
3204
+ materialEnvironmentGain = null;
3205
+ writeEnvironmentDials() {
3160
3206
  this.materials.dirty();
3161
- this.environmentDials[1] = Math.min(1, Math.max(0, amount));
3207
+ this.environmentDials[1] = this.materialReflectivity ?? this.passReflectivity;
3208
+ this.environmentDials[2] = this.materialEnvironmentGain ?? this.passEnvironmentGain;
3162
3209
  this.writeFlatVector('uEnvironmentDials', this.environmentDials);
3163
3210
  }
3164
3211
  /**
@@ -3237,9 +3284,8 @@ export class WebGL2Renderer {
3237
3284
  setEnvironmentGain(gain) {
3238
3285
  if (this.contextLost)
3239
3286
  return;
3240
- this.materials.dirty();
3241
- this.environmentDials[2] = Math.max(0, gain);
3242
- this.writeFlatVector('uEnvironmentDials', this.environmentDials);
3287
+ this.passEnvironmentGain = Math.max(0, gain);
3288
+ this.writeEnvironmentDials();
3243
3289
  }
3244
3290
  /**
3245
3291
  * Kept as a no-op that says so once, because it was a stand-in for a term that now exists.
@@ -3508,8 +3554,95 @@ export class WebGL2Renderer {
3508
3554
  throw new Error('WebGL2: a skinned mesh cannot be instanced — an instanced draw binds the two attribute ' +
3509
3555
  'locations the joint indices and weights occupy, and this mesh supplies them.');
3510
3556
  }
3557
+ const animation = (options.animation ?? null);
3558
+ /* The bone a vertex follows is its second coordinates' u, which a mesh carries in its grain
3559
+ lane; a mesh with none has nothing to name a bone by. See `boneAnimation.ts`. */
3560
+ if (animation !== null && !mesh.hasGrain) {
3561
+ throw new Error('WebGL2: a bone animation reads each vertex’s bone from the mesh’s second coordinates ' +
3562
+ '(`MeshData.lightmapUvs`), and this mesh has none.');
3563
+ }
3511
3564
  this.ensureInstancedProgram();
3512
- return new InstancedBatch(this.gl, mesh, capacity, options.cull ?? false);
3565
+ if (animation !== null)
3566
+ this.ensureAnimatedProgram();
3567
+ return new InstancedBatch(this.gl, mesh, capacity, options.cull ?? false, animation);
3568
+ }
3569
+ /**
3570
+ * A bone animation an instanced batch can play (`InstancedOptions.animation`): per frame, a turn
3571
+ * and a place for each bone, which every vertex of the batch's mesh follows — the bone its second
3572
+ * coordinates' u names, times `boneScale` — at each instance's own moment of the clip. What a
3573
+ * crowd is made of: every instance moves at the frame's rate on its own clock, in the colour pass
3574
+ * and in every shadow, with nothing on the processor after this. See `boneAnimation.ts` for the
3575
+ * clip's layout and the arithmetic, which both backends run.
3576
+ *
3577
+ * What it gives up against skinning is the blend: a vertex follows one bone. Refused, with what
3578
+ * was wrong, for a clip whose numbers are not its size, or past 2,048 bones or frames.
3579
+ */
3580
+ createBoneAnimation(clip) {
3581
+ return new GlBoneAnimation(this.gl, packBoneAnimation(clip));
3582
+ }
3583
+ /** Release a clip. A batch still playing it draws nothing, and casts nothing, after. */
3584
+ disposeBoneAnimation(animation) {
3585
+ const clip = animation;
3586
+ if (this.contextLost || clip.disposed)
3587
+ return;
3588
+ clip.dispose(this.gl);
3589
+ }
3590
+ /**
3591
+ * The clock a bone animation plays by this frame, in seconds on the caller's clock: an instance's
3592
+ * moment is this times its rate plus its phase (`MeshInstances.clocks`). **Set it before the
3593
+ * shadows are drawn**, once a frame, as the wind is set: a shadow drawn under one clock and its
3594
+ * caster under another come apart at the frame's rate. 0 until set, which holds every crowd at
3595
+ * the moment its phases name.
3596
+ */
3597
+ setAnimationTime(seconds) {
3598
+ if (Number.isFinite(seconds))
3599
+ this.animationTime = seconds;
3600
+ }
3601
+ /** The clock `setAnimationTime` set. */
3602
+ animationTime = 0;
3603
+ /**
3604
+ * An animated batch's clip, clocks and numbers into whichever program is bound: the three vertex
3605
+ * textures on units of their own (`lightBudget.ts`), the clip's speed and bone scale, the clock.
3606
+ */
3607
+ bindAnimation(u, batch) {
3608
+ const clip = batch.animation;
3609
+ const clocks = batch.clocks;
3610
+ if (clip === null || clocks === null)
3611
+ return;
3612
+ const { gl } = this;
3613
+ gl.activeTexture(gl.TEXTURE0 + BONE_PLACES_TEXTURE_UNIT);
3614
+ gl.bindTexture(gl.TEXTURE_2D, clip.places);
3615
+ gl.activeTexture(gl.TEXTURE0 + BONE_TURNS_TEXTURE_UNIT);
3616
+ gl.bindTexture(gl.TEXTURE_2D, clip.turns);
3617
+ gl.activeTexture(gl.TEXTURE0 + INSTANCE_CLOCKS_TEXTURE_UNIT);
3618
+ gl.bindTexture(gl.TEXTURE_2D, clocks.texture);
3619
+ gl.uniform1i(u['uBonePlaces'] ?? null, BONE_PLACES_TEXTURE_UNIT);
3620
+ gl.uniform1i(u['uBoneTurns'] ?? null, BONE_TURNS_TEXTURE_UNIT);
3621
+ gl.uniform1i(u['uInstanceClocks'] ?? null, INSTANCE_CLOCKS_TEXTURE_UNIT);
3622
+ gl.uniform2f(u['uBoneClip'] ?? null, clip.framesPerSecond, clip.boneScale);
3623
+ gl.uniform1f(u['uSceneTime'] ?? null, this.animationTime);
3624
+ }
3625
+ /** The animated instanced program, compiled on first use. See `ensureInstancedProgram`. */
3626
+ flatAnimatedProgram = null;
3627
+ flatAnimatedUniforms = null;
3628
+ /** Compile the animated variant, once, and feed it this frame's pass state. */
3629
+ ensureAnimatedProgram() {
3630
+ if (this.contextLost || this.flatAnimatedProgram !== null)
3631
+ return;
3632
+ const { gl } = this;
3633
+ const program = compileProgram(gl, flatVert({ skinned: false, morphed: false, instanced: true, animated: true }), this.flatFragSource, 'flat.animated');
3634
+ const uniforms = uniformLocations(gl, program, 'flat.animated');
3635
+ this.flatAnimatedProgram = program;
3636
+ this.flatAnimatedUniforms = uniforms;
3637
+ this.flatTargets.push({ program, uniforms });
3638
+ const camera = this.lastPassCamera;
3639
+ const env = this.lastPassEnv;
3640
+ if (camera !== null && env !== null) {
3641
+ gl.useProgram(program);
3642
+ this.writeMeshPassState(uniforms, camera, env);
3643
+ this.writeMaterialState(uniforms, this.currentMaterial);
3644
+ this.useFlatProgram();
3645
+ }
3513
3646
  }
3514
3647
  /** Push placement and colour. Only the live prefix. */
3515
3648
  uploadInstanced(batch, data) {
@@ -3562,18 +3695,29 @@ export class WebGL2Renderer {
3562
3695
  const culling = !blend && batch.cull;
3563
3696
  if (culling && !batchBoxVisible(batch.box, this.frustum, this.occlusion))
3564
3697
  return;
3698
+ /* A batch playing a bone animation takes the animated variant; a disposed clip draws nothing. */
3699
+ const clip = batch.animation;
3700
+ if (clip !== null && clip.disposed)
3701
+ return;
3565
3702
  this.drawBudget.ask();
3566
3703
  this.ensureInstancedProgram();
3704
+ if (clip !== null)
3705
+ this.ensureAnimatedProgram();
3567
3706
  /* A material with a model takes that model's instanced program; see `modelProgram`. */
3568
3707
  const modelKind = this.currentMaterial?.model?.kind ?? null;
3569
- const modelled = modelKind === null ? null : this.modelProgram('instanced', modelKind);
3570
- const program = modelled?.program ?? this.flatInstancedProgram;
3571
- const u = modelled?.uniforms ?? this.flatInstancedUniforms;
3708
+ const modelled = modelKind === null
3709
+ ? null
3710
+ : this.modelProgram(clip === null ? 'instanced' : 'animated', modelKind);
3711
+ const program = modelled?.program ?? (clip === null ? this.flatInstancedProgram : this.flatAnimatedProgram);
3712
+ const u = modelled?.uniforms ??
3713
+ (clip === null ? this.flatInstancedUniforms : this.flatAnimatedUniforms);
3572
3714
  if (program === null || u === null)
3573
3715
  return;
3574
3716
  const { gl } = this;
3575
3717
  gl.useProgram(program);
3576
3718
  gl.uniform1i(u['uHasTangents'] ?? null, mesh.hasTangents ? 1 : 0);
3719
+ if (clip !== null)
3720
+ this.bindAnimation(u, batch);
3577
3721
  const lit = options.lit ?? true;
3578
3722
  /* Added light fades in the medium rather than receding into it. See `drawFog.ts`. */
3579
3723
  const fog = fogModeOf(options.fog ?? true, blend && options.additive === true);
@@ -3643,10 +3787,14 @@ export class WebGL2Renderer {
3643
3787
  const keeps = !blend && this.reflectionKeeps();
3644
3788
  if (keeps)
3645
3789
  this.reflectionTargets?.on();
3646
- if (culling && cullsInstances(count, mesh.indexCount))
3790
+ /* An animated batch reads its clocks by instance index, which the culled copy would reorder:
3791
+ it is culled whole and never instance by instance. */
3792
+ if (culling && clip === null && cullsInstances(count, mesh.indexCount)) {
3647
3793
  batch.drawCulled(gl, data, this.frustum);
3648
- else
3794
+ }
3795
+ else {
3649
3796
  batch.draw(gl, count);
3797
+ }
3650
3798
  if (keeps)
3651
3799
  this.reflectionTargets?.off();
3652
3800
  if (coverage)
@@ -3710,7 +3858,9 @@ export class WebGL2Renderer {
3710
3858
  const glass = this.glassScratch;
3711
3859
  /* Strength, path thickness, transmission, frost: one vector, for the budget the shader states. */
3712
3860
  gl.uniform4f(u['uSeeThrough'] ?? null, Math.max(refraction, 0), options.thicknessM ?? 0, glass.transmission, glass.frost);
3713
- gl.uniform3fv(u['uRefractTint'] ?? null, options.refractTint ?? WHITE_TINT);
3861
+ /* w: this framebuffer's first row is the bottom, so up the screen is up the rows. */
3862
+ const tint = options.refractTint ?? WHITE_TINT;
3863
+ gl.uniform4f(u['uRefractTint'] ?? null, tint[0] ?? 1, tint[1] ?? 1, tint[2] ?? 1, 1);
3714
3864
  gl.uniform3fv(u['uGlassTint'] ?? null, glass.tint);
3715
3865
  return true;
3716
3866
  }
@@ -4753,6 +4903,69 @@ export class WebGL2Renderer {
4753
4903
  return;
4754
4904
  casters(this.casterSink);
4755
4905
  }
4906
+ /**
4907
+ * Draws that do not change between frames, enumerated once and kept: what a stage, a set or any
4908
+ * world that stands still draws, recorded so a frame replays it rather than issuing it.
4909
+ *
4910
+ * **The same enumeration `drawSceneCasters` takes**, called once, here: rigid meshes and instanced
4911
+ * batches, each with its material. A skinned mesh or a scatter batch is refused by name, since
4912
+ * both change every frame. What it captures is what the enumeration said at the time — the
4913
+ * matrices and the instance counts are copied, so a caller's scratch is free to move on — while
4914
+ * the meshes, batches, textures and materials stay the caller's: a list must be disposed, or made
4915
+ * again, before anything it holds is, and made again when a material it holds is changed.
4916
+ *
4917
+ * **What it is for is the frame's processor time.** WebGPU records the list into a render bundle
4918
+ * per view and replays it with one call, where a draw issued a frame at a time costs its encoding
4919
+ * every frame and again for every view that draws it. This backend has no bundles and replays the
4920
+ * entries through the scene-caster path every frame, at the cost a frame drawing them itself pays;
4921
+ * the picture is the same.
4922
+ *
4923
+ * What a list gives up against drawing each frame is culling: every entry is drawn in every view,
4924
+ * because a bundle cannot leave one out. A world split into lists by region culls by list.
4925
+ */
4926
+ createStaticDraws(casters) {
4927
+ return captureStaticDraws(casters);
4928
+ }
4929
+ /**
4930
+ * Draw a list into the open mesh pass, under the camera, the lights and the fog `bindMeshPass`
4931
+ * set — the frame's, a mirror's, a capture's. Opaque draws, through each entry's material.
4932
+ *
4933
+ * **Its draws are rigid and wear no overlay**, so a skin palette, morph weights, cloth or a surface
4934
+ * overlay set before it is put back to none, as `bindMeshPass` puts an overlay back; and no
4935
+ * material is left set after it, which is what `setMaterial(null)` leaves. The state a pass set —
4936
+ * the grain, the relief, the fog, the grade — is the list's to draw with, as it is any draw's, and
4937
+ * a list drawn under a different one is recorded again for it on WebGPU.
4938
+ */
4939
+ drawStaticDraws(draws) {
4940
+ if (this.contextLost)
4941
+ return;
4942
+ const list = draws;
4943
+ if (list.disposed) {
4944
+ this.warnStaticDisposed();
4945
+ return;
4946
+ }
4947
+ this.setSkinPalette(null);
4948
+ this.setMorphWeights(null);
4949
+ this.setCloth(null);
4950
+ this.setSurfaceOverlay(null);
4951
+ this.setMaterial(null);
4952
+ this.sceneCasterMaterial = undefined;
4953
+ replayStaticDraws(list, this.sceneCasterSink);
4954
+ this.setMaterial(null);
4955
+ this.sceneCasterMaterial = undefined;
4956
+ }
4957
+ /** Release a list. Its meshes, batches and materials are the caller's and are not released. */
4958
+ disposeStaticDraws(draws) {
4959
+ draws.disposed = true;
4960
+ }
4961
+ /** A disposed list drawn, said once: the frame loop does not throw. */
4962
+ warnStaticDisposed() {
4963
+ if (this.warnedStaticDisposed)
4964
+ return;
4965
+ this.warnedStaticDisposed = true;
4966
+ console.warn('drawStaticDraws: this list was disposed, and draws nothing.');
4967
+ }
4968
+ warnedStaticDisposed = false;
4756
4969
  /**
4757
4970
  * Draw the pass's glass twice: where the nearest pane is, and what the panes let through.
4758
4971
  *
@@ -4965,10 +5178,10 @@ export class WebGL2Renderer {
4965
5178
  const { gl } = this;
4966
5179
  Object.assign(this.litOn, this.litWanted);
4967
5180
  this.flatFragSource = this.flatFragAt(this.lightBudget);
4968
- const recompile = (old, skinned, morphed, instanced, label, eight = false) => {
5181
+ const recompile = (old, skinned, morphed, instanced, label, eight = false, animated = false) => {
4969
5182
  if (old === null)
4970
5183
  return null;
4971
- const vertex = flatVert({ skinned, morphed, instanced });
5184
+ const vertex = flatVert({ skinned, morphed, instanced, animated });
4972
5185
  const program = compileProgram(gl, eight ? skinEight(vertex) : vertex, this.flatFragSource, label);
4973
5186
  gl.deleteProgram(old);
4974
5187
  return { program, uniforms: uniformLocations(gl, program, label) };
@@ -4996,6 +5209,9 @@ export class WebGL2Renderer {
4996
5209
  const instanced = recompile(this.flatInstancedProgram, false, false, true, 'flat.instanced');
4997
5210
  this.flatInstancedProgram = instanced?.program ?? null;
4998
5211
  this.flatInstancedUniforms = instanced?.uniforms ?? null;
5212
+ const animated = recompile(this.flatAnimatedProgram, false, false, true, 'flat.animated', false, true);
5213
+ this.flatAnimatedProgram = animated?.program ?? null;
5214
+ this.flatAnimatedUniforms = animated?.uniforms ?? null;
4999
5215
  for (const [key, entry] of this.clothFlat) {
5000
5216
  const eight = key.startsWith('8');
5001
5217
  const withMorph = key.endsWith('m');
@@ -5013,7 +5229,7 @@ export class WebGL2Renderer {
5013
5229
  this.modelFlat.set(key, { program, uniforms: uniformLocations(gl, program, label) });
5014
5230
  }
5015
5231
  this.flatTargets.length = 0;
5016
- for (const target of [plain, skinned, morphed, both, skinned8, both8, instanced]) {
5232
+ for (const target of [plain, skinned, morphed, both, skinned8, both8, instanced, animated]) {
5017
5233
  if (target !== null)
5018
5234
  this.flatTargets.push(target);
5019
5235
  }
@@ -6782,6 +6998,10 @@ export class WebGL2Renderer {
6782
6998
  /* Nor an ambient of its own: the frame's, until a draw asks. See `setAmbientSH`. */
6783
6999
  this.ambientSH.fill(0);
6784
7000
  /* No reflection and an environment at its own brightness; the emission the world asks for. */
7001
+ this.passReflectivity = 0;
7002
+ this.passEnvironmentGain = 1;
7003
+ this.materialReflectivity = null;
7004
+ this.materialEnvironmentGain = null;
6785
7005
  this.environmentDials[1] = 0;
6786
7006
  this.environmentDials[2] = 1;
6787
7007
  this.emission[0] = env.emissiveGain;
@@ -6911,7 +7131,7 @@ export class WebGL2Renderer {
6911
7131
  /* Neither refracting nor glass, which is every draw that does not say otherwise. See
6912
7132
  `bindSeeThrough` for the four numbers in the one vector. */
6913
7133
  gl.uniform4f(u['uSeeThrough'] ?? null, 0, 0, 0, 0);
6914
- gl.uniform3fv(u['uRefractTint'] ?? null, WHITE_TINT);
7134
+ gl.uniform4f(u['uRefractTint'] ?? null, 1, 1, 1, 1);
6915
7135
  gl.uniform3fv(u['uGlassTint'] ?? null, WHITE_TINT);
6916
7136
  gl.uniform3fv(u['uDirectionalDir'] ?? null, env.directionalDir);
6917
7137
  gl.uniform3fv(u['uDirectionalColor'] ?? null, env.directionalColor);
@@ -7025,7 +7245,10 @@ export class WebGL2Renderer {
7025
7245
  gl.uniform1i(u['uPeeledShadowEnabled'] ?? null, this.peelFilled ? 1 : 0);
7026
7246
  gl.uniform1f(u['uShadowMaxSlope'] ?? null, this.quality.directionalShadowMaxSlope);
7027
7247
  gl.uniform1i(u['uShadowFilterTaps'] ?? null, this.quality.shadowFilterTaps);
7028
- gl.uniform3fv(u['uHighlightMin'] ?? null, env.highlightMin);
7248
+ /* The near corner, and in its w the frame the cutout dither moves with. See `uHighlightMin`.
7249
+ An environment written by hand may leave the box out, as the far corner says below. */
7250
+ const min = env.highlightMin;
7251
+ gl.uniform4f(u['uHighlightMin'] ?? null, min?.[0] ?? 0, min?.[1] ?? 0, min?.[2] ?? 0, this.cutoutFrame);
7029
7252
  /* The far corner, and the gain in its w. See `uHighlightMax`. */
7030
7253
  /* An environment written by hand may leave the box out; then there is none, as before. */
7031
7254
  const max = env.highlightMax ?? NO_HIGHLIGHT;
@@ -8350,6 +8573,12 @@ function litVertexOf(variant) {
8350
8573
  }
8351
8574
  const skinned = variant.startsWith('skinned') || variant.startsWith('both');
8352
8575
  const morphed = variant === 'morphed' || variant.startsWith('both');
8353
- const vertex = flatVert({ skinned, morphed, instanced: variant === 'instanced' });
8576
+ const animated = variant === 'animated';
8577
+ const vertex = flatVert({
8578
+ skinned,
8579
+ morphed,
8580
+ instanced: variant === 'instanced' || animated,
8581
+ animated,
8582
+ });
8354
8583
  return variant.endsWith('8') ? skinEight(vertex) : vertex;
8355
8584
  }
@@ -19,9 +19,11 @@ export declare class SkinPaletteTexture {
19
19
  /**
20
20
  * Upload a palette, reallocating only when the joint count changes.
21
21
  *
22
- * A rig's joint count is fixed for its life, so the reallocation path runs once per skeleton and
23
- * the steady state is one `texSubImage2D`. Reallocating per frame would be a GPU allocation in
24
- * the frame loop, which is the same class of mistake as allocating on the CPU there.
22
+ * **The texture grows and never shrinks**, so the steady state is one `texSubImage2D` whatever
23
+ * mix of rigs a frame draws. It was rebuilt whenever the joint count changed, which with one
24
+ * texture for every skinned draw meant a character and a prop drawn in turn reallocated it at
25
+ * every draw: a GPU allocation in the frame loop. The shader reads joints by index, so a texture
26
+ * wider than the rig is read exactly as one that fits.
25
27
  */
26
28
  update(gl: WebGL2RenderingContext, palette: Float32Array): void;
27
29
  /** Bind to a **vertex-stage** unit. See `SKIN_PALETTE_TEXTURE_UNIT` for why that is free. */
@@ -20,15 +20,17 @@ export class SkinPaletteTexture {
20
20
  /**
21
21
  * Upload a palette, reallocating only when the joint count changes.
22
22
  *
23
- * A rig's joint count is fixed for its life, so the reallocation path runs once per skeleton and
24
- * the steady state is one `texSubImage2D`. Reallocating per frame would be a GPU allocation in
25
- * the frame loop, which is the same class of mistake as allocating on the CPU there.
23
+ * **The texture grows and never shrinks**, so the steady state is one `texSubImage2D` whatever
24
+ * mix of rigs a frame draws. It was rebuilt whenever the joint count changed, which with one
25
+ * texture for every skinned draw meant a character and a prop drawn in turn reallocated it at
26
+ * every draw: a GPU allocation in the frame loop. The shader reads joints by index, so a texture
27
+ * wider than the rig is read exactly as one that fits.
26
28
  */
27
29
  update(gl, palette) {
28
30
  validateSkinPalette(palette);
29
31
  const joints = palette.length / 16;
30
32
  const width = paletteTextureWidth(joints);
31
- if (this.texture === null || this.joints !== joints) {
33
+ if (this.texture === null || joints > this.joints) {
32
34
  if (this.texture !== null)
33
35
  gl.deleteTexture(this.texture);
34
36
  const created = gl.createTexture();
@@ -0,0 +1,34 @@
1
+ /**
2
+ * A bone animation's textures on this backend, and a batch's clocks: what
3
+ * `shaders/boneAnimation.ts` reads by index, made once and never filtered.
4
+ *
5
+ * **`rgba32float` places and turns, and `rg32float` clocks**, all read with `textureLoad` and bound
6
+ * `unfilterable-float`, as the joint palette is: a turn in half floats moves a crowd's hands by a
7
+ * thousandth of their reach, a jitter at the frame's rate, and the bytes are a clip's, once.
8
+ */
9
+ import { type BoneAnimationHandle, type BoneAnimationTexels } from '../../boneAnimation.ts';
10
+ /** A clip on the device, from `createBoneAnimation`. */
11
+ export declare class GpuBoneAnimation implements BoneAnimationHandle {
12
+ readonly bones: number;
13
+ readonly frames: number;
14
+ readonly framesPerSecond: number;
15
+ readonly boneScale: number;
16
+ readonly placesView: GPUTextureView;
17
+ readonly turnsView: GPUTextureView;
18
+ disposed: boolean;
19
+ private readonly places;
20
+ private readonly turns;
21
+ constructor(device: GPUDevice, texels: BoneAnimationTexels);
22
+ dispose(): void;
23
+ }
24
+ /** A batch's clocks: two floats an instance, wrapped onto rows `CLOCK_TEXTURE_WIDTH` wide. */
25
+ export declare class GpuInstanceClocks {
26
+ readonly view: GPUTextureView;
27
+ readonly staging: Float32Array;
28
+ private readonly texture;
29
+ private readonly width;
30
+ constructor(device: GPUDevice, capacity: number, label: string);
31
+ /** The rows that hold the first `count` instances, from `staging`. */
32
+ upload(queue: GPUQueue, count: number): void;
33
+ dispose(): void;
34
+ }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * A bone animation's textures on this backend, and a batch's clocks: what
3
+ * `shaders/boneAnimation.ts` reads by index, made once and never filtered.
4
+ *
5
+ * **`rgba32float` places and turns, and `rg32float` clocks**, all read with `textureLoad` and bound
6
+ * `unfilterable-float`, as the joint palette is: a turn in half floats moves a crowd's hands by a
7
+ * thousandth of their reach, a jitter at the frame's rate, and the bytes are a clip's, once.
8
+ */
9
+ import { CLOCK_TEXTURE_WIDTH, } from '../../boneAnimation.js';
10
+ const USAGE_TEXTURE = 0x4 | 0x2; // TEXTURE_BINDING | COPY_DST
11
+ /** A clip on the device, from `createBoneAnimation`. */
12
+ export class GpuBoneAnimation {
13
+ bones;
14
+ frames;
15
+ framesPerSecond;
16
+ boneScale;
17
+ placesView;
18
+ turnsView;
19
+ disposed = false;
20
+ places;
21
+ turns;
22
+ constructor(device, texels) {
23
+ this.bones = texels.bones;
24
+ this.frames = texels.frames;
25
+ this.framesPerSecond = texels.framesPerSecond;
26
+ this.boneScale = texels.boneScale;
27
+ const make = (label, data) => {
28
+ const texture = device.createTexture({
29
+ label,
30
+ size: [texels.bones, texels.frames],
31
+ format: 'rgba32float',
32
+ usage: USAGE_TEXTURE,
33
+ });
34
+ device.queue.writeTexture({ texture }, data, { bytesPerRow: texels.bones * 16, rowsPerImage: texels.frames }, [texels.bones, texels.frames]);
35
+ return texture;
36
+ };
37
+ this.places = make('bones.places', texels.places);
38
+ this.turns = make('bones.turns', texels.turns);
39
+ this.placesView = this.places.createView();
40
+ this.turnsView = this.turns.createView();
41
+ }
42
+ dispose() {
43
+ this.disposed = true;
44
+ this.places.destroy();
45
+ this.turns.destroy();
46
+ }
47
+ }
48
+ /** A batch's clocks: two floats an instance, wrapped onto rows `CLOCK_TEXTURE_WIDTH` wide. */
49
+ export class GpuInstanceClocks {
50
+ view;
51
+ staging;
52
+ texture;
53
+ width;
54
+ constructor(device, capacity, label) {
55
+ this.width = Math.max(1, Math.min(capacity, CLOCK_TEXTURE_WIDTH));
56
+ const rows = Math.max(1, Math.ceil(capacity / this.width));
57
+ this.staging = new Float32Array(this.width * rows * 2);
58
+ this.texture = device.createTexture({
59
+ label,
60
+ size: [this.width, rows],
61
+ format: 'rg32float',
62
+ usage: USAGE_TEXTURE,
63
+ });
64
+ this.view = this.texture.createView();
65
+ }
66
+ /** The rows that hold the first `count` instances, from `staging`. */
67
+ upload(queue, count) {
68
+ if (count <= 0)
69
+ return;
70
+ const rows = Math.ceil(count / this.width);
71
+ queue.writeTexture({ texture: this.texture }, this.staging, { bytesPerRow: this.width * 8, rowsPerImage: rows }, [this.width, rows]);
72
+ }
73
+ dispose() {
74
+ this.texture.destroy();
75
+ }
76
+ }