@driftengine/core 3.63.0 → 4.1.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 +122 -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,239 @@
1
+ /** Where a cluster actually stands: its bounds, its cone and its error through its mesh transform. */
2
+
3
+ /**
4
+ * **The cull asks about the world and a cluster is baked in its mesh's own space.**
5
+ *
6
+ * `visbufferRaster.wgsl.ts` multiplies every vertex by `transforms[mesh]` and `shade.wgsl.ts`
7
+ * multiplies every normal by the same upper three-by-three. The culling did neither: it read
8
+ * `ClusterBuffers.bounds` and `ClusterBuffers.cones` as they came out of the bake, so a mesh placed
9
+ * anywhere but the origin was frustum-culled against the wrong region, cone-culled with the wrong
10
+ * orientation, LOD-cut on a misprojected error and occlusion-tested on the wrong rectangle — while
11
+ * the picture it drew was still drawn in the right place, because the raster had the transform.
12
+ *
13
+ * **Every rig in `DRAFT_SCENES` uses an identity transform**, which is the only reason it never
14
+ * showed. That is the shape §3 row 79 is about: the device check agreed, the reference agreed, and
15
+ * nothing in the repository placed a mesh anywhere.
16
+ *
17
+ * **It happens once, at upload, and not per frame.** This pipeline owns a fixed cluster set with
18
+ * static transforms — the same property `shadowCamera.ts` fits the light's box to — so the world
19
+ * bounds are a property of the scene. A pipeline that ever moves a mesh has to rewrite these two
20
+ * buffers when it does, and that is the cost of having them.
21
+ */
22
+
23
+ /** What the transform needs of a packed cluster set. `ClusterBuffers` satisfies it. */
24
+ export interface ClusterWorldSource {
25
+ /** Four floats a cluster: centre and radius, in the mesh's own space. */
26
+ readonly bounds: Float32Array;
27
+ /** Four floats a cluster: cone axis and cutoff, in the mesh's own space. */
28
+ readonly cones: Float32Array;
29
+ /** Two floats a cluster: its own error and its parent's. */
30
+ readonly errors: Float32Array;
31
+ /** Which mesh each cluster belongs to, which is what it reaches a transform through. */
32
+ readonly meshOf: Uint32Array;
33
+ readonly count: number;
34
+ }
35
+
36
+ /** The two buffers the culling stages read, in the layout they are bound in. */
37
+ export interface ClusterWorld {
38
+ /** Eight floats a cluster: world centre, radius, cone axis, cutoff. `cullClusters` takes this. */
39
+ cull: Float32Array;
40
+ /** Six floats a cluster: world centre, radius, own error, parent error. `lodCut` reads these. */
41
+ lod: Float32Array;
42
+ }
43
+
44
+ /** Eight floats a cluster in `cull`, matching `CLUSTER_CULL_FLOATS` and the cull shader's stride. */
45
+ const CULL_FLOATS = 8;
46
+
47
+ /** Six floats a cluster in `lod`, matching the LOD shader's `i * 6u`. */
48
+ const LOD_FLOATS = 6;
49
+
50
+ /**
51
+ * How far from a similarity a transform may be before the cone is refused.
52
+ *
53
+ * Relative, and loose on purpose: a rotation composed in single precision loses a few parts in ten
54
+ * million, and the comparison has to pass every matrix a `Transform` produces without passing a
55
+ * scale a modeller meant. A hundredth of a per cent is three orders of magnitude clear of the one
56
+ * and four clear of the other.
57
+ */
58
+ const SIMILARITY_TOLERANCE = 1e-4;
59
+
60
+ /**
61
+ * Transform every cluster into the world, once.
62
+ *
63
+ * `transforms` is sixteen floats a mesh, column-major, indexed by `meshOf`.
64
+ */
65
+ export function worldClusters(
66
+ clusters: ClusterWorldSource,
67
+ transforms: Float32Array,
68
+ ): ClusterWorld {
69
+ const cull = new Float32Array(clusters.count * CULL_FLOATS);
70
+ const lod = new Float32Array(clusters.count * LOD_FLOATS);
71
+ worldClustersInto(clusters, transforms, cull, lod, 0);
72
+ return { cull, lod };
73
+ }
74
+
75
+ /**
76
+ * The same arithmetic, writing into arrays the caller owns at a cluster offset it chooses.
77
+ *
78
+ * **This exists because a streamed mesh computes its own bounds**, at the moment it is added, into
79
+ * the middle of a scene-sized buffer that already holds other meshes'. `worldClusters` is this
80
+ * with the allocation, so the two cannot drift — which this file asserts by composing two meshes
81
+ * both ways and comparing byte for byte.
82
+ *
83
+ * `base` is in clusters, not floats: the record at `base + c` is written for every cluster `c` of
84
+ * the source, and everything outside that range is left exactly as it was found.
85
+ */
86
+ export function worldClustersInto(
87
+ clusters: ClusterWorldSource,
88
+ transforms: Float32Array,
89
+ cull: Float32Array,
90
+ lod: Float32Array,
91
+ base: number,
92
+ ): void {
93
+ const count = clusters.count;
94
+
95
+ /*
96
+ * Per mesh rather than per cluster: a scene is thousands of clusters over a handful of meshes,
97
+ * and the two questions asked of a matrix — how much it scales, and whether it scales evenly —
98
+ * are nine multiplies each.
99
+ */
100
+ const meshes = Math.floor(transforms.length / 16);
101
+ const scales = new Float64Array(meshes);
102
+ const similar = new Uint8Array(meshes);
103
+ for (let m = 0; m < meshes; m += 1) {
104
+ const at = m * 16;
105
+ const l0 = Math.hypot(
106
+ transforms[at] as number,
107
+ transforms[at + 1] as number,
108
+ transforms[at + 2] as number,
109
+ );
110
+ const l1 = Math.hypot(
111
+ transforms[at + 4] as number,
112
+ transforms[at + 5] as number,
113
+ transforms[at + 6] as number,
114
+ );
115
+ const l2 = Math.hypot(
116
+ transforms[at + 8] as number,
117
+ transforms[at + 9] as number,
118
+ transforms[at + 10] as number,
119
+ );
120
+ const longest = Math.max(l0, l1, l2);
121
+ scales[m] = longest;
122
+
123
+ /*
124
+ * **The longest basis vector is the scale, which is exact for `T · R · S` and conservative for
125
+ * nothing else.** A sphere of radius `r` maps to an ellipsoid whose longest semi-axis is the
126
+ * matrix's largest singular value, and for a rotation applied *after* a scale the columns are
127
+ * the scaled axes and their lengths are exactly the singular values. A sheared matrix — a scale
128
+ * applied after a rotation, which nothing in this engine composes — has a largest singular
129
+ * value above its longest column, and there the bound is too small and clusters are culled that
130
+ * should be drawn. `sceneShadowBounds` reads the same number and carries the same limit.
131
+ *
132
+ * A similarity is the case where the *cone* survives too: three columns of equal length, at
133
+ * right angles. Then the inverse transpose is the matrix over the scale squared, so a normal
134
+ * multiplied by the matrix and renormalised is the right direction and the cone's half-angle is
135
+ * unchanged. Anywhere else the two disagree by up to a right angle and the cone is refused.
136
+ */
137
+ const even =
138
+ Math.abs(l0 - longest) <= SIMILARITY_TOLERANCE * longest &&
139
+ Math.abs(l1 - longest) <= SIMILARITY_TOLERANCE * longest &&
140
+ Math.abs(l2 - longest) <= SIMILARITY_TOLERANCE * longest;
141
+ const square = longest * longest;
142
+ const d01 =
143
+ (transforms[at] as number) * (transforms[at + 4] as number) +
144
+ (transforms[at + 1] as number) * (transforms[at + 5] as number) +
145
+ (transforms[at + 2] as number) * (transforms[at + 6] as number);
146
+ const d02 =
147
+ (transforms[at] as number) * (transforms[at + 8] as number) +
148
+ (transforms[at + 1] as number) * (transforms[at + 9] as number) +
149
+ (transforms[at + 2] as number) * (transforms[at + 10] as number);
150
+ const d12 =
151
+ (transforms[at + 4] as number) * (transforms[at + 8] as number) +
152
+ (transforms[at + 5] as number) * (transforms[at + 9] as number) +
153
+ (transforms[at + 6] as number) * (transforms[at + 10] as number);
154
+ const square90 =
155
+ Math.abs(d01) <= SIMILARITY_TOLERANCE * square &&
156
+ Math.abs(d02) <= SIMILARITY_TOLERANCE * square &&
157
+ Math.abs(d12) <= SIMILARITY_TOLERANCE * square;
158
+ similar[m] = longest > 0 && even && square90 ? 1 : 0;
159
+ }
160
+
161
+ for (let c = 0; c < count; c += 1) {
162
+ const at = (clusters.meshOf[c] as number) * 16;
163
+ const scale = scales[clusters.meshOf[c] as number] as number;
164
+
165
+ const cx = clusters.bounds[c * 4] as number;
166
+ const cy = clusters.bounds[c * 4 + 1] as number;
167
+ const cz = clusters.bounds[c * 4 + 2] as number;
168
+ const wx =
169
+ (transforms[at] as number) * cx +
170
+ (transforms[at + 4] as number) * cy +
171
+ (transforms[at + 8] as number) * cz +
172
+ (transforms[at + 12] as number);
173
+ const wy =
174
+ (transforms[at + 1] as number) * cx +
175
+ (transforms[at + 5] as number) * cy +
176
+ (transforms[at + 9] as number) * cz +
177
+ (transforms[at + 13] as number);
178
+ const wz =
179
+ (transforms[at + 2] as number) * cx +
180
+ (transforms[at + 6] as number) * cy +
181
+ (transforms[at + 10] as number) * cz +
182
+ (transforms[at + 14] as number);
183
+ const radius = (clusters.bounds[c * 4 + 3] as number) * scale;
184
+
185
+ const ax = clusters.cones[c * 4] as number;
186
+ const ay = clusters.cones[c * 4 + 1] as number;
187
+ const az = clusters.cones[c * 4 + 2] as number;
188
+ const tx =
189
+ (transforms[at] as number) * ax +
190
+ (transforms[at + 4] as number) * ay +
191
+ (transforms[at + 8] as number) * az;
192
+ const ty =
193
+ (transforms[at + 1] as number) * ax +
194
+ (transforms[at + 5] as number) * ay +
195
+ (transforms[at + 9] as number) * az;
196
+ const tz =
197
+ (transforms[at + 2] as number) * ax +
198
+ (transforms[at + 6] as number) * ay +
199
+ (transforms[at + 10] as number) * az;
200
+ const length = Math.hypot(tx, ty, tz);
201
+ /*
202
+ * **A cutoff of one is a refusal rather than a wide cone.** `cullClusters` culls when
203
+ * `dot(axis, d) >= cutoff * |d| + radius`, and for a unit axis the left-hand side is at most
204
+ * `|d|` — so at one the test cannot fire however the cluster is aimed, which is the
205
+ * conservative answer and is already what a bake writes for a cluster whose normals span a
206
+ * hemisphere.
207
+ */
208
+ const keepsCone = similar[clusters.meshOf[c] as number] === 1 && length > 0;
209
+ const out = (base + c) * CULL_FLOATS;
210
+ cull[out] = wx;
211
+ cull[out + 1] = wy;
212
+ cull[out + 2] = wz;
213
+ cull[out + 3] = radius;
214
+ cull[out + 4] = keepsCone ? tx / length : ax;
215
+ cull[out + 5] = keepsCone ? ty / length : ay;
216
+ cull[out + 6] = keepsCone ? tz / length : az;
217
+ cull[out + 7] = keepsCone ? (clusters.cones[c * 4 + 3] as number) : 1;
218
+
219
+ /*
220
+ * **The errors are lengths and a scale is a length's units.** `projectedError` divides a
221
+ * geometric error by the distance to the eye and multiplies by the screen height; the distance
222
+ * is in world metres now, so the error has to be. A mesh scaled up with its model-space error
223
+ * left alone draws its coarsest level everywhere.
224
+ *
225
+ * `Infinity` is how the coarsest level says it has no parent, and it is left alone: multiplied
226
+ * by a scale of zero it is `NaN`, every comparison against it is false, and the root of every
227
+ * mesh disappears.
228
+ */
229
+ const ownError = clusters.errors[c * 2] as number;
230
+ const parentError = clusters.errors[c * 2 + 1] as number;
231
+ const into = (base + c) * LOD_FLOATS;
232
+ lod[into] = wx;
233
+ lod[into + 1] = wy;
234
+ lod[into + 2] = wz;
235
+ lod[into + 3] = radius;
236
+ lod[into + 4] = ownError * scale;
237
+ lod[into + 5] = Number.isFinite(parentError) ? parentError * scale : parentError;
238
+ }
239
+ }
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Turning three flag buffers into the one list a draw can read, and the block that draws it.
3
+ *
4
+ * **The culls answer per cluster and a draw wants a list.** `lodCut` writes a flag, `cullClusters`
5
+ * writes a flag, and the history is a flag — all three addressed by cluster identifier, because a
6
+ * dispatch decides every cluster at once and cannot append in input order without a prefix sum.
7
+ * The compaction is the step that turns the three into `[4, 17, 18, 40]` and says how many there
8
+ * are, which is what `instanceCount` in an indirect block is.
9
+ *
10
+ * **Order is not part of the answer.** The device appends with an atomic and the arrival order is
11
+ * whatever the scheduler did; this reference appends in cluster order because a loop has one. So a
12
+ * check comparing the two sorts both — the same rule `cullClusters.ts` avoids needing by answering
13
+ * in flags, and the reason the compaction is the one stage that cannot.
14
+ *
15
+ * **Four words, not `INDIRECT_DRAW_WORDS`' five, and that is the plan being wrong rather than a
16
+ * shortcut.** The plan has this as `drawIndexedIndirect`. It cannot be: one block covers every
17
+ * surviving cluster, each cluster's indices begin somewhere different, and an indexed draw has one
18
+ * `firstIndex` for the whole block. So the raster shader fetches indices out of a storage buffer
19
+ * itself — `visbufferRaster.wgsl.ts` already does — and what is issued is the non-indexed
20
+ * `drawIndirect`, whose block is vertex count, instances, first vertex, first instance. The five
21
+ * word block would *happen* to work, because its extra words are zero where the four-word one
22
+ * wants zero; that is a coincidence and not a layout.
23
+ *
24
+ * **The history is per cluster here rather than the bit set `twoPhase.ts` holds.** A shader
25
+ * addressing a bit needs a shift and a mask around every read and write, and the atomic that
26
+ * records this frame's would be a read-modify-write on a word shared by thirty-two clusters. One
27
+ * word a cluster is the device's shape; the bit set is the processor's, and they answer the same
28
+ * question.
29
+ */
30
+
31
+ import { CLUSTER_INDEX_CAP } from './indirect.ts';
32
+
33
+ /** Words in a non-indexed `drawIndirect` block: vertex count, instances, first vertex, first instance. */
34
+ export const DRAW_INDIRECT_WORDS = 4;
35
+
36
+ /**
37
+ * Which half of the frame a compaction is building.
38
+ *
39
+ * Phase one draws what the history holds; phase two draws what it does not. **Never "whether" —
40
+ * every live cluster is in exactly one of the two lists**, which is the invariant `twoPhase.ts`
41
+ * exists to state and the reason a history that is entirely stale costs a test rather than a hole.
42
+ */
43
+ export const PHASE_ONE = 0;
44
+ export const PHASE_TWO = 1;
45
+ export type ClusterPhase = typeof PHASE_ONE | typeof PHASE_TWO;
46
+
47
+ /**
48
+ * Append every cluster this phase draws, and write the block that draws them.
49
+ *
50
+ * **`drawn` is next frame's history, and only phase two writes it** — for every cluster, seen last
51
+ * frame or not, from its own cull, which is the only one that tested against this frame's pyramid.
52
+ * That is `updateHistory` in `twoPhase.ts`: the history is replaced, not accumulated. Phase one's
53
+ * cull has no pyramid, so a cluster it keeps may be hidden; marking it there kept every cluster ever
54
+ * seen drawn for as long as it stayed in the frustum. The caller clears `drawn` once a frame.
55
+ */
56
+ export function compactPhase(
57
+ phase: ClusterPhase,
58
+ selected: Uint32Array,
59
+ keep: Uint32Array,
60
+ history: Uint32Array,
61
+ count: number,
62
+ list: Uint32Array,
63
+ drawn: Uint32Array,
64
+ args: Uint32Array,
65
+ ): number {
66
+ let at = 0;
67
+ for (let cluster = 0; cluster < count; cluster += 1) {
68
+ const seen = (history[cluster] ?? 0) !== 0;
69
+ const visible = (selected[cluster] ?? 0) !== 0 && (keep[cluster] ?? 0) !== 0;
70
+ if (phase === PHASE_TWO && visible) drawn[cluster] = 1;
71
+ if (seen !== (phase === PHASE_ONE) || !visible) continue;
72
+ list[at] = cluster;
73
+ at += 1;
74
+ }
75
+ args[0] = CLUSTER_INDEX_CAP;
76
+ args[1] = at;
77
+ args[2] = 0;
78
+ args[3] = 0;
79
+ return at;
80
+ }
@@ -0,0 +1,333 @@
1
+ /**
2
+ * Which clusters are worth rasterising: the three tests, in the order that makes them cheap.
3
+ *
4
+ * Frustum first, because it is six dot products and throws away most of a world. Then the normal
5
+ * cone, which is one more dot product and throws away roughly half of what is left on a closed
6
+ * surface. Then the hierarchical depth buffer, which costs a projection and four texel reads and
7
+ * is the only one that needs anything from this frame.
8
+ *
9
+ * **Every one of them is conservative in the same direction.** A cluster that might be visible
10
+ * survives; only a cluster that provably cannot be seen is dropped. Culling something visible is a
11
+ * hole in the picture that comes and goes with the camera and is diagnosed as almost anything
12
+ * else; keeping something invisible is a wasted draw. `frustum.ts` says the same in its own words
13
+ * and the two files have to agree about it, because a pipeline whose stages disagree about which
14
+ * way to round is one that drops geometry at exactly the boundaries.
15
+ *
16
+ * **The compaction is here and the flags are the device's.** A dispatch can decide every cluster
17
+ * in parallel and cannot append them in order without a prefix sum, so the shader writes one flag
18
+ * per cluster and this writes the ordered list from the flags. That split is also what
19
+ * `scripts/gpu-parity.mjs` compares: the flags are order-free, so they can be checked
20
+ * element-by-element against a reference that has not had to reproduce an atomic's arrival order.
21
+ */
22
+
23
+ import { sphereOutsideFrustum } from './frustum.ts';
24
+ import { hzbOccluded } from './hzb.ts';
25
+
26
+ /** Eight floats a cluster: centre, radius, cone axis, cone cutoff. `clusterUpload.ts` packs them. */
27
+ export const CLUSTER_CULL_FLOATS = 8;
28
+
29
+ /** What a kept cluster's flag holds. One rather than a bit mask: there is one reason to keep. */
30
+ export const CLUSTER_KEEP = 1;
31
+
32
+ /** A depth pyramid as a dispatch binds it: one flat buffer, one offset a level. */
33
+ export interface ClusterHzb {
34
+ /** Every level's texels, coarsest last, as `hzbReduce` leaves them. */
35
+ readonly depths: Float32Array;
36
+ /** Where each level starts in `depths`. Its length is the level count. */
37
+ readonly offsets: Int32Array;
38
+ /** The base level's dimensions. Every level below is halved, never past one. */
39
+ readonly width: number;
40
+ readonly height: number;
41
+ /** The camera the pyramid was built with, which is this frame's in a two-phase pass. */
42
+ readonly viewProj: Float32Array;
43
+ }
44
+
45
+ /**
46
+ * A cluster's cone as it is carried, turned into the cutoff `coneBackfacing` reads.
47
+ *
48
+ * **A cluster carries a cosine and this test is written in a sine**, and until 2026-09-18 nothing
49
+ * said so. `buildClusters` writes "the minimum dot product of any face normal against the axis",
50
+ * the `MSHL` container stores that and says so, and `ClusterSource` passed it through to the test
51
+ * below untouched. Every cluster before then was a single quad or a near-flat patch, whose cosine
52
+ * of about one reads as a sine of one — a hemisphere, never culled — so the mismatch was silent and
53
+ * cone culling simply did not happen. The voxel sandbox grows its clusters by position, across
54
+ * every direction a block faces: a cosine of zero read as a sine of zero is a flat cluster, and a
55
+ * canopy seen from below lost every block whose cluster pointed up.
56
+ *
57
+ * `cos(a)` at or above zero is `sin(a) = sqrt(1 - cos(a)^2)`. Below zero the cone is wider than a
58
+ * hemisphere and no aim of the eye sees every face from behind, so the answer is one, which the
59
+ * test can never satisfy. Converted where a cluster is packed, once, so neither shader nor
60
+ * reference changes.
61
+ */
62
+ export function cullCutoff(minimumDot: number): number {
63
+ if (minimumDot < 0) return 1;
64
+ return Math.sqrt(Math.max(0, 1 - minimumDot * minimumDot));
65
+ }
66
+
67
+ /**
68
+ * Whether every triangle in a cluster faces away from the eye.
69
+ *
70
+ * **`cutoff` is `sin(a)`**, where `a` is the half-angle of the cone containing every normal. Zero
71
+ * is a flat cluster; one is a cluster whose normals span a hemisphere, which can never be culled
72
+ * because the test below cannot be satisfied. Stating it as a sine rather than a cosine is what
73
+ * makes the expression a single comparison with no trigonometry in the shader.
74
+ *
75
+ * The derivation, because the constant is otherwise unfalsifiable. Let `d` be the vector from the
76
+ * eye to the cluster's centre. Directions from the eye to points on the cluster lie within
77
+ * `b = asin(radius / |d|)` of `d`. A triangle is backfacing when its normal has a positive dot
78
+ * with the direction the eye looks along it, so *every* triangle is backfacing when
79
+ * `angle(axis, d) + a + b < 90 degrees`, which is `dot(axis, normalise(d)) > sin(a + b)`.
80
+ * Expanding `sin(a + b)` to `sin a + radius / |d|` is exact at `a = 0` and errs on the side of
81
+ * keeping a cluster everywhere else, which is the direction this file rounds.
82
+ *
83
+ * Multiplied through by `|d|` so there is no divide and no normalise.
84
+ */
85
+ export function coneBackfacing(
86
+ axisX: number,
87
+ axisY: number,
88
+ axisZ: number,
89
+ cutoff: number,
90
+ centreX: number,
91
+ centreY: number,
92
+ centreZ: number,
93
+ radius: number,
94
+ eyeX: number,
95
+ eyeY: number,
96
+ eyeZ: number,
97
+ ): boolean {
98
+ const dx = centreX - eyeX;
99
+ const dy = centreY - eyeY;
100
+ const dz = centreZ - eyeZ;
101
+ const distance = Math.sqrt(dx * dx + dy * dy + dz * dz);
102
+ return dx * axisX + dy * axisY + dz * axisZ >= cutoff * distance + radius;
103
+ }
104
+
105
+ /**
106
+ * The screen rectangle a sphere covers and the nearest clip depth it reaches.
107
+ *
108
+ * Writes `minX, minY, maxX, maxY, nearestDepth` into `out`, in the [0, 1] range a texture
109
+ * coordinate uses, and answers **false when the sphere has no rectangle at all** — when any part
110
+ * of it is at or behind the eye. That case is the reason this returns a boolean rather than
111
+ * clamping: a sphere straddling the near plane does not project to a rectangle, and every
112
+ * implementation that pretends it does culls it on the frames where the wrap happens to land
113
+ * outside the screen and keeps it on the frames where it does not. What that looks like is
114
+ * geometry flickering as the camera moves through it, which is the defect this function's shape
115
+ * exists to prevent.
116
+ *
117
+ * The eight corners of the sphere's bounding box rather than an analytic silhouette: the box is
118
+ * larger than the sphere, so the rectangle is conservative, and it is eight transforms instead of
119
+ * a quadratic.
120
+ */
121
+ export function sphereScreenBounds(
122
+ viewProj: Float32Array,
123
+ centreX: number,
124
+ centreY: number,
125
+ centreZ: number,
126
+ radius: number,
127
+ out: Float32Array,
128
+ ): boolean {
129
+ let minX = Number.POSITIVE_INFINITY;
130
+ let minY = Number.POSITIVE_INFINITY;
131
+ let maxX = Number.NEGATIVE_INFINITY;
132
+ let maxY = Number.NEGATIVE_INFINITY;
133
+ let nearest = Number.NEGATIVE_INFINITY;
134
+
135
+ for (let corner = 0; corner < 8; corner += 1) {
136
+ const x = centreX + (corner & 1 ? radius : -radius);
137
+ const y = centreY + (corner & 2 ? radius : -radius);
138
+ const z = centreZ + (corner & 4 ? radius : -radius);
139
+ const cw =
140
+ (viewProj[3] as number) * x +
141
+ (viewProj[7] as number) * y +
142
+ (viewProj[11] as number) * z +
143
+ (viewProj[15] as number);
144
+ /* At or behind the eye, so there is no rectangle. See the header. */
145
+ if (cw <= 1e-6) return false;
146
+ const cx =
147
+ (viewProj[0] as number) * x +
148
+ (viewProj[4] as number) * y +
149
+ (viewProj[8] as number) * z +
150
+ (viewProj[12] as number);
151
+ const cy =
152
+ (viewProj[1] as number) * x +
153
+ (viewProj[5] as number) * y +
154
+ (viewProj[9] as number) * z +
155
+ (viewProj[13] as number);
156
+ const cz =
157
+ (viewProj[2] as number) * x +
158
+ (viewProj[6] as number) * y +
159
+ (viewProj[10] as number) * z +
160
+ (viewProj[14] as number);
161
+ const inverse = 1 / cw;
162
+ const u = cx * inverse * 0.5 + 0.5;
163
+ const v = cy * inverse * 0.5 + 0.5;
164
+ minX = Math.min(minX, u);
165
+ maxX = Math.max(maxX, u);
166
+ minY = Math.min(minY, v);
167
+ maxY = Math.max(maxY, v);
168
+ /* Nearest is the *largest* clip depth here; `depthConvention.ts` puts the near plane at 1. */
169
+ nearest = Math.max(nearest, cz * inverse);
170
+ }
171
+
172
+ out[0] = minX;
173
+ out[1] = minY;
174
+ out[2] = maxX;
175
+ out[3] = maxY;
176
+ out[4] = nearest;
177
+ return true;
178
+ }
179
+
180
+ /**
181
+ * The pyramid level whose texels are wide enough that four of them cover the rectangle.
182
+ *
183
+ * `ceil(log2(longest side in texels))` minus one, floored at zero: at that level the rectangle
184
+ * spans at most two texels each way, which is what a four-texel gather reads. One level coarser
185
+ * would read a texel covering geometry outside the rectangle and cull against an occluder that is
186
+ * not in front of this cluster at all.
187
+ *
188
+ * Clamped to the top of the pyramid, because a rectangle larger than the screen is a cluster the
189
+ * camera is inside and there is no level coarse enough for it.
190
+ */
191
+ export function hzbLevelFor(
192
+ rectWidthTexels: number,
193
+ rectHeightTexels: number,
194
+ width: number,
195
+ height: number,
196
+ ): number {
197
+ const longest = Math.max(rectWidthTexels, rectHeightTexels, 1);
198
+ const level = Math.max(0, Math.ceil(Math.log2(longest)) - 1);
199
+ const top = Math.floor(Math.log2(Math.max(1, Math.max(width, height))));
200
+ return Math.min(level, top);
201
+ }
202
+
203
+ /**
204
+ * Whether a rectangle's nearest depth is behind everything already drawn under it.
205
+ *
206
+ * **The weakest of the four texels decides.** Each holds the furthest depth in its own region, so
207
+ * a cluster is hidden only where it is behind *all* of them — and under reversed-Z the furthest of
208
+ * four is the smallest. Taking the nearest instead would cull a cluster visible through a gap,
209
+ * which is the failure mode that reads as holes appearing in geometry as the camera turns.
210
+ */
211
+ export function clusterOccluded(
212
+ depths: Float32Array,
213
+ offsets: Int32Array,
214
+ width: number,
215
+ height: number,
216
+ minX: number,
217
+ minY: number,
218
+ maxX: number,
219
+ maxY: number,
220
+ nearestDepth: number,
221
+ ): boolean {
222
+ const level = hzbLevelFor((maxX - minX) * width, (maxY - minY) * height, width, height);
223
+ if (level >= offsets.length) return false;
224
+ const levelWidth = Math.max(1, width >> level);
225
+ const levelHeight = Math.max(1, height >> level);
226
+ const base = offsets[level] as number;
227
+
228
+ const x0 = Math.max(0, Math.min(levelWidth - 1, Math.floor(minX * levelWidth)));
229
+ const x1 = Math.max(0, Math.min(levelWidth - 1, Math.floor(maxX * levelWidth)));
230
+ /*
231
+ * **The rows run the other way, and the swap is not cosmetic.** `sphereScreenBounds` reports the
232
+ * rectangle in clip-up coordinates — `v` of 1 is clip `y` of +1 — and the pyramid is seeded
233
+ * straight from the depth texture, whose row 0 is exactly there. So a `v` used as a row index
234
+ * reads the mirror of what the cluster covers, and the top edge of the rectangle becomes the
235
+ * bottom row.
236
+ *
237
+ * It shipped that way and drew holes: geometry near the top of the frame tested against whatever
238
+ * stood near the bottom, coming and going with the camera. `gpu-parity.mjs` agreed about it for
239
+ * as long, because the WGSL made the same mistake — a parity check compares two implementations
240
+ * and neither of them is the truth.
241
+ */
242
+ const y0 = Math.max(0, Math.min(levelHeight - 1, Math.floor((1 - maxY) * levelHeight)));
243
+ const y1 = Math.max(0, Math.min(levelHeight - 1, Math.floor((1 - minY) * levelHeight)));
244
+
245
+ let furthest = Number.POSITIVE_INFINITY;
246
+ for (let y = y0; y <= y1; y += 1) {
247
+ for (let x = x0; x <= x1; x += 1) {
248
+ furthest = Math.min(furthest, depths[base + y * levelWidth + x] as number);
249
+ }
250
+ }
251
+ return hzbOccluded(nearestDepth, furthest);
252
+ }
253
+
254
+ /**
255
+ * Every test, over every cluster: one flag each, and the kept indices in input order.
256
+ *
257
+ * `selected`, where given, is the cut's flag a cluster: a cluster it did not select is not tested
258
+ * and not kept, which is how an instance cull reaches the clusters of a mesh it hid. Absent, every
259
+ * cluster is judged.
260
+ *
261
+ * `hzb` is null in the first phase of a two-phase pass, where there is no depth from this frame to
262
+ * test against yet — and null rather than an empty pyramid, because an empty one is a pyramid full
263
+ * of zeroes and under reversed-Z zero is the far plane, which occludes nothing and would look like
264
+ * the test running when it is not.
265
+ *
266
+ * Returns how many survived, which is also what an indirect draw's instance count wants.
267
+ */
268
+ export function cullClusters(
269
+ clusters: Float32Array,
270
+ planes: Float32Array,
271
+ eyeX: number,
272
+ eyeY: number,
273
+ eyeZ: number,
274
+ hzb: ClusterHzb | null,
275
+ keep: Uint32Array,
276
+ list: Uint32Array,
277
+ selected: ArrayLike<number> | null = null,
278
+ /** False for the blended run, whose raster draws both sides. See `CULL_CONES`. */
279
+ cones = true,
280
+ ): number {
281
+ const bounds = new Float32Array(5);
282
+ const count = Math.floor(clusters.length / CLUSTER_CULL_FLOATS);
283
+ let kept = 0;
284
+
285
+ for (let i = 0; i < count; i += 1) {
286
+ if (selected !== null && selected[i] === 0) {
287
+ keep[i] = 0;
288
+ continue;
289
+ }
290
+ const at = i * CLUSTER_CULL_FLOATS;
291
+ const cx = clusters[at] as number;
292
+ const cy = clusters[at + 1] as number;
293
+ const cz = clusters[at + 2] as number;
294
+ const radius = clusters[at + 3] as number;
295
+
296
+ let survives = !sphereOutsideFrustum(planes, cx, cy, cz, radius);
297
+ if (survives && cones) {
298
+ survives = !coneBackfacing(
299
+ clusters[at + 4] as number,
300
+ clusters[at + 5] as number,
301
+ clusters[at + 6] as number,
302
+ clusters[at + 7] as number,
303
+ cx,
304
+ cy,
305
+ cz,
306
+ radius,
307
+ eyeX,
308
+ eyeY,
309
+ eyeZ,
310
+ );
311
+ }
312
+ if (survives && hzb !== null && sphereScreenBounds(hzb.viewProj, cx, cy, cz, radius, bounds)) {
313
+ survives = !clusterOccluded(
314
+ hzb.depths,
315
+ hzb.offsets,
316
+ hzb.width,
317
+ hzb.height,
318
+ bounds[0] as number,
319
+ bounds[1] as number,
320
+ bounds[2] as number,
321
+ bounds[3] as number,
322
+ bounds[4] as number,
323
+ );
324
+ }
325
+
326
+ keep[i] = survives ? CLUSTER_KEEP : 0;
327
+ if (survives) {
328
+ list[kept] = i;
329
+ kept += 1;
330
+ }
331
+ }
332
+ return kept;
333
+ }