@forgeax/engine-render 0.1.19 → 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 (452) 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 +34 -20
  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-RJRUS55L.mjs → chunk-5YADUCA7.mjs} +1896 -1624
  21. package/dist/chunk-5YADUCA7.mjs.map +1 -0
  22. package/dist/{chunk-LOSO4CTV.mjs → chunk-6OPXDQ2E.mjs} +423 -46
  23. package/dist/chunk-6OPXDQ2E.mjs.map +1 -0
  24. package/dist/{chunk-IPXCABEP.mjs → chunk-BXBQQKFQ.mjs} +3 -9
  25. package/dist/chunk-BXBQQKFQ.mjs.map +1 -0
  26. package/dist/chunk-FS2KNUQH.mjs +1294 -0
  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/components/spot-light.d.ts +12 -0
  42. package/dist/components/spot-light.d.ts.map +1 -1
  43. package/dist/construct-renderer.mjs +8552 -1612
  44. package/dist/construct-renderer.mjs.map +1 -1
  45. package/dist/device/gpu-residency.d.ts +19 -2
  46. package/dist/device/gpu-residency.d.ts.map +1 -1
  47. package/dist/environment/frame.d.ts.map +1 -1
  48. package/dist/environment/lifecycle.d.ts +5 -0
  49. package/dist/environment/lifecycle.d.ts.map +1 -1
  50. package/dist/errors/render.d.ts +209 -93
  51. package/dist/errors/render.d.ts.map +1 -1
  52. package/dist/extract/directional-shadow-projection.d.ts +23 -0
  53. package/dist/extract/directional-shadow-projection.d.ts.map +1 -0
  54. package/dist/extract/environment.d.ts +14 -0
  55. package/dist/extract/environment.d.ts.map +1 -1
  56. package/dist/extract/visibility.d.ts.map +1 -1
  57. package/dist/features/fullscreen.d.ts.map +1 -1
  58. package/dist/features/targets.d.ts.map +1 -1
  59. package/dist/features/types.d.ts +10 -0
  60. package/dist/features/types.d.ts.map +1 -1
  61. package/dist/fullscreen-post-process-pass.d.ts +13 -2
  62. package/dist/fullscreen-post-process-pass.d.ts.map +1 -1
  63. package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
  64. package/dist/gpu-driven/production-raster.d.ts +0 -1
  65. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  66. package/dist/gpu-scene.d.ts.map +1 -1
  67. package/dist/gpu-usage.d.ts +2 -0
  68. package/dist/gpu-usage.d.ts.map +1 -1
  69. package/dist/hdrp-buffers.d.ts +19 -17
  70. package/dist/hdrp-buffers.d.ts.map +1 -1
  71. package/dist/ibl/IblPipelineCache.d.ts +4 -0
  72. package/dist/ibl/IblPipelineCache.d.ts.map +1 -1
  73. package/dist/ibl/kernel-cache.d.ts +36 -0
  74. package/dist/ibl/kernel-cache.d.ts.map +1 -0
  75. package/dist/index.d.ts +35 -4
  76. package/dist/index.d.ts.map +1 -1
  77. package/dist/index.mjs +4 -4
  78. package/dist/index.mjs.map +1 -1
  79. package/dist/inspection-types.d.ts +51 -0
  80. package/dist/inspection-types.d.ts.map +1 -1
  81. package/dist/instances-derived-bounds.d.ts +50 -0
  82. package/dist/instances-derived-bounds.d.ts.map +1 -0
  83. package/dist/internal.mjs +1 -1
  84. package/dist/light-buffer-layout.d.ts +30 -13
  85. package/dist/light-buffer-layout.d.ts.map +1 -1
  86. package/dist/mesh-material-bindings.d.ts +54 -0
  87. package/dist/mesh-material-bindings.d.ts.map +1 -1
  88. package/dist/pbr-pipeline.d.ts +20 -7
  89. package/dist/pbr-pipeline.d.ts.map +1 -1
  90. package/dist/pipeline/standard-forward-lane.d.ts +2 -1
  91. package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
  92. package/dist/pipeline/standard-lighting/errors.d.ts +4 -0
  93. package/dist/pipeline/standard-lighting/errors.d.ts.map +1 -0
  94. package/dist/pipeline/standard-lighting/graph.d.ts +24 -0
  95. package/dist/pipeline/standard-lighting/graph.d.ts.map +1 -0
  96. package/dist/pipeline/standard-lighting/inspection.d.ts +38 -0
  97. package/dist/pipeline/standard-lighting/inspection.d.ts.map +1 -0
  98. package/dist/pipeline/standard-lighting/layout.d.ts +28 -0
  99. package/dist/pipeline/standard-lighting/layout.d.ts.map +1 -0
  100. package/dist/pipeline/standard-lighting/light-frame.d.ts +33 -0
  101. package/dist/pipeline/standard-lighting/light-frame.d.ts.map +1 -0
  102. package/dist/pipeline/standard-lighting/membership.d.ts +14 -0
  103. package/dist/pipeline/standard-lighting/membership.d.ts.map +1 -0
  104. package/dist/pipeline/standard-lighting/prepare.d.ts +16 -0
  105. package/dist/pipeline/standard-lighting/prepare.d.ts.map +1 -0
  106. package/dist/pipeline/standard-lighting/topology.d.ts +45 -0
  107. package/dist/pipeline/standard-lighting/topology.d.ts.map +1 -0
  108. package/dist/pipeline/standard-lighting/transport.d.ts +36 -0
  109. package/dist/pipeline/standard-lighting/transport.d.ts.map +1 -0
  110. package/dist/pipeline/standard-pipeline.d.ts +4 -13
  111. package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
  112. package/dist/pipeline/standard-post.d.ts.map +1 -1
  113. package/dist/pipeline/standard-profile.d.ts +12 -20
  114. package/dist/pipeline/standard-profile.d.ts.map +1 -1
  115. package/dist/pipeline-spec.d.ts +40 -7
  116. package/dist/pipeline-spec.d.ts.map +1 -1
  117. package/dist/point-shadow-inspection.d.ts +21 -0
  118. package/dist/point-shadow-inspection.d.ts.map +1 -0
  119. package/dist/record/frame-lighting.d.ts +29 -32
  120. package/dist/record/frame-lighting.d.ts.map +1 -1
  121. package/dist/record/frame-snapshot.d.ts +65 -15
  122. package/dist/record/frame-snapshot.d.ts.map +1 -1
  123. package/dist/record/frame-targets.d.ts +5 -0
  124. package/dist/record/frame-targets.d.ts.map +1 -1
  125. package/dist/record/frame.d.ts +12 -5
  126. package/dist/record/frame.d.ts.map +1 -1
  127. package/dist/record/gpu-pass-timing/contract.d.ts +96 -0
  128. package/dist/record/gpu-pass-timing/contract.d.ts.map +1 -0
  129. package/dist/record/gpu-pass-timing/errors.d.ts +18 -0
  130. package/dist/record/gpu-pass-timing/errors.d.ts.map +1 -0
  131. package/dist/record/gpu-pass-timing/index.d.ts +8 -0
  132. package/dist/record/gpu-pass-timing/index.d.ts.map +1 -0
  133. package/dist/record/gpu-pass-timing/parser.d.ts +11 -0
  134. package/dist/record/gpu-pass-timing/parser.d.ts.map +1 -0
  135. package/dist/record/gpu-pass-timing/session.d.ts +43 -0
  136. package/dist/record/gpu-pass-timing/session.d.ts.map +1 -0
  137. package/dist/record/gpu-timing.d.ts +37 -0
  138. package/dist/record/gpu-timing.d.ts.map +1 -0
  139. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  140. package/dist/record/main-pass-material.d.ts +23 -2
  141. package/dist/record/main-pass-material.d.ts.map +1 -1
  142. package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
  143. package/dist/record/main-pass.d.ts +2 -0
  144. package/dist/record/main-pass.d.ts.map +1 -1
  145. package/dist/record/render-context.d.ts +48 -3
  146. package/dist/record/render-context.d.ts.map +1 -1
  147. package/dist/record/shadow-pass.d.ts.map +1 -1
  148. package/dist/record/skybox-post-pass.d.ts.map +1 -1
  149. package/dist/record/typed-frame-graph.d.ts +81 -5
  150. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  151. package/dist/record/view-ubo.d.ts +7 -4
  152. package/dist/record/view-ubo.d.ts.map +1 -1
  153. package/dist/reflection/filter.d.ts +22 -0
  154. package/dist/reflection/filter.d.ts.map +1 -0
  155. package/dist/reflection/gpu-table.d.ts +25 -0
  156. package/dist/reflection/gpu-table.d.ts.map +1 -0
  157. package/dist/reflection/projection.d.ts +91 -0
  158. package/dist/reflection/projection.d.ts.map +1 -0
  159. package/dist/render-contract.d.ts +114 -7
  160. package/dist/render-contract.d.ts.map +1 -1
  161. package/dist/render-data.d.ts +10 -6
  162. package/dist/render-data.d.ts.map +1 -1
  163. package/dist/render-graph-primitives.d.ts +1 -0
  164. package/dist/render-graph-primitives.d.ts.map +1 -1
  165. package/dist/render-pipeline.d.ts +78 -2
  166. package/dist/render-pipeline.d.ts.map +1 -1
  167. package/dist/render-system-extract.d.ts +113 -20
  168. package/dist/render-system-extract.d.ts.map +1 -1
  169. package/dist/render-system.d.ts +24 -3
  170. package/dist/render-system.d.ts.map +1 -1
  171. package/dist/renderer-inspect.d.ts +29 -0
  172. package/dist/renderer-inspect.d.ts.map +1 -0
  173. package/dist/renderer.d.ts +43 -0
  174. package/dist/renderer.d.ts.map +1 -0
  175. package/dist/scene/render-scene.d.ts +14 -0
  176. package/dist/scene/render-scene.d.ts.map +1 -1
  177. package/dist/shadow-atlas.d.ts +3 -3
  178. package/dist/shadow-atlas.d.ts.map +1 -1
  179. package/dist/ssao-buffers.d.ts +3 -0
  180. package/dist/ssao-buffers.d.ts.map +1 -1
  181. package/dist/targets/contracts.d.ts +66 -0
  182. package/dist/targets/contracts.d.ts.map +1 -0
  183. package/dist/targets/graph-projection.d.ts +29 -0
  184. package/dist/targets/graph-projection.d.ts.map +1 -0
  185. package/dist/targets/material-source.d.ts +34 -0
  186. package/dist/targets/material-source.d.ts.map +1 -0
  187. package/dist/targets/owner.d.ts +32 -0
  188. package/dist/targets/owner.d.ts.map +1 -0
  189. package/dist/targets/physical.d.ts +18 -0
  190. package/dist/targets/physical.d.ts.map +1 -0
  191. package/dist/targets/readback.d.ts +39 -0
  192. package/dist/targets/readback.d.ts.map +1 -0
  193. package/dist/temporal/frame.d.ts +44 -0
  194. package/dist/temporal/frame.d.ts.map +1 -0
  195. package/dist/temporal/index.mjs +3 -3
  196. package/dist/typed-render-graph-primitives.d.ts +6 -1
  197. package/dist/typed-render-graph-primitives.d.ts.map +1 -1
  198. package/dist/typed-shadow-atlas.d.ts +10 -0
  199. package/dist/typed-shadow-atlas.d.ts.map +1 -0
  200. package/dist/typed-shadow-passes.d.ts +1 -1
  201. package/dist/typed-shadow-passes.d.ts.map +1 -1
  202. package/dist/volume/capability.d.ts +57 -0
  203. package/dist/volume/capability.d.ts.map +1 -0
  204. package/dist/volume/component.d.ts +61 -0
  205. package/dist/volume/component.d.ts.map +1 -0
  206. package/dist/volume/extract.d.ts +11 -0
  207. package/dist/volume/extract.d.ts.map +1 -0
  208. package/dist/volume/inspection.d.ts +38 -0
  209. package/dist/volume/inspection.d.ts.map +1 -0
  210. package/dist/volume/passes.d.ts +38 -0
  211. package/dist/volume/passes.d.ts.map +1 -0
  212. package/dist/volume/recovery.d.ts +38 -0
  213. package/dist/volume/recovery.d.ts.map +1 -0
  214. package/dist/volume/resources.d.ts +57 -0
  215. package/dist/volume/resources.d.ts.map +1 -0
  216. package/dist/volume/temporal.d.ts +35 -0
  217. package/dist/volume/temporal.d.ts.map +1 -0
  218. package/package.json +20 -20
  219. package/src/__tests__/bloom-exact-zero.unit.test.ts +48 -19
  220. package/src/__tests__/bloom-lifecycle.integration.test.ts +41 -13
  221. package/src/__tests__/camera-target-consumption.unit.test.ts +53 -0
  222. package/src/__tests__/cluster-binner.unit.test.ts +35 -0
  223. package/src/__tests__/cluster-grid-default-surface.unit.test.ts +7 -2
  224. package/src/__tests__/cube-camera-scheduler.unit.test.ts +70 -0
  225. package/src/__tests__/cube-camera-single-submit.perf.test.ts +75 -0
  226. package/src/__tests__/cube-camera-view-matrices.unit.test.ts +41 -0
  227. package/src/__tests__/cube-camera-vocabulary.unit.test.ts +47 -0
  228. package/src/__tests__/cube-camera.integration.test.ts +42 -0
  229. package/src/__tests__/device-feature-admission.unit.test.ts +73 -0
  230. package/src/__tests__/direct-light-snapshot.unit.test.ts +3 -3
  231. package/src/__tests__/docs-gate-green.unit.test.ts +38 -0
  232. package/src/__tests__/factory-contract.integration.test.ts +255 -29
  233. package/src/__tests__/fog-component.unit.test.ts +67 -0
  234. package/src/__tests__/fog.integration.test.ts +86 -0
  235. package/src/__tests__/fullscreen-feature-plan.unit.test.ts +37 -0
  236. package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +10 -9
  237. package/src/__tests__/gpu-driven-indirect-raster.dawn.test.ts +17 -1
  238. package/src/__tests__/gpu-driven-production.integration.test.ts +16 -17
  239. package/src/__tests__/gpu-driven-view-graph.integration.test.ts +12 -4
  240. package/src/__tests__/gpu-pass-timing-browser-runner.ts +281 -0
  241. package/src/__tests__/gpu-pass-timing-coexistence.integration.test.ts +80 -0
  242. package/src/__tests__/gpu-pass-timing-contract.unit.test.ts +243 -0
  243. package/src/__tests__/gpu-pass-timing-exact-zero.unit.test.ts +131 -0
  244. package/src/__tests__/gpu-pass-timing-lifecycle.integration.test.ts +230 -0
  245. package/src/__tests__/gpu-pass-timing-observation.unit.test.ts +233 -0
  246. package/src/__tests__/gpu-pass-timing-session.unit.test.ts +438 -0
  247. package/src/__tests__/gpu-pass-timing.browser.test.ts +26 -0
  248. package/src/__tests__/gpu-pass-timing.rhi-null.unit.test.ts +125 -0
  249. package/src/__tests__/hdrp-group2-visibility.unit.test.ts +24 -0
  250. package/src/__tests__/instances-culling.integration.test.ts +94 -0
  251. package/src/__tests__/instances-derived-bounds.unit.test.ts +118 -0
  252. package/src/__tests__/ktx2-basis-gpu-consumer.dawn.test.ts +14 -14
  253. package/src/__tests__/material-observation.integration.test.ts +101 -0
  254. package/src/__tests__/mesh-buffer-usage-surface.unit.test.ts +1 -1
  255. package/src/__tests__/pbr-view-layout.unit.test.ts +16 -0
  256. package/src/__tests__/point-shadow-inspection.unit.test.ts +60 -0
  257. package/src/__tests__/public-surface.test-d.ts +10 -0
  258. package/src/__tests__/reflection-probe-authoring.unit.test.ts +71 -0
  259. package/src/__tests__/reflection-probe-filter.unit.test.ts +54 -0
  260. package/src/__tests__/reflection-probe-ibl-topology.unit.test.ts +45 -0
  261. package/src/__tests__/reflection-probe-pass-encode.integration.test.ts +101 -0
  262. package/src/__tests__/reflection-probe-performance.perf.test.ts +60 -0
  263. package/src/__tests__/reflection-probe-recovery.integration.test.ts +55 -0
  264. package/src/__tests__/reflection-probe-selection.unit.test.ts +46 -0
  265. package/src/__tests__/render-error-code-owner.test-d.ts +23 -3
  266. package/src/__tests__/render-feature-docs.unit.test.ts +1 -1
  267. package/src/__tests__/render-target-admission.unit.test.ts +80 -0
  268. package/src/__tests__/render-target-graph-projection.unit.test.ts +102 -0
  269. package/src/__tests__/render-target-inspection.unit.test.ts +52 -0
  270. package/src/__tests__/render-target-material-source.test-d.ts +30 -0
  271. package/src/__tests__/render-target-material-source.unit.test.ts +97 -0
  272. package/src/__tests__/render-target-owner-placement.unit.test.ts +203 -0
  273. package/src/__tests__/render-target-owner.unit.test.ts +122 -0
  274. package/src/__tests__/render-target-probe-soak.perf.test.ts +94 -0
  275. package/src/__tests__/render-target-public-schema.json +107 -0
  276. package/src/__tests__/render-target-public-schema.unit.test.ts +91 -0
  277. package/src/__tests__/render-target-public-surface.test-d.ts +83 -0
  278. package/src/__tests__/render-target-readback.integration.test.ts +90 -0
  279. package/src/__tests__/render-target-recovery.integration.test.ts +85 -0
  280. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +76 -0
  281. package/src/__tests__/renderer-frame-transaction.integration.test.ts +1 -1
  282. package/src/__tests__/renderer-lifecycle.integration.test.ts +14 -0
  283. package/src/__tests__/shader-stage-surface.unit.test.ts +19 -4
  284. package/src/__tests__/skin-motion-regression.unit.test.ts +132 -0
  285. package/src/__tests__/skinned-shadow-caster.test.ts +32 -2
  286. package/src/__tests__/standard-cluster-capability.integration.test.ts +63 -0
  287. package/src/__tests__/standard-cluster-transport.unit.test.ts +121 -0
  288. package/src/__tests__/standard-cluster-variant-resolution.unit.test.ts +232 -0
  289. package/src/__tests__/standard-clustered-pipeline.unit.test.ts +8 -5
  290. package/src/__tests__/standard-lighting-inspection.integration.test.ts +138 -0
  291. package/src/__tests__/standard-lighting-ssot.unit.test.ts +18 -0
  292. package/src/__tests__/standard-lighting-write.unit.test.ts +377 -0
  293. package/src/__tests__/standard-lighting.integration.test.ts +67 -0
  294. package/src/__tests__/standard-pipeline.dawn.test.ts +206 -5
  295. package/src/__tests__/standard-pipeline.integration.test.ts +181 -150
  296. package/src/__tests__/standard-probe-binding.unit.test.ts +25 -0
  297. package/src/__tests__/standard-profile-oracle.unit.test.ts +23 -36
  298. package/src/__tests__/typed-pipeline-topology.unit.test.ts +395 -14
  299. package/src/__tests__/urp-pipeline-targets.unit.test.ts +2 -2
  300. package/src/__tests__/visibility-resolver.unit.test.ts +15 -0
  301. package/src/__tests__/volume-optics-oracle.unit.test.ts +85 -0
  302. package/src/__tests__/volumetric-fog-budget-recovery.integration.test.ts +655 -0
  303. package/src/__tests__/volumetric-fog-component.unit.test.ts +71 -0
  304. package/src/__tests__/volumetric-fog-composite-stage-readback.dawn.test.ts +376 -0
  305. package/src/__tests__/volumetric-fog-errors.unit.test.ts +51 -0
  306. package/src/__tests__/volumetric-fog-inspection.unit.test.ts +77 -0
  307. package/src/__tests__/volumetric-fog-light-capability.unit.test.ts +19 -0
  308. package/src/__tests__/volumetric-fog-light-consumers.integration.test.ts +18 -0
  309. package/src/__tests__/volumetric-fog-light-errors.unit.test.ts +17 -0
  310. package/src/__tests__/volumetric-fog-light-topology.unit.test.ts +21 -0
  311. package/src/__tests__/volumetric-fog-light-transaction.integration.test.ts +53 -0
  312. package/src/__tests__/volumetric-fog-m9-watcher.integration.test.ts +27 -0
  313. package/src/__tests__/volumetric-fog-point-light-owner.integration.test.ts +44 -0
  314. package/src/__tests__/volumetric-fog-point-spot-accumulation.integration.test.ts +49 -0
  315. package/src/__tests__/volumetric-fog-point-spot-contract.unit.test.ts +28 -0
  316. package/src/__tests__/volumetric-fog-point-spot-lkg.integration.test.ts +65 -0
  317. package/src/__tests__/volumetric-fog-point-spot-selection.integration.test.ts +49 -0
  318. package/src/__tests__/volumetric-fog-point-spot-temporal.unit.test.ts +55 -0
  319. package/src/__tests__/volumetric-fog-projector-contract.unit.test.ts +22 -0
  320. package/src/__tests__/volumetric-fog-projector-inspection.unit.test.ts +12 -0
  321. package/src/__tests__/volumetric-fog-projector-surface-volume.integration.test.ts +11 -0
  322. package/src/__tests__/volumetric-fog-projector-transaction.integration.test.ts +26 -0
  323. package/src/__tests__/volumetric-fog-recovery.integration.test.ts +47 -0
  324. package/src/__tests__/volumetric-fog-selected-light-resolution.integration.test.ts +63 -0
  325. package/src/__tests__/volumetric-fog-spot-closure.integration.test.ts +26 -0
  326. package/src/__tests__/volumetric-fog-spot-contract.test-d.ts +21 -0
  327. package/src/__tests__/volumetric-fog-spot-contract.unit.test.ts +27 -0
  328. package/src/__tests__/volumetric-fog-spot-frame-optics.integration.test.ts +14 -0
  329. package/src/__tests__/volumetric-fog-spot-optics.integration.test.ts +29 -0
  330. package/src/__tests__/volumetric-fog-stage-readback.dawn.test.ts +550 -0
  331. package/src/__tests__/volumetric-fog-temporal-selected-light.unit.test.ts +33 -0
  332. package/src/__tests__/volumetric-fog-temporal.unit.test.ts +45 -0
  333. package/src/__tests__/volumetric-fog-topology.unit.test.ts +237 -0
  334. package/src/__tests__/volumetric-fog-transparent.integration.test.ts +27 -0
  335. package/src/assembly/device-feature-admission.ts +73 -0
  336. package/src/assembly/directional-shadow-inspection.ts +122 -0
  337. package/src/assembly/factory.ts +1093 -246
  338. package/src/assembly/host-contract.ts +25 -0
  339. package/src/assembly/render-target-host.ts +847 -0
  340. package/src/assets/asset-decoders.ts +1 -1
  341. package/src/assets/render-asset-port.ts +7 -1
  342. package/src/capture/cube-views.ts +69 -0
  343. package/src/capture/scheduler.ts +265 -0
  344. package/src/components/__tests__/schema-contract.unit.test.ts +12 -0
  345. package/src/components/__tests__/schema-parity.unit.test.ts +9 -0
  346. package/src/components/camera.ts +4 -0
  347. package/src/components/cube-camera.ts +52 -0
  348. package/src/components/directional-light.ts +22 -2
  349. package/src/components/directional-shadow-filter.ts +57 -0
  350. package/src/components/index.ts +2 -0
  351. package/src/components/light-helpers.ts +85 -7
  352. package/src/components/reflection-probe.ts +49 -0
  353. package/src/components/spot-light.ts +22 -3
  354. package/src/device/gpu-residency.ts +168 -58
  355. package/src/environment/frame.ts +6 -24
  356. package/src/environment/lifecycle.ts +12 -0
  357. package/src/errors/render.ts +405 -143
  358. package/src/extract/directional-shadow-projection.ts +61 -0
  359. package/src/extract/environment.ts +57 -0
  360. package/src/extract/visibility.ts +25 -1
  361. package/src/features/fullscreen.ts +3 -0
  362. package/src/features/targets.ts +4 -1
  363. package/src/features/types.ts +10 -0
  364. package/src/fullscreen-post-process-pass.ts +43 -2
  365. package/src/gpu-driven/batch-topology.ts +1 -0
  366. package/src/gpu-driven/production-raster.ts +1 -3
  367. package/src/gpu-scene.ts +8 -1
  368. package/src/gpu-usage.ts +2 -0
  369. package/src/hdrp-buffers.ts +165 -59
  370. package/src/ibl/IblPipelineCache.ts +11 -0
  371. package/src/ibl/kernel-cache.ts +88 -0
  372. package/src/index.ts +186 -1
  373. package/src/inspection-types.ts +56 -0
  374. package/src/instances-derived-bounds.ts +171 -0
  375. package/src/light-buffer-layout.ts +74 -26
  376. package/src/mesh-material-bindings.ts +132 -0
  377. package/src/pbr-pipeline.ts +139 -27
  378. package/src/pipeline/standard-forward-lane.ts +65 -15
  379. package/src/pipeline/standard-lighting/errors.ts +6 -0
  380. package/src/pipeline/standard-lighting/graph.ts +157 -0
  381. package/src/pipeline/standard-lighting/inspection.ts +88 -0
  382. package/src/pipeline/standard-lighting/layout.ts +31 -0
  383. package/src/pipeline/standard-lighting/light-frame.ts +34 -0
  384. package/src/pipeline/standard-lighting/membership.ts +43 -0
  385. package/src/pipeline/standard-lighting/prepare.ts +37 -0
  386. package/src/pipeline/standard-lighting/topology.ts +120 -0
  387. package/src/pipeline/standard-lighting/transport.ts +77 -0
  388. package/src/pipeline/standard-pipeline.ts +141 -192
  389. package/src/pipeline/standard-post.ts +26 -7
  390. package/src/pipeline/standard-profile.ts +23 -30
  391. package/src/pipeline-spec.ts +84 -21
  392. package/src/point-shadow-inspection.ts +55 -0
  393. package/src/record/__tests__/temporal-payload.unit.test.ts +42 -1
  394. package/src/record/__tests__/transparent-group2-selection.unit.test.ts +73 -0
  395. package/src/record/__tests__/view-ubo-layout.unit.test.ts +40 -0
  396. package/src/record/frame-lighting.ts +470 -516
  397. package/src/record/frame-snapshot.ts +76 -28
  398. package/src/record/frame-targets.ts +23 -2
  399. package/src/record/frame.ts +1169 -79
  400. package/src/record/gpu-pass-timing/contract.ts +180 -0
  401. package/src/record/gpu-pass-timing/errors.ts +34 -0
  402. package/src/record/gpu-pass-timing/index.ts +37 -0
  403. package/src/record/gpu-pass-timing/parser.ts +97 -0
  404. package/src/record/gpu-pass-timing/session.ts +938 -0
  405. package/src/record/gpu-timing.ts +233 -0
  406. package/src/record/main-pass-geometry.ts +120 -80
  407. package/src/record/main-pass-material.ts +101 -11
  408. package/src/record/main-pass-sprite-draws.ts +54 -25
  409. package/src/record/main-pass.ts +71 -15
  410. package/src/record/render-context.ts +77 -0
  411. package/src/record/shadow-pass.ts +104 -66
  412. package/src/record/skybox-post-pass.ts +20 -4
  413. package/src/record/typed-frame-graph.ts +1016 -32
  414. package/src/record/view-ubo.ts +63 -28
  415. package/src/reflection/filter.ts +81 -0
  416. package/src/reflection/gpu-table.ts +44 -0
  417. package/src/reflection/projection.ts +281 -0
  418. package/src/render-contract.ts +147 -4
  419. package/src/render-data.ts +50 -22
  420. package/src/render-graph-primitives.ts +9 -4
  421. package/src/render-pipeline.ts +161 -2
  422. package/src/render-system-extract.ts +886 -103
  423. package/src/render-system.ts +1174 -66
  424. package/src/renderer-inspect.ts +66 -0
  425. package/src/renderer.ts +222 -0
  426. package/src/scene/render-scene.ts +117 -9
  427. package/src/shadow-atlas.ts +5 -5
  428. package/src/ssao-buffers.ts +9 -2
  429. package/src/targets/contracts.ts +182 -0
  430. package/src/targets/graph-projection.ts +169 -0
  431. package/src/targets/material-source.ts +143 -0
  432. package/src/targets/owner.ts +214 -0
  433. package/src/targets/physical.ts +18 -0
  434. package/src/targets/readback.ts +153 -0
  435. package/src/temporal/frame.ts +128 -0
  436. package/src/transmission/__tests__/standard-transmission.dawn.test.ts +61 -23
  437. package/src/typed-render-graph-primitives.ts +34 -2
  438. package/src/typed-shadow-atlas.ts +17 -0
  439. package/src/typed-shadow-passes.ts +47 -40
  440. package/src/volume/capability.ts +173 -0
  441. package/src/volume/component.ts +110 -0
  442. package/src/volume/extract.ts +21 -0
  443. package/src/volume/inspection.ts +78 -0
  444. package/src/volume/passes.ts +686 -0
  445. package/src/volume/recovery.ts +86 -0
  446. package/src/volume/resources.ts +236 -0
  447. package/src/volume/temporal.ts +89 -0
  448. package/dist/chunk-IPXCABEP.mjs.map +0 -1
  449. package/dist/chunk-LOSO4CTV.mjs.map +0 -1
  450. package/dist/chunk-RJRUS55L.mjs.map +0 -1
  451. package/dist/chunk-YTQ5C7YQ.mjs +0 -584
  452. package/dist/chunk-YTQ5C7YQ.mjs.map +0 -1
@@ -11,7 +11,7 @@ import {
11
11
  import { ok } from '@forgeax/engine-types';
12
12
  import { describe, expect, it } from 'vitest';
13
13
  import { createRenderer as constructRenderer } from '../assembly/factory';
14
- import { Camera, MeshFilter, MeshRenderer } from '../components';
14
+ import { Camera, MeshFilter, MeshRenderer, PointLight } from '../components';
15
15
  import type { MeshGpuHandles } from '../device/gpu-residency';
16
16
  import { BatchTopology } from '../gpu-driven/batch-topology';
17
17
  import { GpuDrivenProduction } from '../gpu-driven/production-raster';
@@ -29,6 +29,11 @@ import type { MaterialSnapshot, RenderableSnapshot } from '../render-system-extr
29
29
  import { RenderScene } from '../scene/render-scene';
30
30
 
31
31
  const EMPTY_RESOURCE_CLASS = JSON.stringify({ textures: [], samplers: [], video: [] });
32
+ const OUTPUT_TRANSFORM_WGSL =
33
+ '@vertex fn vs_main(@builtin(vertex_index) i: u32) -> @builtin(position) vec4<f32> { ' +
34
+ 'var p = array<vec2<f32>, 3>(vec2<f32>(-1.0, -1.0), vec2<f32>(3.0, -1.0), vec2<f32>(-1.0, 3.0)); ' +
35
+ 'return vec4<f32>(p[i], 0.0, 1.0); } ' +
36
+ '@fragment fn fs_main() -> @location(0) vec4<f32> { return vec4<f32>(0.0); }';
32
37
 
33
38
  const STANDARD_PBR_URP_VARIANT_KEY =
34
39
  'CLUSTER_FORWARD_AVAILABLE=false+STORAGE_BUFFER_AVAILABLE=true+TRANSMISSION_AVAILABLE=false+VERTEX_COLOR_AVAILABLE=false';
@@ -38,7 +43,7 @@ const STANDARD_PBR_TRANSMISSION_VARIANT_KEY =
38
43
  function manifestDataUrl(): string {
39
44
  const pbrWgsl = '/* pbr stub - calls f_schlick( */';
40
45
  const unlitWgsl = '/* unlit stub */';
41
- const tonemapWgsl = '/* tonemap stub - struct TonemapParams { exposure: f32 }; */';
46
+ const tonemapWgsl = OUTPUT_TRANSFORM_WGSL;
42
47
  const standardPbrVariant = (transmissionAvailable: boolean, definesKey: string) => ({
43
48
  definesKey,
44
49
  defines: {
@@ -247,7 +252,6 @@ describe('GPU-driven production projection', () => {
247
252
  meshes,
248
253
  viewBindGroupLayout: viewLayout,
249
254
  meshResidencyEpoch: 1,
250
- hdrp: false,
251
255
  })
252
256
  .unwrap();
253
257
  expect(prepared).toBeDefined();
@@ -272,7 +276,6 @@ describe('GPU-driven production projection', () => {
272
276
  meshes,
273
277
  viewBindGroupLayout: viewLayout,
274
278
  meshResidencyEpoch: 1,
275
- hdrp: false,
276
279
  })
277
280
  .unwrap();
278
281
  expect(stable?.topologySignature).toBe(prepared.topologySignature);
@@ -341,7 +344,6 @@ describe('GPU-driven production projection', () => {
341
344
  meshes,
342
345
  viewBindGroupLayout: viewLayout,
343
346
  meshResidencyEpoch: 1,
344
- hdrp: false,
345
347
  })
346
348
  .unwrap();
347
349
  expect(replacementPrepared?.topologySignature).not.toBe(prepared.topologySignature);
@@ -351,7 +353,7 @@ describe('GPU-driven production projection', () => {
351
353
  availability.scene.dispose();
352
354
  });
353
355
 
354
- it('activates inside the renderer-owned URP graph and removes the CPU forward draw', async () => {
356
+ it('activates inside the renderer-owned clustered Standard graph and removes the CPU draw', async () => {
355
357
  const canvas = {
356
358
  width: 1,
357
359
  height: 1,
@@ -380,6 +382,12 @@ describe('GPU-driven production projection', () => {
380
382
  { component: MeshRenderer, data: {} },
381
383
  )
382
384
  .unwrap();
385
+ world
386
+ .spawn(
387
+ { component: Transform, data: { pos: [0, 0, 1] } },
388
+ { component: PointLight, data: {} },
389
+ )
390
+ .unwrap();
383
391
 
384
392
  for (let frame = 0; frame < 4; frame += 1) {
385
393
  world.update(1 / 60).unwrap();
@@ -391,18 +399,9 @@ describe('GPU-driven production projection', () => {
391
399
  world.update(1 / 60).unwrap();
392
400
  expect(renderer.draw([world], { cameraOwner: 0, resourceOwner: 0 }).ok).toBe(true);
393
401
  expect(renderer.perFramePassNames).toEqual(
394
- expect.arrayContaining([
395
- 'gpu-driven.view-reset',
396
- 'gpu-driven.frustum-compact',
397
- 'gpu-driven.finalize-indirect',
398
- 'main',
399
- ]),
402
+ expect.arrayContaining(['cluster-membership-producer', 'main', 'output-transform']),
400
403
  );
401
- expect(renderer.perFramePassNames).toContain('output-transform');
402
- // Storage-buffer LDR frames keep the scene linear until the one output
403
- // transform writes the encoded surface; the GPU-driven indirect raster
404
- // draw and that fullscreen pass are the only draws in this topology.
405
- expect(device.totalDrawCount).toBe(2);
404
+ expect(device.totalDrawCount).toBeGreaterThan(0);
406
405
  expect(renderer.renderScene).toMatchObject({
407
406
  worldEntitiesScanned: 0,
408
407
  fullRebuilds: 1,
@@ -192,6 +192,7 @@ describe('GpuDrivenView graph projection', () => {
192
192
  const frameState = {
193
193
  compiledFrameGraph: null,
194
194
  compiledFrameGraphTopologyKey: null,
195
+ compiledFrameGraphGeneration: 0,
195
196
  retiredCompiledFrameGraphs: new Set(),
196
197
  activePipeline,
197
198
  installedPipelineConfig: undefined,
@@ -209,14 +210,14 @@ describe('GpuDrivenView graph projection', () => {
209
210
  pointShadow: [],
210
211
  spot: [],
211
212
  } as unknown as ExtractedLights;
212
- const compile = (prepared: PreparedGpuDrivenFrame) =>
213
+ const compile = (prepared: PreparedGpuDrivenFrame, width = 1) =>
213
214
  ensureCompiledFrameGraph(
214
215
  internals,
215
216
  frameState,
216
217
  pipelineState,
217
218
  makeZeroCameraFallbackSnapshot(),
218
219
  lights,
219
- 1,
220
+ width,
220
221
  1,
221
222
  undefined,
222
223
  prepared,
@@ -243,6 +244,7 @@ describe('GpuDrivenView graph projection', () => {
243
244
  const compiledPrevious = compile(preparedFrame(view));
244
245
  expect(compiledPrevious).not.toBeNull();
245
246
  if (compiledPrevious === null) return;
247
+ expect(frameState.compiledFrameGraphGeneration).toBe(1);
246
248
 
247
249
  Object.defineProperty(device, 'surfaceViewFormats', {
248
250
  configurable: true,
@@ -269,7 +271,8 @@ describe('GpuDrivenView graph projection', () => {
269
271
 
270
272
  expect(view.inspect()).toMatchObject({ candidateCount: 2, candidateCapacity: 2 });
271
273
  forceCompileFailure = true;
272
- expect(compile(preparedFrame(view))).toBe(compiledPrevious);
274
+ expect(compile(preparedFrame(view), 2)).toBe(compiledPrevious);
275
+ expect(frameState.compiledFrameGraphGeneration).toBe(1);
273
276
  expect(errors).toHaveLength(1);
274
277
  expect(destroyBuffer.mock.calls.some(([buffer]) => buffer === previousVisible)).toBe(false);
275
278
  const encoder = device.createCommandEncoder({ label: 'gpu-driven-view-lkg' }).unwrap();
@@ -277,13 +280,18 @@ describe('GpuDrivenView graph projection', () => {
277
280
 
278
281
  forceCompileFailure = false;
279
282
  const retirePrevious = vi.spyOn(compiledPrevious, 'retire');
280
- const acceptedGraph = compile(preparedFrame(view));
283
+ const acceptedGraph = compile(preparedFrame(view), 2);
281
284
  expect(acceptedGraph).not.toBe(compiledPrevious);
285
+ expect(frameState.compiledFrameGraphGeneration).toBe(2);
282
286
  expect(retirePrevious).toHaveBeenCalledOnce();
283
287
  await device.queue.onSubmittedWorkDone();
284
288
  await Promise.resolve();
285
289
  expect(destroyBuffer.mock.calls.some(([buffer]) => buffer === previousVisible)).toBe(true);
286
290
 
291
+ const returnedToPreviousTopology = compile(preparedFrame(view), 1);
292
+ expect(returnedToPreviousTopology).not.toBe(acceptedGraph);
293
+ expect(frameState.compiledFrameGraphGeneration).toBe(3);
294
+
287
295
  view.dispose();
288
296
  sceneAvailability.scene.dispose();
289
297
  });
@@ -0,0 +1,281 @@
1
+ import { World } from '@forgeax/engine-ecs';
2
+ import { rhi } from '@forgeax/engine-rhi-webgpu';
3
+ import { Transform } from '@forgeax/engine-scene';
4
+ import { createRenderer, exposeRenderer } from '../assembly/factory.js';
5
+ import { Camera } from '../components/camera.js';
6
+ import type { GpuPassTimingFrame } from '../record/gpu-pass-timing/contract.js';
7
+ import type { FrameReceipt, Renderer, RenderWorldLease } from '../render-contract.js';
8
+
9
+ export interface BrowserGpuPassTimingBenchDraw {
10
+ readonly token: number;
11
+ readonly frameDurationMicroseconds: number;
12
+ }
13
+
14
+ export interface BrowserGpuPassTimingBenchObservation {
15
+ readonly status: 'complete' | 'partial' | 'failed' | 'unavailable';
16
+ readonly frame?: GpuPassTimingFrame | undefined;
17
+ }
18
+
19
+ export interface BrowserGpuPassTimingBenchController {
20
+ readonly supported: boolean;
21
+ readonly realGpu: boolean;
22
+ readonly backend: string;
23
+ readonly frameGeneration: string;
24
+ readonly draw: (timingEnabled: boolean) => BrowserGpuPassTimingBenchDraw;
25
+ readonly observe: (token: number) => Promise<BrowserGpuPassTimingBenchObservation>;
26
+ readonly dispose: () => Promise<void>;
27
+ }
28
+
29
+ interface BrowserTimingFixture {
30
+ readonly renderer: Renderer;
31
+ readonly world: World;
32
+ readonly lease: RenderWorldLease;
33
+ readonly canvas: HTMLCanvasElement;
34
+ }
35
+
36
+ interface BrowserPendingFrame {
37
+ readonly fixture: BrowserTimingFixture;
38
+ readonly receipt: FrameReceipt;
39
+ readonly timingEnabled: boolean;
40
+ }
41
+
42
+ function createTimingCamera(world: World): void {
43
+ world
44
+ .spawn(
45
+ { component: Transform, data: { pos: [0, 0, 5], quat: [0, 0, 0, 1], scale: [1, 1, 1] } },
46
+ {
47
+ component: Camera,
48
+ data: { fov: Math.PI / 4, aspect: 1, near: 0.1, far: 100 },
49
+ },
50
+ )
51
+ .unwrap();
52
+ world.update(1 / 60).unwrap();
53
+ }
54
+
55
+ async function createBrowserTimingFixture(timingEnabled: boolean): Promise<BrowserTimingFixture> {
56
+ const canvas = document.createElement('canvas');
57
+ canvas.width = 32;
58
+ canvas.height = 32;
59
+ canvas.style.width = '32px';
60
+ canvas.style.height = '32px';
61
+ document.body.append(canvas);
62
+ let renderer: Renderer | undefined;
63
+ try {
64
+ const implementation = await createRenderer(
65
+ canvas,
66
+ timingEnabled
67
+ ? {
68
+ rhi,
69
+ gpuPassTiming: { maxPassesPerFrame: 64, maxFramesInFlight: 2, retentionFrames: 8 },
70
+ }
71
+ : { rhi },
72
+ { shaderManifestUrl: '/shaders/manifest.json' },
73
+ );
74
+ const ready = await implementation.initialization;
75
+ if (!ready.ok) throw new Error(`${ready.error.code}: ${ready.error.hint}`);
76
+ renderer = exposeRenderer(implementation);
77
+ const world = new World();
78
+ const attached = renderer.attach(world);
79
+ if (!attached.ok) throw new Error(attached.error.hint);
80
+ createTimingCamera(world);
81
+ return { renderer, world, lease: attached.value, canvas };
82
+ } catch (error) {
83
+ if (renderer !== undefined) await renderer.dispose();
84
+ canvas.remove();
85
+ throw error;
86
+ }
87
+ }
88
+
89
+ function drawTimingFrame(fixture: BrowserTimingFixture): {
90
+ readonly receipt: FrameReceipt;
91
+ readonly frameDurationMicroseconds: number;
92
+ } {
93
+ const startedAt = performance.now();
94
+ fixture.world.update(1 / 60).unwrap();
95
+ const drawn = fixture.renderer.draw({
96
+ leases: [fixture.lease],
97
+ camera: { lease: fixture.lease },
98
+ environment: { lease: fixture.lease },
99
+ });
100
+ if (!drawn.ok) throw new Error(drawn.error.hint);
101
+ return {
102
+ receipt: drawn.value,
103
+ frameDurationMicroseconds: Math.max((performance.now() - startedAt) * 1000, 0.001),
104
+ };
105
+ }
106
+
107
+ function adapterString(value: unknown): string | undefined {
108
+ return typeof value === 'string' && value.length > 0 ? value : undefined;
109
+ }
110
+
111
+ export async function createBrowserGpuPassTimingBenchController(): Promise<BrowserGpuPassTimingBenchController> {
112
+ const offFixture = await createBrowserTimingFixture(false);
113
+ let onFixture: BrowserTimingFixture | undefined;
114
+ try {
115
+ const timingFixture = await createBrowserTimingFixture(true);
116
+ onFixture = timingFixture;
117
+ const adapter = await navigator.gpu?.requestAdapter();
118
+ const adapterInfo = adapter?.info as Record<string, unknown> | undefined;
119
+ const adapterParts = [
120
+ adapterInfo?.vendor,
121
+ adapterInfo?.architecture,
122
+ adapterInfo?.device,
123
+ adapterInfo?.description,
124
+ ]
125
+ .map(adapterString)
126
+ .filter((part): part is string => part !== undefined);
127
+ const backend = adapterParts.join('/') || 'browser-adapter-unknown';
128
+ const realGpu =
129
+ adapter !== null &&
130
+ adapter !== undefined &&
131
+ !/swiftshader|llvmpipe|software|basic renderer/i.test(backend);
132
+ const inspection = timingFixture.renderer.inspect();
133
+ const pending = new Map<number, BrowserPendingFrame>();
134
+ let nextToken = 1;
135
+ let disposed = false;
136
+
137
+ return {
138
+ supported: inspection.capabilities.timestampQuery,
139
+ realGpu,
140
+ backend,
141
+ frameGeneration: `device-${inspection.frame.deviceGeneration}:graph-standard`,
142
+ draw: (timingEnabled) => {
143
+ if (disposed) throw new Error('browser GPU pass timing controller is disposed');
144
+ const fixture = timingEnabled ? timingFixture : offFixture;
145
+ const drawn = drawTimingFrame(fixture);
146
+ const token = nextToken;
147
+ nextToken += 1;
148
+ pending.set(token, {
149
+ fixture,
150
+ receipt: drawn.receipt,
151
+ timingEnabled,
152
+ });
153
+ return { token, frameDurationMicroseconds: drawn.frameDurationMicroseconds };
154
+ },
155
+ observe: async (token) => {
156
+ const frame = pending.get(token);
157
+ if (frame === undefined) throw new Error(`unknown browser timing frame token: ${token}`);
158
+ pending.delete(token);
159
+ const observed = await frame.fixture.renderer.observe(frame.receipt, {
160
+ include: frame.timingEnabled ? ['timings'] : [],
161
+ });
162
+ if (!observed.ok) return { status: 'failed' };
163
+ if (!frame.timingEnabled) return { status: 'complete' };
164
+ switch (observed.value.timings?.status) {
165
+ case 'complete':
166
+ return { status: 'complete', frame: observed.value.timings.frame };
167
+ case 'partial':
168
+ return { status: 'partial', frame: observed.value.timings.frame };
169
+ case 'unavailable':
170
+ return { status: 'unavailable' };
171
+ case 'failed':
172
+ case undefined:
173
+ return { status: 'failed' };
174
+ }
175
+ },
176
+ dispose: async () => {
177
+ if (disposed) return;
178
+ disposed = true;
179
+ pending.clear();
180
+ let firstError: unknown;
181
+ for (const fixture of [offFixture, timingFixture]) {
182
+ const disposedRenderer = await fixture.renderer.dispose();
183
+ fixture.canvas.remove();
184
+ if (!disposedRenderer.ok && firstError === undefined) firstError = disposedRenderer.error;
185
+ }
186
+ if (firstError !== undefined)
187
+ throw new Error(`browser benchmark dispose failed: ${String(firstError)}`);
188
+ },
189
+ };
190
+ } catch (error) {
191
+ await offFixture.renderer.dispose();
192
+ offFixture.canvas.remove();
193
+ if (onFixture !== undefined) {
194
+ await onFixture.renderer.dispose();
195
+ onFixture.canvas.remove();
196
+ }
197
+ throw error;
198
+ }
199
+ }
200
+
201
+ export async function runBrowserGpuPassTiming(
202
+ options: { readonly suppressRaster?: boolean } = {},
203
+ ): Promise<{
204
+ readonly supported: boolean;
205
+ readonly frames: number;
206
+ readonly unavailable: boolean;
207
+ readonly measuredPasses: number;
208
+ readonly visible: boolean;
209
+ readonly rasterSuppressed: boolean;
210
+ }> {
211
+ const canvas = document.createElement('canvas');
212
+ canvas.width = 64;
213
+ canvas.height = 64;
214
+ document.body.append(canvas);
215
+ if (options.suppressRaster === true) canvas.style.visibility = 'hidden';
216
+ const implementation = await createRenderer(
217
+ canvas,
218
+ { rhi, gpuPassTiming: { maxPassesPerFrame: 64, maxFramesInFlight: 2, retentionFrames: 8 } },
219
+ { shaderManifestUrl: '/shaders/manifest.json' },
220
+ );
221
+ const ready = await implementation.initialization;
222
+ if (!ready.ok) throw new Error(`${ready.error.code}: ${ready.error.hint}`);
223
+ const renderer = exposeRenderer(implementation);
224
+ const world = new World();
225
+ const attached = renderer.attach(world);
226
+ if (!attached.ok) throw new Error(attached.error.hint);
227
+ world
228
+ .spawn(
229
+ { component: Transform, data: { pos: [0, 0, 5], quat: [0, 0, 0, 1], scale: [1, 1, 1] } },
230
+ {
231
+ component: Camera,
232
+ data: { fov: Math.PI / 4, aspect: 1, near: 0.1, far: 100 },
233
+ },
234
+ )
235
+ .unwrap();
236
+ world.update(1 / 60).unwrap();
237
+ const supported = renderer.inspect().capabilities.timestampQuery;
238
+ let frames = 0;
239
+ let unavailable = false;
240
+ let measuredPasses = 0;
241
+ try {
242
+ for (let index = 0; index < 3; index += 1) {
243
+ world.update(1 / 60).unwrap();
244
+ const drawn = renderer.draw({
245
+ leases: [attached.value],
246
+ camera: { lease: attached.value },
247
+ environment: { lease: attached.value },
248
+ });
249
+ if (!drawn.ok) continue;
250
+ const observed = await renderer.observe(drawn.value, { include: ['timings'] });
251
+ if (!observed.ok) continue;
252
+ frames += 1;
253
+ if (observed.value.timings?.status === 'unavailable') {
254
+ unavailable = true;
255
+ if ('tick' in observed.value.timings || 'durationNanoseconds' in observed.value.timings) {
256
+ throw new Error('unavailable timing observation exposed numeric GPU facts');
257
+ }
258
+ continue;
259
+ }
260
+ if (
261
+ observed.value.timings?.status === 'complete' ||
262
+ observed.value.timings?.status === 'partial'
263
+ ) {
264
+ measuredPasses += observed.value.timings.frame.passes.filter(
265
+ (pass) => pass.status === 'measured',
266
+ ).length;
267
+ }
268
+ }
269
+ } finally {
270
+ await renderer.dispose();
271
+ canvas.remove();
272
+ }
273
+ return {
274
+ supported,
275
+ frames,
276
+ unavailable,
277
+ measuredPasses,
278
+ visible: !document.body.contains(canvas),
279
+ rasterSuppressed: options.suppressRaster === true,
280
+ };
281
+ }
@@ -0,0 +1,80 @@
1
+ import type { Buffer, QuerySet, RhiCommandEncoder, RhiDevice } from '@forgeax/engine-rhi';
2
+ import { ok } from '@forgeax/engine-types';
3
+ import { describe, expect, it, vi } from 'vitest';
4
+ import {
5
+ createGpuPassTimingSession,
6
+ type GpuPassTimingFrameIdentity,
7
+ } from '../record/gpu-pass-timing/session.js';
8
+
9
+ function makeDevice() {
10
+ const device = {
11
+ caps: {
12
+ backendKind: 'webgpu',
13
+ timestampQuery: true,
14
+ timestampPeriodNanoseconds: 1,
15
+ },
16
+ createQuerySet: vi.fn(() => ok({} as QuerySet)),
17
+ createBuffer: vi.fn(() => ok({} as Buffer)),
18
+ destroyQuerySet: vi.fn(() => ok(undefined)),
19
+ destroyBuffer: vi.fn(() => ok(undefined)),
20
+ queue: { onSubmittedWorkDone: vi.fn(async () => undefined) },
21
+ } as unknown as RhiDevice;
22
+ return device;
23
+ }
24
+
25
+ const identity: GpuPassTimingFrameIdentity = {
26
+ frameId: 3,
27
+ deviceGeneration: 1,
28
+ graphGeneration: 22,
29
+ };
30
+
31
+ describe('GPU pass timing coexistence with producer-owned timestamps', () => {
32
+ it('keeps the producer timestamp owner and reports generic timing as unmeasured', async () => {
33
+ const device = makeDevice();
34
+ const session = createGpuPassTimingSession(device, {
35
+ maxPassesPerFrame: 2,
36
+ maxFramesInFlight: 1,
37
+ retentionFrames: 8,
38
+ }).unwrap();
39
+ const capture = session.beginFrame(identity).unwrap();
40
+ const producerPass = {
41
+ passName: 'cluster-membership-producer',
42
+ passKind: 'compute' as const,
43
+ executionIndex: 0,
44
+ };
45
+ capture.recordPass(producerPass);
46
+ capture.markOwnerConflict(producerPass);
47
+
48
+ expect(capture.timestampWrites(producerPass)).toBeUndefined();
49
+ expect(capture.snapshot()).toMatchObject({
50
+ measuredPassCount: 0,
51
+ passes: [
52
+ {
53
+ passName: 'cluster-membership-producer',
54
+ status: 'unmeasured',
55
+ reason: { code: 'timestamp-owner-conflict' },
56
+ },
57
+ ],
58
+ });
59
+ const commandEncoder = {
60
+ encodeEmptyComputePass: vi.fn(),
61
+ resolveQuerySet: vi.fn(() => ok(undefined)),
62
+ copyBufferToBuffer: vi.fn(),
63
+ } as unknown as RhiCommandEncoder;
64
+ expect(capture.encodeTail(commandEncoder)).toMatchObject({ ok: true });
65
+ expect(commandEncoder.resolveQuerySet).not.toHaveBeenCalled();
66
+ expect(commandEncoder.copyBufferToBuffer).not.toHaveBeenCalled();
67
+ capture.markSubmitted();
68
+ expect(device.queue.onSubmittedWorkDone).not.toHaveBeenCalled();
69
+ await expect(capture.observe()).resolves.toMatchObject({
70
+ ok: true,
71
+ value: {
72
+ frameId: 3,
73
+ deviceGeneration: 1,
74
+ graphGeneration: 22,
75
+ passes: [{ passName: 'cluster-membership-producer', executionIndex: 0 }],
76
+ },
77
+ });
78
+ session.dispose();
79
+ });
80
+ });