@forgeax/engine-render 0.1.32 → 0.1.34

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 (624) hide show
  1. package/README.md +1374 -142
  2. package/dist/assembly/analytic-fog-registration.d.ts +3 -0
  3. package/dist/assembly/analytic-fog-registration.d.ts.map +1 -0
  4. package/dist/assembly/backend-contract.d.ts +2 -0
  5. package/dist/assembly/backend-contract.d.ts.map +1 -1
  6. package/dist/assembly/barrel-distortion-inspection.d.ts +3 -0
  7. package/dist/assembly/barrel-distortion-inspection.d.ts.map +1 -0
  8. package/dist/assembly/canvas-draw-guard.d.ts +3 -0
  9. package/dist/assembly/canvas-draw-guard.d.ts.map +1 -0
  10. package/dist/assembly/cloud-shadow-entry.d.ts +4 -0
  11. package/dist/assembly/cloud-shadow-entry.d.ts.map +1 -0
  12. package/dist/assembly/dynamic-geometry-host.d.ts +4 -1
  13. package/dist/assembly/dynamic-geometry-host.d.ts.map +1 -1
  14. package/dist/assembly/dynamic-geometry-runtime.d.ts +1 -1
  15. package/dist/assembly/dynamic-geometry-runtime.d.ts.map +1 -1
  16. package/dist/assembly/factory.d.ts +1 -1
  17. package/dist/assembly/factory.d.ts.map +1 -1
  18. package/dist/assembly/fxaa-registration.d.ts +4 -0
  19. package/dist/assembly/fxaa-registration.d.ts.map +1 -0
  20. package/dist/assembly/host-contract.d.ts +3 -3
  21. package/dist/assembly/host-contract.d.ts.map +1 -1
  22. package/dist/assembly/lod-observation.d.ts +4 -0
  23. package/dist/assembly/lod-observation.d.ts.map +1 -0
  24. package/dist/assembly/material/assembly.d.ts.map +1 -1
  25. package/dist/assembly/material/pipeline-helpers.d.ts +5 -0
  26. package/dist/assembly/material/pipeline-helpers.d.ts.map +1 -1
  27. package/dist/assembly/material-shader-policy.d.ts +16 -2
  28. package/dist/assembly/material-shader-policy.d.ts.map +1 -1
  29. package/dist/assembly/material-shader-prewarm.d.ts.map +1 -1
  30. package/dist/assembly/motion-blur-registration.d.ts +3 -0
  31. package/dist/assembly/motion-blur-registration.d.ts.map +1 -0
  32. package/dist/assembly/recovery/device-loss-fanout.d.ts.map +1 -1
  33. package/dist/assembly/recovery/renderer-recover.d.ts +2 -0
  34. package/dist/assembly/recovery/renderer-recover.d.ts.map +1 -1
  35. package/dist/assembly/render-feature-post-processes.d.ts +6 -0
  36. package/dist/assembly/render-feature-post-processes.d.ts.map +1 -0
  37. package/dist/assembly/renderer-facade.d.ts.map +1 -1
  38. package/dist/assembly/renderer-feature-inspection.d.ts +1 -1
  39. package/dist/assembly/renderer-feature-inspection.d.ts.map +1 -1
  40. package/dist/assembly/renderer-frame-transaction.d.ts +12 -0
  41. package/dist/assembly/renderer-frame-transaction.d.ts.map +1 -1
  42. package/dist/assembly/renderer-inspection.d.ts +11 -0
  43. package/dist/assembly/renderer-inspection.d.ts.map +1 -1
  44. package/dist/assembly/temporal-post-process-prewarm.d.ts +13 -0
  45. package/dist/assembly/temporal-post-process-prewarm.d.ts.map +1 -0
  46. package/dist/assembly/webgpu-pbr-ready.d.ts +1 -0
  47. package/dist/assembly/webgpu-pbr-ready.d.ts.map +1 -1
  48. package/dist/assembly/webgpu-ready-contract.d.ts +4 -3
  49. package/dist/assembly/webgpu-ready-contract.d.ts.map +1 -1
  50. package/dist/assembly/webgpu-ready-per-pass.d.ts +39 -0
  51. package/dist/assembly/webgpu-ready-per-pass.d.ts.map +1 -0
  52. package/dist/assembly/webgpu-ready.d.ts +2 -2
  53. package/dist/assembly/webgpu-ready.d.ts.map +1 -1
  54. package/dist/assembly/webgpu-renderer-bootstrap.d.ts.map +1 -1
  55. package/dist/assembly/webgpu-renderer-observation-owner.d.ts +35 -0
  56. package/dist/assembly/webgpu-renderer-observation-owner.d.ts.map +1 -0
  57. package/dist/assembly/webgpu-renderer.d.ts +1 -1
  58. package/dist/assembly/webgpu-renderer.d.ts.map +1 -1
  59. package/dist/authoring.mjs +1 -1
  60. package/dist/barrel-distortion.d.ts +47 -0
  61. package/dist/barrel-distortion.d.ts.map +1 -0
  62. package/dist/bloom-admission.d.ts +13 -0
  63. package/dist/bloom-admission.d.ts.map +1 -0
  64. package/dist/{chunk-PA5DRJNI.mjs → chunk-ELJFJ2BU.mjs} +2 -2
  65. package/dist/chunk-ELJFJ2BU.mjs.map +1 -0
  66. package/dist/{chunk-JCIRYQ4V.mjs → chunk-HEB564WF.mjs} +10 -4
  67. package/dist/chunk-HEB564WF.mjs.map +1 -0
  68. package/dist/{chunk-JRIPCMYC.mjs → chunk-MKDIT2EM.mjs} +1584 -375
  69. package/dist/chunk-MKDIT2EM.mjs.map +1 -0
  70. package/dist/{chunk-S665NJNF.mjs → chunk-OCDLEI6I.mjs} +3 -3
  71. package/dist/{chunk-S665NJNF.mjs.map → chunk-OCDLEI6I.mjs.map} +1 -1
  72. package/dist/chunk-ODZWR6JQ.mjs +4927 -0
  73. package/dist/chunk-ODZWR6JQ.mjs.map +1 -0
  74. package/dist/{chunk-DRR7D3KX.mjs → chunk-SHFUFJ5N.mjs} +192 -18
  75. package/dist/chunk-SHFUFJ5N.mjs.map +1 -0
  76. package/dist/{chunk-4NNZQCL3.mjs → chunk-UI5SFODX.mjs} +223 -277
  77. package/dist/chunk-UI5SFODX.mjs.map +1 -0
  78. package/dist/{chunk-GWCEMDVR.mjs → chunk-UINHLCZA.mjs} +787 -80
  79. package/dist/chunk-UINHLCZA.mjs.map +1 -0
  80. package/dist/{chunk-6TFBXLRJ.mjs → chunk-WEX45HKH.mjs} +222 -337
  81. package/dist/chunk-WEX45HKH.mjs.map +1 -0
  82. package/dist/cloud/density.d.ts +55 -0
  83. package/dist/cloud/density.d.ts.map +1 -0
  84. package/dist/cloud/extract.d.ts +25 -0
  85. package/dist/cloud/extract.d.ts.map +1 -0
  86. package/dist/cloud/feature.d.ts +57 -0
  87. package/dist/cloud/feature.d.ts.map +1 -0
  88. package/dist/cloud/frame-context.d.ts +8 -0
  89. package/dist/cloud/frame-context.d.ts.map +1 -0
  90. package/dist/cloud/inspection.d.ts +59 -0
  91. package/dist/cloud/inspection.d.ts.map +1 -0
  92. package/dist/cloud/optics.d.ts +56 -0
  93. package/dist/cloud/optics.d.ts.map +1 -0
  94. package/dist/cloud/parameters.d.ts +73 -0
  95. package/dist/cloud/parameters.d.ts.map +1 -0
  96. package/dist/cloud/resources.d.ts +60 -0
  97. package/dist/cloud/resources.d.ts.map +1 -0
  98. package/dist/cloud/shadow.d.ts +42 -0
  99. package/dist/cloud/shadow.d.ts.map +1 -0
  100. package/dist/cloud/temporal.d.ts +74 -0
  101. package/dist/cloud/temporal.d.ts.map +1 -0
  102. package/dist/components/atmosphere.d.ts +2 -0
  103. package/dist/components/atmosphere.d.ts.map +1 -1
  104. package/dist/components/barrel-distortion.d.ts +13 -0
  105. package/dist/components/barrel-distortion.d.ts.map +1 -0
  106. package/dist/components/camera.d.ts +20 -4
  107. package/dist/components/camera.d.ts.map +1 -1
  108. package/dist/components/cloud-layer.d.ts +29 -0
  109. package/dist/components/cloud-layer.d.ts.map +1 -0
  110. package/dist/components/index.d.ts +2 -0
  111. package/dist/components/index.d.ts.map +1 -1
  112. package/dist/components/instances.d.ts +20 -6
  113. package/dist/components/instances.d.ts.map +1 -1
  114. package/dist/components/mesh-filter.d.ts +6 -5
  115. package/dist/components/mesh-filter.d.ts.map +1 -1
  116. package/dist/components/motion-blur.d.ts +1 -0
  117. package/dist/components/motion-blur.d.ts.map +1 -1
  118. package/dist/construct-renderer.d.ts +1 -1
  119. package/dist/construct-renderer.d.ts.map +1 -1
  120. package/dist/construct-renderer.mjs +9751 -3528
  121. package/dist/construct-renderer.mjs.map +1 -1
  122. package/dist/device/gpu-residency.d.ts +6 -0
  123. package/dist/device/gpu-residency.d.ts.map +1 -1
  124. package/dist/dynamic-geometry.d.ts +5 -1
  125. package/dist/dynamic-geometry.d.ts.map +1 -1
  126. package/dist/environment/analytic-fog.d.ts +6 -0
  127. package/dist/environment/analytic-fog.d.ts.map +1 -0
  128. package/dist/errors/cloud.d.ts +59 -0
  129. package/dist/errors/cloud.d.ts.map +1 -0
  130. package/dist/errors/render.d.ts +23 -5
  131. package/dist/errors/render.d.ts.map +1 -1
  132. package/dist/examples/single-layer-medium-surface.d.ts +47 -0
  133. package/dist/examples/single-layer-medium-surface.d.ts.map +1 -0
  134. package/dist/extract/camera.d.ts.map +1 -1
  135. package/dist/extract/contracts.d.ts +12 -1
  136. package/dist/extract/contracts.d.ts.map +1 -1
  137. package/dist/extract/environment.d.ts +2 -0
  138. package/dist/extract/environment.d.ts.map +1 -1
  139. package/dist/features/barrel-distortion.d.ts +29 -0
  140. package/dist/features/barrel-distortion.d.ts.map +1 -0
  141. package/dist/features/error-reporting.d.ts +17 -0
  142. package/dist/features/error-reporting.d.ts.map +1 -0
  143. package/dist/features/host.d.ts +15 -0
  144. package/dist/features/host.d.ts.map +1 -1
  145. package/dist/features/motion-blur/motion-blur-feature.d.ts +20 -2
  146. package/dist/features/motion-blur/motion-blur-feature.d.ts.map +1 -1
  147. package/dist/features/motion-blur/motion-blur-params.d.ts +21 -0
  148. package/dist/features/motion-blur/motion-blur-params.d.ts.map +1 -1
  149. package/dist/features/motion-blur/motion-blur-runtime.d.ts +54 -0
  150. package/dist/features/motion-blur/motion-blur-runtime.d.ts.map +1 -0
  151. package/dist/features/plan.d.ts +15 -4
  152. package/dist/features/plan.d.ts.map +1 -1
  153. package/dist/features/prepared-gpu-work.d.ts +9 -5
  154. package/dist/features/prepared-gpu-work.d.ts.map +1 -1
  155. package/dist/features/render-graph-compute.d.ts.map +1 -1
  156. package/dist/features/render-graph-contribution.d.ts +5 -0
  157. package/dist/features/render-graph-contribution.d.ts.map +1 -1
  158. package/dist/features/render-graph-raster.d.ts +1 -0
  159. package/dist/features/render-graph-raster.d.ts.map +1 -1
  160. package/dist/features/targets.d.ts +2 -0
  161. package/dist/features/targets.d.ts.map +1 -1
  162. package/dist/features/types.d.ts +44 -0
  163. package/dist/features/types.d.ts.map +1 -1
  164. package/dist/fullscreen-post-process-pass.d.ts +15 -7
  165. package/dist/fullscreen-post-process-pass.d.ts.map +1 -1
  166. package/dist/gpu-dirty-ranges.d.ts +11 -0
  167. package/dist/gpu-dirty-ranges.d.ts.map +1 -0
  168. package/dist/gpu-driven/batch-topology.d.ts +17 -0
  169. package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
  170. package/dist/gpu-driven/material-artifacts.d.ts +6 -0
  171. package/dist/gpu-driven/material-artifacts.d.ts.map +1 -1
  172. package/dist/gpu-driven/prepared-draw.d.ts +7 -0
  173. package/dist/gpu-driven/prepared-draw.d.ts.map +1 -1
  174. package/dist/gpu-driven/production-raster-material.d.ts +178 -0
  175. package/dist/gpu-driven/production-raster-material.d.ts.map +1 -0
  176. package/dist/gpu-driven/production-raster.d.ts +50 -4
  177. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  178. package/dist/gpu-driven/resource-allocation.d.ts +31 -0
  179. package/dist/gpu-driven/resource-allocation.d.ts.map +1 -0
  180. package/dist/gpu-driven/shadow-ownership.d.ts +14 -0
  181. package/dist/gpu-driven/shadow-ownership.d.ts.map +1 -0
  182. package/dist/gpu-driven/view-gpu.d.ts +35 -2
  183. package/dist/gpu-driven/view-gpu.d.ts.map +1 -1
  184. package/dist/gpu-scene.d.ts +11 -1
  185. package/dist/gpu-scene.d.ts.map +1 -1
  186. package/dist/hdrp-buffers.d.ts +2 -2
  187. package/dist/hdrp-buffers.d.ts.map +1 -1
  188. package/dist/ibl/skylight-bind-group.d.ts +10 -1
  189. package/dist/ibl/skylight-bind-group.d.ts.map +1 -1
  190. package/dist/index.d.ts +31 -8
  191. package/dist/index.d.ts.map +1 -1
  192. package/dist/index.mjs +258 -8
  193. package/dist/index.mjs.map +1 -1
  194. package/dist/inspection-types.d.ts +178 -3
  195. package/dist/inspection-types.d.ts.map +1 -1
  196. package/dist/instance-buffer-cache.d.ts +4 -4
  197. package/dist/instance-buffer-cache.d.ts.map +1 -1
  198. package/dist/instances.d.ts +49 -94
  199. package/dist/instances.d.ts.map +1 -1
  200. package/dist/internal.mjs +7 -7
  201. package/dist/pbr-pipeline.d.ts +19 -1
  202. package/dist/pbr-pipeline.d.ts.map +1 -1
  203. package/dist/pipeline/single-layer-medium-passes.d.ts +73 -0
  204. package/dist/pipeline/single-layer-medium-passes.d.ts.map +1 -0
  205. package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
  206. package/dist/pipeline/standard-output/graph.d.ts.map +1 -1
  207. package/dist/pipeline/standard-output/types.d.ts +4 -2
  208. package/dist/pipeline/standard-output/types.d.ts.map +1 -1
  209. package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
  210. package/dist/pipeline/standard-post.d.ts +28 -19
  211. package/dist/pipeline/standard-post.d.ts.map +1 -1
  212. package/dist/pipeline-builder.d.ts +1 -1
  213. package/dist/pipeline-builder.d.ts.map +1 -1
  214. package/dist/pipeline-spec.d.ts +2 -2
  215. package/dist/plugin.d.ts.map +1 -1
  216. package/dist/prepare/prepared-graphics-resolver.d.ts +2 -0
  217. package/dist/prepare/prepared-graphics-resolver.d.ts.map +1 -1
  218. package/dist/record/barrel-distortion-frame.d.ts +15 -0
  219. package/dist/record/barrel-distortion-frame.d.ts.map +1 -0
  220. package/dist/record/dynamic-geometry-consumption.d.ts +10 -2
  221. package/dist/record/dynamic-geometry-consumption.d.ts.map +1 -1
  222. package/dist/record/fold-buckets.d.ts +95 -0
  223. package/dist/record/fold-buckets.d.ts.map +1 -0
  224. package/dist/record/fold-instance-buffer.d.ts +6 -0
  225. package/dist/record/fold-instance-buffer.d.ts.map +1 -0
  226. package/dist/record/frame-lighting.d.ts +1 -0
  227. package/dist/record/frame-lighting.d.ts.map +1 -1
  228. package/dist/record/frame-snapshot.d.ts +40 -3
  229. package/dist/record/frame-snapshot.d.ts.map +1 -1
  230. package/dist/record/frame-targets.d.ts.map +1 -1
  231. package/dist/record/frame.d.ts +14 -62
  232. package/dist/record/frame.d.ts.map +1 -1
  233. package/dist/record/gpu-draw-receipts.d.ts +11 -0
  234. package/dist/record/gpu-draw-receipts.d.ts.map +1 -0
  235. package/dist/record/gpu-timing.d.ts +8 -0
  236. package/dist/record/gpu-timing.d.ts.map +1 -1
  237. package/dist/record/main-pass-geometry.d.ts +21 -5
  238. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  239. package/dist/record/main-pass-material.d.ts +7 -1
  240. package/dist/record/main-pass-material.d.ts.map +1 -1
  241. package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
  242. package/dist/record/main-pass.d.ts +13 -2
  243. package/dist/record/main-pass.d.ts.map +1 -1
  244. package/dist/record/mesh-ssbo.d.ts +6 -5
  245. package/dist/record/mesh-ssbo.d.ts.map +1 -1
  246. package/dist/record/motion-blur-receipt.d.ts +11 -0
  247. package/dist/record/motion-blur-receipt.d.ts.map +1 -0
  248. package/dist/record/probe-blend-buffer.d.ts +16 -0
  249. package/dist/record/probe-blend-buffer.d.ts.map +1 -0
  250. package/dist/record/recovery-pipeline.d.ts.map +1 -1
  251. package/dist/record/render-context.d.ts +34 -31
  252. package/dist/record/render-context.d.ts.map +1 -1
  253. package/dist/record/shadow-pass.d.ts +2 -0
  254. package/dist/record/shadow-pass.d.ts.map +1 -1
  255. package/dist/record/skybox-post-pass.d.ts +6 -4
  256. package/dist/record/skybox-post-pass.d.ts.map +1 -1
  257. package/dist/record/typed-frame-graph.d.ts +22 -2
  258. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  259. package/dist/record/view-ubo.d.ts +11 -3
  260. package/dist/record/view-ubo.d.ts.map +1 -1
  261. package/dist/record/volume-params.d.ts +9 -0
  262. package/dist/record/volume-params.d.ts.map +1 -0
  263. package/dist/recovery/render-system-candidate.d.ts +10 -0
  264. package/dist/recovery/render-system-candidate.d.ts.map +1 -1
  265. package/dist/reflection/record-owner.d.ts.map +1 -1
  266. package/dist/render-contract.d.ts +66 -8
  267. package/dist/render-contract.d.ts.map +1 -1
  268. package/dist/render-graph-primitives.d.ts.map +1 -1
  269. package/dist/render-pipeline.d.ts +106 -3
  270. package/dist/render-pipeline.d.ts.map +1 -1
  271. package/dist/render-system-extract-tail.d.ts +3 -2
  272. package/dist/render-system-extract-tail.d.ts.map +1 -1
  273. package/dist/render-system-extract.d.ts +24 -10
  274. package/dist/render-system-extract.d.ts.map +1 -1
  275. package/dist/render-system-presentation.d.ts +23 -0
  276. package/dist/render-system-presentation.d.ts.map +1 -0
  277. package/dist/render-system-producer-roots.d.ts +10 -0
  278. package/dist/render-system-producer-roots.d.ts.map +1 -0
  279. package/dist/render-system-residency.d.ts +12 -0
  280. package/dist/render-system-residency.d.ts.map +1 -0
  281. package/dist/render-system.d.ts +26 -15
  282. package/dist/render-system.d.ts.map +1 -1
  283. package/dist/scene/probe-blend.d.ts +27 -0
  284. package/dist/scene/probe-blend.d.ts.map +1 -1
  285. package/dist/scene/render-scene.d.ts +14 -5
  286. package/dist/scene/render-scene.d.ts.map +1 -1
  287. package/dist/ssao-buffers.d.ts +9 -0
  288. package/dist/ssao-buffers.d.ts.map +1 -1
  289. package/dist/surface/dynamic-input.d.ts +133 -0
  290. package/dist/surface/dynamic-input.d.ts.map +1 -0
  291. package/dist/surface/gpu-driven.d.ts +41 -0
  292. package/dist/surface/gpu-driven.d.ts.map +1 -0
  293. package/dist/surface/submission-observation.d.ts +136 -0
  294. package/dist/surface/submission-observation.d.ts.map +1 -0
  295. package/dist/systems/skin-palette-allocator.d.ts.map +1 -1
  296. package/dist/temporal/frame-coordinator.d.ts.map +1 -1
  297. package/dist/temporal/frame.d.ts +7 -0
  298. package/dist/temporal/frame.d.ts.map +1 -1
  299. package/dist/temporal/gpu.d.ts +13 -1
  300. package/dist/temporal/gpu.d.ts.map +1 -1
  301. package/dist/temporal/index.mjs +5 -5
  302. package/dist/temporal/inspection.d.ts.map +1 -1
  303. package/dist/temporal/standard-scene-data.d.ts.map +1 -1
  304. package/dist/temporal/target-coverage-attachment.d.ts.map +1 -1
  305. package/dist/temporal/view.d.ts +6 -1
  306. package/dist/temporal/view.d.ts.map +1 -1
  307. package/dist/transmission/single-layer-medium.d.ts +89 -0
  308. package/dist/transmission/single-layer-medium.d.ts.map +1 -0
  309. package/dist/typed-render-graph-primitives.d.ts +24 -9
  310. package/dist/typed-render-graph-primitives.d.ts.map +1 -1
  311. package/dist/volume/component.d.ts +9 -5
  312. package/dist/volume/component.d.ts.map +1 -1
  313. package/dist/volume/extract.d.ts +1 -1
  314. package/dist/volume/extract.d.ts.map +1 -1
  315. package/dist/volume/inspection.d.ts +2 -0
  316. package/dist/volume/inspection.d.ts.map +1 -1
  317. package/dist/volume/passes.d.ts +4 -1
  318. package/dist/volume/passes.d.ts.map +1 -1
  319. package/dist/volume/resources.d.ts.map +1 -1
  320. package/dist/volume/temporal.d.ts +3 -1
  321. package/dist/volume/temporal.d.ts.map +1 -1
  322. package/package.json +22 -21
  323. package/src/__tests__/analytic-fog-graph.unit.test.ts +199 -0
  324. package/src/__tests__/analytic-fog-optics.unit.test.ts +38 -0
  325. package/src/__tests__/atmosphere-daylight.dawn.test.ts +72 -0
  326. package/src/__tests__/barrel-distortion-lkg.integration.test.ts +353 -0
  327. package/src/__tests__/barrel-distortion.dawn.test.ts +41 -0
  328. package/src/__tests__/barrel-distortion.unit.test.ts +364 -0
  329. package/src/__tests__/bloom-exact-zero.unit.test.ts +112 -18
  330. package/src/__tests__/bloom-lifecycle.integration.test.ts +17 -4
  331. package/src/__tests__/construct-renderer-identity.unit.test.ts +49 -0
  332. package/src/__tests__/detached-recovery-graph.unit.test.ts +277 -0
  333. package/src/__tests__/device-loss-fanout.unit.test.ts +36 -1
  334. package/src/__tests__/docs-gate-green.unit.test.ts +112 -1
  335. package/src/__tests__/dynamic-resolution-plugin.integration.test.ts +20 -0
  336. package/src/__tests__/environment-fog-lanes.integration.test.ts +14 -0
  337. package/src/__tests__/environment-selection.unit.test.ts +12 -0
  338. package/src/__tests__/factory-contract.integration.test.ts +626 -113
  339. package/src/__tests__/feature-depth-input.dawn.test.ts +129 -2
  340. package/src/__tests__/frame-camera-selection.unit.test.ts +39 -1
  341. package/src/__tests__/frame-plan-contract.unit.test.ts +2 -0
  342. package/src/__tests__/fullscreen-feature-plan.unit.test.ts +14 -3
  343. package/src/__tests__/gpu-dirty-ranges.unit.test.ts +22 -0
  344. package/src/__tests__/gpu-driven-baseline.characterization.test.ts +16 -3
  345. package/src/__tests__/gpu-driven-batch-topology-pbr.unit.test.ts +12 -0
  346. package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +2 -1
  347. package/src/__tests__/gpu-driven-material-artifacts.unit.test.ts +143 -0
  348. package/src/__tests__/gpu-driven-pbr.dawn.test.ts +2 -2
  349. package/src/__tests__/gpu-driven-production.integration.test.ts +482 -3
  350. package/src/__tests__/gpu-driven-shadow-views.dawn.test.ts +7 -6
  351. package/src/__tests__/gpu-driven-shadow-views.integration.test.ts +1 -1
  352. package/src/__tests__/gpu-driven-view-gpu-evidence.ts +103 -4
  353. package/src/__tests__/gpu-driven-view-graph.integration.test.ts +18 -2
  354. package/src/__tests__/gpu-driven-view.browser.test.ts +5 -8
  355. package/src/__tests__/gpu-driven-view.dawn.test.ts +6 -2
  356. package/src/__tests__/gpu-driven-view.unit.test.ts +1 -1
  357. package/src/__tests__/gpu-pass-timing-lifecycle.integration.test.ts +1 -0
  358. package/src/__tests__/gpu-pass-timing.browser.test.ts +6 -4
  359. package/src/__tests__/gpu-pass-timing.rhi-null.unit.test.ts +1 -0
  360. package/src/__tests__/gpu-resource-store-stride.unit.test.ts +12 -0
  361. package/src/__tests__/gpu-scene.unit.test.ts +210 -9
  362. package/src/__tests__/instance-allocation-failure.unit.test.ts +57 -0
  363. package/src/__tests__/instances-culling.integration.test.ts +10 -36
  364. package/src/__tests__/instances-store.unit.test.ts +105 -238
  365. package/src/__tests__/instances-world-ownership.integration.test.ts +62 -0
  366. package/src/__tests__/main-pass-material-medium-selection.unit.test.ts +146 -0
  367. package/src/__tests__/material-shader-prewarm.unit.test.ts +59 -0
  368. package/src/__tests__/material-snapshot-mutation.integration.test.ts +39 -1
  369. package/src/__tests__/mesh-submission-lifetime.unit.test.ts +31 -0
  370. package/src/__tests__/morph-culling-reentry.dawn.test.ts +5 -5
  371. package/src/__tests__/morph-visual-evidence.integration.test.ts +8 -7
  372. package/src/__tests__/motion-blur-compute.dawn.test.ts +589 -0
  373. package/src/__tests__/motion-blur.pixel.integration.test.ts +2 -2
  374. package/src/__tests__/nineslice-handle-surface.unit.test.ts +4 -4
  375. package/src/__tests__/pbr-view-layout.unit.test.ts +9 -0
  376. package/src/__tests__/physical-clearcoat.integration.test.ts +58 -0
  377. package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +21 -0
  378. package/src/__tests__/prepared-graphics-capability.unit.test.ts +66 -2
  379. package/src/__tests__/prepared-graphics-lifecycle.unit.test.ts +86 -0
  380. package/src/__tests__/probe-bindings.unit.test.ts +582 -1
  381. package/src/__tests__/probe-scene-dirty.integration.test.ts +28 -1
  382. package/src/__tests__/public-surface.test-d.ts +8 -0
  383. package/src/__tests__/receipt-observation-retirement.unit.test.ts +65 -0
  384. package/src/__tests__/recovery-candidate-prepare.contract.test.ts +69 -0
  385. package/src/__tests__/render-error-code-owner.test-d.ts +9 -0
  386. package/src/__tests__/render-feature-diagnostics.unit.test.ts +15 -2
  387. package/src/__tests__/render-feature-error-reporting.unit.test.ts +44 -0
  388. package/src/__tests__/render-feature-prepared-diagnostics.unit.test.ts +10 -1
  389. package/src/__tests__/render-feature-retry.unit.test.ts +12 -1
  390. package/src/__tests__/render-feature-stage-fault-recovery.unit.test.ts +23 -0
  391. package/src/__tests__/render-scene-mixed-updates.integration.test.ts +145 -19
  392. package/src/__tests__/render-scene-projection.unit.test.ts +36 -11
  393. package/src/__tests__/render-scene-temporal-retry.unit.test.ts +37 -6
  394. package/src/__tests__/render-target-probe-soak.perf.test.ts +2 -2
  395. package/src/__tests__/render-temporal-environment-consumer.browser.test.ts +3 -3
  396. package/src/__tests__/render-temporal-environment-consumer.dawn.test.ts +5 -2
  397. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +41 -7
  398. package/src/__tests__/renderer-frame-generation-fence.integration.test.ts +16 -0
  399. package/src/__tests__/renderer-frame-transaction.integration.test.ts +33 -11
  400. package/src/__tests__/resource-allocation.unit.test.ts +123 -0
  401. package/src/__tests__/shader-manifest-fixture.ts +4 -4
  402. package/src/__tests__/single-layer-medium-passes.unit.test.ts +275 -0
  403. package/src/__tests__/skin-motion-regression.unit.test.ts +70 -1
  404. package/src/__tests__/skin-palette-recycling.dawn.test.ts +97 -0
  405. package/src/__tests__/skinned-shadow-caster.test.ts +17 -0
  406. package/src/__tests__/ssr-carrier-public-contract.unit.test.ts +21 -0
  407. package/src/__tests__/ssr-gpu-dispatch.browser.test.ts +4 -4
  408. package/src/__tests__/ssr-gpu-dispatch.dawn.test.ts +2 -2
  409. package/src/__tests__/ssr-gpu-dispatch.ts +1 -1
  410. package/src/__tests__/ssr-receiver-coverage.dawn.test.ts +6 -2
  411. package/src/__tests__/standard-lighting-write.unit.test.ts +2 -1
  412. package/src/__tests__/standard-output-chain-contract.unit.test.ts +28 -0
  413. package/src/__tests__/standard-pbr-artifact-assembly.unit.test.ts +46 -1
  414. package/src/__tests__/standard-pipeline.dawn.test.ts +1 -1
  415. package/src/__tests__/standard-pipeline.integration.test.ts +200 -9
  416. package/src/__tests__/surface-dynamic-buffer-lifecycle.unit.test.ts +506 -0
  417. package/src/__tests__/surface-submission-observation.unit.test.ts +812 -0
  418. package/src/__tests__/temporal-history-lifecycle.unit.test.ts +32 -0
  419. package/src/__tests__/temporal-performance.integration.test.ts +3 -3
  420. package/src/__tests__/temporal-reset-matrix.unit.test.ts +10 -0
  421. package/src/__tests__/temporal-view.unit.test.ts +2 -1
  422. package/src/__tests__/typed-pipeline-topology.unit.test.ts +20 -4
  423. package/src/__tests__/visibility-instances.integration.test.ts +7 -19
  424. package/src/__tests__/visibility-producer-matrix.integration.test.ts +9 -20
  425. package/src/__tests__/volume-optics-oracle.unit.test.ts +7 -3
  426. package/src/__tests__/volumetric-fog-budget-recovery.integration.test.ts +189 -15
  427. package/src/__tests__/volumetric-fog-errors.unit.test.ts +4 -3
  428. package/src/__tests__/volumetric-fog-multiple.unit.test.ts +55 -0
  429. package/src/__tests__/volumetric-fog-spot-frame-optics.integration.test.ts +1 -1
  430. package/src/__tests__/volumetric-fog-stage-readback.dawn.test.ts +558 -68
  431. package/src/__tests__/volumetric-fog-temporal.unit.test.ts +27 -0
  432. package/src/__tests__/volumetric-fog-topology.unit.test.ts +23 -5
  433. package/src/assembly/analytic-fog-registration.ts +14 -0
  434. package/src/assembly/backend-contract.ts +30 -0
  435. package/src/assembly/barrel-distortion-inspection.ts +22 -0
  436. package/src/assembly/canvas-draw-guard.ts +11 -0
  437. package/src/assembly/cloud-shadow-entry.ts +22 -0
  438. package/src/assembly/dynamic-geometry-host.ts +65 -37
  439. package/src/assembly/dynamic-geometry-runtime.ts +7 -1
  440. package/src/assembly/factory.ts +2 -30
  441. package/src/assembly/fxaa-registration.ts +17 -0
  442. package/src/assembly/host-contract.ts +3 -3
  443. package/src/assembly/lod-observation.ts +12 -0
  444. package/src/assembly/material/assembly.ts +25 -13
  445. package/src/assembly/material/pipeline-helpers.ts +53 -12
  446. package/src/assembly/material-shader-policy.ts +55 -0
  447. package/src/assembly/material-shader-prewarm.ts +21 -10
  448. package/src/assembly/motion-blur-registration.ts +17 -0
  449. package/src/assembly/recovery/device-loss-fanout.ts +13 -0
  450. package/src/assembly/recovery/renderer-recover.ts +41 -18
  451. package/src/assembly/render-feature-post-processes.ts +54 -0
  452. package/src/assembly/renderer-facade.ts +6 -1
  453. package/src/assembly/renderer-feature-inspection.ts +8 -2
  454. package/src/assembly/renderer-frame-transaction.ts +23 -0
  455. package/src/assembly/renderer-inspection.ts +28 -0
  456. package/src/assembly/skin-palette-owner.ts +1 -1
  457. package/src/assembly/temporal-post-process-prewarm.ts +40 -0
  458. package/src/assembly/webgpu-pbr-ready.ts +9 -1
  459. package/src/assembly/webgpu-ready-contract.ts +8 -3
  460. package/src/assembly/webgpu-ready-per-pass.ts +104 -0
  461. package/src/assembly/webgpu-ready.ts +296 -306
  462. package/src/assembly/webgpu-renderer-bootstrap.ts +0 -12
  463. package/src/assembly/webgpu-renderer-observation-owner.ts +222 -0
  464. package/src/assembly/webgpu-renderer.ts +228 -281
  465. package/src/assembly/webgpu-vertex-layouts.ts +1 -1
  466. package/src/barrel-distortion.ts +247 -0
  467. package/src/bloom-admission.ts +21 -0
  468. package/src/cloud/__tests__/cloud-density-parity.dawn.test.ts +173 -0
  469. package/src/cloud/__tests__/cloud-layer-framebuffer-y.dawn.test.ts +265 -0
  470. package/src/cloud/__tests__/cloud-layer.browser.test.ts +432 -0
  471. package/src/cloud/__tests__/cloud-layer.unit.test.ts +634 -0
  472. package/src/cloud/density.ts +600 -0
  473. package/src/cloud/extract.ts +110 -0
  474. package/src/cloud/feature.ts +1810 -0
  475. package/src/cloud/frame-context.ts +42 -0
  476. package/src/cloud/inspection.ts +130 -0
  477. package/src/cloud/optics.ts +285 -0
  478. package/src/cloud/parameters.ts +255 -0
  479. package/src/cloud/resources.ts +122 -0
  480. package/src/cloud/shadow.ts +167 -0
  481. package/src/cloud/temporal.ts +226 -0
  482. package/src/components/__tests__/motion-blur.unit.test.ts +1 -0
  483. package/src/components/atmosphere.ts +4 -0
  484. package/src/components/barrel-distortion.ts +46 -0
  485. package/src/components/camera.ts +66 -10
  486. package/src/components/cloud-layer.ts +47 -0
  487. package/src/components/index.ts +2 -0
  488. package/src/components/instances.ts +18 -45
  489. package/src/components/mesh-filter.ts +11 -9
  490. package/src/components/motion-blur.ts +1 -0
  491. package/src/components/sprite-instances.ts +1 -1
  492. package/src/construct-renderer.ts +1 -1
  493. package/src/device/gpu-residency.ts +56 -12
  494. package/src/dynamic-geometry.ts +12 -1
  495. package/src/environment/analytic-fog.ts +44 -0
  496. package/src/environment/background.ts +2 -2
  497. package/src/errors/cloud.ts +103 -0
  498. package/src/errors/render.ts +58 -8
  499. package/src/examples/single-layer-medium-surface.ts +59 -0
  500. package/src/extract/camera.ts +35 -8
  501. package/src/extract/contracts.ts +12 -0
  502. package/src/extract/environment.ts +4 -0
  503. package/src/features/barrel-distortion.ts +172 -0
  504. package/src/features/error-reporting.ts +98 -0
  505. package/src/features/host.ts +138 -24
  506. package/src/features/motion-blur/__tests__/motion-blur-feature.unit.test.ts +111 -1
  507. package/src/features/motion-blur/__tests__/motion-blur-params.unit.test.ts +58 -3
  508. package/src/features/motion-blur/motion-blur-feature.ts +1519 -15
  509. package/src/features/motion-blur/motion-blur-params.ts +70 -2
  510. package/src/features/motion-blur/motion-blur-runtime.ts +176 -0
  511. package/src/features/plan.ts +33 -8
  512. package/src/features/prepared-gpu-work.ts +86 -35
  513. package/src/features/render-graph-compute.ts +13 -5
  514. package/src/features/render-graph-contribution.ts +11 -0
  515. package/src/features/render-graph-raster.ts +45 -3
  516. package/src/features/targets.ts +66 -0
  517. package/src/features/types.ts +47 -0
  518. package/src/fullscreen-post-process-pass.ts +41 -9
  519. package/src/gpu-dirty-ranges.ts +48 -0
  520. package/src/gpu-driven/batch-topology.ts +53 -0
  521. package/src/gpu-driven/material-artifacts.ts +35 -32
  522. package/src/gpu-driven/prepared-draw.ts +27 -14
  523. package/src/gpu-driven/production-raster-material.ts +1894 -0
  524. package/src/gpu-driven/production-raster.ts +904 -787
  525. package/src/gpu-driven/resource-allocation.ts +142 -0
  526. package/src/gpu-driven/shadow-ownership.ts +71 -0
  527. package/src/gpu-driven/view-gpu.ts +181 -10
  528. package/src/gpu-scene.ts +259 -101
  529. package/src/hdrp-buffers.ts +4 -3
  530. package/src/ibl/skylight-bind-group.ts +49 -7
  531. package/src/index.ts +228 -9
  532. package/src/inspection-types.ts +222 -2
  533. package/src/instance-buffer-cache.ts +4 -4
  534. package/src/instances.ts +151 -492
  535. package/src/pbr-pipeline.ts +219 -13
  536. package/src/pipeline/single-layer-medium-passes.ts +267 -0
  537. package/src/pipeline/standard-forward-lane.ts +313 -57
  538. package/src/pipeline/standard-output/graph.ts +8 -2
  539. package/src/pipeline/standard-output/types.ts +12 -1
  540. package/src/pipeline/standard-pipeline.ts +291 -18
  541. package/src/pipeline/standard-post.ts +397 -102
  542. package/src/pipeline-builder.ts +1 -1
  543. package/src/pipeline-spec.ts +5 -5
  544. package/src/plugin.ts +4 -0
  545. package/src/points-lines/__tests__/points-lines.bench.ts +17 -8
  546. package/src/points-lines/__tests__/view-ubo.unit.test.ts +2 -1
  547. package/src/prepare/prepared-graphics-resolver.ts +2 -0
  548. package/src/record/__tests__/main-pass-geometry.unit.test.ts +30 -0
  549. package/src/record/__tests__/sprite-material-abi.unit.test.ts +1 -1
  550. package/src/record/__tests__/temporal-payload.unit.test.ts +3 -2
  551. package/src/record/__tests__/transparent-group2-selection.unit.test.ts +11 -0
  552. package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +1 -0
  553. package/src/record/__tests__/view-ubo-layout.unit.test.ts +4 -3
  554. package/src/record/barrel-distortion-frame.ts +110 -0
  555. package/src/record/dynamic-geometry-consumption.ts +39 -9
  556. package/src/record/fold-buckets.ts +852 -0
  557. package/src/record/fold-instance-buffer.ts +51 -0
  558. package/src/record/frame-lighting.ts +36 -2
  559. package/src/record/frame-snapshot.ts +44 -4
  560. package/src/record/frame-targets.ts +10 -6
  561. package/src/record/frame.ts +373 -1099
  562. package/src/record/gpu-draw-receipts.ts +38 -0
  563. package/src/record/gpu-timing.ts +48 -5
  564. package/src/record/main-pass-geometry.ts +281 -83
  565. package/src/record/main-pass-material.ts +66 -4
  566. package/src/record/main-pass-sprite-draws.ts +13 -66
  567. package/src/record/main-pass.ts +156 -20
  568. package/src/record/mesh-ssbo.ts +43 -6
  569. package/src/record/motion-blur-receipt.ts +91 -0
  570. package/src/record/probe-blend-buffer.ts +180 -0
  571. package/src/record/recovery-pipeline.ts +35 -3
  572. package/src/record/render-context.ts +51 -31
  573. package/src/record/shadow-pass.ts +36 -2
  574. package/src/record/skybox-post-pass.ts +150 -200
  575. package/src/record/typed-frame-graph.ts +824 -44
  576. package/src/record/view-ubo.ts +26 -3
  577. package/src/record/volume-params.ts +225 -0
  578. package/src/recovery/render-system-candidate.ts +257 -52
  579. package/src/reflection/record-owner.ts +7 -3
  580. package/src/render-contract.ts +71 -8
  581. package/src/render-graph-primitives.ts +7 -1
  582. package/src/render-pipeline.ts +136 -1
  583. package/src/render-system-extract-tail.ts +72 -60
  584. package/src/render-system-extract.ts +117 -87
  585. package/src/render-system-presentation.ts +56 -0
  586. package/src/render-system-producer-roots.ts +96 -0
  587. package/src/render-system-residency.ts +159 -0
  588. package/src/render-system.ts +601 -482
  589. package/src/scene/probe-blend.ts +96 -4
  590. package/src/scene/render-scene.ts +431 -281
  591. package/src/ssao-buffers.ts +95 -2
  592. package/src/surface/__tests__/dynamic-input.unit.test.ts +374 -0
  593. package/src/surface/__tests__/gpu-driven.unit.test.ts +87 -0
  594. package/src/surface/dynamic-input.ts +658 -0
  595. package/src/surface/gpu-driven.ts +147 -0
  596. package/src/surface/submission-observation.ts +517 -0
  597. package/src/systems/skin-palette-allocator.ts +4 -1
  598. package/src/temporal/__tests__/temporal-frame-transaction.unit.test.ts +36 -0
  599. package/src/temporal/frame-coordinator.ts +8 -1
  600. package/src/temporal/frame.ts +20 -0
  601. package/src/temporal/gpu.ts +101 -17
  602. package/src/temporal/inspection.ts +7 -1
  603. package/src/temporal/standard-scene-data.ts +5 -0
  604. package/src/temporal/target-coverage-attachment.ts +4 -0
  605. package/src/temporal/view.ts +13 -2
  606. package/src/transmission/__tests__/single-layer-medium.unit.test.ts +149 -0
  607. package/src/transmission/__tests__/standard-transmission.dawn.test.ts +12 -11
  608. package/src/transmission/single-layer-medium.ts +322 -0
  609. package/src/typed-render-graph-primitives.ts +191 -50
  610. package/src/volume/component.ts +10 -10
  611. package/src/volume/extract.ts +11 -5
  612. package/src/volume/inspection.ts +3 -0
  613. package/src/volume/passes.ts +95 -8
  614. package/src/volume/resources.ts +2 -1
  615. package/src/volume/temporal.ts +11 -0
  616. package/dist/chunk-4NNZQCL3.mjs.map +0 -1
  617. package/dist/chunk-6TFBXLRJ.mjs.map +0 -1
  618. package/dist/chunk-DRR7D3KX.mjs.map +0 -1
  619. package/dist/chunk-GWCEMDVR.mjs.map +0 -1
  620. package/dist/chunk-JCIRYQ4V.mjs.map +0 -1
  621. package/dist/chunk-JRIPCMYC.mjs.map +0 -1
  622. package/dist/chunk-LULCA3OF.mjs +0 -1713
  623. package/dist/chunk-LULCA3OF.mjs.map +0 -1
  624. package/dist/chunk-PA5DRJNI.mjs.map +0 -1
@@ -7,8 +7,13 @@ import { BatchTopology } from '../gpu-driven/batch-topology';
7
7
  import { GPU_DRIVEN_VIEW_WGSL, GpuDrivenView } from '../gpu-driven/view-gpu';
8
8
  import { GpuScene } from '../gpu-scene';
9
9
  import { GPU_BUFFER_USAGE_COPY_DST, GPU_BUFFER_USAGE_MAP_READ } from '../gpu-usage';
10
+ import { getOpaqueResourceIdentity } from '../record/frame-snapshot';
10
11
  import type { MaterialSnapshot, RenderableSnapshot } from '../render-system-extract';
11
12
  import { RenderScene } from '../scene/render-scene';
13
+ import {
14
+ type SurfaceSubmissionCandidate,
15
+ SurfaceSubmissionObservationOwner,
16
+ } from '../surface/submission-observation';
12
17
 
13
18
  const GPU_MAP_MODE_READ = 0x0001;
14
19
 
@@ -62,6 +67,10 @@ export interface GpuDrivenViewGpuEvidence {
62
67
  readonly firstInstance: number;
63
68
  readonly overflow: number;
64
69
  readonly persistentTranslationX: number;
70
+ readonly firstReadbackFrame: number;
71
+ readonly recoveredReadbackFrame: number;
72
+ readonly firstVisible: number;
73
+ readonly recoveredVisible: number;
65
74
  }
66
75
 
67
76
  export interface GpuDrivenViewLifecycleEvidence {
@@ -71,7 +80,7 @@ export interface GpuDrivenViewLifecycleEvidence {
71
80
  }
72
81
 
73
82
  export async function runGpuDrivenViewLifecycleEvidence(
74
- frames = 300,
83
+ frames = 60,
75
84
  ): Promise<GpuDrivenViewLifecycleEvidence> {
76
85
  const adapter = (await rhi.requestAdapter()).unwrap();
77
86
  const device = (await adapter.requestDevice()).unwrap();
@@ -247,7 +256,27 @@ export async function runGpuDrivenViewGpuEvidence(): Promise<GpuDrivenViewGpuEvi
247
256
  })
248
257
  .unwrap();
249
258
  const graph = new RenderGraphBuilder<{ readonly encoder: RhiCommandEncoder }>();
250
- view.addPasses(graph).unwrap();
259
+ const submissionOwner = new SurfaceSubmissionObservationOwner(() => 1);
260
+ const beginSubmission = (frameId: number, indirectOffset: number): SurfaceSubmissionCandidate => {
261
+ const candidate = submissionOwner.begin({
262
+ frameId,
263
+ requestedLane: 'gpu-driven',
264
+ deviceGeneration: 1,
265
+ resourceGeneration: view.inspect().resourceGeneration,
266
+ });
267
+ for (const pass of ['nearest-layer', 'color'] as const) {
268
+ candidate.record(pass, {
269
+ kind: 'draw-indexed-indirect',
270
+ indirectBufferIdentity: getOpaqueResourceIdentity(indirect as object),
271
+ indirectOffset,
272
+ pipelineIdentity: 33,
273
+ });
274
+ }
275
+ return candidate;
276
+ };
277
+ let currentSubmission = beginSubmission(10, 0);
278
+ view.setTelemetrySubmit({ frameId: 1, deviceGeneration: 0 });
279
+ view.addPasses(graph, 'gpu-driven', true, () => currentSubmission).unwrap();
251
280
  const compiled = graph.compile({ device, surfaceSize: { width: 1, height: 1 } }).unwrap();
252
281
  const encoder = device.createCommandEncoder({ label: 'gpu-driven-evidence' }).unwrap();
253
282
  compiled.execute({ encoder }).unwrap();
@@ -262,6 +291,7 @@ export async function runGpuDrivenViewGpuEvidence(): Promise<GpuDrivenViewGpuEvi
262
291
  64,
263
292
  );
264
293
  device.queue.submit([encoder.finish().unwrap()]).unwrap();
294
+ currentSubmission.submit(device.queue.onSubmittedWorkDone(), 1);
265
295
 
266
296
  // Start observing immediately, then submit the cached graph again before
267
297
  // the first mapAsync settles. The second copy must be skipped while the
@@ -276,9 +306,45 @@ export async function runGpuDrivenViewGpuEvidence(): Promise<GpuDrivenViewGpuEvi
276
306
  compiled.execute({ encoder: overlappingEncoder }).unwrap();
277
307
  device.queue.submit([overlappingEncoder.finish().unwrap()]).unwrap();
278
308
  const lodSelection = await pendingLodSelection;
279
- if (lodSelection === undefined || lodSelection.batches.length !== lodSelection.batchCount) {
309
+ if (lodSelection === undefined) {
310
+ throw new Error('GPU-driven LOD selection readback unavailable or incomplete');
311
+ }
312
+ if (lodSelection.batches.length !== lodSelection.batchCount) {
280
313
  throw new Error('GPU-driven LOD selection readback unavailable or incomplete');
281
314
  }
315
+ const surfaceIndirectParameters = lodSelection.surfaceIndirectParameters;
316
+ if (surfaceIndirectParameters === undefined || surfaceIndirectParameters.length !== 2) {
317
+ throw new Error('GPU-driven Surface indirect parameter readback unavailable');
318
+ }
319
+ const firstSurfaceCommand = surfaceIndirectParameters[0];
320
+ if (
321
+ firstSurfaceCommand === undefined ||
322
+ firstSurfaceCommand.sequence !== lodSelection.surfaceReadback?.sequence ||
323
+ firstSurfaceCommand.frameId !== lodSelection.surfaceReadback?.frameId ||
324
+ firstSurfaceCommand.deviceGeneration !== lodSelection.surfaceReadback?.deviceGeneration ||
325
+ firstSurfaceCommand.resourceGeneration !== lodSelection.surfaceReadback?.resourceGeneration ||
326
+ firstSurfaceCommand.pass !== 'nearest-layer' ||
327
+ firstSurfaceCommand.indirectOffset !== 0 ||
328
+ firstSurfaceCommand.count !== 12 ||
329
+ firstSurfaceCommand.instanceCount !== 1 ||
330
+ firstSurfaceCommand.first !== 9 ||
331
+ firstSurfaceCommand.baseVertex !== 4 ||
332
+ firstSurfaceCommand.firstInstance !== 0
333
+ ) {
334
+ throw new Error(
335
+ `GPU-driven Surface indirect parameters do not match the submitted batch: ${JSON.stringify({
336
+ firstSurfaceCommand,
337
+ surfaceReadback: lodSelection.surfaceReadback,
338
+ })}`,
339
+ );
340
+ }
341
+ if (
342
+ lodSelection.submit === undefined ||
343
+ lodSelection.submit.frameId !== 1 ||
344
+ lodSelection.submit.deviceGeneration !== 0
345
+ ) {
346
+ throw new Error('GPU-driven LOD telemetry lost the copy submit identity');
347
+ }
282
348
  await device.queue.onSubmittedWorkDone();
283
349
 
284
350
  // After unmap, a later cached-graph submission must restore telemetry
@@ -292,20 +358,49 @@ export async function runGpuDrivenViewGpuEvidence(): Promise<GpuDrivenViewGpuEvi
292
358
  frustum.fromViewProjection(frustum.create(), recoveryProjection),
293
359
  )
294
360
  .unwrap();
361
+ view.setTelemetrySubmit({ frameId: 2, deviceGeneration: 0 });
295
362
  const recoveryEncoder = device
296
363
  .createCommandEncoder({ label: 'gpu-driven-evidence-recovery' })
297
364
  .unwrap();
365
+ currentSubmission = beginSubmission(11, 32);
298
366
  compiled.execute({ encoder: recoveryEncoder }).unwrap();
299
367
  device.queue.submit([recoveryEncoder.finish().unwrap()]).unwrap();
368
+ currentSubmission.submit(device.queue.onSubmittedWorkDone(), 1);
300
369
  await device.queue.onSubmittedWorkDone();
301
370
  const recoveredLodSelection = await view.readLodSelection();
302
371
  if (
303
372
  recoveredLodSelection === undefined ||
304
373
  recoveredLodSelection === lodSelection ||
305
- recoveredLodSelection.visible === lodSelection.visible
374
+ recoveredLodSelection.visible === lodSelection.visible ||
375
+ lodSelection.surfaceReadback?.frameId !== 10 ||
376
+ recoveredLodSelection.surfaceReadback?.frameId !== 11
306
377
  ) {
307
378
  throw new Error('GPU-driven LOD telemetry did not recover after a skipped copy');
308
379
  }
380
+ if (
381
+ recoveredLodSelection.surfaceIndirectParameters?.some(
382
+ (command) =>
383
+ command.sequence !== recoveredLodSelection.surfaceReadback?.sequence ||
384
+ command.frameId !== recoveredLodSelection.surfaceReadback?.frameId ||
385
+ command.deviceGeneration !== recoveredLodSelection.surfaceReadback?.deviceGeneration ||
386
+ command.resourceGeneration !== recoveredLodSelection.surfaceReadback?.resourceGeneration,
387
+ )
388
+ ) {
389
+ throw new Error('GPU-driven recovered Surface indirect parameters lost readback identity');
390
+ }
391
+ if (
392
+ recoveredLodSelection.submit === undefined ||
393
+ recoveredLodSelection.submit.frameId !== 2 ||
394
+ recoveredLodSelection.submit.deviceGeneration !== 0
395
+ ) {
396
+ throw new Error('GPU-driven LOD telemetry reused a stale submit identity');
397
+ }
398
+ if (
399
+ lodSelection.surfaceReadback === undefined ||
400
+ recoveredLodSelection.surfaceReadback === undefined
401
+ ) {
402
+ throw new Error('GPU-driven LOD telemetry lost Surface readback identity');
403
+ }
309
404
  const validationError = await rawDevice.popErrorScope();
310
405
  if (validationError !== null) {
311
406
  throw new Error(`GPU-driven telemetry overlap validation failed: ${validationError.message}`);
@@ -337,6 +432,10 @@ export async function runGpuDrivenViewGpuEvidence(): Promise<GpuDrivenViewGpuEvi
337
432
  firstInstance: indirectValue.getUint32(16, true),
338
433
  overflow: overflowValue,
339
434
  persistentTranslationX,
435
+ firstReadbackFrame: lodSelection.surfaceReadback.frameId,
436
+ recoveredReadbackFrame: recoveredLodSelection.surfaceReadback.frameId,
437
+ firstVisible: lodSelection.visible,
438
+ recoveredVisible: recoveredLodSelection.visible,
340
439
  };
341
440
  view.dispose();
342
441
  sceneAvailability.scene.dispose();
@@ -12,7 +12,7 @@ import { RhiErrorListenerRegistry } from '../lifecycle';
12
12
  import type { RenderFrameState } from '../record/frame-snapshot';
13
13
  import { makeZeroCameraFallbackSnapshot } from '../record/frame-snapshot';
14
14
  import type { PipelineState, RenderSystemInternals } from '../record/render-context';
15
- import { ensureCompiledFrameGraph } from '../record/typed-frame-graph';
15
+ import { ensureCompiledFrameGraph, executeCompiledFrameGraph } from '../record/typed-frame-graph';
16
16
  import type { RenderPipeline, RenderPipelineFrame } from '../render-pipeline';
17
17
  import type {
18
18
  ExtractedLights,
@@ -65,6 +65,7 @@ function updateSnapshot(value: RenderableSnapshot) {
65
65
  function preparedFrame(view: GpuDrivenView): PreparedGpuDrivenFrame {
66
66
  return {
67
67
  topologySignature: `view-generation:${view.inspect().resourceGeneration}`,
68
+ worldKeys: [],
68
69
  ownsAllDrawItems: true,
69
70
  standardPbrFrameResources: {
70
71
  materialBindGroups: [],
@@ -141,6 +142,7 @@ describe('GpuDrivenView graph projection', () => {
141
142
  if (first === null) return;
142
143
  const prepared: PreparedGpuDrivenFrame = {
143
144
  topologySignature: 'stable-gpu-driven-test',
145
+ worldKeys: [],
144
146
  ownsAllDrawItems: true,
145
147
  standardPbrFrameResources: {
146
148
  materialBindGroups: [],
@@ -239,7 +241,7 @@ describe('GpuDrivenView graph projection', () => {
239
241
  {
240
242
  name: 'gpu-driven.lod-selection-readback',
241
243
  kind: 'copy',
242
- dependencies: ['gpu-driven.frustum-compact'],
244
+ dependencies: ['gpu-driven.frustum-compact', 'gpu-driven.finalize-indirect'],
243
245
  },
244
246
  ]);
245
247
  const encoder = device.createCommandEncoder({ label: 'gpu-driven-view' }).unwrap();
@@ -390,7 +392,21 @@ describe('GpuDrivenView graph projection', () => {
390
392
  const retirePrevious = vi.spyOn(compiledPrevious, 'retire');
391
393
  const acceptedGraph = compile(preparedFrame(view), 2);
392
394
  expect(acceptedGraph).not.toBe(compiledPrevious);
395
+ expect(retirePrevious).not.toHaveBeenCalled();
396
+ expect(destroyBuffer.mock.calls.some(([buffer]) => buffer === previousVisible)).toBe(false);
393
397
  expect(frameState.compiledFrameGraphGeneration).toBe(2);
398
+ expect(retirePrevious).not.toHaveBeenCalled();
399
+ const acceptedEncoder = device
400
+ .createCommandEncoder({ label: 'gpu-driven-view-accepted' })
401
+ .unwrap();
402
+ expect(
403
+ executeCompiledFrameGraph(
404
+ internals,
405
+ frameState,
406
+ { encoder: acceptedEncoder } as RenderPipelineFrame,
407
+ acceptedEncoder,
408
+ ),
409
+ ).toBe(true);
394
410
  expect(retirePrevious).toHaveBeenCalledOnce();
395
411
  await device.queue.onSubmittedWorkDone();
396
412
  await Promise.resolve();
@@ -5,14 +5,7 @@ import {
5
5
  } from './gpu-driven-view-gpu-evidence';
6
6
 
7
7
  const browserReady = typeof navigator !== 'undefined' && navigator.gpu !== undefined;
8
- const lifecycleFrames =
9
- (
10
- import.meta as ImportMeta & {
11
- readonly env?: { readonly FORGEAX_BROWSER_CI_LIGHTWEIGHT?: string };
12
- }
13
- ).env?.FORGEAX_BROWSER_CI_LIGHTWEIGHT === '1'
14
- ? 60
15
- : 300;
8
+ const lifecycleFrames = 60;
16
9
 
17
10
  describe.skipIf(!browserReady)('GPU-driven View Browser WebGPU', () => {
18
11
  it('produces compact visible IDs and portable indirect args on the real browser device', async () => {
@@ -25,6 +18,10 @@ describe.skipIf(!browserReady)('GPU-driven View Browser WebGPU', () => {
25
18
  firstInstance: 0,
26
19
  overflow: 0,
27
20
  persistentTranslationX: 0,
21
+ firstReadbackFrame: 10,
22
+ recoveredReadbackFrame: 11,
23
+ firstVisible: 1,
24
+ recoveredVisible: 0,
28
25
  });
29
26
  });
30
27
 
@@ -15,12 +15,16 @@ describe('GPU-driven View Dawn', () => {
15
15
  firstInstance: 0,
16
16
  overflow: 0,
17
17
  persistentTranslationX: 0,
18
+ firstReadbackFrame: 10,
19
+ recoveredReadbackFrame: 11,
20
+ firstVisible: 1,
21
+ recoveredVisible: 0,
18
22
  });
19
23
  });
20
24
 
21
- it('survives 300 frames of spawn/despawn capacity crossings without stale buffers', async () => {
25
+ it('survives 60 frames of spawn/despawn capacity crossings without stale buffers', async () => {
22
26
  await expect(runGpuDrivenViewLifecycleEvidence()).resolves.toEqual({
23
- frames: 300,
27
+ frames: 60,
24
28
  bufferRebuilds: 4,
25
29
  candidateCapacity: 8,
26
30
  });
@@ -99,7 +99,7 @@ describe('GPU-driven batch topology and CPU oracle', () => {
99
99
 
100
100
  it('projects compact visible items into batch-local mesh and material indices', () => {
101
101
  expect(GPU_DRIVEN_VIEW_WGSL).toMatch(
102
- /vec2<u32>\(\s*candidate\.submitAdmission >> 1u,\s*materialOrPalette,/,
102
+ /vec4<u32>\(\s*candidate\.submitAdmission >> 1u,\s*materialOrPalette,\s*candidate\.primitiveIndex \+ 1u,\s*candidate\.generation,/,
103
103
  );
104
104
  expect(GPU_DRIVEN_VIEW_WGSL).toContain('(candidate.submitAdmission & 1u)');
105
105
  expect(GPU_DRIVEN_VIEW_WGSL).toContain('instance.customDataStart');
@@ -145,6 +145,7 @@ describe('GPU pass timing generation-safe lifecycle', () => {
145
145
  const oldReceipt = {
146
146
  frameId: oldIdentity.frameId,
147
147
  deviceGeneration: oldIdentity.deviceGeneration,
148
+ presentation: 'ready',
148
149
  completed: Promise.resolve(ok(undefined)),
149
150
  } as FrameReceipt;
150
151
  store.register(oldReceipt, async () => {
@@ -17,9 +17,9 @@ describe.skipIf(!browserReady)('GPU pass timing Browser dev-server runner', () =
17
17
  expect(omitted.timings).toBeUndefined();
18
18
  });
19
19
 
20
- // The first live lavapipe renderer pair can exceed Vitest's 15s default while
21
- // the shader manifest and timestamp-query path are cold; keep this bound
22
- // local to the real WebGPU owner instead of weakening the global timeout.
20
+ // Each timing case creates a fresh renderer. Its lavapipe initialization can
21
+ // exceed Vitest's 15s default, including the raster-suppressed case below.
22
+ // Both retain the same bounded startup allowance and three-frame assertions.
23
23
  it('keeps receipt facts and unsupported timing structured without stopping draw', {
24
24
  timeout: 30_000,
25
25
  }, async () => {
@@ -35,7 +35,9 @@ describe.skipIf(!browserReady)('GPU pass timing Browser dev-server runner', () =
35
35
  expect(result.visible).toBe(true);
36
36
  });
37
37
 
38
- it('keeps raster suppression as a test-only falsifier, never timing evidence', async () => {
38
+ it('keeps raster suppression as a test-only falsifier, never timing evidence', {
39
+ timeout: 30_000,
40
+ }, async () => {
39
41
  const result = await runBrowserGpuPassTiming({ suppressRaster: true });
40
42
  expect(result.rasterSuppressed).toBe(true);
41
43
  expect(result.frames).toBe(3);
@@ -16,6 +16,7 @@ function receipt(frameId: number): FrameReceipt {
16
16
  return {
17
17
  frameId,
18
18
  deviceGeneration: 1,
19
+ presentation: 'ready',
19
20
  completed: Promise.resolve(ok(undefined)),
20
21
  } as FrameReceipt;
21
22
  }
@@ -107,6 +107,13 @@ describe('GpuResidencyCache mesh stride', () => {
107
107
  };
108
108
  const world = new World();
109
109
  const handle = world.allocSharedRef('MeshAsset', root);
110
+ // The root can be pulled before the lower pack finishes loading. A later
111
+ // call with the complete chain must upgrade the same cache key.
112
+ const rootOnly = store.ensureResident(handle, root);
113
+ expect(rootOnly.ok).toBe(true);
114
+ if (!rootOnly.ok) return;
115
+ expect(rootOnly.value.lodRanges).toBeUndefined();
116
+
110
117
  const resident = store.ensureResident(handle, root, 0, [lower]);
111
118
  expect(resident.ok).toBe(true);
112
119
  if (!resident.ok) return;
@@ -120,6 +127,11 @@ describe('GpuResidencyCache mesh stride', () => {
120
127
  expect(packedIndices).toBeDefined();
121
128
  if (packedIndices === undefined) return;
122
129
  expect(new Uint16Array(packedIndices.buffer)).toEqual(new Uint16Array([0, 1, 2, 0, 2, 1]));
130
+
131
+ // A stable chain remains an O(1) hit and does not replace the entry again.
132
+ const stable = store.ensureResident(handle, root, 0, [lower]);
133
+ expect(stable.ok).toBe(true);
134
+ if (stable.ok) expect(stable.value).toBe(resident.value);
123
135
  });
124
136
 
125
137
  it('refuses a payload whose bytes do not match the projected stride', async () => {
@@ -126,6 +126,34 @@ function instanceSnapshot(entityKey: number, translationX: number): RenderableSn
126
126
  };
127
127
  }
128
128
 
129
+ function multiInstanceSnapshot(
130
+ entityKey: number,
131
+ translations: readonly number[],
132
+ generations: readonly number[],
133
+ ): RenderableSnapshot {
134
+ const base = snapshot(entityKey);
135
+ const transforms = new Float32Array(translations.length * 16);
136
+ for (let ordinal = 0; ordinal < translations.length; ordinal += 1) {
137
+ const offset = ordinal * 16;
138
+ transforms[offset] = 1;
139
+ transforms[offset + 5] = 1;
140
+ transforms[offset + 10] = 1;
141
+ transforms[offset + 12] = translations[ordinal] ?? 0;
142
+ transforms[offset + 15] = 1;
143
+ }
144
+ return {
145
+ ...base,
146
+ instances: {
147
+ transforms,
148
+ generations: new Uint32Array(generations),
149
+ instanceCount: translations.length,
150
+ cacheKey: entityKey,
151
+ archVersion: 1,
152
+ revision: 1,
153
+ },
154
+ };
155
+ }
156
+
129
157
  function persistentFrame(
130
158
  renderables: readonly RenderableSnapshot[],
131
159
  cameras: ExtractedFrame['cameras'] = [],
@@ -163,6 +191,123 @@ function createDevice(queue: RecordingQueue): RhiNullDevice {
163
191
  }
164
192
 
165
193
  describe('GpuScene', () => {
194
+ it('keeps renderer-owned tables resident across World detach and reattach', () => {
195
+ const queue = new RecordingQueue();
196
+ const device = createDevice(queue);
197
+ const persistent = new PersistentRenderScene({ getDevice: () => device });
198
+ const firstWorld = new World();
199
+ const firstLease = createRenderReadLease(firstWorld);
200
+
201
+ persistent.extractComposition(
202
+ [firstWorld],
203
+ { cameraOwner: 0, resourceOwner: 0 },
204
+ 0,
205
+ () => persistentFrame([snapshot(1)]),
206
+ [firstLease],
207
+ );
208
+ const first = persistent.compositionGpuDrivenState();
209
+ expect(first).toBeDefined();
210
+ if (first === undefined) return;
211
+
212
+ persistent.detach(firstWorld);
213
+ firstLease.dispose();
214
+ const secondWorld = new World();
215
+ const secondLease = createRenderReadLease(secondWorld);
216
+ persistent.extractComposition(
217
+ [secondWorld],
218
+ { cameraOwner: 0, resourceOwner: 0 },
219
+ 1,
220
+ () => persistentFrame([snapshot(2)]),
221
+ [secondLease],
222
+ );
223
+ const second = persistent.compositionGpuDrivenState();
224
+ expect(second).toBeDefined();
225
+ expect(second?.scene).toBe(first.scene);
226
+
227
+ secondLease.dispose();
228
+ persistent.dispose();
229
+ });
230
+
231
+ it('uploads only changed material bytes for shared value updates and retries failed writes', () => {
232
+ const queue = new FailingQueue();
233
+ const created = GpuScene.create(createDevice(queue), 8).unwrap();
234
+ expect(created.status).toBe('available');
235
+ if (created.status !== 'available') return;
236
+ const scene = created.scene;
237
+ const projection = new RenderScene();
238
+ const sources = Array.from({ length: 4 }, (_, index) => ({
239
+ ...snapshot(index),
240
+ gpuDrivenDraws: [
241
+ {
242
+ kind: 'indexed' as const,
243
+ first: 0,
244
+ count: 36,
245
+ baseVertex: 0,
246
+ materialSlot: 0,
247
+ topology: 'triangle-list' as const,
248
+ pipelineClass: 'pbr',
249
+ materialResourceClass: '{}',
250
+ },
251
+ ],
252
+ }));
253
+ scene.sync(projection.apply(sources.map(updateSnapshot))).unwrap();
254
+ const changed = { ...material, roughness: 0.3 };
255
+ const updated = sources.map((source) => ({
256
+ ...source,
257
+ material: changed,
258
+ materials: [changed],
259
+ }));
260
+ const delta = projection.apply(updated.map(updateSnapshot));
261
+ queue.failWrites = true;
262
+ expect(scene.sync(delta).ok).toBe(false);
263
+ queue.failWrites = false;
264
+ queue.writes.length = 0;
265
+ const retried = scene.sync(projection.apply([])).unwrap();
266
+ expect(retried.bytes).toBe(4 * GPU_SCENE_LAYOUTS.material.stride);
267
+ expect(queue.writes).toHaveLength(1);
268
+ const roughness = gpuSceneFieldOffset(GPU_SCENE_LAYOUTS.material, 'roughness');
269
+ const upload = queue.writes[0];
270
+ if (upload === undefined) throw new Error('expected material upload');
271
+ for (let index = 0; index < 4; index++) {
272
+ expect(
273
+ new DataView(upload.bytes.buffer).getFloat32(
274
+ index * GPU_SCENE_LAYOUTS.material.stride + roughness,
275
+ true,
276
+ ),
277
+ ).toBeCloseTo(0.3);
278
+ }
279
+ // Fresh snapshots with identical bytes must not upload. Later in-place
280
+ // caller edits remain visible: packed rows are shared only within sync.
281
+ expect(scene.sync(projection.apply(updated.map(updateSnapshot))).unwrap().bytes).toBe(0);
282
+ changed.roughness = 0.6;
283
+ expect(scene.sync(projection.apply(updated.map(updateSnapshot))).unwrap().bytes).toBe(
284
+ 4 * GPU_SCENE_LAYOUTS.material.stride,
285
+ );
286
+ const first = updated[0];
287
+ const draw = first?.gpuDrivenDraws[0];
288
+ if (first === undefined || draw === undefined) throw new Error('expected draw fixture');
289
+ queue.writes.length = 0;
290
+ const geometryChanged = {
291
+ ...first,
292
+ localAabb: new Float32Array([-2, -1, -1, 2, 1, 1]),
293
+ gpuDrivenDraws: [{ ...draw, first: 6, count: 12, baseVertex: -3 }],
294
+ };
295
+ expect(scene.sync(projection.apply([updateSnapshot(geometryChanged)])).unwrap().bytes).toBe(
296
+ GPU_SCENE_LAYOUTS.primitive.stride + GPU_SCENE_LAYOUTS.drawTemplate.stride,
297
+ );
298
+ const drawUpload = queue.writes.find(
299
+ ({ bytes }) => bytes.length === GPU_SCENE_LAYOUTS.drawTemplate.stride,
300
+ );
301
+ if (drawUpload === undefined) throw new Error('expected changed draw upload');
302
+ expect(
303
+ new DataView(drawUpload.bytes.buffer).getInt32(
304
+ gpuSceneFieldOffset(GPU_SCENE_LAYOUTS.drawTemplate, 'baseVertex'),
305
+ true,
306
+ ),
307
+ ).toBe(-3);
308
+ scene.dispose();
309
+ });
310
+
166
311
  it('preserves authored base-color alpha in the scene material row', () => {
167
312
  const row = packStandardPbrMaterialRow(createStandardPbrArtifactReceipt(), {
168
313
  ...material,
@@ -399,6 +544,69 @@ describe('GpuScene', () => {
399
544
  });
400
545
  });
401
546
 
547
+ it('maps GPU previous instance transforms by stable generation after reorder', () => {
548
+ const queue = new RecordingQueue();
549
+ const created = GpuScene.create(createDevice(queue), 4).unwrap();
550
+ if (created.status !== 'available') return;
551
+ const projection = new RenderScene();
552
+ projection.setTemporalTracking(true);
553
+ const initial = multiInstanceSnapshot(0, [2, 8], [101, 202]);
554
+ created.scene.sync(projection.apply([updateSnapshot(initial)])).unwrap();
555
+ projection.commitSubmission(projection.captureSubmission([{ worldId: 0, entityKey: 0 }]));
556
+
557
+ const reordered = multiInstanceSnapshot(0, [9, 3], [202, 101]);
558
+ const delta = projection.apply([updateInstances(reordered)]);
559
+ const slot = projection.slotsSnapshot()[0];
560
+ expect(slot).toBeDefined();
561
+ if (slot === undefined) return;
562
+ expect(projection.temporalSnapshotBySlot(slot.slot)?.motionValid).toBe(true);
563
+
564
+ queue.writes.length = 0;
565
+ created.scene
566
+ .sync(delta, (current) => projection.temporalSnapshotBySlot(current.slot))
567
+ .unwrap();
568
+ const transformWrite = queue.writes.find(
569
+ ({ bytes }) => bytes.byteLength === 2 * GPU_SCENE_LAYOUTS.transform.stride,
570
+ );
571
+ expect(transformWrite).toBeDefined();
572
+ if (transformWrite === undefined) return;
573
+ const previousTranslation =
574
+ gpuSceneFieldOffset(GPU_SCENE_LAYOUTS.transform, 'previousWorld') + 12 * 4;
575
+ const transforms = new DataView(transformWrite.bytes.buffer);
576
+ expect(transforms.getFloat32(previousTranslation, true)).toBe(8);
577
+ expect(
578
+ transforms.getFloat32(GPU_SCENE_LAYOUTS.transform.stride + previousTranslation, true),
579
+ ).toBe(2);
580
+
581
+ created.scene.commitTemporalFrame().unwrap();
582
+ projection.commitSubmission(projection.captureSubmission([{ worldId: 0, entityKey: 0 }]));
583
+ const retired = multiInstanceSnapshot(0, [11], [202]);
584
+ const retiredDelta = projection.apply([updateInstances(retired)]);
585
+ expect(projection.temporalSnapshotBySlot(slot.slot)?.motionValid).toBe(true);
586
+ queue.writes.length = 0;
587
+ created.scene
588
+ .sync(retiredDelta, (current) => projection.temporalSnapshotBySlot(current.slot))
589
+ .unwrap();
590
+ const compactedRows = queue.writes
591
+ .filter(({ bytes }) => bytes.byteLength % GPU_SCENE_LAYOUTS.transform.stride === 0)
592
+ .flatMap(({ bytes }) =>
593
+ Array.from({ length: bytes.byteLength / GPU_SCENE_LAYOUTS.transform.stride }, (_, row) => {
594
+ const view = new DataView(bytes.buffer);
595
+ return {
596
+ current: view.getFloat32(
597
+ row * GPU_SCENE_LAYOUTS.transform.stride + currentWorldOffset() + 12 * 4,
598
+ true,
599
+ ),
600
+ previous: view.getFloat32(
601
+ row * GPU_SCENE_LAYOUTS.transform.stride + previousTranslation,
602
+ true,
603
+ ),
604
+ };
605
+ }),
606
+ );
607
+ expect(compactedRows).toContainEqual({ current: 11, previous: 9 });
608
+ });
609
+
402
610
  it('advances GPU previous transforms after a successful temporal commit', () => {
403
611
  const queue = new RecordingQueue();
404
612
  const created = GpuScene.create(createDevice(queue), 4).unwrap();
@@ -797,20 +1005,13 @@ describe('GpuScene', () => {
797
1005
  expect(delta.updated).toBe(1);
798
1006
  const result = created.scene.sync(delta).unwrap();
799
1007
  expect(result.bytes).toBe(
800
- GPU_SCENE_LAYOUTS.primitive.stride +
801
- GPU_SCENE_LAYOUTS.drawTemplate.stride +
802
- GPU_SCENE_LAYOUTS.material.stride +
803
- 2 * GPU_SCENE_LAYOUTS.transform.stride,
1008
+ GPU_SCENE_LAYOUTS.material.stride + 2 * GPU_SCENE_LAYOUTS.transform.stride,
804
1009
  );
805
1010
  expect(result.grew).toBe(false);
806
1011
  const next = { ...mixed, material: { ...nextMaterial, roughness: 0.7 } };
807
1012
  next.materials = [next.material];
808
1013
  const materialOnly = created.scene.sync(projection.apply([updateSnapshot(next)])).unwrap();
809
- expect(materialOnly.bytes).toBe(
810
- GPU_SCENE_LAYOUTS.primitive.stride +
811
- GPU_SCENE_LAYOUTS.drawTemplate.stride +
812
- GPU_SCENE_LAYOUTS.material.stride,
813
- );
1014
+ expect(materialOnly.bytes).toBe(GPU_SCENE_LAYOUTS.material.stride);
814
1015
  });
815
1016
  });
816
1017
 
@@ -0,0 +1,57 @@
1
+ import { err, RhiError } from '@forgeax/engine-rhi';
2
+ import { rhi } from '@forgeax/engine-rhi-null';
3
+ import { expect, it, vi } from 'vitest';
4
+ import { GPU_SHADER_STAGE_VERTEX } from '../gpu-stage';
5
+ import { GPU_BUFFER_USAGE_STORAGE } from '../gpu-usage';
6
+ import { resolveGeometryInstanceBuffer } from '../record/main-pass-geometry';
7
+
8
+ it('refuses an instance draw when allocation fails instead of drawing the identity fallback', async () => {
9
+ const device = (await (await rhi.requestAdapter()).unwrap().requestDevice()).unwrap();
10
+ const identity = device.createBuffer({ size: 128, usage: GPU_BUFFER_USAGE_STORAGE }).unwrap();
11
+ const layout = device
12
+ .createBindGroupLayout({
13
+ entries: [
14
+ { binding: 0, visibility: GPU_SHADER_STAGE_VERTEX, buffer: { type: 'read-only-storage' } },
15
+ ],
16
+ })
17
+ .unwrap();
18
+ const failure = new RhiError({
19
+ code: 'rhi-not-available',
20
+ expected: 'an instance buffer allocation',
21
+ hint: 'recover the renderer',
22
+ });
23
+ const allocation = vi.spyOn(device, 'createBuffer').mockReturnValue(err(failure));
24
+ const bindGroup = vi.spyOn(device, 'createBindGroup');
25
+ const fire = vi.fn();
26
+ try {
27
+ const result = resolveGeometryInstanceBuffer(
28
+ {
29
+ runtime: { device, errorRegistry: { fire } },
30
+ pipelineState: { identityInstanceBuffer: identity, instancesBindGroupLayout: layout },
31
+ frameState: { instanceBuffers: new Map() },
32
+ bindGroupCounts: { createBindGroup: 0, keys: [] },
33
+ } as unknown as Parameters<typeof resolveGeometryInstanceBuffer>[0],
34
+ {
35
+ source: {
36
+ worldId: 0,
37
+ entityKey: 1,
38
+ instances: {
39
+ transforms: new Float32Array(32),
40
+ instanceCount: 2,
41
+ cacheKey: 1,
42
+ archVersion: 0,
43
+ },
44
+ },
45
+ } as Parameters<typeof resolveGeometryInstanceBuffer>[1],
46
+ [],
47
+ false,
48
+ );
49
+ expect(result).toBeNull();
50
+ expect(fire).toHaveBeenCalledWith(failure);
51
+ expect(bindGroup).not.toHaveBeenCalled();
52
+ } finally {
53
+ allocation.mockRestore();
54
+ bindGroup.mockRestore();
55
+ device.destroyBuffer(identity).unwrap();
56
+ }
57
+ });