@forgeax/engine-render 0.1.20 → 0.1.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (645) hide show
  1. package/README.md +365 -29
  2. package/dist/assembly/backend-contract.d.ts +13 -0
  3. package/dist/assembly/backend-contract.d.ts.map +1 -1
  4. package/dist/assembly/device-feature-admission.d.ts +18 -0
  5. package/dist/assembly/device-feature-admission.d.ts.map +1 -0
  6. package/dist/assembly/directional-shadow-inspection.d.ts +38 -0
  7. package/dist/assembly/directional-shadow-inspection.d.ts.map +1 -0
  8. package/dist/assembly/factory.d.ts +30 -19
  9. package/dist/assembly/factory.d.ts.map +1 -1
  10. package/dist/assembly/host-contract.d.ts +19 -0
  11. package/dist/assembly/host-contract.d.ts.map +1 -1
  12. package/dist/assembly/render-target-host.d.ts +46 -0
  13. package/dist/assembly/render-target-host.d.ts.map +1 -0
  14. package/dist/assets/render-asset-port.d.ts.map +1 -1
  15. package/dist/authoring.mjs +4 -1
  16. package/dist/authoring.mjs.map +1 -1
  17. package/dist/capture/cube-views.d.ts +21 -0
  18. package/dist/capture/cube-views.d.ts.map +1 -0
  19. package/dist/capture/scheduler.d.ts +54 -0
  20. package/dist/capture/scheduler.d.ts.map +1 -0
  21. package/dist/{chunk-PCHBPYML.mjs → chunk-4L3P6DT5.mjs} +2 -2
  22. package/dist/chunk-4L3P6DT5.mjs.map +1 -0
  23. package/dist/{chunk-VFPLTKCU.mjs → chunk-4NPUASZR.mjs} +15 -4
  24. package/dist/chunk-4NPUASZR.mjs.map +1 -0
  25. package/dist/chunk-DIYW3M5D.mjs +5085 -0
  26. package/dist/chunk-DIYW3M5D.mjs.map +1 -0
  27. package/dist/chunk-FBC4R44J.mjs +1003 -0
  28. package/dist/chunk-FBC4R44J.mjs.map +1 -0
  29. package/dist/{chunk-CDTWTHZR.mjs → chunk-FF7K3FSO.mjs} +474 -168
  30. package/dist/chunk-FF7K3FSO.mjs.map +1 -0
  31. package/dist/chunk-HZUPSYW7.mjs +1104 -0
  32. package/dist/chunk-HZUPSYW7.mjs.map +1 -0
  33. package/dist/{chunk-TBQJOK2L.mjs → chunk-QODA5UKU.mjs} +321 -269
  34. package/dist/chunk-QODA5UKU.mjs.map +1 -0
  35. package/dist/chunk-YALPS44A.mjs +296 -0
  36. package/dist/chunk-YALPS44A.mjs.map +1 -0
  37. package/dist/{chunk-4XINDLAY.mjs → chunk-ZUWKAZ2I.mjs} +1963 -2178
  38. package/dist/chunk-ZUWKAZ2I.mjs.map +1 -0
  39. package/dist/cluster-binner.d.ts +5 -0
  40. package/dist/cluster-binner.d.ts.map +1 -1
  41. package/dist/components/camera.d.ts +1 -0
  42. package/dist/components/camera.d.ts.map +1 -1
  43. package/dist/components/cube-camera.d.ts +20 -0
  44. package/dist/components/cube-camera.d.ts.map +1 -0
  45. package/dist/components/directional-light.d.ts +11 -2
  46. package/dist/components/directional-light.d.ts.map +1 -1
  47. package/dist/components/directional-shadow-filter.d.ts +25 -0
  48. package/dist/components/directional-shadow-filter.d.ts.map +1 -0
  49. package/dist/components/index.d.ts +5 -0
  50. package/dist/components/index.d.ts.map +1 -1
  51. package/dist/components/light-helpers.d.ts +4 -0
  52. package/dist/components/light-helpers.d.ts.map +1 -1
  53. package/dist/components/light-probe.d.ts +9 -0
  54. package/dist/components/light-probe.d.ts.map +1 -0
  55. package/dist/components/rect-area-light.d.ts +12 -0
  56. package/dist/components/rect-area-light.d.ts.map +1 -0
  57. package/dist/components/reflection-probe.d.ts +17 -0
  58. package/dist/components/reflection-probe.d.ts.map +1 -0
  59. package/dist/components/scene-instance.d.ts +4 -0
  60. package/dist/components/scene-instance.d.ts.map +1 -1
  61. package/dist/components/spot-light.d.ts +3 -0
  62. package/dist/components/spot-light.d.ts.map +1 -1
  63. package/dist/construct-renderer.mjs +14836 -9620
  64. package/dist/construct-renderer.mjs.map +1 -1
  65. package/dist/device/gpu-residency.d.ts +8 -1
  66. package/dist/device/gpu-residency.d.ts.map +1 -1
  67. package/dist/device/lifecycle-transaction.d.ts +2 -0
  68. package/dist/device/lifecycle-transaction.d.ts.map +1 -0
  69. package/dist/environment/frame.d.ts.map +1 -1
  70. package/dist/environment/lifecycle.d.ts +5 -0
  71. package/dist/environment/lifecycle.d.ts.map +1 -1
  72. package/dist/errors/gpu-driven.d.ts +26 -0
  73. package/dist/errors/gpu-driven.d.ts.map +1 -0
  74. package/dist/errors/index.d.ts +1 -0
  75. package/dist/errors/index.d.ts.map +1 -1
  76. package/dist/errors/recover.d.ts +9 -1
  77. package/dist/errors/recover.d.ts.map +1 -1
  78. package/dist/errors/render.d.ts +166 -93
  79. package/dist/errors/render.d.ts.map +1 -1
  80. package/dist/extract/directional-shadow-projection.d.ts +23 -0
  81. package/dist/extract/directional-shadow-projection.d.ts.map +1 -0
  82. package/dist/extract/environment.d.ts +14 -0
  83. package/dist/extract/environment.d.ts.map +1 -1
  84. package/dist/extract/gpu-driven.d.ts +50 -0
  85. package/dist/extract/gpu-driven.d.ts.map +1 -0
  86. package/dist/extract/visibility.d.ts.map +1 -1
  87. package/dist/features/fullscreen.d.ts.map +1 -1
  88. package/dist/features/targets.d.ts.map +1 -1
  89. package/dist/features/types.d.ts +10 -0
  90. package/dist/features/types.d.ts.map +1 -1
  91. package/dist/fullscreen-post-process-pass.d.ts +13 -2
  92. package/dist/fullscreen-post-process-pass.d.ts.map +1 -1
  93. package/dist/gpu-driven/batch-topology.d.ts +34 -0
  94. package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
  95. package/dist/gpu-driven/prepared-draw.d.ts +63 -0
  96. package/dist/gpu-driven/prepared-draw.d.ts.map +1 -0
  97. package/dist/gpu-driven/production-raster.d.ts +97 -2
  98. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  99. package/dist/gpu-driven/view-gpu.d.ts +64 -1
  100. package/dist/gpu-driven/view-gpu.d.ts.map +1 -1
  101. package/dist/gpu-scene-schema.d.ts +14 -0
  102. package/dist/gpu-scene-schema.d.ts.map +1 -1
  103. package/dist/gpu-scene.d.ts +8 -1
  104. package/dist/gpu-scene.d.ts.map +1 -1
  105. package/dist/gpu-usage.d.ts +0 -1
  106. package/dist/gpu-usage.d.ts.map +1 -1
  107. package/dist/hdrp-buffers.d.ts +21 -19
  108. package/dist/hdrp-buffers.d.ts.map +1 -1
  109. package/dist/ibl/IblPipelineCache.d.ts +4 -0
  110. package/dist/ibl/IblPipelineCache.d.ts.map +1 -1
  111. package/dist/ibl/kernel-cache.d.ts +36 -0
  112. package/dist/ibl/kernel-cache.d.ts.map +1 -0
  113. package/dist/index.d.ts +39 -3
  114. package/dist/index.d.ts.map +1 -1
  115. package/dist/index.mjs +91 -6
  116. package/dist/index.mjs.map +1 -1
  117. package/dist/inspection-types.d.ts +214 -1
  118. package/dist/inspection-types.d.ts.map +1 -1
  119. package/dist/instances-derived-bounds.d.ts +50 -0
  120. package/dist/instances-derived-bounds.d.ts.map +1 -0
  121. package/dist/internal.d.ts +9 -0
  122. package/dist/internal.d.ts.map +1 -1
  123. package/dist/internal.mjs +23 -2
  124. package/dist/internal.mjs.map +1 -1
  125. package/dist/light-buffer-layout.d.ts +58 -140
  126. package/dist/light-buffer-layout.d.ts.map +1 -1
  127. package/dist/materials.d.ts.map +1 -1
  128. package/dist/mesh-material-bindings.d.ts +54 -0
  129. package/dist/mesh-material-bindings.d.ts.map +1 -1
  130. package/dist/pbr-pipeline.d.ts +38 -14
  131. package/dist/pbr-pipeline.d.ts.map +1 -1
  132. package/dist/pipeline/standard-forward-lane.d.ts +2 -1
  133. package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
  134. package/dist/pipeline/standard-lighting/errors.d.ts +4 -0
  135. package/dist/pipeline/standard-lighting/errors.d.ts.map +1 -0
  136. package/dist/pipeline/standard-lighting/graph.d.ts +24 -0
  137. package/dist/pipeline/standard-lighting/graph.d.ts.map +1 -0
  138. package/dist/pipeline/standard-lighting/inspection.d.ts +38 -0
  139. package/dist/pipeline/standard-lighting/inspection.d.ts.map +1 -0
  140. package/dist/pipeline/standard-lighting/layout.d.ts +28 -0
  141. package/dist/pipeline/standard-lighting/layout.d.ts.map +1 -0
  142. package/dist/pipeline/standard-lighting/light-frame.d.ts +33 -0
  143. package/dist/pipeline/standard-lighting/light-frame.d.ts.map +1 -0
  144. package/dist/pipeline/standard-lighting/membership.d.ts +14 -0
  145. package/dist/pipeline/standard-lighting/membership.d.ts.map +1 -0
  146. package/dist/pipeline/standard-lighting/prepare.d.ts +16 -0
  147. package/dist/pipeline/standard-lighting/prepare.d.ts.map +1 -0
  148. package/dist/pipeline/standard-lighting/topology.d.ts +45 -0
  149. package/dist/pipeline/standard-lighting/topology.d.ts.map +1 -0
  150. package/dist/pipeline/standard-lighting/transport.d.ts +36 -0
  151. package/dist/pipeline/standard-lighting/transport.d.ts.map +1 -0
  152. package/dist/pipeline/standard-pipeline.d.ts +4 -13
  153. package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
  154. package/dist/pipeline/standard-post.d.ts.map +1 -1
  155. package/dist/pipeline/standard-profile.d.ts +5 -26
  156. package/dist/pipeline/standard-profile.d.ts.map +1 -1
  157. package/dist/pipeline-builder.d.ts +2 -0
  158. package/dist/pipeline-builder.d.ts.map +1 -1
  159. package/dist/pipeline-spec.d.ts +40 -7
  160. package/dist/pipeline-spec.d.ts.map +1 -1
  161. package/dist/plugin.d.ts.map +1 -1
  162. package/dist/point-shadow-inspection.d.ts +21 -0
  163. package/dist/point-shadow-inspection.d.ts.map +1 -0
  164. package/dist/prepare/extended-lighting/ltc-resources.d.ts +31 -0
  165. package/dist/prepare/extended-lighting/ltc-resources.d.ts.map +1 -0
  166. package/dist/prepare/extended-lighting/resources.d.ts +72 -0
  167. package/dist/prepare/extended-lighting/resources.d.ts.map +1 -0
  168. package/dist/prepare/extended-lighting/spot-modifiers.d.ts +42 -0
  169. package/dist/prepare/extended-lighting/spot-modifiers.d.ts.map +1 -0
  170. package/dist/prepare/extended-lighting/state.d.ts +24 -0
  171. package/dist/prepare/extended-lighting/state.d.ts.map +1 -0
  172. package/dist/record/frame-lighting.d.ts +33 -36
  173. package/dist/record/frame-lighting.d.ts.map +1 -1
  174. package/dist/record/frame-snapshot.d.ts +78 -24
  175. package/dist/record/frame-snapshot.d.ts.map +1 -1
  176. package/dist/record/frame-targets.d.ts +5 -0
  177. package/dist/record/frame-targets.d.ts.map +1 -1
  178. package/dist/record/frame.d.ts +16 -6
  179. package/dist/record/frame.d.ts.map +1 -1
  180. package/dist/record/gpu-pass-timing/contract.d.ts +96 -0
  181. package/dist/record/gpu-pass-timing/contract.d.ts.map +1 -0
  182. package/dist/record/gpu-pass-timing/errors.d.ts +18 -0
  183. package/dist/record/gpu-pass-timing/errors.d.ts.map +1 -0
  184. package/dist/record/gpu-pass-timing/index.d.ts +8 -0
  185. package/dist/record/gpu-pass-timing/index.d.ts.map +1 -0
  186. package/dist/record/gpu-pass-timing/parser.d.ts +11 -0
  187. package/dist/record/gpu-pass-timing/parser.d.ts.map +1 -0
  188. package/dist/record/gpu-pass-timing/session.d.ts +43 -0
  189. package/dist/record/gpu-pass-timing/session.d.ts.map +1 -0
  190. package/dist/record/gpu-timing.d.ts +20 -3
  191. package/dist/record/gpu-timing.d.ts.map +1 -1
  192. package/dist/record/helpers.d.ts +0 -20
  193. package/dist/record/helpers.d.ts.map +1 -1
  194. package/dist/record/main-pass-geometry.d.ts +6 -1
  195. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  196. package/dist/record/main-pass-material.d.ts +25 -2
  197. package/dist/record/main-pass-material.d.ts.map +1 -1
  198. package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
  199. package/dist/record/main-pass.d.ts +2 -0
  200. package/dist/record/main-pass.d.ts.map +1 -1
  201. package/dist/record/mesh-ssbo.d.ts.map +1 -1
  202. package/dist/record/render-context.d.ts +98 -6
  203. package/dist/record/render-context.d.ts.map +1 -1
  204. package/dist/record/shadow-pass.d.ts.map +1 -1
  205. package/dist/record/skybox-post-pass.d.ts.map +1 -1
  206. package/dist/record/typed-frame-graph.d.ts +119 -4
  207. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  208. package/dist/record/view-ubo.d.ts +7 -5
  209. package/dist/record/view-ubo.d.ts.map +1 -1
  210. package/dist/reflection/filter.d.ts +22 -0
  211. package/dist/reflection/filter.d.ts.map +1 -0
  212. package/dist/reflection/gpu-table.d.ts +25 -0
  213. package/dist/reflection/gpu-table.d.ts.map +1 -0
  214. package/dist/reflection/inspection.d.ts +10 -0
  215. package/dist/reflection/inspection.d.ts.map +1 -0
  216. package/dist/reflection/projection.d.ts +140 -0
  217. package/dist/reflection/projection.d.ts.map +1 -0
  218. package/dist/reflection/record-owner.d.ts +60 -0
  219. package/dist/reflection/record-owner.d.ts.map +1 -0
  220. package/dist/render-contract.d.ts +62 -14
  221. package/dist/render-contract.d.ts.map +1 -1
  222. package/dist/render-graph-primitives.d.ts +1 -0
  223. package/dist/render-graph-primitives.d.ts.map +1 -1
  224. package/dist/render-pipeline.d.ts +49 -2
  225. package/dist/render-pipeline.d.ts.map +1 -1
  226. package/dist/render-system-extract.d.ts +160 -31
  227. package/dist/render-system-extract.d.ts.map +1 -1
  228. package/dist/render-system.d.ts +28 -2
  229. package/dist/render-system.d.ts.map +1 -1
  230. package/dist/renderer.d.ts +43 -0
  231. package/dist/renderer.d.ts.map +1 -0
  232. package/dist/scene/probe-blend-record.d.ts +22 -0
  233. package/dist/scene/probe-blend-record.d.ts.map +1 -0
  234. package/dist/scene/probe-blend.d.ts +142 -0
  235. package/dist/scene/probe-blend.d.ts.map +1 -0
  236. package/dist/scene/render-scene-types.d.ts +0 -6
  237. package/dist/scene/render-scene-types.d.ts.map +1 -1
  238. package/dist/scene/render-scene.d.ts +117 -9
  239. package/dist/scene/render-scene.d.ts.map +1 -1
  240. package/dist/scene/visibility/bounds.d.ts +20 -0
  241. package/dist/scene/visibility/bounds.d.ts.map +1 -0
  242. package/dist/scene/visibility/budget.d.ts +16 -0
  243. package/dist/scene/visibility/budget.d.ts.map +1 -0
  244. package/dist/scene/visibility/facet.d.ts +73 -0
  245. package/dist/scene/visibility/facet.d.ts.map +1 -0
  246. package/dist/scene/visibility/gpu-frame-timing.d.ts +41 -0
  247. package/dist/scene/visibility/gpu-frame-timing.d.ts.map +1 -0
  248. package/dist/scene/visibility/gpu-lod.d.ts +46 -0
  249. package/dist/scene/visibility/gpu-lod.d.ts.map +1 -0
  250. package/dist/scene/visibility/inspection.d.ts +132 -0
  251. package/dist/scene/visibility/inspection.d.ts.map +1 -0
  252. package/dist/scene/visibility/lod-selector.d.ts +25 -0
  253. package/dist/scene/visibility/lod-selector.d.ts.map +1 -0
  254. package/dist/scene/visibility/occlusion-confidence.d.ts +81 -0
  255. package/dist/scene/visibility/occlusion-confidence.d.ts.map +1 -0
  256. package/dist/scene/visibility/occlusion-pass.d.ts +57 -0
  257. package/dist/scene/visibility/occlusion-pass.d.ts.map +1 -0
  258. package/dist/scene/visibility/occlusion-query-pool.d.ts +57 -0
  259. package/dist/scene/visibility/occlusion-query-pool.d.ts.map +1 -0
  260. package/dist/scene/visibility/occlusion-runtime.d.ts +96 -0
  261. package/dist/scene/visibility/occlusion-runtime.d.ts.map +1 -0
  262. package/dist/scene/visibility/types.d.ts +34 -0
  263. package/dist/scene/visibility/types.d.ts.map +1 -0
  264. package/dist/shadow-atlas.d.ts +3 -3
  265. package/dist/shadow-atlas.d.ts.map +1 -1
  266. package/dist/ssao-buffers.d.ts +3 -0
  267. package/dist/ssao-buffers.d.ts.map +1 -1
  268. package/dist/ssr/admission.d.ts +122 -0
  269. package/dist/ssr/admission.d.ts.map +1 -0
  270. package/dist/ssr/composition.d.ts +32 -0
  271. package/dist/ssr/composition.d.ts.map +1 -0
  272. package/dist/ssr/errors.d.ts +39 -0
  273. package/dist/ssr/errors.d.ts.map +1 -0
  274. package/dist/ssr/identity.d.ts +13 -0
  275. package/dist/ssr/identity.d.ts.map +1 -0
  276. package/dist/systems/skin-palette-allocator.d.ts +1 -1
  277. package/dist/targets/contracts.d.ts +66 -0
  278. package/dist/targets/contracts.d.ts.map +1 -0
  279. package/dist/targets/graph-projection.d.ts +29 -0
  280. package/dist/targets/graph-projection.d.ts.map +1 -0
  281. package/dist/targets/material-source.d.ts +34 -0
  282. package/dist/targets/material-source.d.ts.map +1 -0
  283. package/dist/targets/owner.d.ts +32 -0
  284. package/dist/targets/owner.d.ts.map +1 -0
  285. package/dist/targets/physical.d.ts +18 -0
  286. package/dist/targets/physical.d.ts.map +1 -0
  287. package/dist/targets/readback.d.ts +39 -0
  288. package/dist/targets/readback.d.ts.map +1 -0
  289. package/dist/temporal/frame-coordinator.d.ts +13 -0
  290. package/dist/temporal/frame-coordinator.d.ts.map +1 -1
  291. package/dist/temporal/frame.d.ts +2 -0
  292. package/dist/temporal/frame.d.ts.map +1 -1
  293. package/dist/temporal/index.mjs +6 -5
  294. package/dist/temporal/view.d.ts +7 -0
  295. package/dist/temporal/view.d.ts.map +1 -1
  296. package/dist/tilemap-chunk-extract-system.d.ts +1 -1
  297. package/dist/typed-render-graph-primitives.d.ts +5 -1
  298. package/dist/typed-render-graph-primitives.d.ts.map +1 -1
  299. package/dist/typed-shadow-passes.d.ts +1 -1
  300. package/dist/typed-shadow-passes.d.ts.map +1 -1
  301. package/dist/volume/passes.d.ts +9 -5
  302. package/dist/volume/passes.d.ts.map +1 -1
  303. package/package.json +20 -20
  304. package/src/__tests__/bloom-exact-zero.unit.test.ts +48 -19
  305. package/src/__tests__/bloom-lifecycle.integration.test.ts +41 -13
  306. package/src/__tests__/camera-target-consumption.unit.test.ts +53 -0
  307. package/src/__tests__/cluster-binner.unit.test.ts +35 -0
  308. package/src/__tests__/cluster-grid-default-surface.unit.test.ts +12 -2
  309. package/src/__tests__/cube-camera-scheduler.unit.test.ts +70 -0
  310. package/src/__tests__/cube-camera-single-submit.perf.test.ts +75 -0
  311. package/src/__tests__/cube-camera-view-matrices.unit.test.ts +41 -0
  312. package/src/__tests__/cube-camera-vocabulary.unit.test.ts +47 -0
  313. package/src/__tests__/cube-camera.integration.test.ts +42 -0
  314. package/src/__tests__/detached-inspection.integration.test.ts +6 -0
  315. package/src/__tests__/device-feature-admission.unit.test.ts +85 -0
  316. package/src/__tests__/direct-light-snapshot.unit.test.ts +62 -6
  317. package/src/__tests__/docs-gate-green.unit.test.ts +38 -0
  318. package/src/__tests__/environment-generation.integration.test.ts +16 -0
  319. package/src/__tests__/evidence-identity.ts +13 -0
  320. package/src/__tests__/evidence-identity.unit.test.ts +24 -0
  321. package/src/__tests__/extended-lighting-recovery.browser.test.ts +14 -0
  322. package/src/__tests__/extended-lighting-recovery.dawn.test.ts +14 -0
  323. package/src/__tests__/extended-lighting-recovery.integration.test.ts +81 -0
  324. package/src/__tests__/extended-lighting-resources.unit.test.ts +51 -0
  325. package/src/__tests__/extended-lighting-zero-feature.integration.test.ts +52 -0
  326. package/src/__tests__/factory-contract.integration.test.ts +288 -29
  327. package/src/__tests__/fog-component.unit.test.ts +67 -0
  328. package/src/__tests__/fog.integration.test.ts +86 -0
  329. package/src/__tests__/frame-observation.contract.test.ts +15 -0
  330. package/src/__tests__/fullscreen-feature-plan.unit.test.ts +37 -0
  331. package/src/__tests__/gpu-driven-batch-topology-pbr.unit.test.ts +146 -0
  332. package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +10 -8
  333. package/src/__tests__/gpu-driven-indirect-raster.dawn.test.ts +18 -1
  334. package/src/__tests__/gpu-driven-production.integration.test.ts +579 -17
  335. package/src/__tests__/gpu-driven-scaling.unit.test.ts +66 -2
  336. package/src/__tests__/gpu-driven-view-gpu-evidence.ts +26 -1
  337. package/src/__tests__/gpu-driven-view-graph.integration.test.ts +101 -6
  338. package/src/__tests__/gpu-driven-view.browser.test.ts +3 -3
  339. package/src/__tests__/gpu-driven-view.dawn.test.ts +3 -3
  340. package/src/__tests__/gpu-driven-view.unit.test.ts +50 -0
  341. package/src/__tests__/gpu-lod-evidence.ts +188 -0
  342. package/src/__tests__/gpu-lod-fallback.integration.test.ts +10 -0
  343. package/src/__tests__/gpu-lod-indirect.integration.test.ts +40 -0
  344. package/src/__tests__/gpu-lod-parity.dawn.test.ts +29 -0
  345. package/src/__tests__/gpu-lod-readiness.integration.test.ts +52 -0
  346. package/src/__tests__/gpu-lod-schema.unit.test.ts +66 -0
  347. package/src/__tests__/gpu-lod-selector.unit.test.ts +28 -0
  348. package/src/__tests__/gpu-lod-silhouette.browser.test.ts +13 -0
  349. package/src/__tests__/gpu-pass-timing-browser-runner.ts +281 -0
  350. package/src/__tests__/gpu-pass-timing-coexistence.integration.test.ts +80 -0
  351. package/src/__tests__/gpu-pass-timing-contract.unit.test.ts +243 -0
  352. package/src/__tests__/gpu-pass-timing-exact-zero.unit.test.ts +131 -0
  353. package/src/__tests__/gpu-pass-timing-lifecycle.integration.test.ts +230 -0
  354. package/src/__tests__/gpu-pass-timing-observation.unit.test.ts +233 -0
  355. package/src/__tests__/gpu-pass-timing-session.unit.test.ts +438 -0
  356. package/src/__tests__/gpu-pass-timing.browser.test.ts +26 -0
  357. package/src/__tests__/gpu-pass-timing.rhi-null.unit.test.ts +125 -0
  358. package/src/__tests__/gpu-resource-store-stride.unit.test.ts +66 -1
  359. package/src/__tests__/gpu-scene.dawn.test.ts +8 -1
  360. package/src/__tests__/gpu-scene.unit.test.ts +49 -3
  361. package/src/__tests__/hdrp-group2-visibility.unit.test.ts +24 -0
  362. package/src/__tests__/instances-culling.integration.test.ts +94 -0
  363. package/src/__tests__/instances-derived-bounds.unit.test.ts +118 -0
  364. package/src/__tests__/light-buffer-layout.unit.test.ts +113 -3
  365. package/src/__tests__/light-inspection.unit.test.ts +55 -0
  366. package/src/__tests__/light-resource-error.unit.test.ts +43 -0
  367. package/src/__tests__/lighting-scene-roundtrip.integration.test.ts +109 -0
  368. package/src/__tests__/lod-bounds.unit.test.ts +42 -0
  369. package/src/__tests__/lod-cpu-lane.integration.test.ts +55 -0
  370. package/src/__tests__/lod-cpu-no-change.perf.test.ts +19 -0
  371. package/src/__tests__/lod-gpu-frame-timing.unit.test.ts +49 -0
  372. package/src/__tests__/lod-multi-view.integration.test.ts +63 -0
  373. package/src/__tests__/lod-occlusion-device-loss.integration.test.ts +27 -0
  374. package/src/__tests__/lod-occlusion-final-decision.integration.test.ts +47 -0
  375. package/src/__tests__/lod-occlusion-inspection.integration.test.ts +217 -0
  376. package/src/__tests__/lod-occlusion-inspection.schema.test-d.ts +14 -0
  377. package/src/__tests__/lod-occlusion-inspection.unit.test.ts +72 -0
  378. package/src/__tests__/lod-occlusion-visual-evidence.ts +91 -0
  379. package/src/__tests__/lod-occlusion.browser.test.ts +15 -0
  380. package/src/__tests__/lod-occlusion.dawn.test.ts +20 -0
  381. package/src/__tests__/lod-selector.profile.test.ts +35 -0
  382. package/src/__tests__/lod-selector.unit.test.ts +68 -0
  383. package/src/__tests__/lod-visibility-identity.unit.test.ts +198 -0
  384. package/src/__tests__/lod-visibility-lifecycle.integration.test.ts +132 -0
  385. package/src/__tests__/ltc-resources.unit.test.ts +32 -0
  386. package/src/__tests__/material-contract-inventory.unit.test.ts +17 -0
  387. package/src/__tests__/material-observation.integration.test.ts +101 -0
  388. package/src/__tests__/occlusion-confidence.unit.test.ts +168 -0
  389. package/src/__tests__/occlusion-invalidation.integration.test.ts +37 -0
  390. package/src/__tests__/occlusion-pass-routing.unit.test.ts +16 -0
  391. package/src/__tests__/occlusion-pass.dawn.test.ts +20 -0
  392. package/src/__tests__/occlusion-pass.integration.test.ts +43 -0
  393. package/src/__tests__/occlusion-pass.rhi-null.test.ts +22 -0
  394. package/src/__tests__/occlusion-query-pool.rhi-null.test.ts +21 -0
  395. package/src/__tests__/occlusion-query-pool.unit.test.ts +95 -0
  396. package/src/__tests__/occlusion-query-ticket.integration.test.ts +47 -0
  397. package/src/__tests__/occlusion-query.dawn.test.ts +16 -0
  398. package/src/__tests__/occlusion-query.evidence.ts +178 -0
  399. package/src/__tests__/occlusion-query.rhi-null.test.ts +31 -0
  400. package/src/__tests__/occlusion-runtime.unit.test.ts +120 -0
  401. package/src/__tests__/pbr-view-layout.unit.test.ts +16 -0
  402. package/src/__tests__/point-shadow-inspection.unit.test.ts +60 -0
  403. package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +196 -0
  404. package/src/__tests__/probe-bindings.unit.test.ts +200 -0
  405. package/src/__tests__/probe-blend-numeric.unit.test.ts +67 -0
  406. package/src/__tests__/probe-blend-oracle.unit.test.ts +136 -0
  407. package/src/__tests__/probe-capacity.integration.test.ts +97 -0
  408. package/src/__tests__/probe-recovery.integration.test.ts +104 -0
  409. package/src/__tests__/probe-scene-dirty.integration.test.ts +211 -0
  410. package/src/__tests__/profiler-phase-catalog.test.ts +4 -1
  411. package/src/__tests__/profiler-phase-token.test.ts +1 -0
  412. package/src/__tests__/public-surface.test-d.ts +10 -0
  413. package/src/__tests__/rect-area-boundary.unit.test.ts +24 -0
  414. package/src/__tests__/reflection-fallback-graph.unit.test.ts +67 -0
  415. package/src/__tests__/reflection-fallback-inspection.unit.test.ts +77 -0
  416. package/src/__tests__/reflection-fallback-lifecycle.integration.test.ts +21 -0
  417. package/src/__tests__/reflection-fallback-projection.unit.test.ts +90 -0
  418. package/src/__tests__/reflection-fallback-source.unit.test.ts +32 -0
  419. package/src/__tests__/reflection-probe-authoring.unit.test.ts +71 -0
  420. package/src/__tests__/reflection-probe-filter.unit.test.ts +54 -0
  421. package/src/__tests__/reflection-probe-ibl-topology.unit.test.ts +45 -0
  422. package/src/__tests__/reflection-probe-owner-placement.unit.test.ts +39 -0
  423. package/src/__tests__/reflection-probe-pass-encode.integration.test.ts +101 -0
  424. package/src/__tests__/reflection-probe-performance.perf.test.ts +60 -0
  425. package/src/__tests__/reflection-probe-record-owner.characterization.unit.test.ts +120 -0
  426. package/src/__tests__/reflection-probe-recovery.integration.test.ts +55 -0
  427. package/src/__tests__/reflection-probe-selection.unit.test.ts +46 -0
  428. package/src/__tests__/render-error-code-owner.test-d.ts +19 -3
  429. package/src/__tests__/render-feature-docs.unit.test.ts +12 -7
  430. package/src/__tests__/render-graph-fallback.integration.test.ts +24 -0
  431. package/src/__tests__/render-perf/extended-lighting.bench.test.ts +66 -0
  432. package/src/__tests__/render-scene-projection.unit.test.ts +447 -18
  433. package/src/__tests__/render-target-admission.unit.test.ts +80 -0
  434. package/src/__tests__/render-target-graph-projection.unit.test.ts +102 -0
  435. package/src/__tests__/render-target-inspection.unit.test.ts +52 -0
  436. package/src/__tests__/render-target-material-source.test-d.ts +30 -0
  437. package/src/__tests__/render-target-material-source.unit.test.ts +97 -0
  438. package/src/__tests__/render-target-owner-placement.unit.test.ts +203 -0
  439. package/src/__tests__/render-target-owner.unit.test.ts +122 -0
  440. package/src/__tests__/render-target-probe-soak.perf.test.ts +94 -0
  441. package/src/__tests__/render-target-public-schema.json +107 -0
  442. package/src/__tests__/render-target-public-schema.unit.test.ts +91 -0
  443. package/src/__tests__/render-target-public-surface.test-d.ts +83 -0
  444. package/src/__tests__/render-target-readback.integration.test.ts +90 -0
  445. package/src/__tests__/render-target-recovery.integration.test.ts +85 -0
  446. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +124 -0
  447. package/src/__tests__/renderer-lifecycle.integration.test.ts +14 -0
  448. package/src/__tests__/shader-stage-surface.unit.test.ts +19 -4
  449. package/src/__tests__/skin-motion-regression.unit.test.ts +154 -0
  450. package/src/__tests__/skinned-shadow-caster.test.ts +13 -4
  451. package/src/__tests__/spot-modifier-projection.unit.test.ts +70 -0
  452. package/src/__tests__/ssr-admission-matrix.integration.test.ts +206 -0
  453. package/src/__tests__/ssr-composition.unit.test.ts +46 -0
  454. package/src/__tests__/ssr-fallback-zero-work.bench.test.ts +94 -0
  455. package/src/__tests__/ssr-recovery-errors.unit.test.ts +58 -0
  456. package/src/__tests__/standard-cluster-capability.integration.test.ts +63 -0
  457. package/src/__tests__/standard-cluster-transport.unit.test.ts +121 -0
  458. package/src/__tests__/standard-cluster-variant-resolution.unit.test.ts +299 -0
  459. package/src/__tests__/standard-clustered-pipeline.unit.test.ts +15 -5
  460. package/src/__tests__/standard-lighting-inspection.integration.test.ts +138 -0
  461. package/src/__tests__/standard-lighting-ssot.unit.test.ts +18 -0
  462. package/src/__tests__/standard-lighting-write.unit.test.ts +441 -0
  463. package/src/__tests__/standard-lighting.integration.test.ts +67 -0
  464. package/src/__tests__/standard-pipeline.dawn.test.ts +206 -5
  465. package/src/__tests__/standard-pipeline.integration.test.ts +181 -150
  466. package/src/__tests__/standard-probe-binding.unit.test.ts +25 -0
  467. package/src/__tests__/standard-profile-oracle.unit.test.ts +23 -38
  468. package/src/__tests__/temporal-fallback-generation.integration.test.ts +16 -0
  469. package/src/__tests__/temporal-view.unit.test.ts +3 -3
  470. package/src/__tests__/transmission-prewarm.unit.test.ts +69 -0
  471. package/src/__tests__/typed-pipeline-topology.unit.test.ts +395 -14
  472. package/src/__tests__/urp-pipeline-targets.unit.test.ts +2 -2
  473. package/src/__tests__/visibility-budget.integration.test.ts +140 -0
  474. package/src/__tests__/visibility-budget.unit.test.ts +70 -0
  475. package/src/__tests__/visibility-extract.integration.test.ts +11 -6
  476. package/src/__tests__/visibility-resolver.unit.test.ts +15 -0
  477. package/src/__tests__/volumetric-fog-point-spot-accumulation.integration.test.ts +6 -3
  478. package/src/__tests__/volumetric-fog-stage-readback.dawn.test.ts +31 -28
  479. package/src/assembly/backend-contract.ts +13 -0
  480. package/src/assembly/device-feature-admission.ts +80 -0
  481. package/src/assembly/directional-shadow-inspection.ts +122 -0
  482. package/src/assembly/factory.ts +1411 -366
  483. package/src/assembly/host-contract.ts +35 -0
  484. package/src/assembly/render-target-host.ts +847 -0
  485. package/src/assets/asset-decoders.ts +1 -1
  486. package/src/assets/render-asset-port.ts +7 -1
  487. package/src/capture/cube-views.ts +69 -0
  488. package/src/capture/scheduler.ts +265 -0
  489. package/src/cluster-binner.ts +31 -0
  490. package/src/components/__tests__/lighting-authoring.unit.test.ts +39 -0
  491. package/src/components/__tests__/rect-area-light.unit.test.ts +33 -0
  492. package/src/components/__tests__/schema-contract.unit.test.ts +12 -0
  493. package/src/components/__tests__/schema-parity.unit.test.ts +9 -0
  494. package/src/components/camera.ts +4 -0
  495. package/src/components/cube-camera.ts +52 -0
  496. package/src/components/directional-light.ts +22 -2
  497. package/src/components/directional-shadow-filter.ts +57 -0
  498. package/src/components/index.ts +5 -0
  499. package/src/components/light-helpers.ts +133 -8
  500. package/src/components/light-probe.ts +13 -0
  501. package/src/components/rect-area-light.ts +15 -0
  502. package/src/components/reflection-probe.ts +49 -0
  503. package/src/components/scene-instance.ts +6 -0
  504. package/src/components/spot-light.ts +3 -0
  505. package/src/device/__tests__/extended-lighting-recovery.unit.test.ts +24 -0
  506. package/src/device/gpu-residency.ts +140 -9
  507. package/src/device/lifecycle-transaction.ts +12 -0
  508. package/src/environment/frame.ts +6 -24
  509. package/src/environment/lifecycle.ts +20 -1
  510. package/src/errors/gpu-driven.ts +92 -0
  511. package/src/errors/index.ts +1 -0
  512. package/src/errors/recover.ts +32 -0
  513. package/src/errors/render.ts +341 -143
  514. package/src/extract/camera.ts +3 -3
  515. package/src/extract/directional-shadow-projection.ts +61 -0
  516. package/src/extract/environment.ts +57 -0
  517. package/src/extract/gpu-driven.ts +108 -0
  518. package/src/extract/visibility.ts +2 -1
  519. package/src/features/fullscreen.ts +3 -0
  520. package/src/features/targets.ts +4 -1
  521. package/src/features/types.ts +10 -0
  522. package/src/fullscreen-post-process-pass.ts +43 -2
  523. package/src/gpu-driven/batch-topology.ts +207 -28
  524. package/src/gpu-driven/prepared-draw.ts +208 -0
  525. package/src/gpu-driven/production-raster.ts +953 -43
  526. package/src/gpu-driven/view-gpu.ts +411 -29
  527. package/src/gpu-scene-schema.ts +13 -0
  528. package/src/gpu-scene.ts +140 -64
  529. package/src/gpu-usage.ts +0 -1
  530. package/src/hdrp-buffers.ts +169 -63
  531. package/src/ibl/IblPipelineCache.ts +11 -0
  532. package/src/ibl/kernel-cache.ts +88 -0
  533. package/src/index.ts +222 -0
  534. package/src/inspection-types.ts +276 -1
  535. package/src/instances-derived-bounds.ts +171 -0
  536. package/src/internal.ts +38 -0
  537. package/src/light-buffer-layout.ts +155 -310
  538. package/src/materials.ts +16 -2
  539. package/src/mesh-material-bindings.ts +132 -0
  540. package/src/pbr-pipeline.ts +255 -60
  541. package/src/pipeline/standard-forward-lane.ts +93 -16
  542. package/src/pipeline/standard-lighting/errors.ts +6 -0
  543. package/src/pipeline/standard-lighting/graph.ts +158 -0
  544. package/src/pipeline/standard-lighting/inspection.ts +88 -0
  545. package/src/pipeline/standard-lighting/layout.ts +31 -0
  546. package/src/pipeline/standard-lighting/light-frame.ts +35 -0
  547. package/src/pipeline/standard-lighting/membership.ts +43 -0
  548. package/src/pipeline/standard-lighting/prepare.ts +37 -0
  549. package/src/pipeline/standard-lighting/topology.ts +120 -0
  550. package/src/pipeline/standard-lighting/transport.ts +78 -0
  551. package/src/pipeline/standard-pipeline.ts +124 -193
  552. package/src/pipeline/standard-post.ts +26 -7
  553. package/src/pipeline/standard-profile.ts +17 -47
  554. package/src/pipeline-builder.ts +5 -2
  555. package/src/pipeline-spec.ts +95 -21
  556. package/src/plugin.ts +4 -0
  557. package/src/point-shadow-inspection.ts +55 -0
  558. package/src/prepare/__tests__/extended-lighting-capability.unit.test.ts +56 -0
  559. package/src/prepare/extended-lighting/ltc-resources.ts +136 -0
  560. package/src/prepare/extended-lighting/resources.ts +389 -0
  561. package/src/prepare/extended-lighting/spot-modifiers.ts +194 -0
  562. package/src/prepare/extended-lighting/state.ts +92 -0
  563. package/src/record/__tests__/gpu-timing.unit.test.ts +177 -0
  564. package/src/record/__tests__/temporal-payload.unit.test.ts +64 -1
  565. package/src/record/__tests__/transparent-group2-selection.unit.test.ts +73 -0
  566. package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +20 -1
  567. package/src/record/__tests__/view-ubo-layout.unit.test.ts +40 -0
  568. package/src/record/frame-lighting.ts +591 -452
  569. package/src/record/frame-snapshot.ts +92 -39
  570. package/src/record/frame-targets.ts +17 -0
  571. package/src/record/frame.ts +662 -158
  572. package/src/record/gpu-pass-timing/contract.ts +180 -0
  573. package/src/record/gpu-pass-timing/errors.ts +34 -0
  574. package/src/record/gpu-pass-timing/index.ts +37 -0
  575. package/src/record/gpu-pass-timing/parser.ts +97 -0
  576. package/src/record/gpu-pass-timing/session.ts +938 -0
  577. package/src/record/gpu-timing.ts +112 -39
  578. package/src/record/helpers.ts +0 -58
  579. package/src/record/main-pass-geometry.ts +324 -119
  580. package/src/record/main-pass-material.ts +103 -10
  581. package/src/record/main-pass-sprite-draws.ts +99 -103
  582. package/src/record/main-pass.ts +71 -15
  583. package/src/record/mesh-ssbo.ts +47 -36
  584. package/src/record/render-context.ts +136 -3
  585. package/src/record/shadow-pass.ts +83 -68
  586. package/src/record/skybox-post-pass.ts +20 -4
  587. package/src/record/typed-frame-graph.ts +1034 -43
  588. package/src/record/view-ubo.ts +72 -47
  589. package/src/reflection/filter.ts +81 -0
  590. package/src/reflection/gpu-table.ts +44 -0
  591. package/src/reflection/inspection.ts +67 -0
  592. package/src/reflection/projection.ts +476 -0
  593. package/src/reflection/record-owner.ts +1283 -0
  594. package/src/render-contract.ts +93 -10
  595. package/src/render-graph-primitives.ts +9 -4
  596. package/src/render-pipeline.ts +122 -2
  597. package/src/render-system-extract.ts +1041 -225
  598. package/src/render-system.ts +1310 -85
  599. package/src/renderer.ts +222 -0
  600. package/src/scene/__tests__/changed-block-summary.candidate.test.ts +36 -0
  601. package/src/scene/probe-blend-record.ts +54 -0
  602. package/src/scene/probe-blend.ts +813 -0
  603. package/src/scene/render-scene-types.ts +0 -7
  604. package/src/scene/render-scene.ts +942 -171
  605. package/src/scene/visibility/bounds.ts +57 -0
  606. package/src/scene/visibility/budget.ts +40 -0
  607. package/src/scene/visibility/facet.ts +400 -0
  608. package/src/scene/visibility/gpu-frame-timing.ts +101 -0
  609. package/src/scene/visibility/gpu-lod.ts +119 -0
  610. package/src/scene/visibility/inspection.ts +313 -0
  611. package/src/scene/visibility/lod-selector.ts +84 -0
  612. package/src/scene/visibility/occlusion-confidence.ts +478 -0
  613. package/src/scene/visibility/occlusion-pass.ts +165 -0
  614. package/src/scene/visibility/occlusion-query-pool.ts +178 -0
  615. package/src/scene/visibility/occlusion-runtime.ts +672 -0
  616. package/src/scene/visibility/types.ts +50 -0
  617. package/src/shadow-atlas.ts +5 -5
  618. package/src/ssao-buffers.ts +9 -2
  619. package/src/ssr/admission.ts +451 -0
  620. package/src/ssr/composition.ts +73 -0
  621. package/src/ssr/errors.ts +76 -0
  622. package/src/ssr/identity.ts +12 -0
  623. package/src/systems/skin-palette-allocator.ts +1 -1
  624. package/src/targets/contracts.ts +182 -0
  625. package/src/targets/graph-projection.ts +169 -0
  626. package/src/targets/material-source.ts +143 -0
  627. package/src/targets/owner.ts +214 -0
  628. package/src/targets/physical.ts +18 -0
  629. package/src/targets/readback.ts +153 -0
  630. package/src/temporal/frame-coordinator.ts +26 -0
  631. package/src/temporal/frame.ts +14 -1
  632. package/src/temporal/view.ts +11 -0
  633. package/src/tilemap-chunk-extract-system.ts +4 -4
  634. package/src/transmission/__tests__/standard-transmission.dawn.test.ts +59 -20
  635. package/src/typed-render-graph-primitives.ts +53 -3
  636. package/src/typed-shadow-passes.ts +46 -40
  637. package/src/volume/passes.ts +61 -58
  638. package/dist/.tsbuildinfo +0 -1
  639. package/dist/chunk-4XINDLAY.mjs.map +0 -1
  640. package/dist/chunk-CDTWTHZR.mjs.map +0 -1
  641. package/dist/chunk-M2MP4ZOU.mjs +0 -73
  642. package/dist/chunk-M2MP4ZOU.mjs.map +0 -1
  643. package/dist/chunk-PCHBPYML.mjs.map +0 -1
  644. package/dist/chunk-TBQJOK2L.mjs.map +0 -1
  645. package/dist/chunk-VFPLTKCU.mjs.map +0 -1
@@ -11,7 +11,11 @@ import type {
11
11
  RenderPipelineFrame,
12
12
  RenderPipelineTopology,
13
13
  } from '../render-pipeline';
14
- import { createRenderPipelineTarget, type RenderPipelineTarget } from '../render-pipeline';
14
+ import {
15
+ createRenderPipelineTarget,
16
+ type RenderPipelineTarget,
17
+ resolveOutputDither,
18
+ } from '../render-pipeline';
15
19
  import {
16
20
  addTypedBloomPasses,
17
21
  addTypedCompositePostEffects,
@@ -134,7 +138,7 @@ function missingTemporalContributor(
134
138
  return new SceneDataUnavailableError({
135
139
  featureIdentity: 'forgeax::standard',
136
140
  schema: 'forgeax::scene-data::temporal-v1',
137
- lane: topology.lane.compute ? (topology.standardProfile?.lighting ?? 'direct') : 'cpu-webgl2',
141
+ lane: topology.lane.compute ? 'clustered' : 'cpu-webgl2',
138
142
  reason: 'producer-missing',
139
143
  missingContributorIds: [contributorId],
140
144
  omittedMissingContributorCount: 0,
@@ -165,9 +169,13 @@ export function addStandardPost(
165
169
  // left clear and the following Output Transform presents a black frame.
166
170
  const bloomActive = hdr && topology.camera.bloom === 'on' && topology.camera.tonemap !== 'none';
167
171
  const fxaa = !clearOnly && topology.camera.antialias === 'fxaa';
168
- const msaa = !clearOnly && topology.camera.antialias === 'msaa' && topology.lane.multisample;
169
- const linearLdr = !clearOnly && ((!hdr && topology.lane.storageBuffer) || fxaa || msaa);
172
+ // All non-clear Standard frames use the graph-owned float scene target and
173
+ // the shared Output Transform, even when AA is disabled or the backend has
174
+ // no storage buffers. This keeps the final dither at one owner-level
175
+ // surface boundary instead of making no-AA/WebGL2 a silent bypass.
176
+ const linearLdr = !clearOnly && !hdr;
170
177
  const rawOnly = !clearOnly && topology.surface.profile?.kind === 'raw-only';
178
+ const outputDither = resolveOutputDither(topology.config);
171
179
  const motionBlur = !clearOnly && topology.temporal?.motionBlur === true;
172
180
  const postEffects = clearOnly ? [] : (topology.config?.postEffects ?? []);
173
181
  const rawOnlyPostInput =
@@ -312,7 +320,11 @@ export function addStandardPost(
312
320
  domain: 'display-encoded',
313
321
  });
314
322
  if (!output.ok) return output;
315
- const outputTransform = addTypedOutputTransformPass(context.graph, postInput, output.value);
323
+ // FXAA samples this display-encoded intermediate; leave dither to the
324
+ // final FXAA writer so the same pixel is not dithered twice.
325
+ const outputTransform = addTypedOutputTransformPass(context.graph, postInput, output.value, {
326
+ dither: false,
327
+ });
316
328
  if (!outputTransform.ok) return outputTransform;
317
329
  const aa = addTypedFullscreenPass(context.graph, {
318
330
  name: 'fxaa',
@@ -326,13 +338,20 @@ export function addStandardPost(
326
338
  context.graph,
327
339
  postInput,
328
340
  rawOnlyPostInput.value,
341
+ {
342
+ dither: outputDither && postEffects.length === 0,
343
+ },
329
344
  );
330
345
  if (!transformed.ok) return transformed;
331
346
  } else if (rawOnly || linearLdr) {
332
- const transformed = addTypedOutputTransformPass(context.graph, postInput, finalPresent);
347
+ const transformed = addTypedOutputTransformPass(context.graph, postInput, finalPresent, {
348
+ dither: outputDither,
349
+ });
333
350
  if (!transformed.ok) return transformed;
334
351
  } else {
335
- const outputTransform = addTypedOutputTransformPass(context.graph, postInput, finalPresent);
352
+ const outputTransform = addTypedOutputTransformPass(context.graph, postInput, finalPresent, {
353
+ dither: outputDither,
354
+ });
336
355
  if (!outputTransform.ok) return outputTransform;
337
356
  }
338
357
  }
@@ -1,25 +1,22 @@
1
1
  export const STANDARD_PIPELINE_ID = 'forgeax::standard' as const;
2
2
  export const STANDARD_LIGHT_COUNTS = [1, 32, 256] as const;
3
- /** Shared clustered-lighting facts consumed by pipeline, record, and buffers. */
4
- export const DEFAULT_CLUSTER_GRID = { x: 16, y: 9, z: 24 } as const;
5
- export const CLUSTER_GRID_STRIDE_U32 = 2;
6
- export const LIGHT_INDEX_LIST_CAPACITY = 1048576;
7
- export const MAX_LIGHTS = 256;
3
+ export {
4
+ CLUSTER_GRID_STRIDE_U32,
5
+ DEFAULT_CLUSTER_GRID,
6
+ LIGHT_INDEX_LIST_CAPACITY,
7
+ MAX_LIGHTS,
8
+ } from './standard-lighting/layout';
8
9
 
9
10
  export type StandardLightCount = (typeof STANDARD_LIGHT_COUNTS)[number];
10
- export type StandardLightingLane = 'direct' | 'clustered';
11
- export type StandardFallbackLane = 'native' | 'cpu-webgl2';
12
11
  export type StandardShadowMode = 'off' | 'hard' | 'filtered';
13
- export type StandardTone =
14
- | 'none'
15
- | 'aces-filmic'
16
- | 'agx'
17
- | 'cineon'
18
- | 'linear'
19
- | 'neutral'
20
- | 'reinhard'
21
- | 'reinhard-extended';
22
- export type StandardAntialias = 'none' | 'fxaa' | 'msaa';
12
+ export type StandardVolumetricFogQuality = 'low' | 'high';
13
+
14
+ export interface StandardVolumetricFogProfile {
15
+ readonly quality: StandardVolumetricFogQuality;
16
+ readonly depth: 48 | 64;
17
+ readonly tileSize: 4 | 16;
18
+ }
19
+
23
20
  export const STANDARD_POST_STAGE_NAMES = [
24
21
  'transparent-blend',
25
22
  'bloom',
@@ -29,45 +26,28 @@ export const STANDARD_POST_STAGE_NAMES = [
29
26
  'present',
30
27
  ] as const;
31
28
  export type StandardPostStage = (typeof STANDARD_POST_STAGE_NAMES)[number];
32
- export type StandardVolumetricFogQuality = 'low' | 'high';
33
-
34
- export interface StandardVolumetricFogProfile {
35
- readonly quality: StandardVolumetricFogQuality;
36
- readonly depth: 48 | 64;
37
- readonly tileSize: 4 | 16;
38
- }
39
-
40
29
  export interface StandardProfile {
41
30
  readonly pipelineId: typeof STANDARD_PIPELINE_ID;
42
31
  readonly lightCount: StandardLightCount;
43
- readonly lighting: StandardLightingLane;
32
+ /** Graph topology selector; all local lights use the shared Cluster path. */
33
+ readonly renderPath: 'forward' | 'deferred';
44
34
  readonly shadows: StandardShadowMode;
45
35
  readonly pbr: boolean;
46
36
  readonly ibl: boolean;
47
37
  readonly ssao: boolean;
48
- readonly bloom: boolean;
49
- readonly tone: StandardTone;
50
- readonly antialias: StandardAntialias;
51
- readonly sky: boolean;
52
38
  /** Renderer-owned 1080p volume profile; tile width/height remain surface-derived. */
53
39
  readonly volumetricFog?: StandardVolumetricFogProfile | undefined;
54
- readonly fallback: StandardFallbackLane;
55
40
  readonly postStages: typeof STANDARD_POST_STAGE_NAMES;
56
41
  }
57
42
 
58
43
  export const DEFAULT_STANDARD_PROFILE: StandardProfile = Object.freeze({
59
44
  pipelineId: STANDARD_PIPELINE_ID,
60
45
  lightCount: 32,
61
- lighting: 'direct',
46
+ renderPath: 'forward',
62
47
  shadows: 'filtered',
63
48
  pbr: true,
64
49
  ibl: true,
65
50
  ssao: false,
66
- bloom: true,
67
- tone: 'aces-filmic',
68
- antialias: 'fxaa',
69
- sky: true,
70
- fallback: 'native',
71
51
  postStages: STANDARD_POST_STAGE_NAMES,
72
52
  });
73
53
 
@@ -76,13 +56,3 @@ export function resolveVolumetricFogProfile(
76
56
  ): StandardVolumetricFogProfile {
77
57
  return profile.volumetricFog ?? { quality: 'high', depth: 64, tileSize: 4 };
78
58
  }
79
-
80
- export type StandardLane = StandardLightingLane | 'cpu-webgl2';
81
-
82
- export function resolveStandardLane(
83
- profile: Pick<StandardProfile, 'lighting'>,
84
- capabilities: { readonly compute: boolean; readonly storageBuffer: boolean },
85
- ): StandardLane {
86
- if (!capabilities.compute || !capabilities.storageBuffer) return 'cpu-webgl2';
87
- return profile.lighting;
88
- }
@@ -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
  },
@@ -287,6 +287,80 @@ export function variantSetFromVertexLayoutProjection(
287
287
  return ok(parts.join('+'));
288
288
  }
289
289
 
290
+ /**
291
+ * Derive the capability axes shared by every Standard PBR draw owner.
292
+ *
293
+ * The all-true key is the canonical empty key used by the shader manifest;
294
+ * every other capability pair stays explicit so a no-local-light frame on a
295
+ * device without storage buffers cannot accidentally select a storage-backed
296
+ * PSO. Vertex-color is supplied here because the empty-key rule is only valid
297
+ * when that geometry-owned axis is also true.
298
+ */
299
+ export function standardCapabilityVariantSet(
300
+ clustered: boolean,
301
+ storageBuffer: boolean,
302
+ vertexColorAvailable: boolean,
303
+ ): string {
304
+ if (clustered && storageBuffer && vertexColorAvailable) return '';
305
+ return `CLUSTER_FORWARD_AVAILABLE=${clustered}+STORAGE_BUFFER_AVAILABLE=${storageBuffer}`;
306
+ }
307
+
308
+ /**
309
+ * Project the frame-owned Standard topology onto its material variant axes.
310
+ *
311
+ * `StandardTopologyInputValue.kind` is the only runtime authority for the
312
+ * cluster axis: a frame with local lights must request the clustered artifact,
313
+ * while the no-local-light topology requests the direct artifact. Keeping
314
+ * this projection beside the canonical key builder prevents PBR, skin, and
315
+ * sprite-lit record owners from independently inferring the active lane from
316
+ * whichever bind group happened to be constructed first.
317
+ */
318
+ export function standardTopologyVariantSet(
319
+ topology: { readonly kind: 'no-local-lights' | 'clustered' } | undefined,
320
+ storageBuffer: boolean,
321
+ vertexColorAvailable: boolean,
322
+ probeBlendAvailable = false,
323
+ ): string {
324
+ const capabilityVariantSet = standardCapabilityVariantSet(
325
+ topology?.kind === 'clustered',
326
+ storageBuffer,
327
+ vertexColorAvailable,
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`;
339
+ }
340
+
341
+ /**
342
+ * Keep a clustered frame fail-closed when its unified group(2) resource is
343
+ * unavailable. A missing group is not evidence that the frame is direct; it
344
+ * is an invalid prepared resource state.
345
+ */
346
+ export function standardTopologyBindGroupReady(
347
+ topology: { readonly kind: 'no-local-lights' | 'clustered' } | undefined,
348
+ clusterBindGroup: unknown,
349
+ ): boolean {
350
+ return (
351
+ topology?.kind !== 'clustered' || (clusterBindGroup !== null && clusterBindGroup !== undefined)
352
+ );
353
+ }
354
+
355
+ /** Derive the storage axis for Standard-compatible unlit material draws. */
356
+ export function standardStorageVariantSet(
357
+ storageBuffer: boolean,
358
+ vertexColorAvailable: boolean,
359
+ ): string {
360
+ if (storageBuffer && vertexColorAvailable) return '';
361
+ return `STORAGE_BUFFER_AVAILABLE=${storageBuffer}+VERTEX_COLOR_AVAILABLE=${vertexColorAvailable}`;
362
+ }
363
+
290
364
  /**
291
365
  * Build a `GPURenderPipelineDescriptor` from a PipelineSpec + shader modules.
292
366
  *
@@ -925,14 +999,14 @@ const TRI_GEOMETRY_PBR = {
925
999
  shaderUvSetCount: 8,
926
1000
  };
927
1001
 
928
- // M6 fix-up: URP-variant key string for the standard PBR shader. Mirrors the
929
- // boot-time variantSet computed at createRenderer.ts step 1b when
930
- // `isHdrpActive=false` and `storageBufferCapable=true` (the URP default
931
- // surface). The URP record path requests this exact string at every
1002
+ // M6 fix-up: non-clustered boot-variant key string for the standard PBR shader.
1003
+ // Mirrors the boot-time variantSet computed at createRenderer.ts step 1b when
1004
+ // clustered lighting is not yet admitted and `storageBufferCapable=true` (the
1005
+ // surface). The default record path requests this exact string at every
932
1006
  // `getMaterialShaderPipeline` call site, so seeding the table with these
933
- // variants keeps the URP cache lookup hot from frame 1 instead of
1007
+ // variants keeps the default cache lookup hot from frame 1 instead of
934
1008
  // skip-drawing the first ~1 frames while the async compile resolves.
935
- const URP_PBR_VARIANT_SET = 'CLUSTER_FORWARD_AVAILABLE=false+STORAGE_BUFFER_AVAILABLE=true';
1009
+ const STANDARD_BOOT_VARIANT_SET = standardCapabilityVariantSet(false, true, false);
936
1010
 
937
1011
  /**
938
1012
  * Build the boot-time pre-warm table: 15 standard PipelineSpec variants.
@@ -944,22 +1018,22 @@ const URP_PBR_VARIANT_SET = 'CLUSTER_FORWARD_AVAILABLE=false+STORAGE_BUFFER_AVAI
944
1018
  *
945
1019
  * Matrix:
946
1020
  * - 8 base material variants — {unlit, standard-pbr} x {LDR, HDR} x {S1, S4}
947
- * - 4 URP-variant standard-pbr — standard-pbr (variantSet=URP_PBR_VARIANT_SET)
948
- * x {LDR, HDR} x {S1, S4}; URP record path requests these specs by
1021
+ * - 4 non-clustered standard-pbr — standard-pbr (variantSet=STANDARD_BOOT_VARIANT_SET)
1022
+ * x {LDR, HDR} x {S1, S4}; the default record path requests these specs by
949
1023
  * `getMaterialShaderPipeline` keying off `cacheKeyOf` so the boot-time
950
1024
  * prewarm seeds `materialShaderPipelineCache` and avoids a 1-frame
951
- * async-compile skip-draw (M6 fix-up; see render-system-record.ts §URP)
1025
+ * async-compile skip-draw (M6 fix-up; see render-system-record.ts)
952
1026
  * - 3 fullscreen-post — tonemap (LDR S1) + skybox (HDR S1, HDR S4)
953
1027
  *
954
- * Total: 15. The URP variant is PBR-only because:
955
- * - unlit URP record path passes `variantSet=undefined` (the no-variant
1028
+ * Total: 15. The non-clustered variant is PBR-only because:
1029
+ * - unlit record path passes `variantSet=undefined` (the no-variant
956
1030
  * boot-default entries already cover it; createRenderer.ts §unlitRsp)
957
1031
  * - sprite goes through `getMaterialShaderPipeline('forgeax::sprite', ...)`
958
1032
  * lazily at first transparent-LDR-split draw — no boot-time pre-warm.
959
- * - HDRP variant (`variantSet=''` or compound `=true` form) is registered
1033
+ * - clustered variant (`variantSet=''` or compound `=true` form) is registered
960
1034
  * lazily when the clustered Standard profile activates, not at boot — adding clustered
961
1035
  * prewarm here would build PSOs against the URP layout (the boot-time
962
- * `pbrModule` is the URP variant when `isHdrpActive=false`)
1036
+ * `pbrModule` is the non-clustered variant before a frame admits local lights)
963
1037
  *
964
1038
  * `ldrViewFormat` parameterises the LDR color attachment format. Callers
965
1039
  * pass the runtime-resolved swap-chain view format (Channel 2 typically
@@ -1074,12 +1148,12 @@ export function buildSpecConstTable(
1074
1148
  renderState: undefined,
1075
1149
  },
1076
1150
 
1077
- // ── M6 fix-up: URP-variant standard-pbr prewarm ─────────────────────────
1078
- // URP record path (`getMaterialShaderPipeline`) requests
1079
- // `variantSet=URP_PBR_VARIANT_SET` for standard-shading entities; pre-M6
1151
+ // ── M6 fix-up: non-clustered standard-pbr prewarm ─────────────────────────
1152
+ // The default record path (`getMaterialShaderPipeline`) requests
1153
+ // `variantSet=STANDARD_BOOT_VARIANT_SET` for standard-shading entities; pre-M6
1080
1154
  // the silent `selectStandardFallbackPipeline` shim served the no-variant
1081
1155
  // boot prewarm during the 1-frame async-compile warmup. With M6's
1082
- // explicit-failure surface, those URP requests must hit the boot prewarm
1156
+ // explicit-failure surface, those requests must hit the boot prewarm
1083
1157
  // by their own variantSet — these 4 entries close that gap.
1084
1158
 
1085
1159
  // standard PBR URP LDR S1
@@ -1087,7 +1161,7 @@ export function buildSpecConstTable(
1087
1161
  shader: {
1088
1162
  id: 'forgeax::default-standard-pbr',
1089
1163
  passKind: 'forward',
1090
- variantSet: URP_PBR_VARIANT_SET,
1164
+ variantSet: STANDARD_BOOT_VARIANT_SET,
1091
1165
  },
1092
1166
  attachments: TRI_ATTACHMENTS_LDR_S1,
1093
1167
  geometry: TRI_GEOMETRY_PBR,
@@ -1098,7 +1172,7 @@ export function buildSpecConstTable(
1098
1172
  shader: {
1099
1173
  id: 'forgeax::default-standard-pbr',
1100
1174
  passKind: 'forward',
1101
- variantSet: URP_PBR_VARIANT_SET,
1175
+ variantSet: STANDARD_BOOT_VARIANT_SET,
1102
1176
  },
1103
1177
  attachments: TRI_ATTACHMENTS_LDR_S4,
1104
1178
  geometry: TRI_GEOMETRY_PBR,
@@ -1109,7 +1183,7 @@ export function buildSpecConstTable(
1109
1183
  shader: {
1110
1184
  id: 'forgeax::default-standard-pbr',
1111
1185
  passKind: 'forward',
1112
- variantSet: URP_PBR_VARIANT_SET,
1186
+ variantSet: STANDARD_BOOT_VARIANT_SET,
1113
1187
  },
1114
1188
  attachments: TRI_ATTACHMENTS_HDR_S1,
1115
1189
  geometry: TRI_GEOMETRY_PBR,
@@ -1120,7 +1194,7 @@ export function buildSpecConstTable(
1120
1194
  shader: {
1121
1195
  id: 'forgeax::default-standard-pbr',
1122
1196
  passKind: 'forward',
1123
- variantSet: URP_PBR_VARIANT_SET,
1197
+ variantSet: STANDARD_BOOT_VARIANT_SET,
1124
1198
  },
1125
1199
  attachments: TRI_ATTACHMENTS_HDR_S4,
1126
1200
  geometry: TRI_GEOMETRY_PBR,
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,55 @@
1
+ import type { PointShadowSnapshot } from './render-system-extract';
2
+
3
+ /**
4
+ * Detached point-shadow budget facts from one completed extract/record frame.
5
+ *
6
+ * `requested` is the number of PointLight + PointLightShadow pairs. The
7
+ * renderer owns the bounded cube-array atlas, so `admitted`, `shadowed`, and
8
+ * `shadowAtlasOccupancy` are all derived from the layer sentinel emitted by
9
+ * the same extract owner. No consumer needs to maintain a second budget
10
+ * ledger.
11
+ */
12
+ export interface PointShadowInspection {
13
+ readonly status: 'inactive' | 'ready' | 'over-budget';
14
+ readonly requested: number;
15
+ readonly admitted: number;
16
+ readonly shadowed: number;
17
+ readonly shadowAtlasOccupancy: number;
18
+ readonly shadowAtlasCapacity: number;
19
+ }
20
+
21
+ /** Project point-shadow snapshots to the public, JSON-safe inspection shape. */
22
+ export function inspectPointShadow(
23
+ snapshots: readonly PointShadowSnapshot[],
24
+ shadowAtlasCapacity: number,
25
+ ): PointShadowInspection {
26
+ const requested = snapshots.length;
27
+ const admitted = snapshots.reduce(
28
+ (count, snapshot) =>
29
+ snapshot.shadowAtlasLayer >= 0 && snapshot.shadowAtlasLayer < shadowAtlasCapacity
30
+ ? count + 1
31
+ : count,
32
+ 0,
33
+ );
34
+ // Never publish a misleading `ready` state when any requested shadow owns
35
+ // no atlas layer. The extract sentinel is the single admission projection;
36
+ // a partial or all-sentinel frame is therefore a failed/over-budget
37
+ // admission, not an active shadow path with incomplete output.
38
+ const status =
39
+ requested === 0
40
+ ? ('inactive' as const)
41
+ : requested > shadowAtlasCapacity || admitted !== requested
42
+ ? ('over-budget' as const)
43
+ : ('ready' as const);
44
+ return Object.freeze({
45
+ status,
46
+ requested,
47
+ admitted,
48
+ // A point shadow only samples an atlas after it owns an atlas layer. Keep
49
+ // these names explicit for AI diagnostics while deriving both from the
50
+ // one layer projection above.
51
+ shadowed: admitted,
52
+ shadowAtlasOccupancy: admitted,
53
+ shadowAtlasCapacity,
54
+ });
55
+ }
@@ -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
+ });
@@ -0,0 +1,136 @@
1
+ // @forgeax/engine-render - DeviceScope-owned RectArea LTC resources.
2
+
3
+ import type { Result, RhiDevice, RhiError, Texture } from '@forgeax/engine-rhi';
4
+ import { LTC_TABLE_HEIGHT, LTC_TABLE_WIDTH, LTC_TABLES } from '@forgeax/engine-shader';
5
+ import { err, ok } from '@forgeax/engine-types';
6
+ import { type DeviceScope, LifecycleTransaction } from '../../device/device-scope';
7
+ import {
8
+ GPU_TEXTURE_USAGE_COPY_DST,
9
+ GPU_TEXTURE_USAGE_TEXTURE_BINDING,
10
+ } from '../../gpu-texture-usage';
11
+ import { deriveExtendedLightingCapability, EXTENDED_LIGHTING_TOPOLOGY } from './resources';
12
+
13
+ export interface LtcResourcePlanInput {
14
+ readonly scope: DeviceScope;
15
+ readonly ltcAvailable: boolean;
16
+ readonly residentGeneration?: number;
17
+ }
18
+
19
+ export interface LtcResourcePlan {
20
+ readonly topology: typeof EXTENDED_LIGHTING_TOPOLOGY;
21
+ readonly generation: number;
22
+ readonly rectAdmission: 'admitted' | 'omitted';
23
+ readonly tableCount: number;
24
+ readonly uploadCount: number;
25
+ }
26
+
27
+ export interface LtcResourceCandidate {
28
+ readonly topology: typeof EXTENDED_LIGHTING_TOPOLOGY;
29
+ readonly generation: number;
30
+ readonly scope: DeviceScope;
31
+ readonly lambertTexture: Texture;
32
+ readonly ggxTexture: Texture;
33
+ readonly tableCount: 2;
34
+ readonly uploadCount: 2;
35
+ }
36
+
37
+ export function deriveLtcResourcePlan(input: LtcResourcePlanInput): LtcResourcePlan {
38
+ const admitted = input.ltcAvailable;
39
+ return {
40
+ topology: EXTENDED_LIGHTING_TOPOLOGY,
41
+ generation: input.scope.generation,
42
+ rectAdmission: admitted ? 'admitted' : 'omitted',
43
+ tableCount: admitted ? 2 : 0,
44
+ uploadCount: admitted && input.residentGeneration !== input.scope.generation ? 2 : 0,
45
+ };
46
+ }
47
+
48
+ function failure(expected: string, actual: string): Result<never, RhiError> {
49
+ return err({
50
+ code: 'webgpu-runtime-error',
51
+ expected,
52
+ hint: 'inspect device capabilities and retry the extendedLighting candidate',
53
+ detail: { error: { code: 'ltc-resource', message: actual } },
54
+ } as RhiError);
55
+ }
56
+
57
+ function textureBytes(table: Uint16Array): Uint8Array {
58
+ return new Uint8Array(table.buffer, table.byteOffset, table.byteLength);
59
+ }
60
+
61
+ /** Create both tracked tables atomically; unavailable LTC only omits Rect. */
62
+ export async function createLtcResourceCandidate(input: {
63
+ readonly device: RhiDevice | undefined;
64
+ readonly scope: DeviceScope;
65
+ }): Promise<Result<LtcResourceCandidate | undefined, RhiError>> {
66
+ if (input.device === undefined) return ok(undefined);
67
+ if (!deriveExtendedLightingCapability(input.device).admitted) return ok(undefined);
68
+ const device = input.device;
69
+
70
+ const transaction = new LifecycleTransaction(input.scope);
71
+ let lambertTexture: Texture | undefined;
72
+ let ggxTexture: Texture | undefined;
73
+ const createTable = (label: string, table: Uint16Array): Texture => {
74
+ const texture = device.createTexture({
75
+ label,
76
+ size: { width: LTC_TABLE_WIDTH, height: LTC_TABLE_HEIGHT, depthOrArrayLayers: 1 },
77
+ mipLevelCount: 1,
78
+ sampleCount: 1,
79
+ dimension: '2d',
80
+ format: 'rgba16float',
81
+ usage: GPU_TEXTURE_USAGE_COPY_DST | GPU_TEXTURE_USAGE_TEXTURE_BINDING,
82
+ viewFormats: [],
83
+ textureBindingViewDimension: undefined,
84
+ });
85
+ if (texture === undefined || !texture.ok) {
86
+ throw texture?.error ?? new Error('LTC texture creation returned no result');
87
+ }
88
+ const write = device.queue.writeTexture(
89
+ { texture: texture.value, mipLevel: 0, origin: { x: 0, y: 0, z: 0 } },
90
+ textureBytes(table),
91
+ { offset: 0, bytesPerRow: LTC_TABLE_WIDTH * 8, rowsPerImage: LTC_TABLE_HEIGHT },
92
+ { width: LTC_TABLE_WIDTH, height: LTC_TABLE_HEIGHT, depthOrArrayLayers: 1 },
93
+ );
94
+ if (!write.ok) throw write.error;
95
+ return texture.value;
96
+ };
97
+
98
+ transaction.add({
99
+ kind: 'texture',
100
+ create: () => {
101
+ lambertTexture = createTable('extended-lighting-ltc-lambert', LTC_TABLES.lambert);
102
+ return lambertTexture;
103
+ },
104
+ cleanup: (value) => {
105
+ const destroyed = input.device?.destroyTexture(value);
106
+ if (destroyed !== undefined && !destroyed.ok) throw destroyed.error;
107
+ },
108
+ });
109
+ transaction.add({
110
+ kind: 'texture',
111
+ create: () => {
112
+ ggxTexture = createTable('extended-lighting-ltc-ggx', LTC_TABLES.ggx);
113
+ return ggxTexture;
114
+ },
115
+ cleanup: (value) => {
116
+ const destroyed = input.device?.destroyTexture(value);
117
+ if (destroyed !== undefined && !destroyed.ok) throw destroyed.error;
118
+ },
119
+ });
120
+
121
+ const committed = await transaction.commit();
122
+ if (!committed.ok)
123
+ return failure(committed.error.primary.expected, committed.error.primary.message);
124
+ if (lambertTexture === undefined || ggxTexture === undefined) {
125
+ return failure('two LTC textures', 'the transaction committed without both tables');
126
+ }
127
+ return ok({
128
+ topology: EXTENDED_LIGHTING_TOPOLOGY,
129
+ generation: input.scope.generation,
130
+ scope: input.scope,
131
+ lambertTexture,
132
+ ggxTexture,
133
+ tableCount: 2,
134
+ uploadCount: 2,
135
+ });
136
+ }