@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
@@ -103,6 +103,28 @@ export const VERTEX_LAYOUT: readonly VertexAttribute[] = [
103
103
  ];
104
104
 
105
105
  /** Geometry on the device, with the buffers bound in layout order. */
106
+ /**
107
+ * The texture coordinates of a mesh that names texture-array layers: (u, v, layer), three floats.
108
+ *
109
+ * **Three only where layers exist.** Every location is spent, so the layer rides in this attribute,
110
+ * and widening it for every textured mesh would add four bytes to each of its vertices for nothing.
111
+ * A mesh without layers keeps two, and the shader's `vec3` input reads z as 0 — WebGPU fills a
112
+ * component the format does not carry with 0 (and w with 1) — which is layer 0. The pipeline key
113
+ * carries `:layers`, so the two vertex layouts never share a pipeline.
114
+ */
115
+ export const LAYERED_UVS: VertexAttribute = {
116
+ name: 'uvs',
117
+ shaderLocation: 5,
118
+ format: 'float32x3',
119
+ components: 3,
120
+ optional: true,
121
+ };
122
+
123
+ /** The attribute as this mesh lays it out: the UV field widens where the mesh names layers. */
124
+ function laidOut(attribute: VertexAttribute, layered: boolean): VertexAttribute {
125
+ return layered && attribute.name === 'uvs' ? LAYERED_UVS : attribute;
126
+ }
127
+
106
128
  export interface GpuMesh {
107
129
  /**
108
130
  * The buffers a draw binds, in the order `vertexBufferLayouts` describes them.
@@ -125,6 +147,12 @@ export interface GpuMesh {
125
147
  * its own does.
126
148
  */
127
149
  readonly vertexStride: number;
150
+ /**
151
+ * Byte offsets of the joint indices and weights in the interleaved vertex, or null for a mesh
152
+ * that supplied neither. For a pass that skins through a shader of its own — the motion pass —
153
+ * which has to find them without the `present` map the generated pipelines are keyed by.
154
+ */
155
+ readonly skinOffsets: { readonly joints: number; readonly weights: number } | null;
128
156
  readonly indexBuffer: GPUBuffer;
129
157
  readonly indexCount: number;
130
158
  /**
@@ -192,7 +220,19 @@ export interface GpuMesh {
192
220
  * than every mesh paying it.
193
221
  */
194
222
  readonly update:
195
- ((device: GPUDevice, positions: Float32Array, normals?: Float32Array) => void) | null;
223
+ | ((
224
+ device: GPUDevice,
225
+ positions: Float32Array,
226
+ normals?: Float32Array,
227
+ previous?: GPUBuffer | null,
228
+ ) => void)
229
+ | null;
230
+ /**
231
+ * A dynamic mesh's motion state, which the renderer owns: a buffer for last frame's interleaved
232
+ * rows, made the first time a reconstructing renderer rewrites the mesh, and the frame of the
233
+ * last rewrite. Null on a mesh created without `{ dynamic: true }`, which never moves a vertex.
234
+ */
235
+ readonly motion: { previous: GPUBuffer | null; changed: number } | null;
196
236
  dispose(): void;
197
237
  }
198
238
 
@@ -263,9 +303,10 @@ export function createGpuMeshIncremental(
263
303
  validateMeshData(data);
264
304
  const bounds = boundsOfPositions(data.positions, createBounds());
265
305
  const vertexCount = data.positions.length / 3;
306
+ const layered = data.layers !== undefined;
266
307
  const supplied = VERTEX_LAYOUT.filter(
267
308
  (attribute) => !attribute.optional || data[attribute.name as keyof MeshData] !== undefined,
268
- );
309
+ ).map((attribute) => laidOut(attribute, layered));
269
310
 
270
311
  /* Interleaved: one stride holding every attribute the mesh actually supplied. */
271
312
  const stride = supplied.reduce((sum, attribute) => sum + attribute.components * 4, 0);
@@ -377,6 +418,7 @@ export function createGpuMeshIncremental(
377
418
  progress,
378
419
  hasTangents: data.tangents !== undefined,
379
420
  isSkinned: data.joints !== undefined,
421
+ skinOffsets: skinOffsetsOf(supplied),
380
422
  hasChannel: data.channel !== undefined,
381
423
  morph:
382
424
  data.morphTargets === undefined || data.morphTargetCount === undefined
@@ -440,7 +482,21 @@ function* uploadSteps(job: UploadJob): Generator<void, void, void> {
440
482
  let fieldOffset = 0;
441
483
  for (const attribute of supplied) {
442
484
  const source = data[attribute.name as keyof MeshData] as Float32Array;
443
- interleaveField(interleaved, source, attribute.components, step, fieldOffset, from, to);
485
+ if (attribute === LAYERED_UVS) {
486
+ /* (u, v) from the coordinates and the layer after them, one field of three. */
487
+ interleaveField(interleaved, source, 2, step, fieldOffset, from, to);
488
+ interleaveField(
489
+ interleaved,
490
+ data.layers as Float32Array,
491
+ 1,
492
+ step,
493
+ fieldOffset + 2,
494
+ from,
495
+ to,
496
+ );
497
+ } else {
498
+ interleaveField(interleaved, source, attribute.components, step, fieldOffset, from, to);
499
+ }
444
500
  fieldOffset += attribute.components;
445
501
  }
446
502
  device.queue.writeBuffer(vertices, from * stride, interleaved, from * step, (to - from) * step);
@@ -505,6 +561,22 @@ function interleaveField(
505
561
  }
506
562
  }
507
563
 
564
+ /**
565
+ * Where the joints and weights sit in a vertex of `supplied`, the attributes a mesh interleaves in
566
+ * layout order; null unless it supplied both.
567
+ */
568
+ function skinOffsetsOf(supplied: readonly VertexAttribute[]): GpuMesh['skinOffsets'] {
569
+ let floats = 0;
570
+ let joints = -1;
571
+ let weights = -1;
572
+ for (const attribute of supplied) {
573
+ if (attribute.name === 'joints') joints = floats * 4;
574
+ if (attribute.name === 'weights') weights = floats * 4;
575
+ floats += attribute.components;
576
+ }
577
+ return joints >= 0 && weights >= 0 ? { joints, weights } : null;
578
+ }
579
+
508
580
  /** The handle, made where it can see only what it hands out. */
509
581
  function gpuMeshOf(parts: {
510
582
  readonly vertices: GPUBuffer;
@@ -516,14 +588,17 @@ function gpuMeshOf(parts: {
516
588
  readonly progress: { readonly uploaded: boolean };
517
589
  readonly hasTangents: boolean;
518
590
  readonly isSkinned: boolean;
591
+ readonly skinOffsets: GpuMesh['skinOffsets'];
519
592
  readonly hasChannel: boolean;
520
593
  readonly morph: MorphTexture | null;
521
594
  readonly update: GpuMesh['update'];
522
595
  }): GpuMesh {
596
+ const motion = parts.update === null ? null : { previous: null as GPUBuffer | null, changed: -1 };
523
597
  const { vertices, constants, indexBuffer, progress } = parts;
524
598
  return {
525
599
  vertexBuffers: [vertices, constants],
526
600
  vertexStride: parts.stride,
601
+ skinOffsets: parts.skinOffsets,
527
602
  indexBuffer,
528
603
  indexCount: parts.indexCount,
529
604
  bounds: parts.bounds,
@@ -535,7 +610,9 @@ function gpuMeshOf(parts: {
535
610
  hasChannel: parts.hasChannel,
536
611
  morph: parts.morph,
537
612
  update: parts.update,
613
+ motion,
538
614
  dispose(): void {
615
+ motion?.previous?.destroy();
539
616
  vertices.destroy();
540
617
  constants.destroy();
541
618
  indexBuffer.destroy();
@@ -557,7 +634,12 @@ function dynamicUpdate(
557
634
  positionField: number,
558
635
  normalField: number,
559
636
  ): NonNullable<GpuMesh['update']> {
560
- return (target: GPUDevice, positions: Float32Array, normals?: Float32Array): void => {
637
+ return (
638
+ target: GPUDevice,
639
+ positions: Float32Array,
640
+ normals?: Float32Array,
641
+ previous: GPUBuffer | null = null,
642
+ ): void => {
561
643
  if (positions.length !== vertexCount * 3) {
562
644
  throw new Error(
563
645
  `this mesh has ${vertexCount} vertices and the update has ${positions.length / 3}. ` +
@@ -565,6 +647,9 @@ function dynamicUpdate(
565
647
  'every other attribute are sized against it.',
566
648
  );
567
649
  }
650
+ /* The rows as they stand are last frame's, handed over before a position of them changes.
651
+ `writeBuffer` copies at the call, so patching the array afterwards cannot reach this copy. */
652
+ if (previous !== null) target.queue.writeBuffer(previous, 0, interleaved);
568
653
  /* Everything that decides whether this is on screen starts from the bounds, so a cloth
569
654
  that blew sideways out of its original box would be culled while still visible. */
570
655
  boundsOfPositions(positions, bounds);
@@ -630,7 +715,9 @@ export function vertexBufferLayouts(
630
715
  const constants: GPUVertexAttribute[] = [];
631
716
  let offset = 0;
632
717
 
633
- VERTEX_LAYOUT.forEach((attribute, index) => {
718
+ const layered = present['layers'] === true;
719
+ VERTEX_LAYOUT.forEach((declared, index) => {
720
+ const attribute = laidOut(declared, layered);
634
721
  /*
635
722
  * **The instanced pipeline reclaims the two skinning locations, and it is allowed to because
636
723
  * it cannot skin.** Locations 11 and 12 are the joint indices and weights; the INSTANCED
@@ -122,7 +122,8 @@ export function createDepthBindGroupLayout(
122
122
  {
123
123
  binding: PREVIOUS.texture,
124
124
  visibility: VISIBILITY_FRAGMENT,
125
- texture: { sampleType: 'unfilterable-float' },
125
+ /* An array since the sun's maps became one (`shadowMap.ts`): the peel reads its static layer. */
126
+ texture: { sampleType: 'unfilterable-float', viewDimension: '2d-array' },
126
127
  },
127
128
  {
128
129
  binding: PREVIOUS.sampler,
@@ -136,7 +137,11 @@ export function createDepthBindGroupLayout(
136
137
  {
137
138
  binding: CUTOUT_MAP.texture,
138
139
  visibility: VISIBILITY_FRAGMENT,
139
- texture: { sampleType: 'float' as GPUTextureSampleType },
140
+ /* A surface texture, and every surface texture is a `2d-array` view. */
141
+ texture: {
142
+ sampleType: 'float' as GPUTextureSampleType,
143
+ viewDimension: '2d-array' as GPUTextureViewDimension,
144
+ },
140
145
  },
141
146
  {
142
147
  binding: CUTOUT_MAP.sampler,
@@ -28,6 +28,11 @@ export interface DrawCommand {
28
28
  indexed: boolean;
29
29
  count: number;
30
30
  instances: number;
31
+ /**
32
+ * A `drawIndexedIndirect` record to draw from instead of `count` and `instances`: an instance
33
+ * cull's survivors, counted on the device. Null for every other draw.
34
+ */
35
+ indirect: GPUBuffer | null;
31
36
  }
32
37
 
33
38
  export interface CommandPool {
@@ -50,6 +55,7 @@ function emptyCommand(): DrawCommand {
50
55
  indexed: false,
51
56
  count: 0,
52
57
  instances: 1,
58
+ indirect: null,
53
59
  };
54
60
  }
55
61
 
@@ -8,6 +8,14 @@ import { vertexBufferLayouts } from './buffers.ts';
8
8
  import type { PipelineCache } from './pipelineCache.ts';
9
9
  import { shaderModule } from './shaderModules.ts';
10
10
 
11
+ /** The material maps the flat pass reads: `2d-array` colour, filtered, not shadow maps. */
12
+ const SURFACE_MAPS: ReadonlySet<string> = new Set([
13
+ 'uAlbedo',
14
+ 'uNormalMap',
15
+ 'uOrmMap',
16
+ 'uEmissiveMap',
17
+ ]);
18
+
11
19
  /**
12
20
  * The flat pipeline: the shader that draws the world.
13
21
  *
@@ -82,6 +90,8 @@ export interface StageBindings {
82
90
  >
83
91
  >;
84
92
  readonly textures: Readonly<Record<string, TextureBinding>>;
93
+ /** Each pipeline-overridable constant's id, by the GLSL name it was declared under. */
94
+ readonly overrides?: Readonly<Record<string, number>>;
85
95
  }
86
96
 
87
97
  /*
@@ -188,6 +198,20 @@ export function flatFragmentBindings(variant: FlatVariant): StageBindings {
188
198
  return found;
189
199
  }
190
200
 
201
+ /**
202
+ * The lit fragment stage's constants, set from the cache: the glass switch, keyed by its **id**.
203
+ *
204
+ * Not by its name, because an override declared with `@id` answers to the id alone — WebGPU's
205
+ * "pipeline-overridable constant identifier string" — and naga writes one on every specialisation
206
+ * constant. Keyed by name, Chrome refused every lit pipeline: *Pipeline overridable constant
207
+ * "GLASS_SHADOWS" not found*, and the frame drew nothing.
208
+ */
209
+ function flatFragmentConstants(variant: FlatVariant, cache: PipelineCache): Record<string, number> {
210
+ const id = flatFragmentBindings(variant).overrides?.['GLASS_SHADOWS'];
211
+ if (id === undefined) throw new Error(`flatPass: variant "${variant}" has no glass switch`);
212
+ return { [String(id)]: cache.glassShadows ? 1 : 0 };
213
+ }
214
+
191
215
  /** The depth format both backends compare with. */
192
216
  /* Re-exported so the twenty passes that already import it from here do not each learn a new
193
217
  module; `depthConvention.ts` is where the decision lives and why. */
@@ -272,8 +296,22 @@ export function createFlatBindGroupLayout(
272
296
  const integer = texture.type === 'usampler2D' || texture.type === 'usampler3D';
273
297
  /* DriftLight's brick index and atlas, the lit pass's only volumes. See `driftLight.ts`. */
274
298
  const volume = texture.type === 'sampler3D' || texture.type === 'usampler3D';
299
+ /* Glass tints are colour, mipmapped and filtered: a frosted pane is read at a coarser level. */
300
+ const tint = name.endsWith('GlassTints');
301
+ /*
302
+ * **The four material maps are arrays too, and they are colour.** Every surface texture is a
303
+ * `2d-array` view since texture arrays landed, so the rule below — an array is a shadow map —
304
+ * would declare albedo unfilterable and bind a non-filtering sampler to it, which reads as
305
+ * every textured surface going nearest-neighbour and losing its mip chain. Named, as
306
+ * `uEnvironment` is, because a pattern wide enough to catch them catches the shadow arrays.
307
+ */
308
+ const surface = SURFACE_MAPS.has(name);
275
309
  const isShadow =
276
- integer || (name !== 'uEnvironment' && (name.endsWith('ShadowMap') || cube || array));
310
+ integer ||
311
+ (!tint &&
312
+ !surface &&
313
+ name !== 'uEnvironment' &&
314
+ (name.endsWith('ShadowMap') || cube || array));
277
315
  entries.push({
278
316
  binding: texture.texture,
279
317
  visibility: VISIBILITY_FRAGMENT,
@@ -443,8 +481,11 @@ export function flatPipeline(
443
481
  * `depthMask` alone and only turns blending on. A translucent part of a loaded model is a
444
482
  * pane of glass in a solid object, not a particle, and it has to occlude what is behind it
445
483
  * for the rest of the model to sort correctly.
484
+ *
485
+ * `'additive'` adds instead: colour times alpha onto the target, its alpha kept. The key must
486
+ * carry it (`|add`).
446
487
  */
447
- translucent = false,
488
+ translucent: boolean | 'additive' = false,
448
489
  /** Whether this pipeline reads a joint palette. See `FlatVertexOptions`. */
449
490
  skinned = false,
450
491
  /** Whether this pipeline reads morph deltas. See `FlatVertexOptions`. */
@@ -557,7 +598,7 @@ function flatDescriptor(
557
598
  variant: FlatVariant,
558
599
  key: string,
559
600
  present: Readonly<Record<string, boolean>>,
560
- translucent: boolean,
601
+ translucent: boolean | 'additive',
561
602
  /** Whether this pipeline reads a joint palette. See `FlatVertexOptions`. */
562
603
  skinned: boolean,
563
604
  /** Whether this pipeline reads morph deltas. See `FlatVertexOptions`. */
@@ -603,21 +644,31 @@ function flatDescriptor(
603
644
  code: FLAT_FRAG_WGSL[variant] ?? '',
604
645
  }),
605
646
  entryPoint: 'main',
647
+ /* Every variant declares it, so every lit pipeline may set it: see `PipelineCache`. */
648
+ constants: flatFragmentConstants(variant, cache),
606
649
  targets: [
607
650
  oitTarget(oit) ??
608
- (translucent
651
+ (translucent === 'additive'
609
652
  ? {
610
653
  format: cache.format,
611
654
  blend: {
612
- color: {
613
- srcFactor: 'src-alpha',
614
- dstFactor: 'one-minus-src-alpha',
615
- operation: 'add',
616
- },
617
- alpha: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
655
+ color: { srcFactor: 'src-alpha', dstFactor: 'one', operation: 'add' },
656
+ alpha: { srcFactor: 'zero', dstFactor: 'one', operation: 'add' },
618
657
  },
619
658
  }
620
- : { format: cache.format }),
659
+ : translucent
660
+ ? {
661
+ format: cache.format,
662
+ blend: {
663
+ color: {
664
+ srcFactor: 'src-alpha',
665
+ dstFactor: 'one-minus-src-alpha',
666
+ operation: 'add',
667
+ },
668
+ alpha: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
669
+ },
670
+ }
671
+ : { format: cache.format }),
621
672
  ],
622
673
  },
623
674
  primitive: { topology: 'triangle-list', cullMode: doubleSided ? 'none' : 'back' },
@@ -0,0 +1,245 @@
1
+ /**
2
+ * What a pane lets through, drawn into a shadow's tint layer on this device: the layouts, the
3
+ * per-draw block, the bind groups and the pipelines for the five tint programs (`depth.ts`).
4
+ *
5
+ * **The vertex half is the depth pass's**, block for block — the tint's vertex stages are the depth
6
+ * ones with the world position handed on — so a draw's vertex slot comes from the shadow pass's own
7
+ * ring and only the pane's 32 bytes are this file's. **The fragment half multiplies**: an
8
+ * `rgba8unorm` target cleared to white, `dst × src` for colour and `dst-alpha × src` for clarity, and
9
+ * no depth, so every pane on a ray counts in any order. Built the first time glass casts, so a scene
10
+ * with no glass builds none of it.
11
+ */
12
+ import { DEPTH_BINDINGS } from '../../shaders/generated/depth.wgsl.ts';
13
+ import {
14
+ GLASS_TINT_CUTOUT_FRAG_WGSL,
15
+ GLASS_TINT_CUTOUT_VERT_WGSL,
16
+ GLASS_TINT_FRAG_WGSL,
17
+ GLASS_TINT_INSTANCED_CUTOUT_VERT_WGSL,
18
+ GLASS_TINT_INSTANCED_VERT_WGSL,
19
+ GLASS_TINT_SKINNED_VERT_WGSL,
20
+ GLASS_TINT_VERT_WGSL,
21
+ } from '../../shaders/generated/depth.wgsl.ts';
22
+ import type { ResolvedGlass } from '../../glass.ts';
23
+ import { vertexBufferLayouts } from './buffers.ts';
24
+ import type { PipelineCache } from './pipelineCache.ts';
25
+ import { shaderModule } from './shaderModules.ts';
26
+ import { UniformRing } from './uniformRing.ts';
27
+
28
+ const VISIBILITY_VERTEX = 0x1;
29
+ const VISIBILITY_FRAGMENT = 0x2;
30
+ const USAGE_UNIFORM_DST = 0x40 | 0x8; // UNIFORM | COPY_DST
31
+
32
+ const VERT = DEPTH_BINDINGS.GLASS_TINT_VERT;
33
+ const CUTOUT_VERT = DEPTH_BINDINGS.GLASS_TINT_CUTOUT_VERT;
34
+ const FRAG = DEPTH_BINDINGS.GLASS_TINT_FRAG;
35
+ const CUTOUT_MAP = DEPTH_BINDINGS.GLASS_TINT_CUTOUT_FRAG.textures.uCutoutMap;
36
+ const PALETTE = DEPTH_BINDINGS.GLASS_TINT_SKINNED_VERT.textures.uJointPalette;
37
+ const PANE = FRAG.fields.uGlassPane.offset;
38
+ const LIGHT = FRAG.fields.uGlassLight.offset;
39
+
40
+ /** Panes a frame can tint: a pane is a draw per shadow it reaches, and panes are few. */
41
+ const MAX_TINT_DRAWS = 512;
42
+
43
+ export type GlassTintKind = 'rigid' | 'instanced' | 'skinned' | 'cutout' | 'instancedCutout';
44
+
45
+ /** The multiplying state: colour times what passes, clarity times clarity, order-free. */
46
+ export const GLASS_TINT_BLEND: GPUBlendState = {
47
+ color: { operation: 'add', srcFactor: 'dst', dstFactor: 'zero' },
48
+ alpha: { operation: 'add', srcFactor: 'dst-alpha', dstFactor: 'zero' },
49
+ };
50
+
51
+ export class GpuGlassTint {
52
+ readonly frag: UniformRing;
53
+ private readonly layouts: Record<'plain' | 'cutout' | 'skinned', GPUBindGroupLayout>;
54
+ private plainGroup: GPUBindGroup | null = null;
55
+ private groupVertBuffer: GPUBuffer | null = null;
56
+ private groupFragBuffer: GPUBuffer | null = null;
57
+ private readonly cutoutGroups = new Map<GPUTextureView, GPUBindGroup>();
58
+ private readonly skinnedGroups = new Map<GPUTextureView, GPUBindGroup>();
59
+ private readonly device: GPUDevice;
60
+
61
+ constructor(device: GPUDevice) {
62
+ this.device = device;
63
+ this.frag = new UniformRing(
64
+ device,
65
+ FRAG.uniformSize,
66
+ MAX_TINT_DRAWS,
67
+ USAGE_UNIFORM_DST,
68
+ 'glassTint.ring',
69
+ );
70
+ const vert = (size: number): GPUBindGroupLayoutEntry => ({
71
+ binding: VERT.uniforms,
72
+ visibility: VISIBILITY_VERTEX,
73
+ buffer: { type: 'uniform', hasDynamicOffset: true, minBindingSize: size },
74
+ });
75
+ const frag: GPUBindGroupLayoutEntry = {
76
+ binding: FRAG.uniforms,
77
+ visibility: VISIBILITY_FRAGMENT,
78
+ buffer: { type: 'uniform', hasDynamicOffset: true, minBindingSize: FRAG.uniformSize },
79
+ };
80
+ this.layouts = {
81
+ plain: device.createBindGroupLayout({
82
+ label: 'glassTint.layout',
83
+ entries: [vert(VERT.uniformSize), frag],
84
+ }),
85
+ cutout: device.createBindGroupLayout({
86
+ label: 'glassTint.layout.cutout',
87
+ entries: [
88
+ vert(CUTOUT_VERT.uniformSize),
89
+ frag,
90
+ {
91
+ binding: CUTOUT_MAP.texture,
92
+ visibility: VISIBILITY_FRAGMENT,
93
+ /* A surface texture, and every surface texture is a `2d-array` view. */
94
+ texture: { sampleType: 'float', viewDimension: '2d-array' },
95
+ },
96
+ {
97
+ binding: CUTOUT_MAP.sampler,
98
+ visibility: VISIBILITY_FRAGMENT,
99
+ sampler: { type: 'filtering' },
100
+ },
101
+ ],
102
+ }),
103
+ skinned: device.createBindGroupLayout({
104
+ label: 'glassTint.layout.skinned',
105
+ entries: [
106
+ {
107
+ binding: PALETTE.texture,
108
+ visibility: VISIBILITY_VERTEX,
109
+ texture: { sampleType: 'unfilterable-float' },
110
+ },
111
+ vert(VERT.uniformSize),
112
+ frag,
113
+ ],
114
+ }),
115
+ };
116
+ }
117
+
118
+ /** One pane's block: what it lets through, how clear it is, and the light it is drawn for. */
119
+ writePane(slot: number, glass: ResolvedGlass, light: Float32Array): void {
120
+ const pane = paneScratch;
121
+ pane[0] = glass.transmission * glass.tint[0];
122
+ pane[1] = glass.transmission * glass.tint[1];
123
+ pane[2] = glass.transmission * glass.tint[2];
124
+ pane[3] = 1 - glass.frost;
125
+ this.frag.writeFloats(slot, PANE, pane);
126
+ this.frag.writeFloats(slot, LIGHT, light);
127
+ }
128
+
129
+ /** The plain group, rebuilt only when a ring has grown a new buffer under it. */
130
+ group(vertBuffer: GPUBuffer): GPUBindGroup {
131
+ if (
132
+ this.plainGroup === null ||
133
+ this.groupVertBuffer !== vertBuffer ||
134
+ this.groupFragBuffer !== this.frag.buffer
135
+ ) {
136
+ this.cutoutGroups.clear();
137
+ this.skinnedGroups.clear();
138
+ this.groupVertBuffer = vertBuffer;
139
+ this.groupFragBuffer = this.frag.buffer;
140
+ this.plainGroup = this.device.createBindGroup({
141
+ label: 'glassTint.group',
142
+ layout: this.layouts.plain,
143
+ entries: [
144
+ { binding: VERT.uniforms, resource: { buffer: vertBuffer, size: VERT.uniformSize } },
145
+ {
146
+ binding: FRAG.uniforms,
147
+ resource: { buffer: this.frag.buffer, size: FRAG.uniformSize },
148
+ },
149
+ ],
150
+ });
151
+ }
152
+ return this.plainGroup;
153
+ }
154
+
155
+ /** A cutout pane's group, one per map, built the first time that map casts colour. */
156
+ cutoutGroup(vertBuffer: GPUBuffer, view: GPUTextureView, sampler: GPUSampler): GPUBindGroup {
157
+ this.group(vertBuffer);
158
+ const held = this.cutoutGroups.get(view);
159
+ if (held !== undefined) return held;
160
+ const made = this.device.createBindGroup({
161
+ label: 'glassTint.group.cutout',
162
+ layout: this.layouts.cutout,
163
+ entries: [
164
+ {
165
+ binding: CUTOUT_VERT.uniforms,
166
+ resource: { buffer: vertBuffer, size: CUTOUT_VERT.uniformSize },
167
+ },
168
+ { binding: FRAG.uniforms, resource: { buffer: this.frag.buffer, size: FRAG.uniformSize } },
169
+ { binding: CUTOUT_MAP.texture, resource: view },
170
+ { binding: CUTOUT_MAP.sampler, resource: sampler },
171
+ ],
172
+ });
173
+ this.cutoutGroups.set(view, made);
174
+ return made;
175
+ }
176
+
177
+ /** A skinned pane's group, one per palette view. */
178
+ skinnedGroup(vertBuffer: GPUBuffer, palette: GPUTextureView): GPUBindGroup {
179
+ this.group(vertBuffer);
180
+ const held = this.skinnedGroups.get(palette);
181
+ if (held !== undefined) return held;
182
+ const made = this.device.createBindGroup({
183
+ label: 'glassTint.group.skinned',
184
+ layout: this.layouts.skinned,
185
+ entries: [
186
+ { binding: PALETTE.texture, resource: palette },
187
+ { binding: VERT.uniforms, resource: { buffer: vertBuffer, size: VERT.uniformSize } },
188
+ { binding: FRAG.uniforms, resource: { buffer: this.frag.buffer, size: FRAG.uniformSize } },
189
+ ],
190
+ });
191
+ this.skinnedGroups.set(palette, made);
192
+ return made;
193
+ }
194
+
195
+ /** The pipeline for one kind of caster, cull mode and vertex layout, cached by key. */
196
+ pipeline(
197
+ cache: PipelineCache,
198
+ kind: GlassTintKind,
199
+ key: string,
200
+ present: Readonly<Record<string, boolean>>,
201
+ cullMode: GPUCullMode,
202
+ ): GPURenderPipeline {
203
+ const cutout = kind === 'cutout' || kind === 'instancedCutout';
204
+ const instanced = kind === 'instanced' || kind === 'instancedCutout';
205
+ const layout =
206
+ kind === 'skinned' ? this.layouts.skinned : cutout ? this.layouts.cutout : this.layouts.plain;
207
+ const vertex =
208
+ kind === 'rigid'
209
+ ? GLASS_TINT_VERT_WGSL
210
+ : kind === 'instanced'
211
+ ? GLASS_TINT_INSTANCED_VERT_WGSL
212
+ : kind === 'skinned'
213
+ ? GLASS_TINT_SKINNED_VERT_WGSL
214
+ : kind === 'cutout'
215
+ ? GLASS_TINT_CUTOUT_VERT_WGSL
216
+ : GLASS_TINT_INSTANCED_CUTOUT_VERT_WGSL;
217
+ const label = `glass-tint|${kind}|${cullMode}|${key}`;
218
+ return cache.get(label, () => ({
219
+ label,
220
+ layout: this.device.createPipelineLayout({ bindGroupLayouts: [layout] }),
221
+ vertex: {
222
+ module: shaderModule(this.device, { label: `glassTint.${kind}.vert`, code: vertex }),
223
+ entryPoint: 'main',
224
+ buffers: vertexBufferLayouts(present, instanced),
225
+ },
226
+ fragment: {
227
+ module: shaderModule(this.device, {
228
+ label: cutout ? 'glassTint.cutout.frag' : 'glassTint.frag',
229
+ code: cutout ? GLASS_TINT_CUTOUT_FRAG_WGSL : GLASS_TINT_FRAG_WGSL,
230
+ }),
231
+ entryPoint: 'main',
232
+ targets: [{ format: 'rgba8unorm', blend: GLASS_TINT_BLEND }],
233
+ },
234
+ /* The depth pass's primitive state exactly — its winding included, for the reason
235
+ `depthPipeline` gives: this map is sampled, not presented. */
236
+ primitive: { topology: 'triangle-list', cullMode, frontFace: 'cw' },
237
+ }));
238
+ }
239
+
240
+ dispose(): void {
241
+ this.frag.dispose();
242
+ }
243
+ }
244
+
245
+ const paneScratch = new Float32Array(4);