@forgeax/engine-render 0.1.20 → 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 (645) hide show
  1. package/README.md +365 -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 +18 -0
  5. package/dist/assembly/device-feature-admission.d.ts.map +1 -0
  6. package/dist/assembly/directional-shadow-inspection.d.ts +38 -0
  7. package/dist/assembly/directional-shadow-inspection.d.ts.map +1 -0
  8. package/dist/assembly/factory.d.ts +30 -19
  9. package/dist/assembly/factory.d.ts.map +1 -1
  10. package/dist/assembly/host-contract.d.ts +19 -0
  11. package/dist/assembly/host-contract.d.ts.map +1 -1
  12. package/dist/assembly/render-target-host.d.ts +46 -0
  13. package/dist/assembly/render-target-host.d.ts.map +1 -0
  14. package/dist/assets/render-asset-port.d.ts.map +1 -1
  15. package/dist/authoring.mjs +4 -1
  16. package/dist/authoring.mjs.map +1 -1
  17. package/dist/capture/cube-views.d.ts +21 -0
  18. package/dist/capture/cube-views.d.ts.map +1 -0
  19. package/dist/capture/scheduler.d.ts +54 -0
  20. package/dist/capture/scheduler.d.ts.map +1 -0
  21. package/dist/{chunk-PCHBPYML.mjs → chunk-4L3P6DT5.mjs} +2 -2
  22. package/dist/chunk-4L3P6DT5.mjs.map +1 -0
  23. package/dist/{chunk-VFPLTKCU.mjs → chunk-4NPUASZR.mjs} +15 -4
  24. package/dist/chunk-4NPUASZR.mjs.map +1 -0
  25. package/dist/chunk-DIYW3M5D.mjs +5085 -0
  26. package/dist/chunk-DIYW3M5D.mjs.map +1 -0
  27. package/dist/chunk-FBC4R44J.mjs +1003 -0
  28. package/dist/chunk-FBC4R44J.mjs.map +1 -0
  29. package/dist/{chunk-CDTWTHZR.mjs → chunk-FF7K3FSO.mjs} +474 -168
  30. package/dist/chunk-FF7K3FSO.mjs.map +1 -0
  31. package/dist/chunk-HZUPSYW7.mjs +1104 -0
  32. package/dist/chunk-HZUPSYW7.mjs.map +1 -0
  33. package/dist/{chunk-TBQJOK2L.mjs → chunk-QODA5UKU.mjs} +321 -269
  34. package/dist/chunk-QODA5UKU.mjs.map +1 -0
  35. package/dist/chunk-YALPS44A.mjs +296 -0
  36. package/dist/chunk-YALPS44A.mjs.map +1 -0
  37. package/dist/{chunk-4XINDLAY.mjs → chunk-ZUWKAZ2I.mjs} +1963 -2178
  38. package/dist/chunk-ZUWKAZ2I.mjs.map +1 -0
  39. package/dist/cluster-binner.d.ts +5 -0
  40. package/dist/cluster-binner.d.ts.map +1 -1
  41. package/dist/components/camera.d.ts +1 -0
  42. package/dist/components/camera.d.ts.map +1 -1
  43. package/dist/components/cube-camera.d.ts +20 -0
  44. package/dist/components/cube-camera.d.ts.map +1 -0
  45. package/dist/components/directional-light.d.ts +11 -2
  46. package/dist/components/directional-light.d.ts.map +1 -1
  47. package/dist/components/directional-shadow-filter.d.ts +25 -0
  48. package/dist/components/directional-shadow-filter.d.ts.map +1 -0
  49. package/dist/components/index.d.ts +5 -0
  50. package/dist/components/index.d.ts.map +1 -1
  51. package/dist/components/light-helpers.d.ts +4 -0
  52. package/dist/components/light-helpers.d.ts.map +1 -1
  53. package/dist/components/light-probe.d.ts +9 -0
  54. package/dist/components/light-probe.d.ts.map +1 -0
  55. package/dist/components/rect-area-light.d.ts +12 -0
  56. package/dist/components/rect-area-light.d.ts.map +1 -0
  57. package/dist/components/reflection-probe.d.ts +17 -0
  58. package/dist/components/reflection-probe.d.ts.map +1 -0
  59. package/dist/components/scene-instance.d.ts +4 -0
  60. package/dist/components/scene-instance.d.ts.map +1 -1
  61. package/dist/components/spot-light.d.ts +3 -0
  62. package/dist/components/spot-light.d.ts.map +1 -1
  63. package/dist/construct-renderer.mjs +14836 -9620
  64. package/dist/construct-renderer.mjs.map +1 -1
  65. package/dist/device/gpu-residency.d.ts +8 -1
  66. package/dist/device/gpu-residency.d.ts.map +1 -1
  67. package/dist/device/lifecycle-transaction.d.ts +2 -0
  68. package/dist/device/lifecycle-transaction.d.ts.map +1 -0
  69. package/dist/environment/frame.d.ts.map +1 -1
  70. package/dist/environment/lifecycle.d.ts +5 -0
  71. package/dist/environment/lifecycle.d.ts.map +1 -1
  72. package/dist/errors/gpu-driven.d.ts +26 -0
  73. package/dist/errors/gpu-driven.d.ts.map +1 -0
  74. package/dist/errors/index.d.ts +1 -0
  75. package/dist/errors/index.d.ts.map +1 -1
  76. package/dist/errors/recover.d.ts +9 -1
  77. package/dist/errors/recover.d.ts.map +1 -1
  78. package/dist/errors/render.d.ts +166 -93
  79. package/dist/errors/render.d.ts.map +1 -1
  80. package/dist/extract/directional-shadow-projection.d.ts +23 -0
  81. package/dist/extract/directional-shadow-projection.d.ts.map +1 -0
  82. package/dist/extract/environment.d.ts +14 -0
  83. package/dist/extract/environment.d.ts.map +1 -1
  84. package/dist/extract/gpu-driven.d.ts +50 -0
  85. package/dist/extract/gpu-driven.d.ts.map +1 -0
  86. package/dist/extract/visibility.d.ts.map +1 -1
  87. package/dist/features/fullscreen.d.ts.map +1 -1
  88. package/dist/features/targets.d.ts.map +1 -1
  89. package/dist/features/types.d.ts +10 -0
  90. package/dist/features/types.d.ts.map +1 -1
  91. package/dist/fullscreen-post-process-pass.d.ts +13 -2
  92. package/dist/fullscreen-post-process-pass.d.ts.map +1 -1
  93. package/dist/gpu-driven/batch-topology.d.ts +34 -0
  94. package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
  95. package/dist/gpu-driven/prepared-draw.d.ts +63 -0
  96. package/dist/gpu-driven/prepared-draw.d.ts.map +1 -0
  97. package/dist/gpu-driven/production-raster.d.ts +97 -2
  98. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  99. package/dist/gpu-driven/view-gpu.d.ts +64 -1
  100. package/dist/gpu-driven/view-gpu.d.ts.map +1 -1
  101. package/dist/gpu-scene-schema.d.ts +14 -0
  102. package/dist/gpu-scene-schema.d.ts.map +1 -1
  103. package/dist/gpu-scene.d.ts +8 -1
  104. package/dist/gpu-scene.d.ts.map +1 -1
  105. package/dist/gpu-usage.d.ts +0 -1
  106. package/dist/gpu-usage.d.ts.map +1 -1
  107. package/dist/hdrp-buffers.d.ts +21 -19
  108. package/dist/hdrp-buffers.d.ts.map +1 -1
  109. package/dist/ibl/IblPipelineCache.d.ts +4 -0
  110. package/dist/ibl/IblPipelineCache.d.ts.map +1 -1
  111. package/dist/ibl/kernel-cache.d.ts +36 -0
  112. package/dist/ibl/kernel-cache.d.ts.map +1 -0
  113. package/dist/index.d.ts +39 -3
  114. package/dist/index.d.ts.map +1 -1
  115. package/dist/index.mjs +91 -6
  116. package/dist/index.mjs.map +1 -1
  117. package/dist/inspection-types.d.ts +214 -1
  118. package/dist/inspection-types.d.ts.map +1 -1
  119. package/dist/instances-derived-bounds.d.ts +50 -0
  120. package/dist/instances-derived-bounds.d.ts.map +1 -0
  121. package/dist/internal.d.ts +9 -0
  122. package/dist/internal.d.ts.map +1 -1
  123. package/dist/internal.mjs +23 -2
  124. package/dist/internal.mjs.map +1 -1
  125. package/dist/light-buffer-layout.d.ts +58 -140
  126. package/dist/light-buffer-layout.d.ts.map +1 -1
  127. package/dist/materials.d.ts.map +1 -1
  128. package/dist/mesh-material-bindings.d.ts +54 -0
  129. package/dist/mesh-material-bindings.d.ts.map +1 -1
  130. package/dist/pbr-pipeline.d.ts +38 -14
  131. package/dist/pbr-pipeline.d.ts.map +1 -1
  132. package/dist/pipeline/standard-forward-lane.d.ts +2 -1
  133. package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
  134. package/dist/pipeline/standard-lighting/errors.d.ts +4 -0
  135. package/dist/pipeline/standard-lighting/errors.d.ts.map +1 -0
  136. package/dist/pipeline/standard-lighting/graph.d.ts +24 -0
  137. package/dist/pipeline/standard-lighting/graph.d.ts.map +1 -0
  138. package/dist/pipeline/standard-lighting/inspection.d.ts +38 -0
  139. package/dist/pipeline/standard-lighting/inspection.d.ts.map +1 -0
  140. package/dist/pipeline/standard-lighting/layout.d.ts +28 -0
  141. package/dist/pipeline/standard-lighting/layout.d.ts.map +1 -0
  142. package/dist/pipeline/standard-lighting/light-frame.d.ts +33 -0
  143. package/dist/pipeline/standard-lighting/light-frame.d.ts.map +1 -0
  144. package/dist/pipeline/standard-lighting/membership.d.ts +14 -0
  145. package/dist/pipeline/standard-lighting/membership.d.ts.map +1 -0
  146. package/dist/pipeline/standard-lighting/prepare.d.ts +16 -0
  147. package/dist/pipeline/standard-lighting/prepare.d.ts.map +1 -0
  148. package/dist/pipeline/standard-lighting/topology.d.ts +45 -0
  149. package/dist/pipeline/standard-lighting/topology.d.ts.map +1 -0
  150. package/dist/pipeline/standard-lighting/transport.d.ts +36 -0
  151. package/dist/pipeline/standard-lighting/transport.d.ts.map +1 -0
  152. package/dist/pipeline/standard-pipeline.d.ts +4 -13
  153. package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
  154. package/dist/pipeline/standard-post.d.ts.map +1 -1
  155. package/dist/pipeline/standard-profile.d.ts +5 -26
  156. package/dist/pipeline/standard-profile.d.ts.map +1 -1
  157. package/dist/pipeline-builder.d.ts +2 -0
  158. package/dist/pipeline-builder.d.ts.map +1 -1
  159. package/dist/pipeline-spec.d.ts +40 -7
  160. package/dist/pipeline-spec.d.ts.map +1 -1
  161. package/dist/plugin.d.ts.map +1 -1
  162. package/dist/point-shadow-inspection.d.ts +21 -0
  163. package/dist/point-shadow-inspection.d.ts.map +1 -0
  164. package/dist/prepare/extended-lighting/ltc-resources.d.ts +31 -0
  165. package/dist/prepare/extended-lighting/ltc-resources.d.ts.map +1 -0
  166. package/dist/prepare/extended-lighting/resources.d.ts +72 -0
  167. package/dist/prepare/extended-lighting/resources.d.ts.map +1 -0
  168. package/dist/prepare/extended-lighting/spot-modifiers.d.ts +42 -0
  169. package/dist/prepare/extended-lighting/spot-modifiers.d.ts.map +1 -0
  170. package/dist/prepare/extended-lighting/state.d.ts +24 -0
  171. package/dist/prepare/extended-lighting/state.d.ts.map +1 -0
  172. package/dist/record/frame-lighting.d.ts +33 -36
  173. package/dist/record/frame-lighting.d.ts.map +1 -1
  174. package/dist/record/frame-snapshot.d.ts +78 -24
  175. package/dist/record/frame-snapshot.d.ts.map +1 -1
  176. package/dist/record/frame-targets.d.ts +5 -0
  177. package/dist/record/frame-targets.d.ts.map +1 -1
  178. package/dist/record/frame.d.ts +16 -6
  179. package/dist/record/frame.d.ts.map +1 -1
  180. package/dist/record/gpu-pass-timing/contract.d.ts +96 -0
  181. package/dist/record/gpu-pass-timing/contract.d.ts.map +1 -0
  182. package/dist/record/gpu-pass-timing/errors.d.ts +18 -0
  183. package/dist/record/gpu-pass-timing/errors.d.ts.map +1 -0
  184. package/dist/record/gpu-pass-timing/index.d.ts +8 -0
  185. package/dist/record/gpu-pass-timing/index.d.ts.map +1 -0
  186. package/dist/record/gpu-pass-timing/parser.d.ts +11 -0
  187. package/dist/record/gpu-pass-timing/parser.d.ts.map +1 -0
  188. package/dist/record/gpu-pass-timing/session.d.ts +43 -0
  189. package/dist/record/gpu-pass-timing/session.d.ts.map +1 -0
  190. package/dist/record/gpu-timing.d.ts +20 -3
  191. package/dist/record/gpu-timing.d.ts.map +1 -1
  192. package/dist/record/helpers.d.ts +0 -20
  193. package/dist/record/helpers.d.ts.map +1 -1
  194. package/dist/record/main-pass-geometry.d.ts +6 -1
  195. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  196. package/dist/record/main-pass-material.d.ts +25 -2
  197. package/dist/record/main-pass-material.d.ts.map +1 -1
  198. package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
  199. package/dist/record/main-pass.d.ts +2 -0
  200. package/dist/record/main-pass.d.ts.map +1 -1
  201. package/dist/record/mesh-ssbo.d.ts.map +1 -1
  202. package/dist/record/render-context.d.ts +98 -6
  203. package/dist/record/render-context.d.ts.map +1 -1
  204. package/dist/record/shadow-pass.d.ts.map +1 -1
  205. package/dist/record/skybox-post-pass.d.ts.map +1 -1
  206. package/dist/record/typed-frame-graph.d.ts +119 -4
  207. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  208. package/dist/record/view-ubo.d.ts +7 -5
  209. package/dist/record/view-ubo.d.ts.map +1 -1
  210. package/dist/reflection/filter.d.ts +22 -0
  211. package/dist/reflection/filter.d.ts.map +1 -0
  212. package/dist/reflection/gpu-table.d.ts +25 -0
  213. package/dist/reflection/gpu-table.d.ts.map +1 -0
  214. package/dist/reflection/inspection.d.ts +10 -0
  215. package/dist/reflection/inspection.d.ts.map +1 -0
  216. package/dist/reflection/projection.d.ts +140 -0
  217. package/dist/reflection/projection.d.ts.map +1 -0
  218. package/dist/reflection/record-owner.d.ts +60 -0
  219. package/dist/reflection/record-owner.d.ts.map +1 -0
  220. package/dist/render-contract.d.ts +62 -14
  221. package/dist/render-contract.d.ts.map +1 -1
  222. package/dist/render-graph-primitives.d.ts +1 -0
  223. package/dist/render-graph-primitives.d.ts.map +1 -1
  224. package/dist/render-pipeline.d.ts +49 -2
  225. package/dist/render-pipeline.d.ts.map +1 -1
  226. package/dist/render-system-extract.d.ts +160 -31
  227. package/dist/render-system-extract.d.ts.map +1 -1
  228. package/dist/render-system.d.ts +28 -2
  229. package/dist/render-system.d.ts.map +1 -1
  230. package/dist/renderer.d.ts +43 -0
  231. package/dist/renderer.d.ts.map +1 -0
  232. package/dist/scene/probe-blend-record.d.ts +22 -0
  233. package/dist/scene/probe-blend-record.d.ts.map +1 -0
  234. package/dist/scene/probe-blend.d.ts +142 -0
  235. package/dist/scene/probe-blend.d.ts.map +1 -0
  236. package/dist/scene/render-scene-types.d.ts +0 -6
  237. package/dist/scene/render-scene-types.d.ts.map +1 -1
  238. package/dist/scene/render-scene.d.ts +117 -9
  239. package/dist/scene/render-scene.d.ts.map +1 -1
  240. package/dist/scene/visibility/bounds.d.ts +20 -0
  241. package/dist/scene/visibility/bounds.d.ts.map +1 -0
  242. package/dist/scene/visibility/budget.d.ts +16 -0
  243. package/dist/scene/visibility/budget.d.ts.map +1 -0
  244. package/dist/scene/visibility/facet.d.ts +73 -0
  245. package/dist/scene/visibility/facet.d.ts.map +1 -0
  246. package/dist/scene/visibility/gpu-frame-timing.d.ts +41 -0
  247. package/dist/scene/visibility/gpu-frame-timing.d.ts.map +1 -0
  248. package/dist/scene/visibility/gpu-lod.d.ts +46 -0
  249. package/dist/scene/visibility/gpu-lod.d.ts.map +1 -0
  250. package/dist/scene/visibility/inspection.d.ts +132 -0
  251. package/dist/scene/visibility/inspection.d.ts.map +1 -0
  252. package/dist/scene/visibility/lod-selector.d.ts +25 -0
  253. package/dist/scene/visibility/lod-selector.d.ts.map +1 -0
  254. package/dist/scene/visibility/occlusion-confidence.d.ts +81 -0
  255. package/dist/scene/visibility/occlusion-confidence.d.ts.map +1 -0
  256. package/dist/scene/visibility/occlusion-pass.d.ts +57 -0
  257. package/dist/scene/visibility/occlusion-pass.d.ts.map +1 -0
  258. package/dist/scene/visibility/occlusion-query-pool.d.ts +57 -0
  259. package/dist/scene/visibility/occlusion-query-pool.d.ts.map +1 -0
  260. package/dist/scene/visibility/occlusion-runtime.d.ts +96 -0
  261. package/dist/scene/visibility/occlusion-runtime.d.ts.map +1 -0
  262. package/dist/scene/visibility/types.d.ts +34 -0
  263. package/dist/scene/visibility/types.d.ts.map +1 -0
  264. package/dist/shadow-atlas.d.ts +3 -3
  265. package/dist/shadow-atlas.d.ts.map +1 -1
  266. package/dist/ssao-buffers.d.ts +3 -0
  267. package/dist/ssao-buffers.d.ts.map +1 -1
  268. package/dist/ssr/admission.d.ts +122 -0
  269. package/dist/ssr/admission.d.ts.map +1 -0
  270. package/dist/ssr/composition.d.ts +32 -0
  271. package/dist/ssr/composition.d.ts.map +1 -0
  272. package/dist/ssr/errors.d.ts +39 -0
  273. package/dist/ssr/errors.d.ts.map +1 -0
  274. package/dist/ssr/identity.d.ts +13 -0
  275. package/dist/ssr/identity.d.ts.map +1 -0
  276. package/dist/systems/skin-palette-allocator.d.ts +1 -1
  277. package/dist/targets/contracts.d.ts +66 -0
  278. package/dist/targets/contracts.d.ts.map +1 -0
  279. package/dist/targets/graph-projection.d.ts +29 -0
  280. package/dist/targets/graph-projection.d.ts.map +1 -0
  281. package/dist/targets/material-source.d.ts +34 -0
  282. package/dist/targets/material-source.d.ts.map +1 -0
  283. package/dist/targets/owner.d.ts +32 -0
  284. package/dist/targets/owner.d.ts.map +1 -0
  285. package/dist/targets/physical.d.ts +18 -0
  286. package/dist/targets/physical.d.ts.map +1 -0
  287. package/dist/targets/readback.d.ts +39 -0
  288. package/dist/targets/readback.d.ts.map +1 -0
  289. package/dist/temporal/frame-coordinator.d.ts +13 -0
  290. package/dist/temporal/frame-coordinator.d.ts.map +1 -1
  291. package/dist/temporal/frame.d.ts +2 -0
  292. package/dist/temporal/frame.d.ts.map +1 -1
  293. package/dist/temporal/index.mjs +6 -5
  294. package/dist/temporal/view.d.ts +7 -0
  295. package/dist/temporal/view.d.ts.map +1 -1
  296. package/dist/tilemap-chunk-extract-system.d.ts +1 -1
  297. package/dist/typed-render-graph-primitives.d.ts +5 -1
  298. package/dist/typed-render-graph-primitives.d.ts.map +1 -1
  299. package/dist/typed-shadow-passes.d.ts +1 -1
  300. package/dist/typed-shadow-passes.d.ts.map +1 -1
  301. package/dist/volume/passes.d.ts +9 -5
  302. package/dist/volume/passes.d.ts.map +1 -1
  303. package/package.json +20 -20
  304. package/src/__tests__/bloom-exact-zero.unit.test.ts +48 -19
  305. package/src/__tests__/bloom-lifecycle.integration.test.ts +41 -13
  306. package/src/__tests__/camera-target-consumption.unit.test.ts +53 -0
  307. package/src/__tests__/cluster-binner.unit.test.ts +35 -0
  308. package/src/__tests__/cluster-grid-default-surface.unit.test.ts +12 -2
  309. package/src/__tests__/cube-camera-scheduler.unit.test.ts +70 -0
  310. package/src/__tests__/cube-camera-single-submit.perf.test.ts +75 -0
  311. package/src/__tests__/cube-camera-view-matrices.unit.test.ts +41 -0
  312. package/src/__tests__/cube-camera-vocabulary.unit.test.ts +47 -0
  313. package/src/__tests__/cube-camera.integration.test.ts +42 -0
  314. package/src/__tests__/detached-inspection.integration.test.ts +6 -0
  315. package/src/__tests__/device-feature-admission.unit.test.ts +85 -0
  316. package/src/__tests__/direct-light-snapshot.unit.test.ts +62 -6
  317. package/src/__tests__/docs-gate-green.unit.test.ts +38 -0
  318. package/src/__tests__/environment-generation.integration.test.ts +16 -0
  319. package/src/__tests__/evidence-identity.ts +13 -0
  320. package/src/__tests__/evidence-identity.unit.test.ts +24 -0
  321. package/src/__tests__/extended-lighting-recovery.browser.test.ts +14 -0
  322. package/src/__tests__/extended-lighting-recovery.dawn.test.ts +14 -0
  323. package/src/__tests__/extended-lighting-recovery.integration.test.ts +81 -0
  324. package/src/__tests__/extended-lighting-resources.unit.test.ts +51 -0
  325. package/src/__tests__/extended-lighting-zero-feature.integration.test.ts +52 -0
  326. package/src/__tests__/factory-contract.integration.test.ts +288 -29
  327. package/src/__tests__/fog-component.unit.test.ts +67 -0
  328. package/src/__tests__/fog.integration.test.ts +86 -0
  329. package/src/__tests__/frame-observation.contract.test.ts +15 -0
  330. package/src/__tests__/fullscreen-feature-plan.unit.test.ts +37 -0
  331. package/src/__tests__/gpu-driven-batch-topology-pbr.unit.test.ts +146 -0
  332. package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +10 -8
  333. package/src/__tests__/gpu-driven-indirect-raster.dawn.test.ts +18 -1
  334. package/src/__tests__/gpu-driven-production.integration.test.ts +579 -17
  335. package/src/__tests__/gpu-driven-scaling.unit.test.ts +66 -2
  336. package/src/__tests__/gpu-driven-view-gpu-evidence.ts +26 -1
  337. package/src/__tests__/gpu-driven-view-graph.integration.test.ts +101 -6
  338. package/src/__tests__/gpu-driven-view.browser.test.ts +3 -3
  339. package/src/__tests__/gpu-driven-view.dawn.test.ts +3 -3
  340. package/src/__tests__/gpu-driven-view.unit.test.ts +50 -0
  341. package/src/__tests__/gpu-lod-evidence.ts +188 -0
  342. package/src/__tests__/gpu-lod-fallback.integration.test.ts +10 -0
  343. package/src/__tests__/gpu-lod-indirect.integration.test.ts +40 -0
  344. package/src/__tests__/gpu-lod-parity.dawn.test.ts +29 -0
  345. package/src/__tests__/gpu-lod-readiness.integration.test.ts +52 -0
  346. package/src/__tests__/gpu-lod-schema.unit.test.ts +66 -0
  347. package/src/__tests__/gpu-lod-selector.unit.test.ts +28 -0
  348. package/src/__tests__/gpu-lod-silhouette.browser.test.ts +13 -0
  349. package/src/__tests__/gpu-pass-timing-browser-runner.ts +281 -0
  350. package/src/__tests__/gpu-pass-timing-coexistence.integration.test.ts +80 -0
  351. package/src/__tests__/gpu-pass-timing-contract.unit.test.ts +243 -0
  352. package/src/__tests__/gpu-pass-timing-exact-zero.unit.test.ts +131 -0
  353. package/src/__tests__/gpu-pass-timing-lifecycle.integration.test.ts +230 -0
  354. package/src/__tests__/gpu-pass-timing-observation.unit.test.ts +233 -0
  355. package/src/__tests__/gpu-pass-timing-session.unit.test.ts +438 -0
  356. package/src/__tests__/gpu-pass-timing.browser.test.ts +26 -0
  357. package/src/__tests__/gpu-pass-timing.rhi-null.unit.test.ts +125 -0
  358. package/src/__tests__/gpu-resource-store-stride.unit.test.ts +66 -1
  359. package/src/__tests__/gpu-scene.dawn.test.ts +8 -1
  360. package/src/__tests__/gpu-scene.unit.test.ts +49 -3
  361. package/src/__tests__/hdrp-group2-visibility.unit.test.ts +24 -0
  362. package/src/__tests__/instances-culling.integration.test.ts +94 -0
  363. package/src/__tests__/instances-derived-bounds.unit.test.ts +118 -0
  364. package/src/__tests__/light-buffer-layout.unit.test.ts +113 -3
  365. package/src/__tests__/light-inspection.unit.test.ts +55 -0
  366. package/src/__tests__/light-resource-error.unit.test.ts +43 -0
  367. package/src/__tests__/lighting-scene-roundtrip.integration.test.ts +109 -0
  368. package/src/__tests__/lod-bounds.unit.test.ts +42 -0
  369. package/src/__tests__/lod-cpu-lane.integration.test.ts +55 -0
  370. package/src/__tests__/lod-cpu-no-change.perf.test.ts +19 -0
  371. package/src/__tests__/lod-gpu-frame-timing.unit.test.ts +49 -0
  372. package/src/__tests__/lod-multi-view.integration.test.ts +63 -0
  373. package/src/__tests__/lod-occlusion-device-loss.integration.test.ts +27 -0
  374. package/src/__tests__/lod-occlusion-final-decision.integration.test.ts +47 -0
  375. package/src/__tests__/lod-occlusion-inspection.integration.test.ts +217 -0
  376. package/src/__tests__/lod-occlusion-inspection.schema.test-d.ts +14 -0
  377. package/src/__tests__/lod-occlusion-inspection.unit.test.ts +72 -0
  378. package/src/__tests__/lod-occlusion-visual-evidence.ts +91 -0
  379. package/src/__tests__/lod-occlusion.browser.test.ts +15 -0
  380. package/src/__tests__/lod-occlusion.dawn.test.ts +20 -0
  381. package/src/__tests__/lod-selector.profile.test.ts +35 -0
  382. package/src/__tests__/lod-selector.unit.test.ts +68 -0
  383. package/src/__tests__/lod-visibility-identity.unit.test.ts +198 -0
  384. package/src/__tests__/lod-visibility-lifecycle.integration.test.ts +132 -0
  385. package/src/__tests__/ltc-resources.unit.test.ts +32 -0
  386. package/src/__tests__/material-contract-inventory.unit.test.ts +17 -0
  387. package/src/__tests__/material-observation.integration.test.ts +101 -0
  388. package/src/__tests__/occlusion-confidence.unit.test.ts +168 -0
  389. package/src/__tests__/occlusion-invalidation.integration.test.ts +37 -0
  390. package/src/__tests__/occlusion-pass-routing.unit.test.ts +16 -0
  391. package/src/__tests__/occlusion-pass.dawn.test.ts +20 -0
  392. package/src/__tests__/occlusion-pass.integration.test.ts +43 -0
  393. package/src/__tests__/occlusion-pass.rhi-null.test.ts +22 -0
  394. package/src/__tests__/occlusion-query-pool.rhi-null.test.ts +21 -0
  395. package/src/__tests__/occlusion-query-pool.unit.test.ts +95 -0
  396. package/src/__tests__/occlusion-query-ticket.integration.test.ts +47 -0
  397. package/src/__tests__/occlusion-query.dawn.test.ts +16 -0
  398. package/src/__tests__/occlusion-query.evidence.ts +178 -0
  399. package/src/__tests__/occlusion-query.rhi-null.test.ts +31 -0
  400. package/src/__tests__/occlusion-runtime.unit.test.ts +120 -0
  401. package/src/__tests__/pbr-view-layout.unit.test.ts +16 -0
  402. package/src/__tests__/point-shadow-inspection.unit.test.ts +60 -0
  403. package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +196 -0
  404. package/src/__tests__/probe-bindings.unit.test.ts +200 -0
  405. package/src/__tests__/probe-blend-numeric.unit.test.ts +67 -0
  406. package/src/__tests__/probe-blend-oracle.unit.test.ts +136 -0
  407. package/src/__tests__/probe-capacity.integration.test.ts +97 -0
  408. package/src/__tests__/probe-recovery.integration.test.ts +104 -0
  409. package/src/__tests__/probe-scene-dirty.integration.test.ts +211 -0
  410. package/src/__tests__/profiler-phase-catalog.test.ts +4 -1
  411. package/src/__tests__/profiler-phase-token.test.ts +1 -0
  412. package/src/__tests__/public-surface.test-d.ts +10 -0
  413. package/src/__tests__/rect-area-boundary.unit.test.ts +24 -0
  414. package/src/__tests__/reflection-fallback-graph.unit.test.ts +67 -0
  415. package/src/__tests__/reflection-fallback-inspection.unit.test.ts +77 -0
  416. package/src/__tests__/reflection-fallback-lifecycle.integration.test.ts +21 -0
  417. package/src/__tests__/reflection-fallback-projection.unit.test.ts +90 -0
  418. package/src/__tests__/reflection-fallback-source.unit.test.ts +32 -0
  419. package/src/__tests__/reflection-probe-authoring.unit.test.ts +71 -0
  420. package/src/__tests__/reflection-probe-filter.unit.test.ts +54 -0
  421. package/src/__tests__/reflection-probe-ibl-topology.unit.test.ts +45 -0
  422. package/src/__tests__/reflection-probe-owner-placement.unit.test.ts +39 -0
  423. package/src/__tests__/reflection-probe-pass-encode.integration.test.ts +101 -0
  424. package/src/__tests__/reflection-probe-performance.perf.test.ts +60 -0
  425. package/src/__tests__/reflection-probe-record-owner.characterization.unit.test.ts +120 -0
  426. package/src/__tests__/reflection-probe-recovery.integration.test.ts +55 -0
  427. package/src/__tests__/reflection-probe-selection.unit.test.ts +46 -0
  428. package/src/__tests__/render-error-code-owner.test-d.ts +19 -3
  429. package/src/__tests__/render-feature-docs.unit.test.ts +12 -7
  430. package/src/__tests__/render-graph-fallback.integration.test.ts +24 -0
  431. package/src/__tests__/render-perf/extended-lighting.bench.test.ts +66 -0
  432. package/src/__tests__/render-scene-projection.unit.test.ts +447 -18
  433. package/src/__tests__/render-target-admission.unit.test.ts +80 -0
  434. package/src/__tests__/render-target-graph-projection.unit.test.ts +102 -0
  435. package/src/__tests__/render-target-inspection.unit.test.ts +52 -0
  436. package/src/__tests__/render-target-material-source.test-d.ts +30 -0
  437. package/src/__tests__/render-target-material-source.unit.test.ts +97 -0
  438. package/src/__tests__/render-target-owner-placement.unit.test.ts +203 -0
  439. package/src/__tests__/render-target-owner.unit.test.ts +122 -0
  440. package/src/__tests__/render-target-probe-soak.perf.test.ts +94 -0
  441. package/src/__tests__/render-target-public-schema.json +107 -0
  442. package/src/__tests__/render-target-public-schema.unit.test.ts +91 -0
  443. package/src/__tests__/render-target-public-surface.test-d.ts +83 -0
  444. package/src/__tests__/render-target-readback.integration.test.ts +90 -0
  445. package/src/__tests__/render-target-recovery.integration.test.ts +85 -0
  446. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +124 -0
  447. package/src/__tests__/renderer-lifecycle.integration.test.ts +14 -0
  448. package/src/__tests__/shader-stage-surface.unit.test.ts +19 -4
  449. package/src/__tests__/skin-motion-regression.unit.test.ts +154 -0
  450. package/src/__tests__/skinned-shadow-caster.test.ts +13 -4
  451. package/src/__tests__/spot-modifier-projection.unit.test.ts +70 -0
  452. package/src/__tests__/ssr-admission-matrix.integration.test.ts +206 -0
  453. package/src/__tests__/ssr-composition.unit.test.ts +46 -0
  454. package/src/__tests__/ssr-fallback-zero-work.bench.test.ts +94 -0
  455. package/src/__tests__/ssr-recovery-errors.unit.test.ts +58 -0
  456. package/src/__tests__/standard-cluster-capability.integration.test.ts +63 -0
  457. package/src/__tests__/standard-cluster-transport.unit.test.ts +121 -0
  458. package/src/__tests__/standard-cluster-variant-resolution.unit.test.ts +299 -0
  459. package/src/__tests__/standard-clustered-pipeline.unit.test.ts +15 -5
  460. package/src/__tests__/standard-lighting-inspection.integration.test.ts +138 -0
  461. package/src/__tests__/standard-lighting-ssot.unit.test.ts +18 -0
  462. package/src/__tests__/standard-lighting-write.unit.test.ts +441 -0
  463. package/src/__tests__/standard-lighting.integration.test.ts +67 -0
  464. package/src/__tests__/standard-pipeline.dawn.test.ts +206 -5
  465. package/src/__tests__/standard-pipeline.integration.test.ts +181 -150
  466. package/src/__tests__/standard-probe-binding.unit.test.ts +25 -0
  467. package/src/__tests__/standard-profile-oracle.unit.test.ts +23 -38
  468. package/src/__tests__/temporal-fallback-generation.integration.test.ts +16 -0
  469. package/src/__tests__/temporal-view.unit.test.ts +3 -3
  470. package/src/__tests__/transmission-prewarm.unit.test.ts +69 -0
  471. package/src/__tests__/typed-pipeline-topology.unit.test.ts +395 -14
  472. package/src/__tests__/urp-pipeline-targets.unit.test.ts +2 -2
  473. package/src/__tests__/visibility-budget.integration.test.ts +140 -0
  474. package/src/__tests__/visibility-budget.unit.test.ts +70 -0
  475. package/src/__tests__/visibility-extract.integration.test.ts +11 -6
  476. package/src/__tests__/visibility-resolver.unit.test.ts +15 -0
  477. package/src/__tests__/volumetric-fog-point-spot-accumulation.integration.test.ts +6 -3
  478. package/src/__tests__/volumetric-fog-stage-readback.dawn.test.ts +31 -28
  479. package/src/assembly/backend-contract.ts +13 -0
  480. package/src/assembly/device-feature-admission.ts +80 -0
  481. package/src/assembly/directional-shadow-inspection.ts +122 -0
  482. package/src/assembly/factory.ts +1411 -366
  483. package/src/assembly/host-contract.ts +35 -0
  484. package/src/assembly/render-target-host.ts +847 -0
  485. package/src/assets/asset-decoders.ts +1 -1
  486. package/src/assets/render-asset-port.ts +7 -1
  487. package/src/capture/cube-views.ts +69 -0
  488. package/src/capture/scheduler.ts +265 -0
  489. package/src/cluster-binner.ts +31 -0
  490. package/src/components/__tests__/lighting-authoring.unit.test.ts +39 -0
  491. package/src/components/__tests__/rect-area-light.unit.test.ts +33 -0
  492. package/src/components/__tests__/schema-contract.unit.test.ts +12 -0
  493. package/src/components/__tests__/schema-parity.unit.test.ts +9 -0
  494. package/src/components/camera.ts +4 -0
  495. package/src/components/cube-camera.ts +52 -0
  496. package/src/components/directional-light.ts +22 -2
  497. package/src/components/directional-shadow-filter.ts +57 -0
  498. package/src/components/index.ts +5 -0
  499. package/src/components/light-helpers.ts +133 -8
  500. package/src/components/light-probe.ts +13 -0
  501. package/src/components/rect-area-light.ts +15 -0
  502. package/src/components/reflection-probe.ts +49 -0
  503. package/src/components/scene-instance.ts +6 -0
  504. package/src/components/spot-light.ts +3 -0
  505. package/src/device/__tests__/extended-lighting-recovery.unit.test.ts +24 -0
  506. package/src/device/gpu-residency.ts +140 -9
  507. package/src/device/lifecycle-transaction.ts +12 -0
  508. package/src/environment/frame.ts +6 -24
  509. package/src/environment/lifecycle.ts +20 -1
  510. package/src/errors/gpu-driven.ts +92 -0
  511. package/src/errors/index.ts +1 -0
  512. package/src/errors/recover.ts +32 -0
  513. package/src/errors/render.ts +341 -143
  514. package/src/extract/camera.ts +3 -3
  515. package/src/extract/directional-shadow-projection.ts +61 -0
  516. package/src/extract/environment.ts +57 -0
  517. package/src/extract/gpu-driven.ts +108 -0
  518. package/src/extract/visibility.ts +2 -1
  519. package/src/features/fullscreen.ts +3 -0
  520. package/src/features/targets.ts +4 -1
  521. package/src/features/types.ts +10 -0
  522. package/src/fullscreen-post-process-pass.ts +43 -2
  523. package/src/gpu-driven/batch-topology.ts +207 -28
  524. package/src/gpu-driven/prepared-draw.ts +208 -0
  525. package/src/gpu-driven/production-raster.ts +953 -43
  526. package/src/gpu-driven/view-gpu.ts +411 -29
  527. package/src/gpu-scene-schema.ts +13 -0
  528. package/src/gpu-scene.ts +140 -64
  529. package/src/gpu-usage.ts +0 -1
  530. package/src/hdrp-buffers.ts +169 -63
  531. package/src/ibl/IblPipelineCache.ts +11 -0
  532. package/src/ibl/kernel-cache.ts +88 -0
  533. package/src/index.ts +222 -0
  534. package/src/inspection-types.ts +276 -1
  535. package/src/instances-derived-bounds.ts +171 -0
  536. package/src/internal.ts +38 -0
  537. package/src/light-buffer-layout.ts +155 -310
  538. package/src/materials.ts +16 -2
  539. package/src/mesh-material-bindings.ts +132 -0
  540. package/src/pbr-pipeline.ts +255 -60
  541. package/src/pipeline/standard-forward-lane.ts +93 -16
  542. package/src/pipeline/standard-lighting/errors.ts +6 -0
  543. package/src/pipeline/standard-lighting/graph.ts +158 -0
  544. package/src/pipeline/standard-lighting/inspection.ts +88 -0
  545. package/src/pipeline/standard-lighting/layout.ts +31 -0
  546. package/src/pipeline/standard-lighting/light-frame.ts +35 -0
  547. package/src/pipeline/standard-lighting/membership.ts +43 -0
  548. package/src/pipeline/standard-lighting/prepare.ts +37 -0
  549. package/src/pipeline/standard-lighting/topology.ts +120 -0
  550. package/src/pipeline/standard-lighting/transport.ts +78 -0
  551. package/src/pipeline/standard-pipeline.ts +124 -193
  552. package/src/pipeline/standard-post.ts +26 -7
  553. package/src/pipeline/standard-profile.ts +17 -47
  554. package/src/pipeline-builder.ts +5 -2
  555. package/src/pipeline-spec.ts +95 -21
  556. package/src/plugin.ts +4 -0
  557. package/src/point-shadow-inspection.ts +55 -0
  558. package/src/prepare/__tests__/extended-lighting-capability.unit.test.ts +56 -0
  559. package/src/prepare/extended-lighting/ltc-resources.ts +136 -0
  560. package/src/prepare/extended-lighting/resources.ts +389 -0
  561. package/src/prepare/extended-lighting/spot-modifiers.ts +194 -0
  562. package/src/prepare/extended-lighting/state.ts +92 -0
  563. package/src/record/__tests__/gpu-timing.unit.test.ts +177 -0
  564. package/src/record/__tests__/temporal-payload.unit.test.ts +64 -1
  565. package/src/record/__tests__/transparent-group2-selection.unit.test.ts +73 -0
  566. package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +20 -1
  567. package/src/record/__tests__/view-ubo-layout.unit.test.ts +40 -0
  568. package/src/record/frame-lighting.ts +591 -452
  569. package/src/record/frame-snapshot.ts +92 -39
  570. package/src/record/frame-targets.ts +17 -0
  571. package/src/record/frame.ts +662 -158
  572. package/src/record/gpu-pass-timing/contract.ts +180 -0
  573. package/src/record/gpu-pass-timing/errors.ts +34 -0
  574. package/src/record/gpu-pass-timing/index.ts +37 -0
  575. package/src/record/gpu-pass-timing/parser.ts +97 -0
  576. package/src/record/gpu-pass-timing/session.ts +938 -0
  577. package/src/record/gpu-timing.ts +112 -39
  578. package/src/record/helpers.ts +0 -58
  579. package/src/record/main-pass-geometry.ts +324 -119
  580. package/src/record/main-pass-material.ts +103 -10
  581. package/src/record/main-pass-sprite-draws.ts +99 -103
  582. package/src/record/main-pass.ts +71 -15
  583. package/src/record/mesh-ssbo.ts +47 -36
  584. package/src/record/render-context.ts +136 -3
  585. package/src/record/shadow-pass.ts +83 -68
  586. package/src/record/skybox-post-pass.ts +20 -4
  587. package/src/record/typed-frame-graph.ts +1034 -43
  588. package/src/record/view-ubo.ts +72 -47
  589. package/src/reflection/filter.ts +81 -0
  590. package/src/reflection/gpu-table.ts +44 -0
  591. package/src/reflection/inspection.ts +67 -0
  592. package/src/reflection/projection.ts +476 -0
  593. package/src/reflection/record-owner.ts +1283 -0
  594. package/src/render-contract.ts +93 -10
  595. package/src/render-graph-primitives.ts +9 -4
  596. package/src/render-pipeline.ts +122 -2
  597. package/src/render-system-extract.ts +1041 -225
  598. package/src/render-system.ts +1310 -85
  599. package/src/renderer.ts +222 -0
  600. package/src/scene/__tests__/changed-block-summary.candidate.test.ts +36 -0
  601. package/src/scene/probe-blend-record.ts +54 -0
  602. package/src/scene/probe-blend.ts +813 -0
  603. package/src/scene/render-scene-types.ts +0 -7
  604. package/src/scene/render-scene.ts +942 -171
  605. package/src/scene/visibility/bounds.ts +57 -0
  606. package/src/scene/visibility/budget.ts +40 -0
  607. package/src/scene/visibility/facet.ts +400 -0
  608. package/src/scene/visibility/gpu-frame-timing.ts +101 -0
  609. package/src/scene/visibility/gpu-lod.ts +119 -0
  610. package/src/scene/visibility/inspection.ts +313 -0
  611. package/src/scene/visibility/lod-selector.ts +84 -0
  612. package/src/scene/visibility/occlusion-confidence.ts +478 -0
  613. package/src/scene/visibility/occlusion-pass.ts +165 -0
  614. package/src/scene/visibility/occlusion-query-pool.ts +178 -0
  615. package/src/scene/visibility/occlusion-runtime.ts +672 -0
  616. package/src/scene/visibility/types.ts +50 -0
  617. package/src/shadow-atlas.ts +5 -5
  618. package/src/ssao-buffers.ts +9 -2
  619. package/src/ssr/admission.ts +451 -0
  620. package/src/ssr/composition.ts +73 -0
  621. package/src/ssr/errors.ts +76 -0
  622. package/src/ssr/identity.ts +12 -0
  623. package/src/systems/skin-palette-allocator.ts +1 -1
  624. package/src/targets/contracts.ts +182 -0
  625. package/src/targets/graph-projection.ts +169 -0
  626. package/src/targets/material-source.ts +143 -0
  627. package/src/targets/owner.ts +214 -0
  628. package/src/targets/physical.ts +18 -0
  629. package/src/targets/readback.ts +153 -0
  630. package/src/temporal/frame-coordinator.ts +26 -0
  631. package/src/temporal/frame.ts +14 -1
  632. package/src/temporal/view.ts +11 -0
  633. package/src/tilemap-chunk-extract-system.ts +4 -4
  634. package/src/transmission/__tests__/standard-transmission.dawn.test.ts +59 -20
  635. package/src/typed-render-graph-primitives.ts +53 -3
  636. package/src/typed-shadow-passes.ts +46 -40
  637. package/src/volume/passes.ts +61 -58
  638. package/dist/.tsbuildinfo +0 -1
  639. package/dist/chunk-4XINDLAY.mjs.map +0 -1
  640. package/dist/chunk-CDTWTHZR.mjs.map +0 -1
  641. package/dist/chunk-M2MP4ZOU.mjs +0 -73
  642. package/dist/chunk-M2MP4ZOU.mjs.map +0 -1
  643. package/dist/chunk-PCHBPYML.mjs.map +0 -1
  644. package/dist/chunk-TBQJOK2L.mjs.map +0 -1
  645. 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();
@@ -192,6 +279,7 @@ describe('GpuDrivenView graph projection', () => {
192
279
  const frameState = {
193
280
  compiledFrameGraph: null,
194
281
  compiledFrameGraphTopologyKey: null,
282
+ compiledFrameGraphGeneration: 0,
195
283
  retiredCompiledFrameGraphs: new Set(),
196
284
  activePipeline,
197
285
  installedPipelineConfig: undefined,
@@ -209,14 +297,14 @@ describe('GpuDrivenView graph projection', () => {
209
297
  pointShadow: [],
210
298
  spot: [],
211
299
  } as unknown as ExtractedLights;
212
- const compile = (prepared: PreparedGpuDrivenFrame) =>
300
+ const compile = (prepared: PreparedGpuDrivenFrame, width = 1) =>
213
301
  ensureCompiledFrameGraph(
214
302
  internals,
215
303
  frameState,
216
304
  pipelineState,
217
305
  makeZeroCameraFallbackSnapshot(),
218
306
  lights,
219
- 1,
307
+ width,
220
308
  1,
221
309
  undefined,
222
310
  prepared,
@@ -243,6 +331,7 @@ describe('GpuDrivenView graph projection', () => {
243
331
  const compiledPrevious = compile(preparedFrame(view));
244
332
  expect(compiledPrevious).not.toBeNull();
245
333
  if (compiledPrevious === null) return;
334
+ expect(frameState.compiledFrameGraphGeneration).toBe(1);
246
335
 
247
336
  Object.defineProperty(device, 'surfaceViewFormats', {
248
337
  configurable: true,
@@ -269,7 +358,8 @@ describe('GpuDrivenView graph projection', () => {
269
358
 
270
359
  expect(view.inspect()).toMatchObject({ candidateCount: 2, candidateCapacity: 2 });
271
360
  forceCompileFailure = true;
272
- expect(compile(preparedFrame(view))).toBe(compiledPrevious);
361
+ expect(compile(preparedFrame(view), 2)).toBe(compiledPrevious);
362
+ expect(frameState.compiledFrameGraphGeneration).toBe(1);
273
363
  expect(errors).toHaveLength(1);
274
364
  expect(destroyBuffer.mock.calls.some(([buffer]) => buffer === previousVisible)).toBe(false);
275
365
  const encoder = device.createCommandEncoder({ label: 'gpu-driven-view-lkg' }).unwrap();
@@ -277,13 +367,18 @@ describe('GpuDrivenView graph projection', () => {
277
367
 
278
368
  forceCompileFailure = false;
279
369
  const retirePrevious = vi.spyOn(compiledPrevious, 'retire');
280
- const acceptedGraph = compile(preparedFrame(view));
370
+ const acceptedGraph = compile(preparedFrame(view), 2);
281
371
  expect(acceptedGraph).not.toBe(compiledPrevious);
372
+ expect(frameState.compiledFrameGraphGeneration).toBe(2);
282
373
  expect(retirePrevious).toHaveBeenCalledOnce();
283
374
  await device.queue.onSubmittedWorkDone();
284
375
  await Promise.resolve();
285
376
  expect(destroyBuffer.mock.calls.some(([buffer]) => buffer === previousVisible)).toBe(true);
286
377
 
378
+ const returnedToPreviousTopology = compile(preparedFrame(view), 1);
379
+ expect(returnedToPreviousTopology).not.toBe(acceptedGraph);
380
+ expect(frameState.compiledFrameGraphGeneration).toBe(3);
381
+
287
382
  view.dispose();
288
383
  sceneAvailability.scene.dispose();
289
384
  });
@@ -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
+ });