@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,672 @@
1
+ import type {
2
+ Buffer,
3
+ QuerySet,
4
+ RenderPipeline,
5
+ Result,
6
+ RhiCommandEncoder,
7
+ RhiDevice,
8
+ RhiError,
9
+ RhiRenderPassEncoder,
10
+ } from '@forgeax/engine-rhi';
11
+ import { err, ok, RhiError as RhiErrorClass } from '@forgeax/engine-rhi';
12
+ import {
13
+ GPU_BUFFER_USAGE_COPY_DST,
14
+ GPU_BUFFER_USAGE_MAP_READ,
15
+ GPU_BUFFER_USAGE_VERTEX,
16
+ } from '../../gpu-usage';
17
+ import type { PipelineBuilderShaderModuleFactory } from '../../pipeline-builder';
18
+ import type { VisibilityBudget } from './budget';
19
+ import type { VisibilityFacetStore } from './facet';
20
+ import {
21
+ createOcclusionQueryResources,
22
+ type OcclusionBounds,
23
+ type OcclusionQueryResources,
24
+ recordOcclusionResolve,
25
+ } from './occlusion-pass';
26
+ import {
27
+ OCCLUSION_QUERY_PAGE_INDEX_LIMIT,
28
+ OcclusionQueryPool,
29
+ type OcclusionQueryReservation,
30
+ type OcclusionQueryTicket,
31
+ } from './occlusion-query-pool';
32
+ import {
33
+ type PrimitiveKey,
34
+ primitiveKeyId,
35
+ type ViewKey,
36
+ type VisibilityCandidate,
37
+ viewKeyId,
38
+ } from './types';
39
+
40
+ const PROXY_VERTEX_FLOATS_PER_CANDIDATE = 36 * 4;
41
+ const PROXY_GEOMETRY_BYTES =
42
+ OCCLUSION_QUERY_PAGE_INDEX_LIMIT * PROXY_VERTEX_FLOATS_PER_CANDIDATE * 4;
43
+
44
+ /** @internal Deterministic producer hook; never configured by the player API. */
45
+ export interface OcclusionRuntimeTestHooks {
46
+ readonly beforeMap?: () => Promise<void>;
47
+ }
48
+
49
+ let runtimeTestHooks: OcclusionRuntimeTestHooks | undefined;
50
+
51
+ /** @internal Used only by the production evidence falsification harness. */
52
+ export function setOcclusionRuntimeTestHooks(hooks: OcclusionRuntimeTestHooks | undefined): void {
53
+ runtimeTestHooks = hooks;
54
+ }
55
+
56
+ export interface OcclusionRuntimeCandidate {
57
+ readonly view: ViewKey;
58
+ readonly primitive: PrimitiveKey;
59
+ readonly epoch: number;
60
+ readonly bounds: OcclusionBounds;
61
+ readonly candidate: VisibilityCandidate;
62
+ readonly deviceGeneration: number;
63
+ /** Clip-space triangles for the conservative bounds proxy. */
64
+ readonly proxyVertices?: Float32Array;
65
+ }
66
+
67
+ export interface OcclusionFrameProjection {
68
+ readonly querySet: QuerySet;
69
+ readonly queryIndex: number;
70
+ readonly queryCount: number;
71
+ readonly sampleCount: 1 | 4;
72
+ readonly pageIndex: number;
73
+ readonly reservationId: number;
74
+ readonly encodeProxyBounds: (pass: RhiRenderPassEncoder) => Result<void, RhiError>;
75
+ readonly beginCandidate: (pass: RhiRenderPassEncoder) => Result<void, RhiError>;
76
+ readonly endCandidate: (pass: RhiRenderPassEncoder) => Result<void, RhiError>;
77
+ readonly recordResolve: (encoder: RhiCommandEncoder) => Result<void, RhiError>;
78
+ readonly commit: (
79
+ submitted: boolean,
80
+ submissionGeneration: number,
81
+ profilePhase?: OcclusionProfilePhaseRunner,
82
+ ) => void;
83
+ }
84
+
85
+ export type OcclusionProfilePhaseRunner = <T>(
86
+ phase: 'record/occlusion-query-submit' | 'record/occlusion-global-advance',
87
+ action: () => T,
88
+ ) => T;
89
+
90
+ interface PendingFrame {
91
+ readonly reservations: readonly OcclusionQueryReservation[];
92
+ readonly resources: OcclusionQueryResources;
93
+ readonly candidates: readonly OcclusionRuntimeCandidate[];
94
+ readonly sampleCount: 1 | 4;
95
+ endedCount: number;
96
+ readonly proxyFloatCount: number;
97
+ }
98
+
99
+ /**
100
+ * RenderSystem-owned query transport. It is deliberately the only bridge
101
+ * between the logical pool, the typed graph raster pass, and facet completion.
102
+ */
103
+ export class OcclusionRenderRuntime {
104
+ private readonly pool = new OcclusionQueryPool();
105
+ private readonly resources = new Map<number, OcclusionQueryResources>();
106
+ private readonly activeCompletions = new Map<OcclusionQueryResources, Promise<void>>();
107
+ private readonly retiredResources = new Set<OcclusionQueryResources>();
108
+ /** Candidate descriptors are rebuilt only when the extracted frame changes. */
109
+ private candidateSource: readonly OcclusionRuntimeCandidate[] | undefined;
110
+ private validCandidates: readonly OcclusionRuntimeCandidate[] = [];
111
+ private readonly candidateByKey = new Map<string, OcclusionRuntimeCandidate>();
112
+ private lastPrepareUnavailable = false;
113
+ private pendingFrame: PendingFrame | undefined;
114
+ private disposed = false;
115
+ private proxyBuffer: Buffer | undefined;
116
+ private readonly proxyScratch: Float32Array;
117
+ private proxyPipeline: RenderPipeline | undefined;
118
+ private proxyPipelineSampleCount: 1 | 4 | undefined;
119
+ private readonly queryLatencySamplesUs: number[] = [];
120
+ private lastQueryLatencyUs = 0;
121
+ private readonly projection: OcclusionFrameProjection;
122
+
123
+ constructor(
124
+ private readonly device: RhiDevice,
125
+ private readonly facets: VisibilityFacetStore,
126
+ private readonly shaderModuleFactory?: PipelineBuilderShaderModuleFactory,
127
+ private readonly budget: VisibilityBudget = facets.visibilityBudget(),
128
+ ) {
129
+ const runtime = this;
130
+ this.proxyScratch = new Float32Array(
131
+ Math.min(this.budget.effectiveQueryBudget, OCCLUSION_QUERY_PAGE_INDEX_LIMIT) *
132
+ PROXY_VERTEX_FLOATS_PER_CANDIDATE,
133
+ );
134
+ this.projection = {
135
+ get querySet() {
136
+ return runtime.pendingFrame?.resources.querySet as QuerySet;
137
+ },
138
+ get queryIndex() {
139
+ return runtime.pendingFrame?.reservations[0]?.queryIndex ?? 0;
140
+ },
141
+ get queryCount() {
142
+ return runtime.pendingFrame?.reservations.length ?? 0;
143
+ },
144
+ get sampleCount() {
145
+ return runtime.pendingFrame?.sampleCount ?? 1;
146
+ },
147
+ get pageIndex() {
148
+ return runtime.pendingFrame?.reservations[0]?.pageIndex ?? 0;
149
+ },
150
+ get reservationId() {
151
+ return runtime.pendingFrame?.reservations[0]?.reservationId ?? 0;
152
+ },
153
+ encodeProxyBounds: (pass) => {
154
+ const pending = runtime.pendingFrame;
155
+ return pending === undefined
156
+ ? err(runtimeError('the current RenderSystem frame owns the occlusion candidate'))
157
+ : runtime.encodeProxyBounds(pass, pending);
158
+ },
159
+ beginCandidate: (pass) => {
160
+ const pending = runtime.pendingFrame;
161
+ if (pending === undefined || pending.endedCount >= pending.reservations.length) {
162
+ return err(runtimeError('the current RenderSystem frame owns the occlusion candidate'));
163
+ }
164
+ return pass.beginOcclusionQuery(pending.reservations[pending.endedCount]?.queryIndex ?? 0);
165
+ },
166
+ endCandidate: (pass) => {
167
+ const pending = runtime.pendingFrame;
168
+ if (pending === undefined || pending.endedCount >= pending.reservations.length) {
169
+ return err(runtimeError('the current RenderSystem frame owns the occlusion candidate'));
170
+ }
171
+ const ended = pass.endOcclusionQuery();
172
+ if (ended.ok) pending.endedCount += 1;
173
+ return ended;
174
+ },
175
+ recordResolve: (encoder) => {
176
+ const pending = runtime.pendingFrame;
177
+ if (pending === undefined || pending.endedCount !== pending.reservations.length) {
178
+ return err(runtimeError('the real geometry pass must end its occlusion query first'));
179
+ }
180
+ return recordOcclusionResolve(
181
+ encoder,
182
+ pending.resources,
183
+ pending.reservations[0]?.queryIndex ?? 0,
184
+ pending.reservations.length,
185
+ );
186
+ },
187
+ commit: (submitted, submissionGeneration, profilePhase) => {
188
+ const pending = runtime.pendingFrame;
189
+ if (pending !== undefined)
190
+ runtime.commit(pending, submitted, submissionGeneration, profilePhase);
191
+ },
192
+ };
193
+ }
194
+
195
+ prepare(candidate: OcclusionRuntimeCandidate): OcclusionFrameProjection | undefined {
196
+ // Keep the single-candidate helper useful for transport unit tests and
197
+ // direct callers. The RenderSystem uses prepareBatch's facet-owned queue.
198
+ return this.prepareBatch([candidate], 1, false);
199
+ }
200
+
201
+ prepareBatch(
202
+ candidates: readonly OcclusionRuntimeCandidate[],
203
+ sampleCount: 1 | 4 = 1,
204
+ useReadyQueue = true,
205
+ ): OcclusionFrameProjection | undefined {
206
+ this.lastPrepareUnavailable = false;
207
+ if (this.disposed || candidates.length === 0) return undefined;
208
+ if (this.pendingFrame !== undefined) {
209
+ this.lastPrepareUnavailable = true;
210
+ return undefined;
211
+ }
212
+ if (this.candidateSource !== candidates) {
213
+ this.candidateSource = candidates;
214
+ this.candidateByKey.clear();
215
+ const valid: OcclusionRuntimeCandidate[] = [];
216
+ for (const candidate of candidates) {
217
+ this.candidateByKey.set(candidateKey(candidate.view, candidate.primitive), candidate);
218
+ if (finiteBounds(candidate.bounds)) valid.push(candidate);
219
+ }
220
+ this.validCandidates = valid;
221
+ }
222
+ const valid = this.validCandidates;
223
+ if (valid.length === 0) return undefined;
224
+
225
+ let selectedCandidates: OcclusionRuntimeCandidate[];
226
+ if (!useReadyQueue) {
227
+ selectedCandidates = valid.slice(0, this.budget.effectiveQueryBudget);
228
+ } else {
229
+ const firstView = valid[0]?.view;
230
+ if (firstView === undefined) return undefined;
231
+ selectedCandidates = [];
232
+ for (const entry of this.facets.dequeueQueryCandidates(
233
+ firstView,
234
+ this.budget.effectiveQueryBudget,
235
+ )) {
236
+ const candidate = this.candidateByKey.get(candidateKey(entry.view, entry.primitive));
237
+ if (candidate === undefined || !finiteBounds(candidate.bounds)) {
238
+ this.facets.requeueQueryCandidate(entry.view, entry.primitive);
239
+ continue;
240
+ }
241
+ selectedCandidates.push(candidate);
242
+ }
243
+ // No ready work is a normal bounded-query frame, not a transport error.
244
+ if (selectedCandidates.length === 0) return undefined;
245
+ }
246
+
247
+ const reservations: OcclusionQueryReservation[] = [];
248
+ let selectedCount = 0;
249
+ while (selectedCount < selectedCandidates.length) {
250
+ const candidate = selectedCandidates[selectedCount];
251
+ if (candidate === undefined) break;
252
+ const reservation = this.pool.reserve({
253
+ viewKey: viewKeyId(candidate.view),
254
+ attachmentId: candidate.view.attachmentId,
255
+ deviceGeneration: candidate.deviceGeneration,
256
+ worldGeneration: candidate.primitive.worldGeneration,
257
+ primitiveSlot: candidate.primitive.primitiveSlot,
258
+ slotGeneration: candidate.primitive.slotGeneration,
259
+ });
260
+ if (reservation === undefined) {
261
+ this.lastPrepareUnavailable = true;
262
+ break;
263
+ }
264
+ if (reservations.length > 0 && reservation.pageIndex !== reservations[0]?.pageIndex) {
265
+ this.pool.cancel(reservation);
266
+ this.lastPrepareUnavailable = true;
267
+ break;
268
+ }
269
+ reservations.push(reservation);
270
+ selectedCount += 1;
271
+ if (reservations.length >= this.budget.effectiveQueryBudget) break;
272
+ }
273
+ if (reservations.length === 0) {
274
+ for (const candidate of selectedCandidates) {
275
+ if (useReadyQueue) this.facets.requeueQueryCandidate(candidate.view, candidate.primitive);
276
+ }
277
+ return undefined;
278
+ }
279
+ if (reservations.length < selectedCandidates.length) {
280
+ for (const reservation of reservations) this.pool.cancel(reservation);
281
+ for (const candidate of selectedCandidates) {
282
+ if (useReadyQueue) this.facets.requeueQueryCandidate(candidate.view, candidate.primitive);
283
+ }
284
+ return undefined;
285
+ }
286
+ const resources = this.ensureResources(reservations[0]?.pageIndex ?? 0);
287
+ if (resources === undefined) {
288
+ for (const reservation of reservations) this.pool.cancel(reservation);
289
+ this.lastPrepareUnavailable = true;
290
+ for (const candidate of selectedCandidates) {
291
+ if (useReadyQueue) this.facets.requeueQueryCandidate(candidate.view, candidate.primitive);
292
+ }
293
+ return undefined;
294
+ }
295
+ const selected = selectedCandidates;
296
+ let proxyFloatCount = 0;
297
+ for (const candidate of selected) {
298
+ const vertices = candidate.proxyVertices;
299
+ if (vertices === undefined) continue;
300
+ if (proxyFloatCount + vertices.length > this.proxyScratch.length) {
301
+ for (const reservation of reservations) this.pool.cancel(reservation);
302
+ for (const queuedCandidate of selectedCandidates) {
303
+ if (useReadyQueue) {
304
+ this.facets.requeueQueryCandidate(queuedCandidate.view, queuedCandidate.primitive);
305
+ }
306
+ }
307
+ this.lastPrepareUnavailable = true;
308
+ return undefined;
309
+ }
310
+ this.proxyScratch.set(vertices, proxyFloatCount);
311
+ proxyFloatCount += vertices.length;
312
+ }
313
+ const pending: PendingFrame = {
314
+ reservations,
315
+ resources,
316
+ candidates: selected,
317
+ sampleCount,
318
+ endedCount: 0,
319
+ proxyFloatCount,
320
+ };
321
+ this.pendingFrame = pending;
322
+ return this.projection;
323
+ }
324
+
325
+ inspect(): ReturnType<OcclusionQueryPool['inspect']> {
326
+ return this.pool.inspect();
327
+ }
328
+
329
+ get prepareUnavailable(): boolean {
330
+ return this.lastPrepareUnavailable;
331
+ }
332
+
333
+ /** Map-only latency from the submitted query page; queue wait is excluded. */
334
+ inspectQueryLatency(): { readonly median: number; readonly p95: number; readonly last: number } {
335
+ if (this.queryLatencySamplesUs.length === 0) {
336
+ return { median: 0, p95: 0, last: this.lastQueryLatencyUs };
337
+ }
338
+ const sorted = [...this.queryLatencySamplesUs].sort((left, right) => left - right);
339
+ const rank = (percentile: number): number =>
340
+ sorted[Math.max(0, Math.ceil(sorted.length * percentile) - 1)] ?? 0;
341
+ return {
342
+ median: rank(0.5),
343
+ p95: rank(0.95),
344
+ last: this.lastQueryLatencyUs,
345
+ };
346
+ }
347
+
348
+ /**
349
+ * Wait until query readbacks submitted before the current observation have
350
+ * settled. The renderer receipt only fences command submission; query
351
+ * completion also includes the asynchronous map/unmap transition. Keeping
352
+ * that fence here makes `renderer.observe()` publish the latest visibility
353
+ * result and measured map latency instead of racing the readback promise.
354
+ */
355
+ async waitForCompletions(): Promise<void> {
356
+ // A submission callback can publish the next page while the previous
357
+ // completion is settling. Drain until the map stays empty so observation
358
+ // cannot return with a newer query still racing the inspection update.
359
+ for (;;) {
360
+ const completions = [...this.activeCompletions.values()];
361
+ if (completions.length === 0) return;
362
+ await Promise.all(completions);
363
+ }
364
+ }
365
+
366
+ /** Count a successful frame for hidden rows that were not queried this frame. */
367
+ advanceSuccessfulSubmit(submissionGeneration: number): void {
368
+ this.facets.advanceSuccessfulSubmits(submissionGeneration);
369
+ }
370
+
371
+ dispose(): void {
372
+ if (this.disposed) return;
373
+ this.disposed = true;
374
+ this.pool.dispose();
375
+ for (const resource of this.resources.values()) {
376
+ const completion = this.activeCompletions.get(resource);
377
+ if (completion === undefined) {
378
+ this.destroyResources(resource);
379
+ } else {
380
+ // A completion may still be awaiting mapAsync. Destroying its staging
381
+ // buffer here makes Dawn report a use-after-destroy validation error.
382
+ void completion.then(() => this.destroyResources(resource));
383
+ }
384
+ }
385
+ this.resources.clear();
386
+ if (this.proxyBuffer !== undefined) this.device.destroyBuffer(this.proxyBuffer);
387
+ this.proxyBuffer = undefined;
388
+ this.proxyPipeline = undefined;
389
+ this.proxyPipelineSampleCount = undefined;
390
+ this.candidateSource = undefined;
391
+ this.validCandidates = [];
392
+ this.candidateByKey.clear();
393
+ this.lastPrepareUnavailable = false;
394
+ this.pendingFrame = undefined;
395
+ }
396
+
397
+ private ensureResources(pageIndex: number): OcclusionQueryResources | undefined {
398
+ const existing = this.resources.get(pageIndex);
399
+ if (existing !== undefined) return existing;
400
+ const created = createOcclusionQueryResources(this.device, pageIndex);
401
+ if (!created.ok) return undefined;
402
+ this.resources.set(pageIndex, created.value);
403
+ return created.value;
404
+ }
405
+
406
+ private commit(
407
+ pending: PendingFrame,
408
+ submitted: boolean,
409
+ submissionGeneration: number,
410
+ profilePhase?: OcclusionProfilePhaseRunner,
411
+ ): void {
412
+ if (this.pendingFrame !== pending) return;
413
+ this.pendingFrame = undefined;
414
+ const tickets: OcclusionQueryTicket[] = [];
415
+ const accepted =
416
+ profilePhase === undefined
417
+ ? this.publishAndApplySubmit(pending, submitted, submissionGeneration, tickets)
418
+ : profilePhase('record/occlusion-query-submit', () =>
419
+ this.publishAndApplySubmit(pending, submitted, submissionGeneration, tickets),
420
+ );
421
+ if (!accepted) {
422
+ for (const reservation of pending.reservations) this.pool.cancel(reservation);
423
+ for (const candidate of pending.candidates) {
424
+ this.facets.applyConfidence(candidate.view, candidate.primitive, {
425
+ type: 'failure',
426
+ submissionGeneration,
427
+ });
428
+ }
429
+ return;
430
+ }
431
+
432
+ if (profilePhase === undefined) {
433
+ this.facets.advanceSuccessfulSubmits(submissionGeneration);
434
+ } else {
435
+ profilePhase('record/occlusion-global-advance', () =>
436
+ this.facets.advanceSuccessfulSubmits(submissionGeneration),
437
+ );
438
+ }
439
+ const completion = this.completeAfterSubmit(tickets, pending);
440
+ this.activeCompletions.set(pending.resources, completion);
441
+ void completion.then(() => {
442
+ if (this.activeCompletions.get(pending.resources) === completion) {
443
+ this.activeCompletions.delete(pending.resources);
444
+ }
445
+ });
446
+ }
447
+
448
+ private publishAndApplySubmit(
449
+ pending: PendingFrame,
450
+ submitted: boolean,
451
+ submissionGeneration: number,
452
+ tickets: OcclusionQueryTicket[],
453
+ ): boolean {
454
+ for (const reservation of pending.reservations) {
455
+ const ticket = this.pool.publish(reservation, { submitted, submissionGeneration });
456
+ if (ticket !== undefined) tickets.push(ticket);
457
+ }
458
+ if (!submitted || tickets.length !== pending.reservations.length) return false;
459
+ const submitEvent = Object.freeze({ type: 'submit' as const, submissionGeneration });
460
+ for (const candidate of pending.candidates) {
461
+ this.facets.applyConfidence(candidate.view, candidate.primitive, submitEvent);
462
+ }
463
+ return true;
464
+ }
465
+
466
+ private destroyResources(resource: OcclusionQueryResources): void {
467
+ if (this.retiredResources.has(resource)) return;
468
+ this.retiredResources.add(resource);
469
+ this.device.destroyBuffer(resource.resolveBuffer);
470
+ this.device.destroyBuffer(resource.stagingBuffer);
471
+ this.device.destroyQuerySet(resource.querySet);
472
+ }
473
+
474
+ private async completeAfterSubmit(
475
+ tickets: readonly OcclusionQueryTicket[],
476
+ pending: PendingFrame,
477
+ ): Promise<void> {
478
+ try {
479
+ await this.device.queue.onSubmittedWorkDone();
480
+ const mapStart = performance.now();
481
+ await runtimeTestHooks?.beforeMap?.();
482
+ const mapped = await pending.resources.stagingBuffer.mapAsync(
483
+ GPU_BUFFER_USAGE_MAP_READ,
484
+ 0,
485
+ pending.reservations.length * 8,
486
+ );
487
+ if (!mapped.ok) throw mapped.error;
488
+ // Inspection is a bounded integer POD; keep the real map-only duration
489
+ // while rounding sub-microsecond timer noise at the transport boundary.
490
+ this.lastQueryLatencyUs = Math.max(0, Math.round((performance.now() - mapStart) * 1000));
491
+ this.queryLatencySamplesUs.push(this.lastQueryLatencyUs);
492
+ if (this.queryLatencySamplesUs.length > 64) this.queryLatencySamplesUs.shift();
493
+ const bytes = mapped.value.getMappedRange(0, pending.reservations.length * 8);
494
+ if (!bytes.ok) throw bytes.error;
495
+ // WebGPU detaches the mapped range when unmap() is called. Copy the
496
+ // bounded query payload while it is still mapped, then release the
497
+ // staging buffer before applying any facet mutations. Reading the view
498
+ // after unmap would throw on real Dawn and turn every result into the
499
+ // fail-open failure path (leaving candidates permanently visible).
500
+ const data = new Uint8Array(bytes.value.slice(0));
501
+ const dataView = new DataView(data.buffer);
502
+ const firstQuery = pending.reservations[0]?.queryIndex ?? 0;
503
+ mapped.value.unmap();
504
+ for (let ticketIndex = tickets.length - 1; ticketIndex >= 0; ticketIndex -= 1) {
505
+ const ticket = tickets[ticketIndex];
506
+ if (ticket === undefined) continue;
507
+ const candidateIndex = ticket.queryIndex - firstQuery;
508
+ const samples = dataView.getUint32(candidateIndex * 8, true);
509
+ const completion = this.pool.complete(ticket, samples);
510
+ if (completion.status !== 'accepted') continue;
511
+ const candidate = pending.candidates[candidateIndex];
512
+ if (candidate === undefined) continue;
513
+ this.facets.applyConfidence(candidate.view, candidate.primitive, {
514
+ type: 'result',
515
+ samples,
516
+ submissionGeneration: ticket.submissionGeneration,
517
+ });
518
+ this.facets.applyCompletion(
519
+ candidate.view,
520
+ candidate.primitive,
521
+ candidate.epoch,
522
+ candidate.candidate,
523
+ );
524
+ }
525
+ } catch {
526
+ for (const ticket of tickets) {
527
+ this.pool.complete(ticket, Number.NaN);
528
+ const candidate =
529
+ pending.candidates[ticket.queryIndex - (pending.reservations[0]?.queryIndex ?? 0)];
530
+ if (candidate !== undefined) {
531
+ this.facets.applyConfidence(candidate.view, candidate.primitive, {
532
+ type: 'failure',
533
+ submissionGeneration: ticket.submissionGeneration,
534
+ });
535
+ }
536
+ }
537
+ }
538
+ }
539
+
540
+ private encodeProxyBounds(
541
+ pass: RhiRenderPassEncoder,
542
+ pending: PendingFrame,
543
+ ): Result<void, RhiError> {
544
+ if (pending.proxyFloatCount === 0) {
545
+ return err(runtimeError('every occlusion candidate needs conservative proxy geometry'));
546
+ }
547
+ const pipeline = this.ensureProxyPipeline(pending.sampleCount);
548
+ if (pipeline === undefined) {
549
+ // RhiNull has no shader-module factory. It still records the complete
550
+ // query fan-out structurally so lifecycle and identity tests exercise
551
+ // the same bounded transport; real backends take the mesh path below.
552
+ for (const reservation of pending.reservations) {
553
+ const began = pass.beginOcclusionQuery(reservation.queryIndex);
554
+ if (!began.ok) return began;
555
+ const ended = pass.endOcclusionQuery();
556
+ if (!ended.ok) return ended;
557
+ }
558
+ pending.endedCount = pending.reservations.length;
559
+ return ok(undefined);
560
+ }
561
+ const buffer = this.ensureProxyBuffer(pending.proxyFloatCount * Float32Array.BYTES_PER_ELEMENT);
562
+ if (buffer === undefined) {
563
+ return err(runtimeError('the backend must allocate the bounded proxy geometry buffer'));
564
+ }
565
+ const uploaded = this.device.queue.writeBuffer(
566
+ buffer,
567
+ 0,
568
+ this.proxyScratch.subarray(0, pending.proxyFloatCount),
569
+ );
570
+ if (!uploaded.ok) return uploaded;
571
+ pass.setPipeline(pipeline);
572
+ pass.setVertexBuffer(0, buffer);
573
+ let vertexOffset = 0;
574
+ for (let index = 0; index < pending.reservations.length; index += 1) {
575
+ const vertices = pending.candidates[index]?.proxyVertices;
576
+ if (vertices === undefined || vertices.length === 0) continue;
577
+ const began = pass.beginOcclusionQuery(pending.reservations[index]?.queryIndex ?? 0);
578
+ if (!began.ok) return began;
579
+ pass.draw(vertices.length / 4, 1, vertexOffset / 4, 0);
580
+ const ended = pass.endOcclusionQuery();
581
+ if (!ended.ok) return ended;
582
+ vertexOffset += vertices.length;
583
+ }
584
+ pending.endedCount = pending.reservations.length;
585
+ return ok(undefined);
586
+ }
587
+
588
+ private ensureProxyBuffer(byteLength: number): Buffer | undefined {
589
+ if (this.proxyBuffer !== undefined) return this.proxyBuffer;
590
+ const created = this.device.createBuffer({
591
+ label: 'occlusion-bounds-proxy-geometry',
592
+ size: Math.max(16, PROXY_GEOMETRY_BYTES, byteLength),
593
+ usage: GPU_BUFFER_USAGE_VERTEX | GPU_BUFFER_USAGE_COPY_DST,
594
+ });
595
+ if (!created.ok) return undefined;
596
+ this.proxyBuffer = created.value;
597
+ return created.value;
598
+ }
599
+
600
+ private ensureProxyPipeline(sampleCount: 1 | 4): RenderPipeline | undefined {
601
+ if (this.proxyPipelineSampleCount === sampleCount && this.proxyPipeline !== undefined) {
602
+ return this.proxyPipeline;
603
+ }
604
+ if (this.shaderModuleFactory === undefined) return undefined;
605
+ const module = this.shaderModuleFactory.createShaderModule({
606
+ label: 'occlusion-bounds-proxy',
607
+ code: PROXY_BOUNDS_WGSL,
608
+ });
609
+ if (!module.ok) return undefined;
610
+ const created = this.device.createRenderPipeline({
611
+ label: 'occlusion-bounds-proxy',
612
+ layout: 'auto',
613
+ vertex: {
614
+ module: module.value,
615
+ entryPoint: 'vs_main',
616
+ buffers: [
617
+ {
618
+ arrayStride: 16,
619
+ stepMode: 'vertex',
620
+ attributes: [{ shaderLocation: 0, offset: 0, format: 'float32x4' }],
621
+ },
622
+ ],
623
+ },
624
+ // The proxy is recorded in the typed main pass, whose color attachment
625
+ // remains RGBA16Float. Keep the pass compatible while suppressing all
626
+ // color writes; occlusion only consumes depth samples.
627
+ fragment: {
628
+ module: module.value,
629
+ entryPoint: 'fs_main',
630
+ targets: [{ format: 'rgba16float', writeMask: 0 }],
631
+ },
632
+ primitive: { topology: 'triangle-list' },
633
+ depthStencil: {
634
+ format: 'depth24plus-stencil8',
635
+ depthWriteEnabled: false,
636
+ depthCompare: 'less-equal',
637
+ },
638
+ multisample: { count: sampleCount },
639
+ });
640
+ if (!created.ok) return undefined;
641
+ this.proxyPipeline = created.value;
642
+ this.proxyPipelineSampleCount = sampleCount;
643
+ return created.value;
644
+ }
645
+ }
646
+
647
+ const PROXY_BOUNDS_WGSL = `
648
+ @vertex
649
+ fn vs_main(@location(0) position: vec4f) -> @builtin(position) vec4f {
650
+ return position;
651
+ }
652
+ @fragment
653
+ fn fs_main() -> @location(0) vec4f {
654
+ return vec4f(0.0);
655
+ }
656
+ `;
657
+
658
+ function candidateKey(view: ViewKey, primitive: PrimitiveKey): string {
659
+ return `${viewKeyId(view)}|${primitiveKeyId(primitive)}`;
660
+ }
661
+
662
+ function finiteBounds(bounds: OcclusionBounds): boolean {
663
+ return [...bounds.min, ...bounds.max].every((value) => Number.isFinite(value));
664
+ }
665
+
666
+ function runtimeError(hint: string): RhiError {
667
+ return new RhiErrorClass({
668
+ code: 'webgpu-runtime-error',
669
+ expected: 'RenderSystem owns one active occlusion candidate for the shared frame',
670
+ hint,
671
+ });
672
+ }