@forgeax/engine-render 0.1.29 → 0.1.31

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 (350) hide show
  1. package/README.md +248 -21
  2. package/dist/assembly/atmosphere-shader-prewarm.d.ts +1 -1
  3. package/dist/assembly/atmosphere-shader-prewarm.d.ts.map +1 -1
  4. package/dist/assembly/device-feature-admission.d.ts +4 -3
  5. package/dist/assembly/device-feature-admission.d.ts.map +1 -1
  6. package/dist/assembly/dynamic-geometry-host.d.ts +6 -0
  7. package/dist/assembly/dynamic-geometry-host.d.ts.map +1 -1
  8. package/dist/assembly/host-contract.d.ts +6 -0
  9. package/dist/assembly/host-contract.d.ts.map +1 -1
  10. package/dist/assembly/material/assembly.d.ts +26 -3
  11. package/dist/assembly/material/assembly.d.ts.map +1 -1
  12. package/dist/assembly/material/pipeline-helpers.d.ts +24 -0
  13. package/dist/assembly/material/pipeline-helpers.d.ts.map +1 -0
  14. package/dist/assembly/material-shader-policy.d.ts.map +1 -1
  15. package/dist/assembly/recovery/device-loss-fanout.d.ts.map +1 -1
  16. package/dist/assembly/recovery/recovery-attempt.d.ts +1 -6
  17. package/dist/assembly/recovery/recovery-attempt.d.ts.map +1 -1
  18. package/dist/assembly/recovery/renderer-recover.d.ts +1 -0
  19. package/dist/assembly/recovery/renderer-recover.d.ts.map +1 -1
  20. package/dist/assembly/renderer-facade.d.ts.map +1 -1
  21. package/dist/assembly/shader-prewarm-policy.d.ts +1 -1
  22. package/dist/assembly/shader-prewarm-policy.d.ts.map +1 -1
  23. package/dist/assembly/webgpu-ready.d.ts +1 -1
  24. package/dist/assembly/webgpu-ready.d.ts.map +1 -1
  25. package/dist/assembly/webgpu-renderer.d.ts.map +1 -1
  26. package/dist/capture/scene-projection.d.ts +41 -0
  27. package/dist/capture/scene-projection.d.ts.map +1 -0
  28. package/dist/{chunk-BQVQOQJH.mjs → chunk-7MQ3XBSP.mjs} +20 -14
  29. package/dist/chunk-7MQ3XBSP.mjs.map +1 -0
  30. package/dist/{chunk-S4DNKQYQ.mjs → chunk-C3DDSXYK.mjs} +86 -33
  31. package/dist/chunk-C3DDSXYK.mjs.map +1 -0
  32. package/dist/{chunk-BTWH7BP4.mjs → chunk-FUUO4UND.mjs} +73 -29
  33. package/dist/chunk-FUUO4UND.mjs.map +1 -0
  34. package/dist/{chunk-NRNXBLIJ.mjs → chunk-JRBDO2KB.mjs} +46 -6
  35. package/dist/chunk-JRBDO2KB.mjs.map +1 -0
  36. package/dist/{chunk-NICXSHAT.mjs → chunk-MDQJPY3T.mjs} +8 -6
  37. package/dist/chunk-MDQJPY3T.mjs.map +1 -0
  38. package/dist/{chunk-JQTSEI3Z.mjs → chunk-NO4WZCAH.mjs} +3 -3
  39. package/dist/chunk-NO4WZCAH.mjs.map +1 -0
  40. package/dist/{chunk-FUODTTIA.mjs → chunk-PSEQ6QPH.mjs} +621 -108
  41. package/dist/chunk-PSEQ6QPH.mjs.map +1 -0
  42. package/dist/{chunk-33XZFTQU.mjs → chunk-S5UE2T5P.mjs} +48 -6
  43. package/dist/chunk-S5UE2T5P.mjs.map +1 -0
  44. package/dist/components/directional-light.d.ts +2 -2
  45. package/dist/components/dynamic-resolution.d.ts +15 -0
  46. package/dist/components/dynamic-resolution.d.ts.map +1 -0
  47. package/dist/components/index.d.ts +1 -0
  48. package/dist/components/index.d.ts.map +1 -1
  49. package/dist/components/light-helpers.d.ts.map +1 -1
  50. package/dist/components/reflection-probe.d.ts +1 -0
  51. package/dist/components/reflection-probe.d.ts.map +1 -1
  52. package/dist/components/skylight.d.ts +4 -1
  53. package/dist/components/skylight.d.ts.map +1 -1
  54. package/dist/construct-renderer.mjs +2172 -796
  55. package/dist/construct-renderer.mjs.map +1 -1
  56. package/dist/device/device-scope.d.ts +2 -0
  57. package/dist/device/device-scope.d.ts.map +1 -1
  58. package/dist/device/gpu-residency.d.ts +2 -2
  59. package/dist/device/gpu-residency.d.ts.map +1 -1
  60. package/dist/dynamic-geometry.d.ts.map +1 -1
  61. package/dist/environment/background.d.ts +3 -2
  62. package/dist/environment/background.d.ts.map +1 -1
  63. package/dist/environment/ibl.d.ts +11 -0
  64. package/dist/environment/ibl.d.ts.map +1 -0
  65. package/dist/environment/storage.d.ts +14 -0
  66. package/dist/environment/storage.d.ts.map +1 -0
  67. package/dist/errors/gpu-driven.d.ts +2 -2
  68. package/dist/errors/gpu-driven.d.ts.map +1 -1
  69. package/dist/errors/render.d.ts +35 -1
  70. package/dist/errors/render.d.ts.map +1 -1
  71. package/dist/extract/camera.d.ts.map +1 -1
  72. package/dist/extract/contracts.d.ts +7 -0
  73. package/dist/extract/contracts.d.ts.map +1 -1
  74. package/dist/extract/gpu-driven.d.ts +18 -1
  75. package/dist/extract/gpu-driven.d.ts.map +1 -1
  76. package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
  77. package/dist/gpu-driven/material-artifacts.d.ts +24 -0
  78. package/dist/gpu-driven/material-artifacts.d.ts.map +1 -0
  79. package/dist/gpu-driven/material-bindings.d.ts +7 -0
  80. package/dist/gpu-driven/material-bindings.d.ts.map +1 -0
  81. package/dist/gpu-driven/prepared-draw.d.ts +2 -0
  82. package/dist/gpu-driven/prepared-draw.d.ts.map +1 -1
  83. package/dist/gpu-driven/production-raster.d.ts +27 -6
  84. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  85. package/dist/gpu-resource.d.ts +5 -2
  86. package/dist/gpu-resource.d.ts.map +1 -1
  87. package/dist/gpu-scene-schema.d.ts.map +1 -1
  88. package/dist/gpu-scene.d.ts.map +1 -1
  89. package/dist/hdrp-buffers.d.ts +3 -0
  90. package/dist/hdrp-buffers.d.ts.map +1 -1
  91. package/dist/ibl/IblPipelineCache.d.ts +2 -0
  92. package/dist/ibl/IblPipelineCache.d.ts.map +1 -1
  93. package/dist/ibl/skylight-bind-group.d.ts +3 -1
  94. package/dist/ibl/skylight-bind-group.d.ts.map +1 -1
  95. package/dist/index.d.ts +83 -81
  96. package/dist/index.d.ts.map +1 -1
  97. package/dist/index.mjs +7 -5
  98. package/dist/index.mjs.map +1 -1
  99. package/dist/inspection-types.d.ts +16 -1
  100. package/dist/inspection-types.d.ts.map +1 -1
  101. package/dist/internal.mjs +6 -6
  102. package/dist/material-row.d.ts +10 -0
  103. package/dist/material-row.d.ts.map +1 -1
  104. package/dist/pbr-pipeline.d.ts +3 -1
  105. package/dist/pbr-pipeline.d.ts.map +1 -1
  106. package/dist/pipeline/render-extent.d.ts +51 -0
  107. package/dist/pipeline/render-extent.d.ts.map +1 -0
  108. package/dist/pipeline/standard-build-context.d.ts +7 -0
  109. package/dist/pipeline/standard-build-context.d.ts.map +1 -0
  110. package/dist/pipeline/standard-forward-lane.d.ts +3 -2
  111. package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
  112. package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
  113. package/dist/pipeline/standard-post.d.ts +1 -1
  114. package/dist/pipeline/standard-post.d.ts.map +1 -1
  115. package/dist/pipeline/standard-profile.d.ts +2 -1
  116. package/dist/pipeline/standard-profile.d.ts.map +1 -1
  117. package/dist/pipeline-builder.d.ts +3 -1
  118. package/dist/pipeline-builder.d.ts.map +1 -1
  119. package/dist/plugin.d.ts.map +1 -1
  120. package/dist/post-process-errors.d.ts +9 -2
  121. package/dist/post-process-errors.d.ts.map +1 -1
  122. package/dist/prepare/extended-lighting/resources.d.ts +3 -3
  123. package/dist/record/frame-lighting.d.ts.map +1 -1
  124. package/dist/record/frame-snapshot.d.ts +3 -8
  125. package/dist/record/frame-snapshot.d.ts.map +1 -1
  126. package/dist/record/frame.d.ts +3 -2
  127. package/dist/record/frame.d.ts.map +1 -1
  128. package/dist/record/main-pass-geometry.d.ts +7 -2
  129. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  130. package/dist/record/main-pass-material.d.ts +6 -3
  131. package/dist/record/main-pass-material.d.ts.map +1 -1
  132. package/dist/record/main-pass.d.ts +2 -0
  133. package/dist/record/main-pass.d.ts.map +1 -1
  134. package/dist/record/prepared-material-bindings.d.ts.map +1 -1
  135. package/dist/record/recovery-pipeline.d.ts +9 -1
  136. package/dist/record/recovery-pipeline.d.ts.map +1 -1
  137. package/dist/record/render-context.d.ts +34 -3
  138. package/dist/record/render-context.d.ts.map +1 -1
  139. package/dist/record/shadow-pass.d.ts +2 -2
  140. package/dist/record/shadow-pass.d.ts.map +1 -1
  141. package/dist/record/typed-frame-graph.d.ts +5 -2
  142. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  143. package/dist/recovery/render-system-candidate.d.ts.map +1 -1
  144. package/dist/reflection/background.d.ts +14 -0
  145. package/dist/reflection/background.d.ts.map +1 -0
  146. package/dist/reflection/filter.d.ts.map +1 -1
  147. package/dist/reflection/projection.d.ts +1 -0
  148. package/dist/reflection/projection.d.ts.map +1 -1
  149. package/dist/reflection/record-owner.d.ts +4 -2
  150. package/dist/reflection/record-owner.d.ts.map +1 -1
  151. package/dist/render-contract.d.ts +47 -32
  152. package/dist/render-contract.d.ts.map +1 -1
  153. package/dist/render-graph-primitives.d.ts.map +1 -1
  154. package/dist/render-pipeline.d.ts +17 -1
  155. package/dist/render-pipeline.d.ts.map +1 -1
  156. package/dist/render-system-extract-tail.d.ts +9 -2
  157. package/dist/render-system-extract-tail.d.ts.map +1 -1
  158. package/dist/render-system-extract.d.ts +29 -2
  159. package/dist/render-system-extract.d.ts.map +1 -1
  160. package/dist/render-system.d.ts.map +1 -1
  161. package/dist/scene/render-scene.d.ts +2 -0
  162. package/dist/scene/render-scene.d.ts.map +1 -1
  163. package/dist/ssao-config.d.ts +8 -6
  164. package/dist/ssao-config.d.ts.map +1 -1
  165. package/dist/ssr/graph.d.ts.map +1 -1
  166. package/dist/temporal/gpu.d.ts.map +1 -1
  167. package/dist/temporal/index.mjs +5 -5
  168. package/dist/temporal/standard-scene-data.d.ts +2 -1
  169. package/dist/temporal/standard-scene-data.d.ts.map +1 -1
  170. package/dist/temporal/target-coverage-attachment.d.ts +15 -0
  171. package/dist/temporal/target-coverage-attachment.d.ts.map +1 -0
  172. package/dist/temporal/temporal-view.d.ts +6 -2
  173. package/dist/temporal/temporal-view.d.ts.map +1 -1
  174. package/dist/temporal/view.d.ts +6 -0
  175. package/dist/temporal/view.d.ts.map +1 -1
  176. package/dist/typed-render-graph-primitives.d.ts +6 -0
  177. package/dist/typed-render-graph-primitives.d.ts.map +1 -1
  178. package/dist/volume/component.d.ts +1 -0
  179. package/dist/volume/component.d.ts.map +1 -1
  180. package/package.json +21 -21
  181. package/src/__tests__/capture-scene-projection.unit.test.ts +24 -0
  182. package/src/__tests__/device-feature-admission.unit.test.ts +15 -24
  183. package/src/__tests__/device-loss-fanout.unit.test.ts +43 -0
  184. package/src/__tests__/dynamic-geometry-batch.unit.test.ts +157 -0
  185. package/src/__tests__/dynamic-geometry.unit.test.ts +36 -0
  186. package/src/__tests__/dynamic-resolution-plugin.integration.test.ts +81 -0
  187. package/src/__tests__/factory-contract.integration.test.ts +164 -1
  188. package/src/__tests__/frame-observation.contract.test.ts +21 -0
  189. package/src/__tests__/gpu-driven-baseline.characterization.test.ts +1 -1
  190. package/src/__tests__/gpu-driven-batch-topology-pbr.unit.test.ts +20 -1
  191. package/src/__tests__/gpu-driven-lod-cache.unit.test.ts +0 -3
  192. package/src/__tests__/gpu-driven-mesh-world-identity.unit.test.ts +0 -3
  193. package/src/__tests__/gpu-driven-pbr.dawn.test.ts +11 -6
  194. package/src/__tests__/gpu-driven-production.integration.test.ts +13 -25
  195. package/src/__tests__/gpu-driven-view.unit.test.ts +1 -0
  196. package/src/__tests__/gpu-pass-timing-browser-runner.ts +15 -0
  197. package/src/__tests__/gpu-pass-timing.browser.test.ts +14 -1
  198. package/src/__tests__/gpu-resource-scope.unit.test.ts +71 -0
  199. package/src/__tests__/gpu-scene.unit.test.ts +55 -1
  200. package/src/__tests__/materials-standard-contract.unit.test.ts +5 -1
  201. package/src/__tests__/mesh-submission-lifetime.unit.test.ts +8 -0
  202. package/src/__tests__/morph-neutral-admission.unit.test.ts +19 -0
  203. package/src/__tests__/physical-clearcoat.integration.test.ts +6 -1
  204. package/src/__tests__/post-process-error-policy-owner.unit.test.ts +6 -5
  205. package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +216 -5
  206. package/src/__tests__/reflection-fallback-async-publication.unit.test.ts +68 -1
  207. package/src/__tests__/reflection-probe-authoring.unit.test.ts +12 -10
  208. package/src/__tests__/reflection-probe-box.dawn.test.ts +71 -0
  209. package/src/__tests__/reflection-probe-filter.unit.test.ts +9 -0
  210. package/src/__tests__/reflection-probe-orientation.dawn.test.ts +294 -0
  211. package/src/__tests__/reflection-probe-record-owner.characterization.unit.test.ts +5 -15
  212. package/src/__tests__/reflection-probe-updates.unit.test.ts +129 -0
  213. package/src/__tests__/render-error-code-owner.test-d.ts +3 -0
  214. package/src/__tests__/render-error-detail-ssot.test-d.ts +5 -1
  215. package/src/__tests__/render-extent.unit.test.ts +63 -0
  216. package/src/__tests__/render-scene-projection.unit.test.ts +3 -2
  217. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +173 -9
  218. package/src/__tests__/shader-manifest-fixture.ts +6 -0
  219. package/src/__tests__/skinned-shadow-caster.test.ts +8 -1
  220. package/src/__tests__/specular-environment-scale.dawn.test.ts +4 -2
  221. package/src/__tests__/ssao-config.unit.test.ts +7 -0
  222. package/src/__tests__/ssao-surface.dawn.test.ts +252 -0
  223. package/src/__tests__/ssr-cube-silhouette.dawn.test.ts +270 -0
  224. package/src/__tests__/ssr-gpu-dispatch.browser.test.ts +51 -43
  225. package/src/__tests__/ssr-gpu-dispatch.dawn.test.ts +4 -0
  226. package/src/__tests__/ssr-gpu-dispatch.ts +10 -5
  227. package/src/__tests__/ssr-graph-topology.unit.test.ts +2 -2
  228. package/src/__tests__/ssr-hiz.browser.test.ts +1 -1
  229. package/src/__tests__/ssr-hiz.dawn.test.ts +1 -1
  230. package/src/__tests__/ssr-spatial.integration.test.ts +1 -1
  231. package/src/__tests__/standard-cluster-variant-resolution.unit.test.ts +40 -0
  232. package/src/__tests__/standard-pbr-artifact-assembly.unit.test.ts +238 -1
  233. package/src/__tests__/standard-profile-oracle.unit.test.ts +27 -0
  234. package/src/__tests__/taa-coverage-edge.dawn.test.ts +6 -0
  235. package/src/__tests__/taa-neighborhood-equivalence.dawn.test.ts +5 -1
  236. package/src/__tests__/taa-stability-feedback.ts +1 -0
  237. package/src/__tests__/temporal-reset-matrix.unit.test.ts +2 -2
  238. package/src/__tests__/temporal-view.unit.test.ts +17 -0
  239. package/src/__tests__/transmission-prewarm.unit.test.ts +57 -0
  240. package/src/__tests__/volumetric-fog-stage-readback.dawn.test.ts +62 -13
  241. package/src/__tests__/volumetric-fog-topology.unit.test.ts +7 -7
  242. package/src/assembly/atmosphere-shader-prewarm.ts +3 -2
  243. package/src/assembly/device-feature-admission.ts +9 -4
  244. package/src/assembly/dynamic-geometry-host.ts +236 -163
  245. package/src/assembly/host-contract.ts +9 -0
  246. package/src/assembly/material/assembly.ts +350 -6
  247. package/src/assembly/material/pipeline-helpers.ts +100 -0
  248. package/src/assembly/material-shader-policy.ts +39 -18
  249. package/src/assembly/recovery/device-loss-fanout.ts +5 -0
  250. package/src/assembly/recovery/recovery-attempt.ts +2 -29
  251. package/src/assembly/recovery/renderer-recover.ts +6 -9
  252. package/src/assembly/renderer-facade.ts +22 -1
  253. package/src/assembly/shader-prewarm-policy.ts +14 -1
  254. package/src/assembly/webgpu-ready.ts +28 -19
  255. package/src/assembly/webgpu-renderer.ts +95 -95
  256. package/src/capture/scene-projection.ts +37 -0
  257. package/src/components/__tests__/dynamic-resolution-schema.unit.test.ts +65 -0
  258. package/src/components/__tests__/light-radius.integration.test.ts +59 -0
  259. package/src/components/directional-light.ts +3 -3
  260. package/src/components/dynamic-resolution.ts +67 -0
  261. package/src/components/index.ts +1 -0
  262. package/src/components/light-helpers.ts +6 -1
  263. package/src/components/reflection-probe.ts +1 -0
  264. package/src/components/skylight.ts +4 -1
  265. package/src/device/device-scope.ts +13 -7
  266. package/src/device/gpu-residency.ts +11 -11
  267. package/src/dynamic-geometry.ts +13 -8
  268. package/src/environment/background.ts +30 -12
  269. package/src/environment/ibl.ts +211 -0
  270. package/src/environment/storage.ts +63 -0
  271. package/src/errors/gpu-driven.ts +10 -3
  272. package/src/errors/render.ts +64 -0
  273. package/src/extract/camera.ts +12 -1
  274. package/src/extract/contracts.ts +10 -0
  275. package/src/extract/gpu-driven.ts +72 -17
  276. package/src/gpu-driven/batch-topology.ts +33 -11
  277. package/src/gpu-driven/material-artifacts.ts +179 -0
  278. package/src/gpu-driven/material-bindings.ts +41 -0
  279. package/src/gpu-driven/prepared-draw.ts +13 -2
  280. package/src/gpu-driven/production-raster.ts +681 -164
  281. package/src/gpu-resource.ts +21 -2
  282. package/src/gpu-scene-schema.ts +11 -2
  283. package/src/gpu-scene.ts +14 -3
  284. package/src/hdrp-buffers.ts +69 -0
  285. package/src/ibl/IblPipelineCache.ts +5 -0
  286. package/src/ibl/skylight-bind-group.ts +27 -17
  287. package/src/index.ts +92 -81
  288. package/src/inspection-types.ts +16 -1
  289. package/src/material-row.ts +60 -11
  290. package/src/pbr-pipeline.ts +13 -2
  291. package/src/pipeline/render-extent.ts +111 -0
  292. package/src/pipeline/standard-build-context.ts +7 -0
  293. package/src/pipeline/standard-forward-lane.ts +41 -24
  294. package/src/pipeline/standard-pipeline.ts +47 -18
  295. package/src/pipeline/standard-post.ts +4 -0
  296. package/src/pipeline/standard-profile.ts +2 -1
  297. package/src/pipeline-builder.ts +12 -3
  298. package/src/plugin.ts +4 -0
  299. package/src/post-process-errors.ts +26 -18
  300. package/src/prepare/__tests__/extended-lighting-capability.unit.test.ts +2 -2
  301. package/src/prepare/extended-lighting/resources.ts +3 -3
  302. package/src/record/__tests__/main-pass-geometry.unit.test.ts +68 -0
  303. package/src/record/__tests__/material-bg-cache.unit.test.ts +45 -0
  304. package/src/record/__tests__/material-slot-plan.unit.test.ts +9 -0
  305. package/src/record/__tests__/recovery-pipeline.unit.test.ts +47 -1
  306. package/src/record/__tests__/sprite-material-abi.unit.test.ts +7 -3
  307. package/src/record/__tests__/temporal-payload.unit.test.ts +2 -0
  308. package/src/record/frame-lighting.ts +3 -1
  309. package/src/record/frame-snapshot.ts +13 -11
  310. package/src/record/frame.ts +150 -74
  311. package/src/record/main-pass-geometry.ts +60 -9
  312. package/src/record/main-pass-material.ts +47 -19
  313. package/src/record/main-pass.ts +63 -4
  314. package/src/record/prepared-material-bindings.ts +4 -0
  315. package/src/record/recovery-pipeline.ts +56 -0
  316. package/src/record/render-context.ts +34 -3
  317. package/src/record/shadow-pass.ts +187 -50
  318. package/src/record/typed-frame-graph.ts +60 -3
  319. package/src/record/view-ubo.ts +1 -1
  320. package/src/recovery/render-system-candidate.ts +54 -11
  321. package/src/reflection/background.ts +151 -0
  322. package/src/reflection/filter.ts +3 -0
  323. package/src/reflection/projection.ts +16 -6
  324. package/src/reflection/record-owner.ts +282 -86
  325. package/src/render-contract.ts +50 -32
  326. package/src/render-graph-primitives.ts +28 -3
  327. package/src/render-pipeline.ts +20 -1
  328. package/src/render-system-extract-tail.ts +23 -3
  329. package/src/render-system-extract.ts +127 -42
  330. package/src/render-system.ts +35 -7
  331. package/src/scene/render-scene.ts +8 -0
  332. package/src/ssao-config.ts +25 -8
  333. package/src/ssr/graph.ts +78 -58
  334. package/src/temporal/gpu.ts +5 -0
  335. package/src/temporal/standard-scene-data.ts +7 -1
  336. package/src/temporal/target-coverage-attachment.ts +72 -0
  337. package/src/temporal/temporal-view.ts +10 -4
  338. package/src/temporal/view.ts +12 -2
  339. package/src/transmission/__tests__/standard-transmission.dawn.test.ts +24 -9
  340. package/src/typed-render-graph-primitives.ts +38 -0
  341. package/src/volume/component.ts +1 -0
  342. package/src/volume/passes.ts +3 -3
  343. package/dist/chunk-33XZFTQU.mjs.map +0 -1
  344. package/dist/chunk-BQVQOQJH.mjs.map +0 -1
  345. package/dist/chunk-BTWH7BP4.mjs.map +0 -1
  346. package/dist/chunk-FUODTTIA.mjs.map +0 -1
  347. package/dist/chunk-JQTSEI3Z.mjs.map +0 -1
  348. package/dist/chunk-NICXSHAT.mjs.map +0 -1
  349. package/dist/chunk-NRNXBLIJ.mjs.map +0 -1
  350. package/dist/chunk-S4DNKQYQ.mjs.map +0 -1
@@ -139,6 +139,7 @@ import {
139
139
  cameraExposureFromColumns,
140
140
  cameraProjectionFromF32,
141
141
  cubeCameraUpdateIntentFromF32,
142
+ DynamicResolution,
142
143
  Fog,
143
144
  MotionBlur,
144
145
  ReflectionProbe,
@@ -146,6 +147,7 @@ import {
146
147
  ScreenSpaceReflection,
147
148
  tonemapFromF32,
148
149
  tonemapToU32,
150
+ validateDynamicResolutionCamera,
149
151
  } from './components';
150
152
  import type { DirectionalShadowQuality } from './components/directional-shadow-filter';
151
153
  import type { LightValidationError } from './components/light-helpers';
@@ -932,6 +934,10 @@ export interface MaterialSnapshot {
932
934
  */
933
935
  readonly materialShaderId?: string | undefined;
934
936
  readonly materialProgramKeys?: Readonly<Record<string, string>> | undefined;
937
+ /** Producer-selected scene-index program keys, when a cooked publication carries them. */
938
+ readonly materialSceneIndexProgramKeys?:
939
+ | Readonly<Record<string, MaterialSceneIndexProgramKey>>
940
+ | undefined;
935
941
  /**
936
942
  * Stable source handle for the material asset. Record uses this only for
937
943
  * same-frame assembly reuse; GPU bind-group validity still comes from the
@@ -1055,6 +1061,15 @@ export interface MaterialSnapshot {
1055
1061
  readonly transparent?: boolean | undefined;
1056
1062
  }
1057
1063
 
1064
+ /**
1065
+ * One producer-selected scene-index program, retaining the effective Pass
1066
+ * kind so record does not infer shadow/depth semantics from an authored name.
1067
+ */
1068
+ export interface MaterialSceneIndexProgramKey {
1069
+ readonly specializationKey: string;
1070
+ readonly pass: 'forward' | 'shadow' | 'depth';
1071
+ }
1072
+
1058
1073
  export interface MaterialCacheChainIdentity {
1059
1074
  readonly asset: MaterialAsset;
1060
1075
  readonly parentGuid: string | undefined;
@@ -1262,8 +1277,13 @@ export interface ShadowCasterMembership {
1262
1277
  readonly materialHandle: number;
1263
1278
  readonly passIndex: number;
1264
1279
  readonly materialShaderId?: string;
1280
+ /** Selected authored ShadowCaster entry points, when published. */
1281
+ readonly vertexEntry?: string;
1282
+ readonly fragmentEntry?: string;
1265
1283
  readonly renderState?: MaterialRenderState;
1266
1284
  readonly cpuReason?: ShadowCasterCpuReason;
1285
+ /** Producer-owned result of the complete ShadowCaster admission check. */
1286
+ readonly gpuDrivenEligible?: boolean;
1267
1287
  }
1268
1288
 
1269
1289
  /** Producer evidence for a concrete ShadowCaster pass that remains on CPU. */
@@ -1520,28 +1540,85 @@ export function materialProgramKeysForMaterial(
1520
1540
  assets: AssetRegistry,
1521
1541
  context: MaterialCookProgramContext | undefined,
1522
1542
  ): Readonly<Record<string, string>> | undefined {
1543
+ return materialProgramKeysForAddress(material, assets, context, 'direct');
1544
+ }
1545
+
1546
+ /** Resolve the producer's scene-index program key for each cooked Pass. */
1547
+ export function materialSceneIndexProgramKeysForMaterial(
1548
+ material: MaterialAsset,
1549
+ assets: AssetRegistry,
1550
+ context: MaterialCookProgramContext | undefined,
1551
+ ): Readonly<Record<string, MaterialSceneIndexProgramKey>> | undefined {
1523
1552
  const projection = assets.getMaterialProjectionForPayload(material);
1524
1553
  if (projection === undefined) return undefined;
1525
1554
  if (context === undefined)
1526
1555
  throw new Error('Published material selection requires renderer-owned compiler context');
1527
- return Object.fromEntries(
1528
- projection.passes.map((pass) => {
1529
- const mode = String(
1530
- (pass.renderState?.tags as Record<string, unknown> | undefined)?.LightMode ?? pass.name,
1531
- );
1532
- const selectedContext: MaterialCookProgramContext = {
1533
- ...context,
1534
- pass: /shadow/i.test(mode) ? 'shadow' : /depth/i.test(mode) ? 'depth' : 'forward',
1535
- pipeline: /deferred|gbuffer/i.test(mode) ? 'deferred' : context.pipeline,
1536
- };
1537
- return [
1556
+ const entries: Record<string, MaterialSceneIndexProgramKey> = {};
1557
+ for (const pass of projection.passes) {
1558
+ const mode = String(
1559
+ (pass.renderState?.tags as Record<string, unknown> | undefined)?.LightMode ?? pass.name,
1560
+ );
1561
+ const passKind = /shadow/i.test(mode)
1562
+ ? ('shadow' as const)
1563
+ : /depth/i.test(mode)
1564
+ ? ('depth' as const)
1565
+ : ('forward' as const);
1566
+ const selectedContext: MaterialCookProgramContext = {
1567
+ ...context,
1568
+ pass: passKind,
1569
+ pipeline: /deferred|gbuffer/i.test(mode) ? 'deferred' : context.pipeline,
1570
+ };
1571
+ try {
1572
+ const selected = selectMaterialPassProgram(
1573
+ projection,
1538
1574
  pass.name,
1539
- selectMaterialPassProgram(projection, pass.name, selectedContext).specializationKey,
1540
- ];
1541
- }),
1575
+ selectedContext,
1576
+ 'scene-index',
1577
+ );
1578
+ entries[pass.name] = { specializationKey: selected.specializationKey, pass: passKind };
1579
+ } catch (error) {
1580
+ if (!isMissingMaterialProgram(error)) throw error;
1581
+ }
1582
+ }
1583
+ return Object.keys(entries).length === 0 ? undefined : entries;
1584
+ }
1585
+
1586
+ function isMissingMaterialProgram(error: unknown): boolean {
1587
+ return (
1588
+ error !== null &&
1589
+ typeof error === 'object' &&
1590
+ (error as { readonly code?: unknown }).code === 'material-specialization-not-cooked'
1542
1591
  );
1543
1592
  }
1544
1593
 
1594
+ function materialProgramKeysForAddress(
1595
+ material: MaterialAsset,
1596
+ assets: AssetRegistry,
1597
+ context: MaterialCookProgramContext | undefined,
1598
+ address: 'direct' | 'scene-index',
1599
+ ): Readonly<Record<string, string>> | undefined {
1600
+ const projection = assets.getMaterialProjectionForPayload(material);
1601
+ if (projection === undefined) return undefined;
1602
+ if (context === undefined)
1603
+ throw new Error('Published material selection requires renderer-owned compiler context');
1604
+ const entries: [string, string][] = [];
1605
+ for (const pass of projection.passes) {
1606
+ const mode = String(
1607
+ (pass.renderState?.tags as Record<string, unknown> | undefined)?.LightMode ?? pass.name,
1608
+ );
1609
+ const selectedContext: MaterialCookProgramContext = {
1610
+ ...context,
1611
+ pass: /shadow/i.test(mode) ? 'shadow' : /depth/i.test(mode) ? 'depth' : 'forward',
1612
+ pipeline: /deferred|gbuffer/i.test(mode) ? 'deferred' : context.pipeline,
1613
+ };
1614
+ entries.push([
1615
+ pass.name,
1616
+ selectMaterialPassProgram(projection, pass.name, selectedContext, address).specializationKey,
1617
+ ]);
1618
+ }
1619
+ return entries.length === 0 ? undefined : Object.fromEntries(entries);
1620
+ }
1621
+
1545
1622
  export function runtimeMaterialShaderIdForMaterial(
1546
1623
  passes: readonly MaterialPass[],
1547
1624
  programs: Readonly<Record<string, string>> | undefined,
@@ -1620,38 +1697,18 @@ export function shadowCasterCpuReason(
1620
1697
  return 'transparent';
1621
1698
  }
1622
1699
  if (morph !== undefined) return 'morph';
1623
- const isStandardPbr =
1624
- material.materialShaderId === 'forgeax::default-standard-pbr' ||
1625
- material.materialShaderId === 'forgeax::pbr-skin' ||
1626
- material.materialShaderId === 'forgeax::default-standard-pbr-skin';
1627
- if (!isStandardPbr) return 'unsupported-shader';
1628
1700
  const shadowEntry = shadowEntries[0];
1629
- if (
1630
- shadowEntry !== undefined &&
1631
- shadowEntry.materialShaderId !== undefined &&
1632
- shadowEntry.materialShaderId !== 'forgeax::default-shadow-caster'
1633
- ) {
1634
- return 'unsupported-shader';
1635
- }
1636
1701
  if (shadowEntry?.renderState !== undefined) return 'unsupported-render-state';
1637
1702
  if (draw === undefined) return 'missing-gpu-draw';
1638
1703
  if (draw.prepared === undefined) return 'unprepared';
1639
- const isSkinnedPbr =
1640
- material.materialShaderId === 'forgeax::pbr-skin' ||
1641
- material.materialShaderId === 'forgeax::default-standard-pbr-skin';
1642
1704
  const preparedMaterial = draw.prepared.identity.material;
1643
1705
  const preparedDeformation = draw.prepared.identity.deformation;
1644
- const materialContractMatches =
1645
- (material.materialShaderId === 'forgeax::default-standard-pbr' &&
1646
- preparedMaterial === 'forgeax::default-standard-pbr') ||
1647
- (isSkinnedPbr &&
1648
- (preparedMaterial === 'forgeax::pbr-skin' ||
1649
- preparedMaterial === 'forgeax::default-standard-pbr-skin'));
1706
+ const materialContractMatches = preparedMaterial === material.materialShaderId;
1650
1707
  const deformationContractMatches =
1651
1708
  (skin === undefined && preparedDeformation === 'rigid') ||
1652
1709
  (skin !== undefined && preparedDeformation === 'skin');
1653
1710
  if (!materialContractMatches || !deformationContractMatches) return 'prepared-contract';
1654
- if (isSkinnedPbr && !hasFiniteOrderedSkinBounds(skin?.bounds)) {
1711
+ if (preparedDeformation === 'skin' && !hasFiniteOrderedSkinBounds(skin?.bounds)) {
1655
1712
  return 'missing-skin-bounds';
1656
1713
  }
1657
1714
  return undefined;
@@ -2247,6 +2304,11 @@ export function resolveMaterialSnapshot(
2247
2304
  const resolved = resolvedResult.value;
2248
2305
  const allPasses = resolved.passes;
2249
2306
  const materialProgramKeys = materialProgramKeysForMaterial(asset, assetsRef, materialContext);
2307
+ const materialSceneIndexProgramKeys = materialSceneIndexProgramKeysForMaterial(
2308
+ asset,
2309
+ assetsRef,
2310
+ materialContext,
2311
+ );
2250
2312
  const authoredFirstPassShader = runtimeMaterialShaderIdForMaterial(allPasses, undefined);
2251
2313
  const firstPassShader = runtimeMaterialShaderIdForMaterial(allPasses, materialProgramKeys);
2252
2314
  const pv = materialValuesToLinearRuntime(
@@ -2351,6 +2413,7 @@ export function resolveMaterialSnapshot(
2351
2413
  normalScale: normalScalePv,
2352
2414
  materialShaderId: firstPassShader,
2353
2415
  materialProgramKeys,
2416
+ materialSceneIndexProgramKeys,
2354
2417
  materialHandle: handleRaw,
2355
2418
  renderState: pipelineRenderState(allPasses[0]?.renderState),
2356
2419
  paramSnapshot: paramSnap,
@@ -2726,7 +2789,11 @@ export function computeDirectionalCsm(
2726
2789
  resultLightViewProjs.push(new Float32Array(16));
2727
2790
  continue;
2728
2791
  }
2729
- const cascadeNear = cIdx === 0 ? sNear : (splits[cIdx - 1] ?? sFar);
2792
+ // The receiver blends into this cascade before the preceding split.
2793
+ // Fit that same overlap instead of sampling outside the next projection.
2794
+ const previousSplit = splits[cIdx - 1] ?? sNear;
2795
+ const cascadeNear =
2796
+ cIdx === 0 ? sNear : Math.max(sNear, previousSplit * (1 - config.cascadeBlend));
2730
2797
  const cascadeFar = splits[cIdx] ?? sFar;
2731
2798
  const corners = computeFrustumCorners(
2732
2799
  cameraVP,
@@ -2768,7 +2835,7 @@ export function computeDirectionalCsm(
2768
2835
  minY,
2769
2836
  maxY,
2770
2837
  minZ,
2771
- maxZ,
2838
+ maxZ: nearZ,
2772
2839
  });
2773
2840
  resultLightViewProjs.push(new Float32Array(mat4.multiply(mat4.create(), orthoProj, lightView)));
2774
2841
  }
@@ -2831,7 +2898,12 @@ export interface PreparedExtractContext {
2831
2898
  readonly getMaterialShaderArtifact:
2832
2899
  | ((
2833
2900
  materialShaderId: string,
2834
- vertexColorAvailable?: boolean,
2901
+ request?: {
2902
+ readonly vertexColorAvailable?: boolean;
2903
+ readonly deformation?: 'rigid' | 'skin';
2904
+ readonly pass?: 'forward' | 'shadow' | 'depth';
2905
+ readonly address?: 'direct' | 'scene-index';
2906
+ },
2835
2907
  ) => MaterialShaderArtifact | undefined)
2836
2908
  | undefined;
2837
2909
  /** Renderer-owned canonical source for explicit large instance collections. */
@@ -2852,13 +2924,14 @@ function collectCameraSnapshots(world: World): CameraSnapshot[] {
2852
2924
  const cameraQuery = world
2853
2925
  .query({
2854
2926
  read: [Camera],
2855
- optional: [MotionBlur, ScreenSpaceReflection],
2927
+ optional: [MotionBlur, DynamicResolution, ScreenSpaceReflection],
2856
2928
  with: [Transform, GlobalTransform],
2857
2929
  })
2858
2930
  .unwrap();
2859
2931
  for (const row of cameraQuery) {
2860
2932
  const cam = row.get(Camera);
2861
2933
  const motionBlur = row.get(MotionBlur);
2934
+ const dynamicResolution = row.get(DynamicResolution);
2862
2935
  const screenSpaceReflection = row.get(ScreenSpaceReflection);
2863
2936
  const entity = row.entity;
2864
2937
  const view = worldInternal._getArrayView(entity, GlobalTransform, 'world');
@@ -2881,6 +2954,9 @@ function collectCameraSnapshots(world: World): CameraSnapshot[] {
2881
2954
  colorLutStrength: cam.colorLutStrength,
2882
2955
  }
2883
2956
  : undefined;
2957
+ const antialias = antialiasFromF32(cam.antialias);
2958
+ const dynamicResolutionResult = validateDynamicResolutionCamera(dynamicResolution, antialias);
2959
+ if (!dynamicResolutionResult.ok) throw dynamicResolutionResult.error;
2884
2960
  cameras.push({
2885
2961
  entityKey: entity as number,
2886
2962
  ...(target === undefined ? {} : { target }),
@@ -2900,7 +2976,10 @@ function collectCameraSnapshots(world: World): CameraSnapshot[] {
2900
2976
  exposure: cam.exposure,
2901
2977
  whitePoint: cam.whitePoint,
2902
2978
  ...(output === undefined ? {} : { output }),
2903
- antialias: antialiasFromF32(cam.antialias),
2979
+ antialias,
2980
+ ...(dynamicResolutionResult.value === undefined
2981
+ ? {}
2982
+ : { dynamicResolution: dynamicResolutionResult.value }),
2904
2983
  bloom: bloomEnabledFromF32(cam.bloom),
2905
2984
  bloomThreshold: cam.bloomThreshold,
2906
2985
  bloomIntensity: cam.bloomIntensity,
@@ -2982,6 +3061,7 @@ export function collectReflectionProbeFacts(world: World): ReflectionProbeFact[]
2982
3061
  priority: probe.priority,
2983
3062
  intensity: probe.intensity,
2984
3063
  resolution: probe.resolution,
3064
+ boxProjection: probe.boxProjection,
2985
3065
  revision: probe.invalidationVersion,
2986
3066
  updateIntent: reflectionProbeUpdateIntentFromF32(probe.updateIntent),
2987
3067
  invalidationVersion: probe.invalidationVersion,
@@ -3068,7 +3148,12 @@ export function prepareExtractContext(
3068
3148
  readonly worldId?: number;
3069
3149
  readonly getMaterialShaderArtifact?: (
3070
3150
  materialShaderId: string,
3071
- vertexColorAvailable?: boolean,
3151
+ request?: {
3152
+ readonly vertexColorAvailable?: boolean;
3153
+ readonly deformation?: 'rigid' | 'skin';
3154
+ readonly pass?: 'forward' | 'shadow' | 'depth';
3155
+ readonly address?: 'direct' | 'scene-index';
3156
+ },
3072
3157
  ) => MaterialShaderArtifact | undefined;
3073
3158
  readonly instanceCollections?: InstanceCollectionStore;
3074
3159
  } = {},
@@ -1,3 +1,4 @@
1
+ import { projectCaptureScene } from './capture/scene-projection';
1
2
  import type { RenderSceneBounds } from './scene/render-scene-types';
2
3
  // @forgeax/engine-runtime - RenderSystem main entry (D-S2 three-stage Extract ->
3
4
  // Prepare -> Record + 4-tier error fan-out).
@@ -1246,7 +1247,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
1246
1247
  // explicit texture/sampler handles resolved to resident GPU resources.
1247
1248
  materialBgAssemblyCache: new Map(),
1248
1249
  // singleton material cache (flat Map<variant, BindGroup>; D-6).
1249
- singletonMaterialCache: new Map(),
1250
+ shadowMaterialBindGroups: new WeakMap(),
1250
1251
  // post-process bind group cache (bloom / fxaa / ssao): identity-keyed
1251
1252
  // WeakMap chain so resize-retired transient targets rebuild automatically.
1252
1253
  postProcessBgCache: new WeakMap(),
@@ -2294,8 +2295,9 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2294
2295
  if (producer === undefined || target === undefined) {
2295
2296
  return undefined;
2296
2297
  }
2297
- const width = Math.max(1, internals.canvas.width);
2298
- const height = Math.max(1, internals.canvas.height);
2298
+ const descriptor = target.descriptor.kind === 'texture' ? target.descriptor : undefined;
2299
+ const width = descriptor?.width ?? Math.max(1, internals.canvas.width);
2300
+ const height = descriptor?.height ?? Math.max(1, internals.canvas.height);
2299
2301
  return Object.freeze({
2300
2302
  identity: 'standard-scene-temporal',
2301
2303
  producerId: 'forgeax::standard::scene-data',
@@ -2420,6 +2422,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2420
2422
  }
2421
2423
  }
2422
2424
  try {
2425
+ internals.recoveryColdWorkGuard?.beginFrame();
2423
2426
  pointsLinesOwner.beginFrame();
2424
2427
  // cameraOwner drives the surfaced cameras + frustum
2425
2428
  // cull; resourceOwner drives skylight/skybox/postProcess + per-world
@@ -2540,6 +2543,19 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2540
2543
  cameras[0],
2541
2544
  frameState.frameNumber,
2542
2545
  skylight,
2546
+ frame.reflectionProbes?.some((probe) => probe.updateIntent === 'on-change')
2547
+ ? JSON.stringify([
2548
+ persistentRenderScene.reflectionCaptureRevision(),
2549
+ lights.directional,
2550
+ lights.point,
2551
+ lights.spot,
2552
+ lights.rect,
2553
+ skylight,
2554
+ frame.skybox,
2555
+ environment?.environmentSignature,
2556
+ ])
2557
+ : '',
2558
+ frame.skybox,
2543
2559
  );
2544
2560
  latestCamera = cameras[0];
2545
2561
  frameState.ssrRequested = latestCamera?.screenSpaceReflection !== undefined;
@@ -3144,14 +3160,19 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3144
3160
  Number.isFinite(deltaTime) ? Math.max(0, deltaTime) : 0,
3145
3161
  );
3146
3162
  }
3163
+ const probeCaptureScene =
3164
+ shadowCasterProjection !== undefined &&
3165
+ reflectionProbeRecord.graph.work.some((work) => work.rawCaptureFace !== undefined)
3166
+ ? projectCaptureScene(shadowCasterProjection, submissionRenderables, orderedDispatch)
3167
+ : undefined;
3147
3168
  submitted = runProfiledRenderPhase(profileSession, 'record', () =>
3148
3169
  recordFrame(
3149
3170
  internals,
3150
3171
  resourceWorld,
3151
3172
  cameras,
3152
3173
  lights,
3153
- submissionRenderables,
3154
- orderedDispatch,
3174
+ probeCaptureScene?.renderables ?? submissionRenderables,
3175
+ probeCaptureScene?.displayDispatch ?? orderedDispatch,
3155
3176
  frameState,
3156
3177
  dispatchCounts,
3157
3178
  bindGroupCounts,
@@ -3181,6 +3202,9 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3181
3202
  scheduler: cubeCaptureScheduler,
3182
3203
  state: cubeCaptureState,
3183
3204
  reflectionProbes: reflectionProbeRecord,
3205
+ ...(probeCaptureScene === undefined
3206
+ ? {}
3207
+ : { captureDispatch: probeCaptureScene.captureDispatch }),
3184
3208
  } satisfies CubeCaptureFrameInput,
3185
3209
  environment?.environmentSignature ?? '',
3186
3210
  environment?.fogSignature ?? '',
@@ -3322,6 +3346,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3322
3346
  }),
3323
3347
  );
3324
3348
  } finally {
3349
+ internals.recoveryColdWorkGuard?.endFrame();
3325
3350
  if (ownsProfileFrame) {
3326
3351
  try {
3327
3352
  profileSession?.endFrame();
@@ -3439,7 +3464,10 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3439
3464
  ? config
3440
3465
  : {
3441
3466
  clusterGrid: DEFAULT_CLUSTER_GRID,
3442
- ...(profileConfig.ssao ? { ssao: { enabled: true } } : {}),
3467
+ ssao: {
3468
+ enabled: profileConfig.ssao !== false,
3469
+ ...(typeof profileConfig.ssao === 'object' ? profileConfig.ssao : {}),
3470
+ },
3443
3471
  ...config,
3444
3472
  };
3445
3473
  if (resolvedConfig?.clusterGrid !== undefined) {
@@ -3792,7 +3820,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3792
3820
  frameState.instancesBgPerEntity.clear();
3793
3821
  frameState.materialBgShared.clear();
3794
3822
  frameState.materialBgAssemblyCache.clear();
3795
- frameState.singletonMaterialCache.clear();
3823
+ frameState.shadowMaterialBindGroups = new WeakMap();
3796
3824
  frameState.postProcessBgCache = new WeakMap();
3797
3825
  // Fullscreen PSOs are cached outside frameState because the normal
3798
3826
  // path reuses them across frames. They still carry opaque handles from
@@ -758,6 +758,10 @@ export class RenderScene {
758
758
  return records;
759
759
  }
760
760
 
761
+ get contentRevision(): number {
762
+ return this.revision;
763
+ }
764
+
761
765
  inspect(): RenderSceneInspection {
762
766
  return {
763
767
  records: this.slotsSnapshot().map(({ slot, generation, worldId, entityKey }) => ({
@@ -2449,6 +2453,10 @@ export class PersistentRenderScene {
2449
2453
  return { min: [...bounds.min], max: [...bounds.max] };
2450
2454
  }
2451
2455
 
2456
+ reflectionCaptureRevision(): string {
2457
+ return `${this.fullRebuilds}:${this.composition?.projection.contentRevision ?? 0}`;
2458
+ }
2459
+
2452
2460
  inspect(): PersistentRenderSceneInspection {
2453
2461
  const entry = this.composition;
2454
2462
  const pointsLines = this.pointsLinesInspections;
@@ -1,22 +1,24 @@
1
1
  // @forgeax/engine-render - SSAO parameter authority.
2
2
 
3
- import { err, ok, type Result } from '@forgeax/engine-types';
3
+ import { err, ok, type RenderPipelineAsset, type Result } from '@forgeax/engine-types';
4
4
  import { PostProcessError } from './post-process-errors';
5
5
 
6
6
  export const SSAO_DEFAULT_RADIUS = 0.5;
7
7
  export const SSAO_DEFAULT_BIAS = 0.025;
8
8
  export const SSAO_DEFAULT_INTENSITY = 1.0;
9
9
 
10
- export interface SsaoParameterConfig {
11
- readonly radius?: number | undefined;
12
- readonly bias?: number | undefined;
13
- readonly intensity?: number | undefined;
14
- }
10
+ export type SsaoParameterConfig = Omit<
11
+ NonNullable<NonNullable<RenderPipelineAsset['config']>['ssao']>,
12
+ 'enabled'
13
+ >;
14
+
15
+ export const SSAO_SAMPLE_COUNTS = { low: 16, medium: 32, high: 64 } as const;
15
16
 
16
17
  export interface ResolvedSsaoParameters {
17
18
  readonly radius: number;
18
19
  readonly bias: number;
19
20
  readonly intensity: number;
21
+ readonly quality: 'low' | 'medium' | 'high';
20
22
  }
21
23
 
22
24
  /**
@@ -29,6 +31,7 @@ export function getSsaoParameters(config: SsaoParameterConfig | undefined): Reso
29
31
  radius: config?.radius ?? SSAO_DEFAULT_RADIUS,
30
32
  bias: config?.bias ?? SSAO_DEFAULT_BIAS,
31
33
  intensity: config?.intensity ?? SSAO_DEFAULT_INTENSITY,
34
+ quality: config?.quality ?? 'high',
32
35
  };
33
36
  }
34
37
 
@@ -41,7 +44,7 @@ export function resolveSsaoParameters(
41
44
  config: SsaoParameterConfig | undefined,
42
45
  ): Result<ResolvedSsaoParameters, PostProcessError> {
43
46
  const resolved = getSsaoParameters(config);
44
- if (resolved.radius <= 0) {
47
+ if (!Number.isFinite(resolved.radius) || resolved.radius <= 0) {
45
48
  return err(
46
49
  new PostProcessError({
47
50
  code: 'ssao-radius-non-positive',
@@ -49,7 +52,7 @@ export function resolveSsaoParameters(
49
52
  }),
50
53
  );
51
54
  }
52
- if (resolved.bias < 0) {
55
+ if (!Number.isFinite(resolved.bias) || resolved.bias < 0) {
53
56
  return err(
54
57
  new PostProcessError({
55
58
  code: 'ssao-bias-negative',
@@ -57,5 +60,19 @@ export function resolveSsaoParameters(
57
60
  }),
58
61
  );
59
62
  }
63
+ if (
64
+ !Number.isFinite(resolved.intensity) ||
65
+ resolved.intensity < 0 ||
66
+ !Object.hasOwn(SSAO_SAMPLE_COUNTS, resolved.quality)
67
+ ) {
68
+ const paramName =
69
+ !Number.isFinite(resolved.intensity) || resolved.intensity < 0 ? 'intensity' : 'quality';
70
+ return err(
71
+ new PostProcessError({
72
+ code: 'ssao-parameter-invalid',
73
+ detail: { paramName, value: resolved[paramName] },
74
+ }),
75
+ );
76
+ }
60
77
  return ok(resolved);
61
78
  }