@forgeax/engine-render 0.1.19 → 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 (452) 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 +34 -20
  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-RJRUS55L.mjs → chunk-5YADUCA7.mjs} +1896 -1624
  21. package/dist/chunk-5YADUCA7.mjs.map +1 -0
  22. package/dist/{chunk-LOSO4CTV.mjs → chunk-6OPXDQ2E.mjs} +423 -46
  23. package/dist/chunk-6OPXDQ2E.mjs.map +1 -0
  24. package/dist/{chunk-IPXCABEP.mjs → chunk-BXBQQKFQ.mjs} +3 -9
  25. package/dist/chunk-BXBQQKFQ.mjs.map +1 -0
  26. package/dist/chunk-FS2KNUQH.mjs +1294 -0
  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/components/spot-light.d.ts +12 -0
  42. package/dist/components/spot-light.d.ts.map +1 -1
  43. package/dist/construct-renderer.mjs +8552 -1612
  44. package/dist/construct-renderer.mjs.map +1 -1
  45. package/dist/device/gpu-residency.d.ts +19 -2
  46. package/dist/device/gpu-residency.d.ts.map +1 -1
  47. package/dist/environment/frame.d.ts.map +1 -1
  48. package/dist/environment/lifecycle.d.ts +5 -0
  49. package/dist/environment/lifecycle.d.ts.map +1 -1
  50. package/dist/errors/render.d.ts +209 -93
  51. package/dist/errors/render.d.ts.map +1 -1
  52. package/dist/extract/directional-shadow-projection.d.ts +23 -0
  53. package/dist/extract/directional-shadow-projection.d.ts.map +1 -0
  54. package/dist/extract/environment.d.ts +14 -0
  55. package/dist/extract/environment.d.ts.map +1 -1
  56. package/dist/extract/visibility.d.ts.map +1 -1
  57. package/dist/features/fullscreen.d.ts.map +1 -1
  58. package/dist/features/targets.d.ts.map +1 -1
  59. package/dist/features/types.d.ts +10 -0
  60. package/dist/features/types.d.ts.map +1 -1
  61. package/dist/fullscreen-post-process-pass.d.ts +13 -2
  62. package/dist/fullscreen-post-process-pass.d.ts.map +1 -1
  63. package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
  64. package/dist/gpu-driven/production-raster.d.ts +0 -1
  65. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  66. package/dist/gpu-scene.d.ts.map +1 -1
  67. package/dist/gpu-usage.d.ts +2 -0
  68. package/dist/gpu-usage.d.ts.map +1 -1
  69. package/dist/hdrp-buffers.d.ts +19 -17
  70. package/dist/hdrp-buffers.d.ts.map +1 -1
  71. package/dist/ibl/IblPipelineCache.d.ts +4 -0
  72. package/dist/ibl/IblPipelineCache.d.ts.map +1 -1
  73. package/dist/ibl/kernel-cache.d.ts +36 -0
  74. package/dist/ibl/kernel-cache.d.ts.map +1 -0
  75. package/dist/index.d.ts +35 -4
  76. package/dist/index.d.ts.map +1 -1
  77. package/dist/index.mjs +4 -4
  78. package/dist/index.mjs.map +1 -1
  79. package/dist/inspection-types.d.ts +51 -0
  80. package/dist/inspection-types.d.ts.map +1 -1
  81. package/dist/instances-derived-bounds.d.ts +50 -0
  82. package/dist/instances-derived-bounds.d.ts.map +1 -0
  83. package/dist/internal.mjs +1 -1
  84. package/dist/light-buffer-layout.d.ts +30 -13
  85. package/dist/light-buffer-layout.d.ts.map +1 -1
  86. package/dist/mesh-material-bindings.d.ts +54 -0
  87. package/dist/mesh-material-bindings.d.ts.map +1 -1
  88. package/dist/pbr-pipeline.d.ts +20 -7
  89. package/dist/pbr-pipeline.d.ts.map +1 -1
  90. package/dist/pipeline/standard-forward-lane.d.ts +2 -1
  91. package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
  92. package/dist/pipeline/standard-lighting/errors.d.ts +4 -0
  93. package/dist/pipeline/standard-lighting/errors.d.ts.map +1 -0
  94. package/dist/pipeline/standard-lighting/graph.d.ts +24 -0
  95. package/dist/pipeline/standard-lighting/graph.d.ts.map +1 -0
  96. package/dist/pipeline/standard-lighting/inspection.d.ts +38 -0
  97. package/dist/pipeline/standard-lighting/inspection.d.ts.map +1 -0
  98. package/dist/pipeline/standard-lighting/layout.d.ts +28 -0
  99. package/dist/pipeline/standard-lighting/layout.d.ts.map +1 -0
  100. package/dist/pipeline/standard-lighting/light-frame.d.ts +33 -0
  101. package/dist/pipeline/standard-lighting/light-frame.d.ts.map +1 -0
  102. package/dist/pipeline/standard-lighting/membership.d.ts +14 -0
  103. package/dist/pipeline/standard-lighting/membership.d.ts.map +1 -0
  104. package/dist/pipeline/standard-lighting/prepare.d.ts +16 -0
  105. package/dist/pipeline/standard-lighting/prepare.d.ts.map +1 -0
  106. package/dist/pipeline/standard-lighting/topology.d.ts +45 -0
  107. package/dist/pipeline/standard-lighting/topology.d.ts.map +1 -0
  108. package/dist/pipeline/standard-lighting/transport.d.ts +36 -0
  109. package/dist/pipeline/standard-lighting/transport.d.ts.map +1 -0
  110. package/dist/pipeline/standard-pipeline.d.ts +4 -13
  111. package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
  112. package/dist/pipeline/standard-post.d.ts.map +1 -1
  113. package/dist/pipeline/standard-profile.d.ts +12 -20
  114. package/dist/pipeline/standard-profile.d.ts.map +1 -1
  115. package/dist/pipeline-spec.d.ts +40 -7
  116. package/dist/pipeline-spec.d.ts.map +1 -1
  117. package/dist/point-shadow-inspection.d.ts +21 -0
  118. package/dist/point-shadow-inspection.d.ts.map +1 -0
  119. package/dist/record/frame-lighting.d.ts +29 -32
  120. package/dist/record/frame-lighting.d.ts.map +1 -1
  121. package/dist/record/frame-snapshot.d.ts +65 -15
  122. package/dist/record/frame-snapshot.d.ts.map +1 -1
  123. package/dist/record/frame-targets.d.ts +5 -0
  124. package/dist/record/frame-targets.d.ts.map +1 -1
  125. package/dist/record/frame.d.ts +12 -5
  126. package/dist/record/frame.d.ts.map +1 -1
  127. package/dist/record/gpu-pass-timing/contract.d.ts +96 -0
  128. package/dist/record/gpu-pass-timing/contract.d.ts.map +1 -0
  129. package/dist/record/gpu-pass-timing/errors.d.ts +18 -0
  130. package/dist/record/gpu-pass-timing/errors.d.ts.map +1 -0
  131. package/dist/record/gpu-pass-timing/index.d.ts +8 -0
  132. package/dist/record/gpu-pass-timing/index.d.ts.map +1 -0
  133. package/dist/record/gpu-pass-timing/parser.d.ts +11 -0
  134. package/dist/record/gpu-pass-timing/parser.d.ts.map +1 -0
  135. package/dist/record/gpu-pass-timing/session.d.ts +43 -0
  136. package/dist/record/gpu-pass-timing/session.d.ts.map +1 -0
  137. package/dist/record/gpu-timing.d.ts +37 -0
  138. package/dist/record/gpu-timing.d.ts.map +1 -0
  139. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  140. package/dist/record/main-pass-material.d.ts +23 -2
  141. package/dist/record/main-pass-material.d.ts.map +1 -1
  142. package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
  143. package/dist/record/main-pass.d.ts +2 -0
  144. package/dist/record/main-pass.d.ts.map +1 -1
  145. package/dist/record/render-context.d.ts +48 -3
  146. package/dist/record/render-context.d.ts.map +1 -1
  147. package/dist/record/shadow-pass.d.ts.map +1 -1
  148. package/dist/record/skybox-post-pass.d.ts.map +1 -1
  149. package/dist/record/typed-frame-graph.d.ts +81 -5
  150. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  151. package/dist/record/view-ubo.d.ts +7 -4
  152. package/dist/record/view-ubo.d.ts.map +1 -1
  153. package/dist/reflection/filter.d.ts +22 -0
  154. package/dist/reflection/filter.d.ts.map +1 -0
  155. package/dist/reflection/gpu-table.d.ts +25 -0
  156. package/dist/reflection/gpu-table.d.ts.map +1 -0
  157. package/dist/reflection/projection.d.ts +91 -0
  158. package/dist/reflection/projection.d.ts.map +1 -0
  159. package/dist/render-contract.d.ts +114 -7
  160. package/dist/render-contract.d.ts.map +1 -1
  161. package/dist/render-data.d.ts +10 -6
  162. package/dist/render-data.d.ts.map +1 -1
  163. package/dist/render-graph-primitives.d.ts +1 -0
  164. package/dist/render-graph-primitives.d.ts.map +1 -1
  165. package/dist/render-pipeline.d.ts +78 -2
  166. package/dist/render-pipeline.d.ts.map +1 -1
  167. package/dist/render-system-extract.d.ts +113 -20
  168. package/dist/render-system-extract.d.ts.map +1 -1
  169. package/dist/render-system.d.ts +24 -3
  170. package/dist/render-system.d.ts.map +1 -1
  171. package/dist/renderer-inspect.d.ts +29 -0
  172. package/dist/renderer-inspect.d.ts.map +1 -0
  173. package/dist/renderer.d.ts +43 -0
  174. package/dist/renderer.d.ts.map +1 -0
  175. package/dist/scene/render-scene.d.ts +14 -0
  176. package/dist/scene/render-scene.d.ts.map +1 -1
  177. package/dist/shadow-atlas.d.ts +3 -3
  178. package/dist/shadow-atlas.d.ts.map +1 -1
  179. package/dist/ssao-buffers.d.ts +3 -0
  180. package/dist/ssao-buffers.d.ts.map +1 -1
  181. package/dist/targets/contracts.d.ts +66 -0
  182. package/dist/targets/contracts.d.ts.map +1 -0
  183. package/dist/targets/graph-projection.d.ts +29 -0
  184. package/dist/targets/graph-projection.d.ts.map +1 -0
  185. package/dist/targets/material-source.d.ts +34 -0
  186. package/dist/targets/material-source.d.ts.map +1 -0
  187. package/dist/targets/owner.d.ts +32 -0
  188. package/dist/targets/owner.d.ts.map +1 -0
  189. package/dist/targets/physical.d.ts +18 -0
  190. package/dist/targets/physical.d.ts.map +1 -0
  191. package/dist/targets/readback.d.ts +39 -0
  192. package/dist/targets/readback.d.ts.map +1 -0
  193. package/dist/temporal/frame.d.ts +44 -0
  194. package/dist/temporal/frame.d.ts.map +1 -0
  195. package/dist/temporal/index.mjs +3 -3
  196. package/dist/typed-render-graph-primitives.d.ts +6 -1
  197. package/dist/typed-render-graph-primitives.d.ts.map +1 -1
  198. package/dist/typed-shadow-atlas.d.ts +10 -0
  199. package/dist/typed-shadow-atlas.d.ts.map +1 -0
  200. package/dist/typed-shadow-passes.d.ts +1 -1
  201. package/dist/typed-shadow-passes.d.ts.map +1 -1
  202. package/dist/volume/capability.d.ts +57 -0
  203. package/dist/volume/capability.d.ts.map +1 -0
  204. package/dist/volume/component.d.ts +61 -0
  205. package/dist/volume/component.d.ts.map +1 -0
  206. package/dist/volume/extract.d.ts +11 -0
  207. package/dist/volume/extract.d.ts.map +1 -0
  208. package/dist/volume/inspection.d.ts +38 -0
  209. package/dist/volume/inspection.d.ts.map +1 -0
  210. package/dist/volume/passes.d.ts +38 -0
  211. package/dist/volume/passes.d.ts.map +1 -0
  212. package/dist/volume/recovery.d.ts +38 -0
  213. package/dist/volume/recovery.d.ts.map +1 -0
  214. package/dist/volume/resources.d.ts +57 -0
  215. package/dist/volume/resources.d.ts.map +1 -0
  216. package/dist/volume/temporal.d.ts +35 -0
  217. package/dist/volume/temporal.d.ts.map +1 -0
  218. package/package.json +20 -20
  219. package/src/__tests__/bloom-exact-zero.unit.test.ts +48 -19
  220. package/src/__tests__/bloom-lifecycle.integration.test.ts +41 -13
  221. package/src/__tests__/camera-target-consumption.unit.test.ts +53 -0
  222. package/src/__tests__/cluster-binner.unit.test.ts +35 -0
  223. package/src/__tests__/cluster-grid-default-surface.unit.test.ts +7 -2
  224. package/src/__tests__/cube-camera-scheduler.unit.test.ts +70 -0
  225. package/src/__tests__/cube-camera-single-submit.perf.test.ts +75 -0
  226. package/src/__tests__/cube-camera-view-matrices.unit.test.ts +41 -0
  227. package/src/__tests__/cube-camera-vocabulary.unit.test.ts +47 -0
  228. package/src/__tests__/cube-camera.integration.test.ts +42 -0
  229. package/src/__tests__/device-feature-admission.unit.test.ts +73 -0
  230. package/src/__tests__/direct-light-snapshot.unit.test.ts +3 -3
  231. package/src/__tests__/docs-gate-green.unit.test.ts +38 -0
  232. package/src/__tests__/factory-contract.integration.test.ts +255 -29
  233. package/src/__tests__/fog-component.unit.test.ts +67 -0
  234. package/src/__tests__/fog.integration.test.ts +86 -0
  235. package/src/__tests__/fullscreen-feature-plan.unit.test.ts +37 -0
  236. package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +10 -9
  237. package/src/__tests__/gpu-driven-indirect-raster.dawn.test.ts +17 -1
  238. package/src/__tests__/gpu-driven-production.integration.test.ts +16 -17
  239. package/src/__tests__/gpu-driven-view-graph.integration.test.ts +12 -4
  240. package/src/__tests__/gpu-pass-timing-browser-runner.ts +281 -0
  241. package/src/__tests__/gpu-pass-timing-coexistence.integration.test.ts +80 -0
  242. package/src/__tests__/gpu-pass-timing-contract.unit.test.ts +243 -0
  243. package/src/__tests__/gpu-pass-timing-exact-zero.unit.test.ts +131 -0
  244. package/src/__tests__/gpu-pass-timing-lifecycle.integration.test.ts +230 -0
  245. package/src/__tests__/gpu-pass-timing-observation.unit.test.ts +233 -0
  246. package/src/__tests__/gpu-pass-timing-session.unit.test.ts +438 -0
  247. package/src/__tests__/gpu-pass-timing.browser.test.ts +26 -0
  248. package/src/__tests__/gpu-pass-timing.rhi-null.unit.test.ts +125 -0
  249. package/src/__tests__/hdrp-group2-visibility.unit.test.ts +24 -0
  250. package/src/__tests__/instances-culling.integration.test.ts +94 -0
  251. package/src/__tests__/instances-derived-bounds.unit.test.ts +118 -0
  252. package/src/__tests__/ktx2-basis-gpu-consumer.dawn.test.ts +14 -14
  253. package/src/__tests__/material-observation.integration.test.ts +101 -0
  254. package/src/__tests__/mesh-buffer-usage-surface.unit.test.ts +1 -1
  255. package/src/__tests__/pbr-view-layout.unit.test.ts +16 -0
  256. package/src/__tests__/point-shadow-inspection.unit.test.ts +60 -0
  257. package/src/__tests__/public-surface.test-d.ts +10 -0
  258. package/src/__tests__/reflection-probe-authoring.unit.test.ts +71 -0
  259. package/src/__tests__/reflection-probe-filter.unit.test.ts +54 -0
  260. package/src/__tests__/reflection-probe-ibl-topology.unit.test.ts +45 -0
  261. package/src/__tests__/reflection-probe-pass-encode.integration.test.ts +101 -0
  262. package/src/__tests__/reflection-probe-performance.perf.test.ts +60 -0
  263. package/src/__tests__/reflection-probe-recovery.integration.test.ts +55 -0
  264. package/src/__tests__/reflection-probe-selection.unit.test.ts +46 -0
  265. package/src/__tests__/render-error-code-owner.test-d.ts +23 -3
  266. package/src/__tests__/render-feature-docs.unit.test.ts +1 -1
  267. package/src/__tests__/render-target-admission.unit.test.ts +80 -0
  268. package/src/__tests__/render-target-graph-projection.unit.test.ts +102 -0
  269. package/src/__tests__/render-target-inspection.unit.test.ts +52 -0
  270. package/src/__tests__/render-target-material-source.test-d.ts +30 -0
  271. package/src/__tests__/render-target-material-source.unit.test.ts +97 -0
  272. package/src/__tests__/render-target-owner-placement.unit.test.ts +203 -0
  273. package/src/__tests__/render-target-owner.unit.test.ts +122 -0
  274. package/src/__tests__/render-target-probe-soak.perf.test.ts +94 -0
  275. package/src/__tests__/render-target-public-schema.json +107 -0
  276. package/src/__tests__/render-target-public-schema.unit.test.ts +91 -0
  277. package/src/__tests__/render-target-public-surface.test-d.ts +83 -0
  278. package/src/__tests__/render-target-readback.integration.test.ts +90 -0
  279. package/src/__tests__/render-target-recovery.integration.test.ts +85 -0
  280. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +76 -0
  281. package/src/__tests__/renderer-frame-transaction.integration.test.ts +1 -1
  282. package/src/__tests__/renderer-lifecycle.integration.test.ts +14 -0
  283. package/src/__tests__/shader-stage-surface.unit.test.ts +19 -4
  284. package/src/__tests__/skin-motion-regression.unit.test.ts +132 -0
  285. package/src/__tests__/skinned-shadow-caster.test.ts +32 -2
  286. package/src/__tests__/standard-cluster-capability.integration.test.ts +63 -0
  287. package/src/__tests__/standard-cluster-transport.unit.test.ts +121 -0
  288. package/src/__tests__/standard-cluster-variant-resolution.unit.test.ts +232 -0
  289. package/src/__tests__/standard-clustered-pipeline.unit.test.ts +8 -5
  290. package/src/__tests__/standard-lighting-inspection.integration.test.ts +138 -0
  291. package/src/__tests__/standard-lighting-ssot.unit.test.ts +18 -0
  292. package/src/__tests__/standard-lighting-write.unit.test.ts +377 -0
  293. package/src/__tests__/standard-lighting.integration.test.ts +67 -0
  294. package/src/__tests__/standard-pipeline.dawn.test.ts +206 -5
  295. package/src/__tests__/standard-pipeline.integration.test.ts +181 -150
  296. package/src/__tests__/standard-probe-binding.unit.test.ts +25 -0
  297. package/src/__tests__/standard-profile-oracle.unit.test.ts +23 -36
  298. package/src/__tests__/typed-pipeline-topology.unit.test.ts +395 -14
  299. package/src/__tests__/urp-pipeline-targets.unit.test.ts +2 -2
  300. package/src/__tests__/visibility-resolver.unit.test.ts +15 -0
  301. package/src/__tests__/volume-optics-oracle.unit.test.ts +85 -0
  302. package/src/__tests__/volumetric-fog-budget-recovery.integration.test.ts +655 -0
  303. package/src/__tests__/volumetric-fog-component.unit.test.ts +71 -0
  304. package/src/__tests__/volumetric-fog-composite-stage-readback.dawn.test.ts +376 -0
  305. package/src/__tests__/volumetric-fog-errors.unit.test.ts +51 -0
  306. package/src/__tests__/volumetric-fog-inspection.unit.test.ts +77 -0
  307. package/src/__tests__/volumetric-fog-light-capability.unit.test.ts +19 -0
  308. package/src/__tests__/volumetric-fog-light-consumers.integration.test.ts +18 -0
  309. package/src/__tests__/volumetric-fog-light-errors.unit.test.ts +17 -0
  310. package/src/__tests__/volumetric-fog-light-topology.unit.test.ts +21 -0
  311. package/src/__tests__/volumetric-fog-light-transaction.integration.test.ts +53 -0
  312. package/src/__tests__/volumetric-fog-m9-watcher.integration.test.ts +27 -0
  313. package/src/__tests__/volumetric-fog-point-light-owner.integration.test.ts +44 -0
  314. package/src/__tests__/volumetric-fog-point-spot-accumulation.integration.test.ts +49 -0
  315. package/src/__tests__/volumetric-fog-point-spot-contract.unit.test.ts +28 -0
  316. package/src/__tests__/volumetric-fog-point-spot-lkg.integration.test.ts +65 -0
  317. package/src/__tests__/volumetric-fog-point-spot-selection.integration.test.ts +49 -0
  318. package/src/__tests__/volumetric-fog-point-spot-temporal.unit.test.ts +55 -0
  319. package/src/__tests__/volumetric-fog-projector-contract.unit.test.ts +22 -0
  320. package/src/__tests__/volumetric-fog-projector-inspection.unit.test.ts +12 -0
  321. package/src/__tests__/volumetric-fog-projector-surface-volume.integration.test.ts +11 -0
  322. package/src/__tests__/volumetric-fog-projector-transaction.integration.test.ts +26 -0
  323. package/src/__tests__/volumetric-fog-recovery.integration.test.ts +47 -0
  324. package/src/__tests__/volumetric-fog-selected-light-resolution.integration.test.ts +63 -0
  325. package/src/__tests__/volumetric-fog-spot-closure.integration.test.ts +26 -0
  326. package/src/__tests__/volumetric-fog-spot-contract.test-d.ts +21 -0
  327. package/src/__tests__/volumetric-fog-spot-contract.unit.test.ts +27 -0
  328. package/src/__tests__/volumetric-fog-spot-frame-optics.integration.test.ts +14 -0
  329. package/src/__tests__/volumetric-fog-spot-optics.integration.test.ts +29 -0
  330. package/src/__tests__/volumetric-fog-stage-readback.dawn.test.ts +550 -0
  331. package/src/__tests__/volumetric-fog-temporal-selected-light.unit.test.ts +33 -0
  332. package/src/__tests__/volumetric-fog-temporal.unit.test.ts +45 -0
  333. package/src/__tests__/volumetric-fog-topology.unit.test.ts +237 -0
  334. package/src/__tests__/volumetric-fog-transparent.integration.test.ts +27 -0
  335. package/src/assembly/device-feature-admission.ts +73 -0
  336. package/src/assembly/directional-shadow-inspection.ts +122 -0
  337. package/src/assembly/factory.ts +1093 -246
  338. package/src/assembly/host-contract.ts +25 -0
  339. package/src/assembly/render-target-host.ts +847 -0
  340. package/src/assets/asset-decoders.ts +1 -1
  341. package/src/assets/render-asset-port.ts +7 -1
  342. package/src/capture/cube-views.ts +69 -0
  343. package/src/capture/scheduler.ts +265 -0
  344. package/src/components/__tests__/schema-contract.unit.test.ts +12 -0
  345. package/src/components/__tests__/schema-parity.unit.test.ts +9 -0
  346. package/src/components/camera.ts +4 -0
  347. package/src/components/cube-camera.ts +52 -0
  348. package/src/components/directional-light.ts +22 -2
  349. package/src/components/directional-shadow-filter.ts +57 -0
  350. package/src/components/index.ts +2 -0
  351. package/src/components/light-helpers.ts +85 -7
  352. package/src/components/reflection-probe.ts +49 -0
  353. package/src/components/spot-light.ts +22 -3
  354. package/src/device/gpu-residency.ts +168 -58
  355. package/src/environment/frame.ts +6 -24
  356. package/src/environment/lifecycle.ts +12 -0
  357. package/src/errors/render.ts +405 -143
  358. package/src/extract/directional-shadow-projection.ts +61 -0
  359. package/src/extract/environment.ts +57 -0
  360. package/src/extract/visibility.ts +25 -1
  361. package/src/features/fullscreen.ts +3 -0
  362. package/src/features/targets.ts +4 -1
  363. package/src/features/types.ts +10 -0
  364. package/src/fullscreen-post-process-pass.ts +43 -2
  365. package/src/gpu-driven/batch-topology.ts +1 -0
  366. package/src/gpu-driven/production-raster.ts +1 -3
  367. package/src/gpu-scene.ts +8 -1
  368. package/src/gpu-usage.ts +2 -0
  369. package/src/hdrp-buffers.ts +165 -59
  370. package/src/ibl/IblPipelineCache.ts +11 -0
  371. package/src/ibl/kernel-cache.ts +88 -0
  372. package/src/index.ts +186 -1
  373. package/src/inspection-types.ts +56 -0
  374. package/src/instances-derived-bounds.ts +171 -0
  375. package/src/light-buffer-layout.ts +74 -26
  376. package/src/mesh-material-bindings.ts +132 -0
  377. package/src/pbr-pipeline.ts +139 -27
  378. package/src/pipeline/standard-forward-lane.ts +65 -15
  379. package/src/pipeline/standard-lighting/errors.ts +6 -0
  380. package/src/pipeline/standard-lighting/graph.ts +157 -0
  381. package/src/pipeline/standard-lighting/inspection.ts +88 -0
  382. package/src/pipeline/standard-lighting/layout.ts +31 -0
  383. package/src/pipeline/standard-lighting/light-frame.ts +34 -0
  384. package/src/pipeline/standard-lighting/membership.ts +43 -0
  385. package/src/pipeline/standard-lighting/prepare.ts +37 -0
  386. package/src/pipeline/standard-lighting/topology.ts +120 -0
  387. package/src/pipeline/standard-lighting/transport.ts +77 -0
  388. package/src/pipeline/standard-pipeline.ts +141 -192
  389. package/src/pipeline/standard-post.ts +26 -7
  390. package/src/pipeline/standard-profile.ts +23 -30
  391. package/src/pipeline-spec.ts +84 -21
  392. package/src/point-shadow-inspection.ts +55 -0
  393. package/src/record/__tests__/temporal-payload.unit.test.ts +42 -1
  394. package/src/record/__tests__/transparent-group2-selection.unit.test.ts +73 -0
  395. package/src/record/__tests__/view-ubo-layout.unit.test.ts +40 -0
  396. package/src/record/frame-lighting.ts +470 -516
  397. package/src/record/frame-snapshot.ts +76 -28
  398. package/src/record/frame-targets.ts +23 -2
  399. package/src/record/frame.ts +1169 -79
  400. package/src/record/gpu-pass-timing/contract.ts +180 -0
  401. package/src/record/gpu-pass-timing/errors.ts +34 -0
  402. package/src/record/gpu-pass-timing/index.ts +37 -0
  403. package/src/record/gpu-pass-timing/parser.ts +97 -0
  404. package/src/record/gpu-pass-timing/session.ts +938 -0
  405. package/src/record/gpu-timing.ts +233 -0
  406. package/src/record/main-pass-geometry.ts +120 -80
  407. package/src/record/main-pass-material.ts +101 -11
  408. package/src/record/main-pass-sprite-draws.ts +54 -25
  409. package/src/record/main-pass.ts +71 -15
  410. package/src/record/render-context.ts +77 -0
  411. package/src/record/shadow-pass.ts +104 -66
  412. package/src/record/skybox-post-pass.ts +20 -4
  413. package/src/record/typed-frame-graph.ts +1016 -32
  414. package/src/record/view-ubo.ts +63 -28
  415. package/src/reflection/filter.ts +81 -0
  416. package/src/reflection/gpu-table.ts +44 -0
  417. package/src/reflection/projection.ts +281 -0
  418. package/src/render-contract.ts +147 -4
  419. package/src/render-data.ts +50 -22
  420. package/src/render-graph-primitives.ts +9 -4
  421. package/src/render-pipeline.ts +161 -2
  422. package/src/render-system-extract.ts +886 -103
  423. package/src/render-system.ts +1174 -66
  424. package/src/renderer-inspect.ts +66 -0
  425. package/src/renderer.ts +222 -0
  426. package/src/scene/render-scene.ts +117 -9
  427. package/src/shadow-atlas.ts +5 -5
  428. package/src/ssao-buffers.ts +9 -2
  429. package/src/targets/contracts.ts +182 -0
  430. package/src/targets/graph-projection.ts +169 -0
  431. package/src/targets/material-source.ts +143 -0
  432. package/src/targets/owner.ts +214 -0
  433. package/src/targets/physical.ts +18 -0
  434. package/src/targets/readback.ts +153 -0
  435. package/src/temporal/frame.ts +128 -0
  436. package/src/transmission/__tests__/standard-transmission.dawn.test.ts +61 -23
  437. package/src/typed-render-graph-primitives.ts +34 -2
  438. package/src/typed-shadow-atlas.ts +17 -0
  439. package/src/typed-shadow-passes.ts +47 -40
  440. package/src/volume/capability.ts +173 -0
  441. package/src/volume/component.ts +110 -0
  442. package/src/volume/extract.ts +21 -0
  443. package/src/volume/inspection.ts +78 -0
  444. package/src/volume/passes.ts +686 -0
  445. package/src/volume/recovery.ts +86 -0
  446. package/src/volume/resources.ts +236 -0
  447. package/src/volume/temporal.ts +89 -0
  448. package/dist/chunk-IPXCABEP.mjs.map +0 -1
  449. package/dist/chunk-LOSO4CTV.mjs.map +0 -1
  450. package/dist/chunk-RJRUS55L.mjs.map +0 -1
  451. package/dist/chunk-YTQ5C7YQ.mjs +0 -584
  452. package/dist/chunk-YTQ5C7YQ.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-LOSO4CTV.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-IPXCABEP.mjs';
5
- import { ok, err as err$1, toShared, ASSET_ERROR_HINTS, derive, 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';
4
+ import { worldEntityKey } from './chunk-BXBQQKFQ.mjs';
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 } 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';
10
- import { resolveAssetHandle, numMipLevels } from '@forgeax/engine-assets-runtime';
9
+ import { deriveVertexLayoutProjection, deriveVertexBufferLayout } from '@forgeax/engine-geometry';
10
+ import { resolveAssetHandle } from '@forgeax/engine-assets-runtime';
11
11
  import { probeVideoHighPerfUpload, VIDEO_ELEMENT_PROVIDER_KEY } from '@forgeax/engine-graphics-extras';
12
- import { blockParamsForFormat, isCompressedFormat, bytesPerRow } from '@forgeax/engine-codec';
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,225 @@ 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 BYTES_PER_LIGHT_SLOT = 64;
482
- var LightSlotKind = {
483
- POINT: 0,
484
- SPOT: 1
485
- };
486
- var STORAGE_BUFFER_MIN_REQUIRED = 4;
487
- var POINT_LIGHT_STD430_BYTES = 32;
488
- var SPOT_LIGHT_STD430_BYTES = 64;
489
- var SPOT_LIGHT_SHADOW_TILE_SENTINEL = -1;
490
- var LIGHT_ARRAY_HEADER_BYTES = 16;
491
- var LIGHT_ARRAY_MAX_SLOTS = 4;
492
- var POINT_LIGHT_SHADOW_LAYER_SENTINEL = -1;
493
- function packPointLight(snap) {
494
- const out = new Float32Array(8);
495
- out[0] = snap.position[0] ?? 0;
496
- out[1] = snap.position[1] ?? 0;
497
- out[2] = snap.position[2] ?? 0;
498
- out[3] = snap.invRangeSquared;
499
- out[4] = snap.color[0] ?? 0;
500
- out[5] = snap.color[1] ?? 0;
501
- out[6] = snap.color[2] ?? 0;
502
- const i32 = new Int32Array(out.buffer);
503
- i32[7] = snap.shadowAtlasLayer ?? POINT_LIGHT_SHADOW_LAYER_SENTINEL;
504
- return out;
505
- }
506
- function packSpotLight(snap) {
507
- const out = new Float32Array(16);
508
- out[0] = snap.position[0] ?? 0;
509
- out[1] = snap.position[1] ?? 0;
510
- out[2] = snap.position[2] ?? 0;
511
- out[3] = snap.invRangeSquared;
512
- out[4] = snap.color[0] ?? 0;
513
- out[5] = snap.color[1] ?? 0;
514
- out[6] = snap.color[2] ?? 0;
515
- out[7] = snap.cosInner;
516
- out[8] = snap.direction[0] ?? 0;
517
- out[9] = snap.direction[1] ?? 0;
518
- out[10] = snap.direction[2] ?? 0;
519
- out[11] = snap.cosOuter;
520
- const i32 = new Int32Array(out.buffer);
521
- i32[15] = snap.shadowAtlasTile ?? SPOT_LIGHT_SHADOW_TILE_SENTINEL;
522
- return out;
523
- }
524
- function packLightArrayHeader(count) {
525
- const buf = new ArrayBuffer(LIGHT_ARRAY_HEADER_BYTES);
526
- const u32 = new Uint32Array(buf);
527
- u32[0] = count >>> 0;
528
- return buf;
529
- }
530
- function packLightSlot(snap, shadow) {
531
- const out = new Float32Array(16);
532
- out[0] = snap.position[0] ?? 0;
533
- out[1] = snap.position[1] ?? 0;
534
- out[2] = snap.position[2] ?? 0;
535
- out[3] = snap.invRangeSquared;
536
- out[4] = snap.color[0] ?? 0;
537
- out[5] = snap.color[1] ?? 0;
538
- out[6] = snap.color[2] ?? 0;
539
- out[7] = snap.kind === "point" ? 1 : snap.cosInner;
540
- out[8] = snap.kind === "point" ? 0 : snap.direction[0] ?? 0;
541
- out[9] = snap.kind === "point" ? 0 : snap.direction[1] ?? 0;
542
- out[10] = snap.kind === "point" ? 0 : snap.direction[2] ?? 0;
543
- out[11] = snap.kind === "point" ? 0 : snap.cosOuter;
544
- const u32 = new Uint32Array(out.buffer);
545
- u32[12] = snap.kind === "point" ? LightSlotKind.POINT : LightSlotKind.SPOT;
546
- const i32 = new Int32Array(out.buffer);
547
- if (snap.kind === "point" && shadow !== void 0) {
548
- i32[13] = shadow.shadowAtlasLayer;
549
- out[14] = shadow.near;
550
- out[15] = shadow.far;
551
- } else if (snap.kind === "spot") {
552
- i32[13] = snap.shadowAtlasTile;
553
- } else {
554
- i32[13] = POINT_LIGHT_SHADOW_LAYER_SENTINEL;
555
- }
556
- return out;
557
- }
558
- function assertStorageBufferCap(cap) {
559
- if (cap >= STORAGE_BUFFER_MIN_REQUIRED) {
560
- return ok$1(true);
561
- }
562
- if (cap === 0) {
563
- return ok$1(false);
564
- }
565
- return err(
566
- new RhiError({
567
- code: "limit-exceeded",
568
- expected: `device.limits.maxStorageBuffersPerShaderStage >= ${STORAGE_BUFFER_MIN_REQUIRED}, or exactly 0 for uniform-fallback path`,
569
- 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.`,
570
- detail: {
571
- maxStorageBufferBindingSize: cap,
572
- requestedBytes: STORAGE_BUFFER_MIN_REQUIRED
573
- }
574
- })
575
- );
576
- }
577
- var BGL_ONLY_SPEC_STUB = Object.freeze({
578
- shader: { id: "", passKind: "forward", variantSet: void 0 },
579
- attachments: { colorFormats: [], depthFormat: void 0, sampleCount: 1 },
580
- geometry: { topology: "triangle-list", vertexLayout: {} },
581
- renderState: void 0
582
- });
583
- function buildPbrMaterialUserRegionEntries(paramSchema = DEFAULT_STANDARD_PBR_PARAM_SCHEMA) {
584
- const derived = derive(paramSchema);
585
- const entries = derived.bglEntries.map(
586
- (entry) => ({
587
- binding: entry.binding,
588
- visibility: entry.visibility,
589
- ...entry.buffer === void 0 ? {} : { buffer: entry.buffer },
590
- ...entry.sampler === void 0 ? {} : { sampler: entry.sampler },
591
- ...entry.texture === void 0 ? {} : { texture: entry.texture },
592
- ...entry.storageTexture === void 0 ? {} : { storageTexture: entry.storageTexture }
593
- })
594
- );
595
- const ubo = entries[0];
596
- if (ubo !== void 0 && ubo.binding === 0 && ubo.buffer !== void 0) {
597
- entries[0] = {
598
- binding: 0,
599
- visibility: GPU_SHADER_STAGE_VERTEX | GPU_SHADER_STAGE_FRAGMENT,
600
- buffer: { type: "uniform", hasDynamicOffset: true }
601
- };
602
- }
603
- return entries;
604
- }
605
- function appendInjection(bgl, kind) {
606
- const start = bgl.length;
607
- switch (kind) {
608
- case "ibl":
609
- return [
610
- // binding start+0: irradianceMap (texture_cube)
611
- {
612
- binding: start,
613
- visibility: GPU_SHADER_STAGE_FRAGMENT,
614
- texture: { sampleType: "float", viewDimension: "cube" }
615
- },
616
- // binding start+1: irradianceSampler
617
- {
618
- binding: start + 1,
619
- visibility: GPU_SHADER_STAGE_FRAGMENT,
620
- sampler: { type: "filtering" }
621
- },
622
- // binding start+2: prefilterMap (texture_cube)
623
- {
624
- binding: start + 2,
625
- visibility: GPU_SHADER_STAGE_FRAGMENT,
626
- texture: { sampleType: "float", viewDimension: "cube" }
627
- },
628
- // binding start+3: prefilterSampler
629
- {
630
- binding: start + 3,
631
- visibility: GPU_SHADER_STAGE_FRAGMENT,
632
- sampler: { type: "filtering" }
633
- },
634
- // binding start+4: brdfLut (texture_2d)
635
- {
636
- binding: start + 4,
637
- visibility: GPU_SHADER_STAGE_FRAGMENT,
638
- texture: { sampleType: "float", viewDimension: "2d" }
639
- },
640
- // binding start+5: brdfLutSampler
641
- {
642
- binding: start + 5,
643
- visibility: GPU_SHADER_STAGE_FRAGMENT,
644
- sampler: { type: "filtering" }
645
- },
646
- // binding start+6: uniform { intensity: f32 }
647
- {
648
- binding: start + 6,
649
- visibility: GPU_SHADER_STAGE_FRAGMENT,
650
- buffer: { type: "uniform" }
651
- }
652
- ];
653
- case "lightmap":
654
- return [
655
- // emissive sampler + texture pair
656
- {
657
- binding: start,
658
- visibility: GPU_SHADER_STAGE_FRAGMENT,
659
- sampler: { type: "filtering" }
660
- },
661
- {
662
- binding: start + 1,
663
- visibility: GPU_SHADER_STAGE_FRAGMENT,
664
- texture: { sampleType: "float", viewDimension: "2d" }
665
- },
666
- // occlusion sampler + texture pair
667
- {
668
- binding: start + 2,
669
- visibility: GPU_SHADER_STAGE_FRAGMENT,
670
- sampler: { type: "filtering" }
671
- },
672
- {
673
- binding: start + 3,
674
- visibility: GPU_SHADER_STAGE_FRAGMENT,
675
- texture: { sampleType: "float", viewDimension: "2d" }
676
- }
677
- ];
678
- case "shadow":
679
- return [
680
- {
681
- binding: start,
682
- visibility: GPU_SHADER_STAGE_FRAGMENT,
683
- sampler: { type: "comparison" }
684
- },
685
- {
686
- binding: start + 1,
687
- visibility: GPU_SHADER_STAGE_FRAGMENT,
688
- texture: { sampleType: "depth", viewDimension: "2d" }
689
- }
690
- ];
691
- case "transmission":
692
- return [
693
- {
694
- binding: start,
695
- visibility: GPU_SHADER_STAGE_FRAGMENT,
696
- sampler: { type: "filtering" }
697
- },
698
- {
699
- binding: start + 1,
700
- visibility: GPU_SHADER_STAGE_FRAGMENT,
701
- texture: { sampleType: "float", viewDimension: "2d" }
702
- }
703
- ];
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
+ ];
704
294
  }
705
295
  }
706
296
  function buildPbrViewBglEntries(caps) {
707
297
  const lightBufType = caps.storageBuffer ? "read-only-storage" : "uniform";
708
- return [
298
+ const entries = [
709
299
  {
710
300
  binding: 0,
711
301
  visibility: GPU_SHADER_STAGE_VERTEX | GPU_SHADER_STAGE_FRAGMENT,
712
- 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 }
713
305
  },
714
306
  {
715
307
  binding: 1,
@@ -797,13 +389,28 @@ function buildPbrViewBglEntries(caps) {
797
389
  binding: 8,
798
390
  visibility: GPU_SHADER_STAGE_FRAGMENT,
799
391
  texture: { sampleType: "depth", viewDimension: "2d" }
800
- },
801
- {
802
- binding: 10,
803
- visibility: GPU_SHADER_STAGE_VERTEX,
804
- buffer: { type: "uniform", hasDynamicOffset: true }
805
392
  }
806
393
  ];
394
+ if (caps.projectorAvailable !== false) {
395
+ entries.push(
396
+ {
397
+ binding: 11,
398
+ visibility: GPU_SHADER_STAGE_FRAGMENT,
399
+ texture: { sampleType: "float", viewDimension: "2d" }
400
+ },
401
+ {
402
+ binding: 12,
403
+ visibility: GPU_SHADER_STAGE_FRAGMENT,
404
+ sampler: { type: "filtering" }
405
+ }
406
+ );
407
+ }
408
+ entries.push({
409
+ binding: 10,
410
+ visibility: GPU_SHADER_STAGE_VERTEX,
411
+ buffer: { type: "uniform", hasDynamicOffset: true }
412
+ });
413
+ return entries;
807
414
  }
808
415
  function buildPbrPipelineLayouts(device, caps) {
809
416
  const viewBglRes = device.createBindGroupLayout(
@@ -849,8 +456,17 @@ function shadowCasterVariantSet(storageBuffer, skinned) {
849
456
  if (storageBuffer && skinningDisabled) return "";
850
457
  return `SKINNING_DISABLED=${String(skinningDisabled)}+STORAGE_BUFFER_AVAILABLE=${String(storageBuffer)}`;
851
458
  }
852
- function isSkinnedShadowCasterVariant(materialShaderId, variantSet) {
853
- 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";
854
470
  }
855
471
  function isStandardPbrMaterialShader(shaderId) {
856
472
  return shaderId === "forgeax::default-standard-pbr" || shaderId === SKIN_MATERIAL_SHADER_ID || shaderId === "forgeax::default-standard-pbr-skin";
@@ -883,16 +499,43 @@ function buildPbrSkinLayouts(device, caps, pbr) {
883
499
  bindGroupLayouts: layouts
884
500
  };
885
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
+ }
886
524
  var SPRITE_PASS_PER_INSTANCE_REGION_VARIANT_SET = "";
887
- function createHdrpBindGroupLayoutDescriptor(storageBuffer = true) {
888
- const meshBufType = storageBuffer ? "read-only-storage" : "uniform";
889
- const clusterBufType = storageBuffer ? "read-only-storage" : "uniform";
525
+ function createHdrpBindGroupLayoutDescriptor() {
526
+ const meshBufType = "read-only-storage";
527
+ const clusterBufType = "read-only-storage";
890
528
  return {
891
529
  label: "hdrp-unified-bgl-group2",
892
530
  entries: [
893
531
  {
894
532
  binding: 0,
895
- 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,
896
539
  buffer: { type: meshBufType, hasDynamicOffset: true }
897
540
  },
898
541
  // Bindings 1 and 2 stay absent for the URP physical isolation gap.
@@ -929,6 +572,31 @@ function createHdrpBindGroupLayoutDescriptor(storageBuffer = true) {
929
572
  ]
930
573
  };
931
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
+ }
932
600
  function resolveMaterialParamSchema(spec, options) {
933
601
  if (options.materialParamSchema !== void 0) return options.materialParamSchema;
934
602
  if (options.registry !== void 0) {
@@ -993,6 +661,11 @@ function buildBindGroupLayoutDescriptor(spec, options) {
993
661
  binding: 1,
994
662
  visibility: GPU_SHADER_STAGE_VERTEX,
995
663
  buffer: { type: meshBufType, hasDynamicOffset: true }
664
+ },
665
+ {
666
+ binding: 2,
667
+ visibility: GPU_SHADER_STAGE_VERTEX,
668
+ buffer: { type: meshBufType, hasDynamicOffset: true }
996
669
  }
997
670
  ]
998
671
  };
@@ -1015,8 +688,7 @@ function buildBindGroupLayoutDescriptor(spec, options) {
1015
688
  };
1016
689
  }
1017
690
  case "hdrp-7-slot": {
1018
- const caps = options.caps ?? { storageBuffer: true };
1019
- const desc = createHdrpBindGroupLayoutDescriptor(caps.storageBuffer);
691
+ const desc = createHdrpBindGroupLayoutDescriptor();
1020
692
  return {
1021
693
  label: desc.label ?? "hdrp-unified-bgl-group2",
1022
694
  entries: [...desc.entries ?? []]
@@ -1107,8 +779,6 @@ function buildFullscreenPostInputEntries(spec) {
1107
779
  }
1108
780
  ];
1109
781
  }
1110
-
1111
- // src/pipeline-spec.ts
1112
782
  var DEFERRED_COLOR_FORMATS = [
1113
783
  "rgba16float",
1114
784
  "rgba8unorm",
@@ -1206,6 +876,24 @@ function variantSetFromVertexLayoutProjection(projection, authoredVariantSet) {
1206
876
  parts.push(axis);
1207
877
  return ok(parts.join("+"));
1208
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
+ }
1209
897
  function buildPipelineDescriptor(spec, modules) {
1210
898
  const { attachments, geometry, renderState } = spec;
1211
899
  const vertexBuffers = geometry.vertexLayoutProjection === void 0 ? deriveVertexBufferLayout(geometry.vertexLayout, {
@@ -1528,7 +1216,7 @@ var TRI_GEOMETRY_PBR = {
1528
1216
  vertexLayout: PROCEDURAL_ATTR_LAYOUT,
1529
1217
  shaderUvSetCount: 8
1530
1218
  };
1531
- var URP_PBR_VARIANT_SET = "CLUSTER_FORWARD_AVAILABLE=false+STORAGE_BUFFER_AVAILABLE=true";
1219
+ var STANDARD_BOOT_VARIANT_SET = standardCapabilityVariantSet(false, true, false);
1532
1220
  function buildSpecConstTable(ldrViewFormat) {
1533
1221
  const TRI_ATTACHMENTS_LDR_S1 = {
1534
1222
  colorFormats: [ldrViewFormat],
@@ -1607,19 +1295,19 @@ function buildSpecConstTable(ldrViewFormat) {
1607
1295
  geometry: TRI_GEOMETRY_PBR,
1608
1296
  renderState: void 0
1609
1297
  },
1610
- // ── M6 fix-up: URP-variant standard-pbr prewarm ─────────────────────────
1611
- // URP record path (`getMaterialShaderPipeline`) requests
1612
- // `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
1613
1301
  // the silent `selectStandardFallbackPipeline` shim served the no-variant
1614
1302
  // boot prewarm during the 1-frame async-compile warmup. With M6's
1615
- // explicit-failure surface, those URP requests must hit the boot prewarm
1303
+ // explicit-failure surface, those requests must hit the boot prewarm
1616
1304
  // by their own variantSet — these 4 entries close that gap.
1617
1305
  // standard PBR URP LDR S1
1618
1306
  {
1619
1307
  shader: {
1620
1308
  id: "forgeax::default-standard-pbr",
1621
1309
  passKind: "forward",
1622
- variantSet: URP_PBR_VARIANT_SET
1310
+ variantSet: STANDARD_BOOT_VARIANT_SET
1623
1311
  },
1624
1312
  attachments: TRI_ATTACHMENTS_LDR_S1,
1625
1313
  geometry: TRI_GEOMETRY_PBR,
@@ -1630,7 +1318,7 @@ function buildSpecConstTable(ldrViewFormat) {
1630
1318
  shader: {
1631
1319
  id: "forgeax::default-standard-pbr",
1632
1320
  passKind: "forward",
1633
- variantSet: URP_PBR_VARIANT_SET
1321
+ variantSet: STANDARD_BOOT_VARIANT_SET
1634
1322
  },
1635
1323
  attachments: TRI_ATTACHMENTS_LDR_S4,
1636
1324
  geometry: TRI_GEOMETRY_PBR,
@@ -1641,7 +1329,7 @@ function buildSpecConstTable(ldrViewFormat) {
1641
1329
  shader: {
1642
1330
  id: "forgeax::default-standard-pbr",
1643
1331
  passKind: "forward",
1644
- variantSet: URP_PBR_VARIANT_SET
1332
+ variantSet: STANDARD_BOOT_VARIANT_SET
1645
1333
  },
1646
1334
  attachments: TRI_ATTACHMENTS_HDR_S1,
1647
1335
  geometry: TRI_GEOMETRY_PBR,
@@ -1652,7 +1340,7 @@ function buildSpecConstTable(ldrViewFormat) {
1652
1340
  shader: {
1653
1341
  id: "forgeax::default-standard-pbr",
1654
1342
  passKind: "forward",
1655
- variantSet: URP_PBR_VARIANT_SET
1343
+ variantSet: STANDARD_BOOT_VARIANT_SET
1656
1344
  },
1657
1345
  attachments: TRI_ATTACHMENTS_HDR_S4,
1658
1346
  geometry: TRI_GEOMETRY_PBR,
@@ -2078,7 +1766,8 @@ var UNIFORM_BYTES = 256;
2078
1766
  var fallbackCache = /* @__PURE__ */ new WeakMap();
2079
1767
  function getOrCreateSsaoFallbackTexture(runtime) {
2080
1768
  const cached = fallbackCache.get(runtime);
2081
- 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);
2082
1771
  const device = runtime.device;
2083
1772
  const texRes = device.createTexture({
2084
1773
  label: "hdrp-ssao-fallback-white",
@@ -2137,6 +1826,7 @@ function getOrCreateSsaoFallbackTexture(runtime) {
2137
1826
  return null;
2138
1827
  }
2139
1828
  const resources = {
1829
+ device,
2140
1830
  texture: texRes.value,
2141
1831
  view: viewRes.value,
2142
1832
  sampler: samplerRes.value
@@ -2147,7 +1837,8 @@ function getOrCreateSsaoFallbackTexture(runtime) {
2147
1837
  var cache = /* @__PURE__ */ new WeakMap();
2148
1838
  function getOrCreateSsaoBuffers(runtime) {
2149
1839
  const cached = cache.get(runtime);
2150
- 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);
2151
1842
  const device = runtime.device;
2152
1843
  const kernelSamples = generateSsaoKernel();
2153
1844
  const kernelData = new Float32Array(SSAO_KERNEL_SAMPLE_COUNT * 4);
@@ -2215,391 +1906,794 @@ function getOrCreateSsaoBuffers(runtime) {
2215
1906
  usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
2216
1907
  mappedAtCreation: false
2217
1908
  });
2218
- if (!uniformBufRes.ok) {
2219
- runtime.errorRegistry.fire(uniformBufRes.error);
1909
+ if (!uniformBufRes.ok) {
1910
+ runtime.errorRegistry.fire(uniformBufRes.error);
1911
+ return null;
1912
+ }
1913
+ const buffers = {
1914
+ device,
1915
+ kernelBuffer: kernelBufferRes.value,
1916
+ kernelBytes: SSAO_KERNEL_SAMPLE_COUNT * BYTES_PER_KERNEL_ELEMENT,
1917
+ noiseTexture: noiseTexRes.value,
1918
+ uniformBuffer: uniformBufRes.value,
1919
+ uniformBytes: UNIFORM_BYTES
1920
+ };
1921
+ cache.set(runtime, buffers);
1922
+ return buffers;
1923
+ }
1924
+
1925
+ // src/hdrp-buffers.ts
1926
+ function createHdrpClusterMembershipBindGroupLayoutDescriptor() {
1927
+ return {
1928
+ label: "hdrp-cluster-membership-bgl",
1929
+ entries: [
1930
+ {
1931
+ binding: 0,
1932
+ visibility: GPU_SHADER_STAGE_COMPUTE,
1933
+ buffer: { type: "read-only-storage", hasDynamicOffset: false }
1934
+ },
1935
+ {
1936
+ binding: 1,
1937
+ visibility: GPU_SHADER_STAGE_COMPUTE,
1938
+ buffer: { type: "storage", hasDynamicOffset: false }
1939
+ },
1940
+ {
1941
+ binding: 2,
1942
+ visibility: GPU_SHADER_STAGE_COMPUTE,
1943
+ buffer: { type: "uniform", hasDynamicOffset: false }
1944
+ },
1945
+ {
1946
+ binding: 3,
1947
+ visibility: GPU_SHADER_STAGE_COMPUTE,
1948
+ buffer: { type: "read-only-storage", hasDynamicOffset: false }
1949
+ }
1950
+ ]
1951
+ };
1952
+ }
1953
+ var cache2 = /* @__PURE__ */ new WeakMap();
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
+ }
1960
+ const cached = cache2.get(runtime);
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
+ }
1966
+ const device = runtime.device;
1967
+ const clusterUniformBytes = 32;
1968
+ const lightDataBytes = MAX_LIGHTS * BYTES_PER_LIGHT_SLOT;
1969
+ const clusterCells = grid.x * grid.y * grid.z;
1970
+ const clusterGridBytes = clusterCells * CLUSTER_GRID_STRIDE_U32 * 4;
1971
+ const lightIndexListBytes = LIGHT_INDEX_LIST_CAPACITY * 4;
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
+ };
1986
+ const lightData = device.createBuffer({
1987
+ label: "hdrp-light-data",
1988
+ size: lightDataBytes,
1989
+ usage: GPU_BUFFER_USAGE_STORAGE | GPU_BUFFER_USAGE_COPY_DST,
1990
+ mappedAtCreation: false
1991
+ });
1992
+ if (!lightData.ok) {
1993
+ return failAllocation(lightData.error);
1994
+ }
1995
+ created.push(lightData.value);
1996
+ const clusterGrid = device.createBuffer({
1997
+ label: "hdrp-cluster-grid",
1998
+ size: clusterGridBytes,
1999
+ usage: GPU_BUFFER_USAGE_STORAGE | GPU_BUFFER_USAGE_COPY_DST,
2000
+ mappedAtCreation: false
2001
+ });
2002
+ if (!clusterGrid.ok) {
2003
+ return failAllocation(clusterGrid.error);
2004
+ }
2005
+ created.push(clusterGrid.value);
2006
+ const lightIndexList = device.createBuffer({
2007
+ label: "hdrp-light-index-list",
2008
+ size: lightIndexListBytes,
2009
+ usage: GPU_BUFFER_USAGE_STORAGE | GPU_BUFFER_USAGE_COPY_DST,
2010
+ mappedAtCreation: false
2011
+ });
2012
+ if (!lightIndexList.ok) {
2013
+ return failAllocation(lightIndexList.error);
2014
+ }
2015
+ created.push(lightIndexList.value);
2016
+ const clusterUniform = device.createBuffer({
2017
+ label: "hdrp-cluster-uniform",
2018
+ size: clusterUniformBytes,
2019
+ usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
2020
+ mappedAtCreation: false
2021
+ });
2022
+ if (!clusterUniform.ok) {
2023
+ return failAllocation(clusterUniform.error);
2024
+ }
2025
+ created.push(clusterUniform.value);
2026
+ const lightBounds = device.createBuffer({
2027
+ label: "hdrp-light-bounds",
2028
+ size: lightBoundsBytes,
2029
+ usage: GPU_BUFFER_USAGE_STORAGE | GPU_BUFFER_USAGE_COPY_DST,
2030
+ mappedAtCreation: false
2031
+ });
2032
+ if (!lightBounds.ok) {
2033
+ return failAllocation(lightBounds.error);
2034
+ }
2035
+ created.push(lightBounds.value);
2036
+ const unifiedBglRes = device.createBindGroupLayout(createHdrpBindGroupLayoutDescriptor());
2037
+ if (!unifiedBglRes.ok) {
2038
+ return failAllocation(unifiedBglRes.error);
2039
+ }
2040
+ const buffers = {
2041
+ device,
2042
+ lightDataBuffer: lightData.value,
2043
+ lightDataBytes,
2044
+ clusterGridBuffer: clusterGrid.value,
2045
+ clusterGridBytes,
2046
+ lightIndexListBuffer: lightIndexList.value,
2047
+ lightIndexListBytes,
2048
+ clusterUniformBuffer: clusterUniform.value,
2049
+ clusterUniformBytes,
2050
+ lightBoundsBuffer: lightBounds.value,
2051
+ lightBoundsBytes,
2052
+ grid: { x: grid.x, y: grid.y, z: grid.z },
2053
+ unifiedBindGroupLayout: unifiedBglRes.value
2054
+ };
2055
+ cache2.set(runtime, buffers);
2056
+ if (cached !== void 0) {
2057
+ retireHdrpBuffers(runtime, cached);
2058
+ }
2059
+ return buffers;
2060
+ }
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) {
2089
+ const buf = new ArrayBuffer(32);
2090
+ const u32 = new Uint32Array(buf);
2091
+ const f32 = new Float32Array(buf);
2092
+ u32[0] = grid.x >>> 0;
2093
+ u32[1] = grid.y >>> 0;
2094
+ u32[2] = grid.z >>> 0;
2095
+ u32[3] = Math.min(Math.max(lightCount, 0), MAX_LIGHTS) >>> 0;
2096
+ f32[4] = near;
2097
+ f32[5] = far;
2098
+ f32[6] = near > 0 && far > near ? Math.log(far / near) : 0;
2099
+ f32[7] = ssaoIntensity;
2100
+ return buf;
2101
+ }
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);
2220
2205
  return null;
2221
2206
  }
2222
- const buffers = {
2223
- kernelBuffer: kernelBufferRes.value,
2224
- kernelBytes: SSAO_KERNEL_SAMPLE_COUNT * BYTES_PER_KERNEL_ELEMENT,
2225
- noiseTexture: noiseTexRes.value,
2226
- uniformBuffer: uniformBufRes.value,
2227
- uniformBytes: UNIFORM_BYTES
2228
- };
2229
- cache.set(runtime, buffers);
2230
- return buffers;
2207
+ return result.value;
2231
2208
  }
2232
-
2233
- // src/hdrp-buffers.ts
2234
- var HDRP_UNIFORM_LIGHT_CAPACITY = 128;
2235
- function createHdrpClusterMembershipBindGroupLayoutDescriptor() {
2236
- return {
2237
- label: "hdrp-cluster-membership-bgl",
2209
+ function createHdrpClusterMembershipBindGroup(runtime, hdrpBuffers, layout) {
2210
+ const result = runtime.device.createBindGroup({
2211
+ label: "hdrp-cluster-membership-bg",
2212
+ layout,
2238
2213
  entries: [
2239
2214
  {
2240
2215
  binding: 0,
2241
- visibility: GPU_SHADER_STAGE_COMPUTE,
2242
- buffer: { type: "read-only-storage", hasDynamicOffset: false }
2216
+ resource: { kind: "buffer", value: { buffer: hdrpBuffers.clusterGridBuffer } }
2243
2217
  },
2244
2218
  {
2245
2219
  binding: 1,
2246
- visibility: GPU_SHADER_STAGE_COMPUTE,
2247
- buffer: { type: "storage", hasDynamicOffset: false }
2220
+ resource: { kind: "buffer", value: { buffer: hdrpBuffers.lightIndexListBuffer } }
2248
2221
  },
2249
2222
  {
2250
2223
  binding: 2,
2251
- visibility: GPU_SHADER_STAGE_COMPUTE,
2252
- buffer: { type: "uniform", hasDynamicOffset: false }
2224
+ resource: { kind: "buffer", value: { buffer: hdrpBuffers.clusterUniformBuffer } }
2253
2225
  },
2254
2226
  {
2255
2227
  binding: 3,
2256
- visibility: GPU_SHADER_STAGE_COMPUTE,
2257
- buffer: { type: "read-only-storage", hasDynamicOffset: false }
2228
+ resource: { kind: "buffer", value: { buffer: hdrpBuffers.lightBoundsBuffer } }
2258
2229
  }
2259
2230
  ]
2260
- };
2261
- }
2262
- var cache2 = /* @__PURE__ */ new WeakMap();
2263
- function getOrCreateHdrpBuffers(runtime, grid = DEFAULT_CLUSTER_GRID) {
2264
- const cached = cache2.get(runtime);
2265
- if (cached !== void 0) return cached;
2266
- const device = runtime.device;
2267
- const storageBuffer = runtime.device.caps?.storageBuffer === true;
2268
- const clusterUniformBytes = 32;
2269
- const lightDataBytes = (storageBuffer ? MAX_LIGHTS : HDRP_UNIFORM_LIGHT_CAPACITY) * BYTES_PER_LIGHT_SLOT;
2270
- const clusterCells = grid.x * grid.y * grid.z;
2271
- const clusterGridBytes = storageBuffer ? clusterCells * CLUSTER_GRID_STRIDE_U32 * 4 : clusterUniformBytes;
2272
- const lightIndexListBytes = storageBuffer ? LIGHT_INDEX_LIST_CAPACITY * 4 : clusterUniformBytes;
2273
- const lightBoundsBytes = MAX_LIGHTS * 6 * 4;
2274
- const lightData = device.createBuffer({
2275
- label: "hdrp-light-data",
2276
- size: lightDataBytes,
2277
- usage: (storageBuffer ? GPU_BUFFER_USAGE_STORAGE : GPU_BUFFER_USAGE_UNIFORM) | GPU_BUFFER_USAGE_COPY_DST,
2278
- mappedAtCreation: false
2279
2231
  });
2280
- if (!lightData.ok) {
2281
- runtime.errorRegistry.fire(lightData.error);
2232
+ if (!result.ok) {
2233
+ runtime.errorRegistry.fire(result.error);
2282
2234
  return null;
2283
2235
  }
2284
- const clusterGrid = device.createBuffer({
2285
- label: "hdrp-cluster-grid",
2286
- size: clusterGridBytes,
2287
- usage: (storageBuffer ? GPU_BUFFER_USAGE_STORAGE : GPU_BUFFER_USAGE_UNIFORM) | GPU_BUFFER_USAGE_COPY_DST,
2288
- mappedAtCreation: false
2289
- });
2290
- if (!clusterGrid.ok) {
2291
- runtime.errorRegistry.fire(clusterGrid.error);
2292
- return null;
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);
2293
2261
  }
2294
- const lightIndexList = device.createBuffer({
2295
- label: "hdrp-light-index-list",
2296
- size: lightIndexListBytes,
2297
- usage: (storageBuffer ? GPU_BUFFER_USAGE_STORAGE : GPU_BUFFER_USAGE_UNIFORM) | GPU_BUFFER_USAGE_COPY_DST,
2298
- mappedAtCreation: false
2299
- });
2300
- if (!lightIndexList.ok) {
2301
- runtime.errorRegistry.fire(lightIndexList.error);
2302
- return null;
2262
+ if (scratch.lightBounds.length < lightCount * 6) {
2263
+ scratch.lightBounds = new Int32Array(lightCount * 6);
2303
2264
  }
2304
- const clusterUniform = device.createBuffer({
2305
- label: "hdrp-cluster-uniform",
2306
- size: clusterUniformBytes,
2307
- usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
2308
- mappedAtCreation: false
2309
- });
2310
- if (!clusterUniform.ok) {
2311
- runtime.errorRegistry.fire(clusterUniform.error);
2312
- return null;
2265
+ const differenceVolumeSize = (gridX + 1) * (gridY + 1) * (gridZ + 1);
2266
+ if (scratch.clusterDeltas.length < differenceVolumeSize) {
2267
+ scratch.clusterDeltas = new Int32Array(differenceVolumeSize);
2313
2268
  }
2314
- const lightBounds = device.createBuffer({
2315
- label: "hdrp-light-bounds",
2316
- size: lightBoundsBytes,
2317
- usage: (storageBuffer ? GPU_BUFFER_USAGE_STORAGE : GPU_BUFFER_USAGE_UNIFORM) | GPU_BUFFER_USAGE_COPY_DST,
2318
- mappedAtCreation: false
2319
- });
2320
- if (!lightBounds.ok) {
2321
- runtime.errorRegistry.fire(lightBounds.error);
2322
- return null;
2269
+ }
2270
+ function deriveCullingRadius(range, intensity, threshold = 1e-3) {
2271
+ if (Number.isFinite(range)) {
2272
+ return Math.max(0, range);
2323
2273
  }
2324
- const storageBufferCap = runtime.device.caps?.storageBuffer === true;
2325
- const unifiedBglRes = device.createBindGroupLayout(
2326
- createHdrpBindGroupLayoutDescriptor(storageBufferCap)
2327
- );
2328
- if (!unifiedBglRes.ok) {
2329
- runtime.errorRegistry.fire(unifiedBglRes.error);
2330
- return null;
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];
2331
2421
  }
2332
- const buffers = {
2333
- storageBuffer,
2334
- lightDataBuffer: lightData.value,
2335
- lightDataBytes,
2336
- clusterGridBuffer: clusterGrid.value,
2337
- clusterGridBytes,
2338
- lightIndexListBuffer: lightIndexList.value,
2339
- lightIndexListBytes,
2340
- clusterUniformBuffer: clusterUniform.value,
2341
- clusterUniformBytes,
2342
- lightBoundsBuffer: lightBounds.value,
2343
- lightBoundsBytes,
2344
- grid: { x: grid.x, y: grid.y, z: grid.z },
2345
- unifiedBindGroupLayout: unifiedBglRes.value
2346
- };
2347
- cache2.set(runtime, buffers);
2348
- return buffers;
2422
+ const invW = 1 / cw;
2423
+ return [cx * invW, cy * invW, cz * invW];
2349
2424
  }
2350
- function packClusterUniform(grid, near, far, ssaoIntensity = 0, lightCount = 0, lightCapacity = HDRP_UNIFORM_LIGHT_CAPACITY) {
2351
- const buf = new ArrayBuffer(32);
2352
- const u32 = new Uint32Array(buf);
2353
- const f32 = new Float32Array(buf);
2354
- u32[0] = grid.x >>> 0;
2355
- u32[1] = grid.y >>> 0;
2356
- u32[2] = grid.z >>> 0;
2357
- u32[3] = Math.min(Math.max(lightCount, 0), lightCapacity) >>> 0;
2358
- f32[4] = near;
2359
- f32[5] = far;
2360
- f32[6] = near > 0 && far > near ? Math.log(far / near) : 0;
2361
- f32[7] = ssaoIntensity;
2362
- return buf;
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 };
2363
2436
  }
2364
- function createHdrpUnifiedBindGroup(runtime, hdrpBuffers, meshStorageBuffer, ssaoOptions = { enabled: false }) {
2365
- const fallback = getOrCreateSsaoFallbackTexture(runtime);
2366
- if (fallback === null) return null;
2367
- const ssaoTexView = ssaoOptions.enabled ? ssaoOptions.ssaoBlurredView : fallback.view;
2368
- const ssaoSampler = fallback.sampler;
2369
- const result = runtime.device.createBindGroup({
2370
- label: "hdrp-unified-bg-group2",
2371
- layout: hdrpBuffers.unifiedBindGroupLayout,
2372
- entries: [
2373
- {
2374
- binding: 0,
2375
- resource: {
2376
- kind: "buffer",
2377
- value: {
2378
- buffer: meshStorageBuffer,
2379
- offset: 0,
2380
- size: hdrpBuffers.storageBuffer ? MESH_PER_ENTITY_STRIDE : MESH_UBO_FULL_ARRAY_BYTES
2381
- }
2382
- }
2383
- },
2384
- {
2385
- binding: 3,
2386
- resource: {
2387
- kind: "buffer",
2388
- value: { buffer: hdrpBuffers.lightDataBuffer }
2389
- }
2390
- },
2391
- {
2392
- binding: 4,
2393
- resource: {
2394
- kind: "buffer",
2395
- value: { buffer: hdrpBuffers.clusterGridBuffer }
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;
2396
2475
  }
2397
- },
2398
- {
2399
- binding: 5,
2400
- resource: {
2401
- kind: "buffer",
2402
- 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;
2403
2494
  }
2404
- },
2405
- {
2406
- binding: 6,
2407
- resource: {
2408
- kind: "buffer",
2409
- 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
+ }
2410
2552
  }
2411
- },
2412
- {
2413
- binding: 7,
2414
- resource: { kind: "textureView", value: ssaoTexView }
2415
- },
2416
- {
2417
- binding: 8,
2418
- resource: { kind: "sampler", value: ssaoSampler }
2419
2553
  }
2420
- ]
2421
- });
2422
- if (!result.ok) {
2423
- runtime.errorRegistry.fire(result.error);
2424
- return null;
2554
+ });
2555
+ });
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
+ });
2563
+ }
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;
2574
+ });
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
+ }
2610
+ });
2425
2611
  }
2426
- return result.value;
2612
+ return ok$1(writeCount);
2427
2613
  }
2428
- function createHdrpClusterMembershipBindGroup(runtime, hdrpBuffers, layout) {
2429
- const result = runtime.device.createBindGroup({
2430
- label: "hdrp-cluster-membership-bg",
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,
2431
2658
  layout,
2432
- entries: [
2433
- {
2434
- binding: 0,
2435
- resource: { kind: "buffer", value: { buffer: hdrpBuffers.clusterGridBuffer } }
2436
- },
2437
- {
2438
- binding: 1,
2439
- resource: { kind: "buffer", value: { buffer: hdrpBuffers.lightIndexListBuffer } }
2440
- },
2441
- {
2442
- binding: 2,
2443
- resource: { kind: "buffer", value: { buffer: hdrpBuffers.clusterUniformBuffer } }
2444
- },
2445
- {
2446
- binding: 3,
2447
- resource: { kind: "buffer", value: { buffer: hdrpBuffers.lightBoundsBuffer } }
2448
- }
2449
- ]
2659
+ membershipLightCount: frame.local.length,
2660
+ renderPath: frame.renderPath,
2661
+ maxLights: frame.lightCount
2450
2662
  });
2451
- if (!result.ok) {
2452
- runtime.errorRegistry.fire(result.error);
2453
- return null;
2454
- }
2455
- return result.value;
2456
2663
  }
2457
- var SHADOW_ATLAS_DEFAULT_FACE_SIZE = 512;
2458
- var SHADOW_ATLAS_DEFAULT_LAYERS = 4;
2459
- var ShadowAtlas = class {
2460
- /** Backing cube_array depth texture. `null` until `ensure()` has run. */
2461
- texture = null;
2462
- /**
2463
- * Whole-atlas cube_array sampling view used by URP shaders that bind the
2464
- * atlas at `@group(0) @binding(5)`. `null` until `ensure()` has run.
2465
- */
2466
- cubeArrayView = null;
2467
- /** Comparison sampler for `texture_depth_cube_array`. */
2468
- compareSampler = null;
2469
- /** Per-face 2D depth view cache, keyed by `layer * 6 + face`. */
2470
- faceViews = /* @__PURE__ */ new Map();
2471
- /** Per-face label cache (debug aid). Reused on re-creation. */
2472
- device;
2473
- /** Per-face square size in pixels. */
2474
- faceSize;
2475
- /** Cube layer count. */
2476
- layers;
2477
- constructor(device, options = {}) {
2478
- this.device = device;
2479
- this.faceSize = options.faceSize ?? SHADOW_ATLAS_DEFAULT_FACE_SIZE;
2480
- this.layers = options.layers ?? SHADOW_ATLAS_DEFAULT_LAYERS;
2481
- }
2482
- /**
2483
- * Returns `true` once the atlas texture is allocated. Used by the extract /
2484
- * record path to gate "no PointLightShadow → no allocation" (AC-09) — the
2485
- * caller calls {@link ensure} on a frame that has at least one snapshot;
2486
- * before that, `isAllocated()` stays `false` and tests can assert no GPU
2487
- * resources were created.
2488
- */
2489
- isAllocated() {
2490
- return this.texture !== null;
2491
- }
2492
- /**
2493
- * Lazy-allocate the atlas texture + cube_array sampling view + comparison
2494
- * sampler. Idempotent: subsequent calls are zero-cost no-ops once the
2495
- * resources exist.
2496
- *
2497
- * Throws (via the RHI error registry) on allocation failure. This is the
2498
- * only path that touches the GPU — keeping it inside one method makes the
2499
- * "zero-shadow scene = zero allocation" gate easy to enforce: the renderer
2500
- * never calls this method on a frame with no PointLightShadow entities.
2501
- */
2502
- ensure() {
2503
- if (this.texture !== null) return;
2504
- const usage = GPU_TEXTURE_USAGE_RENDER_ATTACHMENT_AND_TEXTURE_BINDING;
2505
- const texDesc = cubeArrayDepthDescriptor(this.faceSize, this.layers, usage);
2506
- const texRes = this.device.createTexture(texDesc);
2507
- if (!texRes.ok) throw texRes.error;
2508
- this.texture = texRes.value;
2509
- const viewRes = this.device.createTextureView(this.texture, {
2510
- label: "shadow-atlas-cube-array-view",
2511
- dimension: "cube-array",
2512
- aspect: "depth-only",
2513
- baseArrayLayer: 0,
2514
- arrayLayerCount: 6 * this.layers,
2515
- baseMipLevel: 0,
2516
- mipLevelCount: 1
2517
- });
2518
- if (!viewRes.ok) {
2519
- this.device.destroyTexture(this.texture);
2520
- this.texture = null;
2521
- throw viewRes.error;
2522
- }
2523
- this.cubeArrayView = viewRes.value;
2524
- const sampRes = this.device.createSampler(comparisonSamplerDescriptor());
2525
- if (!sampRes.ok) {
2526
- this.device.destroyTexture(this.texture);
2527
- this.texture = null;
2528
- this.cubeArrayView = null;
2529
- throw sampRes.error;
2530
- }
2531
- this.compareSampler = sampRes.value;
2532
- }
2533
- /**
2534
- * Whole-atlas cube_array sampling view (`texture_depth_cube_array`). Returns
2535
- * `null` if the atlas is not yet allocated; the caller should gate on a
2536
- * non-null pointShadow array before calling {@link ensure} + this getter.
2537
- */
2538
- getAtlasView() {
2539
- return this.cubeArrayView;
2540
- }
2541
- /**
2542
- * Comparison sampler (`compare: 'less'`) for cube_array depth sampling.
2543
- * Returns `null` if the atlas is not yet allocated.
2544
- */
2545
- getComparisonSampler() {
2546
- return this.compareSampler;
2547
- }
2548
- /**
2549
- * Backing texture handle. Returns `null` if the atlas is not yet allocated.
2550
- */
2551
- getTexture() {
2552
- return this.texture;
2553
- }
2554
- /**
2555
- * Lazy 2D depth view selecting `(layer, face)` (the caller passes
2556
- * `shadowAtlasLayer` from the snapshot and a face index 0..5). Used as the
2557
- * depth attachment of the shadow-caster render pass for one cube face.
2558
- *
2559
- * Caches per `(layer, face)` so the per-frame shadow loop pays zero
2560
- * `createTextureView` cost after the first allocation. Cache is cleared on
2561
- * {@link dispose}.
2562
- *
2563
- * @throws PointShadowAtlasUninitializedError if the atlas is not yet
2564
- * allocated (caller must call {@link ensure} first).
2565
- * @throws PointShadowAtlasBoundsViolationError if `layer` or `face` is out
2566
- * of range.
2567
- * @throws RhiError if the underlying `createTextureView` fails.
2568
- */
2569
- faceView(layer, face) {
2570
- if (this.texture === null) {
2571
- throw new PointShadowAtlasUninitializedError();
2572
- }
2573
- if (layer < 0 || layer >= this.layers) {
2574
- throw new PointShadowAtlasBoundsViolationError("layer", layer, this.layers);
2575
- }
2576
- if (face < 0 || face >= 6) {
2577
- throw new PointShadowAtlasBoundsViolationError("face", face, 6);
2578
- }
2579
- const key = layer * 6 + face;
2580
- const cached = this.faceViews.get(key);
2581
- if (cached !== void 0) return cached;
2582
- const desc = cubeArrayDepthFaceView(layer, face);
2583
- const r = this.device.createTextureView(this.texture, desc);
2584
- if (!r.ok) throw r.error;
2585
- this.faceViews.set(key, r.value);
2586
- return r.value;
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;
2587
2668
  }
2588
- /**
2589
- * Release the GPU resources and clear the per-face view cache. Idempotent;
2590
- * subsequent calls are zero-cost. After dispose, `isAllocated()` returns
2591
- * `false` and the next `ensure()` re-allocates from scratch.
2592
- */
2593
- dispose() {
2594
- this.faceViews.clear();
2595
- if (this.texture !== null) {
2596
- this.device.destroyTexture(this.texture);
2597
- this.texture = null;
2598
- }
2599
- this.cubeArrayView = null;
2600
- 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
+ );
2601
2680
  }
2602
- };
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
+ }
2603
2697
 
2604
2698
  // src/post-process-errors.ts
2605
2699
  var POST_PROCESS_POLICY = {
@@ -2722,38 +2816,6 @@ function warnMultiLightDirectional(frameState, directionalCount, envOverride) {
2722
2816
  }
2723
2817
  }
2724
2818
  }
2725
- function warnMultiLightPoint(frameState, pointCount, envOverride) {
2726
- if (!frameState.warnedMultiLightPoint && pointCount > 4) {
2727
- frameState.warnedMultiLightPoint = true;
2728
- const env = globalThis.process;
2729
- if (env?.env?.NODE_ENV !== "production") {
2730
- console.warn(
2731
- "[forgeax] render-system-multi-light point: at most 4 entities (got N=" + pointCount + "). First 4 used; rest dropped.",
2732
- {
2733
- code: "render-system-multi-light",
2734
- expected: "at most 4 point",
2735
- detail: { type: "point", got: pointCount }
2736
- }
2737
- );
2738
- }
2739
- }
2740
- }
2741
- function warnMultiLightSpot(frameState, spotCount, envOverride) {
2742
- if (!frameState.warnedMultiLightSpot && spotCount > 4) {
2743
- frameState.warnedMultiLightSpot = true;
2744
- const env = globalThis.process;
2745
- if (env?.env?.NODE_ENV !== "production") {
2746
- console.warn(
2747
- "[forgeax] render-system-multi-light spot: at most 4 entities (got N=" + spotCount + "). First 4 used; rest dropped.",
2748
- {
2749
- code: "render-system-multi-light",
2750
- expected: "at most 4 spot",
2751
- detail: { type: "spot", got: spotCount }
2752
- }
2753
- );
2754
- }
2755
- }
2756
- }
2757
2819
  function warnMultiSkylight(frameState, skylightCount, winningEntityHandle) {
2758
2820
  if (!frameState.warnedMultiSkylight && skylightCount > 1) {
2759
2821
  frameState.warnedMultiSkylight = true;
@@ -2820,10 +2882,32 @@ var POINTS_LINES_VIEW_SLOT_COUNT = 1024;
2820
2882
  var POINTS_LINES_VIEW_BUFFER_SIZE = POINTS_LINES_VIEW_SLOT_STRIDE * POINTS_LINES_VIEW_SLOT_COUNT;
2821
2883
  var VIEW_UNIFORM_SLOT_STRIDE = 1024;
2822
2884
  var POINT_SHADOW_VIEW_SLOT_COUNT = 24;
2823
- 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);
2824
2889
  function pointShadowViewOffset(layer, face) {
2825
2890
  return VIEW_UNIFORM_SLOT_STRIDE * (1 + layer * 6 + face);
2826
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
+ }
2827
2911
  function writePointsLinesViewUbo(queue, buffer, camera, width, height, model, style, byteOffset = 0) {
2828
2912
  const projection = computeProjectionMatrix(camera);
2829
2913
  const view = computeViewMatrix(camera);
@@ -2855,7 +2939,9 @@ function writePointsLinesViewUbo(queue, buffer, camera, width, height, model, st
2855
2939
  const uploaded = queue.writeBuffer(buffer, byteOffset, payload);
2856
2940
  if (!uploaded.ok) throw uploaded.error;
2857
2941
  }
2858
- 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;
2859
2945
  const isTemporal = fogOrTemporal !== void 0 && ("currentJitterUv" in fogOrTemporal || "currentUnjitteredViewProjection" in fogOrTemporal);
2860
2946
  const fog = isTemporal ? void 0 : fogOrTemporal;
2861
2947
  const resolvedTemporal = temporal ?? (isTemporal ? fogOrTemporal : void 0);
@@ -2911,14 +2997,20 @@ function writeViewUbo(queue, viewUniformBuffer, camera, light, lights, spotShado
2911
2997
  viewPayload[230] = camera.projection === "orthographic" ? 1 : 0;
2912
2998
  if (lights.splitPlanes !== void 0) {
2913
2999
  for (let s = 0; s < 4; s++) {
2914
- 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
+ }
2915
3003
  }
2916
3004
  }
2917
3005
  viewPayload[124] = lights.cascadeCount ?? 0;
2918
3006
  viewPayload[125] = lights.cascadeBlend ?? 0;
2919
3007
  viewPayload[126] = lights.depthBias ?? 5e-3;
2920
3008
  viewPayload[127] = lights.normalBias ?? 0.05;
2921
- 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;
2922
3014
  {
2923
3015
  const SPOT_LVP_BASE_FLOAT = 132;
2924
3016
  const SPOT_LVP_LANE_COUNT = 4;
@@ -2928,10 +3020,11 @@ function writeViewUbo(queue, viewUniformBuffer, camera, light, lights, spotShado
2928
3020
  const ss = spotSnaps[i];
2929
3021
  if (ss === void 0) continue;
2930
3022
  const tile = ss.shadowAtlasTile;
2931
- if (tile < 0 || tile >= SPOT_LVP_LANE_COUNT) continue;
2932
3023
  const lvp = ss.lightViewProj;
2933
3024
  if (lvp === void 0) continue;
2934
- 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;
2935
3028
  for (let f = 0; f < SPOT_LVP_FLOATS_PER_LANE; f++) {
2936
3029
  viewPayload[base + f] = lvp[f] ?? 0;
2937
3030
  }
@@ -2945,10 +3038,11 @@ function writeViewUbo(queue, viewUniformBuffer, camera, light, lights, spotShado
2945
3038
  viewPayload[236] = fog.heightFalloff;
2946
3039
  viewPayload[237] = fog.maxOpacity;
2947
3040
  }
2948
- const viewUploadResult = queue.writeBuffer(viewUniformBuffer, 0, viewPayload);
3041
+ const viewUploadResult = queue.writeBuffer(viewUniformBuffer, byteOffset, viewPayload);
2949
3042
  if (!viewUploadResult.ok) throw viewUploadResult.error;
2950
3043
  for (const snapshot of lights.pointShadow ?? []) {
2951
- if (snapshot.shadowAtlasLayer < 0 || snapshot.shadowAtlasLayer >= 4) continue;
3044
+ if (snapshot.shadowAtlasLayer < 0 || snapshot.shadowAtlasLayer >= SHADOW_ATLAS_DEFAULT_LAYERS)
3045
+ continue;
2952
3046
  for (let face = 0; face < 6; face += 1) {
2953
3047
  const facePayload = viewPayload.slice();
2954
3048
  const matrix = snapshot.shadowMatrices.subarray(face * 16, (face + 1) * 16);
@@ -2964,14 +3058,42 @@ function writeViewUbo(queue, viewUniformBuffer, camera, light, lights, spotShado
2964
3058
  }
2965
3059
 
2966
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
+ }
2967
3070
  function runHdrpClusterProfilePhase(runner, phase, action) {
2968
3071
  return runner === void 0 ? action() : runner(phase, action);
2969
3072
  }
2970
- 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) {
2971
3085
  let viewBindGroup = null;
2972
3086
  let meshBindGroup = null;
2973
3087
  let hdrpClusterBindGroup = null;
2974
- 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;
2975
3097
  if (hasValidated) {
2976
3098
  if (includeView) {
2977
3099
  const shadowSampler = pipelineState.perPassResources.shadowSampler;
@@ -2990,6 +3112,9 @@ function buildPerFrameBindGroups(internals, frameState, pipelineState, hasValida
2990
3112
  const b5View = atlasViewMaybe !== null ? atlasViewMaybe : pipelineState.shadowAtlasFallbackTextureView;
2991
3113
  const graphSpotShadowView = graphTargets?.spotShadow;
2992
3114
  const b8View = graphSpotShadowView !== void 0 ? graphSpotShadowView : pipelineState.shadowFallbackTextureView;
3115
+ const projectorAvailable = pipelineState.projectorAvailable !== false;
3116
+ const b11View = graphTargets?.projector ?? pipelineState.defaultWhiteTextureView;
3117
+ const b12Sampler = graphTargets?.projectorSampler ?? pipelineState.defaultSampler;
2993
3118
  viewBindGroup = getOrCreateFromChain(
2994
3119
  frameState.viewBindGroupCache,
2995
3120
  [
@@ -3001,110 +3126,116 @@ function buildPerFrameBindGroups(internals, frameState, pipelineState, hasValida
3001
3126
  b5View,
3002
3127
  pipelineState.shadowParamsBuffer,
3003
3128
  b8View,
3129
+ ...projectorAvailable ? [b11View, b12Sampler] : [],
3004
3130
  pipelineState.pointsLinesViewBuffer ?? pipelineState.viewUniformBuffer
3005
3131
  ],
3006
3132
  "view-main",
3007
3133
  () => {
3008
- const viewBindGroupResult = internals.device.createBindGroup({
3009
- label: "pbr-view-bg",
3010
- layout: pipelineState.viewBindGroupLayout,
3011
- entries: [
3012
- {
3013
- binding: 0,
3014
- resource: {
3015
- kind: "buffer",
3016
- value: { buffer: pipelineState.viewUniformBuffer, size: VIEW_UNIFORM_BYTES }
3017
- }
3018
- },
3019
- {
3020
- binding: 1,
3021
- resource: {
3022
- kind: "buffer",
3023
- value: { buffer: pipelineState.pointLightsBuffer }
3024
- }
3025
- },
3026
- {
3027
- binding: 2,
3028
- resource: {
3029
- kind: "buffer",
3030
- value: { buffer: pipelineState.spotLightsBuffer }
3031
- }
3032
- },
3033
- {
3034
- binding: 3,
3035
- resource: {
3036
- kind: "textureView",
3037
- value: b3View
3038
- }
3039
- },
3040
- {
3041
- binding: 4,
3042
- resource: {
3043
- kind: "sampler",
3044
- value: shadowSampler
3045
- }
3046
- },
3047
- // feat-20260612-point-light-shadows-urp-hdrp Round-2 F-1:
3048
- // cube_array shadow atlas view (real ShadowAtlas when point
3049
- // shadows are active; else 1x1x6 fallback).
3050
- {
3051
- binding: 5,
3052
- resource: {
3053
- kind: "textureView",
3054
- value: b5View
3055
- }
3056
- },
3057
- // feat-20260612-point-light-shadows-urp-hdrp Round-2 F-1:
3058
- // shadowParams UBO (`array<vec4<f32>, 4>` = 64 B). Lane N
3059
- // stores `(near, far, 1/(far-near), 0)` for the point light
3060
- // with shadowAtlasLayer === N. Updated per frame from
3061
- // `pointShadowSnapshots` below.
3062
- {
3063
- binding: 6,
3064
- resource: {
3065
- kind: "buffer",
3066
- value: { buffer: pipelineState.shadowParamsBuffer }
3067
- }
3068
- },
3069
- // feat-20260613-csm-cascaded-shadow-maps M5 / w28 (rebased to
3070
- // binding 7 on 2026-06-13 to make room for point-shadow 5/6):
3071
- // forward shaders declare binding 7 in common.wgsl (shared
3072
- // view BGL) but never reference it; only shadow_caster.wgsl
3073
- // reads it. Host writes a stable singleton buffer so every
3074
- // forward bind group entry stays populated.
3075
- {
3076
- binding: 7,
3077
- resource: {
3078
- kind: "buffer",
3079
- value: { buffer: pipelineState.shadowCasterCascadeBuffer }
3080
- }
3081
- },
3082
- // feat-20260625-spot-light-shadow-mapping M3 / w21 (D-5):
3083
- // spot shadow 2D atlas (real spotShadowDepth view when spot
3084
- // shadows run this frame, else the 1x1 depth fallback). Always-on.
3085
- {
3086
- binding: 8,
3087
- resource: {
3088
- kind: "textureView",
3089
- value: b8View
3090
- }
3091
- },
3092
- {
3093
- binding: 10,
3094
- resource: {
3095
- kind: "buffer",
3096
- value: {
3097
- buffer: pipelineState.pointsLinesViewBuffer ?? pipelineState.viewUniformBuffer,
3098
- size: POINTS_LINES_VIEW_BYTES
3099
- }
3134
+ const entries = [
3135
+ {
3136
+ binding: 0,
3137
+ resource: {
3138
+ kind: "buffer",
3139
+ value: { buffer: pipelineState.viewUniformBuffer, size: VIEW_UNIFORM_BYTES }
3140
+ }
3141
+ },
3142
+ {
3143
+ binding: 1,
3144
+ resource: {
3145
+ kind: "buffer",
3146
+ value: { buffer: pipelineState.pointLightsBuffer }
3147
+ }
3148
+ },
3149
+ {
3150
+ binding: 2,
3151
+ resource: {
3152
+ kind: "buffer",
3153
+ value: { buffer: pipelineState.spotLightsBuffer }
3154
+ }
3155
+ },
3156
+ {
3157
+ binding: 3,
3158
+ resource: {
3159
+ kind: "textureView",
3160
+ value: b3View
3161
+ }
3162
+ },
3163
+ {
3164
+ binding: 4,
3165
+ resource: {
3166
+ kind: "sampler",
3167
+ value: shadowSampler
3168
+ }
3169
+ },
3170
+ // feat-20260612-point-light-shadows-urp-hdrp Round-2 F-1:
3171
+ // cube_array shadow atlas view (real ShadowAtlas when point
3172
+ // shadows are active; else 1x1x6 fallback).
3173
+ {
3174
+ binding: 5,
3175
+ resource: {
3176
+ kind: "textureView",
3177
+ value: b5View
3178
+ }
3179
+ },
3180
+ // feat-20260612-point-light-shadows-urp-hdrp Round-2 F-1:
3181
+ // shadowParams UBO (`array<vec4<f32>, 4>` = 64 B). Lane N
3182
+ // stores `(near, far, 1/(far-near), 0)` for the point light
3183
+ // with shadowAtlasLayer === N. Updated per frame from
3184
+ // `pointShadowSnapshots` below.
3185
+ {
3186
+ binding: 6,
3187
+ resource: {
3188
+ kind: "buffer",
3189
+ value: { buffer: pipelineState.shadowParamsBuffer }
3190
+ }
3191
+ },
3192
+ // feat-20260613-csm-cascaded-shadow-maps M5 / w28 (rebased to
3193
+ // binding 7 on 2026-06-13 to make room for point-shadow 5/6):
3194
+ // forward shaders declare binding 7 in common.wgsl (shared
3195
+ // view BGL) but never reference it; only shadow_caster.wgsl
3196
+ // reads it. Host writes a stable singleton buffer so every
3197
+ // forward bind group entry stays populated.
3198
+ {
3199
+ binding: 7,
3200
+ resource: {
3201
+ kind: "buffer",
3202
+ value: { buffer: pipelineState.shadowCasterCascadeBuffer }
3203
+ }
3204
+ },
3205
+ // feat-20260625-spot-light-shadow-mapping M3 / w21 (D-5):
3206
+ // spot shadow 2D atlas (real spotShadowDepth view when spot
3207
+ // shadows run this frame, else the 1x1 depth fallback). Always-on.
3208
+ {
3209
+ binding: 8,
3210
+ resource: {
3211
+ kind: "textureView",
3212
+ value: b8View
3213
+ }
3214
+ },
3215
+ {
3216
+ binding: 10,
3217
+ resource: {
3218
+ kind: "buffer",
3219
+ value: {
3220
+ buffer: pipelineState.pointsLinesViewBuffer ?? pipelineState.viewUniformBuffer,
3221
+ size: POINTS_LINES_VIEW_BYTES
3100
3222
  }
3101
3223
  }
3102
- // feat-20260625-spot-light-shadow-mapping w25: the per-spot
3103
- // fragment-read lightViewProj matrices fold into the View UBO
3104
- // (binding 0, `view.spotLightViewProj`) no standalone binding 9
3105
- // (WebGL2 fragment uniform-buffer budget). binding 10 is the
3106
- // dedicated vertex-only Points/Lines viewport UBO.
3107
- ]
3224
+ },
3225
+ ...projectorAvailable ? [
3226
+ { binding: 11, resource: { kind: "textureView", value: b11View } },
3227
+ { binding: 12, resource: { kind: "sampler", value: b12Sampler } }
3228
+ ] : []
3229
+ // feat-20260625-spot-light-shadow-mapping w25: the per-spot
3230
+ // fragment-read lightViewProj matrices fold into the View UBO
3231
+ // (binding 0, `view.spotLightViewProj`) — no standalone binding 9
3232
+ // (WebGL2 fragment uniform-buffer budget). binding 10 is the
3233
+ // dedicated vertex-only Points/Lines viewport UBO.
3234
+ ];
3235
+ const viewBindGroupResult = internals.device.createBindGroup({
3236
+ label: "pbr-view-bg",
3237
+ layout: pipelineState.viewBindGroupLayout,
3238
+ entries
3108
3239
  });
3109
3240
  if (!viewBindGroupResult.ok) throw viewBindGroupResult.error;
3110
3241
  return viewBindGroupResult.value;
@@ -3140,11 +3271,8 @@ function buildPerFrameBindGroups(internals, frameState, pipelineState, hasValida
3140
3271
  },
3141
3272
  bindGroupCounts
3142
3273
  );
3143
- if (frameState.isHdrpActive) {
3144
- const hdrpBuffers = getOrCreateHdrpBuffers(
3145
- internals,
3146
- frameState.installedPipelineConfig?.clusterGrid
3147
- );
3274
+ if (clusteredLighting) {
3275
+ const hdrpBuffers = expectedHdrpBuffers;
3148
3276
  if (hdrpBuffers !== null) {
3149
3277
  hdrpClusterBindGroup = getOrCreateFromChain(
3150
3278
  frameState.meshBindGroupCache,
@@ -3178,27 +3306,19 @@ function buildPerFrameBindGroups(internals, frameState, pipelineState, hasValida
3178
3306
  }
3179
3307
  return { viewBindGroup, meshBindGroup, hdrpClusterBindGroup, hdrpClusterMembershipBindGroup };
3180
3308
  }
3181
- function prepareFrameLighting(internals, frameState, lights) {
3309
+ function prepareFrameLighting(internals, frameState, lights, camera, pipelineState) {
3182
3310
  if (lights.directionalCount > 1) {
3183
3311
  warnMultiLightDirectional(frameState, lights.directionalCount);
3184
3312
  }
3185
- if (!frameState.isHdrpActive) {
3186
- if (lights.point.length > LIGHT_ARRAY_MAX_SLOTS) {
3187
- warnMultiLightPoint(frameState, lights.point.length);
3188
- }
3189
- if (lights.spot.length > LIGHT_ARRAY_MAX_SLOTS) {
3190
- warnMultiLightSpot(frameState, lights.spot.length);
3191
- }
3192
- }
3193
3313
  frameState.pointShadowSnapshots = lights.pointShadow;
3194
3314
  frameState.spotShadowSnapshots = lights.spot;
3195
3315
  if (lights.pointShadow.length > 0) {
3196
3316
  if (frameState.pointShadowAtlas === null) {
3197
3317
  const firstSnap = lights.pointShadow[0];
3198
- const faceSize = firstSnap?.mapSize ?? 512;
3318
+ const faceSize = firstSnap?.mapSize ?? SHADOW_ATLAS_DEFAULT_FACE_SIZE;
3199
3319
  frameState.pointShadowAtlas = new ShadowAtlas(internals.device, {
3200
3320
  faceSize,
3201
- layers: 4
3321
+ layers: SHADOW_ATLAS_DEFAULT_LAYERS
3202
3322
  });
3203
3323
  }
3204
3324
  try {
@@ -3221,49 +3341,60 @@ function prepareFrameLighting(internals, frameState, lights) {
3221
3341
  intensity: 0
3222
3342
  };
3223
3343
  const totalLightCount = (directionalLight !== void 0 ? 1 : 0) + pointLights.length + spotLights.length;
3224
- return { light, pointLights, spotLights, totalLightCount };
3225
- }
3226
- function writePointSpotLightBuffers(internals, pipelineState, lights) {
3227
- const pointSlots = lights.point.length <= LIGHT_ARRAY_MAX_SLOTS ? lights.point : lights.point.slice(0, LIGHT_ARRAY_MAX_SLOTS);
3228
- const pointHeader = packLightArrayHeader(pointSlots.length);
3229
- const pointHeaderUpload = internals.device.queue.writeBuffer(
3230
- pipelineState.pointLightsBuffer,
3231
- 0,
3232
- new Uint8Array(pointHeader)
3233
- );
3234
- if (!pointHeaderUpload.ok) throw pointHeaderUpload.error;
3235
- for (let i = 0; i < pointSlots.length; i++) {
3236
- const slot = pointSlots[i];
3237
- if (slot === void 0) continue;
3238
- const packed = packPointLight(slot);
3239
- const offset = LIGHT_ARRAY_HEADER_BYTES + i * POINT_LIGHT_STD430_BYTES;
3240
- const writeRes = internals.device.queue.writeBuffer(
3241
- pipelineState.pointLightsBuffer,
3242
- offset,
3243
- packed
3244
- );
3245
- 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
+ });
3246
3381
  }
3247
- const spotSlots = lights.spot.length <= LIGHT_ARRAY_MAX_SLOTS ? lights.spot : lights.spot.slice(0, LIGHT_ARRAY_MAX_SLOTS);
3248
- const spotHeader = packLightArrayHeader(spotSlots.length);
3249
- const spotHeaderUpload = internals.device.queue.writeBuffer(
3250
- pipelineState.spotLightsBuffer,
3251
- 0,
3252
- 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
3253
3389
  );
3254
- if (!spotHeaderUpload.ok) throw spotHeaderUpload.error;
3255
- for (let i = 0; i < spotSlots.length; i++) {
3256
- const slot = spotSlots[i];
3257
- if (slot === void 0) continue;
3258
- const packed = packSpotLight(slot);
3259
- const offset = LIGHT_ARRAY_HEADER_BYTES + i * SPOT_LIGHT_STD430_BYTES;
3260
- const writeRes = internals.device.queue.writeBuffer(
3261
- pipelineState.spotLightsBuffer,
3262
- offset,
3263
- packed
3264
- );
3265
- if (!writeRes.ok) throw writeRes.error;
3266
- }
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
+ });
3267
3398
  }
3268
3399
  function writeShadowParamsBuffer(internals, frameState, pipelineState) {
3269
3400
  const SHADOW_PARAMS_LANE_COUNT = 4;
@@ -3306,191 +3437,81 @@ function warnZeroLightStandard(frameState, renderables, skylight, totalLightCoun
3306
3437
  }
3307
3438
  }
3308
3439
  }
3309
- function writeHdrpClusterAndSsaoBuffers(internals, frameState, camera, pointLights, spotLights, profilePhase, useGpuMembership = false, membershipBindGroupLayout = null) {
3310
- const hdrpLightCount = pointLights.length + spotLights.length;
3311
- if (!frameState.isHdrpActive) return;
3312
- if (hdrpLightCount === 0) {
3313
- const grid = frameState.installedPipelineConfig?.clusterGrid ?? DEFAULT_CLUSTER_GRID;
3314
- const hdrpBuffers = getOrCreateHdrpBuffers(internals, grid);
3315
- if (hdrpBuffers === null) return;
3316
- const lightCapacity = hdrpBuffers.storageBuffer ? 256 : HDRP_UNIFORM_LIGHT_CAPACITY;
3317
- const clusterSsaoConfig = frameState.installedPipelineConfig?.ssao;
3318
- const clusterSsaoIntensity = clusterSsaoConfig !== void 0 && clusterSsaoConfig.enabled === true ? clusterSsaoConfig.intensity ?? 1 : 0;
3319
- const clusterUniformPayload = packClusterUniform(
3320
- grid,
3321
- camera.near,
3322
- camera.far,
3323
- clusterSsaoIntensity,
3324
- 0,
3325
- lightCapacity
3326
- );
3327
- const clusterUniformUpload = internals.device.queue.writeBuffer(
3328
- hdrpBuffers.clusterUniformBuffer,
3329
- 0,
3330
- new Uint8Array(clusterUniformPayload)
3331
- );
3332
- if (!clusterUniformUpload.ok) internals.errorRegistry.fire(clusterUniformUpload.error);
3333
- const clusterGridClear = internals.device.queue.writeBuffer(
3334
- hdrpBuffers.clusterGridBuffer,
3335
- 0,
3336
- new Uint8Array(hdrpBuffers.clusterGridBytes)
3337
- );
3338
- if (!clusterGridClear.ok) internals.errorRegistry.fire(clusterGridClear.error);
3339
- const lightIndexListClear = internals.device.queue.writeBuffer(
3340
- hdrpBuffers.lightIndexListBuffer,
3341
- 0,
3342
- 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
+ )
3343
3449
  );
3344
- if (!lightIndexListClear.ok) internals.errorRegistry.fire(lightIndexListClear.error);
3345
- return;
3346
- }
3347
- const HDRP_LIGHT_BUDGET = 256;
3348
- let effectivePointLights = pointLights;
3349
- let effectiveSpotLights = spotLights;
3350
- if (hdrpLightCount > HDRP_LIGHT_BUDGET) {
3351
- if (!frameState.hdrpOncePerFrameFired.has("hdrp-light-budget-exceeded")) {
3352
- frameState.hdrpOncePerFrameFired.add("hdrp-light-budget-exceeded");
3353
- internals.errorRegistry.fire(
3354
- new HdrpLightBudgetExceededError(hdrpLightCount, HDRP_LIGHT_BUDGET)
3355
- );
3356
- }
3357
- if (pointLights.length >= HDRP_LIGHT_BUDGET) {
3358
- effectivePointLights = pointLights.slice(0, HDRP_LIGHT_BUDGET);
3359
- effectiveSpotLights = [];
3360
- } else {
3361
- effectivePointLights = pointLights;
3362
- effectiveSpotLights = spotLights.slice(0, HDRP_LIGHT_BUDGET - pointLights.length);
3363
- }
3364
- }
3365
- const effectiveLightCount = effectivePointLights.length + effectiveSpotLights.length;
3366
- const configuredClusterGrid = frameState.installedPipelineConfig?.clusterGrid ?? DEFAULT_CLUSTER_GRID;
3367
- let gpuMembership = useGpuMembership && membershipBindGroupLayout !== null;
3368
- if (gpuMembership && membershipBindGroupLayout !== null && frameState.hdrpClusterMembershipBindGroup === null) {
3369
- const hdrpBuffers = getOrCreateHdrpBuffers(internals, configuredClusterGrid);
3370
- if (hdrpBuffers !== null) {
3371
- frameState.hdrpClusterMembershipBindGroup = createHdrpClusterMembershipBindGroup(
3372
- internals,
3373
- hdrpBuffers,
3374
- membershipBindGroupLayout
3375
- );
3376
- }
3377
- gpuMembership = frameState.hdrpClusterMembershipBindGroup !== null;
3378
3450
  }
3379
- const { clusterGrid, gridX, gridY, gridZ, clusterGridBuf, lightIndexListBuf, lightIndexCount } = runHdrpClusterProfilePhase(profilePhase, "record/scene-state/hdrp-cluster/binner", () => {
3380
- const binnerProfile = profilePhase === void 0 ? void 0 : (phase, action) => runHdrpClusterProfilePhase(
3381
- profilePhase,
3382
- `record/scene-state/hdrp-cluster/binner/${phase}`,
3383
- action
3384
- );
3385
- const {
3386
- hdrpLights,
3387
- clusterGrid: clusterGrid2,
3388
- gridX: gridX2,
3389
- gridY: gridY2,
3390
- gridZ: gridZ2,
3391
- clusterGridBuf: clusterGridBuf2,
3392
- lightIndexListBuf: lightIndexListBuf2,
3393
- projMatrix,
3394
- viewMatrix
3395
- } = runHdrpClusterProfilePhase(
3396
- profilePhase,
3397
- "record/scene-state/hdrp-cluster/binner/input-preparation",
3398
- () => {
3399
- const hdrpLights2 = [];
3400
- for (const pl of effectivePointLights) {
3401
- const range = Number.isFinite(pl.invRangeSquared) && pl.invRangeSquared > 0 ? Math.sqrt(1 / pl.invRangeSquared) : 1e3;
3402
- hdrpLights2.push({ position: pl.position, range });
3403
- }
3404
- for (const sl of effectiveSpotLights) {
3405
- const range = Number.isFinite(sl.invRangeSquared) && sl.invRangeSquared > 0 ? Math.sqrt(1 / sl.invRangeSquared) : 1e3;
3406
- hdrpLights2.push({ position: sl.position, range });
3407
- }
3408
- const clusterGrid3 = frameState.installedPipelineConfig?.clusterGrid ?? DEFAULT_CLUSTER_GRID;
3409
- const gridX3 = clusterGrid3.x;
3410
- const gridY3 = clusterGrid3.y;
3411
- const gridZ3 = clusterGrid3.z;
3412
- const clusterCount = gridX3 * gridY3 * gridZ3;
3413
- const projMatrix2 = computeProjectionMatrix(camera);
3414
- const viewMatrix2 = computeViewMatrix(camera);
3415
- const clusterGridBuf3 = frameState.hdrpClusterGridScratch === null || frameState.hdrpClusterGridScratch.length < clusterCount * 2 ? new Uint32Array(clusterCount * 2) : frameState.hdrpClusterGridScratch;
3416
- frameState.hdrpClusterGridScratch = clusterGridBuf3;
3417
- const lightIndexListBuf3 = frameState.hdrpLightIndexListScratch === null || frameState.hdrpLightIndexListScratch.length < LIGHT_INDEX_LIST_CAPACITY ? new Uint32Array(LIGHT_INDEX_LIST_CAPACITY) : frameState.hdrpLightIndexListScratch;
3418
- frameState.hdrpLightIndexListScratch = lightIndexListBuf3;
3419
- return {
3420
- hdrpLights: hdrpLights2,
3421
- clusterGrid: clusterGrid3,
3422
- gridX: gridX3,
3423
- gridY: gridY3,
3424
- gridZ: gridZ3,
3425
- clusterGridBuf: clusterGridBuf3,
3426
- lightIndexListBuf: lightIndexListBuf3,
3427
- projMatrix: projMatrix2,
3428
- viewMatrix: viewMatrix2
3429
- };
3430
- }
3431
- );
3432
- const binResult = runHdrpClusterProfilePhase(
3433
- profilePhase,
3434
- "record/scene-state/hdrp-cluster/binner/bin-core",
3435
- () => bin(
3436
- hdrpLights,
3437
- viewMatrix,
3438
- projMatrix,
3439
- { x: gridX2, y: gridY2, z: gridZ2 },
3440
- camera.near,
3441
- camera.far,
3442
- clusterGridBuf2,
3443
- lightIndexListBuf2,
3444
- LIGHT_INDEX_LIST_CAPACITY,
3445
- frameState.hdrpClusterBinScratch,
3446
- binnerProfile,
3447
- { 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"
3448
3457
  )
3449
3458
  );
3450
- if (!binResult.ok && !frameState.hdrpOncePerFrameFired.has("hdrp-index-list-overflow")) {
3451
- frameState.hdrpOncePerFrameFired.add("hdrp-index-list-overflow");
3452
- const detail = binResult.error.detail;
3453
- internals.errorRegistry.fire(
3454
- 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
+ )
3455
3479
  );
3456
3480
  }
3457
- const envFalsify = globalThis.process?.env;
3458
- if (envFalsify?.FORGEAX_HDRP_FALSIFY_CLUSTER_GRID_ZERO) {
3459
- clusterGridBuf2.fill(0);
3460
- }
3461
- return {
3462
- clusterGrid: clusterGrid2,
3463
- gridX: gridX2,
3464
- gridY: gridY2,
3465
- gridZ: gridZ2,
3466
- clusterGridBuf: clusterGridBuf2,
3467
- lightIndexListBuf: lightIndexListBuf2,
3468
- lightIndexCount: binResult.ok ? binResult.value : 0
3481
+ frameState.hdrpClusterMembership = {
3482
+ device: internals.device,
3483
+ layout: membershipBindGroupLayout,
3484
+ bindGroup,
3485
+ clusterGridBuffer: hdrpBuffers.clusterGridBuffer
3469
3486
  };
3470
- });
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);
3471
3496
  const hdrpPayload = runHdrpClusterProfilePhase(
3472
3497
  profilePhase,
3473
3498
  "record/scene-state/hdrp-cluster/payload-packing",
3474
3499
  () => {
3475
- const hdrpBuffers = getOrCreateHdrpBuffers(internals, clusterGrid);
3476
- if (hdrpBuffers === null) return null;
3477
- const lightCapacity = hdrpBuffers.storageBuffer ? 256 : HDRP_UNIFORM_LIGHT_CAPACITY;
3478
- const lightDataPayload = new Float32Array(lightCapacity * 16);
3500
+ const lightDataPayload = new Float32Array(prepared.layout.lightDataSlotCount * 16);
3479
3501
  let slotIdx = 0;
3480
- for (const pl of effectivePointLights) {
3481
- if (slotIdx >= lightCapacity) break;
3482
- const layer = pl.shadowAtlasLayer;
3483
- const packed = layer !== void 0 && layer >= 0 && pl.shadowNear !== void 0 && pl.shadowFar !== void 0 ? packLightSlot(pl, {
3484
- shadowAtlasLayer: layer,
3485
- near: pl.shadowNear,
3486
- far: pl.shadowFar
3487
- }) : packLightSlot(pl);
3488
- lightDataPayload.set(packed, slotIdx * 16);
3489
- slotIdx += 1;
3490
- }
3491
- for (const sl of effectiveSpotLights) {
3492
- if (slotIdx >= lightCapacity) break;
3493
- 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);
3494
3515
  lightDataPayload.set(packed, slotIdx * 16);
3495
3516
  slotIdx += 1;
3496
3517
  }
@@ -3501,8 +3522,7 @@ function writeHdrpClusterAndSsaoBuffers(internals, frameState, camera, pointLigh
3501
3522
  camera.near,
3502
3523
  camera.far,
3503
3524
  clusterSsaoIntensity,
3504
- Math.min(effectivePointLights.length + effectiveSpotLights.length, lightCapacity),
3505
- lightCapacity
3525
+ transport.admittedLightCount
3506
3526
  );
3507
3527
  return {
3508
3528
  hdrpBuffers,
@@ -3511,73 +3531,97 @@ function writeHdrpClusterAndSsaoBuffers(internals, frameState, camera, pointLigh
3511
3531
  };
3512
3532
  }
3513
3533
  );
3514
- runHdrpClusterProfilePhase(profilePhase, "record/scene-state/hdrp-cluster/buffer-upload", () => {
3515
- if (hdrpPayload !== null) {
3516
- 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;
3517
3547
  const lightDataUpload = internals.device.queue.writeBuffer(
3518
- hdrpBuffers.lightDataBuffer,
3548
+ hdrpBuffers2.lightDataBuffer,
3519
3549
  0,
3520
3550
  lightDataUploadPayload
3521
3551
  );
3522
- if (!lightDataUpload.ok) internals.errorRegistry.fire(lightDataUpload.error);
3523
- if (hdrpBuffers.storageBuffer) {
3524
- if (gpuMembership) {
3525
- const lightBoundsUpload = internals.device.queue.writeBuffer(
3526
- hdrpBuffers.lightBoundsBuffer,
3527
- 0,
3528
- frameState.hdrpClusterBinScratch.lightBounds.subarray(0, effectiveLightCount * 6)
3529
- );
3530
- if (!lightBoundsUpload.ok) internals.errorRegistry.fire(lightBoundsUpload.error);
3531
- }
3532
- const clusterGridUpload = internals.device.queue.writeBuffer(
3533
- 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,
3534
3557
  0,
3535
- clusterGridBuf.subarray(0, gridX * gridY * gridZ * 2)
3558
+ prepared.lightBounds
3536
3559
  );
3537
- if (!clusterGridUpload.ok) internals.errorRegistry.fire(clusterGridUpload.error);
3538
- if (!gpuMembership && lightIndexCount > 0) {
3539
- const lightIndexListUpload = internals.device.queue.writeBuffer(
3540
- hdrpBuffers.lightIndexListBuffer,
3541
- 0,
3542
- lightIndexListBuf.subarray(0, lightIndexCount)
3543
- );
3544
- 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));
3545
3578
  }
3546
3579
  }
3547
3580
  const clusterUniformUpload = internals.device.queue.writeBuffer(
3548
- hdrpBuffers.clusterUniformBuffer,
3581
+ hdrpBuffers2.clusterUniformBuffer,
3549
3582
  0,
3550
3583
  clusterUniformPayload
3551
3584
  );
3552
- 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);
3553
3589
  }
3554
- {
3590
+ );
3591
+ if (!bufferUpload.ok) return bufferUpload;
3592
+ const ssaoUpload = runHdrpClusterProfilePhase(
3593
+ profilePhase,
3594
+ "record/scene-state/hdrp-cluster/buffer-upload",
3595
+ () => {
3555
3596
  const ssaoBufs = getOrCreateSsaoBuffers(internals);
3556
- if (ssaoBufs !== null) {
3557
- const sProj = computeProjectionMatrix(camera);
3558
- const sView = computeViewMatrix(camera);
3559
- const invProjOnly = mat4.create();
3560
- mat4.invert(invProjOnly, sProj);
3561
- const ssaoUniformPayload = new Float32Array(64);
3562
- ssaoUniformPayload.set(sView, 0);
3563
- ssaoUniformPayload.set(sProj, 16);
3564
- ssaoUniformPayload.set(invProjOnly, 32);
3565
- const ssaoConfig = frameState.installedPipelineConfig?.ssao;
3566
- const parameters = getSsaoParameters(
3567
- ssaoConfig !== void 0 && ssaoConfig.enabled === true ? ssaoConfig : void 0
3568
- );
3569
- ssaoUniformPayload[48] = parameters.intensity;
3570
- ssaoUniformPayload[49] = parameters.radius;
3571
- ssaoUniformPayload[50] = parameters.bias;
3572
- const ssaoUniformRes = internals.device.queue.writeBuffer(
3573
- ssaoBufs.uniformBuffer,
3574
- 0,
3575
- ssaoUniformPayload
3576
- );
3577
- if (!ssaoUniformRes.ok) internals.errorRegistry.fire(ssaoUniformRes.error);
3578
- }
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);
3579
3621
  }
3580
- });
3622
+ );
3623
+ if (!ssaoUpload.ok) return ssaoUpload;
3624
+ return ok(void 0);
3581
3625
  }
3582
3626
  function resolveSkyboxActive(internals, frameState, camera, skybox) {
3583
3627
  const tonemapActive = camera.tonemap !== "none";
@@ -3603,6 +3647,7 @@ function getOrCreateIblCache(scope) {
3603
3647
  const existing = scopeCaches.get(scope);
3604
3648
  if (existing !== void 0) return existing;
3605
3649
  const cache3 = {
3650
+ probeKernel: createIblKernelCache(scope.generation),
3606
3651
  irradianceBakeCount: 0,
3607
3652
  prefilterBakeCount: 0,
3608
3653
  brdfLutBakeCount: 0
@@ -4643,35 +4688,6 @@ function deriveTextureExtent(format, width, height) {
4643
4688
  uvScale: [width / physicalWidth, height / physicalHeight]
4644
4689
  };
4645
4690
  }
4646
- function deriveMipUploadLayout(format, baseWidth, baseHeight, mipLevelCount) {
4647
- const params = blockParamsForFormat(format);
4648
- if (params === null) return [];
4649
- const levels = [];
4650
- let byteOffset = 0;
4651
- for (let level = 0; level < mipLevelCount; level++) {
4652
- const width = Math.max(1, baseWidth >> level);
4653
- const height = Math.max(1, baseHeight >> level);
4654
- const blockCols = Math.ceil(width / params.blockW);
4655
- const blockRows = Math.ceil(height / params.blockH);
4656
- const rowBytes = blockCols * params.bytesPerBlock;
4657
- const byteLength = rowBytes * blockRows;
4658
- levels.push({
4659
- level,
4660
- width,
4661
- height,
4662
- physicalWidth: blockCols * params.blockW,
4663
- physicalHeight: blockRows * params.blockH,
4664
- copyWidth: blockCols * params.blockW,
4665
- copyHeight: blockRows * params.blockH,
4666
- bytesPerRow: rowBytes,
4667
- rowsPerImage: blockRows,
4668
- byteOffset,
4669
- byteLength
4670
- });
4671
- byteOffset += byteLength;
4672
- }
4673
- return levels;
4674
- }
4675
4691
  function deriveRenderDataMesh(mesh) {
4676
4692
  const indices = mesh.indices;
4677
4693
  const indexBytesUnpadded = indices === void 0 ? 0 : indices.byteLength;
@@ -4689,6 +4705,8 @@ function deriveRenderDataMesh(mesh) {
4689
4705
  });
4690
4706
  }
4691
4707
  function deriveRenderDataTexture(tex) {
4708
+ const width = tex.shape.extent.width;
4709
+ const height = tex.shape.extent.height;
4692
4710
  const isSrgbFormat = tex.format.endsWith("-srgb");
4693
4711
  const expectedColorSpace = isSrgbFormat ? "srgb" : "linear";
4694
4712
  if (tex.colorSpace !== expectedColorSpace) {
@@ -4701,8 +4719,17 @@ function deriveRenderDataTexture(tex) {
4701
4719
  );
4702
4720
  }
4703
4721
  const compressed = isCompressedFormat(tex.format);
4704
- const hasOfflineMipChain = (tex.mipLevelCount ?? 1) > 1;
4705
- if (compressed && tex.mipmap === true && !hasOfflineMipChain) {
4722
+ const layout = deriveTextureLayout({ shape: tex.shape, format: tex.format, mips: tex.mips });
4723
+ if (!layout.ok) {
4724
+ return err(
4725
+ projectionError({
4726
+ code: "invalid-source-format",
4727
+ expected: layout.error.expected,
4728
+ hint: layout.error.hint
4729
+ })
4730
+ );
4731
+ }
4732
+ if (compressed && tex.mips.kind === "generate") {
4706
4733
  return err(
4707
4734
  projectionError({
4708
4735
  code: "mipgen-unsupported-compressed-format",
@@ -4711,9 +4738,18 @@ function deriveRenderDataTexture(tex) {
4711
4738
  })
4712
4739
  );
4713
4740
  }
4714
- const mipLevelCount = compressed ? Math.max(1, tex.mipLevelCount ?? 1) : tex.mipmap ? numMipLevels(tex) : 1;
4715
- const uncompressedBytesPerTexel = tex.format === "rgba32float" ? 16 : tex.format === "rgba16float" ? 8 : 4;
4716
- if (!compressed && tex.data.byteLength < tex.width * tex.height * uncompressedBytesPerTexel) {
4741
+ const mipLevelCount = layout.value.levels.length;
4742
+ const baseLevel = layout.value.levels[0];
4743
+ if (baseLevel === void 0) {
4744
+ return err(
4745
+ projectionError({
4746
+ code: "invalid-source-format",
4747
+ expected: "texture layout contains a base mip level",
4748
+ hint: ASSET_ERROR_HINTS["invalid-source-format"]
4749
+ })
4750
+ );
4751
+ }
4752
+ if (!compressed && tex.data.byteLength < baseLevel.byteLength) {
4717
4753
  return err(
4718
4754
  projectionError({
4719
4755
  code: "invalid-source-format",
@@ -4722,12 +4758,14 @@ function deriveRenderDataTexture(tex) {
4722
4758
  })
4723
4759
  );
4724
4760
  }
4725
- const bytesPerRow$1 = compressed ? bytesPerRow(tex.format, tex.width) ?? tex.width * 4 : tex.width * uncompressedBytesPerTexel;
4761
+ const bytesPerRow$1 = compressed ? bytesPerRow(tex.format, width) ?? baseLevel.bytesPerRow : baseLevel.bytesPerRow;
4726
4762
  const usage = compressed ? GPU_TEXTURE_USAGE_TEXTURE_BINDING | GPU_TEXTURE_USAGE_COPY_DST | GPU_TEXTURE_USAGE_COPY_SRC : GPU_TEXTURE_USAGE_RENDER_ATTACHMENT_AND_TEXTURE_BINDING | GPU_TEXTURE_USAGE_COPY_DST | GPU_TEXTURE_USAGE_COPY_SRC;
4727
4763
  return ok$1({
4728
- width: tex.width,
4729
- height: tex.height,
4730
- ...deriveTextureExtent(tex.format, tex.width, tex.height),
4764
+ width,
4765
+ height,
4766
+ shape: tex.shape,
4767
+ depthOrArrayLayers: tex.shape.viewDimension === "2d" ? 1 : tex.shape.viewDimension === "2d-array" ? tex.shape.extent.layers : tex.shape.extent.depth,
4768
+ ...deriveTextureExtent(tex.format, width, height),
4731
4769
  format: tex.format,
4732
4770
  mipLevelCount,
4733
4771
  usage,
@@ -4746,13 +4784,59 @@ function deriveRenderDataCubemap(source) {
4746
4784
  })
4747
4785
  );
4748
4786
  }
4787
+ const sourceHeight = source.shape.viewDimension === "2d" ? source.shape.extent.height : 0;
4749
4788
  return ok$1({
4750
- cubeFaceSize: source.height,
4789
+ cubeFaceSize: sourceHeight,
4751
4790
  outputFormat: "rgba16float",
4752
4791
  needsHalfConversion: source.format === "rgba32float",
4753
4792
  cubeUsage: GPU_TEXTURE_USAGE_RENDER_ATTACHMENT_AND_TEXTURE_BINDING | GPU_TEXTURE_USAGE_COPY_DST | GPU_TEXTURE_USAGE_COPY_SRC
4754
4793
  });
4755
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
+ }
4756
4840
  var STANDARD_PBR_UBO_SIZE = 384;
4757
4841
  var MATERIAL_PER_ENTITY_STRIDE = 512;
4758
4842
  function resolveSurfaceProfile(storageFormat, viewFormat, facts) {
@@ -4865,6 +4949,11 @@ function selectSwapChainFormat(storageBufferCapable, surfaceViewFormats = true)
4865
4949
  }
4866
4950
 
4867
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
+ }
4868
4957
  var DERIVED_PARAM_SCHEMA_CACHE = /* @__PURE__ */ new WeakMap();
4869
4958
  function derivedParamSchema(schema) {
4870
4959
  const cached = DERIVED_PARAM_SCHEMA_CACHE.get(schema);
@@ -5081,13 +5170,21 @@ var DEFAULT_STANDARD_TEXTURE_COORDINATES = new Float32Array([
5081
5170
  ]);
5082
5171
  function materialTextureUvScale(texture) {
5083
5172
  if (texture === void 0) return [1, 1];
5084
- return deriveTextureExtent(texture.format, texture.width, texture.height).uvScale;
5173
+ return deriveTextureExtent(
5174
+ texture.format,
5175
+ texture.shape.extent.width,
5176
+ texture.shape.extent.height
5177
+ ).uvScale;
5085
5178
  }
5086
5179
  function materialTextureForField(material, field) {
5087
5180
  if (field === "emissiveTexture") return material.emissiveTexture;
5088
5181
  if (field === "occlusionTexture") return material.occlusionTexture;
5089
5182
  return material.textureHandles?.get(field);
5090
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
+ }
5091
5188
  function materialUboFloatView(payload) {
5092
5189
  if (payload instanceof Float32Array) return payload;
5093
5190
  return payload instanceof Uint8Array ? new Float32Array(payload.buffer, payload.byteOffset, payload.byteLength / 4) : new Float32Array(payload);
@@ -5179,7 +5276,11 @@ function userRegionTextureFieldOrder(schema) {
5179
5276
  const fields = [...derivedParamSchema(schema).textureFieldNames];
5180
5277
  return fields;
5181
5278
  }
5182
- 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
+ }
5183
5284
  if (field === "normalTexture") return pipelineState.defaultNormalTextureView;
5184
5285
  if (field === "baseColorTexture") return pipelineState.fallbackTextureView;
5185
5286
  return pipelineState.defaultWhiteTextureView;
@@ -5289,11 +5390,14 @@ function buildPerSubmeshMaterialBg(deps, submeshMaterial, entityKey, materialWor
5289
5390
  materialSlice,
5290
5391
  videoHighPerfAvailable,
5291
5392
  skylightResources,
5393
+ resolveRenderTargetTextureSource,
5394
+ resolveReflectionProbeResources,
5292
5395
  materialBgShared,
5293
5396
  materialBgAssemblyCache,
5294
5397
  transmissionBackdropView,
5295
5398
  bindGroupCounts
5296
5399
  } = deps;
5400
+ const effectiveSkylightResources = resolveReflectionProbeResources?.(materialWorld, entityKey) ?? skylightResources;
5297
5401
  const smMaterialHandle = submeshMaterial.materialHandle;
5298
5402
  const materialCacheKey = smMaterialHandle === void 0 ? void 0 : `${materialWorld.identity}:${smMaterialHandle}`;
5299
5403
  const smHasVideoFields = (submeshMaterial.videoTextureFields?.size ?? 0) > 0;
@@ -5308,7 +5412,7 @@ function buildPerSubmeshMaterialBg(deps, submeshMaterial, entityKey, materialWor
5308
5412
  submeshMaterial,
5309
5413
  smMaterialBgl,
5310
5414
  pipelineState.materialUniformBuffer.buffer,
5311
- skylightResources,
5415
+ effectiveSkylightResources,
5312
5416
  store.materialResourceEpoch,
5313
5417
  transmissionBackdropView
5314
5418
  )) {
@@ -5343,9 +5447,17 @@ function buildPerSubmeshMaterialBg(deps, submeshMaterial, entityKey, materialWor
5343
5447
  const field = smUserRegionFields[fi];
5344
5448
  const samplerBinding = 1 + fi * 2;
5345
5449
  const textureBinding = samplerBinding + 1;
5346
- let smView = field !== void 0 ? defaultViewForUserRegionField(field, pipelineState) : pipelineState.defaultWhiteTextureView;
5450
+ let smView = field !== void 0 ? defaultViewForUserRegionField(field, pipelineState, smSchema) : pipelineState.defaultWhiteTextureView;
5347
5451
  const smVideoClip = field !== void 0 ? submeshMaterial.videoTextureFields?.get(field) : void 0;
5348
- 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) {
5349
5461
  const view = videoTextureView(
5350
5462
  materialWorld,
5351
5463
  runtime.dynamicTextureStore,
@@ -5374,11 +5486,15 @@ function buildPerSubmeshMaterialBg(deps, submeshMaterial, entityKey, materialWor
5374
5486
  }
5375
5487
  );
5376
5488
  }
5377
- const transmissionSampler = transmissionBackdropView != null ? skylightResources.prefilterSampler : pipelineState.defaultSampler;
5378
- const smMergedEntries = assembleMaterialWithSkylightEntries(smBaseEntries, skylightResources, {
5379
- sampler: transmissionSampler,
5380
- ...transmissionBackdropView === void 0 ? {} : { backdropView: transmissionBackdropView }
5381
- });
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
+ );
5382
5498
  const createBindGroup = () => {
5383
5499
  const result = runtime.device.createBindGroup({
5384
5500
  label: "pbr-material-skylight-bg",
@@ -5403,7 +5519,7 @@ function buildPerSubmeshMaterialBg(deps, submeshMaterial, entityKey, materialWor
5403
5519
  materialResourceEpoch: store.materialResourceEpoch,
5404
5520
  materialBgl: smMaterialBgl,
5405
5521
  materialBuffer: pipelineState.materialUniformBuffer.buffer,
5406
- skylightResources,
5522
+ skylightResources: effectiveSkylightResources,
5407
5523
  bindGroup: smBindGroup
5408
5524
  });
5409
5525
  }
@@ -5464,9 +5580,11 @@ function recordGeometryDraws(c, pass, matchedMaterials, materialSlotIndices, sam
5464
5580
  msaaActive,
5465
5581
  validatedOrdered,
5466
5582
  splitLdrSprite,
5467
- viewBindGroup
5583
+ viewBindGroup,
5584
+ viewBindGroupDynamicOffset = 0
5468
5585
  } = c;
5469
5586
  const colorFormatOverride = c.transparentColorFormat;
5587
+ const clusteredLighting = c.standardLighting?.kind === "clustered";
5470
5588
  const isHdrTarget = tonemapActive || colorFormatOverride === "rgba16float";
5471
5589
  let lastVertexBuffer = null;
5472
5590
  let lastIndexBuffer = null;
@@ -5485,7 +5603,6 @@ function recordGeometryDraws(c, pass, matchedMaterials, materialSlotIndices, sam
5485
5603
  const profilePhase = c.profilePhase;
5486
5604
  const materialGroup1DynamicOffsets = new Uint32Array(1);
5487
5605
  const meshGroup2DynamicOffsets = [0];
5488
- const skinGroup2DynamicOffsets = [0, 0];
5489
5606
  let lastStencilReference = null;
5490
5607
  const materialPipelineLookupCache = /* @__PURE__ */ new Map();
5491
5608
  const resolveMaterialPipeline = (materialShaderId, renderState, topology, indexFormat, variantSet, colorFormat, layoutProjection) => {
@@ -5505,7 +5622,7 @@ function recordGeometryDraws(c, pass, matchedMaterials, materialSlotIndices, sam
5505
5622
  return null;
5506
5623
  }
5507
5624
  const resolvedVariantSet = resolvedVariantSetResult.value;
5508
- const resolvePipeline = () => runtime.getMaterialShaderPipeline?.(
5625
+ const resolvePipeline = () => runtime.getMaterialShaderPipelineEntry?.(
5509
5626
  materialShaderId,
5510
5627
  isHdrTarget,
5511
5628
  renderState,
@@ -5552,7 +5669,7 @@ function recordGeometryDraws(c, pass, matchedMaterials, materialSlotIndices, sam
5552
5669
  for (let i = 0; i < validatedOrdered.length; i++) {
5553
5670
  const entry = validatedOrdered[i];
5554
5671
  if (entry === void 0) continue;
5555
- const pointsLinesSubmission = c.pointsLines?.prepare(entry, frameState.isHdrpActive);
5672
+ const pointsLinesSubmission = c.pointsLines?.prepare(entry, clusteredLighting);
5556
5673
  if (entry.source.pointsLines !== void 0 && pointsLinesSubmission?.plan.drawCount !== 1)
5557
5674
  continue;
5558
5675
  if (passKind === "forward" && c.gpuDrivenEntityKeys.has(worldEntityKey(entry.source.worldId, entry.source.entityKey))) {
@@ -5578,7 +5695,7 @@ function recordGeometryDraws(c, pass, matchedMaterials, materialSlotIndices, sam
5578
5695
  entry.source.entityKey,
5579
5696
  entry.world ?? c.world
5580
5697
  );
5581
- const pointsLinesPipeline = resolveMaterialPipeline(
5698
+ const pointsLinesPipelineEntry = resolveMaterialPipeline(
5582
5699
  POINTS_LINES_MATERIAL_SHADER_ID,
5583
5700
  {
5584
5701
  ...pointsLinesMaterial.renderState,
@@ -5590,7 +5707,8 @@ function recordGeometryDraws(c, pass, matchedMaterials, materialSlotIndices, sam
5590
5707
  colorFormatOverride,
5591
5708
  pointsLinesSubmission.layoutProjection
5592
5709
  );
5593
- if (pointsLinesPipeline === null) continue;
5710
+ if (pointsLinesPipelineEntry === null) continue;
5711
+ const pointsLinesPipeline = pointsLinesPipelineEntry.pipeline;
5594
5712
  if (pipelineState.pointsLinesViewBuffer === void 0) continue;
5595
5713
  writePointsLinesViewUbo(
5596
5714
  runtime.device.queue,
@@ -5605,9 +5723,9 @@ function recordGeometryDraws(c, pass, matchedMaterials, materialSlotIndices, sam
5605
5723
  pass.setBindGroup(
5606
5724
  0,
5607
5725
  viewBindGroup,
5608
- new Uint32Array([i * POINTS_LINES_VIEW_SLOT_STRIDE]),
5726
+ new Uint32Array([0, i * POINTS_LINES_VIEW_SLOT_STRIDE]),
5609
5727
  0,
5610
- 1
5728
+ 2
5611
5729
  );
5612
5730
  pass.setPipeline(pointsLinesPipeline);
5613
5731
  pass.setVertexBuffer(0, pointsLinesSubmission.vertexBuffer);
@@ -5622,7 +5740,7 @@ function recordGeometryDraws(c, pass, matchedMaterials, materialSlotIndices, sam
5622
5740
  identityInstanceDraws[0]?.instanceBindGroup ?? resolveGeometryInstancesBindGroup(c, identityInstanceBuffer)
5623
5741
  );
5624
5742
  recordPointsLinesDraw(pass, pointsLinesSubmission.plan);
5625
- pass.setBindGroup(0, viewBindGroup, [0]);
5743
+ pass.setBindGroup(0, viewBindGroup, [viewBindGroupDynamicOffset, 0]);
5626
5744
  continue;
5627
5745
  }
5628
5746
  if (entry.mesh.vertexBuffer !== lastVertexBuffer) {
@@ -5644,14 +5762,22 @@ function recordGeometryDraws(c, pass, matchedMaterials, materialSlotIndices, sam
5644
5762
  const isSkinEntry = entry.source.skin !== void 0;
5645
5763
  const skinAllocator = pipelineState.skinPaletteAllocator;
5646
5764
  const skinSlice = entry.source.skin;
5647
- const skinResources = isSkinEntry && pipelineState.pbrSkinMeshBindGroupLayout !== null && skinAllocator !== null && skinSlice !== void 0 ? {
5648
- 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,
5649
5770
  paletteBuffer: skinSlice.buffer,
5650
- paletteBindingWindowBytes: skinAllocator.bindingWindowBytes
5771
+ paletteBindingWindowBytes: skinAllocator.bindingWindowBytes,
5772
+ hdrpBuffers: hdrpSkinBuffers
5651
5773
  } : null;
5652
5774
  const skinVariantSetResult = variantSetFromVertexLayoutProjection(
5653
5775
  entry.mesh.layoutProjection,
5654
- 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
+ )
5655
5781
  );
5656
5782
  if (!skinVariantSetResult.ok) runtime.errorRegistry.fire(skinVariantSetResult.error);
5657
5783
  const skinVariantSet = skinVariantSetResult.ok ? skinVariantSetResult.value : void 0;
@@ -5678,45 +5804,45 @@ function recordGeometryDraws(c, pass, matchedMaterials, materialSlotIndices, sam
5678
5804
  const skinMissesBefore = bindGroupCounts.createBindGroup;
5679
5805
  const skinBindGroup = getOrCreateFromChain(
5680
5806
  frameState.meshBindGroupCache,
5681
- [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
+ ],
5682
5817
  "pbr-skin-mesh",
5683
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
+ }
5684
5837
  const result = runtime.device.createBindGroup({
5685
5838
  label: "pbr-skin-mesh-bg",
5686
5839
  layout: skinResources.meshArrayBgl,
5687
- entries: [
5688
- {
5689
- binding: 0,
5690
- resource: {
5691
- kind: "buffer",
5692
- value: {
5693
- buffer: pipelineState.meshStorageBuffer.buffer,
5694
- offset: 0,
5695
- size: meshBindSize
5696
- }
5697
- }
5698
- },
5699
- {
5700
- binding: 1,
5701
- resource: {
5702
- kind: "buffer",
5703
- value: {
5704
- buffer: skinResources.paletteBuffer,
5705
- offset: 0,
5706
- // M6 SSOT: static BG entry size (= MAX_JOINTS * 64 =
5707
- // 16320 B) sourced from the allocator. The per-draw
5708
- // window slides via `group2DynamicOffsets[1]`
5709
- // (= entry.source.skin.byteOffset) so this size
5710
- // stays at the worst case across all skin entries
5711
- // -- one BG covers every skinned draw in the frame
5712
- // (m3-1b miss=1 + hit=N-1 contract). The allocator
5713
- // guarantees `buffer.size >= byteOffset + this size`
5714
- // so dynOffset[1] passes WebGPU validation.
5715
- size: skinResources.paletteBindingWindowBytes
5716
- }
5717
- }
5718
- }
5719
- ]
5840
+ entries: createPbrSkinMeshBindGroupEntries(
5841
+ pipelineState.meshStorageBuffer.buffer,
5842
+ meshBindSize,
5843
+ skinResources.paletteBuffer,
5844
+ skinResources.paletteBindingWindowBytes
5845
+ )
5720
5846
  });
5721
5847
  if (!result.ok) throw result.error;
5722
5848
  return result.value;
@@ -5728,9 +5854,10 @@ function recordGeometryDraws(c, pass, matchedMaterials, materialSlotIndices, sam
5728
5854
  else skinStats.hit += 1;
5729
5855
  }
5730
5856
  group2BindGroup = skinBindGroup;
5731
- skinGroup2DynamicOffsets[0] = i * MESH_PER_ENTITY_STRIDE;
5732
- skinGroup2DynamicOffsets[1] = entry.source.skin?.byteOffset ?? 0;
5733
- group2DynamicOffsets = skinGroup2DynamicOffsets;
5857
+ group2DynamicOffsets = pbrSkinMeshDynamicOffsets(
5858
+ i * MESH_PER_ENTITY_STRIDE,
5859
+ entry.source.skin?.byteOffset ?? 0
5860
+ );
5734
5861
  } else if (isSkinEntry) {
5735
5862
  continue;
5736
5863
  }
@@ -5780,6 +5907,8 @@ function recordGeometryDraws(c, pass, matchedMaterials, materialSlotIndices, sam
5780
5907
  } : geometryRenderStateForTopology(smTopology, submeshMaterial.renderState);
5781
5908
  const pipelineRenderState = geometryRenderStateForPass(basePipelineRenderState, passKind);
5782
5909
  let smPipelineHandle;
5910
+ let materialGroup2Contract;
5911
+ let materialPipelineEntry = null;
5783
5912
  if (smMaterialShaderId === void 0 || smMaterialShaderId === "forgeax::default-unlit") {
5784
5913
  const unlitShaderId = smMaterialShaderId ?? "forgeax::default-unlit";
5785
5914
  const unlitProjectionVariantResult = variantSetFromVertexLayoutProjection(
@@ -5791,7 +5920,10 @@ function recordGeometryDraws(c, pass, matchedMaterials, materialSlotIndices, sam
5791
5920
  continue;
5792
5921
  }
5793
5922
  const unlitHasColor = unlitProjectionVariantResult.value === "VERTEX_COLOR_AVAILABLE=true";
5794
- const unlitVariantSet = unlitHasColor ? "" : "STORAGE_BUFFER_AVAILABLE=true+VERTEX_COLOR_AVAILABLE=false";
5923
+ const unlitVariantSet = standardStorageVariantSet(
5924
+ runtime.device.caps.storageBuffer,
5925
+ unlitHasColor
5926
+ );
5795
5927
  const unlitRsp = resolveMaterialPipeline(
5796
5928
  unlitShaderId,
5797
5929
  pipelineRenderState,
@@ -5801,12 +5933,17 @@ function recordGeometryDraws(c, pass, matchedMaterials, materialSlotIndices, sam
5801
5933
  colorFormatOverride,
5802
5934
  entry.mesh.layoutProjection
5803
5935
  );
5804
- 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);
5805
5938
  } else if (smMaterialShaderId !== void 0) {
5806
5939
  const hasVertexColor = entry.mesh.layoutProjection.attributes.some(
5807
5940
  (attribute) => attribute.key === "color"
5808
5941
  );
5809
- 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
+ );
5810
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}`;
5811
5948
  const projectionVariantSetResult = isSpriteShader ? { ok: true, value: variantSet } : variantSetFromVertexLayoutProjection(entry.mesh.layoutProjection, variantSet);
5812
5949
  if (!projectionVariantSetResult.ok) {
@@ -5822,23 +5959,24 @@ function recordGeometryDraws(c, pass, matchedMaterials, materialSlotIndices, sam
5822
5959
  colorFormatOverride,
5823
5960
  entry.mesh.layoutProjection
5824
5961
  );
5825
- smPipelineHandle = cachedPipeline ?? null;
5962
+ materialPipelineEntry = cachedPipeline;
5963
+ smPipelineHandle = cachedPipeline?.pipeline ?? null;
5826
5964
  } else {
5827
5965
  smPipelineHandle = selectGeometryPipeline(pipelineState, isHdrTarget, msaaActive);
5828
5966
  }
5829
5967
  if (smPipelineHandle === null) {
5830
5968
  continue;
5831
5969
  }
5832
- const usesStandardClusterBindings = smMaterialShaderId === "forgeax::default-standard-pbr";
5833
5970
  if (!isSkinEntry) {
5834
- if (usesStandardClusterBindings) {
5835
- if (meshGroup2 === null) continue;
5836
- group2BindGroup = meshGroup2;
5837
- } else {
5838
- const fallbackGroup = meshBindGroup ?? meshGroup2;
5839
- if (fallbackGroup === null) continue;
5840
- group2BindGroup = fallbackGroup;
5841
- }
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;
5842
5980
  }
5843
5981
  pass.setBindGroup(2, group2BindGroup, group2DynamicOffsets);
5844
5982
  if (profilePhase === void 0) {
@@ -5876,6 +6014,7 @@ function resolveGeometryInstanceBuffer(c, entry, identityInstanceDraws) {
5876
6014
  const { runtime, pipelineState, frameState } = c;
5877
6015
  const inst = entry.source.instances;
5878
6016
  if (inst === void 0) return identityInstanceDraws;
6017
+ if (inst.instanceCount === 0) return [];
5879
6018
  let instanceBuffer = pipelineState.identityInstanceBuffer;
5880
6019
  let instanceCount = 1;
5881
6020
  {
@@ -6095,8 +6234,13 @@ function recordSpritePass(c, pass, matchedIndices, materialSlotIndices, sampleCo
6095
6234
  ldrSpriteColorView,
6096
6235
  geometryDepthView,
6097
6236
  viewBindGroup,
6237
+ meshBindGroup,
6238
+ hdrpClusterBindGroup,
6239
+ viewBindGroupDynamicOffset = 0,
6098
6240
  splitLdrSprite
6099
6241
  } = c;
6242
+ const clusteredLighting = c.standardLighting?.kind === "clustered";
6243
+ const meshGroup2 = clusteredLighting ? hdrpClusterBindGroup : meshBindGroup;
6100
6244
  const spriteIsHdr = c.tonemapActive || transparentPassColorFormat(c, pipelineState) === "rgba16float";
6101
6245
  let geometryPassEnded = false;
6102
6246
  if (splitLdrSprite && (graphPass !== void 0 || ldrSpritePassView !== null)) {
@@ -6124,7 +6268,7 @@ function recordSpritePass(c, pass, matchedIndices, materialSlotIndices, sampleCo
6124
6268
  { colorLoadOp: "load", depthLoadOp: "load" }
6125
6269
  )
6126
6270
  );
6127
- spritePass.setBindGroup(0, viewBindGroup, [0]);
6271
+ spritePass.setBindGroup(0, viewBindGroup, [viewBindGroupDynamicOffset, 0]);
6128
6272
  const spritePremulBlend = {
6129
6273
  depthWriteEnabled: false,
6130
6274
  depthCompare: "less-equal",
@@ -6180,7 +6324,12 @@ function recordSpritePass(c, pass, matchedIndices, materialSlotIndices, sampleCo
6180
6324
  spritePremulBlend,
6181
6325
  "triangle-list",
6182
6326
  void 0,
6183
- 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),
6184
6333
  "forward",
6185
6334
  void 0,
6186
6335
  msaaActive ? 4 : 1,
@@ -6202,14 +6351,15 @@ function recordSpritePass(c, pass, matchedIndices, materialSlotIndices, sampleCo
6202
6351
  matchedIndices,
6203
6352
  materialSlotIndices,
6204
6353
  sampleCount,
6205
- resolveMaterialBindGroup
6354
+ resolveMaterialBindGroup,
6355
+ meshGroup2
6206
6356
  );
6207
6357
  if (graphPass === void 0) spritePass.end();
6208
6358
  }
6209
6359
  return geometryPassEnded;
6210
6360
  }
6211
- function recordSpriteTransparentPbrDraws(c, spritePass, matchedIndices, materialSlotIndices, sampleCount, resolveMaterialBindGroup) {
6212
- 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;
6213
6363
  const isHdrTarget = c.tonemapActive || transparentPassColorFormat(c, pipelineState) === "rgba16float";
6214
6364
  let lastPbrSubPipelineHandle = null;
6215
6365
  let lastPbrSubVertexBuffer = null;
@@ -6232,7 +6382,6 @@ function recordSpriteTransparentPbrDraws(c, spritePass, matchedIndices, material
6232
6382
  spritePass.setIndexBuffer(entry.mesh.indexBuffer.handle, entry.mesh.indexFormat);
6233
6383
  lastPbrSubIndexBuffer = entry.mesh.indexBuffer;
6234
6384
  }
6235
- spritePass.setBindGroup(2, meshBindGroup, [i * MESH_PER_ENTITY_STRIDE]);
6236
6385
  const identityInstBg = getOrCreatePerEntity(
6237
6386
  frameState.instancesBgPerEntity,
6238
6387
  worldEntityKey(entry.source.worldId, entry.source.entityKey),
@@ -6258,7 +6407,11 @@ function recordSpriteTransparentPbrDraws(c, spritePass, matchedIndices, material
6258
6407
  bindGroupCounts
6259
6408
  );
6260
6409
  spritePass.setBindGroup(3, identityInstBg);
6261
- 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
+ );
6262
6415
  const projectionVariantSetResult = variantSetFromVertexLayoutProjection(
6263
6416
  entry.mesh.layoutProjection,
6264
6417
  subVariantSet
@@ -6276,15 +6429,7 @@ function recordSpriteTransparentPbrDraws(c, spritePass, matchedIndices, material
6276
6429
  if (submeshMaterial.transparent !== true) continue;
6277
6430
  const smShaderId = submeshMaterial.materialShaderId;
6278
6431
  if (smShaderId === void 0) continue;
6279
- const materialSlot = materialSlotIndices[i]?.[matSlotIdx] ?? materialSlotIndices[i]?.[0] ?? 0;
6280
- const subBg = resolveMaterialBindGroup(
6281
- materialSlot,
6282
- submeshMaterial,
6283
- entry.source.entityKey,
6284
- entry.world ?? c.world
6285
- );
6286
- spritePass.setBindGroup(1, subBg, [materialSlot * MATERIAL_PER_ENTITY_STRIDE]);
6287
- const subPipeline = runtime.getMaterialShaderPipeline?.(
6432
+ const subPipelineEntry = c.runtime.getMaterialShaderPipelineEntry?.(
6288
6433
  smShaderId,
6289
6434
  isHdrTarget,
6290
6435
  submeshMaterial.renderState,
@@ -6301,7 +6446,20 @@ function recordSpriteTransparentPbrDraws(c, spritePass, matchedIndices, material
6301
6446
  void 0,
6302
6447
  entry.mesh.layoutProjection
6303
6448
  ) ?? null;
6304
- 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;
6305
6463
  if (lastPbrSubPipelineHandle !== subPipeline) {
6306
6464
  spritePass.setPipeline(subPipeline);
6307
6465
  lastPbrSubPipelineHandle = subPipeline;
@@ -6533,6 +6691,7 @@ function recordSpriteEntityDraws(c, spritePass, matchedIndices, skylightResource
6533
6691
  );
6534
6692
  spriteInstanceBuffer = _si.buffer;
6535
6693
  spriteInstanceCount = _si.count;
6694
+ const spriteGroup2 = entityShaderId === "forgeax::sprite-lit" && c.standardLighting?.kind === "clustered" ? c.hdrpClusterBindGroup : meshBindGroup;
6536
6695
  const spriteInstancesBg = getOrCreatePerEntity(
6537
6696
  frameState.instancesBgPerEntity,
6538
6697
  worldEntityKey(spriteEntry.source.worldId, spriteEntry.source.entityKey),
@@ -6557,7 +6716,7 @@ function recordSpriteEntityDraws(c, spritePass, matchedIndices, skylightResource
6557
6716
  },
6558
6717
  bindGroupCounts
6559
6718
  );
6560
- spritePass.setBindGroup(2, meshBindGroup, [i * MESH_PER_ENTITY_STRIDE]);
6719
+ spritePass.setBindGroup(2, spriteGroup2, [i * MESH_PER_ENTITY_STRIDE]);
6561
6720
  const spriteTexHandle = spriteEntry.source.material.baseColorTexture;
6562
6721
  let spriteTexView = pipelineState.defaultWhiteTextureView;
6563
6722
  if (spriteTexHandle !== void 0) {
@@ -6725,6 +6884,7 @@ function ensureTypedShadowViewBg(c, viewOffset, cascadeOffset, variant) {
6725
6884
  const { runtime, frameState, pipelineState } = c;
6726
6885
  const shadowSampler = pipelineState.perPassResources.shadowSampler;
6727
6886
  if (shadowSampler === null) return null;
6887
+ const projectorAvailable = pipelineState.projectorAvailable !== false;
6728
6888
  try {
6729
6889
  return getOrCreateFromChain(
6730
6890
  frameState.viewBindGroupCache,
@@ -6738,6 +6898,7 @@ function ensureTypedShadowViewBg(c, viewOffset, cascadeOffset, variant) {
6738
6898
  pipelineState.shadowParamsBuffer,
6739
6899
  pipelineState.shadowCasterCascadeBuffer,
6740
6900
  pipelineState.shadowFallbackTextureView,
6901
+ ...projectorAvailable ? [pipelineState.defaultWhiteTextureView, pipelineState.defaultSampler] : [],
6741
6902
  pipelineState.pointsLinesViewBuffer ?? pipelineState.viewUniformBuffer
6742
6903
  ],
6743
6904
  variant,
@@ -6808,7 +6969,20 @@ function ensureTypedShadowViewBg(c, viewOffset, cascadeOffset, variant) {
6808
6969
  size: 80
6809
6970
  }
6810
6971
  }
6811
- }
6972
+ },
6973
+ ...projectorAvailable ? [
6974
+ {
6975
+ binding: 11,
6976
+ resource: {
6977
+ kind: "textureView",
6978
+ value: pipelineState.defaultWhiteTextureView
6979
+ }
6980
+ },
6981
+ {
6982
+ binding: 12,
6983
+ resource: { kind: "sampler", value: pipelineState.defaultSampler }
6984
+ }
6985
+ ] : []
6812
6986
  ]
6813
6987
  });
6814
6988
  if (!created.ok) throw created.error;
@@ -6828,7 +7002,15 @@ function shadowShaderMap(c) {
6828
7002
  const shaders = /* @__PURE__ */ new Map();
6829
7003
  for (const entry of c.dispatch) {
6830
7004
  if (entry.tags.LightMode === "ShadowCaster" && entry.materialShaderId !== void 0) {
6831
- shaders.set(entry.renderableIndex, entry.materialShaderId);
7005
+ let byMaterial = shaders.get(entry.renderableIndex);
7006
+ if (byMaterial === void 0) {
7007
+ byMaterial = /* @__PURE__ */ new Map();
7008
+ shaders.set(entry.renderableIndex, byMaterial);
7009
+ }
7010
+ byMaterial.set(entry.materialHandle, {
7011
+ materialShaderId: entry.materialShaderId,
7012
+ renderState: entry.renderState
7013
+ });
6832
7014
  }
6833
7015
  }
6834
7016
  return shaders;
@@ -6886,25 +7068,12 @@ function skinnedShadowMeshBindGroup(c, entry) {
6886
7068
  const created = c.runtime.device.createBindGroup({
6887
7069
  label: "shadow-pbr-skin-mesh-bg",
6888
7070
  layout,
6889
- entries: [
6890
- {
6891
- binding: 0,
6892
- resource: {
6893
- kind: "buffer",
6894
- value: {
6895
- buffer: c.pipelineState.meshStorageBuffer.buffer,
6896
- size: c.runtime.device.caps.storageBuffer ? MESH_SSBO_BYTES : MESH_UBO_FULL_ARRAY_BYTES
6897
- }
6898
- }
6899
- },
6900
- {
6901
- binding: 1,
6902
- resource: {
6903
- kind: "buffer",
6904
- value: { buffer: skin.buffer, size: allocator.bindingWindowBytes }
6905
- }
6906
- }
6907
- ]
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
+ )
6908
7077
  });
6909
7078
  if (!created.ok) throw created.error;
6910
7079
  return created.value;
@@ -6926,7 +7095,7 @@ function encodeDirectionalShadowPass(c, pass, cascadeIndex, viewport) {
6926
7095
  c.frameState.directionalShadowCacheRecorded = true;
6927
7096
  pass.setViewport(viewport.x, viewport.y, viewport.w, viewport.h, 0, 1);
6928
7097
  pass.setPipeline(pipeline);
6929
- pass.setBindGroup(0, viewBg, [0]);
7098
+ pass.setBindGroup(0, viewBg, [0, 0]);
6930
7099
  pass.setBindGroup(1, materialBg, [0]);
6931
7100
  recordShadowCasterDraws(
6932
7101
  c,
@@ -6950,7 +7119,7 @@ function encodePointShadowPass(c, pass, snapshotIndex, face) {
6950
7119
  const materialBg = ensureSpotShadowMaterialBg(c);
6951
7120
  if (pipeline === null || viewBg === null || materialBg === null) return;
6952
7121
  pass.setPipeline(pipeline);
6953
- pass.setBindGroup(0, viewBg, [0]);
7122
+ pass.setBindGroup(0, viewBg, [0, 0]);
6954
7123
  pass.setBindGroup(1, materialBg, [0]);
6955
7124
  recordShadowCasterDraws(
6956
7125
  c,
@@ -6988,7 +7157,7 @@ function encodeSpotShadowPass(c, pass, snapshotIndex) {
6988
7157
  1
6989
7158
  );
6990
7159
  pass.setPipeline(pipeline);
6991
- pass.setBindGroup(0, viewBg, [0]);
7160
+ pass.setBindGroup(0, viewBg, [0, 0]);
6992
7161
  pass.setBindGroup(1, materialBg, [0]);
6993
7162
  recordShadowCasterDraws(
6994
7163
  c,
@@ -7026,7 +7195,7 @@ function buildMatchedMaterialHandlesByRenderable(dispatch, selector) {
7026
7195
  }
7027
7196
  return handlesByRenderable;
7028
7197
  }
7029
- function recordShadowCasterDraws(c, shadowPass, shadowPipeline2, shadowMeshBindGroup, matchedIndices, shadowShaderByRenderableIdx) {
7198
+ function recordShadowCasterDraws(c, shadowPass, shadowPipeline2, shadowMeshBindGroup, matchedIndices, shadowDispatchByRenderableIdx) {
7030
7199
  const { runtime, pipelineState, validatedOrdered } = c;
7031
7200
  let shadowLastVertexBuffer = null;
7032
7201
  let shadowLastIndexBuffer = null;
@@ -7035,32 +7204,13 @@ function recordShadowCasterDraws(c, shadowPass, shadowPipeline2, shadowMeshBindG
7035
7204
  const entry = validatedOrdered[i];
7036
7205
  if (entry === void 0) continue;
7037
7206
  if (matchedIndices !== null && !matchedIndices.has(entry.renderableIndex)) continue;
7038
- const entryShadowShaderId = shadowShaderByRenderableIdx.get(entry.renderableIndex);
7039
- let entryShadowPipeline = shadowPipeline2;
7040
7207
  if (entry.source.skin !== void 0) {
7041
7208
  const skinPipeline = skinnedShadowPipeline(c, entry);
7042
7209
  if (skinPipeline === null) continue;
7043
- entryShadowPipeline = skinPipeline;
7044
- } else if (entryShadowShaderId !== void 0 && entryShadowShaderId !== SHADOW_CASTER_SHADER_ID) {
7045
- entryShadowPipeline = runtime.getMaterialShaderPipeline?.(
7046
- entryShadowShaderId,
7047
- false,
7048
- // isHdr — shadow depth pass is always LDR
7049
- void 0,
7050
- // renderState
7051
- "triangle-list",
7052
- // topology — shadow PSO targets triangle-list
7053
- void 0,
7054
- // indexFormat
7055
- void 0,
7056
- // variantSet — shadow caster has no variant axes
7057
- "shadow-caster"
7058
- // passKind
7059
- ) ?? shadowPipeline2;
7060
- }
7061
- if (entryShadowPipeline !== shadowLastPipeline && entryShadowPipeline !== null) {
7062
- shadowPass.setPipeline(entryShadowPipeline);
7063
- shadowLastPipeline = entryShadowPipeline;
7210
+ if (skinPipeline !== shadowLastPipeline) {
7211
+ shadowPass.setPipeline(skinPipeline);
7212
+ shadowLastPipeline = skinPipeline;
7213
+ }
7064
7214
  }
7065
7215
  const shadowSubmeshes = entry.mesh.submeshes;
7066
7216
  const hasAnyShadowSubmesh = shadowSubmeshes.some(
@@ -7082,16 +7232,18 @@ function recordShadowCasterDraws(c, shadowPass, shadowPipeline2, shadowMeshBindG
7082
7232
  if (entry.source.skin !== void 0) {
7083
7233
  const skinMeshBindGroup = skinnedShadowMeshBindGroup(c, entry);
7084
7234
  if (skinMeshBindGroup === null) continue;
7085
- shadowPass.setBindGroup(2, skinMeshBindGroup, [
7086
- i * MESH_PER_ENTITY_STRIDE,
7087
- entry.source.skin.byteOffset
7088
- ]);
7235
+ shadowPass.setBindGroup(
7236
+ 2,
7237
+ skinMeshBindGroup,
7238
+ pbrSkinMeshDynamicOffsets(i * MESH_PER_ENTITY_STRIDE, entry.source.skin.byteOffset)
7239
+ );
7089
7240
  } else {
7090
7241
  shadowPass.setBindGroup(2, shadowMeshBindGroup, [i * MESH_PER_ENTITY_STRIDE]);
7091
7242
  }
7092
7243
  let shadowInstanceBuffer = pipelineState.identityInstanceBuffer;
7093
7244
  let shadowInstanceCount = 1;
7094
7245
  const shadowInst = entry.source.instances;
7246
+ if (shadowInst?.instanceCount === 0) continue;
7095
7247
  if (shadowInst !== void 0) {
7096
7248
  const uniformFallback = runtime.device.caps.storageBuffer === false;
7097
7249
  if (uniformFallback && shadowInst.instanceCount > MAX_UNIFORM_INSTANCES) {
@@ -7176,6 +7328,31 @@ function recordShadowCasterDraws(c, shadowPass, shadowPipeline2, shadowMeshBindG
7176
7328
  if (sm.topology !== "triangle-list" && sm.topology !== "triangle-strip") {
7177
7329
  continue;
7178
7330
  }
7331
+ const submeshMaterial = entry.source.materials[sm.materialSlot] ?? entry.source.material;
7332
+ const shadowDispatch = shadowDispatchByRenderableIdx.get(entry.renderableIndex)?.get(submeshMaterial.materialHandle ?? 0);
7333
+ const entryShadowShaderId = shadowDispatch?.materialShaderId;
7334
+ const entryShadowRenderState = shadowDispatch?.renderState;
7335
+ let entryShadowPipeline = entry.source.skin !== void 0 ? shadowLastPipeline : shadowPipeline2;
7336
+ if (entry.source.skin === void 0 && entryShadowShaderId !== void 0 && (entryShadowShaderId !== "forgeax::default-shadow-caster" || entryShadowRenderState !== void 0)) {
7337
+ entryShadowPipeline = runtime.getMaterialShaderPipeline?.(
7338
+ entryShadowShaderId,
7339
+ false,
7340
+ // isHdr — shadow depth pass is always LDR
7341
+ entryShadowRenderState,
7342
+ "triangle-list",
7343
+ // topology — shadow PSO targets triangle-list
7344
+ void 0,
7345
+ // indexFormat
7346
+ void 0,
7347
+ // variantSet — shadow caster has no variant axes
7348
+ "shadow-caster"
7349
+ // passKind
7350
+ ) ?? shadowPipeline2;
7351
+ }
7352
+ if (entryShadowPipeline !== shadowLastPipeline && entryShadowPipeline !== null) {
7353
+ shadowPass.setPipeline(entryShadowPipeline);
7354
+ shadowLastPipeline = entryShadowPipeline;
7355
+ }
7179
7356
  if (entry.mesh.indexed) {
7180
7357
  shadowPass.drawIndexed(sm.indexCount, shadowInstanceCount, sm.indexOffset, 0, 0);
7181
7358
  } else {
@@ -7281,6 +7458,7 @@ function recordMainPass(c, selector, options, graphPass) {
7281
7458
  geometryDepthView,
7282
7459
  validatedOrdered,
7283
7460
  viewBindGroup,
7461
+ viewBindGroupDynamicOffset = 0,
7284
7462
  meshBindGroup,
7285
7463
  frameState,
7286
7464
  bindGroupCounts,
@@ -7315,8 +7493,10 @@ function recordMainPass(c, selector, options, graphPass) {
7315
7493
  const targetDepthView = options?.depthView ?? geometryDepthView;
7316
7494
  const clearColor = options?.clearColor ?? clear;
7317
7495
  const videoHighPerfAvailable = probeVideoHighPerfUpload(runtime.device);
7496
+ const clusteredLighting = c.standardLighting?.kind === "clustered";
7497
+ const clusteredBindGroupMissing = validatedOrdered.length > 0 && !standardTopologyBindGroupReady(c.standardLighting, hdrpClusterBindGroup);
7318
7498
  let hdrpSsaoBindGroup = null;
7319
- if (frameState.isHdrpActive && hdrpClusterBindGroup !== null && c.hdrpSsaoBlurredView !== void 0) {
7499
+ if (clusteredLighting && hdrpClusterBindGroup !== null && c.hdrpSsaoBlurredView !== void 0) {
7320
7500
  const hdrpBuffers = getOrCreateHdrpBuffers(
7321
7501
  runtime,
7322
7502
  frameState.installedPipelineConfig?.clusterGrid
@@ -7330,7 +7510,7 @@ function recordMainPass(c, selector, options, graphPass) {
7330
7510
  );
7331
7511
  }
7332
7512
  }
7333
- const meshGroup2 = frameState.isHdrpActive && hdrpClusterBindGroup !== null ? hdrpSsaoBindGroup ?? hdrpClusterBindGroup : meshBindGroup;
7513
+ const meshGroup2 = clusteredLighting ? hdrpSsaoBindGroup ?? hdrpClusterBindGroup : meshBindGroup;
7334
7514
  let geometryPassEnded = false;
7335
7515
  const mainColorLoadOp = skyboxActive ? "load" : "clear";
7336
7516
  const mainPassResolves = passKind === "forward" && msaaActive && geometryColorResolveView !== null && !splitLdrSprite;
@@ -7366,7 +7546,16 @@ function recordMainPass(c, selector, options, graphPass) {
7366
7546
  ...recordMode === "transmission" ? { variantSet: TRANSMISSION_VARIANT_SET } : {}
7367
7547
  }))
7368
7548
  };
7369
- 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) {
7370
7559
  const MATERIAL_SLICE = STANDARD_PBR_UBO_SIZE;
7371
7560
  const skylightFallback = pipelineState.skylightFallback;
7372
7561
  if (skylightFallback === null) {
@@ -7417,6 +7606,31 @@ function recordMainPass(c, selector, options, graphPass) {
7417
7606
  materialSlice: MATERIAL_SLICE,
7418
7607
  videoHighPerfAvailable,
7419
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
+ },
7420
7634
  materialBgShared: frameState.materialBgShared,
7421
7635
  materialBgAssemblyCache: c.materialBgAssemblyCache,
7422
7636
  bindGroupCounts,
@@ -7525,7 +7739,7 @@ function recordMainPass(c, selector, options, graphPass) {
7525
7739
  );
7526
7740
  }
7527
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);
7528
- pass.setBindGroup(0, viewBindGroup, [0]);
7742
+ pass.setBindGroup(0, viewBindGroup, [viewBindGroupDynamicOffset, 0]);
7529
7743
  const recordGeometry = () => {
7530
7744
  recordGeometryDraws(
7531
7745
  recordContext,
@@ -7924,14 +8138,20 @@ function recordBloomCompositePass(_c, resolve, graphPass) {
7924
8138
  function recordFxaaPass(c, resolve, graphPass) {
7925
8139
  const { runtime, pipelineState, encoder, camera, currentTexture } = c;
7926
8140
  const fxaaActive = camera.antialias === "fxaa";
7927
- 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) {
7928
8142
  const inputView = resolve.resolve("ldrColor");
7929
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;
7930
8150
  const fxaaBglLayout = pipelineState.perPassResources.fxaaBindGroupLayout;
7931
8151
  const fxaaSampler = pipelineState.perPassResources.fxaaSampler;
7932
8152
  const fxaaBg = getOrCreateFromChain(
7933
8153
  c.frameState.postProcessBgCache,
7934
- [inputView],
8154
+ [inputView, fxaaParams],
7935
8155
  "fxaa",
7936
8156
  () => {
7937
8157
  const fxaaBgRes = runtime.device.createBindGroup({
@@ -7948,6 +8168,10 @@ function recordFxaaPass(c, resolve, graphPass) {
7948
8168
  {
7949
8169
  binding: 1,
7950
8170
  resource: { kind: "sampler", value: fxaaSampler }
8171
+ },
8172
+ {
8173
+ binding: 2,
8174
+ resource: { kind: "buffer", value: { buffer: fxaaParams } }
7951
8175
  }
7952
8176
  ]
7953
8177
  });
@@ -7993,6 +8217,32 @@ var FULLSCREEN_DEFAULT_SPEC = Object.freeze({
7993
8217
  geometry: { topology: "triangle-list", vertexLayout: {} },
7994
8218
  renderState: void 0
7995
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
+ }
7996
8246
  function isTemporalFullscreenBinding(values) {
7997
8247
  return values.temporal !== void 0 || values.shader === "forgeax.taa-resolve" || values.shader === "forgeax.motion-blur";
7998
8248
  }
@@ -8341,7 +8591,7 @@ function recordSsaoBlurPass(_c, resolveCtx, ssaoBlurredKey, ssaoRawKey, gbuf0Key
8341
8591
  pass.draw(3, 1, 0, 0);
8342
8592
  if (graphPass === void 0) pass.end();
8343
8593
  }
8344
- 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) {
8345
8595
  if (shader === "fxaa") {
8346
8596
  if (resolveCtx === void 0) {
8347
8597
  throw new PostProcessError({
@@ -8452,7 +8702,7 @@ function dispatchFullscreenPass(ctx, name, shader, color, reads, resolveCtx, com
8452
8702
  if (entry.params !== void 0) {
8453
8703
  const ubo = ctx.runtime.getPostProcessParamsBuffer?.(shader);
8454
8704
  if (ubo !== void 0) {
8455
- const data = ctx.postProcessParams.get(shader);
8705
+ const data = paramsOverride ?? ctx.postProcessParams.get(shader);
8456
8706
  if (data !== void 0) {
8457
8707
  if (data.byteLength !== entry.params.byteSize) {
8458
8708
  throw new PostProcessError({
@@ -8499,9 +8749,7 @@ function dispatchFullscreenPass(ctx, name, shader, color, reads, resolveCtx, com
8499
8749
  if (lookupPipeline === void 0) return;
8500
8750
  const postColorFormat = writeFormat;
8501
8751
  const pipeline = lookupPipeline(shader, built.bindGroupLayout, postColorFormat);
8502
- if (pipeline === null) {
8503
- return;
8504
- }
8752
+ if (pipeline === null) return;
8505
8753
  const handle = built.createHandle(name, pipeline, paramsBuffer);
8506
8754
  const pass = graphPass ?? ctx.encoder.beginRenderPass(
8507
8755
  buildBeginRenderPassDescriptor(
@@ -8527,7 +8775,8 @@ function encodeFullscreenPass(ctx, pass, input) {
8527
8775
  input.rawSwapchainOutput ?? false,
8528
8776
  pass,
8529
8777
  input.outputFormat,
8530
- input.depthView
8778
+ input.depthView,
8779
+ input.paramsOverride
8531
8780
  );
8532
8781
  }
8533
8782
 
@@ -8847,7 +9096,14 @@ function addTypedScenePass(graph, options) {
8847
9096
  internal.pipelineState,
8848
9097
  internal.validated.length > 0 || options.gpuDriven !== void 0,
8849
9098
  internal.bindGroupCounts,
8850
- { directionalShadow, spotShadow }
9099
+ {
9100
+ directionalShadow,
9101
+ spotShadow,
9102
+ projector: internal.spotLightProjector?.view ?? internal.volumetricFog?.projectorView,
9103
+ projectorSampler: internal.spotLightProjector?.sampler ?? internal.volumetricFog?.projectorSampler
9104
+ },
9105
+ true,
9106
+ internal.standardLighting
8851
9107
  );
8852
9108
  if (groups.viewBindGroup !== null) {
8853
9109
  options.gpuDriven?.encode(groups.viewBindGroup, pass, resources);
@@ -9169,7 +9425,10 @@ function addTypedFullscreenPass(graph, options) {
9169
9425
  }),
9170
9426
  outputFormat: options.output.format,
9171
9427
  ...options.depth === void 0 ? {} : { depthView: resolvedDepthView(frame, resources, options.depth) },
9172
- ...options.rawSwapchainOutput === void 0 ? {} : { rawSwapchainOutput: options.rawSwapchainOutput }
9428
+ ...options.rawSwapchainOutput === void 0 ? {} : { rawSwapchainOutput: options.rawSwapchainOutput },
9429
+ ...options.paramsTransform === void 0 ? {} : {
9430
+ paramsOverride: options.paramsTransform(frame.postProcessParams.get(options.shader))
9431
+ }
9173
9432
  });
9174
9433
  }
9175
9434
  });
@@ -9220,14 +9479,27 @@ function addTypedCompositePostEffects(graph, effects, input, output, depth, size
9220
9479
  }
9221
9480
  return ok(void 0);
9222
9481
  }
9223
- function addTypedOutputTransformPass(graph, input, output, outputOnly = false) {
9482
+ function addTypedOutputTransformPass(graph, input, output, options = {}) {
9483
+ const outputOnly = options.outputOnly ?? false;
9224
9484
  return addTypedFullscreenPass(graph, {
9225
9485
  name: outputOnly ? "present" : "output-transform",
9226
9486
  shader: STANDARD_OUTPUT_TRANSFORM_FEATURE_ID,
9227
9487
  input,
9228
9488
  output,
9229
9489
  outputOnly,
9230
- rawSwapchainOutput: outputOnly
9490
+ rawSwapchainOutput: outputOnly,
9491
+ ...options.dither !== void 0 ? {
9492
+ paramsTransform: (params) => {
9493
+ if (params === void 0 || params.byteLength !== 16) return params;
9494
+ const transformed = params.slice();
9495
+ new DataView(
9496
+ transformed.buffer,
9497
+ transformed.byteOffset,
9498
+ transformed.byteLength
9499
+ ).setFloat32(12, options.dither === true ? 1 : 0, true);
9500
+ return transformed;
9501
+ }
9502
+ } : {}
9231
9503
  });
9232
9504
  }
9233
9505
 
@@ -9318,6 +9590,6 @@ function addTaaResolvePass(graph, current, currentTemporal, previousColor, previ
9318
9590
  });
9319
9591
  }
9320
9592
 
9321
- 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_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, abortTemporalGpuSubmit, addStandardSceneDataPass, addTaaResolvePass, addTypedBloomPasses, addTypedCompositePostEffects, addTypedFrameObservationPass, addTypedFullscreenPass, addTypedOutputTransformPass, addTypedScenePass, addTypedSkyboxPass, addTypedSsaoPasses, addTypedTemporalResolvePass, aggregateTemporalDemand, applyParamSnapshotToUbo, assembleMaterialWithSkylightEntries, assertStorageBufferCap, buildBindGroupLayoutDescriptor, buildFoldDispatchPlan, buildFullscreenPostProcessPass, buildLinearLdrMaterialSpecTable, buildPbrPipelineLayouts, buildPbrSkinLayouts, buildPerFrameBindGroups, buildSpecConstTable, cacheKeyOf, cleanPerEntityCache, colorFormatsForPassKind, commitTemporalGpuSubmit, computeProjectionMatrix, computeSplitLdrSprite, computeViewMatrix, configureSurface, createClusterBinScratch, createFullscreenBindGroup, createHdrpBindGroupLayoutDescriptor, createHdrpClusterMembershipBindGroupLayoutDescriptor, createIblPipelines, createPointsLinesLaneAdapter, createPointsLinesLaneContract, createRenderPipelineTarget, createSkylightFallback, createStandardSceneDataTarget, deriveMipUploadLayout, 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 };
9322
- //# sourceMappingURL=chunk-RJRUS55L.mjs.map
9323
- //# sourceMappingURL=chunk-RJRUS55L.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