@forgeax/engine-render 0.1.21 → 0.1.23

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 (426) hide show
  1. package/README.md +150 -29
  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/device-feature-admission.d.ts.map +1 -1
  5. package/dist/assembly/factory.d.ts +14 -6
  6. package/dist/assembly/factory.d.ts.map +1 -1
  7. package/dist/assembly/host-contract.d.ts +10 -0
  8. package/dist/assembly/host-contract.d.ts.map +1 -1
  9. package/dist/authoring.mjs +1 -1
  10. package/dist/{chunk-PCHBPYML.mjs → chunk-4L3P6DT5.mjs} +2 -2
  11. package/dist/chunk-4L3P6DT5.mjs.map +1 -0
  12. package/dist/{chunk-VFPLTKCU.mjs → chunk-4NPUASZR.mjs} +15 -4
  13. package/dist/chunk-4NPUASZR.mjs.map +1 -0
  14. package/dist/chunk-DIYW3M5D.mjs +5085 -0
  15. package/dist/chunk-DIYW3M5D.mjs.map +1 -0
  16. package/dist/chunk-FBC4R44J.mjs +1003 -0
  17. package/dist/chunk-FBC4R44J.mjs.map +1 -0
  18. package/dist/{chunk-FS2KNUQH.mjs → chunk-FF7K3FSO.mjs} +253 -489
  19. package/dist/chunk-FF7K3FSO.mjs.map +1 -0
  20. package/dist/chunk-HZUPSYW7.mjs +1104 -0
  21. package/dist/chunk-HZUPSYW7.mjs.map +1 -0
  22. package/dist/{chunk-6OPXDQ2E.mjs → chunk-QODA5UKU.mjs} +33 -294
  23. package/dist/chunk-QODA5UKU.mjs.map +1 -0
  24. package/dist/chunk-YALPS44A.mjs +296 -0
  25. package/dist/chunk-YALPS44A.mjs.map +1 -0
  26. package/dist/{chunk-5YADUCA7.mjs → chunk-ZUWKAZ2I.mjs} +585 -991
  27. package/dist/chunk-ZUWKAZ2I.mjs.map +1 -0
  28. package/dist/cluster-binner.d.ts +5 -0
  29. package/dist/cluster-binner.d.ts.map +1 -1
  30. package/dist/components/index.d.ts +3 -0
  31. package/dist/components/index.d.ts.map +1 -1
  32. package/dist/components/light-helpers.d.ts +4 -0
  33. package/dist/components/light-helpers.d.ts.map +1 -1
  34. package/dist/components/light-probe.d.ts +9 -0
  35. package/dist/components/light-probe.d.ts.map +1 -0
  36. package/dist/components/rect-area-light.d.ts +12 -0
  37. package/dist/components/rect-area-light.d.ts.map +1 -0
  38. package/dist/components/scene-instance.d.ts +4 -0
  39. package/dist/components/scene-instance.d.ts.map +1 -1
  40. package/dist/components/spot-light.d.ts +3 -0
  41. package/dist/components/spot-light.d.ts.map +1 -1
  42. package/dist/construct-renderer.mjs +10437 -10044
  43. package/dist/construct-renderer.mjs.map +1 -1
  44. package/dist/device/gpu-residency.d.ts +8 -1
  45. package/dist/device/gpu-residency.d.ts.map +1 -1
  46. package/dist/device/lifecycle-transaction.d.ts +2 -0
  47. package/dist/device/lifecycle-transaction.d.ts.map +1 -0
  48. package/dist/environment/lifecycle.d.ts.map +1 -1
  49. package/dist/errors/gpu-driven.d.ts +26 -0
  50. package/dist/errors/gpu-driven.d.ts.map +1 -0
  51. package/dist/errors/index.d.ts +1 -0
  52. package/dist/errors/index.d.ts.map +1 -1
  53. package/dist/errors/recover.d.ts +9 -1
  54. package/dist/errors/recover.d.ts.map +1 -1
  55. package/dist/errors/render.d.ts +26 -1
  56. package/dist/errors/render.d.ts.map +1 -1
  57. package/dist/extract/gpu-driven.d.ts +50 -0
  58. package/dist/extract/gpu-driven.d.ts.map +1 -0
  59. package/dist/extract/visibility.d.ts.map +1 -1
  60. package/dist/gpu-driven/batch-topology.d.ts +34 -0
  61. package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
  62. package/dist/gpu-driven/prepared-draw.d.ts +63 -0
  63. package/dist/gpu-driven/prepared-draw.d.ts.map +1 -0
  64. package/dist/gpu-driven/production-raster.d.ts +98 -2
  65. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  66. package/dist/gpu-driven/view-gpu.d.ts +64 -1
  67. package/dist/gpu-driven/view-gpu.d.ts.map +1 -1
  68. package/dist/gpu-scene-schema.d.ts +14 -0
  69. package/dist/gpu-scene-schema.d.ts.map +1 -1
  70. package/dist/gpu-scene.d.ts +8 -1
  71. package/dist/gpu-scene.d.ts.map +1 -1
  72. package/dist/gpu-usage.d.ts +0 -1
  73. package/dist/gpu-usage.d.ts.map +1 -1
  74. package/dist/hdrp-buffers.d.ts +3 -3
  75. package/dist/hdrp-buffers.d.ts.map +1 -1
  76. package/dist/index.d.ts +12 -1
  77. package/dist/index.d.ts.map +1 -1
  78. package/dist/index.mjs +90 -5
  79. package/dist/index.mjs.map +1 -1
  80. package/dist/inspection-types.d.ts +163 -1
  81. package/dist/inspection-types.d.ts.map +1 -1
  82. package/dist/internal.d.ts +9 -0
  83. package/dist/internal.d.ts.map +1 -1
  84. package/dist/internal.mjs +23 -2
  85. package/dist/internal.mjs.map +1 -1
  86. package/dist/light-buffer-layout.d.ts +58 -149
  87. package/dist/light-buffer-layout.d.ts.map +1 -1
  88. package/dist/materials.d.ts.map +1 -1
  89. package/dist/pbr-pipeline.d.ts +26 -12
  90. package/dist/pbr-pipeline.d.ts.map +1 -1
  91. package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
  92. package/dist/pipeline/standard-lighting/graph.d.ts.map +1 -1
  93. package/dist/pipeline/standard-lighting/light-frame.d.ts +3 -3
  94. package/dist/pipeline/standard-lighting/light-frame.d.ts.map +1 -1
  95. package/dist/pipeline/standard-lighting/topology.d.ts +1 -1
  96. package/dist/pipeline/standard-lighting/topology.d.ts.map +1 -1
  97. package/dist/pipeline/standard-lighting/transport.d.ts.map +1 -1
  98. package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
  99. package/dist/pipeline-builder.d.ts +2 -0
  100. package/dist/pipeline-builder.d.ts.map +1 -1
  101. package/dist/pipeline-spec.d.ts +1 -1
  102. package/dist/pipeline-spec.d.ts.map +1 -1
  103. package/dist/plugin.d.ts.map +1 -1
  104. package/dist/prepare/extended-lighting/ltc-resources.d.ts +31 -0
  105. package/dist/prepare/extended-lighting/ltc-resources.d.ts.map +1 -0
  106. package/dist/prepare/extended-lighting/resources.d.ts +72 -0
  107. package/dist/prepare/extended-lighting/resources.d.ts.map +1 -0
  108. package/dist/prepare/extended-lighting/spot-modifiers.d.ts +42 -0
  109. package/dist/prepare/extended-lighting/spot-modifiers.d.ts.map +1 -0
  110. package/dist/prepare/extended-lighting/state.d.ts +24 -0
  111. package/dist/prepare/extended-lighting/state.d.ts.map +1 -0
  112. package/dist/record/frame-lighting.d.ts +8 -6
  113. package/dist/record/frame-lighting.d.ts.map +1 -1
  114. package/dist/record/frame-snapshot.d.ts +50 -10
  115. package/dist/record/frame-snapshot.d.ts.map +1 -1
  116. package/dist/record/frame.d.ts +7 -3
  117. package/dist/record/frame.d.ts.map +1 -1
  118. package/dist/record/gpu-timing.d.ts +19 -7
  119. package/dist/record/gpu-timing.d.ts.map +1 -1
  120. package/dist/record/helpers.d.ts +0 -20
  121. package/dist/record/helpers.d.ts.map +1 -1
  122. package/dist/record/main-pass-geometry.d.ts +6 -1
  123. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  124. package/dist/record/main-pass-material.d.ts +3 -1
  125. package/dist/record/main-pass-material.d.ts.map +1 -1
  126. package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
  127. package/dist/record/mesh-ssbo.d.ts.map +1 -1
  128. package/dist/record/render-context.d.ts +57 -6
  129. package/dist/record/render-context.d.ts.map +1 -1
  130. package/dist/record/shadow-pass.d.ts.map +1 -1
  131. package/dist/record/typed-frame-graph.d.ts +49 -2
  132. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  133. package/dist/record/view-ubo.d.ts +1 -2
  134. package/dist/record/view-ubo.d.ts.map +1 -1
  135. package/dist/reflection/inspection.d.ts +10 -0
  136. package/dist/reflection/inspection.d.ts.map +1 -0
  137. package/dist/reflection/projection.d.ts +49 -0
  138. package/dist/reflection/projection.d.ts.map +1 -1
  139. package/dist/reflection/record-owner.d.ts +60 -0
  140. package/dist/reflection/record-owner.d.ts.map +1 -0
  141. package/dist/render-contract.d.ts +14 -8
  142. package/dist/render-contract.d.ts.map +1 -1
  143. package/dist/render-pipeline.d.ts +7 -0
  144. package/dist/render-pipeline.d.ts.map +1 -1
  145. package/dist/render-system-extract.d.ts +104 -16
  146. package/dist/render-system-extract.d.ts.map +1 -1
  147. package/dist/render-system.d.ts +12 -1
  148. package/dist/render-system.d.ts.map +1 -1
  149. package/dist/scene/probe-blend-record.d.ts +22 -0
  150. package/dist/scene/probe-blend-record.d.ts.map +1 -0
  151. package/dist/scene/probe-blend.d.ts +142 -0
  152. package/dist/scene/probe-blend.d.ts.map +1 -0
  153. package/dist/scene/render-scene-types.d.ts +0 -6
  154. package/dist/scene/render-scene-types.d.ts.map +1 -1
  155. package/dist/scene/render-scene.d.ts +103 -9
  156. package/dist/scene/render-scene.d.ts.map +1 -1
  157. package/dist/scene/visibility/bounds.d.ts +20 -0
  158. package/dist/scene/visibility/bounds.d.ts.map +1 -0
  159. package/dist/scene/visibility/budget.d.ts +16 -0
  160. package/dist/scene/visibility/budget.d.ts.map +1 -0
  161. package/dist/scene/visibility/facet.d.ts +73 -0
  162. package/dist/scene/visibility/facet.d.ts.map +1 -0
  163. package/dist/scene/visibility/gpu-frame-timing.d.ts +41 -0
  164. package/dist/scene/visibility/gpu-frame-timing.d.ts.map +1 -0
  165. package/dist/scene/visibility/gpu-lod.d.ts +46 -0
  166. package/dist/scene/visibility/gpu-lod.d.ts.map +1 -0
  167. package/dist/scene/visibility/inspection.d.ts +132 -0
  168. package/dist/scene/visibility/inspection.d.ts.map +1 -0
  169. package/dist/scene/visibility/lod-selector.d.ts +25 -0
  170. package/dist/scene/visibility/lod-selector.d.ts.map +1 -0
  171. package/dist/scene/visibility/occlusion-confidence.d.ts +81 -0
  172. package/dist/scene/visibility/occlusion-confidence.d.ts.map +1 -0
  173. package/dist/scene/visibility/occlusion-pass.d.ts +57 -0
  174. package/dist/scene/visibility/occlusion-pass.d.ts.map +1 -0
  175. package/dist/scene/visibility/occlusion-query-pool.d.ts +57 -0
  176. package/dist/scene/visibility/occlusion-query-pool.d.ts.map +1 -0
  177. package/dist/scene/visibility/occlusion-runtime.d.ts +96 -0
  178. package/dist/scene/visibility/occlusion-runtime.d.ts.map +1 -0
  179. package/dist/scene/visibility/types.d.ts +34 -0
  180. package/dist/scene/visibility/types.d.ts.map +1 -0
  181. package/dist/ssr/admission.d.ts +122 -0
  182. package/dist/ssr/admission.d.ts.map +1 -0
  183. package/dist/ssr/composition.d.ts +32 -0
  184. package/dist/ssr/composition.d.ts.map +1 -0
  185. package/dist/ssr/errors.d.ts +39 -0
  186. package/dist/ssr/errors.d.ts.map +1 -0
  187. package/dist/ssr/identity.d.ts +13 -0
  188. package/dist/ssr/identity.d.ts.map +1 -0
  189. package/dist/systems/skin-palette-allocator.d.ts +1 -1
  190. package/dist/temporal/frame-coordinator.d.ts +13 -0
  191. package/dist/temporal/frame-coordinator.d.ts.map +1 -1
  192. package/dist/temporal/frame.d.ts +2 -0
  193. package/dist/temporal/frame.d.ts.map +1 -1
  194. package/dist/temporal/index.mjs +6 -5
  195. package/dist/temporal/view.d.ts +7 -0
  196. package/dist/temporal/view.d.ts.map +1 -1
  197. package/dist/tilemap-chunk-extract-system.d.ts +1 -1
  198. package/dist/typed-render-graph-primitives.d.ts +4 -0
  199. package/dist/typed-render-graph-primitives.d.ts.map +1 -1
  200. package/dist/volume/passes.d.ts +8 -4
  201. package/dist/volume/passes.d.ts.map +1 -1
  202. package/package.json +20 -20
  203. package/src/__tests__/cluster-grid-default-surface.unit.test.ts +5 -0
  204. package/src/__tests__/detached-inspection.integration.test.ts +6 -0
  205. package/src/__tests__/device-feature-admission.unit.test.ts +13 -1
  206. package/src/__tests__/direct-light-snapshot.unit.test.ts +59 -3
  207. package/src/__tests__/environment-generation.integration.test.ts +16 -0
  208. package/src/__tests__/evidence-identity.ts +13 -0
  209. package/src/__tests__/evidence-identity.unit.test.ts +24 -0
  210. package/src/__tests__/extended-lighting-recovery.browser.test.ts +14 -0
  211. package/src/__tests__/extended-lighting-recovery.dawn.test.ts +14 -0
  212. package/src/__tests__/extended-lighting-recovery.integration.test.ts +81 -0
  213. package/src/__tests__/extended-lighting-resources.unit.test.ts +51 -0
  214. package/src/__tests__/extended-lighting-zero-feature.integration.test.ts +52 -0
  215. package/src/__tests__/factory-contract.integration.test.ts +33 -0
  216. package/src/__tests__/frame-observation.contract.test.ts +15 -0
  217. package/src/__tests__/gpu-driven-batch-topology-pbr.unit.test.ts +146 -0
  218. package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +1 -0
  219. package/src/__tests__/gpu-driven-indirect-raster.dawn.test.ts +1 -0
  220. package/src/__tests__/gpu-driven-production.integration.test.ts +572 -9
  221. package/src/__tests__/gpu-driven-scaling.unit.test.ts +66 -2
  222. package/src/__tests__/gpu-driven-view-gpu-evidence.ts +26 -1
  223. package/src/__tests__/gpu-driven-view-graph.integration.test.ts +89 -2
  224. package/src/__tests__/gpu-driven-view.browser.test.ts +3 -3
  225. package/src/__tests__/gpu-driven-view.dawn.test.ts +3 -3
  226. package/src/__tests__/gpu-driven-view.unit.test.ts +50 -0
  227. package/src/__tests__/gpu-lod-evidence.ts +188 -0
  228. package/src/__tests__/gpu-lod-fallback.integration.test.ts +10 -0
  229. package/src/__tests__/gpu-lod-indirect.integration.test.ts +40 -0
  230. package/src/__tests__/gpu-lod-parity.dawn.test.ts +29 -0
  231. package/src/__tests__/gpu-lod-readiness.integration.test.ts +52 -0
  232. package/src/__tests__/gpu-lod-schema.unit.test.ts +66 -0
  233. package/src/__tests__/gpu-lod-selector.unit.test.ts +28 -0
  234. package/src/__tests__/gpu-lod-silhouette.browser.test.ts +13 -0
  235. package/src/__tests__/gpu-resource-store-stride.unit.test.ts +66 -1
  236. package/src/__tests__/gpu-scene.dawn.test.ts +8 -1
  237. package/src/__tests__/gpu-scene.unit.test.ts +49 -3
  238. package/src/__tests__/light-buffer-layout.unit.test.ts +113 -3
  239. package/src/__tests__/light-inspection.unit.test.ts +55 -0
  240. package/src/__tests__/light-resource-error.unit.test.ts +43 -0
  241. package/src/__tests__/lighting-scene-roundtrip.integration.test.ts +109 -0
  242. package/src/__tests__/lod-bounds.unit.test.ts +42 -0
  243. package/src/__tests__/lod-cpu-lane.integration.test.ts +55 -0
  244. package/src/__tests__/lod-cpu-no-change.perf.test.ts +19 -0
  245. package/src/__tests__/lod-gpu-frame-timing.unit.test.ts +49 -0
  246. package/src/__tests__/lod-multi-view.integration.test.ts +63 -0
  247. package/src/__tests__/lod-occlusion-device-loss.integration.test.ts +27 -0
  248. package/src/__tests__/lod-occlusion-final-decision.integration.test.ts +47 -0
  249. package/src/__tests__/lod-occlusion-inspection.integration.test.ts +217 -0
  250. package/src/__tests__/lod-occlusion-inspection.schema.test-d.ts +14 -0
  251. package/src/__tests__/lod-occlusion-inspection.unit.test.ts +72 -0
  252. package/src/__tests__/lod-occlusion-visual-evidence.ts +91 -0
  253. package/src/__tests__/lod-occlusion.browser.test.ts +15 -0
  254. package/src/__tests__/lod-occlusion.dawn.test.ts +20 -0
  255. package/src/__tests__/lod-selector.profile.test.ts +35 -0
  256. package/src/__tests__/lod-selector.unit.test.ts +68 -0
  257. package/src/__tests__/lod-visibility-identity.unit.test.ts +198 -0
  258. package/src/__tests__/lod-visibility-lifecycle.integration.test.ts +132 -0
  259. package/src/__tests__/ltc-resources.unit.test.ts +32 -0
  260. package/src/__tests__/material-contract-inventory.unit.test.ts +17 -0
  261. package/src/__tests__/occlusion-confidence.unit.test.ts +168 -0
  262. package/src/__tests__/occlusion-invalidation.integration.test.ts +37 -0
  263. package/src/__tests__/occlusion-pass-routing.unit.test.ts +16 -0
  264. package/src/__tests__/occlusion-pass.dawn.test.ts +20 -0
  265. package/src/__tests__/occlusion-pass.integration.test.ts +43 -0
  266. package/src/__tests__/occlusion-pass.rhi-null.test.ts +22 -0
  267. package/src/__tests__/occlusion-query-pool.rhi-null.test.ts +21 -0
  268. package/src/__tests__/occlusion-query-pool.unit.test.ts +95 -0
  269. package/src/__tests__/occlusion-query-ticket.integration.test.ts +47 -0
  270. package/src/__tests__/occlusion-query.dawn.test.ts +16 -0
  271. package/src/__tests__/occlusion-query.evidence.ts +178 -0
  272. package/src/__tests__/occlusion-query.rhi-null.test.ts +31 -0
  273. package/src/__tests__/occlusion-runtime.unit.test.ts +120 -0
  274. package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +196 -0
  275. package/src/__tests__/probe-bindings.unit.test.ts +200 -0
  276. package/src/__tests__/probe-blend-numeric.unit.test.ts +67 -0
  277. package/src/__tests__/probe-blend-oracle.unit.test.ts +136 -0
  278. package/src/__tests__/probe-capacity.integration.test.ts +97 -0
  279. package/src/__tests__/probe-recovery.integration.test.ts +104 -0
  280. package/src/__tests__/probe-scene-dirty.integration.test.ts +211 -0
  281. package/src/__tests__/profiler-phase-catalog.test.ts +4 -1
  282. package/src/__tests__/profiler-phase-token.test.ts +1 -0
  283. package/src/__tests__/rect-area-boundary.unit.test.ts +24 -0
  284. package/src/__tests__/reflection-fallback-graph.unit.test.ts +67 -0
  285. package/src/__tests__/reflection-fallback-inspection.unit.test.ts +77 -0
  286. package/src/__tests__/reflection-fallback-lifecycle.integration.test.ts +21 -0
  287. package/src/__tests__/reflection-fallback-projection.unit.test.ts +90 -0
  288. package/src/__tests__/reflection-fallback-source.unit.test.ts +32 -0
  289. package/src/__tests__/reflection-probe-owner-placement.unit.test.ts +39 -0
  290. package/src/__tests__/reflection-probe-record-owner.characterization.unit.test.ts +120 -0
  291. package/src/__tests__/render-error-code-owner.test-d.ts +1 -0
  292. package/src/__tests__/render-feature-docs.unit.test.ts +12 -7
  293. package/src/__tests__/render-graph-fallback.integration.test.ts +24 -0
  294. package/src/__tests__/render-perf/extended-lighting.bench.test.ts +66 -0
  295. package/src/__tests__/render-scene-projection.unit.test.ts +447 -18
  296. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +76 -0
  297. package/src/__tests__/skin-motion-regression.unit.test.ts +22 -0
  298. package/src/__tests__/skinned-shadow-caster.test.ts +2 -2
  299. package/src/__tests__/spot-modifier-projection.unit.test.ts +70 -0
  300. package/src/__tests__/ssr-admission-matrix.integration.test.ts +206 -0
  301. package/src/__tests__/ssr-composition.unit.test.ts +46 -0
  302. package/src/__tests__/ssr-fallback-zero-work.bench.test.ts +94 -0
  303. package/src/__tests__/ssr-recovery-errors.unit.test.ts +58 -0
  304. package/src/__tests__/standard-cluster-variant-resolution.unit.test.ts +67 -0
  305. package/src/__tests__/standard-clustered-pipeline.unit.test.ts +7 -0
  306. package/src/__tests__/standard-lighting-inspection.integration.test.ts +1 -1
  307. package/src/__tests__/standard-lighting-write.unit.test.ts +66 -2
  308. package/src/__tests__/temporal-fallback-generation.integration.test.ts +16 -0
  309. package/src/__tests__/temporal-view.unit.test.ts +3 -3
  310. package/src/__tests__/transmission-prewarm.unit.test.ts +69 -0
  311. package/src/__tests__/visibility-budget.integration.test.ts +140 -0
  312. package/src/__tests__/visibility-budget.unit.test.ts +70 -0
  313. package/src/__tests__/visibility-extract.integration.test.ts +11 -6
  314. package/src/__tests__/volumetric-fog-point-spot-accumulation.integration.test.ts +6 -3
  315. package/src/__tests__/volumetric-fog-stage-readback.dawn.test.ts +31 -28
  316. package/src/assembly/backend-contract.ts +13 -0
  317. package/src/assembly/device-feature-admission.ts +16 -9
  318. package/src/assembly/factory.ts +547 -93
  319. package/src/assembly/host-contract.ts +10 -0
  320. package/src/cluster-binner.ts +31 -0
  321. package/src/components/__tests__/lighting-authoring.unit.test.ts +39 -0
  322. package/src/components/__tests__/rect-area-light.unit.test.ts +33 -0
  323. package/src/components/index.ts +3 -0
  324. package/src/components/light-helpers.ts +52 -1
  325. package/src/components/light-probe.ts +13 -0
  326. package/src/components/rect-area-light.ts +15 -0
  327. package/src/components/scene-instance.ts +6 -0
  328. package/src/components/spot-light.ts +3 -0
  329. package/src/device/__tests__/extended-lighting-recovery.unit.test.ts +24 -0
  330. package/src/device/gpu-residency.ts +140 -9
  331. package/src/device/lifecycle-transaction.ts +12 -0
  332. package/src/environment/lifecycle.ts +8 -1
  333. package/src/errors/gpu-driven.ts +92 -0
  334. package/src/errors/index.ts +1 -0
  335. package/src/errors/recover.ts +32 -0
  336. package/src/errors/render.ts +55 -1
  337. package/src/extract/camera.ts +3 -3
  338. package/src/extract/gpu-driven.ts +108 -0
  339. package/src/extract/visibility.ts +0 -23
  340. package/src/gpu-driven/batch-topology.ts +206 -28
  341. package/src/gpu-driven/prepared-draw.ts +208 -0
  342. package/src/gpu-driven/production-raster.ts +953 -41
  343. package/src/gpu-driven/view-gpu.ts +411 -29
  344. package/src/gpu-scene-schema.ts +13 -0
  345. package/src/gpu-scene.ts +134 -65
  346. package/src/gpu-usage.ts +0 -1
  347. package/src/hdrp-buffers.ts +6 -6
  348. package/src/index.ts +74 -0
  349. package/src/inspection-types.ts +220 -1
  350. package/src/internal.ts +38 -0
  351. package/src/light-buffer-layout.ts +154 -324
  352. package/src/materials.ts +16 -2
  353. package/src/pbr-pipeline.ts +151 -45
  354. package/src/pipeline/standard-forward-lane.ts +47 -0
  355. package/src/pipeline/standard-lighting/graph.ts +2 -1
  356. package/src/pipeline/standard-lighting/light-frame.ts +3 -2
  357. package/src/pipeline/standard-lighting/topology.ts +1 -1
  358. package/src/pipeline/standard-lighting/transport.ts +2 -1
  359. package/src/pipeline/standard-pipeline.ts +2 -0
  360. package/src/pipeline-builder.ts +5 -2
  361. package/src/pipeline-spec.ts +12 -1
  362. package/src/plugin.ts +4 -0
  363. package/src/prepare/__tests__/extended-lighting-capability.unit.test.ts +56 -0
  364. package/src/prepare/extended-lighting/ltc-resources.ts +136 -0
  365. package/src/prepare/extended-lighting/resources.ts +389 -0
  366. package/src/prepare/extended-lighting/spot-modifiers.ts +194 -0
  367. package/src/prepare/extended-lighting/state.ts +92 -0
  368. package/src/record/__tests__/gpu-timing.unit.test.ts +177 -0
  369. package/src/record/__tests__/temporal-payload.unit.test.ts +23 -1
  370. package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +20 -1
  371. package/src/record/frame-lighting.ts +252 -53
  372. package/src/record/frame-snapshot.ts +57 -12
  373. package/src/record/frame.ts +410 -93
  374. package/src/record/gpu-timing.ts +108 -44
  375. package/src/record/helpers.ts +0 -58
  376. package/src/record/main-pass-geometry.ts +204 -39
  377. package/src/record/main-pass-material.ts +8 -1
  378. package/src/record/main-pass-sprite-draws.ts +52 -85
  379. package/src/record/mesh-ssbo.ts +47 -36
  380. package/src/record/render-context.ts +65 -4
  381. package/src/record/shadow-pass.ts +66 -40
  382. package/src/record/typed-frame-graph.ts +225 -83
  383. package/src/record/view-ubo.ts +14 -24
  384. package/src/reflection/inspection.ts +67 -0
  385. package/src/reflection/projection.ts +196 -1
  386. package/src/reflection/record-owner.ts +1283 -0
  387. package/src/render-contract.ts +17 -5
  388. package/src/render-pipeline.ts +5 -0
  389. package/src/render-system-extract.ts +493 -134
  390. package/src/render-system.ts +811 -615
  391. package/src/scene/__tests__/changed-block-summary.candidate.test.ts +36 -0
  392. package/src/scene/probe-blend-record.ts +54 -0
  393. package/src/scene/probe-blend.ts +813 -0
  394. package/src/scene/render-scene-types.ts +0 -7
  395. package/src/scene/render-scene.ts +848 -183
  396. package/src/scene/visibility/bounds.ts +57 -0
  397. package/src/scene/visibility/budget.ts +40 -0
  398. package/src/scene/visibility/facet.ts +400 -0
  399. package/src/scene/visibility/gpu-frame-timing.ts +101 -0
  400. package/src/scene/visibility/gpu-lod.ts +119 -0
  401. package/src/scene/visibility/inspection.ts +313 -0
  402. package/src/scene/visibility/lod-selector.ts +84 -0
  403. package/src/scene/visibility/occlusion-confidence.ts +478 -0
  404. package/src/scene/visibility/occlusion-pass.ts +165 -0
  405. package/src/scene/visibility/occlusion-query-pool.ts +178 -0
  406. package/src/scene/visibility/occlusion-runtime.ts +672 -0
  407. package/src/scene/visibility/types.ts +50 -0
  408. package/src/ssr/admission.ts +451 -0
  409. package/src/ssr/composition.ts +73 -0
  410. package/src/ssr/errors.ts +76 -0
  411. package/src/ssr/identity.ts +12 -0
  412. package/src/systems/skin-palette-allocator.ts +1 -1
  413. package/src/temporal/frame-coordinator.ts +26 -0
  414. package/src/temporal/frame.ts +14 -1
  415. package/src/temporal/view.ts +11 -0
  416. package/src/tilemap-chunk-extract-system.ts +4 -4
  417. package/src/typed-render-graph-primitives.ts +48 -0
  418. package/src/volume/passes.ts +46 -49
  419. package/dist/.tsbuildinfo +0 -1
  420. package/dist/chunk-5YADUCA7.mjs.map +0 -1
  421. package/dist/chunk-6OPXDQ2E.mjs.map +0 -1
  422. package/dist/chunk-BXBQQKFQ.mjs +0 -67
  423. package/dist/chunk-BXBQQKFQ.mjs.map +0 -1
  424. package/dist/chunk-FS2KNUQH.mjs.map +0 -1
  425. package/dist/chunk-PCHBPYML.mjs.map +0 -1
  426. package/dist/chunk-VFPLTKCU.mjs.map +0 -1
@@ -0,0 +1,57 @@
1
+ export interface ConservativeBounds {
2
+ readonly min: readonly [number, number, number];
3
+ readonly max: readonly [number, number, number];
4
+ }
5
+
6
+ export interface BoundsCandidate {
7
+ readonly bounds: ConservativeBounds | undefined;
8
+ readonly transparent: boolean;
9
+ readonly deformed: boolean;
10
+ }
11
+
12
+ export function conservativeBounds(
13
+ localMin: readonly [number, number, number],
14
+ localMax: readonly [number, number, number],
15
+ ): ConservativeBounds {
16
+ return {
17
+ min: [
18
+ Math.min(localMin[0], localMax[0]),
19
+ Math.min(localMin[1], localMax[1]),
20
+ Math.min(localMin[2], localMax[2]),
21
+ ],
22
+ max: [
23
+ Math.max(localMin[0], localMax[0]),
24
+ Math.max(localMin[1], localMax[1]),
25
+ Math.max(localMin[2], localMax[2]),
26
+ ],
27
+ };
28
+ }
29
+
30
+ export function encloseBounds(
31
+ boundsList: readonly (ConservativeBounds | undefined)[],
32
+ ): ConservativeBounds | undefined {
33
+ const present = boundsList.filter((bounds): bounds is ConservativeBounds => bounds !== undefined);
34
+ if (present.length === 0) return undefined;
35
+ const min: [number, number, number] = [Infinity, Infinity, Infinity];
36
+ const max: [number, number, number] = [-Infinity, -Infinity, -Infinity];
37
+ for (const bounds of present) {
38
+ for (let axis = 0; axis < 3; axis += 1) {
39
+ const lower = axis === 0 ? bounds.min[0] : axis === 1 ? bounds.min[1] : bounds.min[2];
40
+ const upper = axis === 0 ? bounds.max[0] : axis === 1 ? bounds.max[1] : bounds.max[2];
41
+ min[axis] = Math.min(min[axis] ?? Infinity, lower ?? 0);
42
+ max[axis] = Math.max(max[axis] ?? -Infinity, upper ?? 0);
43
+ }
44
+ }
45
+ return { min, max };
46
+ }
47
+
48
+ export function conservativeVisibleCandidate(
49
+ candidate: BoundsCandidate,
50
+ ):
51
+ | { readonly visible: true; readonly queryable: false }
52
+ | { readonly visible: false; readonly queryable: true; readonly bounds: ConservativeBounds } {
53
+ if (candidate.bounds === undefined || candidate.transparent || candidate.deformed) {
54
+ return { visible: true, queryable: false };
55
+ }
56
+ return { visible: false, queryable: true, bounds: candidate.bounds };
57
+ }
@@ -0,0 +1,40 @@
1
+ export const DEFAULT_CONFIGURED_QUERY_BUDGET = 2048;
2
+ export const MAX_QUERY_BUDGET = 4096;
3
+ export const VISIBILITY_CANDIDATE_COUNT = 100_000;
4
+ export const VISIBILITY_OCCLUDED_COUNT = 90_000;
5
+
6
+ export interface VisibilityBudget {
7
+ readonly configuredQueryBudget: number;
8
+ readonly effectiveQueryBudget: number;
9
+ readonly oneSweepCandidates: number;
10
+ readonly oneSweepOccluded: number;
11
+ readonly settleSubmits: number;
12
+ readonly retestSubmits: number;
13
+ readonly expirySubmits: number;
14
+ }
15
+
16
+ /** Derive every renderer visibility window from one bounded query budget. */
17
+ export function createVisibilityBudget(
18
+ configuredQueryBudget = DEFAULT_CONFIGURED_QUERY_BUDGET,
19
+ ): VisibilityBudget {
20
+ if (!Number.isSafeInteger(configuredQueryBudget) || configuredQueryBudget <= 0) {
21
+ throw new RangeError('configuredQueryBudget must be a positive safe integer');
22
+ }
23
+ const effectiveQueryBudget = Math.min(configuredQueryBudget, MAX_QUERY_BUDGET);
24
+ const oneSweepCandidates = oneSweep(VISIBILITY_CANDIDATE_COUNT, effectiveQueryBudget);
25
+ const oneSweepOccluded = oneSweep(VISIBILITY_OCCLUDED_COUNT, effectiveQueryBudget);
26
+ const retestSubmits = Math.max(6, oneSweepOccluded);
27
+ return Object.freeze({
28
+ configuredQueryBudget,
29
+ effectiveQueryBudget,
30
+ oneSweepCandidates,
31
+ oneSweepOccluded,
32
+ settleSubmits: 2 * oneSweepCandidates,
33
+ retestSubmits,
34
+ expirySubmits: Math.max(8, retestSubmits + oneSweepOccluded),
35
+ });
36
+ }
37
+
38
+ function oneSweep(candidateCount: number, effectiveQueryBudget: number): number {
39
+ return Math.ceil(candidateCount / effectiveQueryBudget);
40
+ }
@@ -0,0 +1,400 @@
1
+ import { createVisibilityBudget, type VisibilityBudget } from './budget';
2
+ import {
3
+ type ConfidenceEvent,
4
+ type ConfidenceState,
5
+ OcclusionConfidenceScheduler,
6
+ shouldIssueRetest,
7
+ } from './occlusion-confidence';
8
+ import {
9
+ type PrimitiveKey,
10
+ primitiveKeyId,
11
+ type ViewKey,
12
+ type VisibilityCandidate,
13
+ type VisibilityFacetInspection,
14
+ viewKeyId,
15
+ } from './types';
16
+
17
+ interface ViewState {
18
+ readonly key: ViewKey;
19
+ epoch: number;
20
+ readonly rows: Map<string, VisibilityCandidate>;
21
+ }
22
+
23
+ interface QueryEntry {
24
+ readonly view: ViewKey;
25
+ readonly primitive: PrimitiveKey;
26
+ }
27
+
28
+ interface QueryNode {
29
+ readonly viewId: string;
30
+ readonly key: string;
31
+ previous: string | undefined;
32
+ next: string | undefined;
33
+ }
34
+
35
+ interface QueryQueue {
36
+ head: string | undefined;
37
+ tail: string | undefined;
38
+ }
39
+
40
+ /**
41
+ * Renderer-owned CPU visibility state indexed by ViewKey x PrimitiveKey.
42
+ *
43
+ * Attachment order is intentionally not part of the key. This keeps world
44
+ * reorder, detach, and slot ABA recovery generation-safe without a second
45
+ * lane-specific visibility map.
46
+ */
47
+ export class VisibilityFacetStore {
48
+ private readonly views = new Map<string, ViewState>();
49
+ private readonly confidence: OcclusionConfidenceScheduler;
50
+ private visibilityRevision = 0;
51
+ private drawRevision = 0;
52
+ private queryRevision = 0;
53
+ private readonly queryEntries = new Map<string, QueryEntry>();
54
+ private readonly readyQueryKeys = new Set<string>();
55
+ private readonly inFlightQueryKeys = new Set<string>();
56
+ private readonly queryQueues = new Map<string, QueryQueue>();
57
+ /** Intrusive FIFO nodes make removal/requeue O(1), so dequeue never walks stale rows. */
58
+ private readonly queryNodes = new Map<string, QueryNode>();
59
+
60
+ constructor(private readonly budget: VisibilityBudget = createVisibilityBudget()) {
61
+ this.confidence = new OcclusionConfidenceScheduler(budget);
62
+ }
63
+
64
+ visibilityBudget(): VisibilityBudget {
65
+ return this.budget;
66
+ }
67
+
68
+ /** Monotonic token for confidence changes that can alter draw admission. */
69
+ get revision(): number {
70
+ return this.visibilityRevision;
71
+ }
72
+
73
+ /** Revision for the bounded query-candidate admission set only. */
74
+ get queryRevisionValue(): number {
75
+ return this.queryRevision;
76
+ }
77
+
78
+ /** Revision for the active draw admission set (status/queryable identity). */
79
+ get drawRevisionValue(): number {
80
+ return this.drawRevision;
81
+ }
82
+
83
+ activateView(key: ViewKey): void {
84
+ const id = viewKeyId(key);
85
+ const existing = this.views.get(id);
86
+ if (existing !== undefined) return;
87
+ this.views.set(id, { key, epoch: 0, rows: new Map() });
88
+ }
89
+
90
+ retireView(key: ViewKey): void {
91
+ const id = viewKeyId(key);
92
+ const state = this.views.get(id);
93
+ this.views.delete(id);
94
+ if (state !== undefined) {
95
+ this.visibilityRevision += 1;
96
+ this.drawRevision += 1;
97
+ this.queryRevision += 1;
98
+ for (const primitive of state.rows.keys()) {
99
+ this.removeQueryKey(`${id}|${primitive}`);
100
+ }
101
+ }
102
+ }
103
+
104
+ detachAttachment(attachmentId: string): void {
105
+ let detached = false;
106
+ for (const [id, state] of this.views) {
107
+ if (state.key.attachmentId === attachmentId) {
108
+ this.views.delete(id);
109
+ for (const primitive of state.rows.keys()) {
110
+ this.removeQueryKey(`${id}|${primitive}`);
111
+ }
112
+ detached = true;
113
+ }
114
+ }
115
+ if (detached) {
116
+ this.visibilityRevision += 1;
117
+ this.drawRevision += 1;
118
+ this.queryRevision += 1;
119
+ }
120
+ }
121
+
122
+ /** Drop every view/row when a composition owner is detached. */
123
+ clear(): void {
124
+ if (this.views.size > 0 || this.visibilityRevision > 0) {
125
+ this.visibilityRevision += 1;
126
+ this.drawRevision += 1;
127
+ this.queryRevision += 1;
128
+ }
129
+ this.views.clear();
130
+ this.confidence.clear();
131
+ this.queryEntries.clear();
132
+ this.readyQueryKeys.clear();
133
+ this.inFlightQueryKeys.clear();
134
+ this.queryQueues.clear();
135
+ this.queryNodes.clear();
136
+ }
137
+
138
+ reorderAttachments(_attachmentIds: readonly string[]): void {
139
+ // Attachment order is deliberately not stored. Keys remain stable when
140
+ // the caller changes the order of its world list.
141
+ }
142
+
143
+ setCandidate(view: ViewKey, primitive: PrimitiveKey, candidate: VisibilityCandidate): boolean {
144
+ const state = this.views.get(viewKeyId(view));
145
+ if (state === undefined || primitive.attachmentId !== view.attachmentId) return false;
146
+ const primitiveId = primitiveKeyId(primitive);
147
+ const key = `${viewKeyId(view)}|${primitiveId}`;
148
+ const previous = state.rows.get(primitiveId);
149
+ if (
150
+ previous === undefined ||
151
+ previous.level !== candidate.level ||
152
+ previous.confidence !== candidate.confidence
153
+ ) {
154
+ this.visibilityRevision += 1;
155
+ this.drawRevision += 1;
156
+ }
157
+ state.rows.set(primitiveId, { ...candidate });
158
+ this.queryEntries.set(key, { view, primitive });
159
+ this.syncQueryKey(key, this.confidence.ensure(key));
160
+ return true;
161
+ }
162
+
163
+ /** Remove rows that are no longer queryable in the extracted frame. */
164
+ pruneCandidates(view: ViewKey, activePrimitives: ReadonlySet<string>): void {
165
+ const state = this.views.get(viewKeyId(view));
166
+ if (state === undefined) return;
167
+ for (const primitiveId of [...state.rows.keys()]) {
168
+ if (activePrimitives.has(primitiveId)) continue;
169
+ state.rows.delete(primitiveId);
170
+ this.removeQueryKey(`${viewKeyId(view)}|${primitiveId}`);
171
+ this.visibilityRevision += 1;
172
+ this.drawRevision += 1;
173
+ this.queryRevision += 1;
174
+ }
175
+ }
176
+
177
+ applyConfidence(view: ViewKey, primitive: PrimitiveKey, event: ConfidenceEvent): ConfidenceState {
178
+ const key = `${viewKeyId(view)}|${primitiveKeyId(primitive)}`;
179
+ const before = this.confidence.ensure(key);
180
+ const after =
181
+ event.type === 'submit' && this.inFlightQueryKeys.has(key)
182
+ ? this.confidence.applyInFlightSubmit(key, event)
183
+ : this.confidence.apply(key, event);
184
+ if (event.type === 'failure') this.inFlightQueryKeys.delete(key);
185
+ if (
186
+ before.status !== after.status ||
187
+ before.zeroStreak !== after.zeroStreak ||
188
+ before.successfulSubmits !== after.successfulSubmits ||
189
+ before.queryable !== after.queryable ||
190
+ before.dirty !== after.dirty ||
191
+ before.lastResultGeneration !== after.lastResultGeneration
192
+ ) {
193
+ this.visibilityRevision += 1;
194
+ }
195
+ if (before.status !== after.status || before.queryable !== after.queryable) {
196
+ this.drawRevision += 1;
197
+ }
198
+ const beforeNeedsQuery =
199
+ (before.dirty && before.successfulSubmits === 0) || shouldIssueRetest(before, this.budget);
200
+ const afterNeedsQuery =
201
+ (after.dirty && after.successfulSubmits === 0) || shouldIssueRetest(after, this.budget);
202
+ if (beforeNeedsQuery !== afterNeedsQuery) this.queryRevision += 1;
203
+ this.syncQueryKey(key, after);
204
+ return after;
205
+ }
206
+
207
+ /** Advance hidden rows on a successful frame, excluding rows queried now. */
208
+ advanceSuccessfulSubmits(
209
+ submissionGeneration: number,
210
+ excluded?: readonly { readonly view: ViewKey; readonly primitive: PrimitiveKey }[],
211
+ ): void {
212
+ let excludedKeys: ReadonlySet<string> | undefined;
213
+ if (excluded !== undefined && excluded.length > 0) {
214
+ const keys = new Set<string>();
215
+ for (const { view, primitive } of excluded) {
216
+ keys.add(`${viewKeyId(view)}|${primitiveKeyId(primitive)}`);
217
+ }
218
+ excludedKeys = keys;
219
+ }
220
+ const result = this.confidence.advanceSuccessfulSubmits(submissionGeneration, excludedKeys);
221
+ if (result.changed) {
222
+ this.visibilityRevision += 1;
223
+ }
224
+ if (result.drawChangedKeys.length > 0) this.drawRevision += 1;
225
+ if (result.queryChangedKeys.length > 0) this.queryRevision += 1;
226
+ for (const key of [...result.drawChangedKeys, ...result.queryChangedKeys]) {
227
+ this.syncQueryKey(key, this.confidence.get(key));
228
+ }
229
+ }
230
+
231
+ getConfidence(view: ViewKey, primitive: PrimitiveKey): ConfidenceState {
232
+ return this.confidence.ensure(`${viewKeyId(view)}|${primitiveKeyId(primitive)}`);
233
+ }
234
+
235
+ /** Whether this candidate needs a bounded query this frame. */
236
+ shouldQuery(view: ViewKey, primitive: PrimitiveKey): boolean {
237
+ const state = this.getConfidence(view, primitive);
238
+ return (state.dirty && state.successfulSubmits === 0) || shouldIssueRetest(state, this.budget);
239
+ }
240
+
241
+ drainConfidenceRetests(limit: number): string[] {
242
+ return this.confidence.drainRetests(limit);
243
+ }
244
+
245
+ /**
246
+ * Remove a bounded page of ready query work for one view. Queue entries are
247
+ * identity keyed, so confidence completion and resource failure can safely
248
+ * requeue the same primitive without scanning the scene.
249
+ */
250
+ dequeueQueryCandidates(view: ViewKey, limit: number): readonly QueryEntry[] {
251
+ if (!Number.isSafeInteger(limit) || limit <= 0) return [];
252
+ const viewId = viewKeyId(view);
253
+ const queue = this.queryQueues.get(viewId);
254
+ if (queue === undefined) return [];
255
+ const result: QueryEntry[] = [];
256
+ while (queue.head !== undefined && result.length < limit) {
257
+ const key = queue.head;
258
+ this.removeQueuedKey(key);
259
+ if (!this.readyQueryKeys.delete(key)) continue;
260
+ const entry = this.queryEntries.get(key);
261
+ if (entry === undefined || viewKeyId(entry.view) !== viewId) continue;
262
+ this.inFlightQueryKeys.add(key);
263
+ result.push(entry);
264
+ }
265
+ return result;
266
+ }
267
+
268
+ /** Put a candidate back after a query page could not be reserved. */
269
+ requeueQueryCandidate(view: ViewKey, primitive: PrimitiveKey): void {
270
+ const key = `${viewKeyId(view)}|${primitiveKeyId(primitive)}`;
271
+ this.inFlightQueryKeys.delete(key);
272
+ this.syncQueryKey(key, this.confidence.get(key));
273
+ }
274
+
275
+ getCandidate(view: ViewKey, primitive: PrimitiveKey): VisibilityCandidate | undefined {
276
+ const state = this.views.get(viewKeyId(view));
277
+ if (state === undefined || primitive.attachmentId !== view.attachmentId) return undefined;
278
+ const candidate = state.rows.get(primitiveKeyId(primitive));
279
+ return candidate === undefined ? undefined : { ...candidate };
280
+ }
281
+
282
+ beginViewEpoch(view: ViewKey): number {
283
+ const state = this.views.get(viewKeyId(view));
284
+ if (state === undefined) return -1;
285
+ for (const primitive of state.rows.keys()) {
286
+ this.removeQueryKey(`${viewKeyId(view)}|${primitive}`);
287
+ }
288
+ state.epoch += 1;
289
+ state.rows.clear();
290
+ this.visibilityRevision += 1;
291
+ this.drawRevision += 1;
292
+ this.queryRevision += 1;
293
+ return state.epoch;
294
+ }
295
+
296
+ applyCompletion(
297
+ view: ViewKey,
298
+ primitive: PrimitiveKey,
299
+ epoch: number,
300
+ candidate: VisibilityCandidate,
301
+ ): boolean {
302
+ const state = this.views.get(viewKeyId(view));
303
+ if (
304
+ state === undefined ||
305
+ state.epoch !== epoch ||
306
+ primitive.attachmentId !== view.attachmentId
307
+ ) {
308
+ return false;
309
+ }
310
+ const primitiveId = primitiveKeyId(primitive);
311
+ const previous = state.rows.get(primitiveId);
312
+ if (
313
+ previous === undefined ||
314
+ previous.level !== candidate.level ||
315
+ previous.confidence !== candidate.confidence
316
+ ) {
317
+ this.visibilityRevision += 1;
318
+ this.drawRevision += 1;
319
+ }
320
+ state.rows.set(primitiveId, { ...candidate });
321
+ const key = `${viewKeyId(view)}|${primitiveId}`;
322
+ this.queryEntries.set(key, { view, primitive });
323
+ this.syncQueryKey(key, this.confidence.ensure(key));
324
+ return true;
325
+ }
326
+
327
+ inspect(): VisibilityFacetInspection {
328
+ let facetRows = 0;
329
+ for (const state of this.views.values()) facetRows += state.rows.size;
330
+ return { owner: 'persistent-render-scene', activeViews: this.views.size, facetRows };
331
+ }
332
+
333
+ private syncQueryKey(key: string, state: ConfidenceState | undefined): void {
334
+ if (state === undefined) return;
335
+ const shouldQuery =
336
+ (state.dirty && state.successfulSubmits === 0) || shouldIssueRetest(state, this.budget);
337
+ if (!shouldQuery) {
338
+ this.removeQueuedKey(key);
339
+ this.readyQueryKeys.delete(key);
340
+ this.inFlightQueryKeys.delete(key);
341
+ return;
342
+ }
343
+ if (this.readyQueryKeys.has(key) || this.inFlightQueryKeys.has(key)) return;
344
+ const entry = this.queryEntries.get(key);
345
+ if (entry === undefined) return;
346
+ this.readyQueryKeys.add(key);
347
+ const viewId = viewKeyId(entry.view);
348
+ const queue = this.queryQueues.get(viewId) ?? { head: undefined, tail: undefined };
349
+ const node: QueryNode = {
350
+ viewId,
351
+ key,
352
+ previous: queue.tail,
353
+ next: undefined,
354
+ };
355
+ if (queue.tail !== undefined) {
356
+ const tail = this.queryNodes.get(queue.tail);
357
+ if (tail !== undefined) tail.next = key;
358
+ } else {
359
+ queue.head = key;
360
+ }
361
+ queue.tail = key;
362
+ this.queryQueues.set(viewId, queue);
363
+ this.queryNodes.set(key, node);
364
+ }
365
+
366
+ private removeQueryKey(key: string): void {
367
+ this.confidence.remove(key);
368
+ this.queryEntries.delete(key);
369
+ this.removeQueuedKey(key);
370
+ this.readyQueryKeys.delete(key);
371
+ this.inFlightQueryKeys.delete(key);
372
+ }
373
+
374
+ private removeQueuedKey(key: string): void {
375
+ const node = this.queryNodes.get(key);
376
+ if (node === undefined) return;
377
+ const queue = this.queryQueues.get(node.viewId);
378
+ this.queryNodes.delete(key);
379
+ if (queue === undefined) return;
380
+ if (node.previous === undefined) queue.head = node.next;
381
+ else {
382
+ const previous = this.queryNodes.get(node.previous);
383
+ if (previous !== undefined) previous.next = node.next;
384
+ }
385
+ if (node.next === undefined) queue.tail = node.previous;
386
+ else {
387
+ const next = this.queryNodes.get(node.next);
388
+ if (next !== undefined) next.previous = node.previous;
389
+ }
390
+ if (queue.head === undefined) this.queryQueues.delete(node.viewId);
391
+ }
392
+ }
393
+
394
+ export type {
395
+ PrimitiveKey,
396
+ ViewKey,
397
+ VisibilityCandidate,
398
+ VisibilityFacetInspection,
399
+ } from './types';
400
+ export { primitiveKey, primitiveKeyId, viewKey, viewKeyId } from './types';
@@ -0,0 +1,101 @@
1
+ /** Renderer-owned timestamp facts spanning one graph and one queue submit. */
2
+
3
+ export interface GpuFrameTimingProtocol {
4
+ readonly firstMarker: 'frame.first-pass';
5
+ readonly terminalMarker: 'occlusion.resolve-copy';
6
+ readonly sameGraph: true;
7
+ readonly sameSubmit: true;
8
+ }
9
+
10
+ export interface GpuFrameTiming {
11
+ readonly frameId: number;
12
+ readonly deviceGeneration: number;
13
+ readonly protocol: GpuFrameTimingProtocol;
14
+ aborted: boolean;
15
+ submitId: number | undefined;
16
+ }
17
+
18
+ export interface GpuFrameTimingRecord {
19
+ readonly firstPassTimestampNs: number;
20
+ readonly occlusionResolveTimestampNs: number;
21
+ readonly submitted: boolean;
22
+ readonly graphGeneration: number;
23
+ readonly submitId: number;
24
+ }
25
+
26
+ export interface GpuFrameTimingSample {
27
+ readonly frameId: number;
28
+ readonly deviceGeneration: number;
29
+ readonly durationUs: number;
30
+ readonly protocol: GpuFrameTimingProtocol;
31
+ readonly graphGeneration: number;
32
+ readonly submitId: number;
33
+ }
34
+
35
+ export interface GpuFrameTimingSummary {
36
+ readonly medianUs: number;
37
+ readonly p95Us: number;
38
+ readonly samples: number;
39
+ }
40
+
41
+ const PROTOCOL: GpuFrameTimingProtocol = Object.freeze({
42
+ firstMarker: 'frame.first-pass',
43
+ terminalMarker: 'occlusion.resolve-copy',
44
+ sameGraph: true,
45
+ sameSubmit: true,
46
+ });
47
+
48
+ export function createGpuFrameTiming(input: {
49
+ readonly frameId: number;
50
+ readonly deviceGeneration: number;
51
+ }): GpuFrameTiming {
52
+ return { ...input, protocol: PROTOCOL, aborted: false, submitId: undefined };
53
+ }
54
+
55
+ export function recordGpuFrameTiming(
56
+ timing: GpuFrameTiming,
57
+ record: GpuFrameTimingRecord,
58
+ ): GpuFrameTimingSample {
59
+ if (!record.submitted) {
60
+ timing.aborted = true;
61
+ throw new Error('GPU frame timing requires a submitted frame');
62
+ }
63
+ if (timing.aborted) throw new Error('GPU frame timing cannot cross a failed submit');
64
+ if (timing.submitId !== undefined && timing.submitId !== record.submitId) {
65
+ throw new Error('GPU frame timing markers must share one submit');
66
+ }
67
+ if (
68
+ !Number.isFinite(record.firstPassTimestampNs) ||
69
+ !Number.isFinite(record.occlusionResolveTimestampNs)
70
+ ) {
71
+ throw new Error('GPU frame timing requires finite timestamps');
72
+ }
73
+ if (record.occlusionResolveTimestampNs < record.firstPassTimestampNs) {
74
+ throw new Error('GPU frame timing terminal marker precedes first marker');
75
+ }
76
+ timing.submitId = record.submitId;
77
+ return Object.freeze({
78
+ frameId: timing.frameId,
79
+ deviceGeneration: timing.deviceGeneration,
80
+ durationUs: (record.occlusionResolveTimestampNs - record.firstPassTimestampNs) / 1_000,
81
+ protocol: PROTOCOL,
82
+ graphGeneration: record.graphGeneration,
83
+ submitId: record.submitId,
84
+ });
85
+ }
86
+
87
+ function nearestRank(values: readonly number[], percentile: number): number {
88
+ const sorted = [...values].sort((left, right) => left - right);
89
+ return sorted[Math.max(0, Math.ceil(percentile * sorted.length) - 1)] ?? 0;
90
+ }
91
+
92
+ export function summarizeGpuFrameTiming(
93
+ samples: readonly GpuFrameTimingSample[],
94
+ ): GpuFrameTimingSummary {
95
+ const values = samples.map((sample) => sample.durationUs);
96
+ return Object.freeze({
97
+ medianUs: nearestRank(values, 0.5),
98
+ p95Us: nearestRank(values, 0.95),
99
+ samples: values.length,
100
+ });
101
+ }