@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
@@ -9,8 +9,13 @@ import {
9
9
  import {
10
10
  createStandardPbrArtifactReceipt,
11
11
  type MaterialShaderArtifact,
12
+ type MaterialShaderArtifactReceipt,
13
+ type MaterialShaderEntry,
14
+ type MaterialShaderManifestEntry,
15
+ type ShaderRegistry,
12
16
  } from '@forgeax/engine-shader';
13
- import type { StandardLayerPlan } from '@forgeax/engine-types';
17
+ import type { MaterialProgramAddress, StandardLayerPlan } from '@forgeax/engine-types';
18
+ import { resolveMaterialShaderUvSetCount } from '../material-shader-policy';
14
19
  import { projectStandardLayerPlan } from './standard-layer-projection.js';
15
20
 
16
21
  export type { MaterialRenderPassProjection } from '@forgeax/engine-assets-runtime';
@@ -36,28 +41,142 @@ export function assembleMaterialProjection(record: CookedMaterialRecord): Materi
36
41
  }
37
42
 
38
43
  /**
39
- * Assemble the one built-in Standard PBR artifact from the selected producer WGSL.
44
+ * Assemble the one built-in Standard PBR artifact from the registry-owned entry.
40
45
  * Both rigid and skinned identifiers use this producer; unsupported identifiers
41
46
  * stay outside the GPU-driven Standard PBR lane.
42
47
  */
43
48
  export function assembleStandardPbrArtifact(
44
49
  materialShaderId: string,
45
50
  wgsl: string,
46
- vertexColorAvailable = false,
51
+ vertexColorAvailable?: boolean,
52
+ ): MaterialShaderArtifact | undefined;
53
+ export function assembleStandardPbrArtifact(
54
+ materialShaderId: string,
55
+ shaderEntry: MaterialShaderEntry,
56
+ options?: MaterialShaderArtifactRequest,
57
+ ): MaterialShaderArtifact | undefined;
58
+ export function assembleStandardPbrArtifact(
59
+ materialShaderId: string,
60
+ shaderEntryOrWgsl: MaterialShaderEntry | string,
61
+ optionsOrVertexColor: MaterialShaderArtifactRequest | boolean = {},
47
62
  ): MaterialShaderArtifact | undefined {
48
63
  const skinned =
49
64
  materialShaderId === 'forgeax::pbr-skin' ||
50
65
  materialShaderId === 'forgeax::default-standard-pbr-skin';
51
66
  if (materialShaderId !== 'forgeax::default-standard-pbr' && !skinned) return undefined;
52
67
 
53
- const receipt = createStandardPbrArtifactReceipt(skinned, vertexColorAvailable);
68
+ const options =
69
+ typeof optionsOrVertexColor === 'boolean'
70
+ ? { vertexColorAvailable: optionsOrVertexColor }
71
+ : optionsOrVertexColor;
72
+ const shaderEntry: MaterialShaderEntry =
73
+ typeof shaderEntryOrWgsl === 'string'
74
+ ? {
75
+ source: shaderEntryOrWgsl,
76
+ paramSchema: [],
77
+ receipt: createStandardPbrArtifactReceipt(skinned, options.vertexColorAvailable ?? false),
78
+ }
79
+ : shaderEntryOrWgsl;
80
+
81
+ const publishedReceipt = shaderEntry.receipt;
82
+ // The legacy no-request call remains source-compatible for the built-in
83
+ // Standard lane. Once a geometry fact is requested, however, the receipt
84
+ // must come from the producer-selected artifact; synthesising a different
85
+ // color layout here would let source, pipeline, and vertex ABI diverge.
86
+ if (
87
+ options.vertexColorAvailable !== undefined &&
88
+ (publishedReceipt === undefined ||
89
+ receiptHasVertexColor(publishedReceipt) !== options.vertexColorAvailable)
90
+ ) {
91
+ return undefined;
92
+ }
93
+ const receipt = publishedReceipt ?? createStandardPbrArtifactReceipt(skinned);
94
+ return {
95
+ material: materialShaderId,
96
+ pass: options.pass ?? 'forward',
97
+ wgsl: shaderEntry.source,
98
+ layoutIdentity: receipt.reflection.layoutIdentity,
99
+ bindings: [],
100
+ deps: [],
101
+ ...(options.variantSet === undefined ? {} : { variantSet: options.variantSet }),
102
+ ...(options.address === undefined
103
+ ? {}
104
+ : {
105
+ vertexEntry:
106
+ options.address === 'scene-index' ? receipt.sceneIndexEntry : receipt.directEntry,
107
+ }),
108
+ vertexInputs: receipt.vertexInputs.map(
109
+ (input): Readonly<Record<string, unknown>> => ({
110
+ semantic: input.semantic,
111
+ location: input.location,
112
+ format: input.format,
113
+ }),
114
+ ),
115
+ receipt,
116
+ };
117
+ }
118
+
119
+ export interface MaterialShaderArtifactRequest {
120
+ /** Geometry-owned COLOR_0 fact used to select the matching published variant. */
121
+ readonly vertexColorAvailable?: boolean;
122
+ /** Deformation lane used to select a skin-compatible shadow variant. */
123
+ readonly deformation?: 'rigid' | 'skin';
124
+ readonly clustered?: boolean;
125
+ readonly reflectionFallback?: boolean;
126
+ readonly variantSet?: string;
127
+ /** Submission address requested by the consumer; GPU-driven uses scene-index. */
128
+ readonly address?: MaterialProgramAddress;
129
+ /** Concrete Pass whose entry metadata is being assembled. */
130
+ readonly pass?: 'forward' | 'shadow' | 'depth';
131
+ }
132
+
133
+ function receiptHasVertexColor(receipt: MaterialShaderArtifactReceipt): boolean {
134
+ return receipt.vertexInputs.some((input) => input.semantic === 'color');
135
+ }
136
+
137
+ function receiptUvSetCount(receipt: MaterialShaderArtifactReceipt): number {
138
+ let count = 0;
139
+ for (const input of receipt.vertexInputs) {
140
+ if (input.semantic === 'uv') count = Math.max(count, 1);
141
+ else if (/^uv([1-7])$/.test(input.semantic)) {
142
+ count = Math.max(count, Number(input.semantic.slice(2)) + 1);
143
+ }
144
+ }
145
+ return count;
146
+ }
147
+
148
+ function assemblePublishedArtifact(
149
+ materialShaderId: string,
150
+ shaderEntry: MaterialShaderEntry,
151
+ options: MaterialShaderArtifactRequest,
152
+ reflectedUvSetCount?: number,
153
+ ): MaterialShaderArtifact | undefined {
154
+ const receipt = shaderEntry.receipt;
155
+ if (receipt === undefined) return undefined;
156
+ if (
157
+ options.vertexColorAvailable !== undefined &&
158
+ receiptHasVertexColor(receipt) !== options.vertexColorAvailable
159
+ ) {
160
+ return undefined;
161
+ }
54
162
  return {
55
163
  material: materialShaderId,
56
- pass: 'forward',
57
- wgsl,
164
+ pass: options.pass ?? 'forward',
165
+ wgsl: shaderEntry.source,
58
166
  layoutIdentity: receipt.reflection.layoutIdentity,
59
167
  bindings: [],
60
168
  deps: [],
169
+ uvSetCount:
170
+ resolveMaterialShaderUvSetCount(shaderEntry.source, reflectedUvSetCount) ??
171
+ receiptUvSetCount(receipt),
172
+ ...(options.variantSet === undefined ? {} : { variantSet: options.variantSet }),
173
+ ...(options.address === undefined
174
+ ? {}
175
+ : {
176
+ vertexEntry:
177
+ options.address === 'scene-index' ? receipt.sceneIndexEntry : receipt.directEntry,
178
+ }),
179
+ ...(options.pass === 'shadow' ? { fragmentEntry: 'fs_shadow' } : {}),
61
180
  vertexInputs: receipt.vertexInputs.map(
62
181
  (input): Readonly<Record<string, unknown>> => ({
63
182
  semantic: input.semantic,
@@ -68,3 +187,228 @@ export function assembleStandardPbrArtifact(
68
187
  receipt,
69
188
  };
70
189
  }
190
+
191
+ function selectedMaterialShaderEntry(
192
+ entry: Pick<MaterialShaderEntry, 'paramSchema'>,
193
+ source: string,
194
+ receipt: MaterialShaderArtifactReceipt | undefined,
195
+ ): MaterialShaderEntry {
196
+ return {
197
+ source,
198
+ paramSchema: entry.paramSchema,
199
+ ...(receipt === undefined ? {} : { receipt }),
200
+ };
201
+ }
202
+
203
+ function selectPublishedVariant(
204
+ entry: {
205
+ readonly variants: readonly {
206
+ readonly definesKey: string;
207
+ readonly defines: Readonly<Record<string, boolean>>;
208
+ readonly composedWgsl: string;
209
+ readonly receipt?: MaterialShaderArtifactReceipt;
210
+ }[];
211
+ },
212
+ options: MaterialShaderArtifactRequest,
213
+ ): (typeof entry.variants)[number] | undefined {
214
+ const desiredColor = options.vertexColorAvailable;
215
+ const declaresColorAxis = entry.variants.some((variant) =>
216
+ Object.hasOwn(variant.defines, 'VERTEX_COLOR_AVAILABLE'),
217
+ );
218
+ let withColor =
219
+ desiredColor === undefined || !declaresColorAxis
220
+ ? [...entry.variants]
221
+ : entry.variants.filter((variant) => variant.defines.VERTEX_COLOR_AVAILABLE === desiredColor);
222
+ const declaresSkinningAxis = entry.variants.some((variant) =>
223
+ Object.hasOwn(variant.defines, 'SKINNING_DISABLED'),
224
+ );
225
+ if (options.deformation !== undefined && declaresSkinningAxis) {
226
+ const skinningDisabled = options.deformation !== 'skin';
227
+ withColor = withColor.filter(
228
+ (variant) => variant.defines.SKINNING_DISABLED === skinningDisabled,
229
+ );
230
+ }
231
+ for (const [axis, requested] of [
232
+ ['CLUSTER_FORWARD_AVAILABLE', options.clustered],
233
+ ['REFLECTION_FALLBACK_AVAILABLE', options.reflectionFallback],
234
+ ] as const) {
235
+ if (
236
+ requested !== undefined &&
237
+ entry.variants.some((variant) => Object.hasOwn(variant.defines, axis))
238
+ ) {
239
+ withColor = withColor.filter((variant) => variant.defines[axis] === requested);
240
+ }
241
+ }
242
+ if (options.variantSet === undefined) {
243
+ // Coverage-only is an explicit temporal producer variant. Ordinary
244
+ // material publication must stay on the color-producing shader.
245
+ withColor = withColor.filter((variant) => variant.defines.COVERAGE_ONLY !== true);
246
+ }
247
+ if (options.variantSet !== undefined) {
248
+ return withColor.find((variant) => variant.definesKey === options.variantSet);
249
+ }
250
+ // GPU-driven Standard owns the storage-backed scene-index variant. Prefer that exact family, then fall back to the closest variant
251
+ // carrying the requested COLOR_0 fact. This keeps the selection deterministic
252
+ // without making capability names an admission whitelist.
253
+ const ranked = withColor
254
+ .map((variant) => ({
255
+ variant,
256
+ score:
257
+ (variant.defines.STORAGE_BUFFER_AVAILABLE === true ? 8 : 0) +
258
+ (variant.defines.GPU_DRIVEN_SCENE_INDEX_AVAILABLE === true ? 4 : 0) +
259
+ (variant.defines.CLUSTER_FORWARD_AVAILABLE === false ? 2 : 0) +
260
+ (variant.defines.PROBE_BLEND_AVAILABLE === false ? 1 : 0) +
261
+ (variant.defines.TRANSMISSION_AVAILABLE === false ? 1 : 0),
262
+ }))
263
+ .sort(
264
+ (left, right) =>
265
+ right.score - left.score || left.variant.definesKey.localeCompare(right.variant.definesKey),
266
+ );
267
+ return ranked[0]?.variant;
268
+ }
269
+
270
+ function materialShaderLookupIds(materialShaderId: string): readonly string[] {
271
+ return materialShaderId === 'forgeax::pbr-skin'
272
+ ? ['forgeax::pbr-skin', 'forgeax::default-standard-pbr-skin']
273
+ : materialShaderId === 'forgeax::default-standard-pbr-skin'
274
+ ? ['forgeax::default-standard-pbr-skin', 'forgeax::pbr-skin']
275
+ : [materialShaderId];
276
+ }
277
+
278
+ const STANDARD_SHADOW_CASTER_ID = 'forgeax::default-shadow-caster';
279
+
280
+ function isBuiltinStandardPbrMaterialShader(materialShaderId: string): boolean {
281
+ return (
282
+ materialShaderId === 'forgeax::default-standard-pbr' ||
283
+ materialShaderId === 'forgeax::default-standard-pbr-skin' ||
284
+ materialShaderId === 'forgeax::pbr-skin'
285
+ );
286
+ }
287
+
288
+ /** Resolve the registry-owned Standard PBR artifact, including the historical skin alias. */
289
+ export function resolveStandardPbrArtifact(
290
+ materialShaderId: string,
291
+ shader: Pick<ShaderRegistry, 'findMaterialArtifact'> &
292
+ Partial<Pick<ShaderRegistry, 'materialShaderManifestEntries'>>,
293
+ options: MaterialShaderArtifactRequest = {},
294
+ ): MaterialShaderArtifact | undefined {
295
+ const lookupIds = materialShaderLookupIds(materialShaderId);
296
+ for (const lookupId of lookupIds) {
297
+ const lookup = shader.findMaterialArtifact(lookupId);
298
+ if (!lookup.ok) continue;
299
+ const manifestEntry =
300
+ shader.materialShaderManifestEntries === undefined
301
+ ? undefined
302
+ : Array.from(shader.materialShaderManifestEntries()).find(
303
+ (entry) => entry.identifier === lookupId,
304
+ );
305
+ const selected =
306
+ manifestEntry === undefined || manifestEntry.variants.length === 0
307
+ ? undefined
308
+ : selectPublishedVariant(manifestEntry, options);
309
+ const selectedEntry =
310
+ selected === undefined
311
+ ? lookup.value
312
+ : selectedMaterialShaderEntry(lookup.value, selected.composedWgsl, selected.receipt);
313
+ const artifact = assembleStandardPbrArtifact(materialShaderId, selectedEntry, options);
314
+ if (artifact === undefined) return undefined;
315
+ // A variant source without producer receipt is not safe to admit on a
316
+ // geometry-specific path. The assembly helper keeps its legacy fallback
317
+ // only for callers that did not provide a geometry fact.
318
+ if (
319
+ options.vertexColorAvailable !== undefined &&
320
+ artifact.receipt !== undefined &&
321
+ receiptHasVertexColor(artifact.receipt) !== options.vertexColorAvailable
322
+ ) {
323
+ return undefined;
324
+ }
325
+ return selected === undefined ? artifact : { ...artifact, variantSet: selected.definesKey };
326
+ }
327
+ return undefined;
328
+ }
329
+
330
+ // Registry entries and manifest publications are immutable. Replace the projection
331
+ // when either owner identity changes; old registrations remain weakly held.
332
+ const publishedArtifacts = new WeakMap<
333
+ MaterialShaderEntry,
334
+ {
335
+ readonly manifest: MaterialShaderManifestEntry | undefined;
336
+ readonly requests: Map<string, MaterialShaderArtifact>;
337
+ }
338
+ >();
339
+
340
+ /**
341
+ * Resolve any producer-published material artifact through the same manifest
342
+ * variant relation as Standard. Unlike the Standard compatibility helper this
343
+ * never fabricates a receipt: a custom shader without producer ABI remains
344
+ * outside the GPU-driven lane with a structured preparation error.
345
+ */
346
+ export function resolveMaterialShaderArtifact(
347
+ materialShaderId: string,
348
+ shader: Pick<ShaderRegistry, 'findMaterialArtifact'> &
349
+ Partial<Pick<ShaderRegistry, 'materialShaderManifestEntries'>>,
350
+ options: MaterialShaderArtifactRequest = {},
351
+ ): MaterialShaderArtifact | undefined {
352
+ const lookupIds =
353
+ options.pass === 'shadow' && isBuiltinStandardPbrMaterialShader(materialShaderId)
354
+ ? [STANDARD_SHADOW_CASTER_ID]
355
+ : materialShaderLookupIds(materialShaderId);
356
+ for (const lookupId of lookupIds) {
357
+ const lookup = shader.findMaterialArtifact(lookupId);
358
+ if (!lookup.ok) continue;
359
+ const manifestEntry =
360
+ shader.materialShaderManifestEntries === undefined
361
+ ? undefined
362
+ : Array.from(shader.materialShaderManifestEntries()).find(
363
+ (entry) => entry.identifier === lookupId,
364
+ );
365
+ let cache = publishedArtifacts.get(lookup.value);
366
+ if (cache === undefined || cache.manifest !== manifestEntry) {
367
+ cache = { manifest: manifestEntry, requests: new Map() };
368
+ publishedArtifacts.set(lookup.value, cache);
369
+ }
370
+ const requestKey = JSON.stringify([
371
+ materialShaderId,
372
+ options.vertexColorAvailable,
373
+ options.deformation,
374
+ options.clustered,
375
+ options.reflectionFallback,
376
+ options.variantSet,
377
+ options.address,
378
+ options.pass,
379
+ ]);
380
+ const cached = cache.requests.get(requestKey);
381
+ if (cached !== undefined) return cached;
382
+ const selected =
383
+ manifestEntry === undefined || manifestEntry.variants.length === 0
384
+ ? undefined
385
+ : selectPublishedVariant(manifestEntry, options);
386
+ // A published variant set is an explicit producer contract. Falling back
387
+ // to the primary entry when the requested axes have no variant would pair
388
+ // one source with another artifact's receipt.
389
+ if (
390
+ manifestEntry !== undefined &&
391
+ manifestEntry.variants.length > 0 &&
392
+ selected === undefined
393
+ ) {
394
+ continue;
395
+ }
396
+ const selectedEntry =
397
+ selected === undefined
398
+ ? lookup.value
399
+ : selectedMaterialShaderEntry(lookup.value, selected.composedWgsl, selected.receipt);
400
+ const artifact = assemblePublishedArtifact(
401
+ lookupId === STANDARD_SHADOW_CASTER_ID ? lookupId : materialShaderId,
402
+ selectedEntry,
403
+ options,
404
+ manifestEntry?.uvSetCount,
405
+ );
406
+ if (artifact !== undefined) {
407
+ const published =
408
+ selected === undefined ? artifact : { ...artifact, variantSet: selected.definesKey };
409
+ cache.requests.set(requestKey, published);
410
+ return published;
411
+ }
412
+ }
413
+ return undefined;
414
+ }
@@ -0,0 +1,100 @@
1
+ import type { BindGroupLayout } from '@forgeax/engine-rhi';
2
+ import type { MaterialShaderArtifact, ShaderRegistry } from '@forgeax/engine-shader';
3
+ import { type GpuDrivenPbrProgram, resolveStandardPbrProgram } from '../../gpu-driven/pbr-program';
4
+ import type { LayoutKind } from '../material-shader-policy';
5
+ import { type MaterialShaderArtifactRequest, resolveMaterialShaderArtifact } from './assembly';
6
+
7
+ type MaterialArtifactRegistry = Pick<ShaderRegistry, 'findMaterialArtifact'> &
8
+ Partial<Pick<ShaderRegistry, 'materialShaderManifestEntries'>>;
9
+
10
+ export function resolveRendererMaterialShaderArtifact(
11
+ materialShaderId: string,
12
+ shader: MaterialArtifactRegistry,
13
+ programs: ReadonlyMap<string, GpuDrivenPbrProgram> | undefined,
14
+ options: MaterialShaderArtifactRequest | boolean = {},
15
+ ): MaterialShaderArtifact | undefined {
16
+ if (typeof options === 'boolean') {
17
+ return resolveStandardPbrProgram(programs, materialShaderId, options)?.artifact;
18
+ }
19
+ const resolved = resolveMaterialShaderArtifact(materialShaderId, shader, options);
20
+ if (resolved !== undefined) return resolved;
21
+ // Legacy callers without geometry facts may reuse a prewarmed Standard
22
+ // program; geometry-specific requests remain fail-closed.
23
+ return Object.keys(options).length === 0
24
+ ? resolveStandardPbrProgram(programs, materialShaderId, false)?.artifact
25
+ : undefined;
26
+ }
27
+
28
+ export function resolveMaterialPipelineShaderId(
29
+ materialShaderId: string,
30
+ passKind: string,
31
+ ): string {
32
+ return passKind === 'shadow-caster' &&
33
+ (materialShaderId === 'forgeax::default-standard-pbr' ||
34
+ materialShaderId === 'forgeax::default-standard-pbr-skin' ||
35
+ materialShaderId === 'forgeax::pbr-skin')
36
+ ? 'forgeax::default-shadow-caster'
37
+ : materialShaderId;
38
+ }
39
+
40
+ export function resolveMaterialPipelineVertexEntry(
41
+ layoutKind: LayoutKind | undefined,
42
+ vertexEntryPoint: string | undefined,
43
+ authoredVertexEntry: string | undefined,
44
+ ): string | undefined {
45
+ const sceneIndex = layoutKind === 'gpu-driven-pbr' || layoutKind === 'gpu-driven-skin';
46
+ // Scene-index is a renderer submission ABI. An authored/direct entry may
47
+ // accompany the artifact for diagnostics, but it must not replace the
48
+ // receipt entry when the GPU-driven layout is selected.
49
+ return sceneIndex
50
+ ? (vertexEntryPoint ?? authoredVertexEntry)
51
+ : (authoredVertexEntry ?? vertexEntryPoint);
52
+ }
53
+
54
+ export interface MaterialPipelineBindGroupState {
55
+ readonly meshBindGroupLayout: BindGroupLayout;
56
+ readonly pbrSkinMeshBindGroupLayout: BindGroupLayout | null;
57
+ readonly hdrpSkinMeshBindGroupLayout: BindGroupLayout | null;
58
+ readonly hdrpMeshBindGroupLayout?: BindGroupLayout | null;
59
+ readonly instancesBindGroupLayout: BindGroupLayout;
60
+ readonly gpuDrivenInstancesBindGroupLayout?: BindGroupLayout | null;
61
+ }
62
+
63
+ export function resolveMaterialPipelineBindGroups(
64
+ layoutKind: LayoutKind,
65
+ state: MaterialPipelineBindGroupState,
66
+ clustered = false,
67
+ ): { readonly meshLayout: BindGroupLayout; readonly instancesLayout: BindGroupLayout } | null {
68
+ const meshLayout =
69
+ clustered && layoutKind === 'gpu-driven-pbr'
70
+ ? state.hdrpMeshBindGroupLayout
71
+ : clustered && layoutKind === 'gpu-driven-skin'
72
+ ? state.hdrpSkinMeshBindGroupLayout
73
+ : layoutKind === 'pbr-skin' || layoutKind === 'gpu-driven-skin'
74
+ ? state.pbrSkinMeshBindGroupLayout
75
+ : layoutKind === 'hdrp-skin'
76
+ ? state.hdrpSkinMeshBindGroupLayout
77
+ : state.meshBindGroupLayout;
78
+ if (meshLayout == null) return null;
79
+ const instancesLayout =
80
+ layoutKind === 'gpu-driven-pbr' || layoutKind === 'gpu-driven-skin'
81
+ ? state.gpuDrivenInstancesBindGroupLayout
82
+ : state.instancesBindGroupLayout;
83
+ return instancesLayout === null || instancesLayout === undefined
84
+ ? null
85
+ : { meshLayout, instancesLayout };
86
+ }
87
+
88
+ export function isEngineOwnedMaterialShader(materialShaderId: string): boolean {
89
+ return (
90
+ materialShaderId === 'forgeax::default-unlit' ||
91
+ materialShaderId === 'forgeax::default-shadow-caster' ||
92
+ materialShaderId === 'forgeax::sprite' ||
93
+ materialShaderId === 'forgeax::sprite-lit' ||
94
+ materialShaderId === 'forgeax::default-sprite' ||
95
+ materialShaderId === 'forgeax::msdf-text' ||
96
+ materialShaderId === 'forgeax::default-standard-pbr' ||
97
+ materialShaderId === 'forgeax::pbr-skin' ||
98
+ materialShaderId === 'forgeax::default-standard-pbr-skin'
99
+ );
100
+ }
@@ -280,8 +280,7 @@ export async function prepareMaterialShaders(
280
280
  ]);
281
281
 
282
282
  // ── Step 0: storage-buffer cap gate ───────────────────────────────────────
283
- const capValue = (rhiDevice.limits as Readonly<Record<string, number>>)
284
- .maxStorageBuffersPerShaderStage;
283
+ const capValue = rhiDevice.limits.maxStorageBuffersPerShaderStage;
285
284
  const webgl2Downlevel = rhiDevice.caps.backendKind === 'wgpu-webgl2';
286
285
  let storageBufferCapable = !webgl2Downlevel && rhiDevice.caps.storageBuffer;
287
286
  if (typeof capValue === 'number') {
@@ -350,6 +349,12 @@ export async function prepareMaterialShaders(
350
349
  if (msEntry.variants.some((v) => 'VERTEX_COLOR_AVAILABLE' in v.defines)) {
351
350
  variantDefines.VERTEX_COLOR_AVAILABLE = false;
352
351
  }
352
+ if (msEntry.variants.some((v) => 'COVERAGE_ONLY' in v.defines)) {
353
+ // Coverage-only is a producer-owned opt-in for sprite/temporal paths.
354
+ // Keep the ordinary boot material on the opaque variant; record-time
355
+ // coverage batches request the true variant explicitly.
356
+ variantDefines.COVERAGE_ONLY = false;
357
+ }
353
358
  if (msEntry.variants.some((v) => 'PROBE_BLEND_AVAILABLE' in v.defines)) {
354
359
  variantDefines.PROBE_BLEND_AVAILABLE = false;
355
360
  }
@@ -422,6 +427,9 @@ export async function prepareMaterialShaders(
422
427
  registry.installMaterialArtifact(msEntry.identifier, {
423
428
  source: wgsl,
424
429
  paramSchema,
430
+ ...(chosen?.receipt === undefined && msEntry.receipt === undefined
431
+ ? {}
432
+ : { receipt: chosen?.receipt ?? msEntry.receipt }),
425
433
  });
426
434
  // feat-20260629 M4: store uvSetCount from naga reflection for
427
435
  // auto-fill in getMaterialShaderPipeline clamp-to-last alias.
@@ -470,6 +478,7 @@ export async function prepareMaterialShaders(
470
478
  registry.installMaterialArtifact(shadowCasterIdentifier, {
471
479
  source: manifestEntry.composedWgsl,
472
480
  paramSchema: STANDARD_PIPELINE_PARAM_SCHEMA,
481
+ ...(manifestEntry.receipt === undefined ? {} : { receipt: manifestEntry.receipt }),
473
482
  });
474
483
  const shaderGuid = ENGINE_SHADER_GUIDS.get(shadowCasterIdentifier);
475
484
  if (shaderGuid !== undefined) {
@@ -604,9 +613,12 @@ export function selectNoColorPbrVariant(
604
613
  ): MaterialShaderManifestEntry['variants'][number] | undefined {
605
614
  const hdrpRequested = variantSet === '' || requestsClusterVariant(variantSet);
606
615
  const probeBlendRequested = requestsProbeBlendVariant(variantSet);
616
+ const coverageOnlyRequested = variantSet?.includes('COVERAGE_ONLY=true') === true;
607
617
  return manifestEntry?.variants.find(
608
618
  (variant) =>
609
619
  variant.defines.STORAGE_BUFFER_AVAILABLE === storageBufferCapable &&
620
+ (!('COVERAGE_ONLY' in variant.defines) ||
621
+ variant.defines.COVERAGE_ONLY === coverageOnlyRequested) &&
610
622
  (!('EXTENDED_LIGHTING_AVAILABLE' in variant.defines) ||
611
623
  variant.defines.EXTENDED_LIGHTING_AVAILABLE === extendedLightingShaderAvailable) &&
612
624
  variant.defines.VERTEX_COLOR_AVAILABLE === false &&
@@ -896,6 +908,7 @@ export function resolveMaterialShaderVariantSet(
896
908
  ): string | undefined {
897
909
  const declaredAxes = new Set(variants.flatMap((variant) => Object.keys(variant.defines)));
898
910
  const declaresCapabilityVariants =
911
+ declaredAxes.has('COVERAGE_ONLY') ||
899
912
  declaredAxes.has('WEBGL2_COMPAT') ||
900
913
  declaredAxes.has('STORAGE_BUFFER_AVAILABLE') ||
901
914
  declaredAxes.has('EXTENDED_LIGHTING_AVAILABLE') ||
@@ -933,23 +946,31 @@ export function resolveMaterialShaderVariantSet(
933
946
  ? backendKind !== 'wgpu-webgl2' && storageBuffer
934
947
  : axis === 'SKINNING_DISABLED'
935
948
  ? (requested[axis] ?? true)
936
- : axis === 'CLUSTER_FORWARD_AVAILABLE'
937
- ? storageBuffer && backendKind !== 'wgpu-webgl2' && (requested[axis] ?? false)
938
- : axis === 'PER_INSTANCE_REGION'
939
- ? requestedVariantSet === '' || (requested[axis] ?? false)
940
- : axis === 'VERTEX_COLOR_AVAILABLE'
949
+ : axis === 'ALPHA_MASK' ||
950
+ axis === 'GPU_DRIVEN_SCENE_INDEX_AVAILABLE' ||
951
+ axis === 'GPU_DRIVEN_SCENE_INDEX_EXPLICIT' ||
952
+ axis === 'COVERAGE_ONLY'
953
+ ? (requested[axis] ?? false)
954
+ : axis === 'CLUSTER_FORWARD_AVAILABLE'
955
+ ? storageBuffer && backendKind !== 'wgpu-webgl2' && (requested[axis] ?? false)
956
+ : axis === 'PER_INSTANCE_REGION'
941
957
  ? requestedVariantSet === '' || (requested[axis] ?? false)
942
- : axis === 'EXTENDED_LIGHTING_AVAILABLE'
943
- ? extendedLightingAvailable
944
- : axis === 'PROBE_BLEND_AVAILABLE'
945
- ? storageBuffer && (requested[axis] ?? false)
946
- : axis === 'TRANSMISSION_AVAILABLE'
947
- ? (requested[axis] ?? false)
948
- : axis === 'DIRECTIONAL_PCSS_AVAILABLE'
949
- ? directionalPcssCapable(backendKind)
950
- : axis === 'PROJECTOR_AVAILABLE'
951
- ? projectorAvailable
952
- : (requested[axis] ?? false);
958
+ : axis === 'VERTEX_COLOR_AVAILABLE'
959
+ ? requestedVariantSet === '' || (requested[axis] ?? false)
960
+ : axis === 'EXTENDED_LIGHTING_AVAILABLE'
961
+ ? extendedLightingAvailable
962
+ : axis === 'PROBE_BLEND_AVAILABLE'
963
+ ? storageBuffer && (requested[axis] ?? false)
964
+ : axis === 'TRANSMISSION_AVAILABLE' ||
965
+ axis === 'REFLECTION_FALLBACK_AVAILABLE'
966
+ ? (requested[axis] ?? false)
967
+ : axis === 'DIRECTIONAL_PCSS_AVAILABLE'
968
+ ? directionalPcssCapable(backendKind)
969
+ : axis === 'PROJECTOR_AVAILABLE'
970
+ ? projectorAvailable
971
+ : // Authored axes keep the manifest's all-true default.
972
+ // Only Engine-owned admission axes default to disabled.
973
+ (requested[axis] ?? true);
953
974
  }
954
975
  const sorted = Object.entries(filtered).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
955
976
  return sorted.every(([, value]) => value)
@@ -50,6 +50,11 @@ export function attachDeviceLostFanout(
50
50
  }
51
51
  onDeviceLost?.(detail);
52
52
  lostRegistry.fire(safe);
53
+ // A `destroyed` loss is the terminal signal from an intentional
54
+ // GPUDevice.destroy() call. It is observable through the lost channel,
55
+ // but must not put the renderer into recoverable device-lost health or
56
+ // emit an error during normal teardown. Only an unexpected loss keeps the
57
+ // recovery/error fan-out active.
53
58
  if (safe.reason !== 'destroyed') {
54
59
  pack.instrumentation?.onDeviceLost?.();
55
60
  healthRegistry.fire({
@@ -12,6 +12,7 @@ import {
12
12
  } from '@forgeax/engine-rhi';
13
13
  import { ok, type Result } from '@forgeax/engine-types';
14
14
  import type { RhiBackendPack } from '../backend-contract';
15
+ import { deviceOptionsForAdapter } from '../device-feature-admission';
15
16
  import {
16
17
  RECOVERY_ADAPTER_DEADLINE_MS,
17
18
  RECOVERY_DEVICE_DEADLINE_MS,
@@ -20,13 +21,7 @@ import {
20
21
  type RecoveryPhase,
21
22
  } from '../renderer-lifecycle';
22
23
 
23
- const COMPRESSION_FEATURES: GPUFeatureName[] = [
24
- 'texture-compression-bc',
25
- 'texture-compression-etc2',
26
- 'texture-compression-astc',
27
- ];
28
-
29
- export const STANDARD_PBR_REQUIRED_SAMPLED_TEXTURES = 17;
24
+ export { deviceOptionsForAdapter } from '../device-feature-admission';
30
25
 
31
26
  const PROBE_TEXTURE_USAGE_COPY_SRC = 0x01;
32
27
  const PROBE_TEXTURE_USAGE_RENDER_ATTACHMENT = 0x10;
@@ -573,28 +568,6 @@ export async function probeCandidateGraphExecution(
573
568
  }
574
569
  }
575
570
 
576
- export function deviceOptionsForAdapter(adapter: Pick<RhiAdapter, 'features' | 'limits'>):
577
- | {
578
- requiredFeatures?: GPUFeatureName[];
579
- requiredLimits?: GPUDeviceDescriptor['requiredLimits'];
580
- }
581
- | undefined {
582
- const requiredFeatures = COMPRESSION_FEATURES.filter((feature) => adapter.features.has(feature));
583
- const supportsTransmission =
584
- (adapter.limits.maxSampledTexturesPerShaderStage ?? 0) >=
585
- STANDARD_PBR_REQUIRED_SAMPLED_TEXTURES;
586
- return {
587
- ...(requiredFeatures.length > 0 ? { requiredFeatures } : {}),
588
- ...(supportsTransmission
589
- ? {
590
- requiredLimits: {
591
- maxSampledTexturesPerShaderStage: STANDARD_PBR_REQUIRED_SAMPLED_TEXTURES,
592
- },
593
- }
594
- : {}),
595
- };
596
- }
597
-
598
571
  export interface AcquiredDeviceGeneration {
599
572
  readonly adapter: RhiAdapter;
600
573
  readonly device: RhiDevice;