@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
@@ -0,0 +1,67 @@
1
+ import { defineComponent, type SchemaOf, type ShapeOf } from '@forgeax/engine-ecs';
2
+ import { err, ok, type Result } from '@forgeax/engine-types';
3
+ import {
4
+ type DynamicResolutionError,
5
+ DynamicResolutionInvalidParameterError,
6
+ DynamicResolutionRequiresTaaError,
7
+ } from '../errors/render';
8
+
9
+ /** Optional Camera companion that enables fixed or adaptive internal scaling. */
10
+ export const DynamicResolution = defineComponent('DynamicResolution', {
11
+ targetGpuMs: { type: 'f32', default: 16.67 },
12
+ minScale: { type: 'f32', default: 0.67 },
13
+ maxScale: { type: 'f32', default: 1.0 },
14
+ });
15
+
16
+ export type DynamicResolutionData = ShapeOf<SchemaOf<typeof DynamicResolution>>;
17
+
18
+ const DEFAULT_DYNAMIC_RESOLUTION: DynamicResolutionData = Object.freeze({
19
+ targetGpuMs: 16.67,
20
+ minScale: 0.67,
21
+ maxScale: 1,
22
+ });
23
+
24
+ function invalid(
25
+ field: 'targetGpuMs' | 'minScale' | 'maxScale',
26
+ value: number,
27
+ expected: string,
28
+ ): DynamicResolutionInvalidParameterError {
29
+ return new DynamicResolutionInvalidParameterError({ field, value, expected });
30
+ }
31
+
32
+ /** Validate one detached DynamicResolution authoring POD before graph work. */
33
+ export function validateDynamicResolutionParameters(
34
+ input: Partial<DynamicResolutionData> | undefined,
35
+ ): Result<DynamicResolutionData, DynamicResolutionInvalidParameterError> {
36
+ const value = input ?? {};
37
+ const targetGpuMs = value.targetGpuMs ?? DEFAULT_DYNAMIC_RESOLUTION.targetGpuMs;
38
+ const minScale = value.minScale ?? DEFAULT_DYNAMIC_RESOLUTION.minScale;
39
+ const maxScale = value.maxScale ?? DEFAULT_DYNAMIC_RESOLUTION.maxScale;
40
+ if (!Number.isFinite(targetGpuMs) || targetGpuMs <= 0) {
41
+ return err(invalid('targetGpuMs', targetGpuMs, 'finite and greater than 0'));
42
+ }
43
+ if (!Number.isFinite(minScale) || minScale < 0.5 || minScale > 1) {
44
+ return err(invalid('minScale', minScale, 'finite and in [0.5, 1.0]'));
45
+ }
46
+ if (!Number.isFinite(maxScale) || maxScale < 0.5 || maxScale > 1) {
47
+ return err(invalid('maxScale', maxScale, 'finite and in [0.5, 1.0]'));
48
+ }
49
+ if (minScale > maxScale) {
50
+ return err(invalid('minScale', minScale, 'less than or equal to maxScale'));
51
+ }
52
+ return ok(Object.freeze({ targetGpuMs, minScale, maxScale }));
53
+ }
54
+
55
+ /** Validate the Camera companion relationship at the extraction boundary. */
56
+ export function validateDynamicResolutionCamera(
57
+ input: Partial<DynamicResolutionData> | undefined,
58
+ antialias: 'none' | 'fxaa' | 'msaa' | 'taa',
59
+ ): Result<DynamicResolutionData | undefined, DynamicResolutionError> {
60
+ if (input === undefined) return ok(undefined);
61
+ const parameters = validateDynamicResolutionParameters(input);
62
+ if (!parameters.ok) return parameters;
63
+ if (antialias !== 'taa') {
64
+ return err(new DynamicResolutionRequiresTaaError({ antialias }));
65
+ }
66
+ return parameters;
67
+ }
@@ -2,6 +2,7 @@ export * from './atmosphere';
2
2
  export * from './camera';
3
3
  export * from './cube-camera';
4
4
  export * from './directional-light';
5
+ export * from './dynamic-resolution';
5
6
  export * from './fog';
6
7
  export { Instances, type InstancesData } from './instances';
7
8
  export { Layer } from './layer';
@@ -149,7 +149,12 @@ export function validateDirectionalLightData(
149
149
  }),
150
150
  );
151
151
  }
152
- if (shadowAngularRadius < PCSS_RADIUS_MIN || shadowAngularRadius > PCSS_RADIUS_MAX) {
152
+ // ECS stores this field as f32; both authored endpoints round outward.
153
+ // Accept their stored representations, but not the adjacent out-of-range f32.
154
+ if (
155
+ shadowAngularRadius < Math.fround(PCSS_RADIUS_MIN) ||
156
+ shadowAngularRadius > Math.fround(PCSS_RADIUS_MAX)
157
+ ) {
153
158
  return err(
154
159
  new ShadowInvalidConfigError('shadowAngularRadius', shadowAngularRadius, {
155
160
  kind: 'range',
@@ -40,6 +40,7 @@ export function reflectionProbeUpdateIntentToF32(intent: ReflectionProbeUpdateIn
40
40
  export const ReflectionProbe = defineComponent('ReflectionProbe', {
41
41
  halfExtents: { type: 'array<f32, 3>', default: new Float32Array([1, 1, 1]) },
42
42
  priority: { type: 'f32', default: 0 },
43
+ boxProjection: { type: 'bool', default: false },
43
44
  intensity: { type: 'f32', default: 1 },
44
45
  resolution: { type: 'u32', default: 256 },
45
46
  updateIntent: { type: 'f32', default: REFLECTION_PROBE_UPDATE_ONCE },
@@ -40,7 +40,10 @@ import { defineComponent } from '@forgeax/engine-ecs';
40
40
  * Skylight ambient environment light.
41
41
  *
42
42
  * A single Skylight entity provides ambient lighting for all StandardMaterial
43
- * surfaces. Two modes share one component:
43
+ * surfaces. When Atmosphere is selected and equirect is omitted, the same sky
44
+ * cube supplies diffuse and roughness-filtered specular IBL. Color/intensity
45
+ * still scale the result; without Atmosphere the solid-color behavior applies.
46
+ * Two authored inputs share one component:
44
47
  *
45
48
  * - **Solid-color ambient (no equirect).** Omit `equirect` for a constant
46
49
  * ambient = `color` * `intensity` applied immediately, with no async GPU
@@ -62,7 +62,7 @@ export class DeviceScope {
62
62
  readonly owner: string;
63
63
  readonly parent: DeviceScope | undefined;
64
64
  private lifecycleState: DeviceScopeState = 'active';
65
- private readonly resources: OwnedResource[] = [];
65
+ private readonly resources = new Map<ResourceRef<unknown>, OwnedResource>();
66
66
  private readonly children: DeviceScope[] = [];
67
67
 
68
68
  private constructor(generation: number, owner: string, parent?: DeviceScope) {
@@ -99,7 +99,7 @@ export class DeviceScope {
99
99
 
100
100
  ref<T>(kind: DeviceResourceKind, value: T): ResourceRef<T> {
101
101
  const ref = new DeviceResourceRef(kind, this.owner, this.generation, value);
102
- this.resources.push({ ref: ref as DeviceResourceRef<unknown>, cleanup: () => undefined });
102
+ this.resources.set(ref, { ref, cleanup: () => undefined });
103
103
  return ref;
104
104
  }
105
105
 
@@ -116,7 +116,7 @@ export class DeviceScope {
116
116
  }
117
117
 
118
118
  resourceDelta(): number {
119
- return this.resources.length;
119
+ return this.resources.size;
120
120
  }
121
121
 
122
122
  /** @internal */
@@ -126,16 +126,22 @@ export class DeviceScope {
126
126
  cleanup: (value: T) => void | Promise<void>,
127
127
  ): ResourceRef<T> {
128
128
  const ref = new DeviceResourceRef(kind, this.owner, this.generation, value);
129
- this.resources.push({
130
- ref: ref as DeviceResourceRef<unknown>,
129
+ this.resources.set(ref, {
130
+ ref,
131
131
  cleanup: idempotentCleanup(cleanup) as (resource: unknown) => void | Promise<void>,
132
132
  });
133
133
  return ref;
134
134
  }
135
135
 
136
+ /** @internal Remove ownership only after the resource's cleanup succeeded. */
137
+ _release(ref: ResourceRef<unknown>): void {
138
+ this.resources.delete(ref);
139
+ }
140
+
136
141
  /** @internal */
137
142
  _clearResources(): OwnedResource[] {
138
- const resources = this.resources.splice(0);
143
+ const resources = [...this.resources.values()];
144
+ this.resources.clear();
139
145
  for (const resource of resources.reverse()) {
140
146
  try {
141
147
  const pending = resource.cleanup(resource.ref.value);
@@ -214,7 +220,7 @@ export class DeviceScope {
214
220
  return {
215
221
  owner: this.owner,
216
222
  generation: this.generation,
217
- resourceCount: this.resources.length,
223
+ resourceCount: this.resources.size,
218
224
  };
219
225
  }
220
226
  }
@@ -183,6 +183,7 @@ export function writePrefilterUniforms(
183
183
  roughness: number,
184
184
  mipFaceSize: number,
185
185
  sourceMipLevelCount = 1,
186
+ sourceSpace: 'image' | 'camera' = 'image',
186
187
  ): Result<void, RhiError> {
187
188
  if (subPassIdx < 0 || subPassIdx >= PREFILTER_SUBPASS_COUNT) {
188
189
  return invalidUniformInput(
@@ -196,7 +197,12 @@ export function writePrefilterUniforms(
196
197
  `writePrefilterUniforms received sourceMipLevelCount=${sourceMipLevelCount}`,
197
198
  );
198
199
  }
199
- const payload = new Float32Array([roughness, mipFaceSize, sourceMipLevelCount, 0]);
200
+ const payload = new Float32Array([
201
+ roughness,
202
+ mipFaceSize,
203
+ sourceMipLevelCount,
204
+ sourceSpace === 'camera' ? 1 : 0,
205
+ ]);
200
206
  return device.queue.writeBuffer(buffer, subPassIdx * DYNAMIC_OFFSET_STRIDE, payload);
201
207
  }
202
208
 
@@ -204,6 +210,7 @@ export function writeAllPrefilterUniforms(
204
210
  device: FaceUniformsDevice,
205
211
  buffer: Buffer,
206
212
  sourceMipLevelCount = 1,
213
+ sourceSpace: 'image' | 'camera' = 'image',
207
214
  ): Result<void, RhiError> {
208
215
  for (let mip = 0; mip < PREFILTER_MIP_LEVELS; mip++) {
209
216
  const roughness = mip / (PREFILTER_MIP_LEVELS - 1);
@@ -216,6 +223,7 @@ export function writeAllPrefilterUniforms(
216
223
  roughness,
217
224
  mipFaceSize,
218
225
  sourceMipLevelCount,
226
+ sourceSpace,
219
227
  );
220
228
  if (!result.ok) return result;
221
229
  }
@@ -677,11 +685,7 @@ export class GpuResidencyCache {
677
685
  if (device === undefined) {
678
686
  throw new Error('GpuResidencyCache.wrapBuf called before configureGpuDevice');
679
687
  }
680
- const resource = new GpuBuffer(device, rawHandle);
681
- this.deviceScope?._adopt('buffer', resource, (value: GpuBuffer) => {
682
- if (!value.isDestroyed) value.destroy();
683
- });
684
- return resource;
688
+ return new GpuBuffer(device, rawHandle, this.deviceScope);
685
689
  }
686
690
 
687
691
  /** Wrap a raw RHI Texture handle into a GpuTexture (mirror of wrapBuf). */
@@ -690,11 +694,7 @@ export class GpuResidencyCache {
690
694
  if (device === undefined) {
691
695
  throw new Error('GpuResidencyCache.wrapTex called before configureGpuDevice');
692
696
  }
693
- const resource = new GpuTexture(device, rawHandle);
694
- this.deviceScope?._adopt('texture', resource, (value: GpuTexture) => {
695
- if (!value.isDestroyed) value.destroy();
696
- });
697
- return resource;
697
+ return new GpuTexture(device, rawHandle, this.deviceScope);
698
698
  }
699
699
 
700
700
  /**
@@ -199,13 +199,11 @@ function cloneCandidate(candidate: DynamicGeometryCandidate): DynamicGeometryCan
199
199
 
200
200
  function validateMesh(mesh: MeshAsset): string | undefined {
201
201
  if (mesh.kind !== 'mesh') return 'MeshAsset.kind must be mesh';
202
- if (
203
- !(mesh.vertices instanceof Float32Array) ||
204
- mesh.vertices.length === 0 ||
205
- mesh.vertices.length % 3 !== 0
206
- ) {
207
- return 'MeshAsset.vertices must be a non-empty Float32Array with xyz triples';
202
+ if (!(mesh.vertices instanceof Float32Array) || mesh.vertices.length === 0) {
203
+ return 'MeshAsset.vertices must be a non-empty Float32Array';
208
204
  }
205
+ // vertices may be interleaved. Attribute layout validation belongs to the
206
+ // mesh producer/residency path; this gate must not assume an xyz-only stride.
209
207
  if (
210
208
  mesh.indices !== undefined &&
211
209
  !(mesh.indices instanceof Uint16Array || mesh.indices instanceof Uint32Array)
@@ -214,8 +212,15 @@ function validateMesh(mesh: MeshAsset): string | undefined {
214
212
  }
215
213
  if (mesh.submeshes.length === 0)
216
214
  return 'MeshAsset.submeshes must contain at least one topology range';
217
- if (mesh.materialSlots.length < mesh.submeshes.length) {
218
- return 'MeshAsset.materialSlots must cover every submesh';
215
+ if (
216
+ mesh.submeshes.some(
217
+ (submesh) =>
218
+ !Number.isSafeInteger(submesh.materialSlot) ||
219
+ submesh.materialSlot < 0 ||
220
+ submesh.materialSlot >= mesh.materialSlots.length,
221
+ )
222
+ ) {
223
+ return 'MeshAsset.materialSlots must cover every referenced materialSlot';
219
224
  }
220
225
  if (mesh.vertices.some((value) => !Number.isFinite(value)))
221
226
  return 'MeshAsset.vertices must be finite';
@@ -5,24 +5,39 @@ import type { Result } from '@forgeax/engine-types';
5
5
  import type { _InternalRenderPipelineContext } from '../record/render-context';
6
6
  import { VIEW_UNIFORM_BYTES } from '../record/view-ubo';
7
7
  import type { RenderPipelineFrame, RenderPipelineTarget } from '../render-pipeline';
8
+ import { addAtmosphereIbl, type GraphEnvironment } from './ibl';
9
+ import { atmosphereStorage } from './storage';
8
10
 
9
11
  const CUBE_SIZE = 128;
10
12
 
11
- /** One graph-owned sky cube feeds the background; the sun disc is added only there. */
13
+ /** One device-owned sky cube feeds the graph background; the sun disc is added only there. */
12
14
  export function addAtmosphereBackground(
13
15
  graph: RenderGraphBuilder<RenderPipelineFrame>,
14
16
  color: RenderPipelineTarget,
15
- ): Result<void, RenderGraphError> {
16
- const cube = graph.createTexture('atmosphere-sky', {
17
- format: 'rgba16float',
18
- size: { width: CUBE_SIZE, height: CUBE_SIZE, depthOrArrayLayers: 6 },
19
- });
17
+ ): Result<GraphEnvironment, RenderGraphError> {
18
+ const cube = graph.importTexture(
19
+ 'atmosphere-sky',
20
+ {
21
+ format: 'rgba16float',
22
+ size: { width: CUBE_SIZE, height: CUBE_SIZE, depthOrArrayLayers: 6 },
23
+ usage: 0x14,
24
+ },
25
+ (frame) => atmosphereStorage(frame).sky,
26
+ );
20
27
  if (!cube.ok) return cube;
21
28
  const cubeView = graph.view(cube.value, { dimension: 'cube', arrayLayerCount: 6 });
22
29
  if (!cubeView.ok) return cubeView;
23
- const params = graph.createBuffer('atmosphere-params', { size: 64 });
30
+ const params = graph.importBuffer(
31
+ 'atmosphere-params',
32
+ { size: 64, usage: 0x48 },
33
+ (frame) => atmosphereStorage(frame).params,
34
+ );
24
35
  if (!params.ok) return params;
25
- const vertices = graph.createBuffer('atmosphere-face-vertices', { size: 6 * 3 * 3 * 4 });
36
+ const vertices = graph.importBuffer(
37
+ 'atmosphere-face-vertices',
38
+ { size: 6 * 3 * 3 * 4, usage: 0x28 },
39
+ (frame) => atmosphereStorage(frame).vertices,
40
+ );
26
41
  if (!vertices.ok) return vertices;
27
42
  const view = graph.importBuffer(
28
43
  'atmosphere-view',
@@ -30,7 +45,6 @@ export function addAtmosphereBackground(
30
45
  (frame) => (frame as _InternalRenderPipelineContext).pipelineState.viewUniformBuffer,
31
46
  );
32
47
  if (!view.ok) return view;
33
- let submittedSignature: string | undefined;
34
48
  let state:
35
49
  | {
36
50
  cubePipeline: RenderPipeline;
@@ -44,7 +58,8 @@ export function addAtmosphereBackground(
44
58
  | undefined;
45
59
  const frameOf = (frame: RenderPipelineFrame) => frame as _InternalRenderPipelineContext;
46
60
  const dirty = (frame: RenderPipelineFrame) =>
47
- frameOf(frame).frameState.environmentFrame?.environmentSignature !== submittedSignature;
61
+ frameOf(frame).frameState.environmentFrame?.environmentSignature !==
62
+ atmosphereStorage(frame).submittedSignature;
48
63
  const upload = graph.addCopyPass('atmosphere-prepare', {
49
64
  accesses: [
50
65
  { resource: params.value, usage: 'copy-dst' },
@@ -206,14 +221,16 @@ export function addAtmosphereBackground(
206
221
  if (face === 5) {
207
222
  const signature = frameOf(frame).frameState.environmentFrame?.environmentSignature;
208
223
  frameOf(frame).frameState.pendingAtmospherePublish = () => {
209
- submittedSignature = signature;
224
+ atmosphereStorage(frame).submittedSignature = signature;
210
225
  };
211
226
  }
212
227
  },
213
228
  });
214
229
  if (!pass.ok) return pass;
215
230
  }
216
- return graph.addRasterPass('atmosphere-background', {
231
+ const ibl = addAtmosphereIbl(graph, cubeView.value, vertices.value, dirty);
232
+ if (!ibl.ok) return ibl;
233
+ const background = graph.addRasterPass('atmosphere-background', {
217
234
  accesses: [
218
235
  { resource: cubeView.value, usage: 'sampled-read' },
219
236
  { resource: params.value, usage: 'uniform-read' },
@@ -260,4 +277,5 @@ export function addAtmosphereBackground(
260
277
  pass.draw(3);
261
278
  },
262
279
  });
280
+ return background.ok ? ibl : background;
263
281
  }
@@ -0,0 +1,211 @@
1
+ import type {
2
+ GraphBuffer,
3
+ GraphTextureView,
4
+ RenderGraphBuilder,
5
+ RenderGraphError,
6
+ } from '@forgeax/engine-render-graph';
7
+ import {
8
+ type BindGroup,
9
+ type BindGroupLayout,
10
+ type RenderPipeline,
11
+ RhiError,
12
+ type Sampler,
13
+ } from '@forgeax/engine-rhi';
14
+ import { ok, type Result } from '@forgeax/engine-types';
15
+ import type { RenderPipelineFrame } from '../render-pipeline';
16
+ import { atmosphereStorage } from './storage';
17
+
18
+ /** Graph views of the shared device-owned sky and its lighting products. */
19
+ export interface GraphEnvironment {
20
+ readonly sky: GraphTextureView;
21
+ readonly irradiance: GraphTextureView;
22
+ readonly prefilter: GraphTextureView;
23
+ }
24
+
25
+ export function addAtmosphereIbl(
26
+ graph: RenderGraphBuilder<RenderPipelineFrame>,
27
+ sky: GraphTextureView,
28
+ vertices: GraphBuffer,
29
+ dirty: (frame: RenderPipelineFrame) => boolean,
30
+ ): Result<GraphEnvironment, RenderGraphError> {
31
+ const irradiance = graph.importTexture(
32
+ 'atmosphere-irradiance',
33
+ {
34
+ format: 'rgba16float',
35
+ size: { width: 16, height: 16, depthOrArrayLayers: 6 },
36
+ usage: 0x14,
37
+ },
38
+ (frame) => atmosphereStorage(frame).irradiance,
39
+ );
40
+ if (!irradiance.ok) return irradiance;
41
+ const prefilter = graph.importTexture(
42
+ 'atmosphere-prefilter',
43
+ {
44
+ format: 'rgba16float',
45
+ size: { width: 64, height: 64, depthOrArrayLayers: 6 },
46
+ mipLevelCount: 5,
47
+ usage: 0x14,
48
+ },
49
+ (frame) => atmosphereStorage(frame).prefilter,
50
+ );
51
+ if (!prefilter.ok) return prefilter;
52
+ const irradianceView = graph.view(irradiance.value, { dimension: 'cube', arrayLayerCount: 6 });
53
+ if (!irradianceView.ok) return irradianceView;
54
+ const prefilterView = graph.view(prefilter.value, { dimension: 'cube', arrayLayerCount: 6 });
55
+ if (!prefilterView.ok) return prefilterView;
56
+ const parameters = graph.importBuffer(
57
+ 'atmosphere-ibl-params',
58
+ { size: 5 * 256, usage: 0x48 },
59
+ (frame) => atmosphereStorage(frame).iblParams,
60
+ );
61
+ if (!parameters.ok) return parameters;
62
+ const upload = graph.addCopyPass('atmosphere-ibl-prepare', {
63
+ accesses: [{ resource: parameters.value, usage: 'copy-dst' }],
64
+ executeIf: dirty,
65
+ encode: ({ frame, resources }) => {
66
+ const values = new Float32Array(5 * 64);
67
+ for (let mip = 0; mip < 5; mip++) values[mip * 64 + 14] = mip / 4;
68
+ frame.runtime.device.queue
69
+ .writeBuffer(resources.buffer(parameters.value).unwrap(), 0, values)
70
+ .unwrap();
71
+ },
72
+ });
73
+ if (!upload.ok) return upload;
74
+ let state:
75
+ | {
76
+ layout: BindGroupLayout;
77
+ inputLayout: BindGroupLayout;
78
+ sampler: Sampler;
79
+ irradiance: RenderPipeline;
80
+ prefilter: RenderPipeline;
81
+ inputs?: BindGroup;
82
+ parameters: BindGroup[];
83
+ }
84
+ | undefined;
85
+ const ensureState = (frame: RenderPipelineFrame) => {
86
+ if (state !== undefined) return state;
87
+ const device = frame.runtime.device;
88
+ const source = frame.atmosphereShaders?.ibl;
89
+ const factory = frame.runtime.shaderModuleFactory;
90
+ if (source === undefined || factory === undefined)
91
+ throw new RhiError({
92
+ code: 'shader-compile-failed',
93
+ expected: 'the compiled Atmosphere IBL program is available',
94
+ hint: 'rebuild the Engine shader manifest before selecting Atmosphere lighting',
95
+ });
96
+ const module = factory.createShaderModule({ code: source, label: 'atmosphere_ibl' }).unwrap();
97
+ const layout = device
98
+ .createBindGroupLayout({
99
+ entries: [{ binding: 0, visibility: 2, buffer: { type: 'uniform', minBindingSize: 64 } }],
100
+ })
101
+ .unwrap();
102
+ const inputLayout = device
103
+ .createBindGroupLayout({
104
+ entries: [
105
+ { binding: 0, visibility: 2, texture: { sampleType: 'float', viewDimension: 'cube' } },
106
+ { binding: 1, visibility: 2, sampler: { type: 'filtering' } },
107
+ ],
108
+ })
109
+ .unwrap();
110
+ const pipelineLayout = device
111
+ .createPipelineLayout({ bindGroupLayouts: [layout, inputLayout] })
112
+ .unwrap();
113
+ const pipeline = (entryPoint: string) =>
114
+ device
115
+ .createRenderPipeline({
116
+ label: entryPoint,
117
+ layout: pipelineLayout,
118
+ vertex: {
119
+ module,
120
+ entryPoint: 'atmosphere_ibl_vs',
121
+ buffers: [
122
+ {
123
+ arrayStride: 12,
124
+ attributes: [{ shaderLocation: 0, offset: 0, format: 'float32x3' }],
125
+ },
126
+ ],
127
+ },
128
+ fragment: { module, entryPoint, targets: [{ format: 'rgba16float' }] },
129
+ primitive: { topology: 'triangle-list' },
130
+ })
131
+ .unwrap();
132
+ state = {
133
+ layout,
134
+ inputLayout,
135
+ sampler: device
136
+ .createSampler({ magFilter: 'linear', minFilter: 'linear', mipmapFilter: 'linear' })
137
+ .unwrap(),
138
+ irradiance: pipeline('atmosphere_irradiance_fs'),
139
+ prefilter: pipeline('atmosphere_prefilter_fs'),
140
+ parameters: [],
141
+ };
142
+ return state;
143
+ };
144
+ for (const product of ['irradiance', 'prefilter'] as const) {
145
+ for (let mip = 0; mip < (product === 'irradiance' ? 1 : 5); mip++)
146
+ for (let face = 0; face < 6; face++) {
147
+ const target = graph.view(product === 'irradiance' ? irradiance.value : prefilter.value, {
148
+ dimension: '2d',
149
+ baseArrayLayer: face,
150
+ arrayLayerCount: 1,
151
+ baseMipLevel: mip,
152
+ mipLevelCount: 1,
153
+ });
154
+ if (!target.ok) return target;
155
+ const pass = graph.addRasterPass(`atmosphere-${product}-${mip}-${face}`, {
156
+ accesses: [
157
+ { resource: sky, usage: 'sampled-read' },
158
+ { resource: parameters.value, usage: 'uniform-read' },
159
+ { resource: vertices, usage: 'vertex-read' },
160
+ { resource: target.value, usage: 'color-attachment' },
161
+ ],
162
+ colorAttachments: [{ view: target.value, loadOp: 'clear', storeOp: 'store' }],
163
+ executeIf: dirty,
164
+ encode: ({ frame, resources, pass }) => {
165
+ const current = ensureState(frame);
166
+ current.inputs ??= frame.runtime.device
167
+ .createBindGroup({
168
+ layout: current.inputLayout,
169
+ entries: [
170
+ {
171
+ binding: 0,
172
+ resource: { kind: 'textureView', value: resources.textureView(sky).unwrap() },
173
+ },
174
+ { binding: 1, resource: { kind: 'sampler', value: current.sampler } },
175
+ ],
176
+ })
177
+ .unwrap();
178
+ let parameterGroup = current.parameters[mip];
179
+ if (parameterGroup === undefined) {
180
+ parameterGroup = frame.runtime.device
181
+ .createBindGroup({
182
+ layout: current.layout,
183
+ entries: [
184
+ {
185
+ binding: 0,
186
+ resource: {
187
+ kind: 'buffer',
188
+ value: {
189
+ buffer: resources.buffer(parameters.value).unwrap(),
190
+ offset: mip * 256,
191
+ size: 64,
192
+ },
193
+ },
194
+ },
195
+ ],
196
+ })
197
+ .unwrap();
198
+ current.parameters[mip] = parameterGroup;
199
+ }
200
+ pass.setPipeline(current[product]);
201
+ pass.setBindGroup(0, parameterGroup);
202
+ pass.setBindGroup(1, current.inputs);
203
+ pass.setVertexBuffer(0, resources.buffer(vertices).unwrap());
204
+ pass.draw(3, 1, face * 3);
205
+ },
206
+ });
207
+ if (!pass.ok) return pass;
208
+ }
209
+ }
210
+ return ok({ sky, irradiance: irradianceView.value, prefilter: prefilterView.value });
211
+ }
@@ -0,0 +1,63 @@
1
+ import type { Buffer, Texture } from '@forgeax/engine-rhi';
2
+ import type { DeviceScope } from '../device/device-scope';
3
+ import type { _InternalRenderPipelineContext } from '../record/render-context';
4
+ import type { RenderPipelineFrame } from '../render-pipeline';
5
+
6
+ /** Device-generation resources survive unrelated probe/scene graph topology changes. */
7
+ export interface AtmosphereStorage {
8
+ readonly sky: Texture;
9
+ readonly irradiance: Texture;
10
+ readonly prefilter: Texture;
11
+ readonly params: Buffer;
12
+ readonly vertices: Buffer;
13
+ readonly iblParams: Buffer;
14
+ submittedSignature?: string | undefined;
15
+ }
16
+ const generations = new WeakMap<DeviceScope, AtmosphereStorage>();
17
+
18
+ export function atmosphereStorage(frame: RenderPipelineFrame): AtmosphereStorage {
19
+ const runtime = (frame as _InternalRenderPipelineContext).runtime;
20
+ const owner = runtime.deviceScope;
21
+ const existing = generations.get(owner);
22
+ if (existing !== undefined) return existing;
23
+ const scope = owner.createChild('atmosphere');
24
+ const device = runtime.device;
25
+ try {
26
+ const texture = (name: string, size: number, mipLevelCount = 1) => {
27
+ const value = device
28
+ .createTexture({
29
+ label: name,
30
+ format: 'rgba16float',
31
+ size: { width: size, height: size, depthOrArrayLayers: 6 },
32
+ mipLevelCount,
33
+ usage: 0x14,
34
+ textureBindingViewDimension: undefined,
35
+ })
36
+ .unwrap();
37
+ scope._adopt('texture', value, (value) => {
38
+ device.destroyTexture(value).unwrap();
39
+ });
40
+ return value;
41
+ };
42
+ const buffer = (name: string, size: number, usage: number) => {
43
+ const value = device.createBuffer({ label: name, size, usage }).unwrap();
44
+ scope._adopt('buffer', value, (value) => {
45
+ device.destroyBuffer(value).unwrap();
46
+ });
47
+ return value;
48
+ };
49
+ const created: AtmosphereStorage = {
50
+ sky: texture('atmosphere-sky', 128),
51
+ irradiance: texture('atmosphere-irradiance', 16),
52
+ prefilter: texture('atmosphere-prefilter', 64, 5),
53
+ params: buffer('atmosphere-params', 64, 0x48),
54
+ vertices: buffer('atmosphere-face-vertices', 216, 0x28),
55
+ iblParams: buffer('atmosphere-ibl-params', 1280, 0x48),
56
+ };
57
+ generations.set(owner, created);
58
+ return created;
59
+ } catch (cause) {
60
+ scope.retire();
61
+ throw cause;
62
+ }
63
+ }