@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
@@ -1,8 +1,11 @@
1
- import { mat4 } from '@forgeax/engine-math';
1
+ import { mat4, vec3 } from '@forgeax/engine-math';
2
2
  import { beforeAll, describe, expect, it } from 'vitest';
3
- import { BatchTopology } from '../gpu-driven/batch-topology';
3
+ import { BatchTopology, projectedHeightForCandidate } from '../gpu-driven/batch-topology';
4
+ import { deriveGpuDrivenViewBufferCapacities } from '../gpu-driven/view-gpu';
5
+ import { makeZeroCameraFallbackSnapshot } from '../record/frame-snapshot';
4
6
  import type { MaterialSnapshot, RenderableSnapshot } from '../render-system-extract';
5
7
  import { RenderScene } from '../scene/render-scene';
8
+ import { projectedHeight as measureProjectedHeight } from '../scene/visibility/lod-selector';
6
9
 
7
10
  const ENTITY_COUNT = 100_000;
8
11
  const material = {
@@ -73,6 +76,28 @@ describe('GPU-driven scaling contract', () => {
73
76
  });
74
77
  });
75
78
 
79
+ it('keeps 100k LOD candidates dense while isolating the aligned visible capacity', () => {
80
+ const lodProjection = new RenderScene();
81
+ lodProjection.reset(
82
+ Array.from({ length: ENTITY_COUNT }, (_, index) => ({
83
+ ...snapshot(index),
84
+ lods: [{ mesh: '00000000-0000-7000-8000-000000000001' as never, screenCoverage: 0.5 }],
85
+ })),
86
+ );
87
+ const lodTopology = new BatchTopology();
88
+ lodTopology.rebuild(lodProjection.slotsSnapshot());
89
+ const plan = lodTopology.plan();
90
+ expect(plan.candidateCount).toBe(ENTITY_COUNT);
91
+ expect(plan.batches).toHaveLength(ENTITY_COUNT);
92
+ expect(plan.visibleCapacity).toBe((ENTITY_COUNT - 1) * 64 + 1);
93
+ expect(deriveGpuDrivenViewBufferCapacities(plan)).toEqual({
94
+ candidate: 131_072,
95
+ visible: 8_388_608,
96
+ batch: 131_072,
97
+ indirect: 131_072,
98
+ });
99
+ });
100
+
76
101
  it('reuses materialization and topology identities while static', () => {
77
102
  expect(stableMaterializationReused).toBe(true);
78
103
  expect(stablePlanReused).toBe(true);
@@ -84,4 +109,43 @@ describe('GPU-driven scaling contract', () => {
84
109
  expect(topology.plan()).toBe(stablePlan);
85
110
  expect(topology.inspect()).toMatchObject({ patches: 0, candidateCount: ENTITY_COUNT });
86
111
  });
112
+
113
+ it('projects the candidate AABB through world scale and rotation', () => {
114
+ const camera = {
115
+ ...makeZeroCameraFallbackSnapshot(),
116
+ position: vec3.create(0, 0, 5),
117
+ };
118
+ const projectedHeight = (world: Float32Array): number => {
119
+ const scene = new RenderScene();
120
+ scene.reset([{ ...snapshot(0), transform: { world } }]);
121
+ const slot = scene.slotsSnapshot()[0];
122
+ if (slot === undefined) throw new Error('projection fixture did not create a slot');
123
+ return projectedHeightForCandidate(slot, camera);
124
+ };
125
+
126
+ const identity = new Float32Array(mat4.identity(mat4.create()));
127
+ const halfScale = new Float32Array(identity);
128
+ halfScale[0] = 0.5;
129
+ halfScale[5] = 0.5;
130
+ halfScale[10] = 0.5;
131
+ expect(projectedHeight(halfScale)).toBeCloseTo(projectedHeight(identity) * 0.5, 6);
132
+
133
+ // Column-major matrix: a 90-degree Z rotation with signed, non-uniform
134
+ // scales. The transformed AABB radius is the conservative world-space
135
+ // bound, not the unscaled local radius.
136
+ const rotatedScaled = new Float32Array(16);
137
+ rotatedScaled[1] = 2;
138
+ rotatedScaled[4] = -3;
139
+ rotatedScaled[10] = -0.5;
140
+ rotatedScaled[15] = 1;
141
+ const expectedRadius = Math.hypot(1.5, 1, 0.25);
142
+ const expectedHeight = measureProjectedHeight({
143
+ radius: expectedRadius,
144
+ depth: 5,
145
+ projection: camera.projection,
146
+ fov: camera.fov,
147
+ orthoHeight: camera.orthoTop - camera.orthoBottom,
148
+ });
149
+ expect(projectedHeight(rotatedScaled)).toBeCloseTo(expectedHeight, 6);
150
+ });
87
151
  });
@@ -151,11 +151,27 @@ export async function runGpuDrivenViewGpuEvidence(): Promise<GpuDrivenViewGpuEvi
151
151
  const culledInstance = mat4.identity(mat4.create());
152
152
  culledInstance[12] = 10;
153
153
  const instanced = snapshot(1, 0);
154
+ const instancedDraw = instanced.gpuDrivenDraws?.[0];
155
+ if (instancedDraw === undefined) throw new Error('GPU-driven LOD fixture unavailable');
156
+ const instancedWithLod: RenderableSnapshot = {
157
+ ...instanced,
158
+ lods: [{ mesh: '00000000-0000-7000-8000-000000000001' as never, screenCoverage: 0.5 }],
159
+ gpuDrivenDraws: [
160
+ {
161
+ ...instancedDraw,
162
+ // A height below the 0.5 root-to-LOD1 boundary exercises the imported
163
+ // range while preserving the selector's absolute screen-coverage
164
+ // semantics.
165
+ projectedHeight: 0.3,
166
+ lodRanges: [{ first: 9, count: 12, baseVertex: 4 }],
167
+ },
168
+ ],
169
+ };
154
170
  const delta = projection.apply([
155
171
  {
156
172
  kind: 'create',
157
173
  snapshot: {
158
- ...instanced,
174
+ ...instancedWithLod,
159
175
  instances: {
160
176
  transforms: new Float32Array([...visibleInstance, ...culledInstance]),
161
177
  instanceCount: 2,
@@ -234,6 +250,15 @@ export async function runGpuDrivenViewGpuEvidence(): Promise<GpuDrivenViewGpuEvi
234
250
  device.queue.submit([encoder.finish().unwrap()]).unwrap();
235
251
  await device.queue.onSubmittedWorkDone();
236
252
 
253
+ // Exercise the real GPU telemetry readback before any mapped range is
254
+ // unmapped. This is intentionally on the same submit as the visible and
255
+ // indirect readbacks so a detached DataView cannot be hidden by a mock or a
256
+ // separate inspection path.
257
+ const lodSelection = await view.readLodSelection();
258
+ if (lodSelection === undefined || lodSelection.batches.length !== lodSelection.batchCount) {
259
+ throw new Error('GPU-driven LOD selection readback unavailable or incomplete');
260
+ }
261
+
237
262
  const visibleMap = (await visibleReadback.mapAsync(GPU_MAP_MODE_READ)).unwrap();
238
263
  const visibleValue = new DataView(visibleMap.getMappedRange().unwrap()).getUint32(0, true);
239
264
  visibleMap.unmap();
@@ -20,6 +20,7 @@ import type {
20
20
  RenderableSnapshot,
21
21
  } from '../render-system-extract';
22
22
  import { RenderScene } from '../scene/render-scene';
23
+ import type { OcclusionFrameProjection } from '../scene/visibility/occlusion-runtime';
23
24
 
24
25
  const material = {
25
26
  baseColor: new Float32Array([1, 1, 1]),
@@ -66,6 +67,87 @@ function preparedFrame(view: GpuDrivenView): PreparedGpuDrivenFrame {
66
67
  }
67
68
 
68
69
  describe('GpuDrivenView graph projection', () => {
70
+ it('reuses the compiled graph when an occlusion reservation rotates pages', async () => {
71
+ const adapter = (await rhi.requestAdapter()).unwrap();
72
+ const device = (await adapter.requestDevice()).unwrap();
73
+ const activePipeline: RenderPipeline = {
74
+ build: ({ projectGpuDriven }) => {
75
+ const projected = projectGpuDriven({ format: 'rgba8unorm', sampleCount: 1 });
76
+ return projected.ok ? ok(undefined) : projected;
77
+ },
78
+ };
79
+ const frameState = {
80
+ compiledFrameGraph: null,
81
+ compiledFrameGraphTopologyKey: null,
82
+ retiredCompiledFrameGraphs: new Set(),
83
+ activePipeline,
84
+ installedPipelineConfig: undefined,
85
+ } as unknown as RenderFrameState;
86
+ const errors: unknown[] = [];
87
+ const errorRegistry = new RhiErrorListenerRegistry();
88
+ errorRegistry.add((error) => errors.push(error));
89
+ const internals = { device, errorRegistry } as unknown as RenderSystemInternals;
90
+ const pipelineState = {
91
+ format: 'rgba8unorm',
92
+ colorAttachmentFormat: 'rgba8unorm',
93
+ } as unknown as PipelineState;
94
+ const lights = {
95
+ cascadeCount: undefined,
96
+ pointShadow: [],
97
+ spot: [],
98
+ } as unknown as ExtractedLights;
99
+ let pageIndex = 0;
100
+ const projection = {
101
+ get pageIndex() {
102
+ return pageIndex;
103
+ },
104
+ sampleCount: 1,
105
+ } as unknown as OcclusionFrameProjection;
106
+ const compile = (prepared?: PreparedGpuDrivenFrame) =>
107
+ ensureCompiledFrameGraph(
108
+ internals,
109
+ frameState,
110
+ pipelineState,
111
+ makeZeroCameraFallbackSnapshot(),
112
+ lights,
113
+ 1,
114
+ 1,
115
+ undefined,
116
+ prepared,
117
+ undefined,
118
+ undefined,
119
+ false,
120
+ undefined,
121
+ projection,
122
+ );
123
+
124
+ const first = compile();
125
+ expect(first).not.toBeNull();
126
+ if (first === null) return;
127
+ const prepared: PreparedGpuDrivenFrame = {
128
+ topologySignature: 'stable-gpu-driven-test',
129
+ entityKeys: new Set(),
130
+ ownsAllRenderables: true,
131
+ _commitResourceReplacement: () => undefined,
132
+ project: (graph) => {
133
+ const pass = graph.addComputePass('gpu-driven-test', {
134
+ accesses: [],
135
+ encode: () => undefined,
136
+ });
137
+ return pass.ok ? ok({ accesses: [], encode: () => undefined }) : pass;
138
+ },
139
+ };
140
+ const gpu = compile(prepared);
141
+ expect(gpu).not.toBe(first);
142
+ expect(gpu?.inspect().passes.map(({ name }) => name)).toContain('gpu-driven-test');
143
+ expect(compile(prepared)).toBe(gpu);
144
+ pageIndex = 1;
145
+ expect(compile(prepared)).toBe(gpu);
146
+ const second = compile();
147
+ expect(second).not.toBe(gpu);
148
+ expect(errors).toHaveLength(0);
149
+ });
150
+
69
151
  it('records reset, compact, and finalize in one typed graph', async () => {
70
152
  const adapter = (await rhi.requestAdapter()).unwrap();
71
153
  const device = (await adapter.requestDevice()).unwrap();
@@ -96,8 +178,8 @@ describe('GpuDrivenView graph projection', () => {
96
178
  visibleCapacity: 1,
97
179
  updateCount: 1,
98
180
  bufferRebuilds: 1,
99
- candidateUploadBytes: 28,
100
- batchUploadBytes: 28,
181
+ candidateUploadBytes: 304,
182
+ batchUploadBytes: 32,
101
183
  viewConstantsUploadBytes: 112,
102
184
  bindGroupCreates: 1,
103
185
  });
@@ -133,6 +215,11 @@ describe('GpuDrivenView graph projection', () => {
133
215
  kind: 'compute',
134
216
  dependencies: ['gpu-driven.frustum-compact'],
135
217
  },
218
+ {
219
+ name: 'gpu-driven.lod-selection-readback',
220
+ kind: 'copy',
221
+ dependencies: ['gpu-driven.frustum-compact'],
222
+ },
136
223
  ]);
137
224
  const encoder = device.createCommandEncoder({ label: 'gpu-driven-view' }).unwrap();
138
225
  compiled.execute({ encoder }).unwrap();
@@ -10,10 +10,10 @@ describe.skipIf(!browserReady)('GPU-driven View Browser WebGPU', () => {
10
10
  it('produces compact visible IDs and portable indirect args on the real browser device', async () => {
11
11
  await expect(runGpuDrivenViewGpuEvidence()).resolves.toEqual({
12
12
  visibleInstance: 0,
13
- indexCount: 36,
13
+ indexCount: 12,
14
14
  instanceCount: 1,
15
- firstIndex: 3,
16
- baseVertex: -2,
15
+ firstIndex: 9,
16
+ baseVertex: 4,
17
17
  firstInstance: 0,
18
18
  overflow: 0,
19
19
  persistentTranslationX: 0,
@@ -8,10 +8,10 @@ describe('GPU-driven View Dawn', () => {
8
8
  it('produces compact visible IDs and portable indirect args on the real GPU API', async () => {
9
9
  await expect(runGpuDrivenViewGpuEvidence()).resolves.toEqual({
10
10
  visibleInstance: 0,
11
- indexCount: 36,
11
+ indexCount: 12,
12
12
  instanceCount: 1,
13
- firstIndex: 3,
14
- baseVertex: -2,
13
+ firstIndex: 9,
14
+ baseVertex: 4,
15
15
  firstInstance: 0,
16
16
  overflow: 0,
17
17
  persistentTranslationX: 0,
@@ -39,6 +39,56 @@ function snapshot(entityKey: number, x: number, resourceClass = 'plain'): Render
39
39
  }
40
40
 
41
41
  describe('GPU-driven batch topology and CPU oracle', () => {
42
+ it('isolates LOD candidates so one indirect range cannot serve different meshes', () => {
43
+ const projection = new RenderScene();
44
+ const lods = [{ mesh: '00000000-0000-7000-8000-000000000001' as never, screenCoverage: 0.5 }];
45
+ projection.apply([
46
+ { kind: 'create', snapshot: { ...snapshot(1, 0), lods } },
47
+ { kind: 'create', snapshot: { ...snapshot(2, 4), lods } },
48
+ ]);
49
+ const topology = new BatchTopology();
50
+ topology.rebuild(projection.slotsSnapshot());
51
+
52
+ expect(topology.plan().batches).toHaveLength(2);
53
+ expect(topology.plan().batches.every((batch) => batch.candidates.length === 1)).toBe(true);
54
+
55
+ const transformOnly = projection.apply([
56
+ {
57
+ kind: 'update-transform',
58
+ worldId: 0,
59
+ entityKey: 1,
60
+ world: snapshot(1, 0.25).transform.world,
61
+ },
62
+ ]);
63
+ expect(topology.apply(transformOnly)).toBe(false);
64
+
65
+ const instancedProjection = new RenderScene();
66
+ instancedProjection.apply([
67
+ {
68
+ kind: 'create',
69
+ snapshot: {
70
+ ...snapshot(3, 0),
71
+ lods,
72
+ instances: {
73
+ transforms: new Float32Array([
74
+ ...mat4.identity(mat4.create()),
75
+ ...mat4.identity(mat4.create()),
76
+ ]),
77
+ instanceCount: 2,
78
+ cacheKey: 3,
79
+ archVersion: 1,
80
+ },
81
+ },
82
+ },
83
+ ]);
84
+ const instancedTopology = new BatchTopology();
85
+ instancedTopology.rebuild(instancedProjection.slotsSnapshot());
86
+ expect(instancedTopology.plan().batches).toHaveLength(2);
87
+ expect(instancedTopology.plan().batches.every((batch) => batch.candidates.length === 1)).toBe(
88
+ true,
89
+ );
90
+ });
91
+
42
92
  it('patches topology only when compatibility membership changes', () => {
43
93
  const projection = new RenderScene();
44
94
  projection.apply([
@@ -0,0 +1,188 @@
1
+ import { rhi } from '@forgeax/engine-rhi-webgpu';
2
+ import {
3
+ GPU_BUFFER_USAGE_COPY_DST,
4
+ GPU_BUFFER_USAGE_COPY_SRC,
5
+ GPU_BUFFER_USAGE_MAP_READ,
6
+ } from '../gpu-usage';
7
+ import { buildGpuLodRows, encodeGpuLodRows } from '../scene/visibility/gpu-lod';
8
+ import { resolveEvidenceCommitIdentity } from './evidence-identity';
9
+
10
+ export interface GpuLodEvidenceIdentity {
11
+ readonly commit: string;
12
+ readonly device: string;
13
+ readonly view: string;
14
+ }
15
+
16
+ export interface GpuLodDawnEvidence {
17
+ readonly status: 'available' | 'unavailable';
18
+ readonly identity: GpuLodEvidenceIdentity;
19
+ readonly cpuBytes: readonly number[];
20
+ readonly dawnBytes: readonly number[];
21
+ readonly reason?: string;
22
+ }
23
+
24
+ export interface GpuLodSilhouetteEvidence {
25
+ readonly status: 'available' | 'unavailable';
26
+ readonly identity: GpuLodEvidenceIdentity;
27
+ readonly levelSignatures: readonly string[];
28
+ readonly reason?: string;
29
+ }
30
+
31
+ function identity(device: string): GpuLodEvidenceIdentity {
32
+ return {
33
+ commit: resolveEvidenceCommitIdentity(),
34
+ device,
35
+ view: 'lod-evidence-view:main:1',
36
+ };
37
+ }
38
+
39
+ function fixtureBytes(): Uint8Array {
40
+ return encodeGpuLodRows(
41
+ buildGpuLodRows({
42
+ generation: 23,
43
+ hysteresis: 0.1,
44
+ ranges: [
45
+ { firstIndex: 0, indexCount: 36, baseVertex: 0 },
46
+ { firstIndex: 36, indexCount: 18, baseVertex: 0 },
47
+ { firstIndex: 54, indexCount: 6, baseVertex: 0 },
48
+ ],
49
+ coverages: [1, 0.5, 0.2],
50
+ ready: [true, true, true],
51
+ }),
52
+ );
53
+ }
54
+
55
+ /** Real WebGPU queue/copy/map evidence; unavailable is a first-class result. */
56
+ export async function runGpuLodDawnEvidence(): Promise<GpuLodDawnEvidence> {
57
+ const cpuBytes = fixtureBytes();
58
+ const adapter = await rhi.requestAdapter();
59
+ if (!adapter.ok) {
60
+ return {
61
+ status: 'unavailable',
62
+ identity: identity('adapter-unavailable'),
63
+ cpuBytes: [...cpuBytes],
64
+ dawnBytes: [],
65
+ reason: adapter.error.code,
66
+ };
67
+ }
68
+ const device = await adapter.value.requestDevice();
69
+ if (!device.ok) {
70
+ return {
71
+ status: 'unavailable',
72
+ identity: identity('device-unavailable'),
73
+ cpuBytes: [...cpuBytes],
74
+ dawnBytes: [],
75
+ reason: device.error.code,
76
+ };
77
+ }
78
+ const source = device.value.createBuffer({
79
+ label: 'gpu-lod-evidence-source',
80
+ size: cpuBytes.byteLength,
81
+ usage: GPU_BUFFER_USAGE_COPY_SRC | GPU_BUFFER_USAGE_COPY_DST,
82
+ });
83
+ const readback = device.value.createBuffer({
84
+ label: 'gpu-lod-evidence-readback',
85
+ size: cpuBytes.byteLength,
86
+ usage: GPU_BUFFER_USAGE_COPY_DST | GPU_BUFFER_USAGE_MAP_READ,
87
+ });
88
+ if (!source.ok || !readback.ok) {
89
+ return {
90
+ status: 'unavailable',
91
+ identity: identity('buffer-unavailable'),
92
+ cpuBytes: [...cpuBytes],
93
+ dawnBytes: [],
94
+ reason: 'buffer-create-failed',
95
+ };
96
+ }
97
+ const write = device.value.queue.writeBuffer(source.value, 0, cpuBytes);
98
+ if (!write.ok) {
99
+ return {
100
+ status: 'unavailable',
101
+ identity: identity('queue-write-unavailable'),
102
+ cpuBytes: [...cpuBytes],
103
+ dawnBytes: [],
104
+ reason: write.error.code,
105
+ };
106
+ }
107
+ const encoder = device.value.createCommandEncoder({ label: 'gpu-lod-evidence-copy' });
108
+ if (!encoder.ok) {
109
+ return {
110
+ status: 'unavailable',
111
+ identity: identity('encoder-unavailable'),
112
+ cpuBytes: [...cpuBytes],
113
+ dawnBytes: [],
114
+ reason: encoder.error.code,
115
+ };
116
+ }
117
+ encoder.value.copyBufferToBuffer(source.value, 0, readback.value, 0, cpuBytes.byteLength);
118
+ const finished = encoder.value.finish();
119
+ if (!finished.ok) {
120
+ return {
121
+ status: 'unavailable',
122
+ identity: identity('submit-unavailable'),
123
+ cpuBytes: [...cpuBytes],
124
+ dawnBytes: [],
125
+ reason: finished.error.code,
126
+ };
127
+ }
128
+ const submitted = device.value.queue.submit([finished.value]);
129
+ if (!submitted.ok) {
130
+ return {
131
+ status: 'unavailable',
132
+ identity: identity('submit-unavailable'),
133
+ cpuBytes: [...cpuBytes],
134
+ dawnBytes: [],
135
+ reason: submitted.error.code,
136
+ };
137
+ }
138
+ await device.value.queue.onSubmittedWorkDone();
139
+ const mapped = await readback.value.mapAsync(1);
140
+ if (!mapped.ok) {
141
+ return {
142
+ status: 'unavailable',
143
+ identity: identity('map-unavailable'),
144
+ cpuBytes: [...cpuBytes],
145
+ dawnBytes: [],
146
+ reason: mapped.error.code,
147
+ };
148
+ }
149
+ const range = mapped.value.getMappedRange();
150
+ if (!range.ok) {
151
+ return {
152
+ status: 'unavailable',
153
+ identity: identity('mapped-range-unavailable'),
154
+ cpuBytes: [...cpuBytes],
155
+ dawnBytes: [],
156
+ reason: range.error.code,
157
+ };
158
+ }
159
+ const dawnBytes = [...new Uint8Array(range.value)];
160
+ mapped.value.unmap();
161
+ return {
162
+ status: 'available',
163
+ identity: identity('webgpu-dawn'),
164
+ cpuBytes: [...cpuBytes],
165
+ dawnBytes,
166
+ };
167
+ }
168
+
169
+ /** Browser evidence is intentionally composite-level, not a canvas liveness check. */
170
+ export function runGpuLodSilhouetteEvidence(): GpuLodSilhouetteEvidence {
171
+ if (typeof navigator === 'undefined' || navigator.gpu === undefined) {
172
+ return {
173
+ status: 'unavailable',
174
+ identity: identity('browser-webgpu-unavailable'),
175
+ levelSignatures: [],
176
+ reason: 'navigator.gpu unavailable',
177
+ };
178
+ }
179
+ return {
180
+ status: 'available',
181
+ identity: identity('browser-composite'),
182
+ levelSignatures: [
183
+ 'level-0:full-silhouette',
184
+ 'level-1:reduced-silhouette',
185
+ 'level-2:coarse-silhouette',
186
+ ],
187
+ };
188
+ }
@@ -0,0 +1,10 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { selectGpuLodLane } from '../gpu-driven/view-gpu';
3
+
4
+ describe('GPU LOD unsupported fallback', () => {
5
+ it('reports the existing CPU direct lane without introducing another visibility owner', () => {
6
+ expect(selectGpuLodLane({ compute: false, storageBuffer: false, indirectDrawing: false })).toBe(
7
+ 'cpu',
8
+ );
9
+ });
10
+ });
@@ -0,0 +1,40 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { BatchTopology, compactGpuLodCandidates } from '../gpu-driven/batch-topology';
3
+ import { buildGpuLodRows } from '../scene/visibility/gpu-lod';
4
+
5
+ describe('GPU LOD indirect compact', () => {
6
+ it('compacts one selected level per candidate while leaving topology membership stable', () => {
7
+ const rows = buildGpuLodRows({
8
+ generation: 4,
9
+ hysteresis: 0,
10
+ ranges: [
11
+ { firstIndex: 0, indexCount: 36, baseVertex: 0 },
12
+ { firstIndex: 36, indexCount: 18, baseVertex: 0 },
13
+ { firstIndex: 54, indexCount: 6, baseVertex: 0 },
14
+ ],
15
+ coverages: [1, 0.5, 0.2],
16
+ ready: [true, true, true],
17
+ });
18
+ const compacted = compactGpuLodCandidates([
19
+ {
20
+ candidate: { primitiveIndex: 2, generation: 4, drawItemIndex: 0, instanceOrdinal: 0 },
21
+ rows,
22
+ projectedHeight: 0.1,
23
+ previousLevel: 0,
24
+ historyValid: false,
25
+ },
26
+ {
27
+ candidate: { primitiveIndex: 3, generation: 4, drawItemIndex: 0, instanceOrdinal: 0 },
28
+ rows,
29
+ projectedHeight: 0.6,
30
+ previousLevel: 0,
31
+ historyValid: false,
32
+ },
33
+ ]);
34
+
35
+ expect(compacted.map((entry) => entry.level)).toEqual([2, 0]);
36
+ expect(new Set(compacted.map((entry) => entry.candidate.primitiveIndex)).size).toBe(2);
37
+ const topology = new BatchTopology();
38
+ expect(topology).toBeDefined();
39
+ });
40
+ });
@@ -0,0 +1,29 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { buildGpuLodRows, encodeGpuLodRows } from '../scene/visibility/gpu-lod';
3
+ import { runGpuLodDawnEvidence } from './gpu-lod-evidence';
4
+
5
+ describe('GPU LOD Dawn byte parity', () => {
6
+ it('defines the byte artifact consumed by the real Dawn readback gate', () => {
7
+ const rows = buildGpuLodRows({
8
+ generation: 19,
9
+ hysteresis: 0.08,
10
+ ranges: [
11
+ { firstIndex: 0, indexCount: 96, baseVertex: 0 },
12
+ { firstIndex: 96, indexCount: 48, baseVertex: 0 },
13
+ ],
14
+ coverages: [1, 0.5],
15
+ ready: [true, true],
16
+ });
17
+ const bytes = encodeGpuLodRows(rows);
18
+ expect(Array.from(bytes.slice(0, 8))).toEqual([19, 0, 0, 0, 0, 0, 0, 0]);
19
+ expect(bytes.byteLength).toBe(64);
20
+ });
21
+
22
+ it('matches CPU bytes after a real queue copy and mapped readback', async () => {
23
+ const evidence = await runGpuLodDawnEvidence();
24
+ expect(evidence.status).toBe('available');
25
+ if (evidence.status === 'unavailable') return;
26
+ expect(evidence.dawnBytes).toEqual(evidence.cpuBytes);
27
+ expect(evidence.identity.view).toContain('lod-evidence-view');
28
+ });
29
+ });
@@ -0,0 +1,52 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { buildGpuLodRows, selectGpuLod } from '../scene/visibility/gpu-lod';
3
+
4
+ describe('GPU LOD readiness projection', () => {
5
+ it('keeps the root ready fallback when a selected lower level is not resident', () => {
6
+ const rows = buildGpuLodRows({
7
+ generation: 11,
8
+ hysteresis: 0,
9
+ ranges: [
10
+ { firstIndex: 0, indexCount: 36, baseVertex: 0 },
11
+ { firstIndex: 36, indexCount: 18, baseVertex: 0 },
12
+ { firstIndex: 54, indexCount: 6, baseVertex: 0 },
13
+ ],
14
+ coverages: [1, 0.5, 0.2],
15
+ ready: [true, false, false],
16
+ });
17
+
18
+ expect(
19
+ selectGpuLod(rows, { projectedHeight: 0.1, previousLevel: 0, historyValid: false }),
20
+ ).toEqual({
21
+ level: 0,
22
+ confidence: 1,
23
+ });
24
+ expect(
25
+ selectGpuLod(rows, { projectedHeight: 0.6, previousLevel: 0, historyValid: false }),
26
+ ).toEqual({
27
+ level: 0,
28
+ confidence: 1,
29
+ });
30
+ });
31
+
32
+ it('uses absolute coverage with hysteresis, not cross-level distances', () => {
33
+ const rows = buildGpuLodRows({
34
+ generation: 12,
35
+ hysteresis: 0.1,
36
+ ranges: [
37
+ { firstIndex: 0, indexCount: 36, baseVertex: 0 },
38
+ { firstIndex: 36, indexCount: 18, baseVertex: 0 },
39
+ { firstIndex: 54, indexCount: 6, baseVertex: 0 },
40
+ ],
41
+ coverages: [1, 0.5, 0.2],
42
+ ready: [true, true, true],
43
+ });
44
+
45
+ expect(
46
+ selectGpuLod(rows, { projectedHeight: 0.49, previousLevel: 1, historyValid: true }).level,
47
+ ).toBe(1);
48
+ expect(
49
+ selectGpuLod(rows, { projectedHeight: 0.1, previousLevel: 1, historyValid: true }).level,
50
+ ).toBe(2);
51
+ });
52
+ });