@forgeax/engine-render 0.1.21 → 0.1.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (426) hide show
  1. package/README.md +150 -29
  2. package/dist/assembly/backend-contract.d.ts +13 -0
  3. package/dist/assembly/backend-contract.d.ts.map +1 -1
  4. package/dist/assembly/device-feature-admission.d.ts.map +1 -1
  5. package/dist/assembly/factory.d.ts +14 -6
  6. package/dist/assembly/factory.d.ts.map +1 -1
  7. package/dist/assembly/host-contract.d.ts +10 -0
  8. package/dist/assembly/host-contract.d.ts.map +1 -1
  9. package/dist/authoring.mjs +1 -1
  10. package/dist/{chunk-PCHBPYML.mjs → chunk-4L3P6DT5.mjs} +2 -2
  11. package/dist/chunk-4L3P6DT5.mjs.map +1 -0
  12. package/dist/{chunk-VFPLTKCU.mjs → chunk-4NPUASZR.mjs} +15 -4
  13. package/dist/chunk-4NPUASZR.mjs.map +1 -0
  14. package/dist/chunk-DIYW3M5D.mjs +5085 -0
  15. package/dist/chunk-DIYW3M5D.mjs.map +1 -0
  16. package/dist/chunk-FBC4R44J.mjs +1003 -0
  17. package/dist/chunk-FBC4R44J.mjs.map +1 -0
  18. package/dist/{chunk-FS2KNUQH.mjs → chunk-FF7K3FSO.mjs} +253 -489
  19. package/dist/chunk-FF7K3FSO.mjs.map +1 -0
  20. package/dist/chunk-HZUPSYW7.mjs +1104 -0
  21. package/dist/chunk-HZUPSYW7.mjs.map +1 -0
  22. package/dist/{chunk-6OPXDQ2E.mjs → chunk-QODA5UKU.mjs} +33 -294
  23. package/dist/chunk-QODA5UKU.mjs.map +1 -0
  24. package/dist/chunk-YALPS44A.mjs +296 -0
  25. package/dist/chunk-YALPS44A.mjs.map +1 -0
  26. package/dist/{chunk-5YADUCA7.mjs → chunk-ZUWKAZ2I.mjs} +585 -991
  27. package/dist/chunk-ZUWKAZ2I.mjs.map +1 -0
  28. package/dist/cluster-binner.d.ts +5 -0
  29. package/dist/cluster-binner.d.ts.map +1 -1
  30. package/dist/components/index.d.ts +3 -0
  31. package/dist/components/index.d.ts.map +1 -1
  32. package/dist/components/light-helpers.d.ts +4 -0
  33. package/dist/components/light-helpers.d.ts.map +1 -1
  34. package/dist/components/light-probe.d.ts +9 -0
  35. package/dist/components/light-probe.d.ts.map +1 -0
  36. package/dist/components/rect-area-light.d.ts +12 -0
  37. package/dist/components/rect-area-light.d.ts.map +1 -0
  38. package/dist/components/scene-instance.d.ts +4 -0
  39. package/dist/components/scene-instance.d.ts.map +1 -1
  40. package/dist/components/spot-light.d.ts +3 -0
  41. package/dist/components/spot-light.d.ts.map +1 -1
  42. package/dist/construct-renderer.mjs +10437 -10044
  43. package/dist/construct-renderer.mjs.map +1 -1
  44. package/dist/device/gpu-residency.d.ts +8 -1
  45. package/dist/device/gpu-residency.d.ts.map +1 -1
  46. package/dist/device/lifecycle-transaction.d.ts +2 -0
  47. package/dist/device/lifecycle-transaction.d.ts.map +1 -0
  48. package/dist/environment/lifecycle.d.ts.map +1 -1
  49. package/dist/errors/gpu-driven.d.ts +26 -0
  50. package/dist/errors/gpu-driven.d.ts.map +1 -0
  51. package/dist/errors/index.d.ts +1 -0
  52. package/dist/errors/index.d.ts.map +1 -1
  53. package/dist/errors/recover.d.ts +9 -1
  54. package/dist/errors/recover.d.ts.map +1 -1
  55. package/dist/errors/render.d.ts +26 -1
  56. package/dist/errors/render.d.ts.map +1 -1
  57. package/dist/extract/gpu-driven.d.ts +50 -0
  58. package/dist/extract/gpu-driven.d.ts.map +1 -0
  59. package/dist/extract/visibility.d.ts.map +1 -1
  60. package/dist/gpu-driven/batch-topology.d.ts +34 -0
  61. package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
  62. package/dist/gpu-driven/prepared-draw.d.ts +63 -0
  63. package/dist/gpu-driven/prepared-draw.d.ts.map +1 -0
  64. package/dist/gpu-driven/production-raster.d.ts +98 -2
  65. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  66. package/dist/gpu-driven/view-gpu.d.ts +64 -1
  67. package/dist/gpu-driven/view-gpu.d.ts.map +1 -1
  68. package/dist/gpu-scene-schema.d.ts +14 -0
  69. package/dist/gpu-scene-schema.d.ts.map +1 -1
  70. package/dist/gpu-scene.d.ts +8 -1
  71. package/dist/gpu-scene.d.ts.map +1 -1
  72. package/dist/gpu-usage.d.ts +0 -1
  73. package/dist/gpu-usage.d.ts.map +1 -1
  74. package/dist/hdrp-buffers.d.ts +3 -3
  75. package/dist/hdrp-buffers.d.ts.map +1 -1
  76. package/dist/index.d.ts +12 -1
  77. package/dist/index.d.ts.map +1 -1
  78. package/dist/index.mjs +90 -5
  79. package/dist/index.mjs.map +1 -1
  80. package/dist/inspection-types.d.ts +163 -1
  81. package/dist/inspection-types.d.ts.map +1 -1
  82. package/dist/internal.d.ts +9 -0
  83. package/dist/internal.d.ts.map +1 -1
  84. package/dist/internal.mjs +23 -2
  85. package/dist/internal.mjs.map +1 -1
  86. package/dist/light-buffer-layout.d.ts +58 -149
  87. package/dist/light-buffer-layout.d.ts.map +1 -1
  88. package/dist/materials.d.ts.map +1 -1
  89. package/dist/pbr-pipeline.d.ts +26 -12
  90. package/dist/pbr-pipeline.d.ts.map +1 -1
  91. package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
  92. package/dist/pipeline/standard-lighting/graph.d.ts.map +1 -1
  93. package/dist/pipeline/standard-lighting/light-frame.d.ts +3 -3
  94. package/dist/pipeline/standard-lighting/light-frame.d.ts.map +1 -1
  95. package/dist/pipeline/standard-lighting/topology.d.ts +1 -1
  96. package/dist/pipeline/standard-lighting/topology.d.ts.map +1 -1
  97. package/dist/pipeline/standard-lighting/transport.d.ts.map +1 -1
  98. package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
  99. package/dist/pipeline-builder.d.ts +2 -0
  100. package/dist/pipeline-builder.d.ts.map +1 -1
  101. package/dist/pipeline-spec.d.ts +1 -1
  102. package/dist/pipeline-spec.d.ts.map +1 -1
  103. package/dist/plugin.d.ts.map +1 -1
  104. package/dist/prepare/extended-lighting/ltc-resources.d.ts +31 -0
  105. package/dist/prepare/extended-lighting/ltc-resources.d.ts.map +1 -0
  106. package/dist/prepare/extended-lighting/resources.d.ts +72 -0
  107. package/dist/prepare/extended-lighting/resources.d.ts.map +1 -0
  108. package/dist/prepare/extended-lighting/spot-modifiers.d.ts +42 -0
  109. package/dist/prepare/extended-lighting/spot-modifiers.d.ts.map +1 -0
  110. package/dist/prepare/extended-lighting/state.d.ts +24 -0
  111. package/dist/prepare/extended-lighting/state.d.ts.map +1 -0
  112. package/dist/record/frame-lighting.d.ts +8 -6
  113. package/dist/record/frame-lighting.d.ts.map +1 -1
  114. package/dist/record/frame-snapshot.d.ts +50 -10
  115. package/dist/record/frame-snapshot.d.ts.map +1 -1
  116. package/dist/record/frame.d.ts +7 -3
  117. package/dist/record/frame.d.ts.map +1 -1
  118. package/dist/record/gpu-timing.d.ts +19 -7
  119. package/dist/record/gpu-timing.d.ts.map +1 -1
  120. package/dist/record/helpers.d.ts +0 -20
  121. package/dist/record/helpers.d.ts.map +1 -1
  122. package/dist/record/main-pass-geometry.d.ts +6 -1
  123. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  124. package/dist/record/main-pass-material.d.ts +3 -1
  125. package/dist/record/main-pass-material.d.ts.map +1 -1
  126. package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
  127. package/dist/record/mesh-ssbo.d.ts.map +1 -1
  128. package/dist/record/render-context.d.ts +57 -6
  129. package/dist/record/render-context.d.ts.map +1 -1
  130. package/dist/record/shadow-pass.d.ts.map +1 -1
  131. package/dist/record/typed-frame-graph.d.ts +49 -2
  132. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  133. package/dist/record/view-ubo.d.ts +1 -2
  134. package/dist/record/view-ubo.d.ts.map +1 -1
  135. package/dist/reflection/inspection.d.ts +10 -0
  136. package/dist/reflection/inspection.d.ts.map +1 -0
  137. package/dist/reflection/projection.d.ts +49 -0
  138. package/dist/reflection/projection.d.ts.map +1 -1
  139. package/dist/reflection/record-owner.d.ts +60 -0
  140. package/dist/reflection/record-owner.d.ts.map +1 -0
  141. package/dist/render-contract.d.ts +14 -8
  142. package/dist/render-contract.d.ts.map +1 -1
  143. package/dist/render-pipeline.d.ts +7 -0
  144. package/dist/render-pipeline.d.ts.map +1 -1
  145. package/dist/render-system-extract.d.ts +104 -16
  146. package/dist/render-system-extract.d.ts.map +1 -1
  147. package/dist/render-system.d.ts +12 -1
  148. package/dist/render-system.d.ts.map +1 -1
  149. package/dist/scene/probe-blend-record.d.ts +22 -0
  150. package/dist/scene/probe-blend-record.d.ts.map +1 -0
  151. package/dist/scene/probe-blend.d.ts +142 -0
  152. package/dist/scene/probe-blend.d.ts.map +1 -0
  153. package/dist/scene/render-scene-types.d.ts +0 -6
  154. package/dist/scene/render-scene-types.d.ts.map +1 -1
  155. package/dist/scene/render-scene.d.ts +103 -9
  156. package/dist/scene/render-scene.d.ts.map +1 -1
  157. package/dist/scene/visibility/bounds.d.ts +20 -0
  158. package/dist/scene/visibility/bounds.d.ts.map +1 -0
  159. package/dist/scene/visibility/budget.d.ts +16 -0
  160. package/dist/scene/visibility/budget.d.ts.map +1 -0
  161. package/dist/scene/visibility/facet.d.ts +73 -0
  162. package/dist/scene/visibility/facet.d.ts.map +1 -0
  163. package/dist/scene/visibility/gpu-frame-timing.d.ts +41 -0
  164. package/dist/scene/visibility/gpu-frame-timing.d.ts.map +1 -0
  165. package/dist/scene/visibility/gpu-lod.d.ts +46 -0
  166. package/dist/scene/visibility/gpu-lod.d.ts.map +1 -0
  167. package/dist/scene/visibility/inspection.d.ts +132 -0
  168. package/dist/scene/visibility/inspection.d.ts.map +1 -0
  169. package/dist/scene/visibility/lod-selector.d.ts +25 -0
  170. package/dist/scene/visibility/lod-selector.d.ts.map +1 -0
  171. package/dist/scene/visibility/occlusion-confidence.d.ts +81 -0
  172. package/dist/scene/visibility/occlusion-confidence.d.ts.map +1 -0
  173. package/dist/scene/visibility/occlusion-pass.d.ts +57 -0
  174. package/dist/scene/visibility/occlusion-pass.d.ts.map +1 -0
  175. package/dist/scene/visibility/occlusion-query-pool.d.ts +57 -0
  176. package/dist/scene/visibility/occlusion-query-pool.d.ts.map +1 -0
  177. package/dist/scene/visibility/occlusion-runtime.d.ts +96 -0
  178. package/dist/scene/visibility/occlusion-runtime.d.ts.map +1 -0
  179. package/dist/scene/visibility/types.d.ts +34 -0
  180. package/dist/scene/visibility/types.d.ts.map +1 -0
  181. package/dist/ssr/admission.d.ts +122 -0
  182. package/dist/ssr/admission.d.ts.map +1 -0
  183. package/dist/ssr/composition.d.ts +32 -0
  184. package/dist/ssr/composition.d.ts.map +1 -0
  185. package/dist/ssr/errors.d.ts +39 -0
  186. package/dist/ssr/errors.d.ts.map +1 -0
  187. package/dist/ssr/identity.d.ts +13 -0
  188. package/dist/ssr/identity.d.ts.map +1 -0
  189. package/dist/systems/skin-palette-allocator.d.ts +1 -1
  190. package/dist/temporal/frame-coordinator.d.ts +13 -0
  191. package/dist/temporal/frame-coordinator.d.ts.map +1 -1
  192. package/dist/temporal/frame.d.ts +2 -0
  193. package/dist/temporal/frame.d.ts.map +1 -1
  194. package/dist/temporal/index.mjs +6 -5
  195. package/dist/temporal/view.d.ts +7 -0
  196. package/dist/temporal/view.d.ts.map +1 -1
  197. package/dist/tilemap-chunk-extract-system.d.ts +1 -1
  198. package/dist/typed-render-graph-primitives.d.ts +4 -0
  199. package/dist/typed-render-graph-primitives.d.ts.map +1 -1
  200. package/dist/volume/passes.d.ts +8 -4
  201. package/dist/volume/passes.d.ts.map +1 -1
  202. package/package.json +20 -20
  203. package/src/__tests__/cluster-grid-default-surface.unit.test.ts +5 -0
  204. package/src/__tests__/detached-inspection.integration.test.ts +6 -0
  205. package/src/__tests__/device-feature-admission.unit.test.ts +13 -1
  206. package/src/__tests__/direct-light-snapshot.unit.test.ts +59 -3
  207. package/src/__tests__/environment-generation.integration.test.ts +16 -0
  208. package/src/__tests__/evidence-identity.ts +13 -0
  209. package/src/__tests__/evidence-identity.unit.test.ts +24 -0
  210. package/src/__tests__/extended-lighting-recovery.browser.test.ts +14 -0
  211. package/src/__tests__/extended-lighting-recovery.dawn.test.ts +14 -0
  212. package/src/__tests__/extended-lighting-recovery.integration.test.ts +81 -0
  213. package/src/__tests__/extended-lighting-resources.unit.test.ts +51 -0
  214. package/src/__tests__/extended-lighting-zero-feature.integration.test.ts +52 -0
  215. package/src/__tests__/factory-contract.integration.test.ts +33 -0
  216. package/src/__tests__/frame-observation.contract.test.ts +15 -0
  217. package/src/__tests__/gpu-driven-batch-topology-pbr.unit.test.ts +146 -0
  218. package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +1 -0
  219. package/src/__tests__/gpu-driven-indirect-raster.dawn.test.ts +1 -0
  220. package/src/__tests__/gpu-driven-production.integration.test.ts +572 -9
  221. package/src/__tests__/gpu-driven-scaling.unit.test.ts +66 -2
  222. package/src/__tests__/gpu-driven-view-gpu-evidence.ts +26 -1
  223. package/src/__tests__/gpu-driven-view-graph.integration.test.ts +89 -2
  224. package/src/__tests__/gpu-driven-view.browser.test.ts +3 -3
  225. package/src/__tests__/gpu-driven-view.dawn.test.ts +3 -3
  226. package/src/__tests__/gpu-driven-view.unit.test.ts +50 -0
  227. package/src/__tests__/gpu-lod-evidence.ts +188 -0
  228. package/src/__tests__/gpu-lod-fallback.integration.test.ts +10 -0
  229. package/src/__tests__/gpu-lod-indirect.integration.test.ts +40 -0
  230. package/src/__tests__/gpu-lod-parity.dawn.test.ts +29 -0
  231. package/src/__tests__/gpu-lod-readiness.integration.test.ts +52 -0
  232. package/src/__tests__/gpu-lod-schema.unit.test.ts +66 -0
  233. package/src/__tests__/gpu-lod-selector.unit.test.ts +28 -0
  234. package/src/__tests__/gpu-lod-silhouette.browser.test.ts +13 -0
  235. package/src/__tests__/gpu-resource-store-stride.unit.test.ts +66 -1
  236. package/src/__tests__/gpu-scene.dawn.test.ts +8 -1
  237. package/src/__tests__/gpu-scene.unit.test.ts +49 -3
  238. package/src/__tests__/light-buffer-layout.unit.test.ts +113 -3
  239. package/src/__tests__/light-inspection.unit.test.ts +55 -0
  240. package/src/__tests__/light-resource-error.unit.test.ts +43 -0
  241. package/src/__tests__/lighting-scene-roundtrip.integration.test.ts +109 -0
  242. package/src/__tests__/lod-bounds.unit.test.ts +42 -0
  243. package/src/__tests__/lod-cpu-lane.integration.test.ts +55 -0
  244. package/src/__tests__/lod-cpu-no-change.perf.test.ts +19 -0
  245. package/src/__tests__/lod-gpu-frame-timing.unit.test.ts +49 -0
  246. package/src/__tests__/lod-multi-view.integration.test.ts +63 -0
  247. package/src/__tests__/lod-occlusion-device-loss.integration.test.ts +27 -0
  248. package/src/__tests__/lod-occlusion-final-decision.integration.test.ts +47 -0
  249. package/src/__tests__/lod-occlusion-inspection.integration.test.ts +217 -0
  250. package/src/__tests__/lod-occlusion-inspection.schema.test-d.ts +14 -0
  251. package/src/__tests__/lod-occlusion-inspection.unit.test.ts +72 -0
  252. package/src/__tests__/lod-occlusion-visual-evidence.ts +91 -0
  253. package/src/__tests__/lod-occlusion.browser.test.ts +15 -0
  254. package/src/__tests__/lod-occlusion.dawn.test.ts +20 -0
  255. package/src/__tests__/lod-selector.profile.test.ts +35 -0
  256. package/src/__tests__/lod-selector.unit.test.ts +68 -0
  257. package/src/__tests__/lod-visibility-identity.unit.test.ts +198 -0
  258. package/src/__tests__/lod-visibility-lifecycle.integration.test.ts +132 -0
  259. package/src/__tests__/ltc-resources.unit.test.ts +32 -0
  260. package/src/__tests__/material-contract-inventory.unit.test.ts +17 -0
  261. package/src/__tests__/occlusion-confidence.unit.test.ts +168 -0
  262. package/src/__tests__/occlusion-invalidation.integration.test.ts +37 -0
  263. package/src/__tests__/occlusion-pass-routing.unit.test.ts +16 -0
  264. package/src/__tests__/occlusion-pass.dawn.test.ts +20 -0
  265. package/src/__tests__/occlusion-pass.integration.test.ts +43 -0
  266. package/src/__tests__/occlusion-pass.rhi-null.test.ts +22 -0
  267. package/src/__tests__/occlusion-query-pool.rhi-null.test.ts +21 -0
  268. package/src/__tests__/occlusion-query-pool.unit.test.ts +95 -0
  269. package/src/__tests__/occlusion-query-ticket.integration.test.ts +47 -0
  270. package/src/__tests__/occlusion-query.dawn.test.ts +16 -0
  271. package/src/__tests__/occlusion-query.evidence.ts +178 -0
  272. package/src/__tests__/occlusion-query.rhi-null.test.ts +31 -0
  273. package/src/__tests__/occlusion-runtime.unit.test.ts +120 -0
  274. package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +196 -0
  275. package/src/__tests__/probe-bindings.unit.test.ts +200 -0
  276. package/src/__tests__/probe-blend-numeric.unit.test.ts +67 -0
  277. package/src/__tests__/probe-blend-oracle.unit.test.ts +136 -0
  278. package/src/__tests__/probe-capacity.integration.test.ts +97 -0
  279. package/src/__tests__/probe-recovery.integration.test.ts +104 -0
  280. package/src/__tests__/probe-scene-dirty.integration.test.ts +211 -0
  281. package/src/__tests__/profiler-phase-catalog.test.ts +4 -1
  282. package/src/__tests__/profiler-phase-token.test.ts +1 -0
  283. package/src/__tests__/rect-area-boundary.unit.test.ts +24 -0
  284. package/src/__tests__/reflection-fallback-graph.unit.test.ts +67 -0
  285. package/src/__tests__/reflection-fallback-inspection.unit.test.ts +77 -0
  286. package/src/__tests__/reflection-fallback-lifecycle.integration.test.ts +21 -0
  287. package/src/__tests__/reflection-fallback-projection.unit.test.ts +90 -0
  288. package/src/__tests__/reflection-fallback-source.unit.test.ts +32 -0
  289. package/src/__tests__/reflection-probe-owner-placement.unit.test.ts +39 -0
  290. package/src/__tests__/reflection-probe-record-owner.characterization.unit.test.ts +120 -0
  291. package/src/__tests__/render-error-code-owner.test-d.ts +1 -0
  292. package/src/__tests__/render-feature-docs.unit.test.ts +12 -7
  293. package/src/__tests__/render-graph-fallback.integration.test.ts +24 -0
  294. package/src/__tests__/render-perf/extended-lighting.bench.test.ts +66 -0
  295. package/src/__tests__/render-scene-projection.unit.test.ts +447 -18
  296. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +76 -0
  297. package/src/__tests__/skin-motion-regression.unit.test.ts +22 -0
  298. package/src/__tests__/skinned-shadow-caster.test.ts +2 -2
  299. package/src/__tests__/spot-modifier-projection.unit.test.ts +70 -0
  300. package/src/__tests__/ssr-admission-matrix.integration.test.ts +206 -0
  301. package/src/__tests__/ssr-composition.unit.test.ts +46 -0
  302. package/src/__tests__/ssr-fallback-zero-work.bench.test.ts +94 -0
  303. package/src/__tests__/ssr-recovery-errors.unit.test.ts +58 -0
  304. package/src/__tests__/standard-cluster-variant-resolution.unit.test.ts +67 -0
  305. package/src/__tests__/standard-clustered-pipeline.unit.test.ts +7 -0
  306. package/src/__tests__/standard-lighting-inspection.integration.test.ts +1 -1
  307. package/src/__tests__/standard-lighting-write.unit.test.ts +66 -2
  308. package/src/__tests__/temporal-fallback-generation.integration.test.ts +16 -0
  309. package/src/__tests__/temporal-view.unit.test.ts +3 -3
  310. package/src/__tests__/transmission-prewarm.unit.test.ts +69 -0
  311. package/src/__tests__/visibility-budget.integration.test.ts +140 -0
  312. package/src/__tests__/visibility-budget.unit.test.ts +70 -0
  313. package/src/__tests__/visibility-extract.integration.test.ts +11 -6
  314. package/src/__tests__/volumetric-fog-point-spot-accumulation.integration.test.ts +6 -3
  315. package/src/__tests__/volumetric-fog-stage-readback.dawn.test.ts +31 -28
  316. package/src/assembly/backend-contract.ts +13 -0
  317. package/src/assembly/device-feature-admission.ts +16 -9
  318. package/src/assembly/factory.ts +547 -93
  319. package/src/assembly/host-contract.ts +10 -0
  320. package/src/cluster-binner.ts +31 -0
  321. package/src/components/__tests__/lighting-authoring.unit.test.ts +39 -0
  322. package/src/components/__tests__/rect-area-light.unit.test.ts +33 -0
  323. package/src/components/index.ts +3 -0
  324. package/src/components/light-helpers.ts +52 -1
  325. package/src/components/light-probe.ts +13 -0
  326. package/src/components/rect-area-light.ts +15 -0
  327. package/src/components/scene-instance.ts +6 -0
  328. package/src/components/spot-light.ts +3 -0
  329. package/src/device/__tests__/extended-lighting-recovery.unit.test.ts +24 -0
  330. package/src/device/gpu-residency.ts +140 -9
  331. package/src/device/lifecycle-transaction.ts +12 -0
  332. package/src/environment/lifecycle.ts +8 -1
  333. package/src/errors/gpu-driven.ts +92 -0
  334. package/src/errors/index.ts +1 -0
  335. package/src/errors/recover.ts +32 -0
  336. package/src/errors/render.ts +55 -1
  337. package/src/extract/camera.ts +3 -3
  338. package/src/extract/gpu-driven.ts +108 -0
  339. package/src/extract/visibility.ts +0 -23
  340. package/src/gpu-driven/batch-topology.ts +206 -28
  341. package/src/gpu-driven/prepared-draw.ts +208 -0
  342. package/src/gpu-driven/production-raster.ts +953 -41
  343. package/src/gpu-driven/view-gpu.ts +411 -29
  344. package/src/gpu-scene-schema.ts +13 -0
  345. package/src/gpu-scene.ts +134 -65
  346. package/src/gpu-usage.ts +0 -1
  347. package/src/hdrp-buffers.ts +6 -6
  348. package/src/index.ts +74 -0
  349. package/src/inspection-types.ts +220 -1
  350. package/src/internal.ts +38 -0
  351. package/src/light-buffer-layout.ts +154 -324
  352. package/src/materials.ts +16 -2
  353. package/src/pbr-pipeline.ts +151 -45
  354. package/src/pipeline/standard-forward-lane.ts +47 -0
  355. package/src/pipeline/standard-lighting/graph.ts +2 -1
  356. package/src/pipeline/standard-lighting/light-frame.ts +3 -2
  357. package/src/pipeline/standard-lighting/topology.ts +1 -1
  358. package/src/pipeline/standard-lighting/transport.ts +2 -1
  359. package/src/pipeline/standard-pipeline.ts +2 -0
  360. package/src/pipeline-builder.ts +5 -2
  361. package/src/pipeline-spec.ts +12 -1
  362. package/src/plugin.ts +4 -0
  363. package/src/prepare/__tests__/extended-lighting-capability.unit.test.ts +56 -0
  364. package/src/prepare/extended-lighting/ltc-resources.ts +136 -0
  365. package/src/prepare/extended-lighting/resources.ts +389 -0
  366. package/src/prepare/extended-lighting/spot-modifiers.ts +194 -0
  367. package/src/prepare/extended-lighting/state.ts +92 -0
  368. package/src/record/__tests__/gpu-timing.unit.test.ts +177 -0
  369. package/src/record/__tests__/temporal-payload.unit.test.ts +23 -1
  370. package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +20 -1
  371. package/src/record/frame-lighting.ts +252 -53
  372. package/src/record/frame-snapshot.ts +57 -12
  373. package/src/record/frame.ts +410 -93
  374. package/src/record/gpu-timing.ts +108 -44
  375. package/src/record/helpers.ts +0 -58
  376. package/src/record/main-pass-geometry.ts +204 -39
  377. package/src/record/main-pass-material.ts +8 -1
  378. package/src/record/main-pass-sprite-draws.ts +52 -85
  379. package/src/record/mesh-ssbo.ts +47 -36
  380. package/src/record/render-context.ts +65 -4
  381. package/src/record/shadow-pass.ts +66 -40
  382. package/src/record/typed-frame-graph.ts +225 -83
  383. package/src/record/view-ubo.ts +14 -24
  384. package/src/reflection/inspection.ts +67 -0
  385. package/src/reflection/projection.ts +196 -1
  386. package/src/reflection/record-owner.ts +1283 -0
  387. package/src/render-contract.ts +17 -5
  388. package/src/render-pipeline.ts +5 -0
  389. package/src/render-system-extract.ts +493 -134
  390. package/src/render-system.ts +811 -615
  391. package/src/scene/__tests__/changed-block-summary.candidate.test.ts +36 -0
  392. package/src/scene/probe-blend-record.ts +54 -0
  393. package/src/scene/probe-blend.ts +813 -0
  394. package/src/scene/render-scene-types.ts +0 -7
  395. package/src/scene/render-scene.ts +848 -183
  396. package/src/scene/visibility/bounds.ts +57 -0
  397. package/src/scene/visibility/budget.ts +40 -0
  398. package/src/scene/visibility/facet.ts +400 -0
  399. package/src/scene/visibility/gpu-frame-timing.ts +101 -0
  400. package/src/scene/visibility/gpu-lod.ts +119 -0
  401. package/src/scene/visibility/inspection.ts +313 -0
  402. package/src/scene/visibility/lod-selector.ts +84 -0
  403. package/src/scene/visibility/occlusion-confidence.ts +478 -0
  404. package/src/scene/visibility/occlusion-pass.ts +165 -0
  405. package/src/scene/visibility/occlusion-query-pool.ts +178 -0
  406. package/src/scene/visibility/occlusion-runtime.ts +672 -0
  407. package/src/scene/visibility/types.ts +50 -0
  408. package/src/ssr/admission.ts +451 -0
  409. package/src/ssr/composition.ts +73 -0
  410. package/src/ssr/errors.ts +76 -0
  411. package/src/ssr/identity.ts +12 -0
  412. package/src/systems/skin-palette-allocator.ts +1 -1
  413. package/src/temporal/frame-coordinator.ts +26 -0
  414. package/src/temporal/frame.ts +14 -1
  415. package/src/temporal/view.ts +11 -0
  416. package/src/tilemap-chunk-extract-system.ts +4 -4
  417. package/src/typed-render-graph-primitives.ts +48 -0
  418. package/src/volume/passes.ts +46 -49
  419. package/dist/.tsbuildinfo +0 -1
  420. package/dist/chunk-5YADUCA7.mjs.map +0 -1
  421. package/dist/chunk-6OPXDQ2E.mjs.map +0 -1
  422. package/dist/chunk-BXBQQKFQ.mjs +0 -67
  423. package/dist/chunk-BXBQQKFQ.mjs.map +0 -1
  424. package/dist/chunk-FS2KNUQH.mjs.map +0 -1
  425. package/dist/chunk-PCHBPYML.mjs.map +0 -1
  426. package/dist/chunk-VFPLTKCU.mjs.map +0 -1
@@ -132,7 +132,7 @@ describe('TemporalView v1', () => {
132
132
  } as unknown as ExtractedLights;
133
133
  const buffer = {} as Parameters<typeof writeViewUbo>[1];
134
134
  const first = createTemporalView({ antialias: 'taa', width: 4, height: 4, frameIndex: 0 });
135
- writeViewUbo(queue, buffer, camera, light, lights, [], undefined, first);
135
+ writeViewUbo(queue, buffer, camera, light, lights, [], first);
136
136
  expect(Array.from(uploads[0]?.slice(212, 228) ?? [])).toEqual(new Array(16).fill(0));
137
137
 
138
138
  const previous = Float32Array.from({ length: 16 }, (_value, index) => index + 1);
@@ -143,10 +143,10 @@ describe('TemporalView v1', () => {
143
143
  frameIndex: 1,
144
144
  previousUnjitteredViewProjection: previous,
145
145
  });
146
- writeViewUbo(queue, buffer, camera, light, lights, [], undefined, second);
146
+ writeViewUbo(queue, buffer, camera, light, lights, [], second);
147
147
  expect(Array.from(uploads[1]?.slice(212, 228) ?? [])).toEqual(Array.from(previous));
148
148
 
149
- writeViewUbo(queue, buffer, camera, light, lights, [], undefined, second);
149
+ writeViewUbo(queue, buffer, camera, light, lights, [], second);
150
150
  expect(Array.from(uploads[2]?.slice(212, 228) ?? [])).toEqual(Array.from(previous));
151
151
  });
152
152
  });
@@ -13,6 +13,10 @@ const hdrpFalseKey =
13
13
  'CLUSTER_FORWARD_AVAILABLE=true+STORAGE_BUFFER_AVAILABLE=true+TRANSMISSION_AVAILABLE=false+VERTEX_COLOR_AVAILABLE=false';
14
14
  const hdrpTrueKey =
15
15
  'CLUSTER_FORWARD_AVAILABLE=true+STORAGE_BUFFER_AVAILABLE=true+TRANSMISSION_AVAILABLE=true+VERTEX_COLOR_AVAILABLE=false';
16
+ const probeFalseKey =
17
+ 'CLUSTER_FORWARD_AVAILABLE=false+PROBE_BLEND_AVAILABLE=true+STORAGE_BUFFER_AVAILABLE=true+TRANSMISSION_AVAILABLE=false+VERTEX_COLOR_AVAILABLE=false';
18
+ const probeTrueKey =
19
+ 'CLUSTER_FORWARD_AVAILABLE=false+PROBE_BLEND_AVAILABLE=true+STORAGE_BUFFER_AVAILABLE=true+TRANSMISSION_AVAILABLE=true+VERTEX_COLOR_AVAILABLE=false';
16
20
 
17
21
  function standardEntry(
18
22
  variants: readonly MaterialShaderManifestEntry['variants'][number][],
@@ -52,6 +56,33 @@ function hdrpVariant(definesKey: string, transmission: boolean) {
52
56
  };
53
57
  }
54
58
 
59
+ function probeTransmissionVariant(definesKey: string, transmission: boolean) {
60
+ const base = transmissionVariant(definesKey, transmission);
61
+ return {
62
+ ...base,
63
+ definesKey,
64
+ defines: { ...base.defines, PROBE_BLEND_AVAILABLE: true },
65
+ };
66
+ }
67
+
68
+ function capabilityTransmissionVariant(
69
+ definesKey: string,
70
+ transmission: boolean,
71
+ extendedLighting: boolean,
72
+ ) {
73
+ const base = transmissionVariant(definesKey, transmission);
74
+ return {
75
+ ...base,
76
+ definesKey,
77
+ defines: {
78
+ ...base.defines,
79
+ EXTENDED_LIGHTING_AVAILABLE: extendedLighting,
80
+ PROJECTOR_AVAILABLE: true,
81
+ },
82
+ composedWgsl: `standard-${extendedLighting ? 'extended' : 'fallback'}-${transmission}`,
83
+ };
84
+ }
85
+
55
86
  describe('Standard transmission exact-key prewarm selection', () => {
56
87
  it('returns both declared variants through exact manifest lookup', () => {
57
88
  const entry = standardEntry([
@@ -74,6 +105,44 @@ describe('Standard transmission exact-key prewarm selection', () => {
74
105
  'manifest row is missing',
75
106
  );
76
107
  });
108
+
109
+ it('keeps object-level probe variants out of the boot prewarm selection', () => {
110
+ const entry = standardEntry([
111
+ probeTransmissionVariant(probeFalseKey, false),
112
+ transmissionVariant(falseKey, false),
113
+ probeTransmissionVariant(probeTrueKey, true),
114
+ transmissionVariant(trueKey, true),
115
+ ]);
116
+ expect(
117
+ selectStandardPbrTransmissionPrewarmVariants(entry, true).map(
118
+ (variant) => variant.definesKey,
119
+ ),
120
+ ).toEqual([falseKey, trueKey]);
121
+ });
122
+
123
+ it('selects the prewarm pair for the admitted extended-lighting capability', () => {
124
+ const fallbackFalseKey = `${falseKey}+EXTENDED_LIGHTING_AVAILABLE=false+PROJECTOR_AVAILABLE=true`;
125
+ const fallbackTrueKey = `${trueKey}+EXTENDED_LIGHTING_AVAILABLE=false+PROJECTOR_AVAILABLE=true`;
126
+ const extendedFalseKey = `${falseKey}+EXTENDED_LIGHTING_AVAILABLE=true+PROJECTOR_AVAILABLE=true`;
127
+ const extendedTrueKey = `${trueKey}+EXTENDED_LIGHTING_AVAILABLE=true+PROJECTOR_AVAILABLE=true`;
128
+ const entry = standardEntry([
129
+ capabilityTransmissionVariant(fallbackFalseKey, false, false),
130
+ capabilityTransmissionVariant(fallbackTrueKey, true, false),
131
+ capabilityTransmissionVariant(extendedFalseKey, false, true),
132
+ capabilityTransmissionVariant(extendedTrueKey, true, true),
133
+ ]);
134
+
135
+ expect(
136
+ selectStandardPbrTransmissionPrewarmVariants(entry, true, true, true, false).map(
137
+ (variant) => variant.definesKey,
138
+ ),
139
+ ).toEqual([fallbackFalseKey, fallbackTrueKey]);
140
+ expect(
141
+ selectStandardPbrTransmissionPrewarmVariants(entry, true, true, true, true).map(
142
+ (variant) => variant.definesKey,
143
+ ),
144
+ ).toEqual([extendedFalseKey, extendedTrueKey]);
145
+ });
77
146
  });
78
147
 
79
148
  describe('HDRP PBR prewarm capability selection', () => {
@@ -0,0 +1,140 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { createVisibilityBudget } from '../scene/visibility/budget';
3
+ import {
4
+ primitiveKey,
5
+ primitiveKeyId,
6
+ VisibilityFacetStore,
7
+ viewKey,
8
+ viewKeyId,
9
+ } from '../scene/visibility/facet';
10
+
11
+ const view = (cameraEntity: number) =>
12
+ viewKey({
13
+ attachmentId: 'budget-scene',
14
+ cameraEntity,
15
+ viewRole: 'main',
16
+ viewGeneration: 1,
17
+ });
18
+
19
+ const primitive = (slot: number) =>
20
+ primitiveKey({
21
+ attachmentId: 'budget-scene',
22
+ worldGeneration: 1,
23
+ primitiveSlot: slot,
24
+ slotGeneration: 1,
25
+ });
26
+
27
+ describe('visibility budget integration', () => {
28
+ it('shares one derived window across views and LOD levels', () => {
29
+ const budget = createVisibilityBudget(2048);
30
+ const facets = new VisibilityFacetStore(budget);
31
+ const views = [view(1), view(2)];
32
+ for (const currentView of views) {
33
+ facets.activateView(currentView);
34
+ for (let level = 0; level < 8; level += 1) {
35
+ const currentPrimitive = primitive(currentView.cameraEntity * 10 + level);
36
+ facets.setCandidate(currentView, currentPrimitive, { level, confidence: 1 });
37
+ facets.applyConfidence(currentView, currentPrimitive, {
38
+ type: 'result',
39
+ samples: 0,
40
+ submissionGeneration: 1,
41
+ });
42
+ facets.applyConfidence(currentView, currentPrimitive, {
43
+ type: 'result',
44
+ samples: 0,
45
+ submissionGeneration: 2,
46
+ });
47
+ }
48
+ }
49
+
50
+ expect(facets.visibilityBudget()).toBe(budget);
51
+ expect(facets.visibilityBudget()).toMatchObject({
52
+ effectiveQueryBudget: 2048,
53
+ settleSubmits: 98,
54
+ retestSubmits: 44,
55
+ expirySubmits: 88,
56
+ });
57
+
58
+ facets.advanceSuccessfulSubmits(2 + budget.retestSubmits);
59
+ for (const currentView of views) {
60
+ expect(facets.shouldQuery(currentView, primitive(currentView.cameraEntity * 10))).toBe(true);
61
+ }
62
+ });
63
+
64
+ it('keeps invalidation visible and requeues the same identity without extending evidence', () => {
65
+ const budget = createVisibilityBudget(4096);
66
+ const facets = new VisibilityFacetStore(budget);
67
+ const currentView = view(3);
68
+ const currentPrimitive = primitive(3);
69
+ facets.activateView(currentView);
70
+ facets.setCandidate(currentView, currentPrimitive, { level: 2, confidence: 1 });
71
+ facets.applyConfidence(currentView, currentPrimitive, {
72
+ type: 'result',
73
+ samples: 0,
74
+ submissionGeneration: 1,
75
+ });
76
+ facets.applyConfidence(currentView, currentPrimitive, {
77
+ type: 'result',
78
+ samples: 0,
79
+ submissionGeneration: 2,
80
+ });
81
+ facets.advanceSuccessfulSubmits(2 + budget.retestSubmits, [
82
+ { view: currentView, primitive: currentPrimitive },
83
+ ]);
84
+ expect(facets.getConfidence(currentView, currentPrimitive).status).toBe('hidden');
85
+
86
+ facets.applyConfidence(currentView, currentPrimitive, {
87
+ type: 'invalidate',
88
+ reason: 'world-reorder',
89
+ });
90
+ expect(facets.getConfidence(currentView, currentPrimitive)).toMatchObject({
91
+ status: 'visible',
92
+ dirty: true,
93
+ queryable: true,
94
+ });
95
+ expect(facets.dequeueQueryCandidates(currentView, 1)).toHaveLength(1);
96
+ });
97
+
98
+ it('uses expiry-only scheduling for a due hidden row while its query is in flight', () => {
99
+ const budget = createVisibilityBudget();
100
+ const facets = new VisibilityFacetStore(budget);
101
+ const currentView = view(4);
102
+ const currentPrimitive = primitive(4);
103
+ facets.activateView(currentView);
104
+ facets.setCandidate(currentView, currentPrimitive, { level: 0, confidence: 1 });
105
+ facets.applyConfidence(currentView, currentPrimitive, {
106
+ type: 'result',
107
+ samples: 0,
108
+ submissionGeneration: 1,
109
+ });
110
+ facets.applyConfidence(currentView, currentPrimitive, {
111
+ type: 'result',
112
+ samples: 0,
113
+ submissionGeneration: 2,
114
+ });
115
+ const dueGeneration = 2 + budget.retestSubmits;
116
+ facets.advanceSuccessfulSubmits(dueGeneration);
117
+ expect(facets.dequeueQueryCandidates(currentView, 1)).toHaveLength(1);
118
+
119
+ facets.applyConfidence(currentView, currentPrimitive, {
120
+ type: 'submit',
121
+ submissionGeneration: dueGeneration,
122
+ });
123
+ expect(facets.getConfidence(currentView, currentPrimitive).successfulSubmits).toBe(
124
+ budget.retestSubmits + 1,
125
+ );
126
+ const scheduler = facets as unknown as {
127
+ confidence: { wakeByGeneration: Map<number, Set<string>> };
128
+ };
129
+ const key = `${viewKeyId(currentView)}|${primitiveKeyId(currentPrimitive)}`;
130
+ expect([...scheduler.confidence.wakeByGeneration.values()].flat()).not.toContain(key);
131
+
132
+ facets.advanceSuccessfulSubmits(
133
+ dueGeneration + budget.expirySubmits - budget.retestSubmits - 1,
134
+ );
135
+ expect(facets.getConfidence(currentView, currentPrimitive)).toMatchObject({
136
+ status: 'visible',
137
+ dirty: true,
138
+ });
139
+ });
140
+ });
@@ -0,0 +1,70 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import {
3
+ createVisibilityBudget,
4
+ DEFAULT_CONFIGURED_QUERY_BUDGET,
5
+ MAX_QUERY_BUDGET,
6
+ } from '../scene/visibility/budget';
7
+
8
+ describe('visibility budget derivation', () => {
9
+ it.each([
10
+ [
11
+ 2048,
12
+ {
13
+ effectiveQueryBudget: 2048,
14
+ oneSweepCandidates: 49,
15
+ oneSweepOccluded: 44,
16
+ settleSubmits: 98,
17
+ retestSubmits: 44,
18
+ expirySubmits: 88,
19
+ },
20
+ ],
21
+ [
22
+ 4096,
23
+ {
24
+ effectiveQueryBudget: 4096,
25
+ oneSweepCandidates: 25,
26
+ oneSweepOccluded: 22,
27
+ settleSubmits: 50,
28
+ retestSubmits: 22,
29
+ expirySubmits: 44,
30
+ },
31
+ ],
32
+ ])('derives the approved windows for Q=%d', (configuredQueryBudget, expected) => {
33
+ expect(createVisibilityBudget(configuredQueryBudget)).toMatchObject({
34
+ configuredQueryBudget,
35
+ ...expected,
36
+ });
37
+ });
38
+
39
+ it('uses the default and clamps larger configured budgets without changing page policy', () => {
40
+ expect(createVisibilityBudget()).toMatchObject({
41
+ configuredQueryBudget: DEFAULT_CONFIGURED_QUERY_BUDGET,
42
+ effectiveQueryBudget: DEFAULT_CONFIGURED_QUERY_BUDGET,
43
+ });
44
+ expect(createVisibilityBudget(MAX_QUERY_BUDGET + 1).effectiveQueryBudget).toBe(
45
+ MAX_QUERY_BUDGET,
46
+ );
47
+ });
48
+
49
+ it.each([
50
+ 0,
51
+ -1,
52
+ 1.5,
53
+ Number.NaN,
54
+ Number.POSITIVE_INFINITY,
55
+ ])('rejects invalid configured budget %s', (configuredQueryBudget) => {
56
+ expect(() => createVisibilityBudget(configuredQueryBudget)).toThrow(
57
+ 'configuredQueryBudget must be a positive safe integer',
58
+ );
59
+ });
60
+
61
+ it.each([1, 2, 3, 4, 8])('keeps derived windows independent of %d LOD levels', (levels) => {
62
+ const budget = createVisibilityBudget(2048);
63
+ expect({ levels, ...budget }).toMatchObject({
64
+ levels,
65
+ settleSubmits: 98,
66
+ retestSubmits: 44,
67
+ expirySubmits: 88,
68
+ });
69
+ });
70
+ });
@@ -1,5 +1,10 @@
1
1
  import { World } from '@forgeax/engine-ecs';
2
- import { ChildOf, registerPropagateTransforms, Transform } from '@forgeax/engine-scene';
2
+ import {
3
+ ChildOf,
4
+ GlobalTransform,
5
+ registerPropagateTransforms,
6
+ Transform,
7
+ } from '@forgeax/engine-scene';
3
8
  import { describe, expect, it, vi } from 'vitest';
4
9
  import { Fog } from '../components/fog';
5
10
  import { Visibility, VisibilityStateValue } from '../components/visibility';
@@ -67,13 +72,13 @@ describe('visibility extract orchestration', () => {
67
72
  const snapshot = frame.visibilitySnapshots[0];
68
73
  expect(snapshot?.get(parent)?.effective).toBe('visible');
69
74
  expect(snapshot?.get(child)?.effective).toBe('visible');
70
- expect(world.get(child, Transform).unwrap().world[12]).toBeCloseTo(3);
75
+ expect(world.get(child, GlobalTransform).unwrap().world[12]).toBeCloseTo(3);
71
76
  });
72
77
 
73
- it('keeps renderer extraction read-only over derived Transform.world', () => {
78
+ it('keeps renderer extraction read-only over derived GlobalTransform.world', () => {
74
79
  const { world, parent, child } = makeWorld();
75
80
  world.update(0).unwrap();
76
- const before = [...world.get(child, Transform).unwrap().world];
81
+ const before = [...world.get(child, GlobalTransform).unwrap().world];
77
82
  const beforeInspection = world.inspect();
78
83
  const beforeShape = {
79
84
  entityCount: beforeInspection.entityCount,
@@ -87,7 +92,7 @@ describe('visibility extract orchestration', () => {
87
92
  world.set(parent, Transform, { pos: [9, 0, 0] }).unwrap();
88
93
  extractFrames([world], 0);
89
94
 
90
- expect([...world.get(child, Transform).unwrap().world]).toEqual(before);
95
+ expect([...world.get(child, GlobalTransform).unwrap().world]).toEqual(before);
91
96
  const afterInspection = world.inspect();
92
97
  expect({
93
98
  entityCount: afterInspection.entityCount,
@@ -98,7 +103,7 @@ describe('visibility extract orchestration', () => {
98
103
  resourceKeys: afterInspection.resourceKeys,
99
104
  }).toEqual(beforeShape);
100
105
  world.update(0).unwrap();
101
- expect(world.get(child, Transform).unwrap().world[12]).toBeCloseTo(11);
106
+ expect(world.get(child, GlobalTransform).unwrap().world[12]).toBeCloseTo(11);
102
107
  });
103
108
 
104
109
  it('keeps direct prepared-kernel output aligned with extractFrames output', () => {
@@ -13,8 +13,10 @@ const passes = readFileSync(new URL('../volume/passes.ts', import.meta.url), 'ut
13
13
 
14
14
  describe('volumetric fog Point and Spot accumulation', () => {
15
15
  it('accumulates Point and Spot in one fixed inject stage', () => {
16
- expect(inject).toContain('pointLightsBuffer');
17
- expect(inject).toContain('spotLightsBuffer');
16
+ expect(inject).toContain('light_data');
17
+ expect(inject).toContain('cluster_uniform');
18
+ expect(inject).not.toContain('pointLightsBuffer');
19
+ expect(inject).not.toContain('spotLightsBuffer');
18
20
  expect(inject).toContain('evalVolumePoint');
19
21
  expect(inject).toContain('evalVolumeSpot');
20
22
  expect(inject).toContain('shadow_visibility');
@@ -37,7 +39,8 @@ describe('volumetric fog Point and Spot accumulation', () => {
37
39
  });
38
40
 
39
41
  it('retains falsifier hooks for off lights, occluders, and shadow visibility', () => {
40
- expect(inject).toContain('count');
42
+ expect(inject).toContain('selected_cluster_light_slot');
43
+ expect(inject).toContain('cluster_uniform.grid.w');
41
44
  expect(inject).toContain('shadowAtlasTile');
42
45
  expect(inject).toContain('return 0.0');
43
46
  expect(integrate).toContain('evalSpotAttenuation');
@@ -2,12 +2,7 @@ import type { RhiDevice, Texture, TextureView } from '@forgeax/engine-rhi';
2
2
  import { createShaderModule, rhi } from '@forgeax/engine-rhi-webgpu';
3
3
  import { buildEngineShaderManifest } from '@forgeax/engine-vite-plugin-shader';
4
4
  import { describe, expect, it } from 'vitest';
5
- import {
6
- LIGHT_ARRAY_HEADER_BYTES,
7
- LIGHT_ARRAY_MAX_SLOTS,
8
- POINT_LIGHT_STD430_BYTES,
9
- SPOT_LIGHT_STD430_BYTES,
10
- } from '../light-buffer-layout';
5
+ import { BYTES_PER_DIRECT_LIGHT_SLOT } from '../light-buffer-layout';
11
6
 
12
7
  const MAP_READ = 0x0001;
13
8
  const BUFFER_COPY_DST = 0x0008;
@@ -21,6 +16,9 @@ const SIZE = 8;
21
16
  const PACKED_LAYERS = Math.ceil(SIZE / 4);
22
17
  const BYTES_PER_ROW = 256;
23
18
  const BYTES_PER_PIXEL = 8;
19
+ const MAX_CLUSTER_LIGHTS = 256;
20
+ const CLUSTER_LIGHT_DATA_BYTES = MAX_CLUSTER_LIGHTS * BYTES_PER_DIRECT_LIGHT_SLOT;
21
+ const CLUSTER_UNIFORM_BYTES = 32;
24
22
  const engineManifest = await buildEngineShaderManifest();
25
23
 
26
24
  interface StageValues {
@@ -190,15 +188,15 @@ async function runInjectReadback(sceneDepthClear = 1): Promise<VolumeStageReadba
190
188
  size: 128,
191
189
  usage: 0x0040 | BUFFER_COPY_DST,
192
190
  });
193
- const spotLightsBuffer = device.createBuffer({
194
- label: 'stage-readback.spot-lights',
195
- size: LIGHT_ARRAY_HEADER_BYTES + LIGHT_ARRAY_MAX_SLOTS * SPOT_LIGHT_STD430_BYTES,
191
+ const lightDataBuffer = device.createBuffer({
192
+ label: 'stage-readback.cluster-light-data',
193
+ size: CLUSTER_LIGHT_DATA_BYTES,
196
194
  usage: STORAGE_BUFFER | BUFFER_COPY_DST,
197
195
  });
198
- const pointLightsBuffer = device.createBuffer({
199
- label: 'stage-readback.point-lights',
200
- size: LIGHT_ARRAY_HEADER_BYTES + LIGHT_ARRAY_MAX_SLOTS * POINT_LIGHT_STD430_BYTES,
201
- usage: STORAGE_BUFFER | BUFFER_COPY_DST,
196
+ const clusterUniformBuffer = device.createBuffer({
197
+ label: 'stage-readback.cluster-uniform',
198
+ size: CLUSTER_UNIFORM_BYTES,
199
+ usage: 0x0040 | BUFFER_COPY_DST,
202
200
  });
203
201
  const injectReadback = device.createBuffer({
204
202
  label: 'stage-readback.inject',
@@ -218,8 +216,8 @@ async function runInjectReadback(sceneDepthClear = 1): Promise<VolumeStageReadba
218
216
  if (
219
217
  !viewBuffer.ok ||
220
218
  !paramsBuffer.ok ||
221
- !spotLightsBuffer.ok ||
222
- !pointLightsBuffer.ok ||
219
+ !lightDataBuffer.ok ||
220
+ !clusterUniformBuffer.ok ||
223
221
  !injectReadback.ok ||
224
222
  !temporalReadback.ok ||
225
223
  !integrateReadback.ok
@@ -245,19 +243,22 @@ async function runInjectReadback(sceneDepthClear = 1): Promise<VolumeStageReadba
245
243
  paramsData.set([0, -1, 0, 0], 20);
246
244
  paramsData.set([1, 1, 1, 0], 24);
247
245
  paramsData.set([2, 0, 0, 0], 28);
246
+ const lightData = new Uint8Array(CLUSTER_LIGHT_DATA_BYTES);
247
+ const clusterUniformData = new Float32Array(8);
248
+ const clusterGrid = new Uint32Array(clusterUniformData.buffer);
249
+ clusterGrid[0] = 1;
250
+ clusterGrid[1] = 1;
251
+ clusterGrid[2] = 1;
252
+ clusterGrid[3] = 1;
248
253
  const viewWrite = device.queue.writeBuffer(viewBuffer.value, 0, viewData);
249
254
  const paramsWrite = device.queue.writeBuffer(paramsBuffer.value, 0, paramsData);
250
- const spotLightsWrite = device.queue.writeBuffer(
251
- spotLightsBuffer.value,
252
- 0,
253
- new Uint32Array([0, 0, 0, 0]),
254
- );
255
- const pointLightsWrite = device.queue.writeBuffer(
256
- pointLightsBuffer.value,
255
+ const lightDataWrite = device.queue.writeBuffer(lightDataBuffer.value, 0, lightData);
256
+ const clusterUniformWrite = device.queue.writeBuffer(
257
+ clusterUniformBuffer.value,
257
258
  0,
258
- new Uint32Array([0, 0, 0, 0]),
259
+ clusterUniformData,
259
260
  );
260
- if (!viewWrite.ok || !paramsWrite.ok || !spotLightsWrite.ok || !pointLightsWrite.ok)
261
+ if (!viewWrite.ok || !paramsWrite.ok || !lightDataWrite.ok || !clusterUniformWrite.ok)
261
262
  throw new Error('Dawn volume stage readback unavailable: uniform upload failed');
262
263
  const densityData = new Uint8Array((BYTES_PER_PIXEL / 2) * SIZE * SIZE * SIZE);
263
264
  densityData.fill(255);
@@ -291,6 +292,7 @@ async function runInjectReadback(sceneDepthClear = 1): Promise<VolumeStageReadba
291
292
  storageTexture: { access: 'write-only', format: 'rgba8unorm', viewDimension: '2d-array' },
292
293
  },
293
294
  { binding: 7, visibility: 4, buffer: { type: 'read-only-storage' } },
295
+ { binding: 8, visibility: 4, buffer: { type: 'uniform' } },
294
296
  ],
295
297
  });
296
298
  if (!layout.ok) throw layout.error;
@@ -354,7 +356,7 @@ async function runInjectReadback(sceneDepthClear = 1): Promise<VolumeStageReadba
354
356
  storageTexture: { access: 'write-only', format: 'rgba16float', viewDimension: '2d' },
355
357
  },
356
358
  { binding: 10, visibility: 4, buffer: { type: 'read-only-storage' } },
357
- { binding: 11, visibility: 4, buffer: { type: 'read-only-storage' } },
359
+ { binding: 11, visibility: 4, buffer: { type: 'uniform' } },
358
360
  { binding: 12, visibility: 4, texture: { sampleType: 'float', viewDimension: '2d' } },
359
361
  { binding: 13, visibility: 4, sampler: { type: 'filtering' } },
360
362
  ],
@@ -393,7 +395,8 @@ async function runInjectReadback(sceneDepthClear = 1): Promise<VolumeStageReadba
393
395
  { binding: 4, resource: { kind: 'sampler', value: comparisonSampler.value } },
394
396
  { binding: 5, resource: { kind: 'buffer', value: { buffer: paramsBuffer.value } } },
395
397
  { binding: 6, resource: { kind: 'textureView', value: outputView } },
396
- { binding: 7, resource: { kind: 'buffer', value: { buffer: spotLightsBuffer.value } } },
398
+ { binding: 7, resource: { kind: 'buffer', value: { buffer: lightDataBuffer.value } } },
399
+ { binding: 8, resource: { kind: 'buffer', value: { buffer: clusterUniformBuffer.value } } },
397
400
  ],
398
401
  });
399
402
  if (!bindings.ok) throw bindings.error;
@@ -420,8 +423,8 @@ async function runInjectReadback(sceneDepthClear = 1): Promise<VolumeStageReadba
420
423
  { binding: 7, resource: { kind: 'sampler', value: densitySampler.value } },
421
424
  { binding: 8, resource: { kind: 'textureView', value: historyView } },
422
425
  { binding: 9, resource: { kind: 'textureView', value: temporalView } },
423
- { binding: 10, resource: { kind: 'buffer', value: { buffer: spotLightsBuffer.value } } },
424
- { binding: 11, resource: { kind: 'buffer', value: { buffer: pointLightsBuffer.value } } },
426
+ { binding: 10, resource: { kind: 'buffer', value: { buffer: lightDataBuffer.value } } },
427
+ { binding: 11, resource: { kind: 'buffer', value: { buffer: clusterUniformBuffer.value } } },
425
428
  { binding: 12, resource: { kind: 'textureView', value: projectorView } },
426
429
  { binding: 13, resource: { kind: 'sampler', value: densitySampler.value } },
427
430
  ],
@@ -10,6 +10,19 @@ import type {
10
10
  /** Optional host hooks for recorder and device-lifecycle capabilities. */
11
11
  export interface RhiBackendInstrumentation {
12
12
  readonly resolveSurfaceDevice?: (device: RhiDevice) => Result<RhiDevice, RhiError>;
13
+ /**
14
+ * Optional deterministic submit fault seam for backend-owned integration
15
+ * fixtures. The callback runs after `finish()` and before the queue submit;
16
+ * returning an error aborts the frame transaction without changing the
17
+ * production queue contract.
18
+ */
19
+ readonly beforeSubmit?: (device: RhiDevice) => RhiError | undefined;
20
+ /**
21
+ * Optional device-loss projection for deterministic recovery fixtures. A
22
+ * backend may compose this promise with its native `device.lost` promise;
23
+ * normal production packs leave it undefined and use the native signal.
24
+ */
25
+ readonly deviceLost?: (device: RhiDevice) => RhiDevice['lost'];
13
26
  readonly onFrameBoundary?: () => void;
14
27
  readonly onDeviceLost?: () => void;
15
28
  }
@@ -1,4 +1,5 @@
1
1
  import type { RhiAdapter, RhiDevice } from '@forgeax/engine-rhi';
2
+ import { EXTENDED_LIGHTING_REQUIRED_SAMPLED_TEXTURES } from '../prepare/extended-lighting/resources';
2
3
 
3
4
  const COMPRESSION_FEATURES: GPUFeatureName[] = [
4
5
  'texture-compression-bc',
@@ -9,7 +10,9 @@ const COMPRESSION_FEATURES: GPUFeatureName[] = [
9
10
  // The Standard transmission fragment ABI needs 17 sampled textures across its
10
11
  // material, IBL, clustered-light, and SSAO bindings. Ordinary Standard draws
11
12
  // use the non-transmission variant and remain valid on the WebGPU minimum of
12
- // 16; only request the elevated limit when the adapter can provide it.
13
+ // 16. The extended-lighting topology reserves 20, so a device descriptor must
14
+ // request that larger limit when the adapter can provide it; adapter limits are
15
+ // not automatically inherited by requestDevice().
13
16
  export const STANDARD_PBR_REQUIRED_SAMPLED_TEXTURES = 17;
14
17
 
15
18
  export interface DeviceFeatureAdmissionOptions {
@@ -36,18 +39,22 @@ export function deriveDeviceFeatureAdmission(
36
39
  if (timestampRequested && adapter.features.has('timestamp-query')) {
37
40
  requiredFeatures.push('timestamp-query');
38
41
  }
39
- const supportsTransmission =
40
- (adapter.limits?.maxSampledTexturesPerShaderStage ?? 0) >=
41
- STANDARD_PBR_REQUIRED_SAMPLED_TEXTURES;
42
+ const adapterSampledTextureLimit = adapter.limits?.maxSampledTexturesPerShaderStage ?? 0;
43
+ const requiredSampledTextureLimit =
44
+ adapterSampledTextureLimit >= EXTENDED_LIGHTING_REQUIRED_SAMPLED_TEXTURES
45
+ ? EXTENDED_LIGHTING_REQUIRED_SAMPLED_TEXTURES
46
+ : adapterSampledTextureLimit >= STANDARD_PBR_REQUIRED_SAMPLED_TEXTURES
47
+ ? STANDARD_PBR_REQUIRED_SAMPLED_TEXTURES
48
+ : undefined;
42
49
  return {
43
50
  requiredFeatures,
44
- ...(supportsTransmission
45
- ? {
51
+ ...(requiredSampledTextureLimit === undefined
52
+ ? {}
53
+ : {
46
54
  requiredLimits: {
47
- maxSampledTexturesPerShaderStage: STANDARD_PBR_REQUIRED_SAMPLED_TEXTURES,
55
+ maxSampledTexturesPerShaderStage: requiredSampledTextureLimit,
48
56
  },
49
- }
50
- : {}),
57
+ }),
51
58
  };
52
59
  }
53
60