@driftengine/core 4.4.2 → 4.6.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 (339) hide show
  1. package/CHANGELOG.json +97 -0
  2. package/README.md +1 -1
  3. package/dist/geometry/boxCsg.d.ts +28 -0
  4. package/dist/geometry/boxCsg.js +139 -0
  5. package/dist/geometry/csg.d.ts +36 -0
  6. package/dist/geometry/csg.js +329 -0
  7. package/dist/geometry/meshBuilder.js +13 -0
  8. package/dist/geometry/meshConcat.js +1 -0
  9. package/dist/geometry/pixelFont.js +2 -0
  10. package/dist/geometry/solid.d.ts +61 -0
  11. package/dist/geometry/solid.js +152 -0
  12. package/dist/geometry/solidFlat.d.ts +35 -0
  13. package/dist/geometry/solidFlat.js +428 -0
  14. package/dist/geometry/solidHarness.d.ts +19 -0
  15. package/dist/geometry/solidHarness.js +66 -0
  16. package/dist/geometry/solidNormals.d.ts +17 -0
  17. package/dist/geometry/solidNormals.js +86 -0
  18. package/dist/geometry/solidRound.d.ts +41 -0
  19. package/dist/geometry/solidRound.js +323 -0
  20. package/dist/geometry/solidWriter.d.ts +16 -0
  21. package/dist/geometry/solidWriter.js +42 -0
  22. package/dist/index.d.ts +28 -3
  23. package/dist/index.js +20 -1
  24. package/dist/render/backend/api.d.ts +34 -14
  25. package/dist/render/backend/drawFog.d.ts +26 -0
  26. package/dist/render/backend/drawFog.js +30 -0
  27. package/dist/render/backend/materialChanges.d.ts +2 -1
  28. package/dist/render/backend/materialChanges.js +6 -1
  29. package/dist/render/backend/webgl2/glassTintPrograms.d.ts +13 -0
  30. package/dist/render/backend/webgl2/glassTintPrograms.js +38 -0
  31. package/dist/render/backend/webgl2/instanced.d.ts +24 -5
  32. package/dist/render/backend/webgl2/instanced.js +72 -15
  33. package/dist/render/backend/webgl2/renderer.d.ts +201 -16
  34. package/dist/render/backend/webgl2/renderer.js +596 -224
  35. package/dist/render/backend/webgpu/buffers.d.ts +29 -1
  36. package/dist/render/backend/webgpu/buffers.js +61 -4
  37. package/dist/render/backend/webgpu/depthPass.d.ts +1 -1
  38. package/dist/render/backend/webgpu/depthPass.js +7 -2
  39. package/dist/render/backend/webgpu/drawCommand.d.ts +5 -0
  40. package/dist/render/backend/webgpu/drawCommand.js +1 -0
  41. package/dist/render/backend/webgpu/flatPass.d.ts +6 -1
  42. package/dist/render/backend/webgpu/flatPass.js +57 -9
  43. package/dist/render/backend/webgpu/glassTintPass.d.ts +28 -0
  44. package/dist/render/backend/webgpu/glassTintPass.js +206 -0
  45. package/dist/render/backend/webgpu/gpuDrivenPass.d.ts +60 -1
  46. package/dist/render/backend/webgpu/gpuDrivenPass.js +305 -5
  47. package/dist/render/backend/webgpu/instanceCullPass.d.ts +34 -0
  48. package/dist/render/backend/webgpu/instanceCullPass.js +144 -0
  49. package/dist/render/backend/webgpu/instanced.d.ts +23 -2
  50. package/dist/render/backend/webgpu/instanced.js +41 -4
  51. package/dist/render/backend/webgpu/latePass.d.ts +30 -0
  52. package/dist/render/backend/webgpu/latePass.js +140 -0
  53. package/dist/render/backend/webgpu/lightVolumePass.js +5 -1
  54. package/dist/render/backend/webgpu/motionPass.d.ts +95 -0
  55. package/dist/render/backend/webgpu/motionPass.js +491 -0
  56. package/dist/render/backend/webgpu/pipelineCache.d.ts +10 -1
  57. package/dist/render/backend/webgpu/pipelineCache.js +11 -1
  58. package/dist/render/backend/webgpu/pointGlassTintPass.d.ts +32 -0
  59. package/dist/render/backend/webgpu/pointGlassTintPass.js +193 -0
  60. package/dist/render/backend/webgpu/pointShadowPass.d.ts +35 -6
  61. package/dist/render/backend/webgpu/pointShadowPass.js +105 -20
  62. package/dist/render/backend/webgpu/postPass.d.ts +3 -13
  63. package/dist/render/backend/webgpu/renderer.d.ts +271 -64
  64. package/dist/render/backend/webgpu/renderer.js +1395 -580
  65. package/dist/render/backend/webgpu/scatterPass.js +2 -1
  66. package/dist/render/backend/webgpu/sdfTextPass.js +6 -1
  67. package/dist/render/backend/webgpu/shaders/instanceCull.wgsl.d.ts +18 -0
  68. package/dist/render/backend/webgpu/shaders/instanceCull.wgsl.js +59 -0
  69. package/dist/render/backend/webgpu/skinPaletteRing.d.ts +4 -0
  70. package/dist/render/backend/webgpu/skinPaletteRing.js +4 -0
  71. package/dist/render/backend/webgpu/snapshotMips.d.ts +29 -0
  72. package/dist/render/backend/webgpu/snapshotMips.js +52 -0
  73. package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +22 -3
  74. package/dist/render/backend/webgpu/surfaceTexturePass.js +78 -39
  75. package/dist/render/driftLight/bake.d.ts +7 -0
  76. package/dist/render/driftLight/bake.js +61 -56
  77. package/dist/render/driftLight/denseField.d.ts +46 -0
  78. package/dist/render/driftLight/denseField.js +109 -0
  79. package/dist/render/driftLight/lightField.d.ts +10 -5
  80. package/dist/render/driftLight/lightField.js +25 -19
  81. package/dist/render/driftLight/presence.d.ts +50 -0
  82. package/dist/render/driftLight/presence.js +43 -0
  83. package/dist/render/driftLight/uniforms.d.ts +3 -3
  84. package/dist/render/driftLight/uniforms.js +5 -6
  85. package/dist/render/driftLight/worldLightField.d.ts +41 -0
  86. package/dist/render/driftLight/worldLightField.js +66 -0
  87. package/dist/render/fixtureAtlas.d.ts +20 -0
  88. package/dist/render/fixtureAtlas.js +49 -0
  89. package/dist/render/glass.d.ts +45 -0
  90. package/dist/render/glass.js +39 -0
  91. package/dist/render/glassCasters.d.ts +40 -0
  92. package/dist/render/glassCasters.js +84 -0
  93. package/dist/render/glassShadow.d.ts +78 -0
  94. package/dist/render/glassShadow.js +125 -0
  95. package/dist/render/globalMediumPass.d.ts +2 -3
  96. package/dist/render/globalMediumPass.js +2 -8
  97. package/dist/render/gpudriven/lit.d.ts +12 -0
  98. package/dist/render/gpudriven/lit.js +23 -0
  99. package/dist/render/gpudriven/materialTable.d.ts +15 -5
  100. package/dist/render/gpudriven/materialTable.js +26 -6
  101. package/dist/render/gpudriven/pipeline.d.ts +1 -1
  102. package/dist/render/gpudriven/pipeline.js +4 -3
  103. package/dist/render/gpudriven/sunGlass.d.ts +35 -0
  104. package/dist/render/gpudriven/sunGlass.js +89 -0
  105. package/dist/render/instanceCull.d.ts +63 -0
  106. package/dist/render/instanceCull.js +160 -0
  107. package/dist/render/instances.d.ts +15 -0
  108. package/dist/render/lightBudget.d.ts +36 -35
  109. package/dist/render/lightBudget.js +38 -37
  110. package/dist/render/lightGrid.d.ts +42 -0
  111. package/dist/render/lightGrid.js +87 -0
  112. package/dist/render/mesh.d.ts +18 -24
  113. package/dist/render/mesh.js +51 -68
  114. package/dist/render/occlusion.d.ts +10 -0
  115. package/dist/render/occlusion.js +36 -7
  116. package/dist/render/pointGlassTint.d.ts +59 -0
  117. package/dist/render/pointGlassTint.js +198 -0
  118. package/dist/render/pointLightSelection.d.ts +7 -0
  119. package/dist/render/pointLightSelection.js +29 -4
  120. package/dist/render/pointShadowArray.d.ts +56 -3
  121. package/dist/render/pointShadowArray.js +127 -16
  122. package/dist/render/pointShadowMap.d.ts +23 -1
  123. package/dist/render/pointShadowMap.js +27 -1
  124. package/dist/render/rainField.d.ts +13 -0
  125. package/dist/render/rainField.js +27 -5
  126. package/dist/render/recon/changeFrames.d.ts +15 -0
  127. package/dist/render/recon/changeFrames.js +21 -0
  128. package/dist/render/recon/lateDepth.d.ts +11 -0
  129. package/dist/render/recon/lateDepth.js +70 -0
  130. package/dist/render/recon/lateRouting.d.ts +14 -0
  131. package/dist/render/recon/lateRouting.js +18 -0
  132. package/dist/render/recon/mover.d.ts +57 -0
  133. package/dist/render/recon/mover.js +86 -0
  134. package/dist/render/recon/resolve.d.ts +34 -4
  135. package/dist/render/recon/resolve.js +161 -20
  136. package/dist/render/renderQuality.d.ts +35 -0
  137. package/dist/render/renderQuality.js +7 -0
  138. package/dist/render/rendererHarness.js +16 -0
  139. package/dist/render/sceneTarget.d.ts +9 -1
  140. package/dist/render/sceneTarget.js +60 -12
  141. package/dist/render/shaders/depth.d.ts +16 -1
  142. package/dist/render/shaders/depth.js +94 -15
  143. package/dist/render/shaders/flat/directionalShadow.js +91 -6
  144. package/dist/render/shaders/flat/driftLight.d.ts +1 -1
  145. package/dist/render/shaders/flat/driftLight.js +32 -7
  146. package/dist/render/shaders/flat/glassTint.d.ts +6 -0
  147. package/dist/render/shaders/flat/glassTint.js +25 -0
  148. package/dist/render/shaders/flat/index.d.ts +13 -0
  149. package/dist/render/shaders/flat/index.js +9 -3
  150. package/dist/render/shaders/flat/lampGlass.d.ts +1 -0
  151. package/dist/render/shaders/flat/lampGlass.js +183 -0
  152. package/dist/render/shaders/flat/main.d.ts +1 -1
  153. package/dist/render/shaders/flat/main.js +190 -38
  154. package/dist/render/shaders/flat/pointShadow.d.ts +1 -1
  155. package/dist/render/shaders/flat/pointShadow.js +2 -0
  156. package/dist/render/shaders/flat/preamble.js +96 -26
  157. package/dist/render/shaders/flat/surfaceEffects.d.ts +29 -0
  158. package/dist/render/shaders/flat/surfaceEffects.js +152 -0
  159. package/dist/render/shaders/generated/depth.wgsl.d.ts +230 -7
  160. package/dist/render/shaders/generated/depth.wgsl.js +230 -7
  161. package/dist/render/shaders/generated/flat.wgsl.d.ts +1644 -1596
  162. package/dist/render/shaders/generated/flat.wgsl.js +1683 -1621
  163. package/dist/render/shaders/generated/globalMedium.wgsl.d.ts +3 -13
  164. package/dist/render/shaders/generated/globalMedium.wgsl.js +3 -13
  165. package/dist/render/shaders/generated/lightVolume.wgsl.d.ts +3 -13
  166. package/dist/render/shaders/generated/lightVolume.wgsl.js +21 -17
  167. package/dist/render/shaders/generated/octahedralResolve.wgsl.d.ts +29 -0
  168. package/dist/render/shaders/generated/octahedralResolve.wgsl.js +29 -0
  169. package/dist/render/shaders/generated/rush.wgsl.d.ts +21 -16
  170. package/dist/render/shaders/generated/rush.wgsl.js +21 -16
  171. package/dist/render/shaders/generated/sdfText.wgsl.d.ts +2 -2
  172. package/dist/render/shaders/generated/sdfText.wgsl.js +2 -2
  173. package/dist/render/shaders/generated/text.wgsl.d.ts +1 -1
  174. package/dist/render/shaders/generated/text.wgsl.js +1 -1
  175. package/dist/render/shaders/gi/driftLight.wgsl.d.ts +1 -1
  176. package/dist/render/shaders/gi/driftLight.wgsl.js +27 -4
  177. package/dist/render/shaders/globalMedium.js +6 -6
  178. package/dist/render/shaders/gpudriven/blendRaster.wgsl.d.ts +7 -0
  179. package/dist/render/shaders/gpudriven/blendRaster.wgsl.js +86 -1
  180. package/dist/render/shaders/gpudriven/cull.wgsl.d.ts +1 -1
  181. package/dist/render/shaders/gpudriven/cull.wgsl.js +11 -2
  182. package/dist/render/shaders/gpudriven/materialTable.wgsl.d.ts +1 -1
  183. package/dist/render/shaders/gpudriven/materialTable.wgsl.js +3 -1
  184. package/dist/render/shaders/gpudriven/shade.wgsl.js +50 -1
  185. package/dist/render/shaders/gpudriven/sunGlass.wgsl.d.ts +9 -0
  186. package/dist/render/shaders/gpudriven/sunGlass.wgsl.js +133 -0
  187. package/dist/render/shaders/lightVolume.js +6 -6
  188. package/dist/render/shaders/octahedralResolve.d.ts +8 -0
  189. package/dist/render/shaders/octahedralResolve.js +48 -17
  190. package/dist/render/shaders/recon/lateDepth.wgsl.d.ts +1 -0
  191. package/dist/render/shaders/recon/lateDepth.wgsl.js +69 -0
  192. package/dist/render/shaders/recon/motion.wgsl.d.ts +1 -1
  193. package/dist/render/shaders/recon/motion.wgsl.js +84 -0
  194. package/dist/render/shaders/recon/resolve.wgsl.d.ts +8 -7
  195. package/dist/render/shaders/recon/resolve.wgsl.js +161 -27
  196. package/dist/render/shaders/rush.js +11 -1
  197. package/dist/render/shaders/sdfText.d.ts +1 -1
  198. package/dist/render/shaders/sdfText.js +3 -2
  199. package/dist/render/shaders/text.d.ts +1 -1
  200. package/dist/render/shaders/text.js +18 -9
  201. package/dist/render/shadowCasters.d.ts +20 -1
  202. package/dist/render/shadowCasters.js +15 -1
  203. package/dist/render/shadowMap.d.ts +54 -5
  204. package/dist/render/shadowMap.js +190 -36
  205. package/dist/render/sunGlassTint.d.ts +25 -0
  206. package/dist/render/sunGlassTint.js +79 -0
  207. package/dist/render/surfaceEffects.d.ts +83 -0
  208. package/dist/render/surfaceEffects.js +75 -0
  209. package/dist/render/surfaceScene.d.ts +8 -0
  210. package/dist/render/surfaceScene.js +8 -0
  211. package/dist/render/surfaceTexture.d.ts +18 -2
  212. package/dist/render/surfaceTexture.js +68 -15
  213. package/dist/render/temporalAa.d.ts +25 -6
  214. package/dist/render/temporalAa.js +45 -15
  215. package/dist/render/textureSource.d.ts +30 -0
  216. package/dist/render/textureSource.js +53 -0
  217. package/dist/render/translucentQueue.d.ts +6 -0
  218. package/dist/render/translucentQueue.js +15 -1
  219. package/dist/render/vertexDefaults.js +2 -0
  220. package/dist/world/hlod.d.ts +104 -0
  221. package/dist/world/hlod.js +214 -0
  222. package/package.json +6 -6
  223. package/src/geometry/boxCsg.ts +186 -0
  224. package/src/geometry/csg.ts +384 -0
  225. package/src/geometry/meshBuilder.ts +15 -0
  226. package/src/geometry/meshConcat.ts +1 -0
  227. package/src/geometry/pixelFont.ts +2 -0
  228. package/src/geometry/solid.ts +189 -0
  229. package/src/geometry/solidFlat.ts +522 -0
  230. package/src/geometry/solidHarness.ts +83 -0
  231. package/src/geometry/solidNormals.ts +103 -0
  232. package/src/geometry/solidRound.ts +443 -0
  233. package/src/geometry/solidWriter.ts +58 -0
  234. package/src/index.ts +61 -1
  235. package/src/render/backend/api.ts +36 -11
  236. package/src/render/backend/drawFog.ts +31 -0
  237. package/src/render/backend/materialChanges.ts +9 -2
  238. package/src/render/backend/webgl2/glassTintPrograms.ts +58 -0
  239. package/src/render/backend/webgl2/instanced.ts +80 -14
  240. package/src/render/backend/webgl2/renderer.ts +700 -250
  241. package/src/render/backend/webgpu/buffers.ts +92 -5
  242. package/src/render/backend/webgpu/depthPass.ts +7 -2
  243. package/src/render/backend/webgpu/drawCommand.ts +6 -0
  244. package/src/render/backend/webgpu/flatPass.ts +62 -11
  245. package/src/render/backend/webgpu/glassTintPass.ts +245 -0
  246. package/src/render/backend/webgpu/gpuDrivenPass.ts +349 -5
  247. package/src/render/backend/webgpu/instanceCullPass.ts +165 -0
  248. package/src/render/backend/webgpu/instanced.ts +42 -4
  249. package/src/render/backend/webgpu/latePass.ts +161 -0
  250. package/src/render/backend/webgpu/lightVolumePass.ts +5 -1
  251. package/src/render/backend/webgpu/motionPass.ts +535 -0
  252. package/src/render/backend/webgpu/pipelineCache.ts +12 -1
  253. package/src/render/backend/webgpu/pointGlassTintPass.ts +233 -0
  254. package/src/render/backend/webgpu/pointShadowPass.ts +158 -26
  255. package/src/render/backend/webgpu/renderer.ts +1671 -670
  256. package/src/render/backend/webgpu/scatterPass.ts +2 -1
  257. package/src/render/backend/webgpu/sdfTextPass.ts +6 -1
  258. package/src/render/backend/webgpu/shaders/instanceCull.wgsl.ts +61 -0
  259. package/src/render/backend/webgpu/skinPaletteRing.ts +4 -0
  260. package/src/render/backend/webgpu/snapshotMips.ts +80 -0
  261. package/src/render/backend/webgpu/surfaceTexturePass.ts +91 -51
  262. package/src/render/driftLight/bake.ts +85 -62
  263. package/src/render/driftLight/denseField.ts +149 -0
  264. package/src/render/driftLight/lightField.ts +33 -18
  265. package/src/render/driftLight/presence.ts +69 -0
  266. package/src/render/driftLight/uniforms.ts +8 -9
  267. package/src/render/driftLight/worldLightField.ts +83 -0
  268. package/src/render/fixtureAtlas.ts +63 -0
  269. package/src/render/glass.ts +74 -0
  270. package/src/render/glassCasters.ts +164 -0
  271. package/src/render/glassShadow.ts +153 -0
  272. package/src/render/globalMediumPass.ts +4 -11
  273. package/src/render/gpudriven/lit.ts +31 -0
  274. package/src/render/gpudriven/materialTable.ts +28 -6
  275. package/src/render/gpudriven/pipeline.ts +4 -3
  276. package/src/render/gpudriven/sunGlass.ts +117 -0
  277. package/src/render/instanceCull.ts +205 -0
  278. package/src/render/instances.ts +16 -0
  279. package/src/render/lightBudget.ts +41 -38
  280. package/src/render/lightGrid.ts +130 -0
  281. package/src/render/mesh.ts +59 -72
  282. package/src/render/occlusion.ts +51 -7
  283. package/src/render/pointGlassTint.ts +250 -0
  284. package/src/render/pointLightSelection.ts +72 -4
  285. package/src/render/pointShadowArray.ts +188 -22
  286. package/src/render/pointShadowMap.ts +41 -0
  287. package/src/render/rainField.ts +39 -5
  288. package/src/render/recon/changeFrames.ts +24 -0
  289. package/src/render/recon/lateDepth.ts +83 -0
  290. package/src/render/recon/lateRouting.ts +29 -0
  291. package/src/render/recon/mover.ts +118 -0
  292. package/src/render/recon/resolve.ts +206 -20
  293. package/src/render/renderQuality.ts +47 -0
  294. package/src/render/rendererHarness.ts +16 -0
  295. package/src/render/sceneTarget.ts +62 -11
  296. package/src/render/shaders/depth.ts +118 -15
  297. package/src/render/shaders/flat/directionalShadow.ts +97 -6
  298. package/src/render/shaders/flat/driftLight.ts +32 -7
  299. package/src/render/shaders/flat/glassTint.ts +25 -0
  300. package/src/render/shaders/flat/index.ts +22 -3
  301. package/src/render/shaders/flat/lampGlass.ts +185 -0
  302. package/src/render/shaders/flat/main.ts +190 -38
  303. package/src/render/shaders/flat/pointShadow.ts +2 -0
  304. package/src/render/shaders/flat/preamble.ts +96 -26
  305. package/src/render/shaders/flat/surfaceEffects.ts +152 -0
  306. package/src/render/shaders/generated/depth.wgsl.ts +237 -7
  307. package/src/render/shaders/generated/flat.wgsl.ts +1685 -1621
  308. package/src/render/shaders/generated/globalMedium.wgsl.ts +3 -13
  309. package/src/render/shaders/generated/lightVolume.wgsl.ts +23 -17
  310. package/src/render/shaders/generated/octahedralResolve.wgsl.ts +30 -0
  311. package/src/render/shaders/generated/rush.wgsl.ts +21 -16
  312. package/src/render/shaders/generated/sdfText.wgsl.ts +2 -2
  313. package/src/render/shaders/generated/text.wgsl.ts +1 -1
  314. package/src/render/shaders/gi/driftLight.wgsl.ts +27 -4
  315. package/src/render/shaders/globalMedium.ts +6 -6
  316. package/src/render/shaders/gpudriven/blendRaster.wgsl.ts +88 -1
  317. package/src/render/shaders/gpudriven/cull.wgsl.ts +11 -2
  318. package/src/render/shaders/gpudriven/materialTable.wgsl.ts +3 -1
  319. package/src/render/shaders/gpudriven/shade.wgsl.ts +50 -1
  320. package/src/render/shaders/gpudriven/sunGlass.wgsl.ts +139 -0
  321. package/src/render/shaders/lightVolume.ts +6 -6
  322. package/src/render/shaders/octahedralResolve.ts +50 -17
  323. package/src/render/shaders/recon/lateDepth.wgsl.ts +72 -0
  324. package/src/render/shaders/recon/motion.wgsl.ts +84 -0
  325. package/src/render/shaders/recon/resolve.wgsl.ts +161 -27
  326. package/src/render/shaders/rush.ts +11 -1
  327. package/src/render/shaders/sdfText.ts +3 -2
  328. package/src/render/shaders/text.ts +18 -9
  329. package/src/render/shadowCasters.ts +28 -1
  330. package/src/render/shadowMap.ts +231 -37
  331. package/src/render/sunGlassTint.ts +86 -0
  332. package/src/render/surfaceEffects.ts +132 -0
  333. package/src/render/surfaceScene.ts +16 -0
  334. package/src/render/surfaceTexture.ts +110 -19
  335. package/src/render/temporalAa.ts +78 -14
  336. package/src/render/textureSource.ts +72 -0
  337. package/src/render/translucentQueue.ts +20 -1
  338. package/src/render/vertexDefaults.ts +2 -0
  339. package/src/world/hlod.ts +290 -0
@@ -0,0 +1,198 @@
1
+ import { mat3 } from 'gl-matrix';
2
+ import { createEmptyTexture2D } from './emptyTexture.js';
3
+ import { pointShadowFaceRotation } from './pointShadowImage.js';
4
+ import { compileProgram, uniformLocations } from './shader.js';
5
+ import { OCTAHEDRAL_RESOLVE_TINT_FRAG, OCTAHEDRAL_RESOLVE_VERT, } from './shaders/octahedralResolve.js';
6
+ /** Filled per resolved face, at module scope, because a bake runs inside the frame's budget. */
7
+ const faceRotation = mat3.create();
8
+ const CLEAR = new Float32Array([1, 1, 1, 1]);
9
+ /**
10
+ * What a lamp's light keeps through glass: an RGBA8 array of one layer a light, parallel to the
11
+ * point shadow array and indexed by the light, not by the interleaved depth layer. RGB is the
12
+ * product of every pane's `transmission × (1 − F) × tint`, A the product of their clarities
13
+ * (`glassShadow.ts`).
14
+ *
15
+ * **Drawn a face at a time, as the depth is**: into a face-sized colour scratch cleared to white,
16
+ * with a multiplying blend and no depth test, then resolved into the face's region of the layer by
17
+ * `OCTAHEDRAL_RESOLVE_TINT_FRAG`. One scratch for the whole engine, for the reason
18
+ * `PointShadowArray` gives for its own.
19
+ *
20
+ * **Which faces hold glass is remembered, six bits a layer**, so a face with no glass and none to
21
+ * forget costs nothing, and a face that inherited a previous owner's glass is cleared. **The chain
22
+ * frost reads is rebuilt once a round**, for the layers the round touched, through a 2D staging
23
+ * chain: `generateMipmap` on an array rebuilds every layer, which is the whole pool's texels for one
24
+ * lamp's glass. What that costs is one layer of memory with its chain — 5.6 MB at 1024, 1.4 MB at
25
+ * `'half'` — and a copy out and back per touched layer.
26
+ */
27
+ export class PointGlassTint {
28
+ texture;
29
+ edge;
30
+ layers;
31
+ levels;
32
+ faceSize;
33
+ scratch;
34
+ scratchFramebuffer;
35
+ staging;
36
+ framebuffer;
37
+ readFramebuffer;
38
+ /** Parked on the resolve's unit while the scratch is a render target; see `beginFace`. */
39
+ parked;
40
+ program;
41
+ uniforms;
42
+ vao;
43
+ /** Six bits a layer: which faces' regions hold glass. */
44
+ heldFaces;
45
+ /** Layers whose level 0 changed since the chain was last rebuilt. */
46
+ dirty;
47
+ constructor(gl, faceSize, edge, layers) {
48
+ this.edge = edge;
49
+ this.layers = layers;
50
+ this.faceSize = faceSize;
51
+ this.levels = Math.floor(Math.log2(Math.max(1, edge))) + 1;
52
+ this.heldFaces = new Uint8Array(layers);
53
+ this.dirty = new Uint8Array(layers);
54
+ this.texture = colourTexture(gl, gl.TEXTURE_2D_ARRAY, this.levels, edge, layers);
55
+ this.scratch = colourTexture(gl, gl.TEXTURE_2D, 1, faceSize, 1);
56
+ this.staging = colourTexture(gl, gl.TEXTURE_2D, this.levels, edge, 1);
57
+ this.parked = createEmptyTexture2D(gl);
58
+ this.scratchFramebuffer = framebufferOf(gl);
59
+ this.framebuffer = framebufferOf(gl);
60
+ this.readFramebuffer = framebufferOf(gl);
61
+ gl.bindFramebuffer(gl.FRAMEBUFFER, this.scratchFramebuffer);
62
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.scratch, 0);
63
+ this.program = compileProgram(gl, OCTAHEDRAL_RESOLVE_VERT, OCTAHEDRAL_RESOLVE_TINT_FRAG, 'octahedralResolveTint');
64
+ this.uniforms = uniformLocations(gl, this.program, 'octahedralResolveTint');
65
+ const vao = gl.createVertexArray();
66
+ if (vao === null)
67
+ throw new Error('PointGlassTint: createVertexArray failed');
68
+ this.vao = vao;
69
+ /* Every layer born clear: a light no pane has reached lets everything through. */
70
+ gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
71
+ for (let layer = 0; layer < layers; layer++) {
72
+ gl.framebufferTextureLayer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, this.texture, 0, layer);
73
+ gl.clearBufferfv(gl.COLOR, 0, CLEAR);
74
+ }
75
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
76
+ /* Once, for every layer: white all the way down. */
77
+ gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.texture);
78
+ gl.generateMipmap(gl.TEXTURE_2D_ARRAY);
79
+ gl.bindTexture(gl.TEXTURE_2D_ARRAY, null);
80
+ }
81
+ /** Whether `face` of `layer` holds glass: a face that does must be cleared before it is reused. */
82
+ held(layer, face) {
83
+ return (((this.heldFaces[layer] ?? 0) >> face) & 1) === 1;
84
+ }
85
+ /**
86
+ * Bind the colour scratch, clear it to white, and set the multiplying state.
87
+ *
88
+ * The previous resolve left the scratch bound for sampling on unit 0, and it is about to be the
89
+ * target: parked first, for the reason `PointShadowArray.beginFace` gives.
90
+ */
91
+ beginFace(gl) {
92
+ gl.activeTexture(gl.TEXTURE0);
93
+ gl.bindTexture(gl.TEXTURE_2D, this.parked);
94
+ gl.bindFramebuffer(gl.FRAMEBUFFER, this.scratchFramebuffer);
95
+ gl.viewport(0, 0, this.faceSize, this.faceSize);
96
+ gl.clearBufferfv(gl.COLOR, 0, CLEAR);
97
+ gl.disable(gl.DEPTH_TEST);
98
+ gl.depthMask(false);
99
+ gl.enable(gl.BLEND);
100
+ gl.blendEquation(gl.FUNC_ADD);
101
+ gl.blendFuncSeparate(gl.DST_COLOR, gl.ZERO, gl.DST_ALPHA, gl.ZERO);
102
+ }
103
+ /**
104
+ * Copy the scratch into `face`'s region of `layer`, and remember whether it held glass.
105
+ *
106
+ * Leaves depth testing and writing on, which is what the next face's casters expect: the depth
107
+ * resolve that runs before them turns both on, and this one ran after it and turned them off.
108
+ */
109
+ resolveFace(gl, layer, face, drawn) {
110
+ gl.disable(gl.BLEND);
111
+ gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
112
+ gl.framebufferTextureLayer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, this.texture, 0, layer);
113
+ gl.viewport(0, 0, this.edge, this.edge);
114
+ gl.useProgram(this.program);
115
+ gl.bindVertexArray(this.vao);
116
+ gl.activeTexture(gl.TEXTURE0);
117
+ gl.bindTexture(gl.TEXTURE_2D, this.scratch);
118
+ gl.uniform1i(this.uniforms['uFace'] ?? null, 0);
119
+ gl.uniform1i(this.uniforms['uFaceIndex'] ?? null, face);
120
+ gl.uniformMatrix3fv(this.uniforms['uFaceRotation'] ?? null, false, pointShadowFaceRotation(face, faceRotation));
121
+ gl.uniform1f(this.uniforms['uEdge'] ?? null, this.edge);
122
+ gl.drawArrays(gl.TRIANGLES, 0, 3);
123
+ gl.bindVertexArray(null);
124
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
125
+ gl.enable(gl.DEPTH_TEST);
126
+ gl.depthMask(true);
127
+ const bit = 1 << face;
128
+ const was = this.heldFaces[layer] ?? 0;
129
+ this.heldFaces[layer] = drawn ? was | bit : was & ~bit;
130
+ this.dirty[layer] = 1;
131
+ }
132
+ /** Rebuild the chain of every layer touched since the last call, one layer at a time. */
133
+ fillMips(gl) {
134
+ for (let layer = 0; layer < this.layers; layer++) {
135
+ if (this.dirty[layer] !== 1)
136
+ continue;
137
+ this.dirty[layer] = 0;
138
+ /* Level 0 out into the staging chain, which mipmaps as one 2D texture. */
139
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, this.readFramebuffer);
140
+ gl.framebufferTextureLayer(gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, this.texture, 0, layer);
141
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, this.framebuffer);
142
+ gl.framebufferTexture2D(gl.DRAW_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.staging, 0);
143
+ blit(gl, this.edge, this.edge);
144
+ gl.bindTexture(gl.TEXTURE_2D, this.staging);
145
+ gl.generateMipmap(gl.TEXTURE_2D);
146
+ gl.bindTexture(gl.TEXTURE_2D, null);
147
+ /* And every level after it back into the layer. */
148
+ for (let level = 1; level < this.levels; level++) {
149
+ const size = Math.max(1, this.edge >> level);
150
+ gl.framebufferTexture2D(gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.staging, level);
151
+ gl.framebufferTextureLayer(gl.DRAW_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, this.texture, level, layer);
152
+ blit(gl, size, size);
153
+ }
154
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, null);
155
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null);
156
+ }
157
+ }
158
+ dispose(gl) {
159
+ gl.deleteFramebuffer(this.scratchFramebuffer);
160
+ gl.deleteFramebuffer(this.framebuffer);
161
+ gl.deleteFramebuffer(this.readFramebuffer);
162
+ gl.deleteTexture(this.texture);
163
+ gl.deleteTexture(this.scratch);
164
+ gl.deleteTexture(this.staging);
165
+ gl.deleteTexture(this.parked);
166
+ gl.deleteProgram(this.program);
167
+ gl.deleteVertexArray(this.vao);
168
+ }
169
+ }
170
+ /** A colour texture with `levels` levels: nearest for a single level, trilinear for a chain. */
171
+ function colourTexture(gl, target, levels, edge, layers) {
172
+ const texture = gl.createTexture();
173
+ if (texture === null)
174
+ throw new Error('PointGlassTint: createTexture failed');
175
+ gl.bindTexture(target, texture);
176
+ if (target === gl.TEXTURE_2D_ARRAY) {
177
+ gl.texStorage3D(target, levels, gl.RGBA8, edge, edge, layers);
178
+ }
179
+ else {
180
+ gl.texStorage2D(target, levels, gl.RGBA8, edge, edge);
181
+ }
182
+ gl.texParameteri(target, gl.TEXTURE_MIN_FILTER, levels > 1 ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST);
183
+ gl.texParameteri(target, gl.TEXTURE_MAG_FILTER, levels > 1 ? gl.LINEAR : gl.NEAREST);
184
+ gl.texParameteri(target, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
185
+ gl.texParameteri(target, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
186
+ gl.bindTexture(target, null);
187
+ return texture;
188
+ }
189
+ function framebufferOf(gl) {
190
+ const framebuffer = gl.createFramebuffer();
191
+ if (framebuffer === null)
192
+ throw new Error('PointGlassTint: createFramebuffer failed');
193
+ return framebuffer;
194
+ }
195
+ /** The bound read framebuffer's colour into the bound draw framebuffer's, texel for texel. */
196
+ function blit(gl, width, height) {
197
+ gl.blitFramebuffer(0, 0, width, height, 0, 0, width, height, gl.COLOR_BUFFER_BIT, gl.NEAREST);
198
+ }
@@ -1,4 +1,5 @@
1
1
  /** Allocation-free selection and packing of point lights for a fixed shader budget. */
2
+ import type { LightGrid } from './lightGrid.ts';
2
3
  export declare const DEFAULT_POINT_LIGHT_VIEW_RANGE = 180;
3
4
  export interface PointLightSource {
4
5
  x: number;
@@ -189,3 +190,9 @@ export declare function createPointLightBuffer(capacity?: number): PointLightBuf
189
190
  * makes the distance the *same arithmetic on the same operands*, not merely a close number.
190
191
  */
191
192
  export declare function selectPointLights(sources: readonly PointLightSource[], x: number, y: number, z: number, out: PointLightBuffer, timeSeconds?: number, viewRange?: number, castX?: number, castY?: number, castZ?: number): void;
193
+ /**
194
+ * `selectPointLights` over a grid's sources, looking only at the lights near the eye: the same
195
+ * choice, tie for tie, for a world of thousands of fixed lights that the plain scan would visit
196
+ * every frame. See `lightGrid.ts` for why the result is the scan's exactly.
197
+ */
198
+ export declare function selectGridLights(grid: LightGrid, x: number, y: number, z: number, out: PointLightBuffer, timeSeconds?: number, viewRange?: number, castX?: number, castY?: number, castZ?: number): void;
@@ -1,6 +1,7 @@
1
1
  /** Allocation-free selection and packing of point lights for a fixed shader budget. */
2
2
  import { POINT_LIGHT_COS_INNER, POINT_LIGHT_COS_OUTER } from './clusteredLights.js';
3
3
  import { MAX_POINT_LIGHTS, POINT_SHADOW_POOL } from './lightBudget.js';
4
+ import { gatherLights } from './lightGrid.js';
4
5
  export const DEFAULT_POINT_LIGHT_VIEW_RANGE = 180;
5
6
  /**
6
7
  * How far out a light fades to nothing, in metres before its cull distance.
@@ -38,8 +39,15 @@ const FADE_BAND_M = 40;
38
39
  * while it is still too dim to show one.
39
40
  */
40
41
  const CONTENTION_BAND_M = 3;
41
- /** Sort keys for the shadow list, which is ordered independently of the shaded one. */
42
- const shadowRank = new Float32Array(POINT_SHADOW_POOL);
42
+ /**
43
+ * Sort keys for the shadow list, which is ordered independently of the shaded one.
44
+ *
45
+ * **Float64, and ties broken by source index**, the shaded list's rule. These were single precision
46
+ * compared against a double, so which of two lamps exactly as far away came first depended on
47
+ * which way the distance happened to round — and on the order the lamps were met, which is what a
48
+ * grid changes. Now the list is the same whatever order its candidates arrive in.
49
+ */
50
+ const shadowRank = new Float64Array(POINT_SHADOW_POOL);
43
51
  /**
44
52
  * Every light in range this frame, by source index and squared distance, before the nearest are
45
53
  * chosen. Grown when a world offers more than it has held, and never shrunk, so a frame allocates
@@ -177,6 +185,19 @@ export function createPointLightBuffer(capacity = MAX_POINT_LIGHTS) {
177
185
  * makes the distance the *same arithmetic on the same operands*, not merely a close number.
178
186
  */
179
187
  export function selectPointLights(sources, x, y, z, out, timeSeconds = 0, viewRange = DEFAULT_POINT_LIGHT_VIEW_RANGE, castX = x, castY = y, castZ = z) {
188
+ choose(sources, null, sources.length, x, y, z, out, timeSeconds, viewRange, castX, castY, castZ);
189
+ }
190
+ /**
191
+ * `selectPointLights` over a grid's sources, looking only at the lights near the eye: the same
192
+ * choice, tie for tie, for a world of thousands of fixed lights that the plain scan would visit
193
+ * every frame. See `lightGrid.ts` for why the result is the scan's exactly.
194
+ */
195
+ export function selectGridLights(grid, x, y, z, out, timeSeconds = 0, viewRange = DEFAULT_POINT_LIGHT_VIEW_RANGE, castX = x, castY = y, castZ = z) {
196
+ const count = gatherLights(grid, x, y, z, viewRange + grid.maxRadius);
197
+ choose(grid.sources, grid.gathered, count, x, y, z, out, timeSeconds, viewRange, castX, castY, castZ);
198
+ }
199
+ /** The choice itself, over every source or over the ones `visit` names, in source order. */
200
+ function choose(sources, visit, visitCount, x, y, z, out, timeSeconds, viewRange, castX, castY, castZ) {
180
201
  out.count = 0;
181
202
  out.shadowCount = 0;
182
203
  /*
@@ -193,7 +214,8 @@ export function selectPointLights(sources, x, y, z, out, timeSeconds = 0, viewRa
193
214
  */
194
215
  let cutSq = Infinity;
195
216
  let candidateCount = 0;
196
- for (let sourceIndex = 0; sourceIndex < sources.length; sourceIndex++) {
217
+ for (let visited = 0; visited < visitCount; visited++) {
218
+ const sourceIndex = visit === null ? visited : visit[visited];
197
219
  const light = sources[sourceIndex];
198
220
  if (light === undefined)
199
221
  continue;
@@ -265,7 +287,10 @@ export function selectPointLights(sources, x, y, z, out, timeSeconds = 0, viewRa
265
287
  if (castDistanceSq >= light.radius * light.radius)
266
288
  continue;
267
289
  let shadowSlot = out.shadowCount;
268
- while (shadowSlot > 0 && (shadowRank[shadowSlot - 1] ?? 0) > castDistanceSq)
290
+ while (shadowSlot > 0 &&
291
+ ((shadowRank[shadowSlot - 1] ?? 0) > castDistanceSq ||
292
+ ((shadowRank[shadowSlot - 1] ?? 0) === castDistanceSq &&
293
+ (out.shadowIndex[shadowSlot - 1] ?? -1) > sourceIndex)))
269
294
  shadowSlot--;
270
295
  if (shadowSlot >= POINT_SHADOW_POOL)
271
296
  continue;
@@ -16,7 +16,25 @@ export declare const OCTAHEDRAL_EDGE = 1024;
16
16
  export interface PointShadowTarget {
17
17
  beginFace(gl: WebGL2RenderingContext): void;
18
18
  resolveFace(gl: WebGL2RenderingContext, layer: number, face: number, far: number, near: number): void;
19
+ /** Whether the array has grown its glass layers: `growForGlass`. */
20
+ readonly hasGlass: boolean;
21
+ /** Whether `face` of `layer` holds glass, which a face that offers none must clear. */
22
+ heldGlass(layer: number, face: number): boolean;
23
+ /** The depth scratch again, cleared, for the face's glass. */
24
+ beginGlassFace(gl: WebGL2RenderingContext): void;
25
+ resolveGlassFace(gl: WebGL2RenderingContext, layer: number, face: number, far: number, near: number): void;
26
+ /** The colour scratch, cleared to white, for what the face's glass lets through. */
27
+ beginTintFace(gl: WebGL2RenderingContext): void;
28
+ resolveTintFace(gl: WebGL2RenderingContext, layer: number, face: number, drawn: boolean): void;
19
29
  }
30
+ /**
31
+ * Every published layer as the array stores it: `layer × stride`, and −1 left as −1.
32
+ *
33
+ * **Applied to the resolved arrays right after each resolve**, so every consumer of an uploaded
34
+ * index reads the physical layer: once glass arrives, light L's depth is at 2L and its glass at
35
+ * 2L + 1 (`PointShadowArray.growForGlass`). In place, because the resolve rewrites these every time.
36
+ */
37
+ export declare function strideLayers(layers: Int32Array, stride: number): void;
20
38
  /**
21
39
  * Every point light's shadow as one array texture, and the scratch that fills it.
22
40
  *
@@ -34,12 +52,16 @@ export interface PointShadowTarget {
34
52
  * never the same object.
35
53
  */
36
54
  export declare class PointShadowArray implements PointShadowTarget {
37
- readonly texture: WebGLTexture;
38
55
  /**
39
- * How many layers this array holds: the point pool, the two live transition maps, and two more
40
- * for each rectangle a world said would cast. See the constructor's `extraLayers`.
56
+ * How many lights this array holds: the point pool, the two live transition maps, and two more
57
+ * for each rectangle a world said would cast. See the constructor's `extraLayers`. Counted in
58
+ * lights, so it does not move when glass doubles the storage under it.
41
59
  */
42
60
  readonly layers: number;
61
+ /** Replaced once, when glass first casts: see `growForGlass`. */
62
+ private current;
63
+ /** What the lights' glass lets through, made with the glass layers. */
64
+ private tint;
43
65
  private readonly framebuffer;
44
66
  private readonly scratchTexture;
45
67
  private readonly scratchFramebuffer;
@@ -62,6 +84,35 @@ export declare class PointShadowArray implements PointShadowTarget {
62
84
  * map.
63
85
  */
64
86
  extraLayers?: number);
87
+ /** The array texture the lit pass samples. Replaced once, when glass first casts. */
88
+ get texture(): WebGLTexture;
89
+ get hasGlass(): boolean;
90
+ /** Physical layers a light: 1, or 2 once glass has interleaved the array. */
91
+ get stride(): number;
92
+ /** The glass tint, one layer a light, once glass has cast; the lit pass samples it. */
93
+ get tintTexture(): WebGLTexture | null;
94
+ /**
95
+ * Interleave the array for glass: light L's depth moves to 2L and its glass takes 2L + 1, and
96
+ * the tint is made. Returns whether it grew — once, on the first face whose casters held glass.
97
+ *
98
+ * **Interleaved rather than stacked above the opaque half** because a shader cannot ask an array
99
+ * its layer count under naga, so it cannot find `L + N`; `layer + 1` it can. **Every map already
100
+ * baked is carried across** by a depth blit, so nothing goes dark, and **no map needs baking
101
+ * again**: this runs on the first face that saw glass, so no map baked before it saw any, and a
102
+ * glass layer born at the far plane with a white tint is exactly what its bake would draw. What
103
+ * it costs is the doubled depth (4.19 MB a light at 1024) and the tint (5.6 MB a light with its
104
+ * chain, 1.4 MB at `'half'`), from then on.
105
+ */
106
+ growForGlass(gl: WebGL2RenderingContext, tintEdge: number): boolean;
107
+ heldGlass(layer: number, face: number): boolean;
108
+ /** The depth scratch, cleared, for the face's glass: `beginFace` exactly. */
109
+ beginGlassFace(gl: WebGL2RenderingContext): void;
110
+ /** The face's glass depth into light `layer`'s glass layer, 2L + 1. */
111
+ resolveGlassFace(gl: WebGL2RenderingContext, layer: number, face: number, far: number, near: number): void;
112
+ beginTintFace(gl: WebGL2RenderingContext): void;
113
+ resolveTintFace(gl: WebGL2RenderingContext, layer: number, face: number, drawn: boolean): void;
114
+ /** The chain frost reads, for every light whose tint a round touched: once a round. */
115
+ finishTints(gl: WebGL2RenderingContext): void;
65
116
  /**
66
117
  * Bind the scratch as the render target for one face of one bake, and clear it.
67
118
  *
@@ -91,5 +142,7 @@ export declare class PointShadowArray implements PointShadowTarget {
91
142
  * from an empty vertex array.
92
143
  */
93
144
  resolveFace(gl: WebGL2RenderingContext, layer: number, face: number, far: number, near: number): void;
145
+ /** The scratch into physical layer `physical`'s region for `face`. */
146
+ private resolveInto;
94
147
  dispose(gl: WebGL2RenderingContext): void;
95
148
  }
@@ -1,6 +1,7 @@
1
1
  import { mat3 } from 'gl-matrix';
2
2
  import { createEmptyTexture2D } from './emptyTexture.js';
3
3
  import { LIVE_POINT_SHADOW_MAPS, POINT_SHADOW_POOL } from './lightBudget.js';
4
+ import { PointGlassTint } from './pointGlassTint.js';
4
5
  import { pointShadowFaceRotation } from './pointShadowImage.js';
5
6
  import { compileProgram, uniformLocations } from './shader.js';
6
7
  import { OCTAHEDRAL_RESOLVE_FRAG, OCTAHEDRAL_RESOLVE_VERT } from './shaders/octahedralResolve.js';
@@ -15,6 +16,22 @@ import { OCTAHEDRAL_RESOLVE_FRAG, OCTAHEDRAL_RESOLVE_VERT } from './shaders/octa
15
16
  export const OCTAHEDRAL_EDGE = 1024;
16
17
  /** Filled per resolved face, at module scope, because a bake runs inside the frame's budget. */
17
18
  const faceRotation = mat3.create();
19
+ /**
20
+ * Every published layer as the array stores it: `layer × stride`, and −1 left as −1.
21
+ *
22
+ * **Applied to the resolved arrays right after each resolve**, so every consumer of an uploaded
23
+ * index reads the physical layer: once glass arrives, light L's depth is at 2L and its glass at
24
+ * 2L + 1 (`PointShadowArray.growForGlass`). In place, because the resolve rewrites these every time.
25
+ */
26
+ export function strideLayers(layers, stride) {
27
+ if (stride === 1)
28
+ return;
29
+ for (let i = 0; i < layers.length; i++) {
30
+ const layer = layers[i];
31
+ if (layer >= 0)
32
+ layers[i] = layer * stride;
33
+ }
34
+ }
18
35
  /**
19
36
  * Every point light's shadow as one array texture, and the scratch that fills it.
20
37
  *
@@ -32,12 +49,16 @@ const faceRotation = mat3.create();
32
49
  * never the same object.
33
50
  */
34
51
  export class PointShadowArray {
35
- texture;
36
52
  /**
37
- * How many layers this array holds: the point pool, the two live transition maps, and two more
38
- * for each rectangle a world said would cast. See the constructor's `extraLayers`.
53
+ * How many lights this array holds: the point pool, the two live transition maps, and two more
54
+ * for each rectangle a world said would cast. See the constructor's `extraLayers`. Counted in
55
+ * lights, so it does not move when glass doubles the storage under it.
39
56
  */
40
57
  layers;
58
+ /** Replaced once, when glass first casts: see `growForGlass`. */
59
+ current;
60
+ /** What the lights' glass lets through, made with the glass layers. */
61
+ tint = null;
41
62
  framebuffer;
42
63
  scratchTexture;
43
64
  scratchFramebuffer;
@@ -68,17 +89,8 @@ export class PointShadowArray {
68
89
  */
69
90
  this.layers =
70
91
  Math.min(lightCount, POINT_SHADOW_POOL) + LIVE_POINT_SHADOW_MAPS + Math.max(0, extraLayers);
71
- const texture = gl.createTexture();
72
- if (texture === null)
73
- throw new Error('PointShadowArray: createTexture failed');
74
- this.texture = texture;
75
- gl.bindTexture(gl.TEXTURE_2D_ARRAY, texture);
76
- gl.texStorage3D(gl.TEXTURE_2D_ARRAY, 1, gl.DEPTH_COMPONENT24, OCTAHEDRAL_EDGE, OCTAHEDRAL_EDGE, this.layers);
77
- /* One storage level and no filtering, so textureLod(..., 0.0) is the only fetch there is. */
78
- gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
79
- gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
80
- gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
81
- gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
92
+ const texture = depthArray(gl, this.layers);
93
+ this.current = texture;
82
94
  const scratchTexture = gl.createTexture();
83
95
  if (scratchTexture === null)
84
96
  throw new Error('PointShadowArray: createTexture failed');
@@ -141,6 +153,86 @@ export class PointShadowArray {
141
153
  gl.bindTexture(gl.TEXTURE_2D, null);
142
154
  gl.bindTexture(gl.TEXTURE_2D_ARRAY, null);
143
155
  }
156
+ /** The array texture the lit pass samples. Replaced once, when glass first casts. */
157
+ get texture() {
158
+ return this.current;
159
+ }
160
+ get hasGlass() {
161
+ return this.tint !== null;
162
+ }
163
+ /** Physical layers a light: 1, or 2 once glass has interleaved the array. */
164
+ get stride() {
165
+ return this.tint === null ? 1 : 2;
166
+ }
167
+ /** The glass tint, one layer a light, once glass has cast; the lit pass samples it. */
168
+ get tintTexture() {
169
+ return this.tint?.texture ?? null;
170
+ }
171
+ /**
172
+ * Interleave the array for glass: light L's depth moves to 2L and its glass takes 2L + 1, and
173
+ * the tint is made. Returns whether it grew — once, on the first face whose casters held glass.
174
+ *
175
+ * **Interleaved rather than stacked above the opaque half** because a shader cannot ask an array
176
+ * its layer count under naga, so it cannot find `L + N`; `layer + 1` it can. **Every map already
177
+ * baked is carried across** by a depth blit, so nothing goes dark, and **no map needs baking
178
+ * again**: this runs on the first face that saw glass, so no map baked before it saw any, and a
179
+ * glass layer born at the far plane with a white tint is exactly what its bake would draw. What
180
+ * it costs is the doubled depth (4.19 MB a light at 1024) and the tint (5.6 MB a light with its
181
+ * chain, 1.4 MB at `'half'`), from then on.
182
+ */
183
+ growForGlass(gl, tintEdge) {
184
+ if (this.tint !== null)
185
+ return false;
186
+ const old = this.current;
187
+ const texture = depthArray(gl, this.layers * 2);
188
+ /* Off the unit it was made on, as the constructor leaves its own: see `beginFace`. */
189
+ gl.bindTexture(gl.TEXTURE_2D_ARRAY, null);
190
+ const read = gl.createFramebuffer();
191
+ if (read === null)
192
+ throw new Error('PointShadowArray: createFramebuffer failed');
193
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, read);
194
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, this.framebuffer);
195
+ for (let layer = 0; layer < this.layers; layer++) {
196
+ gl.framebufferTextureLayer(gl.READ_FRAMEBUFFER, gl.DEPTH_ATTACHMENT, old, 0, layer);
197
+ gl.framebufferTextureLayer(gl.DRAW_FRAMEBUFFER, gl.DEPTH_ATTACHMENT, texture, 0, 2 * layer);
198
+ gl.blitFramebuffer(0, 0, OCTAHEDRAL_EDGE, OCTAHEDRAL_EDGE, 0, 0, OCTAHEDRAL_EDGE, OCTAHEDRAL_EDGE, gl.DEPTH_BUFFER_BIT, gl.NEAREST);
199
+ }
200
+ /* Nothing is nearer a lamp than a pane that has not been drawn. */
201
+ for (let layer = 0; layer < this.layers; layer++) {
202
+ gl.framebufferTextureLayer(gl.DRAW_FRAMEBUFFER, gl.DEPTH_ATTACHMENT, texture, 0, 2 * layer + 1);
203
+ gl.clearBufferfv(gl.DEPTH, 0, FAR_PLANE);
204
+ }
205
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, null);
206
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
207
+ gl.deleteFramebuffer(read);
208
+ gl.deleteTexture(old);
209
+ this.current = texture;
210
+ this.tint = new PointGlassTint(gl, this.faceSize, tintEdge, this.layers);
211
+ return true;
212
+ }
213
+ heldGlass(layer, face) {
214
+ return this.tint?.held(layer, face) ?? false;
215
+ }
216
+ /** The depth scratch, cleared, for the face's glass: `beginFace` exactly. */
217
+ beginGlassFace(gl) {
218
+ this.beginFace(gl);
219
+ }
220
+ /** The face's glass depth into light `layer`'s glass layer, 2L + 1. */
221
+ resolveGlassFace(gl, layer, face, far, near) {
222
+ if (this.tint === null)
223
+ return;
224
+ this.resolveInto(gl, 2 * layer + 1, face, far, near);
225
+ }
226
+ beginTintFace(gl) {
227
+ this.tint?.beginFace(gl);
228
+ }
229
+ resolveTintFace(gl, layer, face, drawn) {
230
+ this.tint?.resolveFace(gl, layer, face, drawn);
231
+ }
232
+ /** The chain frost reads, for every light whose tint a round touched: once a round. */
233
+ finishTints(gl) {
234
+ this.tint?.fillMips(gl);
235
+ }
144
236
  /**
145
237
  * Bind the scratch as the render target for one face of one bake, and clear it.
146
238
  *
@@ -176,8 +268,12 @@ export class PointShadowArray {
176
268
  * from an empty vertex array.
177
269
  */
178
270
  resolveFace(gl, layer, face, far, near) {
271
+ this.resolveInto(gl, layer * this.stride, face, far, near);
272
+ }
273
+ /** The scratch into physical layer `physical`'s region for `face`. */
274
+ resolveInto(gl, physical, face, far, near) {
179
275
  gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
180
- gl.framebufferTextureLayer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, this.texture, 0, layer);
276
+ gl.framebufferTextureLayer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, this.current, 0, physical);
181
277
  gl.viewport(0, 0, OCTAHEDRAL_EDGE, OCTAHEDRAL_EDGE);
182
278
  gl.useProgram(this.program);
183
279
  gl.bindVertexArray(this.vao);
@@ -210,10 +306,25 @@ export class PointShadowArray {
210
306
  dispose(gl) {
211
307
  gl.deleteFramebuffer(this.framebuffer);
212
308
  gl.deleteFramebuffer(this.scratchFramebuffer);
213
- gl.deleteTexture(this.texture);
309
+ gl.deleteTexture(this.current);
214
310
  gl.deleteTexture(this.scratchTexture);
311
+ this.tint?.dispose(gl);
215
312
  gl.deleteTexture(this.parked);
216
313
  gl.deleteProgram(this.program);
217
314
  gl.deleteVertexArray(this.vao);
218
315
  }
219
316
  }
317
+ const FAR_PLANE = new Float32Array([1]);
318
+ /** The depth array: one storage level and no filtering, so textureLod(..., 0.0) is the only fetch. */
319
+ function depthArray(gl, layers) {
320
+ const texture = gl.createTexture();
321
+ if (texture === null)
322
+ throw new Error('PointShadowArray: createTexture failed');
323
+ gl.bindTexture(gl.TEXTURE_2D_ARRAY, texture);
324
+ gl.texStorage3D(gl.TEXTURE_2D_ARRAY, 1, gl.DEPTH_COMPONENT24, OCTAHEDRAL_EDGE, OCTAHEDRAL_EDGE, layers);
325
+ gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
326
+ gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
327
+ gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
328
+ gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
329
+ return texture;
330
+ }
@@ -21,6 +21,18 @@ import { FACE_COUNT, POINT_SHADOW_NEAR } from './pointShadowImage.ts';
21
21
  * projection the shader reads. Nothing about the bake's cost, order or resumption moved.
22
22
  */
23
23
  export { FACE_COUNT, POINT_SHADOW_NEAR };
24
+ /**
25
+ * What a bake asks of the renderer about glass, after each face's opaque depth.
26
+ *
27
+ * **Asked before it is drawn** because the answer decides whether the face binds a glass target at
28
+ * all: a face that offers no glass and holds none from a previous owner does no glass work.
29
+ */
30
+ export interface PointGlassDraw {
31
+ /** Whether the face just drawn offered any glass. */
32
+ offered(): boolean;
33
+ /** Draw that glass: its depth, or what it lets through lit from the bake origin. */
34
+ draw(pass: 'depth' | 'tint', x: number, y: number, z: number): void;
35
+ }
24
36
  export declare class PointShadowMap {
25
37
  readonly size: number;
26
38
  /**
@@ -85,7 +97,9 @@ export declare class PointShadowMap {
85
97
  * bake is not sampled at all until it completes, so this only matters for a re-bake,
86
98
  * where holding the old values keeps them matched to the old faces.
87
99
  */
88
- bake(gl: WebGL2RenderingContext, x: number, y: number, z: number, range: number, drawCasters: (viewProj: mat4) => void, near?: number, sourceRadius?: number, maxFaces?: number): number;
100
+ bake(gl: WebGL2RenderingContext, x: number, y: number, z: number, range: number, drawCasters: (viewProj: mat4) => void, near?: number, sourceRadius?: number, maxFaces?: number,
101
+ /** The face's glass, when glass shadows are on: `PointGlassDraw`. */
102
+ glass?: PointGlassDraw | null): number;
89
103
  /**
90
104
  * Nothing to free: every GPU object this map used to own belongs to `PointShadowArray` now.
91
105
  *
@@ -93,5 +107,13 @@ export declare class PointShadowMap {
93
107
  * needing disposal is exactly the shape of a leak nobody notices. The array is disposed by the
94
108
  * renderer that built it.
95
109
  */
110
+ /**
111
+ * The face's glass, twice: where the nearest pane is, then what the panes let through.
112
+ *
113
+ * A face that offers no glass still clears a region a previous owner's glass is in — the depth to
114
+ * the far plane and the tint to white, from scratches cleared and not drawn into — because a pool
115
+ * slot changes hands with its layer.
116
+ */
117
+ private bakeGlassFace;
96
118
  dispose(_gl: WebGL2RenderingContext): void;
97
119
  }
@@ -113,7 +113,9 @@ export class PointShadowMap {
113
113
  * bake is not sampled at all until it completes, so this only matters for a re-bake,
114
114
  * where holding the old values keeps them matched to the old faces.
115
115
  */
116
- bake(gl, x, y, z, range, drawCasters, near = POINT_SHADOW_NEAR, sourceRadius = DEFAULT_SOURCE_RADIUS, maxFaces = FACE_COUNT) {
116
+ bake(gl, x, y, z, range, drawCasters, near = POINT_SHADOW_NEAR, sourceRadius = DEFAULT_SOURCE_RADIUS, maxFaces = FACE_COUNT,
117
+ /** The face's glass, when glass shadows are on: `PointGlassDraw`. */
118
+ glass = null) {
117
119
  /*
118
120
  * What to render now, **and what to render it for**. A bake in flight owns the parameters
119
121
  * it started with, so the arguments here are only a proposal for a bake that is not already
@@ -159,6 +161,8 @@ export class PointShadowMap {
159
161
  * next face's pass does care about.
160
162
  */
161
163
  array.resolveFace(gl, this.layer, face, plan.range, plan.near);
164
+ if (glass !== null)
165
+ this.bakeGlassFace(gl, array, glass, face, plan);
162
166
  }
163
167
  gl.enable(gl.CULL_FACE);
164
168
  gl.cullFace(gl.BACK);
@@ -173,5 +177,27 @@ export class PointShadowMap {
173
177
  * needing disposal is exactly the shape of a leak nobody notices. The array is disposed by the
174
178
  * renderer that built it.
175
179
  */
180
+ /**
181
+ * The face's glass, twice: where the nearest pane is, then what the panes let through.
182
+ *
183
+ * A face that offers no glass still clears a region a previous owner's glass is in — the depth to
184
+ * the far plane and the tint to white, from scratches cleared and not drawn into — because a pool
185
+ * slot changes hands with its layer.
186
+ */
187
+ bakeGlassFace(gl, array, glass, face, plan) {
188
+ if (!array.hasGlass)
189
+ return;
190
+ const offered = glass.offered();
191
+ if (!offered && !array.heldGlass(this.layer, face))
192
+ return;
193
+ array.beginGlassFace(gl);
194
+ if (offered)
195
+ glass.draw('depth', plan.x, plan.y, plan.z);
196
+ array.resolveGlassFace(gl, this.layer, face, plan.range, plan.near);
197
+ array.beginTintFace(gl);
198
+ if (offered)
199
+ glass.draw('tint', plan.x, plan.y, plan.z);
200
+ array.resolveTintFace(gl, this.layer, face, offered);
201
+ }
176
202
  dispose(_gl) { }
177
203
  }