@driftengine/core 3.63.0 → 4.0.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 (428) hide show
  1. package/CHANGELOG.json +111 -0
  2. package/NOTICE +20 -0
  3. package/README.md +1 -1
  4. package/dist/core/loop.js +18 -2
  5. package/dist/core/storage.js +5 -0
  6. package/dist/geometry/meshBuilder.d.ts +1 -1
  7. package/dist/geometry/meshBuilder.js +6 -2
  8. package/dist/index.d.ts +69 -4
  9. package/dist/index.js +112 -2
  10. package/dist/input/input.js +6 -1
  11. package/dist/math/exact.d.ts +13 -3
  12. package/dist/math/exact.js +68 -3
  13. package/dist/math/frustum.d.ts +17 -2
  14. package/dist/math/frustum.js +24 -0
  15. package/dist/render/backend/api.d.ts +34 -1
  16. package/dist/render/backend/api.js +22 -1
  17. package/dist/render/backend/budget.d.ts +7 -6
  18. package/dist/render/backend/budget.js +7 -6
  19. package/dist/render/backend/createRenderer.d.ts +57 -0
  20. package/dist/render/backend/createRenderer.js +51 -2
  21. package/dist/render/backend/materialChanges.d.ts +41 -0
  22. package/dist/render/backend/materialChanges.js +38 -0
  23. package/dist/render/backend/select.js +8 -0
  24. package/dist/render/backend/webgl2/renderer.d.ts +80 -1
  25. package/dist/render/backend/webgl2/renderer.js +208 -10
  26. package/dist/render/backend/webgpu/buffers.d.ts +20 -1
  27. package/dist/render/backend/webgpu/buffers.js +116 -58
  28. package/dist/render/backend/webgpu/fieldCompose.d.ts +156 -0
  29. package/dist/render/backend/webgpu/fieldCompose.js +396 -0
  30. package/dist/render/backend/webgpu/giFieldPass.d.ts +66 -0
  31. package/dist/render/backend/webgpu/giFieldPass.js +137 -0
  32. package/dist/render/backend/webgpu/gpuDrivenPass.d.ts +728 -0
  33. package/dist/render/backend/webgpu/gpuDrivenPass.js +2473 -0
  34. package/dist/render/backend/webgpu/linePass.d.ts +4 -14
  35. package/dist/render/backend/webgpu/linePass.js +4 -33
  36. package/dist/render/backend/webgpu/probeBake.d.ts +148 -0
  37. package/dist/render/backend/webgpu/probeBake.js +493 -0
  38. package/dist/render/backend/webgpu/renderer.d.ts +379 -24
  39. package/dist/render/backend/webgpu/renderer.js +1369 -76
  40. package/dist/render/backend/webgpu/scatterPass.js +15 -1
  41. package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +19 -9
  42. package/dist/render/backend/webgpu/surfaceTexturePass.js +58 -32
  43. package/dist/render/causticsRenderer.d.ts +3 -1
  44. package/dist/render/causticsRenderer.js +4 -1
  45. package/dist/render/depthConvention.d.ts +12 -0
  46. package/dist/render/depthConvention.js +14 -0
  47. package/dist/render/deviceFeatures.d.ts +32 -0
  48. package/dist/render/deviceFeatures.js +41 -0
  49. package/dist/render/fog.d.ts +79 -0
  50. package/dist/render/fog.js +138 -0
  51. package/dist/render/frame/alias.d.ts +30 -0
  52. package/dist/render/frame/alias.js +61 -0
  53. package/dist/render/frame/deps.d.ts +37 -0
  54. package/dist/render/frame/deps.js +122 -0
  55. package/dist/render/frame/flushGraph.d.ts +43 -0
  56. package/dist/render/frame/flushGraph.js +84 -0
  57. package/dist/render/frame/graphSchedule.d.ts +49 -0
  58. package/dist/render/frame/graphSchedule.js +120 -0
  59. package/dist/render/frame/index.d.ts +15 -0
  60. package/dist/render/frame/index.js +8 -0
  61. package/dist/render/frame/lifetime.d.ts +24 -0
  62. package/dist/render/frame/lifetime.js +36 -0
  63. package/dist/render/frame/passTimings.d.ts +42 -0
  64. package/dist/render/frame/passTimings.js +38 -0
  65. package/dist/render/frame/validate.d.ts +15 -0
  66. package/dist/render/frame/validate.js +22 -0
  67. package/dist/render/frame/virtual.d.ts +44 -0
  68. package/dist/render/frame/virtual.js +61 -0
  69. package/dist/render/gi/chain.d.ts +82 -0
  70. package/dist/render/gi/chain.js +135 -0
  71. package/dist/render/gi/denoise.d.ts +114 -0
  72. package/dist/render/gi/denoise.js +169 -0
  73. package/dist/render/gi/fieldScene.d.ts +125 -0
  74. package/dist/render/gi/fieldScene.js +219 -0
  75. package/dist/render/gi/globalField.d.ts +157 -0
  76. package/dist/render/gi/globalField.js +373 -0
  77. package/dist/render/gi/probeGridFit.d.ts +24 -0
  78. package/dist/render/gi/probeGridFit.js +71 -0
  79. package/dist/render/gi/probeTrace.d.ts +82 -0
  80. package/dist/render/gi/probeTrace.js +128 -0
  81. package/dist/render/gi/probeVolume.d.ts +136 -0
  82. package/dist/render/gi/probeVolume.js +191 -0
  83. package/dist/render/gi/reflection.d.ts +73 -0
  84. package/dist/render/gi/reflection.js +109 -0
  85. package/dist/render/gi/traceField.d.ts +91 -0
  86. package/dist/render/gi/traceField.js +129 -0
  87. package/dist/render/gi/traceScreen.d.ts +68 -0
  88. package/dist/render/gi/traceScreen.js +82 -0
  89. package/dist/render/gpuScene.d.ts +38 -0
  90. package/dist/render/gpuScene.js +41 -0
  91. package/dist/render/gpudriven/alphaTest.d.ts +28 -0
  92. package/dist/render/gpudriven/alphaTest.js +32 -0
  93. package/dist/render/gpudriven/clusterUpload.d.ts +59 -0
  94. package/dist/render/gpudriven/clusterUpload.js +75 -0
  95. package/dist/render/gpudriven/clusterWorld.d.ts +57 -0
  96. package/dist/render/gpudriven/clusterWorld.js +155 -0
  97. package/dist/render/gpudriven/compact.d.ts +51 -0
  98. package/dist/render/gpudriven/compact.js +68 -0
  99. package/dist/render/gpudriven/cullClusters.d.ts +129 -0
  100. package/dist/render/gpudriven/cullClusters.js +237 -0
  101. package/dist/render/gpudriven/decodeTables.d.ts +64 -0
  102. package/dist/render/gpudriven/decodeTables.js +219 -0
  103. package/dist/render/gpudriven/frustum.d.ts +25 -0
  104. package/dist/render/gpudriven/frustum.js +59 -0
  105. package/dist/render/gpudriven/geometryHarness.d.ts +15 -0
  106. package/dist/render/gpudriven/geometryHarness.js +31 -0
  107. package/dist/render/gpudriven/hzb.d.ts +60 -0
  108. package/dist/render/gpudriven/hzb.js +97 -0
  109. package/dist/render/gpudriven/ibl.d.ts +85 -0
  110. package/dist/render/gpudriven/ibl.js +129 -0
  111. package/dist/render/gpudriven/indirect.d.ts +67 -0
  112. package/dist/render/gpudriven/indirect.js +77 -0
  113. package/dist/render/gpudriven/instances.d.ts +43 -0
  114. package/dist/render/gpudriven/instances.js +172 -0
  115. package/dist/render/gpudriven/lit.d.ts +165 -0
  116. package/dist/render/gpudriven/lit.js +165 -0
  117. package/dist/render/gpudriven/lodCut.d.ts +28 -0
  118. package/dist/render/gpudriven/lodCut.js +38 -0
  119. package/dist/render/gpudriven/materialBin.d.ts +60 -0
  120. package/dist/render/gpudriven/materialBin.js +108 -0
  121. package/dist/render/gpudriven/materialTable.d.ts +88 -0
  122. package/dist/render/gpudriven/materialTable.js +115 -0
  123. package/dist/render/gpudriven/pipeline.d.ts +135 -0
  124. package/dist/render/gpudriven/pipeline.js +237 -0
  125. package/dist/render/gpudriven/rangeAlloc.d.ts +50 -0
  126. package/dist/render/gpudriven/rangeAlloc.js +112 -0
  127. package/dist/render/gpudriven/sceneUpload.d.ts +96 -0
  128. package/dist/render/gpudriven/sceneUpload.js +115 -0
  129. package/dist/render/gpudriven/shadeBins.d.ts +92 -0
  130. package/dist/render/gpudriven/shadeBins.js +182 -0
  131. package/dist/render/gpudriven/shadow.d.ts +136 -0
  132. package/dist/render/gpudriven/shadow.js +224 -0
  133. package/dist/render/gpudriven/shadowCamera.d.ts +87 -0
  134. package/dist/render/gpudriven/shadowCamera.js +345 -0
  135. package/dist/render/gpudriven/streamScene.d.ts +194 -0
  136. package/dist/render/gpudriven/streamScene.js +459 -0
  137. package/dist/render/gpudriven/surfaceFrame.d.ts +25 -0
  138. package/dist/render/gpudriven/surfaceFrame.js +51 -0
  139. package/dist/render/gpudriven/twoPhase.d.ts +122 -0
  140. package/dist/render/gpudriven/twoPhase.js +160 -0
  141. package/dist/render/gpudriven/visbuffer.d.ts +25 -0
  142. package/dist/render/gpudriven/visbuffer.js +37 -0
  143. package/dist/render/halfFloat.d.ts +10 -0
  144. package/dist/render/halfFloat.js +23 -4
  145. package/dist/render/inference/attentionTiled.d.ts +27 -0
  146. package/dist/render/inference/attentionTiled.js +117 -0
  147. package/dist/render/inference/denseKernels.d.ts +29 -0
  148. package/dist/render/inference/denseKernels.js +312 -0
  149. package/dist/render/inference/device.d.ts +21 -0
  150. package/dist/render/inference/device.js +32 -0
  151. package/dist/render/inference/deviceGraph.d.ts +34 -0
  152. package/dist/render/inference/deviceGraph.js +17 -0
  153. package/dist/render/inference/fuse.d.ts +15 -0
  154. package/dist/render/inference/fuse.js +37 -0
  155. package/dist/render/inference/kernelKit.d.ts +52 -0
  156. package/dist/render/inference/kernelKit.js +70 -0
  157. package/dist/render/inference/kernels.d.ts +3 -0
  158. package/dist/render/inference/kernels.js +11 -0
  159. package/dist/render/inference/plan.d.ts +13 -0
  160. package/dist/render/inference/plan.js +18 -0
  161. package/dist/render/inference/reuse.d.ts +39 -0
  162. package/dist/render/inference/reuse.js +65 -0
  163. package/dist/render/inference/runner.d.ts +31 -0
  164. package/dist/render/inference/runner.js +175 -0
  165. package/dist/render/inference/schedule.d.ts +39 -0
  166. package/dist/render/inference/schedule.js +59 -0
  167. package/dist/render/inference/shapeKernels.d.ts +10 -0
  168. package/dist/render/inference/shapeKernels.js +80 -0
  169. package/dist/render/inference/spatialKernels.d.ts +21 -0
  170. package/dist/render/inference/spatialKernels.js +179 -0
  171. package/dist/render/inference/splatRaster.wgsl.d.ts +75 -0
  172. package/dist/render/inference/splatRaster.wgsl.js +337 -0
  173. package/dist/render/inference/splatTiles.d.ts +79 -0
  174. package/dist/render/inference/splatTiles.js +161 -0
  175. package/dist/render/lineBatch.d.ts +2 -5
  176. package/dist/render/lineBatch.js +4 -24
  177. package/dist/render/mesh.d.ts +4 -0
  178. package/dist/render/mesh.js +45 -1
  179. package/dist/render/pass.d.ts +145 -8
  180. package/dist/render/prefilterEnvMap.d.ts +22 -0
  181. package/dist/render/prefilterEnvMap.js +31 -0
  182. package/dist/render/readback.d.ts +30 -0
  183. package/dist/render/readback.js +47 -0
  184. package/dist/render/recon/clamp.d.ts +36 -0
  185. package/dist/render/recon/clamp.js +92 -0
  186. package/dist/render/recon/disocclusion.d.ts +60 -0
  187. package/dist/render/recon/disocclusion.js +109 -0
  188. package/dist/render/recon/frameGen.d.ts +81 -0
  189. package/dist/render/recon/frameGen.js +150 -0
  190. package/dist/render/recon/frameSizes.d.ts +34 -0
  191. package/dist/render/recon/frameSizes.js +58 -0
  192. package/dist/render/recon/jitter.d.ts +60 -0
  193. package/dist/render/recon/jitter.js +118 -0
  194. package/dist/render/recon/motionVectors.d.ts +55 -0
  195. package/dist/render/recon/motionVectors.js +107 -0
  196. package/dist/render/recon/reproject.d.ts +41 -0
  197. package/dist/render/recon/reproject.js +131 -0
  198. package/dist/render/recon/resolve.d.ts +90 -0
  199. package/dist/render/recon/resolve.js +313 -0
  200. package/dist/render/recon/tier.d.ts +24 -0
  201. package/dist/render/recon/tier.js +8 -0
  202. package/dist/render/reflectionProbe.d.ts +26 -0
  203. package/dist/render/reflectionProbe.js +33 -0
  204. package/dist/render/renderQuality.d.ts +69 -0
  205. package/dist/render/renderQuality.js +22 -0
  206. package/dist/render/screenSpaceReflection.d.ts +23 -0
  207. package/dist/render/screenSpaceReflection.js +22 -3
  208. package/dist/render/sdfFont.js +2 -0
  209. package/dist/render/segmentQuads.d.ts +13 -0
  210. package/dist/render/segmentQuads.js +32 -0
  211. package/dist/render/shaders/flat/lobes.d.ts +27 -0
  212. package/dist/render/shaders/flat/lobes.js +33 -3
  213. package/dist/render/shaders/flat/preamble.d.ts +7 -0
  214. package/dist/render/shaders/flat/preamble.js +24 -1
  215. package/dist/render/shaders/flat/probeGrid.d.ts +1 -1
  216. package/dist/render/shaders/flat/probeGrid.js +71 -2
  217. package/dist/render/shaders/generated/flat.wgsl.d.ts +48 -8
  218. package/dist/render/shaders/generated/flat.wgsl.js +64 -24
  219. package/dist/render/shaders/gi/composeField.wgsl.d.ts +56 -0
  220. package/dist/render/shaders/gi/composeField.wgsl.js +231 -0
  221. package/dist/render/shaders/gi/marchField.wgsl.d.ts +30 -0
  222. package/dist/render/shaders/gi/marchField.wgsl.js +141 -0
  223. package/dist/render/shaders/gi/probeBake.wgsl.d.ts +113 -0
  224. package/dist/render/shaders/gi/probeBake.wgsl.js +635 -0
  225. package/dist/render/shaders/gi/probeTrace.wgsl.d.ts +72 -0
  226. package/dist/render/shaders/gi/probeTrace.wgsl.js +261 -0
  227. package/dist/render/shaders/gi/probeVolume.wgsl.d.ts +49 -0
  228. package/dist/render/shaders/gi/probeVolume.wgsl.js +242 -0
  229. package/dist/render/shaders/gi/sampleField.wgsl.d.ts +32 -0
  230. package/dist/render/shaders/gi/sampleField.wgsl.js +134 -0
  231. package/dist/render/shaders/gpudriven/blendRaster.wgsl.d.ts +26 -0
  232. package/dist/render/shaders/gpudriven/blendRaster.wgsl.js +339 -0
  233. package/dist/render/shaders/gpudriven/compact.wgsl.d.ts +29 -0
  234. package/dist/render/shaders/gpudriven/compact.wgsl.js +58 -0
  235. package/dist/render/shaders/gpudriven/cull.wgsl.d.ts +90 -0
  236. package/dist/render/shaders/gpudriven/cull.wgsl.js +423 -0
  237. package/dist/render/shaders/gpudriven/decode.wgsl.d.ts +72 -0
  238. package/dist/render/shaders/gpudriven/decode.wgsl.js +349 -0
  239. package/dist/render/shaders/gpudriven/environment.wgsl.d.ts +44 -0
  240. package/dist/render/shaders/gpudriven/environment.wgsl.js +137 -0
  241. package/dist/render/shaders/gpudriven/fog.wgsl.d.ts +28 -0
  242. package/dist/render/shaders/gpudriven/fog.wgsl.js +107 -0
  243. package/dist/render/shaders/gpudriven/frameBlock.wgsl.d.ts +13 -0
  244. package/dist/render/shaders/gpudriven/frameBlock.wgsl.js +62 -0
  245. package/dist/render/shaders/gpudriven/materialBin.wgsl.d.ts +51 -0
  246. package/dist/render/shaders/gpudriven/materialBin.wgsl.js +112 -0
  247. package/dist/render/shaders/gpudriven/materialTable.wgsl.d.ts +25 -0
  248. package/dist/render/shaders/gpudriven/materialTable.wgsl.js +43 -0
  249. package/dist/render/shaders/gpudriven/meshTransform.wgsl.d.ts +18 -0
  250. package/dist/render/shaders/gpudriven/meshTransform.wgsl.js +37 -0
  251. package/dist/render/shaders/gpudriven/present.wgsl.d.ts +43 -0
  252. package/dist/render/shaders/gpudriven/present.wgsl.js +127 -0
  253. package/dist/render/shaders/gpudriven/shade.wgsl.d.ts +127 -0
  254. package/dist/render/shaders/gpudriven/shade.wgsl.js +874 -0
  255. package/dist/render/shaders/gpudriven/shadow.wgsl.d.ts +44 -0
  256. package/dist/render/shaders/gpudriven/shadow.wgsl.js +308 -0
  257. package/dist/render/shaders/gpudriven/visbufferRaster.wgsl.d.ts +28 -0
  258. package/dist/render/shaders/gpudriven/visbufferRaster.wgsl.js +245 -0
  259. package/dist/render/shaders/network.wgsl.d.ts +101 -0
  260. package/dist/render/shaders/network.wgsl.js +206 -0
  261. package/dist/render/shaders/recon/motion.wgsl.d.ts +44 -0
  262. package/dist/render/shaders/recon/motion.wgsl.js +96 -0
  263. package/dist/render/shaders/recon/resolve.wgsl.d.ts +51 -0
  264. package/dist/render/shaders/recon/resolve.wgsl.js +733 -0
  265. package/dist/render/temporalAa.js +8 -33
  266. package/dist/render/textLayout.js +12 -6
  267. package/dist/render/textRenderer.d.ts +3 -1
  268. package/dist/render/textRenderer.js +4 -1
  269. package/dist/render/translucentQueue.d.ts +6 -0
  270. package/dist/render/translucentQueue.js +11 -0
  271. package/dist/render/windStreakRenderer.d.ts +2 -1
  272. package/dist/render/windStreakRenderer.js +3 -1
  273. package/dist/world/cell.d.ts +63 -0
  274. package/dist/world/cell.js +181 -0
  275. package/dist/world/cellStream.d.ts +103 -0
  276. package/dist/world/cellStream.js +198 -0
  277. package/dist/world/freeze.d.ts +81 -0
  278. package/dist/world/freeze.js +114 -0
  279. package/dist/world/rebase.d.ts +29 -0
  280. package/dist/world/rebase.js +41 -0
  281. package/package.json +8 -3
  282. package/scripts/frames.mjs +92 -0
  283. package/scripts/gpuCompute.mjs +323 -0
  284. package/scripts/png.d.mts +16 -0
  285. package/scripts/png.mjs +25 -0
  286. package/src/core/loop.ts +19 -2
  287. package/src/core/storage.ts +5 -0
  288. package/src/geometry/meshBuilder.ts +5 -1
  289. package/src/index.ts +246 -4
  290. package/src/input/input.ts +6 -1
  291. package/src/math/exact.ts +69 -3
  292. package/src/math/frustum.ts +52 -3
  293. package/src/render/backend/api.ts +42 -1
  294. package/src/render/backend/budget.ts +7 -6
  295. package/src/render/backend/createRenderer.ts +92 -3
  296. package/src/render/backend/materialChanges.ts +54 -0
  297. package/src/render/backend/select.ts +8 -0
  298. package/src/render/backend/webgl2/renderer.ts +211 -9
  299. package/src/render/backend/webgpu/buffers.ts +182 -69
  300. package/src/render/backend/webgpu/fieldCompose.ts +482 -0
  301. package/src/render/backend/webgpu/giFieldPass.ts +200 -0
  302. package/src/render/backend/webgpu/gpuDrivenPass.ts +2993 -0
  303. package/src/render/backend/webgpu/linePass.ts +4 -39
  304. package/src/render/backend/webgpu/probeBake.ts +588 -0
  305. package/src/render/backend/webgpu/renderer.ts +1481 -76
  306. package/src/render/backend/webgpu/scatterPass.ts +15 -1
  307. package/src/render/backend/webgpu/surfaceTexturePass.ts +63 -36
  308. package/src/render/causticsRenderer.ts +5 -2
  309. package/src/render/depthConvention.ts +15 -0
  310. package/src/render/deviceFeatures.ts +77 -0
  311. package/src/render/fog.ts +185 -0
  312. package/src/render/frame/alias.ts +97 -0
  313. package/src/render/frame/deps.ts +155 -0
  314. package/src/render/frame/flushGraph.ts +135 -0
  315. package/src/render/frame/graphSchedule.ts +170 -0
  316. package/src/render/frame/index.ts +52 -0
  317. package/src/render/frame/lifetime.ts +61 -0
  318. package/src/render/frame/passTimings.ts +69 -0
  319. package/src/render/frame/validate.ts +40 -0
  320. package/src/render/frame/virtual.ts +91 -0
  321. package/src/render/gi/chain.ts +225 -0
  322. package/src/render/gi/denoise.ts +276 -0
  323. package/src/render/gi/fieldScene.ts +264 -0
  324. package/src/render/gi/globalField.ts +543 -0
  325. package/src/render/gi/probeGridFit.ts +89 -0
  326. package/src/render/gi/probeTrace.ts +143 -0
  327. package/src/render/gi/probeVolume.ts +288 -0
  328. package/src/render/gi/reflection.ts +163 -0
  329. package/src/render/gi/traceField.ts +223 -0
  330. package/src/render/gi/traceScreen.ts +106 -0
  331. package/src/render/gpuScene.ts +80 -0
  332. package/src/render/gpudriven/alphaTest.ts +33 -0
  333. package/src/render/gpudriven/clusterUpload.ts +123 -0
  334. package/src/render/gpudriven/clusterWorld.ts +239 -0
  335. package/src/render/gpudriven/compact.ts +80 -0
  336. package/src/render/gpudriven/cullClusters.ts +333 -0
  337. package/src/render/gpudriven/decodeTables.ts +332 -0
  338. package/src/render/gpudriven/frustum.ts +70 -0
  339. package/src/render/gpudriven/geometryHarness.ts +51 -0
  340. package/src/render/gpudriven/hzb.ts +113 -0
  341. package/src/render/gpudriven/ibl.ts +147 -0
  342. package/src/render/gpudriven/indirect.ts +89 -0
  343. package/src/render/gpudriven/instances.ts +234 -0
  344. package/src/render/gpudriven/lit.ts +329 -0
  345. package/src/render/gpudriven/lodCut.ts +62 -0
  346. package/src/render/gpudriven/materialBin.ts +131 -0
  347. package/src/render/gpudriven/materialTable.ts +164 -0
  348. package/src/render/gpudriven/pipeline.ts +336 -0
  349. package/src/render/gpudriven/rangeAlloc.ts +122 -0
  350. package/src/render/gpudriven/sceneUpload.ts +173 -0
  351. package/src/render/gpudriven/shadeBins.ts +222 -0
  352. package/src/render/gpudriven/shadow.ts +285 -0
  353. package/src/render/gpudriven/shadowCamera.ts +406 -0
  354. package/src/render/gpudriven/streamScene.ts +575 -0
  355. package/src/render/gpudriven/surfaceFrame.ts +68 -0
  356. package/src/render/gpudriven/twoPhase.ts +214 -0
  357. package/src/render/gpudriven/visbuffer.ts +41 -0
  358. package/src/render/halfFloat.ts +22 -4
  359. package/src/render/inference/attentionTiled.ts +132 -0
  360. package/src/render/inference/denseKernels.ts +369 -0
  361. package/src/render/inference/device.ts +44 -0
  362. package/src/render/inference/deviceGraph.ts +39 -0
  363. package/src/render/inference/fuse.ts +47 -0
  364. package/src/render/inference/kernelKit.ts +120 -0
  365. package/src/render/inference/kernels.ts +18 -0
  366. package/src/render/inference/plan.ts +26 -0
  367. package/src/render/inference/reuse.ts +79 -0
  368. package/src/render/inference/runner.ts +204 -0
  369. package/src/render/inference/schedule.ts +80 -0
  370. package/src/render/inference/shapeKernels.ts +112 -0
  371. package/src/render/inference/spatialKernels.ts +220 -0
  372. package/src/render/inference/splatRaster.wgsl.ts +348 -0
  373. package/src/render/inference/splatTiles.ts +186 -0
  374. package/src/render/lineBatch.ts +4 -24
  375. package/src/render/mesh.ts +46 -1
  376. package/src/render/pass.ts +155 -10
  377. package/src/render/prefilterEnvMap.ts +33 -0
  378. package/src/render/readback.ts +73 -0
  379. package/src/render/recon/clamp.ts +113 -0
  380. package/src/render/recon/disocclusion.ts +143 -0
  381. package/src/render/recon/frameGen.ts +191 -0
  382. package/src/render/recon/frameSizes.ts +64 -0
  383. package/src/render/recon/jitter.ts +126 -0
  384. package/src/render/recon/motionVectors.ts +138 -0
  385. package/src/render/recon/reproject.ts +179 -0
  386. package/src/render/recon/resolve.ts +444 -0
  387. package/src/render/recon/tier.ts +31 -0
  388. package/src/render/reflectionProbe.ts +34 -0
  389. package/src/render/renderQuality.ts +87 -0
  390. package/src/render/screenSpaceReflection.ts +37 -3
  391. package/src/render/sdfFont.ts +2 -0
  392. package/src/render/segmentQuads.ts +39 -0
  393. package/src/render/shaders/flat/lobes.ts +35 -3
  394. package/src/render/shaders/flat/preamble.ts +25 -1
  395. package/src/render/shaders/flat/probeGrid.ts +77 -2
  396. package/src/render/shaders/generated/flat.wgsl.ts +64 -24
  397. package/src/render/shaders/gi/composeField.wgsl.ts +239 -0
  398. package/src/render/shaders/gi/marchField.wgsl.ts +146 -0
  399. package/src/render/shaders/gi/probeBake.wgsl.ts +649 -0
  400. package/src/render/shaders/gi/probeTrace.wgsl.ts +269 -0
  401. package/src/render/shaders/gi/probeVolume.wgsl.ts +246 -0
  402. package/src/render/shaders/gi/sampleField.wgsl.ts +140 -0
  403. package/src/render/shaders/gpudriven/blendRaster.wgsl.ts +343 -0
  404. package/src/render/shaders/gpudriven/compact.wgsl.ts +59 -0
  405. package/src/render/shaders/gpudriven/cull.wgsl.ts +433 -0
  406. package/src/render/shaders/gpudriven/decode.wgsl.ts +373 -0
  407. package/src/render/shaders/gpudriven/environment.wgsl.ts +141 -0
  408. package/src/render/shaders/gpudriven/fog.wgsl.ts +110 -0
  409. package/src/render/shaders/gpudriven/frameBlock.wgsl.ts +62 -0
  410. package/src/render/shaders/gpudriven/materialBin.wgsl.ts +116 -0
  411. package/src/render/shaders/gpudriven/materialTable.wgsl.ts +46 -0
  412. package/src/render/shaders/gpudriven/meshTransform.wgsl.ts +37 -0
  413. package/src/render/shaders/gpudriven/present.wgsl.ts +129 -0
  414. package/src/render/shaders/gpudriven/shade.wgsl.ts +884 -0
  415. package/src/render/shaders/gpudriven/shadow.wgsl.ts +322 -0
  416. package/src/render/shaders/gpudriven/visbufferRaster.wgsl.ts +251 -0
  417. package/src/render/shaders/network.wgsl.ts +238 -0
  418. package/src/render/shaders/recon/motion.wgsl.ts +101 -0
  419. package/src/render/shaders/recon/resolve.wgsl.ts +741 -0
  420. package/src/render/temporalAa.ts +9 -33
  421. package/src/render/textLayout.ts +12 -6
  422. package/src/render/textRenderer.ts +5 -2
  423. package/src/render/translucentQueue.ts +11 -0
  424. package/src/render/windStreakRenderer.ts +4 -2
  425. package/src/world/cell.ts +231 -0
  426. package/src/world/cellStream.ts +278 -0
  427. package/src/world/freeze.ts +157 -0
  428. package/src/world/rebase.ts +56 -0
@@ -0,0 +1,173 @@
1
+ /**
2
+ * A scene's meshes packed into the one set of buffers a GPU-driven frame reads.
3
+ *
4
+ * **`clusterUpload.ts` packs the clusters and this packs everything they point at.** That split is
5
+ * not arbitrary: the cluster packing is about identifiers and spans and has no idea a vertex
6
+ * exists, and it rebases each mesh's index *offsets* by the indices already written while leaving
7
+ * the index *values* alone. Which is right — a mesh's indices address that mesh's vertices — and
8
+ * is exactly wrong the moment two meshes share one buffer, which is the whole point of the
9
+ * pipeline. Every mesh after the first would address vertex zero of the *scene*, a real vertex
10
+ * belonging to the first mesh, drawn in the wrong place with nothing reporting anything.
11
+ *
12
+ * So the rebasing happens here, where the vertex base is known, and the packer is handed sources
13
+ * whose indices are already scene-global.
14
+ *
15
+ * **Three parallel arrays rather than one interleaved vertex.** The raster shader reads
16
+ * `positions[index * 3]` and the shading pass reads normals and colours the same way; an
17
+ * interleaved stride would put the layout in three shaders instead of one place, and the memory
18
+ * this saves is not the memory the pipeline is short of.
19
+ *
20
+ * **Colour is a vertex attribute, because that is what this engine's geometry carries.**
21
+ * `MeshData` has `colors` and most of it is untextured, so the shading pass interpolates them the
22
+ * way the forward path does rather than inventing a material system to sit beside the one that
23
+ * exists. What a *material* then selects is the shading each bin gets — see `materialBin.ts` — and
24
+ * a mesh names one.
25
+ */
26
+
27
+ import { packClusters, type ClusterBuffers, type ClusterSource } from './clusterUpload.ts';
28
+
29
+ /**
30
+ * Floats a vertex in the buffer the GPU-driven pass uploads: position, normal, colour, uv, glow.
31
+ *
32
+ * **One number read by four consumers** — the upload, the raster, the blended raster and the
33
+ * shading pass — and `vertexLayout.test.ts` holds all four to it. It was nine until UVs arrived on
34
+ * 2026-09-17, and eleven until the voxel sandbox's block light needed a glow a vertex on 2026-09-18.
35
+ */
36
+ export const GPU_DRIVEN_VERTEX_FLOATS = 12;
37
+
38
+ /** One mesh of a GPU-driven scene: its vertices, its clusters, and which material shades it. */
39
+ export interface GpuDrivenMesh {
40
+ /** Three floats a vertex. */
41
+ readonly positions: Float32Array;
42
+ /** Three floats a vertex. */
43
+ readonly normals: Float32Array;
44
+ /** Three floats a vertex. */
45
+ readonly colours: Float32Array;
46
+ /**
47
+ * Two floats a vertex. Optional: a mesh without them uploads zeros, and a textured material on
48
+ * it reads one texel everywhere and turns no normal — a statement about the mesh, and what the
49
+ * forward path does with an absent attribute.
50
+ */
51
+ readonly uvs?: Float32Array;
52
+ /**
53
+ * One float a vertex, added to the material's own emissive. Optional: a mesh without it glows as
54
+ * its material says and nothing more, which is every mesh before 2026-09-18.
55
+ *
56
+ * **For light a colour cannot carry.** The voxel sandbox folds occlusion and sky light into the
57
+ * vertex colour and keeps block light here, because it has to survive the sun going down and a
58
+ * colour is multiplied by the sun. The forward path reads the same attribute from `MeshData`.
59
+ */
60
+ readonly emissive?: Float32Array;
61
+ /** This mesh's clusters, with indices addressing *this mesh's* vertices. */
62
+ readonly clusters: ClusterSource;
63
+ /** Which material bin every cluster of this mesh shades in. */
64
+ readonly material: number;
65
+ }
66
+
67
+ export interface GpuDrivenScene {
68
+ /** Every mesh's positions, concatenated in the order they were given. */
69
+ readonly positions: Float32Array;
70
+ readonly normals: Float32Array;
71
+ readonly colours: Float32Array;
72
+ /** Two floats a vertex, zeros where a mesh carried none. */
73
+ readonly uvs: Float32Array;
74
+ /** One float a vertex, zeros where a mesh carried none. */
75
+ readonly emissive: Float32Array;
76
+ /** The clusters, with indices addressing `positions` rather than any one mesh. */
77
+ readonly clusters: ClusterBuffers;
78
+ /** One material a cluster, from the mesh it came from. */
79
+ readonly materialOf: Uint32Array;
80
+ /** Where each mesh's vertices begin. One entry a mesh, including meshes with no clusters. */
81
+ readonly vertexBase: Uint32Array;
82
+ readonly vertexCount: number;
83
+ }
84
+
85
+ /**
86
+ * A mesh's per-vertex glow, refused by name when it is not one float a vertex.
87
+ *
88
+ * Shared by both packers, because a glow of the wrong length read one float a vertex is somebody
89
+ * else's torchlight on the next mesh along — or `NaN` past the end, which lights nothing and says
90
+ * nothing about why.
91
+ */
92
+ export function glowOf(mesh: GpuDrivenMesh, at: number | string): Float32Array | undefined {
93
+ const glow = mesh.emissive;
94
+ if (glow === undefined) return undefined;
95
+ const count = mesh.positions.length / 3;
96
+ if (glow.length !== count) {
97
+ throw new Error(
98
+ `[driftengine] mesh ${at} carries ${glow.length} emissive values for ${count} vertices`,
99
+ );
100
+ }
101
+ return glow;
102
+ }
103
+
104
+ /**
105
+ * Pack a scene.
106
+ *
107
+ * **A mesh with no clusters still takes its slot**, in `vertexBase` and in the mesh numbering the
108
+ * clusters carry. `meshOf` indexes the caller's transform array, so dropping an empty mesh shifts
109
+ * every mesh after it and each of their clusters draws with somebody else's matrix — the same
110
+ * reasoning `packClusters` already gives, one level up.
111
+ */
112
+ export function buildGpuDrivenScene(meshes: readonly GpuDrivenMesh[]): GpuDrivenScene {
113
+ let vertices = 0;
114
+ let clusters = 0;
115
+ const vertexBase = new Uint32Array(meshes.length);
116
+ for (let at = 0; at < meshes.length; at += 1) {
117
+ const mesh = meshes[at] as GpuDrivenMesh;
118
+ vertexBase[at] = vertices;
119
+ vertices += mesh.positions.length / 3;
120
+ clusters += mesh.clusters.count;
121
+ }
122
+
123
+ const positions = new Float32Array(vertices * 3);
124
+ const normals = new Float32Array(vertices * 3);
125
+ const colours = new Float32Array(vertices * 3);
126
+ const uvs = new Float32Array(vertices * 2);
127
+ const emissive = new Float32Array(vertices);
128
+ const rebased: ClusterSource[] = [];
129
+ const materialOf = new Uint32Array(clusters);
130
+
131
+ let cluster = 0;
132
+ for (let at = 0; at < meshes.length; at += 1) {
133
+ const mesh = meshes[at] as GpuDrivenMesh;
134
+ const base = vertexBase[at] as number;
135
+ positions.set(mesh.positions, base * 3);
136
+ normals.set(mesh.normals, base * 3);
137
+ colours.set(mesh.colours, base * 3);
138
+ if (mesh.uvs !== undefined) {
139
+ const count = mesh.positions.length / 3;
140
+ if (mesh.uvs.length !== count * 2) {
141
+ throw new Error(
142
+ `[driftengine] mesh ${at} carries ${mesh.uvs.length} uv values for ${count} vertices`,
143
+ );
144
+ }
145
+ uvs.set(mesh.uvs, base * 2);
146
+ }
147
+ const glow = glowOf(mesh, at);
148
+ if (glow !== undefined) emissive.set(glow, base);
149
+
150
+ const indices = new Uint32Array(mesh.clusters.indices.length);
151
+ for (let i = 0; i < indices.length; i += 1) {
152
+ indices[i] = (mesh.clusters.indices[i] as number) + base;
153
+ }
154
+ rebased.push({ ...mesh.clusters, indices });
155
+
156
+ for (let c = 0; c < mesh.clusters.count; c += 1) {
157
+ materialOf[cluster] = mesh.material;
158
+ cluster += 1;
159
+ }
160
+ }
161
+
162
+ return {
163
+ positions,
164
+ normals,
165
+ colours,
166
+ uvs,
167
+ emissive,
168
+ clusters: packClusters(rebased),
169
+ materialOf,
170
+ vertexBase,
171
+ vertexCount: vertices,
172
+ };
173
+ }
@@ -0,0 +1,222 @@
1
+ /**
2
+ * Rebuilding a pixel's surface from the triangle it recorded.
3
+ *
4
+ * **This is the only part of the shading that differs between the two pipelines.** The forward
5
+ * path gets its surface attributes from the rasteriser's varyings; a visibility buffer has none,
6
+ * so every attribute has to be interpolated here from the three vertices of the triangle the pixel
7
+ * wrote down. The lighting itself is not this module's business and must not become a second copy
8
+ * of the forward path's — a pair of lighting expressions drifts, and the parity gate is then the
9
+ * only thing that notices.
10
+ *
11
+ * **Perspective correction is not optional and screen-space barycentrics are not enough.** A
12
+ * triangle seen at an angle covers pixels whose interpolated attributes are *not* linear in screen
13
+ * position: the correct weights are the screen-space ones divided by each vertex's clip `w` and
14
+ * renormalised. Skipping it is the classic affine-texturing look, and on a floor it is unmistakable.
15
+ *
16
+ * **The gradients are derived analytically, and this is the subtle one.** A fragment shader gets
17
+ * `dFdx` and `dFdy` for free because it runs in quads of four pixels; a compute invocation has no
18
+ * neighbours and no derivative at all. A texture read without one either picks a fixed level —
19
+ * which is a blurry picture everywhere, or an aliased one — or picks level zero, which sparkles.
20
+ * So the rate of change of every barycentric with respect to screen x and y is computed from the
21
+ * triangle's own edge functions, and an attribute's gradient is the same weighted sum its value
22
+ * is. `attributeGradients` is that, and `barycentricGradients` is what it rests on.
23
+ *
24
+ * Everything here takes plain numbers and fills arrays the caller owns, because the shader does the
25
+ * same arithmetic per pixel and the two have to be comparable term by term.
26
+ */
27
+
28
+ /** Screen position and reciprocal clip w of one vertex: x, y, 1/w. */
29
+ export const SCREEN_VERTEX_FLOATS = 3;
30
+
31
+ /**
32
+ * Project one clip-space vertex into pixel coordinates.
33
+ *
34
+ * Fills `x, y, 1/w`. Answers **false behind the eye**, where the division has no meaning: a
35
+ * visibility buffer cannot record such a pixel in the first place — the rasteriser clipped the
36
+ * triangle before it was drawn — so reaching this with `w <= 0` means the identifier and the
37
+ * geometry have gone out of step, and a silent NaN would shade a plausible-looking wrong surface.
38
+ */
39
+ export function screenVertex(
40
+ clipX: number,
41
+ clipY: number,
42
+ clipW: number,
43
+ width: number,
44
+ height: number,
45
+ out: Float32Array,
46
+ at: number,
47
+ ): boolean {
48
+ if (!(clipW > 1e-9)) return false;
49
+ const inverse = 1 / clipW;
50
+ out[at] = (clipX * inverse * 0.5 + 0.5) * width;
51
+ out[at + 1] = (clipY * inverse * 0.5 + 0.5) * height;
52
+ out[at + 2] = inverse;
53
+ return true;
54
+ }
55
+
56
+ /** Twice the signed area of the screen triangle. Zero where it is degenerate. */
57
+ export function screenArea(screen: Float32Array): number {
58
+ const x0 = screen[0] as number;
59
+ const y0 = screen[1] as number;
60
+ const x1 = screen[3] as number;
61
+ const y1 = screen[4] as number;
62
+ const x2 = screen[6] as number;
63
+ const y2 = screen[7] as number;
64
+ return (x1 - x0) * (y2 - y0) - (x2 - x0) * (y1 - y0);
65
+ }
66
+
67
+ /**
68
+ * The screen-space barycentric weights of a point, before perspective correction.
69
+ *
70
+ * Answers false for a degenerate triangle — one whose three screen positions are collinear, which
71
+ * covers no pixel and whose weights are a division by zero. A caller that reaches this has a
72
+ * visibility identifier for a triangle the rasteriser never drew.
73
+ *
74
+ * **A point outside the triangle gets a negative weight rather than a clamp.** Clamping is how a
75
+ * pixel on the far side of an edge ends up shaded as though it were on the triangle, which along a
76
+ * silhouette is a fringe of the wrong material.
77
+ */
78
+ export function screenBarycentrics(
79
+ screen: Float32Array,
80
+ x: number,
81
+ y: number,
82
+ out: Float32Array,
83
+ ): boolean {
84
+ const area = screenArea(screen);
85
+ if (Math.abs(area) < 1e-12) return false;
86
+ const x0 = screen[0] as number;
87
+ const y0 = screen[1] as number;
88
+ const x1 = screen[3] as number;
89
+ const y1 = screen[4] as number;
90
+ const x2 = screen[6] as number;
91
+ const y2 = screen[7] as number;
92
+ const inverse = 1 / area;
93
+ out[1] = ((x - x0) * (y2 - y0) - (x2 - x0) * (y - y0)) * inverse;
94
+ out[2] = ((x1 - x0) * (y - y0) - (x - x0) * (y1 - y0)) * inverse;
95
+ out[0] = 1 - out[1] - out[2];
96
+ return true;
97
+ }
98
+
99
+ /**
100
+ * The perspective-correct weights, from the screen-space ones and the vertices' reciprocal `w`.
101
+ *
102
+ * Each screen weight is scaled by its vertex's `1/w` and the three renormalised. At an orthographic
103
+ * projection every `1/w` is equal and this is the identity, which is why the affine version looks
104
+ * right on a test quad facing the camera and wrong on the floor beneath it.
105
+ */
106
+ export function perspectiveBarycentrics(
107
+ screen: Float32Array,
108
+ lambda: Float32Array,
109
+ out: Float32Array,
110
+ ): boolean {
111
+ const w0 = (lambda[0] as number) * (screen[2] as number);
112
+ const w1 = (lambda[1] as number) * (screen[5] as number);
113
+ const w2 = (lambda[2] as number) * (screen[8] as number);
114
+ const sum = w0 + w1 + w2;
115
+ if (Math.abs(sum) < 1e-20) return false;
116
+ const inverse = 1 / sum;
117
+ out[0] = w0 * inverse;
118
+ out[1] = w1 * inverse;
119
+ out[2] = w2 * inverse;
120
+ return true;
121
+ }
122
+
123
+ /**
124
+ * How each perspective-correct weight changes with screen x and y, at one pixel.
125
+ *
126
+ * Fills six numbers: `d0/dx, d1/dx, d2/dx, d0/dy, d1/dy, d2/dy`.
127
+ *
128
+ * **Derived rather than differenced.** The screen weights are linear in `x` and `y`, so their
129
+ * gradients are constants of the triangle; the perspective-correct ones are a ratio of two linear
130
+ * functions, and the quotient rule gives the rest. What this buys over a finite difference is that
131
+ * it is exact at the pixel rather than an average over one, and that it costs no neighbours — a
132
+ * compute invocation has none.
133
+ */
134
+ export function barycentricGradients(
135
+ screen: Float32Array,
136
+ lambda: Float32Array,
137
+ out: Float32Array,
138
+ ): boolean {
139
+ const area = screenArea(screen);
140
+ if (Math.abs(area) < 1e-12) return false;
141
+ const inverse = 1 / area;
142
+ const x0 = screen[0] as number;
143
+ const y0 = screen[1] as number;
144
+ const x1 = screen[3] as number;
145
+ const y1 = screen[4] as number;
146
+ const x2 = screen[6] as number;
147
+ const y2 = screen[7] as number;
148
+
149
+ /* The screen weights' own gradients, which are constant over the triangle. */
150
+ const d1dx = (y2 - y0) * inverse;
151
+ const d1dy = (x0 - x2) * inverse;
152
+ const d2dx = (y0 - y1) * inverse;
153
+ const d2dy = (x1 - x0) * inverse;
154
+ const d0dx = -d1dx - d2dx;
155
+ const d0dy = -d1dy - d2dy;
156
+
157
+ const iw0 = screen[2] as number;
158
+ const iw1 = screen[5] as number;
159
+ const iw2 = screen[8] as number;
160
+ const n0 = (lambda[0] as number) * iw0;
161
+ const n1 = (lambda[1] as number) * iw1;
162
+ const n2 = (lambda[2] as number) * iw2;
163
+ const sum = n0 + n1 + n2;
164
+ if (Math.abs(sum) < 1e-20) return false;
165
+ const inverseSum = 1 / sum;
166
+
167
+ /* The denominator's own gradient: the same weighted sum of the reciprocal w values. */
168
+ const dSumDx = d0dx * iw0 + d1dx * iw1 + d2dx * iw2;
169
+ const dSumDy = d0dy * iw0 + d1dy * iw1 + d2dy * iw2;
170
+
171
+ /* Quotient rule, per weight, with the shared denominator factored out. */
172
+ out[0] = (d0dx * iw0 - n0 * inverseSum * dSumDx) * inverseSum;
173
+ out[1] = (d1dx * iw1 - n1 * inverseSum * dSumDx) * inverseSum;
174
+ out[2] = (d2dx * iw2 - n2 * inverseSum * dSumDx) * inverseSum;
175
+ out[3] = (d0dy * iw0 - n0 * inverseSum * dSumDy) * inverseSum;
176
+ out[4] = (d1dy * iw1 - n1 * inverseSum * dSumDy) * inverseSum;
177
+ out[5] = (d2dy * iw2 - n2 * inverseSum * dSumDy) * inverseSum;
178
+ return true;
179
+ }
180
+
181
+ /** One attribute interpolated by the weights already computed. */
182
+ export function interpolate(a0: number, a1: number, a2: number, weights: Float32Array): number {
183
+ return a0 * (weights[0] as number) + a1 * (weights[1] as number) + a2 * (weights[2] as number);
184
+ }
185
+
186
+ /**
187
+ * One attribute's rate of change with screen x and y, from the weight gradients.
188
+ *
189
+ * An attribute is a weighted sum of three constants, so its gradient is the same sum of the
190
+ * weights' gradients. This is what a texture fetch's level of detail is computed from.
191
+ */
192
+ export function attributeGradients(
193
+ a0: number,
194
+ a1: number,
195
+ a2: number,
196
+ gradients: Float32Array,
197
+ ): { dx: number; dy: number } {
198
+ return {
199
+ dx:
200
+ a0 * (gradients[0] as number) + a1 * (gradients[1] as number) + a2 * (gradients[2] as number),
201
+ dy:
202
+ a0 * (gradients[3] as number) + a1 * (gradients[4] as number) + a2 * (gradients[5] as number),
203
+ };
204
+ }
205
+
206
+ /**
207
+ * Which pixel a shading invocation is responsible for.
208
+ *
209
+ * A group covers `BIN_GROUP_SIZE` entries of one material's slice; an invocation past the end of
210
+ * the slice has no pixel, which is the ordinary case because the slice is not a multiple of the
211
+ * group size. Answers -1 there rather than the last pixel of the bin, which would shade it once
212
+ * per spare lane.
213
+ */
214
+ export function binPixel(
215
+ pixels: Uint32Array,
216
+ offset: number,
217
+ count: number,
218
+ invocation: number,
219
+ ): number {
220
+ if (invocation < 0 || invocation >= count) return -1;
221
+ return (pixels[offset + invocation] as number) ?? -1;
222
+ }
@@ -0,0 +1,285 @@
1
+ /** The directional shadow lookup, in TypeScript, so the second pipeline's copy has a reference. */
2
+
3
+ import { DIRECTIONAL_SHADOW_FADE_START, POINT_SHADOW_FADE_START } from '../renderQuality.ts';
4
+
5
+ /**
6
+ * **`flat/directionalShadow.ts` in TypeScript, for one depth layer.**
7
+ *
8
+ * The lesson of the lit expression was a week old when this was written: a term that exists only
9
+ * as a shader is a term nothing can disagree with. So the lookup was written here first, tested
10
+ * here, and `SHADE_SHADOW_WGSL` beside it is the same arithmetic for the device with
11
+ * `gpu-parity.mjs` running both — which is the order the map itself then followed.
12
+ *
13
+ * **One layer, where the forward path has three.** That path keeps a static map, a peeled one
14
+ * holding the second static occluder, and a dynamic one for movers, and multiplies their
15
+ * transmissions — an arrangement its own comments argue for at length and which exists because its
16
+ * shadow *pool* is shared between lights and rationed across frames. The second pipeline has no
17
+ * such pool: it owns its geometry, and a pass owns whatever target it renders in `prepare`. When
18
+ * there is one caster set there is one map, and stating that is better than pretending the shape
19
+ * is the same.
20
+ *
21
+ * **Everything else is term for term**, because the two pipelines drawing one scene must not
22
+ * disagree about where a shadow ends. Each fade below returns *fully lit*, which is the thing that
23
+ * makes them dangerous: a guard that fires when it should not removes a shadow, and a missing
24
+ * shadow reads as a scene that never had one.
25
+ */
26
+
27
+ /** How much of a texel the filter reaches, which sets the softness of an edge. */
28
+ export const SHADOW_TEXEL_SCALE = 1.35;
29
+
30
+ /**
31
+ * The most depth the receiver-plane compensation may carry a tap, in texels.
32
+ *
33
+ * **A product of an unbounded gradient and a bounded offset**, and `directionalShadow.ts` records
34
+ * paying for the version where only the offset was bounded: a gradient that has blown up turns a
35
+ * fraction of a texel into a depth swing of whole map units, the comparison flips on alternate
36
+ * shadow texels, and what draws is hard straight stripes along the map's texel grid — oblique on
37
+ * screen, because that grid is turned by the sun rather than by the camera.
38
+ */
39
+ export const SHADOW_SLOPE_TEXELS = 9.1;
40
+
41
+ /** Subtracted from every comparison, in depth-span units. The contact bias. */
42
+ export const SHADOW_DEPTH_BIAS = 0.14;
43
+
44
+ /** The band of `n · l` over which the shadow modulation is given up. See `shadowFactor`. */
45
+ export const SHADOW_TERMINATOR_BAND: readonly [number, number] = [0.08, 0.2];
46
+
47
+ /**
48
+ * The stretch of stored depth, 0 to 1, over which a shadow is given up at the map's far end.
49
+ *
50
+ * **A fit has to keep its receivers short of it**, and that is why it is a name rather than two
51
+ * literals: in the scene fit the far end is the scene's own rim, where nothing lies beyond, and a
52
+ * fit that followed the eye first put the whole of the eye's sphere inside it — `fitShadow` derives
53
+ * its least depth reach from the first of these two numbers.
54
+ */
55
+ export const SHADOW_DEPTH_FADE: readonly [number, number] = [0.9, 1];
56
+
57
+ /**
58
+ * The twelve taps, transcribed from `flat/pointShadow.ts`.
59
+ *
60
+ * A Poisson set rather than a grid: a grid filters along its own axes and bands across them, and
61
+ * the sun turns the map's grid relative to the screen, so the banding arrives at an angle nobody
62
+ * can attribute to anything.
63
+ */
64
+ export const SHADOW_PCF_OFFSETS: readonly (readonly [number, number])[] = [
65
+ [-0.326, -0.406],
66
+ [0.519, 0.767],
67
+ [0.962, -0.195],
68
+ [-0.696, 0.457],
69
+ [-0.84, -0.074],
70
+ [0.185, -0.893],
71
+ [0.896, 0.412],
72
+ [-0.203, 0.621],
73
+ [0.473, -0.48],
74
+ [0.507, 0.064],
75
+ [-0.322, -0.933],
76
+ [-0.792, -0.598],
77
+ ];
78
+
79
+ export interface ShadowSettings {
80
+ /** 0 turns the whole lookup off, and off is exact rather than nearly. */
81
+ readonly strength: number;
82
+ readonly mapSize: number;
83
+ /** Metres the map's depth range spans, which turns a stored depth into a distance. */
84
+ readonly depthSpan: number;
85
+ /** Metres a shadow may travel along the ground before it has faded out. */
86
+ readonly maxDistance: number;
87
+ /** The steepest `|light.xz| / light.y` that still casts. */
88
+ readonly maxSlope: number;
89
+ readonly taps: number;
90
+ /** Toward the light, unit length, as `Environment.directionalDir` is. */
91
+ readonly lightDir: readonly [number, number, number];
92
+ }
93
+
94
+ /**
95
+ * The receiver plane: how the light-space depth changes per unit of the light's own `u` and `v`.
96
+ *
97
+ * **A fragment shader gets `dFdx`/`dFdy` and a compute invocation has none**, having no neighbours
98
+ * to difference against — the same fact `SHADE_SURFACE_WGSL` exists for. So this takes the two
99
+ * derivatives rather than computing them: the forward path passes what the hardware gave it, and
100
+ * the second pipeline passes what it derives from the barycentric gradients it already has. The
101
+ * arithmetic between them is one function, which is the whole point.
102
+ *
103
+ * **It is a two-by-two solve and it is refused where the system is ill-conditioned.** A triangle
104
+ * seen almost edge-on in the light's projection has a determinant near zero, and dividing by it
105
+ * turns a fraction of a texel into a depth swing of whole map units — which the clamp downstream
106
+ * then has to catch. Refusing here is cheaper and says what happened: a plane whose slope cannot be
107
+ * measured gets no compensation rather than a wrong one.
108
+ *
109
+ * **Exact only where the light's projection is orthographic**, which a directional light's is: `w`
110
+ * is one, so the light-space position is linear in the world position and a barycentric
111
+ * interpolation of the three corners is the real thing rather than an approximation of it.
112
+ */
113
+ export function receiverPlaneDepthGradient(
114
+ dx: readonly [number, number, number],
115
+ dy: readonly [number, number, number],
116
+ out: Float32Array,
117
+ ): Float32Array {
118
+ const determinant = dx[0] * dy[1] - dx[1] * dy[0];
119
+ const conditioning = Math.hypot(dx[0], dx[1]) * Math.hypot(dy[0], dy[1]);
120
+ if (Math.abs(determinant) < 1e-4 * conditioning) {
121
+ out[0] = 0;
122
+ out[1] = 0;
123
+ return out;
124
+ }
125
+ out[0] = (dx[2] * dy[1] - dx[1] * dy[2]) / determinant;
126
+ out[1] = (dx[0] * dy[2] - dx[2] * dy[0]) / determinant;
127
+ return out;
128
+ }
129
+
130
+ /**
131
+ * The receiver plane a shading invocation can actually get, from the two things it already has.
132
+ *
133
+ * **A compute invocation has no neighbours, so the derivative has to be built rather than taken.**
134
+ * `SHADE_SURFACE_WGSL` already computes how each barycentric weight changes per pixel of screen —
135
+ * that is what a visibility buffer's shading needs for texture gradients — and a light-space
136
+ * position is a linear combination of the three corners with exactly those weights. So the
137
+ * derivative of the light-space position is the same combination of the corners with the *weight
138
+ * gradients*, which is what this assembles before handing it to the solve above.
139
+ *
140
+ * **Exact rather than approximate, because a directional light's projection is orthographic.** `w`
141
+ * is one everywhere, so the light-space position is linear in the world position and no perspective
142
+ * divide stands between the weights and the answer. A spot light would need the quotient rule here
143
+ * and this function would be wrong for it, which is worth knowing before one arrives.
144
+ *
145
+ * **The halving is the remap.** The lookup addresses the map in 0-to-1 coordinates and clip `x` and
146
+ * `y` run -1 to 1, so a slope stated per clip unit is twice the slope per map unit. Depth is not
147
+ * halved: this pipeline's light matrix already emits `z` in 0..1, which is the same asymmetry
148
+ * `shadowFactor` carries and for the same reason.
149
+ *
150
+ * Each corner is a light-space clip position, `x`, `y` and `z`, with `w` taken as one.
151
+ */
152
+ export function receiverPlaneFromWeights(
153
+ l0: readonly [number, number, number],
154
+ l1: readonly [number, number, number],
155
+ l2: readonly [number, number, number],
156
+ gx: readonly [number, number, number],
157
+ gy: readonly [number, number, number],
158
+ out: Float32Array,
159
+ ): Float32Array {
160
+ const along = (g: readonly [number, number, number], axis: 0 | 1 | 2): number =>
161
+ (g[0] as number) * (l0[axis] as number) +
162
+ (g[1] as number) * (l1[axis] as number) +
163
+ (g[2] as number) * (l2[axis] as number);
164
+ return receiverPlaneDepthGradient(
165
+ [along(gx, 0) * 0.5, along(gx, 1) * 0.5, along(gx, 2)],
166
+ [along(gy, 0) * 0.5, along(gy, 1) * 0.5, along(gy, 2)],
167
+ out,
168
+ );
169
+ }
170
+
171
+ /** GLSL's `smoothstep`, which is not `Math`'s anything. */
172
+ function smoothstep(edge0: number, edge1: number, x: number): number {
173
+ const t = Math.min(Math.max((x - edge0) / (edge1 - edge0), 0), 1);
174
+ return t * t * (3 - 2 * t);
175
+ }
176
+
177
+ /** How much of a shadow survives having been cast this far along the ground. */
178
+ export function shadowReach(distance: number, maxDistance: number): number {
179
+ return 1 - smoothstep(maxDistance * POINT_SHADOW_FADE_START, maxDistance, distance);
180
+ }
181
+
182
+ /**
183
+ * Whether one tap is lit, and by how much.
184
+ *
185
+ * **A tap behind an occluder is not simply dark**: how far the shadow has travelled along the
186
+ * ground decides how much of it is left, which is what keeps one map's worth of geometry from
187
+ * shadowing the whole world.
188
+ */
189
+ export function directionalVisibility(
190
+ receiverDepth: number,
191
+ compareDepth: number,
192
+ storedDepth: number,
193
+ settings: ShadowSettings,
194
+ ): number {
195
+ if (compareDepth <= storedDepth) return 1;
196
+ const rayDistance = Math.max(receiverDepth - storedDepth, 0) * settings.depthSpan;
197
+ const ground =
198
+ rayDistance * Math.hypot(settings.lightDir[0] as number, settings.lightDir[2] as number);
199
+ return 1 - shadowReach(ground, settings.maxDistance);
200
+ }
201
+
202
+ /**
203
+ * How much light reaches a receiver, from 0 in full shadow to 1 in full light.
204
+ *
205
+ * `lightPos` is the receiver in the light's clip space, before the divide. `depthGradient` is how
206
+ * the light-space depth changes across a pixel of the map — the receiver plane — which a fragment
207
+ * shader gets from `dFdx`/`dFdy` and a **compute invocation cannot**, having no neighbours. The
208
+ * second pipeline derives it from the barycentric gradients `SHADE_SURFACE_WGSL` already computes,
209
+ * which is the same reason that file computes them: a derivative has to come from somewhere.
210
+ *
211
+ * `depthAt` reads the map with nearest filtering, which is what the comparison assumes: the fetched
212
+ * depth belongs to the centre of the containing texel, and following the continuous coordinate
213
+ * instead leaves half a texel of uncompensated slope — the repeating ribs a grazing face shows.
214
+ */
215
+ export function shadowFactor(
216
+ lightPos: readonly [number, number, number, number],
217
+ depthGradient: readonly [number, number],
218
+ ndl: number,
219
+ settings: ShadowSettings,
220
+ depthAt: (u: number, v: number) => number,
221
+ ): number {
222
+ /*
223
+ * **Four early-outs below, and every one of them is a cost rather than a correctness guard.**
224
+ * The result is `1 + (lit - 1) * amount` and `amount` is the product of all four fades, so a fade
225
+ * at zero returns exactly one whether or not the return fires — perturbing any of them changes
226
+ * no answer, which is recorded here rather than papered over. They stay because each one skips a
227
+ * loop of twelve texture reads, which on a full screen is the difference between a lookup and a
228
+ * pass. The bounds check is the one with a second job: without it the taps read outside the map.
229
+ */
230
+ if (settings.strength <= 0) return 1;
231
+
232
+ const w = lightPos[3];
233
+ if (w === 0) return 1;
234
+ /*
235
+ * **`x` and `y` are remapped and `z` is not, and that is a convention rather than an asymmetry.**
236
+ * WebGPU clips `z` to 0..1 already; OpenGL clips it to -1..1, which is why the GLSL this is
237
+ * transcribed from remaps all three. This module is the second pipeline's and the second pipeline
238
+ * is WebGPU only, so it takes WebGPU's convention — and a caller handing it a GL-style depth gets
239
+ * a shadow that is half as deep as it should be and biased by half the map, which reads as
240
+ * everything being lit. `CLIP_CORRECTION` records the same family of mistake one level up.
241
+ */
242
+ const px = (lightPos[0] / w) * 0.5 + 0.5;
243
+ const py = (lightPos[1] / w) * 0.5 + 0.5;
244
+ const pz = lightPos[2] / w;
245
+
246
+ const receiverFade = smoothstep(SHADOW_TERMINATOR_BAND[0], SHADOW_TERMINATOR_BAND[1], ndl);
247
+ if (receiverFade <= 0) return 1;
248
+ if (pz > 1 || px < 0 || px > 1 || py < 0 || py > 1) return 1;
249
+
250
+ const fromCentre = Math.max(Math.abs(px - 0.5) * 2, Math.abs(py - 0.5) * 2);
251
+ const edgeFade =
252
+ (1 - smoothstep(0.72, 0.98, fromCentre)) *
253
+ (1 - smoothstep(SHADOW_DEPTH_FADE[0], SHADOW_DEPTH_FADE[1], pz));
254
+ if (edgeFade <= 0) return 1;
255
+
256
+ const slope =
257
+ Math.hypot(settings.lightDir[0] as number, settings.lightDir[2] as number) /
258
+ Math.max(settings.lightDir[1] as number, 0.001);
259
+ const lowElevationFade =
260
+ 1 - smoothstep(settings.maxSlope * DIRECTIONAL_SHADOW_FADE_START, settings.maxSlope, slope);
261
+ if (lowElevationFade <= 0) return 1;
262
+
263
+ const texel = SHADOW_TEXEL_SCALE / settings.mapSize;
264
+ const slopeLimit = SHADOW_SLOPE_TEXELS / settings.mapSize;
265
+ const taps = Math.max(1, Math.min(Math.trunc(settings.taps), SHADOW_PCF_OFFSETS.length));
266
+
267
+ let lit = 0;
268
+ for (let tap = 0; tap < taps; tap += 1) {
269
+ const offset = SHADOW_PCF_OFFSETS[tap] as readonly [number, number];
270
+ const u = px + offset[0] * texel;
271
+ const v = py + offset[1] * texel;
272
+ const centreU = (Math.floor(u * settings.mapSize) + 0.5) / settings.mapSize;
273
+ const centreV = (Math.floor(v * settings.mapSize) + 0.5) / settings.mapSize;
274
+ const raw =
275
+ (depthGradient[0] as number) * (centreU - px) + (depthGradient[1] as number) * (centreV - py);
276
+ const slopeOffset = Math.min(Math.max(raw, -slopeLimit), slopeLimit);
277
+ const receiverDepth = pz + slopeOffset;
278
+ const compare = receiverDepth - SHADOW_DEPTH_BIAS / settings.depthSpan;
279
+ lit += directionalVisibility(receiverDepth, compare, depthAt(u, v), settings);
280
+ }
281
+ lit /= taps;
282
+
283
+ const amount = settings.strength * edgeFade * lowElevationFade * receiverFade;
284
+ return 1 + (lit - 1) * amount;
285
+ }