@forgeax/engine-render 0.1.20 → 0.1.21

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 (372) hide show
  1. package/README.md +215 -0
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/assembly/device-feature-admission.d.ts +18 -0
  4. package/dist/assembly/device-feature-admission.d.ts.map +1 -0
  5. package/dist/assembly/directional-shadow-inspection.d.ts +38 -0
  6. package/dist/assembly/directional-shadow-inspection.d.ts.map +1 -0
  7. package/dist/assembly/factory.d.ts +21 -18
  8. package/dist/assembly/factory.d.ts.map +1 -1
  9. package/dist/assembly/host-contract.d.ts +9 -0
  10. package/dist/assembly/host-contract.d.ts.map +1 -1
  11. package/dist/assembly/render-target-host.d.ts +46 -0
  12. package/dist/assembly/render-target-host.d.ts.map +1 -0
  13. package/dist/assets/render-asset-port.d.ts.map +1 -1
  14. package/dist/authoring.mjs +3 -0
  15. package/dist/authoring.mjs.map +1 -1
  16. package/dist/capture/cube-views.d.ts +21 -0
  17. package/dist/capture/cube-views.d.ts.map +1 -0
  18. package/dist/capture/scheduler.d.ts +54 -0
  19. package/dist/capture/scheduler.d.ts.map +1 -0
  20. package/dist/{chunk-4XINDLAY.mjs → chunk-5YADUCA7.mjs} +1552 -1361
  21. package/dist/chunk-5YADUCA7.mjs.map +1 -0
  22. package/dist/{chunk-TBQJOK2L.mjs → chunk-6OPXDQ2E.mjs} +364 -51
  23. package/dist/chunk-6OPXDQ2E.mjs.map +1 -0
  24. package/dist/{chunk-M2MP4ZOU.mjs → chunk-BXBQQKFQ.mjs} +3 -9
  25. package/dist/chunk-BXBQQKFQ.mjs.map +1 -0
  26. package/dist/{chunk-CDTWTHZR.mjs → chunk-FS2KNUQH.mjs} +548 -6
  27. package/dist/chunk-FS2KNUQH.mjs.map +1 -0
  28. package/dist/components/camera.d.ts +1 -0
  29. package/dist/components/camera.d.ts.map +1 -1
  30. package/dist/components/cube-camera.d.ts +20 -0
  31. package/dist/components/cube-camera.d.ts.map +1 -0
  32. package/dist/components/directional-light.d.ts +11 -2
  33. package/dist/components/directional-light.d.ts.map +1 -1
  34. package/dist/components/directional-shadow-filter.d.ts +25 -0
  35. package/dist/components/directional-shadow-filter.d.ts.map +1 -0
  36. package/dist/components/index.d.ts +2 -0
  37. package/dist/components/index.d.ts.map +1 -1
  38. package/dist/components/light-helpers.d.ts.map +1 -1
  39. package/dist/components/reflection-probe.d.ts +17 -0
  40. package/dist/components/reflection-probe.d.ts.map +1 -0
  41. package/dist/construct-renderer.mjs +5826 -1003
  42. package/dist/construct-renderer.mjs.map +1 -1
  43. package/dist/environment/frame.d.ts.map +1 -1
  44. package/dist/environment/lifecycle.d.ts +5 -0
  45. package/dist/environment/lifecycle.d.ts.map +1 -1
  46. package/dist/errors/render.d.ts +141 -93
  47. package/dist/errors/render.d.ts.map +1 -1
  48. package/dist/extract/directional-shadow-projection.d.ts +23 -0
  49. package/dist/extract/directional-shadow-projection.d.ts.map +1 -0
  50. package/dist/extract/environment.d.ts +14 -0
  51. package/dist/extract/environment.d.ts.map +1 -1
  52. package/dist/extract/visibility.d.ts.map +1 -1
  53. package/dist/features/fullscreen.d.ts.map +1 -1
  54. package/dist/features/targets.d.ts.map +1 -1
  55. package/dist/features/types.d.ts +10 -0
  56. package/dist/features/types.d.ts.map +1 -1
  57. package/dist/fullscreen-post-process-pass.d.ts +13 -2
  58. package/dist/fullscreen-post-process-pass.d.ts.map +1 -1
  59. package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
  60. package/dist/gpu-driven/production-raster.d.ts +0 -1
  61. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  62. package/dist/gpu-scene.d.ts.map +1 -1
  63. package/dist/hdrp-buffers.d.ts +19 -17
  64. package/dist/hdrp-buffers.d.ts.map +1 -1
  65. package/dist/ibl/IblPipelineCache.d.ts +4 -0
  66. package/dist/ibl/IblPipelineCache.d.ts.map +1 -1
  67. package/dist/ibl/kernel-cache.d.ts +36 -0
  68. package/dist/ibl/kernel-cache.d.ts.map +1 -0
  69. package/dist/index.d.ts +28 -3
  70. package/dist/index.d.ts.map +1 -1
  71. package/dist/index.mjs +4 -4
  72. package/dist/index.mjs.map +1 -1
  73. package/dist/inspection-types.d.ts +51 -0
  74. package/dist/inspection-types.d.ts.map +1 -1
  75. package/dist/instances-derived-bounds.d.ts +50 -0
  76. package/dist/instances-derived-bounds.d.ts.map +1 -0
  77. package/dist/internal.mjs +1 -1
  78. package/dist/light-buffer-layout.d.ts +10 -1
  79. package/dist/light-buffer-layout.d.ts.map +1 -1
  80. package/dist/mesh-material-bindings.d.ts +54 -0
  81. package/dist/mesh-material-bindings.d.ts.map +1 -1
  82. package/dist/pbr-pipeline.d.ts +14 -4
  83. package/dist/pbr-pipeline.d.ts.map +1 -1
  84. package/dist/pipeline/standard-forward-lane.d.ts +2 -1
  85. package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
  86. package/dist/pipeline/standard-lighting/errors.d.ts +4 -0
  87. package/dist/pipeline/standard-lighting/errors.d.ts.map +1 -0
  88. package/dist/pipeline/standard-lighting/graph.d.ts +24 -0
  89. package/dist/pipeline/standard-lighting/graph.d.ts.map +1 -0
  90. package/dist/pipeline/standard-lighting/inspection.d.ts +38 -0
  91. package/dist/pipeline/standard-lighting/inspection.d.ts.map +1 -0
  92. package/dist/pipeline/standard-lighting/layout.d.ts +28 -0
  93. package/dist/pipeline/standard-lighting/layout.d.ts.map +1 -0
  94. package/dist/pipeline/standard-lighting/light-frame.d.ts +33 -0
  95. package/dist/pipeline/standard-lighting/light-frame.d.ts.map +1 -0
  96. package/dist/pipeline/standard-lighting/membership.d.ts +14 -0
  97. package/dist/pipeline/standard-lighting/membership.d.ts.map +1 -0
  98. package/dist/pipeline/standard-lighting/prepare.d.ts +16 -0
  99. package/dist/pipeline/standard-lighting/prepare.d.ts.map +1 -0
  100. package/dist/pipeline/standard-lighting/topology.d.ts +45 -0
  101. package/dist/pipeline/standard-lighting/topology.d.ts.map +1 -0
  102. package/dist/pipeline/standard-lighting/transport.d.ts +36 -0
  103. package/dist/pipeline/standard-lighting/transport.d.ts.map +1 -0
  104. package/dist/pipeline/standard-pipeline.d.ts +4 -13
  105. package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
  106. package/dist/pipeline/standard-post.d.ts.map +1 -1
  107. package/dist/pipeline/standard-profile.d.ts +5 -26
  108. package/dist/pipeline/standard-profile.d.ts.map +1 -1
  109. package/dist/pipeline-spec.d.ts +40 -7
  110. package/dist/pipeline-spec.d.ts.map +1 -1
  111. package/dist/point-shadow-inspection.d.ts +21 -0
  112. package/dist/point-shadow-inspection.d.ts.map +1 -0
  113. package/dist/record/frame-lighting.d.ts +27 -32
  114. package/dist/record/frame-lighting.d.ts.map +1 -1
  115. package/dist/record/frame-snapshot.d.ts +28 -14
  116. package/dist/record/frame-snapshot.d.ts.map +1 -1
  117. package/dist/record/frame-targets.d.ts +5 -0
  118. package/dist/record/frame-targets.d.ts.map +1 -1
  119. package/dist/record/frame.d.ts +10 -4
  120. package/dist/record/frame.d.ts.map +1 -1
  121. package/dist/record/gpu-pass-timing/contract.d.ts +96 -0
  122. package/dist/record/gpu-pass-timing/contract.d.ts.map +1 -0
  123. package/dist/record/gpu-pass-timing/errors.d.ts +18 -0
  124. package/dist/record/gpu-pass-timing/errors.d.ts.map +1 -0
  125. package/dist/record/gpu-pass-timing/index.d.ts +8 -0
  126. package/dist/record/gpu-pass-timing/index.d.ts.map +1 -0
  127. package/dist/record/gpu-pass-timing/parser.d.ts +11 -0
  128. package/dist/record/gpu-pass-timing/parser.d.ts.map +1 -0
  129. package/dist/record/gpu-pass-timing/session.d.ts +43 -0
  130. package/dist/record/gpu-pass-timing/session.d.ts.map +1 -0
  131. package/dist/record/gpu-timing.d.ts +8 -3
  132. package/dist/record/gpu-timing.d.ts.map +1 -1
  133. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  134. package/dist/record/main-pass-material.d.ts +22 -1
  135. package/dist/record/main-pass-material.d.ts.map +1 -1
  136. package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
  137. package/dist/record/main-pass.d.ts +2 -0
  138. package/dist/record/main-pass.d.ts.map +1 -1
  139. package/dist/record/render-context.d.ts +43 -2
  140. package/dist/record/render-context.d.ts.map +1 -1
  141. package/dist/record/shadow-pass.d.ts.map +1 -1
  142. package/dist/record/skybox-post-pass.d.ts.map +1 -1
  143. package/dist/record/typed-frame-graph.d.ts +72 -4
  144. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  145. package/dist/record/view-ubo.d.ts +7 -4
  146. package/dist/record/view-ubo.d.ts.map +1 -1
  147. package/dist/reflection/filter.d.ts +22 -0
  148. package/dist/reflection/filter.d.ts.map +1 -0
  149. package/dist/reflection/gpu-table.d.ts +25 -0
  150. package/dist/reflection/gpu-table.d.ts.map +1 -0
  151. package/dist/reflection/projection.d.ts +91 -0
  152. package/dist/reflection/projection.d.ts.map +1 -0
  153. package/dist/render-contract.d.ts +49 -7
  154. package/dist/render-contract.d.ts.map +1 -1
  155. package/dist/render-graph-primitives.d.ts +1 -0
  156. package/dist/render-graph-primitives.d.ts.map +1 -1
  157. package/dist/render-pipeline.d.ts +42 -2
  158. package/dist/render-pipeline.d.ts.map +1 -1
  159. package/dist/render-system-extract.d.ts +58 -17
  160. package/dist/render-system-extract.d.ts.map +1 -1
  161. package/dist/render-system.d.ts +17 -2
  162. package/dist/render-system.d.ts.map +1 -1
  163. package/dist/renderer.d.ts +43 -0
  164. package/dist/renderer.d.ts.map +1 -0
  165. package/dist/scene/render-scene.d.ts +14 -0
  166. package/dist/scene/render-scene.d.ts.map +1 -1
  167. package/dist/shadow-atlas.d.ts +3 -3
  168. package/dist/shadow-atlas.d.ts.map +1 -1
  169. package/dist/ssao-buffers.d.ts +3 -0
  170. package/dist/ssao-buffers.d.ts.map +1 -1
  171. package/dist/targets/contracts.d.ts +66 -0
  172. package/dist/targets/contracts.d.ts.map +1 -0
  173. package/dist/targets/graph-projection.d.ts +29 -0
  174. package/dist/targets/graph-projection.d.ts.map +1 -0
  175. package/dist/targets/material-source.d.ts +34 -0
  176. package/dist/targets/material-source.d.ts.map +1 -0
  177. package/dist/targets/owner.d.ts +32 -0
  178. package/dist/targets/owner.d.ts.map +1 -0
  179. package/dist/targets/physical.d.ts +18 -0
  180. package/dist/targets/physical.d.ts.map +1 -0
  181. package/dist/targets/readback.d.ts +39 -0
  182. package/dist/targets/readback.d.ts.map +1 -0
  183. package/dist/temporal/index.mjs +3 -3
  184. package/dist/typed-render-graph-primitives.d.ts +1 -1
  185. package/dist/typed-render-graph-primitives.d.ts.map +1 -1
  186. package/dist/typed-shadow-passes.d.ts +1 -1
  187. package/dist/typed-shadow-passes.d.ts.map +1 -1
  188. package/dist/volume/passes.d.ts +2 -2
  189. package/dist/volume/passes.d.ts.map +1 -1
  190. package/package.json +20 -20
  191. package/src/__tests__/bloom-exact-zero.unit.test.ts +48 -19
  192. package/src/__tests__/bloom-lifecycle.integration.test.ts +41 -13
  193. package/src/__tests__/camera-target-consumption.unit.test.ts +53 -0
  194. package/src/__tests__/cluster-binner.unit.test.ts +35 -0
  195. package/src/__tests__/cluster-grid-default-surface.unit.test.ts +7 -2
  196. package/src/__tests__/cube-camera-scheduler.unit.test.ts +70 -0
  197. package/src/__tests__/cube-camera-single-submit.perf.test.ts +75 -0
  198. package/src/__tests__/cube-camera-view-matrices.unit.test.ts +41 -0
  199. package/src/__tests__/cube-camera-vocabulary.unit.test.ts +47 -0
  200. package/src/__tests__/cube-camera.integration.test.ts +42 -0
  201. package/src/__tests__/device-feature-admission.unit.test.ts +73 -0
  202. package/src/__tests__/direct-light-snapshot.unit.test.ts +3 -3
  203. package/src/__tests__/docs-gate-green.unit.test.ts +38 -0
  204. package/src/__tests__/factory-contract.integration.test.ts +255 -29
  205. package/src/__tests__/fog-component.unit.test.ts +67 -0
  206. package/src/__tests__/fog.integration.test.ts +86 -0
  207. package/src/__tests__/fullscreen-feature-plan.unit.test.ts +37 -0
  208. package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +10 -9
  209. package/src/__tests__/gpu-driven-indirect-raster.dawn.test.ts +17 -1
  210. package/src/__tests__/gpu-driven-production.integration.test.ts +16 -17
  211. package/src/__tests__/gpu-driven-view-graph.integration.test.ts +12 -4
  212. package/src/__tests__/gpu-pass-timing-browser-runner.ts +281 -0
  213. package/src/__tests__/gpu-pass-timing-coexistence.integration.test.ts +80 -0
  214. package/src/__tests__/gpu-pass-timing-contract.unit.test.ts +243 -0
  215. package/src/__tests__/gpu-pass-timing-exact-zero.unit.test.ts +131 -0
  216. package/src/__tests__/gpu-pass-timing-lifecycle.integration.test.ts +230 -0
  217. package/src/__tests__/gpu-pass-timing-observation.unit.test.ts +233 -0
  218. package/src/__tests__/gpu-pass-timing-session.unit.test.ts +438 -0
  219. package/src/__tests__/gpu-pass-timing.browser.test.ts +26 -0
  220. package/src/__tests__/gpu-pass-timing.rhi-null.unit.test.ts +125 -0
  221. package/src/__tests__/hdrp-group2-visibility.unit.test.ts +24 -0
  222. package/src/__tests__/instances-culling.integration.test.ts +94 -0
  223. package/src/__tests__/instances-derived-bounds.unit.test.ts +118 -0
  224. package/src/__tests__/material-observation.integration.test.ts +101 -0
  225. package/src/__tests__/pbr-view-layout.unit.test.ts +16 -0
  226. package/src/__tests__/point-shadow-inspection.unit.test.ts +60 -0
  227. package/src/__tests__/public-surface.test-d.ts +10 -0
  228. package/src/__tests__/reflection-probe-authoring.unit.test.ts +71 -0
  229. package/src/__tests__/reflection-probe-filter.unit.test.ts +54 -0
  230. package/src/__tests__/reflection-probe-ibl-topology.unit.test.ts +45 -0
  231. package/src/__tests__/reflection-probe-pass-encode.integration.test.ts +101 -0
  232. package/src/__tests__/reflection-probe-performance.perf.test.ts +60 -0
  233. package/src/__tests__/reflection-probe-recovery.integration.test.ts +55 -0
  234. package/src/__tests__/reflection-probe-selection.unit.test.ts +46 -0
  235. package/src/__tests__/render-error-code-owner.test-d.ts +18 -3
  236. package/src/__tests__/render-feature-docs.unit.test.ts +1 -1
  237. package/src/__tests__/render-target-admission.unit.test.ts +80 -0
  238. package/src/__tests__/render-target-graph-projection.unit.test.ts +102 -0
  239. package/src/__tests__/render-target-inspection.unit.test.ts +52 -0
  240. package/src/__tests__/render-target-material-source.test-d.ts +30 -0
  241. package/src/__tests__/render-target-material-source.unit.test.ts +97 -0
  242. package/src/__tests__/render-target-owner-placement.unit.test.ts +203 -0
  243. package/src/__tests__/render-target-owner.unit.test.ts +122 -0
  244. package/src/__tests__/render-target-probe-soak.perf.test.ts +94 -0
  245. package/src/__tests__/render-target-public-schema.json +107 -0
  246. package/src/__tests__/render-target-public-schema.unit.test.ts +91 -0
  247. package/src/__tests__/render-target-public-surface.test-d.ts +83 -0
  248. package/src/__tests__/render-target-readback.integration.test.ts +90 -0
  249. package/src/__tests__/render-target-recovery.integration.test.ts +85 -0
  250. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +48 -0
  251. package/src/__tests__/renderer-lifecycle.integration.test.ts +14 -0
  252. package/src/__tests__/shader-stage-surface.unit.test.ts +19 -4
  253. package/src/__tests__/skin-motion-regression.unit.test.ts +132 -0
  254. package/src/__tests__/skinned-shadow-caster.test.ts +11 -2
  255. package/src/__tests__/standard-cluster-capability.integration.test.ts +63 -0
  256. package/src/__tests__/standard-cluster-transport.unit.test.ts +121 -0
  257. package/src/__tests__/standard-cluster-variant-resolution.unit.test.ts +232 -0
  258. package/src/__tests__/standard-clustered-pipeline.unit.test.ts +8 -5
  259. package/src/__tests__/standard-lighting-inspection.integration.test.ts +138 -0
  260. package/src/__tests__/standard-lighting-ssot.unit.test.ts +18 -0
  261. package/src/__tests__/standard-lighting-write.unit.test.ts +377 -0
  262. package/src/__tests__/standard-lighting.integration.test.ts +67 -0
  263. package/src/__tests__/standard-pipeline.dawn.test.ts +206 -5
  264. package/src/__tests__/standard-pipeline.integration.test.ts +181 -150
  265. package/src/__tests__/standard-probe-binding.unit.test.ts +25 -0
  266. package/src/__tests__/standard-profile-oracle.unit.test.ts +23 -38
  267. package/src/__tests__/typed-pipeline-topology.unit.test.ts +395 -14
  268. package/src/__tests__/urp-pipeline-targets.unit.test.ts +2 -2
  269. package/src/__tests__/visibility-resolver.unit.test.ts +15 -0
  270. package/src/assembly/device-feature-admission.ts +73 -0
  271. package/src/assembly/directional-shadow-inspection.ts +122 -0
  272. package/src/assembly/factory.ts +877 -286
  273. package/src/assembly/host-contract.ts +25 -0
  274. package/src/assembly/render-target-host.ts +847 -0
  275. package/src/assets/asset-decoders.ts +1 -1
  276. package/src/assets/render-asset-port.ts +7 -1
  277. package/src/capture/cube-views.ts +69 -0
  278. package/src/capture/scheduler.ts +265 -0
  279. package/src/components/__tests__/schema-contract.unit.test.ts +12 -0
  280. package/src/components/__tests__/schema-parity.unit.test.ts +9 -0
  281. package/src/components/camera.ts +4 -0
  282. package/src/components/cube-camera.ts +52 -0
  283. package/src/components/directional-light.ts +22 -2
  284. package/src/components/directional-shadow-filter.ts +57 -0
  285. package/src/components/index.ts +2 -0
  286. package/src/components/light-helpers.ts +81 -7
  287. package/src/components/reflection-probe.ts +49 -0
  288. package/src/environment/frame.ts +6 -24
  289. package/src/environment/lifecycle.ts +12 -0
  290. package/src/errors/render.ts +286 -142
  291. package/src/extract/directional-shadow-projection.ts +61 -0
  292. package/src/extract/environment.ts +57 -0
  293. package/src/extract/visibility.ts +25 -1
  294. package/src/features/fullscreen.ts +3 -0
  295. package/src/features/targets.ts +4 -1
  296. package/src/features/types.ts +10 -0
  297. package/src/fullscreen-post-process-pass.ts +43 -2
  298. package/src/gpu-driven/batch-topology.ts +1 -0
  299. package/src/gpu-driven/production-raster.ts +1 -3
  300. package/src/gpu-scene.ts +8 -1
  301. package/src/hdrp-buffers.ts +165 -59
  302. package/src/ibl/IblPipelineCache.ts +11 -0
  303. package/src/ibl/kernel-cache.ts +88 -0
  304. package/src/index.ts +148 -0
  305. package/src/inspection-types.ts +56 -0
  306. package/src/instances-derived-bounds.ts +171 -0
  307. package/src/light-buffer-layout.ts +20 -5
  308. package/src/mesh-material-bindings.ts +132 -0
  309. package/src/pbr-pipeline.ts +106 -17
  310. package/src/pipeline/standard-forward-lane.ts +46 -16
  311. package/src/pipeline/standard-lighting/errors.ts +6 -0
  312. package/src/pipeline/standard-lighting/graph.ts +157 -0
  313. package/src/pipeline/standard-lighting/inspection.ts +88 -0
  314. package/src/pipeline/standard-lighting/layout.ts +31 -0
  315. package/src/pipeline/standard-lighting/light-frame.ts +34 -0
  316. package/src/pipeline/standard-lighting/membership.ts +43 -0
  317. package/src/pipeline/standard-lighting/prepare.ts +37 -0
  318. package/src/pipeline/standard-lighting/topology.ts +120 -0
  319. package/src/pipeline/standard-lighting/transport.ts +77 -0
  320. package/src/pipeline/standard-pipeline.ts +122 -193
  321. package/src/pipeline/standard-post.ts +26 -7
  322. package/src/pipeline/standard-profile.ts +17 -47
  323. package/src/pipeline-spec.ts +84 -21
  324. package/src/point-shadow-inspection.ts +55 -0
  325. package/src/record/__tests__/temporal-payload.unit.test.ts +42 -1
  326. package/src/record/__tests__/transparent-group2-selection.unit.test.ts +73 -0
  327. package/src/record/__tests__/view-ubo-layout.unit.test.ts +40 -0
  328. package/src/record/frame-lighting.ts +367 -427
  329. package/src/record/frame-snapshot.ts +35 -27
  330. package/src/record/frame-targets.ts +17 -0
  331. package/src/record/frame.ts +261 -74
  332. package/src/record/gpu-pass-timing/contract.ts +180 -0
  333. package/src/record/gpu-pass-timing/errors.ts +34 -0
  334. package/src/record/gpu-pass-timing/index.ts +37 -0
  335. package/src/record/gpu-pass-timing/parser.ts +97 -0
  336. package/src/record/gpu-pass-timing/session.ts +938 -0
  337. package/src/record/gpu-timing.ts +21 -12
  338. package/src/record/main-pass-geometry.ts +120 -80
  339. package/src/record/main-pass-material.ts +95 -9
  340. package/src/record/main-pass-sprite-draws.ts +54 -25
  341. package/src/record/main-pass.ts +71 -15
  342. package/src/record/render-context.ts +72 -0
  343. package/src/record/shadow-pass.ts +17 -28
  344. package/src/record/skybox-post-pass.ts +20 -4
  345. package/src/record/typed-frame-graph.ts +893 -44
  346. package/src/record/view-ubo.ts +63 -28
  347. package/src/reflection/filter.ts +81 -0
  348. package/src/reflection/gpu-table.ts +44 -0
  349. package/src/reflection/projection.ts +281 -0
  350. package/src/render-contract.ts +76 -5
  351. package/src/render-graph-primitives.ts +9 -4
  352. package/src/render-pipeline.ts +117 -2
  353. package/src/render-system-extract.ts +557 -100
  354. package/src/render-system.ts +1095 -66
  355. package/src/renderer.ts +222 -0
  356. package/src/scene/render-scene.ts +115 -9
  357. package/src/shadow-atlas.ts +5 -5
  358. package/src/ssao-buffers.ts +9 -2
  359. package/src/targets/contracts.ts +182 -0
  360. package/src/targets/graph-projection.ts +169 -0
  361. package/src/targets/material-source.ts +143 -0
  362. package/src/targets/owner.ts +214 -0
  363. package/src/targets/physical.ts +18 -0
  364. package/src/targets/readback.ts +153 -0
  365. package/src/transmission/__tests__/standard-transmission.dawn.test.ts +59 -20
  366. package/src/typed-render-graph-primitives.ts +5 -3
  367. package/src/typed-shadow-passes.ts +46 -40
  368. package/src/volume/passes.ts +15 -9
  369. package/dist/chunk-4XINDLAY.mjs.map +0 -1
  370. package/dist/chunk-CDTWTHZR.mjs.map +0 -1
  371. package/dist/chunk-M2MP4ZOU.mjs.map +0 -1
  372. package/dist/chunk-TBQJOK2L.mjs.map +0 -1
@@ -1,24 +1,19 @@
1
1
  import { TRANSPARENT_SORT_MODE_LAYER_Z, TRANSPARENT_SORT_MODE_LAYER_Y } from './chunk-RXZ3RGOU.mjs';
2
- import { DEFAULT_CLUSTER_GRID, CLUSTER_GRID_STRIDE_U32, LIGHT_INDEX_LIST_CAPACITY, EquirectProjectionFailedError, PointShadowAtlasUninitializedError, PointShadowAtlasBoundsViolationError, HdrpLightBudgetExceededError, STANDARD_OUTPUT_TRANSFORM_FEATURE_ID, MAX_LIGHTS, HdrpIndexListOverflowError, VideoUploadUnsupportedError, VertexColorVariantConflictError } from './chunk-TBQJOK2L.mjs';
2
+ import { DEFAULT_CLUSTER_GRID, GPU_TEXTURE_USAGE_RENDER_ATTACHMENT, GPU_TEXTURE_USAGE_COPY_SRC, EquirectProjectionFailedError, SHADOW_ATLAS_DEFAULT_LAYERS, SHADOW_ATLAS_DEFAULT_FACE_SIZE, ShadowAtlas, PointShadowAtlasUninitializedError, PointShadowAtlasBoundsViolationError, DEFAULT_STANDARD_PROFILE, StandardClusterTransportUnavailableError, createIblKernelCache, GPU_TEXTURE_USAGE_RENDER_ATTACHMENT_AND_TEXTURE_BINDING, GPU_TEXTURE_USAGE_COPY_DST, GPU_TEXTURE_USAGE_TEXTURE_BINDING, STANDARD_OUTPUT_TRANSFORM_FEATURE_ID, MAX_LIGHTS, CLUSTER_GRID_STRIDE_U32, LIGHT_INDEX_LIST_CAPACITY, StandardLightBudgetExceededError, createStandardClusterLayout, RenderTargetDescriptorInvalidError, RendererOperationError, StandardClusterIndexOverflowError, FXAA_POST_PROCESS_ID, VideoUploadUnsupportedError, VertexColorVariantConflictError } from './chunk-6OPXDQ2E.mjs';
3
3
  import { SPRITE_PREMULTIPLIED_ALPHA_BLEND } from './chunk-VFPLTKCU.mjs';
4
- import { clampPcfKernelSize, worldEntityKey } from './chunk-M2MP4ZOU.mjs';
4
+ import { worldEntityKey } from './chunk-BXBQQKFQ.mjs';
5
5
  import { ok, derive, err as err$1, toShared, ASSET_ERROR_HINTS, deriveTextureLayout, KNOWN_PASS_KINDS, resolveMaterialTextureCoordinates, handleSlot } from '@forgeax/engine-types';
6
- import { ok as ok$1, err, RhiError, cubeArrayDepthDescriptor, comparisonSamplerDescriptor, cubeArrayDepthFaceView } from '@forgeax/engine-rhi';
7
- import { vec3, mat3, mat4 } from '@forgeax/engine-math';
8
- import { deriveVertexLayoutProjection, deriveVertexBufferLayout } from '@forgeax/engine-geometry';
6
+ import { ok as ok$1, err, RhiError } from '@forgeax/engine-rhi';
7
+ import { mat3, vec3, mat4 } from '@forgeax/engine-math';
9
8
  import { DEFAULT_STANDARD_PBR_PARAM_SCHEMA, DEFAULT_MSDF_TEXT_PARAM_SCHEMA, DEFAULT_SPRITE_PARAM_SCHEMA, DEFAULT_UNLIT_PARAM_SCHEMA } from '@forgeax/engine-shader';
9
+ import { deriveVertexLayoutProjection, deriveVertexBufferLayout } from '@forgeax/engine-geometry';
10
10
  import { resolveAssetHandle } from '@forgeax/engine-assets-runtime';
11
11
  import { probeVideoHighPerfUpload, VIDEO_ELEMENT_PROVIDER_KEY } from '@forgeax/engine-graphics-extras';
12
12
  import { isCompressedFormat, bytesPerRow, blockParamsForFormat } from '@forgeax/engine-codec';
13
13
 
14
- // src/gpu-texture-usage.ts
15
- var GPU_TEXTURE_USAGE_COPY_SRC = 1;
16
- var GPU_TEXTURE_USAGE_COPY_DST = 2;
17
- var GPU_TEXTURE_USAGE_TEXTURE_BINDING = 4;
18
- var GPU_TEXTURE_USAGE_RENDER_ATTACHMENT = 16;
19
- var GPU_TEXTURE_USAGE_RENDER_ATTACHMENT_AND_TEXTURE_BINDING = GPU_TEXTURE_USAGE_RENDER_ATTACHMENT | GPU_TEXTURE_USAGE_TEXTURE_BINDING;
20
-
21
- // src/render-pipeline.ts
14
+ function resolveOutputDither(config) {
15
+ return config?.outputDither ?? true;
16
+ }
22
17
  function createRenderPipelineTarget(graph, label, descriptor, viewDescriptor = {}) {
23
18
  const texture = graph.createTexture(label, descriptor);
24
19
  if (!texture.ok) return texture;
@@ -88,628 +83,214 @@ function importRenderPipelineSurface(graph, topology) {
88
83
  }
89
84
  });
90
85
  }
91
- function runClusterBinProfilePhase(runner, phase, action) {
92
- return runner === void 0 ? action() : runner(phase, action);
93
- }
94
- function createClusterBinScratch() {
95
- return {
96
- clusterCounts: new Uint32Array(0),
97
- clusterCursors: new Uint32Array(0),
98
- lightBounds: new Int32Array(0),
99
- clusterDeltas: new Int32Array(0),
100
- aabbOutput: {
101
- min: vec3.create(0, 0, 0),
102
- max: vec3.create(0, 0, 0)
103
- },
104
- boundsOutput: {
105
- min: { x: 0, y: 0, z: 0 },
106
- max: { x: 0, y: 0, z: 0 }
107
- }
108
- };
109
- }
110
- function ensureScratchCapacity(scratch, clusterCount, lightCount, gridX, gridY, gridZ) {
111
- if (scratch.clusterCounts.length < clusterCount) {
112
- scratch.clusterCounts = new Uint32Array(clusterCount);
113
- scratch.clusterCursors = new Uint32Array(clusterCount);
86
+
87
+ // src/gpu-stage.ts
88
+ var GPU_SHADER_STAGE_VERTEX = 1;
89
+ var GPU_SHADER_STAGE_FRAGMENT = 2;
90
+ var GPU_SHADER_STAGE_COMPUTE = 4;
91
+
92
+ // src/gpu-usage.ts
93
+ var GPU_BUFFER_USAGE_VERTEX = 32;
94
+ var GPU_BUFFER_USAGE_INDEX = 16;
95
+ var GPU_BUFFER_USAGE_COPY_DST = 8;
96
+ var GPU_BUFFER_USAGE_COPY_SRC = 4;
97
+ var GPU_BUFFER_USAGE_UNIFORM = 64;
98
+ var GPU_BUFFER_USAGE_STORAGE = 128;
99
+ var GPU_BUFFER_USAGE_INDIRECT = 256;
100
+ var GPU_BUFFER_USAGE_MAP_READ = 1;
101
+ var GPU_BUFFER_USAGE_QUERY_RESOLVE = 512;
102
+ var BYTES_PER_LIGHT_SLOT = 64;
103
+ var LightSlotKind = {
104
+ POINT: 0,
105
+ SPOT: 1
106
+ };
107
+ var STORAGE_BUFFER_MIN_REQUIRED = 4;
108
+ var POINT_LIGHT_STD430_BYTES = 32;
109
+ var SPOT_LIGHT_STD430_BYTES = 80;
110
+ var SPOT_LIGHT_SHADOW_TILE_SENTINEL = -1;
111
+ var SPOT_LIGHT_PROJECTOR_FLAG = 1073741824;
112
+ var SPOT_LIGHT_TILE_MASK = SPOT_LIGHT_PROJECTOR_FLAG - 1;
113
+ var LIGHT_ARRAY_HEADER_BYTES = 16;
114
+ var LIGHT_ARRAY_MAX_SLOTS = 4;
115
+ var POINT_LIGHT_SHADOW_LAYER_SENTINEL = -1;
116
+ function packLightSlot(snap, shadow, projectorSelected) {
117
+ const out = new Float32Array(16);
118
+ out[0] = snap.position[0] ?? 0;
119
+ out[1] = snap.position[1] ?? 0;
120
+ out[2] = snap.position[2] ?? 0;
121
+ out[3] = snap.invRangeSquared;
122
+ out[4] = snap.color[0] ?? 0;
123
+ out[5] = snap.color[1] ?? 0;
124
+ out[6] = snap.color[2] ?? 0;
125
+ out[7] = snap.kind === "point" ? 1 : snap.cosInner;
126
+ out[8] = snap.kind === "point" ? 0 : snap.direction[0] ?? 0;
127
+ out[9] = snap.kind === "point" ? 0 : snap.direction[1] ?? 0;
128
+ out[10] = snap.kind === "point" ? 0 : snap.direction[2] ?? 0;
129
+ out[11] = snap.kind === "point" ? 0 : snap.cosOuter;
130
+ const u32 = new Uint32Array(out.buffer);
131
+ u32[12] = snap.kind === "point" ? LightSlotKind.POINT : LightSlotKind.SPOT;
132
+ const i32 = new Int32Array(out.buffer);
133
+ if (snap.kind === "point" && shadow !== void 0) {
134
+ i32[13] = shadow.shadowAtlasLayer;
135
+ out[14] = shadow.near;
136
+ out[15] = shadow.far;
137
+ } else if (snap.kind === "spot") {
138
+ const hasProjector = projectorSelected ?? (snap.projectorAsset !== void 0 && snap.lightViewProj !== void 0);
139
+ const tile = snap.shadowAtlasTile >= 0 ? snap.shadowAtlasTile : 0;
140
+ i32[13] = hasProjector ? SPOT_LIGHT_PROJECTOR_FLAG | tile & SPOT_LIGHT_TILE_MASK : snap.shadowAtlasTile >= 0 ? snap.shadowAtlasTile : SPOT_LIGHT_SHADOW_TILE_SENTINEL;
141
+ out[14] = snap.shadowIntensity ?? 1;
142
+ out[15] = snap.pcfKernelSize ?? 3;
143
+ } else {
144
+ i32[13] = POINT_LIGHT_SHADOW_LAYER_SENTINEL;
114
145
  }
115
- if (scratch.lightBounds.length < lightCount * 6) {
116
- scratch.lightBounds = new Int32Array(lightCount * 6);
146
+ return out;
147
+ }
148
+ function assertStorageBufferCap(cap) {
149
+ if (cap >= STORAGE_BUFFER_MIN_REQUIRED) {
150
+ return ok$1(true);
117
151
  }
118
- const differenceVolumeSize = (gridX + 1) * (gridY + 1) * (gridZ + 1);
119
- if (scratch.clusterDeltas.length < differenceVolumeSize) {
120
- scratch.clusterDeltas = new Int32Array(differenceVolumeSize);
152
+ if (cap === 0) {
153
+ return ok$1(false);
121
154
  }
155
+ return err(
156
+ new RhiError({
157
+ code: "limit-exceeded",
158
+ expected: `device.limits.maxStorageBuffersPerShaderStage >= ${STORAGE_BUFFER_MIN_REQUIRED}, or exactly 0 for uniform-fallback path`,
159
+ hint: `device.limits.maxStorageBuffersPerShaderStage = ${cap} < ${STORAGE_BUFFER_MIN_REQUIRED} and != 0. WebGPU spec default minimum is 8; this may be a downlevel adapter with partial storage support. Wait for todo-125 multi-light-pack which collapses point + spot into a single cluster storage buffer (1 entry instead of 2) for 2-3 buffer environments.`,
160
+ detail: {
161
+ maxStorageBufferBindingSize: cap,
162
+ requestedBytes: STORAGE_BUFFER_MIN_REQUIRED
163
+ }
164
+ })
165
+ );
122
166
  }
123
- function deriveCullingRadius(range, intensity, threshold = 1e-3) {
124
- if (Number.isFinite(range)) {
125
- return Math.max(0, range);
167
+ var BGL_ONLY_SPEC_STUB = Object.freeze({
168
+ shader: { id: "", passKind: "forward", variantSet: void 0 },
169
+ attachments: { colorFormats: [], depthFormat: void 0, sampleCount: 1 },
170
+ geometry: { topology: "triangle-list", vertexLayout: {} },
171
+ renderState: void 0
172
+ });
173
+ function buildPbrMaterialUserRegionEntries(paramSchema = DEFAULT_STANDARD_PBR_PARAM_SCHEMA) {
174
+ const derived = derive(paramSchema);
175
+ const entries = derived.bglEntries.map(
176
+ (entry) => ({
177
+ binding: entry.binding,
178
+ visibility: entry.visibility,
179
+ ...entry.buffer === void 0 ? {} : { buffer: entry.buffer },
180
+ ...entry.sampler === void 0 ? {} : { sampler: entry.sampler },
181
+ ...entry.texture === void 0 ? {} : { texture: entry.texture },
182
+ ...entry.storageTexture === void 0 ? {} : { storageTexture: entry.storageTexture }
183
+ })
184
+ );
185
+ const ubo = entries[0];
186
+ if (ubo !== void 0 && ubo.binding === 0 && ubo.buffer?.type === "uniform") {
187
+ entries[0] = {
188
+ binding: 0,
189
+ visibility: GPU_SHADER_STAGE_VERTEX | GPU_SHADER_STAGE_FRAGMENT,
190
+ buffer: { type: "uniform", hasDynamicOffset: true }
191
+ };
126
192
  }
127
- const derived = Math.sqrt(intensity / threshold);
128
- return Math.min(derived, 1e3);
193
+ return entries;
129
194
  }
130
- function viewZToZSlice(viewZ, gridZ, near, far) {
131
- if (viewZ >= -near) {
132
- return 0;
133
- }
134
- const logFarOverNear = Math.log(far / near);
135
- const slice = Math.floor(Math.log(-viewZ / near) / logFarOverNear * gridZ);
136
- if (slice < 0) return 0;
137
- if (slice >= gridZ) return gridZ - 1;
138
- return slice;
139
- }
140
- function clusterSpaceObjectAabb(center, radius, view, proj, output) {
141
- const result = output ?? {
142
- min: vec3.create(0, 0, 0),
143
- max: vec3.create(0, 0, 0)
144
- };
145
- const cx = center[0] ?? 0;
146
- const cy = center[1] ?? 0;
147
- const cz = center[2] ?? 0;
148
- const v00 = view[0] ?? 0;
149
- const v01 = view[1] ?? 0;
150
- const v02 = view[2] ?? 0;
151
- const v10 = view[4] ?? 0;
152
- const v11 = view[5] ?? 0;
153
- const v12 = view[6] ?? 0;
154
- const v20 = view[8] ?? 0;
155
- const v21 = view[9] ?? 0;
156
- const v22 = view[10] ?? 0;
157
- const v30 = view[12] ?? 0;
158
- const v31 = view[13] ?? 0;
159
- const v32 = view[14] ?? 0;
160
- const vx = v00 * cx + v10 * cy + v20 * cz + v30;
161
- const vy = v01 * cx + v11 * cy + v21 * cz + v31;
162
- const vz = v02 * cx + v12 * cy + v22 * cz + v32;
163
- const viewScaleX = Math.hypot(v00, v01, v02);
164
- const viewScaleY = Math.hypot(v10, v11, v12);
165
- const viewScaleZ = Math.hypot(v20, v21, v22);
166
- const rx = radius * viewScaleX;
167
- const ry = radius * viewScaleY;
168
- const rz = radius * viewScaleZ;
169
- const vMinX = vx - rx;
170
- const vMaxX = vx + rx;
171
- const vMinY = vy - ry;
172
- const vMaxY = vy + ry;
173
- const vMinZ = vz - rz;
174
- const vMaxZ = vz + rz;
175
- const minViewZ = vMinZ;
176
- const maxViewZ = Math.min(vMaxZ, -1e-5);
177
- if (minViewZ > maxViewZ) {
178
- result.min[0] = 1;
179
- result.min[1] = 1;
180
- result.min[2] = -1e-5;
181
- result.max[0] = -1;
182
- result.max[1] = -1;
183
- result.max[2] = -1e-5;
184
- return result;
185
- }
186
- let ndcMinX = Infinity;
187
- let ndcMaxX = -Infinity;
188
- let ndcMinY = Infinity;
189
- let ndcMaxY = -Infinity;
190
- const p00 = proj[0] ?? 0;
191
- const p10 = proj[4] ?? 0;
192
- const p20 = proj[8] ?? 0;
193
- const p30 = proj[12] ?? 0;
194
- const p01 = proj[1] ?? 0;
195
- const p11 = proj[5] ?? 0;
196
- const p21 = proj[9] ?? 0;
197
- const p31 = proj[13] ?? 0;
198
- const p23 = proj[11] ?? 0;
199
- const perspectiveWOnlyZ = Math.abs(proj[3] ?? 0) < 1e-8 && Math.abs(proj[7] ?? 0) < 1e-8 && Math.abs(proj[15] ?? 0) < 1e-8 && Math.abs(p23) >= 1e-5;
200
- if (perspectiveWOnlyZ) {
201
- const xExtent = Math.abs(p00) * rx + Math.abs(p10) * ry;
202
- const yExtent = Math.abs(p01) * rx + Math.abs(p11) * ry;
203
- for (let face = 0; face < 2; face++) {
204
- const zTest = face === 0 ? minViewZ : maxViewZ;
205
- if (zTest > 0) continue;
206
- const invW = 1 / (p23 * zTest);
207
- const xCenter = p20 * zTest + p30;
208
- const yCenter = p21 * zTest + p31;
209
- const x0 = (xCenter - xExtent) * invW;
210
- const x1 = (xCenter + xExtent) * invW;
211
- const y0 = (yCenter - yExtent) * invW;
212
- const y1 = (yCenter + yExtent) * invW;
213
- ndcMinX = Math.min(ndcMinX, x0, x1);
214
- ndcMaxX = Math.max(ndcMaxX, x0, x1);
215
- ndcMinY = Math.min(ndcMinY, y0, y1);
216
- ndcMaxY = Math.max(ndcMaxY, y0, y1);
217
- }
218
- } else {
219
- const corners = [
220
- [vMinX, vMinY],
221
- [vMinX, vMaxY],
222
- [vMaxX, vMinY],
223
- [vMaxX, vMaxY]
224
- ];
225
- for (const zTest of [minViewZ, maxViewZ]) {
226
- if (zTest <= 0) {
227
- for (const corner of corners) {
228
- const sx = corner[0];
229
- const sy = corner[1];
230
- const ndc = projectToNdc(sx, sy, zTest, proj);
231
- ndcMinX = Math.min(ndcMinX, ndc[0]);
232
- ndcMaxX = Math.max(ndcMaxX, ndc[0]);
233
- ndcMinY = Math.min(ndcMinY, ndc[1]);
234
- ndcMaxY = Math.max(ndcMaxY, ndc[1]);
235
- }
236
- }
237
- }
238
- }
239
- ndcMinX = Math.max(ndcMinX, -1);
240
- ndcMinY = Math.max(ndcMinY, -1);
241
- ndcMaxX = Math.min(ndcMaxX, 1);
242
- ndcMaxY = Math.min(ndcMaxY, 1);
243
- result.min[0] = ndcMinX;
244
- result.min[1] = ndcMinY;
245
- result.min[2] = minViewZ;
246
- result.max[0] = ndcMaxX;
247
- result.max[1] = ndcMaxY;
248
- result.max[2] = maxViewZ;
249
- return result;
250
- }
251
- function projectToNdc(vx, vy, vz, proj) {
252
- const p00 = proj[0] ?? 0;
253
- const p10 = proj[4] ?? 0;
254
- const p20 = proj[8] ?? 0;
255
- const p30 = proj[12] ?? 0;
256
- const p01 = proj[1] ?? 0;
257
- const p11 = proj[5] ?? 0;
258
- const p21 = proj[9] ?? 0;
259
- const p31 = proj[13] ?? 0;
260
- const p02 = proj[2] ?? 0;
261
- const p12 = proj[6] ?? 0;
262
- const p22 = proj[10] ?? 0;
263
- const p32 = proj[14] ?? 0;
264
- const p03 = proj[3] ?? 0;
265
- const p13 = proj[7] ?? 0;
266
- const p23 = proj[11] ?? 0;
267
- const p33 = proj[15] ?? 0;
268
- const cx = p00 * vx + p10 * vy + p20 * vz + p30;
269
- const cy = p01 * vx + p11 * vy + p21 * vz + p31;
270
- const cz = p02 * vx + p12 * vy + p22 * vz + p32;
271
- const cw = p03 * vx + p13 * vy + p23 * vz + p33;
272
- if (Math.abs(cw) < 1e-10) {
273
- return [vx < 0 ? -1 : 1, vy < 0 ? -1 : 1, cz];
274
- }
275
- const invW = 1 / cw;
276
- return [cx * invW, cy * invW, cz * invW];
277
- }
278
- function ndcPositionToCluster(ndc, viewZ, gridX, gridY, gridZ, near, far) {
279
- const ndcX = ndc[0] ?? 0;
280
- const ndcY = ndc[1] ?? 0;
281
- let cx = Math.floor((ndcX * 0.5 + 0.5) * gridX);
282
- let cy = Math.floor((ndcY * 0.5 + 0.5) * gridY);
283
- if (cx < 0) cx = 0;
284
- if (cx >= gridX) cx = gridX - 1;
285
- if (cy < 0) cy = 0;
286
- if (cy >= gridY) cy = gridY - 1;
287
- const cz = viewZToZSlice(viewZ, gridZ, near, far);
288
- return { x: cx, y: cy, z: cz };
289
- }
290
- function calculateSphereClusterBounds(aabb, gridX, gridY, gridZ, near, far, output) {
291
- const result = output ?? {
292
- min: { x: 0, y: 0, z: 0 },
293
- max: { x: 0, y: 0, z: 0 }
294
- };
295
- const minZ = aabb.min[2] ?? 0;
296
- const maxZ = aabb.max[2] ?? 0;
297
- const idxMin = ndcPositionToCluster(aabb.min, minZ, gridX, gridY, gridZ, near, far);
298
- const idxMax = ndcPositionToCluster(aabb.max, maxZ, gridX, gridY, gridZ, near, far);
299
- result.min.x = Math.min(idxMin.x, idxMax.x);
300
- result.min.y = Math.min(idxMin.y, idxMax.y);
301
- result.min.z = Math.min(idxMin.z, idxMax.z);
302
- result.max.x = Math.max(idxMin.x, idxMax.x);
303
- result.max.y = Math.max(idxMin.y, idxMax.y);
304
- result.max.z = Math.max(idxMin.z, idxMax.z);
305
- return result;
306
- }
307
- function bin(lights, view, proj, grid, near, far, clusterGrid, lightIndexList, capacity, scratch, profile, options = {}) {
308
- const gridX = grid.x;
309
- const gridY = grid.y;
310
- const gridZ = grid.z;
311
- const clusterCount = gridX * gridY * gridZ;
312
- const binScratch = scratch ?? createClusterBinScratch();
313
- ensureScratchCapacity(binScratch, clusterCount, lights.length, gridX, gridY, gridZ);
314
- const { clusterCounts, clusterCursors, lightBounds, clusterDeltas } = binScratch;
315
- clusterGrid.fill(0, 0, clusterCount * 2);
316
- clusterCounts.fill(0, 0, clusterCount);
317
- let attemptedTotal = 0;
318
- runClusterBinProfilePhase(profile, "light-bounds-and-occupancy", () => {
319
- runClusterBinProfilePhase(profile, "light-bounds-and-occupancy/light-aabb", () => {
320
- for (let lightIdx = 0; lightIdx < lights.length; lightIdx++) {
321
- const boundsOffset = lightIdx * 6;
322
- lightBounds[boundsOffset] = -1;
323
- const light = lights[lightIdx];
324
- if (!light) continue;
325
- const radius = deriveCullingRadius(light.range, 1);
326
- if (radius <= 0) {
327
- continue;
328
- }
329
- const aabb = clusterSpaceObjectAabb(
330
- light.position,
331
- radius,
332
- view,
333
- proj,
334
- binScratch.aabbOutput
335
- );
336
- const bounds = calculateSphereClusterBounds(
337
- aabb,
338
- gridX,
339
- gridY,
340
- gridZ,
341
- near,
342
- far,
343
- binScratch.boundsOutput
344
- );
345
- if (bounds.min.x > bounds.max.x || bounds.min.y > bounds.max.y || bounds.min.z > bounds.max.z) {
346
- continue;
347
- }
348
- lightBounds[boundsOffset] = bounds.min.x;
349
- lightBounds[boundsOffset + 1] = bounds.min.y;
350
- lightBounds[boundsOffset + 2] = bounds.min.z;
351
- lightBounds[boundsOffset + 3] = bounds.max.x;
352
- lightBounds[boundsOffset + 4] = bounds.max.y;
353
- lightBounds[boundsOffset + 5] = bounds.max.z;
354
- }
355
- });
356
- runClusterBinProfilePhase(profile, "light-bounds-and-occupancy/cluster-occupancy", () => {
357
- const diffX = gridX + 1;
358
- const diffY = gridY + 1;
359
- const diffRow = diffX * diffY;
360
- clusterDeltas.fill(0, 0, diffRow * (gridZ + 1));
361
- const addDifference = (x, y, z, delta) => {
362
- const index = z * diffRow + y * diffX + x;
363
- clusterDeltas[index] = (clusterDeltas[index] ?? 0) + delta;
364
- };
365
- for (let lightIdx = 0; lightIdx < lights.length; lightIdx++) {
366
- const boundsOffset = lightIdx * 6;
367
- if ((lightBounds[boundsOffset] ?? -1) < 0) continue;
368
- const minX = lightBounds[boundsOffset] ?? 0;
369
- const minY = lightBounds[boundsOffset + 1] ?? 0;
370
- const minZ = lightBounds[boundsOffset + 2] ?? 0;
371
- const maxX = lightBounds[boundsOffset + 3] ?? -1;
372
- const maxY = lightBounds[boundsOffset + 4] ?? -1;
373
- const maxZ = lightBounds[boundsOffset + 5] ?? -1;
374
- const endX = maxX + 1;
375
- const endY = maxY + 1;
376
- const endZ = maxZ + 1;
377
- addDifference(minX, minY, minZ, 1);
378
- addDifference(endX, minY, minZ, -1);
379
- addDifference(minX, endY, minZ, -1);
380
- addDifference(minX, minY, endZ, -1);
381
- addDifference(endX, endY, minZ, 1);
382
- addDifference(endX, minY, endZ, 1);
383
- addDifference(minX, endY, endZ, 1);
384
- addDifference(endX, endY, endZ, -1);
385
- }
386
- for (let cz = 0; cz < gridZ; cz++) {
387
- for (let cy = 0; cy < gridY; cy++) {
388
- for (let cx = 0; cx < gridX; cx++) {
389
- const diffIdx = cz * diffRow + cy * diffX + cx;
390
- let count = clusterDeltas[diffIdx] ?? 0;
391
- if (cx > 0) count += clusterDeltas[diffIdx - 1] ?? 0;
392
- if (cy > 0) count += clusterDeltas[diffIdx - diffX] ?? 0;
393
- if (cz > 0) count += clusterDeltas[diffIdx - diffRow] ?? 0;
394
- if (cx > 0 && cy > 0) count -= clusterDeltas[diffIdx - diffX - 1] ?? 0;
395
- if (cx > 0 && cz > 0) count -= clusterDeltas[diffIdx - diffRow - 1] ?? 0;
396
- if (cy > 0 && cz > 0) count -= clusterDeltas[diffIdx - diffRow - diffX] ?? 0;
397
- if (cx > 0 && cy > 0 && cz > 0) {
398
- count += clusterDeltas[diffIdx - diffRow - diffX - 1] ?? 0;
399
- }
400
- clusterDeltas[diffIdx] = count;
401
- const clusterIdx = cz * gridY * gridX + cy * gridX + cx;
402
- clusterCounts[clusterIdx] = count;
403
- attemptedTotal += count;
404
- }
405
- }
406
- }
407
- });
408
- });
409
- if (attemptedTotal > capacity) {
410
- return err({
411
- code: "index-overflow",
412
- expected: `writeCount <= ${capacity}`,
413
- hint: `light index list overflow: needed ${attemptedTotal} entries, capacity ${capacity}; reduce lights, shrink grid, or shrink ranges (overrun = ${attemptedTotal - capacity})`,
414
- detail: { actual: attemptedTotal, capacity }
415
- });
416
- }
417
- const writeCount = runClusterBinProfilePhase(profile, "cluster-reserve", () => {
418
- let reserved = 0;
419
- for (let ci = 0; ci < clusterCount; ci++) {
420
- const base = ci * 2;
421
- clusterGrid[base] = reserved;
422
- clusterGrid[base + 1] = clusterCounts[ci] ?? 0;
423
- clusterCursors[ci] = reserved;
424
- reserved += clusterCounts[ci] ?? 0;
425
- }
426
- return reserved;
427
- });
428
- if (options.writeMembership !== false) {
429
- runClusterBinProfilePhase(profile, "light-index-write", () => {
430
- for (let lightIdx = 0; lightIdx < lights.length; lightIdx++) {
431
- const boundsOffset = lightIdx * 6;
432
- let valid = false;
433
- let minX = 0;
434
- let minY = 0;
435
- let minZ = 0;
436
- let maxX = -1;
437
- let maxY = -1;
438
- let maxZ = -1;
439
- runClusterBinProfilePhase(profile, "light-index-write/bounds-read", () => {
440
- if ((lightBounds[boundsOffset] ?? -1) < 0) return;
441
- valid = true;
442
- minX = lightBounds[boundsOffset] ?? 0;
443
- minY = lightBounds[boundsOffset + 1] ?? 0;
444
- minZ = lightBounds[boundsOffset + 2] ?? 0;
445
- maxX = lightBounds[boundsOffset + 3] ?? -1;
446
- maxY = lightBounds[boundsOffset + 4] ?? -1;
447
- maxZ = lightBounds[boundsOffset + 5] ?? -1;
448
- });
449
- if (!valid) continue;
450
- runClusterBinProfilePhase(profile, "light-index-write/cluster-write", () => {
451
- for (let cz = minZ; cz <= maxZ; cz++) {
452
- for (let cy = minY; cy <= maxY; cy++) {
453
- for (let cx = minX; cx <= maxX; cx++) {
454
- const clusterIdx = cz * gridY * gridX + cy * gridX + cx;
455
- const cursor = clusterCursors[clusterIdx] ?? 0;
456
- lightIndexList[cursor] = lightIdx;
457
- clusterCursors[clusterIdx] = cursor + 1;
458
- }
459
- }
460
- }
461
- });
462
- }
463
- });
464
- }
465
- return ok$1(writeCount);
466
- }
467
-
468
- // src/gpu-stage.ts
469
- var GPU_SHADER_STAGE_VERTEX = 1;
470
- var GPU_SHADER_STAGE_FRAGMENT = 2;
471
- var GPU_SHADER_STAGE_COMPUTE = 4;
472
-
473
- // src/gpu-usage.ts
474
- var GPU_BUFFER_USAGE_VERTEX = 32;
475
- var GPU_BUFFER_USAGE_INDEX = 16;
476
- var GPU_BUFFER_USAGE_COPY_DST = 8;
477
- var GPU_BUFFER_USAGE_COPY_SRC = 4;
478
- var GPU_BUFFER_USAGE_UNIFORM = 64;
479
- var GPU_BUFFER_USAGE_STORAGE = 128;
480
- var GPU_BUFFER_USAGE_INDIRECT = 256;
481
- var GPU_BUFFER_USAGE_MAP_READ = 1;
482
- var GPU_BUFFER_USAGE_QUERY_RESOLVE = 512;
483
- var BYTES_PER_LIGHT_SLOT = 64;
484
- var LightSlotKind = {
485
- POINT: 0,
486
- SPOT: 1
487
- };
488
- var STORAGE_BUFFER_MIN_REQUIRED = 4;
489
- var POINT_LIGHT_STD430_BYTES = 32;
490
- var SPOT_LIGHT_STD430_BYTES = 80;
491
- var SPOT_LIGHT_SHADOW_TILE_SENTINEL = -1;
492
- var SPOT_LIGHT_PROJECTOR_ONLY_SENTINEL = -2;
493
- var LIGHT_ARRAY_HEADER_BYTES = 16;
494
- var LIGHT_ARRAY_MAX_SLOTS = 4;
495
- var POINT_LIGHT_SHADOW_LAYER_SENTINEL = -1;
496
- function packPointLight(snap) {
497
- const out = new Float32Array(8);
498
- out[0] = snap.position[0] ?? 0;
499
- out[1] = snap.position[1] ?? 0;
500
- out[2] = snap.position[2] ?? 0;
501
- out[3] = snap.invRangeSquared;
502
- out[4] = snap.color[0] ?? 0;
503
- out[5] = snap.color[1] ?? 0;
504
- out[6] = snap.color[2] ?? 0;
505
- const i32 = new Int32Array(out.buffer);
506
- i32[7] = snap.shadowAtlasLayer ?? POINT_LIGHT_SHADOW_LAYER_SENTINEL;
507
- return out;
508
- }
509
- function packSpotLight(snap) {
510
- const out = new Float32Array(20);
511
- out[0] = snap.position[0] ?? 0;
512
- out[1] = snap.position[1] ?? 0;
513
- out[2] = snap.position[2] ?? 0;
514
- out[3] = snap.invRangeSquared;
515
- out[4] = snap.color[0] ?? 0;
516
- out[5] = snap.color[1] ?? 0;
517
- out[6] = snap.color[2] ?? 0;
518
- out[7] = snap.cosInner;
519
- out[8] = snap.direction[0] ?? 0;
520
- out[9] = snap.direction[1] ?? 0;
521
- out[10] = snap.direction[2] ?? 0;
522
- out[11] = snap.cosOuter;
523
- out[12] = snap.depthBias ?? 5e-3;
524
- out[13] = snap.normalBias ?? 0.05;
525
- out[14] = snap.pcfKernelSize ?? 3;
526
- const i32 = new Int32Array(out.buffer);
527
- i32[15] = snap.shadowAtlasTile >= 0 ? snap.shadowAtlasTile : snap.projectorAsset !== void 0 && snap.lightViewProj !== void 0 ? SPOT_LIGHT_PROJECTOR_ONLY_SENTINEL : SPOT_LIGHT_SHADOW_TILE_SENTINEL;
528
- out[16] = snap.shadowIntensity ?? 1;
529
- return out;
530
- }
531
- function packLightArrayHeader(count) {
532
- const buf = new ArrayBuffer(LIGHT_ARRAY_HEADER_BYTES);
533
- const u32 = new Uint32Array(buf);
534
- u32[0] = count >>> 0;
535
- return buf;
536
- }
537
- function packLightSlot(snap, shadow) {
538
- const out = new Float32Array(16);
539
- out[0] = snap.position[0] ?? 0;
540
- out[1] = snap.position[1] ?? 0;
541
- out[2] = snap.position[2] ?? 0;
542
- out[3] = snap.invRangeSquared;
543
- out[4] = snap.color[0] ?? 0;
544
- out[5] = snap.color[1] ?? 0;
545
- out[6] = snap.color[2] ?? 0;
546
- out[7] = snap.kind === "point" ? 1 : snap.cosInner;
547
- out[8] = snap.kind === "point" ? 0 : snap.direction[0] ?? 0;
548
- out[9] = snap.kind === "point" ? 0 : snap.direction[1] ?? 0;
549
- out[10] = snap.kind === "point" ? 0 : snap.direction[2] ?? 0;
550
- out[11] = snap.kind === "point" ? 0 : snap.cosOuter;
551
- const u32 = new Uint32Array(out.buffer);
552
- u32[12] = snap.kind === "point" ? LightSlotKind.POINT : LightSlotKind.SPOT;
553
- const i32 = new Int32Array(out.buffer);
554
- if (snap.kind === "point" && shadow !== void 0) {
555
- i32[13] = shadow.shadowAtlasLayer;
556
- out[14] = shadow.near;
557
- out[15] = shadow.far;
558
- } else if (snap.kind === "spot") {
559
- i32[13] = snap.shadowAtlasTile >= 0 ? snap.shadowAtlasTile : snap.projectorAsset !== void 0 && snap.lightViewProj !== void 0 ? SPOT_LIGHT_PROJECTOR_ONLY_SENTINEL : SPOT_LIGHT_SHADOW_TILE_SENTINEL;
560
- out[14] = snap.shadowIntensity ?? 1;
561
- out[15] = snap.pcfKernelSize ?? 3;
562
- } else {
563
- i32[13] = POINT_LIGHT_SHADOW_LAYER_SENTINEL;
564
- }
565
- return out;
566
- }
567
- function assertStorageBufferCap(cap) {
568
- if (cap >= STORAGE_BUFFER_MIN_REQUIRED) {
569
- return ok$1(true);
570
- }
571
- if (cap === 0) {
572
- return ok$1(false);
573
- }
574
- return err(
575
- new RhiError({
576
- code: "limit-exceeded",
577
- expected: `device.limits.maxStorageBuffersPerShaderStage >= ${STORAGE_BUFFER_MIN_REQUIRED}, or exactly 0 for uniform-fallback path`,
578
- hint: `device.limits.maxStorageBuffersPerShaderStage = ${cap} < ${STORAGE_BUFFER_MIN_REQUIRED} and != 0. WebGPU spec default minimum is 8; this may be a downlevel adapter with partial storage support. Wait for todo-125 multi-light-pack which collapses point + spot into a single cluster storage buffer (1 entry instead of 2) for 2-3 buffer environments.`,
579
- detail: {
580
- maxStorageBufferBindingSize: cap,
581
- requestedBytes: STORAGE_BUFFER_MIN_REQUIRED
582
- }
583
- })
584
- );
585
- }
586
- var BGL_ONLY_SPEC_STUB = Object.freeze({
587
- shader: { id: "", passKind: "forward", variantSet: void 0 },
588
- attachments: { colorFormats: [], depthFormat: void 0, sampleCount: 1 },
589
- geometry: { topology: "triangle-list", vertexLayout: {} },
590
- renderState: void 0
591
- });
592
- function buildPbrMaterialUserRegionEntries(paramSchema = DEFAULT_STANDARD_PBR_PARAM_SCHEMA) {
593
- const derived = derive(paramSchema);
594
- const entries = derived.bglEntries.map(
595
- (entry) => ({
596
- binding: entry.binding,
597
- visibility: entry.visibility,
598
- ...entry.buffer === void 0 ? {} : { buffer: entry.buffer },
599
- ...entry.sampler === void 0 ? {} : { sampler: entry.sampler },
600
- ...entry.texture === void 0 ? {} : { texture: entry.texture },
601
- ...entry.storageTexture === void 0 ? {} : { storageTexture: entry.storageTexture }
602
- })
603
- );
604
- const ubo = entries[0];
605
- if (ubo !== void 0 && ubo.binding === 0 && ubo.buffer?.type === "uniform") {
606
- entries[0] = {
607
- binding: 0,
608
- visibility: GPU_SHADER_STAGE_VERTEX | GPU_SHADER_STAGE_FRAGMENT,
609
- buffer: { type: "uniform", hasDynamicOffset: true }
610
- };
611
- }
612
- return entries;
613
- }
614
- function appendInjection(bgl, kind) {
615
- const start = bgl.length;
616
- switch (kind) {
617
- case "ibl":
618
- return [
619
- // binding start+0: irradianceMap (texture_cube)
620
- {
621
- binding: start,
622
- visibility: GPU_SHADER_STAGE_FRAGMENT,
623
- texture: { sampleType: "float", viewDimension: "cube" }
624
- },
625
- // binding start+1: irradianceSampler
626
- {
627
- binding: start + 1,
628
- visibility: GPU_SHADER_STAGE_FRAGMENT,
629
- sampler: { type: "filtering" }
630
- },
631
- // binding start+2: prefilterMap (texture_cube)
632
- {
633
- binding: start + 2,
634
- visibility: GPU_SHADER_STAGE_FRAGMENT,
635
- texture: { sampleType: "float", viewDimension: "cube" }
636
- },
637
- // binding start+3: prefilterSampler
638
- {
639
- binding: start + 3,
640
- visibility: GPU_SHADER_STAGE_FRAGMENT,
641
- sampler: { type: "filtering" }
642
- },
643
- // binding start+4: brdfLut (texture_2d)
644
- {
645
- binding: start + 4,
646
- visibility: GPU_SHADER_STAGE_FRAGMENT,
647
- texture: { sampleType: "float", viewDimension: "2d" }
648
- },
649
- // binding start+5: brdfLutSampler
650
- {
651
- binding: start + 5,
652
- visibility: GPU_SHADER_STAGE_FRAGMENT,
653
- sampler: { type: "filtering" }
654
- },
655
- // binding start+6: uniform { intensity: f32 }
656
- {
657
- binding: start + 6,
658
- visibility: GPU_SHADER_STAGE_FRAGMENT,
659
- buffer: { type: "uniform" }
660
- }
661
- ];
662
- case "lightmap":
663
- return [
664
- // emissive sampler + texture pair
665
- {
666
- binding: start,
667
- visibility: GPU_SHADER_STAGE_FRAGMENT,
668
- sampler: { type: "filtering" }
669
- },
670
- {
671
- binding: start + 1,
672
- visibility: GPU_SHADER_STAGE_FRAGMENT,
673
- texture: { sampleType: "float", viewDimension: "2d" }
674
- },
675
- // occlusion sampler + texture pair
676
- {
677
- binding: start + 2,
678
- visibility: GPU_SHADER_STAGE_FRAGMENT,
679
- sampler: { type: "filtering" }
680
- },
681
- {
682
- binding: start + 3,
683
- visibility: GPU_SHADER_STAGE_FRAGMENT,
684
- texture: { sampleType: "float", viewDimension: "2d" }
685
- }
686
- ];
687
- case "shadow":
688
- return [
689
- {
690
- binding: start,
691
- visibility: GPU_SHADER_STAGE_FRAGMENT,
692
- sampler: { type: "comparison" }
693
- },
694
- {
695
- binding: start + 1,
696
- visibility: GPU_SHADER_STAGE_FRAGMENT,
697
- texture: { sampleType: "depth", viewDimension: "2d" }
698
- }
699
- ];
700
- case "transmission":
701
- return [
702
- {
703
- binding: start,
704
- visibility: GPU_SHADER_STAGE_FRAGMENT,
705
- sampler: { type: "filtering" }
706
- },
707
- {
708
- binding: start + 1,
709
- visibility: GPU_SHADER_STAGE_FRAGMENT,
710
- texture: { sampleType: "float", viewDimension: "2d" }
711
- }
712
- ];
195
+ function appendInjection(bgl, kind) {
196
+ const start = bgl.length;
197
+ switch (kind) {
198
+ case "ibl":
199
+ return [
200
+ // binding start+0: irradianceMap (texture_cube)
201
+ {
202
+ binding: start,
203
+ visibility: GPU_SHADER_STAGE_FRAGMENT,
204
+ texture: { sampleType: "float", viewDimension: "cube" }
205
+ },
206
+ // binding start+1: irradianceSampler
207
+ {
208
+ binding: start + 1,
209
+ visibility: GPU_SHADER_STAGE_FRAGMENT,
210
+ sampler: { type: "filtering" }
211
+ },
212
+ // binding start+2: prefilterMap (texture_cube)
213
+ {
214
+ binding: start + 2,
215
+ visibility: GPU_SHADER_STAGE_FRAGMENT,
216
+ texture: { sampleType: "float", viewDimension: "cube" }
217
+ },
218
+ // binding start+3: prefilterSampler
219
+ {
220
+ binding: start + 3,
221
+ visibility: GPU_SHADER_STAGE_FRAGMENT,
222
+ sampler: { type: "filtering" }
223
+ },
224
+ // binding start+4: brdfLut (texture_2d)
225
+ {
226
+ binding: start + 4,
227
+ visibility: GPU_SHADER_STAGE_FRAGMENT,
228
+ texture: { sampleType: "float", viewDimension: "2d" }
229
+ },
230
+ // binding start+5: brdfLutSampler
231
+ {
232
+ binding: start + 5,
233
+ visibility: GPU_SHADER_STAGE_FRAGMENT,
234
+ sampler: { type: "filtering" }
235
+ },
236
+ // binding start+6: uniform { intensity: f32 }
237
+ {
238
+ binding: start + 6,
239
+ visibility: GPU_SHADER_STAGE_FRAGMENT,
240
+ buffer: { type: "uniform" }
241
+ }
242
+ ];
243
+ case "lightmap":
244
+ return [
245
+ // emissive sampler + texture pair
246
+ {
247
+ binding: start,
248
+ visibility: GPU_SHADER_STAGE_FRAGMENT,
249
+ sampler: { type: "filtering" }
250
+ },
251
+ {
252
+ binding: start + 1,
253
+ visibility: GPU_SHADER_STAGE_FRAGMENT,
254
+ texture: { sampleType: "float", viewDimension: "2d" }
255
+ },
256
+ // occlusion sampler + texture pair
257
+ {
258
+ binding: start + 2,
259
+ visibility: GPU_SHADER_STAGE_FRAGMENT,
260
+ sampler: { type: "filtering" }
261
+ },
262
+ {
263
+ binding: start + 3,
264
+ visibility: GPU_SHADER_STAGE_FRAGMENT,
265
+ texture: { sampleType: "float", viewDimension: "2d" }
266
+ }
267
+ ];
268
+ case "shadow":
269
+ return [
270
+ {
271
+ binding: start,
272
+ visibility: GPU_SHADER_STAGE_FRAGMENT,
273
+ sampler: { type: "comparison" }
274
+ },
275
+ {
276
+ binding: start + 1,
277
+ visibility: GPU_SHADER_STAGE_FRAGMENT,
278
+ texture: { sampleType: "depth", viewDimension: "2d" }
279
+ }
280
+ ];
281
+ case "transmission":
282
+ return [
283
+ {
284
+ binding: start,
285
+ visibility: GPU_SHADER_STAGE_FRAGMENT,
286
+ sampler: { type: "filtering" }
287
+ },
288
+ {
289
+ binding: start + 1,
290
+ visibility: GPU_SHADER_STAGE_FRAGMENT,
291
+ texture: { sampleType: "float", viewDimension: "2d" }
292
+ }
293
+ ];
713
294
  }
714
295
  }
715
296
  function buildPbrViewBglEntries(caps) {
@@ -718,7 +299,9 @@ function buildPbrViewBglEntries(caps) {
718
299
  {
719
300
  binding: 0,
720
301
  visibility: GPU_SHADER_STAGE_VERTEX | GPU_SHADER_STAGE_FRAGMENT,
721
- buffer: { type: "uniform" }
302
+ // View captures use the same bind group with a per-face view slot.
303
+ // Keeping this dynamic is also valid for the display slot at offset 0.
304
+ buffer: { type: "uniform", hasDynamicOffset: true }
722
305
  },
723
306
  {
724
307
  binding: 1,
@@ -873,8 +456,17 @@ function shadowCasterVariantSet(storageBuffer, skinned) {
873
456
  if (storageBuffer && skinningDisabled) return "";
874
457
  return `SKINNING_DISABLED=${String(skinningDisabled)}+STORAGE_BUFFER_AVAILABLE=${String(storageBuffer)}`;
875
458
  }
876
- function isSkinnedShadowCasterVariant(materialShaderId, variantSet) {
877
- return materialShaderId === SHADOW_CASTER_SHADER_ID && variantSet?.includes("SKINNING_DISABLED=false") === true;
459
+ function resolvePipelineGroup2Contract(source) {
460
+ const withoutComments = source.replace(/\/\*[\s\S]*?\*\//gu, "").replace(/\/\/.*$/gmu, "");
461
+ const bindings = [
462
+ ...withoutComments.matchAll(/@group\s*\(\s*2\s*\)\s*@binding\s*\(\s*(\d+)\s*\)/gu)
463
+ ].map((match) => Number(match[1]));
464
+ const hasSkinBindings = bindings.includes(1) || bindings.includes(2);
465
+ const hasClusterBindings = bindings.some((binding) => binding >= 3);
466
+ if (hasSkinBindings && hasClusterBindings) return "skin-cluster";
467
+ if (hasSkinBindings) return "skin";
468
+ if (hasClusterBindings) return "cluster";
469
+ return "mesh";
878
470
  }
879
471
  function isStandardPbrMaterialShader(shaderId) {
880
472
  return shaderId === "forgeax::default-standard-pbr" || shaderId === SKIN_MATERIAL_SHADER_ID || shaderId === "forgeax::default-standard-pbr-skin";
@@ -907,16 +499,43 @@ function buildPbrSkinLayouts(device, caps, pbr) {
907
499
  bindGroupLayouts: layouts
908
500
  };
909
501
  }
502
+ function createPbrSkinMeshBindGroupEntries(meshBuffer, meshSize, paletteBuffer, paletteWindowBytes) {
503
+ return [
504
+ {
505
+ binding: 0,
506
+ resource: { kind: "buffer", value: { buffer: meshBuffer, offset: 0, size: meshSize } }
507
+ },
508
+ ...[1, 2].map((binding) => ({
509
+ binding,
510
+ resource: {
511
+ kind: "buffer",
512
+ value: {
513
+ buffer: paletteBuffer,
514
+ offset: 0,
515
+ size: paletteWindowBytes
516
+ }
517
+ }
518
+ }))
519
+ ];
520
+ }
521
+ function pbrSkinMeshDynamicOffsets(meshOffset, paletteOffset) {
522
+ return [meshOffset, paletteOffset, paletteOffset];
523
+ }
910
524
  var SPRITE_PASS_PER_INSTANCE_REGION_VARIANT_SET = "";
911
- function createHdrpBindGroupLayoutDescriptor(storageBuffer = true) {
912
- const meshBufType = storageBuffer ? "read-only-storage" : "uniform";
913
- const clusterBufType = storageBuffer ? "read-only-storage" : "uniform";
525
+ function createHdrpBindGroupLayoutDescriptor() {
526
+ const meshBufType = "read-only-storage";
527
+ const clusterBufType = "read-only-storage";
914
528
  return {
915
529
  label: "hdrp-unified-bgl-group2",
916
530
  entries: [
917
531
  {
918
532
  binding: 0,
919
- visibility: GPU_SHADER_STAGE_VERTEX,
533
+ // Standard fragment lighting (transmission/refraction and clustered
534
+ // evaluation) reads the same mesh transform window as the vertex
535
+ // stage. Keep this visibility in the HDRP descriptor aligned with
536
+ // the URP mesh-array descriptor; restricting it to vertex makes the
537
+ // HDRP pipeline invalid when fs_main accesses `meshes[0]`.
538
+ visibility: GPU_SHADER_STAGE_VERTEX | GPU_SHADER_STAGE_FRAGMENT,
920
539
  buffer: { type: meshBufType, hasDynamicOffset: true }
921
540
  },
922
541
  // Bindings 1 and 2 stay absent for the URP physical isolation gap.
@@ -953,6 +572,31 @@ function createHdrpBindGroupLayoutDescriptor(storageBuffer = true) {
953
572
  ]
954
573
  };
955
574
  }
575
+ function createHdrpSkinBindGroupLayoutDescriptor() {
576
+ const base = createHdrpBindGroupLayoutDescriptor();
577
+ const baseEntries = base.entries;
578
+ if (baseEntries === void 0 || baseEntries[0] === void 0) {
579
+ throw new Error("HDRP base group(2) layout must declare mesh binding 0");
580
+ }
581
+ const paletteType = "read-only-storage";
582
+ return {
583
+ label: "hdrp-skin-unified-bgl-group2",
584
+ entries: [
585
+ baseEntries[0],
586
+ {
587
+ binding: 1,
588
+ visibility: GPU_SHADER_STAGE_VERTEX,
589
+ buffer: { type: paletteType, hasDynamicOffset: true }
590
+ },
591
+ {
592
+ binding: 2,
593
+ visibility: GPU_SHADER_STAGE_VERTEX,
594
+ buffer: { type: paletteType, hasDynamicOffset: true }
595
+ },
596
+ ...baseEntries.slice(1)
597
+ ]
598
+ };
599
+ }
956
600
  function resolveMaterialParamSchema(spec, options) {
957
601
  if (options.materialParamSchema !== void 0) return options.materialParamSchema;
958
602
  if (options.registry !== void 0) {
@@ -1017,6 +661,11 @@ function buildBindGroupLayoutDescriptor(spec, options) {
1017
661
  binding: 1,
1018
662
  visibility: GPU_SHADER_STAGE_VERTEX,
1019
663
  buffer: { type: meshBufType, hasDynamicOffset: true }
664
+ },
665
+ {
666
+ binding: 2,
667
+ visibility: GPU_SHADER_STAGE_VERTEX,
668
+ buffer: { type: meshBufType, hasDynamicOffset: true }
1020
669
  }
1021
670
  ]
1022
671
  };
@@ -1039,8 +688,7 @@ function buildBindGroupLayoutDescriptor(spec, options) {
1039
688
  };
1040
689
  }
1041
690
  case "hdrp-7-slot": {
1042
- const caps = options.caps ?? { storageBuffer: true };
1043
- const desc = createHdrpBindGroupLayoutDescriptor(caps.storageBuffer);
691
+ const desc = createHdrpBindGroupLayoutDescriptor();
1044
692
  return {
1045
693
  label: desc.label ?? "hdrp-unified-bgl-group2",
1046
694
  entries: [...desc.entries ?? []]
@@ -1131,8 +779,6 @@ function buildFullscreenPostInputEntries(spec) {
1131
779
  }
1132
780
  ];
1133
781
  }
1134
-
1135
- // src/pipeline-spec.ts
1136
782
  var DEFERRED_COLOR_FORMATS = [
1137
783
  "rgba16float",
1138
784
  "rgba8unorm",
@@ -1230,6 +876,24 @@ function variantSetFromVertexLayoutProjection(projection, authoredVariantSet) {
1230
876
  parts.push(axis);
1231
877
  return ok(parts.join("+"));
1232
878
  }
879
+ function standardCapabilityVariantSet(clustered, storageBuffer, vertexColorAvailable) {
880
+ if (clustered && storageBuffer && vertexColorAvailable) return "";
881
+ return `CLUSTER_FORWARD_AVAILABLE=${clustered}+STORAGE_BUFFER_AVAILABLE=${storageBuffer}`;
882
+ }
883
+ function standardTopologyVariantSet(topology, storageBuffer, vertexColorAvailable) {
884
+ return standardCapabilityVariantSet(
885
+ topology?.kind === "clustered",
886
+ storageBuffer,
887
+ vertexColorAvailable
888
+ );
889
+ }
890
+ function standardTopologyBindGroupReady(topology, clusterBindGroup) {
891
+ return topology?.kind !== "clustered" || clusterBindGroup !== null && clusterBindGroup !== void 0;
892
+ }
893
+ function standardStorageVariantSet(storageBuffer, vertexColorAvailable) {
894
+ if (storageBuffer && vertexColorAvailable) return "";
895
+ return `STORAGE_BUFFER_AVAILABLE=${storageBuffer}+VERTEX_COLOR_AVAILABLE=${vertexColorAvailable}`;
896
+ }
1233
897
  function buildPipelineDescriptor(spec, modules) {
1234
898
  const { attachments, geometry, renderState } = spec;
1235
899
  const vertexBuffers = geometry.vertexLayoutProjection === void 0 ? deriveVertexBufferLayout(geometry.vertexLayout, {
@@ -1552,7 +1216,7 @@ var TRI_GEOMETRY_PBR = {
1552
1216
  vertexLayout: PROCEDURAL_ATTR_LAYOUT,
1553
1217
  shaderUvSetCount: 8
1554
1218
  };
1555
- var URP_PBR_VARIANT_SET = "CLUSTER_FORWARD_AVAILABLE=false+STORAGE_BUFFER_AVAILABLE=true";
1219
+ var STANDARD_BOOT_VARIANT_SET = standardCapabilityVariantSet(false, true, false);
1556
1220
  function buildSpecConstTable(ldrViewFormat) {
1557
1221
  const TRI_ATTACHMENTS_LDR_S1 = {
1558
1222
  colorFormats: [ldrViewFormat],
@@ -1631,19 +1295,19 @@ function buildSpecConstTable(ldrViewFormat) {
1631
1295
  geometry: TRI_GEOMETRY_PBR,
1632
1296
  renderState: void 0
1633
1297
  },
1634
- // ── M6 fix-up: URP-variant standard-pbr prewarm ─────────────────────────
1635
- // URP record path (`getMaterialShaderPipeline`) requests
1636
- // `variantSet=URP_PBR_VARIANT_SET` for standard-shading entities; pre-M6
1298
+ // ── M6 fix-up: non-clustered standard-pbr prewarm ─────────────────────────
1299
+ // The default record path (`getMaterialShaderPipeline`) requests
1300
+ // `variantSet=STANDARD_BOOT_VARIANT_SET` for standard-shading entities; pre-M6
1637
1301
  // the silent `selectStandardFallbackPipeline` shim served the no-variant
1638
1302
  // boot prewarm during the 1-frame async-compile warmup. With M6's
1639
- // explicit-failure surface, those URP requests must hit the boot prewarm
1303
+ // explicit-failure surface, those requests must hit the boot prewarm
1640
1304
  // by their own variantSet — these 4 entries close that gap.
1641
1305
  // standard PBR URP LDR S1
1642
1306
  {
1643
1307
  shader: {
1644
1308
  id: "forgeax::default-standard-pbr",
1645
1309
  passKind: "forward",
1646
- variantSet: URP_PBR_VARIANT_SET
1310
+ variantSet: STANDARD_BOOT_VARIANT_SET
1647
1311
  },
1648
1312
  attachments: TRI_ATTACHMENTS_LDR_S1,
1649
1313
  geometry: TRI_GEOMETRY_PBR,
@@ -1654,7 +1318,7 @@ function buildSpecConstTable(ldrViewFormat) {
1654
1318
  shader: {
1655
1319
  id: "forgeax::default-standard-pbr",
1656
1320
  passKind: "forward",
1657
- variantSet: URP_PBR_VARIANT_SET
1321
+ variantSet: STANDARD_BOOT_VARIANT_SET
1658
1322
  },
1659
1323
  attachments: TRI_ATTACHMENTS_LDR_S4,
1660
1324
  geometry: TRI_GEOMETRY_PBR,
@@ -1665,7 +1329,7 @@ function buildSpecConstTable(ldrViewFormat) {
1665
1329
  shader: {
1666
1330
  id: "forgeax::default-standard-pbr",
1667
1331
  passKind: "forward",
1668
- variantSet: URP_PBR_VARIANT_SET
1332
+ variantSet: STANDARD_BOOT_VARIANT_SET
1669
1333
  },
1670
1334
  attachments: TRI_ATTACHMENTS_HDR_S1,
1671
1335
  geometry: TRI_GEOMETRY_PBR,
@@ -1676,7 +1340,7 @@ function buildSpecConstTable(ldrViewFormat) {
1676
1340
  shader: {
1677
1341
  id: "forgeax::default-standard-pbr",
1678
1342
  passKind: "forward",
1679
- variantSet: URP_PBR_VARIANT_SET
1343
+ variantSet: STANDARD_BOOT_VARIANT_SET
1680
1344
  },
1681
1345
  attachments: TRI_ATTACHMENTS_HDR_S4,
1682
1346
  geometry: TRI_GEOMETRY_PBR,
@@ -2102,7 +1766,8 @@ var UNIFORM_BYTES = 256;
2102
1766
  var fallbackCache = /* @__PURE__ */ new WeakMap();
2103
1767
  function getOrCreateSsaoFallbackTexture(runtime) {
2104
1768
  const cached = fallbackCache.get(runtime);
2105
- if (cached !== void 0) return cached;
1769
+ if (cached !== void 0 && cached.device === runtime.device) return cached;
1770
+ if (cached !== void 0) fallbackCache.delete(runtime);
2106
1771
  const device = runtime.device;
2107
1772
  const texRes = device.createTexture({
2108
1773
  label: "hdrp-ssao-fallback-white",
@@ -2161,6 +1826,7 @@ function getOrCreateSsaoFallbackTexture(runtime) {
2161
1826
  return null;
2162
1827
  }
2163
1828
  const resources = {
1829
+ device,
2164
1830
  texture: texRes.value,
2165
1831
  view: viewRes.value,
2166
1832
  sampler: samplerRes.value
@@ -2171,7 +1837,8 @@ function getOrCreateSsaoFallbackTexture(runtime) {
2171
1837
  var cache = /* @__PURE__ */ new WeakMap();
2172
1838
  function getOrCreateSsaoBuffers(runtime) {
2173
1839
  const cached = cache.get(runtime);
2174
- if (cached !== void 0) return cached;
1840
+ if (cached !== void 0 && cached.device === runtime.device) return cached;
1841
+ if (cached !== void 0) cache.delete(runtime);
2175
1842
  const device = runtime.device;
2176
1843
  const kernelSamples = generateSsaoKernel();
2177
1844
  const kernelData = new Float32Array(SSAO_KERNEL_SAMPLE_COUNT * 4);
@@ -2244,6 +1911,7 @@ function getOrCreateSsaoBuffers(runtime) {
2244
1911
  return null;
2245
1912
  }
2246
1913
  const buffers = {
1914
+ device,
2247
1915
  kernelBuffer: kernelBufferRes.value,
2248
1916
  kernelBytes: SSAO_KERNEL_SAMPLE_COUNT * BYTES_PER_KERNEL_ELEMENT,
2249
1917
  noiseTexture: noiseTexRes.value,
@@ -2255,7 +1923,6 @@ function getOrCreateSsaoBuffers(runtime) {
2255
1923
  }
2256
1924
 
2257
1925
  // src/hdrp-buffers.ts
2258
- var HDRP_UNIFORM_LIGHT_CAPACITY = 128;
2259
1926
  function createHdrpClusterMembershipBindGroupLayoutDescriptor() {
2260
1927
  return {
2261
1928
  label: "hdrp-cluster-membership-bgl",
@@ -2285,46 +1952,67 @@ function createHdrpClusterMembershipBindGroupLayoutDescriptor() {
2285
1952
  }
2286
1953
  var cache2 = /* @__PURE__ */ new WeakMap();
2287
1954
  function getOrCreateHdrpBuffers(runtime, grid = DEFAULT_CLUSTER_GRID) {
1955
+ if (runtime.device.caps?.storageBuffer !== true) return null;
1956
+ const deviceSurface = runtime.device;
1957
+ if (typeof deviceSurface.createBuffer !== "function" || typeof deviceSurface.createBindGroupLayout !== "function" || typeof deviceSurface.destroyBuffer !== "function") {
1958
+ return null;
1959
+ }
2288
1960
  const cached = cache2.get(runtime);
2289
- if (cached !== void 0) return cached;
1961
+ if (cached !== void 0 && cached.device === runtime.device) {
1962
+ const requestedCells = grid.x * grid.y * grid.z;
1963
+ const cachedCells = cached.grid.x * cached.grid.y * cached.grid.z;
1964
+ if (requestedCells <= cachedCells) return cached;
1965
+ }
2290
1966
  const device = runtime.device;
2291
- const storageBuffer = runtime.device.caps?.storageBuffer === true;
2292
1967
  const clusterUniformBytes = 32;
2293
- const lightDataBytes = (storageBuffer ? MAX_LIGHTS : HDRP_UNIFORM_LIGHT_CAPACITY) * BYTES_PER_LIGHT_SLOT;
1968
+ const lightDataBytes = MAX_LIGHTS * BYTES_PER_LIGHT_SLOT;
2294
1969
  const clusterCells = grid.x * grid.y * grid.z;
2295
- const clusterGridBytes = storageBuffer ? clusterCells * CLUSTER_GRID_STRIDE_U32 * 4 : clusterUniformBytes;
2296
- const lightIndexListBytes = storageBuffer ? LIGHT_INDEX_LIST_CAPACITY * 4 : clusterUniformBytes;
1970
+ const clusterGridBytes = clusterCells * CLUSTER_GRID_STRIDE_U32 * 4;
1971
+ const lightIndexListBytes = LIGHT_INDEX_LIST_CAPACITY * 4;
2297
1972
  const lightBoundsBytes = MAX_LIGHTS * 6 * 4;
1973
+ const created = [];
1974
+ const releaseCreated = () => {
1975
+ for (const buffer of created) {
1976
+ const result = device.destroyBuffer(buffer);
1977
+ if (!result.ok) runtime.errorRegistry.fire(result.error);
1978
+ }
1979
+ created.length = 0;
1980
+ };
1981
+ const failAllocation = (error) => {
1982
+ runtime.errorRegistry.fire(error);
1983
+ releaseCreated();
1984
+ return null;
1985
+ };
2298
1986
  const lightData = device.createBuffer({
2299
1987
  label: "hdrp-light-data",
2300
1988
  size: lightDataBytes,
2301
- usage: (storageBuffer ? GPU_BUFFER_USAGE_STORAGE : GPU_BUFFER_USAGE_UNIFORM) | GPU_BUFFER_USAGE_COPY_DST,
1989
+ usage: GPU_BUFFER_USAGE_STORAGE | GPU_BUFFER_USAGE_COPY_DST,
2302
1990
  mappedAtCreation: false
2303
1991
  });
2304
1992
  if (!lightData.ok) {
2305
- runtime.errorRegistry.fire(lightData.error);
2306
- return null;
1993
+ return failAllocation(lightData.error);
2307
1994
  }
1995
+ created.push(lightData.value);
2308
1996
  const clusterGrid = device.createBuffer({
2309
1997
  label: "hdrp-cluster-grid",
2310
1998
  size: clusterGridBytes,
2311
- usage: (storageBuffer ? GPU_BUFFER_USAGE_STORAGE : GPU_BUFFER_USAGE_UNIFORM) | GPU_BUFFER_USAGE_COPY_DST,
1999
+ usage: GPU_BUFFER_USAGE_STORAGE | GPU_BUFFER_USAGE_COPY_DST,
2312
2000
  mappedAtCreation: false
2313
2001
  });
2314
2002
  if (!clusterGrid.ok) {
2315
- runtime.errorRegistry.fire(clusterGrid.error);
2316
- return null;
2003
+ return failAllocation(clusterGrid.error);
2317
2004
  }
2005
+ created.push(clusterGrid.value);
2318
2006
  const lightIndexList = device.createBuffer({
2319
2007
  label: "hdrp-light-index-list",
2320
2008
  size: lightIndexListBytes,
2321
- usage: (storageBuffer ? GPU_BUFFER_USAGE_STORAGE : GPU_BUFFER_USAGE_UNIFORM) | GPU_BUFFER_USAGE_COPY_DST,
2009
+ usage: GPU_BUFFER_USAGE_STORAGE | GPU_BUFFER_USAGE_COPY_DST,
2322
2010
  mappedAtCreation: false
2323
2011
  });
2324
2012
  if (!lightIndexList.ok) {
2325
- runtime.errorRegistry.fire(lightIndexList.error);
2326
- return null;
2013
+ return failAllocation(lightIndexList.error);
2327
2014
  }
2015
+ created.push(lightIndexList.value);
2328
2016
  const clusterUniform = device.createBuffer({
2329
2017
  label: "hdrp-cluster-uniform",
2330
2018
  size: clusterUniformBytes,
@@ -2332,29 +2020,25 @@ function getOrCreateHdrpBuffers(runtime, grid = DEFAULT_CLUSTER_GRID) {
2332
2020
  mappedAtCreation: false
2333
2021
  });
2334
2022
  if (!clusterUniform.ok) {
2335
- runtime.errorRegistry.fire(clusterUniform.error);
2336
- return null;
2023
+ return failAllocation(clusterUniform.error);
2337
2024
  }
2025
+ created.push(clusterUniform.value);
2338
2026
  const lightBounds = device.createBuffer({
2339
2027
  label: "hdrp-light-bounds",
2340
2028
  size: lightBoundsBytes,
2341
- usage: (storageBuffer ? GPU_BUFFER_USAGE_STORAGE : GPU_BUFFER_USAGE_UNIFORM) | GPU_BUFFER_USAGE_COPY_DST,
2029
+ usage: GPU_BUFFER_USAGE_STORAGE | GPU_BUFFER_USAGE_COPY_DST,
2342
2030
  mappedAtCreation: false
2343
2031
  });
2344
2032
  if (!lightBounds.ok) {
2345
- runtime.errorRegistry.fire(lightBounds.error);
2346
- return null;
2033
+ return failAllocation(lightBounds.error);
2347
2034
  }
2348
- const storageBufferCap = runtime.device.caps?.storageBuffer === true;
2349
- const unifiedBglRes = device.createBindGroupLayout(
2350
- createHdrpBindGroupLayoutDescriptor(storageBufferCap)
2351
- );
2035
+ created.push(lightBounds.value);
2036
+ const unifiedBglRes = device.createBindGroupLayout(createHdrpBindGroupLayoutDescriptor());
2352
2037
  if (!unifiedBglRes.ok) {
2353
- runtime.errorRegistry.fire(unifiedBglRes.error);
2354
- return null;
2038
+ return failAllocation(unifiedBglRes.error);
2355
2039
  }
2356
2040
  const buffers = {
2357
- storageBuffer,
2041
+ device,
2358
2042
  lightDataBuffer: lightData.value,
2359
2043
  lightDataBytes,
2360
2044
  clusterGridBuffer: clusterGrid.value,
@@ -2369,261 +2053,647 @@ function getOrCreateHdrpBuffers(runtime, grid = DEFAULT_CLUSTER_GRID) {
2369
2053
  unifiedBindGroupLayout: unifiedBglRes.value
2370
2054
  };
2371
2055
  cache2.set(runtime, buffers);
2056
+ if (cached !== void 0) {
2057
+ retireHdrpBuffers(runtime, cached);
2058
+ }
2372
2059
  return buffers;
2373
2060
  }
2374
- function packClusterUniform(grid, near, far, ssaoIntensity = 0, lightCount = 0, lightCapacity = HDRP_UNIFORM_LIGHT_CAPACITY) {
2061
+ function resetHdrpBuffers(runtime) {
2062
+ const cached = cache2.get(runtime);
2063
+ if (cached === void 0) return;
2064
+ cache2.delete(runtime);
2065
+ retireHdrpBuffers(runtime, cached);
2066
+ }
2067
+ function retireHdrpBuffers(runtime, buffers) {
2068
+ const device = buffers.device;
2069
+ const destroy = () => {
2070
+ for (const buffer of [
2071
+ buffers.lightDataBuffer,
2072
+ buffers.clusterGridBuffer,
2073
+ buffers.lightIndexListBuffer,
2074
+ buffers.clusterUniformBuffer,
2075
+ buffers.lightBoundsBuffer
2076
+ ]) {
2077
+ const result = device.destroyBuffer(buffer);
2078
+ if (!result.ok) runtime.errorRegistry.fire(result.error);
2079
+ }
2080
+ };
2081
+ const queue = device.queue;
2082
+ if (typeof queue.onSubmittedWorkDone === "function") {
2083
+ queue.onSubmittedWorkDone().then(destroy, destroy);
2084
+ } else {
2085
+ destroy();
2086
+ }
2087
+ }
2088
+ function packClusterUniform(grid, near, far, ssaoIntensity = 0, lightCount = 0) {
2375
2089
  const buf = new ArrayBuffer(32);
2376
2090
  const u32 = new Uint32Array(buf);
2377
2091
  const f32 = new Float32Array(buf);
2378
2092
  u32[0] = grid.x >>> 0;
2379
2093
  u32[1] = grid.y >>> 0;
2380
2094
  u32[2] = grid.z >>> 0;
2381
- u32[3] = Math.min(Math.max(lightCount, 0), lightCapacity) >>> 0;
2095
+ u32[3] = Math.min(Math.max(lightCount, 0), MAX_LIGHTS) >>> 0;
2382
2096
  f32[4] = near;
2383
2097
  f32[5] = far;
2384
2098
  f32[6] = near > 0 && far > near ? Math.log(far / near) : 0;
2385
2099
  f32[7] = ssaoIntensity;
2386
2100
  return buf;
2387
2101
  }
2388
- function createHdrpUnifiedBindGroup(runtime, hdrpBuffers, meshStorageBuffer, ssaoOptions = { enabled: false }) {
2389
- const fallback = getOrCreateSsaoFallbackTexture(runtime);
2390
- if (fallback === null) return null;
2391
- const ssaoTexView = ssaoOptions.enabled ? ssaoOptions.ssaoBlurredView : fallback.view;
2392
- const ssaoSampler = fallback.sampler;
2102
+ function createHdrpUnifiedBindGroup(runtime, hdrpBuffers, meshStorageBuffer, ssaoOptions = { enabled: false }) {
2103
+ const fallback = getOrCreateSsaoFallbackTexture(runtime);
2104
+ if (fallback === null) return null;
2105
+ const ssaoTexView = ssaoOptions.enabled ? ssaoOptions.ssaoBlurredView : fallback.view;
2106
+ const ssaoSampler = fallback.sampler;
2107
+ const result = runtime.device.createBindGroup({
2108
+ label: "hdrp-unified-bg-group2",
2109
+ layout: hdrpBuffers.unifiedBindGroupLayout,
2110
+ entries: [
2111
+ {
2112
+ binding: 0,
2113
+ resource: {
2114
+ kind: "buffer",
2115
+ value: {
2116
+ buffer: meshStorageBuffer,
2117
+ offset: 0,
2118
+ size: MESH_PER_ENTITY_STRIDE
2119
+ }
2120
+ }
2121
+ },
2122
+ {
2123
+ binding: 3,
2124
+ resource: {
2125
+ kind: "buffer",
2126
+ value: { buffer: hdrpBuffers.lightDataBuffer }
2127
+ }
2128
+ },
2129
+ {
2130
+ binding: 4,
2131
+ resource: {
2132
+ kind: "buffer",
2133
+ value: { buffer: hdrpBuffers.clusterGridBuffer }
2134
+ }
2135
+ },
2136
+ {
2137
+ binding: 5,
2138
+ resource: {
2139
+ kind: "buffer",
2140
+ value: { buffer: hdrpBuffers.lightIndexListBuffer }
2141
+ }
2142
+ },
2143
+ {
2144
+ binding: 6,
2145
+ resource: {
2146
+ kind: "buffer",
2147
+ value: { buffer: hdrpBuffers.clusterUniformBuffer }
2148
+ }
2149
+ },
2150
+ {
2151
+ binding: 7,
2152
+ resource: { kind: "textureView", value: ssaoTexView }
2153
+ },
2154
+ {
2155
+ binding: 8,
2156
+ resource: { kind: "sampler", value: ssaoSampler }
2157
+ }
2158
+ ]
2159
+ });
2160
+ if (!result.ok) {
2161
+ runtime.errorRegistry.fire(result.error);
2162
+ return null;
2163
+ }
2164
+ return result.value;
2165
+ }
2166
+ function createHdrpSkinUnifiedBindGroup(runtime, hdrpBuffers, layout, meshStorageBuffer, paletteBuffer, paletteBindingWindowBytes, ssaoOptions = { enabled: false }) {
2167
+ const fallback = getOrCreateSsaoFallbackTexture(runtime);
2168
+ if (fallback === null) return null;
2169
+ const meshSize = MESH_PER_ENTITY_STRIDE;
2170
+ const result = runtime.device.createBindGroup({
2171
+ label: "hdrp-skin-unified-bg-group2",
2172
+ layout,
2173
+ entries: [
2174
+ ...createPbrSkinMeshBindGroupEntries(
2175
+ meshStorageBuffer,
2176
+ meshSize,
2177
+ paletteBuffer,
2178
+ paletteBindingWindowBytes
2179
+ ),
2180
+ { binding: 3, resource: { kind: "buffer", value: { buffer: hdrpBuffers.lightDataBuffer } } },
2181
+ {
2182
+ binding: 4,
2183
+ resource: { kind: "buffer", value: { buffer: hdrpBuffers.clusterGridBuffer } }
2184
+ },
2185
+ {
2186
+ binding: 5,
2187
+ resource: { kind: "buffer", value: { buffer: hdrpBuffers.lightIndexListBuffer } }
2188
+ },
2189
+ {
2190
+ binding: 6,
2191
+ resource: { kind: "buffer", value: { buffer: hdrpBuffers.clusterUniformBuffer } }
2192
+ },
2193
+ {
2194
+ binding: 7,
2195
+ resource: {
2196
+ kind: "textureView",
2197
+ value: ssaoOptions.enabled ? ssaoOptions.ssaoBlurredView : fallback.view
2198
+ }
2199
+ },
2200
+ { binding: 8, resource: { kind: "sampler", value: fallback.sampler } }
2201
+ ]
2202
+ });
2203
+ if (!result.ok) {
2204
+ runtime.errorRegistry.fire(result.error);
2205
+ return null;
2206
+ }
2207
+ return result.value;
2208
+ }
2209
+ function createHdrpClusterMembershipBindGroup(runtime, hdrpBuffers, layout) {
2393
2210
  const result = runtime.device.createBindGroup({
2394
- label: "hdrp-unified-bg-group2",
2395
- layout: hdrpBuffers.unifiedBindGroupLayout,
2211
+ label: "hdrp-cluster-membership-bg",
2212
+ layout,
2396
2213
  entries: [
2397
2214
  {
2398
2215
  binding: 0,
2399
- resource: {
2400
- kind: "buffer",
2401
- value: {
2402
- buffer: meshStorageBuffer,
2403
- offset: 0,
2404
- size: hdrpBuffers.storageBuffer ? MESH_PER_ENTITY_STRIDE : MESH_UBO_FULL_ARRAY_BYTES
2405
- }
2406
- }
2216
+ resource: { kind: "buffer", value: { buffer: hdrpBuffers.clusterGridBuffer } }
2407
2217
  },
2408
2218
  {
2409
- binding: 3,
2410
- resource: {
2411
- kind: "buffer",
2412
- value: { buffer: hdrpBuffers.lightDataBuffer }
2413
- }
2219
+ binding: 1,
2220
+ resource: { kind: "buffer", value: { buffer: hdrpBuffers.lightIndexListBuffer } }
2414
2221
  },
2415
2222
  {
2416
- binding: 4,
2417
- resource: {
2418
- kind: "buffer",
2419
- value: { buffer: hdrpBuffers.clusterGridBuffer }
2223
+ binding: 2,
2224
+ resource: { kind: "buffer", value: { buffer: hdrpBuffers.clusterUniformBuffer } }
2225
+ },
2226
+ {
2227
+ binding: 3,
2228
+ resource: { kind: "buffer", value: { buffer: hdrpBuffers.lightBoundsBuffer } }
2229
+ }
2230
+ ]
2231
+ });
2232
+ if (!result.ok) {
2233
+ runtime.errorRegistry.fire(result.error);
2234
+ return null;
2235
+ }
2236
+ return result.value;
2237
+ }
2238
+ function runClusterBinProfilePhase(runner, phase, action) {
2239
+ return action() ;
2240
+ }
2241
+ function createClusterBinScratch() {
2242
+ return {
2243
+ clusterCounts: new Uint32Array(0),
2244
+ clusterCursors: new Uint32Array(0),
2245
+ lightBounds: new Int32Array(0),
2246
+ clusterDeltas: new Int32Array(0),
2247
+ aabbOutput: {
2248
+ min: vec3.create(0, 0, 0),
2249
+ max: vec3.create(0, 0, 0)
2250
+ },
2251
+ boundsOutput: {
2252
+ min: { x: 0, y: 0, z: 0 },
2253
+ max: { x: 0, y: 0, z: 0 }
2254
+ }
2255
+ };
2256
+ }
2257
+ function ensureScratchCapacity(scratch, clusterCount, lightCount, gridX, gridY, gridZ) {
2258
+ if (scratch.clusterCounts.length < clusterCount) {
2259
+ scratch.clusterCounts = new Uint32Array(clusterCount);
2260
+ scratch.clusterCursors = new Uint32Array(clusterCount);
2261
+ }
2262
+ if (scratch.lightBounds.length < lightCount * 6) {
2263
+ scratch.lightBounds = new Int32Array(lightCount * 6);
2264
+ }
2265
+ const differenceVolumeSize = (gridX + 1) * (gridY + 1) * (gridZ + 1);
2266
+ if (scratch.clusterDeltas.length < differenceVolumeSize) {
2267
+ scratch.clusterDeltas = new Int32Array(differenceVolumeSize);
2268
+ }
2269
+ }
2270
+ function deriveCullingRadius(range, intensity, threshold = 1e-3) {
2271
+ if (Number.isFinite(range)) {
2272
+ return Math.max(0, range);
2273
+ }
2274
+ const derived = Math.sqrt(intensity / threshold);
2275
+ return Math.min(derived, 1e3);
2276
+ }
2277
+ function viewZToZSlice(viewZ, gridZ, near, far) {
2278
+ if (viewZ >= -near) {
2279
+ return 0;
2280
+ }
2281
+ const logFarOverNear = Math.log(far / near);
2282
+ const slice = Math.floor(Math.log(-viewZ / near) / logFarOverNear * gridZ);
2283
+ if (slice < 0) return 0;
2284
+ if (slice >= gridZ) return gridZ - 1;
2285
+ return slice;
2286
+ }
2287
+ function clusterSpaceObjectAabb(center, radius, view, proj, output) {
2288
+ const result = output ?? {
2289
+ min: vec3.create(0, 0, 0),
2290
+ max: vec3.create(0, 0, 0)
2291
+ };
2292
+ const cx = center[0] ?? 0;
2293
+ const cy = center[1] ?? 0;
2294
+ const cz = center[2] ?? 0;
2295
+ const v00 = view[0] ?? 0;
2296
+ const v01 = view[1] ?? 0;
2297
+ const v02 = view[2] ?? 0;
2298
+ const v10 = view[4] ?? 0;
2299
+ const v11 = view[5] ?? 0;
2300
+ const v12 = view[6] ?? 0;
2301
+ const v20 = view[8] ?? 0;
2302
+ const v21 = view[9] ?? 0;
2303
+ const v22 = view[10] ?? 0;
2304
+ const v30 = view[12] ?? 0;
2305
+ const v31 = view[13] ?? 0;
2306
+ const v32 = view[14] ?? 0;
2307
+ const vx = v00 * cx + v10 * cy + v20 * cz + v30;
2308
+ const vy = v01 * cx + v11 * cy + v21 * cz + v31;
2309
+ const vz = v02 * cx + v12 * cy + v22 * cz + v32;
2310
+ const viewScaleX = Math.hypot(v00, v01, v02);
2311
+ const viewScaleY = Math.hypot(v10, v11, v12);
2312
+ const viewScaleZ = Math.hypot(v20, v21, v22);
2313
+ const rx = radius * viewScaleX;
2314
+ const ry = radius * viewScaleY;
2315
+ const rz = radius * viewScaleZ;
2316
+ const vMinX = vx - rx;
2317
+ const vMaxX = vx + rx;
2318
+ const vMinY = vy - ry;
2319
+ const vMaxY = vy + ry;
2320
+ const vMinZ = vz - rz;
2321
+ const vMaxZ = vz + rz;
2322
+ const minViewZ = vMinZ;
2323
+ const maxViewZ = Math.min(vMaxZ, -1e-5);
2324
+ if (minViewZ > maxViewZ) {
2325
+ result.min[0] = 1;
2326
+ result.min[1] = 1;
2327
+ result.min[2] = -1e-5;
2328
+ result.max[0] = -1;
2329
+ result.max[1] = -1;
2330
+ result.max[2] = -1e-5;
2331
+ return result;
2332
+ }
2333
+ let ndcMinX = Infinity;
2334
+ let ndcMaxX = -Infinity;
2335
+ let ndcMinY = Infinity;
2336
+ let ndcMaxY = -Infinity;
2337
+ const p00 = proj[0] ?? 0;
2338
+ const p10 = proj[4] ?? 0;
2339
+ const p20 = proj[8] ?? 0;
2340
+ const p30 = proj[12] ?? 0;
2341
+ const p01 = proj[1] ?? 0;
2342
+ const p11 = proj[5] ?? 0;
2343
+ const p21 = proj[9] ?? 0;
2344
+ const p31 = proj[13] ?? 0;
2345
+ const p23 = proj[11] ?? 0;
2346
+ const perspectiveWOnlyZ = Math.abs(proj[3] ?? 0) < 1e-8 && Math.abs(proj[7] ?? 0) < 1e-8 && Math.abs(proj[15] ?? 0) < 1e-8 && Math.abs(p23) >= 1e-5;
2347
+ if (perspectiveWOnlyZ) {
2348
+ const xExtent = Math.abs(p00) * rx + Math.abs(p10) * ry;
2349
+ const yExtent = Math.abs(p01) * rx + Math.abs(p11) * ry;
2350
+ for (let face = 0; face < 2; face++) {
2351
+ const zTest = face === 0 ? minViewZ : maxViewZ;
2352
+ if (zTest > 0) continue;
2353
+ const invW = 1 / (p23 * zTest);
2354
+ const xCenter = p20 * zTest + p30;
2355
+ const yCenter = p21 * zTest + p31;
2356
+ const x0 = (xCenter - xExtent) * invW;
2357
+ const x1 = (xCenter + xExtent) * invW;
2358
+ const y0 = (yCenter - yExtent) * invW;
2359
+ const y1 = (yCenter + yExtent) * invW;
2360
+ ndcMinX = Math.min(ndcMinX, x0, x1);
2361
+ ndcMaxX = Math.max(ndcMaxX, x0, x1);
2362
+ ndcMinY = Math.min(ndcMinY, y0, y1);
2363
+ ndcMaxY = Math.max(ndcMaxY, y0, y1);
2364
+ }
2365
+ } else {
2366
+ const corners = [
2367
+ [vMinX, vMinY],
2368
+ [vMinX, vMaxY],
2369
+ [vMaxX, vMinY],
2370
+ [vMaxX, vMaxY]
2371
+ ];
2372
+ for (const zTest of [minViewZ, maxViewZ]) {
2373
+ if (zTest <= 0) {
2374
+ for (const corner of corners) {
2375
+ const sx = corner[0];
2376
+ const sy = corner[1];
2377
+ const ndc = projectToNdc(sx, sy, zTest, proj);
2378
+ ndcMinX = Math.min(ndcMinX, ndc[0]);
2379
+ ndcMaxX = Math.max(ndcMaxX, ndc[0]);
2380
+ ndcMinY = Math.min(ndcMinY, ndc[1]);
2381
+ ndcMaxY = Math.max(ndcMaxY, ndc[1]);
2382
+ }
2383
+ }
2384
+ }
2385
+ }
2386
+ ndcMinX = Math.max(ndcMinX, -1);
2387
+ ndcMinY = Math.max(ndcMinY, -1);
2388
+ ndcMaxX = Math.min(ndcMaxX, 1);
2389
+ ndcMaxY = Math.min(ndcMaxY, 1);
2390
+ result.min[0] = ndcMinX;
2391
+ result.min[1] = ndcMinY;
2392
+ result.min[2] = minViewZ;
2393
+ result.max[0] = ndcMaxX;
2394
+ result.max[1] = ndcMaxY;
2395
+ result.max[2] = maxViewZ;
2396
+ return result;
2397
+ }
2398
+ function projectToNdc(vx, vy, vz, proj) {
2399
+ const p00 = proj[0] ?? 0;
2400
+ const p10 = proj[4] ?? 0;
2401
+ const p20 = proj[8] ?? 0;
2402
+ const p30 = proj[12] ?? 0;
2403
+ const p01 = proj[1] ?? 0;
2404
+ const p11 = proj[5] ?? 0;
2405
+ const p21 = proj[9] ?? 0;
2406
+ const p31 = proj[13] ?? 0;
2407
+ const p02 = proj[2] ?? 0;
2408
+ const p12 = proj[6] ?? 0;
2409
+ const p22 = proj[10] ?? 0;
2410
+ const p32 = proj[14] ?? 0;
2411
+ const p03 = proj[3] ?? 0;
2412
+ const p13 = proj[7] ?? 0;
2413
+ const p23 = proj[11] ?? 0;
2414
+ const p33 = proj[15] ?? 0;
2415
+ const cx = p00 * vx + p10 * vy + p20 * vz + p30;
2416
+ const cy = p01 * vx + p11 * vy + p21 * vz + p31;
2417
+ const cz = p02 * vx + p12 * vy + p22 * vz + p32;
2418
+ const cw = p03 * vx + p13 * vy + p23 * vz + p33;
2419
+ if (Math.abs(cw) < 1e-10) {
2420
+ return [vx < 0 ? -1 : 1, vy < 0 ? -1 : 1, cz];
2421
+ }
2422
+ const invW = 1 / cw;
2423
+ return [cx * invW, cy * invW, cz * invW];
2424
+ }
2425
+ function ndcPositionToCluster(ndc, viewZ, gridX, gridY, gridZ, near, far) {
2426
+ const ndcX = ndc[0] ?? 0;
2427
+ const ndcY = ndc[1] ?? 0;
2428
+ let cx = Math.floor((ndcX * 0.5 + 0.5) * gridX);
2429
+ let cy = Math.floor((ndcY * 0.5 + 0.5) * gridY);
2430
+ if (cx < 0) cx = 0;
2431
+ if (cx >= gridX) cx = gridX - 1;
2432
+ if (cy < 0) cy = 0;
2433
+ if (cy >= gridY) cy = gridY - 1;
2434
+ const cz = viewZToZSlice(viewZ, gridZ, near, far);
2435
+ return { x: cx, y: cy, z: cz };
2436
+ }
2437
+ function calculateSphereClusterBounds(aabb, gridX, gridY, gridZ, near, far, output) {
2438
+ const result = output ?? {
2439
+ min: { x: 0, y: 0, z: 0 },
2440
+ max: { x: 0, y: 0, z: 0 }
2441
+ };
2442
+ const minZ = aabb.min[2] ?? 0;
2443
+ const maxZ = aabb.max[2] ?? 0;
2444
+ const idxMin = ndcPositionToCluster(aabb.min, minZ, gridX, gridY, gridZ, near, far);
2445
+ const idxMax = ndcPositionToCluster(aabb.max, maxZ, gridX, gridY, gridZ, near, far);
2446
+ result.min.x = Math.min(idxMin.x, idxMax.x);
2447
+ result.min.y = Math.min(idxMin.y, idxMax.y);
2448
+ result.min.z = Math.min(idxMin.z, idxMax.z);
2449
+ result.max.x = Math.max(idxMin.x, idxMax.x);
2450
+ result.max.y = Math.max(idxMin.y, idxMax.y);
2451
+ result.max.z = Math.max(idxMin.z, idxMax.z);
2452
+ return result;
2453
+ }
2454
+ function bin(lights, view, proj, grid, near, far, clusterGrid, lightIndexList, capacity, scratch, profile, options = {}) {
2455
+ const gridX = grid.x;
2456
+ const gridY = grid.y;
2457
+ const gridZ = grid.z;
2458
+ const clusterCount = gridX * gridY * gridZ;
2459
+ const binScratch = scratch ?? createClusterBinScratch();
2460
+ ensureScratchCapacity(binScratch, clusterCount, lights.length, gridX, gridY, gridZ);
2461
+ const { clusterCounts, clusterCursors, lightBounds, clusterDeltas } = binScratch;
2462
+ clusterGrid.fill(0, 0, clusterCount * 2);
2463
+ clusterCounts.fill(0, 0, clusterCount);
2464
+ let attemptedTotal = 0;
2465
+ runClusterBinProfilePhase(profile, "light-bounds-and-occupancy", () => {
2466
+ runClusterBinProfilePhase(profile, "light-bounds-and-occupancy/light-aabb", () => {
2467
+ for (let lightIdx = 0; lightIdx < lights.length; lightIdx++) {
2468
+ const boundsOffset = lightIdx * 6;
2469
+ lightBounds[boundsOffset] = -1;
2470
+ const light = lights[lightIdx];
2471
+ if (!light) continue;
2472
+ const radius = deriveCullingRadius(light.range, 1);
2473
+ if (radius <= 0) {
2474
+ continue;
2420
2475
  }
2421
- },
2422
- {
2423
- binding: 5,
2424
- resource: {
2425
- kind: "buffer",
2426
- value: { buffer: hdrpBuffers.lightIndexListBuffer }
2476
+ const aabb = clusterSpaceObjectAabb(
2477
+ light.position,
2478
+ radius,
2479
+ view,
2480
+ proj,
2481
+ binScratch.aabbOutput
2482
+ );
2483
+ const bounds = calculateSphereClusterBounds(
2484
+ aabb,
2485
+ gridX,
2486
+ gridY,
2487
+ gridZ,
2488
+ near,
2489
+ far,
2490
+ binScratch.boundsOutput
2491
+ );
2492
+ if (bounds.min.x > bounds.max.x || bounds.min.y > bounds.max.y || bounds.min.z > bounds.max.z) {
2493
+ continue;
2427
2494
  }
2428
- },
2429
- {
2430
- binding: 6,
2431
- resource: {
2432
- kind: "buffer",
2433
- value: { buffer: hdrpBuffers.clusterUniformBuffer }
2495
+ lightBounds[boundsOffset] = bounds.min.x;
2496
+ lightBounds[boundsOffset + 1] = bounds.min.y;
2497
+ lightBounds[boundsOffset + 2] = bounds.min.z;
2498
+ lightBounds[boundsOffset + 3] = bounds.max.x;
2499
+ lightBounds[boundsOffset + 4] = bounds.max.y;
2500
+ lightBounds[boundsOffset + 5] = bounds.max.z;
2501
+ }
2502
+ });
2503
+ runClusterBinProfilePhase(profile, "light-bounds-and-occupancy/cluster-occupancy", () => {
2504
+ const diffX = gridX + 1;
2505
+ const diffY = gridY + 1;
2506
+ const diffRow = diffX * diffY;
2507
+ clusterDeltas.fill(0, 0, diffRow * (gridZ + 1));
2508
+ const addDifference = (x, y, z, delta) => {
2509
+ const index = z * diffRow + y * diffX + x;
2510
+ clusterDeltas[index] = (clusterDeltas[index] ?? 0) + delta;
2511
+ };
2512
+ for (let lightIdx = 0; lightIdx < lights.length; lightIdx++) {
2513
+ const boundsOffset = lightIdx * 6;
2514
+ if ((lightBounds[boundsOffset] ?? -1) < 0) continue;
2515
+ const minX = lightBounds[boundsOffset] ?? 0;
2516
+ const minY = lightBounds[boundsOffset + 1] ?? 0;
2517
+ const minZ = lightBounds[boundsOffset + 2] ?? 0;
2518
+ const maxX = lightBounds[boundsOffset + 3] ?? -1;
2519
+ const maxY = lightBounds[boundsOffset + 4] ?? -1;
2520
+ const maxZ = lightBounds[boundsOffset + 5] ?? -1;
2521
+ const endX = maxX + 1;
2522
+ const endY = maxY + 1;
2523
+ const endZ = maxZ + 1;
2524
+ addDifference(minX, minY, minZ, 1);
2525
+ addDifference(endX, minY, minZ, -1);
2526
+ addDifference(minX, endY, minZ, -1);
2527
+ addDifference(minX, minY, endZ, -1);
2528
+ addDifference(endX, endY, minZ, 1);
2529
+ addDifference(endX, minY, endZ, 1);
2530
+ addDifference(minX, endY, endZ, 1);
2531
+ addDifference(endX, endY, endZ, -1);
2532
+ }
2533
+ for (let cz = 0; cz < gridZ; cz++) {
2534
+ for (let cy = 0; cy < gridY; cy++) {
2535
+ for (let cx = 0; cx < gridX; cx++) {
2536
+ const diffIdx = cz * diffRow + cy * diffX + cx;
2537
+ let count = clusterDeltas[diffIdx] ?? 0;
2538
+ if (cx > 0) count += clusterDeltas[diffIdx - 1] ?? 0;
2539
+ if (cy > 0) count += clusterDeltas[diffIdx - diffX] ?? 0;
2540
+ if (cz > 0) count += clusterDeltas[diffIdx - diffRow] ?? 0;
2541
+ if (cx > 0 && cy > 0) count -= clusterDeltas[diffIdx - diffX - 1] ?? 0;
2542
+ if (cx > 0 && cz > 0) count -= clusterDeltas[diffIdx - diffRow - 1] ?? 0;
2543
+ if (cy > 0 && cz > 0) count -= clusterDeltas[diffIdx - diffRow - diffX] ?? 0;
2544
+ if (cx > 0 && cy > 0 && cz > 0) {
2545
+ count += clusterDeltas[diffIdx - diffRow - diffX - 1] ?? 0;
2546
+ }
2547
+ clusterDeltas[diffIdx] = count;
2548
+ const clusterIdx = cz * gridY * gridX + cy * gridX + cx;
2549
+ clusterCounts[clusterIdx] = count;
2550
+ attemptedTotal += count;
2551
+ }
2434
2552
  }
2435
- },
2436
- {
2437
- binding: 7,
2438
- resource: { kind: "textureView", value: ssaoTexView }
2439
- },
2440
- {
2441
- binding: 8,
2442
- resource: { kind: "sampler", value: ssaoSampler }
2443
2553
  }
2444
- ]
2554
+ });
2445
2555
  });
2446
- if (!result.ok) {
2447
- runtime.errorRegistry.fire(result.error);
2448
- return null;
2556
+ if (attemptedTotal > capacity) {
2557
+ return err({
2558
+ code: "index-overflow",
2559
+ expected: `writeCount <= ${capacity}`,
2560
+ hint: `light index list overflow: needed ${attemptedTotal} entries, capacity ${capacity}; reduce lights, shrink grid, or shrink ranges (overrun = ${attemptedTotal - capacity})`,
2561
+ detail: { actual: attemptedTotal, capacity }
2562
+ });
2449
2563
  }
2450
- return result.value;
2451
- }
2452
- function createHdrpClusterMembershipBindGroup(runtime, hdrpBuffers, layout) {
2453
- const result = runtime.device.createBindGroup({
2454
- label: "hdrp-cluster-membership-bg",
2455
- layout,
2456
- entries: [
2457
- {
2458
- binding: 0,
2459
- resource: { kind: "buffer", value: { buffer: hdrpBuffers.clusterGridBuffer } }
2460
- },
2461
- {
2462
- binding: 1,
2463
- resource: { kind: "buffer", value: { buffer: hdrpBuffers.lightIndexListBuffer } }
2464
- },
2465
- {
2466
- binding: 2,
2467
- resource: { kind: "buffer", value: { buffer: hdrpBuffers.clusterUniformBuffer } }
2468
- },
2469
- {
2470
- binding: 3,
2471
- resource: { kind: "buffer", value: { buffer: hdrpBuffers.lightBoundsBuffer } }
2472
- }
2473
- ]
2564
+ const writeCount = runClusterBinProfilePhase(profile, "cluster-reserve", () => {
2565
+ let reserved = 0;
2566
+ for (let ci = 0; ci < clusterCount; ci++) {
2567
+ const base = ci * 2;
2568
+ clusterGrid[base] = reserved;
2569
+ clusterGrid[base + 1] = clusterCounts[ci] ?? 0;
2570
+ clusterCursors[ci] = reserved;
2571
+ reserved += clusterCounts[ci] ?? 0;
2572
+ }
2573
+ return reserved;
2474
2574
  });
2475
- if (!result.ok) {
2476
- runtime.errorRegistry.fire(result.error);
2477
- return null;
2478
- }
2479
- return result.value;
2480
- }
2481
- var SHADOW_ATLAS_DEFAULT_FACE_SIZE = 512;
2482
- var SHADOW_ATLAS_DEFAULT_LAYERS = 4;
2483
- var ShadowAtlas = class {
2484
- /** Backing cube_array depth texture. `null` until `ensure()` has run. */
2485
- texture = null;
2486
- /**
2487
- * Whole-atlas cube_array sampling view used by URP shaders that bind the
2488
- * atlas at `@group(0) @binding(5)`. `null` until `ensure()` has run.
2489
- */
2490
- cubeArrayView = null;
2491
- /** Comparison sampler for `texture_depth_cube_array`. */
2492
- compareSampler = null;
2493
- /** Per-face 2D depth view cache, keyed by `layer * 6 + face`. */
2494
- faceViews = /* @__PURE__ */ new Map();
2495
- /** Per-face label cache (debug aid). Reused on re-creation. */
2496
- device;
2497
- /** Per-face square size in pixels. */
2498
- faceSize;
2499
- /** Cube layer count. */
2500
- layers;
2501
- constructor(device, options = {}) {
2502
- this.device = device;
2503
- this.faceSize = options.faceSize ?? SHADOW_ATLAS_DEFAULT_FACE_SIZE;
2504
- this.layers = options.layers ?? SHADOW_ATLAS_DEFAULT_LAYERS;
2505
- }
2506
- /**
2507
- * Returns `true` once the atlas texture is allocated. Used by the extract /
2508
- * record path to gate "no PointLightShadow → no allocation" (AC-09) — the
2509
- * caller calls {@link ensure} on a frame that has at least one snapshot;
2510
- * before that, `isAllocated()` stays `false` and tests can assert no GPU
2511
- * resources were created.
2512
- */
2513
- isAllocated() {
2514
- return this.texture !== null;
2515
- }
2516
- /**
2517
- * Lazy-allocate the atlas texture + cube_array sampling view + comparison
2518
- * sampler. Idempotent: subsequent calls are zero-cost no-ops once the
2519
- * resources exist.
2520
- *
2521
- * Throws (via the RHI error registry) on allocation failure. This is the
2522
- * only path that touches the GPU — keeping it inside one method makes the
2523
- * "zero-shadow scene = zero allocation" gate easy to enforce: the renderer
2524
- * never calls this method on a frame with no PointLightShadow entities.
2525
- */
2526
- ensure() {
2527
- if (this.texture !== null) return;
2528
- const usage = GPU_TEXTURE_USAGE_RENDER_ATTACHMENT_AND_TEXTURE_BINDING;
2529
- const texDesc = cubeArrayDepthDescriptor(this.faceSize, this.layers, usage);
2530
- const texRes = this.device.createTexture(texDesc);
2531
- if (!texRes.ok) throw texRes.error;
2532
- this.texture = texRes.value;
2533
- const viewRes = this.device.createTextureView(this.texture, {
2534
- label: "shadow-atlas-cube-array-view",
2535
- dimension: "cube-array",
2536
- aspect: "depth-only",
2537
- baseArrayLayer: 0,
2538
- arrayLayerCount: 6 * this.layers,
2539
- baseMipLevel: 0,
2540
- mipLevelCount: 1
2575
+ if (options.writeMembership !== false) {
2576
+ runClusterBinProfilePhase(profile, "light-index-write", () => {
2577
+ for (let lightIdx = 0; lightIdx < lights.length; lightIdx++) {
2578
+ const boundsOffset = lightIdx * 6;
2579
+ let valid = false;
2580
+ let minX = 0;
2581
+ let minY = 0;
2582
+ let minZ = 0;
2583
+ let maxX = -1;
2584
+ let maxY = -1;
2585
+ let maxZ = -1;
2586
+ runClusterBinProfilePhase(profile, "light-index-write/bounds-read", () => {
2587
+ if ((lightBounds[boundsOffset] ?? -1) < 0) return;
2588
+ valid = true;
2589
+ minX = lightBounds[boundsOffset] ?? 0;
2590
+ minY = lightBounds[boundsOffset + 1] ?? 0;
2591
+ minZ = lightBounds[boundsOffset + 2] ?? 0;
2592
+ maxX = lightBounds[boundsOffset + 3] ?? -1;
2593
+ maxY = lightBounds[boundsOffset + 4] ?? -1;
2594
+ maxZ = lightBounds[boundsOffset + 5] ?? -1;
2595
+ });
2596
+ if (!valid) continue;
2597
+ runClusterBinProfilePhase(profile, "light-index-write/cluster-write", () => {
2598
+ for (let cz = minZ; cz <= maxZ; cz++) {
2599
+ for (let cy = minY; cy <= maxY; cy++) {
2600
+ for (let cx = minX; cx <= maxX; cx++) {
2601
+ const clusterIdx = cz * gridY * gridX + cy * gridX + cx;
2602
+ const cursor = clusterCursors[clusterIdx] ?? 0;
2603
+ lightIndexList[cursor] = lightIdx;
2604
+ clusterCursors[clusterIdx] = cursor + 1;
2605
+ }
2606
+ }
2607
+ }
2608
+ });
2609
+ }
2541
2610
  });
2542
- if (!viewRes.ok) {
2543
- this.device.destroyTexture(this.texture);
2544
- this.texture = null;
2545
- throw viewRes.error;
2546
- }
2547
- this.cubeArrayView = viewRes.value;
2548
- const sampRes = this.device.createSampler(comparisonSamplerDescriptor());
2549
- if (!sampRes.ok) {
2550
- this.device.destroyTexture(this.texture);
2551
- this.texture = null;
2552
- this.cubeArrayView = null;
2553
- throw sampRes.error;
2554
- }
2555
- this.compareSampler = sampRes.value;
2556
- }
2557
- /**
2558
- * Whole-atlas cube_array sampling view (`texture_depth_cube_array`). Returns
2559
- * `null` if the atlas is not yet allocated; the caller should gate on a
2560
- * non-null pointShadow array before calling {@link ensure} + this getter.
2561
- */
2562
- getAtlasView() {
2563
- return this.cubeArrayView;
2564
- }
2565
- /**
2566
- * Comparison sampler (`compare: 'less'`) for cube_array depth sampling.
2567
- * Returns `null` if the atlas is not yet allocated.
2568
- */
2569
- getComparisonSampler() {
2570
- return this.compareSampler;
2571
2611
  }
2572
- /**
2573
- * Backing texture handle. Returns `null` if the atlas is not yet allocated.
2574
- */
2575
- getTexture() {
2576
- return this.texture;
2577
- }
2578
- /**
2579
- * Lazy 2D depth view selecting `(layer, face)` (the caller passes
2580
- * `shadowAtlasLayer` from the snapshot and a face index 0..5). Used as the
2581
- * depth attachment of the shadow-caster render pass for one cube face.
2582
- *
2583
- * Caches per `(layer, face)` so the per-frame shadow loop pays zero
2584
- * `createTextureView` cost after the first allocation. Cache is cleared on
2585
- * {@link dispose}.
2586
- *
2587
- * @throws PointShadowAtlasUninitializedError if the atlas is not yet
2588
- * allocated (caller must call {@link ensure} first).
2589
- * @throws PointShadowAtlasBoundsViolationError if `layer` or `face` is out
2590
- * of range.
2591
- * @throws RhiError if the underlying `createTextureView` fails.
2592
- */
2593
- faceView(layer, face) {
2594
- if (this.texture === null) {
2595
- throw new PointShadowAtlasUninitializedError();
2596
- }
2597
- if (layer < 0 || layer >= this.layers) {
2598
- throw new PointShadowAtlasBoundsViolationError("layer", layer, this.layers);
2599
- }
2600
- if (face < 0 || face >= 6) {
2601
- throw new PointShadowAtlasBoundsViolationError("face", face, 6);
2602
- }
2603
- const key = layer * 6 + face;
2604
- const cached = this.faceViews.get(key);
2605
- if (cached !== void 0) return cached;
2606
- const desc = cubeArrayDepthFaceView(layer, face);
2607
- const r = this.device.createTextureView(this.texture, desc);
2608
- if (!r.ok) throw r.error;
2609
- this.faceViews.set(key, r.value);
2610
- return r.value;
2612
+ return ok$1(writeCount);
2613
+ }
2614
+
2615
+ // src/pipeline/standard-lighting/errors.ts
2616
+ function fromClusterBinError(error) {
2617
+ return new StandardClusterIndexOverflowError(error.detail.actual, error.detail.capacity);
2618
+ }
2619
+
2620
+ // src/pipeline/standard-lighting/membership.ts
2621
+ function deriveStandardMembership(frame, layout, scratch = createClusterBinScratch()) {
2622
+ const clusterGrid = new Uint32Array(layout.clusterGridU32Length);
2623
+ const lightIndexList = new Uint32Array(layout.lightIndexListCapacity);
2624
+ const result = bin(
2625
+ frame.local,
2626
+ frame.view,
2627
+ frame.projection,
2628
+ layout.grid,
2629
+ frame.near,
2630
+ frame.far,
2631
+ clusterGrid,
2632
+ lightIndexList,
2633
+ layout.lightIndexListCapacity,
2634
+ scratch
2635
+ );
2636
+ if (!result.ok) return err$1(fromClusterBinError(result.error));
2637
+ return ok({
2638
+ clusterGrid,
2639
+ lightIndexList,
2640
+ lightBounds: scratch.lightBounds.slice(0, frame.local.length * 6),
2641
+ membershipEntryCount: result.value,
2642
+ scratch
2643
+ });
2644
+ }
2645
+
2646
+ // src/pipeline/standard-lighting/prepare.ts
2647
+ function prepareStandardLighting(frame) {
2648
+ if (frame.local.length > frame.lightCount || frame.local.length > MAX_LIGHTS) {
2649
+ return err$1(new StandardLightBudgetExceededError(frame.local.length, frame.lightCount));
2650
+ }
2651
+ const layout = createStandardClusterLayout(frame.grid);
2652
+ const membership = deriveStandardMembership(frame, layout);
2653
+ if (!membership.ok) return membership;
2654
+ return ok({
2655
+ ...membership.value,
2656
+ directional: frame.directional,
2657
+ local: frame.local,
2658
+ layout,
2659
+ membershipLightCount: frame.local.length,
2660
+ renderPath: frame.renderPath,
2661
+ maxLights: frame.lightCount
2662
+ });
2663
+ }
2664
+ function occupiedClusterCount(prepared) {
2665
+ let occupied = 0;
2666
+ for (let index = 1; index < prepared.clusterGrid.length; index += 2) {
2667
+ if ((prepared.clusterGrid[index] ?? 0) > 0) occupied += 1;
2611
2668
  }
2612
- /**
2613
- * Release the GPU resources and clear the per-face view cache. Idempotent;
2614
- * subsequent calls are zero-cost. After dispose, `isAllocated()` returns
2615
- * `false` and the next `ensure()` re-allocates from scratch.
2616
- */
2617
- dispose() {
2618
- this.faceViews.clear();
2619
- if (this.texture !== null) {
2620
- this.device.destroyTexture(this.texture);
2621
- this.texture = null;
2622
- }
2623
- this.cubeArrayView = null;
2624
- this.compareSampler = null;
2669
+ return occupied;
2670
+ }
2671
+ function selectStandardClusterTransport(capabilities, prepared) {
2672
+ const requested = prepared.local.length;
2673
+ if (!capabilities.storageBuffer) {
2674
+ return err$1(
2675
+ new StandardClusterTransportUnavailableError(
2676
+ requested,
2677
+ "enable a proven storage-buffer Cluster transport; compute without storage is not a membership path"
2678
+ )
2679
+ );
2625
2680
  }
2626
- };
2681
+ const producer = capabilities.compute && capabilities.membershipPipelineReady ? "gpu" : "cpu";
2682
+ const kind = producer === "gpu" ? "compute-storage" : "cpu-storage";
2683
+ return ok({
2684
+ kind,
2685
+ producer,
2686
+ layout: prepared.layout,
2687
+ requestedLightCount: requested,
2688
+ admittedLightCount: requested,
2689
+ membershipEntryCount: prepared.membershipEntryCount,
2690
+ occupiedClusterCount: occupiedClusterCount(prepared),
2691
+ clusterGridBytes: prepared.layout.clusterGridU32Length * Uint32Array.BYTES_PER_ELEMENT,
2692
+ lightIndexListBytes: prepared.membershipEntryCount * Uint32Array.BYTES_PER_ELEMENT,
2693
+ lightDataBytes: requested * 64,
2694
+ lightBoundsBytes: prepared.lightBounds.byteLength
2695
+ });
2696
+ }
2627
2697
 
2628
2698
  // src/post-process-errors.ts
2629
2699
  var POST_PROCESS_POLICY = {
@@ -2746,38 +2816,6 @@ function warnMultiLightDirectional(frameState, directionalCount, envOverride) {
2746
2816
  }
2747
2817
  }
2748
2818
  }
2749
- function warnMultiLightPoint(frameState, pointCount, envOverride) {
2750
- if (!frameState.warnedMultiLightPoint && pointCount > 4) {
2751
- frameState.warnedMultiLightPoint = true;
2752
- const env = globalThis.process;
2753
- if (env?.env?.NODE_ENV !== "production") {
2754
- console.warn(
2755
- "[forgeax] render-system-multi-light point: at most 4 entities (got N=" + pointCount + "). First 4 used; rest dropped.",
2756
- {
2757
- code: "render-system-multi-light",
2758
- expected: "at most 4 point",
2759
- detail: { type: "point", got: pointCount }
2760
- }
2761
- );
2762
- }
2763
- }
2764
- }
2765
- function warnMultiLightSpot(frameState, spotCount, envOverride) {
2766
- if (!frameState.warnedMultiLightSpot && spotCount > 4) {
2767
- frameState.warnedMultiLightSpot = true;
2768
- const env = globalThis.process;
2769
- if (env?.env?.NODE_ENV !== "production") {
2770
- console.warn(
2771
- "[forgeax] render-system-multi-light spot: at most 4 entities (got N=" + spotCount + "). First 4 used; rest dropped.",
2772
- {
2773
- code: "render-system-multi-light",
2774
- expected: "at most 4 spot",
2775
- detail: { type: "spot", got: spotCount }
2776
- }
2777
- );
2778
- }
2779
- }
2780
- }
2781
2819
  function warnMultiSkylight(frameState, skylightCount, winningEntityHandle) {
2782
2820
  if (!frameState.warnedMultiSkylight && skylightCount > 1) {
2783
2821
  frameState.warnedMultiSkylight = true;
@@ -2844,10 +2882,32 @@ var POINTS_LINES_VIEW_SLOT_COUNT = 1024;
2844
2882
  var POINTS_LINES_VIEW_BUFFER_SIZE = POINTS_LINES_VIEW_SLOT_STRIDE * POINTS_LINES_VIEW_SLOT_COUNT;
2845
2883
  var VIEW_UNIFORM_SLOT_STRIDE = 1024;
2846
2884
  var POINT_SHADOW_VIEW_SLOT_COUNT = 24;
2847
- var VIEW_UNIFORM_BUFFER_SIZE = VIEW_UNIFORM_SLOT_STRIDE * (1 + POINT_SHADOW_VIEW_SLOT_COUNT);
2885
+ var CUBE_CAPTURE_VIEW_SLOT_BASE = 1 + POINT_SHADOW_VIEW_SLOT_COUNT;
2886
+ var REFLECTION_PROBE_VIEW_SLOT_BASE = CUBE_CAPTURE_VIEW_SLOT_BASE + 6;
2887
+ var REFLECTION_PROBE_VIEW_SLOT_COUNT = 16;
2888
+ var VIEW_UNIFORM_BUFFER_SIZE = VIEW_UNIFORM_SLOT_STRIDE * (REFLECTION_PROBE_VIEW_SLOT_BASE + REFLECTION_PROBE_VIEW_SLOT_COUNT);
2848
2889
  function pointShadowViewOffset(layer, face) {
2849
2890
  return VIEW_UNIFORM_SLOT_STRIDE * (1 + layer * 6 + face);
2850
2891
  }
2892
+ function directionalShadowFilterCarrier(quality) {
2893
+ if (quality === void 0) return [0, 0, 0, 0];
2894
+ if (quality.kind === "pcss") {
2895
+ return [
2896
+ quality.preset === "medium" ? 4 : 5,
2897
+ quality.angularRadiusRadians,
2898
+ quality.maxPenumbraTexels,
2899
+ 0
2900
+ ];
2901
+ }
2902
+ switch (quality.kernel) {
2903
+ case 1:
2904
+ return [1, 0, 0, 0];
2905
+ case 3:
2906
+ return [2, 0, 0, 0];
2907
+ case 5:
2908
+ return [3, 0, 0, 0];
2909
+ }
2910
+ }
2851
2911
  function writePointsLinesViewUbo(queue, buffer, camera, width, height, model, style, byteOffset = 0) {
2852
2912
  const projection = computeProjectionMatrix(camera);
2853
2913
  const view = computeViewMatrix(camera);
@@ -2879,7 +2939,9 @@ function writePointsLinesViewUbo(queue, buffer, camera, width, height, model, st
2879
2939
  const uploaded = queue.writeBuffer(buffer, byteOffset, payload);
2880
2940
  if (!uploaded.ok) throw uploaded.error;
2881
2941
  }
2882
- function writeViewUbo(queue, viewUniformBuffer, camera, light, lights, spotShadowSnapshots, fogOrTemporal, temporal) {
2942
+ function writeViewUbo(queue, viewUniformBuffer, camera, light, lights, spotShadowSnapshots, fogOrTemporalOrOffset, temporal, projectorSpotIndex) {
2943
+ const byteOffset = typeof fogOrTemporalOrOffset === "number" ? fogOrTemporalOrOffset : 0;
2944
+ const fogOrTemporal = typeof fogOrTemporalOrOffset === "number" ? void 0 : fogOrTemporalOrOffset;
2883
2945
  const isTemporal = fogOrTemporal !== void 0 && ("currentJitterUv" in fogOrTemporal || "currentUnjitteredViewProjection" in fogOrTemporal);
2884
2946
  const fog = isTemporal ? void 0 : fogOrTemporal;
2885
2947
  const resolvedTemporal = temporal ?? (isTemporal ? fogOrTemporal : void 0);
@@ -2935,14 +2997,20 @@ function writeViewUbo(queue, viewUniformBuffer, camera, light, lights, spotShado
2935
2997
  viewPayload[230] = camera.projection === "orthographic" ? 1 : 0;
2936
2998
  if (lights.splitPlanes !== void 0) {
2937
2999
  for (let s = 0; s < 4; s++) {
2938
- viewPayload[108 + s * 4] = lights.splitPlanes[s] ?? 0;
3000
+ for (let lane = 0; lane < 4; lane++) {
3001
+ viewPayload[108 + s * 4 + lane] = lights.splitPlanes[s * 4 + lane] ?? 0;
3002
+ }
2939
3003
  }
2940
3004
  }
2941
3005
  viewPayload[124] = lights.cascadeCount ?? 0;
2942
3006
  viewPayload[125] = lights.cascadeBlend ?? 0;
2943
3007
  viewPayload[126] = lights.depthBias ?? 5e-3;
2944
3008
  viewPayload[127] = lights.normalBias ?? 0.05;
2945
- viewPayload[128] = clampPcfKernelSize(lights.pcfKernelSize);
3009
+ const filterCarrier = directionalShadowFilterCarrier(lights.directionalShadowQuality);
3010
+ viewPayload[128] = filterCarrier[0] ?? 0;
3011
+ viewPayload[129] = filterCarrier[1] ?? 0;
3012
+ viewPayload[130] = filterCarrier[2] ?? 0;
3013
+ viewPayload[131] = filterCarrier[3] ?? 0;
2946
3014
  {
2947
3015
  const SPOT_LVP_BASE_FLOAT = 132;
2948
3016
  const SPOT_LVP_LANE_COUNT = 4;
@@ -2952,10 +3020,11 @@ function writeViewUbo(queue, viewUniformBuffer, camera, light, lights, spotShado
2952
3020
  const ss = spotSnaps[i];
2953
3021
  if (ss === void 0) continue;
2954
3022
  const tile = ss.shadowAtlasTile;
2955
- if (tile < 0 || tile >= SPOT_LVP_LANE_COUNT) continue;
2956
3023
  const lvp = ss.lightViewProj;
2957
3024
  if (lvp === void 0) continue;
2958
- const base = SPOT_LVP_BASE_FLOAT + tile * SPOT_LVP_FLOATS_PER_LANE;
3025
+ const lane = tile >= 0 && tile < SPOT_LVP_LANE_COUNT ? tile : i === projectorSpotIndex ? 0 : -1;
3026
+ if (lane < 0) continue;
3027
+ const base = SPOT_LVP_BASE_FLOAT + lane * SPOT_LVP_FLOATS_PER_LANE;
2959
3028
  for (let f = 0; f < SPOT_LVP_FLOATS_PER_LANE; f++) {
2960
3029
  viewPayload[base + f] = lvp[f] ?? 0;
2961
3030
  }
@@ -2969,10 +3038,11 @@ function writeViewUbo(queue, viewUniformBuffer, camera, light, lights, spotShado
2969
3038
  viewPayload[236] = fog.heightFalloff;
2970
3039
  viewPayload[237] = fog.maxOpacity;
2971
3040
  }
2972
- const viewUploadResult = queue.writeBuffer(viewUniformBuffer, 0, viewPayload);
3041
+ const viewUploadResult = queue.writeBuffer(viewUniformBuffer, byteOffset, viewPayload);
2973
3042
  if (!viewUploadResult.ok) throw viewUploadResult.error;
2974
3043
  for (const snapshot of lights.pointShadow ?? []) {
2975
- if (snapshot.shadowAtlasLayer < 0 || snapshot.shadowAtlasLayer >= 4) continue;
3044
+ if (snapshot.shadowAtlasLayer < 0 || snapshot.shadowAtlasLayer >= SHADOW_ATLAS_DEFAULT_LAYERS)
3045
+ continue;
2976
3046
  for (let face = 0; face < 6; face += 1) {
2977
3047
  const facePayload = viewPayload.slice();
2978
3048
  const matrix = snapshot.shadowMatrices.subarray(face * 16, (face + 1) * 16);
@@ -2988,14 +3058,42 @@ function writeViewUbo(queue, viewUniformBuffer, camera, light, lights, spotShado
2988
3058
  }
2989
3059
 
2990
3060
  // src/record/frame-lighting.ts
3061
+ function resolveReflectionProbeBinding(selection, table) {
3062
+ if (selection.kind !== "probe") return { probeIndex: void 0, useSkylight: true };
3063
+ const primitiveKey = `${selection.worldId}:${selection.entityKey}`;
3064
+ const tableIndex = table?.cpuIndexByPrimitive(primitiveKey);
3065
+ return { probeIndex: tableIndex ?? selection.entityKey, useSkylight: false };
3066
+ }
3067
+ function projectDeviceOwnedBindGroup(state, device, layout) {
3068
+ return state !== null && state !== void 0 && state.device === device && state.layout === layout ? state.bindGroup : null;
3069
+ }
2991
3070
  function runHdrpClusterProfilePhase(runner, phase, action) {
2992
3071
  return runner === void 0 ? action() : runner(phase, action);
2993
3072
  }
2994
- function buildPerFrameBindGroups(internals, frameState, pipelineState, hasValidated, bindGroupCounts, graphTargets, includeView = true) {
3073
+ function mapClusterUploadFailure(resource, error) {
3074
+ return new RendererOperationError("device-operation-failed", {
3075
+ operation: "draw",
3076
+ cause: {
3077
+ code: error.code,
3078
+ expected: `queue.writeBuffer succeeds for Standard Cluster ${resource}`,
3079
+ hint: `repair the device write for Standard Cluster ${resource} and retry the frame`,
3080
+ detail: error
3081
+ }
3082
+ });
3083
+ }
3084
+ function buildPerFrameBindGroups(internals, frameState, pipelineState, hasValidated, bindGroupCounts, graphTargets, includeView = true, standardLighting) {
2995
3085
  let viewBindGroup = null;
2996
3086
  let meshBindGroup = null;
2997
3087
  let hdrpClusterBindGroup = null;
2998
- const hdrpClusterMembershipBindGroup = frameState.isHdrpActive ? frameState.hdrpClusterMembershipBindGroup : null;
3088
+ const clusteredLighting = standardLighting?.kind === "clustered";
3089
+ const membershipLayout = pipelineState.hdrpClusterMembershipBindGroupLayout;
3090
+ const membership = frameState.hdrpClusterMembership;
3091
+ const expectedHdrpBuffers = hasValidated && clusteredLighting ? getOrCreateHdrpBuffers(internals, frameState.installedPipelineConfig?.clusterGrid) : null;
3092
+ const currentMembership = expectedHdrpBuffers !== null && membership?.clusterGridBuffer === expectedHdrpBuffers.clusterGridBuffer ? projectDeviceOwnedBindGroup(membership, internals.device, membershipLayout) : null;
3093
+ if (membership !== null && currentMembership === null) {
3094
+ frameState.hdrpClusterMembership = null;
3095
+ }
3096
+ const hdrpClusterMembershipBindGroup = clusteredLighting ? currentMembership : null;
2999
3097
  if (hasValidated) {
3000
3098
  if (includeView) {
3001
3099
  const shadowSampler = pipelineState.perPassResources.shadowSampler;
@@ -3173,11 +3271,8 @@ function buildPerFrameBindGroups(internals, frameState, pipelineState, hasValida
3173
3271
  },
3174
3272
  bindGroupCounts
3175
3273
  );
3176
- if (frameState.isHdrpActive) {
3177
- const hdrpBuffers = getOrCreateHdrpBuffers(
3178
- internals,
3179
- frameState.installedPipelineConfig?.clusterGrid
3180
- );
3274
+ if (clusteredLighting) {
3275
+ const hdrpBuffers = expectedHdrpBuffers;
3181
3276
  if (hdrpBuffers !== null) {
3182
3277
  hdrpClusterBindGroup = getOrCreateFromChain(
3183
3278
  frameState.meshBindGroupCache,
@@ -3211,27 +3306,19 @@ function buildPerFrameBindGroups(internals, frameState, pipelineState, hasValida
3211
3306
  }
3212
3307
  return { viewBindGroup, meshBindGroup, hdrpClusterBindGroup, hdrpClusterMembershipBindGroup };
3213
3308
  }
3214
- function prepareFrameLighting(internals, frameState, lights) {
3309
+ function prepareFrameLighting(internals, frameState, lights, camera, pipelineState) {
3215
3310
  if (lights.directionalCount > 1) {
3216
3311
  warnMultiLightDirectional(frameState, lights.directionalCount);
3217
3312
  }
3218
- if (!frameState.isHdrpActive) {
3219
- if (lights.point.length > LIGHT_ARRAY_MAX_SLOTS) {
3220
- warnMultiLightPoint(frameState, lights.point.length);
3221
- }
3222
- if (lights.spot.length > LIGHT_ARRAY_MAX_SLOTS) {
3223
- warnMultiLightSpot(frameState, lights.spot.length);
3224
- }
3225
- }
3226
3313
  frameState.pointShadowSnapshots = lights.pointShadow;
3227
3314
  frameState.spotShadowSnapshots = lights.spot;
3228
3315
  if (lights.pointShadow.length > 0) {
3229
3316
  if (frameState.pointShadowAtlas === null) {
3230
3317
  const firstSnap = lights.pointShadow[0];
3231
- const faceSize = firstSnap?.mapSize ?? 512;
3318
+ const faceSize = firstSnap?.mapSize ?? SHADOW_ATLAS_DEFAULT_FACE_SIZE;
3232
3319
  frameState.pointShadowAtlas = new ShadowAtlas(internals.device, {
3233
3320
  faceSize,
3234
- layers: 4
3321
+ layers: SHADOW_ATLAS_DEFAULT_LAYERS
3235
3322
  });
3236
3323
  }
3237
3324
  try {
@@ -3254,49 +3341,60 @@ function prepareFrameLighting(internals, frameState, lights) {
3254
3341
  intensity: 0
3255
3342
  };
3256
3343
  const totalLightCount = (directionalLight !== void 0 ? 1 : 0) + pointLights.length + spotLights.length;
3257
- return { light, pointLights, spotLights, totalLightCount };
3258
- }
3259
- function writePointSpotLightBuffers(internals, pipelineState, lights) {
3260
- const pointSlots = lights.point.length <= LIGHT_ARRAY_MAX_SLOTS ? lights.point : lights.point.slice(0, LIGHT_ARRAY_MAX_SLOTS);
3261
- const pointHeader = packLightArrayHeader(pointSlots.length);
3262
- const pointHeaderUpload = internals.device.queue.writeBuffer(
3263
- pipelineState.pointLightsBuffer,
3264
- 0,
3265
- new Uint8Array(pointHeader)
3266
- );
3267
- if (!pointHeaderUpload.ok) throw pointHeaderUpload.error;
3268
- for (let i = 0; i < pointSlots.length; i++) {
3269
- const slot = pointSlots[i];
3270
- if (slot === void 0) continue;
3271
- const packed = packPointLight(slot);
3272
- const offset = LIGHT_ARRAY_HEADER_BYTES + i * POINT_LIGHT_STD430_BYTES;
3273
- const writeRes = internals.device.queue.writeBuffer(
3274
- pipelineState.pointLightsBuffer,
3275
- offset,
3276
- packed
3277
- );
3278
- if (!writeRes.ok) throw writeRes.error;
3344
+ const clusterGrid = frameState.installedPipelineConfig?.clusterGrid ?? DEFAULT_CLUSTER_GRID;
3345
+ const local = [
3346
+ ...pointLights.map((source) => ({
3347
+ kind: "point",
3348
+ shadowed: source.shadowAtlasLayer !== void 0 && source.shadowAtlasLayer >= 0,
3349
+ position: source.position,
3350
+ range: Number.isFinite(source.invRangeSquared) && source.invRangeSquared > 0 ? Math.sqrt(1 / source.invRangeSquared) : 1e3,
3351
+ source
3352
+ })),
3353
+ ...spotLights.map((source) => ({
3354
+ kind: "spot",
3355
+ shadowed: source.castShadow && source.shadowAtlasTile >= 0 && source.lightViewProj !== void 0,
3356
+ position: source.position,
3357
+ range: Number.isFinite(source.invRangeSquared) && source.invRangeSquared > 0 ? Math.sqrt(1 / source.invRangeSquared) : 1e3,
3358
+ source
3359
+ }))
3360
+ ];
3361
+ const prepared = prepareStandardLighting({
3362
+ directional: directionalLight,
3363
+ local,
3364
+ view: computeViewMatrix(camera),
3365
+ projection: computeProjectionMatrix(camera),
3366
+ near: camera.near,
3367
+ far: camera.far,
3368
+ grid: clusterGrid,
3369
+ lightCount: internals.standardProfile?.lightCount ?? DEFAULT_STANDARD_PROFILE.lightCount,
3370
+ renderPath: internals.standardProfile?.renderPath ?? DEFAULT_STANDARD_PROFILE.renderPath
3371
+ });
3372
+ if (!prepared.ok) return prepared;
3373
+ if (prepared.value.local.length === 0 && !internals.device.caps.storageBuffer) {
3374
+ return ok({
3375
+ light,
3376
+ pointLights,
3377
+ spotLights,
3378
+ totalLightCount,
3379
+ standard: { kind: "no-local-lights", prepared: prepared.value }
3380
+ });
3279
3381
  }
3280
- const spotSlots = lights.spot.length <= LIGHT_ARRAY_MAX_SLOTS ? lights.spot : lights.spot.slice(0, LIGHT_ARRAY_MAX_SLOTS);
3281
- const spotHeader = packLightArrayHeader(spotSlots.length);
3282
- const spotHeaderUpload = internals.device.queue.writeBuffer(
3283
- pipelineState.spotLightsBuffer,
3284
- 0,
3285
- new Uint8Array(spotHeader)
3382
+ const transport = selectStandardClusterTransport(
3383
+ {
3384
+ compute: internals.device.caps.compute,
3385
+ storageBuffer: internals.device.caps.storageBuffer,
3386
+ membershipPipelineReady: pipelineState !== void 0 && pipelineState.hdrpClusterMembershipPipeline !== null && pipelineState.hdrpClusterMembershipBindGroupLayout !== null
3387
+ },
3388
+ prepared.value
3286
3389
  );
3287
- if (!spotHeaderUpload.ok) throw spotHeaderUpload.error;
3288
- for (let i = 0; i < spotSlots.length; i++) {
3289
- const slot = spotSlots[i];
3290
- if (slot === void 0) continue;
3291
- const packed = packSpotLight(slot);
3292
- const offset = LIGHT_ARRAY_HEADER_BYTES + i * SPOT_LIGHT_STD430_BYTES;
3293
- const writeRes = internals.device.queue.writeBuffer(
3294
- pipelineState.spotLightsBuffer,
3295
- offset,
3296
- packed
3297
- );
3298
- if (!writeRes.ok) throw writeRes.error;
3299
- }
3390
+ if (!transport.ok) return transport;
3391
+ return ok({
3392
+ light,
3393
+ pointLights,
3394
+ spotLights,
3395
+ totalLightCount,
3396
+ standard: { kind: "clustered", prepared: prepared.value, transport: transport.value }
3397
+ });
3300
3398
  }
3301
3399
  function writeShadowParamsBuffer(internals, frameState, pipelineState) {
3302
3400
  const SHADOW_PARAMS_LANE_COUNT = 4;
@@ -3339,191 +3437,81 @@ function warnZeroLightStandard(frameState, renderables, skylight, totalLightCoun
3339
3437
  }
3340
3438
  }
3341
3439
  }
3342
- function writeHdrpClusterAndSsaoBuffers(internals, frameState, camera, pointLights, spotLights, profilePhase, useGpuMembership = false, membershipBindGroupLayout = null) {
3343
- const hdrpLightCount = pointLights.length + spotLights.length;
3344
- if (!frameState.isHdrpActive) return;
3345
- if (hdrpLightCount === 0) {
3346
- const grid = frameState.installedPipelineConfig?.clusterGrid ?? DEFAULT_CLUSTER_GRID;
3347
- const hdrpBuffers = getOrCreateHdrpBuffers(internals, grid);
3348
- if (hdrpBuffers === null) return;
3349
- const lightCapacity = hdrpBuffers.storageBuffer ? 256 : HDRP_UNIFORM_LIGHT_CAPACITY;
3350
- const clusterSsaoConfig = frameState.installedPipelineConfig?.ssao;
3351
- const clusterSsaoIntensity = clusterSsaoConfig !== void 0 && clusterSsaoConfig.enabled === true ? clusterSsaoConfig.intensity ?? 1 : 0;
3352
- const clusterUniformPayload = packClusterUniform(
3353
- grid,
3354
- camera.near,
3355
- camera.far,
3356
- clusterSsaoIntensity,
3357
- 0,
3358
- lightCapacity
3359
- );
3360
- const clusterUniformUpload = internals.device.queue.writeBuffer(
3361
- hdrpBuffers.clusterUniformBuffer,
3362
- 0,
3363
- new Uint8Array(clusterUniformPayload)
3364
- );
3365
- if (!clusterUniformUpload.ok) internals.errorRegistry.fire(clusterUniformUpload.error);
3366
- const clusterGridClear = internals.device.queue.writeBuffer(
3367
- hdrpBuffers.clusterGridBuffer,
3368
- 0,
3369
- new Uint8Array(hdrpBuffers.clusterGridBytes)
3370
- );
3371
- if (!clusterGridClear.ok) internals.errorRegistry.fire(clusterGridClear.error);
3372
- const lightIndexListClear = internals.device.queue.writeBuffer(
3373
- hdrpBuffers.lightIndexListBuffer,
3374
- 0,
3375
- new Uint8Array(hdrpBuffers.lightIndexListBytes)
3440
+ function writeHdrpClusterAndSsaoBuffers(internals, frameState, camera, prepared, transport, profilePhase, gpuMembershipPipelineReady = false, membershipBindGroupLayout = null, projectorLightSlotIndex) {
3441
+ const configuredClusterGrid = prepared.layout.grid;
3442
+ const gpuMembership = transport.producer === "gpu";
3443
+ if (gpuMembership && (!gpuMembershipPipelineReady || membershipBindGroupLayout === null)) {
3444
+ return err$1(
3445
+ new StandardClusterTransportUnavailableError(
3446
+ prepared.local.length,
3447
+ "the selected GPU Cluster producer requires a ready membership pipeline and bind-group layout"
3448
+ )
3376
3449
  );
3377
- if (!lightIndexListClear.ok) internals.errorRegistry.fire(lightIndexListClear.error);
3378
- return;
3379
- }
3380
- const HDRP_LIGHT_BUDGET = 256;
3381
- let effectivePointLights = pointLights;
3382
- let effectiveSpotLights = spotLights;
3383
- if (hdrpLightCount > HDRP_LIGHT_BUDGET) {
3384
- if (!frameState.hdrpOncePerFrameFired.has("hdrp-light-budget-exceeded")) {
3385
- frameState.hdrpOncePerFrameFired.add("hdrp-light-budget-exceeded");
3386
- internals.errorRegistry.fire(
3387
- new HdrpLightBudgetExceededError(hdrpLightCount, HDRP_LIGHT_BUDGET)
3388
- );
3389
- }
3390
- if (pointLights.length >= HDRP_LIGHT_BUDGET) {
3391
- effectivePointLights = pointLights.slice(0, HDRP_LIGHT_BUDGET);
3392
- effectiveSpotLights = [];
3393
- } else {
3394
- effectivePointLights = pointLights;
3395
- effectiveSpotLights = spotLights.slice(0, HDRP_LIGHT_BUDGET - pointLights.length);
3396
- }
3397
- }
3398
- const effectiveLightCount = effectivePointLights.length + effectiveSpotLights.length;
3399
- const configuredClusterGrid = frameState.installedPipelineConfig?.clusterGrid ?? DEFAULT_CLUSTER_GRID;
3400
- let gpuMembership = useGpuMembership && membershipBindGroupLayout !== null;
3401
- if (gpuMembership && membershipBindGroupLayout !== null && frameState.hdrpClusterMembershipBindGroup === null) {
3402
- const hdrpBuffers = getOrCreateHdrpBuffers(internals, configuredClusterGrid);
3403
- if (hdrpBuffers !== null) {
3404
- frameState.hdrpClusterMembershipBindGroup = createHdrpClusterMembershipBindGroup(
3405
- internals,
3406
- hdrpBuffers,
3407
- membershipBindGroupLayout
3408
- );
3409
- }
3410
- gpuMembership = frameState.hdrpClusterMembershipBindGroup !== null;
3411
3450
  }
3412
- const { clusterGrid, gridX, gridY, gridZ, clusterGridBuf, lightIndexListBuf, lightIndexCount } = runHdrpClusterProfilePhase(profilePhase, "record/scene-state/hdrp-cluster/binner", () => {
3413
- const binnerProfile = profilePhase === void 0 ? void 0 : (phase, action) => runHdrpClusterProfilePhase(
3414
- profilePhase,
3415
- `record/scene-state/hdrp-cluster/binner/${phase}`,
3416
- action
3417
- );
3418
- const {
3419
- hdrpLights,
3420
- clusterGrid: clusterGrid2,
3421
- gridX: gridX2,
3422
- gridY: gridY2,
3423
- gridZ: gridZ2,
3424
- clusterGridBuf: clusterGridBuf2,
3425
- lightIndexListBuf: lightIndexListBuf2,
3426
- projMatrix,
3427
- viewMatrix
3428
- } = runHdrpClusterProfilePhase(
3429
- profilePhase,
3430
- "record/scene-state/hdrp-cluster/binner/input-preparation",
3431
- () => {
3432
- const hdrpLights2 = [];
3433
- for (const pl of effectivePointLights) {
3434
- const range = Number.isFinite(pl.invRangeSquared) && pl.invRangeSquared > 0 ? Math.sqrt(1 / pl.invRangeSquared) : 1e3;
3435
- hdrpLights2.push({ position: pl.position, range });
3436
- }
3437
- for (const sl of effectiveSpotLights) {
3438
- const range = Number.isFinite(sl.invRangeSquared) && sl.invRangeSquared > 0 ? Math.sqrt(1 / sl.invRangeSquared) : 1e3;
3439
- hdrpLights2.push({ position: sl.position, range });
3440
- }
3441
- const clusterGrid3 = frameState.installedPipelineConfig?.clusterGrid ?? DEFAULT_CLUSTER_GRID;
3442
- const gridX3 = clusterGrid3.x;
3443
- const gridY3 = clusterGrid3.y;
3444
- const gridZ3 = clusterGrid3.z;
3445
- const clusterCount = gridX3 * gridY3 * gridZ3;
3446
- const projMatrix2 = computeProjectionMatrix(camera);
3447
- const viewMatrix2 = computeViewMatrix(camera);
3448
- const clusterGridBuf3 = frameState.hdrpClusterGridScratch === null || frameState.hdrpClusterGridScratch.length < clusterCount * 2 ? new Uint32Array(clusterCount * 2) : frameState.hdrpClusterGridScratch;
3449
- frameState.hdrpClusterGridScratch = clusterGridBuf3;
3450
- const lightIndexListBuf3 = frameState.hdrpLightIndexListScratch === null || frameState.hdrpLightIndexListScratch.length < LIGHT_INDEX_LIST_CAPACITY ? new Uint32Array(LIGHT_INDEX_LIST_CAPACITY) : frameState.hdrpLightIndexListScratch;
3451
- frameState.hdrpLightIndexListScratch = lightIndexListBuf3;
3452
- return {
3453
- hdrpLights: hdrpLights2,
3454
- clusterGrid: clusterGrid3,
3455
- gridX: gridX3,
3456
- gridY: gridY3,
3457
- gridZ: gridZ3,
3458
- clusterGridBuf: clusterGridBuf3,
3459
- lightIndexListBuf: lightIndexListBuf3,
3460
- projMatrix: projMatrix2,
3461
- viewMatrix: viewMatrix2
3462
- };
3463
- }
3464
- );
3465
- const binResult = runHdrpClusterProfilePhase(
3466
- profilePhase,
3467
- "record/scene-state/hdrp-cluster/binner/bin-core",
3468
- () => bin(
3469
- hdrpLights,
3470
- viewMatrix,
3471
- projMatrix,
3472
- { x: gridX2, y: gridY2, z: gridZ2 },
3473
- camera.near,
3474
- camera.far,
3475
- clusterGridBuf2,
3476
- lightIndexListBuf2,
3477
- LIGHT_INDEX_LIST_CAPACITY,
3478
- frameState.hdrpClusterBinScratch,
3479
- binnerProfile,
3480
- { writeMembership: !gpuMembership }
3451
+ const hdrpBuffers = getOrCreateHdrpBuffers(internals, configuredClusterGrid);
3452
+ if (hdrpBuffers === null) {
3453
+ return err$1(
3454
+ new StandardClusterTransportUnavailableError(
3455
+ prepared.local.length,
3456
+ "the selected Standard Cluster transport could not allocate its persistent buffers"
3481
3457
  )
3482
3458
  );
3483
- if (!binResult.ok && !frameState.hdrpOncePerFrameFired.has("hdrp-index-list-overflow")) {
3484
- frameState.hdrpOncePerFrameFired.add("hdrp-index-list-overflow");
3485
- const detail = binResult.error.detail;
3486
- internals.errorRegistry.fire(
3487
- new HdrpIndexListOverflowError(detail.actual, detail.capacity)
3459
+ }
3460
+ const existingMembership = projectDeviceOwnedBindGroup(
3461
+ frameState.hdrpClusterMembership,
3462
+ internals.device,
3463
+ membershipBindGroupLayout
3464
+ );
3465
+ const membershipMatchesBuffers = existingMembership !== null && frameState.hdrpClusterMembership?.clusterGridBuffer === hdrpBuffers.clusterGridBuffer;
3466
+ if (!membershipMatchesBuffers) frameState.hdrpClusterMembership = null;
3467
+ if (gpuMembership && membershipBindGroupLayout !== null && frameState.hdrpClusterMembership === null) {
3468
+ const bindGroup = createHdrpClusterMembershipBindGroup(
3469
+ internals,
3470
+ hdrpBuffers,
3471
+ membershipBindGroupLayout
3472
+ );
3473
+ if (bindGroup === null) {
3474
+ return err$1(
3475
+ new StandardClusterTransportUnavailableError(
3476
+ prepared.local.length,
3477
+ "the selected GPU Cluster producer could not create its membership bind group"
3478
+ )
3488
3479
  );
3489
3480
  }
3490
- const envFalsify = globalThis.process?.env;
3491
- if (envFalsify?.FORGEAX_HDRP_FALSIFY_CLUSTER_GRID_ZERO) {
3492
- clusterGridBuf2.fill(0);
3493
- }
3494
- return {
3495
- clusterGrid: clusterGrid2,
3496
- gridX: gridX2,
3497
- gridY: gridY2,
3498
- gridZ: gridZ2,
3499
- clusterGridBuf: clusterGridBuf2,
3500
- lightIndexListBuf: lightIndexListBuf2,
3501
- lightIndexCount: binResult.ok ? binResult.value : 0
3481
+ frameState.hdrpClusterMembership = {
3482
+ device: internals.device,
3483
+ layout: membershipBindGroupLayout,
3484
+ bindGroup,
3485
+ clusterGridBuffer: hdrpBuffers.clusterGridBuffer
3502
3486
  };
3503
- });
3487
+ }
3488
+ const gridX = prepared.layout.grid.x;
3489
+ const gridY = prepared.layout.grid.y;
3490
+ const gridZ = prepared.layout.grid.z;
3491
+ const clusterGridBuf = prepared.clusterGrid;
3492
+ const lightIndexListBuf = prepared.lightIndexList;
3493
+ const lightIndexCount = prepared.membershipEntryCount;
3494
+ const envFalsify = globalThis.process?.env;
3495
+ if (envFalsify?.FORGEAX_HDRP_FALSIFY_CLUSTER_GRID_ZERO) clusterGridBuf.fill(0);
3504
3496
  const hdrpPayload = runHdrpClusterProfilePhase(
3505
3497
  profilePhase,
3506
3498
  "record/scene-state/hdrp-cluster/payload-packing",
3507
3499
  () => {
3508
- const hdrpBuffers = getOrCreateHdrpBuffers(internals, clusterGrid);
3509
- if (hdrpBuffers === null) return null;
3510
- const lightCapacity = hdrpBuffers.storageBuffer ? 256 : HDRP_UNIFORM_LIGHT_CAPACITY;
3511
- const lightDataPayload = new Float32Array(lightCapacity * 16);
3500
+ const lightDataPayload = new Float32Array(prepared.layout.lightDataSlotCount * 16);
3512
3501
  let slotIdx = 0;
3513
- for (const pl of effectivePointLights) {
3514
- if (slotIdx >= lightCapacity) break;
3515
- const layer = pl.shadowAtlasLayer;
3516
- const packed = layer !== void 0 && layer >= 0 && pl.shadowNear !== void 0 && pl.shadowFar !== void 0 ? packLightSlot(pl, {
3517
- shadowAtlasLayer: layer,
3518
- near: pl.shadowNear,
3519
- far: pl.shadowFar
3520
- }) : packLightSlot(pl);
3521
- lightDataPayload.set(packed, slotIdx * 16);
3522
- slotIdx += 1;
3523
- }
3524
- for (const sl of effectiveSpotLights) {
3525
- if (slotIdx >= lightCapacity) break;
3526
- const packed = packLightSlot(sl);
3502
+ for (const local of prepared.local) {
3503
+ const source = local.source;
3504
+ if (source === void 0) return null;
3505
+ const projectorSelected = projectorLightSlotIndex !== void 0 && projectorLightSlotIndex === slotIdx;
3506
+ const packed = source.kind === "point" && source.shadowAtlasLayer !== void 0 && source.shadowAtlasLayer >= 0 && source.shadowNear !== void 0 && source.shadowFar !== void 0 ? packLightSlot(
3507
+ source,
3508
+ {
3509
+ shadowAtlasLayer: source.shadowAtlasLayer,
3510
+ near: source.shadowNear,
3511
+ far: source.shadowFar
3512
+ },
3513
+ projectorSelected
3514
+ ) : packLightSlot(source, void 0, projectorSelected);
3527
3515
  lightDataPayload.set(packed, slotIdx * 16);
3528
3516
  slotIdx += 1;
3529
3517
  }
@@ -3534,8 +3522,7 @@ function writeHdrpClusterAndSsaoBuffers(internals, frameState, camera, pointLigh
3534
3522
  camera.near,
3535
3523
  camera.far,
3536
3524
  clusterSsaoIntensity,
3537
- Math.min(effectivePointLights.length + effectiveSpotLights.length, lightCapacity),
3538
- lightCapacity
3525
+ transport.admittedLightCount
3539
3526
  );
3540
3527
  return {
3541
3528
  hdrpBuffers,
@@ -3544,73 +3531,97 @@ function writeHdrpClusterAndSsaoBuffers(internals, frameState, camera, pointLigh
3544
3531
  };
3545
3532
  }
3546
3533
  );
3547
- runHdrpClusterProfilePhase(profilePhase, "record/scene-state/hdrp-cluster/buffer-upload", () => {
3548
- if (hdrpPayload !== null) {
3549
- const { hdrpBuffers, lightDataUploadPayload, clusterUniformPayload } = hdrpPayload;
3534
+ if (hdrpPayload === null) {
3535
+ return err$1(
3536
+ new StandardClusterTransportUnavailableError(
3537
+ prepared.local.length,
3538
+ "the prepared Standard Cluster payload could not be materialized"
3539
+ )
3540
+ );
3541
+ }
3542
+ const bufferUpload = runHdrpClusterProfilePhase(
3543
+ profilePhase,
3544
+ "record/scene-state/hdrp-cluster/buffer-upload",
3545
+ () => {
3546
+ const { hdrpBuffers: hdrpBuffers2, lightDataUploadPayload, clusterUniformPayload } = hdrpPayload;
3550
3547
  const lightDataUpload = internals.device.queue.writeBuffer(
3551
- hdrpBuffers.lightDataBuffer,
3548
+ hdrpBuffers2.lightDataBuffer,
3552
3549
  0,
3553
3550
  lightDataUploadPayload
3554
3551
  );
3555
- if (!lightDataUpload.ok) internals.errorRegistry.fire(lightDataUpload.error);
3556
- if (hdrpBuffers.storageBuffer) {
3557
- if (gpuMembership) {
3558
- const lightBoundsUpload = internals.device.queue.writeBuffer(
3559
- hdrpBuffers.lightBoundsBuffer,
3560
- 0,
3561
- frameState.hdrpClusterBinScratch.lightBounds.subarray(0, effectiveLightCount * 6)
3562
- );
3563
- if (!lightBoundsUpload.ok) internals.errorRegistry.fire(lightBoundsUpload.error);
3564
- }
3565
- const clusterGridUpload = internals.device.queue.writeBuffer(
3566
- hdrpBuffers.clusterGridBuffer,
3552
+ if (!lightDataUpload.ok)
3553
+ return err$1(mapClusterUploadFailure("light-data", lightDataUpload.error));
3554
+ if (gpuMembership) {
3555
+ const lightBoundsUpload = internals.device.queue.writeBuffer(
3556
+ hdrpBuffers2.lightBoundsBuffer,
3567
3557
  0,
3568
- clusterGridBuf.subarray(0, gridX * gridY * gridZ * 2)
3558
+ prepared.lightBounds
3569
3559
  );
3570
- if (!clusterGridUpload.ok) internals.errorRegistry.fire(clusterGridUpload.error);
3571
- if (!gpuMembership && lightIndexCount > 0) {
3572
- const lightIndexListUpload = internals.device.queue.writeBuffer(
3573
- hdrpBuffers.lightIndexListBuffer,
3574
- 0,
3575
- lightIndexListBuf.subarray(0, lightIndexCount)
3576
- );
3577
- if (!lightIndexListUpload.ok) internals.errorRegistry.fire(lightIndexListUpload.error);
3560
+ if (!lightBoundsUpload.ok)
3561
+ return err$1(mapClusterUploadFailure("light-bounds", lightBoundsUpload.error));
3562
+ }
3563
+ const clusterGridUpload = internals.device.queue.writeBuffer(
3564
+ hdrpBuffers2.clusterGridBuffer,
3565
+ 0,
3566
+ clusterGridBuf.subarray(0, gridX * gridY * gridZ * 2)
3567
+ );
3568
+ if (!clusterGridUpload.ok)
3569
+ return err$1(mapClusterUploadFailure("cluster-grid", clusterGridUpload.error));
3570
+ if (!gpuMembership && lightIndexCount > 0) {
3571
+ const lightIndexListUpload = internals.device.queue.writeBuffer(
3572
+ hdrpBuffers2.lightIndexListBuffer,
3573
+ 0,
3574
+ lightIndexListBuf.subarray(0, lightIndexCount)
3575
+ );
3576
+ if (!lightIndexListUpload.ok) {
3577
+ return err$1(mapClusterUploadFailure("light-index-list", lightIndexListUpload.error));
3578
3578
  }
3579
3579
  }
3580
3580
  const clusterUniformUpload = internals.device.queue.writeBuffer(
3581
- hdrpBuffers.clusterUniformBuffer,
3581
+ hdrpBuffers2.clusterUniformBuffer,
3582
3582
  0,
3583
3583
  clusterUniformPayload
3584
3584
  );
3585
- if (!clusterUniformUpload.ok) internals.errorRegistry.fire(clusterUniformUpload.error);
3585
+ if (!clusterUniformUpload.ok) {
3586
+ return err$1(mapClusterUploadFailure("cluster-uniform", clusterUniformUpload.error));
3587
+ }
3588
+ return ok(void 0);
3586
3589
  }
3587
- {
3590
+ );
3591
+ if (!bufferUpload.ok) return bufferUpload;
3592
+ const ssaoUpload = runHdrpClusterProfilePhase(
3593
+ profilePhase,
3594
+ "record/scene-state/hdrp-cluster/buffer-upload",
3595
+ () => {
3588
3596
  const ssaoBufs = getOrCreateSsaoBuffers(internals);
3589
- if (ssaoBufs !== null) {
3590
- const sProj = computeProjectionMatrix(camera);
3591
- const sView = computeViewMatrix(camera);
3592
- const invProjOnly = mat4.create();
3593
- mat4.invert(invProjOnly, sProj);
3594
- const ssaoUniformPayload = new Float32Array(64);
3595
- ssaoUniformPayload.set(sView, 0);
3596
- ssaoUniformPayload.set(sProj, 16);
3597
- ssaoUniformPayload.set(invProjOnly, 32);
3598
- const ssaoConfig = frameState.installedPipelineConfig?.ssao;
3599
- const parameters = getSsaoParameters(
3600
- ssaoConfig !== void 0 && ssaoConfig.enabled === true ? ssaoConfig : void 0
3601
- );
3602
- ssaoUniformPayload[48] = parameters.intensity;
3603
- ssaoUniformPayload[49] = parameters.radius;
3604
- ssaoUniformPayload[50] = parameters.bias;
3605
- const ssaoUniformRes = internals.device.queue.writeBuffer(
3606
- ssaoBufs.uniformBuffer,
3607
- 0,
3608
- ssaoUniformPayload
3609
- );
3610
- if (!ssaoUniformRes.ok) internals.errorRegistry.fire(ssaoUniformRes.error);
3611
- }
3597
+ if (ssaoBufs === null) return ok(void 0);
3598
+ const sProj = computeProjectionMatrix(camera);
3599
+ const sView = computeViewMatrix(camera);
3600
+ const invProjOnly = mat4.create();
3601
+ mat4.invert(invProjOnly, sProj);
3602
+ const ssaoUniformPayload = new Float32Array(64);
3603
+ ssaoUniformPayload.set(sView, 0);
3604
+ ssaoUniformPayload.set(sProj, 16);
3605
+ ssaoUniformPayload.set(invProjOnly, 32);
3606
+ const ssaoConfig = frameState.installedPipelineConfig?.ssao;
3607
+ const parameters = getSsaoParameters(
3608
+ ssaoConfig !== void 0 && ssaoConfig.enabled === true ? ssaoConfig : void 0
3609
+ );
3610
+ ssaoUniformPayload[48] = parameters.intensity;
3611
+ ssaoUniformPayload[49] = parameters.radius;
3612
+ ssaoUniformPayload[50] = parameters.bias;
3613
+ const ssaoUniformRes = internals.device.queue.writeBuffer(
3614
+ ssaoBufs.uniformBuffer,
3615
+ 0,
3616
+ ssaoUniformPayload
3617
+ );
3618
+ if (!ssaoUniformRes.ok)
3619
+ return err$1(mapClusterUploadFailure("ssao-uniform", ssaoUniformRes.error));
3620
+ return ok(void 0);
3612
3621
  }
3613
- });
3622
+ );
3623
+ if (!ssaoUpload.ok) return ssaoUpload;
3624
+ return ok(void 0);
3614
3625
  }
3615
3626
  function resolveSkyboxActive(internals, frameState, camera, skybox) {
3616
3627
  const tonemapActive = camera.tonemap !== "none";
@@ -3636,6 +3647,7 @@ function getOrCreateIblCache(scope) {
3636
3647
  const existing = scopeCaches.get(scope);
3637
3648
  if (existing !== void 0) return existing;
3638
3649
  const cache3 = {
3650
+ probeKernel: createIblKernelCache(scope.generation),
3639
3651
  irradianceBakeCount: 0,
3640
3652
  prefilterBakeCount: 0,
3641
3653
  brdfLutBakeCount: 0
@@ -4780,6 +4792,51 @@ function deriveRenderDataCubemap(source) {
4780
4792
  cubeUsage: GPU_TEXTURE_USAGE_RENDER_ATTACHMENT_AND_TEXTURE_BINDING | GPU_TEXTURE_USAGE_COPY_DST | GPU_TEXTURE_USAGE_COPY_SRC
4781
4793
  });
4782
4794
  }
4795
+
4796
+ // src/targets/material-source.ts
4797
+ var sourceBindings = /* @__PURE__ */ new WeakMap();
4798
+ function sourceToken() {
4799
+ return Object.freeze({});
4800
+ }
4801
+ function resolveRenderTargetMaterialSource(source) {
4802
+ return sourceBindings.get(source);
4803
+ }
4804
+ function invalid(field, value, expected) {
4805
+ return {
4806
+ ok: false,
4807
+ error: new RenderTargetDescriptorInvalidError({ field, value, expected })
4808
+ };
4809
+ }
4810
+ function createRenderTargetMaterialSource(target, descriptor, options) {
4811
+ if (options.aspect !== "color") return invalid("aspect", options.aspect, "color");
4812
+ if (options.dimension !== descriptor.shape) {
4813
+ return invalid("dimension", options.dimension, `dimension matches ${descriptor.shape}`);
4814
+ }
4815
+ const mipCount = descriptor.mipLevels === 1 ? 1 : Math.floor(Math.log2(Math.max(descriptor.width, descriptor.height))) + 1;
4816
+ if (!Number.isInteger(options.mipLevel) || options.mipLevel < 0 || options.mipLevel >= mipCount) {
4817
+ return invalid("mipLevel", options.mipLevel, `an admitted mip level below ${mipCount}`);
4818
+ }
4819
+ if (!Number.isInteger(options.generation) || options.generation < 0) {
4820
+ return invalid("generation", options.generation, "a non-negative device generation");
4821
+ }
4822
+ const result = {
4823
+ ok: true,
4824
+ value: Object.freeze({
4825
+ source: sourceToken(),
4826
+ target,
4827
+ generation: options.generation,
4828
+ shape: descriptor.shape,
4829
+ format: descriptor.format,
4830
+ view: Object.freeze({
4831
+ dimension: options.dimension,
4832
+ mipLevel: options.mipLevel,
4833
+ resolveRequired: descriptor.sampleCount === 4
4834
+ })
4835
+ })
4836
+ };
4837
+ sourceBindings.set(result.value.source, result.value);
4838
+ return result;
4839
+ }
4783
4840
  var STANDARD_PBR_UBO_SIZE = 384;
4784
4841
  var MATERIAL_PER_ENTITY_STRIDE = 512;
4785
4842
  function resolveSurfaceProfile(storageFormat, viewFormat, facts) {
@@ -4892,6 +4949,11 @@ function selectSwapChainFormat(storageBufferCapable, surfaceViewFormats = true)
4892
4949
  }
4893
4950
 
4894
4951
  // src/record/main-pass-material.ts
4952
+ function selectMaterialGroup2(clusterGroup, meshGroup, group2Contract) {
4953
+ if (group2Contract === "cluster") return clusterGroup;
4954
+ if (group2Contract === "mesh") return meshGroup;
4955
+ return null;
4956
+ }
4895
4957
  var DERIVED_PARAM_SCHEMA_CACHE = /* @__PURE__ */ new WeakMap();
4896
4958
  function derivedParamSchema(schema) {
4897
4959
  const cached = DERIVED_PARAM_SCHEMA_CACHE.get(schema);
@@ -5119,6 +5181,10 @@ function materialTextureForField(material, field) {
5119
5181
  if (field === "occlusionTexture") return material.occlusionTexture;
5120
5182
  return material.textureHandles?.get(field);
5121
5183
  }
5184
+ function materialRenderTargetSourceForField(material, field, resolveSource) {
5185
+ const source = material.textureSources?.get(field);
5186
+ return source === void 0 ? void 0 : resolveSource?.(source) ?? resolveRenderTargetMaterialSource(source);
5187
+ }
5122
5188
  function materialUboFloatView(payload) {
5123
5189
  if (payload instanceof Float32Array) return payload;
5124
5190
  return payload instanceof Uint8Array ? new Float32Array(payload.buffer, payload.byteOffset, payload.byteLength / 4) : new Float32Array(payload);
@@ -5210,7 +5276,11 @@ function userRegionTextureFieldOrder(schema) {
5210
5276
  const fields = [...derivedParamSchema(schema).textureFieldNames];
5211
5277
  return fields;
5212
5278
  }
5213
- function defaultViewForUserRegionField(field, pipelineState) {
5279
+ function defaultViewForUserRegionField(field, pipelineState, schema) {
5280
+ const parameter = schema?.find((entry) => entry.name === field);
5281
+ if (parameter?.type === "texture_cube") {
5282
+ return pipelineState.skylightFallback?.prefilterView ?? pipelineState.defaultWhiteTextureView;
5283
+ }
5214
5284
  if (field === "normalTexture") return pipelineState.defaultNormalTextureView;
5215
5285
  if (field === "baseColorTexture") return pipelineState.fallbackTextureView;
5216
5286
  return pipelineState.defaultWhiteTextureView;
@@ -5320,11 +5390,14 @@ function buildPerSubmeshMaterialBg(deps, submeshMaterial, entityKey, materialWor
5320
5390
  materialSlice,
5321
5391
  videoHighPerfAvailable,
5322
5392
  skylightResources,
5393
+ resolveRenderTargetTextureSource,
5394
+ resolveReflectionProbeResources,
5323
5395
  materialBgShared,
5324
5396
  materialBgAssemblyCache,
5325
5397
  transmissionBackdropView,
5326
5398
  bindGroupCounts
5327
5399
  } = deps;
5400
+ const effectiveSkylightResources = resolveReflectionProbeResources?.(materialWorld, entityKey) ?? skylightResources;
5328
5401
  const smMaterialHandle = submeshMaterial.materialHandle;
5329
5402
  const materialCacheKey = smMaterialHandle === void 0 ? void 0 : `${materialWorld.identity}:${smMaterialHandle}`;
5330
5403
  const smHasVideoFields = (submeshMaterial.videoTextureFields?.size ?? 0) > 0;
@@ -5339,7 +5412,7 @@ function buildPerSubmeshMaterialBg(deps, submeshMaterial, entityKey, materialWor
5339
5412
  submeshMaterial,
5340
5413
  smMaterialBgl,
5341
5414
  pipelineState.materialUniformBuffer.buffer,
5342
- skylightResources,
5415
+ effectiveSkylightResources,
5343
5416
  store.materialResourceEpoch,
5344
5417
  transmissionBackdropView
5345
5418
  )) {
@@ -5374,9 +5447,17 @@ function buildPerSubmeshMaterialBg(deps, submeshMaterial, entityKey, materialWor
5374
5447
  const field = smUserRegionFields[fi];
5375
5448
  const samplerBinding = 1 + fi * 2;
5376
5449
  const textureBinding = samplerBinding + 1;
5377
- let smView = field !== void 0 ? defaultViewForUserRegionField(field, pipelineState) : pipelineState.defaultWhiteTextureView;
5450
+ let smView = field !== void 0 ? defaultViewForUserRegionField(field, pipelineState, smSchema) : pipelineState.defaultWhiteTextureView;
5378
5451
  const smVideoClip = field !== void 0 ? submeshMaterial.videoTextureFields?.get(field) : void 0;
5379
- if (smVideoClip !== void 0) {
5452
+ const smTargetSource = field === void 0 ? void 0 : materialRenderTargetSourceForField(
5453
+ submeshMaterial,
5454
+ field,
5455
+ resolveRenderTargetTextureSource
5456
+ );
5457
+ if (smTargetSource !== void 0) {
5458
+ if (smTargetSource.textureView !== void 0) smView = smTargetSource.textureView;
5459
+ else materialResourcesResident = false;
5460
+ } else if (smVideoClip !== void 0) {
5380
5461
  const view = videoTextureView(
5381
5462
  materialWorld,
5382
5463
  runtime.dynamicTextureStore,
@@ -5405,11 +5486,15 @@ function buildPerSubmeshMaterialBg(deps, submeshMaterial, entityKey, materialWor
5405
5486
  }
5406
5487
  );
5407
5488
  }
5408
- const transmissionSampler = transmissionBackdropView != null ? skylightResources.prefilterSampler : pipelineState.defaultSampler;
5409
- const smMergedEntries = assembleMaterialWithSkylightEntries(smBaseEntries, skylightResources, {
5410
- sampler: transmissionSampler,
5411
- ...transmissionBackdropView === void 0 ? {} : { backdropView: transmissionBackdropView }
5412
- });
5489
+ const transmissionSampler = transmissionBackdropView != null ? effectiveSkylightResources.prefilterSampler : pipelineState.defaultSampler;
5490
+ const smMergedEntries = assembleMaterialWithSkylightEntries(
5491
+ smBaseEntries,
5492
+ effectiveSkylightResources,
5493
+ {
5494
+ sampler: transmissionSampler,
5495
+ ...transmissionBackdropView === void 0 ? {} : { backdropView: transmissionBackdropView }
5496
+ }
5497
+ );
5413
5498
  const createBindGroup = () => {
5414
5499
  const result = runtime.device.createBindGroup({
5415
5500
  label: "pbr-material-skylight-bg",
@@ -5434,7 +5519,7 @@ function buildPerSubmeshMaterialBg(deps, submeshMaterial, entityKey, materialWor
5434
5519
  materialResourceEpoch: store.materialResourceEpoch,
5435
5520
  materialBgl: smMaterialBgl,
5436
5521
  materialBuffer: pipelineState.materialUniformBuffer.buffer,
5437
- skylightResources,
5522
+ skylightResources: effectiveSkylightResources,
5438
5523
  bindGroup: smBindGroup
5439
5524
  });
5440
5525
  }
@@ -5495,9 +5580,11 @@ function recordGeometryDraws(c, pass, matchedMaterials, materialSlotIndices, sam
5495
5580
  msaaActive,
5496
5581
  validatedOrdered,
5497
5582
  splitLdrSprite,
5498
- viewBindGroup
5583
+ viewBindGroup,
5584
+ viewBindGroupDynamicOffset = 0
5499
5585
  } = c;
5500
5586
  const colorFormatOverride = c.transparentColorFormat;
5587
+ const clusteredLighting = c.standardLighting?.kind === "clustered";
5501
5588
  const isHdrTarget = tonemapActive || colorFormatOverride === "rgba16float";
5502
5589
  let lastVertexBuffer = null;
5503
5590
  let lastIndexBuffer = null;
@@ -5516,7 +5603,6 @@ function recordGeometryDraws(c, pass, matchedMaterials, materialSlotIndices, sam
5516
5603
  const profilePhase = c.profilePhase;
5517
5604
  const materialGroup1DynamicOffsets = new Uint32Array(1);
5518
5605
  const meshGroup2DynamicOffsets = [0];
5519
- const skinGroup2DynamicOffsets = [0, 0];
5520
5606
  let lastStencilReference = null;
5521
5607
  const materialPipelineLookupCache = /* @__PURE__ */ new Map();
5522
5608
  const resolveMaterialPipeline = (materialShaderId, renderState, topology, indexFormat, variantSet, colorFormat, layoutProjection) => {
@@ -5536,7 +5622,7 @@ function recordGeometryDraws(c, pass, matchedMaterials, materialSlotIndices, sam
5536
5622
  return null;
5537
5623
  }
5538
5624
  const resolvedVariantSet = resolvedVariantSetResult.value;
5539
- const resolvePipeline = () => runtime.getMaterialShaderPipeline?.(
5625
+ const resolvePipeline = () => runtime.getMaterialShaderPipelineEntry?.(
5540
5626
  materialShaderId,
5541
5627
  isHdrTarget,
5542
5628
  renderState,
@@ -5583,7 +5669,7 @@ function recordGeometryDraws(c, pass, matchedMaterials, materialSlotIndices, sam
5583
5669
  for (let i = 0; i < validatedOrdered.length; i++) {
5584
5670
  const entry = validatedOrdered[i];
5585
5671
  if (entry === void 0) continue;
5586
- const pointsLinesSubmission = c.pointsLines?.prepare(entry, frameState.isHdrpActive);
5672
+ const pointsLinesSubmission = c.pointsLines?.prepare(entry, clusteredLighting);
5587
5673
  if (entry.source.pointsLines !== void 0 && pointsLinesSubmission?.plan.drawCount !== 1)
5588
5674
  continue;
5589
5675
  if (passKind === "forward" && c.gpuDrivenEntityKeys.has(worldEntityKey(entry.source.worldId, entry.source.entityKey))) {
@@ -5609,7 +5695,7 @@ function recordGeometryDraws(c, pass, matchedMaterials, materialSlotIndices, sam
5609
5695
  entry.source.entityKey,
5610
5696
  entry.world ?? c.world
5611
5697
  );
5612
- const pointsLinesPipeline = resolveMaterialPipeline(
5698
+ const pointsLinesPipelineEntry = resolveMaterialPipeline(
5613
5699
  POINTS_LINES_MATERIAL_SHADER_ID,
5614
5700
  {
5615
5701
  ...pointsLinesMaterial.renderState,
@@ -5621,7 +5707,8 @@ function recordGeometryDraws(c, pass, matchedMaterials, materialSlotIndices, sam
5621
5707
  colorFormatOverride,
5622
5708
  pointsLinesSubmission.layoutProjection
5623
5709
  );
5624
- if (pointsLinesPipeline === null) continue;
5710
+ if (pointsLinesPipelineEntry === null) continue;
5711
+ const pointsLinesPipeline = pointsLinesPipelineEntry.pipeline;
5625
5712
  if (pipelineState.pointsLinesViewBuffer === void 0) continue;
5626
5713
  writePointsLinesViewUbo(
5627
5714
  runtime.device.queue,
@@ -5636,9 +5723,9 @@ function recordGeometryDraws(c, pass, matchedMaterials, materialSlotIndices, sam
5636
5723
  pass.setBindGroup(
5637
5724
  0,
5638
5725
  viewBindGroup,
5639
- new Uint32Array([i * POINTS_LINES_VIEW_SLOT_STRIDE]),
5726
+ new Uint32Array([0, i * POINTS_LINES_VIEW_SLOT_STRIDE]),
5640
5727
  0,
5641
- 1
5728
+ 2
5642
5729
  );
5643
5730
  pass.setPipeline(pointsLinesPipeline);
5644
5731
  pass.setVertexBuffer(0, pointsLinesSubmission.vertexBuffer);
@@ -5653,7 +5740,7 @@ function recordGeometryDraws(c, pass, matchedMaterials, materialSlotIndices, sam
5653
5740
  identityInstanceDraws[0]?.instanceBindGroup ?? resolveGeometryInstancesBindGroup(c, identityInstanceBuffer)
5654
5741
  );
5655
5742
  recordPointsLinesDraw(pass, pointsLinesSubmission.plan);
5656
- pass.setBindGroup(0, viewBindGroup, [0]);
5743
+ pass.setBindGroup(0, viewBindGroup, [viewBindGroupDynamicOffset, 0]);
5657
5744
  continue;
5658
5745
  }
5659
5746
  if (entry.mesh.vertexBuffer !== lastVertexBuffer) {
@@ -5675,14 +5762,22 @@ function recordGeometryDraws(c, pass, matchedMaterials, materialSlotIndices, sam
5675
5762
  const isSkinEntry = entry.source.skin !== void 0;
5676
5763
  const skinAllocator = pipelineState.skinPaletteAllocator;
5677
5764
  const skinSlice = entry.source.skin;
5678
- const skinResources = isSkinEntry && pipelineState.pbrSkinMeshBindGroupLayout !== null && skinAllocator !== null && skinSlice !== void 0 ? {
5679
- meshArrayBgl: pipelineState.pbrSkinMeshBindGroupLayout,
5765
+ const clusteredSkin = clusteredLighting;
5766
+ const skinMeshBindGroupLayout = clusteredSkin ? pipelineState.hdrpSkinMeshBindGroupLayout : pipelineState.pbrSkinMeshBindGroupLayout;
5767
+ const hdrpSkinBuffers = isSkinEntry && clusteredSkin ? getOrCreateHdrpBuffers(runtime, frameState.installedPipelineConfig?.clusterGrid) : null;
5768
+ const skinResources = isSkinEntry && skinMeshBindGroupLayout !== null && skinAllocator !== null && skinSlice !== void 0 && (!clusteredSkin || hdrpSkinBuffers !== null) ? {
5769
+ meshArrayBgl: skinMeshBindGroupLayout,
5680
5770
  paletteBuffer: skinSlice.buffer,
5681
- paletteBindingWindowBytes: skinAllocator.bindingWindowBytes
5771
+ paletteBindingWindowBytes: skinAllocator.bindingWindowBytes,
5772
+ hdrpBuffers: hdrpSkinBuffers
5682
5773
  } : null;
5683
5774
  const skinVariantSetResult = variantSetFromVertexLayoutProjection(
5684
5775
  entry.mesh.layoutProjection,
5685
- frameState.isHdrpActive ? "" : "CLUSTER_FORWARD_AVAILABLE=false+STORAGE_BUFFER_AVAILABLE=true"
5776
+ standardTopologyVariantSet(
5777
+ c.standardLighting,
5778
+ runtime.device.caps.storageBuffer,
5779
+ entry.mesh.layoutProjection.attributes.some((attribute) => attribute.key === "color")
5780
+ )
5686
5781
  );
5687
5782
  if (!skinVariantSetResult.ok) runtime.errorRegistry.fire(skinVariantSetResult.error);
5688
5783
  const skinVariantSet = skinVariantSetResult.ok ? skinVariantSetResult.value : void 0;
@@ -5709,45 +5804,45 @@ function recordGeometryDraws(c, pass, matchedMaterials, materialSlotIndices, sam
5709
5804
  const skinMissesBefore = bindGroupCounts.createBindGroup;
5710
5805
  const skinBindGroup = getOrCreateFromChain(
5711
5806
  frameState.meshBindGroupCache,
5712
- [pipelineState.meshStorageBuffer.buffer, skinResources.paletteBuffer],
5807
+ [
5808
+ pipelineState.meshStorageBuffer.buffer,
5809
+ skinResources.paletteBuffer,
5810
+ ...skinResources.hdrpBuffers === null ? [] : [
5811
+ skinResources.hdrpBuffers.lightDataBuffer,
5812
+ skinResources.hdrpBuffers.clusterGridBuffer,
5813
+ skinResources.hdrpBuffers.lightIndexListBuffer,
5814
+ skinResources.hdrpBuffers.clusterUniformBuffer
5815
+ ]
5816
+ ],
5713
5817
  "pbr-skin-mesh",
5714
5818
  () => {
5819
+ if (skinResources.hdrpBuffers !== null) {
5820
+ const result2 = createHdrpSkinUnifiedBindGroup(
5821
+ runtime,
5822
+ skinResources.hdrpBuffers,
5823
+ skinResources.meshArrayBgl,
5824
+ pipelineState.meshStorageBuffer.buffer,
5825
+ skinResources.paletteBuffer,
5826
+ skinResources.paletteBindingWindowBytes
5827
+ );
5828
+ if (result2 === null) {
5829
+ throw new RhiError({
5830
+ code: "webgpu-runtime-error",
5831
+ expected: "HDRP clustered skin BindGroup creation succeeds",
5832
+ hint: "inspect prior errorRegistry events for createBindGroup failure detail"
5833
+ });
5834
+ }
5835
+ return result2;
5836
+ }
5715
5837
  const result = runtime.device.createBindGroup({
5716
5838
  label: "pbr-skin-mesh-bg",
5717
5839
  layout: skinResources.meshArrayBgl,
5718
- entries: [
5719
- {
5720
- binding: 0,
5721
- resource: {
5722
- kind: "buffer",
5723
- value: {
5724
- buffer: pipelineState.meshStorageBuffer.buffer,
5725
- offset: 0,
5726
- size: meshBindSize
5727
- }
5728
- }
5729
- },
5730
- {
5731
- binding: 1,
5732
- resource: {
5733
- kind: "buffer",
5734
- value: {
5735
- buffer: skinResources.paletteBuffer,
5736
- offset: 0,
5737
- // M6 SSOT: static BG entry size (= MAX_JOINTS * 64 =
5738
- // 16320 B) sourced from the allocator. The per-draw
5739
- // window slides via `group2DynamicOffsets[1]`
5740
- // (= entry.source.skin.byteOffset) so this size
5741
- // stays at the worst case across all skin entries
5742
- // -- one BG covers every skinned draw in the frame
5743
- // (m3-1b miss=1 + hit=N-1 contract). The allocator
5744
- // guarantees `buffer.size >= byteOffset + this size`
5745
- // so dynOffset[1] passes WebGPU validation.
5746
- size: skinResources.paletteBindingWindowBytes
5747
- }
5748
- }
5749
- }
5750
- ]
5840
+ entries: createPbrSkinMeshBindGroupEntries(
5841
+ pipelineState.meshStorageBuffer.buffer,
5842
+ meshBindSize,
5843
+ skinResources.paletteBuffer,
5844
+ skinResources.paletteBindingWindowBytes
5845
+ )
5751
5846
  });
5752
5847
  if (!result.ok) throw result.error;
5753
5848
  return result.value;
@@ -5759,9 +5854,10 @@ function recordGeometryDraws(c, pass, matchedMaterials, materialSlotIndices, sam
5759
5854
  else skinStats.hit += 1;
5760
5855
  }
5761
5856
  group2BindGroup = skinBindGroup;
5762
- skinGroup2DynamicOffsets[0] = i * MESH_PER_ENTITY_STRIDE;
5763
- skinGroup2DynamicOffsets[1] = entry.source.skin?.byteOffset ?? 0;
5764
- group2DynamicOffsets = skinGroup2DynamicOffsets;
5857
+ group2DynamicOffsets = pbrSkinMeshDynamicOffsets(
5858
+ i * MESH_PER_ENTITY_STRIDE,
5859
+ entry.source.skin?.byteOffset ?? 0
5860
+ );
5765
5861
  } else if (isSkinEntry) {
5766
5862
  continue;
5767
5863
  }
@@ -5811,6 +5907,8 @@ function recordGeometryDraws(c, pass, matchedMaterials, materialSlotIndices, sam
5811
5907
  } : geometryRenderStateForTopology(smTopology, submeshMaterial.renderState);
5812
5908
  const pipelineRenderState = geometryRenderStateForPass(basePipelineRenderState, passKind);
5813
5909
  let smPipelineHandle;
5910
+ let materialGroup2Contract;
5911
+ let materialPipelineEntry = null;
5814
5912
  if (smMaterialShaderId === void 0 || smMaterialShaderId === "forgeax::default-unlit") {
5815
5913
  const unlitShaderId = smMaterialShaderId ?? "forgeax::default-unlit";
5816
5914
  const unlitProjectionVariantResult = variantSetFromVertexLayoutProjection(
@@ -5822,7 +5920,10 @@ function recordGeometryDraws(c, pass, matchedMaterials, materialSlotIndices, sam
5822
5920
  continue;
5823
5921
  }
5824
5922
  const unlitHasColor = unlitProjectionVariantResult.value === "VERTEX_COLOR_AVAILABLE=true";
5825
- const unlitVariantSet = unlitHasColor ? "" : "STORAGE_BUFFER_AVAILABLE=true+VERTEX_COLOR_AVAILABLE=false";
5923
+ const unlitVariantSet = standardStorageVariantSet(
5924
+ runtime.device.caps.storageBuffer,
5925
+ unlitHasColor
5926
+ );
5826
5927
  const unlitRsp = resolveMaterialPipeline(
5827
5928
  unlitShaderId,
5828
5929
  pipelineRenderState,
@@ -5832,12 +5933,17 @@ function recordGeometryDraws(c, pass, matchedMaterials, materialSlotIndices, sam
5832
5933
  colorFormatOverride,
5833
5934
  entry.mesh.layoutProjection
5834
5935
  );
5835
- smPipelineHandle = unlitRsp ?? (colorFormatOverride === void 0 ? selectGeometryPipeline(pipelineState, isHdrTarget, msaaActive) : null);
5936
+ materialPipelineEntry = unlitRsp;
5937
+ smPipelineHandle = unlitRsp?.pipeline ?? (colorFormatOverride === void 0 ? selectGeometryPipeline(pipelineState, isHdrTarget, msaaActive) : null);
5836
5938
  } else if (smMaterialShaderId !== void 0) {
5837
5939
  const hasVertexColor = entry.mesh.layoutProjection.attributes.some(
5838
5940
  (attribute) => attribute.key === "color"
5839
5941
  );
5840
- const capabilityVariantSet = frameState.isHdrpActive ? hasVertexColor ? "" : "CLUSTER_FORWARD_AVAILABLE=true+STORAGE_BUFFER_AVAILABLE=true" : "CLUSTER_FORWARD_AVAILABLE=false+STORAGE_BUFFER_AVAILABLE=true";
5942
+ const capabilityVariantSet = standardTopologyVariantSet(
5943
+ c.standardLighting,
5944
+ runtime.device.caps.storageBuffer,
5945
+ hasVertexColor
5946
+ );
5841
5947
  const variantSet = isSpriteShader ? smMaterialShaderId === "forgeax::sprite" && entry.source.spriteInstances !== void 0 ? "" : void 0 : entry.variantSet === void 0 ? capabilityVariantSet : capabilityVariantSet === "" ? entry.variantSet : `${capabilityVariantSet}+${entry.variantSet}`;
5842
5948
  const projectionVariantSetResult = isSpriteShader ? { ok: true, value: variantSet } : variantSetFromVertexLayoutProjection(entry.mesh.layoutProjection, variantSet);
5843
5949
  if (!projectionVariantSetResult.ok) {
@@ -5853,23 +5959,24 @@ function recordGeometryDraws(c, pass, matchedMaterials, materialSlotIndices, sam
5853
5959
  colorFormatOverride,
5854
5960
  entry.mesh.layoutProjection
5855
5961
  );
5856
- smPipelineHandle = cachedPipeline ?? null;
5962
+ materialPipelineEntry = cachedPipeline;
5963
+ smPipelineHandle = cachedPipeline?.pipeline ?? null;
5857
5964
  } else {
5858
5965
  smPipelineHandle = selectGeometryPipeline(pipelineState, isHdrTarget, msaaActive);
5859
5966
  }
5860
5967
  if (smPipelineHandle === null) {
5861
5968
  continue;
5862
5969
  }
5863
- const usesStandardClusterBindings = smMaterialShaderId === "forgeax::default-standard-pbr";
5864
5970
  if (!isSkinEntry) {
5865
- if (usesStandardClusterBindings) {
5866
- if (meshGroup2 === null) continue;
5867
- group2BindGroup = meshGroup2;
5868
- } else {
5869
- const fallbackGroup = meshBindGroup ?? meshGroup2;
5870
- if (fallbackGroup === null) continue;
5871
- group2BindGroup = fallbackGroup;
5872
- }
5971
+ materialGroup2Contract = materialPipelineEntry?.group2Contract;
5972
+ if (materialGroup2Contract === void 0) continue;
5973
+ const selectedGroup = selectMaterialGroup2(
5974
+ meshGroup2,
5975
+ meshBindGroup,
5976
+ materialGroup2Contract
5977
+ );
5978
+ if (selectedGroup === null) continue;
5979
+ group2BindGroup = selectedGroup;
5873
5980
  }
5874
5981
  pass.setBindGroup(2, group2BindGroup, group2DynamicOffsets);
5875
5982
  if (profilePhase === void 0) {
@@ -5907,6 +6014,7 @@ function resolveGeometryInstanceBuffer(c, entry, identityInstanceDraws) {
5907
6014
  const { runtime, pipelineState, frameState } = c;
5908
6015
  const inst = entry.source.instances;
5909
6016
  if (inst === void 0) return identityInstanceDraws;
6017
+ if (inst.instanceCount === 0) return [];
5910
6018
  let instanceBuffer = pipelineState.identityInstanceBuffer;
5911
6019
  let instanceCount = 1;
5912
6020
  {
@@ -6126,8 +6234,13 @@ function recordSpritePass(c, pass, matchedIndices, materialSlotIndices, sampleCo
6126
6234
  ldrSpriteColorView,
6127
6235
  geometryDepthView,
6128
6236
  viewBindGroup,
6237
+ meshBindGroup,
6238
+ hdrpClusterBindGroup,
6239
+ viewBindGroupDynamicOffset = 0,
6129
6240
  splitLdrSprite
6130
6241
  } = c;
6242
+ const clusteredLighting = c.standardLighting?.kind === "clustered";
6243
+ const meshGroup2 = clusteredLighting ? hdrpClusterBindGroup : meshBindGroup;
6131
6244
  const spriteIsHdr = c.tonemapActive || transparentPassColorFormat(c, pipelineState) === "rgba16float";
6132
6245
  let geometryPassEnded = false;
6133
6246
  if (splitLdrSprite && (graphPass !== void 0 || ldrSpritePassView !== null)) {
@@ -6155,7 +6268,7 @@ function recordSpritePass(c, pass, matchedIndices, materialSlotIndices, sampleCo
6155
6268
  { colorLoadOp: "load", depthLoadOp: "load" }
6156
6269
  )
6157
6270
  );
6158
- spritePass.setBindGroup(0, viewBindGroup, [0]);
6271
+ spritePass.setBindGroup(0, viewBindGroup, [viewBindGroupDynamicOffset, 0]);
6159
6272
  const spritePremulBlend = {
6160
6273
  depthWriteEnabled: false,
6161
6274
  depthCompare: "less-equal",
@@ -6211,7 +6324,12 @@ function recordSpritePass(c, pass, matchedIndices, materialSlotIndices, sampleCo
6211
6324
  spritePremulBlend,
6212
6325
  "triangle-list",
6213
6326
  void 0,
6214
- void 0,
6327
+ // sprite-lit declares the same Standard capability axes as PBR. The
6328
+ // boot artifact is intentionally the direct compatibility variant,
6329
+ // so a clustered frame must request its explicit Cluster artifact;
6330
+ // otherwise the host binds the unified group(2) while the shader
6331
+ // still reads the now-cleared direct-light headers.
6332
+ standardTopologyVariantSet(c.standardLighting, runtime.device.caps.storageBuffer, false),
6215
6333
  "forward",
6216
6334
  void 0,
6217
6335
  msaaActive ? 4 : 1,
@@ -6233,14 +6351,15 @@ function recordSpritePass(c, pass, matchedIndices, materialSlotIndices, sampleCo
6233
6351
  matchedIndices,
6234
6352
  materialSlotIndices,
6235
6353
  sampleCount,
6236
- resolveMaterialBindGroup
6354
+ resolveMaterialBindGroup,
6355
+ meshGroup2
6237
6356
  );
6238
6357
  if (graphPass === void 0) spritePass.end();
6239
6358
  }
6240
6359
  return geometryPassEnded;
6241
6360
  }
6242
- function recordSpriteTransparentPbrDraws(c, spritePass, matchedIndices, materialSlotIndices, sampleCount, resolveMaterialBindGroup) {
6243
- const { runtime, pipelineState, frameState, bindGroupCounts, validatedOrdered, meshBindGroup } = c;
6361
+ function recordSpriteTransparentPbrDraws(c, spritePass, matchedIndices, materialSlotIndices, sampleCount, resolveMaterialBindGroup, meshGroup2) {
6362
+ const { runtime, pipelineState, frameState, bindGroupCounts, validatedOrdered } = c;
6244
6363
  const isHdrTarget = c.tonemapActive || transparentPassColorFormat(c, pipelineState) === "rgba16float";
6245
6364
  let lastPbrSubPipelineHandle = null;
6246
6365
  let lastPbrSubVertexBuffer = null;
@@ -6263,7 +6382,6 @@ function recordSpriteTransparentPbrDraws(c, spritePass, matchedIndices, material
6263
6382
  spritePass.setIndexBuffer(entry.mesh.indexBuffer.handle, entry.mesh.indexFormat);
6264
6383
  lastPbrSubIndexBuffer = entry.mesh.indexBuffer;
6265
6384
  }
6266
- spritePass.setBindGroup(2, meshBindGroup, [i * MESH_PER_ENTITY_STRIDE]);
6267
6385
  const identityInstBg = getOrCreatePerEntity(
6268
6386
  frameState.instancesBgPerEntity,
6269
6387
  worldEntityKey(entry.source.worldId, entry.source.entityKey),
@@ -6289,7 +6407,11 @@ function recordSpriteTransparentPbrDraws(c, spritePass, matchedIndices, material
6289
6407
  bindGroupCounts
6290
6408
  );
6291
6409
  spritePass.setBindGroup(3, identityInstBg);
6292
- const subVariantSet = frameState.isHdrpActive ? "" : "CLUSTER_FORWARD_AVAILABLE=false+STORAGE_BUFFER_AVAILABLE=true";
6410
+ const subVariantSet = standardTopologyVariantSet(
6411
+ c.standardLighting,
6412
+ runtime.device.caps.storageBuffer,
6413
+ entry.mesh.layoutProjection.attributes.some((attribute) => attribute.key === "color")
6414
+ );
6293
6415
  const projectionVariantSetResult = variantSetFromVertexLayoutProjection(
6294
6416
  entry.mesh.layoutProjection,
6295
6417
  subVariantSet
@@ -6307,15 +6429,7 @@ function recordSpriteTransparentPbrDraws(c, spritePass, matchedIndices, material
6307
6429
  if (submeshMaterial.transparent !== true) continue;
6308
6430
  const smShaderId = submeshMaterial.materialShaderId;
6309
6431
  if (smShaderId === void 0) continue;
6310
- const materialSlot = materialSlotIndices[i]?.[matSlotIdx] ?? materialSlotIndices[i]?.[0] ?? 0;
6311
- const subBg = resolveMaterialBindGroup(
6312
- materialSlot,
6313
- submeshMaterial,
6314
- entry.source.entityKey,
6315
- entry.world ?? c.world
6316
- );
6317
- spritePass.setBindGroup(1, subBg, [materialSlot * MATERIAL_PER_ENTITY_STRIDE]);
6318
- const subPipeline = runtime.getMaterialShaderPipeline?.(
6432
+ const subPipelineEntry = c.runtime.getMaterialShaderPipelineEntry?.(
6319
6433
  smShaderId,
6320
6434
  isHdrTarget,
6321
6435
  submeshMaterial.renderState,
@@ -6332,7 +6446,20 @@ function recordSpriteTransparentPbrDraws(c, spritePass, matchedIndices, material
6332
6446
  void 0,
6333
6447
  entry.mesh.layoutProjection
6334
6448
  ) ?? null;
6335
- if (subPipeline === null) continue;
6449
+ if (subPipelineEntry === null) continue;
6450
+ const submeshContract = subPipelineEntry.group2Contract;
6451
+ const submeshGroup2 = selectMaterialGroup2(meshGroup2, c.meshBindGroup, submeshContract);
6452
+ if (submeshGroup2 === null) continue;
6453
+ spritePass.setBindGroup(2, submeshGroup2, [i * MESH_PER_ENTITY_STRIDE]);
6454
+ const materialSlot = materialSlotIndices[i]?.[matSlotIdx] ?? materialSlotIndices[i]?.[0] ?? 0;
6455
+ const subBg = resolveMaterialBindGroup(
6456
+ materialSlot,
6457
+ submeshMaterial,
6458
+ entry.source.entityKey,
6459
+ entry.world ?? c.world
6460
+ );
6461
+ spritePass.setBindGroup(1, subBg, [materialSlot * MATERIAL_PER_ENTITY_STRIDE]);
6462
+ const subPipeline = subPipelineEntry.pipeline;
6336
6463
  if (lastPbrSubPipelineHandle !== subPipeline) {
6337
6464
  spritePass.setPipeline(subPipeline);
6338
6465
  lastPbrSubPipelineHandle = subPipeline;
@@ -6564,6 +6691,7 @@ function recordSpriteEntityDraws(c, spritePass, matchedIndices, skylightResource
6564
6691
  );
6565
6692
  spriteInstanceBuffer = _si.buffer;
6566
6693
  spriteInstanceCount = _si.count;
6694
+ const spriteGroup2 = entityShaderId === "forgeax::sprite-lit" && c.standardLighting?.kind === "clustered" ? c.hdrpClusterBindGroup : meshBindGroup;
6567
6695
  const spriteInstancesBg = getOrCreatePerEntity(
6568
6696
  frameState.instancesBgPerEntity,
6569
6697
  worldEntityKey(spriteEntry.source.worldId, spriteEntry.source.entityKey),
@@ -6588,7 +6716,7 @@ function recordSpriteEntityDraws(c, spritePass, matchedIndices, skylightResource
6588
6716
  },
6589
6717
  bindGroupCounts
6590
6718
  );
6591
- spritePass.setBindGroup(2, meshBindGroup, [i * MESH_PER_ENTITY_STRIDE]);
6719
+ spritePass.setBindGroup(2, spriteGroup2, [i * MESH_PER_ENTITY_STRIDE]);
6592
6720
  const spriteTexHandle = spriteEntry.source.material.baseColorTexture;
6593
6721
  let spriteTexView = pipelineState.defaultWhiteTextureView;
6594
6722
  if (spriteTexHandle !== void 0) {
@@ -6940,25 +7068,12 @@ function skinnedShadowMeshBindGroup(c, entry) {
6940
7068
  const created = c.runtime.device.createBindGroup({
6941
7069
  label: "shadow-pbr-skin-mesh-bg",
6942
7070
  layout,
6943
- entries: [
6944
- {
6945
- binding: 0,
6946
- resource: {
6947
- kind: "buffer",
6948
- value: {
6949
- buffer: c.pipelineState.meshStorageBuffer.buffer,
6950
- size: c.runtime.device.caps.storageBuffer ? MESH_SSBO_BYTES : MESH_UBO_FULL_ARRAY_BYTES
6951
- }
6952
- }
6953
- },
6954
- {
6955
- binding: 1,
6956
- resource: {
6957
- kind: "buffer",
6958
- value: { buffer: skin.buffer, size: allocator.bindingWindowBytes }
6959
- }
6960
- }
6961
- ]
7071
+ entries: createPbrSkinMeshBindGroupEntries(
7072
+ c.pipelineState.meshStorageBuffer.buffer,
7073
+ c.runtime.device.caps.storageBuffer ? MESH_SSBO_BYTES : MESH_UBO_FULL_ARRAY_BYTES,
7074
+ skin.buffer,
7075
+ allocator.bindingWindowBytes
7076
+ )
6962
7077
  });
6963
7078
  if (!created.ok) throw created.error;
6964
7079
  return created.value;
@@ -6980,7 +7095,7 @@ function encodeDirectionalShadowPass(c, pass, cascadeIndex, viewport) {
6980
7095
  c.frameState.directionalShadowCacheRecorded = true;
6981
7096
  pass.setViewport(viewport.x, viewport.y, viewport.w, viewport.h, 0, 1);
6982
7097
  pass.setPipeline(pipeline);
6983
- pass.setBindGroup(0, viewBg, [0]);
7098
+ pass.setBindGroup(0, viewBg, [0, 0]);
6984
7099
  pass.setBindGroup(1, materialBg, [0]);
6985
7100
  recordShadowCasterDraws(
6986
7101
  c,
@@ -7004,7 +7119,7 @@ function encodePointShadowPass(c, pass, snapshotIndex, face) {
7004
7119
  const materialBg = ensureSpotShadowMaterialBg(c);
7005
7120
  if (pipeline === null || viewBg === null || materialBg === null) return;
7006
7121
  pass.setPipeline(pipeline);
7007
- pass.setBindGroup(0, viewBg, [0]);
7122
+ pass.setBindGroup(0, viewBg, [0, 0]);
7008
7123
  pass.setBindGroup(1, materialBg, [0]);
7009
7124
  recordShadowCasterDraws(
7010
7125
  c,
@@ -7042,7 +7157,7 @@ function encodeSpotShadowPass(c, pass, snapshotIndex) {
7042
7157
  1
7043
7158
  );
7044
7159
  pass.setPipeline(pipeline);
7045
- pass.setBindGroup(0, viewBg, [0]);
7160
+ pass.setBindGroup(0, viewBg, [0, 0]);
7046
7161
  pass.setBindGroup(1, materialBg, [0]);
7047
7162
  recordShadowCasterDraws(
7048
7163
  c,
@@ -7117,16 +7232,18 @@ function recordShadowCasterDraws(c, shadowPass, shadowPipeline2, shadowMeshBindG
7117
7232
  if (entry.source.skin !== void 0) {
7118
7233
  const skinMeshBindGroup = skinnedShadowMeshBindGroup(c, entry);
7119
7234
  if (skinMeshBindGroup === null) continue;
7120
- shadowPass.setBindGroup(2, skinMeshBindGroup, [
7121
- i * MESH_PER_ENTITY_STRIDE,
7122
- entry.source.skin.byteOffset
7123
- ]);
7235
+ shadowPass.setBindGroup(
7236
+ 2,
7237
+ skinMeshBindGroup,
7238
+ pbrSkinMeshDynamicOffsets(i * MESH_PER_ENTITY_STRIDE, entry.source.skin.byteOffset)
7239
+ );
7124
7240
  } else {
7125
7241
  shadowPass.setBindGroup(2, shadowMeshBindGroup, [i * MESH_PER_ENTITY_STRIDE]);
7126
7242
  }
7127
7243
  let shadowInstanceBuffer = pipelineState.identityInstanceBuffer;
7128
7244
  let shadowInstanceCount = 1;
7129
7245
  const shadowInst = entry.source.instances;
7246
+ if (shadowInst?.instanceCount === 0) continue;
7130
7247
  if (shadowInst !== void 0) {
7131
7248
  const uniformFallback = runtime.device.caps.storageBuffer === false;
7132
7249
  if (uniformFallback && shadowInst.instanceCount > MAX_UNIFORM_INSTANCES) {
@@ -7341,6 +7458,7 @@ function recordMainPass(c, selector, options, graphPass) {
7341
7458
  geometryDepthView,
7342
7459
  validatedOrdered,
7343
7460
  viewBindGroup,
7461
+ viewBindGroupDynamicOffset = 0,
7344
7462
  meshBindGroup,
7345
7463
  frameState,
7346
7464
  bindGroupCounts,
@@ -7375,8 +7493,10 @@ function recordMainPass(c, selector, options, graphPass) {
7375
7493
  const targetDepthView = options?.depthView ?? geometryDepthView;
7376
7494
  const clearColor = options?.clearColor ?? clear;
7377
7495
  const videoHighPerfAvailable = probeVideoHighPerfUpload(runtime.device);
7496
+ const clusteredLighting = c.standardLighting?.kind === "clustered";
7497
+ const clusteredBindGroupMissing = validatedOrdered.length > 0 && !standardTopologyBindGroupReady(c.standardLighting, hdrpClusterBindGroup);
7378
7498
  let hdrpSsaoBindGroup = null;
7379
- if (frameState.isHdrpActive && hdrpClusterBindGroup !== null && c.hdrpSsaoBlurredView !== void 0) {
7499
+ if (clusteredLighting && hdrpClusterBindGroup !== null && c.hdrpSsaoBlurredView !== void 0) {
7380
7500
  const hdrpBuffers = getOrCreateHdrpBuffers(
7381
7501
  runtime,
7382
7502
  frameState.installedPipelineConfig?.clusterGrid
@@ -7390,7 +7510,7 @@ function recordMainPass(c, selector, options, graphPass) {
7390
7510
  );
7391
7511
  }
7392
7512
  }
7393
- const meshGroup2 = frameState.isHdrpActive && hdrpClusterBindGroup !== null ? hdrpSsaoBindGroup ?? hdrpClusterBindGroup : meshBindGroup;
7513
+ const meshGroup2 = clusteredLighting ? hdrpSsaoBindGroup ?? hdrpClusterBindGroup : meshBindGroup;
7394
7514
  let geometryPassEnded = false;
7395
7515
  const mainColorLoadOp = skyboxActive ? "load" : "clear";
7396
7516
  const mainPassResolves = passKind === "forward" && msaaActive && geometryColorResolveView !== null && !splitLdrSprite;
@@ -7426,7 +7546,16 @@ function recordMainPass(c, selector, options, graphPass) {
7426
7546
  ...recordMode === "transmission" ? { variantSet: TRANSMISSION_VARIANT_SET } : {}
7427
7547
  }))
7428
7548
  };
7429
- if (validatedOrdered.length > 0) {
7549
+ if (clusteredBindGroupMissing) {
7550
+ runtime.errorRegistry.fire(
7551
+ new RhiError({
7552
+ code: "webgpu-runtime-error",
7553
+ expected: "clustered Standard frame has a unified group(2) BindGroup",
7554
+ hint: "repair the clustered buffer/layout admission before retrying the frame; direct URP fallback is unsafe"
7555
+ })
7556
+ );
7557
+ }
7558
+ if (validatedOrdered.length > 0 && !clusteredBindGroupMissing) {
7430
7559
  const MATERIAL_SLICE = STANDARD_PBR_UBO_SIZE;
7431
7560
  const skylightFallback = pipelineState.skylightFallback;
7432
7561
  if (skylightFallback === null) {
@@ -7477,6 +7606,31 @@ function recordMainPass(c, selector, options, graphPass) {
7477
7606
  materialSlice: MATERIAL_SLICE,
7478
7607
  videoHighPerfAvailable,
7479
7608
  skylightResources,
7609
+ resolveRenderTargetTextureSource: runtime.resolveRenderTargetTextureSource,
7610
+ resolveReflectionProbeResources: (materialWorld, entityKey) => {
7611
+ const reflectionProbes = c.reflectionProbes;
7612
+ if (reflectionProbes === void 0) return void 0;
7613
+ const selection = reflectionProbes.selections.get(`${materialWorld.identity}:${entityKey}`);
7614
+ if (selection === void 0) return void 0;
7615
+ const binding = resolveReflectionProbeBinding(selection, reflectionProbes.table);
7616
+ if (binding.useSkylight || binding.probeIndex === void 0) return void 0;
7617
+ const row = reflectionProbes.table.rows.find(
7618
+ (candidate) => candidate.index === binding.probeIndex
7619
+ );
7620
+ if (row?.filteredView === void 0 || row.sampler === void 0 || row.uniformBuffer === void 0) {
7621
+ return void 0;
7622
+ }
7623
+ const probeResources = {
7624
+ irradianceView: skylightResources.irradianceView,
7625
+ irradianceSampler: skylightResources.irradianceSampler,
7626
+ prefilterView: row.filteredView,
7627
+ prefilterSampler: row.sampler,
7628
+ brdfLutView: skylightResources.brdfLutView,
7629
+ brdfLutSampler: skylightResources.brdfLutSampler,
7630
+ intensityBuffer: row.uniformBuffer
7631
+ };
7632
+ return probeResources;
7633
+ },
7480
7634
  materialBgShared: frameState.materialBgShared,
7481
7635
  materialBgAssemblyCache: c.materialBgAssemblyCache,
7482
7636
  bindGroupCounts,
@@ -7585,7 +7739,7 @@ function recordMainPass(c, selector, options, graphPass) {
7585
7739
  );
7586
7740
  }
7587
7741
  const resolveMaterialBindGroup = (materialSlot, material, entityKey, materialWorld = world) => material.materialShaderId !== POINTS_LINES_MATERIAL_SHADER_ID ? preparedMaterialBindGroups[materialSlot] ?? buildPerSubmeshMaterialBg2(material, entityKey, materialWorld) : buildPerSubmeshMaterialBg2(material, entityKey, materialWorld);
7588
- pass.setBindGroup(0, viewBindGroup, [0]);
7742
+ pass.setBindGroup(0, viewBindGroup, [viewBindGroupDynamicOffset, 0]);
7589
7743
  const recordGeometry = () => {
7590
7744
  recordGeometryDraws(
7591
7745
  recordContext,
@@ -7984,14 +8138,20 @@ function recordBloomCompositePass(_c, resolve, graphPass) {
7984
8138
  function recordFxaaPass(c, resolve, graphPass) {
7985
8139
  const { runtime, pipelineState, encoder, camera, currentTexture } = c;
7986
8140
  const fxaaActive = camera.antialias === "fxaa";
7987
- if (fxaaActive && pipelineState.perPassResources.fxaaPipeline !== null && pipelineState.perPassResources.fxaaBindGroupLayout !== null && pipelineState.perPassResources.fxaaSampler !== null) {
8141
+ if (fxaaActive && pipelineState.perPassResources.fxaaPipeline !== null && pipelineState.perPassResources.fxaaBindGroupLayout !== null && pipelineState.perPassResources.fxaaSampler !== null && runtime.getPostProcessParamsBuffer !== void 0) {
7988
8142
  const inputView = resolve.resolve("ldrColor");
7989
8143
  if (inputView === void 0) return;
8144
+ const fxaaParams = runtime.getPostProcessParamsBuffer(FXAA_POST_PROCESS_ID);
8145
+ if (fxaaParams === void 0) return;
8146
+ const params = new Float32Array(4);
8147
+ params[0] = resolveOutputDither(c.frameState.installedPipelineConfig) ? 1 : 0;
8148
+ const paramsWrite = runtime.device.queue.writeBuffer(fxaaParams, 0, params);
8149
+ if (!paramsWrite.ok) throw paramsWrite.error;
7990
8150
  const fxaaBglLayout = pipelineState.perPassResources.fxaaBindGroupLayout;
7991
8151
  const fxaaSampler = pipelineState.perPassResources.fxaaSampler;
7992
8152
  const fxaaBg = getOrCreateFromChain(
7993
8153
  c.frameState.postProcessBgCache,
7994
- [inputView],
8154
+ [inputView, fxaaParams],
7995
8155
  "fxaa",
7996
8156
  () => {
7997
8157
  const fxaaBgRes = runtime.device.createBindGroup({
@@ -8008,6 +8168,10 @@ function recordFxaaPass(c, resolve, graphPass) {
8008
8168
  {
8009
8169
  binding: 1,
8010
8170
  resource: { kind: "sampler", value: fxaaSampler }
8171
+ },
8172
+ {
8173
+ binding: 2,
8174
+ resource: { kind: "buffer", value: { buffer: fxaaParams } }
8011
8175
  }
8012
8176
  ]
8013
8177
  });
@@ -8053,6 +8217,32 @@ var FULLSCREEN_DEFAULT_SPEC = Object.freeze({
8053
8217
  geometry: { topology: "triangle-list", vertexLayout: {} },
8054
8218
  renderState: void 0
8055
8219
  });
8220
+ function postProcessSourceDigest(source) {
8221
+ let first = 2166136261;
8222
+ let second = 2654435769;
8223
+ for (let index = 0; index < source.length; index += 1) {
8224
+ const code = source.charCodeAt(index);
8225
+ first = Math.imul(first ^ code, 16777619);
8226
+ second = Math.imul(second ^ code + index, 16777619);
8227
+ }
8228
+ return `${source.length.toString(16)}-${(first >>> 0).toString(16).padStart(8, "0")}-${(second >>> 0).toString(16).padStart(8, "0")}`;
8229
+ }
8230
+ function postProcessShaderPipelineLabel(identity, source) {
8231
+ return `post-process-${identity}-pso-${postProcessSourceDigest(source)}`;
8232
+ }
8233
+ function postProcessShaderModuleLabel(identity, source) {
8234
+ return `${postProcessShaderPipelineLabel(identity, source)}-module`;
8235
+ }
8236
+ function postProcessShaderEntrySignature(entry) {
8237
+ return JSON.stringify({
8238
+ source: entry.source,
8239
+ params: entry.params === void 0 ? void 0 : {
8240
+ byteSize: entry.params.byteSize,
8241
+ defaultValue: Array.from(entry.params.defaultValue)
8242
+ },
8243
+ reads: entry.reads
8244
+ });
8245
+ }
8056
8246
  function isTemporalFullscreenBinding(values) {
8057
8247
  return values.temporal !== void 0 || values.shader === "forgeax.taa-resolve" || values.shader === "forgeax.motion-blur";
8058
8248
  }
@@ -8401,7 +8591,7 @@ function recordSsaoBlurPass(_c, resolveCtx, ssaoBlurredKey, ssaoRawKey, gbuf0Key
8401
8591
  pass.draw(3, 1, 0, 0);
8402
8592
  if (graphPass === void 0) pass.end();
8403
8593
  }
8404
- function dispatchFullscreenPass(ctx, name, shader, color, reads, resolveCtx, compositeOverSwapchain = false, rawSwapchainOutput = false, graphPass, graphOutputFormat, graphDepthView) {
8594
+ function dispatchFullscreenPass(ctx, name, shader, color, reads, resolveCtx, compositeOverSwapchain = false, rawSwapchainOutput = false, graphPass, graphOutputFormat, graphDepthView, paramsOverride) {
8405
8595
  if (shader === "fxaa") {
8406
8596
  if (resolveCtx === void 0) {
8407
8597
  throw new PostProcessError({
@@ -8512,7 +8702,7 @@ function dispatchFullscreenPass(ctx, name, shader, color, reads, resolveCtx, com
8512
8702
  if (entry.params !== void 0) {
8513
8703
  const ubo = ctx.runtime.getPostProcessParamsBuffer?.(shader);
8514
8704
  if (ubo !== void 0) {
8515
- const data = ctx.postProcessParams.get(shader);
8705
+ const data = paramsOverride ?? ctx.postProcessParams.get(shader);
8516
8706
  if (data !== void 0) {
8517
8707
  if (data.byteLength !== entry.params.byteSize) {
8518
8708
  throw new PostProcessError({
@@ -8559,9 +8749,7 @@ function dispatchFullscreenPass(ctx, name, shader, color, reads, resolveCtx, com
8559
8749
  if (lookupPipeline === void 0) return;
8560
8750
  const postColorFormat = writeFormat;
8561
8751
  const pipeline = lookupPipeline(shader, built.bindGroupLayout, postColorFormat);
8562
- if (pipeline === null) {
8563
- return;
8564
- }
8752
+ if (pipeline === null) return;
8565
8753
  const handle = built.createHandle(name, pipeline, paramsBuffer);
8566
8754
  const pass = graphPass ?? ctx.encoder.beginRenderPass(
8567
8755
  buildBeginRenderPassDescriptor(
@@ -8587,7 +8775,8 @@ function encodeFullscreenPass(ctx, pass, input) {
8587
8775
  input.rawSwapchainOutput ?? false,
8588
8776
  pass,
8589
8777
  input.outputFormat,
8590
- input.depthView
8778
+ input.depthView,
8779
+ input.paramsOverride
8591
8780
  );
8592
8781
  }
8593
8782
 
@@ -8912,7 +9101,9 @@ function addTypedScenePass(graph, options) {
8912
9101
  spotShadow,
8913
9102
  projector: internal.spotLightProjector?.view ?? internal.volumetricFog?.projectorView,
8914
9103
  projectorSampler: internal.spotLightProjector?.sampler ?? internal.volumetricFog?.projectorSampler
8915
- }
9104
+ },
9105
+ true,
9106
+ internal.standardLighting
8916
9107
  );
8917
9108
  if (groups.viewBindGroup !== null) {
8918
9109
  options.gpuDriven?.encode(groups.viewBindGroup, pass, resources);
@@ -9297,7 +9488,7 @@ function addTypedOutputTransformPass(graph, input, output, options = {}) {
9297
9488
  output,
9298
9489
  outputOnly,
9299
9490
  rawSwapchainOutput: outputOnly,
9300
- ...options.outputTransform === true ? {
9491
+ ...options.dither !== void 0 ? {
9301
9492
  paramsTransform: (params) => {
9302
9493
  if (params === void 0 || params.byteLength !== 16) return params;
9303
9494
  const transformed = params.slice();
@@ -9305,7 +9496,7 @@ function addTypedOutputTransformPass(graph, input, output, options = {}) {
9305
9496
  transformed.buffer,
9306
9497
  transformed.byteOffset,
9307
9498
  transformed.byteLength
9308
- ).setFloat32(12, 1, true);
9499
+ ).setFloat32(12, options.dither === true ? 1 : 0, true);
9309
9500
  return transformed;
9310
9501
  }
9311
9502
  } : {}
@@ -9399,6 +9590,6 @@ function addTaaResolvePass(graph, current, currentTemporal, previousColor, previ
9399
9590
  });
9400
9591
  }
9401
9592
 
9402
- export { CAPTURE_VIEW_PROJS, CUBEMAP_FACE_VERTICES, DEFERRED_COLOR_FORMATS, DEPTH_MIN_PARAMS_BYTE_SIZE, FALLBACK_BYTES_PER_ROW, GPU_BUFFER_USAGE_COPY_DST, GPU_BUFFER_USAGE_COPY_SRC, GPU_BUFFER_USAGE_INDEX, GPU_BUFFER_USAGE_INDIRECT, GPU_BUFFER_USAGE_MAP_READ, GPU_BUFFER_USAGE_QUERY_RESOLVE, GPU_BUFFER_USAGE_STORAGE, GPU_BUFFER_USAGE_UNIFORM, GPU_BUFFER_USAGE_VERTEX, GPU_SHADER_STAGE_FRAGMENT, GPU_SHADER_STAGE_VERTEX, GPU_TEXTURE_USAGE_COPY_DST, GPU_TEXTURE_USAGE_COPY_SRC, GPU_TEXTURE_USAGE_RENDER_ATTACHMENT, GPU_TEXTURE_USAGE_RENDER_ATTACHMENT_AND_TEXTURE_BINDING, GPU_TEXTURE_USAGE_TEXTURE_BINDING, GpuBuffer, GpuTexture, LIGHT_ARRAY_HEADER_BYTES, LIGHT_ARRAY_MAX_SLOTS, MATERIAL_PER_ENTITY_STRIDE, POINTS_LINES_MATERIAL_SHADER_ID, POINTS_LINES_VIEW_BUFFER_SIZE, POINT_LIGHT_STD430_BYTES, PREFILTER_MIP_LEVELS, PREFILTER_SIZE, PipelineSpecError, PostProcessError, SHADOW_CASTER_BUFFER_SIZE, SKIN_MATERIAL_SHADER_ID, SPOT_LIGHT_STD430_BYTES, TYPED_BLOOM_PASS_NAMES, VIEW_UNIFORM_BUFFER_SIZE, VIEW_UNIFORM_BYTES, abortTemporalGpuSubmit, addStandardSceneDataPass, addTaaResolvePass, addTypedBloomPasses, addTypedCompositePostEffects, addTypedFrameObservationPass, addTypedFullscreenPass, addTypedOutputTransformPass, addTypedScenePass, addTypedSkyboxPass, addTypedSsaoPasses, addTypedTemporalResolvePass, aggregateTemporalDemand, applyParamSnapshotToUbo, assembleMaterialWithSkylightEntries, assertStorageBufferCap, buildBindGroupLayoutDescriptor, buildFoldDispatchPlan, buildFullscreenPostProcessPass, buildLinearLdrMaterialSpecTable, buildPbrMaterialUserRegionEntries, buildPbrPipelineLayouts, buildPbrSkinLayouts, buildPerFrameBindGroups, buildSpecConstTable, cacheKeyOf, cleanPerEntityCache, colorFormatsForPassKind, commitTemporalGpuSubmit, computeProjectionMatrix, computeSplitLdrSprite, computeViewMatrix, configureSurface, createClusterBinScratch, createFullscreenBindGroup, createHdrpBindGroupLayoutDescriptor, createHdrpClusterMembershipBindGroupLayoutDescriptor, createIblPipelines, createPointsLinesLaneAdapter, createPointsLinesLaneContract, createRenderPipelineTarget, createSkylightFallback, createStandardSceneDataTarget, deriveRenderDataCubemap, deriveRenderDataMesh, deriveRenderDataTexture, describeTemporalDemand, driveLazyEquirectProjection, encodeDirectionalShadowPass, encodePointShadowPass, encodeSpotShadowPass, ensureMeshSsboCapacity, entryHasDepthRead, evaluateFoldBucketUniformCap, foldDispatchBuckets, geometryRenderStateForTopology, getOrBuildPipeline, getOrCreateHdrpBuffers, getTemporalGpuState, hasPendingTemporalGpuSubmit, importRenderPipelineSurface, incrementFoldedDrawsMetric, isSkinnedShadowCasterVariant, isStandardPbrMaterialShader, isTemporalFullscreenBinding, prepareFrameLighting, residentTextureView, resolveSkyboxActive, resolveSsaoParameters, resolveSurfaceFormatPair, resolveSurfaceProfile, retireTemporalGpuState, retireTemporalGpuStateAfterFence, runIblPrecompute, selectLazyEquirectHandle, selectPasses, selectSwapChainFormat, setIblComposedShaders, standardTemporalLaneAdmission, standardTemporalPostOrder, temporalReadIndex, temporalWriteIndex, typedFrameClearColor, uploadMeshSsboBatch, variantSetFromDefines, warnMultiSkybox, warnMultiSkylight, warnZeroLightStandard, writeHdrpClusterAndSsaoBuffers, writePointSpotLightBuffers, writePointsLinesViewUbo, writeShadowCasterUniforms, writeShadowParamsBuffer, writeViewUbo };
9403
- //# sourceMappingURL=chunk-4XINDLAY.mjs.map
9404
- //# sourceMappingURL=chunk-4XINDLAY.mjs.map
9593
+ export { CAPTURE_VIEW_PROJS, CUBEMAP_FACE_VERTICES, CUBE_CAPTURE_VIEW_SLOT_BASE, DEFERRED_COLOR_FORMATS, DEPTH_MIN_PARAMS_BYTE_SIZE, FALLBACK_BYTES_PER_ROW, GPU_BUFFER_USAGE_COPY_DST, GPU_BUFFER_USAGE_COPY_SRC, GPU_BUFFER_USAGE_INDEX, GPU_BUFFER_USAGE_INDIRECT, GPU_BUFFER_USAGE_MAP_READ, GPU_BUFFER_USAGE_QUERY_RESOLVE, GPU_BUFFER_USAGE_STORAGE, GPU_BUFFER_USAGE_UNIFORM, GPU_BUFFER_USAGE_VERTEX, GPU_SHADER_STAGE_FRAGMENT, GPU_SHADER_STAGE_VERTEX, GpuBuffer, GpuTexture, LIGHT_ARRAY_HEADER_BYTES, LIGHT_ARRAY_MAX_SLOTS, MATERIAL_PER_ENTITY_STRIDE, POINTS_LINES_MATERIAL_SHADER_ID, POINTS_LINES_VIEW_BUFFER_SIZE, POINT_LIGHT_STD430_BYTES, PREFILTER_MIP_LEVELS, PREFILTER_SIZE, PipelineSpecError, PostProcessError, REFLECTION_PROBE_VIEW_SLOT_BASE, SHADOW_CASTER_BUFFER_SIZE, SKIN_MATERIAL_SHADER_ID, SPOT_LIGHT_STD430_BYTES, TYPED_BLOOM_PASS_NAMES, VIEW_UNIFORM_BUFFER_SIZE, VIEW_UNIFORM_BYTES, VIEW_UNIFORM_SLOT_STRIDE, abortTemporalGpuSubmit, addStandardSceneDataPass, addTaaResolvePass, addTypedBloomPasses, addTypedCompositePostEffects, addTypedFrameObservationPass, addTypedFullscreenPass, addTypedOutputTransformPass, addTypedScenePass, addTypedSkyboxPass, addTypedSsaoPasses, addTypedTemporalResolvePass, aggregateTemporalDemand, applyParamSnapshotToUbo, assembleMaterialWithSkylightEntries, assertStorageBufferCap, buildBindGroupLayoutDescriptor, buildFoldDispatchPlan, buildFullscreenPostProcessPass, buildLinearLdrMaterialSpecTable, buildPbrMaterialUserRegionEntries, buildPbrPipelineLayouts, buildPbrSkinLayouts, buildPerFrameBindGroups, buildSpecConstTable, cacheKeyOf, cleanPerEntityCache, colorFormatsForPassKind, commitTemporalGpuSubmit, computeProjectionMatrix, computeSplitLdrSprite, computeViewMatrix, configureSurface, createClusterBinScratch, createFullscreenBindGroup, createHdrpBindGroupLayoutDescriptor, createHdrpClusterMembershipBindGroupLayoutDescriptor, createHdrpSkinBindGroupLayoutDescriptor, createIblPipelines, createPointsLinesLaneAdapter, createPointsLinesLaneContract, createRenderPipelineTarget, createRenderTargetMaterialSource, createSkylightFallback, createStandardSceneDataTarget, deriveRenderDataCubemap, deriveRenderDataMesh, deriveRenderDataTexture, describeTemporalDemand, driveLazyEquirectProjection, encodeDirectionalShadowPass, encodeMainPass, encodePointShadowPass, encodeSpotShadowPass, ensureMeshSsboCapacity, entryHasDepthRead, evaluateFoldBucketUniformCap, foldDispatchBuckets, geometryRenderStateForTopology, getOrBuildPipeline, getOrCreateHdrpBuffers, getOrCreateIblCache, getTemporalGpuState, hasPendingTemporalGpuSubmit, importRenderPipelineSurface, incrementFoldedDrawsMetric, isStandardPbrMaterialShader, isTemporalFullscreenBinding, postProcessShaderEntrySignature, postProcessShaderModuleLabel, postProcessShaderPipelineLabel, prepareFrameLighting, resetHdrpBuffers, residentTextureView, resolveOutputDither, resolvePipelineGroup2Contract, resolveRenderTargetMaterialSource, resolveSkyboxActive, resolveSsaoParameters, resolveSurfaceFormatPair, resolveSurfaceProfile, retireTemporalGpuState, retireTemporalGpuStateAfterFence, runIblPrecompute, selectLazyEquirectHandle, selectPasses, selectSwapChainFormat, setIblComposedShaders, standardTemporalLaneAdmission, standardTemporalPostOrder, temporalReadIndex, temporalWriteIndex, typedFrameClearColor, uploadMeshSsboBatch, variantSetFromDefines, warnMultiSkybox, warnMultiSkylight, warnZeroLightStandard, writeHdrpClusterAndSsaoBuffers, writePointsLinesViewUbo, writeShadowCasterUniforms, writeShadowParamsBuffer, writeViewUbo };
9594
+ //# sourceMappingURL=chunk-5YADUCA7.mjs.map
9595
+ //# sourceMappingURL=chunk-5YADUCA7.mjs.map