@forgeax/engine-render 0.1.20 → 0.1.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (372) hide show
  1. package/README.md +215 -0
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/assembly/device-feature-admission.d.ts +18 -0
  4. package/dist/assembly/device-feature-admission.d.ts.map +1 -0
  5. package/dist/assembly/directional-shadow-inspection.d.ts +38 -0
  6. package/dist/assembly/directional-shadow-inspection.d.ts.map +1 -0
  7. package/dist/assembly/factory.d.ts +21 -18
  8. package/dist/assembly/factory.d.ts.map +1 -1
  9. package/dist/assembly/host-contract.d.ts +9 -0
  10. package/dist/assembly/host-contract.d.ts.map +1 -1
  11. package/dist/assembly/render-target-host.d.ts +46 -0
  12. package/dist/assembly/render-target-host.d.ts.map +1 -0
  13. package/dist/assets/render-asset-port.d.ts.map +1 -1
  14. package/dist/authoring.mjs +3 -0
  15. package/dist/authoring.mjs.map +1 -1
  16. package/dist/capture/cube-views.d.ts +21 -0
  17. package/dist/capture/cube-views.d.ts.map +1 -0
  18. package/dist/capture/scheduler.d.ts +54 -0
  19. package/dist/capture/scheduler.d.ts.map +1 -0
  20. package/dist/{chunk-4XINDLAY.mjs → chunk-5YADUCA7.mjs} +1552 -1361
  21. package/dist/chunk-5YADUCA7.mjs.map +1 -0
  22. package/dist/{chunk-TBQJOK2L.mjs → chunk-6OPXDQ2E.mjs} +364 -51
  23. package/dist/chunk-6OPXDQ2E.mjs.map +1 -0
  24. package/dist/{chunk-M2MP4ZOU.mjs → chunk-BXBQQKFQ.mjs} +3 -9
  25. package/dist/chunk-BXBQQKFQ.mjs.map +1 -0
  26. package/dist/{chunk-CDTWTHZR.mjs → chunk-FS2KNUQH.mjs} +548 -6
  27. package/dist/chunk-FS2KNUQH.mjs.map +1 -0
  28. package/dist/components/camera.d.ts +1 -0
  29. package/dist/components/camera.d.ts.map +1 -1
  30. package/dist/components/cube-camera.d.ts +20 -0
  31. package/dist/components/cube-camera.d.ts.map +1 -0
  32. package/dist/components/directional-light.d.ts +11 -2
  33. package/dist/components/directional-light.d.ts.map +1 -1
  34. package/dist/components/directional-shadow-filter.d.ts +25 -0
  35. package/dist/components/directional-shadow-filter.d.ts.map +1 -0
  36. package/dist/components/index.d.ts +2 -0
  37. package/dist/components/index.d.ts.map +1 -1
  38. package/dist/components/light-helpers.d.ts.map +1 -1
  39. package/dist/components/reflection-probe.d.ts +17 -0
  40. package/dist/components/reflection-probe.d.ts.map +1 -0
  41. package/dist/construct-renderer.mjs +5826 -1003
  42. package/dist/construct-renderer.mjs.map +1 -1
  43. package/dist/environment/frame.d.ts.map +1 -1
  44. package/dist/environment/lifecycle.d.ts +5 -0
  45. package/dist/environment/lifecycle.d.ts.map +1 -1
  46. package/dist/errors/render.d.ts +141 -93
  47. package/dist/errors/render.d.ts.map +1 -1
  48. package/dist/extract/directional-shadow-projection.d.ts +23 -0
  49. package/dist/extract/directional-shadow-projection.d.ts.map +1 -0
  50. package/dist/extract/environment.d.ts +14 -0
  51. package/dist/extract/environment.d.ts.map +1 -1
  52. package/dist/extract/visibility.d.ts.map +1 -1
  53. package/dist/features/fullscreen.d.ts.map +1 -1
  54. package/dist/features/targets.d.ts.map +1 -1
  55. package/dist/features/types.d.ts +10 -0
  56. package/dist/features/types.d.ts.map +1 -1
  57. package/dist/fullscreen-post-process-pass.d.ts +13 -2
  58. package/dist/fullscreen-post-process-pass.d.ts.map +1 -1
  59. package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
  60. package/dist/gpu-driven/production-raster.d.ts +0 -1
  61. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  62. package/dist/gpu-scene.d.ts.map +1 -1
  63. package/dist/hdrp-buffers.d.ts +19 -17
  64. package/dist/hdrp-buffers.d.ts.map +1 -1
  65. package/dist/ibl/IblPipelineCache.d.ts +4 -0
  66. package/dist/ibl/IblPipelineCache.d.ts.map +1 -1
  67. package/dist/ibl/kernel-cache.d.ts +36 -0
  68. package/dist/ibl/kernel-cache.d.ts.map +1 -0
  69. package/dist/index.d.ts +28 -3
  70. package/dist/index.d.ts.map +1 -1
  71. package/dist/index.mjs +4 -4
  72. package/dist/index.mjs.map +1 -1
  73. package/dist/inspection-types.d.ts +51 -0
  74. package/dist/inspection-types.d.ts.map +1 -1
  75. package/dist/instances-derived-bounds.d.ts +50 -0
  76. package/dist/instances-derived-bounds.d.ts.map +1 -0
  77. package/dist/internal.mjs +1 -1
  78. package/dist/light-buffer-layout.d.ts +10 -1
  79. package/dist/light-buffer-layout.d.ts.map +1 -1
  80. package/dist/mesh-material-bindings.d.ts +54 -0
  81. package/dist/mesh-material-bindings.d.ts.map +1 -1
  82. package/dist/pbr-pipeline.d.ts +14 -4
  83. package/dist/pbr-pipeline.d.ts.map +1 -1
  84. package/dist/pipeline/standard-forward-lane.d.ts +2 -1
  85. package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
  86. package/dist/pipeline/standard-lighting/errors.d.ts +4 -0
  87. package/dist/pipeline/standard-lighting/errors.d.ts.map +1 -0
  88. package/dist/pipeline/standard-lighting/graph.d.ts +24 -0
  89. package/dist/pipeline/standard-lighting/graph.d.ts.map +1 -0
  90. package/dist/pipeline/standard-lighting/inspection.d.ts +38 -0
  91. package/dist/pipeline/standard-lighting/inspection.d.ts.map +1 -0
  92. package/dist/pipeline/standard-lighting/layout.d.ts +28 -0
  93. package/dist/pipeline/standard-lighting/layout.d.ts.map +1 -0
  94. package/dist/pipeline/standard-lighting/light-frame.d.ts +33 -0
  95. package/dist/pipeline/standard-lighting/light-frame.d.ts.map +1 -0
  96. package/dist/pipeline/standard-lighting/membership.d.ts +14 -0
  97. package/dist/pipeline/standard-lighting/membership.d.ts.map +1 -0
  98. package/dist/pipeline/standard-lighting/prepare.d.ts +16 -0
  99. package/dist/pipeline/standard-lighting/prepare.d.ts.map +1 -0
  100. package/dist/pipeline/standard-lighting/topology.d.ts +45 -0
  101. package/dist/pipeline/standard-lighting/topology.d.ts.map +1 -0
  102. package/dist/pipeline/standard-lighting/transport.d.ts +36 -0
  103. package/dist/pipeline/standard-lighting/transport.d.ts.map +1 -0
  104. package/dist/pipeline/standard-pipeline.d.ts +4 -13
  105. package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
  106. package/dist/pipeline/standard-post.d.ts.map +1 -1
  107. package/dist/pipeline/standard-profile.d.ts +5 -26
  108. package/dist/pipeline/standard-profile.d.ts.map +1 -1
  109. package/dist/pipeline-spec.d.ts +40 -7
  110. package/dist/pipeline-spec.d.ts.map +1 -1
  111. package/dist/point-shadow-inspection.d.ts +21 -0
  112. package/dist/point-shadow-inspection.d.ts.map +1 -0
  113. package/dist/record/frame-lighting.d.ts +27 -32
  114. package/dist/record/frame-lighting.d.ts.map +1 -1
  115. package/dist/record/frame-snapshot.d.ts +28 -14
  116. package/dist/record/frame-snapshot.d.ts.map +1 -1
  117. package/dist/record/frame-targets.d.ts +5 -0
  118. package/dist/record/frame-targets.d.ts.map +1 -1
  119. package/dist/record/frame.d.ts +10 -4
  120. package/dist/record/frame.d.ts.map +1 -1
  121. package/dist/record/gpu-pass-timing/contract.d.ts +96 -0
  122. package/dist/record/gpu-pass-timing/contract.d.ts.map +1 -0
  123. package/dist/record/gpu-pass-timing/errors.d.ts +18 -0
  124. package/dist/record/gpu-pass-timing/errors.d.ts.map +1 -0
  125. package/dist/record/gpu-pass-timing/index.d.ts +8 -0
  126. package/dist/record/gpu-pass-timing/index.d.ts.map +1 -0
  127. package/dist/record/gpu-pass-timing/parser.d.ts +11 -0
  128. package/dist/record/gpu-pass-timing/parser.d.ts.map +1 -0
  129. package/dist/record/gpu-pass-timing/session.d.ts +43 -0
  130. package/dist/record/gpu-pass-timing/session.d.ts.map +1 -0
  131. package/dist/record/gpu-timing.d.ts +8 -3
  132. package/dist/record/gpu-timing.d.ts.map +1 -1
  133. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  134. package/dist/record/main-pass-material.d.ts +22 -1
  135. package/dist/record/main-pass-material.d.ts.map +1 -1
  136. package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
  137. package/dist/record/main-pass.d.ts +2 -0
  138. package/dist/record/main-pass.d.ts.map +1 -1
  139. package/dist/record/render-context.d.ts +43 -2
  140. package/dist/record/render-context.d.ts.map +1 -1
  141. package/dist/record/shadow-pass.d.ts.map +1 -1
  142. package/dist/record/skybox-post-pass.d.ts.map +1 -1
  143. package/dist/record/typed-frame-graph.d.ts +72 -4
  144. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  145. package/dist/record/view-ubo.d.ts +7 -4
  146. package/dist/record/view-ubo.d.ts.map +1 -1
  147. package/dist/reflection/filter.d.ts +22 -0
  148. package/dist/reflection/filter.d.ts.map +1 -0
  149. package/dist/reflection/gpu-table.d.ts +25 -0
  150. package/dist/reflection/gpu-table.d.ts.map +1 -0
  151. package/dist/reflection/projection.d.ts +91 -0
  152. package/dist/reflection/projection.d.ts.map +1 -0
  153. package/dist/render-contract.d.ts +49 -7
  154. package/dist/render-contract.d.ts.map +1 -1
  155. package/dist/render-graph-primitives.d.ts +1 -0
  156. package/dist/render-graph-primitives.d.ts.map +1 -1
  157. package/dist/render-pipeline.d.ts +42 -2
  158. package/dist/render-pipeline.d.ts.map +1 -1
  159. package/dist/render-system-extract.d.ts +58 -17
  160. package/dist/render-system-extract.d.ts.map +1 -1
  161. package/dist/render-system.d.ts +17 -2
  162. package/dist/render-system.d.ts.map +1 -1
  163. package/dist/renderer.d.ts +43 -0
  164. package/dist/renderer.d.ts.map +1 -0
  165. package/dist/scene/render-scene.d.ts +14 -0
  166. package/dist/scene/render-scene.d.ts.map +1 -1
  167. package/dist/shadow-atlas.d.ts +3 -3
  168. package/dist/shadow-atlas.d.ts.map +1 -1
  169. package/dist/ssao-buffers.d.ts +3 -0
  170. package/dist/ssao-buffers.d.ts.map +1 -1
  171. package/dist/targets/contracts.d.ts +66 -0
  172. package/dist/targets/contracts.d.ts.map +1 -0
  173. package/dist/targets/graph-projection.d.ts +29 -0
  174. package/dist/targets/graph-projection.d.ts.map +1 -0
  175. package/dist/targets/material-source.d.ts +34 -0
  176. package/dist/targets/material-source.d.ts.map +1 -0
  177. package/dist/targets/owner.d.ts +32 -0
  178. package/dist/targets/owner.d.ts.map +1 -0
  179. package/dist/targets/physical.d.ts +18 -0
  180. package/dist/targets/physical.d.ts.map +1 -0
  181. package/dist/targets/readback.d.ts +39 -0
  182. package/dist/targets/readback.d.ts.map +1 -0
  183. package/dist/temporal/index.mjs +3 -3
  184. package/dist/typed-render-graph-primitives.d.ts +1 -1
  185. package/dist/typed-render-graph-primitives.d.ts.map +1 -1
  186. package/dist/typed-shadow-passes.d.ts +1 -1
  187. package/dist/typed-shadow-passes.d.ts.map +1 -1
  188. package/dist/volume/passes.d.ts +2 -2
  189. package/dist/volume/passes.d.ts.map +1 -1
  190. package/package.json +20 -20
  191. package/src/__tests__/bloom-exact-zero.unit.test.ts +48 -19
  192. package/src/__tests__/bloom-lifecycle.integration.test.ts +41 -13
  193. package/src/__tests__/camera-target-consumption.unit.test.ts +53 -0
  194. package/src/__tests__/cluster-binner.unit.test.ts +35 -0
  195. package/src/__tests__/cluster-grid-default-surface.unit.test.ts +7 -2
  196. package/src/__tests__/cube-camera-scheduler.unit.test.ts +70 -0
  197. package/src/__tests__/cube-camera-single-submit.perf.test.ts +75 -0
  198. package/src/__tests__/cube-camera-view-matrices.unit.test.ts +41 -0
  199. package/src/__tests__/cube-camera-vocabulary.unit.test.ts +47 -0
  200. package/src/__tests__/cube-camera.integration.test.ts +42 -0
  201. package/src/__tests__/device-feature-admission.unit.test.ts +73 -0
  202. package/src/__tests__/direct-light-snapshot.unit.test.ts +3 -3
  203. package/src/__tests__/docs-gate-green.unit.test.ts +38 -0
  204. package/src/__tests__/factory-contract.integration.test.ts +255 -29
  205. package/src/__tests__/fog-component.unit.test.ts +67 -0
  206. package/src/__tests__/fog.integration.test.ts +86 -0
  207. package/src/__tests__/fullscreen-feature-plan.unit.test.ts +37 -0
  208. package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +10 -9
  209. package/src/__tests__/gpu-driven-indirect-raster.dawn.test.ts +17 -1
  210. package/src/__tests__/gpu-driven-production.integration.test.ts +16 -17
  211. package/src/__tests__/gpu-driven-view-graph.integration.test.ts +12 -4
  212. package/src/__tests__/gpu-pass-timing-browser-runner.ts +281 -0
  213. package/src/__tests__/gpu-pass-timing-coexistence.integration.test.ts +80 -0
  214. package/src/__tests__/gpu-pass-timing-contract.unit.test.ts +243 -0
  215. package/src/__tests__/gpu-pass-timing-exact-zero.unit.test.ts +131 -0
  216. package/src/__tests__/gpu-pass-timing-lifecycle.integration.test.ts +230 -0
  217. package/src/__tests__/gpu-pass-timing-observation.unit.test.ts +233 -0
  218. package/src/__tests__/gpu-pass-timing-session.unit.test.ts +438 -0
  219. package/src/__tests__/gpu-pass-timing.browser.test.ts +26 -0
  220. package/src/__tests__/gpu-pass-timing.rhi-null.unit.test.ts +125 -0
  221. package/src/__tests__/hdrp-group2-visibility.unit.test.ts +24 -0
  222. package/src/__tests__/instances-culling.integration.test.ts +94 -0
  223. package/src/__tests__/instances-derived-bounds.unit.test.ts +118 -0
  224. package/src/__tests__/material-observation.integration.test.ts +101 -0
  225. package/src/__tests__/pbr-view-layout.unit.test.ts +16 -0
  226. package/src/__tests__/point-shadow-inspection.unit.test.ts +60 -0
  227. package/src/__tests__/public-surface.test-d.ts +10 -0
  228. package/src/__tests__/reflection-probe-authoring.unit.test.ts +71 -0
  229. package/src/__tests__/reflection-probe-filter.unit.test.ts +54 -0
  230. package/src/__tests__/reflection-probe-ibl-topology.unit.test.ts +45 -0
  231. package/src/__tests__/reflection-probe-pass-encode.integration.test.ts +101 -0
  232. package/src/__tests__/reflection-probe-performance.perf.test.ts +60 -0
  233. package/src/__tests__/reflection-probe-recovery.integration.test.ts +55 -0
  234. package/src/__tests__/reflection-probe-selection.unit.test.ts +46 -0
  235. package/src/__tests__/render-error-code-owner.test-d.ts +18 -3
  236. package/src/__tests__/render-feature-docs.unit.test.ts +1 -1
  237. package/src/__tests__/render-target-admission.unit.test.ts +80 -0
  238. package/src/__tests__/render-target-graph-projection.unit.test.ts +102 -0
  239. package/src/__tests__/render-target-inspection.unit.test.ts +52 -0
  240. package/src/__tests__/render-target-material-source.test-d.ts +30 -0
  241. package/src/__tests__/render-target-material-source.unit.test.ts +97 -0
  242. package/src/__tests__/render-target-owner-placement.unit.test.ts +203 -0
  243. package/src/__tests__/render-target-owner.unit.test.ts +122 -0
  244. package/src/__tests__/render-target-probe-soak.perf.test.ts +94 -0
  245. package/src/__tests__/render-target-public-schema.json +107 -0
  246. package/src/__tests__/render-target-public-schema.unit.test.ts +91 -0
  247. package/src/__tests__/render-target-public-surface.test-d.ts +83 -0
  248. package/src/__tests__/render-target-readback.integration.test.ts +90 -0
  249. package/src/__tests__/render-target-recovery.integration.test.ts +85 -0
  250. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +48 -0
  251. package/src/__tests__/renderer-lifecycle.integration.test.ts +14 -0
  252. package/src/__tests__/shader-stage-surface.unit.test.ts +19 -4
  253. package/src/__tests__/skin-motion-regression.unit.test.ts +132 -0
  254. package/src/__tests__/skinned-shadow-caster.test.ts +11 -2
  255. package/src/__tests__/standard-cluster-capability.integration.test.ts +63 -0
  256. package/src/__tests__/standard-cluster-transport.unit.test.ts +121 -0
  257. package/src/__tests__/standard-cluster-variant-resolution.unit.test.ts +232 -0
  258. package/src/__tests__/standard-clustered-pipeline.unit.test.ts +8 -5
  259. package/src/__tests__/standard-lighting-inspection.integration.test.ts +138 -0
  260. package/src/__tests__/standard-lighting-ssot.unit.test.ts +18 -0
  261. package/src/__tests__/standard-lighting-write.unit.test.ts +377 -0
  262. package/src/__tests__/standard-lighting.integration.test.ts +67 -0
  263. package/src/__tests__/standard-pipeline.dawn.test.ts +206 -5
  264. package/src/__tests__/standard-pipeline.integration.test.ts +181 -150
  265. package/src/__tests__/standard-probe-binding.unit.test.ts +25 -0
  266. package/src/__tests__/standard-profile-oracle.unit.test.ts +23 -38
  267. package/src/__tests__/typed-pipeline-topology.unit.test.ts +395 -14
  268. package/src/__tests__/urp-pipeline-targets.unit.test.ts +2 -2
  269. package/src/__tests__/visibility-resolver.unit.test.ts +15 -0
  270. package/src/assembly/device-feature-admission.ts +73 -0
  271. package/src/assembly/directional-shadow-inspection.ts +122 -0
  272. package/src/assembly/factory.ts +877 -286
  273. package/src/assembly/host-contract.ts +25 -0
  274. package/src/assembly/render-target-host.ts +847 -0
  275. package/src/assets/asset-decoders.ts +1 -1
  276. package/src/assets/render-asset-port.ts +7 -1
  277. package/src/capture/cube-views.ts +69 -0
  278. package/src/capture/scheduler.ts +265 -0
  279. package/src/components/__tests__/schema-contract.unit.test.ts +12 -0
  280. package/src/components/__tests__/schema-parity.unit.test.ts +9 -0
  281. package/src/components/camera.ts +4 -0
  282. package/src/components/cube-camera.ts +52 -0
  283. package/src/components/directional-light.ts +22 -2
  284. package/src/components/directional-shadow-filter.ts +57 -0
  285. package/src/components/index.ts +2 -0
  286. package/src/components/light-helpers.ts +81 -7
  287. package/src/components/reflection-probe.ts +49 -0
  288. package/src/environment/frame.ts +6 -24
  289. package/src/environment/lifecycle.ts +12 -0
  290. package/src/errors/render.ts +286 -142
  291. package/src/extract/directional-shadow-projection.ts +61 -0
  292. package/src/extract/environment.ts +57 -0
  293. package/src/extract/visibility.ts +25 -1
  294. package/src/features/fullscreen.ts +3 -0
  295. package/src/features/targets.ts +4 -1
  296. package/src/features/types.ts +10 -0
  297. package/src/fullscreen-post-process-pass.ts +43 -2
  298. package/src/gpu-driven/batch-topology.ts +1 -0
  299. package/src/gpu-driven/production-raster.ts +1 -3
  300. package/src/gpu-scene.ts +8 -1
  301. package/src/hdrp-buffers.ts +165 -59
  302. package/src/ibl/IblPipelineCache.ts +11 -0
  303. package/src/ibl/kernel-cache.ts +88 -0
  304. package/src/index.ts +148 -0
  305. package/src/inspection-types.ts +56 -0
  306. package/src/instances-derived-bounds.ts +171 -0
  307. package/src/light-buffer-layout.ts +20 -5
  308. package/src/mesh-material-bindings.ts +132 -0
  309. package/src/pbr-pipeline.ts +106 -17
  310. package/src/pipeline/standard-forward-lane.ts +46 -16
  311. package/src/pipeline/standard-lighting/errors.ts +6 -0
  312. package/src/pipeline/standard-lighting/graph.ts +157 -0
  313. package/src/pipeline/standard-lighting/inspection.ts +88 -0
  314. package/src/pipeline/standard-lighting/layout.ts +31 -0
  315. package/src/pipeline/standard-lighting/light-frame.ts +34 -0
  316. package/src/pipeline/standard-lighting/membership.ts +43 -0
  317. package/src/pipeline/standard-lighting/prepare.ts +37 -0
  318. package/src/pipeline/standard-lighting/topology.ts +120 -0
  319. package/src/pipeline/standard-lighting/transport.ts +77 -0
  320. package/src/pipeline/standard-pipeline.ts +122 -193
  321. package/src/pipeline/standard-post.ts +26 -7
  322. package/src/pipeline/standard-profile.ts +17 -47
  323. package/src/pipeline-spec.ts +84 -21
  324. package/src/point-shadow-inspection.ts +55 -0
  325. package/src/record/__tests__/temporal-payload.unit.test.ts +42 -1
  326. package/src/record/__tests__/transparent-group2-selection.unit.test.ts +73 -0
  327. package/src/record/__tests__/view-ubo-layout.unit.test.ts +40 -0
  328. package/src/record/frame-lighting.ts +367 -427
  329. package/src/record/frame-snapshot.ts +35 -27
  330. package/src/record/frame-targets.ts +17 -0
  331. package/src/record/frame.ts +261 -74
  332. package/src/record/gpu-pass-timing/contract.ts +180 -0
  333. package/src/record/gpu-pass-timing/errors.ts +34 -0
  334. package/src/record/gpu-pass-timing/index.ts +37 -0
  335. package/src/record/gpu-pass-timing/parser.ts +97 -0
  336. package/src/record/gpu-pass-timing/session.ts +938 -0
  337. package/src/record/gpu-timing.ts +21 -12
  338. package/src/record/main-pass-geometry.ts +120 -80
  339. package/src/record/main-pass-material.ts +95 -9
  340. package/src/record/main-pass-sprite-draws.ts +54 -25
  341. package/src/record/main-pass.ts +71 -15
  342. package/src/record/render-context.ts +72 -0
  343. package/src/record/shadow-pass.ts +17 -28
  344. package/src/record/skybox-post-pass.ts +20 -4
  345. package/src/record/typed-frame-graph.ts +893 -44
  346. package/src/record/view-ubo.ts +63 -28
  347. package/src/reflection/filter.ts +81 -0
  348. package/src/reflection/gpu-table.ts +44 -0
  349. package/src/reflection/projection.ts +281 -0
  350. package/src/render-contract.ts +76 -5
  351. package/src/render-graph-primitives.ts +9 -4
  352. package/src/render-pipeline.ts +117 -2
  353. package/src/render-system-extract.ts +557 -100
  354. package/src/render-system.ts +1095 -66
  355. package/src/renderer.ts +222 -0
  356. package/src/scene/render-scene.ts +115 -9
  357. package/src/shadow-atlas.ts +5 -5
  358. package/src/ssao-buffers.ts +9 -2
  359. package/src/targets/contracts.ts +182 -0
  360. package/src/targets/graph-projection.ts +169 -0
  361. package/src/targets/material-source.ts +143 -0
  362. package/src/targets/owner.ts +214 -0
  363. package/src/targets/physical.ts +18 -0
  364. package/src/targets/readback.ts +153 -0
  365. package/src/transmission/__tests__/standard-transmission.dawn.test.ts +59 -20
  366. package/src/typed-render-graph-primitives.ts +5 -3
  367. package/src/typed-shadow-passes.ts +46 -40
  368. package/src/volume/passes.ts +15 -9
  369. package/dist/chunk-4XINDLAY.mjs.map +0 -1
  370. package/dist/chunk-CDTWTHZR.mjs.map +0 -1
  371. package/dist/chunk-M2MP4ZOU.mjs.map +0 -1
  372. package/dist/chunk-TBQJOK2L.mjs.map +0 -1
@@ -0,0 +1,18 @@
1
+ import type { Texture, TextureView } from '@forgeax/engine-rhi';
2
+ import type { RenderTargetDescriptor } from './contracts';
3
+
4
+ /** Renderer-private physical storage for one logical target generation. */
5
+ export interface RenderTargetPhysical {
6
+ readonly generation: number;
7
+ readonly descriptor: RenderTargetDescriptor;
8
+ readonly texture: Texture;
9
+ readonly view: TextureView;
10
+ readonly mipViews: readonly TextureView[];
11
+ readonly colorTextures: readonly Texture[];
12
+ readonly faceViews: readonly TextureView[];
13
+ readonly depthTextures: readonly Texture[];
14
+ readonly depthViews: readonly TextureView[];
15
+ readonly resolveTexture?: Texture;
16
+ readonly resolveView: TextureView;
17
+ readonly resolveFaceViews: readonly TextureView[];
18
+ }
@@ -0,0 +1,153 @@
1
+ import type { RenderError } from '../errors/render';
2
+ import { RenderTargetOperationFailedError, RenderTargetStateInvalidError } from '../errors/render';
3
+ import type { RenderResult } from '../render-contract';
4
+ import type { RenderTarget } from './contracts';
5
+
6
+ export interface RenderTargetReadbackReceipt {
7
+ readonly frameId: number;
8
+ readonly deviceGeneration: number;
9
+ }
10
+
11
+ export interface RenderTargetReadbackTicketInput {
12
+ readonly frameId?: number;
13
+ deviceGeneration: number;
14
+ readonly mipLevel: number;
15
+ readonly face?: number;
16
+ readonly width: number;
17
+ readonly height: number;
18
+ readonly bytesPerPixel: number;
19
+ }
20
+
21
+ export interface RenderTargetReadbackTicket {
22
+ readonly target: RenderTarget;
23
+ frameId: number | undefined;
24
+ deviceGeneration: number;
25
+ readonly mipLevel: number;
26
+ readonly face?: number;
27
+ readonly width: number;
28
+ readonly height: number;
29
+ readonly bytesPerRow: number;
30
+ readonly byteLength: number;
31
+ consumed: boolean;
32
+ }
33
+
34
+ export interface RenderTargetReadbackResult {
35
+ readonly bytes: Uint8Array;
36
+ readonly frameId: number;
37
+ readonly deviceGeneration: number;
38
+ readonly mipLevel: number;
39
+ readonly face?: number;
40
+ }
41
+
42
+ function invalidTicket(
43
+ ticket: RenderTargetReadbackTicket,
44
+ reason: 'uninitialized' | 'generation-mismatch' | 'destroyed',
45
+ ): RenderResult<never, RenderError> {
46
+ return {
47
+ ok: false,
48
+ error: new RenderTargetStateInvalidError({
49
+ operation: 'readback',
50
+ reason,
51
+ state: reason === 'destroyed' ? 'destroyed' : 'active',
52
+ generation: ticket.deviceGeneration,
53
+ }),
54
+ };
55
+ }
56
+
57
+ export function bindRenderTargetReadbackTicket(
58
+ ticket: RenderTargetReadbackTicket,
59
+ receipt: RenderTargetReadbackReceipt,
60
+ ): RenderResult<void, RenderError> {
61
+ if (ticket.consumed) return invalidTicket(ticket, 'destroyed');
62
+ if (ticket.frameId !== undefined) {
63
+ return invalidTicket(ticket, 'generation-mismatch');
64
+ }
65
+ ticket.frameId = receipt.frameId;
66
+ ticket.deviceGeneration = receipt.deviceGeneration;
67
+ return { ok: true, value: undefined };
68
+ }
69
+
70
+ export function createRenderTargetReadbackTicket(
71
+ target: RenderTarget,
72
+ input: RenderTargetReadbackTicketInput,
73
+ ): RenderResult<RenderTargetReadbackTicket, RenderError> {
74
+ if (
75
+ (input.frameId !== undefined && (!Number.isInteger(input.frameId) || input.frameId < 0)) ||
76
+ !Number.isInteger(input.deviceGeneration) ||
77
+ input.deviceGeneration < 0 ||
78
+ !Number.isInteger(input.mipLevel) ||
79
+ input.mipLevel < 0 ||
80
+ !Number.isInteger(input.width) ||
81
+ input.width < 1 ||
82
+ !Number.isInteger(input.height) ||
83
+ input.height < 1 ||
84
+ !Number.isInteger(input.bytesPerPixel) ||
85
+ input.bytesPerPixel < 1
86
+ ) {
87
+ return {
88
+ ok: false,
89
+ error: new RenderTargetOperationFailedError({
90
+ operation: 'readback',
91
+ stage: 'copy',
92
+ generation: input.deviceGeneration,
93
+ cause: new Error('readback dimensions and identity must be non-negative integers'),
94
+ recovery: 'retry',
95
+ }),
96
+ };
97
+ }
98
+ const unalignedRowBytes = input.width * input.bytesPerPixel;
99
+ const bytesPerRow = Math.ceil(unalignedRowBytes / 256) * 256;
100
+ return {
101
+ ok: true,
102
+ value: {
103
+ target,
104
+ frameId: input.frameId,
105
+ deviceGeneration: input.deviceGeneration,
106
+ mipLevel: input.mipLevel,
107
+ ...(input.face === undefined ? {} : { face: input.face }),
108
+ width: input.width,
109
+ height: input.height,
110
+ bytesPerRow,
111
+ byteLength: bytesPerRow * input.height,
112
+ consumed: false,
113
+ },
114
+ };
115
+ }
116
+
117
+ export function completeRenderTargetReadback(
118
+ ticket: RenderTargetReadbackTicket,
119
+ receipt: RenderTargetReadbackReceipt,
120
+ bytes: Uint8Array,
121
+ ): RenderResult<RenderTargetReadbackResult, RenderError> {
122
+ if (ticket.consumed) return invalidTicket(ticket, 'destroyed');
123
+ if (
124
+ ticket.frameId === undefined ||
125
+ receipt.frameId !== ticket.frameId ||
126
+ receipt.deviceGeneration !== ticket.deviceGeneration
127
+ ) {
128
+ return invalidTicket(ticket, 'generation-mismatch');
129
+ }
130
+ if (bytes.byteLength !== ticket.byteLength) {
131
+ return {
132
+ ok: false,
133
+ error: new RenderTargetOperationFailedError({
134
+ operation: 'readback',
135
+ stage: 'copy',
136
+ generation: ticket.deviceGeneration,
137
+ cause: new Error(`expected ${ticket.byteLength} bytes, received ${bytes.byteLength}`),
138
+ recovery: 'retry',
139
+ }),
140
+ };
141
+ }
142
+ ticket.consumed = true;
143
+ return {
144
+ ok: true,
145
+ value: {
146
+ bytes,
147
+ frameId: ticket.frameId,
148
+ deviceGeneration: ticket.deviceGeneration,
149
+ mipLevel: ticket.mipLevel,
150
+ ...(ticket.face === undefined ? {} : { face: ticket.face }),
151
+ },
152
+ };
153
+ }
@@ -14,6 +14,7 @@ import {
14
14
  DirectionalLight,
15
15
  MeshFilter,
16
16
  MeshRenderer,
17
+ PointLight,
17
18
  TONEMAP_LINEAR,
18
19
  } from '../../components';
19
20
  import { constructRendererHost, type RhiBackendPack } from '../../construct-renderer';
@@ -57,8 +58,10 @@ interface TransmissionCase {
57
58
  readonly thicknessTexture?: number;
58
59
  readonly masked?: boolean;
59
60
  readonly overlap?: boolean;
60
- readonly lane?: 'direct' | 'clustered';
61
+ readonly renderPath?: 'forward' | 'deferred';
61
62
  readonly surfaceQuat?: readonly [number, number, number, number];
63
+ readonly directionalLight?: boolean;
64
+ readonly localLight?: boolean;
62
65
  }
63
66
 
64
67
  function textureAsset(value: number): TextureAsset {
@@ -106,6 +109,7 @@ async function renderCase(testCase: TransmissionCase): Promise<{
106
109
  readonly pixel: readonly [number, number, number, number];
107
110
  readonly passNames: readonly string[];
108
111
  readonly observation: boolean;
112
+ readonly errors: readonly string[];
109
113
  }> {
110
114
  let device: GPUDevice | undefined;
111
115
  let configuredDevice: GPUDevice | undefined;
@@ -157,7 +161,7 @@ async function renderCase(testCase: TransmissionCase): Promise<{
157
161
  {
158
162
  standardProfile: {
159
163
  ...DEFAULT_STANDARD_PROFILE,
160
- lighting: testCase.lane ?? 'direct',
164
+ renderPath: testCase.renderPath ?? DEFAULT_STANDARD_PROFILE.renderPath,
161
165
  },
162
166
  },
163
167
  { shaderManifestUrl: ENGINE_MANIFEST_URL },
@@ -172,6 +176,10 @@ async function renderCase(testCase: TransmissionCase): Promise<{
172
176
  }
173
177
 
174
178
  const { renderer } = host.value;
179
+ const errors: string[] = [];
180
+ const unsubscribe = renderer.subscribe((event) => {
181
+ if (event.kind === 'error') errors.push(event.error.code);
182
+ });
175
183
  const world = new World();
176
184
  const lease = renderer.attach(world);
177
185
  if (!lease.ok) throw lease.error;
@@ -277,12 +285,22 @@ async function renderCase(testCase: TransmissionCase): Promise<{
277
285
  );
278
286
  expect(cameraEntity.ok, `${testCase.name}: camera spawn must succeed`).toBe(true);
279
287
  if (!cameraEntity.ok) throw cameraEntity.error;
280
- const lightEntity = world.spawn({
281
- component: DirectionalLight,
282
- data: { direction: [0, 0, -1], color: [1, 1, 1], intensity: 1, castShadow: false },
283
- });
284
- expect(lightEntity.ok, `${testCase.name}: light spawn must succeed`).toBe(true);
285
- if (!lightEntity.ok) throw lightEntity.error;
288
+ if (testCase.directionalLight !== false) {
289
+ const lightEntity = world.spawn({
290
+ component: DirectionalLight,
291
+ data: { direction: [0, 0, -1], color: [1, 1, 1], intensity: 1, castShadow: false },
292
+ });
293
+ expect(lightEntity.ok, `${testCase.name}: light spawn must succeed`).toBe(true);
294
+ if (!lightEntity.ok) throw lightEntity.error;
295
+ }
296
+ if (testCase.localLight === true) {
297
+ const localLightEntity = world.spawn(
298
+ { component: Transform, data: { pos: [0, 0, 1] } },
299
+ { component: PointLight, data: { intensity: 8, range: 10 } },
300
+ );
301
+ expect(localLightEntity.ok, `${testCase.name}: local light spawn must succeed`).toBe(true);
302
+ if (!localLightEntity.ok) throw localLightEntity.error;
303
+ }
286
304
  world.update().unwrap();
287
305
  const drawn = renderer.draw({
288
306
  leases: [lease.value],
@@ -299,10 +317,12 @@ async function renderCase(testCase: TransmissionCase): Promise<{
299
317
  const renderDevice = configuredDevice ?? device;
300
318
  const passNames = renderer.inspect().perFramePassNames;
301
319
  expect(renderer.inspect().renderScene.projectionRecords).toBeGreaterThanOrEqual(1);
320
+ unsubscribe();
302
321
  return {
303
322
  pixel: await readCenter(renderDevice, target),
304
323
  passNames,
305
324
  observation: observation.ok,
325
+ errors,
306
326
  };
307
327
  }
308
328
 
@@ -403,9 +423,9 @@ describe('Standard transmission Dawn ROI', () => {
403
423
  );
404
424
  }, 120_000);
405
425
 
406
- it('exercises direct/clustered lanes and transmission-before-BLEND overlap order', async () => {
407
- const direct = await renderCase({
408
- name: 'direct',
426
+ it('exercises both Standard render paths and transmission-before-BLEND overlap order', async () => {
427
+ const forward = await renderCase({
428
+ name: 'forward',
409
429
  transmission: 1,
410
430
  ior: 1.5,
411
431
  thickness: 0.5,
@@ -413,10 +433,10 @@ describe('Standard transmission Dawn ROI', () => {
413
433
  attenuationColor: [0.7, 0.8, 0.9],
414
434
  attenuationDistance: 2,
415
435
  overlap: true,
416
- lane: 'direct',
436
+ renderPath: 'forward',
417
437
  });
418
- const clustered = await renderCase({
419
- name: 'clustered',
438
+ const deferred = await renderCase({
439
+ name: 'deferred',
420
440
  transmission: 1,
421
441
  ior: 1.5,
422
442
  thickness: 0.5,
@@ -424,14 +444,33 @@ describe('Standard transmission Dawn ROI', () => {
424
444
  attenuationColor: [0.7, 0.8, 0.9],
425
445
  attenuationDistance: 2,
426
446
  overlap: true,
427
- lane: 'clustered',
447
+ renderPath: 'deferred',
428
448
  });
429
- const transmissionIndex = direct.passNames.indexOf('transmission-forward');
430
- const transparentIndex = direct.passNames.indexOf('transparent');
449
+ const transmissionIndex = forward.passNames.indexOf('transmission-forward');
450
+ const transparentIndex = forward.passNames.indexOf('transparent');
431
451
  expect(transmissionIndex).toBeGreaterThanOrEqual(0);
432
452
  expect(transparentIndex).toBeGreaterThan(transmissionIndex);
433
- expect(clustered.pixel).not.toEqual(direct.pixel);
434
- expect(clustered.pixel.every(Number.isFinite)).toBe(true);
435
- expect(direct.pixel.every(Number.isFinite)).toBe(true);
453
+ expect(deferred.pixel.every(Number.isFinite)).toBe(true);
454
+ expect(forward.pixel.every(Number.isFinite)).toBe(true);
455
+ }, 120_000);
456
+
457
+ it('submits a real clustered local-light frame through the Standard front door', async () => {
458
+ const clustered = await renderCase({
459
+ name: 'clustered-local-light',
460
+ transmission: 0,
461
+ ior: 1.5,
462
+ thickness: 0,
463
+ roughness: 0.4,
464
+ attenuationColor: [1, 1, 1],
465
+ directionalLight: false,
466
+ localLight: true,
467
+ renderPath: 'forward',
468
+ });
469
+
470
+ expect(clustered.passNames).toEqual(
471
+ expect.arrayContaining(['cluster-membership-producer', 'main', 'output-transform']),
472
+ );
473
+ expect(clustered.observation).toBe(true);
474
+ expect(clustered.errors).toEqual([]);
436
475
  }, 120_000);
437
476
  });
@@ -222,6 +222,8 @@ export function addTypedScenePass(
222
222
  projectorSampler:
223
223
  internal.spotLightProjector?.sampler ?? internal.volumetricFog?.projectorSampler,
224
224
  },
225
+ true,
226
+ internal.standardLighting,
225
227
  );
226
228
  if (groups.viewBindGroup !== null) {
227
229
  options.gpuDriven?.encode(groups.viewBindGroup, pass, resources);
@@ -696,7 +698,7 @@ export function addTypedOutputTransformPass(
696
698
  graph: RenderGraphBuilder<RenderPipelineFrame>,
697
699
  input: RenderPipelineTarget,
698
700
  output: RenderPipelineTarget,
699
- options: { readonly outputOnly?: boolean; readonly outputTransform?: boolean } = {},
701
+ options: { readonly outputOnly?: boolean; readonly dither?: boolean } = {},
700
702
  ): Result<void, RenderGraphError> {
701
703
  const outputOnly = options.outputOnly ?? false;
702
704
  return addTypedFullscreenPass(graph, {
@@ -706,7 +708,7 @@ export function addTypedOutputTransformPass(
706
708
  output,
707
709
  outputOnly,
708
710
  rawSwapchainOutput: outputOnly,
709
- ...(options.outputTransform === true
711
+ ...(options.dither !== undefined
710
712
  ? {
711
713
  paramsTransform: (params: Uint8Array | undefined): Uint8Array | undefined => {
712
714
  if (params === undefined || params.byteLength !== 16) return params;
@@ -715,7 +717,7 @@ export function addTypedOutputTransformPass(
715
717
  transformed.buffer,
716
718
  transformed.byteOffset,
717
719
  transformed.byteLength,
718
- ).setFloat32(12, 1, true);
720
+ ).setFloat32(12, options.dither === true ? 1 : 0, true);
719
721
  return transformed;
720
722
  },
721
723
  }
@@ -13,7 +13,7 @@ import { createRenderPipelineTarget, type RenderPipelineTarget } from './render-
13
13
  import { resolveDirectionalShadowAtlasGrid } from './typed-shadow-atlas';
14
14
 
15
15
  export interface TypedShadowTargets {
16
- readonly directional: RenderPipelineTarget;
16
+ readonly directional?: RenderPipelineTarget | undefined;
17
17
  readonly spot: RenderPipelineTarget;
18
18
  readonly point?: RenderPipelineTarget | undefined;
19
19
  }
@@ -44,44 +44,50 @@ export function addTypedShadowPasses(
44
44
  graph: RenderGraphBuilder<RenderPipelineFrame>,
45
45
  topology: RenderPipelineTopology,
46
46
  ): Result<TypedShadowTargets, RenderGraphError> {
47
- const atlasGrid = resolveDirectionalShadowAtlasGrid(topology.shadow.cascadeCount);
48
- const directional = createRenderPipelineTarget(graph, 'directional-shadow-depth', {
49
- format: 'depth32float',
50
- size: {
51
- width: topology.shadow.mapSize * atlasGrid.columns,
52
- height: topology.shadow.mapSize * atlasGrid.rows,
53
- },
54
- });
55
- if (!directional.ok) return directional;
56
- for (let cascade = 0; cascade < topology.shadow.cascadeCount; cascade += 1) {
57
- const col = cascade % atlasGrid.columns;
58
- const row = Math.floor(cascade / atlasGrid.columns);
59
- const added = recordShadowPass(
60
- graph,
61
- `shadowCascade${cascade}`,
62
- directional.value,
63
- cascade === 0 ? 'clear' : 'load',
64
- ({ pass, frame }) =>
65
- encodeDirectionalShadowPass(frame as never, pass, cascade, {
66
- x: col * topology.shadow.mapSize,
67
- y: row * topology.shadow.mapSize,
68
- w: topology.shadow.mapSize,
69
- h: topology.shadow.mapSize,
70
- }),
71
- (frame) => !(frame as _InternalRenderPipelineContext).directionalShadowCacheReuse,
72
- );
73
- if (!added.ok) return added;
47
+ let directional: RenderPipelineTarget | undefined;
48
+ if (topology.shadow.directional !== 'disabled') {
49
+ const { mapSize, cascadeCount } = topology.shadow.directional;
50
+ const atlasGrid = resolveDirectionalShadowAtlasGrid(cascadeCount);
51
+ const target = createRenderPipelineTarget(graph, 'directional-shadow-depth', {
52
+ format: 'depth32float',
53
+ size: {
54
+ width: mapSize * atlasGrid.columns,
55
+ height: mapSize * atlasGrid.rows,
56
+ },
57
+ });
58
+ if (!target.ok) return target;
59
+ const directionalTarget = target.value;
60
+ directional = directionalTarget;
61
+ for (let cascade = 0; cascade < cascadeCount; cascade += 1) {
62
+ const col = cascade % atlasGrid.columns;
63
+ const row = Math.floor(cascade / atlasGrid.columns);
64
+ const added = recordShadowPass(
65
+ graph,
66
+ `shadowCascade${cascade}`,
67
+ directionalTarget,
68
+ cascade === 0 ? 'clear' : 'load',
69
+ ({ pass, frame }) =>
70
+ encodeDirectionalShadowPass(frame as never, pass, cascade, {
71
+ x: col * mapSize,
72
+ y: row * mapSize,
73
+ w: mapSize,
74
+ h: mapSize,
75
+ }),
76
+ (frame) => !(frame as _InternalRenderPipelineContext).directionalShadowCacheReuse,
77
+ );
78
+ if (!added.ok) return added;
79
+ }
80
+ const observation = graph.addCopyPass('directional-shadow-observation', {
81
+ accesses: [{ resource: directionalTarget.view, usage: 'copy-src' }],
82
+ encode: ({ frame, resources }) => {
83
+ const internal = frame as _InternalRenderPipelineContext;
84
+ const texture = resources.texture(directionalTarget.texture);
85
+ if (!texture.ok) throw texture.error;
86
+ internal.pipelineState.perPassResources.shadowTexture = texture.value;
87
+ },
88
+ });
89
+ if (!observation.ok) return observation;
74
90
  }
75
- const observation = graph.addCopyPass('directional-shadow-observation', {
76
- accesses: [{ resource: directional.value.view, usage: 'copy-src' }],
77
- encode: ({ frame, resources }) => {
78
- const internal = frame as _InternalRenderPipelineContext;
79
- const texture = resources.texture(directional.value.texture);
80
- if (!texture.ok) throw texture.error;
81
- internal.pipelineState.perPassResources.shadowTexture = texture.value;
82
- },
83
- });
84
- if (!observation.ok) return observation;
85
91
 
86
92
  let point: RenderPipelineTarget | undefined;
87
93
  if (topology.shadow.pointCount > 0) {
@@ -148,7 +154,7 @@ export function addTypedShadowPasses(
148
154
 
149
155
  const spot = createRenderPipelineTarget(graph, 'spot-shadow-depth', {
150
156
  format: 'depth32float',
151
- size: { width: topology.shadow.mapSize * 2, height: topology.shadow.mapSize * 2 },
157
+ size: { width: topology.shadow.spotMapSize * 2, height: topology.shadow.spotMapSize * 2 },
152
158
  });
153
159
  if (!spot.ok) return spot;
154
160
  for (let spotIndex = 0; spotIndex < Math.max(1, topology.shadow.spotCount); spotIndex += 1) {
@@ -162,5 +168,5 @@ export function addTypedShadowPasses(
162
168
  if (!added.ok) return added;
163
169
  }
164
170
 
165
- return ok({ directional: directional.value, spot: spot.value, point });
171
+ return ok({ directional, spot: spot.value, point });
166
172
  }
@@ -89,7 +89,7 @@ export interface VolumetricFogPassInputs {
89
89
  readonly density: GraphTextureView;
90
90
  readonly params: GraphBuffer;
91
91
  readonly view: GraphBuffer;
92
- readonly directionalShadow: GraphTextureView;
92
+ readonly directionalShadow?: GraphTextureView;
93
93
  readonly spotShadow: GraphTextureView;
94
94
  readonly pointLights: GraphBuffer;
95
95
  readonly spotLights: GraphBuffer;
@@ -166,7 +166,7 @@ function importVolumeInputs(
166
166
  topology: RenderPipelineTopology,
167
167
  linearHdr: RenderPipelineTarget,
168
168
  sceneDepth: GraphTextureView,
169
- directionalShadow: GraphTextureView,
169
+ directionalShadow: GraphTextureView | undefined,
170
170
  spotShadow: GraphTextureView,
171
171
  selectedLightKind: 'directional' | 'point' | 'spot' | undefined,
172
172
  ): Result<ImportedVolumeInputs | undefined, RenderGraphError> {
@@ -268,7 +268,7 @@ function importVolumeInputs(
268
268
  density: density.value,
269
269
  params: params.value,
270
270
  view: view.value,
271
- directionalShadow,
271
+ ...(directionalShadow === undefined ? {} : { directionalShadow }),
272
272
  spotShadow,
273
273
  pointLights: pointLights.value,
274
274
  spotLights: spotLights.value,
@@ -281,7 +281,7 @@ export function addAuthoredVolumetricFogPasses(
281
281
  topology: RenderPipelineTopology,
282
282
  linearHdr: RenderPipelineTarget,
283
283
  sceneDepth: GraphTextureView,
284
- directionalShadow: GraphTextureView,
284
+ directionalShadow: GraphTextureView | undefined,
285
285
  spotShadow: GraphTextureView,
286
286
  ): Result<void, RenderGraphError> {
287
287
  // A paired Point+Spot volume stores the Spot shadow lane as well. The
@@ -320,6 +320,15 @@ export function addVolumetricFogPasses(
320
320
  const packedLayers = Math.ceil(inputs.resources.extent.depth / VOLUME_PACKING_Z);
321
321
  const resolvedDispatchX = Math.ceil(inputs.resources.resolvedExtent.width / 8);
322
322
  const resolvedDispatchY = Math.ceil(inputs.resources.resolvedExtent.height / 8);
323
+ // Point-light volume injection has no cube-array shadow sampler. It keeps
324
+ // the directional cascade count at zero and therefore needs only a valid
325
+ // 2D depth binding; use the always-present spot target as the inert binding
326
+ // when directional shadows are disabled. Directional selection still uses
327
+ // its own target whenever one exists, and spot selection uses spotShadow.
328
+ const shadowResource =
329
+ inputs.selectedLightKind === 'spot'
330
+ ? inputs.spotShadow
331
+ : (inputs.directionalShadow ?? inputs.spotShadow);
323
332
  let inject: ComputeState | undefined;
324
333
  let integrate: ComputeState | undefined;
325
334
  let temporal: ComputeState | undefined;
@@ -330,8 +339,7 @@ export function addVolumetricFogPasses(
330
339
  { resource: inputs.params, usage: 'uniform-read' },
331
340
  { resource: inputs.view, usage: 'uniform-read' },
332
341
  {
333
- resource:
334
- inputs.selectedLightKind === 'spot' ? inputs.spotShadow : inputs.directionalShadow,
342
+ resource: shadowResource,
335
343
  usage: 'sampled-read',
336
344
  },
337
345
  { resource: inputs.spotLights, usage: 'storage-read' },
@@ -359,9 +367,7 @@ export function addVolumetricFogPasses(
359
367
  { binding: 7, visibility: 4, buffer: { type: 'read-only-storage' } },
360
368
  { binding: 8, visibility: 4, buffer: { type: 'read-only-storage' } },
361
369
  ]);
362
- const shadow = resources.textureView(
363
- inputs.selectedLightKind === 'spot' ? inputs.spotShadow : inputs.directionalShadow,
364
- );
370
+ const shadow = resources.textureView(shadowResource);
365
371
  const params = resources.buffer(inputs.params);
366
372
  const view = resources.buffer(inputs.view);
367
373
  const spotLights = resources.buffer(inputs.spotLights);