@forgeax/engine-render 0.1.29 → 0.1.31

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 (350) hide show
  1. package/README.md +248 -21
  2. package/dist/assembly/atmosphere-shader-prewarm.d.ts +1 -1
  3. package/dist/assembly/atmosphere-shader-prewarm.d.ts.map +1 -1
  4. package/dist/assembly/device-feature-admission.d.ts +4 -3
  5. package/dist/assembly/device-feature-admission.d.ts.map +1 -1
  6. package/dist/assembly/dynamic-geometry-host.d.ts +6 -0
  7. package/dist/assembly/dynamic-geometry-host.d.ts.map +1 -1
  8. package/dist/assembly/host-contract.d.ts +6 -0
  9. package/dist/assembly/host-contract.d.ts.map +1 -1
  10. package/dist/assembly/material/assembly.d.ts +26 -3
  11. package/dist/assembly/material/assembly.d.ts.map +1 -1
  12. package/dist/assembly/material/pipeline-helpers.d.ts +24 -0
  13. package/dist/assembly/material/pipeline-helpers.d.ts.map +1 -0
  14. package/dist/assembly/material-shader-policy.d.ts.map +1 -1
  15. package/dist/assembly/recovery/device-loss-fanout.d.ts.map +1 -1
  16. package/dist/assembly/recovery/recovery-attempt.d.ts +1 -6
  17. package/dist/assembly/recovery/recovery-attempt.d.ts.map +1 -1
  18. package/dist/assembly/recovery/renderer-recover.d.ts +1 -0
  19. package/dist/assembly/recovery/renderer-recover.d.ts.map +1 -1
  20. package/dist/assembly/renderer-facade.d.ts.map +1 -1
  21. package/dist/assembly/shader-prewarm-policy.d.ts +1 -1
  22. package/dist/assembly/shader-prewarm-policy.d.ts.map +1 -1
  23. package/dist/assembly/webgpu-ready.d.ts +1 -1
  24. package/dist/assembly/webgpu-ready.d.ts.map +1 -1
  25. package/dist/assembly/webgpu-renderer.d.ts.map +1 -1
  26. package/dist/capture/scene-projection.d.ts +41 -0
  27. package/dist/capture/scene-projection.d.ts.map +1 -0
  28. package/dist/{chunk-BQVQOQJH.mjs → chunk-7MQ3XBSP.mjs} +20 -14
  29. package/dist/chunk-7MQ3XBSP.mjs.map +1 -0
  30. package/dist/{chunk-S4DNKQYQ.mjs → chunk-C3DDSXYK.mjs} +86 -33
  31. package/dist/chunk-C3DDSXYK.mjs.map +1 -0
  32. package/dist/{chunk-BTWH7BP4.mjs → chunk-FUUO4UND.mjs} +73 -29
  33. package/dist/chunk-FUUO4UND.mjs.map +1 -0
  34. package/dist/{chunk-NRNXBLIJ.mjs → chunk-JRBDO2KB.mjs} +46 -6
  35. package/dist/chunk-JRBDO2KB.mjs.map +1 -0
  36. package/dist/{chunk-NICXSHAT.mjs → chunk-MDQJPY3T.mjs} +8 -6
  37. package/dist/chunk-MDQJPY3T.mjs.map +1 -0
  38. package/dist/{chunk-JQTSEI3Z.mjs → chunk-NO4WZCAH.mjs} +3 -3
  39. package/dist/chunk-NO4WZCAH.mjs.map +1 -0
  40. package/dist/{chunk-FUODTTIA.mjs → chunk-PSEQ6QPH.mjs} +621 -108
  41. package/dist/chunk-PSEQ6QPH.mjs.map +1 -0
  42. package/dist/{chunk-33XZFTQU.mjs → chunk-S5UE2T5P.mjs} +48 -6
  43. package/dist/chunk-S5UE2T5P.mjs.map +1 -0
  44. package/dist/components/directional-light.d.ts +2 -2
  45. package/dist/components/dynamic-resolution.d.ts +15 -0
  46. package/dist/components/dynamic-resolution.d.ts.map +1 -0
  47. package/dist/components/index.d.ts +1 -0
  48. package/dist/components/index.d.ts.map +1 -1
  49. package/dist/components/light-helpers.d.ts.map +1 -1
  50. package/dist/components/reflection-probe.d.ts +1 -0
  51. package/dist/components/reflection-probe.d.ts.map +1 -1
  52. package/dist/components/skylight.d.ts +4 -1
  53. package/dist/components/skylight.d.ts.map +1 -1
  54. package/dist/construct-renderer.mjs +2172 -796
  55. package/dist/construct-renderer.mjs.map +1 -1
  56. package/dist/device/device-scope.d.ts +2 -0
  57. package/dist/device/device-scope.d.ts.map +1 -1
  58. package/dist/device/gpu-residency.d.ts +2 -2
  59. package/dist/device/gpu-residency.d.ts.map +1 -1
  60. package/dist/dynamic-geometry.d.ts.map +1 -1
  61. package/dist/environment/background.d.ts +3 -2
  62. package/dist/environment/background.d.ts.map +1 -1
  63. package/dist/environment/ibl.d.ts +11 -0
  64. package/dist/environment/ibl.d.ts.map +1 -0
  65. package/dist/environment/storage.d.ts +14 -0
  66. package/dist/environment/storage.d.ts.map +1 -0
  67. package/dist/errors/gpu-driven.d.ts +2 -2
  68. package/dist/errors/gpu-driven.d.ts.map +1 -1
  69. package/dist/errors/render.d.ts +35 -1
  70. package/dist/errors/render.d.ts.map +1 -1
  71. package/dist/extract/camera.d.ts.map +1 -1
  72. package/dist/extract/contracts.d.ts +7 -0
  73. package/dist/extract/contracts.d.ts.map +1 -1
  74. package/dist/extract/gpu-driven.d.ts +18 -1
  75. package/dist/extract/gpu-driven.d.ts.map +1 -1
  76. package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
  77. package/dist/gpu-driven/material-artifacts.d.ts +24 -0
  78. package/dist/gpu-driven/material-artifacts.d.ts.map +1 -0
  79. package/dist/gpu-driven/material-bindings.d.ts +7 -0
  80. package/dist/gpu-driven/material-bindings.d.ts.map +1 -0
  81. package/dist/gpu-driven/prepared-draw.d.ts +2 -0
  82. package/dist/gpu-driven/prepared-draw.d.ts.map +1 -1
  83. package/dist/gpu-driven/production-raster.d.ts +27 -6
  84. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  85. package/dist/gpu-resource.d.ts +5 -2
  86. package/dist/gpu-resource.d.ts.map +1 -1
  87. package/dist/gpu-scene-schema.d.ts.map +1 -1
  88. package/dist/gpu-scene.d.ts.map +1 -1
  89. package/dist/hdrp-buffers.d.ts +3 -0
  90. package/dist/hdrp-buffers.d.ts.map +1 -1
  91. package/dist/ibl/IblPipelineCache.d.ts +2 -0
  92. package/dist/ibl/IblPipelineCache.d.ts.map +1 -1
  93. package/dist/ibl/skylight-bind-group.d.ts +3 -1
  94. package/dist/ibl/skylight-bind-group.d.ts.map +1 -1
  95. package/dist/index.d.ts +83 -81
  96. package/dist/index.d.ts.map +1 -1
  97. package/dist/index.mjs +7 -5
  98. package/dist/index.mjs.map +1 -1
  99. package/dist/inspection-types.d.ts +16 -1
  100. package/dist/inspection-types.d.ts.map +1 -1
  101. package/dist/internal.mjs +6 -6
  102. package/dist/material-row.d.ts +10 -0
  103. package/dist/material-row.d.ts.map +1 -1
  104. package/dist/pbr-pipeline.d.ts +3 -1
  105. package/dist/pbr-pipeline.d.ts.map +1 -1
  106. package/dist/pipeline/render-extent.d.ts +51 -0
  107. package/dist/pipeline/render-extent.d.ts.map +1 -0
  108. package/dist/pipeline/standard-build-context.d.ts +7 -0
  109. package/dist/pipeline/standard-build-context.d.ts.map +1 -0
  110. package/dist/pipeline/standard-forward-lane.d.ts +3 -2
  111. package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
  112. package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
  113. package/dist/pipeline/standard-post.d.ts +1 -1
  114. package/dist/pipeline/standard-post.d.ts.map +1 -1
  115. package/dist/pipeline/standard-profile.d.ts +2 -1
  116. package/dist/pipeline/standard-profile.d.ts.map +1 -1
  117. package/dist/pipeline-builder.d.ts +3 -1
  118. package/dist/pipeline-builder.d.ts.map +1 -1
  119. package/dist/plugin.d.ts.map +1 -1
  120. package/dist/post-process-errors.d.ts +9 -2
  121. package/dist/post-process-errors.d.ts.map +1 -1
  122. package/dist/prepare/extended-lighting/resources.d.ts +3 -3
  123. package/dist/record/frame-lighting.d.ts.map +1 -1
  124. package/dist/record/frame-snapshot.d.ts +3 -8
  125. package/dist/record/frame-snapshot.d.ts.map +1 -1
  126. package/dist/record/frame.d.ts +3 -2
  127. package/dist/record/frame.d.ts.map +1 -1
  128. package/dist/record/main-pass-geometry.d.ts +7 -2
  129. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  130. package/dist/record/main-pass-material.d.ts +6 -3
  131. package/dist/record/main-pass-material.d.ts.map +1 -1
  132. package/dist/record/main-pass.d.ts +2 -0
  133. package/dist/record/main-pass.d.ts.map +1 -1
  134. package/dist/record/prepared-material-bindings.d.ts.map +1 -1
  135. package/dist/record/recovery-pipeline.d.ts +9 -1
  136. package/dist/record/recovery-pipeline.d.ts.map +1 -1
  137. package/dist/record/render-context.d.ts +34 -3
  138. package/dist/record/render-context.d.ts.map +1 -1
  139. package/dist/record/shadow-pass.d.ts +2 -2
  140. package/dist/record/shadow-pass.d.ts.map +1 -1
  141. package/dist/record/typed-frame-graph.d.ts +5 -2
  142. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  143. package/dist/recovery/render-system-candidate.d.ts.map +1 -1
  144. package/dist/reflection/background.d.ts +14 -0
  145. package/dist/reflection/background.d.ts.map +1 -0
  146. package/dist/reflection/filter.d.ts.map +1 -1
  147. package/dist/reflection/projection.d.ts +1 -0
  148. package/dist/reflection/projection.d.ts.map +1 -1
  149. package/dist/reflection/record-owner.d.ts +4 -2
  150. package/dist/reflection/record-owner.d.ts.map +1 -1
  151. package/dist/render-contract.d.ts +47 -32
  152. package/dist/render-contract.d.ts.map +1 -1
  153. package/dist/render-graph-primitives.d.ts.map +1 -1
  154. package/dist/render-pipeline.d.ts +17 -1
  155. package/dist/render-pipeline.d.ts.map +1 -1
  156. package/dist/render-system-extract-tail.d.ts +9 -2
  157. package/dist/render-system-extract-tail.d.ts.map +1 -1
  158. package/dist/render-system-extract.d.ts +29 -2
  159. package/dist/render-system-extract.d.ts.map +1 -1
  160. package/dist/render-system.d.ts.map +1 -1
  161. package/dist/scene/render-scene.d.ts +2 -0
  162. package/dist/scene/render-scene.d.ts.map +1 -1
  163. package/dist/ssao-config.d.ts +8 -6
  164. package/dist/ssao-config.d.ts.map +1 -1
  165. package/dist/ssr/graph.d.ts.map +1 -1
  166. package/dist/temporal/gpu.d.ts.map +1 -1
  167. package/dist/temporal/index.mjs +5 -5
  168. package/dist/temporal/standard-scene-data.d.ts +2 -1
  169. package/dist/temporal/standard-scene-data.d.ts.map +1 -1
  170. package/dist/temporal/target-coverage-attachment.d.ts +15 -0
  171. package/dist/temporal/target-coverage-attachment.d.ts.map +1 -0
  172. package/dist/temporal/temporal-view.d.ts +6 -2
  173. package/dist/temporal/temporal-view.d.ts.map +1 -1
  174. package/dist/temporal/view.d.ts +6 -0
  175. package/dist/temporal/view.d.ts.map +1 -1
  176. package/dist/typed-render-graph-primitives.d.ts +6 -0
  177. package/dist/typed-render-graph-primitives.d.ts.map +1 -1
  178. package/dist/volume/component.d.ts +1 -0
  179. package/dist/volume/component.d.ts.map +1 -1
  180. package/package.json +21 -21
  181. package/src/__tests__/capture-scene-projection.unit.test.ts +24 -0
  182. package/src/__tests__/device-feature-admission.unit.test.ts +15 -24
  183. package/src/__tests__/device-loss-fanout.unit.test.ts +43 -0
  184. package/src/__tests__/dynamic-geometry-batch.unit.test.ts +157 -0
  185. package/src/__tests__/dynamic-geometry.unit.test.ts +36 -0
  186. package/src/__tests__/dynamic-resolution-plugin.integration.test.ts +81 -0
  187. package/src/__tests__/factory-contract.integration.test.ts +164 -1
  188. package/src/__tests__/frame-observation.contract.test.ts +21 -0
  189. package/src/__tests__/gpu-driven-baseline.characterization.test.ts +1 -1
  190. package/src/__tests__/gpu-driven-batch-topology-pbr.unit.test.ts +20 -1
  191. package/src/__tests__/gpu-driven-lod-cache.unit.test.ts +0 -3
  192. package/src/__tests__/gpu-driven-mesh-world-identity.unit.test.ts +0 -3
  193. package/src/__tests__/gpu-driven-pbr.dawn.test.ts +11 -6
  194. package/src/__tests__/gpu-driven-production.integration.test.ts +13 -25
  195. package/src/__tests__/gpu-driven-view.unit.test.ts +1 -0
  196. package/src/__tests__/gpu-pass-timing-browser-runner.ts +15 -0
  197. package/src/__tests__/gpu-pass-timing.browser.test.ts +14 -1
  198. package/src/__tests__/gpu-resource-scope.unit.test.ts +71 -0
  199. package/src/__tests__/gpu-scene.unit.test.ts +55 -1
  200. package/src/__tests__/materials-standard-contract.unit.test.ts +5 -1
  201. package/src/__tests__/mesh-submission-lifetime.unit.test.ts +8 -0
  202. package/src/__tests__/morph-neutral-admission.unit.test.ts +19 -0
  203. package/src/__tests__/physical-clearcoat.integration.test.ts +6 -1
  204. package/src/__tests__/post-process-error-policy-owner.unit.test.ts +6 -5
  205. package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +216 -5
  206. package/src/__tests__/reflection-fallback-async-publication.unit.test.ts +68 -1
  207. package/src/__tests__/reflection-probe-authoring.unit.test.ts +12 -10
  208. package/src/__tests__/reflection-probe-box.dawn.test.ts +71 -0
  209. package/src/__tests__/reflection-probe-filter.unit.test.ts +9 -0
  210. package/src/__tests__/reflection-probe-orientation.dawn.test.ts +294 -0
  211. package/src/__tests__/reflection-probe-record-owner.characterization.unit.test.ts +5 -15
  212. package/src/__tests__/reflection-probe-updates.unit.test.ts +129 -0
  213. package/src/__tests__/render-error-code-owner.test-d.ts +3 -0
  214. package/src/__tests__/render-error-detail-ssot.test-d.ts +5 -1
  215. package/src/__tests__/render-extent.unit.test.ts +63 -0
  216. package/src/__tests__/render-scene-projection.unit.test.ts +3 -2
  217. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +173 -9
  218. package/src/__tests__/shader-manifest-fixture.ts +6 -0
  219. package/src/__tests__/skinned-shadow-caster.test.ts +8 -1
  220. package/src/__tests__/specular-environment-scale.dawn.test.ts +4 -2
  221. package/src/__tests__/ssao-config.unit.test.ts +7 -0
  222. package/src/__tests__/ssao-surface.dawn.test.ts +252 -0
  223. package/src/__tests__/ssr-cube-silhouette.dawn.test.ts +270 -0
  224. package/src/__tests__/ssr-gpu-dispatch.browser.test.ts +51 -43
  225. package/src/__tests__/ssr-gpu-dispatch.dawn.test.ts +4 -0
  226. package/src/__tests__/ssr-gpu-dispatch.ts +10 -5
  227. package/src/__tests__/ssr-graph-topology.unit.test.ts +2 -2
  228. package/src/__tests__/ssr-hiz.browser.test.ts +1 -1
  229. package/src/__tests__/ssr-hiz.dawn.test.ts +1 -1
  230. package/src/__tests__/ssr-spatial.integration.test.ts +1 -1
  231. package/src/__tests__/standard-cluster-variant-resolution.unit.test.ts +40 -0
  232. package/src/__tests__/standard-pbr-artifact-assembly.unit.test.ts +238 -1
  233. package/src/__tests__/standard-profile-oracle.unit.test.ts +27 -0
  234. package/src/__tests__/taa-coverage-edge.dawn.test.ts +6 -0
  235. package/src/__tests__/taa-neighborhood-equivalence.dawn.test.ts +5 -1
  236. package/src/__tests__/taa-stability-feedback.ts +1 -0
  237. package/src/__tests__/temporal-reset-matrix.unit.test.ts +2 -2
  238. package/src/__tests__/temporal-view.unit.test.ts +17 -0
  239. package/src/__tests__/transmission-prewarm.unit.test.ts +57 -0
  240. package/src/__tests__/volumetric-fog-stage-readback.dawn.test.ts +62 -13
  241. package/src/__tests__/volumetric-fog-topology.unit.test.ts +7 -7
  242. package/src/assembly/atmosphere-shader-prewarm.ts +3 -2
  243. package/src/assembly/device-feature-admission.ts +9 -4
  244. package/src/assembly/dynamic-geometry-host.ts +236 -163
  245. package/src/assembly/host-contract.ts +9 -0
  246. package/src/assembly/material/assembly.ts +350 -6
  247. package/src/assembly/material/pipeline-helpers.ts +100 -0
  248. package/src/assembly/material-shader-policy.ts +39 -18
  249. package/src/assembly/recovery/device-loss-fanout.ts +5 -0
  250. package/src/assembly/recovery/recovery-attempt.ts +2 -29
  251. package/src/assembly/recovery/renderer-recover.ts +6 -9
  252. package/src/assembly/renderer-facade.ts +22 -1
  253. package/src/assembly/shader-prewarm-policy.ts +14 -1
  254. package/src/assembly/webgpu-ready.ts +28 -19
  255. package/src/assembly/webgpu-renderer.ts +95 -95
  256. package/src/capture/scene-projection.ts +37 -0
  257. package/src/components/__tests__/dynamic-resolution-schema.unit.test.ts +65 -0
  258. package/src/components/__tests__/light-radius.integration.test.ts +59 -0
  259. package/src/components/directional-light.ts +3 -3
  260. package/src/components/dynamic-resolution.ts +67 -0
  261. package/src/components/index.ts +1 -0
  262. package/src/components/light-helpers.ts +6 -1
  263. package/src/components/reflection-probe.ts +1 -0
  264. package/src/components/skylight.ts +4 -1
  265. package/src/device/device-scope.ts +13 -7
  266. package/src/device/gpu-residency.ts +11 -11
  267. package/src/dynamic-geometry.ts +13 -8
  268. package/src/environment/background.ts +30 -12
  269. package/src/environment/ibl.ts +211 -0
  270. package/src/environment/storage.ts +63 -0
  271. package/src/errors/gpu-driven.ts +10 -3
  272. package/src/errors/render.ts +64 -0
  273. package/src/extract/camera.ts +12 -1
  274. package/src/extract/contracts.ts +10 -0
  275. package/src/extract/gpu-driven.ts +72 -17
  276. package/src/gpu-driven/batch-topology.ts +33 -11
  277. package/src/gpu-driven/material-artifacts.ts +179 -0
  278. package/src/gpu-driven/material-bindings.ts +41 -0
  279. package/src/gpu-driven/prepared-draw.ts +13 -2
  280. package/src/gpu-driven/production-raster.ts +681 -164
  281. package/src/gpu-resource.ts +21 -2
  282. package/src/gpu-scene-schema.ts +11 -2
  283. package/src/gpu-scene.ts +14 -3
  284. package/src/hdrp-buffers.ts +69 -0
  285. package/src/ibl/IblPipelineCache.ts +5 -0
  286. package/src/ibl/skylight-bind-group.ts +27 -17
  287. package/src/index.ts +92 -81
  288. package/src/inspection-types.ts +16 -1
  289. package/src/material-row.ts +60 -11
  290. package/src/pbr-pipeline.ts +13 -2
  291. package/src/pipeline/render-extent.ts +111 -0
  292. package/src/pipeline/standard-build-context.ts +7 -0
  293. package/src/pipeline/standard-forward-lane.ts +41 -24
  294. package/src/pipeline/standard-pipeline.ts +47 -18
  295. package/src/pipeline/standard-post.ts +4 -0
  296. package/src/pipeline/standard-profile.ts +2 -1
  297. package/src/pipeline-builder.ts +12 -3
  298. package/src/plugin.ts +4 -0
  299. package/src/post-process-errors.ts +26 -18
  300. package/src/prepare/__tests__/extended-lighting-capability.unit.test.ts +2 -2
  301. package/src/prepare/extended-lighting/resources.ts +3 -3
  302. package/src/record/__tests__/main-pass-geometry.unit.test.ts +68 -0
  303. package/src/record/__tests__/material-bg-cache.unit.test.ts +45 -0
  304. package/src/record/__tests__/material-slot-plan.unit.test.ts +9 -0
  305. package/src/record/__tests__/recovery-pipeline.unit.test.ts +47 -1
  306. package/src/record/__tests__/sprite-material-abi.unit.test.ts +7 -3
  307. package/src/record/__tests__/temporal-payload.unit.test.ts +2 -0
  308. package/src/record/frame-lighting.ts +3 -1
  309. package/src/record/frame-snapshot.ts +13 -11
  310. package/src/record/frame.ts +150 -74
  311. package/src/record/main-pass-geometry.ts +60 -9
  312. package/src/record/main-pass-material.ts +47 -19
  313. package/src/record/main-pass.ts +63 -4
  314. package/src/record/prepared-material-bindings.ts +4 -0
  315. package/src/record/recovery-pipeline.ts +56 -0
  316. package/src/record/render-context.ts +34 -3
  317. package/src/record/shadow-pass.ts +187 -50
  318. package/src/record/typed-frame-graph.ts +60 -3
  319. package/src/record/view-ubo.ts +1 -1
  320. package/src/recovery/render-system-candidate.ts +54 -11
  321. package/src/reflection/background.ts +151 -0
  322. package/src/reflection/filter.ts +3 -0
  323. package/src/reflection/projection.ts +16 -6
  324. package/src/reflection/record-owner.ts +282 -86
  325. package/src/render-contract.ts +50 -32
  326. package/src/render-graph-primitives.ts +28 -3
  327. package/src/render-pipeline.ts +20 -1
  328. package/src/render-system-extract-tail.ts +23 -3
  329. package/src/render-system-extract.ts +127 -42
  330. package/src/render-system.ts +35 -7
  331. package/src/scene/render-scene.ts +8 -0
  332. package/src/ssao-config.ts +25 -8
  333. package/src/ssr/graph.ts +78 -58
  334. package/src/temporal/gpu.ts +5 -0
  335. package/src/temporal/standard-scene-data.ts +7 -1
  336. package/src/temporal/target-coverage-attachment.ts +72 -0
  337. package/src/temporal/temporal-view.ts +10 -4
  338. package/src/temporal/view.ts +12 -2
  339. package/src/transmission/__tests__/standard-transmission.dawn.test.ts +24 -9
  340. package/src/typed-render-graph-primitives.ts +38 -0
  341. package/src/volume/component.ts +1 -0
  342. package/src/volume/passes.ts +3 -3
  343. package/dist/chunk-33XZFTQU.mjs.map +0 -1
  344. package/dist/chunk-BQVQOQJH.mjs.map +0 -1
  345. package/dist/chunk-BTWH7BP4.mjs.map +0 -1
  346. package/dist/chunk-FUODTTIA.mjs.map +0 -1
  347. package/dist/chunk-JQTSEI3Z.mjs.map +0 -1
  348. package/dist/chunk-NICXSHAT.mjs.map +0 -1
  349. package/dist/chunk-NRNXBLIJ.mjs.map +0 -1
  350. package/dist/chunk-S4DNKQYQ.mjs.map +0 -1
@@ -1,6 +1,5 @@
1
1
  // @forgeax/engine-render — WebGPU renderer lifecycle/frame interpreter.
2
2
  // Runtime selects the backend; structured RHI errors and browser isolation stay intact.
3
-
4
3
  import {
5
4
  AssetRegistry,
6
5
  adaptDynamicTextureDevice,
@@ -38,6 +37,7 @@ import type {
38
37
  PrimitiveTopology,
39
38
  VertexAttributeMap,
40
39
  } from '@forgeax/engine-types';
40
+ import { transmissionBackdropAvailable } from './device-feature-admission';
41
41
 
42
42
  export type { MaterialShaderManifestEntry } from '@forgeax/engine-shader';
43
43
 
@@ -61,7 +61,6 @@ import {
61
61
  postProcessShaderModuleLabel,
62
62
  } from '../fullscreen-post-process-pass';
63
63
  import { glyphTextLayoutSystem } from '../glyph-text-layout-system';
64
- import { resolveStandardPbrProgram } from '../gpu-driven/pbr-program';
65
64
  import { GPU_SHADER_STAGE_FRAGMENT, GPU_SHADER_STAGE_VERTEX } from '../gpu-stage';
66
65
  import type { BloomInspection } from '../inspection-types';
67
66
  import { DeviceScope } from '../lifecycle';
@@ -147,6 +146,13 @@ import type { RhiBackendPack } from './backend-contract';
147
146
  import type { BundlerOptions } from './bundler-contract';
148
147
  import { createRendererDynamicGeometryController } from './dynamic-geometry-host';
149
148
  import type { RendererAssemblyImplementation } from './host-contract';
149
+ import {
150
+ isEngineOwnedMaterialShader,
151
+ resolveMaterialPipelineBindGroups,
152
+ resolveMaterialPipelineShaderId,
153
+ resolveMaterialPipelineVertexEntry,
154
+ resolveRendererMaterialShaderArtifact,
155
+ } from './material/pipeline-helpers';
150
156
  import {
151
157
  allowsUnlitPreparedFallback,
152
158
  invokeDeviceCreateShaderModule,
@@ -243,7 +249,6 @@ export {
243
249
  selectPipelineLayoutForVariant,
244
250
  shouldDeferMissingPreparedMaterialShader,
245
251
  } from './material-shader-policy';
246
-
247
252
  export {
248
253
  createMeshSsboGrowController,
249
254
  deriveStorageBufferCeiling,
@@ -282,6 +287,7 @@ async function makeWebGPURenderer(
282
287
  internals: WebGPURendererInternals,
283
288
  ): Promise<RendererAssemblyImplementation> {
284
289
  let disposed = false;
290
+ const observeTiming = observeGpuPassTimingDisabled;
285
291
  const frameContinuations = new Set<ContinuationTerminator>();
286
292
  type RecoveryInspection = RenderInspection['recovery'];
287
293
  let recoveryAttempt = 0;
@@ -571,19 +577,11 @@ async function makeWebGPURenderer(
571
577
  // ShaderCatalogDevice adapter (shared between the catalog and per-MaterialShader
572
578
  // pipeline cache) is already available at this point.
573
579
  const shaderCatalog = getShader();
574
- // feat-20260528-material-shader-registration-unification M3 / w14:
575
- // placeholder hardcoded installMaterialArtifact calls deleted.
576
580
  // bug-20260601-hello-tonemap-material-register M1 (plan-strategy D-1):
577
581
  // All material-shader entries are now registered from the manifest
578
582
  // BEFORE the renderer is returned, so `register<MaterialAsset>` referencing
579
583
  // an engine shader succeeds without waiting for `renderer.initialization`.
580
-
581
- // feat-20260623-asset-payload-generic-open-registry M3 / w10: host apps
582
- // that need custom loaders register them on `assets.loaders.register(...)`.
583
- // feat-20260604-hdr-equirect-cube-importer-loader M4 / w16 (D-3 / AC-05):
584
- // the host-injected ImportTransport (or undefined for the shipped form) is
585
- // threaded into the AssetRegistry ctor -- the construction-time-
586
- // only single injection point (no setter, no illegal intermediate state).
584
+ // Host-injected ImportTransport remains a constructor-only dependency.
587
585
  // feat-20260705-runtime-tier2-decomposition M1 / w10 (D-1 / D-2): the sole
588
586
  // production assembly point injects (a) the post-spawn hook
589
587
  // `postSpawnResolveJoints` (auto-wire Skin.joints; D-1) and the audio
@@ -691,7 +689,6 @@ async function makeWebGPURenderer(
691
689
  etc2: internals.device.caps.textureCompressionEtc2,
692
690
  astc: internals.device.caps.textureCompressionAstc,
693
691
  });
694
-
695
692
  // M1 (bug-20260601-hello-tonemap-material-register D-1/D-2): prepare
696
693
  // engine-shipped material shaders (cap gate + manifest load + registration)
697
694
  // so they are available in ShaderCatalog before `register<MaterialAsset>`.
@@ -799,14 +796,7 @@ async function makeWebGPURenderer(
799
796
  { materialBgl: BindGroupLayout; pipelineLayout: PipelineLayout }
800
797
  >();
801
798
  const preparedMaterialPipelineLayoutCache = new Map<string, PipelineLayout>();
802
- // feat-20260621-learn-render-5-5-parallax M2 / w6 (D-1): per-shader material
803
- // BGL + pipeline layout cache. Every authored material shader owns the
804
- // user-region binding order emitted by its paramSchema, plus the shared
805
- // engine-injection suffix. A custom shader with four-or-fewer textures can
806
- // still differ from the standard PBR order (for example, baseColorTexture
807
- // followed directly by normalTexture), so it cannot safely reuse the
808
- // standard material BGL. Built lazily on first request and cached by
809
- // shaderId; built-in shader IDs continue to use the boot-time shared layout.
799
+ // Cache authored material layouts by their exact mesh and lighting contract.
810
800
  type PerShaderMaterialLayout = {
811
801
  materialBgl: BindGroupLayout;
812
802
  pipelineLayout: PipelineLayout;
@@ -818,16 +808,6 @@ async function makeWebGPURenderer(
818
808
  readonly layout: PerShaderMaterialLayout | null;
819
809
  };
820
810
  const perShaderMaterialLayoutCache = new Map<string, PerShaderMaterialLayoutCacheEntry>();
821
- const isEngineOwnedMaterialShader = (materialShaderId: string): boolean =>
822
- materialShaderId === 'forgeax::default-unlit' ||
823
- materialShaderId === 'forgeax::default-shadow-caster' ||
824
- materialShaderId === 'forgeax::sprite' ||
825
- materialShaderId === 'forgeax::sprite-lit' ||
826
- materialShaderId === 'forgeax::default-sprite' ||
827
- materialShaderId === 'forgeax::msdf-text' ||
828
- materialShaderId === 'forgeax::default-standard-pbr' ||
829
- materialShaderId === 'forgeax::pbr-skin' ||
830
- materialShaderId === 'forgeax::default-standard-pbr-skin';
831
811
  // Material binding contracts are derived from WGSL source, but the lookup
832
812
  // is also used by the per-submesh bind-group path. Cache the derived value
833
813
  // per renderer so a frame does not re-run the comment stripping and regex
@@ -1139,16 +1119,18 @@ async function makeWebGPURenderer(
1139
1119
  materialShaderId: string,
1140
1120
  explicitParamSchema?: readonly ParamSchemaEntry[],
1141
1121
  layoutKind: LayoutKind = 'pbr',
1122
+ clustered = false,
1142
1123
  ): PerShaderMaterialLayout | null => {
1143
1124
  const cacheState = currentPipelineCacheState();
1144
1125
  const currentState = currentPipelineState();
1145
1126
  if (currentState === null) return null;
1146
- if (isEngineOwnedMaterialShader(materialShaderId)) return null;
1127
+ if (isEngineOwnedMaterialShader(materialShaderId) && !clustered) return null;
1147
1128
  const lookup = getShader().findMaterialArtifact(materialShaderId);
1148
1129
  const source = lookup.ok ? lookup.value.source : '';
1149
1130
  const paramSchema = lookup.ok ? lookup.value.paramSchema : explicitParamSchema;
1150
1131
  if (paramSchema === undefined) return null;
1151
- const cached = cacheState.perShaderMaterialLayoutCache.get(materialShaderId);
1132
+ const cacheKey = `${materialShaderId}:${layoutKind}:${clustered}`;
1133
+ const cached = cacheState.perShaderMaterialLayoutCache.get(cacheKey);
1152
1134
  if (
1153
1135
  cached?.source === source &&
1154
1136
  cached.paramSchema === paramSchema &&
@@ -1164,8 +1146,8 @@ async function makeWebGPURenderer(
1164
1146
  isStandardPbrMaterialShader(materialShaderId) ||
1165
1147
  standardPhysicalTextureFields(paramSchema).length > 0;
1166
1148
  const isStandardMapLayout = requiresStandardMapLayout;
1167
- if (!isStandardMapLayout && isSharedMaterialUserRegionCompatible(paramSchema)) {
1168
- cacheState.perShaderMaterialLayoutCache.set(materialShaderId, {
1149
+ if (!clustered && !isStandardMapLayout && isSharedMaterialUserRegionCompatible(paramSchema)) {
1150
+ cacheState.perShaderMaterialLayoutCache.set(cacheKey, {
1169
1151
  source,
1170
1152
  paramSchema,
1171
1153
  layoutKind,
@@ -1182,27 +1164,27 @@ async function makeWebGPURenderer(
1182
1164
  const desc = buildBindGroupLayoutDescriptor(spec, {
1183
1165
  kind: 'pbr-material-merged',
1184
1166
  materialParamSchema: paramSchema,
1185
- caps: { storageBuffer: currentBuildDevice().caps.storageBuffer },
1167
+ caps: {
1168
+ storageBuffer: currentBuildDevice().caps.storageBuffer,
1169
+ transmissionBackdrop: transmissionBackdropAvailable(
1170
+ currentBuildDevice().limits.maxSampledTexturesPerShaderStage,
1171
+ ),
1172
+ },
1186
1173
  });
1187
1174
  const bglRes = currentBuildDevice().createBindGroupLayout(desc);
1188
1175
  if (!bglRes.ok) {
1189
1176
  internals.errorRegistry.fire(bglRes.error);
1190
1177
  return null;
1191
1178
  }
1192
- const meshLayout =
1193
- layoutKind === 'pbr-skin'
1194
- ? currentState.pbrSkinMeshBindGroupLayout
1195
- : layoutKind === 'hdrp-skin'
1196
- ? currentState.hdrpSkinMeshBindGroupLayout
1197
- : currentState.meshBindGroupLayout;
1198
- if (meshLayout === null) return null;
1179
+ const bindGroups = resolveMaterialPipelineBindGroups(layoutKind, currentState, clustered);
1180
+ if (bindGroups === null) return null;
1199
1181
  const plRes = currentBuildDevice().createPipelineLayout({
1200
1182
  label: `pbr-pl-${materialShaderId}`,
1201
1183
  bindGroupLayouts: [
1202
1184
  currentState.viewBindGroupLayout,
1203
1185
  bglRes.value,
1204
- meshLayout,
1205
- currentState.instancesBindGroupLayout,
1186
+ bindGroups.meshLayout,
1187
+ bindGroups.instancesLayout,
1206
1188
  ],
1207
1189
  });
1208
1190
  if (!plRes.ok) {
@@ -1210,7 +1192,7 @@ async function makeWebGPURenderer(
1210
1192
  return null;
1211
1193
  }
1212
1194
  const built = { materialBgl: bglRes.value, pipelineLayout: plRes.value };
1213
- cacheState.perShaderMaterialLayoutCache.set(materialShaderId, {
1195
+ cacheState.perShaderMaterialLayoutCache.set(cacheKey, {
1214
1196
  source,
1215
1197
  paramSchema,
1216
1198
  layoutKind,
@@ -1320,7 +1302,13 @@ async function makeWebGPURenderer(
1320
1302
  residencyStore,
1321
1303
  internals.pack.createShaderModule,
1322
1304
  internals.errorRegistry,
1323
- requiredMaterialShaders,
1305
+ candidateBuildState === undefined
1306
+ ? requiredMaterialShaders
1307
+ : [
1308
+ ...new Set([...requiredMaterialShaders, ...getShader().materialShaderIdentifiers()]),
1309
+ ].filter(
1310
+ (id) => requiredMaterialShaders.includes(id) || !isEngineOwnedMaterialShader(id),
1311
+ ),
1324
1312
  materialShaderUvSetCounts.get('forgeax::default-standard-pbr') ?? 0,
1325
1313
  requiredFullscreenPostProcesses,
1326
1314
  // Publish growth and capacity together for the active or candidate device.
@@ -1333,14 +1321,12 @@ async function makeWebGPURenderer(
1333
1321
  internals.meshSsboState = state;
1334
1322
  }
1335
1323
  },
1336
- // feat-20260609 R3-fixup: seed the lazy adapter cache with the
1337
- // eagerly-compiled shadow_caster module.
1324
+ // Both module adapters consume the generation's eager shader cache.
1338
1325
  (label, module) => {
1339
1326
  getShaderModuleAdapter().seedModule(label, module);
1327
+ getImmediateShaderModuleAdapter().seedModule(label, module);
1340
1328
  },
1341
- // M6 fix-up: seed the active or candidate material pipeline cache from
1342
- // the boot-time SPEC_CONST prewarm. Recovery builds must never put a
1343
- // candidate-device PSO into the retired generation's cache.
1329
+ // Prewarm only the active build generation's material pipeline cache.
1344
1330
  (key, pso, group2Contract) => {
1345
1331
  const pipelineCache = currentPipelineCacheState().materialShaderPipelineCache;
1346
1332
  if (!pipelineCache.has(key)) {
@@ -1475,20 +1461,21 @@ async function makeWebGPURenderer(
1475
1461
  ? 'pbr-skin'
1476
1462
  : 'pbr';
1477
1463
  const layoutKind = layoutKindOverride ?? contractLayoutKind;
1464
+ const gpuClustered =
1465
+ (layoutKind === 'gpu-driven-pbr' || layoutKind === 'gpu-driven-skin') &&
1466
+ (group2Contract === 'cluster' || group2Contract === 'skin-cluster');
1478
1467
  const usesSharedBootMaterialLayout =
1479
- materialShaderId !== undefined && isEngineOwnedMaterialShader(materialShaderId);
1468
+ !gpuClustered &&
1469
+ materialShaderId !== undefined &&
1470
+ isEngineOwnedMaterialShader(materialShaderId);
1480
1471
  // Built-in layouts are assembled at boot; authored layouts must not bypass
1481
1472
  // the HDRP group(2) selector or bind the URP mesh BGL to a cluster PSO.
1482
1473
  const perShaderLayout =
1483
- !usesSharedBootMaterialLayout &&
1484
- layoutKind !== 'pbr-skin' &&
1485
- layoutKind !== 'gpu-driven-pbr' &&
1486
- layoutKind !== 'gpu-driven-skin' &&
1487
- materialShaderId !== undefined
1488
- ? getOrBuildPerShaderMaterialLayout(materialShaderId, undefined, layoutKind)
1474
+ !usesSharedBootMaterialLayout && materialShaderId !== undefined
1475
+ ? getOrBuildPerShaderMaterialLayout(materialShaderId, undefined, layoutKind, gpuClustered)
1489
1476
  : null;
1490
- // Authored shaders use their paramSchema; skin/HDRP retain dedicated
1491
- // layouts and built-in IDs stay on the shared boot-time layout.
1477
+ // Authored shaders retain their own material schema and selected mesh
1478
+ // layout, including skin. Built-in IDs keep their boot-time layout.
1492
1479
  const bindingContract =
1493
1480
  materialShaderId === undefined
1494
1481
  ? 'render-material'
@@ -1634,6 +1621,7 @@ async function makeWebGPURenderer(
1634
1621
  vertexEntry?: string,
1635
1622
  authoredFragmentEntry?: string,
1636
1623
  particleInputLanes?: number,
1624
+ coverageOnly = false,
1637
1625
  ): RenderPipeline | null => {
1638
1626
  const declaredGroup2Contract = resolvePipelineGroup2Contract(entry.source);
1639
1627
  const group2Contract =
@@ -1643,6 +1631,11 @@ async function makeWebGPURenderer(
1643
1631
  (variantSet === '' || variantSet?.includes('CLUSTER_FORWARD_AVAILABLE=true') === true)
1644
1632
  ? 'cluster'
1645
1633
  : declaredGroup2Contract;
1634
+ const resolvedVertexEntry = resolveMaterialPipelineVertexEntry(
1635
+ layoutKindOverride,
1636
+ vertexEntryPoint,
1637
+ vertexEntry,
1638
+ );
1646
1639
  const ctx = buildPipelineContext(
1647
1640
  variantSet,
1648
1641
  materialShaderId,
@@ -1682,6 +1675,7 @@ async function makeWebGPURenderer(
1682
1675
  : passKindPolicyTable[passKind]?.shape === 'depth-only'
1683
1676
  ? (depthFormatOverride ?? 'depth32float')
1684
1677
  : (depthFormatOverride ?? DEPTH_TEXTURE_FORMAT),
1678
+ coverageOnly,
1685
1679
  label,
1686
1680
  // feat-20260604 w16-b: the shader-MODULE cache identity. Stable across
1687
1681
  // per variant. See PipelineBuilderContext.moduleLabel.
@@ -1702,7 +1696,7 @@ async function makeWebGPURenderer(
1702
1696
  : {}),
1703
1697
  }
1704
1698
  : undefined,
1705
- vertexEntry ?? vertexEntryPoint,
1699
+ resolvedVertexEntry,
1706
1700
  fragmentEntry,
1707
1701
  undefined, // defines — none
1708
1702
  passKind,
@@ -1766,6 +1760,8 @@ async function makeWebGPURenderer(
1766
1760
  fragmentEntry?: string,
1767
1761
  particleInputLanes?: number,
1768
1762
  ): RenderPipeline | null => {
1763
+ const coverageOnly =
1764
+ passKind === 'temporal' && variantSet?.split('+').includes('COVERAGE_ONLY=true') === true;
1769
1765
  // WebGL2 is a downlevel backend even when the record stage asks for the
1770
1766
  // native-capability variant string. Resolve the variant axes at this
1771
1767
  // shared PSO seam so the shader source, BGL layout, and cache key agree on
@@ -1778,10 +1774,11 @@ async function makeWebGPURenderer(
1778
1774
  (currentState !== null ? currentState.colorAttachmentFormat : 'bgra8unorm-srgb');
1779
1775
  // feat-20260629 M4: auto-fill shaderUvSetCount from naga reflection
1780
1776
  // (stored in materialShaderUvSetCounts during prepareMaterialShaders).
1777
+ const materialShaderIdForPass = resolveMaterialPipelineShaderId(materialShaderId, passKind);
1781
1778
  const pipelineShaderId = resolveMaterialShaderBackendArtifactKey(
1782
- materialShaderId,
1779
+ materialShaderIdForPass,
1783
1780
  internals.device.caps.backendKind,
1784
- assets.getMaterialArtifact(materialShaderId),
1781
+ assets.getMaterialArtifact(materialShaderIdForPass),
1785
1782
  );
1786
1783
  const materialShaderLookup = getShader().findMaterialArtifact(pipelineShaderId);
1787
1784
  const materialShaderSource = materialShaderLookup.ok
@@ -1952,6 +1949,10 @@ async function makeWebGPURenderer(
1952
1949
  layoutKindOverride,
1953
1950
  vertexEntryPoint,
1954
1951
  additionalColorFormats,
1952
+ undefined,
1953
+ undefined,
1954
+ undefined,
1955
+ coverageOnly,
1955
1956
  );
1956
1957
  }
1957
1958
  const pbrEntry = findStandardPbrEntry(effectiveVariantSet);
@@ -2009,6 +2010,10 @@ async function makeWebGPURenderer(
2009
2010
  layoutKindOverride,
2010
2011
  vertexEntryPoint,
2011
2012
  additionalColorFormats,
2013
+ undefined,
2014
+ undefined,
2015
+ undefined,
2016
+ coverageOnly,
2012
2017
  );
2013
2018
  }
2014
2019
  // feat-20260609 M4 / w31: resolve variant WGSL from manifest when
@@ -2106,6 +2111,7 @@ async function makeWebGPURenderer(
2106
2111
  vertexEntry,
2107
2112
  fragmentEntry,
2108
2113
  particleInputLanes,
2114
+ coverageOnly,
2109
2115
  );
2110
2116
  };
2111
2117
  const getMaterialShaderPipelineEntry = (
@@ -2241,6 +2247,7 @@ async function makeWebGPURenderer(
2241
2247
  return internals.bundler?.build;
2242
2248
  },
2243
2249
  ssrIdentity: internals.options?.ssrIdentity,
2250
+ captureReflectionFallbackReadback: internals.options?.captureReflectionFallbackReadback,
2244
2251
  get standardProfile() {
2245
2252
  return activeProfile;
2246
2253
  },
@@ -2260,11 +2267,8 @@ async function makeWebGPURenderer(
2260
2267
  getImmediateShaderModuleAdapter().createShaderModule(descriptor),
2261
2268
  },
2262
2269
  profiler: internals.options?.profiler,
2263
- // feat-20260622-s5 M3 / w17: device + context read live off `internals` via
2264
- // getters so the recover() rebuild (which swaps internals.device /
2265
- // internals.context for a freshly-acquired pair) is observed by the record
2266
- // stage without reconstructing the RenderSystem (RenderSystemRuntime.device
2267
- // / RenderSystemInternals.context are read at frame time, not cached).
2270
+ // Keep device/context getters live so recovery swaps are observed by the
2271
+ // record stage without reconstructing the RenderSystem.
2268
2272
  get device() {
2269
2273
  return internals.device;
2270
2274
  },
@@ -2329,12 +2333,13 @@ async function makeWebGPURenderer(
2329
2333
  getMaterialShaderPipeline,
2330
2334
  getMaterialShaderPipelineEntry,
2331
2335
  getMaterialShaderBindingContract: getCachedMaterialShaderBindingContract,
2332
- getMaterialShaderArtifact: (materialShaderId, vertexColorAvailable = false) =>
2333
- resolveStandardPbrProgram(
2334
- pipelineState?.gpuDrivenPbrPrograms,
2336
+ getMaterialShaderArtifact: (materialShaderId, options = {}) =>
2337
+ resolveRendererMaterialShaderArtifact(
2335
2338
  materialShaderId,
2336
- vertexColorAvailable,
2337
- )?.artifact,
2339
+ getShader(),
2340
+ pipelineState?.gpuDrivenPbrPrograms,
2341
+ options,
2342
+ ),
2338
2343
  getParamSchema,
2339
2344
  getMaterialBindGroupLayout,
2340
2345
  metrics,
@@ -2469,10 +2474,7 @@ async function makeWebGPURenderer(
2469
2474
  const transformReleases = new Map<World, () => void>();
2470
2475
  const attachmentOwner = {};
2471
2476
  let frameId = 0;
2472
- // Receipts remain observable until their device generation is retired. A
2473
- // registry avoids invalidating a still-valid frame merely because another
2474
- // submission completed in the meantime.
2475
- const currentReceipts = new Set<FrameReceipt>();
2477
+ // Caller-owned receipts remain observable; Renderer does not retain every submitted receipt.
2476
2478
  const issuedReceipts = new WeakSet<FrameReceipt>();
2477
2479
  const receiptTimings = new WeakMap<FrameReceipt, Promise<VolumeTimingObservation>>();
2478
2480
  let latestReceipt: FrameReceipt | undefined;
@@ -2792,6 +2794,7 @@ async function makeWebGPURenderer(
2792
2794
  ),
2793
2795
  dynamicGeometry: dynamicGeometry.inspect(),
2794
2796
  renderScene: renderSystem.renderScene,
2797
+ ...(renderSystem.iblBinding === undefined ? {} : { iblBinding: renderSystem.iblBinding }),
2795
2798
  reflectionProbes: renderSystem.reflectionProbes,
2796
2799
  ssrDependencies: renderSystem.ssrDependencies,
2797
2800
  ssr: renderSystem.ssr,
@@ -3530,7 +3533,6 @@ async function makeWebGPURenderer(
3530
3533
  return { status: 'complete', frame };
3531
3534
  };
3532
3535
  timingObservationStore?.register(receipt, timingSource);
3533
- currentReceipts.add(receipt);
3534
3536
  if (timingRequested && gpuPassTimingOptions === undefined) {
3535
3537
  receiptTimings.set(
3536
3538
  receipt,
@@ -3592,11 +3594,11 @@ async function makeWebGPURenderer(
3592
3594
  }
3593
3595
  if (gpuPassTimingOptions !== undefined && !includesDomain) {
3594
3596
  return timingObservationStore === undefined
3595
- ? observeGpuPassTimingDisabled(receipt, request, () => activeDeviceScope.generation)
3597
+ ? observeTiming(receipt, request, () => activeDeviceScope.generation)
3596
3598
  : timingObservationStore.observe(receipt, request);
3597
3599
  }
3598
3600
  if (
3599
- !currentReceipts.has(receipt) ||
3601
+ disposed ||
3600
3602
  !issuedReceipts.has(receipt) ||
3601
3603
  receipt.deviceGeneration !== activeDeviceScope.generation ||
3602
3604
  receipt.backendId !== internals.device.caps.backendKind
@@ -3726,11 +3728,11 @@ async function makeWebGPURenderer(
3726
3728
  reason: 'timestamp capture was not requested for this frame',
3727
3729
  }))
3728
3730
  : undefined;
3731
+ const observation = await observeTiming(receipt, request, () => activeDeviceScope.generation);
3732
+ if (!observation.ok) return observation;
3729
3733
  return ok(
3730
3734
  Object.freeze({
3731
- frameId: receipt.frameId,
3732
- deviceGeneration: receipt.deviceGeneration,
3733
- include: Object.freeze([...request.include]),
3735
+ ...observation.value,
3734
3736
  ...(targetReadbacks === undefined
3735
3737
  ? {}
3736
3738
  : { targetReadbacks: Object.freeze(targetReadbacks.value) }),
@@ -3795,16 +3797,8 @@ async function makeWebGPURenderer(
3795
3797
  * `disposed` latch flips up-front so a re-entrant dispose (or a draw
3796
3798
  * that races with the cascade) short-circuits.
3797
3799
  *
3798
- * NOT calling `device.destroy()` on the raw GPUDevice (w25 lesson from
3799
- * feat-20260517-vite-plugin-image-build-time-cook CI-fix v4): w23
3800
- * attempted to evict stale devices from the chromium WebGPU adapter
3801
- * pool via `_internal_getRawDevice`, but the explicit destroy
3802
- * accelerated pool poisoning -- after the first `dispose()` the
3803
- * chromium adapter pool started recycling the just-destroyed device
3804
- * into the next `requestDevice` so the NEXT test's `Engine.create`
3805
- * resolved with `ready.error.code: 'device-lost'`. Cross-test isolation
3806
- * moved to the vitest infra layer (`browser.isolate: true`) instead of
3807
- * test code self-managing device lifecycle.
3800
+ * Raw-device destruction is owned by backend lifetime, not this facade.
3801
+ * Browser test isolation handles adapter-pool reuse between tests.
3808
3802
  *
3809
3803
  * Stays in sync with the `Renderer.dispose` row of the README "API index".
3810
3804
  */
@@ -3816,7 +3810,6 @@ async function makeWebGPURenderer(
3816
3810
  for (const continuation of frameContinuations) {
3817
3811
  continuation.terminate({ code: 'disposed' });
3818
3812
  }
3819
- currentReceipts.clear();
3820
3813
  const cleanupFailures: RendererOperationCause[] = [];
3821
3814
  const observationCleanupFailure = disposeReceiptObservationCaptures();
3822
3815
  if (observationCleanupFailure !== undefined) {
@@ -4083,6 +4076,13 @@ async function makeWebGPURenderer(
4083
4076
  buildPipeline,
4084
4077
  getMaterialShaderPipeline,
4085
4078
  getMaterialShaderPipelineEntry,
4079
+ getMaterialShaderArtifact: (materialShaderId, options = {}) =>
4080
+ resolveRendererMaterialShaderArtifact(
4081
+ materialShaderId,
4082
+ getShader(),
4083
+ pipelineState?.gpuDrivenPbrPrograms,
4084
+ options,
4085
+ ),
4086
4086
  getCachedMaterialShaderBindingContract,
4087
4087
  getParamSchema,
4088
4088
  getMaterialBindGroupLayout,
@@ -0,0 +1,37 @@
1
+ import type { DispatchEntry, RenderableSnapshot } from '../render-system-extract';
2
+
3
+ /** One retained index domain for auxiliary views and the display's selected draws. */
4
+ export function projectCaptureScene(
5
+ retained: {
6
+ readonly renderables: readonly RenderableSnapshot[];
7
+ readonly dispatch: readonly DispatchEntry[];
8
+ },
9
+ visible: readonly RenderableSnapshot[],
10
+ displayDispatch: readonly DispatchEntry[],
11
+ ) {
12
+ const renderables: RenderableSnapshot[] = [];
13
+ const retainedIndices = new Map<number, number>();
14
+ const identityIndices = new Map<string, number>();
15
+ for (const [index, row] of retained.renderables.entries()) {
16
+ if (row.authorVisible === false) continue;
17
+ retainedIndices.set(index, renderables.length);
18
+ identityIndices.set(`${row.worldId}:${row.entityKey}`, renderables.length);
19
+ renderables.push(row);
20
+ }
21
+ const remap = (
22
+ entries: readonly DispatchEntry[],
23
+ indexFor: (index: number) => number | undefined,
24
+ ) =>
25
+ entries.flatMap((entry) => {
26
+ const index = indexFor(entry.renderableIndex);
27
+ return index === undefined ? [] : [{ ...entry, renderableIndex: index }];
28
+ });
29
+ return {
30
+ renderables,
31
+ captureDispatch: remap(retained.dispatch, (index) => retainedIndices.get(index)),
32
+ displayDispatch: remap(displayDispatch, (index) => {
33
+ const row = visible[index];
34
+ return row === undefined ? undefined : identityIndices.get(`${row.worldId}:${row.entityKey}`);
35
+ }),
36
+ };
37
+ }
@@ -0,0 +1,65 @@
1
+ import { World } from '@forgeax/engine-ecs';
2
+ import { describe, expect, expectTypeOf, it } from 'vitest';
3
+ import {
4
+ DynamicResolution,
5
+ type DynamicResolutionData,
6
+ validateDynamicResolutionParameters,
7
+ } from '../dynamic-resolution';
8
+
9
+ describe('DynamicResolution authoring schema', () => {
10
+ it('provides deterministic defaults through the real World component path', () => {
11
+ const world = new World();
12
+ const entity = world.spawn({ component: DynamicResolution, data: {} }).unwrap();
13
+
14
+ expect(DynamicResolution.name).toBe('DynamicResolution');
15
+ const defaults = world.get(entity, DynamicResolution).unwrap();
16
+ expect(defaults).toMatchObject({ maxScale: 1 });
17
+ // World stores f32 fields, so the authored decimal is represented by its
18
+ // nearest f32 value rather than the exact JS number.
19
+ expect(defaults.targetGpuMs).toBeCloseTo(16.67);
20
+ expect(defaults.minScale).toBeCloseTo(0.67);
21
+ expect(DynamicResolution.fields).toMatchObject({
22
+ targetGpuMs: { type: 'f32', default: 16.67 },
23
+ minScale: { type: 'f32', default: 0.67 },
24
+ maxScale: { type: 'f32', default: 1 },
25
+ });
26
+ });
27
+
28
+ it.each([
29
+ { targetGpuMs: 0, minScale: 0.67, maxScale: 1, field: 'targetGpuMs' },
30
+ { targetGpuMs: Number.NaN, minScale: 0.67, maxScale: 1, field: 'targetGpuMs' },
31
+ { targetGpuMs: 16.67, minScale: 0.49, maxScale: 1, field: 'minScale' },
32
+ { targetGpuMs: 16.67, minScale: 0.8, maxScale: 1.01, field: 'maxScale' },
33
+ { targetGpuMs: 16.67, minScale: 0.9, maxScale: 0.8, field: 'minScale' },
34
+ ])('rejects invalid $field before graph construction', (input) => {
35
+ const result = validateDynamicResolutionParameters(input);
36
+
37
+ expect(result.ok).toBe(false);
38
+ if (!result.ok) {
39
+ expect(result.error.code).toBe('dynamic-resolution-invalid-parameter');
40
+ expect(result.error.expected).toContain(input.field);
41
+ expect(result.error.hint).toContain(input.field);
42
+ expect(result.error.detail.field).toBe(input.field);
43
+ }
44
+ });
45
+
46
+ it.each([
47
+ { targetGpuMs: 1, minScale: 0.5, maxScale: 0.5 },
48
+ { targetGpuMs: 16.67, minScale: 0.67, maxScale: 1 },
49
+ { targetGpuMs: 100, minScale: 0.5, maxScale: 1 },
50
+ ])('accepts boundary values $minScale..$maxScale', (input) => {
51
+ const result = validateDynamicResolutionParameters(input);
52
+ expect(result).toEqual({ ok: true, value: input });
53
+ });
54
+
55
+ it('keeps the detached authoring POD limited to the three resolution fields', () => {
56
+ expectTypeOf<DynamicResolutionData>().toEqualTypeOf<{
57
+ readonly targetGpuMs: number;
58
+ readonly minScale: number;
59
+ readonly maxScale: number;
60
+ }>();
61
+ expectTypeOf<keyof DynamicResolutionData>().toEqualTypeOf<
62
+ 'targetGpuMs' | 'minScale' | 'maxScale'
63
+ >();
64
+ });
65
+ });
@@ -0,0 +1,59 @@
1
+ import { createWorldContext, World } from '@forgeax/engine-ecs';
2
+ import { describe, expect, it } from 'vitest';
3
+ import { renderComponentsPlugin } from '../../plugin';
4
+ import { DirectionalLight, DirectionalShadowFilterValue } from '../directional-light';
5
+ import { validateDirectionalLightData } from '../light-helpers';
6
+
7
+ function adjacentFloat32(value: number, direction: -1 | 1): number {
8
+ const floats = new Float32Array([value]);
9
+ const bits = new Uint32Array(floats.buffer);
10
+ bits[0] = (bits[0] ?? 0) + direction;
11
+ return floats[0] ?? NaN;
12
+ }
13
+
14
+ describe('PCSS angular radius storage boundary', () => {
15
+ for (const shadowFilter of [
16
+ DirectionalShadowFilterValue.pcssMedium,
17
+ DirectionalShadowFilterValue.pcssHigh,
18
+ ]) {
19
+ it(`accepts both authored endpoints after World spawn and set (${shadowFilter})`, async () => {
20
+ const world = new World();
21
+ const context = await createWorldContext(world, [renderComponentsPlugin()]);
22
+ try {
23
+ for (const radius of [0.0001, 0.05]) {
24
+ const data = { direction: [0, -1, 0], shadowFilter, shadowAngularRadius: radius };
25
+ expect(validateDirectionalLightData(data).ok).toBe(true);
26
+ const entity = world.spawn({ component: DirectionalLight, data }).unwrap();
27
+ const stored = world.get(entity, DirectionalLight).unwrap();
28
+ expect(stored.shadowAngularRadius).toBe(Math.fround(radius));
29
+ expect(validateDirectionalLightData(stored).ok).toBe(true);
30
+ world.set(entity, DirectionalLight, { shadowAngularRadius: 0.025 }).unwrap();
31
+ world.set(entity, DirectionalLight, { shadowAngularRadius: radius }).unwrap();
32
+ expect(
33
+ validateDirectionalLightData(world.get(entity, DirectionalLight).unwrap()).ok,
34
+ ).toBe(true);
35
+ }
36
+ } finally {
37
+ await context.fiber.dispose();
38
+ }
39
+ });
40
+ }
41
+
42
+ it.each([
43
+ adjacentFloat32(0.0001, -1),
44
+ adjacentFloat32(0.05, 1),
45
+ 0,
46
+ -1,
47
+ NaN,
48
+ Infinity,
49
+ -Infinity,
50
+ ])('rejects a radius outside the float32 endpoints: %s', (shadowAngularRadius) => {
51
+ const result = validateDirectionalLightData({
52
+ direction: [0, -1, 0],
53
+ shadowFilter: DirectionalShadowFilterValue.pcssHigh,
54
+ shadowAngularRadius,
55
+ });
56
+ expect(result.ok).toBe(false);
57
+ if (!result.ok) expect(result.error.code).toBe('shadow-invalid-config');
58
+ });
59
+ });
@@ -60,8 +60,8 @@ export {
60
60
  * splitLambda ∈ [0,1] — PSSM split weight (default 0.75)
61
61
  * cascadeBlend ∈ [0,0.5] — blend width between cascades (default 0.2)
62
62
  * mapSize >= 1 — shadow map resolution (default 2048)
63
- * depthBias — shadow acne bias (default 0.005)
64
- * normalBias — shadow acne normal offset (default 0.05)
63
+ * depthBias — normalized receiver depth subtraction (default 0.00001)
64
+ * normalBias — world-space receiver normal offset (default 0.05)
65
65
  * shadowDistance > 0 — how far shadows reach in front of the camera,
66
66
  * in world units (default 200). This is the sole
67
67
  * coverage knob: the CSM cascades are PSSM-split
@@ -123,7 +123,7 @@ export const DirectionalLight = defineComponent('DirectionalLight', {
123
123
  splitLambda: { type: 'f32', default: 0.75 },
124
124
  cascadeBlend: { type: 'f32', default: 0.2 },
125
125
  mapSize: { type: 'f32', default: 2048 },
126
- depthBias: { type: 'f32', default: 0.005 },
126
+ depthBias: { type: 'f32', default: 0.00001 },
127
127
  normalBias: { type: 'f32', default: 0.05 },
128
128
  // Sole shadow coverage knob (feat replaces nearPlane/farPlane). The PSSM
129
129
  // near end derives from the active camera near; shadowDistance is the far