@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
@@ -25,11 +25,11 @@ import type {
25
25
  BindGroup,
26
26
  BindGroupEntry,
27
27
  BindGroupLayout,
28
- BindGroupLayoutDescriptor,
29
28
  Buffer,
30
29
  PipelineLayout,
31
30
  RhiDevice,
32
31
  Sampler,
32
+ TextureFormat,
33
33
  TextureView,
34
34
  } from '@forgeax/engine-rhi';
35
35
  import { DEFAULT_STANDARD_PBR_PARAM_SCHEMA, type ShaderCatalog } from '@forgeax/engine-shader';
@@ -72,6 +72,10 @@ export interface PbrPipelineLayoutBundle {
72
72
  readonly meshArrayBgl: BindGroupLayout;
73
73
  /** Per-instance storage BindGroupLayout (slot 3). */
74
74
  readonly instancesBgl: BindGroupLayout;
75
+ /** Probe-enabled slot-3 BGL; aliases `instancesBgl` when probes are unavailable. */
76
+ readonly probeInstancesBgl: BindGroupLayout;
77
+ /** Same pipeline layout with the probe-enabled slot-3 BGL, when supported. */
78
+ readonly probePipelineLayout: PipelineLayout | null;
75
79
  /**
76
80
  * Same 4 layouts in slot order. Useful for assertion sites that check the
77
81
  * pipeline-layout `bindGroupLayouts` array shape (t57 (a) + (d)).
@@ -84,6 +88,14 @@ export interface PbrPipelineLayoutBundle {
84
88
  ];
85
89
  }
86
90
 
91
+ /** Derives the Standard PBR attachment list without allocating a closed path. */
92
+ export function standardPbrColorFormats(
93
+ baseFormat: TextureFormat,
94
+ fallbackDemand: boolean,
95
+ ): readonly TextureFormat[] {
96
+ return fallbackDemand ? [baseFormat, 'rgba16float'] : [baseFormat];
97
+ }
98
+
87
99
  // ─── Base entries (round-4 SSOT) ────────────────────────────────────────────
88
100
 
89
101
  /**
@@ -325,14 +337,21 @@ export const INJECTION_KIND_LENGTHS = {
325
337
  */
326
338
  export interface PbrCaps {
327
339
  readonly storageBuffer: boolean;
340
+ /**
341
+ * Whether the selected material variant declares the optional extended
342
+ * lighting view resources. The default keeps the historical helper shape
343
+ * for callers that build the canonical full PBR layout; the renderer passes
344
+ * the device/variant decision explicitly.
345
+ */
346
+ readonly extendedLighting?: boolean;
347
+ /** Selects the optional binding(1) ProbeBlendRecord lane. */
348
+ readonly probeBlend?: boolean;
328
349
  /** Whether the device can carry the optional SpotLight projector texture. */
329
350
  readonly projectorAvailable?: boolean;
330
351
  }
331
352
 
332
353
  /**
333
354
  * The view BGL entry list. binding 0 = view UBO (vertex + fragment);
334
- * binding 1 = pointLights (storage or uniform, per caps);
335
- * binding 2 = spotLights (storage or uniform, per caps);
336
355
  * binding 3 = directional shadowMap atlas (texture_depth_2d, vertex+fragment).
337
356
  * feat-20260613-csm-cascaded-shadow-maps: this is the CSM atlas — N
338
357
  * cascades tiled into one 2D depth texture, sampled via per-cascade UV
@@ -372,12 +391,11 @@ export interface PbrCaps {
372
391
  * layout and consumed only by the dedicated points-lines shader).
373
392
  * Isolated here so M4 round-4 tests can recreate the layout.
374
393
  *
375
- * feat-20260526-pbr-uniform-fallback-no-storage-buffer M3 / w9:
376
- * caps.storageBuffer===false switches bindings 1+2 from
377
- * 'read-only-storage' to 'uniform'.
394
+ * Local lights are not part of the view group. Standard material variants
395
+ * consume the one Cluster payload in group(2), while this group retains only
396
+ * camera and shadow resources.
378
397
  */
379
398
  export function buildPbrViewBglEntries(caps: PbrCaps): GPUBindGroupLayoutEntry[] {
380
- const lightBufType: GPUBufferBindingType = caps.storageBuffer ? 'read-only-storage' : 'uniform';
381
399
  const entries: GPUBindGroupLayoutEntry[] = [
382
400
  {
383
401
  binding: 0,
@@ -386,16 +404,6 @@ export function buildPbrViewBglEntries(caps: PbrCaps): GPUBindGroupLayoutEntry[]
386
404
  // Keeping this dynamic is also valid for the display slot at offset 0.
387
405
  buffer: { type: 'uniform', hasDynamicOffset: true },
388
406
  },
389
- {
390
- binding: 1,
391
- visibility: GPU_SHADER_STAGE_FRAGMENT,
392
- buffer: { type: lightBufType },
393
- },
394
- {
395
- binding: 2,
396
- visibility: GPU_SHADER_STAGE_FRAGMENT,
397
- buffer: { type: lightBufType },
398
- },
399
407
  {
400
408
  binding: 3,
401
409
  visibility: GPU_SHADER_STAGE_VERTEX | GPU_SHADER_STAGE_FRAGMENT,
@@ -422,9 +430,9 @@ export function buildPbrViewBglEntries(caps: PbrCaps): GPUBindGroupLayoutEntry[]
422
430
  // shadow-casting point light slot (shadowAtlasLayer in [0, 4)). Each
423
431
  // lane stores `(near, far, 1/(far-near), 0)` so the fragment-shader
424
432
  // depth-ref reconstruction (lighting-punctual.wgsl evalPointShadowed)
425
- // can avoid sampling LightSlot for the URP path. HDRP rides the same
426
- // constants on `LightSlot.kind_and_pad.zw` so binding 6 is unused on
427
- // HDRP shaders even though the BGL declares it (charter P4 single SSOT
433
+ // can avoid sampling DirectLightSlot for the URP path. The shared
434
+ // DirectLightSlot metadata remains the identity owner, so binding 6 is
435
+ // unused on HDRP shaders even though the BGL declares it (charter P4 single SSOT
428
436
  // BGL across pipelines; the HDRP variant simply doesn't reference the
429
437
  // binding in WGSL, which is allowed).
430
438
  {
@@ -474,12 +482,41 @@ export function buildPbrViewBglEntries(caps: PbrCaps): GPUBindGroupLayoutEntry[]
474
482
  texture: { sampleType: 'depth', viewDimension: '2d' },
475
483
  },
476
484
  ];
477
- // The projector is optional, but when present it is a sampled texture in
478
- // the fragment stage. Keep it out of the low-limit view layout so the
479
- // remaining HDRP PBR ABI stays within WebGPU's minimum 16 sampled textures.
480
- // The corresponding PROJECTOR_AVAILABLE shader variant is selected from
481
- // the same device capability bit and returns a white modulation.
482
- if (caps.projectorAvailable !== false) {
485
+ const extendedLighting = caps.extendedLighting ?? true;
486
+ if (extendedLighting) {
487
+ entries.push(
488
+ {
489
+ binding: 9,
490
+ visibility: GPU_SHADER_STAGE_FRAGMENT,
491
+ sampler: { type: 'filtering' },
492
+ },
493
+ {
494
+ binding: 11,
495
+ visibility: GPU_SHADER_STAGE_FRAGMENT,
496
+ texture: { sampleType: 'float', viewDimension: '2d-array' },
497
+ },
498
+ {
499
+ binding: 12,
500
+ visibility: GPU_SHADER_STAGE_FRAGMENT,
501
+ texture: { sampleType: 'float', viewDimension: '2d-array' },
502
+ },
503
+ {
504
+ binding: 13,
505
+ visibility: GPU_SHADER_STAGE_FRAGMENT,
506
+ texture: { sampleType: 'float', viewDimension: '2d' },
507
+ },
508
+ {
509
+ binding: 14,
510
+ visibility: GPU_SHADER_STAGE_FRAGMENT,
511
+ texture: { sampleType: 'float', viewDimension: '2d' },
512
+ },
513
+ {
514
+ binding: 15,
515
+ visibility: GPU_SHADER_STAGE_FRAGMENT,
516
+ buffer: { type: 'uniform' },
517
+ },
518
+ );
519
+ } else if (caps.projectorAvailable !== false) {
483
520
  entries.push(
484
521
  {
485
522
  binding: 11,
@@ -508,8 +545,8 @@ export function buildPbrViewBglEntries(caps: PbrCaps): GPUBindGroupLayoutEntry[]
508
545
  * material, mesh-array, instances]`; the material BGL is derived from the
509
546
  * standard material schema and engine injection chain.
510
547
  *
511
- * `caps.storageBuffer===false` switches every storage-buffer BGL entry
512
- * (view bindings 1+2, mesh-array, instances) to `uniform`.
548
+ * `caps.storageBuffer===false` switches the mesh-array and instances entries
549
+ * to `uniform`; local-light resources are never part of this view group.
513
550
  *
514
551
  * Throws on each `createBindGroupLayout` / `createPipelineLayout` Result
515
552
  * failure -- the engine bootstrap path (createRenderer) wraps the call in
@@ -545,9 +582,27 @@ export function buildPbrPipelineLayouts(
545
582
 
546
583
  // instances BGL.
547
584
  const instancesBglRes = device.createBindGroupLayout(
548
- buildBindGroupLayoutDescriptor(BGL_ONLY_SPEC_STUB, { kind: 'pbr-instances', caps }),
585
+ buildBindGroupLayoutDescriptor(BGL_ONLY_SPEC_STUB, {
586
+ kind: 'pbr-instances',
587
+ caps: { ...caps, probeBlend: false },
588
+ }),
549
589
  );
550
590
  if (!instancesBglRes.ok) throw instancesBglRes.error;
591
+ // ProbeBlendRecord is a storage-buffer-only ABI extension. A uniform
592
+ // fallback cannot afford the extra fragment-stage uniform binding on
593
+ // WebGL2/GLES, so do not even construct the probe BGL on that capability
594
+ // route. The record stage gates the matching variant and bind group with
595
+ // the same storage capability; aliasing the ordinary BGL keeps the bundle
596
+ // total and makes an accidental probe request fail closed at layout select.
597
+ const probeInstancesBglRes = caps.storageBuffer
598
+ ? device.createBindGroupLayout(
599
+ buildBindGroupLayoutDescriptor(BGL_ONLY_SPEC_STUB, {
600
+ kind: 'pbr-instances',
601
+ caps: { ...caps, probeBlend: true },
602
+ }),
603
+ )
604
+ : { ok: true as const, value: instancesBglRes.value };
605
+ if (!probeInstancesBglRes.ok) throw probeInstancesBglRes.error;
551
606
 
552
607
  // Pipeline layout (4 slots).
553
608
  const layouts: readonly [BindGroupLayout, BindGroupLayout, BindGroupLayout, BindGroupLayout] = [
@@ -561,6 +616,21 @@ export function buildPbrPipelineLayouts(
561
616
  bindGroupLayouts: layouts,
562
617
  });
563
618
  if (!pipelineLayoutRes.ok) throw pipelineLayoutRes.error;
619
+ const probeLayouts: readonly [
620
+ BindGroupLayout,
621
+ BindGroupLayout,
622
+ BindGroupLayout,
623
+ BindGroupLayout,
624
+ ] = [viewBglRes.value, materialBglRes.value, meshArrayBglRes.value, probeInstancesBglRes.value];
625
+ let probePipelineLayout: PipelineLayout | null = null;
626
+ if (caps.storageBuffer) {
627
+ const probePipelineLayoutRes = device.createPipelineLayout({
628
+ label: 'pbr-probe-pl',
629
+ bindGroupLayouts: probeLayouts,
630
+ });
631
+ if (!probePipelineLayoutRes.ok) throw probePipelineLayoutRes.error;
632
+ probePipelineLayout = probePipelineLayoutRes.value;
633
+ }
564
634
 
565
635
  return {
566
636
  pipelineLayout: pipelineLayoutRes.value,
@@ -568,6 +638,8 @@ export function buildPbrPipelineLayouts(
568
638
  materialBgl: materialBglRes.value,
569
639
  meshArrayBgl: meshArrayBglRes.value,
570
640
  instancesBgl: instancesBglRes.value,
641
+ probeInstancesBgl: probeInstancesBglRes.value,
642
+ probePipelineLayout,
571
643
  bindGroupLayouts: layouts,
572
644
  };
573
645
  }
@@ -685,6 +757,21 @@ export function buildPbrSkinLayouts(
685
757
  bindGroupLayouts: layouts,
686
758
  });
687
759
  if (!pipelineLayoutRes.ok) throw pipelineLayoutRes.error;
760
+ const probeLayouts: readonly [
761
+ BindGroupLayout,
762
+ BindGroupLayout,
763
+ BindGroupLayout,
764
+ BindGroupLayout,
765
+ ] = [pbr.viewBgl, pbr.materialBgl, skinMeshArrayBglRes.value, pbr.probeInstancesBgl];
766
+ let probePipelineLayout: PipelineLayout | null = null;
767
+ if (caps.storageBuffer) {
768
+ const probePipelineLayoutRes = device.createPipelineLayout({
769
+ label: 'pbr-skin-probe-pl',
770
+ bindGroupLayouts: probeLayouts,
771
+ });
772
+ if (!probePipelineLayoutRes.ok) throw probePipelineLayoutRes.error;
773
+ probePipelineLayout = probePipelineLayoutRes.value;
774
+ }
688
775
 
689
776
  return {
690
777
  pipelineLayout: pipelineLayoutRes.value,
@@ -692,6 +779,8 @@ export function buildPbrSkinLayouts(
692
779
  materialBgl: pbr.materialBgl,
693
780
  meshArrayBgl: skinMeshArrayBglRes.value,
694
781
  instancesBgl: pbr.instancesBgl,
782
+ probeInstancesBgl: pbr.probeInstancesBgl,
783
+ probePipelineLayout,
695
784
  bindGroupLayouts: layouts,
696
785
  };
697
786
  }
@@ -860,8 +949,9 @@ export const SPRITE_PASS_PER_INSTANCE_REGION_VARIANT_SET = '';
860
949
  * - `'unlit-material'` — 7 entries: base PBR material only (no inject)
861
950
  * - `'hdrp-7-slot'` — 7 entries (binding 0 + 3..8): HDRP cluster + SSAO group(2) BGL
862
951
  * 2. Caps-driven literal shapes (no shader):
863
- * - `'pbr-view'` — 12 entries: view UBO + lights + shadow bindings,
864
- * projector texture/sampler (11/12), and the Points/Lines UBO (10)
952
+ * - `'pbr-view'` — camera/shadow bindings, optional projector
953
+ * texture/sampler (11/12), and the Points/Lines UBO (10). Local lights
954
+ * are carried exclusively by the Standard Cluster group(2).
865
955
  * (directional 3/4, point 5/6, cascade 7, spot atlas 8; the former
866
956
  * spot lightViewProj matrices moved into the View UBO)
867
957
  * - `'pbr-mesh-array'` — 1 entry: per-entity mesh SSBO (dynamic-offset)
@@ -909,7 +999,7 @@ export interface BindGroupLayoutDescriptorOutput {
909
999
  }
910
1000
 
911
1001
  /** Build the canonical HDRP group(2) bind-group layout. */
912
- export function createHdrpBindGroupLayoutDescriptor(): BindGroupLayoutDescriptor {
1002
+ export function createHdrpBindGroupLayoutDescriptor(): BindGroupLayoutDescriptorOutput {
913
1003
  const meshBufType: GPUBufferBindingType = 'read-only-storage';
914
1004
  const clusterBufType: GPUBufferBindingType = 'read-only-storage';
915
1005
  return {
@@ -961,7 +1051,7 @@ export function createHdrpBindGroupLayoutDescriptor(): BindGroupLayoutDescriptor
961
1051
  }
962
1052
 
963
1053
  /** Build the HDRP group(2) layout for the clustered skin variant. */
964
- export function createHdrpSkinBindGroupLayoutDescriptor(): BindGroupLayoutDescriptor {
1054
+ export function createHdrpSkinBindGroupLayoutDescriptor(): BindGroupLayoutDescriptorOutput {
965
1055
  const base = createHdrpBindGroupLayoutDescriptor();
966
1056
  const baseEntries = base.entries;
967
1057
  if (baseEntries === undefined || baseEntries[0] === undefined) {
@@ -1088,19 +1178,35 @@ export function buildBindGroupLayoutDescriptor(
1088
1178
  const meshBufType: GPUBufferBindingType = caps.storageBuffer
1089
1179
  ? 'read-only-storage'
1090
1180
  : 'uniform';
1181
+ const entries: GPUBindGroupLayoutEntry[] = [
1182
+ {
1183
+ binding: 0,
1184
+ visibility: GPU_SHADER_STAGE_VERTEX,
1185
+ buffer: { type: meshBufType, hasDynamicOffset: false },
1186
+ },
1187
+ ...(caps.probeBlend === true
1188
+ ? [
1189
+ {
1190
+ // Per-object fragment lane; the record is always exactly 160B.
1191
+ binding: 1,
1192
+ visibility: GPU_SHADER_STAGE_FRAGMENT,
1193
+ buffer: { type: meshBufType, hasDynamicOffset: true },
1194
+ },
1195
+ ]
1196
+ : []),
1197
+ ];
1091
1198
  return {
1092
- label: 'pbr-instances-bgl',
1093
- entries: [
1094
- {
1095
- binding: 0,
1096
- // Standard transmission reads the same per-instance transform in
1097
- // fs_main to convert glTF unit-space thickness into world metres.
1098
- // Keep one shared instances BGL and widen its visibility instead
1099
- // of introducing a second transform upload or six new varyings.
1100
- visibility: GPU_SHADER_STAGE_VERTEX | GPU_SHADER_STAGE_FRAGMENT,
1101
- buffer: { type: meshBufType, hasDynamicOffset: false },
1102
- },
1103
- ],
1199
+ label: caps.probeBlend === true ? 'pbr-probe-instances-bgl' : 'pbr-instances-bgl',
1200
+ entries: entries.map((entry) =>
1201
+ entry.binding === 0
1202
+ ? {
1203
+ ...entry,
1204
+ // Standard transmission reads the same per-instance transform in
1205
+ // fs_main to convert glTF unit-space thickness into world metres.
1206
+ visibility: GPU_SHADER_STAGE_VERTEX | GPU_SHADER_STAGE_FRAGMENT,
1207
+ }
1208
+ : entry,
1209
+ ),
1104
1210
  };
1105
1211
  }
1106
1212
  case 'pbr-skin-mesh-array': {
@@ -5,6 +5,11 @@ import type {
5
5
  } from '@forgeax/engine-render-graph';
6
6
  import { err, ok, type Result } from '@forgeax/engine-types';
7
7
  import { SceneDataUnavailableError } from '../errors/render';
8
+ import {
9
+ GPU_TEXTURE_USAGE_COPY_SRC,
10
+ GPU_TEXTURE_USAGE_RENDER_ATTACHMENT,
11
+ GPU_TEXTURE_USAGE_TEXTURE_BINDING,
12
+ } from '../gpu-texture-usage';
8
13
  import type {
9
14
  RenderPipeline,
10
15
  RenderPipelineBuildContext,
@@ -29,6 +34,7 @@ import {
29
34
  transmissionDemandForTopology,
30
35
  } from '../transmission/backdrop';
31
36
  import {
37
+ addReflectionFallbackObservationPass,
32
38
  addTypedFrameObservationPass,
33
39
  addTypedScenePass,
34
40
  addTypedSkyboxPass,
@@ -135,6 +141,35 @@ function buildUrp(
135
141
  : sceneResolved;
136
142
  if (!scene.ok) return scene;
137
143
 
144
+ const reflectionFallback = topology.reflectionFallback?.enabled
145
+ ? target(graph, 'reflection-fallback-linear-hdr', {
146
+ format: 'rgba16float',
147
+ size: 'surface',
148
+ sampleCount: scene.value.sampleCount,
149
+ domain: 'linear-hdr',
150
+ usage:
151
+ GPU_TEXTURE_USAGE_COPY_SRC |
152
+ GPU_TEXTURE_USAGE_RENDER_ATTACHMENT |
153
+ GPU_TEXTURE_USAGE_TEXTURE_BINDING,
154
+ })
155
+ : ok(undefined);
156
+ if (!reflectionFallback.ok) return reflectionFallback;
157
+ if (reflectionFallback.value !== undefined) {
158
+ const initialized = graph.addRasterPass('reflection-fallback-clear', {
159
+ accesses: [{ resource: reflectionFallback.value.view, usage: 'color-attachment' }],
160
+ colorAttachments: [
161
+ {
162
+ view: reflectionFallback.value.view,
163
+ loadOp: 'clear',
164
+ storeOp: 'store',
165
+ clearValue: { r: 0, g: 0, b: 0, a: 0 },
166
+ },
167
+ ],
168
+ encode: () => {},
169
+ });
170
+ if (!initialized.ok) return initialized;
171
+ }
172
+
138
173
  const shadows = addTypedShadowPasses(graph, topology);
139
174
  if (!shadows.ok) return shadows;
140
175
 
@@ -163,6 +198,9 @@ function buildUrp(
163
198
  const main = addTypedScenePass(graph, {
164
199
  name: 'main',
165
200
  color: scene.value,
201
+ ...(reflectionFallback.value === undefined
202
+ ? {}
203
+ : { colorTargets: [scene.value, reflectionFallback.value] }),
166
204
  depth: depth.value,
167
205
  ...(msaa ? { resolve: sceneResolved.value } : {}),
168
206
  sampled: [
@@ -179,6 +217,7 @@ function buildUrp(
179
217
  extraAccesses: clusterReads,
180
218
  ...(transmissionActive ? { recordMode: 'opaque' as const } : {}),
181
219
  ...(gpuDriven.value === undefined ? {} : { gpuDriven: gpuDriven.value }),
220
+ ...(context.occlusion === undefined ? {} : { occlusion: context.occlusion }),
182
221
  });
183
222
  if (!main.ok) return main;
184
223
 
@@ -286,6 +325,7 @@ function buildUrp(
286
325
  depthSample.value,
287
326
  shadows.value.directional?.view,
288
327
  shadows.value.spot.view,
328
+ clusterBuffers.value,
289
329
  );
290
330
  if (!volume.ok) return volume;
291
331
  }
@@ -318,6 +358,13 @@ function buildUrp(
318
358
  );
319
359
  if (!observation.ok) return observation;
320
360
  }
361
+ if (reflectionFallback.value?.sampleCount === 1) {
362
+ const fallbackObservation = addReflectionFallbackObservationPass(
363
+ graph,
364
+ reflectionFallback.value,
365
+ );
366
+ if (!fallbackObservation.ok) return fallbackObservation;
367
+ }
321
368
 
322
369
  const post = addStandardPost(
323
370
  context,
@@ -11,6 +11,7 @@ import {
11
11
  GPU_BUFFER_USAGE_UNIFORM,
12
12
  } from '../../gpu-usage';
13
13
  import { getOrCreateHdrpBuffers, type HdrpBuffers } from '../../hdrp-buffers';
14
+ import { BYTES_PER_DIRECT_LIGHT_SLOT } from '../../light-buffer-layout';
14
15
  import type { _InternalRenderPipelineContext } from '../../record/render-context';
15
16
  import type { RenderPipelineFrame } from '../../render-pipeline';
16
17
  import type { StandardTopologyInputValue } from './topology';
@@ -65,7 +66,7 @@ export function importStandardClusterBuffers(
65
66
  const usage = GPU_BUFFER_USAGE_STORAGE | GPU_BUFFER_USAGE_COPY_DST;
66
67
  const lightData = graph.importBuffer(
67
68
  'hdrp-light-data',
68
- { size: layout.lightDataSlotCount * 64, usage },
69
+ { size: layout.lightDataSlotCount * BYTES_PER_DIRECT_LIGHT_SLOT, usage },
69
70
  (frame) => resolveBuffers(frame, descriptor).lightDataBuffer,
70
71
  );
71
72
  if (!lightData.ok) return lightData;
@@ -2,12 +2,13 @@ import type { Mat4, Vec3 } from '@forgeax/engine-math';
2
2
  import type {
3
3
  DirectionalLightSnapshot,
4
4
  PointLightSnapshot,
5
+ RectAreaDirectLightSnapshot,
5
6
  SpotLightSnapshot,
6
7
  } from '../../render-system-extract';
7
8
  import type { StandardLightCount } from '../standard-profile';
8
9
 
9
10
  export interface StandardLocalLightInput {
10
- readonly kind: 'point' | 'spot';
11
+ readonly kind: 'point' | 'spot' | 'rect-area';
11
12
  readonly position: Vec3;
12
13
  readonly range: number;
13
14
  /** Whether the shadow producer admitted this local light to its atlas. */
@@ -17,7 +18,7 @@ export interface StandardLocalLightInput {
17
18
  * it on the POD frame prevents Forward/Deferred from rebuilding a second
18
19
  * light list while leaving hand-authored unit fixtures source-free.
19
20
  */
20
- readonly source?: PointLightSnapshot | SpotLightSnapshot;
21
+ readonly source?: PointLightSnapshot | SpotLightSnapshot | RectAreaDirectLightSnapshot;
21
22
  }
22
23
 
23
24
  export interface StandardLightFrame {
@@ -2,7 +2,7 @@ import { err, ok, type Result } from '@forgeax/engine-types';
2
2
  import type { PreparedStandardLighting } from './prepare';
3
3
  import type { StandardClusterTransport } from './transport';
4
4
 
5
- /** A prepared frame with no local lights on a non-storage direct backend. */
5
+ /** A prepared frame with no local lights on a non-storage backend. */
6
6
  export interface StandardNoLocalTopologyInput {
7
7
  readonly kind: 'no-local-lights';
8
8
  readonly prepared: PreparedStandardLighting;
@@ -1,5 +1,6 @@
1
1
  import { err, ok, type Result } from '@forgeax/engine-types';
2
2
  import { type RenderError, StandardClusterTransportUnavailableError } from '../../errors/render';
3
+ import { BYTES_PER_DIRECT_LIGHT_SLOT } from '../../light-buffer-layout';
3
4
  import type { PreparedStandardLighting } from './prepare';
4
5
 
5
6
  export interface StandardClusterCapabilities {
@@ -71,7 +72,7 @@ export function selectStandardClusterTransport(
71
72
  occupiedClusterCount: occupiedClusterCount(prepared),
72
73
  clusterGridBytes: prepared.layout.clusterGridU32Length * Uint32Array.BYTES_PER_ELEMENT,
73
74
  lightIndexListBytes: prepared.membershipEntryCount * Uint32Array.BYTES_PER_ELEMENT,
74
- lightDataBytes: requested * 64,
75
+ lightDataBytes: requested * BYTES_PER_DIRECT_LIGHT_SLOT,
75
76
  lightBoundsBytes: prepared.lightBounds.byteLength,
76
77
  });
77
78
  }
@@ -312,6 +312,7 @@ function buildStandardDeferredLane(
312
312
  ...(gpuDriven.value === undefined ? {} : { gpuDriven: gpuDriven.value }),
313
313
  passKind: 'forward',
314
314
  ...(transmissionActive ? { recordMode: 'opaque' as const } : {}),
315
+ ...(context.occlusion === undefined ? {} : { occlusion: context.occlusion }),
315
316
  extraAccesses: forwardExtraAccesses,
316
317
  });
317
318
  if (!forward.ok) return forward;
@@ -403,6 +404,7 @@ function buildStandardDeferredLane(
403
404
  depthSample.value,
404
405
  shadows.value.directional?.view,
405
406
  shadows.value.spot.view,
407
+ buffers.value,
406
408
  );
407
409
  if (!volume.ok) return volume;
408
410
  }
@@ -122,6 +122,8 @@ export interface PipelineBuilderContext {
122
122
  * tonemap field (M9 keeps the LDR shape; HDR variants are OOS-M9).
123
123
  */
124
124
  readonly colorFormat: GPUTextureFormat;
125
+ /** Additional color targets for an MRT pass; the first target is colorFormat. */
126
+ readonly colorFormats?: readonly GPUTextureFormat[] | undefined;
125
127
  /** Depth attachment format, or `undefined` for a color-only pass. */
126
128
  readonly depthFormat: GPUTextureFormat | undefined;
127
129
  /**
@@ -324,7 +326,8 @@ export function buildPipelineForMaterialShader(
324
326
  // layout. The skinned variant additionally reads JOINTS_0 / WEIGHTS_0;
325
327
  // both layouts come from the same geometry projection used by the main
326
328
  // pass, so shadow rendering cannot silently reinterpret an 18F stream.
327
- if (colorFormatsForPassKind(passKind, ctx.colorFormat).length === 0) {
329
+ const attachmentFormats = ctx.colorFormats ?? colorFormatsForPassKind(passKind, ctx.colorFormat);
330
+ if (attachmentFormats.length === 0) {
328
331
  // bug-20260619-csm RC-3 (AC-10): a material may supply a custom
329
332
  // ShadowCaster pass shader that carries a fragment stage (e.g. an
330
333
  // alpha-test cutout that calls `discard` and returns
@@ -399,7 +402,7 @@ export function buildPipelineForMaterialShader(
399
402
  const forwardSpec: PipelineSpec = {
400
403
  shader: { id, passKind, variantSet: undefined },
401
404
  attachments: {
402
- colorFormats: colorFormatsForPassKind(passKind, ctx.colorFormat),
405
+ colorFormats: attachmentFormats,
403
406
  depthFormat: ctx.depthFormat,
404
407
  sampleCount: (sampleCount === 4 ? 4 : 1) as 1 | 4,
405
408
  },
@@ -319,12 +319,23 @@ export function standardTopologyVariantSet(
319
319
  topology: { readonly kind: 'no-local-lights' | 'clustered' } | undefined,
320
320
  storageBuffer: boolean,
321
321
  vertexColorAvailable: boolean,
322
+ probeBlendAvailable = false,
322
323
  ): string {
323
- return standardCapabilityVariantSet(
324
+ const capabilityVariantSet = standardCapabilityVariantSet(
324
325
  topology?.kind === 'clustered',
325
326
  storageBuffer,
326
327
  vertexColorAvailable,
327
328
  );
329
+ if (!probeBlendAvailable || !storageBuffer) return capabilityVariantSet;
330
+ // The canonical empty key cannot carry the clustered=true fact through
331
+ // resolveMaterialShaderVariantSet (an omitted cluster axis means false).
332
+ // Expand that base before adding the object-level Probe ABI axis so shader,
333
+ // group(3) bind group, and pipeline layout remain one exact variant.
334
+ const explicitCapabilityVariantSet =
335
+ capabilityVariantSet === ''
336
+ ? 'CLUSTER_FORWARD_AVAILABLE=true+STORAGE_BUFFER_AVAILABLE=true'
337
+ : capabilityVariantSet;
338
+ return `${explicitCapabilityVariantSet}+PROBE_BLEND_AVAILABLE=true`;
328
339
  }
329
340
 
330
341
  /**
package/src/plugin.ts CHANGED
@@ -5,6 +5,7 @@ import { Camera } from './components/camera';
5
5
  import { DirectionalLight } from './components/directional-light';
6
6
  import { Instances } from './components/instances';
7
7
  import { Layer } from './components/layer';
8
+ import { LightProbe } from './components/light-probe';
8
9
  import { Lines } from './components/lines';
9
10
  import { MeshFilter } from './components/mesh-filter';
10
11
  import { MeshRenderer } from './components/mesh-renderer';
@@ -12,6 +13,7 @@ import { PointLight } from './components/point-light';
12
13
  import { PointLightShadow } from './components/point-light-shadow';
13
14
  import { Points } from './components/points';
14
15
  import { PostProcessParams } from './components/post-process-params';
16
+ import { RectAreaLight } from './components/rect-area-light';
15
17
  import { SceneInstance } from './components/scene-instance';
16
18
  import { SkyboxBackground } from './components/skybox-background';
17
19
  import { Skylight } from './components/skylight';
@@ -30,6 +32,7 @@ const RENDER_COMPONENTS: readonly Component[] = [
30
32
  DirectionalLight,
31
33
  Instances,
32
34
  Layer,
35
+ LightProbe,
33
36
  MeshFilter,
34
37
  MeshRenderer,
35
38
  PointLight,
@@ -37,6 +40,7 @@ const RENDER_COMPONENTS: readonly Component[] = [
37
40
  Points,
38
41
  Lines,
39
42
  PostProcessParams,
43
+ RectAreaLight,
40
44
  SceneInstance,
41
45
  SkyboxBackground,
42
46
  Skylight,
@@ -0,0 +1,56 @@
1
+ import type { RhiCaps, RhiDevice } from '@forgeax/engine-rhi';
2
+ import { describe, expect, it } from 'vitest';
3
+ import { deriveExtendedLightingCapability } from '../extended-lighting/resources';
4
+
5
+ const device = {
6
+ caps: {
7
+ backendKind: 'null',
8
+ compute: true,
9
+ storageBuffer: true,
10
+ storageTexture: true,
11
+ rgba16floatRenderable: true,
12
+ samplerAliasing: true,
13
+ } as RhiCaps,
14
+ limits: {
15
+ maxSampledTexturesPerShaderStage: 20,
16
+ maxTextureDimension2D: 4096,
17
+ maxTextureArrayLayers: 32,
18
+ maxUniformBuffersPerShaderStage: 12,
19
+ },
20
+ } as unknown as RhiDevice;
21
+
22
+ describe('extended lighting capability admission', () => {
23
+ it('derives admission from real format, filter, array, and uniform facts', () => {
24
+ expect(deriveExtendedLightingCapability(device)).toEqual({
25
+ admitted: true,
26
+ topology: 'extendedLighting',
27
+ reason: undefined,
28
+ });
29
+ });
30
+
31
+ it('rejects a device whose array or uniform limit cannot hold the contract', () => {
32
+ const limited = {
33
+ ...device,
34
+ limits: { ...device.limits, maxTextureArrayLayers: 16, maxUniformBuffersPerShaderStage: 0 },
35
+ } as RhiDevice;
36
+
37
+ expect(deriveExtendedLightingCapability(limited)).toMatchObject({
38
+ admitted: false,
39
+ topology: 'extendedLighting',
40
+ reason: expect.stringContaining('maxTextureArrayLayers'),
41
+ });
42
+ });
43
+
44
+ it('rejects the topology when the sampled-texture limit is below the shader contract', () => {
45
+ const limited = {
46
+ ...device,
47
+ limits: { ...device.limits, maxSampledTexturesPerShaderStage: 16 },
48
+ } as RhiDevice;
49
+
50
+ expect(deriveExtendedLightingCapability(limited)).toEqual({
51
+ admitted: false,
52
+ topology: 'extendedLighting',
53
+ reason: 'maxSampledTexturesPerShaderStage 16 < 20',
54
+ });
55
+ });
56
+ });