@forgeax/engine-render 0.1.20 → 0.1.21

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 (372) hide show
  1. package/README.md +215 -0
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/assembly/device-feature-admission.d.ts +18 -0
  4. package/dist/assembly/device-feature-admission.d.ts.map +1 -0
  5. package/dist/assembly/directional-shadow-inspection.d.ts +38 -0
  6. package/dist/assembly/directional-shadow-inspection.d.ts.map +1 -0
  7. package/dist/assembly/factory.d.ts +21 -18
  8. package/dist/assembly/factory.d.ts.map +1 -1
  9. package/dist/assembly/host-contract.d.ts +9 -0
  10. package/dist/assembly/host-contract.d.ts.map +1 -1
  11. package/dist/assembly/render-target-host.d.ts +46 -0
  12. package/dist/assembly/render-target-host.d.ts.map +1 -0
  13. package/dist/assets/render-asset-port.d.ts.map +1 -1
  14. package/dist/authoring.mjs +3 -0
  15. package/dist/authoring.mjs.map +1 -1
  16. package/dist/capture/cube-views.d.ts +21 -0
  17. package/dist/capture/cube-views.d.ts.map +1 -0
  18. package/dist/capture/scheduler.d.ts +54 -0
  19. package/dist/capture/scheduler.d.ts.map +1 -0
  20. package/dist/{chunk-4XINDLAY.mjs → chunk-5YADUCA7.mjs} +1552 -1361
  21. package/dist/chunk-5YADUCA7.mjs.map +1 -0
  22. package/dist/{chunk-TBQJOK2L.mjs → chunk-6OPXDQ2E.mjs} +364 -51
  23. package/dist/chunk-6OPXDQ2E.mjs.map +1 -0
  24. package/dist/{chunk-M2MP4ZOU.mjs → chunk-BXBQQKFQ.mjs} +3 -9
  25. package/dist/chunk-BXBQQKFQ.mjs.map +1 -0
  26. package/dist/{chunk-CDTWTHZR.mjs → chunk-FS2KNUQH.mjs} +548 -6
  27. package/dist/chunk-FS2KNUQH.mjs.map +1 -0
  28. package/dist/components/camera.d.ts +1 -0
  29. package/dist/components/camera.d.ts.map +1 -1
  30. package/dist/components/cube-camera.d.ts +20 -0
  31. package/dist/components/cube-camera.d.ts.map +1 -0
  32. package/dist/components/directional-light.d.ts +11 -2
  33. package/dist/components/directional-light.d.ts.map +1 -1
  34. package/dist/components/directional-shadow-filter.d.ts +25 -0
  35. package/dist/components/directional-shadow-filter.d.ts.map +1 -0
  36. package/dist/components/index.d.ts +2 -0
  37. package/dist/components/index.d.ts.map +1 -1
  38. package/dist/components/light-helpers.d.ts.map +1 -1
  39. package/dist/components/reflection-probe.d.ts +17 -0
  40. package/dist/components/reflection-probe.d.ts.map +1 -0
  41. package/dist/construct-renderer.mjs +5826 -1003
  42. package/dist/construct-renderer.mjs.map +1 -1
  43. package/dist/environment/frame.d.ts.map +1 -1
  44. package/dist/environment/lifecycle.d.ts +5 -0
  45. package/dist/environment/lifecycle.d.ts.map +1 -1
  46. package/dist/errors/render.d.ts +141 -93
  47. package/dist/errors/render.d.ts.map +1 -1
  48. package/dist/extract/directional-shadow-projection.d.ts +23 -0
  49. package/dist/extract/directional-shadow-projection.d.ts.map +1 -0
  50. package/dist/extract/environment.d.ts +14 -0
  51. package/dist/extract/environment.d.ts.map +1 -1
  52. package/dist/extract/visibility.d.ts.map +1 -1
  53. package/dist/features/fullscreen.d.ts.map +1 -1
  54. package/dist/features/targets.d.ts.map +1 -1
  55. package/dist/features/types.d.ts +10 -0
  56. package/dist/features/types.d.ts.map +1 -1
  57. package/dist/fullscreen-post-process-pass.d.ts +13 -2
  58. package/dist/fullscreen-post-process-pass.d.ts.map +1 -1
  59. package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
  60. package/dist/gpu-driven/production-raster.d.ts +0 -1
  61. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  62. package/dist/gpu-scene.d.ts.map +1 -1
  63. package/dist/hdrp-buffers.d.ts +19 -17
  64. package/dist/hdrp-buffers.d.ts.map +1 -1
  65. package/dist/ibl/IblPipelineCache.d.ts +4 -0
  66. package/dist/ibl/IblPipelineCache.d.ts.map +1 -1
  67. package/dist/ibl/kernel-cache.d.ts +36 -0
  68. package/dist/ibl/kernel-cache.d.ts.map +1 -0
  69. package/dist/index.d.ts +28 -3
  70. package/dist/index.d.ts.map +1 -1
  71. package/dist/index.mjs +4 -4
  72. package/dist/index.mjs.map +1 -1
  73. package/dist/inspection-types.d.ts +51 -0
  74. package/dist/inspection-types.d.ts.map +1 -1
  75. package/dist/instances-derived-bounds.d.ts +50 -0
  76. package/dist/instances-derived-bounds.d.ts.map +1 -0
  77. package/dist/internal.mjs +1 -1
  78. package/dist/light-buffer-layout.d.ts +10 -1
  79. package/dist/light-buffer-layout.d.ts.map +1 -1
  80. package/dist/mesh-material-bindings.d.ts +54 -0
  81. package/dist/mesh-material-bindings.d.ts.map +1 -1
  82. package/dist/pbr-pipeline.d.ts +14 -4
  83. package/dist/pbr-pipeline.d.ts.map +1 -1
  84. package/dist/pipeline/standard-forward-lane.d.ts +2 -1
  85. package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
  86. package/dist/pipeline/standard-lighting/errors.d.ts +4 -0
  87. package/dist/pipeline/standard-lighting/errors.d.ts.map +1 -0
  88. package/dist/pipeline/standard-lighting/graph.d.ts +24 -0
  89. package/dist/pipeline/standard-lighting/graph.d.ts.map +1 -0
  90. package/dist/pipeline/standard-lighting/inspection.d.ts +38 -0
  91. package/dist/pipeline/standard-lighting/inspection.d.ts.map +1 -0
  92. package/dist/pipeline/standard-lighting/layout.d.ts +28 -0
  93. package/dist/pipeline/standard-lighting/layout.d.ts.map +1 -0
  94. package/dist/pipeline/standard-lighting/light-frame.d.ts +33 -0
  95. package/dist/pipeline/standard-lighting/light-frame.d.ts.map +1 -0
  96. package/dist/pipeline/standard-lighting/membership.d.ts +14 -0
  97. package/dist/pipeline/standard-lighting/membership.d.ts.map +1 -0
  98. package/dist/pipeline/standard-lighting/prepare.d.ts +16 -0
  99. package/dist/pipeline/standard-lighting/prepare.d.ts.map +1 -0
  100. package/dist/pipeline/standard-lighting/topology.d.ts +45 -0
  101. package/dist/pipeline/standard-lighting/topology.d.ts.map +1 -0
  102. package/dist/pipeline/standard-lighting/transport.d.ts +36 -0
  103. package/dist/pipeline/standard-lighting/transport.d.ts.map +1 -0
  104. package/dist/pipeline/standard-pipeline.d.ts +4 -13
  105. package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
  106. package/dist/pipeline/standard-post.d.ts.map +1 -1
  107. package/dist/pipeline/standard-profile.d.ts +5 -26
  108. package/dist/pipeline/standard-profile.d.ts.map +1 -1
  109. package/dist/pipeline-spec.d.ts +40 -7
  110. package/dist/pipeline-spec.d.ts.map +1 -1
  111. package/dist/point-shadow-inspection.d.ts +21 -0
  112. package/dist/point-shadow-inspection.d.ts.map +1 -0
  113. package/dist/record/frame-lighting.d.ts +27 -32
  114. package/dist/record/frame-lighting.d.ts.map +1 -1
  115. package/dist/record/frame-snapshot.d.ts +28 -14
  116. package/dist/record/frame-snapshot.d.ts.map +1 -1
  117. package/dist/record/frame-targets.d.ts +5 -0
  118. package/dist/record/frame-targets.d.ts.map +1 -1
  119. package/dist/record/frame.d.ts +10 -4
  120. package/dist/record/frame.d.ts.map +1 -1
  121. package/dist/record/gpu-pass-timing/contract.d.ts +96 -0
  122. package/dist/record/gpu-pass-timing/contract.d.ts.map +1 -0
  123. package/dist/record/gpu-pass-timing/errors.d.ts +18 -0
  124. package/dist/record/gpu-pass-timing/errors.d.ts.map +1 -0
  125. package/dist/record/gpu-pass-timing/index.d.ts +8 -0
  126. package/dist/record/gpu-pass-timing/index.d.ts.map +1 -0
  127. package/dist/record/gpu-pass-timing/parser.d.ts +11 -0
  128. package/dist/record/gpu-pass-timing/parser.d.ts.map +1 -0
  129. package/dist/record/gpu-pass-timing/session.d.ts +43 -0
  130. package/dist/record/gpu-pass-timing/session.d.ts.map +1 -0
  131. package/dist/record/gpu-timing.d.ts +8 -3
  132. package/dist/record/gpu-timing.d.ts.map +1 -1
  133. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  134. package/dist/record/main-pass-material.d.ts +22 -1
  135. package/dist/record/main-pass-material.d.ts.map +1 -1
  136. package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
  137. package/dist/record/main-pass.d.ts +2 -0
  138. package/dist/record/main-pass.d.ts.map +1 -1
  139. package/dist/record/render-context.d.ts +43 -2
  140. package/dist/record/render-context.d.ts.map +1 -1
  141. package/dist/record/shadow-pass.d.ts.map +1 -1
  142. package/dist/record/skybox-post-pass.d.ts.map +1 -1
  143. package/dist/record/typed-frame-graph.d.ts +72 -4
  144. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  145. package/dist/record/view-ubo.d.ts +7 -4
  146. package/dist/record/view-ubo.d.ts.map +1 -1
  147. package/dist/reflection/filter.d.ts +22 -0
  148. package/dist/reflection/filter.d.ts.map +1 -0
  149. package/dist/reflection/gpu-table.d.ts +25 -0
  150. package/dist/reflection/gpu-table.d.ts.map +1 -0
  151. package/dist/reflection/projection.d.ts +91 -0
  152. package/dist/reflection/projection.d.ts.map +1 -0
  153. package/dist/render-contract.d.ts +49 -7
  154. package/dist/render-contract.d.ts.map +1 -1
  155. package/dist/render-graph-primitives.d.ts +1 -0
  156. package/dist/render-graph-primitives.d.ts.map +1 -1
  157. package/dist/render-pipeline.d.ts +42 -2
  158. package/dist/render-pipeline.d.ts.map +1 -1
  159. package/dist/render-system-extract.d.ts +58 -17
  160. package/dist/render-system-extract.d.ts.map +1 -1
  161. package/dist/render-system.d.ts +17 -2
  162. package/dist/render-system.d.ts.map +1 -1
  163. package/dist/renderer.d.ts +43 -0
  164. package/dist/renderer.d.ts.map +1 -0
  165. package/dist/scene/render-scene.d.ts +14 -0
  166. package/dist/scene/render-scene.d.ts.map +1 -1
  167. package/dist/shadow-atlas.d.ts +3 -3
  168. package/dist/shadow-atlas.d.ts.map +1 -1
  169. package/dist/ssao-buffers.d.ts +3 -0
  170. package/dist/ssao-buffers.d.ts.map +1 -1
  171. package/dist/targets/contracts.d.ts +66 -0
  172. package/dist/targets/contracts.d.ts.map +1 -0
  173. package/dist/targets/graph-projection.d.ts +29 -0
  174. package/dist/targets/graph-projection.d.ts.map +1 -0
  175. package/dist/targets/material-source.d.ts +34 -0
  176. package/dist/targets/material-source.d.ts.map +1 -0
  177. package/dist/targets/owner.d.ts +32 -0
  178. package/dist/targets/owner.d.ts.map +1 -0
  179. package/dist/targets/physical.d.ts +18 -0
  180. package/dist/targets/physical.d.ts.map +1 -0
  181. package/dist/targets/readback.d.ts +39 -0
  182. package/dist/targets/readback.d.ts.map +1 -0
  183. package/dist/temporal/index.mjs +3 -3
  184. package/dist/typed-render-graph-primitives.d.ts +1 -1
  185. package/dist/typed-render-graph-primitives.d.ts.map +1 -1
  186. package/dist/typed-shadow-passes.d.ts +1 -1
  187. package/dist/typed-shadow-passes.d.ts.map +1 -1
  188. package/dist/volume/passes.d.ts +2 -2
  189. package/dist/volume/passes.d.ts.map +1 -1
  190. package/package.json +20 -20
  191. package/src/__tests__/bloom-exact-zero.unit.test.ts +48 -19
  192. package/src/__tests__/bloom-lifecycle.integration.test.ts +41 -13
  193. package/src/__tests__/camera-target-consumption.unit.test.ts +53 -0
  194. package/src/__tests__/cluster-binner.unit.test.ts +35 -0
  195. package/src/__tests__/cluster-grid-default-surface.unit.test.ts +7 -2
  196. package/src/__tests__/cube-camera-scheduler.unit.test.ts +70 -0
  197. package/src/__tests__/cube-camera-single-submit.perf.test.ts +75 -0
  198. package/src/__tests__/cube-camera-view-matrices.unit.test.ts +41 -0
  199. package/src/__tests__/cube-camera-vocabulary.unit.test.ts +47 -0
  200. package/src/__tests__/cube-camera.integration.test.ts +42 -0
  201. package/src/__tests__/device-feature-admission.unit.test.ts +73 -0
  202. package/src/__tests__/direct-light-snapshot.unit.test.ts +3 -3
  203. package/src/__tests__/docs-gate-green.unit.test.ts +38 -0
  204. package/src/__tests__/factory-contract.integration.test.ts +255 -29
  205. package/src/__tests__/fog-component.unit.test.ts +67 -0
  206. package/src/__tests__/fog.integration.test.ts +86 -0
  207. package/src/__tests__/fullscreen-feature-plan.unit.test.ts +37 -0
  208. package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +10 -9
  209. package/src/__tests__/gpu-driven-indirect-raster.dawn.test.ts +17 -1
  210. package/src/__tests__/gpu-driven-production.integration.test.ts +16 -17
  211. package/src/__tests__/gpu-driven-view-graph.integration.test.ts +12 -4
  212. package/src/__tests__/gpu-pass-timing-browser-runner.ts +281 -0
  213. package/src/__tests__/gpu-pass-timing-coexistence.integration.test.ts +80 -0
  214. package/src/__tests__/gpu-pass-timing-contract.unit.test.ts +243 -0
  215. package/src/__tests__/gpu-pass-timing-exact-zero.unit.test.ts +131 -0
  216. package/src/__tests__/gpu-pass-timing-lifecycle.integration.test.ts +230 -0
  217. package/src/__tests__/gpu-pass-timing-observation.unit.test.ts +233 -0
  218. package/src/__tests__/gpu-pass-timing-session.unit.test.ts +438 -0
  219. package/src/__tests__/gpu-pass-timing.browser.test.ts +26 -0
  220. package/src/__tests__/gpu-pass-timing.rhi-null.unit.test.ts +125 -0
  221. package/src/__tests__/hdrp-group2-visibility.unit.test.ts +24 -0
  222. package/src/__tests__/instances-culling.integration.test.ts +94 -0
  223. package/src/__tests__/instances-derived-bounds.unit.test.ts +118 -0
  224. package/src/__tests__/material-observation.integration.test.ts +101 -0
  225. package/src/__tests__/pbr-view-layout.unit.test.ts +16 -0
  226. package/src/__tests__/point-shadow-inspection.unit.test.ts +60 -0
  227. package/src/__tests__/public-surface.test-d.ts +10 -0
  228. package/src/__tests__/reflection-probe-authoring.unit.test.ts +71 -0
  229. package/src/__tests__/reflection-probe-filter.unit.test.ts +54 -0
  230. package/src/__tests__/reflection-probe-ibl-topology.unit.test.ts +45 -0
  231. package/src/__tests__/reflection-probe-pass-encode.integration.test.ts +101 -0
  232. package/src/__tests__/reflection-probe-performance.perf.test.ts +60 -0
  233. package/src/__tests__/reflection-probe-recovery.integration.test.ts +55 -0
  234. package/src/__tests__/reflection-probe-selection.unit.test.ts +46 -0
  235. package/src/__tests__/render-error-code-owner.test-d.ts +18 -3
  236. package/src/__tests__/render-feature-docs.unit.test.ts +1 -1
  237. package/src/__tests__/render-target-admission.unit.test.ts +80 -0
  238. package/src/__tests__/render-target-graph-projection.unit.test.ts +102 -0
  239. package/src/__tests__/render-target-inspection.unit.test.ts +52 -0
  240. package/src/__tests__/render-target-material-source.test-d.ts +30 -0
  241. package/src/__tests__/render-target-material-source.unit.test.ts +97 -0
  242. package/src/__tests__/render-target-owner-placement.unit.test.ts +203 -0
  243. package/src/__tests__/render-target-owner.unit.test.ts +122 -0
  244. package/src/__tests__/render-target-probe-soak.perf.test.ts +94 -0
  245. package/src/__tests__/render-target-public-schema.json +107 -0
  246. package/src/__tests__/render-target-public-schema.unit.test.ts +91 -0
  247. package/src/__tests__/render-target-public-surface.test-d.ts +83 -0
  248. package/src/__tests__/render-target-readback.integration.test.ts +90 -0
  249. package/src/__tests__/render-target-recovery.integration.test.ts +85 -0
  250. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +48 -0
  251. package/src/__tests__/renderer-lifecycle.integration.test.ts +14 -0
  252. package/src/__tests__/shader-stage-surface.unit.test.ts +19 -4
  253. package/src/__tests__/skin-motion-regression.unit.test.ts +132 -0
  254. package/src/__tests__/skinned-shadow-caster.test.ts +11 -2
  255. package/src/__tests__/standard-cluster-capability.integration.test.ts +63 -0
  256. package/src/__tests__/standard-cluster-transport.unit.test.ts +121 -0
  257. package/src/__tests__/standard-cluster-variant-resolution.unit.test.ts +232 -0
  258. package/src/__tests__/standard-clustered-pipeline.unit.test.ts +8 -5
  259. package/src/__tests__/standard-lighting-inspection.integration.test.ts +138 -0
  260. package/src/__tests__/standard-lighting-ssot.unit.test.ts +18 -0
  261. package/src/__tests__/standard-lighting-write.unit.test.ts +377 -0
  262. package/src/__tests__/standard-lighting.integration.test.ts +67 -0
  263. package/src/__tests__/standard-pipeline.dawn.test.ts +206 -5
  264. package/src/__tests__/standard-pipeline.integration.test.ts +181 -150
  265. package/src/__tests__/standard-probe-binding.unit.test.ts +25 -0
  266. package/src/__tests__/standard-profile-oracle.unit.test.ts +23 -38
  267. package/src/__tests__/typed-pipeline-topology.unit.test.ts +395 -14
  268. package/src/__tests__/urp-pipeline-targets.unit.test.ts +2 -2
  269. package/src/__tests__/visibility-resolver.unit.test.ts +15 -0
  270. package/src/assembly/device-feature-admission.ts +73 -0
  271. package/src/assembly/directional-shadow-inspection.ts +122 -0
  272. package/src/assembly/factory.ts +877 -286
  273. package/src/assembly/host-contract.ts +25 -0
  274. package/src/assembly/render-target-host.ts +847 -0
  275. package/src/assets/asset-decoders.ts +1 -1
  276. package/src/assets/render-asset-port.ts +7 -1
  277. package/src/capture/cube-views.ts +69 -0
  278. package/src/capture/scheduler.ts +265 -0
  279. package/src/components/__tests__/schema-contract.unit.test.ts +12 -0
  280. package/src/components/__tests__/schema-parity.unit.test.ts +9 -0
  281. package/src/components/camera.ts +4 -0
  282. package/src/components/cube-camera.ts +52 -0
  283. package/src/components/directional-light.ts +22 -2
  284. package/src/components/directional-shadow-filter.ts +57 -0
  285. package/src/components/index.ts +2 -0
  286. package/src/components/light-helpers.ts +81 -7
  287. package/src/components/reflection-probe.ts +49 -0
  288. package/src/environment/frame.ts +6 -24
  289. package/src/environment/lifecycle.ts +12 -0
  290. package/src/errors/render.ts +286 -142
  291. package/src/extract/directional-shadow-projection.ts +61 -0
  292. package/src/extract/environment.ts +57 -0
  293. package/src/extract/visibility.ts +25 -1
  294. package/src/features/fullscreen.ts +3 -0
  295. package/src/features/targets.ts +4 -1
  296. package/src/features/types.ts +10 -0
  297. package/src/fullscreen-post-process-pass.ts +43 -2
  298. package/src/gpu-driven/batch-topology.ts +1 -0
  299. package/src/gpu-driven/production-raster.ts +1 -3
  300. package/src/gpu-scene.ts +8 -1
  301. package/src/hdrp-buffers.ts +165 -59
  302. package/src/ibl/IblPipelineCache.ts +11 -0
  303. package/src/ibl/kernel-cache.ts +88 -0
  304. package/src/index.ts +148 -0
  305. package/src/inspection-types.ts +56 -0
  306. package/src/instances-derived-bounds.ts +171 -0
  307. package/src/light-buffer-layout.ts +20 -5
  308. package/src/mesh-material-bindings.ts +132 -0
  309. package/src/pbr-pipeline.ts +106 -17
  310. package/src/pipeline/standard-forward-lane.ts +46 -16
  311. package/src/pipeline/standard-lighting/errors.ts +6 -0
  312. package/src/pipeline/standard-lighting/graph.ts +157 -0
  313. package/src/pipeline/standard-lighting/inspection.ts +88 -0
  314. package/src/pipeline/standard-lighting/layout.ts +31 -0
  315. package/src/pipeline/standard-lighting/light-frame.ts +34 -0
  316. package/src/pipeline/standard-lighting/membership.ts +43 -0
  317. package/src/pipeline/standard-lighting/prepare.ts +37 -0
  318. package/src/pipeline/standard-lighting/topology.ts +120 -0
  319. package/src/pipeline/standard-lighting/transport.ts +77 -0
  320. package/src/pipeline/standard-pipeline.ts +122 -193
  321. package/src/pipeline/standard-post.ts +26 -7
  322. package/src/pipeline/standard-profile.ts +17 -47
  323. package/src/pipeline-spec.ts +84 -21
  324. package/src/point-shadow-inspection.ts +55 -0
  325. package/src/record/__tests__/temporal-payload.unit.test.ts +42 -1
  326. package/src/record/__tests__/transparent-group2-selection.unit.test.ts +73 -0
  327. package/src/record/__tests__/view-ubo-layout.unit.test.ts +40 -0
  328. package/src/record/frame-lighting.ts +367 -427
  329. package/src/record/frame-snapshot.ts +35 -27
  330. package/src/record/frame-targets.ts +17 -0
  331. package/src/record/frame.ts +261 -74
  332. package/src/record/gpu-pass-timing/contract.ts +180 -0
  333. package/src/record/gpu-pass-timing/errors.ts +34 -0
  334. package/src/record/gpu-pass-timing/index.ts +37 -0
  335. package/src/record/gpu-pass-timing/parser.ts +97 -0
  336. package/src/record/gpu-pass-timing/session.ts +938 -0
  337. package/src/record/gpu-timing.ts +21 -12
  338. package/src/record/main-pass-geometry.ts +120 -80
  339. package/src/record/main-pass-material.ts +95 -9
  340. package/src/record/main-pass-sprite-draws.ts +54 -25
  341. package/src/record/main-pass.ts +71 -15
  342. package/src/record/render-context.ts +72 -0
  343. package/src/record/shadow-pass.ts +17 -28
  344. package/src/record/skybox-post-pass.ts +20 -4
  345. package/src/record/typed-frame-graph.ts +893 -44
  346. package/src/record/view-ubo.ts +63 -28
  347. package/src/reflection/filter.ts +81 -0
  348. package/src/reflection/gpu-table.ts +44 -0
  349. package/src/reflection/projection.ts +281 -0
  350. package/src/render-contract.ts +76 -5
  351. package/src/render-graph-primitives.ts +9 -4
  352. package/src/render-pipeline.ts +117 -2
  353. package/src/render-system-extract.ts +557 -100
  354. package/src/render-system.ts +1095 -66
  355. package/src/renderer.ts +222 -0
  356. package/src/scene/render-scene.ts +115 -9
  357. package/src/shadow-atlas.ts +5 -5
  358. package/src/ssao-buffers.ts +9 -2
  359. package/src/targets/contracts.ts +182 -0
  360. package/src/targets/graph-projection.ts +169 -0
  361. package/src/targets/material-source.ts +143 -0
  362. package/src/targets/owner.ts +214 -0
  363. package/src/targets/physical.ts +18 -0
  364. package/src/targets/readback.ts +153 -0
  365. package/src/transmission/__tests__/standard-transmission.dawn.test.ts +59 -20
  366. package/src/typed-render-graph-primitives.ts +5 -3
  367. package/src/typed-shadow-passes.ts +46 -40
  368. package/src/volume/passes.ts +15 -9
  369. package/dist/chunk-4XINDLAY.mjs.map +0 -1
  370. package/dist/chunk-CDTWTHZR.mjs.map +0 -1
  371. package/dist/chunk-M2MP4ZOU.mjs.map +0 -1
  372. package/dist/chunk-TBQJOK2L.mjs.map +0 -1
@@ -0,0 +1,54 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import {
3
+ advanceProbeFilter,
4
+ boxProjectReflectionDirection,
5
+ createProbeFilterState,
6
+ } from '../reflection/filter';
7
+ import { buildReflectionProbeTable } from '../reflection/gpu-table';
8
+
9
+ describe('ReflectionProbe bounded filtering and table projection', () => {
10
+ it('does at most one face and one mip filter step per frame', () => {
11
+ const state = createProbeFilterState({ probeIndex: 2, faceCount: 6, mipCount: 5 });
12
+ expect(advanceProbeFilter(state)).toEqual({ probeIndex: 2, faceIndex: 0, mipLevel: 0 });
13
+ expect(advanceProbeFilter({ ...state, cursor: 1 })).toEqual({
14
+ probeIndex: 2,
15
+ faceIndex: 1,
16
+ mipLevel: 0,
17
+ });
18
+ });
19
+
20
+ it('keeps active LKG while a candidate is incomplete', () => {
21
+ const state = createProbeFilterState({ probeIndex: 0, faceCount: 6, mipCount: 3 });
22
+ expect(state.activeGeneration).toBe(0);
23
+ const next = advanceProbeFilter({ ...state, cursor: 17 });
24
+ expect(next).toEqual({ probeIndex: 0, faceIndex: 5, mipLevel: 2 });
25
+ expect(state.activeGeneration).toBe(0);
26
+ });
27
+
28
+ it('publishes only filtered probe rows and joins CPU/GPU by one index', () => {
29
+ const table = buildReflectionProbeTable([
30
+ {
31
+ primitiveKey: '0:9',
32
+ index: 1,
33
+ worldId: 0,
34
+ entityKey: 8,
35
+ center: [0, 0, 0],
36
+ halfExtents: [2, 2, 2],
37
+ intensity: 1,
38
+ generation: 4,
39
+ },
40
+ ]);
41
+ expect(table.rows[0]?.index).toBe(1);
42
+ expect(table.rows[0]?.generation).toBe(4);
43
+ expect(table.cpuIndexByPrimitive('0:9')).toBe(1);
44
+ });
45
+
46
+ it('projects a reflection direction from inside and outside the box', () => {
47
+ expect(boxProjectReflectionDirection([1, 0, 0], [0, 0, 0], [2, 2, 2], [0, 0, 0])).toEqual([
48
+ 1, 0, 0,
49
+ ]);
50
+ expect(boxProjectReflectionDirection([1, 0, 0], [0, 0, 0], [2, 2, 2], [3, 0, 0])).toEqual([
51
+ 1, 0, 0,
52
+ ]);
53
+ });
54
+ });
@@ -0,0 +1,45 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import {
3
+ beginProbeIblUpdate,
4
+ createIblKernelCache,
5
+ createProbeIblOutput,
6
+ type ProbeIblOutput,
7
+ publishProbeIblOutput,
8
+ } from '../ibl/kernel-cache';
9
+
10
+ describe('ReflectionProbe IBL topology', () => {
11
+ it('shares only generation-scoped kernels and keeps probe outputs independent', () => {
12
+ const kernels = createIblKernelCache(7);
13
+ const first = createProbeIblOutput({ entityKey: 1, resolution: 256, deviceGeneration: 7 });
14
+ const second = createProbeIblOutput({ entityKey: 2, resolution: 256, deviceGeneration: 7 });
15
+ expect(kernels.generation).toBe(7);
16
+ expect(first.raw).not.toBe(first.filtered);
17
+ expect(first.raw).not.toBe(second.raw);
18
+ expect(first.filteredGeneration).toBe(0);
19
+ expect(first.lkg).toBe(true);
20
+ });
21
+
22
+ it('requires raw, filtered, and publish order before a new generation is visible', () => {
23
+ const initial = createProbeIblOutput({ entityKey: 4, resolution: 128, deviceGeneration: 2 });
24
+ const update = beginProbeIblUpdate(initial);
25
+ expect(update.stages).toEqual(['raw', 'filtered', 'publish']);
26
+ expect(update.nextStage).toBe('raw');
27
+ const published = publishProbeIblOutput(update, { stage: 'raw', ok: true });
28
+ expect(published.filteredGeneration).toBe(0);
29
+ expect(published.lkg).toBe(true);
30
+ });
31
+
32
+ it('retains filtered LKG when PMREM, encode, or submit fails', () => {
33
+ let output: ProbeIblOutput = createProbeIblOutput({
34
+ entityKey: 5,
35
+ resolution: 64,
36
+ deviceGeneration: 3,
37
+ });
38
+ const update = beginProbeIblUpdate(output);
39
+ for (const stage of ['raw', 'filtered', 'publish'] as const) {
40
+ output = publishProbeIblOutput({ ...update, nextStage: stage }, { stage, ok: false });
41
+ expect(output.lkg).toBe(true);
42
+ expect(output.filteredGeneration).toBe(0);
43
+ }
44
+ });
45
+ });
@@ -0,0 +1,101 @@
1
+ import { RenderGraphBuilder } from '@forgeax/engine-render-graph';
2
+ import { rhi } from '@forgeax/engine-rhi-null';
3
+ import { describe, expect, it } from 'vitest';
4
+ import {
5
+ addReflectionProbeGraphPasses,
6
+ type ReflectionProbeGraphState,
7
+ } from '../record/typed-frame-graph';
8
+ import type { RenderPipelineFrame } from '../render-pipeline';
9
+
10
+ describe('ReflectionProbe capture pass encoding', () => {
11
+ it('resolves imported capture views through the execution resolver', async () => {
12
+ const adapter = (await rhi.requestAdapter()).unwrap();
13
+ const device = (await adapter.requestDevice()).unwrap();
14
+ const rawTexture = device
15
+ .createTexture({
16
+ label: 'reflection-test-raw',
17
+ format: 'rgba8unorm',
18
+ size: { width: 1, height: 1, depthOrArrayLayers: 6 },
19
+ mipLevelCount: 1,
20
+ sampleCount: 1,
21
+ dimension: '2d',
22
+ usage: 0x15,
23
+ textureBindingViewDimension: undefined,
24
+ })
25
+ .unwrap();
26
+ const rawCubeView = device
27
+ .createTextureView(rawTexture, { dimension: 'cube', arrayLayerCount: 6 })
28
+ .unwrap();
29
+ const rawFaceViews = Array.from({ length: 6 }, (_, face) =>
30
+ device
31
+ .createTextureView(rawTexture, {
32
+ dimension: '2d',
33
+ baseArrayLayer: face,
34
+ arrayLayerCount: 1,
35
+ })
36
+ .unwrap(),
37
+ );
38
+ const depthTexture = device
39
+ .createTexture({
40
+ label: 'reflection-test-depth',
41
+ format: 'depth24plus-stencil8',
42
+ size: { width: 1, height: 1, depthOrArrayLayers: 1 },
43
+ mipLevelCount: 1,
44
+ sampleCount: 1,
45
+ dimension: '2d',
46
+ usage: 0x10,
47
+ textureBindingViewDimension: undefined,
48
+ })
49
+ .unwrap();
50
+ const depthView = device.createTextureView(depthTexture, { dimension: '2d' }).unwrap();
51
+ const sampler = device.createSampler().unwrap();
52
+ const emptyLayout = device.createBindGroupLayout({ entries: [] }).unwrap();
53
+ const filterGroup1 = device.createBindGroup({ layout: emptyLayout, entries: [] }).unwrap();
54
+ const cubeVertexBuffer = device.createBuffer({ size: 16, usage: 0x20 }).unwrap();
55
+ const shader = (await rhi.createShaderModule(device, { code: 'synthetic' })).unwrap();
56
+ const filterPipeline = device
57
+ .createRenderPipeline({
58
+ label: 'reflection-test-filter',
59
+ layout: 'auto',
60
+ vertex: { module: shader, entryPoint: 'main', buffers: [] },
61
+ fragment: { module: shader, entryPoint: 'main', targets: [{ format: 'rgba8unorm' }] },
62
+ primitive: { topology: 'triangle-list' },
63
+ })
64
+ .unwrap();
65
+ const work: ReflectionProbeGraphState['work'][number] = {
66
+ probeIndex: 0,
67
+ rawTexture,
68
+ rawCubeView,
69
+ rawFaceViews,
70
+ rawDepthTexture: depthTexture,
71
+ rawCaptureFace: 0,
72
+ rawDepthView: depthView,
73
+ rawSize: 1,
74
+ filteredTexture: rawTexture,
75
+ filteredCubeView: rawCubeView,
76
+ filteredFaceViewsByMip: [rawFaceViews],
77
+ outputFormat: 'rgba8unorm',
78
+ sampler,
79
+ filterPipeline,
80
+ filterGroup0: undefined,
81
+ filterGroup1,
82
+ cubeVertexBuffer,
83
+ filteredSize: 1,
84
+ step: undefined,
85
+ };
86
+ const graph = new RenderGraphBuilder<RenderPipelineFrame>();
87
+ expect(addReflectionProbeGraphPasses(graph, { work: [work] }).ok).toBe(true);
88
+ const compiled = graph.compile({ device, surfaceSize: { width: 1, height: 1 } }).unwrap();
89
+ const encoder = device.createCommandEncoder({ label: 'reflection-test-frame' }).unwrap();
90
+ const frame = {
91
+ runtime: { device },
92
+ validatedOrdered: [],
93
+ frameState: { standardLighting: undefined, installedPipelineConfig: undefined },
94
+ msaaActive: false,
95
+ splitLdrSprite: false,
96
+ } as unknown as RenderPipelineFrame;
97
+ const executed = compiled.execute({ ...frame, encoder });
98
+ expect(executed.ok).toBe(true);
99
+ expect(encoder.finish().ok).toBe(true);
100
+ });
101
+ });
@@ -0,0 +1,60 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { boundedReflectionProbeFilterWork } from '../record/typed-frame-graph';
3
+ import {
4
+ advanceProbeFilter,
5
+ createProbeFilterState,
6
+ probeFilterIsSteady,
7
+ } from '../reflection/filter';
8
+ import { ReflectionProbeProjection } from '../reflection/projection';
9
+
10
+ describe('ReflectionProbe steady-state performance contract', () => {
11
+ it('keeps off and steady filter work exactly zero', () => {
12
+ const off = boundedReflectionProbeFilterWork({ work: [], maxStepsPerFrame: 1 });
13
+ const steady = createProbeFilterState({ probeIndex: 0, faceCount: 6, mipCount: 5 });
14
+ const complete = { ...steady, cursor: steady.faceCount * steady.mipCount };
15
+
16
+ expect(off).toHaveLength(0);
17
+ expect(probeFilterIsSteady(complete)).toBe(true);
18
+ expect(advanceProbeFilter(complete)).toBeUndefined();
19
+ });
20
+
21
+ it('keeps one candidate bounded to active output and one table row', () => {
22
+ const state = createProbeFilterState({ probeIndex: 3, faceCount: 6, mipCount: 5 });
23
+ const step = advanceProbeFilter(state);
24
+ expect(step).toBeDefined();
25
+ if (!step) throw new Error('expected an initial reflection probe filter step');
26
+ expect(step).toEqual({ probeIndex: 3, faceIndex: 0, mipLevel: 0 });
27
+ expect(
28
+ boundedReflectionProbeFilterWork({
29
+ work: [step],
30
+ maxStepsPerFrame: 1,
31
+ }),
32
+ ).toHaveLength(1);
33
+ });
34
+
35
+ it('does not advance the projection revision on a stable frame', () => {
36
+ const projection = new ReflectionProbeProjection();
37
+ const facts = [
38
+ {
39
+ worldId: 0,
40
+ entityKey: 3,
41
+ center: [0, 0, 0] as const,
42
+ halfExtents: [2, 2, 2] as const,
43
+ priority: 1,
44
+ intensity: 1,
45
+ resolution: 64,
46
+ revision: 1,
47
+ },
48
+ ];
49
+ const primitives = [
50
+ { worldId: 0, entityKey: 8, center: [0, 0, 0] as const },
51
+ { worldId: 0, entityKey: 9, center: [3, 0, 0] as const },
52
+ ];
53
+ const first = projection.update(facts, primitives);
54
+ const second = projection.update(facts, primitives);
55
+
56
+ expect(first.revision).toBe(1);
57
+ expect(second.revision).toBe(1);
58
+ expect(second.scannedPrimitives).toBe(first.scannedPrimitives);
59
+ });
60
+ });
@@ -0,0 +1,55 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import {
3
+ admitReflectionProbe,
4
+ estimateReflectionProbeBytes,
5
+ ReflectionProbeProjection,
6
+ } from '../reflection/projection';
7
+
8
+ describe('ReflectionProbe recovery integration', () => {
9
+ it('selects by world identity and falls back to Skylight outside all boxes', () => {
10
+ const projection = new ReflectionProbeProjection();
11
+ const snapshot = projection.update(
12
+ [
13
+ {
14
+ worldId: 9,
15
+ entityKey: 4,
16
+ center: [0, 0, 0],
17
+ halfExtents: [2, 2, 2],
18
+ priority: 1,
19
+ intensity: 1,
20
+ resolution: 64,
21
+ revision: 1,
22
+ },
23
+ {
24
+ worldId: 3,
25
+ entityKey: 8,
26
+ center: [0, 0, 0],
27
+ halfExtents: [2, 2, 2],
28
+ priority: 1,
29
+ intensity: 1,
30
+ resolution: 64,
31
+ revision: 1,
32
+ },
33
+ ],
34
+ [
35
+ { worldId: 9, entityKey: 100, center: [0, 0, 0] },
36
+ { worldId: 3, entityKey: 200, center: [4, 0, 0] },
37
+ ],
38
+ );
39
+ expect(snapshot.scannedPrimitives).toBe(2);
40
+ expect(projection.selection(9, 100)).toMatchObject({ kind: 'probe', worldId: 3, entityKey: 8 });
41
+ expect(projection.selection(3, 200)).toEqual({ kind: 'skylight' });
42
+ expect([...snapshot.facts].map((fact) => `${fact.worldId}:${fact.entityKey}`)).toEqual([
43
+ '3:8',
44
+ '9:4',
45
+ ]);
46
+ });
47
+
48
+ it('rejects the next allocation before GPU work exceeds the admission budget', () => {
49
+ const probe = { halfExtents: [1, 1, 1] as const, priority: 0, intensity: 1, resolution: 64 };
50
+ const bytes = estimateReflectionProbeBytes(probe.resolution);
51
+ const result = admitReflectionProbe(probe, { acceptedCount: 16, acceptedBytes: bytes * 16 });
52
+ expect(result.ok).toBe(false);
53
+ if (!result.ok) expect(result.error.code).toBe('reflection-probe-budget-exceeded');
54
+ });
55
+ });
@@ -0,0 +1,46 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { type ReflectionProbeFact, selectReflectionProbe } from '../reflection/projection';
3
+
4
+ const fact = (overrides: Partial<ReflectionProbeFact> = {}): ReflectionProbeFact => ({
5
+ worldId: 0,
6
+ entityKey: 1,
7
+ center: [0, 0, 0],
8
+ halfExtents: [2, 2, 2],
9
+ priority: 0,
10
+ intensity: 1,
11
+ resolution: 256,
12
+ revision: 1,
13
+ ...overrides,
14
+ });
15
+
16
+ describe('ReflectionProbe deterministic selection', () => {
17
+ it('filters by box containment before priority and normalized distance', () => {
18
+ const selected = selectReflectionProbe(
19
+ [fact({ entityKey: 3, priority: 2 }), fact({ entityKey: 2 })],
20
+ [0.5, 0, 0],
21
+ );
22
+ expect(selected.kind).toBe('probe');
23
+ if (selected.kind === 'probe') expect(selected.entityKey).toBe(3);
24
+ expect(selectReflectionProbe([fact({ entityKey: 3 })], [3, 0, 0]).kind).toBe('skylight');
25
+ });
26
+
27
+ it('uses normalized distance and stable identity as tie-breaks', () => {
28
+ const selected = selectReflectionProbe(
29
+ [
30
+ fact({ entityKey: 9, center: [1, 0, 0], halfExtents: [2, 2, 2] }),
31
+ fact({ entityKey: 4, center: [-1, 0, 0], halfExtents: [4, 4, 4] }),
32
+ ],
33
+ [0, 0, 0],
34
+ );
35
+ expect(selected.kind).toBe('probe');
36
+ if (selected.kind === 'probe') expect(selected.entityKey).toBe(4);
37
+ });
38
+
39
+ it('does not depend on World reorder or mix World identities', () => {
40
+ const probes = [fact({ worldId: 2, entityKey: 8 }), fact({ worldId: 1, entityKey: 3 })];
41
+ const first = selectReflectionProbe(probes, [0, 0, 0]);
42
+ const second = selectReflectionProbe([...probes].reverse(), [0, 0, 0]);
43
+ expect(first).toEqual(second);
44
+ expect(selectReflectionProbe(probes, [8, 0, 0]).kind).toBe('skylight');
45
+ });
46
+ });
@@ -1,3 +1,4 @@
1
+ import type { RuntimeErrorCode } from '@forgeax/engine-types';
1
2
  import { describe, expectTypeOf, it } from 'vitest';
2
3
  import type {
3
4
  ObservationUnavailableDetail,
@@ -33,9 +34,10 @@ const expectedCodes = [
33
34
  'taa-unavailable',
34
35
  'shadow-invalid-config',
35
36
  'equirect-projection-failed',
36
- 'hdrp-light-budget-exceeded',
37
- 'hdrp-index-list-overflow',
38
- 'hdrp-deferred-caps-insufficient',
37
+ 'standard-light-budget-exceeded',
38
+ 'standard-cluster-index-overflow',
39
+ 'standard-cluster-transport-unavailable',
40
+ 'standard-profile-invalid',
39
41
  'point-shadow-atlas-uninitialized',
40
42
  'point-shadow-atlas-bounds-violation',
41
43
  'video-upload-unsupported',
@@ -64,6 +66,12 @@ const expectedCodes = [
64
66
  'points-lines-material-unsupported',
65
67
  'points-lines-budget-exceeded',
66
68
  'points-lines-prepare-failed',
69
+ 'render-target-descriptor-invalid',
70
+ 'render-target-capability-missing',
71
+ 'render-target-state-invalid',
72
+ 'render-target-operation-failed',
73
+ 'reflection-probe-budget-exceeded',
74
+ 'render-intent-invalid',
67
75
  'volume-owner-conflict',
68
76
  'volume-density-shape-mismatch',
69
77
  'volume-invalid-bounds',
@@ -104,6 +112,13 @@ describe('RenderError code owner', () => {
104
112
  void unknownCode;
105
113
  });
106
114
 
115
+ it('keeps retired HDRP error namespaces out of every public code union', () => {
116
+ type RetiredRenderCode = Extract<RenderErrorCode, `hdrp-${string}`>;
117
+ type RetiredRuntimeCode = Extract<RuntimeErrorCode, `hdrp-${string}`>;
118
+ expectTypeOf<RetiredRenderCode>().toEqualTypeOf<never>();
119
+ expectTypeOf<RetiredRuntimeCode>().toEqualTypeOf<never>();
120
+ });
121
+
107
122
  it('preserves correlated detail and value declarations', () => {
108
123
  type ObservationError = Extract<RenderError, { readonly code: 'observation-unavailable' }>;
109
124
  expectTypeOf<ObservationError['detail']>().toEqualTypeOf<ObservationUnavailableDetail>();
@@ -17,7 +17,7 @@ const appSkill = read('../../../../skills/forgeax-engine-app/SKILL.md');
17
17
 
18
18
  // M4-06 prepends the direct-light contract; keep the existing first-read
19
19
  // vocabulary assertion wide enough to cover that approved contract.
20
- const topSurface = renderReadme.slice(0, 18000);
20
+ const topSurface = renderReadme.slice(0, 20000);
21
21
 
22
22
  describe('RenderFeature documentation surface', () => {
23
23
  it('keeps the first-read public route and four-term vocabulary indexable', () => {
@@ -0,0 +1,80 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { RenderTargetCapabilityMissingError } from '../errors/render';
3
+ import {
4
+ admitRenderTargetDescriptor,
5
+ type RenderTargetAdmissionLimits,
6
+ type RenderTargetDescriptor,
7
+ } from '../targets/contracts';
8
+
9
+ const limits: RenderTargetAdmissionLimits = {
10
+ maxTextureDimension2D: 4096,
11
+ maxBytesPerTarget: 64 * 1024 * 1024,
12
+ renderableFormats: ['rgba16float', 'rgba8unorm', 'rgba8unorm-srgb'],
13
+ sampleCounts: [1, 4],
14
+ depthFormats: ['depth24plus-stencil8', 'depth32float'],
15
+ };
16
+
17
+ const base: RenderTargetDescriptor = {
18
+ shape: '2d',
19
+ width: 512,
20
+ height: 256,
21
+ format: 'rgba8unorm',
22
+ mipLevels: 1,
23
+ sampleCount: 1,
24
+ sampled: true,
25
+ readback: true,
26
+ };
27
+
28
+ describe('RenderTarget descriptor admission', () => {
29
+ it('accepts the complete first-version descriptor vocabulary', () => {
30
+ const result = admitRenderTargetDescriptor(base, limits);
31
+ expect(result.ok).toBe(true);
32
+ });
33
+
34
+ it.each([
35
+ ['zero width', { width: 0 }],
36
+ ['zero height', { height: 0 }],
37
+ ['non-square cube', { shape: 'cube' as const }],
38
+ ])('rejects %s before allocation', (_name, patch) => {
39
+ const descriptor = { ...base, ...patch };
40
+ if ('shape' in patch && patch.shape === 'cube') descriptor.height = 256;
41
+ const result = admitRenderTargetDescriptor(descriptor, limits);
42
+ expect(result.ok).toBe(false);
43
+ if (!result.ok) expect(result.error.code).toBe('render-target-descriptor-invalid');
44
+ });
45
+
46
+ it('rejects unsupported format, sample count, and byte budget', () => {
47
+ const formatResult = admitRenderTargetDescriptor(
48
+ { ...base, format: 'rgba16float' },
49
+ { ...limits, renderableFormats: ['rgba8unorm'] },
50
+ );
51
+ expect(formatResult.ok).toBe(false);
52
+ if (!formatResult.ok) expect(formatResult.error.code).toBe('render-target-capability-missing');
53
+
54
+ const sampleResult = admitRenderTargetDescriptor(
55
+ { ...base, sampleCount: 4 },
56
+ { ...limits, sampleCounts: [1] },
57
+ );
58
+ expect(sampleResult.ok).toBe(false);
59
+ if (!sampleResult.ok) expect(sampleResult.error.code).toBe('render-target-capability-missing');
60
+
61
+ const bytesResult = admitRenderTargetDescriptor(
62
+ { ...base, width: 2048, height: 2048, mipLevels: 'full' },
63
+ { ...limits, maxBytesPerTarget: 1024 },
64
+ );
65
+ expect(bytesResult.ok).toBe(false);
66
+ if (!bytesResult.ok) expect(bytesResult.error.code).toBe('render-target-descriptor-invalid');
67
+ });
68
+
69
+ it('does not reinterpret format or silently fall back on depth', () => {
70
+ const result = admitRenderTargetDescriptor(
71
+ { ...base, depth: 'depth32float' },
72
+ { ...limits, depthFormats: ['depth24plus-stencil8'] },
73
+ );
74
+ expect(result.ok).toBe(false);
75
+ if (!result.ok && result.error instanceof RenderTargetCapabilityMissingError) {
76
+ expect(result.error.code).toBe('render-target-capability-missing');
77
+ expect(result.error.detail.requested).toBe('depth32float');
78
+ }
79
+ });
80
+ });
@@ -0,0 +1,102 @@
1
+ import { RenderGraphBuilder } from '@forgeax/engine-render-graph';
2
+ import type { RhiCommandEncoder, Texture, TextureView } from '@forgeax/engine-rhi';
3
+ import { describe, expect, it } from 'vitest';
4
+ import type { RenderTargetDescriptor } from '../targets/contracts';
5
+ import { projectRenderTargetGraph, type RenderTargetGraphInput } from '../targets/graph-projection';
6
+ import { createRenderTargetOwner } from '../targets/owner';
7
+
8
+ type Frame = { readonly encoder: RhiCommandEncoder };
9
+
10
+ const owner = createRenderTargetOwner({ rendererId: Symbol('renderer'), initialGeneration: 3 });
11
+ const texture = {} as Texture;
12
+ const view = {} as TextureView;
13
+ const resolveView = {} as TextureView;
14
+
15
+ function targetInput(descriptor: RenderTargetDescriptor): RenderTargetGraphInput {
16
+ const target = owner.create(descriptor);
17
+ if (!target.ok) throw target.error;
18
+ return {
19
+ target: target.value,
20
+ generation: 3,
21
+ descriptor,
22
+ texture,
23
+ view,
24
+ ...(descriptor.sampleCount === 4 ? { resolveView } : {}),
25
+ };
26
+ }
27
+
28
+ describe('RenderTarget graph projection', () => {
29
+ it('imports a 2D target with sampled/readback usage and an exact mip view', () => {
30
+ const descriptor: RenderTargetDescriptor = {
31
+ shape: '2d',
32
+ width: 64,
33
+ height: 32,
34
+ format: 'rgba8unorm',
35
+ mipLevels: 'full',
36
+ sampleCount: 1,
37
+ sampled: true,
38
+ readback: true,
39
+ };
40
+ const graph = new RenderGraphBuilder<Frame>();
41
+ const projected = projectRenderTargetGraph(graph, targetInput(descriptor), {
42
+ mipLevel: 2,
43
+ });
44
+ expect(projected.ok).toBe(true);
45
+ if (!projected.ok) return;
46
+ expect(projected.value.ownership).toBe('imported');
47
+ expect(projected.value.generation).toBe(3);
48
+ expect(projected.value.viewDescriptor).toMatchObject({
49
+ dimension: '2d',
50
+ baseMipLevel: 2,
51
+ mipLevelCount: 1,
52
+ });
53
+ expect(projected.value.usage).toEqual(['sampled-read', 'copy-src']);
54
+ });
55
+
56
+ it('keeps cube subresources as cube views and resolves MSAA before sampling', () => {
57
+ const descriptor: RenderTargetDescriptor = {
58
+ shape: 'cube',
59
+ width: 64,
60
+ height: 64,
61
+ format: 'rgba16float',
62
+ mipLevels: 1,
63
+ sampleCount: 4,
64
+ sampled: true,
65
+ readback: true,
66
+ };
67
+ const graph = new RenderGraphBuilder<Frame>();
68
+ const projected = projectRenderTargetGraph(graph, targetInput(descriptor), {
69
+ mipLevel: 0,
70
+ face: 4,
71
+ });
72
+ expect(projected.ok).toBe(true);
73
+ if (!projected.ok) return;
74
+ expect(projected.value.viewDescriptor).toMatchObject({
75
+ dimension: 'cube',
76
+ baseMipLevel: 0,
77
+ baseArrayLayer: 4,
78
+ arrayLayerCount: 1,
79
+ });
80
+ expect(projected.value.resolveView).toBeDefined();
81
+ expect(projected.value.sampleView).toBe(projected.value.resolveView);
82
+ expect(projected.value.usage).toEqual(['sampled-read', 'copy-src']);
83
+ });
84
+
85
+ it('rejects a missing MSAA resolve view without changing graph ownership', () => {
86
+ const descriptor: RenderTargetDescriptor = {
87
+ shape: '2d',
88
+ width: 32,
89
+ height: 32,
90
+ format: 'rgba8unorm',
91
+ mipLevels: 1,
92
+ sampleCount: 4,
93
+ sampled: true,
94
+ readback: false,
95
+ };
96
+ const graph = new RenderGraphBuilder<Frame>();
97
+ const input = targetInput(descriptor);
98
+ const projected = projectRenderTargetGraph(graph, { ...input, resolveView: undefined });
99
+ expect(projected.ok).toBe(false);
100
+ if (!projected.ok) expect(projected.error.code).toBe('render-target-operation-failed');
101
+ });
102
+ });
@@ -0,0 +1,52 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import type { ReflectionProbeSelectionInspection } from '../inspection-types';
3
+ import { ReflectionProbeProjection } from '../reflection/projection';
4
+ import { createRenderTargetOwner } from '../targets/owner';
5
+
6
+ describe('bounded render-target inspection', () => {
7
+ it('retains only POD owner facts and no GPU handles or unbounded history', () => {
8
+ const owner = createRenderTargetOwner({ rendererId: Symbol('renderer'), initialGeneration: 4 });
9
+ const target = owner.create({
10
+ shape: '2d',
11
+ width: 8,
12
+ height: 8,
13
+ format: 'rgba8unorm',
14
+ mipLevels: 1,
15
+ sampleCount: 1,
16
+ sampled: true,
17
+ readback: true,
18
+ });
19
+ expect(target.ok).toBe(true);
20
+ if (!target.ok) return;
21
+ const inspection = owner.inspect(target.value);
22
+ expect(inspection.ok).toBe(true);
23
+ if (!inspection.ok) return;
24
+ expect(Object.keys(inspection.value).sort()).toEqual([
25
+ 'descriptor',
26
+ 'generation',
27
+ 'state',
28
+ 'token',
29
+ ]);
30
+ expect(inspection.value).not.toHaveProperty('history');
31
+ expect(inspection.value).not.toHaveProperty('device');
32
+ });
33
+
34
+ it('keeps selection inspection bounded to current primitives', () => {
35
+ const projection = new ReflectionProbeProjection();
36
+ const snapshot = projection.update([], []);
37
+ expect(snapshot.scannedPrimitives).toBe(0);
38
+ expect(snapshot.selected.size).toBe(0);
39
+ expect(projection.selection(1, 2)).toEqual({ kind: 'skylight' });
40
+ });
41
+
42
+ it('uses a closed identity for selected probes and Skylight fallback', () => {
43
+ const selected: ReflectionProbeSelectionInspection = {
44
+ kind: 'probe',
45
+ worldId: 2,
46
+ entityKey: 7,
47
+ };
48
+ const fallback: ReflectionProbeSelectionInspection = { kind: 'skylight' };
49
+ expect(selected).toEqual({ kind: 'probe', worldId: 2, entityKey: 7 });
50
+ expect(fallback).toEqual({ kind: 'skylight' });
51
+ });
52
+ });