@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
@@ -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
+ }
@@ -0,0 +1,128 @@
1
+ import { err, ok, type Result } from '@forgeax/engine-types';
2
+ import { TemporalFrameSubmitError } from '../errors/render';
3
+
4
+ export type TemporalResetReason =
5
+ | 'none'
6
+ | 'camera-cut'
7
+ | 'resize'
8
+ | 'signature-change'
9
+ | 'depth-discontinuity'
10
+ | 'out-of-screen'
11
+ | 'queue-submit-failed'
12
+ | 'device-recovery';
13
+
14
+ export interface TemporalFrameInput {
15
+ readonly frameId: number;
16
+ readonly currentViewProjection: Float32Array;
17
+ readonly jitter: readonly [number, number];
18
+ readonly viewport: { readonly width: number; readonly height: number };
19
+ /** Current camera origin, retained only as accepted temporal history. */
20
+ readonly cameraPosition?: readonly [number, number, number];
21
+ readonly resetReason?: Exclude<TemporalResetReason, 'none' | 'queue-submit-failed'>;
22
+ }
23
+
24
+ export interface TemporalFrame {
25
+ readonly frameId: number;
26
+ readonly currentViewProjection: Float32Array;
27
+ readonly previousViewProjection: Float32Array | undefined;
28
+ readonly jitter: readonly [number, number];
29
+ readonly viewport: { readonly width: number; readonly height: number };
30
+ readonly currentCameraPosition: readonly [number, number, number];
31
+ readonly previousCameraPosition: readonly [number, number, number] | undefined;
32
+ readonly resetReason: TemporalResetReason;
33
+ readonly historyEpoch: number;
34
+ readonly deviceEpoch: number;
35
+ }
36
+
37
+ export interface TemporalSubmitResult {
38
+ readonly accepted: boolean;
39
+ readonly reason?: 'queue-submit-failed';
40
+ readonly resetReason?: Exclude<TemporalResetReason, 'none' | 'queue-submit-failed'>;
41
+ }
42
+
43
+ export interface TemporalFrameTransaction {
44
+ stage(input: TemporalFrameInput): void;
45
+ commit(submit: TemporalSubmitResult): Result<TemporalFrame, TemporalFrameSubmitError>;
46
+ snapshot(): TemporalFrame;
47
+ }
48
+
49
+ function copyInput(input: TemporalFrameInput): TemporalFrameInput {
50
+ return {
51
+ ...input,
52
+ currentViewProjection: new Float32Array(input.currentViewProjection),
53
+ jitter: [input.jitter[0], input.jitter[1]],
54
+ viewport: { ...input.viewport },
55
+ ...(input.cameraPosition === undefined
56
+ ? {}
57
+ : { cameraPosition: [...input.cameraPosition] as [number, number, number] }),
58
+ };
59
+ }
60
+
61
+ function copyFrame(frame: TemporalFrame): TemporalFrame {
62
+ return {
63
+ ...frame,
64
+ currentViewProjection: new Float32Array(frame.currentViewProjection),
65
+ previousViewProjection:
66
+ frame.previousViewProjection === undefined
67
+ ? undefined
68
+ : new Float32Array(frame.previousViewProjection),
69
+ jitter: [frame.jitter[0], frame.jitter[1]],
70
+ viewport: { ...frame.viewport },
71
+ currentCameraPosition: [...frame.currentCameraPosition] as [number, number, number],
72
+ previousCameraPosition:
73
+ frame.previousCameraPosition === undefined
74
+ ? undefined
75
+ : ([...frame.previousCameraPosition] as [number, number, number]),
76
+ };
77
+ }
78
+
79
+ export function createTemporalFrameTransaction(options: {
80
+ readonly deviceEpoch: number;
81
+ }): TemporalFrameTransaction {
82
+ let accepted: TemporalFrame | undefined;
83
+ let staged: TemporalFrameInput | undefined;
84
+ let historyEpoch = 0;
85
+
86
+ return {
87
+ stage(input) {
88
+ staged = copyInput(input);
89
+ },
90
+ commit(submit) {
91
+ if (!submit.accepted) {
92
+ staged = undefined;
93
+ return err(new TemporalFrameSubmitError(submit.reason ?? 'queue-submit-failed'));
94
+ }
95
+ if (staged === undefined) {
96
+ return err(new TemporalFrameSubmitError('queue-submit-failed'));
97
+ }
98
+
99
+ const resetReason = submit.resetReason ?? staged.resetReason ?? 'none';
100
+ const shouldReset = resetReason !== 'none';
101
+ if (shouldReset) historyEpoch += 1;
102
+ const next: TemporalFrame = {
103
+ frameId: staged.frameId,
104
+ currentViewProjection: new Float32Array(staged.currentViewProjection),
105
+ previousViewProjection:
106
+ accepted === undefined || shouldReset
107
+ ? undefined
108
+ : new Float32Array(accepted.currentViewProjection),
109
+ jitter: [staged.jitter[0], staged.jitter[1]],
110
+ viewport: { ...staged.viewport },
111
+ currentCameraPosition: staged.cameraPosition ?? [0, 0, 0],
112
+ previousCameraPosition:
113
+ accepted === undefined || shouldReset ? undefined : accepted.currentCameraPosition,
114
+ resetReason,
115
+ historyEpoch,
116
+ deviceEpoch: options.deviceEpoch,
117
+ };
118
+ accepted = next;
119
+ staged = undefined;
120
+ return ok(copyFrame(next));
121
+ },
122
+ snapshot() {
123
+ if (accepted === undefined)
124
+ throw new Error('TemporalFrame snapshot is unavailable before commit');
125
+ return copyFrame(accepted);
126
+ },
127
+ };
128
+ }
@@ -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,20 +58,21 @@ 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 {
65
68
  const channel = Math.round(value * 255);
66
69
  return {
67
70
  kind: 'texture',
68
- width: 1,
69
- height: 1,
71
+ shape: { viewDimension: '2d', extent: { width: 1, height: 1 } },
70
72
  format: 'rgba8unorm',
71
73
  data: Uint8Array.from([channel, channel, channel, 255]),
72
74
  colorSpace: 'linear',
73
- mipmap: false,
75
+ mips: { kind: 'none' },
74
76
  };
75
77
  }
76
78
 
@@ -107,6 +109,7 @@ async function renderCase(testCase: TransmissionCase): Promise<{
107
109
  readonly pixel: readonly [number, number, number, number];
108
110
  readonly passNames: readonly string[];
109
111
  readonly observation: boolean;
112
+ readonly errors: readonly string[];
110
113
  }> {
111
114
  let device: GPUDevice | undefined;
112
115
  let configuredDevice: GPUDevice | undefined;
@@ -158,7 +161,7 @@ async function renderCase(testCase: TransmissionCase): Promise<{
158
161
  {
159
162
  standardProfile: {
160
163
  ...DEFAULT_STANDARD_PROFILE,
161
- lighting: testCase.lane ?? 'direct',
164
+ renderPath: testCase.renderPath ?? DEFAULT_STANDARD_PROFILE.renderPath,
162
165
  },
163
166
  },
164
167
  { shaderManifestUrl: ENGINE_MANIFEST_URL },
@@ -173,6 +176,10 @@ async function renderCase(testCase: TransmissionCase): Promise<{
173
176
  }
174
177
 
175
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
+ });
176
183
  const world = new World();
177
184
  const lease = renderer.attach(world);
178
185
  if (!lease.ok) throw lease.error;
@@ -278,12 +285,22 @@ async function renderCase(testCase: TransmissionCase): Promise<{
278
285
  );
279
286
  expect(cameraEntity.ok, `${testCase.name}: camera spawn must succeed`).toBe(true);
280
287
  if (!cameraEntity.ok) throw cameraEntity.error;
281
- const lightEntity = world.spawn({
282
- component: DirectionalLight,
283
- data: { direction: [0, 0, -1], color: [1, 1, 1], intensity: 1, castShadow: false },
284
- });
285
- expect(lightEntity.ok, `${testCase.name}: light spawn must succeed`).toBe(true);
286
- 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
+ }
287
304
  world.update().unwrap();
288
305
  const drawn = renderer.draw({
289
306
  leases: [lease.value],
@@ -300,10 +317,12 @@ async function renderCase(testCase: TransmissionCase): Promise<{
300
317
  const renderDevice = configuredDevice ?? device;
301
318
  const passNames = renderer.inspect().perFramePassNames;
302
319
  expect(renderer.inspect().renderScene.projectionRecords).toBeGreaterThanOrEqual(1);
320
+ unsubscribe();
303
321
  return {
304
322
  pixel: await readCenter(renderDevice, target),
305
323
  passNames,
306
324
  observation: observation.ok,
325
+ errors,
307
326
  };
308
327
  }
309
328
 
@@ -404,9 +423,9 @@ describe('Standard transmission Dawn ROI', () => {
404
423
  );
405
424
  }, 120_000);
406
425
 
407
- it('exercises direct/clustered lanes and transmission-before-BLEND overlap order', async () => {
408
- const direct = await renderCase({
409
- name: 'direct',
426
+ it('exercises both Standard render paths and transmission-before-BLEND overlap order', async () => {
427
+ const forward = await renderCase({
428
+ name: 'forward',
410
429
  transmission: 1,
411
430
  ior: 1.5,
412
431
  thickness: 0.5,
@@ -414,10 +433,10 @@ describe('Standard transmission Dawn ROI', () => {
414
433
  attenuationColor: [0.7, 0.8, 0.9],
415
434
  attenuationDistance: 2,
416
435
  overlap: true,
417
- lane: 'direct',
436
+ renderPath: 'forward',
418
437
  });
419
- const clustered = await renderCase({
420
- name: 'clustered',
438
+ const deferred = await renderCase({
439
+ name: 'deferred',
421
440
  transmission: 1,
422
441
  ior: 1.5,
423
442
  thickness: 0.5,
@@ -425,14 +444,33 @@ describe('Standard transmission Dawn ROI', () => {
425
444
  attenuationColor: [0.7, 0.8, 0.9],
426
445
  attenuationDistance: 2,
427
446
  overlap: true,
428
- lane: 'clustered',
447
+ renderPath: 'deferred',
429
448
  });
430
- const transmissionIndex = direct.passNames.indexOf('transmission-forward');
431
- const transparentIndex = direct.passNames.indexOf('transparent');
449
+ const transmissionIndex = forward.passNames.indexOf('transmission-forward');
450
+ const transparentIndex = forward.passNames.indexOf('transparent');
432
451
  expect(transmissionIndex).toBeGreaterThanOrEqual(0);
433
452
  expect(transparentIndex).toBeGreaterThan(transmissionIndex);
434
- expect(clustered.pixel).not.toEqual(direct.pixel);
435
- expect(clustered.pixel.every(Number.isFinite)).toBe(true);
436
- 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([]);
437
475
  }, 120_000);
438
476
  });
@@ -215,7 +215,15 @@ export function addTypedScenePass(
215
215
  internal.pipelineState,
216
216
  internal.validated.length > 0 || options.gpuDriven !== undefined,
217
217
  internal.bindGroupCounts,
218
- { directionalShadow, spotShadow },
218
+ {
219
+ directionalShadow,
220
+ spotShadow,
221
+ projector: internal.spotLightProjector?.view ?? internal.volumetricFog?.projectorView,
222
+ projectorSampler:
223
+ internal.spotLightProjector?.sampler ?? internal.volumetricFog?.projectorSampler,
224
+ },
225
+ true,
226
+ internal.standardLighting,
219
227
  );
220
228
  if (groups.viewBindGroup !== null) {
221
229
  options.gpuDriven?.encode(groups.viewBindGroup, pass, resources);
@@ -461,6 +469,10 @@ export interface TypedFullscreenPassOptions {
461
469
  readonly key: string;
462
470
  readonly target: RenderPipelineTarget;
463
471
  }[];
472
+ /** Optional per-pass copy of the params UBO payload. */
473
+ readonly paramsTransform?:
474
+ | ((params: Uint8Array | undefined) => Uint8Array | undefined)
475
+ | undefined;
464
476
  }
465
477
 
466
478
  export interface TypedTemporalResolveTargets {
@@ -615,6 +627,11 @@ export function addTypedFullscreenPass(
615
627
  ...(options.rawSwapchainOutput === undefined
616
628
  ? {}
617
629
  : { rawSwapchainOutput: options.rawSwapchainOutput }),
630
+ ...(options.paramsTransform === undefined
631
+ ? {}
632
+ : {
633
+ paramsOverride: options.paramsTransform(frame.postProcessParams.get(options.shader)),
634
+ }),
618
635
  });
619
636
  },
620
637
  });
@@ -681,8 +698,9 @@ export function addTypedOutputTransformPass(
681
698
  graph: RenderGraphBuilder<RenderPipelineFrame>,
682
699
  input: RenderPipelineTarget,
683
700
  output: RenderPipelineTarget,
684
- outputOnly = false,
701
+ options: { readonly outputOnly?: boolean; readonly dither?: boolean } = {},
685
702
  ): Result<void, RenderGraphError> {
703
+ const outputOnly = options.outputOnly ?? false;
686
704
  return addTypedFullscreenPass(graph, {
687
705
  name: outputOnly ? 'present' : 'output-transform',
688
706
  shader: STANDARD_OUTPUT_TRANSFORM_FEATURE_ID,
@@ -690,5 +708,19 @@ export function addTypedOutputTransformPass(
690
708
  output,
691
709
  outputOnly,
692
710
  rawSwapchainOutput: outputOnly,
711
+ ...(options.dither !== undefined
712
+ ? {
713
+ paramsTransform: (params: Uint8Array | undefined): Uint8Array | undefined => {
714
+ if (params === undefined || params.byteLength !== 16) return params;
715
+ const transformed = params.slice();
716
+ new DataView(
717
+ transformed.buffer,
718
+ transformed.byteOffset,
719
+ transformed.byteLength,
720
+ ).setFloat32(12, options.dither === true ? 1 : 0, true);
721
+ return transformed;
722
+ },
723
+ }
724
+ : {}),
693
725
  });
694
726
  }
@@ -0,0 +1,17 @@
1
+ export interface DirectionalShadowAtlasGrid {
2
+ readonly columns: 1 | 2;
3
+ readonly rows: 1 | 2;
4
+ }
5
+
6
+ /**
7
+ * The directional CSM atlas layout is one compact contract for raster writes
8
+ * and shader reads: 1 -> 1x1, 2 -> 2x1, and 3/4 -> 2x2.
9
+ */
10
+ export function resolveDirectionalShadowAtlasGrid(
11
+ cascadeCount: number,
12
+ ): DirectionalShadowAtlasGrid {
13
+ const count = Math.max(1, Math.min(4, Math.round(cascadeCount)));
14
+ if (count === 1) return { columns: 1, rows: 1 };
15
+ if (count === 2) return { columns: 2, rows: 1 };
16
+ return { columns: 2, rows: 2 };
17
+ }
@@ -10,9 +10,10 @@ import {
10
10
  } from './record/shadow-pass';
11
11
  import type { RenderPipelineFrame, RenderPipelineTopology } from './render-pipeline';
12
12
  import { createRenderPipelineTarget, type RenderPipelineTarget } from './render-pipeline';
13
+ import { resolveDirectionalShadowAtlasGrid } from './typed-shadow-atlas';
13
14
 
14
15
  export interface TypedShadowTargets {
15
- readonly directional: RenderPipelineTarget;
16
+ readonly directional?: RenderPipelineTarget | undefined;
16
17
  readonly spot: RenderPipelineTarget;
17
18
  readonly point?: RenderPipelineTarget | undefined;
18
19
  }
@@ -43,44 +44,50 @@ export function addTypedShadowPasses(
43
44
  graph: RenderGraphBuilder<RenderPipelineFrame>,
44
45
  topology: RenderPipelineTopology,
45
46
  ): Result<TypedShadowTargets, RenderGraphError> {
46
- const tilesPerSide = Math.ceil(Math.sqrt(topology.shadow.cascadeCount));
47
- const directional = createRenderPipelineTarget(graph, 'directional-shadow-depth', {
48
- format: 'depth32float',
49
- size: {
50
- width: topology.shadow.mapSize * tilesPerSide,
51
- height: topology.shadow.mapSize * tilesPerSide,
52
- },
53
- });
54
- if (!directional.ok) return directional;
55
- for (let cascade = 0; cascade < topology.shadow.cascadeCount; cascade += 1) {
56
- const col = cascade % tilesPerSide;
57
- const row = Math.floor(cascade / tilesPerSide);
58
- const added = recordShadowPass(
59
- graph,
60
- `shadowCascade${cascade}`,
61
- directional.value,
62
- cascade === 0 ? 'clear' : 'load',
63
- ({ pass, frame }) =>
64
- encodeDirectionalShadowPass(frame as never, pass, cascade, {
65
- x: col * topology.shadow.mapSize,
66
- y: row * topology.shadow.mapSize,
67
- w: topology.shadow.mapSize,
68
- h: topology.shadow.mapSize,
69
- }),
70
- (frame) => !(frame as _InternalRenderPipelineContext).directionalShadowCacheReuse,
71
- );
72
- 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;
73
90
  }
74
- const observation = graph.addCopyPass('directional-shadow-observation', {
75
- accesses: [{ resource: directional.value.view, usage: 'copy-src' }],
76
- encode: ({ frame, resources }) => {
77
- const internal = frame as _InternalRenderPipelineContext;
78
- const texture = resources.texture(directional.value.texture);
79
- if (!texture.ok) throw texture.error;
80
- internal.pipelineState.perPassResources.shadowTexture = texture.value;
81
- },
82
- });
83
- if (!observation.ok) return observation;
84
91
 
85
92
  let point: RenderPipelineTarget | undefined;
86
93
  if (topology.shadow.pointCount > 0) {
@@ -147,7 +154,7 @@ export function addTypedShadowPasses(
147
154
 
148
155
  const spot = createRenderPipelineTarget(graph, 'spot-shadow-depth', {
149
156
  format: 'depth32float',
150
- size: { width: topology.shadow.mapSize * 2, height: topology.shadow.mapSize * 2 },
157
+ size: { width: topology.shadow.spotMapSize * 2, height: topology.shadow.spotMapSize * 2 },
151
158
  });
152
159
  if (!spot.ok) return spot;
153
160
  for (let spotIndex = 0; spotIndex < Math.max(1, topology.shadow.spotCount); spotIndex += 1) {
@@ -161,5 +168,5 @@ export function addTypedShadowPasses(
161
168
  if (!added.ok) return added;
162
169
  }
163
170
 
164
- return ok({ directional: directional.value, spot: spot.value, point });
171
+ return ok({ directional, spot: spot.value, point });
165
172
  }