@driftengine/core 4.8.7 → 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 (263) hide show
  1. package/CHANGELOG.json +123 -11
  2. package/README.md +43 -38
  3. package/dist/index.d.ts +8 -1
  4. package/dist/index.js +11 -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/atmosphere.d.ts +13 -0
  10. package/dist/render/atmosphere.js +9 -1
  11. package/dist/render/backend/api.d.ts +19 -4
  12. package/dist/render/backend/createRenderer.d.ts +6 -0
  13. package/dist/render/backend/createRenderer.js +4 -1
  14. package/dist/render/backend/select.js +11 -5
  15. package/dist/render/backend/webgl2/boneAnimations.d.ts +29 -0
  16. package/dist/render/backend/webgl2/boneAnimations.js +80 -0
  17. package/dist/render/backend/webgl2/instanced.d.ts +5 -1
  18. package/dist/render/backend/webgl2/instanced.js +13 -1
  19. package/dist/render/backend/webgl2/renderer.d.ts +180 -4
  20. package/dist/render/backend/webgl2/renderer.js +550 -40
  21. package/dist/render/backend/webgl2/skinPaletteTexture.d.ts +5 -3
  22. package/dist/render/backend/webgl2/skinPaletteTexture.js +6 -4
  23. package/dist/render/backend/webgpu/boneAnimations.d.ts +34 -0
  24. package/dist/render/backend/webgpu/boneAnimations.js +76 -0
  25. package/dist/render/backend/webgpu/buffers.d.ts +19 -4
  26. package/dist/render/backend/webgpu/buffers.js +18 -11
  27. package/dist/render/backend/webgpu/compressedUpload.d.ts +0 -1
  28. package/dist/render/backend/webgpu/compressedUpload.js +53 -8
  29. package/dist/render/backend/webgpu/depthPass.d.ts +59 -3
  30. package/dist/render/backend/webgpu/depthPass.js +49 -14
  31. package/dist/render/backend/webgpu/device.d.ts +7 -1
  32. package/dist/render/backend/webgpu/device.js +31 -3
  33. package/dist/render/backend/webgpu/drawCommand.d.ts +18 -4
  34. package/dist/render/backend/webgpu/drawCommand.js +10 -3
  35. package/dist/render/backend/webgpu/flatPass.d.ts +100 -42
  36. package/dist/render/backend/webgpu/flatPass.js +191 -43
  37. package/dist/render/backend/webgpu/instanced.d.ts +14 -1
  38. package/dist/render/backend/webgpu/instanced.js +24 -1
  39. package/dist/render/backend/webgpu/materialStore.d.ts +101 -0
  40. package/dist/render/backend/webgpu/materialStore.js +265 -0
  41. package/dist/render/backend/webgpu/pipelineCache.d.ts +6 -0
  42. package/dist/render/backend/webgpu/pipelineCache.js +7 -0
  43. package/dist/render/backend/webgpu/postPass.d.ts +51 -9
  44. package/dist/render/backend/webgpu/postPass.js +16 -2
  45. package/dist/render/backend/webgpu/reflectionSurfacePass.d.ts +30 -0
  46. package/dist/render/backend/webgpu/reflectionSurfacePass.js +100 -0
  47. package/dist/render/backend/webgpu/renderer.d.ts +283 -37
  48. package/dist/render/backend/webgpu/renderer.js +1363 -202
  49. package/dist/render/backend/webgpu/skinPaletteRing.d.ts +7 -3
  50. package/dist/render/backend/webgpu/skinPaletteRing.js +13 -7
  51. package/dist/render/backend/webgpu/skinPaletteTexture.d.ts +6 -0
  52. package/dist/render/backend/webgpu/skinPaletteTexture.js +7 -1
  53. package/dist/render/backend/webgpu/staticBundles.d.ts +125 -0
  54. package/dist/render/backend/webgpu/staticBundles.js +241 -0
  55. package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +10 -3
  56. package/dist/render/backend/webgpu/surfaceTexturePass.js +20 -2
  57. package/dist/render/backend/webgpu/uniformRing.d.ts +21 -0
  58. package/dist/render/backend/webgpu/uniformRing.js +9 -1
  59. package/dist/render/bloomChain.d.ts +3 -3
  60. package/dist/render/boneAnimation.d.ts +92 -0
  61. package/dist/render/boneAnimation.js +133 -0
  62. package/dist/render/compressedSource.d.ts +33 -13
  63. package/dist/render/compressedSource.js +70 -25
  64. package/dist/render/displayRange.d.ts +31 -0
  65. package/dist/render/displayRange.js +47 -0
  66. package/dist/render/filmicCurve.d.ts +74 -0
  67. package/dist/render/filmicCurve.js +203 -0
  68. package/dist/render/fog.d.ts +4 -1
  69. package/dist/render/fog.js +12 -2
  70. package/dist/render/frame/resources.d.ts +2 -2
  71. package/dist/render/frame/resources.js +6 -0
  72. package/dist/render/frameReflections.d.ts +40 -0
  73. package/dist/render/frameReflections.js +48 -0
  74. package/dist/render/glCompressed.js +83 -0
  75. package/dist/render/instances.d.ts +26 -0
  76. package/dist/render/lightBudget.d.ts +10 -0
  77. package/dist/render/lightBudget.js +10 -0
  78. package/dist/render/lightmap.d.ts +1 -1
  79. package/dist/render/mesh.d.ts +5 -0
  80. package/dist/render/mesh.js +6 -0
  81. package/dist/render/pass.d.ts +49 -0
  82. package/dist/render/pointLightSelection.d.ts +4 -4
  83. package/dist/render/reflectionSurfaceTargets.d.ts +28 -0
  84. package/dist/render/reflectionSurfaceTargets.js +99 -0
  85. package/dist/render/renderQuality.d.ts +39 -8
  86. package/dist/render/renderQuality.js +4 -0
  87. package/dist/render/sceneTarget.d.ts +5 -0
  88. package/dist/render/sceneTarget.js +24 -1
  89. package/dist/render/screenSpaceReflection.d.ts +7 -1
  90. package/dist/render/screenSpaceReflection.js +5 -0
  91. package/dist/render/shaders/arcane.d.ts +1 -1
  92. package/dist/render/shaders/bolt.d.ts +1 -1
  93. package/dist/render/shaders/boneAnimation.d.ts +19 -0
  94. package/dist/render/shaders/boneAnimation.js +63 -0
  95. package/dist/render/shaders/caustics.d.ts +1 -1
  96. package/dist/render/shaders/depth.d.ts +7 -0
  97. package/dist/render/shaders/depth.js +35 -10
  98. package/dist/render/shaders/film.d.ts +1 -1
  99. package/dist/render/shaders/filmLook.js +2 -1
  100. package/dist/render/shaders/fire.d.ts +1 -1
  101. package/dist/render/shaders/flat/glassTint.d.ts +1 -1
  102. package/dist/render/shaders/flat/glassTint.js +2 -0
  103. package/dist/render/shaders/flat/index.d.ts +16 -0
  104. package/dist/render/shaders/flat/index.js +33 -5
  105. package/dist/render/shaders/flat/main.d.ts +1 -1
  106. package/dist/render/shaders/flat/main.js +43 -5
  107. package/dist/render/shaders/flat/models.js +1 -1
  108. package/dist/render/shaders/flat/overlay.d.ts +16 -0
  109. package/dist/render/shaders/flat/overlay.js +117 -0
  110. package/dist/render/shaders/flat/pointShadow.js +3 -3
  111. package/dist/render/shaders/flat/preamble.js +38 -24
  112. package/dist/render/shaders/flat/reflectionSurface.d.ts +21 -0
  113. package/dist/render/shaders/flat/reflectionSurface.js +61 -0
  114. package/dist/render/shaders/fog.d.ts +1 -1
  115. package/dist/render/shaders/fog.js +9 -2
  116. package/dist/render/shaders/fringe.d.ts +29 -0
  117. package/dist/render/shaders/fringe.js +55 -0
  118. package/dist/render/shaders/generated/arcane.wgsl.d.ts +1 -1
  119. package/dist/render/shaders/generated/arcane.wgsl.js +1 -1
  120. package/dist/render/shaders/generated/bolt.wgsl.d.ts +1 -1
  121. package/dist/render/shaders/generated/bolt.wgsl.js +1 -1
  122. package/dist/render/shaders/generated/caustics.wgsl.d.ts +1 -1
  123. package/dist/render/shaders/generated/caustics.wgsl.js +1 -1
  124. package/dist/render/shaders/generated/depth.wgsl.d.ts +93 -0
  125. package/dist/render/shaders/generated/depth.wgsl.js +93 -0
  126. package/dist/render/shaders/generated/film.wgsl.d.ts +1 -1
  127. package/dist/render/shaders/generated/film.wgsl.js +1 -1
  128. package/dist/render/shaders/generated/fire.wgsl.d.ts +1 -1
  129. package/dist/render/shaders/generated/fire.wgsl.js +1 -1
  130. package/dist/render/shaders/generated/flat.wgsl.d.ts +3254 -2920
  131. package/dist/render/shaders/generated/flat.wgsl.js +3292 -2957
  132. package/dist/render/shaders/generated/line.wgsl.d.ts +1 -1
  133. package/dist/render/shaders/generated/line.wgsl.js +1 -1
  134. package/dist/render/shaders/generated/particle.wgsl.d.ts +4 -4
  135. package/dist/render/shaders/generated/particle.wgsl.js +4 -4
  136. package/dist/render/shaders/generated/rush.wgsl.d.ts +56 -26
  137. package/dist/render/shaders/generated/rush.wgsl.js +56 -26
  138. package/dist/render/shaders/generated/scatter.wgsl.d.ts +1 -1
  139. package/dist/render/shaders/generated/scatter.wgsl.js +1 -1
  140. package/dist/render/shaders/generated/smoke.wgsl.d.ts +1 -1
  141. package/dist/render/shaders/generated/smoke.wgsl.js +1 -1
  142. package/dist/render/shaders/generated/ssrResolve.wgsl.d.ts +29 -0
  143. package/dist/render/shaders/generated/ssrResolve.wgsl.js +29 -0
  144. package/dist/render/shaders/generated/ssrTrace.wgsl.d.ts +22 -2
  145. package/dist/render/shaders/generated/ssrTrace.wgsl.js +22 -2
  146. package/dist/render/shaders/generated/water.wgsl.d.ts +1 -1
  147. package/dist/render/shaders/generated/water.wgsl.js +1 -1
  148. package/dist/render/shaders/gpudriven/fog.wgsl.d.ts +2 -2
  149. package/dist/render/shaders/gpudriven/fog.wgsl.js +11 -3
  150. package/dist/render/shaders/line.d.ts +1 -1
  151. package/dist/render/shaders/outputTransform.d.ts +7 -0
  152. package/dist/render/shaders/outputTransform.js +28 -14
  153. package/dist/render/shaders/particle.d.ts +3 -3
  154. package/dist/render/shaders/rush.js +54 -11
  155. package/dist/render/shaders/scatter.d.ts +1 -1
  156. package/dist/render/shaders/smoke.d.ts +1 -1
  157. package/dist/render/shaders/ssrResolve.d.ts +17 -0
  158. package/dist/render/shaders/ssrResolve.js +51 -0
  159. package/dist/render/shaders/ssrTrace.js +26 -0
  160. package/dist/render/shaders/vertexChannel.d.ts +7 -0
  161. package/dist/render/shaders/vertexChannel.js +26 -0
  162. package/dist/render/shaders/water.d.ts +1 -1
  163. package/dist/render/ssrPass.d.ts +16 -0
  164. package/dist/render/ssrPass.js +106 -1
  165. package/dist/render/staticDraws.d.ts +47 -0
  166. package/dist/render/staticDraws.js +43 -0
  167. package/dist/render/surfaceOverlay.d.ts +132 -0
  168. package/dist/render/surfaceOverlay.js +85 -0
  169. package/dist/render/surfaceTexture.d.ts +57 -7
  170. package/dist/render/surfaceTexture.js +125 -17
  171. package/dist/render/temporalAa.d.ts +14 -0
  172. package/dist/render/temporalAa.js +20 -0
  173. package/dist/render/vertexDefaults.d.ts +17 -0
  174. package/dist/render/vertexDefaults.js +24 -0
  175. package/package.json +6 -6
  176. package/src/index.ts +24 -2
  177. package/src/render/ambientHarmonics.ts +1 -1
  178. package/src/render/areaLights.ts +4 -4
  179. package/src/render/atmosphere.ts +22 -1
  180. package/src/render/backend/api.ts +22 -3
  181. package/src/render/backend/createRenderer.ts +18 -1
  182. package/src/render/backend/select.ts +10 -5
  183. package/src/render/backend/webgl2/boneAnimations.ts +105 -0
  184. package/src/render/backend/webgl2/instanced.ts +20 -1
  185. package/src/render/backend/webgl2/renderer.ts +672 -37
  186. package/src/render/backend/webgl2/skinPaletteTexture.ts +6 -4
  187. package/src/render/backend/webgpu/boneAnimations.ts +97 -0
  188. package/src/render/backend/webgpu/buffers.ts +42 -16
  189. package/src/render/backend/webgpu/compressedUpload.ts +59 -9
  190. package/src/render/backend/webgpu/depthPass.ts +51 -10
  191. package/src/render/backend/webgpu/device.ts +42 -3
  192. package/src/render/backend/webgpu/drawCommand.ts +22 -4
  193. package/src/render/backend/webgpu/flatPass.ts +250 -40
  194. package/src/render/backend/webgpu/instanced.ts +32 -1
  195. package/src/render/backend/webgpu/materialStore.ts +276 -0
  196. package/src/render/backend/webgpu/pipelineCache.ts +7 -0
  197. package/src/render/backend/webgpu/postPass.ts +18 -0
  198. package/src/render/backend/webgpu/reflectionSurfacePass.ts +114 -0
  199. package/src/render/backend/webgpu/renderer.ts +1655 -230
  200. package/src/render/backend/webgpu/skinPaletteRing.ts +13 -7
  201. package/src/render/backend/webgpu/skinPaletteTexture.ts +7 -1
  202. package/src/render/backend/webgpu/staticBundles.ts +363 -0
  203. package/src/render/backend/webgpu/surfaceTexturePass.ts +29 -3
  204. package/src/render/backend/webgpu/uniformRing.ts +10 -1
  205. package/src/render/bloomChain.ts +3 -3
  206. package/src/render/boneAnimation.ts +223 -0
  207. package/src/render/compressedSource.ts +121 -29
  208. package/src/render/displayRange.ts +61 -0
  209. package/src/render/filmicCurve.ts +229 -0
  210. package/src/render/fog.ts +19 -3
  211. package/src/render/frame/resources.ts +6 -0
  212. package/src/render/frameReflections.ts +72 -0
  213. package/src/render/glCompressed.ts +83 -0
  214. package/src/render/instances.ts +27 -0
  215. package/src/render/lightBudget.ts +11 -0
  216. package/src/render/lightmap.ts +1 -1
  217. package/src/render/mesh.ts +6 -0
  218. package/src/render/pass.ts +53 -1
  219. package/src/render/pointLightSelection.ts +4 -4
  220. package/src/render/reflectionSurfaceTargets.ts +105 -0
  221. package/src/render/renderQuality.ts +44 -8
  222. package/src/render/sceneTarget.ts +30 -2
  223. package/src/render/screenSpaceReflection.ts +7 -1
  224. package/src/render/shaders/boneAnimation.ts +63 -0
  225. package/src/render/shaders/depth.ts +45 -10
  226. package/src/render/shaders/filmLook.ts +2 -1
  227. package/src/render/shaders/flat/glassTint.ts +5 -1
  228. package/src/render/shaders/flat/index.ts +49 -5
  229. package/src/render/shaders/flat/main.ts +43 -5
  230. package/src/render/shaders/flat/models.ts +1 -1
  231. package/src/render/shaders/flat/overlay.ts +117 -0
  232. package/src/render/shaders/flat/pointShadow.ts +3 -3
  233. package/src/render/shaders/flat/preamble.ts +38 -24
  234. package/src/render/shaders/flat/reflectionSurface.ts +61 -0
  235. package/src/render/shaders/fog.ts +9 -2
  236. package/src/render/shaders/fringe.ts +59 -0
  237. package/src/render/shaders/generated/arcane.wgsl.ts +1 -1
  238. package/src/render/shaders/generated/bolt.wgsl.ts +1 -1
  239. package/src/render/shaders/generated/caustics.wgsl.ts +1 -1
  240. package/src/render/shaders/generated/depth.wgsl.ts +95 -0
  241. package/src/render/shaders/generated/film.wgsl.ts +1 -1
  242. package/src/render/shaders/generated/fire.wgsl.ts +1 -1
  243. package/src/render/shaders/generated/flat.wgsl.ts +3292 -2957
  244. package/src/render/shaders/generated/line.wgsl.ts +1 -1
  245. package/src/render/shaders/generated/particle.wgsl.ts +4 -4
  246. package/src/render/shaders/generated/rush.wgsl.ts +56 -26
  247. package/src/render/shaders/generated/scatter.wgsl.ts +1 -1
  248. package/src/render/shaders/generated/smoke.wgsl.ts +1 -1
  249. package/src/render/shaders/generated/ssrResolve.wgsl.ts +30 -0
  250. package/src/render/shaders/generated/ssrTrace.wgsl.ts +22 -2
  251. package/src/render/shaders/generated/water.wgsl.ts +1 -1
  252. package/src/render/shaders/gpudriven/fog.wgsl.ts +11 -3
  253. package/src/render/shaders/outputTransform.ts +30 -14
  254. package/src/render/shaders/rush.ts +54 -11
  255. package/src/render/shaders/ssrResolve.ts +52 -0
  256. package/src/render/shaders/ssrTrace.ts +26 -0
  257. package/src/render/shaders/vertexChannel.ts +27 -0
  258. package/src/render/ssrPass.ts +138 -1
  259. package/src/render/staticDraws.ts +95 -0
  260. package/src/render/surfaceOverlay.ts +195 -0
  261. package/src/render/surfaceTexture.ts +161 -27
  262. package/src/render/temporalAa.ts +20 -0
  263. package/src/render/vertexDefaults.ts +28 -0
@@ -29,8 +29,8 @@ import { SkinPaletteTexture } from './skinPaletteTexture.ts';
29
29
  * different rig at every set — the comparison is what tells the two apart.
30
30
  *
31
31
  * **It was capped at 128 until 4.8.6, and the cap declined draws.** A stage of three dozen idling
32
- * extras beside two fighters filled it in one frame and every skinned draw past it was refused, so
33
- * the consumer stood its crowd in the bind pose. The ring now holds as many palettes as a frame
32
+ * extras beside two characters filled it in one frame and every skinned draw past it was refused,
33
+ * so the consumer stood its crowd in the bind pose. The ring now holds as many palettes as a frame
34
34
  * holds draws (`RenderQuality.drawsPerFrame`), since no frame can bind more palettes than it draws;
35
35
  * a slot still allocates its texture only the first time a frame reaches it, so a scene with two
36
36
  * characters holds two. **What it costs** is a texture and a cached bind group a slot, 1.5 KB for a
@@ -46,8 +46,12 @@ import { SkinPaletteTexture } from './skinPaletteTexture.ts';
46
46
  */
47
47
  export class SkinPaletteRing {
48
48
  private readonly slots: SkinPaletteTexture[] = [];
49
- /** What each slot was last written with, for the comparison that lets a pass reuse it. */
49
+ /**
50
+ * What each slot was last written with, for the comparison that lets a pass reuse it: a copy
51
+ * that grows with the largest rig the slot has held, and how much of it the last rig filled.
52
+ */
50
53
  private readonly held: Float32Array[] = [];
54
+ private readonly heldLength: number[] = [];
51
55
  /** The slot each palette array last went into, by identity. Weak, so a caller's array can go. */
52
56
  private readonly lastSlot = new WeakMap<Float32Array, number>();
53
57
  private used = 0;
@@ -119,12 +123,13 @@ export class SkinPaletteRing {
119
123
  slot.update(device, palette);
120
124
  this.uploads += 1;
121
125
  let copy = this.held[at];
122
- if (copy === undefined || copy.length !== palette.length) {
123
- /* Once per slot and joint count, the way the slot's own texture is: never per frame. */
126
+ if (copy === undefined || copy.length < palette.length) {
127
+ /* Once per slot and larger rig, the way the slot's own texture is: never per frame. */
124
128
  copy = new Float32Array(palette.length);
125
129
  this.held[at] = copy;
126
130
  }
127
131
  copy.set(palette);
132
+ this.heldLength[at] = palette.length;
128
133
  this.lastSlot.set(palette, at);
129
134
  return at;
130
135
  }
@@ -138,14 +143,15 @@ export class SkinPaletteRing {
138
143
  for (const slot of this.slots) slot.dispose();
139
144
  this.slots.length = 0;
140
145
  this.held.length = 0;
146
+ this.heldLength.length = 0;
141
147
  this.used = 0;
142
148
  }
143
149
 
144
150
  /** Whether a slot was last written with exactly these numbers. */
145
151
  private holds(slot: number, palette: Float32Array): boolean {
146
152
  const copy = this.held[slot];
147
- if (copy === undefined || copy.length !== palette.length) return false;
148
- for (let i = 0; i < copy.length; i++) if (copy[i] !== palette[i]) return false;
153
+ if (copy === undefined || this.heldLength[slot] !== palette.length) return false;
154
+ for (let i = 0; i < palette.length; i++) if (copy[i] !== palette[i]) return false;
149
155
  return true;
150
156
  }
151
157
  }
@@ -22,6 +22,12 @@ const TEXTURE_USAGE = {
22
22
 
23
23
  export class SkinPaletteTexture {
24
24
  private texture: GPUTexture | null = null;
25
+ /**
26
+ * How many joints the texture has room for, which is the most any rig in this slot has had.
27
+ * **It grows and never shrinks**: the shader reads a joint by index, so a texture wider than the
28
+ * rig is read exactly as one that fits, and a slot that a 10-joint prop and a 296-joint character
29
+ * take in turn keeps one texture rather than making one at every change of rig.
30
+ */
25
31
  private joints = 0;
26
32
  /**
27
33
  * The view, cached rather than created per call.
@@ -38,7 +44,7 @@ export class SkinPaletteTexture {
38
44
  const joints = palette.length / 16;
39
45
  const width = paletteTextureWidth(joints);
40
46
 
41
- if (this.texture === null || this.joints !== joints) {
47
+ if (this.texture === null || joints > this.joints) {
42
48
  this.texture?.destroy();
43
49
  this.cachedView = null;
44
50
  this.texture = device.createTexture({
@@ -0,0 +1,363 @@
1
+ /**
2
+ * A static list on this backend: its draws recorded once, through the renderer's own draw path,
3
+ * into slots the list owns, and replayed as one render bundle per view.
4
+ *
5
+ * **What a recording holds is the frame's draws as the renderer would have recorded them**, taken
6
+ * from a pool of the list's instead of the frame graph's: a pipeline, a bind group, vertex buffers
7
+ * and four dynamic offsets each. Three of the four never change once recorded — the draw's own
8
+ * slot in `entries`, written once, and its material's slot in the store, pinned. The other two are
9
+ * the view's: the pass block and the view block, which differ between the frame, a mirror and a
10
+ * capture of the same frame. So each view a frame draws the list into takes a *copy* — a slot of
11
+ * `passes` and one of `views`, written for that view — and a bundle is encoded per copy from the
12
+ * same commands with that copy's two offsets. A frame that draws a list twice uploads two blocks
13
+ * and executes two bundles, and records nothing.
14
+ *
15
+ * **Its own buffers, and bind groups over them.** A bundle bakes the buffers its groups bind, and
16
+ * the frame's rings are reset every frame, so a list binds rings of its own: the renderer swaps
17
+ * them in while it records the list's draws, and the groups that recording builds go into the
18
+ * list's cache rather than the frame's.
19
+ *
20
+ * **Recorded again when what it baked has moved**: the renderer's groups (a texture forgotten, a
21
+ * frame texture rebound, a ring grown), the pipeline cache's lit set, the store's slots, or the
22
+ * list's own rings. Each is a number compared at replay, so a stale recording is never replayed.
23
+ *
24
+ * What it gives up: culling, since a bundle draws everything recorded into it; and memory, a pass
25
+ * block a view a list, which is a few kilobytes.
26
+ */
27
+ import type { StaticDrawsList, StaticEntry } from '../../staticDraws.ts';
28
+ import type { CommandPool, DrawCommand } from './drawCommand.ts';
29
+ import { createCommandPool, resetPool, takeCommand } from './drawCommand.ts';
30
+ import type { PipelineCache } from './pipelineCache.ts';
31
+ import { UniformRing } from './uniformRing.ts';
32
+
33
+ const USAGE_UNIFORM_DST = 0x0040 | 0x0008;
34
+ /** Views a list starts with room for: the frame's and one capture's. Doubles when a frame asks more. */
35
+ const FIRST_COPIES = 2;
36
+ /** Recordings a list keeps before letting the oldest go: a target and a starting state each. */
37
+ const MOST_RECORDINGS = 4;
38
+
39
+ /** What one recording was made against, and what it recorded. */
40
+ export interface StaticRecording {
41
+ readonly cache: PipelineCache;
42
+ /** The material block's words the list started from: the pass's state, which entries patch. */
43
+ readonly start: Int32Array;
44
+ readonly startHash: number;
45
+ readonly cutout: string;
46
+ readonly surfaceKept: boolean;
47
+ /** Stamps compared at replay; any that moved makes the recording stale. */
48
+ cacheVersion: number;
49
+ groupEpoch: number;
50
+ storeEpoch: number;
51
+ ringEpoch: number;
52
+ /** The frame pass's commands, and the reflection pass's surface halves, with the record's offsets. */
53
+ readonly frame: CommandPool;
54
+ readonly surface: CommandPool;
55
+ /** Material slots pinned for these commands, and the material changes recording them asked for. */
56
+ readonly pins: number[];
57
+ materialAsks: number;
58
+ /** Under the screen-space skin blur a skin entry splits into halves no bundle replays. */
59
+ immediate: boolean;
60
+ /** Per copy, the bundles `executeBundles` takes; null until a copy is first replayed. */
61
+ readonly frameBundles: (readonly GPURenderBundle[] | null)[];
62
+ readonly surfaceBundles: (readonly GPURenderBundle[] | null)[];
63
+ /** The frame it was last replayed in, which picks the one to let go. */
64
+ usedIn: number;
65
+ }
66
+
67
+ export class GpuStaticDraws implements StaticDrawsList {
68
+ disposed = false;
69
+ /** The draws' own slots, one a draw, written by the recording. */
70
+ readonly drawRing: UniformRing;
71
+ /** A pass block and a view block a copy. */
72
+ passes: UniformRing;
73
+ views: UniformRing;
74
+ /** Bumped whenever the three rings are made again, which invalidates every recording. */
75
+ ringEpoch = 0;
76
+ /** The list's own lit groups and blank group, built over its rings by the renderer. */
77
+ groups: Map<unknown, unknown> = new Map();
78
+ blank: GPUBindGroup | null = null;
79
+ /** The group epoch the cache above was built in. */
80
+ groupEpoch = -1;
81
+ /** The store epoch the list's pins were taken in: pins from before a forgetting are let go of. */
82
+ pinEpoch = -1;
83
+ readonly recordings: StaticRecording[] = [];
84
+ private copies = 0;
85
+ private copyFrame = -1;
86
+
87
+ constructor(
88
+ private readonly device: GPUDevice,
89
+ readonly list: StaticDrawsList,
90
+ private readonly passSize: number,
91
+ private readonly viewSize: number,
92
+ drawSize: number,
93
+ ) {
94
+ this.drawRing = new UniformRing(
95
+ device,
96
+ drawSize,
97
+ Math.max(1, list.draws),
98
+ USAGE_UNIFORM_DST,
99
+ 'static.draws',
100
+ );
101
+ this.passes = new UniformRing(
102
+ device,
103
+ passSize,
104
+ FIRST_COPIES,
105
+ USAGE_UNIFORM_DST,
106
+ 'static.passes',
107
+ );
108
+ this.views = new UniformRing(device, viewSize, FIRST_COPIES, USAGE_UNIFORM_DST, 'static.views');
109
+ this.claimCopies();
110
+ }
111
+
112
+ /** The captured entries, which the renderer replays to record them. */
113
+ get entries(): readonly StaticEntry[] {
114
+ return this.list.entries;
115
+ }
116
+
117
+ get draws(): number {
118
+ return this.list.draws;
119
+ }
120
+
121
+ /**
122
+ * The next view's copy in frame `frame`, growing the copies when a frame asks for more than there
123
+ * are — which makes every recording stale, since the groups bound the old rings.
124
+ */
125
+ takeCopy(frame: number): number {
126
+ if (frame !== this.copyFrame) {
127
+ this.copyFrame = frame;
128
+ this.copies = 0;
129
+ }
130
+ const copy = this.copies;
131
+ this.copies += 1;
132
+ if (copy >= this.passes.slots) this.growCopies(this.passes.slots * 2);
133
+ return copy;
134
+ }
135
+
136
+ /** The two offsets copy `copy` binds. */
137
+ passOffset(copy: number): number {
138
+ return copy * this.passes.slotSize;
139
+ }
140
+
141
+ viewOffset(copy: number): number {
142
+ return copy * this.views.slotSize;
143
+ }
144
+
145
+ /** Copy `copy`'s pass block, written whole. Uploaded by `uploadCopy`. */
146
+ writePass(copy: number, block: Int32Array): void {
147
+ this.passes.writeBlock(this.passOffset(copy), block);
148
+ }
149
+
150
+ /** Copy `copy`'s two blocks to the device, and no other copy's: each view writes only its own. */
151
+ uploadCopy(copy: number): void {
152
+ this.passes.uploadSlot(this.passOffset(copy));
153
+ this.views.uploadSlot(this.viewOffset(copy));
154
+ }
155
+
156
+ /** The draws' own slots, after a recording wrote them. */
157
+ uploadDraws(): void {
158
+ this.drawRing.flush();
159
+ }
160
+
161
+ /** A recording that matches, if one does: the same target, starting state and side passes. */
162
+ find(
163
+ cache: PipelineCache,
164
+ start: Int32Array,
165
+ startHash: number,
166
+ cutout: string,
167
+ surfaceKept: boolean,
168
+ ): StaticRecording | null {
169
+ for (const recording of this.recordings) {
170
+ if (
171
+ recording.cache === cache &&
172
+ recording.startHash === startHash &&
173
+ recording.cutout === cutout &&
174
+ recording.surfaceKept === surfaceKept &&
175
+ sameWords(recording.start, start)
176
+ ) {
177
+ return recording;
178
+ }
179
+ }
180
+ return null;
181
+ }
182
+
183
+ /** A new, empty recording, letting the least recently used go past the most kept. */
184
+ open(
185
+ cache: PipelineCache,
186
+ start: Int32Array,
187
+ startHash: number,
188
+ cutout: string,
189
+ surfaceKept: boolean,
190
+ unpin: (offset: number) => void,
191
+ ): StaticRecording {
192
+ if (this.recordings.length >= MOST_RECORDINGS) {
193
+ let oldest = 0;
194
+ for (let i = 1; i < this.recordings.length; i++) {
195
+ if (
196
+ (this.recordings[i] as StaticRecording).usedIn <
197
+ (this.recordings[oldest] as StaticRecording).usedIn
198
+ )
199
+ oldest = i;
200
+ }
201
+ const [gone] = this.recordings.splice(oldest, 1);
202
+ if (gone !== undefined) release(gone, unpin);
203
+ }
204
+ const recording: StaticRecording = {
205
+ cache,
206
+ start: new Int32Array(start),
207
+ startHash,
208
+ cutout,
209
+ surfaceKept,
210
+ cacheVersion: -1,
211
+ groupEpoch: -1,
212
+ storeEpoch: -1,
213
+ ringEpoch: -1,
214
+ frame: createCommandPool(this.list.draws),
215
+ surface: createCommandPool(0),
216
+ pins: [],
217
+ materialAsks: 0,
218
+ immediate: false,
219
+ frameBundles: [],
220
+ surfaceBundles: [],
221
+ usedIn: -1,
222
+ };
223
+ this.recordings.push(recording);
224
+ return recording;
225
+ }
226
+
227
+ /** Empty a recording to record it again: its pins let go, its commands and bundles dropped. */
228
+ clear(recording: StaticRecording, unpin: (offset: number) => void): void {
229
+ release(recording, unpin);
230
+ resetPool(recording.frame);
231
+ resetPool(recording.surface);
232
+ recording.frameBundles.length = 0;
233
+ recording.surfaceBundles.length = 0;
234
+ recording.immediate = false;
235
+ recording.materialAsks = 0;
236
+ }
237
+
238
+ /** Every recording, let go; what disposal and a forgetting store both do. */
239
+ forget(unpin: ((offset: number) => void) | null): void {
240
+ for (const recording of this.recordings) release(recording, unpin);
241
+ this.recordings.length = 0;
242
+ }
243
+
244
+ dispose(unpin: ((offset: number) => void) | null): void {
245
+ this.disposed = true;
246
+ this.forget(unpin);
247
+ this.drawRing.dispose();
248
+ this.passes.dispose();
249
+ this.views.dispose();
250
+ }
251
+
252
+ private growCopies(copies: number): void {
253
+ this.passes.dispose();
254
+ this.views.dispose();
255
+ this.passes = new UniformRing(
256
+ this.device,
257
+ this.passSize,
258
+ copies,
259
+ USAGE_UNIFORM_DST,
260
+ 'static.passes',
261
+ );
262
+ this.views = new UniformRing(
263
+ this.device,
264
+ this.viewSize,
265
+ copies,
266
+ USAGE_UNIFORM_DST,
267
+ 'static.views',
268
+ );
269
+ this.claimCopies();
270
+ this.ringEpoch += 1;
271
+ this.groups = new Map();
272
+ this.blank = null;
273
+ }
274
+
275
+ /** Every copy's slot, taken once: a copy is a fixed offset, never handed out again. */
276
+ private claimCopies(): void {
277
+ for (let copy = 0; copy < this.passes.slots; copy++) {
278
+ this.passes.allocate();
279
+ this.views.allocate();
280
+ }
281
+ }
282
+ }
283
+
284
+ /** A command the recording takes in place of the frame graph's. */
285
+ export function takeRecorded(pool: CommandPool): DrawCommand {
286
+ const command = pool.commands[takeCommand(pool)] as DrawCommand;
287
+ command.offsetCount = 0;
288
+ command.vertexCount = 0;
289
+ command.indexBuffer = null;
290
+ command.indexed = false;
291
+ command.instances = 1;
292
+ command.indirect = null;
293
+ return command;
294
+ }
295
+
296
+ /** What a render bundle must agree with: the pass it is executed in. */
297
+ export interface BundleTarget {
298
+ readonly colorFormats: (GPUTextureFormat | null)[];
299
+ readonly depthStencilFormat: GPUTextureFormat;
300
+ readonly sampleCount: number;
301
+ readonly depthReadOnly: boolean;
302
+ }
303
+
304
+ /**
305
+ * One copy's bundle of a pool's commands: each lit command as recorded but for the pass and view
306
+ * offsets, which are the copy's. Allocates, and runs once a copy a recording, never per frame.
307
+ */
308
+ export function encodeBundle(
309
+ device: GPUDevice,
310
+ target: BundleTarget,
311
+ pool: CommandPool,
312
+ passOffset: number,
313
+ viewOffset: number,
314
+ label: string,
315
+ ): readonly GPURenderBundle[] {
316
+ const encoder = device.createRenderBundleEncoder({
317
+ label,
318
+ colorFormats: target.colorFormats,
319
+ depthStencilFormat: target.depthStencilFormat,
320
+ sampleCount: target.sampleCount,
321
+ depthReadOnly: target.depthReadOnly,
322
+ stencilReadOnly: target.depthReadOnly,
323
+ });
324
+ for (let i = 0; i < pool.taken; i++) {
325
+ const command = pool.commands[i] as DrawCommand;
326
+ if (command.pipeline === null || command.bindGroup === null) continue;
327
+ if (command.offsetCount !== 4) {
328
+ throw new Error(
329
+ `static draws: a recorded command bound ${command.offsetCount} offsets, not a lit draw's four`,
330
+ );
331
+ }
332
+ encoder.setPipeline(command.pipeline);
333
+ encoder.setBindGroup(0, command.bindGroup, [
334
+ command.offsetA,
335
+ passOffset,
336
+ command.offsetC,
337
+ viewOffset,
338
+ ]);
339
+ for (let slot = 0; slot < command.vertexCount; slot++) {
340
+ const buffer = command.vertexBuffers[slot];
341
+ if (buffer != null) encoder.setVertexBuffer(slot, buffer);
342
+ }
343
+ if (command.indexed && command.indexBuffer !== null) {
344
+ encoder.setIndexBuffer(command.indexBuffer, 'uint32');
345
+ if (command.indirect !== null) encoder.drawIndexedIndirect(command.indirect, 0);
346
+ else encoder.drawIndexed(command.count, command.instances);
347
+ } else {
348
+ encoder.draw(command.count, command.instances);
349
+ }
350
+ }
351
+ return [encoder.finish({ label })];
352
+ }
353
+
354
+ function release(recording: StaticRecording, unpin: ((offset: number) => void) | null): void {
355
+ if (unpin !== null) for (const offset of recording.pins) unpin(offset);
356
+ recording.pins.length = 0;
357
+ }
358
+
359
+ function sameWords(a: Int32Array, b: Int32Array): boolean {
360
+ if (a.length !== b.length) return false;
361
+ for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
362
+ return true;
363
+ }
@@ -6,7 +6,12 @@ import type { LightmapTexels } from '../../lightmap.ts';
6
6
  import { isSceneCaptureTexels } from '../../sceneCapture.ts';
7
7
  import type { SceneCaptureTexels } from '../../sceneCapture.ts';
8
8
  import { layerSize, refuseArrayUpdate, sourceSize } from '../../textureSource.ts';
9
- import { compressedLayers, planBlocks, refuseBlockUpdate } from '../../compressedSource.ts';
9
+ import {
10
+ compressedLayers,
11
+ isCompressedSource,
12
+ planBlocks,
13
+ refuseBlockUpdate,
14
+ } from '../../compressedSource.ts';
10
15
  import type {
11
16
  BlockFormat,
12
17
  CompressedTextureFormat,
@@ -192,7 +197,9 @@ export class GpuSurfaceTexture {
192
197
  /** Its view, which the flat group binds beside this image when it is a material's albedo. */
193
198
  effectsView: GPUTextureView | null = null;
194
199
  /** The block format a compressed texture holds, or null for one uploaded from images. */
195
- private readonly blockFormat: BlockFormat | null;
200
+ private blockFormat: BlockFormat | null;
201
+ /** Whether the image is colour, decoded from sRGB as it is sampled: what blocks swapped in keep. */
202
+ private readonly srgb: boolean;
196
203
 
197
204
  constructor(
198
205
  private readonly device: GPUDevice,
@@ -210,6 +217,7 @@ export class GpuSurfaceTexture {
210
217
  ) as readonly SurfaceSource[];
211
218
  const blocks = compressedLayers(listed);
212
219
  const srgb = (options.colorSpace ?? 'linear') === 'srgb';
220
+ this.srgb = srgb;
213
221
  /* `SRGB8_ALPHA8`'s equivalent. Decoded in the sampler, before filtering, which is the only
214
222
  place it is correct — `surfaceTexture.ts` makes the argument in full. A capture takes the
215
223
  world's own format, which its pass resolves into. */
@@ -358,11 +366,29 @@ export class GpuSurfaceTexture {
358
366
  * hit that whenever the preview's decode won the race. The view is new then, and the texture it
359
367
  * replaced is handed back rather than destroyed: a draw recorded earlier in this frame may still
360
368
  * read it, so the renderer destroys it once nothing can, and drops every binding of the old view.
369
+ *
370
+ * **Blocks replace an image the same way**, at any size: a texture of their format holds them,
371
+ * read in the colour space this one was made for, and the image's is handed back. Refused, as a
372
+ * texture made from them would be, where `compressed` — the formats the device samples — does not
373
+ * take them. The way back is refused: an image cannot replace blocks.
361
374
  */
362
- update(source: TexImageSource): GPUTexture | null {
375
+ update(
376
+ source: TexImageSource | CompressedTextureSource,
377
+ compressed: readonly CompressedTextureFormat[] = [],
378
+ ): GPUTexture | null {
363
379
  const replaced = this.texture;
364
380
  if (replaced === null) return null;
365
381
  refuseArrayUpdate(this.layers);
382
+ if (isCompressedSource(source)) {
383
+ const plan = planBlocks([source], this.srgb, compressed);
384
+ this.blockFormat = source.format;
385
+ this.width = source.width;
386
+ this.height = source.height;
387
+ this.levels = this.mipmapped ? plan.levels : 1;
388
+ this.texture = createCompressedTexture(this.device, plan.name, [source], this.levels);
389
+ this.current = this.arrayView(this.texture);
390
+ return replaced;
391
+ }
366
392
  refuseBlockUpdate(this.blockFormat);
367
393
  const { width, height } = sourceSize(source);
368
394
  if (width === this.width && height === this.height) {
@@ -23,7 +23,7 @@ export const DYNAMIC_ALIGNMENT = 256;
23
23
  * is five thousand material changes in one frame. The device buffer matches the staging, so a ring
24
24
  * at this ceiling holds 128 MiB in total and only ever reaches it by being asked to.
25
25
  */
26
- const MAX_RING_BYTES = 64 * 1024 * 1024;
26
+ export const MAX_RING_BYTES = 64 * 1024 * 1024;
27
27
 
28
28
  /**
29
29
  * Per-draw uniforms, in slots, written once and bound by offset.
@@ -268,6 +268,15 @@ export class UniformRing {
268
268
  this.clean = end;
269
269
  }
270
270
 
271
+ /**
272
+ * Upload one slot now, and nothing else: for a ring whose slots are all claimed once and written
273
+ * one at a time — a static list's copies — where `flush` would send every slot above the lowest
274
+ * one written. Leaves the watermark as it was, so a later `flush` still owes what it owed.
275
+ */
276
+ uploadSlot(slot: number): void {
277
+ this.device.queue.writeBuffer(this.buffer, slot, this.staging, slot, this.slotSize);
278
+ }
279
+
271
280
  dispose(): void {
272
281
  this.buffer.destroy();
273
282
  }
@@ -86,7 +86,7 @@ export function bloomProfileWarning(quality: {
86
86
  * already takes. Absent, or a field absent, is the response every frame had before 4.8.6.
87
87
  *
88
88
  * **Why it exists: one band over the colour less the threshold washes a stage built for another
89
- * engine.** Unreal's standard bloom keeps `saturate((L − threshold) / 2)` of a colour rather than
89
+ * engine.** A ramped bloom keeps `saturate((L − threshold) / 2)` of a colour rather than
90
90
  * the colour less the threshold, and spreads it over six bands, each in a tint of its own. A
91
91
  * stage authored against that — a threshold of 0.1 and tints summing below one — came out white
92
92
  * here, because subtracting so low a threshold keeps nearly every light in the frame at full
@@ -96,8 +96,8 @@ export interface BloomResponse {
96
96
  /**
97
97
  * Scene units over which a colour comes in, from none of it at the threshold to all of it this
98
98
  * far past. **0, the default, subtracts instead**: a colour keeps what it has past the threshold,
99
- * the response since bloom shipped. Unreal's is 2 after exposure, so a frame exposed by `e` that
100
- * wants it passes `2 / e`, as it passes the threshold over the exposure.
99
+ * the response since bloom shipped. A ramped bloom's is 2 after exposure, so a frame exposed by
100
+ * `e` that wants it passes `2 / e`, as it passes the threshold over the exposure.
101
101
  *
102
102
  * Brightness is still the largest channel rather than luminance, for the reason
103
103
  * `shaders/bloom.ts` gives; on a neutral colour the two are equal.