@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,20 +1,28 @@
1
1
  import { HANDLE_CUBE } from '@forgeax/engine-assets-runtime';
2
2
  import { World } from '@forgeax/engine-ecs';
3
3
  import { deriveVertexLayoutProjection } from '@forgeax/engine-geometry';
4
+ import { vec3 } from '@forgeax/engine-math';
4
5
  import { RenderGraphBuilder } from '@forgeax/engine-render-graph';
6
+ import type { BindGroup } from '@forgeax/engine-rhi';
5
7
  import { type RhiNullDevice, rhi } from '@forgeax/engine-rhi-null';
6
8
  import { Transform } from '@forgeax/engine-scene';
7
9
  import {
10
+ createStandardPbrArtifactReceipt,
8
11
  DEFAULT_STANDARD_PBR_PARAM_SCHEMA,
9
12
  DEFAULT_UNLIT_PARAM_SCHEMA,
10
13
  } from '@forgeax/engine-shader';
11
14
  import { ok } from '@forgeax/engine-types';
12
15
  import { describe, expect, it } from 'vitest';
13
16
  import { createRenderer as constructRenderer } from '../assembly/factory';
14
- import { Camera, MeshFilter, MeshRenderer } from '../components';
17
+ import { Camera, MeshFilter, MeshRenderer, PointLight } from '../components';
15
18
  import type { MeshGpuHandles } from '../device/gpu-residency';
16
19
  import { BatchTopology } from '../gpu-driven/batch-topology';
17
- import { GpuDrivenProduction } from '../gpu-driven/production-raster';
20
+ import {
21
+ adaptStandardPbrFrameResources,
22
+ GpuDrivenProduction,
23
+ projectStandardPbrScene,
24
+ } from '../gpu-driven/production-raster';
25
+ import { GPU_DRIVEN_VIEW_WGSL } from '../gpu-driven/view-gpu';
18
26
  import { GpuBuffer } from '../gpu-resource';
19
27
  import { GpuScene } from '../gpu-scene';
20
28
  import {
@@ -23,12 +31,19 @@ import {
23
31
  GPU_BUFFER_USAGE_UNIFORM,
24
32
  GPU_BUFFER_USAGE_VERTEX,
25
33
  } from '../gpu-usage';
26
- import { makeZeroCameraFallbackSnapshot } from '../record/frame-snapshot';
34
+ import { makeZeroCameraFallbackSnapshot, worldEntityKey } from '../record/frame-snapshot';
27
35
  import type { RenderPipelineFrame } from '../render-pipeline';
28
36
  import type { MaterialSnapshot, RenderableSnapshot } from '../render-system-extract';
29
37
  import { RenderScene } from '../scene/render-scene';
30
38
 
39
+ const STANDARD_PBR_RECEIPT = createStandardPbrArtifactReceipt();
40
+
31
41
  const EMPTY_RESOURCE_CLASS = JSON.stringify({ textures: [], samplers: [], video: [] });
42
+ const OUTPUT_TRANSFORM_WGSL =
43
+ '@vertex fn vs_main(@builtin(vertex_index) i: u32) -> @builtin(position) vec4<f32> { ' +
44
+ 'var p = array<vec2<f32>, 3>(vec2<f32>(-1.0, -1.0), vec2<f32>(3.0, -1.0), vec2<f32>(-1.0, 3.0)); ' +
45
+ 'return vec4<f32>(p[i], 0.0, 1.0); } ' +
46
+ '@fragment fn fs_main() -> @location(0) vec4<f32> { return vec4<f32>(0.0); }';
32
47
 
33
48
  const STANDARD_PBR_URP_VARIANT_KEY =
34
49
  'CLUSTER_FORWARD_AVAILABLE=false+STORAGE_BUFFER_AVAILABLE=true+TRANSMISSION_AVAILABLE=false+VERTEX_COLOR_AVAILABLE=false';
@@ -38,7 +53,7 @@ const STANDARD_PBR_TRANSMISSION_VARIANT_KEY =
38
53
  function manifestDataUrl(): string {
39
54
  const pbrWgsl = '/* pbr stub - calls f_schlick( */';
40
55
  const unlitWgsl = '/* unlit stub */';
41
- const tonemapWgsl = '/* tonemap stub - struct TonemapParams { exposure: f32 }; */';
56
+ const tonemapWgsl = OUTPUT_TRANSFORM_WGSL;
42
57
  const standardPbrVariant = (transmissionAvailable: boolean, definesKey: string) => ({
43
58
  definesKey,
44
59
  defines: {
@@ -121,7 +136,7 @@ function snapshot(entityKey: number, assetHandle: number): RenderableSnapshot {
121
136
  };
122
137
  }
123
138
 
124
- function mesh(device: RhiNullDevice, indexed = true): MeshGpuHandles {
139
+ function mesh(device: RhiNullDevice, indexed = true, withLod = false): MeshGpuHandles {
125
140
  const vertex = device
126
141
  .createBuffer({
127
142
  size: 144,
@@ -163,10 +178,401 @@ function mesh(device: RhiNullDevice, indexed = true): MeshGpuHandles {
163
178
  topology: 'triangle-list',
164
179
  },
165
180
  ],
181
+ ...(withLod
182
+ ? {
183
+ lodRanges: [
184
+ [{ first: 0, count: 3, baseVertex: 0 }],
185
+ [{ first: 3, count: 1, baseVertex: 0 }],
186
+ ],
187
+ }
188
+ : {}),
166
189
  };
167
190
  }
168
191
 
169
192
  describe('GPU-driven production projection', () => {
193
+ it('retains suppressed LOD candidates for selector accounting while admitting only active draws', async () => {
194
+ const adapter = (await rhi.requestAdapter()).unwrap();
195
+ const device = (await adapter.requestDevice()).unwrap() as RhiNullDevice;
196
+ const shader = (await rhi.createShaderModule(device, { code: 'synthetic' })).unwrap();
197
+ const viewLayout = device
198
+ .createBindGroupLayout({
199
+ entries: [{ binding: 0, visibility: 0x1, buffer: { type: 'uniform' } }],
200
+ })
201
+ .unwrap();
202
+ const projection = new RenderScene();
203
+ const lodMeshGuid = '00000000-0000-7000-8000-000000000001' as never;
204
+ const makeLodSnapshot = (entityKey: number): RenderableSnapshot => {
205
+ const base = snapshot(entityKey, 3);
206
+ const draw = base.gpuDrivenDraws?.[0];
207
+ if (draw === undefined) throw new Error('LOD test snapshot has no draw');
208
+ return {
209
+ ...base,
210
+ lods: [{ mesh: lodMeshGuid, screenCoverage: 0.5 }],
211
+ gpuDrivenDraws: [{ ...draw, lodRanges: [{ first: 3, count: 1, baseVertex: 0 }] }],
212
+ };
213
+ };
214
+ const delta = projection.apply([
215
+ { kind: 'create', snapshot: makeLodSnapshot(1) },
216
+ { kind: 'create', snapshot: makeLodSnapshot(2) },
217
+ ]);
218
+ const availability = GpuScene.create(device, 2).unwrap();
219
+ expect(availability.status).toBe('available');
220
+ if (availability.status !== 'available') return;
221
+ availability.scene.sync(delta).unwrap();
222
+ const topology = new BatchTopology();
223
+ topology.rebuild(projection.slotsSnapshot());
224
+ const production = new GpuDrivenProduction(device, {
225
+ createShaderModule: () => ok(shader),
226
+ });
227
+ const prepared = production
228
+ .prepare({
229
+ scene: {
230
+ scene: availability.scene,
231
+ plan: topology.plan(),
232
+ slots: projection.slotsSnapshot(),
233
+ },
234
+ camera: makeZeroCameraFallbackSnapshot(),
235
+ meshes: new Map([[3, mesh(device, true, true)]]),
236
+ viewBindGroupLayout: viewLayout,
237
+ meshResidencyEpoch: 1,
238
+ hdrp: false,
239
+ activeEntityKeys: new Set([worldEntityKey(0, 1)]),
240
+ activeEntityRevision: 1,
241
+ })
242
+ .unwrap();
243
+ expect(prepared?.entityKeys).toEqual(new Set([worldEntityKey(0, 1)]));
244
+ // Both candidates are uploaded to the selector (2 * 304 bytes), while
245
+ // only the active entity is admitted into the compacted draw stream.
246
+ expect(production.inspect()).toMatchObject({
247
+ candidateUploadBytes: 2 * 304,
248
+ batchCount: 1,
249
+ indirectDrawCount: 1,
250
+ });
251
+ if (prepared === undefined) return;
252
+ const projected = prepared
253
+ .project(new RenderGraphBuilder<RenderPipelineFrame>(), 'rgba8unorm', 1)
254
+ .unwrap();
255
+ expect(projected.accesses.length).toBeGreaterThan(0);
256
+ expect(production.inspect()).toMatchObject({
257
+ batchBindGroupCreates: 1,
258
+ indirectDrawCount: 1,
259
+ });
260
+ expect(production.inspect().indirectDrawCount).toBeGreaterThan(0);
261
+ expect(production.inspect().indirectDrawCount).toBeLessThanOrEqual(
262
+ production.inspect().batchCount,
263
+ );
264
+
265
+ const suppressed = production
266
+ .prepare({
267
+ scene: {
268
+ scene: availability.scene,
269
+ plan: topology.plan(),
270
+ slots: projection.slotsSnapshot(),
271
+ },
272
+ camera: makeZeroCameraFallbackSnapshot(),
273
+ meshes: new Map([[3, mesh(device, true, true)]]),
274
+ viewBindGroupLayout: viewLayout,
275
+ meshResidencyEpoch: 1,
276
+ hdrp: false,
277
+ activeEntityKeys: new Set(),
278
+ activeEntityRevision: 2,
279
+ })
280
+ .unwrap();
281
+ expect(suppressed?.topologySignature).not.toBe(prepared.topologySignature);
282
+ expect(production.inspect()).toMatchObject({
283
+ batchCount: 1,
284
+ indirectDrawCount: 0,
285
+ });
286
+
287
+ const restored = production
288
+ .prepare({
289
+ scene: {
290
+ scene: availability.scene,
291
+ plan: topology.plan(),
292
+ slots: projection.slotsSnapshot(),
293
+ },
294
+ camera: makeZeroCameraFallbackSnapshot(),
295
+ meshes: new Map([[3, mesh(device, true, true)]]),
296
+ viewBindGroupLayout: viewLayout,
297
+ meshResidencyEpoch: 1,
298
+ hdrp: false,
299
+ activeEntityKeys: new Set([worldEntityKey(0, 1)]),
300
+ activeEntityRevision: 3,
301
+ })
302
+ .unwrap();
303
+ expect(restored?.topologySignature).not.toBe(suppressed?.topologySignature);
304
+ expect(production.inspect()).toMatchObject({
305
+ batchCount: 1,
306
+ indirectDrawCount: 1,
307
+ });
308
+ expect(GPU_DRIVEN_VIEW_WGSL.indexOf('atomicAdd(&counters[lodCounterIndex')).toBeLessThan(
309
+ GPU_DRIVEN_VIEW_WGSL.indexOf('if (candidate.submitAdmission == 0u)'),
310
+ );
311
+ expect(GPU_DRIVEN_VIEW_WGSL.indexOf('if (candidate.submitAdmission == 0u)')).toBeLessThan(
312
+ GPU_DRIVEN_VIEW_WGSL.indexOf('atomicAdd(&counters[counterIndex(candidate.batchIndex)], 1u)'),
313
+ );
314
+ production.dispose();
315
+ availability.scene.dispose();
316
+ });
317
+
318
+ it('keeps a suppressed-only batch in selector telemetry without raster resources', async () => {
319
+ const adapter = (await rhi.requestAdapter()).unwrap();
320
+ const device = (await adapter.requestDevice()).unwrap() as RhiNullDevice;
321
+ const shader = (await rhi.createShaderModule(device, { code: 'synthetic' })).unwrap();
322
+ const viewLayout = device
323
+ .createBindGroupLayout({
324
+ entries: [{ binding: 0, visibility: 0x1, buffer: { type: 'uniform' } }],
325
+ })
326
+ .unwrap();
327
+ const projection = new RenderScene();
328
+ const candidate = snapshot(1, 3);
329
+ const delta = projection.apply([{ kind: 'create', snapshot: candidate }]);
330
+ const availability = GpuScene.create(device, 1).unwrap();
331
+ expect(availability.status).toBe('available');
332
+ if (availability.status !== 'available') return;
333
+ availability.scene.sync(delta).unwrap();
334
+ const topology = new BatchTopology();
335
+ topology.rebuild(projection.slotsSnapshot());
336
+ const production = new GpuDrivenProduction(device, {
337
+ createShaderModule: () => ok(shader),
338
+ });
339
+ const prepared = production
340
+ .prepare({
341
+ scene: {
342
+ scene: availability.scene,
343
+ plan: topology.plan(),
344
+ slots: projection.slotsSnapshot(),
345
+ },
346
+ camera: makeZeroCameraFallbackSnapshot(),
347
+ meshes: new Map([[3, mesh(device)]]),
348
+ viewBindGroupLayout: viewLayout,
349
+ meshResidencyEpoch: 1,
350
+ hdrp: false,
351
+ activeEntityKeys: new Set(),
352
+ activeEntityRevision: 1,
353
+ })
354
+ .unwrap();
355
+ expect(prepared).toBeDefined();
356
+ expect(production.requiresSceneRows()).toBe(true);
357
+ expect(production.inspect()).toMatchObject({
358
+ batchCount: 1,
359
+ indirectDrawCount: 0,
360
+ batchBindGroupCreates: 0,
361
+ });
362
+ if (prepared === undefined) return;
363
+ prepared.project(new RenderGraphBuilder<RenderPipelineFrame>(), 'rgba8unorm', 1).unwrap();
364
+ expect(production.inspect()).toMatchObject({
365
+ batchCount: 1,
366
+ indirectDrawCount: 0,
367
+ batchBindGroupCreates: 0,
368
+ });
369
+ production.dispose();
370
+ availability.scene.dispose();
371
+ });
372
+
373
+ it('keeps GPU batches World-homogeneous for same-submit counter attribution', async () => {
374
+ const adapter = (await rhi.requestAdapter()).unwrap();
375
+ const device = (await adapter.requestDevice()).unwrap() as RhiNullDevice;
376
+ const shader = (await rhi.createShaderModule(device, { code: 'synthetic' })).unwrap();
377
+ const viewLayout = device
378
+ .createBindGroupLayout({
379
+ entries: [{ binding: 0, visibility: 0x1, buffer: { type: 'uniform' } }],
380
+ })
381
+ .unwrap();
382
+ const projection = new RenderScene();
383
+ const first = snapshot(1, 3);
384
+ const second = { ...snapshot(2, 3), worldId: 1 };
385
+ const delta = projection.apply([
386
+ { kind: 'create', snapshot: first },
387
+ { kind: 'create', snapshot: second },
388
+ ]);
389
+ const availability = GpuScene.create(device, 2).unwrap();
390
+ expect(availability.status).toBe('available');
391
+ if (availability.status !== 'available') return;
392
+ availability.scene.sync(delta).unwrap();
393
+ const topology = new BatchTopology();
394
+ topology.rebuild(projection.slotsSnapshot());
395
+ const production = new GpuDrivenProduction(device, {
396
+ createShaderModule: () => ok(shader),
397
+ });
398
+ const prepared = production
399
+ .prepare({
400
+ scene: {
401
+ scene: availability.scene,
402
+ plan: topology.plan(),
403
+ slots: projection.slotsSnapshot(),
404
+ },
405
+ camera: makeZeroCameraFallbackSnapshot(),
406
+ meshes: new Map([[3, mesh(device)]]),
407
+ viewBindGroupLayout: viewLayout,
408
+ meshResidencyEpoch: 1,
409
+ hdrp: false,
410
+ })
411
+ .unwrap();
412
+ expect(prepared).toBeDefined();
413
+ expect(production.inspect()).toMatchObject({ batchCount: 2, indirectDrawCount: 2 });
414
+ production.dispose();
415
+ availability.scene.dispose();
416
+ });
417
+
418
+ it('uses renderer-local World keys for CPU validation telemetry', async () => {
419
+ const adapter = (await rhi.requestAdapter()).unwrap();
420
+ const device = (await adapter.requestDevice()).unwrap() as RhiNullDevice;
421
+ const shader = (await rhi.createShaderModule(device, { code: 'synthetic' })).unwrap();
422
+ const production = new GpuDrivenProduction(device, {
423
+ createShaderModule: () => ok(shader),
424
+ });
425
+ const gpuLodRow = {
426
+ ...snapshot(7, 3),
427
+ worldId: 1,
428
+ lods: [{ mesh: '00000000-0000-7000-8000-000000000001' as never, screenCoverage: 0.5 }],
429
+ };
430
+ const plainFallbackRow = { ...snapshot(2, 3), worldId: 0 };
431
+ const gpuOwned = new Set([worldEntityKey(101, gpuLodRow.entityKey)]);
432
+ const worldKeys = [202, 101];
433
+
434
+ // A GPU-owned row is removed from validation before this telemetry hook;
435
+ // the remaining plain row must still be counted as the one CPU fallback.
436
+ production.recordCpuValidation([{ source: plainFallbackRow }], gpuOwned, worldKeys);
437
+ expect(production.inspect()).toMatchObject({
438
+ cpuFallbackDrawItems: 1,
439
+ validatedGpuOwnedRows: 0,
440
+ });
441
+
442
+ // Prove the same renderer-local namespace recognizes the GPU row when a
443
+ // diagnostic caller supplies it directly, even though its extracted
444
+ // RenderableSnapshot still carries the reordered array index.
445
+ production.recordCpuValidation([{ source: gpuLodRow }], gpuOwned, worldKeys);
446
+ expect(production.inspect()).toMatchObject({
447
+ cpuFallbackDrawItems: 1,
448
+ validatedGpuOwnedRows: 1,
449
+ });
450
+ production.dispose();
451
+ });
452
+
453
+ it('coalesces LOD candidates that share the selected geometry range', async () => {
454
+ const adapter = (await rhi.requestAdapter()).unwrap();
455
+ const device = (await adapter.requestDevice()).unwrap() as RhiNullDevice;
456
+ const shader = (await rhi.createShaderModule(device, { code: 'synthetic' })).unwrap();
457
+ const viewLayout = device
458
+ .createBindGroupLayout({
459
+ entries: [{ binding: 0, visibility: 0x1, buffer: { type: 'uniform' } }],
460
+ })
461
+ .unwrap();
462
+ const projection = new RenderScene();
463
+ const lodMeshGuid = '00000000-0000-7000-8000-000000000001' as never;
464
+ const makeLodSnapshot = (entityKey: number): RenderableSnapshot => {
465
+ const base = snapshot(entityKey, 3);
466
+ const draw = base.gpuDrivenDraws?.[0];
467
+ if (draw === undefined) throw new Error('LOD test snapshot has no draw');
468
+ return {
469
+ ...base,
470
+ lods: [{ mesh: lodMeshGuid, screenCoverage: 0.5 }],
471
+ gpuDrivenDraws: [{ ...draw, lodRanges: [{ first: 3, count: 1, baseVertex: 0 }] }],
472
+ };
473
+ };
474
+ const delta = projection.apply([
475
+ { kind: 'create', snapshot: makeLodSnapshot(1) },
476
+ { kind: 'create', snapshot: makeLodSnapshot(2) },
477
+ ]);
478
+ const availability = GpuScene.create(device, 2).unwrap();
479
+ expect(availability.status).toBe('available');
480
+ if (availability.status !== 'available') return;
481
+ availability.scene.sync(delta).unwrap();
482
+ const topology = new BatchTopology();
483
+ topology.rebuild(projection.slotsSnapshot());
484
+ const production = new GpuDrivenProduction(device, {
485
+ createShaderModule: () => ok(shader),
486
+ });
487
+ const prepared = production
488
+ .prepare({
489
+ scene: {
490
+ scene: availability.scene,
491
+ plan: topology.plan(),
492
+ slots: projection.slotsSnapshot(),
493
+ },
494
+ camera: { ...makeZeroCameraFallbackSnapshot(), position: vec3.create(0, 0, 5) },
495
+ meshes: new Map([[3, mesh(device, true, true)]]),
496
+ viewBindGroupLayout: viewLayout,
497
+ meshResidencyEpoch: 1,
498
+ hdrp: false,
499
+ })
500
+ .unwrap();
501
+ expect(prepared).toBeDefined();
502
+ expect(production.inspect()).toMatchObject({
503
+ batchCount: 1,
504
+ indirectDrawCount: 1,
505
+ candidateUploadBytes: 2 * 304,
506
+ });
507
+ production.dispose();
508
+ availability.scene.dispose();
509
+ });
510
+
511
+ it('projects scene rows from the receipt-selected scene-index entry', () => {
512
+ const result = projectStandardPbrScene({
513
+ artifact: {
514
+ material: 'forgeax::default-standard-pbr',
515
+ pass: 'forward',
516
+ wgsl: 'standard-pbr',
517
+ layoutIdentity: STANDARD_PBR_RECEIPT.reflection.layoutIdentity,
518
+ bindings: [],
519
+ deps: [],
520
+ vertexInputs: STANDARD_PBR_RECEIPT.vertexInputs.map((input) => ({ ...input })),
521
+ receipt: STANDARD_PBR_RECEIPT,
522
+ },
523
+ candidates: [
524
+ {
525
+ primitiveIndex: 3,
526
+ world: Float32Array.from([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 2, 3, 4, 1]),
527
+ },
528
+ ],
529
+ });
530
+ expect(result.ok).toBe(true);
531
+ if (!result.ok) return;
532
+ expect(result.value.entryPoint).toBe('vs_scene_index');
533
+ expect(result.value.rows[0]).toBe(1);
534
+ expect(result.value.rows[12]).toBe(2);
535
+ expect(result.value.rows[13]).toBe(3);
536
+ expect(result.value.rows[14]).toBe(4);
537
+ });
538
+
539
+ it('adapts the four existing Standard PBR groups from one receipt', () => {
540
+ const groups = {
541
+ view: { id: 'view' },
542
+ material: { id: 'material' },
543
+ mesh: { id: 'mesh' },
544
+ instances: { id: 'instances' },
545
+ } as unknown as {
546
+ readonly view: BindGroup;
547
+ readonly material: BindGroup;
548
+ readonly mesh: BindGroup;
549
+ readonly instances: BindGroup;
550
+ };
551
+ const result = adaptStandardPbrFrameResources({
552
+ artifact: {
553
+ material: 'forgeax::default-standard-pbr',
554
+ pass: 'forward',
555
+ wgsl: 'standard-pbr',
556
+ layoutIdentity: STANDARD_PBR_RECEIPT.reflection.layoutIdentity,
557
+ bindings: [],
558
+ deps: [],
559
+ vertexInputs: STANDARD_PBR_RECEIPT.vertexInputs.map((input) => ({ ...input })),
560
+ receipt: STANDARD_PBR_RECEIPT,
561
+ },
562
+ ...groups,
563
+ });
564
+ expect(result.ok).toBe(true);
565
+ if (!result.ok) return;
566
+ expect(result.value.entryPoint).toBe('vs_scene_index');
567
+ expect(result.value.bindGroups).toEqual([
568
+ groups.view,
569
+ groups.material,
570
+ groups.mesh,
571
+ groups.instances,
572
+ ]);
573
+ expect(result.value.resourceSlots).toEqual(STANDARD_PBR_RECEIPT.resourceSlots);
574
+ });
575
+
170
576
  it('projects aligned multi-batch compute work into one indirect raster pass', async () => {
171
577
  const adapter = (await rhi.requestAdapter()).unwrap();
172
578
  const device = (await adapter.requestDevice()).unwrap() as RhiNullDevice;
@@ -189,7 +595,7 @@ describe('GPU-driven production projection', () => {
189
595
  const firstDraw = first.gpuDrivenDraws?.[0];
190
596
  expect(firstDraw).toBeDefined();
191
597
  if (firstDraw === undefined) return;
192
- const second = snapshot(2, 4);
598
+ const second = { ...snapshot(2, 4), worldId: 1 };
193
599
  const secondDraw = second.gpuDrivenDraws?.[0];
194
600
  expect(secondDraw).toBeDefined();
195
601
  if (secondDraw === undefined) return;
@@ -252,12 +658,12 @@ describe('GPU-driven production projection', () => {
252
658
  .unwrap();
253
659
  expect(prepared).toBeDefined();
254
660
  if (prepared === undefined) return;
255
- expect([...prepared.entityKeys]).toEqual([1, 2]);
661
+ expect([...prepared.entityKeys]).toEqual([worldEntityKey(0, 1), worldEntityKey(1, 2)]);
256
662
  expect(production.inspect()).toMatchObject({
257
663
  gpuOwnedSnapshotsMaterialized: 2,
258
664
  filteredPlanBuilds: 1,
259
- candidateUploadBytes: 84,
260
- batchUploadBytes: 84,
665
+ candidateUploadBytes: 912,
666
+ batchUploadBytes: 96,
261
667
  viewBindGroupCreates: 1,
262
668
  });
263
669
 
@@ -284,10 +690,40 @@ describe('GPU-driven production projection', () => {
284
690
  viewConstantsUploadBytes: 112,
285
691
  viewBindGroupCreates: 0,
286
692
  batchBindGroupCreates: 0,
693
+ indirectDrawCount: 3,
287
694
  });
288
695
 
696
+ // The scene topology is unchanged, but projectedHeight is camera-owned
697
+ // input to the GPU LOD selector. A moved camera must rebuild the filtered
698
+ // candidate plan instead of reusing the previous view's heights.
699
+ const movedCamera = {
700
+ ...makeZeroCameraFallbackSnapshot(),
701
+ position: vec3.create(0, 0, 6),
702
+ };
703
+ const moved = production
704
+ .prepare({
705
+ scene: {
706
+ scene: availability.scene,
707
+ plan: topology.plan(),
708
+ slots: projection.slotsSnapshot(),
709
+ },
710
+ camera: movedCamera,
711
+ meshes,
712
+ viewBindGroupLayout: viewLayout,
713
+ meshResidencyEpoch: 1,
714
+ hdrp: false,
715
+ })
716
+ .unwrap();
717
+ expect(moved).toBeDefined();
718
+ expect(production.inspect()).toMatchObject({
719
+ filteredPlanBuilds: 1,
720
+ candidateUploadBytes: 912,
721
+ batchUploadBytes: 96,
722
+ });
723
+ if (moved === undefined) return;
724
+
289
725
  const graph = new RenderGraphBuilder<RenderPipelineFrame>();
290
- const gpu = prepared.project(graph, 'rgba8unorm', 1).unwrap();
726
+ const gpu = moved.project(graph, 'rgba8unorm', 1).unwrap();
291
727
  const color = graph
292
728
  .createTexture('gpu-driven-production-color', {
293
729
  format: 'rgba8unorm',
@@ -325,6 +761,54 @@ describe('GPU-driven production projection', () => {
325
761
  ]);
326
762
  expect(device.totalDispatchCount).toBe(3);
327
763
  expect(device.totalDrawCount).toBe(3);
764
+ expect(production.inspect().indirectDrawCount).toBe(3);
765
+ expect(production.inspect().indirectDrawCount).toBeLessThanOrEqual(
766
+ production.inspect().batchCount,
767
+ );
768
+
769
+ // The facet projection is the same admission set consumed by the GPU
770
+ // candidate upload. A suppressed entity must disappear from the indirect
771
+ // plan without making the remaining entity fall back to CPU validation.
772
+ const activeOnly = production
773
+ .prepare({
774
+ scene: {
775
+ scene: availability.scene,
776
+ plan: topology.plan(),
777
+ slots: projection.slotsSnapshot(),
778
+ },
779
+ camera: movedCamera,
780
+ meshes,
781
+ viewBindGroupLayout: viewLayout,
782
+ meshResidencyEpoch: 1,
783
+ hdrp: false,
784
+ activeEntityKeys: new Set([worldEntityKey(0, 1)]),
785
+ activeEntityRevision: 1,
786
+ })
787
+ .unwrap();
788
+ expect(activeOnly?.entityKeys).toEqual(new Set([1]));
789
+ expect(activeOnly?.ownsAllRenderables).toBe(true);
790
+
791
+ const changedActive = production
792
+ .prepare({
793
+ scene: {
794
+ scene: availability.scene,
795
+ plan: topology.plan(),
796
+ slots: projection.slotsSnapshot(),
797
+ },
798
+ camera: movedCamera,
799
+ meshes,
800
+ viewBindGroupLayout: viewLayout,
801
+ meshResidencyEpoch: 1,
802
+ hdrp: false,
803
+ activeEntityKeys: new Set([worldEntityKey(1, 2)]),
804
+ activeEntityRevision: 2,
805
+ })
806
+ .unwrap();
807
+ expect(changedActive?.entityKeys).toEqual(new Set([worldEntityKey(1, 2)]));
808
+ // Production inspection counters are per-prepare-frame facts. The changed
809
+ // revision must trigger exactly one rebuild for this frame, not accumulate
810
+ // the prior frame's count.
811
+ expect(production.inspect().filteredPlanBuilds).toBe(1);
328
812
 
329
813
  const replacement = GpuScene.create(device, 2).unwrap();
330
814
  expect(replacement.status).toBe('available');
@@ -351,7 +835,76 @@ describe('GPU-driven production projection', () => {
351
835
  availability.scene.dispose();
352
836
  });
353
837
 
354
- it('activates inside the renderer-owned URP graph and removes the CPU forward draw', async () => {
838
+ it('keeps an ineligible production topology empty while the camera moves', async () => {
839
+ const adapter = (await rhi.requestAdapter()).unwrap();
840
+ const device = (await adapter.requestDevice()).unwrap() as RhiNullDevice;
841
+ const shader = (await rhi.createShaderModule(device, { code: 'synthetic' })).unwrap();
842
+ const viewLayout = device
843
+ .createBindGroupLayout({
844
+ entries: [{ binding: 0, visibility: 0x1, buffer: { type: 'uniform' } }],
845
+ })
846
+ .unwrap();
847
+ const base = snapshot(1, 3);
848
+ const material = {
849
+ ...base.material,
850
+ materialShaderId: 'forgeax::default-standard-pbr',
851
+ };
852
+ const projection = new RenderScene();
853
+ const delta = projection.apply([
854
+ { kind: 'create', snapshot: { ...base, material, materials: [material] } },
855
+ ]);
856
+ const availability = GpuScene.create(device, 1).unwrap();
857
+ expect(availability.status).toBe('available');
858
+ if (availability.status !== 'available') return;
859
+ availability.scene.sync(delta).unwrap();
860
+ const topology = new BatchTopology();
861
+ topology.rebuild(projection.slotsSnapshot());
862
+ const production = new GpuDrivenProduction(device, {
863
+ createShaderModule: () => ok(shader),
864
+ });
865
+ const input = {
866
+ scene: {
867
+ scene: availability.scene,
868
+ plan: topology.plan(),
869
+ slots: projection.slotsSnapshot(),
870
+ },
871
+ meshes: new Map([[3, mesh(device)]]),
872
+ viewBindGroupLayout: viewLayout,
873
+ hdrp: false,
874
+ } as const;
875
+
876
+ expect(
877
+ production
878
+ .prepare({
879
+ ...input,
880
+ camera: makeZeroCameraFallbackSnapshot(),
881
+ meshResidencyEpoch: 1,
882
+ })
883
+ .unwrap(),
884
+ ).toBeUndefined();
885
+ expect(production.requiresSceneRows()).toBe(false);
886
+ expect(production.inspect().filteredPlanBuilds).toBe(1);
887
+ expect(
888
+ production
889
+ .prepare({
890
+ ...input,
891
+ camera: { ...makeZeroCameraFallbackSnapshot(), position: vec3.create(0, 0, 6) },
892
+ meshResidencyEpoch: 2,
893
+ })
894
+ .unwrap(),
895
+ ).toBeUndefined();
896
+ expect(production.requiresSceneRows()).toBe(false);
897
+ expect(production.inspect()).toMatchObject({
898
+ filteredPlanBuilds: 0,
899
+ gpuOwnedSnapshotsMaterialized: 0,
900
+ gpuOwnedEntityCount: 0,
901
+ });
902
+
903
+ production.dispose();
904
+ availability.scene.dispose();
905
+ });
906
+
907
+ it('activates inside the renderer-owned clustered Standard graph and removes the CPU draw', async () => {
355
908
  const canvas = {
356
909
  width: 1,
357
910
  height: 1,
@@ -380,6 +933,12 @@ describe('GPU-driven production projection', () => {
380
933
  { component: MeshRenderer, data: {} },
381
934
  )
382
935
  .unwrap();
936
+ world
937
+ .spawn(
938
+ { component: Transform, data: { pos: [0, 0, 1] } },
939
+ { component: PointLight, data: {} },
940
+ )
941
+ .unwrap();
383
942
 
384
943
  for (let frame = 0; frame < 4; frame += 1) {
385
944
  world.update(1 / 60).unwrap();
@@ -391,18 +950,21 @@ describe('GPU-driven production projection', () => {
391
950
  world.update(1 / 60).unwrap();
392
951
  expect(renderer.draw([world], { cameraOwner: 0, resourceOwner: 0 }).ok).toBe(true);
393
952
  expect(renderer.perFramePassNames).toEqual(
394
- expect.arrayContaining([
395
- 'gpu-driven.view-reset',
396
- 'gpu-driven.frustum-compact',
397
- 'gpu-driven.finalize-indirect',
398
- 'main',
399
- ]),
953
+ expect.arrayContaining(['cluster-membership-producer', 'main', 'output-transform']),
400
954
  );
401
955
  expect(renderer.perFramePassNames).toContain('output-transform');
402
956
  // Storage-buffer LDR frames keep the scene linear until the one output
403
957
  // transform writes the encoded surface; the GPU-driven indirect raster
404
958
  // draw and that fullscreen pass are the only draws in this topology.
959
+ // A plain mesh has no LOD relation, so it stays on the CPU visibility
960
+ // path for occlusion while GPU-driven raster + output-transform remain
961
+ // the only draws in this graph.
405
962
  expect(device.totalDrawCount).toBe(2);
963
+ expect(renderer.inspect().lodOcclusion).toMatchObject({
964
+ fallback: { active: false },
965
+ degradation: { active: false },
966
+ pagePressure: { used: 0, capacity: 3 * 4096 },
967
+ });
406
968
  expect(renderer.renderScene).toMatchObject({
407
969
  worldEntitiesScanned: 0,
408
970
  fullRebuilds: 1,