@forgeax/engine-render 0.1.21 → 0.1.24

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 (584) hide show
  1. package/README.md +281 -38
  2. package/dist/assembly/backend-contract.d.ts +13 -0
  3. package/dist/assembly/backend-contract.d.ts.map +1 -1
  4. package/dist/assembly/bundler-contract.d.ts +9 -0
  5. package/dist/assembly/bundler-contract.d.ts.map +1 -0
  6. package/dist/assembly/device-feature-admission.d.ts +8 -0
  7. package/dist/assembly/device-feature-admission.d.ts.map +1 -1
  8. package/dist/assembly/extended-lighting-fallback.d.ts +13 -0
  9. package/dist/assembly/extended-lighting-fallback.d.ts.map +1 -0
  10. package/dist/assembly/factory.d.ts +7 -235
  11. package/dist/assembly/factory.d.ts.map +1 -1
  12. package/dist/assembly/host-contract.d.ts +10 -0
  13. package/dist/assembly/host-contract.d.ts.map +1 -1
  14. package/dist/assembly/material/assembly.d.ts +3 -2
  15. package/dist/assembly/material/assembly.d.ts.map +1 -1
  16. package/dist/assembly/material/standard-layer-projection.d.ts +10 -0
  17. package/dist/assembly/material/standard-layer-projection.d.ts.map +1 -0
  18. package/dist/assembly/material-shader-policy.d.ts +168 -0
  19. package/dist/assembly/material-shader-policy.d.ts.map +1 -0
  20. package/dist/assembly/material-shader-prewarm.d.ts +21 -0
  21. package/dist/assembly/material-shader-prewarm.d.ts.map +1 -0
  22. package/dist/assembly/mesh-ssbo-grow.d.ts +88 -0
  23. package/dist/assembly/mesh-ssbo-grow.d.ts.map +1 -0
  24. package/dist/assembly/recovery/device-loss-fanout.d.ts +14 -0
  25. package/dist/assembly/recovery/device-loss-fanout.d.ts.map +1 -0
  26. package/dist/assembly/recovery/generation.d.ts +46 -0
  27. package/dist/assembly/recovery/generation.d.ts.map +1 -0
  28. package/dist/assembly/recovery/recovery-attempt.d.ts +63 -0
  29. package/dist/assembly/recovery/recovery-attempt.d.ts.map +1 -0
  30. package/dist/assembly/recovery/renderer-recover.d.ts +115 -0
  31. package/dist/assembly/recovery/renderer-recover.d.ts.map +1 -0
  32. package/dist/assembly/renderer-facade.d.ts +10 -0
  33. package/dist/assembly/renderer-facade.d.ts.map +1 -0
  34. package/dist/assembly/renderer-frame-transaction.d.ts +17 -0
  35. package/dist/assembly/renderer-frame-transaction.d.ts.map +1 -1
  36. package/dist/assembly/renderer-helpers.d.ts +19 -0
  37. package/dist/assembly/renderer-helpers.d.ts.map +1 -0
  38. package/dist/assembly/renderer-lifecycle.d.ts +34 -0
  39. package/dist/assembly/renderer-lifecycle.d.ts.map +1 -1
  40. package/dist/assembly/shader-prewarm-policy.d.ts +15 -0
  41. package/dist/assembly/shader-prewarm-policy.d.ts.map +1 -0
  42. package/dist/assembly/webgpu-ready-contract.d.ts +9 -0
  43. package/dist/assembly/webgpu-ready-contract.d.ts.map +1 -0
  44. package/dist/assembly/webgpu-ready.d.ts +55 -0
  45. package/dist/assembly/webgpu-ready.d.ts.map +1 -0
  46. package/dist/assembly/webgpu-renderer.d.ts +152 -0
  47. package/dist/assembly/webgpu-renderer.d.ts.map +1 -0
  48. package/dist/authoring.mjs +1 -1
  49. package/dist/{chunk-PCHBPYML.mjs → chunk-GE3SDD72.mjs} +37 -5
  50. package/dist/chunk-GE3SDD72.mjs.map +1 -0
  51. package/dist/chunk-KZFTK6DO.mjs +1191 -0
  52. package/dist/chunk-KZFTK6DO.mjs.map +1 -0
  53. package/dist/{chunk-FS2KNUQH.mjs → chunk-Q752SHI5.mjs} +332 -568
  54. package/dist/chunk-Q752SHI5.mjs.map +1 -0
  55. package/dist/{chunk-6OPXDQ2E.mjs → chunk-RR5L4SRS.mjs} +33 -294
  56. package/dist/chunk-RR5L4SRS.mjs.map +1 -0
  57. package/dist/chunk-TFH5I7JJ.mjs +296 -0
  58. package/dist/chunk-TFH5I7JJ.mjs.map +1 -0
  59. package/dist/chunk-UQYS7FRM.mjs +1003 -0
  60. package/dist/chunk-UQYS7FRM.mjs.map +1 -0
  61. package/dist/chunk-YKB5DQV7.mjs +345 -0
  62. package/dist/chunk-YKB5DQV7.mjs.map +1 -0
  63. package/dist/{chunk-5YADUCA7.mjs → chunk-YRUYGA4A.mjs} +1189 -1217
  64. package/dist/chunk-YRUYGA4A.mjs.map +1 -0
  65. package/dist/chunk-Z3YYH2MB.mjs +5082 -0
  66. package/dist/chunk-Z3YYH2MB.mjs.map +1 -0
  67. package/dist/cluster-binner.d.ts +5 -0
  68. package/dist/cluster-binner.d.ts.map +1 -1
  69. package/dist/components/index.d.ts +3 -0
  70. package/dist/components/index.d.ts.map +1 -1
  71. package/dist/components/light-helpers.d.ts +4 -0
  72. package/dist/components/light-helpers.d.ts.map +1 -1
  73. package/dist/components/light-probe.d.ts +9 -0
  74. package/dist/components/light-probe.d.ts.map +1 -0
  75. package/dist/components/rect-area-light.d.ts +12 -0
  76. package/dist/components/rect-area-light.d.ts.map +1 -0
  77. package/dist/components/scene-instance.d.ts +4 -0
  78. package/dist/components/scene-instance.d.ts.map +1 -1
  79. package/dist/components/spot-light.d.ts +3 -0
  80. package/dist/components/spot-light.d.ts.map +1 -1
  81. package/dist/construct-renderer.mjs +27276 -23232
  82. package/dist/construct-renderer.mjs.map +1 -1
  83. package/dist/device/device-scope.d.ts +2 -0
  84. package/dist/device/device-scope.d.ts.map +1 -1
  85. package/dist/device/gpu-residency.d.ts +32 -5
  86. package/dist/device/gpu-residency.d.ts.map +1 -1
  87. package/dist/device/lifecycle-transaction.d.ts +2 -0
  88. package/dist/device/lifecycle-transaction.d.ts.map +1 -0
  89. package/dist/environment/frame.d.ts +5 -0
  90. package/dist/environment/frame.d.ts.map +1 -1
  91. package/dist/environment/inspection.d.ts +7 -0
  92. package/dist/environment/inspection.d.ts.map +1 -1
  93. package/dist/environment/lifecycle.d.ts +20 -2
  94. package/dist/environment/lifecycle.d.ts.map +1 -1
  95. package/dist/errors/gpu-driven.d.ts +26 -0
  96. package/dist/errors/gpu-driven.d.ts.map +1 -0
  97. package/dist/errors/index.d.ts +1 -0
  98. package/dist/errors/index.d.ts.map +1 -1
  99. package/dist/errors/recover.d.ts +67 -3
  100. package/dist/errors/recover.d.ts.map +1 -1
  101. package/dist/errors/render.d.ts +39 -1
  102. package/dist/errors/render.d.ts.map +1 -1
  103. package/dist/extract/contracts.d.ts +899 -0
  104. package/dist/extract/contracts.d.ts.map +1 -0
  105. package/dist/extract/gpu-driven.d.ts +50 -0
  106. package/dist/extract/gpu-driven.d.ts.map +1 -0
  107. package/dist/extract/visibility.d.ts.map +1 -1
  108. package/dist/features/host.d.ts +2 -0
  109. package/dist/features/host.d.ts.map +1 -1
  110. package/dist/features/prepared-gpu-work.d.ts +3 -0
  111. package/dist/features/prepared-gpu-work.d.ts.map +1 -1
  112. package/dist/features/prepared-graphics-store.d.ts +2 -0
  113. package/dist/features/prepared-graphics-store.d.ts.map +1 -1
  114. package/dist/gpu-driven/batch-topology.d.ts +34 -0
  115. package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
  116. package/dist/gpu-driven/prepared-draw.d.ts +63 -0
  117. package/dist/gpu-driven/prepared-draw.d.ts.map +1 -0
  118. package/dist/gpu-driven/production-raster.d.ts +126 -2
  119. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  120. package/dist/gpu-driven/view-gpu.d.ts +64 -1
  121. package/dist/gpu-driven/view-gpu.d.ts.map +1 -1
  122. package/dist/gpu-scene-schema.d.ts +14 -0
  123. package/dist/gpu-scene-schema.d.ts.map +1 -1
  124. package/dist/gpu-scene.d.ts +9 -1
  125. package/dist/gpu-scene.d.ts.map +1 -1
  126. package/dist/gpu-usage.d.ts +0 -1
  127. package/dist/gpu-usage.d.ts.map +1 -1
  128. package/dist/hdrp-buffers.d.ts +3 -3
  129. package/dist/hdrp-buffers.d.ts.map +1 -1
  130. package/dist/ibl/IblPipelineCache.d.ts +7 -6
  131. package/dist/ibl/IblPipelineCache.d.ts.map +1 -1
  132. package/dist/ibl/skylight-bind-group.d.ts +7 -1
  133. package/dist/ibl/skylight-bind-group.d.ts.map +1 -1
  134. package/dist/index.d.ts +13 -2
  135. package/dist/index.d.ts.map +1 -1
  136. package/dist/index.mjs +90 -5
  137. package/dist/index.mjs.map +1 -1
  138. package/dist/inspection-types.d.ts +163 -1
  139. package/dist/inspection-types.d.ts.map +1 -1
  140. package/dist/internal.d.ts +9 -0
  141. package/dist/internal.d.ts.map +1 -1
  142. package/dist/internal.mjs +23 -2
  143. package/dist/internal.mjs.map +1 -1
  144. package/dist/light-buffer-layout.d.ts +60 -148
  145. package/dist/light-buffer-layout.d.ts.map +1 -1
  146. package/dist/materials.d.ts +34 -10
  147. package/dist/materials.d.ts.map +1 -1
  148. package/dist/mesh-material-bindings.d.ts +56 -0
  149. package/dist/mesh-material-bindings.d.ts.map +1 -1
  150. package/dist/occlusion-proxy.d.ts +4 -0
  151. package/dist/occlusion-proxy.d.ts.map +1 -0
  152. package/dist/pbr-pipeline.d.ts +58 -21
  153. package/dist/pbr-pipeline.d.ts.map +1 -1
  154. package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
  155. package/dist/pipeline/standard-lighting/graph.d.ts.map +1 -1
  156. package/dist/pipeline/standard-lighting/light-frame.d.ts +3 -3
  157. package/dist/pipeline/standard-lighting/light-frame.d.ts.map +1 -1
  158. package/dist/pipeline/standard-lighting/topology.d.ts +1 -1
  159. package/dist/pipeline/standard-lighting/topology.d.ts.map +1 -1
  160. package/dist/pipeline/standard-lighting/transport.d.ts.map +1 -1
  161. package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
  162. package/dist/pipeline-builder.d.ts +2 -0
  163. package/dist/pipeline-builder.d.ts.map +1 -1
  164. package/dist/pipeline-spec.d.ts +1 -1
  165. package/dist/pipeline-spec.d.ts.map +1 -1
  166. package/dist/plugin.d.ts.map +1 -1
  167. package/dist/points-lines/prepare.d.ts +3 -0
  168. package/dist/points-lines/prepare.d.ts.map +1 -1
  169. package/dist/points-lines/standard-owner.d.ts +33 -0
  170. package/dist/points-lines/standard-owner.d.ts.map +1 -0
  171. package/dist/prepare/extended-lighting/ltc-resources.d.ts +31 -0
  172. package/dist/prepare/extended-lighting/ltc-resources.d.ts.map +1 -0
  173. package/dist/prepare/extended-lighting/resources.d.ts +72 -0
  174. package/dist/prepare/extended-lighting/resources.d.ts.map +1 -0
  175. package/dist/prepare/extended-lighting/spot-modifiers.d.ts +42 -0
  176. package/dist/prepare/extended-lighting/spot-modifiers.d.ts.map +1 -0
  177. package/dist/prepare/extended-lighting/state.d.ts +24 -0
  178. package/dist/prepare/extended-lighting/state.d.ts.map +1 -0
  179. package/dist/record/frame-lighting.d.ts +8 -6
  180. package/dist/record/frame-lighting.d.ts.map +1 -1
  181. package/dist/record/frame-snapshot.d.ts +55 -10
  182. package/dist/record/frame-snapshot.d.ts.map +1 -1
  183. package/dist/record/frame-targets.d.ts.map +1 -1
  184. package/dist/record/frame.d.ts +21 -5
  185. package/dist/record/frame.d.ts.map +1 -1
  186. package/dist/record/gpu-timing.d.ts +19 -7
  187. package/dist/record/gpu-timing.d.ts.map +1 -1
  188. package/dist/record/helpers.d.ts +0 -20
  189. package/dist/record/helpers.d.ts.map +1 -1
  190. package/dist/record/main-pass-geometry.d.ts +20 -2
  191. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  192. package/dist/record/main-pass-material.d.ts +10 -18
  193. package/dist/record/main-pass-material.d.ts.map +1 -1
  194. package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
  195. package/dist/record/main-pass.d.ts.map +1 -1
  196. package/dist/record/mesh-ssbo.d.ts +11 -3
  197. package/dist/record/mesh-ssbo.d.ts.map +1 -1
  198. package/dist/record/recovery-pipeline.d.ts +30 -0
  199. package/dist/record/recovery-pipeline.d.ts.map +1 -0
  200. package/dist/record/render-context.d.ts +82 -9
  201. package/dist/record/render-context.d.ts.map +1 -1
  202. package/dist/record/shadow-pass.d.ts.map +1 -1
  203. package/dist/record/typed-frame-graph.d.ts +49 -2
  204. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  205. package/dist/record/view-ubo.d.ts +1 -2
  206. package/dist/record/view-ubo.d.ts.map +1 -1
  207. package/dist/recovery/render-system-candidate.d.ts +65 -0
  208. package/dist/recovery/render-system-candidate.d.ts.map +1 -0
  209. package/dist/reflection/inspection.d.ts +10 -0
  210. package/dist/reflection/inspection.d.ts.map +1 -0
  211. package/dist/reflection/projection.d.ts +49 -0
  212. package/dist/reflection/projection.d.ts.map +1 -1
  213. package/dist/reflection/record-owner.d.ts +60 -0
  214. package/dist/reflection/record-owner.d.ts.map +1 -0
  215. package/dist/render-contract.d.ts +78 -15
  216. package/dist/render-contract.d.ts.map +1 -1
  217. package/dist/render-pipeline.d.ts +7 -0
  218. package/dist/render-pipeline.d.ts.map +1 -1
  219. package/dist/render-system-extract-tail.d.ts +12 -0
  220. package/dist/render-system-extract-tail.d.ts.map +1 -0
  221. package/dist/render-system-extract.d.ts +252 -30
  222. package/dist/render-system-extract.d.ts.map +1 -1
  223. package/dist/render-system.d.ts +140 -5
  224. package/dist/render-system.d.ts.map +1 -1
  225. package/dist/scene/probe-blend-record.d.ts +22 -0
  226. package/dist/scene/probe-blend-record.d.ts.map +1 -0
  227. package/dist/scene/probe-blend.d.ts +142 -0
  228. package/dist/scene/probe-blend.d.ts.map +1 -0
  229. package/dist/scene/render-scene-types.d.ts +0 -6
  230. package/dist/scene/render-scene-types.d.ts.map +1 -1
  231. package/dist/scene/render-scene.d.ts +122 -9
  232. package/dist/scene/render-scene.d.ts.map +1 -1
  233. package/dist/scene/visibility/bounds.d.ts +20 -0
  234. package/dist/scene/visibility/bounds.d.ts.map +1 -0
  235. package/dist/scene/visibility/budget.d.ts +16 -0
  236. package/dist/scene/visibility/budget.d.ts.map +1 -0
  237. package/dist/scene/visibility/facet.d.ts +73 -0
  238. package/dist/scene/visibility/facet.d.ts.map +1 -0
  239. package/dist/scene/visibility/gpu-frame-timing.d.ts +41 -0
  240. package/dist/scene/visibility/gpu-frame-timing.d.ts.map +1 -0
  241. package/dist/scene/visibility/gpu-lod.d.ts +46 -0
  242. package/dist/scene/visibility/gpu-lod.d.ts.map +1 -0
  243. package/dist/scene/visibility/inspection.d.ts +132 -0
  244. package/dist/scene/visibility/inspection.d.ts.map +1 -0
  245. package/dist/scene/visibility/lod-selector.d.ts +25 -0
  246. package/dist/scene/visibility/lod-selector.d.ts.map +1 -0
  247. package/dist/scene/visibility/occlusion-confidence.d.ts +81 -0
  248. package/dist/scene/visibility/occlusion-confidence.d.ts.map +1 -0
  249. package/dist/scene/visibility/occlusion-pass.d.ts +57 -0
  250. package/dist/scene/visibility/occlusion-pass.d.ts.map +1 -0
  251. package/dist/scene/visibility/occlusion-query-pool.d.ts +57 -0
  252. package/dist/scene/visibility/occlusion-query-pool.d.ts.map +1 -0
  253. package/dist/scene/visibility/occlusion-runtime.d.ts +96 -0
  254. package/dist/scene/visibility/occlusion-runtime.d.ts.map +1 -0
  255. package/dist/scene/visibility/types.d.ts +34 -0
  256. package/dist/scene/visibility/types.d.ts.map +1 -0
  257. package/dist/ssr/admission.d.ts +122 -0
  258. package/dist/ssr/admission.d.ts.map +1 -0
  259. package/dist/ssr/composition.d.ts +32 -0
  260. package/dist/ssr/composition.d.ts.map +1 -0
  261. package/dist/ssr/errors.d.ts +39 -0
  262. package/dist/ssr/errors.d.ts.map +1 -0
  263. package/dist/ssr/identity.d.ts +13 -0
  264. package/dist/ssr/identity.d.ts.map +1 -0
  265. package/dist/systems/skin-palette-allocator.d.ts +1 -1
  266. package/dist/temporal/frame-coordinator.d.ts +13 -0
  267. package/dist/temporal/frame-coordinator.d.ts.map +1 -1
  268. package/dist/temporal/frame.d.ts +2 -0
  269. package/dist/temporal/frame.d.ts.map +1 -1
  270. package/dist/temporal/index.mjs +6 -5
  271. package/dist/temporal/taa-history-store.d.ts +15 -1
  272. package/dist/temporal/taa-history-store.d.ts.map +1 -1
  273. package/dist/temporal/transaction.d.ts +5 -0
  274. package/dist/temporal/transaction.d.ts.map +1 -1
  275. package/dist/temporal/view.d.ts +7 -0
  276. package/dist/temporal/view.d.ts.map +1 -1
  277. package/dist/tilemap-chunk-extract-system.d.ts +1 -1
  278. package/dist/typed-render-graph-primitives.d.ts +4 -0
  279. package/dist/typed-render-graph-primitives.d.ts.map +1 -1
  280. package/dist/volume/passes.d.ts +8 -4
  281. package/dist/volume/passes.d.ts.map +1 -1
  282. package/package.json +20 -20
  283. package/src/__tests__/cluster-grid-default-surface.unit.test.ts +5 -0
  284. package/src/__tests__/detached-inspection.integration.test.ts +6 -0
  285. package/src/__tests__/device-feature-admission.unit.test.ts +19 -1
  286. package/src/__tests__/device-loss-fanout.unit.test.ts +107 -0
  287. package/src/__tests__/device-scope-lifecycle.unit.test.ts +21 -0
  288. package/src/__tests__/direct-light-snapshot.unit.test.ts +59 -3
  289. package/src/__tests__/environment-generation.integration.test.ts +39 -0
  290. package/src/__tests__/evidence-identity.ts +13 -0
  291. package/src/__tests__/evidence-identity.unit.test.ts +24 -0
  292. package/src/__tests__/expose-renderer-errors.unit.test.ts +34 -0
  293. package/src/__tests__/extended-lighting-recovery.browser.test.ts +14 -0
  294. package/src/__tests__/extended-lighting-recovery.dawn.test.ts +14 -0
  295. package/src/__tests__/extended-lighting-recovery.integration.test.ts +81 -0
  296. package/src/__tests__/extended-lighting-resources.unit.test.ts +51 -0
  297. package/src/__tests__/extended-lighting-zero-feature.integration.test.ts +52 -0
  298. package/src/__tests__/factory-contract.integration.test.ts +154 -6
  299. package/src/__tests__/fallback-row-stride-surface.unit.test.ts +4 -1
  300. package/src/__tests__/frame-observation.contract.test.ts +15 -0
  301. package/src/__tests__/gpu-driven-batch-topology-pbr.unit.test.ts +146 -0
  302. package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +1 -0
  303. package/src/__tests__/gpu-driven-indirect-raster.dawn.test.ts +1 -0
  304. package/src/__tests__/gpu-driven-production.integration.test.ts +572 -9
  305. package/src/__tests__/gpu-driven-scaling.unit.test.ts +66 -2
  306. package/src/__tests__/gpu-driven-view-gpu-evidence.ts +26 -1
  307. package/src/__tests__/gpu-driven-view-graph.integration.test.ts +89 -2
  308. package/src/__tests__/gpu-driven-view.browser.test.ts +3 -3
  309. package/src/__tests__/gpu-driven-view.dawn.test.ts +3 -3
  310. package/src/__tests__/gpu-driven-view.unit.test.ts +50 -0
  311. package/src/__tests__/gpu-lod-evidence.ts +188 -0
  312. package/src/__tests__/gpu-lod-fallback.integration.test.ts +10 -0
  313. package/src/__tests__/gpu-lod-indirect.integration.test.ts +40 -0
  314. package/src/__tests__/gpu-lod-parity.dawn.test.ts +29 -0
  315. package/src/__tests__/gpu-lod-readiness.integration.test.ts +52 -0
  316. package/src/__tests__/gpu-lod-schema.unit.test.ts +66 -0
  317. package/src/__tests__/gpu-lod-selector.unit.test.ts +28 -0
  318. package/src/__tests__/gpu-lod-silhouette.browser.test.ts +13 -0
  319. package/src/__tests__/gpu-resource-store-stride.unit.test.ts +66 -1
  320. package/src/__tests__/gpu-scene.dawn.test.ts +8 -1
  321. package/src/__tests__/gpu-scene.unit.test.ts +49 -3
  322. package/src/__tests__/ibl/face-uniforms.unit.test.ts +2 -0
  323. package/src/__tests__/ibl-residency-publication.unit.test.ts +113 -0
  324. package/src/__tests__/light-buffer-layout.unit.test.ts +113 -3
  325. package/src/__tests__/light-inspection.unit.test.ts +55 -0
  326. package/src/__tests__/light-resource-error.unit.test.ts +43 -0
  327. package/src/__tests__/lighting-scene-roundtrip.integration.test.ts +109 -0
  328. package/src/__tests__/lod-bounds.unit.test.ts +42 -0
  329. package/src/__tests__/lod-cpu-lane.integration.test.ts +55 -0
  330. package/src/__tests__/lod-cpu-no-change.perf.test.ts +19 -0
  331. package/src/__tests__/lod-gpu-frame-timing.unit.test.ts +49 -0
  332. package/src/__tests__/lod-multi-view.integration.test.ts +63 -0
  333. package/src/__tests__/lod-occlusion-device-loss.integration.test.ts +27 -0
  334. package/src/__tests__/lod-occlusion-final-decision.integration.test.ts +47 -0
  335. package/src/__tests__/lod-occlusion-inspection.integration.test.ts +217 -0
  336. package/src/__tests__/lod-occlusion-inspection.schema.test-d.ts +14 -0
  337. package/src/__tests__/lod-occlusion-inspection.unit.test.ts +72 -0
  338. package/src/__tests__/lod-occlusion-visual-evidence.ts +91 -0
  339. package/src/__tests__/lod-occlusion.browser.test.ts +15 -0
  340. package/src/__tests__/lod-occlusion.dawn.test.ts +20 -0
  341. package/src/__tests__/lod-selector.profile.test.ts +35 -0
  342. package/src/__tests__/lod-selector.unit.test.ts +68 -0
  343. package/src/__tests__/lod-visibility-identity.unit.test.ts +198 -0
  344. package/src/__tests__/lod-visibility-lifecycle.integration.test.ts +132 -0
  345. package/src/__tests__/ltc-resources.unit.test.ts +32 -0
  346. package/src/__tests__/material-contract-inventory.unit.test.ts +41 -9
  347. package/src/__tests__/material-cooked-projection.unit.test.ts +19 -1
  348. package/src/__tests__/material-static-dynamic-route.unit.test.ts +29 -0
  349. package/src/__tests__/material-stride-surface.unit.test.ts +7 -3
  350. package/src/__tests__/materials-standard-contract.unit.test.ts +104 -0
  351. package/src/__tests__/mesh-buffer-usage-surface.unit.test.ts +1 -1
  352. package/src/__tests__/occlusion-confidence.unit.test.ts +168 -0
  353. package/src/__tests__/occlusion-invalidation.integration.test.ts +37 -0
  354. package/src/__tests__/occlusion-pass-routing.unit.test.ts +16 -0
  355. package/src/__tests__/occlusion-pass.dawn.test.ts +20 -0
  356. package/src/__tests__/occlusion-pass.integration.test.ts +43 -0
  357. package/src/__tests__/occlusion-pass.rhi-null.test.ts +22 -0
  358. package/src/__tests__/occlusion-query-pool.rhi-null.test.ts +21 -0
  359. package/src/__tests__/occlusion-query-pool.unit.test.ts +95 -0
  360. package/src/__tests__/occlusion-query-ticket.integration.test.ts +47 -0
  361. package/src/__tests__/occlusion-query.dawn.test.ts +16 -0
  362. package/src/__tests__/occlusion-query.evidence.ts +178 -0
  363. package/src/__tests__/occlusion-query.rhi-null.test.ts +31 -0
  364. package/src/__tests__/occlusion-runtime.unit.test.ts +120 -0
  365. package/src/__tests__/physical-clearcoat.integration.test.ts +89 -0
  366. package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +196 -0
  367. package/src/__tests__/prepared-graphics-pipeline-warmup.unit.test.ts +1 -1
  368. package/src/__tests__/probe-bindings.unit.test.ts +200 -0
  369. package/src/__tests__/probe-blend-numeric.unit.test.ts +67 -0
  370. package/src/__tests__/probe-blend-oracle.unit.test.ts +136 -0
  371. package/src/__tests__/probe-capacity.integration.test.ts +97 -0
  372. package/src/__tests__/probe-recovery.integration.test.ts +104 -0
  373. package/src/__tests__/probe-scene-dirty.integration.test.ts +211 -0
  374. package/src/__tests__/profiler-phase-catalog.test.ts +4 -1
  375. package/src/__tests__/profiler-phase-token.test.ts +1 -0
  376. package/src/__tests__/recover-error-policy-owner.unit.test.ts +6 -3
  377. package/src/__tests__/recovery-candidate-prepare.contract.test.ts +140 -0
  378. package/src/__tests__/rect-area-boundary.unit.test.ts +24 -0
  379. package/src/__tests__/reflection-fallback-graph.unit.test.ts +67 -0
  380. package/src/__tests__/reflection-fallback-inspection.unit.test.ts +77 -0
  381. package/src/__tests__/reflection-fallback-lifecycle.integration.test.ts +21 -0
  382. package/src/__tests__/reflection-fallback-projection.unit.test.ts +90 -0
  383. package/src/__tests__/reflection-fallback-source.unit.test.ts +32 -0
  384. package/src/__tests__/reflection-probe-owner-placement.unit.test.ts +39 -0
  385. package/src/__tests__/reflection-probe-record-owner.characterization.unit.test.ts +120 -0
  386. package/src/__tests__/render-error-code-owner.test-d.ts +1 -0
  387. package/src/__tests__/render-feature-docs.unit.test.ts +12 -7
  388. package/src/__tests__/render-graph-fallback.integration.test.ts +24 -0
  389. package/src/__tests__/render-perf/extended-lighting.bench.test.ts +66 -0
  390. package/src/__tests__/render-scene-projection.unit.test.ts +447 -18
  391. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +160 -17
  392. package/src/__tests__/renderer-frame-generation-fence.integration.test.ts +47 -0
  393. package/src/__tests__/renderer-generation-assembly.unit.test.ts +77 -0
  394. package/src/__tests__/renderer-generation-failure.unit.test.ts +108 -0
  395. package/src/__tests__/renderer-producer-root-matrix.integration.test.ts +33 -0
  396. package/src/__tests__/renderer-recovery-capability-matrix.unit.test.ts +59 -0
  397. package/src/__tests__/renderer-recovery-contract.unit.test.ts +73 -0
  398. package/src/__tests__/renderer-recovery-lifecycle.unit.test.ts +216 -0
  399. package/src/__tests__/renderer-target-history-recovery.integration.test.ts +80 -0
  400. package/src/__tests__/shader-manifest-fixture.ts +13 -16
  401. package/src/__tests__/shader-stage-surface.unit.test.ts +1 -1
  402. package/src/__tests__/skin-motion-regression.unit.test.ts +22 -0
  403. package/src/__tests__/skinned-shadow-caster.test.ts +2 -2
  404. package/src/__tests__/spot-modifier-projection.unit.test.ts +70 -0
  405. package/src/__tests__/ssr-admission-matrix.integration.test.ts +206 -0
  406. package/src/__tests__/ssr-composition.unit.test.ts +46 -0
  407. package/src/__tests__/ssr-fallback-zero-work.bench.test.ts +94 -0
  408. package/src/__tests__/ssr-recovery-errors.unit.test.ts +58 -0
  409. package/src/__tests__/standard-cluster-variant-resolution.unit.test.ts +67 -0
  410. package/src/__tests__/standard-clustered-pipeline.unit.test.ts +7 -0
  411. package/src/__tests__/standard-layer-projection.unit.test.ts +23 -0
  412. package/src/__tests__/standard-lighting-inspection.integration.test.ts +1 -1
  413. package/src/__tests__/standard-lighting-write.unit.test.ts +66 -2
  414. package/src/__tests__/temporal-fallback-generation.integration.test.ts +16 -0
  415. package/src/__tests__/temporal-view.unit.test.ts +3 -3
  416. package/src/__tests__/transmission-prewarm.unit.test.ts +111 -32
  417. package/src/__tests__/visibility-budget.integration.test.ts +140 -0
  418. package/src/__tests__/visibility-budget.unit.test.ts +70 -0
  419. package/src/__tests__/visibility-extract.integration.test.ts +11 -6
  420. package/src/__tests__/volumetric-fog-point-spot-accumulation.integration.test.ts +6 -3
  421. package/src/__tests__/volumetric-fog-stage-readback.dawn.test.ts +31 -28
  422. package/src/__tests__/volumetric-fog-topology.unit.test.ts +3 -1
  423. package/src/assembly/backend-contract.ts +13 -0
  424. package/src/assembly/bundler-contract.ts +9 -0
  425. package/src/assembly/device-feature-admission.ts +26 -9
  426. package/src/assembly/extended-lighting-fallback.ts +216 -0
  427. package/src/assembly/factory.ts +81 -9634
  428. package/src/assembly/host-contract.ts +10 -0
  429. package/src/assembly/material/assembly.ts +18 -2
  430. package/src/assembly/material/standard-layer-projection.ts +16 -0
  431. package/src/assembly/material-shader-policy.ts +854 -0
  432. package/src/assembly/material-shader-prewarm.ts +88 -0
  433. package/src/assembly/mesh-ssbo-grow.ts +270 -0
  434. package/src/assembly/recovery/device-loss-fanout.ts +89 -0
  435. package/src/assembly/recovery/generation.ts +141 -0
  436. package/src/assembly/recovery/recovery-attempt.ts +800 -0
  437. package/src/assembly/recovery/renderer-recover.ts +906 -0
  438. package/src/assembly/renderer-facade.ts +342 -0
  439. package/src/assembly/renderer-frame-transaction.ts +45 -0
  440. package/src/assembly/renderer-helpers.ts +99 -0
  441. package/src/assembly/renderer-lifecycle.ts +159 -0
  442. package/src/assembly/shader-prewarm-policy.ts +157 -0
  443. package/src/assembly/webgpu-ready-contract.ts +14 -0
  444. package/src/assembly/webgpu-ready.ts +4079 -0
  445. package/src/assembly/webgpu-renderer.ts +4095 -0
  446. package/src/cluster-binner.ts +31 -0
  447. package/src/components/__tests__/lighting-authoring.unit.test.ts +39 -0
  448. package/src/components/__tests__/rect-area-light.unit.test.ts +33 -0
  449. package/src/components/camera.ts +1 -1
  450. package/src/components/index.ts +3 -0
  451. package/src/components/light-helpers.ts +52 -1
  452. package/src/components/light-probe.ts +13 -0
  453. package/src/components/rect-area-light.ts +15 -0
  454. package/src/components/scene-instance.ts +6 -0
  455. package/src/components/spot-light.ts +3 -0
  456. package/src/debug-draw-glue.ts +1 -1
  457. package/src/device/__tests__/extended-lighting-recovery.unit.test.ts +24 -0
  458. package/src/device/device-scope.ts +35 -6
  459. package/src/device/gpu-residency.ts +334 -35
  460. package/src/device/lifecycle-transaction.ts +12 -0
  461. package/src/environment/frame.ts +6 -0
  462. package/src/environment/inspection.ts +10 -0
  463. package/src/environment/lifecycle.ts +122 -9
  464. package/src/errors/gpu-driven.ts +92 -0
  465. package/src/errors/index.ts +1 -0
  466. package/src/errors/recover.ts +108 -5
  467. package/src/errors/render.ts +72 -1
  468. package/src/extract/camera.ts +3 -3
  469. package/src/extract/contracts.ts +974 -0
  470. package/src/extract/gpu-driven.ts +108 -0
  471. package/src/extract/visibility.ts +0 -23
  472. package/src/features/host.ts +16 -0
  473. package/src/features/prepared-gpu-work.ts +20 -2
  474. package/src/features/prepared-graphics-store.ts +17 -0
  475. package/src/gpu-driven/batch-topology.ts +206 -28
  476. package/src/gpu-driven/prepared-draw.ts +208 -0
  477. package/src/gpu-driven/production-raster.ts +1046 -39
  478. package/src/gpu-driven/view-gpu.ts +411 -29
  479. package/src/gpu-scene-schema.ts +13 -0
  480. package/src/gpu-scene.ts +137 -67
  481. package/src/gpu-usage.ts +0 -1
  482. package/src/hdrp-buffers.ts +6 -6
  483. package/src/ibl/IblPipelineCache.ts +134 -50
  484. package/src/ibl/skylight-bind-group.ts +26 -0
  485. package/src/index.ts +83 -1
  486. package/src/inspection-types.ts +220 -1
  487. package/src/internal.ts +38 -0
  488. package/src/light-buffer-layout.ts +157 -324
  489. package/src/materials.ts +278 -47
  490. package/src/mesh-material-bindings.ts +59 -0
  491. package/src/occlusion-proxy.ts +64 -0
  492. package/src/pbr-pipeline.ts +290 -60
  493. package/src/pipeline/standard-forward-lane.ts +47 -0
  494. package/src/pipeline/standard-lighting/graph.ts +2 -1
  495. package/src/pipeline/standard-lighting/light-frame.ts +3 -2
  496. package/src/pipeline/standard-lighting/topology.ts +1 -1
  497. package/src/pipeline/standard-lighting/transport.ts +2 -1
  498. package/src/pipeline/standard-pipeline.ts +2 -0
  499. package/src/pipeline-builder.ts +5 -2
  500. package/src/pipeline-spec.ts +12 -1
  501. package/src/plugin.ts +4 -0
  502. package/src/points-lines/__tests__/cache-recovery.unit.test.ts +22 -0
  503. package/src/points-lines/prepare.ts +9 -0
  504. package/src/points-lines/standard-owner.ts +403 -0
  505. package/src/prepare/__tests__/extended-lighting-capability.unit.test.ts +56 -0
  506. package/src/prepare/extended-lighting/ltc-resources.ts +136 -0
  507. package/src/prepare/extended-lighting/resources.ts +389 -0
  508. package/src/prepare/extended-lighting/spot-modifiers.ts +194 -0
  509. package/src/prepare/extended-lighting/state.ts +92 -0
  510. package/src/record/__tests__/frame-targets-recovery.unit.test.ts +60 -0
  511. package/src/record/__tests__/gpu-timing.unit.test.ts +177 -0
  512. package/src/record/__tests__/main-pass-geometry.unit.test.ts +26 -1
  513. package/src/record/__tests__/mesh-ssbo.unit.test.ts +38 -0
  514. package/src/record/__tests__/recovery-pipeline.unit.test.ts +124 -0
  515. package/src/record/__tests__/sprite-material-abi.unit.test.ts +28 -13
  516. package/src/record/__tests__/standard-pbr-ubo-layout.unit.test.ts +47 -4
  517. package/src/record/__tests__/temporal-payload.unit.test.ts +29 -28
  518. package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +21 -2
  519. package/src/record/frame-lighting.ts +252 -53
  520. package/src/record/frame-snapshot.ts +72 -12
  521. package/src/record/frame-targets.ts +11 -1
  522. package/src/record/frame.ts +416 -95
  523. package/src/record/gpu-timing.ts +108 -44
  524. package/src/record/helpers.ts +1 -59
  525. package/src/record/main-pass-geometry.ts +434 -44
  526. package/src/record/main-pass-material.ts +324 -210
  527. package/src/record/main-pass-sprite-draws.ts +58 -92
  528. package/src/record/main-pass.ts +94 -6
  529. package/src/record/mesh-ssbo.ts +79 -46
  530. package/src/record/recovery-pipeline.ts +695 -0
  531. package/src/record/render-context.ts +99 -7
  532. package/src/record/shadow-pass.ts +66 -40
  533. package/src/record/typed-frame-graph.ts +270 -84
  534. package/src/record/view-ubo.ts +17 -27
  535. package/src/recovery/render-system-candidate.ts +839 -0
  536. package/src/reflection/inspection.ts +67 -0
  537. package/src/reflection/projection.ts +196 -1
  538. package/src/reflection/record-owner.ts +1283 -0
  539. package/src/render-contract.ts +82 -12
  540. package/src/render-pipeline.ts +5 -0
  541. package/src/render-system-extract-tail.ts +2983 -0
  542. package/src/render-system-extract.ts +290 -2784
  543. package/src/render-system.ts +1597 -1192
  544. package/src/scene/__tests__/changed-block-summary.candidate.test.ts +36 -0
  545. package/src/scene/probe-blend-record.ts +54 -0
  546. package/src/scene/probe-blend.ts +813 -0
  547. package/src/scene/render-scene-types.ts +0 -7
  548. package/src/scene/render-scene.ts +972 -184
  549. package/src/scene/visibility/bounds.ts +57 -0
  550. package/src/scene/visibility/budget.ts +40 -0
  551. package/src/scene/visibility/facet.ts +400 -0
  552. package/src/scene/visibility/gpu-frame-timing.ts +101 -0
  553. package/src/scene/visibility/gpu-lod.ts +119 -0
  554. package/src/scene/visibility/inspection.ts +313 -0
  555. package/src/scene/visibility/lod-selector.ts +84 -0
  556. package/src/scene/visibility/occlusion-confidence.ts +478 -0
  557. package/src/scene/visibility/occlusion-pass.ts +165 -0
  558. package/src/scene/visibility/occlusion-query-pool.ts +178 -0
  559. package/src/scene/visibility/occlusion-runtime.ts +672 -0
  560. package/src/scene/visibility/types.ts +50 -0
  561. package/src/ssr/admission.ts +451 -0
  562. package/src/ssr/composition.ts +73 -0
  563. package/src/ssr/errors.ts +76 -0
  564. package/src/ssr/identity.ts +12 -0
  565. package/src/systems/skin-palette-allocator.ts +1 -1
  566. package/src/temporal/frame-coordinator.ts +30 -1
  567. package/src/temporal/frame.ts +14 -1
  568. package/src/temporal/taa-history-store.ts +46 -2
  569. package/src/temporal/temporal-view.ts +1 -1
  570. package/src/temporal/transaction.ts +9 -0
  571. package/src/temporal/view.ts +11 -0
  572. package/src/tilemap-chunk-extract-system.ts +5 -5
  573. package/src/transmission/__tests__/standard-transmission.dawn.test.ts +19 -12
  574. package/src/typed-render-graph-primitives.ts +48 -0
  575. package/src/volume/passes.ts +46 -49
  576. package/dist/.tsbuildinfo +0 -1
  577. package/dist/chunk-5YADUCA7.mjs.map +0 -1
  578. package/dist/chunk-6OPXDQ2E.mjs.map +0 -1
  579. package/dist/chunk-BXBQQKFQ.mjs +0 -67
  580. package/dist/chunk-BXBQQKFQ.mjs.map +0 -1
  581. package/dist/chunk-FS2KNUQH.mjs.map +0 -1
  582. package/dist/chunk-PCHBPYML.mjs.map +0 -1
  583. package/dist/chunk-VFPLTKCU.mjs +0 -192
  584. package/dist/chunk-VFPLTKCU.mjs.map +0 -1
@@ -0,0 +1,1283 @@
1
+ import { mat4, vec3 } from '@forgeax/engine-math';
2
+ import type {
3
+ BindGroup,
4
+ BindGroupLayout,
5
+ Buffer,
6
+ RenderPipeline,
7
+ Sampler,
8
+ Texture,
9
+ TextureFormat,
10
+ TextureView,
11
+ } from '@forgeax/engine-rhi';
12
+ import { buildCubeCameraFaceViews } from '../capture/cube-views';
13
+ import {
14
+ createFaceUniformsBuffer,
15
+ createPrefilterUniformsBuffer,
16
+ writeAllFaceUniforms,
17
+ writeAllPrefilterUniforms,
18
+ } from '../device/gpu-residency';
19
+ import {
20
+ GPU_TEXTURE_USAGE_COPY_SRC,
21
+ GPU_TEXTURE_USAGE_RENDER_ATTACHMENT_AND_TEXTURE_BINDING,
22
+ } from '../gpu-texture-usage';
23
+ import {
24
+ GPU_BUFFER_USAGE_COPY_DST,
25
+ GPU_BUFFER_USAGE_UNIFORM,
26
+ GPU_BUFFER_USAGE_VERTEX,
27
+ } from '../gpu-usage';
28
+ import {
29
+ CUBEMAP_FACE_VERTICES,
30
+ createIblPipelines,
31
+ getOrCreateIblCache,
32
+ PREFILTER_MIP_LEVELS,
33
+ PREFILTER_SIZE,
34
+ } from '../ibl/IblPipelineCache';
35
+ import type {
36
+ ReflectionFallbackReadbackReceipt,
37
+ ReflectionFallbackReceipt,
38
+ ReflectionProbeInspection,
39
+ ReflectionProbeSelectionInspection,
40
+ } from '../inspection-types';
41
+ import type { ReflectionProbeRecordState, RenderSystemInternals } from '../record/render-context';
42
+ import type { ReflectionProbeGraphWork } from '../record/typed-frame-graph';
43
+ import { REFLECTION_PROBE_VIEW_SLOT_BASE, VIEW_UNIFORM_SLOT_STRIDE } from '../record/view-ubo';
44
+ import type { CameraSnapshot } from '../render-contract';
45
+ import type { SkylightSnapshot } from '../render-system-extract';
46
+ import {
47
+ advanceProbeFilter,
48
+ commitProbeFilterStep,
49
+ createProbeFilterState,
50
+ type ProbeFilterState,
51
+ } from './filter';
52
+ import { buildReflectionProbeTable } from './gpu-table';
53
+ import { inspectReflectionFallback, type ReflectionFallbackFailureInput } from './inspection';
54
+ import {
55
+ admitReflectionProbe,
56
+ DEFAULT_REFLECTION_PROBE_LIMITS,
57
+ deriveReflectionFallbackProjection,
58
+ type ReflectionFallbackProjection,
59
+ type ReflectionProbeFact,
60
+ type ReflectionProbeSelectionResult,
61
+ reflectionFallbackCoverage,
62
+ reflectionFallbackProjectionSignature,
63
+ reflectionFallbackSourceKey,
64
+ resolveReflectionFallbackSource,
65
+ } from './projection';
66
+
67
+ type ProbeCubeSet = {
68
+ readonly texture: Texture;
69
+ readonly cubeView: TextureView;
70
+ readonly faceViews: readonly TextureView[];
71
+ readonly mipViews: readonly (readonly TextureView[])[];
72
+ readonly depthTexture: Texture;
73
+ readonly depthView: TextureView;
74
+ readonly size: number;
75
+ };
76
+
77
+ type ProbeGpuResource = {
78
+ fact: ReflectionProbeFact;
79
+ index: number;
80
+ filter: ProbeFilterState;
81
+ rawFaceCursor: number;
82
+ readonly raw: ProbeCubeSet;
83
+ active: ProbeCubeSet | undefined;
84
+ candidate: ProbeCubeSet;
85
+ spare: ProbeCubeSet | undefined;
86
+ readonly sampler: Sampler;
87
+ readonly uniformBuffer: Buffer;
88
+ readonly filterGroup1: BindGroup;
89
+ pending?: {
90
+ readonly rawCaptureFace: number | undefined;
91
+ readonly step: ReturnType<typeof advanceProbeFilter>;
92
+ };
93
+ };
94
+
95
+ type ProbeSharedResources = {
96
+ readonly faceUniforms: Buffer;
97
+ readonly prefilterUniforms: Buffer;
98
+ readonly vertexBuffer: Buffer;
99
+ readonly sampler: Sampler;
100
+ readonly filterPipeline: RenderPipeline;
101
+ readonly cubeGroup1Bgl: BindGroupLayout;
102
+ readonly prefilterGroup0Bgl: BindGroupLayout;
103
+ };
104
+
105
+ type PendingFallback = {
106
+ readonly ticket: number;
107
+ readonly frameId: number;
108
+ readonly deviceGeneration: number;
109
+ readonly projection: ReflectionFallbackProjection;
110
+ };
111
+
112
+ const REFLECTION_PROBE_PRODUCER_ID = 'forgeax::render::reflection-probe-record-owner';
113
+ // Probe uniforms share the existing Skylight ABI. Offset the negative
114
+ // sentinel so a valid probe intensity of zero remains distinguishable from
115
+ // the ordinary Skylight payload's zero intensity.
116
+ const PROBE_INTENSITY_SENTINEL_OFFSET = 1;
117
+
118
+ /** Renderer-owned raw capture and incremental PMREM/LKG promotion owner. */
119
+ export class ReflectionProbeRecordOwner {
120
+ private readonly resources = new Map<string, ProbeGpuResource>();
121
+ private shared: ProbeSharedResources | undefined;
122
+ private pipelineWarmup: Promise<void> | undefined;
123
+ private pipelineWarmupFailure: string | undefined;
124
+ private pipelineWarmupAttempts = 0;
125
+ private lastFactCount = 0;
126
+ private lastAcceptedCount = 0;
127
+ private lastScheduledRawFaces = 0;
128
+ private lastScheduledFilteredSteps = 0;
129
+ private lastSelection: ReflectionProbeSelectionInspection = { kind: 'skylight' };
130
+ private outputFormat: TextureFormat;
131
+ private fallbackFormatAvailable: boolean;
132
+ private nextFallbackTicket = 0;
133
+ // Readback completion is asynchronous and can overlap the next frame's
134
+ // prepare. Key tickets by immutable id and retain only the latest ticket
135
+ // per renderable; an older completion is stale by definition once a newer
136
+ // candidate has been staged, but cannot erase the newer ticket.
137
+ private pendingFallback = new Map<number, PendingFallback>();
138
+ private latestFallbackTickets = new Map<string, number>();
139
+ private fallbackRetiredFrames = new Map<string, number>();
140
+ private fallbackCommittedFrames = new Map<string, number>();
141
+ private fallbackGenerations = new Map<
142
+ string,
143
+ { readonly source: number; readonly projection: number }
144
+ >();
145
+ private fallbackSignatures = new Map<string, string>();
146
+ private fallbackReceipts = new Map<string, ReflectionFallbackReceipt>();
147
+ private fallbackReadback: ReflectionFallbackReadbackReceipt | undefined;
148
+ private fallbackReceiptKey: string | undefined;
149
+ private fallbackFailure: ReflectionFallbackFailureInput | undefined;
150
+ private fallbackReceipt: ReflectionFallbackReceipt;
151
+
152
+ constructor(private readonly internals: RenderSystemInternals) {
153
+ this.outputFormat = internals.device.caps.rgba16floatRenderable ? 'rgba16float' : 'rgba8unorm';
154
+ this.fallbackFormatAvailable = internals.device.caps.rgba16floatRenderable === true;
155
+ this.fallbackReceipt = this.createFallbackReceipt(
156
+ this.fallbackFormatAvailable ? 'neutral' : 'unavailable',
157
+ );
158
+ }
159
+
160
+ inspect(): ReflectionProbeInspection {
161
+ let rawFacesCaptured = 0;
162
+ let filteredStepsCompleted = 0;
163
+ const filteredMipLevels = new Set<number>();
164
+ let activeCount = 0;
165
+ for (const resource of this.resources.values()) {
166
+ rawFacesCaptured += resource.rawFaceCursor;
167
+ filteredStepsCompleted += resource.filter.cursor;
168
+ const completedMipCount = Math.min(
169
+ PREFILTER_MIP_LEVELS,
170
+ Math.floor(resource.filter.cursor / resource.filter.faceCount),
171
+ );
172
+ for (let mip = 0; mip < completedMipCount; mip += 1) filteredMipLevels.add(mip);
173
+ if (resource.active !== undefined) activeCount += 1;
174
+ }
175
+ return {
176
+ selection: this.lastSelection,
177
+ factCount: this.lastFactCount,
178
+ acceptedCount: this.lastAcceptedCount,
179
+ activeCount,
180
+ rawFacesCaptured,
181
+ filteredStepsCompleted,
182
+ filteredMipLevels: [...filteredMipLevels].sort((a, b) => a - b),
183
+ scheduledRawFaces: this.lastScheduledRawFaces,
184
+ scheduledFilteredSteps: this.lastScheduledFilteredSteps,
185
+ pipelineReady: this.shared !== undefined,
186
+ pipelineWarmupAttempts: this.pipelineWarmupAttempts,
187
+ ...(this.pipelineWarmupFailure === undefined
188
+ ? {}
189
+ : { pipelineWarmupFailure: this.pipelineWarmupFailure }),
190
+ reflectionFallback: this.fallbackReceipt,
191
+ reflectionFallbacks: [...this.fallbackReceipts.values()],
192
+ reflectionFallbackInspection: inspectReflectionFallback(
193
+ this.fallbackReceipt,
194
+ this.fallbackFailure,
195
+ ),
196
+ ...(this.fallbackReadback === undefined
197
+ ? {}
198
+ : { reflectionFallbackReadback: this.fallbackReadback }),
199
+ };
200
+ }
201
+
202
+ private createFallbackReceipt(
203
+ state: ReflectionFallbackReceipt['state'],
204
+ ): ReflectionFallbackReceipt {
205
+ return Object.freeze({
206
+ rendererId: this.internals.deviceScope.owner,
207
+ producerId: REFLECTION_PROBE_PRODUCER_ID,
208
+ ...(this.internals.ssrIdentity === undefined ? {} : { identity: this.internals.ssrIdentity }),
209
+ source: 'neutral',
210
+ sourceKey: 'neutral',
211
+ sourceGeneration: 0,
212
+ projectionGeneration: 0,
213
+ deviceGeneration: this.internals.deviceScope.generation,
214
+ state,
215
+ candidateVisible: false,
216
+ coverage: 0,
217
+ extent: [1, 1, 1] as const,
218
+ brdfSignature: 'standard-pbr-ibl-v1',
219
+ });
220
+ }
221
+
222
+ private refreshFallbackReceipt(): void {
223
+ const preferred =
224
+ this.fallbackReceiptKey === undefined
225
+ ? undefined
226
+ : this.fallbackReceipts.get(this.fallbackReceiptKey);
227
+ if (preferred !== undefined) {
228
+ this.fallbackReceipt = preferred;
229
+ return;
230
+ }
231
+ let replacementKey: string | undefined;
232
+ for (const key of this.fallbackReceipts.keys()) {
233
+ if (replacementKey === undefined || key < replacementKey) replacementKey = key;
234
+ }
235
+ if (replacementKey !== undefined) {
236
+ this.fallbackReceiptKey = replacementKey;
237
+ this.fallbackReceipt = this.fallbackReceipts.get(replacementKey) as ReflectionFallbackReceipt;
238
+ return;
239
+ }
240
+ this.fallbackReceiptKey = undefined;
241
+ this.fallbackReceipt = this.createFallbackReceipt(
242
+ this.fallbackFormatAvailable ? 'neutral' : 'unavailable',
243
+ );
244
+ }
245
+
246
+ private recordFallbackFailure(failure: ReflectionFallbackFailureInput): void {
247
+ this.fallbackFailure = Object.freeze({
248
+ ...failure,
249
+ ...(failure.detail === undefined ? {} : { detail: Object.freeze({ ...failure.detail }) }),
250
+ });
251
+ }
252
+
253
+ private clearFallbackFailure(): void {
254
+ this.fallbackFailure = undefined;
255
+ }
256
+
257
+ private refreshFallbackCapabilities(frameId: number): void {
258
+ const available = this.internals.device.caps.rgba16floatRenderable === true;
259
+ const previous = this.fallbackFormatAvailable;
260
+ // resetForRecover() disposes device-bound probe resources before the
261
+ // replacement device is observed. Update the ordinary IBL format only
262
+ // when no old resource can still reference the previous device.
263
+ if (
264
+ this.resources.size === 0 &&
265
+ this.shared === undefined &&
266
+ this.pipelineWarmup === undefined
267
+ ) {
268
+ this.outputFormat = available ? 'rgba16float' : 'rgba8unorm';
269
+ }
270
+ this.fallbackFormatAvailable = available;
271
+ if (!available) {
272
+ this.retireFallbackState(frameId);
273
+ this.recordFallbackFailure({
274
+ stage: 'prepare',
275
+ code: 'reflection-fallback-format-unavailable',
276
+ expected: 'rgba16float render attachment capability on the current device generation',
277
+ detail: { deviceGeneration: this.internals.deviceScope.generation },
278
+ });
279
+ } else if (!previous) {
280
+ this.clearFallbackFailure();
281
+ this.refreshFallbackReceipt();
282
+ }
283
+ }
284
+
285
+ private retireFallbackState(frameId: number): void {
286
+ for (const pending of this.pendingFallback.values()) {
287
+ const key = pending.projection.renderableKey;
288
+ if (key !== undefined) this.fallbackRetiredFrames.set(key, frameId);
289
+ }
290
+ for (const key of this.fallbackReceipts.keys()) {
291
+ this.fallbackRetiredFrames.set(key, frameId);
292
+ }
293
+ this.pendingFallback.clear();
294
+ this.latestFallbackTickets.clear();
295
+ this.fallbackReceipts.clear();
296
+ this.fallbackSignatures.clear();
297
+ this.fallbackCommittedFrames.clear();
298
+ this.fallbackGenerations.clear();
299
+ this.fallbackReadback = undefined;
300
+ this.fallbackReceiptKey = undefined;
301
+ this.refreshFallbackReceipt();
302
+ }
303
+
304
+ private ensurePipelineWarmup(): void {
305
+ if (this.shared !== undefined || this.pipelineWarmup !== undefined) return;
306
+ const factory = this.internals.shaderModuleFactory;
307
+ if (factory === undefined) return;
308
+ const attempt = this.pipelineWarmupAttempts + 1;
309
+ this.pipelineWarmupAttempts = attempt;
310
+ this.pipelineWarmup = createIblPipelines(
311
+ this.internals.deviceScope,
312
+ this.internals.device,
313
+ (device, descriptor) => {
314
+ const labeled = {
315
+ ...descriptor,
316
+ label: `${descriptor.label ?? 'ibl'}-reflection-probe-${attempt}`,
317
+ };
318
+ return this.internals.createShaderModule === undefined
319
+ ? Promise.resolve(factory.createShaderModule(labeled))
320
+ : this.internals.createShaderModule(device, labeled);
321
+ },
322
+ this.outputFormat,
323
+ ).then((result) => {
324
+ if (!result.ok) {
325
+ this.pipelineWarmupFailure = `${result.error.code}: ${result.error.hint}`;
326
+ console.warn('[forgeax] ReflectionProbe IBL warmup retry', this.pipelineWarmupFailure);
327
+ this.pipelineWarmup = undefined;
328
+ return;
329
+ }
330
+ this.shared = this.createSharedResources(result.value.prefilterPipeline);
331
+ if (this.shared === undefined) {
332
+ this.pipelineWarmupFailure = 'renderer-owned IBL resources could not be allocated';
333
+ console.warn('[forgeax] ReflectionProbe IBL resource allocation retry');
334
+ this.pipelineWarmup = undefined;
335
+ }
336
+ });
337
+ }
338
+
339
+ prepare(
340
+ facts: readonly ReflectionProbeFact[],
341
+ selections: ReadonlyMap<string, ReflectionProbeSelectionResult>,
342
+ displayCamera: CameraSnapshot | undefined,
343
+ frameId = this.nextFallbackTicket + 1,
344
+ skylight: SkylightSnapshot | undefined = undefined,
345
+ ): ReflectionProbeRecordState {
346
+ this.refreshFallbackCapabilities(frameId);
347
+ this.lastFactCount = facts.length;
348
+ this.lastScheduledRawFaces = 0;
349
+ this.lastScheduledFilteredSteps = 0;
350
+ const firstSelection = selections.values().next().value as
351
+ | ReflectionProbeSelectionResult
352
+ | undefined;
353
+ this.lastSelection =
354
+ firstSelection?.kind === 'probe'
355
+ ? {
356
+ kind: 'probe',
357
+ worldId: firstSelection.worldId,
358
+ entityKey: firstSelection.entityKey,
359
+ }
360
+ : { kind: 'skylight' };
361
+ if (facts.length > 0) this.ensurePipelineWarmup();
362
+ void this.pipelineWarmup;
363
+ const ordered = facts
364
+ .slice()
365
+ .sort((a, b) => a.worldId - b.worldId || a.entityKey - b.entityKey);
366
+ let acceptedCount = 0;
367
+ let acceptedBytes = 0;
368
+ const accepted = new Map<string, ProbeGpuResource>();
369
+ for (const fact of ordered) {
370
+ const admission = admitReflectionProbe(
371
+ fact,
372
+ { acceptedCount, acceptedBytes },
373
+ DEFAULT_REFLECTION_PROBE_LIMITS,
374
+ );
375
+ if (!admission.ok) continue;
376
+ const key = `${fact.worldId}:${fact.entityKey}`;
377
+ const resource = this.resourceFor(key, fact, acceptedCount);
378
+ if (resource === undefined) continue;
379
+ acceptedCount += 1;
380
+ acceptedBytes = admission.acceptedBytes;
381
+ accepted.set(key, resource);
382
+ }
383
+ this.lastAcceptedCount = acceptedCount;
384
+ const fallbackTickets = this.fallbackFormatAvailable
385
+ ? this.stageFallbackProjections(facts, selections, accepted, frameId, skylight)
386
+ : new Map<string, PendingFallback>();
387
+ for (const [key, resource] of this.resources) {
388
+ if (!accepted.has(key)) {
389
+ this.destroyProbeResource(resource);
390
+ this.resources.delete(key);
391
+ } else resource.index = [...accepted.keys()].indexOf(key);
392
+ }
393
+ const rows = [...accepted.values()]
394
+ .map((resource) => {
395
+ const active = resource.active;
396
+ if (active === undefined) return undefined;
397
+ return {
398
+ primitiveKey: `${resource.fact.worldId}:${resource.fact.entityKey}`,
399
+ index: resource.index,
400
+ worldId: resource.fact.worldId,
401
+ entityKey: resource.fact.entityKey,
402
+ center: resource.fact.center,
403
+ halfExtents: resource.fact.halfExtents,
404
+ intensity: resource.fact.intensity,
405
+ generation: resource.filter.activeGeneration,
406
+ filteredView: active.cubeView,
407
+ sampler: resource.sampler,
408
+ uniformBuffer: resource.uniformBuffer,
409
+ };
410
+ })
411
+ .filter((row): row is NonNullable<typeof row> => row !== undefined);
412
+ const table = buildReflectionProbeTable(rows);
413
+ const work: ReflectionProbeGraphWork[] = [];
414
+ for (const resource of accepted.values()) {
415
+ const rawCaptureFace =
416
+ resource.rawFaceCursor < resource.raw.faceViews.length ? resource.rawFaceCursor : undefined;
417
+ const step =
418
+ resource.rawFaceCursor >= resource.raw.faceViews.length
419
+ ? advanceProbeFilter(resource.filter)
420
+ : undefined;
421
+ if (this.shared === undefined) continue;
422
+ if (rawCaptureFace === undefined && step === undefined) continue;
423
+ const filterGroup0 =
424
+ step === undefined
425
+ ? undefined
426
+ : this.internals.device.createBindGroup({
427
+ label: `reflection-probe-${resource.index}-pmrem-${step.mipLevel}-${step.faceIndex}`,
428
+ layout: this.shared.prefilterGroup0Bgl,
429
+ entries: [
430
+ {
431
+ binding: 0,
432
+ resource: {
433
+ kind: 'buffer',
434
+ value: {
435
+ buffer: this.shared.faceUniforms,
436
+ offset: step.faceIndex * 256,
437
+ size: 64,
438
+ },
439
+ },
440
+ },
441
+ {
442
+ binding: 1,
443
+ resource: {
444
+ kind: 'buffer',
445
+ value: {
446
+ buffer: this.shared.prefilterUniforms,
447
+ offset: (step.mipLevel * 6 + step.faceIndex) * 256,
448
+ size: 16,
449
+ },
450
+ },
451
+ },
452
+ ],
453
+ });
454
+ if (filterGroup0 !== undefined && !filterGroup0.ok) continue;
455
+ if (resource.pending !== undefined) continue;
456
+ resource.pending = { rawCaptureFace, step };
457
+ if (rawCaptureFace !== undefined) this.lastScheduledRawFaces += 1;
458
+ if (step !== undefined) this.lastScheduledFilteredSteps += 1;
459
+ const face =
460
+ rawCaptureFace === undefined
461
+ ? undefined
462
+ : buildCubeCameraFaceViews({
463
+ position: resource.fact.center,
464
+ near: 0.1,
465
+ far: 100,
466
+ })[rawCaptureFace];
467
+ const faceCamera =
468
+ face === undefined || displayCamera === undefined
469
+ ? undefined
470
+ : {
471
+ ...displayCamera,
472
+ position: vec3.create(
473
+ resource.fact.center[0] ?? 0,
474
+ resource.fact.center[1] ?? 0,
475
+ resource.fact.center[2] ?? 0,
476
+ ),
477
+ world: mat4.invert(mat4.create(), face.view),
478
+ fov: Math.PI / 2,
479
+ aspect: 1,
480
+ near: face.near,
481
+ far: face.far,
482
+ projection: 'perspective' as const,
483
+ };
484
+ work.push({
485
+ probeIndex: resource.index,
486
+ rawTexture: resource.raw.texture,
487
+ rawCubeView: resource.raw.cubeView,
488
+ rawFaceViews: resource.raw.faceViews,
489
+ rawDepthTexture: resource.raw.depthTexture,
490
+ filteredTexture: resource.candidate.texture,
491
+ filteredCubeView: resource.candidate.cubeView,
492
+ filteredFaceViewsByMip: resource.candidate.mipViews,
493
+ outputFormat: this.outputFormat,
494
+ sampler: resource.sampler,
495
+ filterPipeline: this.shared.filterPipeline,
496
+ filterGroup0: filterGroup0?.value,
497
+ filterGroup1: resource.filterGroup1,
498
+ cubeVertexBuffer: this.shared.vertexBuffer,
499
+ rawCaptureFace,
500
+ rawDepthView: resource.raw.depthView,
501
+ rawSize: resource.raw.size,
502
+ ...(faceCamera === undefined ? {} : { faceCamera }),
503
+ ...(rawCaptureFace === undefined
504
+ ? {}
505
+ : {
506
+ viewBindGroupDynamicOffset:
507
+ (REFLECTION_PROBE_VIEW_SLOT_BASE + resource.index) * VIEW_UNIFORM_SLOT_STRIDE,
508
+ }),
509
+ filteredSize: resource.candidate.size,
510
+ step,
511
+ });
512
+ }
513
+ return {
514
+ table,
515
+ selections,
516
+ graph: { work },
517
+ fallbackProjections: new Map(this.fallbackReceipts),
518
+ // A neutral projection is an exact zero and needs no MRT, pipeline
519
+ // variant, or readback work. Demand is therefore derived from the
520
+ // selected producer source rather than from the presence of a
521
+ // renderable row; scenes without a probe or Skylight stay on the
522
+ // ordinary single-target path.
523
+ fallbackDemand:
524
+ this.fallbackFormatAvailable &&
525
+ [...fallbackTickets.values()].some((ticket) => ticket.projection.source !== 'neutral'),
526
+ fallbackHasNonNeutral:
527
+ this.fallbackFormatAvailable &&
528
+ [...fallbackTickets.values()].some((ticket) => ticket.projection.source !== 'neutral'),
529
+ completeSubmission: (submitted, completed, fallbackOutput, fallbackRequested = true) =>
530
+ this.completeSubmission(
531
+ submitted,
532
+ fallbackTickets,
533
+ completed,
534
+ fallbackOutput,
535
+ fallbackRequested,
536
+ ),
537
+ };
538
+ }
539
+
540
+ private async completeSubmission(
541
+ submitted: boolean,
542
+ fallbackTickets: ReadonlyMap<string, PendingFallback>,
543
+ completed?: Promise<unknown>,
544
+ fallbackOutput?: {
545
+ readonly format: import('@forgeax/engine-rhi').TextureFormat;
546
+ readonly size: { readonly width: number; readonly height: number };
547
+ readonly frameId: number;
548
+ readonly graphGeneration: number;
549
+ readonly textureIdentity: number;
550
+ readonly readback?: Promise<{
551
+ readonly linearHdr: readonly [number, number, number, number];
552
+ readonly hash: string;
553
+ readonly graphGeneration: number;
554
+ readonly textureIdentity: number;
555
+ }>;
556
+ },
557
+ fallbackRequested = true,
558
+ ): Promise<void> {
559
+ if (!fallbackRequested) {
560
+ // The scene has no Standard PBR renderable that can consume the
561
+ // producer's second output, so the graph intentionally omits the MRT.
562
+ // Retire only this frame's candidate tickets; keep any previously
563
+ // committed LKG receipt without manufacturing a readback failure.
564
+ this.discardFallbackTickets(fallbackTickets);
565
+ return;
566
+ }
567
+ let completionFailure: ReflectionFallbackFailureInput | undefined;
568
+ if (completed !== undefined) {
569
+ try {
570
+ await completed;
571
+ } catch {
572
+ submitted = false;
573
+ completionFailure = {
574
+ stage: 'completion',
575
+ code: 'reflection-fallback-completion-failed',
576
+ expected: 'the submitted frame completion promise to resolve',
577
+ };
578
+ }
579
+ }
580
+ await this.completeFallbackSubmission(
581
+ submitted,
582
+ fallbackTickets,
583
+ fallbackOutput,
584
+ completionFailure,
585
+ );
586
+ for (const resource of this.resources.values()) {
587
+ const pending = resource.pending;
588
+ delete resource.pending;
589
+ if (!submitted || pending === undefined) continue;
590
+ if (pending.rawCaptureFace !== undefined) {
591
+ resource.rawFaceCursor = Math.min(
592
+ resource.rawFaceCursor + 1,
593
+ resource.raw.faceViews.length,
594
+ );
595
+ continue;
596
+ }
597
+ if (pending.step === undefined) continue;
598
+ resource.filter = commitProbeFilterStep(resource.filter, true);
599
+ if (resource.filter.cursor >= resource.filter.faceCount * resource.filter.mipCount) {
600
+ const previous = resource.active;
601
+ resource.active = resource.candidate;
602
+ resource.candidate = resource.spare ?? previous ?? resource.candidate;
603
+ resource.spare = undefined;
604
+ }
605
+ }
606
+ }
607
+
608
+ private stageFallbackProjections(
609
+ facts: readonly ReflectionProbeFact[],
610
+ selections: ReadonlyMap<string, ReflectionProbeSelectionResult>,
611
+ accepted: ReadonlyMap<string, ProbeGpuResource>,
612
+ frameId: number,
613
+ skylight: SkylightSnapshot | undefined,
614
+ ): ReadonlyMap<string, PendingFallback> {
615
+ const tickets = new Map<string, PendingFallback>();
616
+ // With no environment source every renderable resolves to the same exact
617
+ // zero. Stage that producer fact once instead of allocating one
618
+ // projection, signature, ticket, and map row per visible object. This is
619
+ // source-absence compression, not a static-scene shortcut: animated
620
+ // transforms and visibility still run normally every frame.
621
+ const stagedSelections: ReadonlyMap<string, ReflectionProbeSelectionResult> =
622
+ facts.length === 0 && skylight === undefined
623
+ ? new Map([['__frame__', { kind: 'skylight' as const }]])
624
+ : selections;
625
+ for (const [renderableKey, selection] of stagedSelections) {
626
+ const probeKey =
627
+ selection.kind === 'probe' ? `${selection.worldId}:${selection.entityKey}` : undefined;
628
+ const resource = probeKey === undefined ? undefined : accepted.get(probeKey);
629
+ const source = resolveReflectionFallbackSource(
630
+ selection,
631
+ resource?.active !== undefined,
632
+ skylight !== undefined,
633
+ );
634
+ const projection = deriveReflectionFallbackProjection({
635
+ renderableKey,
636
+ sourceKey: reflectionFallbackSourceKey(source, selection, skylight),
637
+ source,
638
+ coverage: reflectionFallbackCoverage(source, resource?.fact, selection),
639
+ ...(resource === undefined ? {} : { extent: resource.fact.halfExtents }),
640
+ linearHdr: [0, 0, 0, 0],
641
+ brdfSignature: 'standard-pbr-ibl-v1',
642
+ });
643
+ if (!projection.ok) {
644
+ this.recordFallbackFailure({
645
+ stage: 'prepare',
646
+ code: projection.error.code,
647
+ expected: projection.error.expected,
648
+ detail: { renderableKey, field: projection.error.field },
649
+ });
650
+ continue;
651
+ }
652
+ const signature = reflectionFallbackProjectionSignature(projection.value);
653
+ const previousSignature = this.fallbackSignatures.get(renderableKey);
654
+ if (previousSignature !== undefined && previousSignature !== signature) {
655
+ // A changed source, coverage, extent, or BRDF is not a compatible LKG.
656
+ // Hide the old row until this new candidate has its own matching
657
+ // submit/readback receipt; otherwise SSR could subtract a stale lobe.
658
+ this.fallbackReceipts.delete(renderableKey);
659
+ this.fallbackSignatures.delete(renderableKey);
660
+ this.fallbackCommittedFrames.delete(renderableKey);
661
+ this.fallbackRetiredFrames.set(renderableKey, frameId);
662
+ this.fallbackReadback = undefined;
663
+ if (this.fallbackReceiptKey === renderableKey) this.fallbackReceiptKey = undefined;
664
+ }
665
+ const pending: PendingFallback = {
666
+ ticket: ++this.nextFallbackTicket,
667
+ frameId,
668
+ deviceGeneration: this.internals.deviceScope.generation,
669
+ projection: projection.value,
670
+ };
671
+ const previousTicket = this.latestFallbackTickets.get(renderableKey);
672
+ if (previousTicket !== undefined) this.pendingFallback.delete(previousTicket);
673
+ tickets.set(renderableKey, pending);
674
+ this.pendingFallback.set(pending.ticket, pending);
675
+ this.latestFallbackTickets.set(renderableKey, pending.ticket);
676
+ }
677
+ // Retire rows that disappeared from this frame's main-pass selection.
678
+ // Keeping them would make a detached inspection row outlive its source.
679
+ for (const renderableKey of this.fallbackReceipts.keys()) {
680
+ if (tickets.has(renderableKey)) continue;
681
+ this.fallbackReceipts.delete(renderableKey);
682
+ this.fallbackSignatures.delete(renderableKey);
683
+ this.fallbackCommittedFrames.delete(renderableKey);
684
+ this.fallbackGenerations.delete(renderableKey);
685
+ this.latestFallbackTickets.delete(renderableKey);
686
+ this.fallbackRetiredFrames.set(renderableKey, frameId);
687
+ }
688
+ for (const renderableKey of this.fallbackGenerations.keys()) {
689
+ if (!tickets.has(renderableKey)) this.fallbackGenerations.delete(renderableKey);
690
+ }
691
+ // An in-flight ticket for a row that disappeared is no longer admissible.
692
+ // Remove it eagerly so the map remains bounded; completion still sees the
693
+ // missing ticket and safely rejects it as stale.
694
+ for (const [ticket, pending] of this.pendingFallback) {
695
+ const key = pending.projection.renderableKey;
696
+ const current = key === undefined ? undefined : tickets.get(key);
697
+ if (
698
+ key !== undefined &&
699
+ pending.frameId < frameId &&
700
+ (current === undefined ||
701
+ current.projection.source === 'neutral' ||
702
+ reflectionFallbackProjectionSignature(current.projection) !==
703
+ reflectionFallbackProjectionSignature(pending.projection))
704
+ ) {
705
+ this.pendingFallback.delete(ticket);
706
+ if (current === undefined) this.fallbackRetiredFrames.set(key, frameId);
707
+ }
708
+ }
709
+ if (tickets.size === 0 && facts.length === 0) {
710
+ const neutralKey = '__frame__';
711
+ const projection = deriveReflectionFallbackProjection({
712
+ renderableKey: neutralKey,
713
+ sourceKey: 'neutral',
714
+ source: 'neutral',
715
+ coverage: 0,
716
+ linearHdr: [0, 0, 0, 0],
717
+ brdfSignature: 'standard-pbr-ibl-v1',
718
+ });
719
+ if (projection.ok) {
720
+ const pending: PendingFallback = {
721
+ ticket: ++this.nextFallbackTicket,
722
+ frameId,
723
+ deviceGeneration: this.internals.deviceScope.generation,
724
+ projection: projection.value,
725
+ };
726
+ const previousTicket = this.latestFallbackTickets.get(neutralKey);
727
+ if (previousTicket !== undefined) this.pendingFallback.delete(previousTicket);
728
+ tickets.set(neutralKey, pending);
729
+ this.pendingFallback.set(pending.ticket, pending);
730
+ this.latestFallbackTickets.set(neutralKey, pending.ticket);
731
+ }
732
+ }
733
+ this.selectFallbackReceiptKey(tickets, skylight !== undefined);
734
+ this.refreshFallbackReceipt();
735
+ return tickets;
736
+ }
737
+
738
+ private selectFallbackReceiptKey(
739
+ tickets: ReadonlyMap<string, PendingFallback>,
740
+ skylightAvailable: boolean,
741
+ ): void {
742
+ // When the environment source is absent and the first selected consumer
743
+ // is outside every probe, a neutral row is the only producer-owned zero
744
+ // fallback that can represent that sample. Prefer it instead of retaining
745
+ // the previous Skylight representative across the transition. A local
746
+ // probe selection still wins through the source-key match below.
747
+ if (!skylightAvailable && this.lastSelection.kind !== 'probe') {
748
+ let neutralKey: string | undefined;
749
+ for (const [key, pending] of tickets) {
750
+ if (pending.projection.source !== 'neutral') continue;
751
+ if (neutralKey === undefined || key < neutralKey) neutralKey = key;
752
+ }
753
+ if (neutralKey !== undefined) {
754
+ this.fallbackReceiptKey = neutralKey;
755
+ return;
756
+ }
757
+ }
758
+ const selectedSourceKey =
759
+ this.lastSelection.kind === 'probe'
760
+ ? `probe:${this.lastSelection.worldId}:${this.lastSelection.entityKey}`
761
+ : undefined;
762
+ let sourceMatch: string | undefined;
763
+ for (const [key, pending] of tickets) {
764
+ const matches =
765
+ pending.projection.sourceKey === selectedSourceKey ||
766
+ (selectedSourceKey === undefined && pending.projection.source === 'skylight');
767
+ if (matches && (sourceMatch === undefined || key < sourceMatch)) sourceMatch = key;
768
+ }
769
+ if (sourceMatch !== undefined) {
770
+ this.fallbackReceiptKey = sourceMatch;
771
+ return;
772
+ }
773
+ if (this.fallbackReceiptKey !== undefined && tickets.has(this.fallbackReceiptKey)) return;
774
+ let firstKey: string | undefined;
775
+ for (const key of tickets.keys()) {
776
+ if (firstKey === undefined || key < firstKey) firstKey = key;
777
+ }
778
+ this.fallbackReceiptKey = firstKey;
779
+ }
780
+
781
+ private discardFallbackTickets(fallbackTickets: ReadonlyMap<string, PendingFallback>): void {
782
+ for (const pending of fallbackTickets.values()) {
783
+ this.pendingFallback.delete(pending.ticket);
784
+ }
785
+ }
786
+
787
+ /** Commit exact-zero neutral rows even when a non-neutral MRT readback fails. */
788
+ private commitNeutralFallbacks(fallbackTickets: ReadonlyMap<string, PendingFallback>): number {
789
+ let committed = 0;
790
+ for (const [renderableKey, pending] of fallbackTickets) {
791
+ if (
792
+ pending.projection.source === 'neutral' &&
793
+ this.latestFallbackTickets.get(renderableKey) === pending.ticket &&
794
+ pending.deviceGeneration === this.internals.deviceScope.generation
795
+ ) {
796
+ this.commitFallbackProjection(renderableKey, pending, 'neutral');
797
+ committed += 1;
798
+ }
799
+ }
800
+ return committed;
801
+ }
802
+
803
+ private commitFallbackProjection(
804
+ renderableKey: string,
805
+ pending: PendingFallback,
806
+ state: ReflectionFallbackReceipt['state'],
807
+ ): void {
808
+ const signature = reflectionFallbackProjectionSignature(pending.projection);
809
+ const previousSignature = this.fallbackSignatures.get(renderableKey);
810
+ const changed = signature !== previousSignature;
811
+ const previous = this.fallbackReceipts.get(renderableKey);
812
+ const generations = this.fallbackGenerations.get(renderableKey) ?? {
813
+ source: previous?.sourceGeneration ?? 0,
814
+ projection: previous?.projectionGeneration ?? 0,
815
+ };
816
+ const receipt = Object.freeze({
817
+ ...pending.projection,
818
+ rendererId: this.internals.deviceScope.owner,
819
+ producerId: REFLECTION_PROBE_PRODUCER_ID,
820
+ ...(this.internals.ssrIdentity === undefined ? {} : { identity: this.internals.ssrIdentity }),
821
+ frameId: pending.frameId,
822
+ sourceGeneration: generations.source + (changed ? 1 : 0),
823
+ projectionGeneration: generations.projection + (changed ? 1 : 0),
824
+ deviceGeneration: pending.deviceGeneration,
825
+ state,
826
+ candidateVisible: false,
827
+ }) satisfies ReflectionFallbackReceipt;
828
+ this.fallbackGenerations.set(renderableKey, {
829
+ source: receipt.sourceGeneration,
830
+ projection: receipt.projectionGeneration,
831
+ });
832
+ this.fallbackSignatures.set(renderableKey, signature);
833
+ this.fallbackReceipts.set(renderableKey, receipt);
834
+ this.fallbackCommittedFrames.set(renderableKey, pending.frameId);
835
+ if (this.fallbackReceiptKey === undefined) this.fallbackReceiptKey = renderableKey;
836
+ this.refreshFallbackReceipt();
837
+ }
838
+
839
+ /** Retain only a source-identical committed row after candidate failure. */
840
+ private preserveFallbackLkg(fallbackTickets: ReadonlyMap<string, PendingFallback>): void {
841
+ for (const [renderableKey, pending] of fallbackTickets) {
842
+ if (this.latestFallbackTickets.get(renderableKey) !== pending.ticket) continue;
843
+ const previous = this.fallbackReceipts.get(renderableKey);
844
+ const signature = reflectionFallbackProjectionSignature(pending.projection);
845
+ const compatible =
846
+ previous !== undefined && this.fallbackSignatures.get(renderableKey) === signature;
847
+ if (compatible) {
848
+ if (previous.state !== 'neutral' && pending.projection.source !== 'neutral') {
849
+ this.fallbackReceipts.set(
850
+ renderableKey,
851
+ Object.freeze({ ...previous, state: 'lkg' }) satisfies ReflectionFallbackReceipt,
852
+ );
853
+ }
854
+ continue;
855
+ }
856
+ // A source or coverage change cannot use the old row as LKG. The stage
857
+ // normally retires it eagerly; this second guard covers failed first
858
+ // submissions and keeps stale rows fail-closed.
859
+ this.fallbackReceipts.delete(renderableKey);
860
+ this.fallbackSignatures.delete(renderableKey);
861
+ this.fallbackCommittedFrames.delete(renderableKey);
862
+ this.fallbackRetiredFrames.set(renderableKey, pending.frameId);
863
+ }
864
+ this.refreshFallbackReceipt();
865
+ }
866
+
867
+ private async completeFallbackSubmission(
868
+ submitted: boolean,
869
+ fallbackTickets: ReadonlyMap<string, PendingFallback>,
870
+ fallbackOutput?: {
871
+ readonly format: import('@forgeax/engine-rhi').TextureFormat;
872
+ readonly size: { readonly width: number; readonly height: number };
873
+ readonly frameId: number;
874
+ readonly graphGeneration: number;
875
+ readonly textureIdentity: number;
876
+ readonly readback?: Promise<{
877
+ readonly linearHdr: readonly [number, number, number, number];
878
+ readonly hash: string;
879
+ readonly graphGeneration: number;
880
+ readonly textureIdentity: number;
881
+ }>;
882
+ },
883
+ submissionFailure?: ReflectionFallbackFailureInput,
884
+ ): Promise<void> {
885
+ // A late completion from the previous device/capability generation must
886
+ // not resurrect a projection after the current device has failed the
887
+ // fallback attachment probe. The prepare phase already published the
888
+ // structured capability failure and retired all current rows.
889
+ if (!this.fallbackFormatAvailable) {
890
+ this.discardFallbackTickets(fallbackTickets);
891
+ return;
892
+ }
893
+ // A graph attachment descriptor is not a producer receipt. Until the
894
+ // completed frame has supplied mapped bytes and their digest, discard the
895
+ // pending ticket and keep the last committed row (if any) as LKG. This
896
+ // prevents the projection's zero quartet from becoming false active
897
+ // evidence when the copy/readback owner has not run.
898
+ if (!submitted || fallbackTickets.size === 0) {
899
+ if (!submitted) {
900
+ this.recordFallbackFailure(
901
+ submissionFailure ?? {
902
+ stage: 'submit',
903
+ code: 'reflection-fallback-submit-failed',
904
+ expected: 'the frame containing the fallback projection to submit successfully',
905
+ },
906
+ );
907
+ this.preserveFallbackLkg(fallbackTickets);
908
+ }
909
+ this.discardFallbackTickets(fallbackTickets);
910
+ return;
911
+ }
912
+ if (fallbackOutput?.readback === undefined) {
913
+ // Neutral is an exact producer-owned zero and does not need an MRT
914
+ // sample. A successfully submitted frame may commit those rows even
915
+ // when the non-neutral rows have no mapped readback and must stay LKG.
916
+ const committedNeutral = this.commitNeutralFallbacks(fallbackTickets);
917
+ const nonNeutral = new Map(
918
+ [...fallbackTickets].filter(([, pending]) => pending.projection.source !== 'neutral'),
919
+ );
920
+ if (nonNeutral.size === 0) this.fallbackReadback = undefined;
921
+ if (nonNeutral.size === 0 && committedNeutral === fallbackTickets.size) {
922
+ this.clearFallbackFailure();
923
+ }
924
+ if (nonNeutral.size > 0) {
925
+ this.recordFallbackFailure({
926
+ stage: 'completion',
927
+ code: 'reflection-fallback-readback-missing',
928
+ expected: 'a mapped fallback MRT readback for every non-neutral source',
929
+ detail: { frameId: fallbackTickets.values().next().value?.frameId },
930
+ });
931
+ }
932
+ this.preserveFallbackLkg(nonNeutral);
933
+ this.discardFallbackTickets(fallbackTickets);
934
+ return;
935
+ }
936
+ let readback: {
937
+ readonly linearHdr: readonly [number, number, number, number];
938
+ readonly hash: string;
939
+ readonly graphGeneration: number;
940
+ readonly textureIdentity: number;
941
+ };
942
+ try {
943
+ readback = await fallbackOutput.readback;
944
+ } catch (cause) {
945
+ this.recordFallbackFailure({
946
+ stage: 'completion',
947
+ code: 'reflection-fallback-readback-failed',
948
+ expected: 'the fallback MRT readback to map and resolve',
949
+ detail: { reason: cause instanceof Error ? cause.message : String(cause) },
950
+ });
951
+ this.commitNeutralFallbacks(fallbackTickets);
952
+ this.preserveFallbackLkg(
953
+ new Map(
954
+ [...fallbackTickets].filter(([, pending]) => pending.projection.source !== 'neutral'),
955
+ ),
956
+ );
957
+ this.discardFallbackTickets(fallbackTickets);
958
+ return;
959
+ }
960
+ if (
961
+ readback.graphGeneration !== fallbackOutput.graphGeneration ||
962
+ readback.textureIdentity !== fallbackOutput.textureIdentity
963
+ ) {
964
+ this.recordFallbackFailure({
965
+ stage: 'completion',
966
+ code: 'reflection-fallback-identity-mismatch',
967
+ expected:
968
+ 'mapped readback graph and texture identity to match the submitted fallback target',
969
+ detail: {
970
+ expectedGraphGeneration: fallbackOutput.graphGeneration,
971
+ actualGraphGeneration: readback.graphGeneration,
972
+ expectedTextureIdentity: fallbackOutput.textureIdentity,
973
+ actualTextureIdentity: readback.textureIdentity,
974
+ },
975
+ });
976
+ this.commitNeutralFallbacks(fallbackTickets);
977
+ this.preserveFallbackLkg(
978
+ new Map(
979
+ [...fallbackTickets].filter(([, pending]) => pending.projection.source !== 'neutral'),
980
+ ),
981
+ );
982
+ this.discardFallbackTickets(fallbackTickets);
983
+ return;
984
+ }
985
+ const eligible: Array<[string, PendingFallback]> = [];
986
+ let staleCount = 0;
987
+ for (const [renderableKey, pending] of fallbackTickets) {
988
+ const staged = this.pendingFallback.get(pending.ticket);
989
+ const latest = this.latestFallbackTickets.get(renderableKey);
990
+ const retiredAt = this.fallbackRetiredFrames.get(renderableKey);
991
+ const committedFrame = this.fallbackCommittedFrames.get(renderableKey);
992
+ if (
993
+ staged !== pending ||
994
+ latest !== pending.ticket ||
995
+ pending.frameId !== fallbackOutput.frameId ||
996
+ pending.deviceGeneration !== this.internals.deviceScope.generation ||
997
+ (retiredAt !== undefined && pending.frameId < retiredAt) ||
998
+ (committedFrame !== undefined && pending.frameId <= committedFrame)
999
+ ) {
1000
+ this.pendingFallback.delete(pending.ticket);
1001
+ staleCount += 1;
1002
+ continue;
1003
+ }
1004
+ eligible.push([renderableKey, pending]);
1005
+ }
1006
+ if (eligible.length === 0) {
1007
+ if (staleCount > 0) {
1008
+ this.recordFallbackFailure({
1009
+ stage: 'completion',
1010
+ code: 'reflection-fallback-stale-ticket',
1011
+ expected:
1012
+ 'the fallback completion ticket to match the latest frame and device generation',
1013
+ detail: { staleCount },
1014
+ });
1015
+ }
1016
+ this.refreshFallbackReceipt();
1017
+ return;
1018
+ }
1019
+ const hasNonNeutral = eligible.some(([, pending]) => pending.projection.source !== 'neutral');
1020
+ const previousReadback = this.fallbackReadback;
1021
+ if (
1022
+ hasNonNeutral &&
1023
+ (previousReadback === undefined || fallbackOutput.frameId >= previousReadback.frameId)
1024
+ ) {
1025
+ this.fallbackReadback = Object.freeze({
1026
+ rendererId: this.internals.deviceScope.owner,
1027
+ producerId: REFLECTION_PROBE_PRODUCER_ID,
1028
+ frameId: fallbackOutput.frameId,
1029
+ deviceGeneration: this.internals.deviceScope.generation,
1030
+ graphGeneration: readback.graphGeneration,
1031
+ textureIdentity: readback.textureIdentity,
1032
+ format: fallbackOutput.format,
1033
+ size: Object.freeze({ ...fallbackOutput.size }),
1034
+ linearHdr: Object.freeze([...readback.linearHdr]) as readonly [
1035
+ number,
1036
+ number,
1037
+ number,
1038
+ number,
1039
+ ],
1040
+ readbackHash: readback.hash,
1041
+ readbackStatus: 'complete',
1042
+ });
1043
+ }
1044
+ for (const [renderableKey, pending] of eligible) {
1045
+ this.pendingFallback.delete(pending.ticket);
1046
+ this.commitFallbackProjection(
1047
+ renderableKey,
1048
+ pending,
1049
+ pending.projection.source === 'neutral' ? 'neutral' : 'active',
1050
+ );
1051
+ }
1052
+ this.clearFallbackFailure();
1053
+ this.refreshFallbackReceipt();
1054
+ }
1055
+
1056
+ dispose(): void {
1057
+ for (const resource of this.resources.values()) {
1058
+ this.destroyProbeResource(resource);
1059
+ }
1060
+ this.resources.clear();
1061
+ this.pendingFallback.clear();
1062
+ this.latestFallbackTickets.clear();
1063
+ this.fallbackRetiredFrames.clear();
1064
+ this.fallbackCommittedFrames.clear();
1065
+ this.fallbackGenerations.clear();
1066
+ this.fallbackSignatures.clear();
1067
+ this.fallbackReceipts.clear();
1068
+ this.fallbackReadback = undefined;
1069
+ this.fallbackReceiptKey = undefined;
1070
+ this.fallbackFailure = undefined;
1071
+ this.fallbackFormatAvailable = this.internals.device.caps.rgba16floatRenderable === true;
1072
+ this.fallbackReceipt = this.createFallbackReceipt('unavailable');
1073
+ if (this.shared !== undefined) {
1074
+ this.internals.device.destroyBuffer(this.shared.faceUniforms);
1075
+ this.internals.device.destroyBuffer(this.shared.prefilterUniforms);
1076
+ this.internals.device.destroyBuffer(this.shared.vertexBuffer);
1077
+ }
1078
+ this.shared = undefined;
1079
+ this.pipelineWarmup = undefined;
1080
+ }
1081
+
1082
+ private destroyProbeResource(resource: ProbeGpuResource): void {
1083
+ const sets = new Set<ProbeCubeSet>([resource.raw, resource.candidate]);
1084
+ if (resource.active !== undefined) sets.add(resource.active);
1085
+ if (resource.spare !== undefined) sets.add(resource.spare);
1086
+ for (const set of sets) this.destroyCubeSet(set);
1087
+ this.internals.device.destroyBuffer(resource.uniformBuffer);
1088
+ }
1089
+
1090
+ private destroyCubeSet(set: ProbeCubeSet): void {
1091
+ this.internals.device.destroyTexture(set.texture);
1092
+ this.internals.device.destroyTexture(set.depthTexture);
1093
+ }
1094
+
1095
+ private resourceFor(
1096
+ key: string,
1097
+ fact: ReflectionProbeFact,
1098
+ index: number,
1099
+ ): ProbeGpuResource | undefined {
1100
+ const existing = this.resources.get(key);
1101
+ if (existing !== undefined) {
1102
+ if (existing.fact.revision !== fact.revision) {
1103
+ existing.fact = fact;
1104
+ existing.filter = createProbeFilterState({
1105
+ probeIndex: index,
1106
+ mipCount: PREFILTER_MIP_LEVELS,
1107
+ activeGeneration: existing.filter.activeGeneration,
1108
+ });
1109
+ existing.rawFaceCursor = 0;
1110
+ } else {
1111
+ existing.fact = fact;
1112
+ existing.index = index;
1113
+ }
1114
+ return existing;
1115
+ }
1116
+ if (this.shared === undefined) return undefined;
1117
+ const raw = this.createCubeSet(`reflection-probe-${index}-raw`, fact.resolution);
1118
+ const candidate = this.createCubeSet(`reflection-probe-${index}-candidate`, fact.resolution);
1119
+ const spare = this.createCubeSet(`reflection-probe-${index}-spare`, fact.resolution);
1120
+ if (raw === undefined || candidate === undefined || spare === undefined) return undefined;
1121
+ const uniformBuffer = this.internals.device.createBuffer({
1122
+ label: `reflection-probe-${index}-uniforms`,
1123
+ size: 32,
1124
+ usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
1125
+ mappedAtCreation: false,
1126
+ });
1127
+ if (!uniformBuffer.ok) return undefined;
1128
+ const payload = new Float32Array([
1129
+ -(Math.max(0, fact.intensity) + PROBE_INTENSITY_SENTINEL_OFFSET),
1130
+ fact.center[0],
1131
+ fact.center[1],
1132
+ fact.center[2],
1133
+ fact.halfExtents[0],
1134
+ fact.halfExtents[1],
1135
+ fact.halfExtents[2],
1136
+ 0,
1137
+ ]);
1138
+ const written = this.internals.device.queue.writeBuffer(uniformBuffer.value, 0, payload);
1139
+ if (!written.ok) return undefined;
1140
+ const filterGroup1 = this.internals.device.createBindGroup({
1141
+ label: `reflection-probe-${index}-filter-input`,
1142
+ layout: this.shared.cubeGroup1Bgl,
1143
+ entries: [
1144
+ { binding: 0, resource: { kind: 'textureView', value: raw.cubeView } },
1145
+ { binding: 1, resource: { kind: 'sampler', value: this.shared.sampler } },
1146
+ ],
1147
+ });
1148
+ if (!filterGroup1.ok) return undefined;
1149
+ const resource: ProbeGpuResource = {
1150
+ fact,
1151
+ index,
1152
+ filter: createProbeFilterState({ probeIndex: index, mipCount: PREFILTER_MIP_LEVELS }),
1153
+ rawFaceCursor: 0,
1154
+ raw,
1155
+ active: undefined,
1156
+ candidate,
1157
+ spare,
1158
+ sampler: this.shared.sampler,
1159
+ uniformBuffer: uniformBuffer.value,
1160
+ filterGroup1: filterGroup1.value,
1161
+ };
1162
+ this.resources.set(key, resource);
1163
+ return resource;
1164
+ }
1165
+
1166
+ private createSharedResources(filterPipeline: RenderPipeline): ProbeSharedResources | undefined {
1167
+ const faceUniforms = createFaceUniformsBuffer(this.internals.device);
1168
+ const prefilterUniforms = createPrefilterUniformsBuffer(this.internals.device);
1169
+ if (!faceUniforms.ok || !prefilterUniforms.ok) return undefined;
1170
+ if (!writeAllFaceUniforms(this.internals.device, faceUniforms.value).ok) return undefined;
1171
+ if (!writeAllPrefilterUniforms(this.internals.device, prefilterUniforms.value).ok)
1172
+ return undefined;
1173
+ const vertexBuffer = this.internals.device.createBuffer({
1174
+ label: 'reflection-probe-capture-cube-vertices',
1175
+ size: CUBEMAP_FACE_VERTICES.byteLength,
1176
+ usage: GPU_BUFFER_USAGE_VERTEX | GPU_BUFFER_USAGE_COPY_DST,
1177
+ mappedAtCreation: false,
1178
+ });
1179
+ if (!vertexBuffer.ok) return undefined;
1180
+ const vertexWrite = this.internals.device.queue.writeBuffer(
1181
+ vertexBuffer.value,
1182
+ 0,
1183
+ CUBEMAP_FACE_VERTICES,
1184
+ );
1185
+ if (!vertexWrite.ok) return undefined;
1186
+ const sampler = this.internals.device.createSampler({
1187
+ label: 'reflection-probe-pmrem-sampler',
1188
+ magFilter: 'linear',
1189
+ minFilter: 'linear',
1190
+ mipmapFilter: 'linear',
1191
+ addressModeU: 'clamp-to-edge',
1192
+ addressModeV: 'clamp-to-edge',
1193
+ addressModeW: 'clamp-to-edge',
1194
+ });
1195
+ const cache = getOrCreateIblCache(this.internals.deviceScope);
1196
+ if (
1197
+ !sampler.ok ||
1198
+ cache.cubeGroup1Bgl === undefined ||
1199
+ cache.prefilterGroup0Bgl === undefined
1200
+ ) {
1201
+ return undefined;
1202
+ }
1203
+ return {
1204
+ faceUniforms: faceUniforms.value,
1205
+ prefilterUniforms: prefilterUniforms.value,
1206
+ vertexBuffer: vertexBuffer.value,
1207
+ sampler: sampler.value,
1208
+ filterPipeline,
1209
+ cubeGroup1Bgl: cache.cubeGroup1Bgl,
1210
+ prefilterGroup0Bgl: cache.prefilterGroup0Bgl,
1211
+ };
1212
+ }
1213
+
1214
+ private createCubeSet(label: string, requestedSize: number): ProbeCubeSet | undefined {
1215
+ const size = Math.min(Math.max(1, Math.floor(requestedSize)), PREFILTER_SIZE);
1216
+ const mipCount = Math.min(PREFILTER_MIP_LEVELS, Math.floor(Math.log2(size)) + 1);
1217
+ const texture = this.internals.device.createTexture({
1218
+ label,
1219
+ size: { width: size, height: size, depthOrArrayLayers: 6 },
1220
+ mipLevelCount: mipCount,
1221
+ sampleCount: 1,
1222
+ dimension: '2d',
1223
+ format: this.outputFormat,
1224
+ usage: GPU_TEXTURE_USAGE_RENDER_ATTACHMENT_AND_TEXTURE_BINDING | GPU_TEXTURE_USAGE_COPY_SRC,
1225
+ viewFormats: [],
1226
+ textureBindingViewDimension: undefined,
1227
+ });
1228
+ if (!texture.ok) return undefined;
1229
+ const cubeView = this.internals.device.createTextureView(texture.value, {
1230
+ label: `${label}-cube`,
1231
+ dimension: 'cube',
1232
+ baseMipLevel: 0,
1233
+ mipLevelCount: mipCount,
1234
+ arrayLayerCount: 6,
1235
+ });
1236
+ if (!cubeView.ok) return undefined;
1237
+ const faceViews: TextureView[] = [];
1238
+ const mipViews: TextureView[][] = [];
1239
+ for (let mip = 0; mip < mipCount; mip += 1) {
1240
+ const faces: TextureView[] = [];
1241
+ for (let face = 0; face < 6; face += 1) {
1242
+ const view = this.internals.device.createTextureView(texture.value, {
1243
+ label: `${label}-mip${mip}-face${face}`,
1244
+ dimension: '2d',
1245
+ baseMipLevel: mip,
1246
+ mipLevelCount: 1,
1247
+ baseArrayLayer: face,
1248
+ arrayLayerCount: 1,
1249
+ });
1250
+ if (!view.ok) return undefined;
1251
+ if (mip === 0) faceViews.push(view.value);
1252
+ faces.push(view.value);
1253
+ }
1254
+ mipViews.push(faces);
1255
+ }
1256
+ const depthTexture = this.internals.device.createTexture({
1257
+ label: `${label}-depth`,
1258
+ size: { width: size, height: size, depthOrArrayLayers: 1 },
1259
+ mipLevelCount: 1,
1260
+ sampleCount: 1,
1261
+ dimension: '2d',
1262
+ format: 'depth24plus-stencil8',
1263
+ usage: 0x10,
1264
+ viewFormats: [],
1265
+ textureBindingViewDimension: undefined,
1266
+ });
1267
+ if (!depthTexture.ok) return undefined;
1268
+ const depthView = this.internals.device.createTextureView(depthTexture.value, {
1269
+ label: `${label}-depth-view`,
1270
+ dimension: '2d',
1271
+ });
1272
+ if (!depthView.ok) return undefined;
1273
+ return {
1274
+ texture: texture.value,
1275
+ cubeView: cubeView.value,
1276
+ faceViews,
1277
+ mipViews,
1278
+ depthTexture: depthTexture.value,
1279
+ depthView: depthView.value,
1280
+ size,
1281
+ };
1282
+ }
1283
+ }