@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
@@ -9,6 +9,8 @@ import type {
9
9
  TextureFormat,
10
10
  TextureView,
11
11
  } from '@forgeax/engine-rhi';
12
+ import { RhiError } from '@forgeax/engine-rhi';
13
+ import { toShared } from '@forgeax/engine-types';
12
14
  import { buildCubeCameraFaceViews } from '../capture/cube-views';
13
15
  import {
14
16
  createFaceUniformsBuffer,
@@ -30,7 +32,6 @@ import {
30
32
  createIblPipelines,
31
33
  getOrCreateIblCache,
32
34
  PREFILTER_MIP_LEVELS,
33
- PREFILTER_SIZE,
34
35
  } from '../ibl/IblPipelineCache';
35
36
  import type {
36
37
  ReflectionFallbackReadbackReceipt,
@@ -42,7 +43,8 @@ import type { ReflectionProbeRecordState, RenderSystemInternals } from '../recor
42
43
  import type { ReflectionProbeGraphWork } from '../record/typed-frame-graph';
43
44
  import { REFLECTION_PROBE_VIEW_SLOT_BASE, VIEW_UNIFORM_SLOT_STRIDE } from '../record/view-ubo';
44
45
  import type { CameraSnapshot } from '../render-contract';
45
- import type { SkylightSnapshot } from '../render-system-extract';
46
+ import type { SkyboxSnapshot, SkylightSnapshot } from '../render-system-extract';
47
+ import { createProbeBackgroundResources, type ProbeBackgroundResources } from './background';
46
48
  import {
47
49
  advanceProbeFilter,
48
50
  commitProbeFilterStep,
@@ -76,6 +78,12 @@ type ProbeCubeSet = {
76
78
 
77
79
  type ProbeGpuResource = {
78
80
  fact: ReflectionProbeFact;
81
+ captureFact: ReflectionProbeFact;
82
+ diffusePayload: readonly number[];
83
+ captureSceneRevision: string;
84
+ captureStartedFrame: number;
85
+ lastCompletedFrame: number;
86
+ activeFact?: ReflectionProbeFact;
79
87
  index: number;
80
88
  filter: ProbeFilterState;
81
89
  rawFaceCursor: number;
@@ -93,6 +101,7 @@ type ProbeGpuResource = {
93
101
  };
94
102
 
95
103
  type ProbeSharedResources = {
104
+ readonly background: ProbeBackgroundResources | undefined;
96
105
  readonly faceUniforms: Buffer;
97
106
  readonly prefilterUniforms: Buffer;
98
107
  readonly vertexBuffer: Buffer;
@@ -126,6 +135,8 @@ export class ReflectionProbeRecordOwner {
126
135
  private lastAcceptedCount = 0;
127
136
  private lastScheduledRawFaces = 0;
128
137
  private lastScheduledFilteredSteps = 0;
138
+ private workCursor = 0;
139
+ private currentFrame = 0;
129
140
  private lastSelection: ReflectionProbeSelectionInspection = { kind: 'skylight' };
130
141
  private outputFormat: TextureFormat;
131
142
  private fallbackFormatAvailable: boolean;
@@ -171,6 +182,22 @@ export class ReflectionProbeRecordOwner {
171
182
  if (resource.active !== undefined) activeCount += 1;
172
183
  }
173
184
  return {
185
+ updates: [...this.resources.values()].map((resource) => ({
186
+ worldId: resource.fact.worldId,
187
+ entityKey: resource.fact.entityKey,
188
+ intent: resource.fact.updateIntent ?? 'once',
189
+ activeGeneration: resource.filter.activeGeneration,
190
+ requestedRevision: resource.fact.revision,
191
+ capturedRevision: resource.captureFact.revision,
192
+ pending: resource.filter.cursor < resource.filter.faceCount * resource.filter.mipCount,
193
+ captureStartedFrame: resource.captureStartedFrame,
194
+ lastCompletedFrame: resource.lastCompletedFrame,
195
+ latencyFrames:
196
+ resource.lastCompletedFrame < resource.captureStartedFrame
197
+ ? undefined
198
+ : resource.lastCompletedFrame - resource.captureStartedFrame,
199
+ })),
200
+ workBudgetPerFrame: 1,
174
201
  selection: this.lastSelection,
175
202
  factCount: this.lastFactCount,
176
203
  acceptedCount: this.lastAcceptedCount,
@@ -304,7 +331,8 @@ export class ReflectionProbeRecordOwner {
304
331
  if (factory === undefined) return;
305
332
  const attempt = this.pipelineWarmupAttempts + 1;
306
333
  this.pipelineWarmupAttempts = attempt;
307
- this.pipelineWarmup = createIblPipelines(
334
+ const device = this.internals.device;
335
+ const warmup = createIblPipelines(
308
336
  this.internals.deviceScope,
309
337
  this.internals.device,
310
338
  (device, descriptor) => {
@@ -317,20 +345,42 @@ export class ReflectionProbeRecordOwner {
317
345
  : this.internals.createShaderModule(device, labeled);
318
346
  },
319
347
  this.outputFormat,
320
- ).then((result) => {
321
- if (!result.ok) {
322
- this.pipelineWarmupFailure = `${result.error.code}: ${result.error.hint}`;
323
- console.warn('[forgeax] ReflectionProbe IBL warmup retry', this.pipelineWarmupFailure);
324
- this.pipelineWarmup = undefined;
325
- return;
326
- }
327
- this.shared = this.createSharedResources(result.value.prefilterPipeline);
328
- if (this.shared === undefined) {
329
- this.pipelineWarmupFailure = 'renderer-owned IBL resources could not be allocated';
330
- console.warn('[forgeax] ReflectionProbe IBL resource allocation retry');
348
+ )
349
+ .then(async (result) => {
350
+ if (this.pipelineWarmup !== warmup) return;
351
+ if (!result.ok) {
352
+ this.pipelineWarmupFailure = `${result.error.code}: ${result.error.hint}`;
353
+ console.warn('[forgeax] ReflectionProbe IBL warmup retry', this.pipelineWarmupFailure);
354
+ this.pipelineWarmup = undefined;
355
+ return;
356
+ }
357
+ const background = await createProbeBackgroundResources(this.internals, this.outputFormat);
358
+ if (this.pipelineWarmup !== warmup) {
359
+ if (background !== undefined) device.destroyBuffer(background.uniform);
360
+ return;
361
+ }
362
+ this.shared = this.createSharedResources(result.value.prefilterPipeline, background);
363
+ if (this.shared === undefined) {
364
+ this.pipelineWarmupFailure = 'renderer-owned IBL resources could not be allocated';
365
+ console.warn('[forgeax] ReflectionProbe IBL resource allocation retry');
366
+ this.pipelineWarmup = undefined;
367
+ }
368
+ })
369
+ .catch((cause: unknown) => {
370
+ if (this.pipelineWarmup !== warmup) return;
371
+ this.pipelineWarmupFailure = cause instanceof Error ? cause.message : String(cause);
331
372
  this.pipelineWarmup = undefined;
332
- }
333
- });
373
+ this.internals.errorRegistry.fire(
374
+ cause instanceof RhiError
375
+ ? cause
376
+ : new RhiError({
377
+ code: 'shader-compile-failed',
378
+ expected: 'complete reflection probe background and IBL pipelines',
379
+ hint: `inspect the shader manifest and device error: ${this.pipelineWarmupFailure}`,
380
+ }),
381
+ );
382
+ });
383
+ this.pipelineWarmup = warmup;
334
384
  }
335
385
 
336
386
  prepare(
@@ -339,7 +389,10 @@ export class ReflectionProbeRecordOwner {
339
389
  displayCamera: CameraSnapshot | undefined,
340
390
  frameId = this.nextFallbackTicket + 1,
341
391
  skylight: SkylightSnapshot | undefined = undefined,
392
+ sceneRevision = '',
393
+ skybox?: SkyboxSnapshot,
342
394
  ): ReflectionProbeRecordState {
395
+ this.currentFrame = frameId;
343
396
  this.refreshFallbackCapabilities(frameId);
344
397
  this.lastFactCount = facts.length;
345
398
  this.lastScheduledRawFaces = 0;
@@ -371,7 +424,7 @@ export class ReflectionProbeRecordOwner {
371
424
  );
372
425
  if (!admission.ok) continue;
373
426
  const key = `${fact.worldId}:${fact.entityKey}`;
374
- const resource = this.resourceFor(key, fact, acceptedCount);
427
+ const resource = this.resourceFor(key, fact, acceptedCount, sceneRevision, skylight);
375
428
  if (resource === undefined) continue;
376
429
  acceptedCount += 1;
377
430
  acceptedBytes = admission.acceptedBytes;
@@ -389,7 +442,9 @@ export class ReflectionProbeRecordOwner {
389
442
  }
390
443
  const rows = [...accepted.values()]
391
444
  .map((resource) => {
392
- const active = resource.active;
445
+ const active = resource.activeFact?.center.every((v, i) => v === resource.fact.center[i])
446
+ ? resource.active
447
+ : undefined;
393
448
  if (active === undefined) return undefined;
394
449
  return {
395
450
  primitiveKey: `${resource.fact.worldId}:${resource.fact.entityKey}`,
@@ -408,7 +463,23 @@ export class ReflectionProbeRecordOwner {
408
463
  .filter((row): row is NonNullable<typeof row> => row !== undefined);
409
464
  const table = buildReflectionProbeTable(rows);
410
465
  const work: ReflectionProbeGraphWork[] = [];
411
- for (const resource of accepted.values()) {
466
+ const environmentReady = [skylight?.equirectHandle, skybox?.equirectHandle].every(
467
+ (handle) =>
468
+ handle === undefined ||
469
+ handle === 0 ||
470
+ this.internals.gpuStore.getCubemapGpuView(toShared<'EquirectAsset'>(handle)) !== undefined,
471
+ );
472
+ const candidates = [...accepted.values()];
473
+ const captureTickets: Array<{
474
+ resource: ProbeGpuResource;
475
+ pending: NonNullable<ProbeGpuResource['pending']>;
476
+ }> = [];
477
+ const deviceGeneration = this.internals.deviceScope.generation;
478
+ for (let offset = 0; offset < candidates.length; offset++) {
479
+ const candidateIndex = (this.workCursor + offset) % candidates.length;
480
+ const resource = candidates[candidateIndex];
481
+ if (resource === undefined) continue;
482
+ if (resource.pending !== undefined) continue;
412
483
  const rawCaptureFace =
413
484
  resource.rawFaceCursor < resource.raw.faceViews.length ? resource.rawFaceCursor : undefined;
414
485
  const step =
@@ -416,7 +487,7 @@ export class ReflectionProbeRecordOwner {
416
487
  ? advanceProbeFilter(resource.filter)
417
488
  : undefined;
418
489
  if (this.shared === undefined) continue;
419
- if (rawCaptureFace === undefined && step === undefined) continue;
490
+ if (!environmentReady || (rawCaptureFace === undefined && step === undefined)) continue;
420
491
  const filterGroup0 =
421
492
  step === undefined
422
493
  ? undefined
@@ -449,15 +520,15 @@ export class ReflectionProbeRecordOwner {
449
520
  ],
450
521
  });
451
522
  if (filterGroup0 !== undefined && !filterGroup0.ok) continue;
452
- if (resource.pending !== undefined) continue;
453
523
  resource.pending = { rawCaptureFace, step };
524
+ captureTickets.push({ resource, pending: resource.pending });
454
525
  if (rawCaptureFace !== undefined) this.lastScheduledRawFaces += 1;
455
526
  if (step !== undefined) this.lastScheduledFilteredSteps += 1;
456
527
  const face =
457
528
  rawCaptureFace === undefined
458
529
  ? undefined
459
530
  : buildCubeCameraFaceViews({
460
- position: resource.fact.center,
531
+ position: resource.captureFact.center,
461
532
  near: 0.1,
462
533
  far: 100,
463
534
  })[rawCaptureFace];
@@ -467,9 +538,9 @@ export class ReflectionProbeRecordOwner {
467
538
  : {
468
539
  ...displayCamera,
469
540
  position: vec3.create(
470
- resource.fact.center[0] ?? 0,
471
- resource.fact.center[1] ?? 0,
472
- resource.fact.center[2] ?? 0,
541
+ resource.captureFact.center[0] ?? 0,
542
+ resource.captureFact.center[1] ?? 0,
543
+ resource.captureFact.center[2] ?? 0,
473
544
  ),
474
545
  world: mat4.invert(mat4.create(), face.view),
475
546
  fov: Math.PI / 2,
@@ -490,6 +561,7 @@ export class ReflectionProbeRecordOwner {
490
561
  outputFormat: this.outputFormat,
491
562
  sampler: resource.sampler,
492
563
  filterPipeline: this.shared.filterPipeline,
564
+ ...(this.shared.background === undefined ? {} : { background: this.shared.background }),
493
565
  filterGroup0: filterGroup0?.value,
494
566
  filterGroup1: resource.filterGroup1,
495
567
  cubeVertexBuffer: this.shared.vertexBuffer,
@@ -506,6 +578,8 @@ export class ReflectionProbeRecordOwner {
506
578
  filteredSize: resource.candidate.size,
507
579
  step,
508
580
  });
581
+ this.workCursor = (candidateIndex + 1) % candidates.length;
582
+ break;
509
583
  }
510
584
  return {
511
585
  table,
@@ -530,6 +604,9 @@ export class ReflectionProbeRecordOwner {
530
604
  completed,
531
605
  fallbackOutput,
532
606
  fallbackRequested,
607
+ captureTickets,
608
+ deviceGeneration,
609
+ frameId,
533
610
  ),
534
611
  };
535
612
  }
@@ -552,14 +629,13 @@ export class ReflectionProbeRecordOwner {
552
629
  }>;
553
630
  },
554
631
  fallbackRequested = true,
632
+ captureTickets: readonly {
633
+ resource: ProbeGpuResource;
634
+ pending: NonNullable<ProbeGpuResource['pending']>;
635
+ }[] = [],
636
+ deviceGeneration = this.internals.deviceScope.generation,
637
+ submittedFrame = this.currentFrame,
555
638
  ): Promise<void> {
556
- if (!fallbackRequested) {
557
- // The scene has no Standard PBR renderable that can consume the
558
- // producer's second output, so the graph intentionally omits the MRT.
559
- // Retire only this frame's candidate tickets; keep any previously
560
- // committed LKG receipt without manufacturing a readback failure.
561
- return;
562
- }
563
639
  let completionFailure: ReflectionFallbackFailureInput | undefined;
564
640
  if (completed !== undefined) {
565
641
  try {
@@ -573,14 +649,20 @@ export class ReflectionProbeRecordOwner {
573
649
  };
574
650
  }
575
651
  }
576
- await this.completeFallbackSubmission(
577
- submitted,
578
- fallbackTickets,
579
- fallbackOutput,
580
- completionFailure,
581
- );
582
- for (const resource of this.resources.values()) {
583
- const pending = resource.pending;
652
+ if (fallbackRequested)
653
+ await this.completeFallbackSubmission(
654
+ submitted,
655
+ fallbackTickets,
656
+ fallbackOutput,
657
+ completionFailure,
658
+ );
659
+ if (deviceGeneration !== this.internals.deviceScope.generation) return;
660
+ for (const { resource, pending } of captureTickets) {
661
+ if (
662
+ resource.pending !== pending ||
663
+ this.resources.get(`${resource.fact.worldId}:${resource.fact.entityKey}`) !== resource
664
+ )
665
+ continue;
584
666
  delete resource.pending;
585
667
  if (!submitted || pending === undefined) continue;
586
668
  if (pending.rawCaptureFace !== undefined) {
@@ -595,6 +677,8 @@ export class ReflectionProbeRecordOwner {
595
677
  if (resource.filter.cursor >= resource.filter.faceCount * resource.filter.mipCount) {
596
678
  const previous = resource.active;
597
679
  resource.active = resource.candidate;
680
+ resource.activeFact = resource.captureFact;
681
+ resource.lastCompletedFrame = submittedFrame;
598
682
  resource.candidate = resource.spare ?? previous ?? resource.candidate;
599
683
  resource.spare = undefined;
600
684
  }
@@ -618,18 +702,29 @@ export class ReflectionProbeRecordOwner {
618
702
  facts.length === 0 && skylight === undefined
619
703
  ? new Map([['__frame__', { kind: 'skylight' as const }]])
620
704
  : selections;
705
+ const skylightSignature =
706
+ skylight === undefined
707
+ ? ''
708
+ : `:${JSON.stringify([skylight.equirectHandle, skylight.intensity, ...skylight.color, ...skylight.rotation])}`;
621
709
  for (const [renderableKey, selection] of stagedSelections) {
622
710
  const probeKey =
623
711
  selection.kind === 'probe' ? `${selection.worldId}:${selection.entityKey}` : undefined;
624
712
  const resource = probeKey === undefined ? undefined : accepted.get(probeKey);
625
713
  const source = resolveReflectionFallbackSource(
626
714
  selection,
627
- resource?.active !== undefined,
715
+ resource?.active !== undefined &&
716
+ resource.activeFact?.center.every((v, i) => v === resource.fact.center[i]) === true,
628
717
  skylight !== undefined,
629
718
  );
630
719
  const projection = deriveReflectionFallbackProjection({
631
720
  renderableKey,
632
- sourceKey: reflectionFallbackSourceKey(source, selection, skylight),
721
+ sourceKey:
722
+ reflectionFallbackSourceKey(source, selection, skylight) +
723
+ (source === 'probe' && resource !== undefined
724
+ ? `@${resource.filter.activeGeneration}:${resource.fact.boxProjection === true}:${resource.fact.intensity}`
725
+ : '') +
726
+ skylightSignature +
727
+ (source === 'neutral' ? '' : `:catalog:${this.internals.assets.catalogEpoch}`),
633
728
  source,
634
729
  coverage: reflectionFallbackCoverage(source, resource?.fact, selection),
635
730
  ...(resource === undefined ? {} : { extent: resource.fact.halfExtents }),
@@ -748,7 +843,8 @@ export class ReflectionProbeRecordOwner {
748
843
  let sourceMatch: string | undefined;
749
844
  for (const [key, pending] of tickets) {
750
845
  const matches =
751
- pending.projection.sourceKey === selectedSourceKey ||
846
+ (selectedSourceKey !== undefined &&
847
+ pending.projection.sourceKey?.startsWith(`${selectedSourceKey}@`) === true) ||
752
848
  (selectedSourceKey === undefined && pending.projection.source === 'skylight');
753
849
  if (matches && (sourceMatch === undefined || key < sourceMatch)) sourceMatch = key;
754
850
  }
@@ -793,8 +889,9 @@ export class ReflectionProbeRecordOwner {
793
889
  source: previous?.sourceGeneration ?? 0,
794
890
  projection: previous?.projectionGeneration ?? 0,
795
891
  };
892
+ const { linearHdr: _referencePixel, ...projection } = pending.projection;
796
893
  const receipt = Object.freeze({
797
- ...pending.projection,
894
+ ...projection,
798
895
  rendererId: this.internals.deviceScope.owner,
799
896
  producerId: REFLECTION_PROBE_PRODUCER_ID,
800
897
  ...(this.internals.ssrIdentity === undefined ? {} : { identity: this.internals.ssrIdentity }),
@@ -869,11 +966,8 @@ export class ReflectionProbeRecordOwner {
869
966
  if (!this.fallbackFormatAvailable) {
870
967
  return;
871
968
  }
872
- // A graph attachment descriptor is not a producer receipt. Until the
873
- // completed frame has supplied mapped bytes and their digest, discard the
874
- // pending ticket and keep the last committed row (if any) as LKG. This
875
- // prevents the projection's zero quartet from becoming false active
876
- // evidence when the copy/readback owner has not run.
969
+ // Source/coverage publication follows real graph execution and GPU completion.
970
+ // Pixel validation is a separate, explicitly requested diagnostic capability.
877
971
  if (!submitted || fallbackTickets.size === 0) {
878
972
  if (!submitted) {
879
973
  this.recordFallbackFailure(
@@ -887,10 +981,12 @@ export class ReflectionProbeRecordOwner {
887
981
  }
888
982
  return;
889
983
  }
890
- if (fallbackOutput?.readback === undefined) {
891
- // Neutral is an exact producer-owned zero and does not need an MRT
892
- // sample. A successfully submitted frame may commit those rows even
893
- // when the non-neutral rows have no mapped readback and must stay LKG.
984
+ if (
985
+ fallbackOutput === undefined ||
986
+ (this.internals.captureReflectionFallbackReadback === true &&
987
+ fallbackOutput.readback === undefined)
988
+ ) {
989
+ // Neutral rows require no attachment; non-neutral rows need an executed output.
894
990
  const committedNeutral = this.commitNeutralFallbacks(fallbackTickets);
895
991
  const nonNeutral = new Map(
896
992
  [...fallbackTickets].filter(([, pending]) => pending.projection.source !== 'neutral'),
@@ -902,20 +998,25 @@ export class ReflectionProbeRecordOwner {
902
998
  if (nonNeutral.size > 0) {
903
999
  this.recordFallbackFailure({
904
1000
  stage: 'completion',
905
- code: 'reflection-fallback-readback-missing',
906
- expected: 'a mapped fallback MRT readback for every non-neutral source',
1001
+ code:
1002
+ fallbackOutput === undefined
1003
+ ? 'reflection-fallback-output-missing'
1004
+ : 'reflection-fallback-readback-missing',
1005
+ expected: 'a completed fallback MRT output and, when requested, mapped diagnostic bytes',
907
1006
  detail: { frameId: fallbackTickets.values().next().value?.frameId },
908
1007
  });
909
1008
  }
910
1009
  this.preserveFallbackLkg(nonNeutral);
911
1010
  return;
912
1011
  }
913
- let readback: {
914
- readonly linearHdr: readonly [number, number, number, number];
915
- readonly hash: string;
916
- readonly graphGeneration: number;
917
- readonly textureIdentity: number;
918
- };
1012
+ let readback:
1013
+ | {
1014
+ readonly linearHdr: readonly [number, number, number, number];
1015
+ readonly hash: string;
1016
+ readonly graphGeneration: number;
1017
+ readonly textureIdentity: number;
1018
+ }
1019
+ | undefined;
919
1020
  try {
920
1021
  readback = await fallbackOutput.readback;
921
1022
  } catch (cause) {
@@ -934,8 +1035,9 @@ export class ReflectionProbeRecordOwner {
934
1035
  return;
935
1036
  }
936
1037
  if (
937
- readback.graphGeneration !== fallbackOutput.graphGeneration ||
938
- readback.textureIdentity !== fallbackOutput.textureIdentity
1038
+ readback !== undefined &&
1039
+ (readback.graphGeneration !== fallbackOutput.graphGeneration ||
1040
+ readback.textureIdentity !== fallbackOutput.textureIdentity)
939
1041
  ) {
940
1042
  this.recordFallbackFailure({
941
1043
  stage: 'completion',
@@ -994,6 +1096,7 @@ export class ReflectionProbeRecordOwner {
994
1096
  const previousReadback = this.fallbackReadback;
995
1097
  if (
996
1098
  hasNonNeutral &&
1099
+ readback !== undefined &&
997
1100
  (previousReadback === undefined || fallbackOutput.frameId >= previousReadback.frameId)
998
1101
  ) {
999
1102
  this.fallbackReadback = Object.freeze({
@@ -1015,6 +1118,7 @@ export class ReflectionProbeRecordOwner {
1015
1118
  readbackStatus: 'complete',
1016
1119
  });
1017
1120
  }
1121
+ if (readback === undefined) this.fallbackReadback = undefined;
1018
1122
  for (const [renderableKey, pending] of eligible) {
1019
1123
  this.commitFallbackProjection(
1020
1124
  renderableKey,
@@ -1043,6 +1147,8 @@ export class ReflectionProbeRecordOwner {
1043
1147
  this.fallbackFormatAvailable = this.internals.device.caps.rgba16floatRenderable === true;
1044
1148
  this.fallbackReceipt = this.createFallbackReceipt('unavailable');
1045
1149
  if (this.shared !== undefined) {
1150
+ if (this.shared.background !== undefined)
1151
+ this.internals.device.destroyBuffer(this.shared.background.uniform);
1046
1152
  this.internals.device.destroyBuffer(this.shared.faceUniforms);
1047
1153
  this.internals.device.destroyBuffer(this.shared.prefilterUniforms);
1048
1154
  this.internals.device.destroyBuffer(this.shared.vertexBuffer);
@@ -1068,35 +1174,92 @@ export class ReflectionProbeRecordOwner {
1068
1174
  key: string,
1069
1175
  fact: ReflectionProbeFact,
1070
1176
  index: number,
1177
+ sceneRevision = '',
1178
+ skylight?: SkylightSnapshot,
1071
1179
  ): ProbeGpuResource | undefined {
1072
- const existing = this.resources.get(key);
1180
+ const diffuse =
1181
+ skylight === undefined
1182
+ ? [0, 0, 0, 0, 0, 0, 0, 1]
1183
+ : [...skylight.color.map((value) => value * skylight.intensity), 0, ...skylight.rotation];
1184
+ let existing = this.resources.get(key);
1185
+ if (
1186
+ existing !== undefined &&
1187
+ existing.raw.size !== fact.resolution &&
1188
+ existing.pending === undefined
1189
+ ) {
1190
+ this.destroyProbeResource(existing);
1191
+ this.resources.delete(key);
1192
+ existing = undefined;
1193
+ }
1073
1194
  if (existing !== undefined) {
1074
- if (existing.fact.revision !== fact.revision) {
1075
- existing.fact = fact;
1195
+ const previous = existing.fact;
1196
+ const settled =
1197
+ existing.filter.cursor >= existing.filter.faceCount * existing.filter.mipCount;
1198
+ const moved = existing.captureFact.center.some((value, axis) => value !== fact.center[axis]);
1199
+ const requested =
1200
+ existing.captureFact.revision !== fact.revision ||
1201
+ moved ||
1202
+ fact.updateIntent === 'continuous' ||
1203
+ (fact.updateIntent === 'on-change' && existing.captureSceneRevision !== sceneRevision);
1204
+ // Finish the current bounded cycle. Coalesce newer changes into the next
1205
+ // cycle instead of continuously restarting face zero under motion.
1206
+ if (settled && existing.pending === undefined && requested) {
1207
+ existing.captureFact = fact;
1208
+ existing.captureSceneRevision = sceneRevision;
1209
+ existing.captureStartedFrame = this.currentFrame;
1076
1210
  existing.filter = createProbeFilterState({
1077
1211
  probeIndex: index,
1078
1212
  mipCount: PREFILTER_MIP_LEVELS,
1079
1213
  activeGeneration: existing.filter.activeGeneration,
1080
1214
  });
1081
1215
  existing.rawFaceCursor = 0;
1082
- } else {
1083
- existing.fact = fact;
1084
- existing.index = index;
1216
+ }
1217
+ existing.fact = fact;
1218
+ existing.index = index;
1219
+ if (
1220
+ previous.intensity !== fact.intensity ||
1221
+ previous.boxProjection !== fact.boxProjection ||
1222
+ previous.center.some((v, i) => v !== fact.center[i]) ||
1223
+ previous.halfExtents.some((v, i) => v !== fact.halfExtents[i]) ||
1224
+ existing.diffusePayload.some((v, i) => v !== diffuse[i])
1225
+ ) {
1226
+ const written = this.internals.device.queue.writeBuffer(
1227
+ existing.uniformBuffer,
1228
+ 0,
1229
+ new Float32Array([
1230
+ -(Math.max(0, fact.intensity) + PROBE_INTENSITY_SENTINEL_OFFSET),
1231
+ ...fact.center,
1232
+ ...fact.halfExtents,
1233
+ fact.boxProjection === true ? 1 : 0,
1234
+ ...diffuse,
1235
+ ]),
1236
+ );
1237
+ if (!written.ok) this.internals.errorRegistry.fire(written.error);
1238
+ else existing.diffusePayload = diffuse;
1085
1239
  }
1086
1240
  return existing;
1087
1241
  }
1088
1242
  if (this.shared === undefined) return undefined;
1089
- const raw = this.createCubeSet(`reflection-probe-${index}-raw`, fact.resolution);
1243
+ const raw = this.createCubeSet(`reflection-probe-${index}-raw`, fact.resolution, 1);
1090
1244
  const candidate = this.createCubeSet(`reflection-probe-${index}-candidate`, fact.resolution);
1091
1245
  const spare = this.createCubeSet(`reflection-probe-${index}-spare`, fact.resolution);
1092
- if (raw === undefined || candidate === undefined || spare === undefined) return undefined;
1246
+ const releaseSets = () => {
1247
+ for (const set of [raw, candidate, spare]) if (set !== undefined) this.destroyCubeSet(set);
1248
+ };
1249
+ if (raw === undefined || candidate === undefined || spare === undefined) {
1250
+ releaseSets();
1251
+ return undefined;
1252
+ }
1093
1253
  const uniformBuffer = this.internals.device.createBuffer({
1094
1254
  label: `reflection-probe-${index}-uniforms`,
1095
- size: 32,
1255
+ size: 64,
1096
1256
  usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
1097
1257
  mappedAtCreation: false,
1098
1258
  });
1099
- if (!uniformBuffer.ok) return undefined;
1259
+ if (!uniformBuffer.ok) {
1260
+ releaseSets();
1261
+ return undefined;
1262
+ }
1100
1263
  const payload = new Float32Array([
1101
1264
  -(Math.max(0, fact.intensity) + PROBE_INTENSITY_SENTINEL_OFFSET),
1102
1265
  fact.center[0],
@@ -1105,10 +1268,15 @@ export class ReflectionProbeRecordOwner {
1105
1268
  fact.halfExtents[0],
1106
1269
  fact.halfExtents[1],
1107
1270
  fact.halfExtents[2],
1108
- 0,
1271
+ fact.boxProjection === true ? 1 : 0,
1272
+ ...diffuse,
1109
1273
  ]);
1110
1274
  const written = this.internals.device.queue.writeBuffer(uniformBuffer.value, 0, payload);
1111
- if (!written.ok) return undefined;
1275
+ if (!written.ok) {
1276
+ releaseSets();
1277
+ this.internals.device.destroyBuffer(uniformBuffer.value);
1278
+ return undefined;
1279
+ }
1112
1280
  const filterGroup1 = this.internals.device.createBindGroup({
1113
1281
  label: `reflection-probe-${index}-filter-input`,
1114
1282
  layout: this.shared.cubeGroup1Bgl,
@@ -1117,11 +1285,20 @@ export class ReflectionProbeRecordOwner {
1117
1285
  { binding: 1, resource: { kind: 'sampler', value: this.shared.sampler } },
1118
1286
  ],
1119
1287
  });
1120
- if (!filterGroup1.ok) return undefined;
1288
+ if (!filterGroup1.ok) {
1289
+ releaseSets();
1290
+ this.internals.device.destroyBuffer(uniformBuffer.value);
1291
+ return undefined;
1292
+ }
1121
1293
  const resource: ProbeGpuResource = {
1122
1294
  fact,
1295
+ captureFact: fact,
1296
+ diffusePayload: diffuse,
1297
+ captureSceneRevision: sceneRevision,
1298
+ captureStartedFrame: this.currentFrame,
1299
+ lastCompletedFrame: 0,
1123
1300
  index,
1124
- filter: createProbeFilterState({ probeIndex: index, mipCount: PREFILTER_MIP_LEVELS }),
1301
+ filter: createProbeFilterState({ probeIndex: index, mipCount: candidate.mipViews.length }),
1125
1302
  rawFaceCursor: 0,
1126
1303
  raw,
1127
1304
  active: undefined,
@@ -1135,12 +1312,15 @@ export class ReflectionProbeRecordOwner {
1135
1312
  return resource;
1136
1313
  }
1137
1314
 
1138
- private createSharedResources(filterPipeline: RenderPipeline): ProbeSharedResources | undefined {
1315
+ private createSharedResources(
1316
+ filterPipeline: RenderPipeline,
1317
+ background?: ProbeBackgroundResources,
1318
+ ): ProbeSharedResources | undefined {
1139
1319
  const faceUniforms = createFaceUniformsBuffer(this.internals.device);
1140
1320
  const prefilterUniforms = createPrefilterUniformsBuffer(this.internals.device);
1141
1321
  if (!faceUniforms.ok || !prefilterUniforms.ok) return undefined;
1142
1322
  if (!writeAllFaceUniforms(this.internals.device, faceUniforms.value).ok) return undefined;
1143
- if (!writeAllPrefilterUniforms(this.internals.device, prefilterUniforms.value).ok)
1323
+ if (!writeAllPrefilterUniforms(this.internals.device, prefilterUniforms.value, 1, 'camera').ok)
1144
1324
  return undefined;
1145
1325
  const vertexBuffer = this.internals.device.createBuffer({
1146
1326
  label: 'reflection-probe-capture-cube-vertices',
@@ -1173,6 +1353,7 @@ export class ReflectionProbeRecordOwner {
1173
1353
  return undefined;
1174
1354
  }
1175
1355
  return {
1356
+ background,
1176
1357
  faceUniforms: faceUniforms.value,
1177
1358
  prefilterUniforms: prefilterUniforms.value,
1178
1359
  vertexBuffer: vertexBuffer.value,
@@ -1183,9 +1364,11 @@ export class ReflectionProbeRecordOwner {
1183
1364
  };
1184
1365
  }
1185
1366
 
1186
- private createCubeSet(label: string, requestedSize: number): ProbeCubeSet | undefined {
1187
- const size = Math.min(Math.max(1, Math.floor(requestedSize)), PREFILTER_SIZE);
1188
- const mipCount = Math.min(PREFILTER_MIP_LEVELS, Math.floor(Math.log2(size)) + 1);
1367
+ private createCubeSet(
1368
+ label: string,
1369
+ size: number,
1370
+ mipCount = PREFILTER_MIP_LEVELS,
1371
+ ): ProbeCubeSet | undefined {
1189
1372
  const texture = this.internals.device.createTexture({
1190
1373
  label,
1191
1374
  size: { width: size, height: size, depthOrArrayLayers: 6 },
@@ -1205,7 +1388,10 @@ export class ReflectionProbeRecordOwner {
1205
1388
  mipLevelCount: mipCount,
1206
1389
  arrayLayerCount: 6,
1207
1390
  });
1208
- if (!cubeView.ok) return undefined;
1391
+ if (!cubeView.ok) {
1392
+ this.internals.device.destroyTexture(texture.value);
1393
+ return undefined;
1394
+ }
1209
1395
  const faceViews: TextureView[] = [];
1210
1396
  const mipViews: TextureView[][] = [];
1211
1397
  for (let mip = 0; mip < mipCount; mip += 1) {
@@ -1219,7 +1405,10 @@ export class ReflectionProbeRecordOwner {
1219
1405
  baseArrayLayer: face,
1220
1406
  arrayLayerCount: 1,
1221
1407
  });
1222
- if (!view.ok) return undefined;
1408
+ if (!view.ok) {
1409
+ this.internals.device.destroyTexture(texture.value);
1410
+ return undefined;
1411
+ }
1223
1412
  if (mip === 0) faceViews.push(view.value);
1224
1413
  faces.push(view.value);
1225
1414
  }
@@ -1236,12 +1425,19 @@ export class ReflectionProbeRecordOwner {
1236
1425
  viewFormats: [],
1237
1426
  textureBindingViewDimension: undefined,
1238
1427
  });
1239
- if (!depthTexture.ok) return undefined;
1428
+ if (!depthTexture.ok) {
1429
+ this.internals.device.destroyTexture(texture.value);
1430
+ return undefined;
1431
+ }
1240
1432
  const depthView = this.internals.device.createTextureView(depthTexture.value, {
1241
1433
  label: `${label}-depth-view`,
1242
1434
  dimension: '2d',
1243
1435
  });
1244
- if (!depthView.ok) return undefined;
1436
+ if (!depthView.ok) {
1437
+ this.internals.device.destroyTexture(texture.value);
1438
+ this.internals.device.destroyTexture(depthTexture.value);
1439
+ return undefined;
1440
+ }
1245
1441
  return {
1246
1442
  texture: texture.value,
1247
1443
  cubeView: cubeView.value,