@forgeax/engine-render 0.1.28 → 0.1.30

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 (768) hide show
  1. package/README.md +856 -126
  2. package/dist/assembly/atmosphere-shader-prewarm.d.ts +6 -0
  3. package/dist/assembly/atmosphere-shader-prewarm.d.ts.map +1 -0
  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 +27 -0
  7. package/dist/assembly/dynamic-geometry-host.d.ts.map +1 -1
  8. package/dist/assembly/dynamic-geometry-runtime.d.ts +20 -0
  9. package/dist/assembly/dynamic-geometry-runtime.d.ts.map +1 -0
  10. package/dist/assembly/factory.d.ts +3 -1
  11. package/dist/assembly/factory.d.ts.map +1 -1
  12. package/dist/assembly/gpu-driven-owner.d.ts +10 -0
  13. package/dist/assembly/gpu-driven-owner.d.ts.map +1 -0
  14. package/dist/assembly/host-contract.d.ts +14 -0
  15. package/dist/assembly/host-contract.d.ts.map +1 -1
  16. package/dist/assembly/material/assembly.d.ts +31 -1
  17. package/dist/assembly/material/assembly.d.ts.map +1 -1
  18. package/dist/assembly/material/pipeline-helpers.d.ts +24 -0
  19. package/dist/assembly/material/pipeline-helpers.d.ts.map +1 -0
  20. package/dist/assembly/material-shader-policy.d.ts +3 -1
  21. package/dist/assembly/material-shader-policy.d.ts.map +1 -1
  22. package/dist/assembly/recovery/device-loss-fanout.d.ts.map +1 -1
  23. package/dist/assembly/recovery/failure-location.d.ts +12 -0
  24. package/dist/assembly/recovery/failure-location.d.ts.map +1 -0
  25. package/dist/assembly/recovery/recovery-attempt.d.ts +1 -6
  26. package/dist/assembly/recovery/recovery-attempt.d.ts.map +1 -1
  27. package/dist/assembly/recovery/renderer-recover.d.ts +7 -4
  28. package/dist/assembly/recovery/renderer-recover.d.ts.map +1 -1
  29. package/dist/assembly/renderer-facade.d.ts.map +1 -1
  30. package/dist/assembly/renderer-feature-inspection.d.ts +5 -0
  31. package/dist/assembly/renderer-feature-inspection.d.ts.map +1 -0
  32. package/dist/assembly/renderer-frame-transaction.d.ts +19 -0
  33. package/dist/assembly/renderer-frame-transaction.d.ts.map +1 -1
  34. package/dist/assembly/renderer-helpers.d.ts +3 -0
  35. package/dist/assembly/renderer-helpers.d.ts.map +1 -1
  36. package/dist/assembly/renderer-inspection.d.ts +4 -0
  37. package/dist/assembly/renderer-inspection.d.ts.map +1 -1
  38. package/dist/assembly/shader-prewarm-policy.d.ts +7 -1
  39. package/dist/assembly/shader-prewarm-policy.d.ts.map +1 -1
  40. package/dist/assembly/skin-palette-owner.d.ts +8 -0
  41. package/dist/assembly/skin-palette-owner.d.ts.map +1 -0
  42. package/dist/assembly/ssr-shader-prewarm.d.ts +6 -0
  43. package/dist/assembly/ssr-shader-prewarm.d.ts.map +1 -0
  44. package/dist/assembly/webgpu-device-loss.d.ts +15 -0
  45. package/dist/assembly/webgpu-device-loss.d.ts.map +1 -0
  46. package/dist/assembly/webgpu-pbr-ready.d.ts +38 -0
  47. package/dist/assembly/webgpu-pbr-ready.d.ts.map +1 -0
  48. package/dist/assembly/webgpu-ready-mipmap.d.ts +11 -0
  49. package/dist/assembly/webgpu-ready-mipmap.d.ts.map +1 -0
  50. package/dist/assembly/webgpu-ready.d.ts +7 -3
  51. package/dist/assembly/webgpu-ready.d.ts.map +1 -1
  52. package/dist/assembly/webgpu-renderer-bootstrap.d.ts +10 -0
  53. package/dist/assembly/webgpu-renderer-bootstrap.d.ts.map +1 -0
  54. package/dist/assembly/webgpu-renderer-contract.d.ts +62 -0
  55. package/dist/assembly/webgpu-renderer-contract.d.ts.map +1 -0
  56. package/dist/assembly/webgpu-renderer-guards.d.ts +9 -0
  57. package/dist/assembly/webgpu-renderer-guards.d.ts.map +1 -0
  58. package/dist/assembly/webgpu-renderer-observation.d.ts +6 -0
  59. package/dist/assembly/webgpu-renderer-observation.d.ts.map +1 -0
  60. package/dist/assembly/webgpu-renderer-recovery-failure.d.ts +12 -0
  61. package/dist/assembly/webgpu-renderer-recovery-failure.d.ts.map +1 -0
  62. package/dist/assembly/webgpu-renderer.d.ts +7 -141
  63. package/dist/assembly/webgpu-renderer.d.ts.map +1 -1
  64. package/dist/assembly/webgpu-vertex-layouts.d.ts +28 -84
  65. package/dist/assembly/webgpu-vertex-layouts.d.ts.map +1 -1
  66. package/dist/authoring.mjs +2 -2
  67. package/dist/capture/scene-projection.d.ts +41 -0
  68. package/dist/capture/scene-projection.d.ts.map +1 -0
  69. package/dist/{chunk-J52NMQKR.mjs → chunk-7MQ3XBSP.mjs} +1098 -844
  70. package/dist/chunk-7MQ3XBSP.mjs.map +1 -0
  71. package/dist/{chunk-GF523LHF.mjs → chunk-BLXGONR2.mjs} +30 -12
  72. package/dist/chunk-BLXGONR2.mjs.map +1 -0
  73. package/dist/{chunk-VSL23LZE.mjs → chunk-C3DDSXYK.mjs} +1188 -463
  74. package/dist/chunk-C3DDSXYK.mjs.map +1 -0
  75. package/dist/{chunk-IQMKJLNU.mjs → chunk-FUUO4UND.mjs} +443 -30
  76. package/dist/chunk-FUUO4UND.mjs.map +1 -0
  77. package/dist/{chunk-J6KACNOH.mjs → chunk-JRBDO2KB.mjs} +617 -7
  78. package/dist/chunk-JRBDO2KB.mjs.map +1 -0
  79. package/dist/{chunk-GE3SDD72.mjs → chunk-MDQJPY3T.mjs} +20 -40
  80. package/dist/chunk-MDQJPY3T.mjs.map +1 -0
  81. package/dist/{chunk-MYJ5RPJO.mjs → chunk-NO4WZCAH.mjs} +3 -3
  82. package/dist/chunk-NO4WZCAH.mjs.map +1 -0
  83. package/dist/{chunk-RBDBZ5R7.mjs → chunk-PA5DRJNI.mjs} +2 -2
  84. package/dist/chunk-PA5DRJNI.mjs.map +1 -0
  85. package/dist/{chunk-MBU4ZVE7.mjs → chunk-PSEQ6QPH.mjs} +4655 -892
  86. package/dist/chunk-PSEQ6QPH.mjs.map +1 -0
  87. package/dist/{chunk-JAASCMXA.mjs → chunk-S5UE2T5P.mjs} +289 -41
  88. package/dist/chunk-S5UE2T5P.mjs.map +1 -0
  89. package/dist/components/camera.d.ts +62 -2
  90. package/dist/components/camera.d.ts.map +1 -1
  91. package/dist/components/directional-light.d.ts +2 -2
  92. package/dist/components/dynamic-resolution.d.ts +15 -0
  93. package/dist/components/dynamic-resolution.d.ts.map +1 -0
  94. package/dist/components/index.d.ts +2 -0
  95. package/dist/components/index.d.ts.map +1 -1
  96. package/dist/components/instances.d.ts +6 -20
  97. package/dist/components/instances.d.ts.map +1 -1
  98. package/dist/components/light-helpers.d.ts.map +1 -1
  99. package/dist/components/reflection-probe.d.ts +1 -0
  100. package/dist/components/reflection-probe.d.ts.map +1 -1
  101. package/dist/components/scene-instance.d.ts +2 -1
  102. package/dist/components/scene-instance.d.ts.map +1 -1
  103. package/dist/components/screen-space-reflection.d.ts +14 -0
  104. package/dist/components/screen-space-reflection.d.ts.map +1 -0
  105. package/dist/components/skylight.d.ts +4 -1
  106. package/dist/components/skylight.d.ts.map +1 -1
  107. package/dist/construct-renderer.mjs +14809 -7570
  108. package/dist/construct-renderer.mjs.map +1 -1
  109. package/dist/device/device-scope.d.ts +2 -0
  110. package/dist/device/device-scope.d.ts.map +1 -1
  111. package/dist/device/gpu-residency.d.ts +3 -14
  112. package/dist/device/gpu-residency.d.ts.map +1 -1
  113. package/dist/dynamic-geometry.d.ts.map +1 -1
  114. package/dist/environment/background.d.ts +7 -0
  115. package/dist/environment/background.d.ts.map +1 -0
  116. package/dist/environment/ibl.d.ts +11 -0
  117. package/dist/environment/ibl.d.ts.map +1 -0
  118. package/dist/environment/storage.d.ts +14 -0
  119. package/dist/environment/storage.d.ts.map +1 -0
  120. package/dist/errors/gpu-driven.d.ts +6 -3
  121. package/dist/errors/gpu-driven.d.ts.map +1 -1
  122. package/dist/errors/render.d.ts +41 -4
  123. package/dist/errors/render.d.ts.map +1 -1
  124. package/dist/extract/camera.d.ts.map +1 -1
  125. package/dist/extract/contracts.d.ts +7 -0
  126. package/dist/extract/contracts.d.ts.map +1 -1
  127. package/dist/extract/gpu-driven.d.ts +50 -3
  128. package/dist/extract/gpu-driven.d.ts.map +1 -1
  129. package/dist/features/host.d.ts +23 -1
  130. package/dist/features/host.d.ts.map +1 -1
  131. package/dist/features/noise-texture.d.ts +17 -0
  132. package/dist/features/noise-texture.d.ts.map +1 -0
  133. package/dist/features/plan.d.ts +83 -2
  134. package/dist/features/plan.d.ts.map +1 -1
  135. package/dist/features/prepared-gpu-work.d.ts +37 -4
  136. package/dist/features/prepared-gpu-work.d.ts.map +1 -1
  137. package/dist/features/prepared-graphics-store.d.ts.map +1 -1
  138. package/dist/features/prepared-graphics.d.ts +5 -0
  139. package/dist/features/prepared-graphics.d.ts.map +1 -1
  140. package/dist/features/render-graph-compute.d.ts +5 -3
  141. package/dist/features/render-graph-compute.d.ts.map +1 -1
  142. package/dist/features/render-graph-contribution.d.ts +11 -3
  143. package/dist/features/render-graph-contribution.d.ts.map +1 -1
  144. package/dist/features/render-graph-raster.d.ts +19 -3
  145. package/dist/features/render-graph-raster.d.ts.map +1 -1
  146. package/dist/features/types.d.ts +11 -0
  147. package/dist/features/types.d.ts.map +1 -1
  148. package/dist/fullscreen-post-process-pass.d.ts +2 -0
  149. package/dist/fullscreen-post-process-pass.d.ts.map +1 -1
  150. package/dist/gpu-driven/batch-topology.d.ts +34 -1
  151. package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
  152. package/dist/gpu-driven/material-artifacts.d.ts +24 -0
  153. package/dist/gpu-driven/material-artifacts.d.ts.map +1 -0
  154. package/dist/gpu-driven/material-bindings.d.ts +7 -0
  155. package/dist/gpu-driven/material-bindings.d.ts.map +1 -0
  156. package/dist/gpu-driven/pbr-program.d.ts +10 -0
  157. package/dist/gpu-driven/pbr-program.d.ts.map +1 -0
  158. package/dist/gpu-driven/prepared-draw.d.ts +2 -0
  159. package/dist/gpu-driven/prepared-draw.d.ts.map +1 -1
  160. package/dist/gpu-driven/production-raster.d.ts +169 -27
  161. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  162. package/dist/gpu-driven/shadow-views.d.ts +126 -0
  163. package/dist/gpu-driven/shadow-views.d.ts.map +1 -0
  164. package/dist/gpu-driven/view-gpu.d.ts +18 -2
  165. package/dist/gpu-driven/view-gpu.d.ts.map +1 -1
  166. package/dist/gpu-resource.d.ts +5 -2
  167. package/dist/gpu-resource.d.ts.map +1 -1
  168. package/dist/gpu-scene-schema.d.ts +2 -8
  169. package/dist/gpu-scene-schema.d.ts.map +1 -1
  170. package/dist/gpu-scene.d.ts +14 -0
  171. package/dist/gpu-scene.d.ts.map +1 -1
  172. package/dist/gpu-texture-usage.d.ts +1 -0
  173. package/dist/gpu-texture-usage.d.ts.map +1 -1
  174. package/dist/hdrp-buffers.d.ts +9 -0
  175. package/dist/hdrp-buffers.d.ts.map +1 -1
  176. package/dist/ibl/IblPipelineCache.d.ts +2 -0
  177. package/dist/ibl/IblPipelineCache.d.ts.map +1 -1
  178. package/dist/ibl/frame-resources.d.ts +8 -0
  179. package/dist/ibl/frame-resources.d.ts.map +1 -0
  180. package/dist/ibl/skylight-bind-group.d.ts +3 -1
  181. package/dist/ibl/skylight-bind-group.d.ts.map +1 -1
  182. package/dist/index.d.ts +87 -75
  183. package/dist/index.d.ts.map +1 -1
  184. package/dist/index.mjs +10 -8
  185. package/dist/index.mjs.map +1 -1
  186. package/dist/inspection-types.d.ts +180 -1
  187. package/dist/inspection-types.d.ts.map +1 -1
  188. package/dist/instances-derived-bounds.d.ts +11 -0
  189. package/dist/instances-derived-bounds.d.ts.map +1 -1
  190. package/dist/instances.d.ts +89 -44
  191. package/dist/instances.d.ts.map +1 -1
  192. package/dist/internal.d.ts +3 -0
  193. package/dist/internal.d.ts.map +1 -1
  194. package/dist/internal.mjs +117 -9
  195. package/dist/internal.mjs.map +1 -1
  196. package/dist/lifecycle.d.ts +2 -1
  197. package/dist/lifecycle.d.ts.map +1 -1
  198. package/dist/material-row.d.ts +15 -0
  199. package/dist/material-row.d.ts.map +1 -0
  200. package/dist/materials.d.ts +15 -0
  201. package/dist/materials.d.ts.map +1 -1
  202. package/dist/pbr-pipeline.d.ts +23 -5
  203. package/dist/pbr-pipeline.d.ts.map +1 -1
  204. package/dist/pipeline/render-extent.d.ts +51 -0
  205. package/dist/pipeline/render-extent.d.ts.map +1 -0
  206. package/dist/pipeline/standard-build-context.d.ts +7 -0
  207. package/dist/pipeline/standard-build-context.d.ts.map +1 -0
  208. package/dist/pipeline/standard-forward-lane.d.ts +3 -2
  209. package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
  210. package/dist/pipeline/standard-output/auto-exposure/capability.d.ts +12 -0
  211. package/dist/pipeline/standard-output/auto-exposure/capability.d.ts.map +1 -0
  212. package/dist/pipeline/standard-output/auto-exposure/gpu.d.ts +70 -0
  213. package/dist/pipeline/standard-output/auto-exposure/gpu.d.ts.map +1 -0
  214. package/dist/pipeline/standard-output/auto-exposure/graph.d.ts +75 -0
  215. package/dist/pipeline/standard-output/auto-exposure/graph.d.ts.map +1 -0
  216. package/dist/pipeline/standard-output/auto-exposure/inspection.d.ts +111 -0
  217. package/dist/pipeline/standard-output/auto-exposure/inspection.d.ts.map +1 -0
  218. package/dist/pipeline/standard-output/auto-exposure/oracle.d.ts +16 -0
  219. package/dist/pipeline/standard-output/auto-exposure/oracle.d.ts.map +1 -0
  220. package/dist/pipeline/standard-output/auto-exposure/preset.d.ts +14 -0
  221. package/dist/pipeline/standard-output/auto-exposure/preset.d.ts.map +1 -0
  222. package/dist/pipeline/standard-output/auto-exposure/state.d.ts +46 -0
  223. package/dist/pipeline/standard-output/auto-exposure/state.d.ts.map +1 -0
  224. package/dist/pipeline/standard-output/color-transform.d.ts +27 -0
  225. package/dist/pipeline/standard-output/color-transform.d.ts.map +1 -0
  226. package/dist/pipeline/standard-output/graph.d.ts +15 -0
  227. package/dist/pipeline/standard-output/graph.d.ts.map +1 -0
  228. package/dist/pipeline/standard-output/lut-admission.d.ts +29 -0
  229. package/dist/pipeline/standard-output/lut-admission.d.ts.map +1 -0
  230. package/dist/pipeline/standard-output/lut-gpu.d.ts +48 -0
  231. package/dist/pipeline/standard-output/lut-gpu.d.ts.map +1 -0
  232. package/dist/pipeline/standard-output/lut-state.d.ts +68 -0
  233. package/dist/pipeline/standard-output/lut-state.d.ts.map +1 -0
  234. package/dist/pipeline/standard-output/resources.d.ts +10 -0
  235. package/dist/pipeline/standard-output/resources.d.ts.map +1 -0
  236. package/dist/pipeline/standard-output/types.d.ts +39 -0
  237. package/dist/pipeline/standard-output/types.d.ts.map +1 -0
  238. package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
  239. package/dist/pipeline/standard-post.d.ts +2 -2
  240. package/dist/pipeline/standard-post.d.ts.map +1 -1
  241. package/dist/pipeline/standard-profile.d.ts +2 -1
  242. package/dist/pipeline/standard-profile.d.ts.map +1 -1
  243. package/dist/pipeline-builder.d.ts +5 -2
  244. package/dist/pipeline-builder.d.ts.map +1 -1
  245. package/dist/pipeline-spec-types.d.ts +4 -0
  246. package/dist/pipeline-spec-types.d.ts.map +1 -1
  247. package/dist/pipeline-spec.d.ts +1 -9
  248. package/dist/pipeline-spec.d.ts.map +1 -1
  249. package/dist/plugin.d.ts.map +1 -1
  250. package/dist/points-lines/standard-owner.d.ts +1 -1
  251. package/dist/points-lines/standard-owner.d.ts.map +1 -1
  252. package/dist/post-process-errors.d.ts +9 -2
  253. package/dist/post-process-errors.d.ts.map +1 -1
  254. package/dist/prepare/extended-lighting/resources.d.ts +3 -3
  255. package/dist/prepare/prepared-graphics-resolver.d.ts +6 -1
  256. package/dist/prepare/prepared-graphics-resolver.d.ts.map +1 -1
  257. package/dist/record/dynamic-geometry-consumption.d.ts +15 -0
  258. package/dist/record/dynamic-geometry-consumption.d.ts.map +1 -0
  259. package/dist/record/frame-lighting.d.ts +3 -3
  260. package/dist/record/frame-lighting.d.ts.map +1 -1
  261. package/dist/record/frame-snapshot.d.ts +109 -13
  262. package/dist/record/frame-snapshot.d.ts.map +1 -1
  263. package/dist/record/frame-targets.d.ts.map +1 -1
  264. package/dist/record/frame.d.ts +41 -5
  265. package/dist/record/frame.d.ts.map +1 -1
  266. package/dist/record/main-pass-geometry.d.ts +13 -9
  267. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  268. package/dist/record/main-pass-material.d.ts +43 -3
  269. package/dist/record/main-pass-material.d.ts.map +1 -1
  270. package/dist/record/main-pass-sprite-draws.d.ts +2 -10
  271. package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
  272. package/dist/record/main-pass.d.ts +14 -2
  273. package/dist/record/main-pass.d.ts.map +1 -1
  274. package/dist/record/mesh-ssbo.d.ts +15 -6
  275. package/dist/record/mesh-ssbo.d.ts.map +1 -1
  276. package/dist/record/prepared-material-bindings.d.ts +19 -0
  277. package/dist/record/prepared-material-bindings.d.ts.map +1 -0
  278. package/dist/record/recovery-pipeline.d.ts +1 -0
  279. package/dist/record/recovery-pipeline.d.ts.map +1 -1
  280. package/dist/record/render-context.d.ts +101 -22
  281. package/dist/record/render-context.d.ts.map +1 -1
  282. package/dist/record/shadow-pass.d.ts +62 -7
  283. package/dist/record/shadow-pass.d.ts.map +1 -1
  284. package/dist/record/skybox-post-pass.d.ts +1 -1
  285. package/dist/record/skybox-post-pass.d.ts.map +1 -1
  286. package/dist/record/typed-frame-graph.d.ts +39 -8
  287. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  288. package/dist/record/view-ubo.d.ts +1 -0
  289. package/dist/record/view-ubo.d.ts.map +1 -1
  290. package/dist/recovery/render-system-candidate.d.ts +10 -2
  291. package/dist/recovery/render-system-candidate.d.ts.map +1 -1
  292. package/dist/recovery/render-system-roots.d.ts +11 -0
  293. package/dist/recovery/render-system-roots.d.ts.map +1 -0
  294. package/dist/recovery/types.d.ts +76 -0
  295. package/dist/recovery/types.d.ts.map +1 -0
  296. package/dist/reflection/background.d.ts +14 -0
  297. package/dist/reflection/background.d.ts.map +1 -0
  298. package/dist/reflection/filter.d.ts.map +1 -1
  299. package/dist/reflection/projection.d.ts +1 -0
  300. package/dist/reflection/projection.d.ts.map +1 -1
  301. package/dist/reflection/record-owner.d.ts +4 -4
  302. package/dist/reflection/record-owner.d.ts.map +1 -1
  303. package/dist/render-contract.d.ts +211 -53
  304. package/dist/render-contract.d.ts.map +1 -1
  305. package/dist/render-graph-primitives.d.ts +1 -0
  306. package/dist/render-graph-primitives.d.ts.map +1 -1
  307. package/dist/render-pipeline.d.ts +81 -6
  308. package/dist/render-pipeline.d.ts.map +1 -1
  309. package/dist/render-system-extract-tail.d.ts +15 -6
  310. package/dist/render-system-extract-tail.d.ts.map +1 -1
  311. package/dist/render-system-extract.d.ts +128 -41
  312. package/dist/render-system-extract.d.ts.map +1 -1
  313. package/dist/render-system-projections.d.ts +22 -0
  314. package/dist/render-system-projections.d.ts.map +1 -0
  315. package/dist/render-system.d.ts +27 -77
  316. package/dist/render-system.d.ts.map +1 -1
  317. package/dist/scene/render-scene-types.d.ts +14 -7
  318. package/dist/scene/render-scene-types.d.ts.map +1 -1
  319. package/dist/scene/render-scene.d.ts +31 -11
  320. package/dist/scene/render-scene.d.ts.map +1 -1
  321. package/dist/scene/shadow-visibility.d.ts +5 -0
  322. package/dist/scene/shadow-visibility.d.ts.map +1 -0
  323. package/dist/ssao-config.d.ts +8 -6
  324. package/dist/ssao-config.d.ts.map +1 -1
  325. package/dist/ssr/admission.d.ts +70 -1
  326. package/dist/ssr/admission.d.ts.map +1 -1
  327. package/dist/ssr/compose.d.ts +13 -0
  328. package/dist/ssr/compose.d.ts.map +1 -0
  329. package/dist/ssr/errors.d.ts +35 -0
  330. package/dist/ssr/errors.d.ts.map +1 -1
  331. package/dist/ssr/graph.d.ts +60 -0
  332. package/dist/ssr/graph.d.ts.map +1 -0
  333. package/dist/ssr/history.d.ts +97 -0
  334. package/dist/ssr/history.d.ts.map +1 -0
  335. package/dist/ssr/hiz.d.ts +37 -0
  336. package/dist/ssr/hiz.d.ts.map +1 -0
  337. package/dist/ssr/inspection.d.ts +42 -0
  338. package/dist/ssr/inspection.d.ts.map +1 -0
  339. package/dist/ssr/resources.d.ts +58 -0
  340. package/dist/ssr/resources.d.ts.map +1 -0
  341. package/dist/ssr/temporal.d.ts +38 -0
  342. package/dist/ssr/temporal.d.ts.map +1 -0
  343. package/dist/systems/skin-palette-allocator.d.ts +32 -10
  344. package/dist/systems/skin-palette-allocator.d.ts.map +1 -1
  345. package/dist/systems/skin-palette-types.d.ts +15 -0
  346. package/dist/systems/skin-palette-types.d.ts.map +1 -1
  347. package/dist/systems/transparent-dispatch.d.ts +9 -0
  348. package/dist/systems/transparent-dispatch.d.ts.map +1 -0
  349. package/dist/targets/material-source.d.ts +7 -0
  350. package/dist/targets/material-source.d.ts.map +1 -1
  351. package/dist/temporal/frame-coordinator.d.ts +7 -0
  352. package/dist/temporal/frame-coordinator.d.ts.map +1 -1
  353. package/dist/temporal/gpu.d.ts +8 -0
  354. package/dist/temporal/gpu.d.ts.map +1 -1
  355. package/dist/temporal/history.d.ts.map +1 -1
  356. package/dist/temporal/index.mjs +6 -6
  357. package/dist/temporal/inspection.d.ts.map +1 -1
  358. package/dist/temporal/standard-scene-data.d.ts +4 -2
  359. package/dist/temporal/standard-scene-data.d.ts.map +1 -1
  360. package/dist/temporal/target-coverage-attachment.d.ts +15 -0
  361. package/dist/temporal/target-coverage-attachment.d.ts.map +1 -0
  362. package/dist/temporal/temporal-view.d.ts +6 -0
  363. package/dist/temporal/temporal-view.d.ts.map +1 -1
  364. package/dist/temporal/view.d.ts +6 -0
  365. package/dist/temporal/view.d.ts.map +1 -1
  366. package/dist/typed-render-graph-primitives.d.ts +41 -1
  367. package/dist/typed-render-graph-primitives.d.ts.map +1 -1
  368. package/dist/typed-shadow-passes.d.ts +3 -1
  369. package/dist/typed-shadow-passes.d.ts.map +1 -1
  370. package/dist/volume/component.d.ts +1 -0
  371. package/dist/volume/component.d.ts.map +1 -1
  372. package/dist/volume/temporal.d.ts +6 -0
  373. package/dist/volume/temporal.d.ts.map +1 -1
  374. package/package.json +21 -20
  375. package/src/__tests__/auto-exposure-gpu-evidence.ts +291 -0
  376. package/src/__tests__/auto-exposure-inspection.unit.test.ts +80 -0
  377. package/src/__tests__/auto-exposure-transaction.integration.test.ts +99 -0
  378. package/src/__tests__/auto-exposure.browser.test.ts +33 -0
  379. package/src/__tests__/auto-exposure.dawn.test.ts +33 -0
  380. package/src/__tests__/capture-scene-projection.unit.test.ts +24 -0
  381. package/src/__tests__/detached-inspection.integration.test.ts +5 -0
  382. package/src/__tests__/device-feature-admission.unit.test.ts +15 -24
  383. package/src/__tests__/device-loss-fanout.unit.test.ts +43 -0
  384. package/src/__tests__/device-scope-lifecycle.unit.test.ts +18 -0
  385. package/src/__tests__/device-scope-stale-matrix.unit.test.ts +18 -0
  386. package/src/__tests__/docs-gate-green.unit.test.ts +3 -3
  387. package/src/__tests__/dynamic-geometry-batch.unit.test.ts +157 -0
  388. package/src/__tests__/dynamic-geometry.unit.test.ts +36 -0
  389. package/src/__tests__/dynamic-resolution-plugin.integration.test.ts +81 -0
  390. package/src/__tests__/factory-contract.integration.test.ts +231 -9
  391. package/src/__tests__/fallback-row-stride-surface.unit.test.ts +19 -0
  392. package/src/__tests__/feature-depth-input.dawn.test.ts +121 -0
  393. package/src/__tests__/feature-noise.unit.test.ts +38 -0
  394. package/src/__tests__/fixtures/taa-resolve-pinned-baseline.wgsl +301 -0
  395. package/src/__tests__/frame-camera-selection.unit.test.ts +131 -0
  396. package/src/__tests__/frame-observation.contract.test.ts +21 -0
  397. package/src/__tests__/frame-plan-contract.unit.test.ts +24 -0
  398. package/src/__tests__/frame-targets.unit.test.ts +24 -0
  399. package/src/__tests__/fullscreen-feature-plan.unit.test.ts +3 -0
  400. package/src/__tests__/gpu-driven-baseline.characterization.test.ts +79 -0
  401. package/src/__tests__/gpu-driven-batch-topology-pbr.unit.test.ts +307 -7
  402. package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +122 -142
  403. package/src/__tests__/gpu-driven-indirect-raster.browser.test.ts +10 -1
  404. package/src/__tests__/gpu-driven-lod-cache.unit.test.ts +261 -0
  405. package/src/__tests__/gpu-driven-mesh-world-identity.unit.test.ts +346 -0
  406. package/src/__tests__/gpu-driven-pbr.dawn.test.ts +1422 -0
  407. package/src/__tests__/gpu-driven-pbr.integration.test.ts +21 -0
  408. package/src/__tests__/gpu-driven-production-fixture.ts +96 -0
  409. package/src/__tests__/gpu-driven-production.integration.test.ts +1076 -106
  410. package/src/__tests__/gpu-driven-scaling.unit.test.ts +1 -3
  411. package/src/__tests__/gpu-driven-shadow-views.browser.test.ts +63 -0
  412. package/src/__tests__/gpu-driven-shadow-views.dawn.test.ts +294 -0
  413. package/src/__tests__/gpu-driven-shadow-views.integration.test.ts +97 -0
  414. package/src/__tests__/gpu-driven-shadow-views.unit.test.ts +168 -0
  415. package/src/__tests__/gpu-driven-skin-contract.test-d.ts +11 -0
  416. package/src/__tests__/gpu-driven-view-gpu-evidence.ts +72 -20
  417. package/src/__tests__/gpu-driven-view-graph.integration.test.ts +30 -9
  418. package/src/__tests__/gpu-driven-view.unit.test.ts +160 -52
  419. package/src/__tests__/gpu-pass-timing-browser-runner.ts +15 -0
  420. package/src/__tests__/gpu-pass-timing.browser.test.ts +20 -2
  421. package/src/__tests__/gpu-resource-scope.unit.test.ts +71 -0
  422. package/src/__tests__/gpu-resource-store-stride.unit.test.ts +42 -0
  423. package/src/__tests__/gpu-scene-render-graph-gpu.ts +19 -5
  424. package/src/__tests__/gpu-scene.dawn.test.ts +40 -13
  425. package/src/__tests__/gpu-scene.unit.test.ts +613 -33
  426. package/src/__tests__/instances-culling.integration.test.ts +36 -10
  427. package/src/__tests__/instances-derived-bounds.unit.test.ts +54 -0
  428. package/src/__tests__/instances-store.unit.test.ts +242 -39
  429. package/src/__tests__/ktx2-basis-gpu-consumer.dawn.test.ts +6 -0
  430. package/src/__tests__/lighting-scene-roundtrip.integration.test.ts +13 -16
  431. package/src/__tests__/lut-lkg-transaction.integration.test.ts +122 -0
  432. package/src/__tests__/material-contract-inventory.unit.test.ts +5 -6
  433. package/src/__tests__/material-dispatch-shader-id.unit.test.ts +51 -0
  434. package/src/__tests__/material-lane-selection.unit.test.ts +60 -0
  435. package/src/__tests__/material-numeric-upload.unit.test.ts +53 -0
  436. package/src/__tests__/material-texture-handles.unit.test.ts +18 -0
  437. package/src/__tests__/material-texture-source.unit.test.ts +87 -0
  438. package/src/__tests__/materials-authoring-validation.unit.test.ts +41 -0
  439. package/src/__tests__/materials-standard-contract.unit.test.ts +57 -3
  440. package/src/__tests__/mesh-buffer-usage-surface.unit.test.ts +4 -0
  441. package/src/__tests__/mesh-submission-lifetime.unit.test.ts +8 -0
  442. package/src/__tests__/morph-neutral-admission.unit.test.ts +19 -0
  443. package/src/__tests__/normal-fallback.dawn.test.ts +122 -0
  444. package/src/__tests__/particle-material-input-layout.unit.test.ts +104 -0
  445. package/src/__tests__/physical-clearcoat.integration.test.ts +42 -3
  446. package/src/__tests__/pipeline-texture-specialization.unit.test.ts +138 -0
  447. package/src/__tests__/point-shadow-inspection.unit.test.ts +2 -0
  448. package/src/__tests__/post-process-error-policy-owner.unit.test.ts +6 -5
  449. package/src/__tests__/prepared-gpu-driven-pbr-contract.test-d.ts +31 -0
  450. package/src/__tests__/prepared-gpu-driven-pbr-contract.unit.test.ts +39 -0
  451. package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +251 -9
  452. package/src/__tests__/prepared-graphics-capability.unit.test.ts +21 -0
  453. package/src/__tests__/prepared-graphics-pipeline-warmup.unit.test.ts +6 -2
  454. package/src/__tests__/profiler-phase-catalog.test.ts +5 -0
  455. package/src/__tests__/public-surface.test-d.ts +3 -0
  456. package/src/__tests__/recovery-candidate-prepare.contract.test.ts +30 -12
  457. package/src/__tests__/reflection-fallback-async-publication.unit.test.ts +171 -0
  458. package/src/__tests__/reflection-probe-authoring.unit.test.ts +12 -10
  459. package/src/__tests__/reflection-probe-box.dawn.test.ts +71 -0
  460. package/src/__tests__/reflection-probe-filter.unit.test.ts +9 -0
  461. package/src/__tests__/reflection-probe-orientation.dawn.test.ts +294 -0
  462. package/src/__tests__/reflection-probe-record-owner.characterization.unit.test.ts +5 -14
  463. package/src/__tests__/reflection-probe-updates.unit.test.ts +129 -0
  464. package/src/__tests__/render-error-code-owner.test-d.ts +7 -0
  465. package/src/__tests__/render-error-detail-ssot.test-d.ts +27 -1
  466. package/src/__tests__/render-error-exhaustive.test-d.ts +15 -0
  467. package/src/__tests__/render-extent.unit.test.ts +63 -0
  468. package/src/__tests__/render-feature-gpu-work.integration.test.ts +285 -2
  469. package/src/__tests__/render-feature-plan.unit.test.ts +245 -0
  470. package/src/__tests__/render-feature-shadow-order.unit.test.ts +228 -0
  471. package/src/__tests__/render-feature-zero-work.unit.test.ts +35 -1
  472. package/src/__tests__/render-graph-fallback.integration.test.ts +2 -2
  473. package/src/__tests__/render-owner-cohesion.unit.test.ts +55 -0
  474. package/src/__tests__/render-scene-mixed-updates.integration.test.ts +426 -0
  475. package/src/__tests__/render-scene-projection.unit.test.ts +915 -23
  476. package/src/__tests__/render-scene-resource-updates.integration.test.ts +214 -0
  477. package/src/__tests__/render-scene-skin-lifecycle.integration.test.ts +173 -0
  478. package/src/__tests__/render-scene-temporal-retry.unit.test.ts +133 -0
  479. package/src/__tests__/render-target-material-source.test-d.ts +27 -3
  480. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +233 -9
  481. package/src/__tests__/renderer-frame-transaction.integration.test.ts +923 -5
  482. package/src/__tests__/shader-manifest-fixture.ts +45 -0
  483. package/src/__tests__/skin-motion-regression.unit.test.ts +0 -33
  484. package/src/__tests__/skin-palette-persistent.integration.test.ts +28 -0
  485. package/src/__tests__/skin-palette-persistent.unit.test.ts +247 -0
  486. package/src/__tests__/skinned-shadow-caster.test.ts +125 -14
  487. package/src/__tests__/specular-environment-scale.dawn.test.ts +158 -0
  488. package/src/__tests__/ssao-config.unit.test.ts +7 -0
  489. package/src/__tests__/ssao-surface.dawn.test.ts +252 -0
  490. package/src/__tests__/ssr-admission-matrix.integration.test.ts +7 -1
  491. package/src/__tests__/ssr-confidence-compose.unit.test.ts +84 -0
  492. package/src/__tests__/ssr-coordinate-oracle.unit.test.ts +28 -0
  493. package/src/__tests__/ssr-cube-silhouette.dawn.test.ts +270 -0
  494. package/src/__tests__/ssr-format-probe-gating.integration.test.ts +82 -0
  495. package/src/__tests__/ssr-gpu-dispatch.browser.test.ts +99 -0
  496. package/src/__tests__/ssr-gpu-dispatch.dawn.test.ts +116 -0
  497. package/src/__tests__/ssr-gpu-dispatch.ts +951 -0
  498. package/src/__tests__/ssr-graph-topology.unit.test.ts +104 -0
  499. package/src/__tests__/ssr-history-filter.dawn.test.ts +198 -0
  500. package/src/__tests__/ssr-hit-color.dawn.test.ts +273 -0
  501. package/src/__tests__/ssr-hiz-odd-reduction.dawn.test.ts +569 -0
  502. package/src/__tests__/ssr-hiz-rescue.dawn.test.ts +279 -0
  503. package/src/__tests__/ssr-hiz.browser.test.ts +150 -0
  504. package/src/__tests__/ssr-hiz.dawn.test.ts +150 -0
  505. package/src/__tests__/ssr-hiz.unit.test.ts +56 -0
  506. package/src/__tests__/ssr-inspection.unit.test.ts +105 -0
  507. package/src/__tests__/ssr-memory-budget.unit.test.ts +45 -0
  508. package/src/__tests__/ssr-performance-sequence-aggregation.unit.test.ts +15 -0
  509. package/src/__tests__/ssr-public-surface.test-d.ts +37 -0
  510. package/src/__tests__/ssr-receiver-coverage.dawn.test.ts +387 -0
  511. package/src/__tests__/ssr-reflection-mip-reducer.dawn.test.ts +309 -0
  512. package/src/__tests__/ssr-reprojection-probe.ts +90 -0
  513. package/src/__tests__/ssr-roughness-mip.dawn.test.ts +163 -0
  514. package/src/__tests__/ssr-shader-prewarm.unit.test.ts +59 -0
  515. package/src/__tests__/ssr-spatial.integration.test.ts +141 -0
  516. package/src/__tests__/ssr-stable-lattice.dawn.test.ts +475 -0
  517. package/src/__tests__/ssr-temporal-history.unit.test.ts +202 -0
  518. package/src/__tests__/ssr-texel-crossing.dawn.test.ts +448 -0
  519. package/src/__tests__/ssr-zero-work.integration.test.ts +87 -0
  520. package/src/__tests__/standard-cluster-variant-resolution.unit.test.ts +84 -12
  521. package/src/__tests__/standard-clustered-pipeline.unit.test.ts +29 -0
  522. package/src/__tests__/standard-output-chain-contract.unit.test.ts +99 -0
  523. package/src/__tests__/standard-output-chain.integration.test.ts +73 -0
  524. package/src/__tests__/standard-output-gpu-evidence.ts +205 -0
  525. package/src/__tests__/standard-output.browser.test.ts +15 -0
  526. package/src/__tests__/standard-output.dawn.test.ts +15 -0
  527. package/src/__tests__/standard-pbr-artifact-assembly.unit.test.ts +275 -0
  528. package/src/__tests__/standard-pipeline.integration.test.ts +320 -3
  529. package/src/__tests__/standard-profile-oracle.unit.test.ts +49 -0
  530. package/src/__tests__/standard-surface-lighting.unit.test.ts +41 -0
  531. package/src/__tests__/standard-texture-specialization.dawn.test.ts +141 -0
  532. package/src/__tests__/surface-adjacent-regressions.integration.test.ts +6 -3
  533. package/src/__tests__/taa-coverage-edge.dawn.test.ts +199 -0
  534. package/src/__tests__/taa-history-clipping.dawn.test.ts +328 -0
  535. package/src/__tests__/taa-neighborhood-equivalence.dawn.test.ts +782 -0
  536. package/src/__tests__/taa-stability-feedback.browser.test.ts +23 -0
  537. package/src/__tests__/taa-stability-feedback.dawn.test.ts +68 -0
  538. package/src/__tests__/taa-stability-feedback.ts +342 -0
  539. package/src/__tests__/temporal-history-lifecycle.unit.test.ts +49 -7
  540. package/src/__tests__/temporal-reset-matrix.unit.test.ts +9 -0
  541. package/src/__tests__/temporal-transaction.integration.test.ts +28 -0
  542. package/src/__tests__/temporal-view.unit.test.ts +17 -0
  543. package/src/__tests__/texture-usage-surface.unit.test.ts +4 -2
  544. package/src/__tests__/transmission-prewarm.unit.test.ts +57 -0
  545. package/src/__tests__/typed-pipeline-topology.unit.test.ts +109 -4
  546. package/src/__tests__/typed-scene-pass-material-cache.integration.test.ts +126 -0
  547. package/src/__tests__/vertex-layouts-ssot.unit.test.ts +47 -0
  548. package/src/__tests__/visibility-extract.perf.test.ts +20 -8
  549. package/src/__tests__/visibility-instances.integration.test.ts +19 -7
  550. package/src/__tests__/visibility-producer-matrix.integration.test.ts +20 -9
  551. package/src/__tests__/volumetric-fog-stage-readback.dawn.test.ts +62 -13
  552. package/src/__tests__/volumetric-fog-topology.unit.test.ts +7 -7
  553. package/src/assembly/atmosphere-shader-prewarm.ts +39 -0
  554. package/src/assembly/device-feature-admission.ts +9 -4
  555. package/src/assembly/dynamic-geometry-host.ts +284 -163
  556. package/src/assembly/dynamic-geometry-runtime.ts +71 -0
  557. package/src/assembly/factory.ts +4 -0
  558. package/src/assembly/gpu-driven-owner.ts +29 -0
  559. package/src/assembly/host-contract.ts +19 -0
  560. package/src/assembly/material/assembly.ts +383 -1
  561. package/src/assembly/material/pipeline-helpers.ts +100 -0
  562. package/src/assembly/material-shader-policy.ts +110 -46
  563. package/src/assembly/recovery/device-loss-fanout.ts +5 -0
  564. package/src/assembly/recovery/failure-location.ts +62 -0
  565. package/src/assembly/recovery/recovery-attempt.ts +2 -29
  566. package/src/assembly/recovery/renderer-recover.ts +16 -17
  567. package/src/assembly/renderer-facade.ts +34 -1
  568. package/src/assembly/renderer-feature-inspection.ts +13 -0
  569. package/src/assembly/renderer-frame-transaction.ts +63 -0
  570. package/src/assembly/renderer-helpers.ts +26 -0
  571. package/src/assembly/renderer-inspection.ts +6 -0
  572. package/src/assembly/shader-prewarm-policy.ts +46 -1
  573. package/src/assembly/skin-palette-owner.ts +21 -0
  574. package/src/assembly/ssr-shader-prewarm.ts +41 -0
  575. package/src/assembly/webgpu-device-loss.ts +75 -0
  576. package/src/assembly/webgpu-pbr-ready.ts +232 -0
  577. package/src/assembly/webgpu-ready-mipmap.ts +26 -0
  578. package/src/assembly/webgpu-ready.ts +161 -131
  579. package/src/assembly/webgpu-renderer-bootstrap.ts +85 -0
  580. package/src/assembly/webgpu-renderer-contract.ts +69 -0
  581. package/src/assembly/webgpu-renderer-guards.ts +32 -0
  582. package/src/assembly/webgpu-renderer-observation.ts +43 -0
  583. package/src/assembly/webgpu-renderer-recovery-failure.ts +62 -0
  584. package/src/assembly/webgpu-renderer.ts +744 -730
  585. package/src/assembly/webgpu-vertex-layouts.ts +233 -87
  586. package/src/capture/scene-projection.ts +37 -0
  587. package/src/components/__tests__/camera-exposure.contract.unit.test.ts +153 -0
  588. package/src/components/__tests__/dynamic-resolution-schema.unit.test.ts +65 -0
  589. package/src/components/__tests__/light-radius.integration.test.ts +59 -0
  590. package/src/components/camera.ts +287 -7
  591. package/src/components/directional-light.ts +3 -3
  592. package/src/components/dynamic-resolution.ts +67 -0
  593. package/src/components/index.ts +2 -0
  594. package/src/components/instances.ts +45 -18
  595. package/src/components/light-helpers.ts +6 -1
  596. package/src/components/reflection-probe.ts +1 -0
  597. package/src/components/scene-instance.ts +2 -1
  598. package/src/components/screen-space-reflection.ts +21 -0
  599. package/src/components/skylight.ts +4 -1
  600. package/src/components/sprite-animation.ts +2 -2
  601. package/src/components/sprite-instances.ts +1 -1
  602. package/src/device/device-scope.ts +13 -7
  603. package/src/device/gpu-residency.ts +45 -42
  604. package/src/dynamic-geometry.ts +13 -8
  605. package/src/environment/background.ts +281 -0
  606. package/src/environment/ibl.ts +211 -0
  607. package/src/environment/storage.ts +63 -0
  608. package/src/errors/gpu-driven.ts +49 -8
  609. package/src/errors/render.ts +91 -3
  610. package/src/extract/camera.ts +37 -1
  611. package/src/extract/contracts.ts +10 -0
  612. package/src/extract/gpu-driven.ts +240 -10
  613. package/src/features/host.ts +237 -19
  614. package/src/features/noise-texture.ts +91 -0
  615. package/src/features/plan.ts +553 -24
  616. package/src/features/prepared-gpu-work.ts +631 -49
  617. package/src/features/prepared-graphics-store.ts +31 -0
  618. package/src/features/prepared-graphics.ts +5 -0
  619. package/src/features/render-graph-compute.ts +41 -8
  620. package/src/features/render-graph-contribution.ts +141 -3
  621. package/src/features/render-graph-raster.ts +143 -30
  622. package/src/features/types.ts +11 -0
  623. package/src/fullscreen-post-process-pass.ts +3 -0
  624. package/src/gpu-driven/batch-topology.ts +452 -108
  625. package/src/gpu-driven/material-artifacts.ts +179 -0
  626. package/src/gpu-driven/material-bindings.ts +41 -0
  627. package/src/gpu-driven/pbr-program.ts +31 -0
  628. package/src/gpu-driven/prepared-draw.ts +42 -6
  629. package/src/gpu-driven/production-raster.ts +2806 -716
  630. package/src/gpu-driven/shadow-views.ts +539 -0
  631. package/src/gpu-driven/view-gpu.ts +188 -41
  632. package/src/gpu-resource.ts +21 -2
  633. package/src/gpu-scene-schema.ts +61 -11
  634. package/src/gpu-scene.ts +197 -99
  635. package/src/gpu-texture-usage.ts +1 -0
  636. package/src/hdrp-buffers.ts +141 -36
  637. package/src/ibl/IblPipelineCache.ts +5 -0
  638. package/src/ibl/frame-resources.ts +38 -0
  639. package/src/ibl/skylight-bind-group.ts +32 -21
  640. package/src/index.ts +194 -75
  641. package/src/inspection-types.ts +207 -1
  642. package/src/instances-derived-bounds.ts +258 -34
  643. package/src/instances.ts +459 -150
  644. package/src/internal.ts +22 -0
  645. package/src/lifecycle.ts +2 -0
  646. package/src/material-row.ts +194 -0
  647. package/src/materials.ts +44 -8
  648. package/src/pbr-pipeline.ts +172 -19
  649. package/src/pipeline/render-extent.ts +111 -0
  650. package/src/pipeline/standard-build-context.ts +7 -0
  651. package/src/pipeline/standard-forward-lane.ts +74 -29
  652. package/src/pipeline/standard-output/__tests__/lut-admission.unit.test.ts +103 -0
  653. package/src/pipeline/standard-output/auto-exposure/__tests__/capability.unit.test.ts +40 -0
  654. package/src/pipeline/standard-output/auto-exposure/__tests__/gpu.contract.unit.test.ts +125 -0
  655. package/src/pipeline/standard-output/auto-exposure/__tests__/graph.contract.unit.test.ts +94 -0
  656. package/src/pipeline/standard-output/auto-exposure/__tests__/oracle.test.ts +108 -0
  657. package/src/pipeline/standard-output/auto-exposure/__tests__/preset.test.ts +26 -0
  658. package/src/pipeline/standard-output/auto-exposure/capability.ts +38 -0
  659. package/src/pipeline/standard-output/auto-exposure/gpu.ts +567 -0
  660. package/src/pipeline/standard-output/auto-exposure/graph.ts +221 -0
  661. package/src/pipeline/standard-output/auto-exposure/inspection.ts +231 -0
  662. package/src/pipeline/standard-output/auto-exposure/oracle.ts +291 -0
  663. package/src/pipeline/standard-output/auto-exposure/preset.ts +28 -0
  664. package/src/pipeline/standard-output/auto-exposure/state.ts +156 -0
  665. package/src/pipeline/standard-output/color-transform.ts +124 -0
  666. package/src/pipeline/standard-output/graph.ts +139 -0
  667. package/src/pipeline/standard-output/lut-admission.ts +121 -0
  668. package/src/pipeline/standard-output/lut-gpu.ts +399 -0
  669. package/src/pipeline/standard-output/lut-state.ts +176 -0
  670. package/src/pipeline/standard-output/resources.ts +121 -0
  671. package/src/pipeline/standard-output/types.ts +58 -0
  672. package/src/pipeline/standard-pipeline.ts +232 -70
  673. package/src/pipeline/standard-post.ts +190 -27
  674. package/src/pipeline/standard-profile.ts +2 -1
  675. package/src/pipeline-builder.ts +28 -9
  676. package/src/pipeline-spec-types.ts +4 -0
  677. package/src/pipeline-spec.ts +37 -29
  678. package/src/plugin.ts +4 -0
  679. package/src/points-lines/standard-owner.ts +6 -14
  680. package/src/post-process-errors.ts +26 -18
  681. package/src/prepare/__tests__/extended-lighting-capability.unit.test.ts +2 -2
  682. package/src/prepare/extended-lighting/resources.ts +3 -3
  683. package/src/prepare/prepared-graphics-resolver.ts +9 -2
  684. package/src/record/__tests__/main-pass-geometry.unit.test.ts +68 -0
  685. package/src/record/__tests__/material-bg-cache.unit.test.ts +45 -0
  686. package/src/record/__tests__/material-slot-plan.unit.test.ts +57 -0
  687. package/src/record/__tests__/mesh-ssbo.unit.test.ts +40 -1
  688. package/src/record/__tests__/recovery-pipeline.unit.test.ts +20 -1
  689. package/src/record/__tests__/sprite-material-abi.unit.test.ts +41 -6
  690. package/src/record/__tests__/temporal-payload.unit.test.ts +2 -0
  691. package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +45 -0
  692. package/src/record/__tests__/view-ubo-layout.unit.test.ts +5 -0
  693. package/src/record/dynamic-geometry-consumption.ts +59 -0
  694. package/src/record/frame-lighting.ts +15 -12
  695. package/src/record/frame-snapshot.ts +146 -34
  696. package/src/record/frame-targets.ts +9 -0
  697. package/src/record/frame.ts +838 -170
  698. package/src/record/main-pass-geometry.ts +209 -103
  699. package/src/record/main-pass-material.ts +157 -40
  700. package/src/record/main-pass-sprite-draws.ts +16 -107
  701. package/src/record/main-pass.ts +141 -12
  702. package/src/record/material-uniforms.ts +1 -1
  703. package/src/record/mesh-ssbo.ts +59 -15
  704. package/src/record/prepared-material-bindings.ts +238 -0
  705. package/src/record/recovery-pipeline.ts +69 -90
  706. package/src/record/render-context.ts +123 -24
  707. package/src/record/shadow-pass.ts +1389 -230
  708. package/src/record/skybox-post-pass.ts +14 -3
  709. package/src/record/typed-frame-graph.ts +1064 -47
  710. package/src/record/view-ubo.ts +12 -1
  711. package/src/recovery/render-system-candidate.ts +135 -68
  712. package/src/recovery/render-system-roots.ts +113 -0
  713. package/src/recovery/types.ts +77 -0
  714. package/src/reflection/background.ts +151 -0
  715. package/src/reflection/filter.ts +3 -0
  716. package/src/reflection/projection.ts +16 -6
  717. package/src/reflection/record-owner.ts +308 -140
  718. package/src/render-contract.ts +237 -49
  719. package/src/render-graph-primitives.ts +34 -5
  720. package/src/render-pipeline.ts +90 -2
  721. package/src/render-system-extract-tail.ts +431 -162
  722. package/src/render-system-extract.ts +478 -119
  723. package/src/render-system-projections.ts +241 -0
  724. package/src/render-system.ts +715 -828
  725. package/src/scene/render-scene-types.ts +13 -4
  726. package/src/scene/render-scene.ts +577 -355
  727. package/src/scene/shadow-visibility.ts +21 -0
  728. package/src/ssao-config.ts +25 -8
  729. package/src/ssr/admission.ts +380 -14
  730. package/src/ssr/compose.ts +144 -0
  731. package/src/ssr/errors.ts +48 -0
  732. package/src/ssr/graph.ts +797 -0
  733. package/src/ssr/history.ts +436 -0
  734. package/src/ssr/hiz.ts +124 -0
  735. package/src/ssr/inspection.ts +124 -0
  736. package/src/ssr/resources.ts +202 -0
  737. package/src/ssr/temporal.ts +164 -0
  738. package/src/systems/skin-palette-allocator.ts +495 -148
  739. package/src/systems/skin-palette-types.ts +17 -0
  740. package/src/systems/transparent-dispatch.ts +90 -0
  741. package/src/targets/material-source.ts +34 -0
  742. package/src/temporal/coverage.ts +1 -1
  743. package/src/temporal/frame-coordinator.ts +23 -2
  744. package/src/temporal/gpu.ts +79 -12
  745. package/src/temporal/history.ts +3 -1
  746. package/src/temporal/inspection.ts +2 -1
  747. package/src/temporal/standard-scene-data.ts +10 -2
  748. package/src/temporal/target-coverage-attachment.ts +72 -0
  749. package/src/temporal/temporal-view.ts +11 -2
  750. package/src/temporal/view.ts +12 -2
  751. package/src/transmission/__tests__/standard-transmission.dawn.test.ts +219 -139
  752. package/src/typed-render-graph-primitives.ts +304 -74
  753. package/src/typed-shadow-passes.ts +122 -12
  754. package/src/volume/component.ts +1 -0
  755. package/src/volume/passes.ts +3 -3
  756. package/src/volume/temporal.ts +10 -0
  757. package/dist/chunk-GE3SDD72.mjs.map +0 -1
  758. package/dist/chunk-GF523LHF.mjs.map +0 -1
  759. package/dist/chunk-IQMKJLNU.mjs.map +0 -1
  760. package/dist/chunk-J52NMQKR.mjs.map +0 -1
  761. package/dist/chunk-J6KACNOH.mjs.map +0 -1
  762. package/dist/chunk-JAASCMXA.mjs.map +0 -1
  763. package/dist/chunk-MBU4ZVE7.mjs.map +0 -1
  764. package/dist/chunk-MYJ5RPJO.mjs.map +0 -1
  765. package/dist/chunk-RBDBZ5R7.mjs.map +0 -1
  766. package/dist/chunk-VSL23LZE.mjs.map +0 -1
  767. package/src/__tests__/instances-world-ownership.integration.test.ts +0 -62
  768. package/src/scene/__tests__/changed-block-summary.candidate.test.ts +0 -36
@@ -9,8 +9,21 @@ import {
9
9
  type RhiRenderPassEncoder,
10
10
  } from '@forgeax/engine-rhi';
11
11
  import type { MaterialRenderState, PassSelector } from '@forgeax/engine-types';
12
- import type { GpuBuffer } from '../gpu-resource';
12
+ import { transmissionBackdropAvailable } from '../assembly/device-feature-admission';
13
+ import { gpuDrivenShadowDrawKey } from '../extract/gpu-driven';
14
+ import type { SubmissionPlan } from '../gpu-driven/batch-topology';
15
+ import type { GpuDrivenShadowBatchProjection } from '../gpu-driven/production-raster';
16
+ import { type ShadowViewIdentity, shadowViewIdentityKey } from '../gpu-driven/shadow-views';
17
+ import type { GpuDrivenView } from '../gpu-driven/view-gpu';
18
+ import { GpuBuffer } from '../gpu-resource';
19
+ import {
20
+ GPU_BUFFER_USAGE_COPY_DST,
21
+ GPU_BUFFER_USAGE_STORAGE,
22
+ GPU_BUFFER_USAGE_UNIFORM,
23
+ } from '../gpu-usage';
13
24
  import { assembleMaterialWithSkylightEntries } from '../ibl/skylight-bind-group';
25
+ import type { InstanceBufferCacheEntry } from '../instance-buffer-cache';
26
+ import { instanceUploadRangesForResident } from '../instances';
14
27
  import {
15
28
  buildPbrMaterialUserRegionEntries,
16
29
  createPbrSkinMeshBindGroupEntries,
@@ -19,11 +32,18 @@ import {
19
32
  shadowCasterVariantSet,
20
33
  } from '../pbr-pipeline';
21
34
  import { COOKIE_MATRIX_BYTES } from '../prepare/extended-lighting/resources';
22
- import type { DispatchEntry, ExtractedLights } from '../render-system-extract';
35
+ import type {
36
+ DispatchEntry,
37
+ ExtractedLights,
38
+ RenderableSnapshot,
39
+ ShadowCasterMembership,
40
+ } from '../render-system-extract';
23
41
  import { matchPass } from '../systems/pass-selector';
42
+ import { instanceCollectionCacheKey, worldEntityKey } from './frame-snapshot';
24
43
  import {
25
- resolveGeometryInstanceBuffer,
44
+ reportInstanceResidency,
26
45
  resolveGeometryInstancesBindGroup,
46
+ resolveStorageInstanceChunks,
27
47
  } from './main-pass-geometry';
28
48
  import {
29
49
  buildPerSubmeshMaterialBg,
@@ -31,10 +51,15 @@ import {
31
51
  prepareMaterialSkylight,
32
52
  } from './main-pass-material';
33
53
  import {
54
+ findFromChain,
34
55
  getOrCreateFromChain,
56
+ getOrCreateFromChainResult,
57
+ INSTANCE_UBO_FULL_ARRAY_BYTES,
58
+ MAX_UNIFORM_INSTANCES,
35
59
  MESH_PER_ENTITY_STRIDE,
36
60
  MESH_SSBO_BYTES,
37
61
  MESH_UBO_FULL_ARRAY_BYTES,
62
+ packInstanceStorageBuffer,
38
63
  } from './mesh-ssbo';
39
64
  import type { _InternalRenderPipelineContext } from './render-context';
40
65
  import { MATERIAL_PER_ENTITY_STRIDE, STANDARD_PBR_UBO_SIZE } from './render-context';
@@ -43,6 +68,32 @@ import { POINTS_LINES_VIEW_BYTES, pointShadowViewOffset, VIEW_UNIFORM_BYTES } fr
43
68
  export const SHADOW_CASTER_SLOT_STRIDE = 256;
44
69
  export const SHADOW_CASTER_BUFFER_SIZE = SHADOW_CASTER_SLOT_STRIDE * 8;
45
70
 
71
+ /**
72
+ * Renderer-owned capable shadow submission. The view/counter/indirect ABI is
73
+ * shared with the main GPU-driven lane; this record only supplies the
74
+ * prepared per-batch raster state that cannot be inferred from a raw scene.
75
+ * When present, the capable lane emits one indirect command per topology batch
76
+ * and never walks `validatedOrdered`.
77
+ */
78
+ export interface GpuDrivenShadowBatch {
79
+ readonly pipeline: RenderPipeline;
80
+ readonly meshBindGroup: BindGroup;
81
+ readonly visibleBindGroup: BindGroup;
82
+ readonly deformation: 'rigid' | 'skin';
83
+ /** Complete binding for this batch, including opaque batches after custom casters. */
84
+ readonly materialGroup: BindGroup;
85
+ readonly vertexBuffer: Buffer;
86
+ readonly indexBuffer?: Buffer | undefined;
87
+ readonly indexFormat?: 'uint16' | 'uint32' | undefined;
88
+ }
89
+
90
+ export interface GpuDrivenShadowSubmission {
91
+ readonly view: GpuDrivenView;
92
+ readonly plan: SubmissionPlan;
93
+ readonly batches: ReadonlyMap<number, GpuDrivenShadowBatch>;
94
+ readonly instancesBindGroup?: BindGroup | undefined;
95
+ }
96
+
46
97
  export function directionalShadowCasterOffset(cascadeIndex: number): number {
47
98
  return SHADOW_CASTER_SLOT_STRIDE * cascadeIndex;
48
99
  }
@@ -253,43 +304,76 @@ function ensureTypedShadowViewBg(
253
304
  }
254
305
 
255
306
  interface ShadowDispatch {
307
+ readonly passIndex: number;
308
+ readonly materialHandle: number;
256
309
  readonly vertexEntry: string | undefined;
257
310
  readonly fragmentEntry: string | undefined;
258
311
  readonly materialShaderId: string;
259
312
  readonly renderState: MaterialRenderState | undefined;
313
+ readonly materialSlot: number | undefined;
314
+ readonly paramSnapshot: DispatchEntry['paramSnapshot'];
260
315
  }
261
316
 
262
- type ShadowDispatchMap = ReadonlyMap<number, ReadonlyMap<number, ShadowDispatch>>;
317
+ type ShadowDispatchMap = ReadonlyMap<number, ReadonlyMap<number, readonly ShadowDispatch[]>>;
318
+
319
+ function shadowDispatchEntries(c: _InternalRenderPipelineContext): readonly DispatchEntry[] {
320
+ return c.shadowDispatch ?? c.dispatch;
321
+ }
263
322
 
264
323
  export function shadowShaderMap(c: _InternalRenderPipelineContext): ShadowDispatchMap {
265
- const shaders = new Map<number, Map<number, ShadowDispatch>>();
266
- for (const entry of c.dispatch) {
267
- if (entry.tags.LightMode === 'ShadowCaster' && entry.materialShaderId !== undefined) {
268
- let byMaterial = shaders.get(entry.renderableIndex);
269
- if (byMaterial === undefined) {
270
- byMaterial = new Map<number, ShadowDispatch>();
271
- shaders.set(entry.renderableIndex, byMaterial);
272
- }
273
- byMaterial.set(entry.materialHandle, {
274
- vertexEntry: entry.vertexEntry,
275
- fragmentEntry: entry.fragmentEntry,
276
- materialShaderId:
277
- entry.materialShaderId === 'forgeax::default-standard-pbr'
278
- ? SHADOW_CASTER_SHADER_ID
279
- : entry.materialShaderId,
280
- renderState: entry.renderState,
281
- });
324
+ const shaders = new Map<number, Map<number, ShadowDispatch[]>>();
325
+ const shadowRows = c.shadowDispatch !== undefined;
326
+ const rows = (shadowRows ? c.shadowValidatedOrdered : c.validatedOrdered) ?? [];
327
+ const base = shadowRows ? c.validatedOrdered.length : 0;
328
+ const slotsByRenderable = new Map(
329
+ rows.map((row, index) => [row.renderableIndex, c.materialSlotIndices?.[base + index] ?? []]),
330
+ );
331
+ for (const entry of shadowDispatchEntries(c)) {
332
+ if (entry.tags.LightMode !== 'ShadowCaster' || entry.materialShaderId === undefined) continue;
333
+ let byMaterial = shaders.get(entry.renderableIndex);
334
+ if (byMaterial === undefined) {
335
+ byMaterial = new Map<number, ShadowDispatch[]>();
336
+ shaders.set(entry.renderableIndex, byMaterial);
282
337
  }
338
+ const candidateSlots = slotsByRenderable.get(entry.renderableIndex) ?? [];
339
+ const materialSlot = candidateSlots.find(
340
+ (slot) => c.materialSlots?.[slot]?.materialHandle === entry.materialHandle,
341
+ );
342
+ const dispatches = byMaterial.get(entry.materialHandle) ?? [];
343
+ dispatches.push({
344
+ passIndex: entry.passIndex,
345
+ materialHandle: entry.materialHandle,
346
+ vertexEntry: entry.vertexEntry,
347
+ fragmentEntry: entry.fragmentEntry,
348
+ materialShaderId:
349
+ entry.materialShaderId === 'forgeax::default-standard-pbr'
350
+ ? SHADOW_CASTER_SHADER_ID
351
+ : entry.materialShaderId,
352
+ renderState: entry.renderState,
353
+ materialSlot,
354
+ paramSnapshot: entry.paramSnapshot,
355
+ });
356
+ byMaterial.set(entry.materialHandle, dispatches);
283
357
  }
284
358
  return shaders;
285
359
  }
286
360
 
287
- function shadowPipeline(c: _InternalRenderPipelineContext): RenderPipeline | null {
361
+ function shadowRasterState(
362
+ state: MaterialRenderState | undefined,
363
+ reflected: boolean,
364
+ ): MaterialRenderState | undefined {
365
+ return reflected ? { ...state, frontFace: state?.frontFace === 'cw' ? 'ccw' : 'cw' } : state;
366
+ }
367
+
368
+ function shadowPipeline(
369
+ c: _InternalRenderPipelineContext,
370
+ reflected = false,
371
+ ): RenderPipeline | null {
288
372
  return (
289
373
  c.runtime.getMaterialShaderPipeline?.(
290
374
  SHADOW_CASTER_SHADER_ID,
291
375
  false,
292
- undefined,
376
+ shadowRasterState(undefined, reflected),
293
377
  'triangle-list',
294
378
  undefined,
295
379
  shadowCasterVariantSet(c.runtime.device.caps.storageBuffer, false),
@@ -307,32 +391,6 @@ function shadowPipeline(c: _InternalRenderPipelineContext): RenderPipeline | nul
307
391
  );
308
392
  }
309
393
 
310
- function skinnedShadowPipeline(
311
- c: _InternalRenderPipelineContext,
312
- entry: _InternalRenderPipelineContext['validatedOrdered'][number],
313
- ): RenderPipeline | null {
314
- return (
315
- c.runtime.getMaterialShaderPipeline?.(
316
- SHADOW_CASTER_SHADER_ID,
317
- false,
318
- undefined,
319
- entry.mesh.submeshes[0]?.topology ?? 'triangle-list',
320
- entry.mesh.indexFormat,
321
- shadowCasterVariantSet(c.runtime.device.caps.storageBuffer, true),
322
- 'shadow-caster',
323
- undefined,
324
- 1,
325
- undefined,
326
- undefined,
327
- undefined,
328
- undefined,
329
- entry.mesh.layoutProjection,
330
- undefined,
331
- 'pbr-skin',
332
- ) ?? null
333
- );
334
- }
335
-
336
394
  function skinnedShadowMeshBindGroup(
337
395
  c: _InternalRenderPipelineContext,
338
396
  entry: _InternalRenderPipelineContext['validatedOrdered'][number],
@@ -363,13 +421,410 @@ function skinnedShadowMeshBindGroup(
363
421
  );
364
422
  }
365
423
 
424
+ /**
425
+ * Record the capable shadow lane from the shared view's indirect arguments.
426
+ * The submission is an explicit prepared contract: missing view buffers or a
427
+ * missing batch raster projection are terminal rather than a reason to fall
428
+ * back to a CPU caster walk. The caller may then keep the existing CPU path
429
+ * when no capable submission was published for the current view.
430
+ */
431
+ export function recordGpuDrivenShadowIndirect(
432
+ pass: RhiRenderPassEncoder,
433
+ submission: GpuDrivenShadowSubmission,
434
+ ): void {
435
+ const indirect = submission.view.indirectBuffer;
436
+ if (indirect === undefined) {
437
+ throw new RhiError({
438
+ code: 'internal-error',
439
+ expected: 'GPU-driven shadow view indirect buffer',
440
+ hint: 'publish a prepared view generation before recording a capable shadow pass',
441
+ });
442
+ }
443
+ const indexed = new Map<number, GpuDrivenShadowBatch>();
444
+ for (const [batchId, batch] of submission.batches) indexed.set(batchId, batch);
445
+ let currentPipeline: RenderPipeline | undefined;
446
+ let currentVertex: Buffer | undefined;
447
+ let currentIndex: Buffer | undefined;
448
+ for (const batch of submission.plan.batches) {
449
+ const raster = indexed.get(batch.batchId);
450
+ if (raster === undefined) {
451
+ throw new RhiError({
452
+ code: 'internal-error',
453
+ expected: `prepared shadow raster batch ${batch.batchId}`,
454
+ hint: 'keep the shadow view plan and prepared batch projection on one generation',
455
+ });
456
+ }
457
+ if (currentPipeline !== raster.pipeline) {
458
+ pass.setPipeline(raster.pipeline);
459
+ currentPipeline = raster.pipeline;
460
+ }
461
+ if (currentVertex !== raster.vertexBuffer) {
462
+ pass.setVertexBuffer(0, raster.vertexBuffer);
463
+ currentVertex = raster.vertexBuffer;
464
+ }
465
+ pass.setBindGroup(1, raster.materialGroup, [0]);
466
+ if (raster.indexBuffer !== undefined) {
467
+ if (raster.indexFormat === undefined) {
468
+ throw new RhiError({
469
+ code: 'internal-error',
470
+ expected: `index format for prepared shadow raster batch ${batch.batchId}`,
471
+ hint: 'publish the geometry layout projection with the capable shadow batch',
472
+ });
473
+ }
474
+ if (currentIndex !== raster.indexBuffer) {
475
+ pass.setIndexBuffer(raster.indexBuffer, raster.indexFormat);
476
+ currentIndex = raster.indexBuffer;
477
+ }
478
+ pass.setBindGroup(2, raster.meshBindGroup, raster.deformation === 'skin' ? [0, 0, 0] : [0]);
479
+ pass.setBindGroup(3, raster.visibleBindGroup);
480
+ pass.drawIndexedIndirect(indirect, batch.indirectOffset);
481
+ } else {
482
+ pass.setBindGroup(2, raster.meshBindGroup, raster.deformation === 'skin' ? [0, 0, 0] : [0]);
483
+ pass.setBindGroup(3, raster.visibleBindGroup);
484
+ pass.drawIndirect(indirect, batch.indirectOffset);
485
+ }
486
+ }
487
+ }
488
+
489
+ function shadowDrawIndicesBySubmesh(
490
+ source: RenderableSnapshot,
491
+ mesh: _InternalRenderPipelineContext['validatedOrdered'][number]['mesh'],
492
+ ): ReadonlyMap<number, number> {
493
+ const draws = source.gpuDrivenDraws;
494
+ if (draws === undefined || draws.length === 0) return new Map();
495
+ const indices = new Map<number, number>();
496
+ let hasStableSourceIdentity = false;
497
+ for (const [compactIndex, draw] of draws.entries()) {
498
+ const drawItemIndex = draw.drawItemIndex;
499
+ if (drawItemIndex === undefined) continue;
500
+ hasStableSourceIdentity = true;
501
+ if (mesh.submeshes[drawItemIndex] !== undefined) indices.set(drawItemIndex, compactIndex);
502
+ }
503
+ if (hasStableSourceIdentity) return indices;
504
+
505
+ // Legacy snapshots predating drawItemIndex are matched by their compact
506
+ // geometry facts. New extracted snapshots never take this path.
507
+ let nonIndexedFirst = 0;
508
+ let drawCursor = 0;
509
+ for (const [submeshIndex, submesh] of mesh.submeshes.entries()) {
510
+ const first = mesh.indexed ? submesh.indexOffset : nonIndexedFirst;
511
+ const count = mesh.indexed ? submesh.indexCount : submesh.vertexCount;
512
+ if (!mesh.indexed) nonIndexedFirst += submesh.vertexCount;
513
+ const draw = draws[drawCursor];
514
+ if (
515
+ draw !== undefined &&
516
+ draw.first === first &&
517
+ draw.count === count &&
518
+ draw.materialSlot === submesh.materialSlot &&
519
+ draw.topology === submesh.topology
520
+ ) {
521
+ indices.set(submeshIndex, drawCursor);
522
+ drawCursor += 1;
523
+ }
524
+ }
525
+ return indices;
526
+ }
527
+
528
+ function isGpuShadowClaimedSubmesh(
529
+ source: RenderableSnapshot,
530
+ renderableIndex: number,
531
+ submeshIndex: number,
532
+ drawIndices: ReadonlyMap<number, number>,
533
+ gpuDrivenShadowDrawKeys: ReadonlySet<string> | undefined,
534
+ shadowDispatchByRenderableIdx: ShadowDispatchMap,
535
+ shadowCasterMembership: readonly ShadowCasterMembership[] | undefined,
536
+ shadowCasterWorldKeys: readonly number[] | undefined,
537
+ ): boolean {
538
+ if (gpuDrivenShadowDrawKeys === undefined) return false;
539
+ const drawItemIndex = drawIndices.get(submeshIndex);
540
+ const draw = drawItemIndex === undefined ? undefined : source.gpuDrivenDraws?.[drawItemIndex];
541
+ if (drawItemIndex === undefined || draw === undefined) return false;
542
+ const material = source.materials[draw.materialSlot] ?? source.material;
543
+ const worldEntity = worldEntityKey(
544
+ shadowCasterWorldKeys?.[source.worldId] ?? source.worldId,
545
+ source.entityKey,
546
+ );
547
+ const sourceDrawItemIndex = draw.drawItemIndex ?? drawItemIndex;
548
+ if (shadowCasterMembership !== undefined) {
549
+ const matching = shadowCasterMembership.filter(
550
+ (membership) =>
551
+ membership.worldEntity === worldEntity &&
552
+ membership.materialHandle === (material.materialHandle ?? -1) &&
553
+ membership.drawItemIndex === sourceDrawItemIndex,
554
+ );
555
+ if (
556
+ matching.length === 0 ||
557
+ matching.some((membership) => membership.cpuReason !== undefined)
558
+ ) {
559
+ return false;
560
+ }
561
+ return matching.every((membership) =>
562
+ gpuDrivenShadowDrawKeys.has(
563
+ gpuDrivenShadowDrawKey(
564
+ membership.worldEntity,
565
+ membership.materialHandle,
566
+ membership.drawItemIndex,
567
+ membership.passIndex,
568
+ ),
569
+ ),
570
+ );
571
+ }
572
+ const dispatches =
573
+ shadowDispatchByRenderableIdx.get(renderableIndex)?.get(material.materialHandle ?? 0) ?? [];
574
+ if (dispatches.length > 0) {
575
+ return dispatches.every((dispatch) =>
576
+ gpuDrivenShadowDrawKeys.has(
577
+ gpuDrivenShadowDrawKey(
578
+ worldEntity,
579
+ material.materialHandle ?? -1,
580
+ draw.drawItemIndex ?? drawItemIndex,
581
+ dispatch.passIndex,
582
+ ),
583
+ ),
584
+ );
585
+ }
586
+ const base = gpuDrivenShadowDrawKey(
587
+ worldEntity,
588
+ material.materialHandle ?? -1,
589
+ sourceDrawItemIndex,
590
+ 0,
591
+ ).split(':');
592
+ return [...gpuDrivenShadowDrawKeys].some((key) => {
593
+ const parts = key.split(':');
594
+ return (
595
+ parts.length === 4 && parts[0] === base[0] && parts[1] === base[1] && parts[2] === base[2]
596
+ );
597
+ });
598
+ }
599
+
600
+ interface GpuDrivenShadowRecordResult {
601
+ readonly claimedKeys: ReadonlySet<string>;
602
+ }
603
+
604
+ function shadowCasterKeys(c: _InternalRenderPipelineContext): ReadonlySet<string> {
605
+ if (c.shadowCasterMembership !== undefined) {
606
+ const keys = new Set<string>();
607
+ for (const membership of c.shadowCasterMembership) {
608
+ keys.add(
609
+ gpuDrivenShadowDrawKey(
610
+ membership.worldEntity,
611
+ membership.materialHandle,
612
+ membership.drawItemIndex,
613
+ membership.passIndex,
614
+ ),
615
+ );
616
+ }
617
+ return keys;
618
+ }
619
+ return c.gpuDrivenShadowDrawKeys ?? new Set<string>();
620
+ }
621
+
622
+ function validateShadowOwnership(
623
+ c: _InternalRenderPipelineContext,
624
+ identity: ShadowViewIdentity,
625
+ claimedKeys: ReadonlySet<string> | undefined,
626
+ recordedKeys: ReadonlySet<string>,
627
+ ): void {
628
+ if (
629
+ c.shadowCasterMembership === undefined &&
630
+ (c.gpuDrivenShadowDrawKeys === undefined || c.gpuDrivenShadowDrawKeys.size === 0)
631
+ )
632
+ return;
633
+ const expectedKeys = shadowCasterKeys(c);
634
+ const claimed = claimedKeys ?? new Set<string>();
635
+ const overlap = [...claimed].filter((key) => recordedKeys.has(key));
636
+ const union = new Set([...claimed, ...recordedKeys]);
637
+ const missing = [...expectedKeys].filter((key) => !union.has(key));
638
+ const unexpectedClaims = [...claimed].filter((key) => !expectedKeys.has(key));
639
+ // `shadowCasterMembership` describes the concrete draws that the GPU
640
+ // producer is eligible to claim. A renderable that stays on the CPU lane
641
+ // (for example a clustered frame without a compatible GPU-driven PBR
642
+ // variant, or a dynamic mesh whose producer receipt is not ready) still
643
+ // records a valid ShadowCaster draw, but it has no membership row. Treating
644
+ // every CPU-only key as an "extra" made the ownership check reject that
645
+ // legitimate residual path and stopped the whole shadow pass. Ownership
646
+ // needs two invariants here: a draw is never both GPU-claimed and CPU-
647
+ // recorded, and every GPU-eligible membership is covered by one of those
648
+ // lanes. CPU-only residuals are already owned by the record path.
649
+ if (overlap.length === 0 && missing.length === 0 && unexpectedClaims.length === 0) return;
650
+ throw new RhiError({
651
+ code: 'internal-error',
652
+ expected: `disjoint ShadowCaster ownership for ${shadowViewIdentityKey(identity)}`,
653
+ hint: `claimed=${[...claimed].sort().join(',')} recorded=${[...recordedKeys].sort().join(',')} missing=${missing.sort().join(',')} unexpectedClaims=${unexpectedClaims.sort().join(',')}`,
654
+ });
655
+ }
656
+
657
+ function hasUnclaimedShadowCasters(
658
+ c: _InternalRenderPipelineContext,
659
+ claimedKeys: ReadonlySet<string> | undefined,
660
+ ): boolean {
661
+ const extractedKeys =
662
+ c.shadowCasterMembership === undefined
663
+ ? [...(c.gpuDrivenShadowDrawKeys ?? [])]
664
+ : c.shadowCasterMembership.map((membership) =>
665
+ gpuDrivenShadowDrawKey(
666
+ membership.worldEntity,
667
+ membership.materialHandle,
668
+ membership.drawItemIndex,
669
+ membership.passIndex,
670
+ ),
671
+ );
672
+ if (extractedKeys.length === 0) return false;
673
+ return claimedKeys === undefined || extractedKeys.some((key) => !claimedKeys.has(key));
674
+ }
675
+
676
+ function hasExpectedShadowCasters(c: _InternalRenderPipelineContext): boolean {
677
+ if ((c.shadowCasterMembership?.length ?? 0) > 0) return true;
678
+ if ((c.gpuDrivenShadowDrawKeys?.size ?? 0) > 0) return true;
679
+ return shadowDispatchEntries(c).some((entry) => entry.tags.LightMode === 'ShadowCaster');
680
+ }
681
+
682
+ function recordGpuDrivenShadowIfPublished(
683
+ c: _InternalRenderPipelineContext,
684
+ pass: RhiRenderPassEncoder,
685
+ identity: ShadowViewIdentity,
686
+ ): GpuDrivenShadowRecordResult | undefined {
687
+ const viewSubmission = c.gpuDrivenShadowViews?.submission(identity);
688
+ const projections = c.gpuDrivenShadowBatchProjections?.get(shadowViewIdentityKey(identity));
689
+ if (viewSubmission === undefined) {
690
+ return undefined;
691
+ }
692
+ if (projections === undefined) {
693
+ throw new RhiError({
694
+ code: 'rhi-not-available',
695
+ expected: `GPU-driven shadow projection for ${shadowViewIdentityKey(identity)}`,
696
+ hint: 'terminate the capable shadow frame instead of falling through to CPU caster enumeration',
697
+ });
698
+ }
699
+ const batches = new Map<number, GpuDrivenShadowBatch>();
700
+ for (const batch of viewSubmission.plan.batches) {
701
+ const projection: GpuDrivenShadowBatchProjection | undefined = projections.get(batch.batchId);
702
+ if (projection === undefined) {
703
+ throw new RhiError({
704
+ code: 'internal-error',
705
+ expected: `projected mesh binding for GPU-driven shadow batch ${batch.batchId}`,
706
+ hint: 'keep shadow batch scene rows aligned with the shared SubmissionPlan',
707
+ });
708
+ }
709
+ const shadowArtifact = projection.shadowArtifact;
710
+ const shadowReceipt = shadowArtifact.receipt;
711
+ if (shadowReceipt === undefined) {
712
+ throw new RhiError({
713
+ code: 'rhi-not-available',
714
+ expected: `published shadow MaterialProgramAbi for batch ${batch.batchId}`,
715
+ hint: 'keep the shadow draw out of the capable lane until its producer receipt is ready',
716
+ });
717
+ }
718
+ const shadowVariantSet =
719
+ shadowArtifact.variantSet ??
720
+ (shadowArtifact.material === SHADOW_CASTER_SHADER_ID
721
+ ? shadowCasterVariantSet(
722
+ c.runtime.device.caps.storageBuffer,
723
+ projection.deformation === 'skin',
724
+ true,
725
+ batch.key.admission === 'alpha-mask',
726
+ projection.vertexColorAvailable,
727
+ )
728
+ : undefined);
729
+ const pipeline = c.runtime.getMaterialShaderPipeline?.(
730
+ // A scene-index shadow artifact may retain the logical material id for
731
+ // batching while its specialization key owns the actual WGSL module.
732
+ shadowArtifact.specializationKey ?? shadowArtifact.material,
733
+ false,
734
+ shadowRasterState(undefined, identity.kind === 'point'),
735
+ batch.key.topology,
736
+ batch.key.drawKind === 'indexed' ? projection.mesh.indexFormat : undefined,
737
+ shadowVariantSet,
738
+ 'shadow-caster',
739
+ undefined,
740
+ undefined,
741
+ undefined,
742
+ undefined,
743
+ undefined,
744
+ undefined,
745
+ projection.mesh.layoutProjection,
746
+ // The shadow projection carries the producer-validated receipt. Select
747
+ // the immediate module adapter so a newly loaded custom Surface cannot
748
+ // lose its first capable shadow draw to async pre-bake warm-up.
749
+ 'immediate',
750
+ projection.deformation === 'skin' ? 'gpu-driven-skin' : 'gpu-driven-pbr',
751
+ shadowReceipt.sceneIndexEntry,
752
+ undefined,
753
+ projection.shadowVertexEntry,
754
+ projection.shadowFragmentEntry,
755
+ );
756
+ if (pipeline === null || pipeline === undefined) {
757
+ throw new RhiError({
758
+ code: 'rhi-not-available',
759
+ expected: `GPU-driven shadow pipeline variant for batch ${batch.batchId}`,
760
+ hint: 'publish the exact scene-index shadow variant before entering the capable lane',
761
+ });
762
+ }
763
+ if (batch.key.drawKind === 'indexed' && projection.mesh.indexBuffer === null) {
764
+ throw new RhiError({
765
+ code: 'internal-error',
766
+ expected: `indexed mesh buffer for GPU-driven shadow batch ${batch.batchId}`,
767
+ hint: 'keep the geometry residency and topology draw kind aligned before capable recording',
768
+ });
769
+ }
770
+ const materialGroup =
771
+ shadowArtifact.material !== SHADOW_CASTER_SHADER_ID
772
+ ? ensureGpuDrivenCustomShadowMaterialBg(c, projection)
773
+ : batch.key.admission === 'alpha-mask'
774
+ ? ensureGpuDrivenAlphaMaskMaterialBg(c, projection)
775
+ : ensureSpotShadowMaterialBg(c);
776
+ if (materialGroup === null) {
777
+ throw new RhiError({
778
+ code: 'rhi-not-available',
779
+ expected: 'material resources for every GPU shadow batch',
780
+ hint: 'publish the shadow material binding before recording the indirect batch',
781
+ });
782
+ }
783
+ batches.set(batch.batchId, {
784
+ pipeline,
785
+ meshBindGroup: projection.meshBindGroup,
786
+ visibleBindGroup: projection.visibleBindGroup,
787
+ deformation: projection.deformation,
788
+ materialGroup,
789
+ vertexBuffer: projection.mesh.vertexBuffer.handle,
790
+ ...(batch.key.drawKind === 'indexed' && projection.mesh.indexBuffer !== null
791
+ ? {
792
+ indexBuffer: projection.mesh.indexBuffer.handle,
793
+ indexFormat: projection.mesh.indexFormat,
794
+ }
795
+ : {}),
796
+ });
797
+ }
798
+ const submission: GpuDrivenShadowSubmission = {
799
+ view: viewSubmission.view,
800
+ plan: viewSubmission.plan,
801
+ batches,
802
+ };
803
+ recordGpuDrivenShadowIndirect(pass, submission);
804
+ const viewKey = shadowViewIdentityKey(identity);
805
+ const viewClaims = c.gpuDrivenShadowDrawKeysByView;
806
+ return {
807
+ claimedKeys:
808
+ viewClaims === undefined
809
+ ? (c.gpuDrivenShadowDrawKeys ?? new Set<string>())
810
+ : (viewClaims.get(viewKey) ?? new Set<string>()),
811
+ };
812
+ }
813
+
366
814
  export function encodeDirectionalShadowPass(
367
815
  c: _InternalRenderPipelineContext,
368
816
  pass: RhiRenderPassEncoder,
369
817
  cascadeIndex: number,
370
818
  viewport: { readonly x: number; readonly y: number; readonly w: number; readonly h: number },
819
+ drawFeatures?: (view: BindGroup) => void,
371
820
  ): void {
372
- if (c.directionalShadowCacheReuse || c.meshBindGroup === null) return;
821
+ const directionalIdentity = { kind: 'directional' as const, index: cascadeIndex };
822
+ if (
823
+ (c.gpuDrivenShadowViews?.cacheHit(directionalIdentity) === true ||
824
+ (c.gpuDrivenShadowViews === undefined && c.directionalShadowCacheReuse)) &&
825
+ drawFeatures === undefined
826
+ )
827
+ return;
373
828
  const pipeline = shadowPipeline(c);
374
829
  const viewBg = ensureTypedShadowViewBg(
375
830
  c,
@@ -377,25 +832,94 @@ export function encodeDirectionalShadowPass(
377
832
  directionalShadowCasterOffset(cascadeIndex),
378
833
  `view-shadow-directional-${cascadeIndex}`,
379
834
  );
380
- const materialBg = ensureSpotShadowMaterialBg(c);
381
- if (pipeline === null || viewBg === null || materialBg === null) {
835
+ const materialBg = viewBg === null ? null : ensureSpotShadowMaterialBg(c);
836
+ const capable = c.gpuDrivenShadowViews?.submission(directionalIdentity) !== undefined;
837
+ if (viewBg === null || materialBg === null) {
382
838
  c.frameState.directionalShadowCacheRecorded = false;
839
+ if (capable || hasExpectedShadowCasters(c)) {
840
+ throw new RhiError({
841
+ code: 'rhi-not-available',
842
+ expected: 'directional ShadowCaster bind groups',
843
+ hint: 'publish the view/material resources before recording the shadow lane',
844
+ });
845
+ }
383
846
  return;
384
847
  }
848
+ c.frameState.directionalShadowCacheRecorded = true;
385
849
  pass.setViewport(viewport.x, viewport.y, viewport.w, viewport.h, 0, 1);
386
- pass.setPipeline(pipeline);
850
+ drawFeatures?.(viewBg);
387
851
  pass.setBindGroup(0, viewBg, [0, 0]);
388
852
  pass.setBindGroup(1, materialBg, [0]);
389
- const complete = recordShadowCasterDraws(
853
+ const gpuRecord = recordGpuDrivenShadowIfPublished(c, pass, directionalIdentity);
854
+ const gpuRecorded = gpuRecord !== undefined;
855
+ const claimedKeys = gpuRecord?.claimedKeys;
856
+ if (capable && !gpuRecorded) {
857
+ throw new RhiError({
858
+ code: 'rhi-not-available',
859
+ expected: 'capable directional GPU-driven shadow projection',
860
+ hint: 'terminate the capable shadow frame instead of enumerating CPU casters',
861
+ });
862
+ }
863
+ if (!hasUnclaimedShadowCasters(c, claimedKeys)) {
864
+ if (gpuRecorded || (!capable && !hasExpectedShadowCasters(c))) {
865
+ validateShadowOwnership(c, directionalIdentity, claimedKeys, new Set());
866
+ return;
867
+ }
868
+ }
869
+ if (pipeline === null) {
870
+ if (gpuRecorded && !hasUnclaimedShadowCasters(c, claimedKeys)) {
871
+ validateShadowOwnership(c, directionalIdentity, claimedKeys, new Set());
872
+ return;
873
+ }
874
+ if (gpuRecorded) {
875
+ throw new RhiError({
876
+ code: 'rhi-not-available',
877
+ expected: 'CPU residual directional shadow pipeline',
878
+ hint: 'publish the legacy residual caster pipeline alongside the partial GPU shadow lane',
879
+ });
880
+ }
881
+ throw new RhiError({
882
+ code: 'rhi-not-available',
883
+ expected: 'directional ShadowCaster pipeline',
884
+ hint: 'publish the CPU ShadowCaster pipeline before recording residual casters',
885
+ });
886
+ }
887
+ pass.setPipeline(pipeline);
888
+ const legacyMeshBindGroup = c.meshBindGroup;
889
+ if (legacyMeshBindGroup === null) {
890
+ if (gpuRecorded && !hasUnclaimedShadowCasters(c, claimedKeys)) {
891
+ validateShadowOwnership(c, directionalIdentity, claimedKeys, new Set());
892
+ return;
893
+ }
894
+ if (gpuRecorded) {
895
+ throw new RhiError({
896
+ code: 'rhi-not-available',
897
+ expected: 'CPU residual directional shadow mesh binding',
898
+ hint: 'publish legacy mesh resources for the unclaimed ShadowCaster residual',
899
+ });
900
+ }
901
+ throw new RhiError({
902
+ code: 'rhi-not-available',
903
+ expected: 'directional ShadowCaster mesh binding',
904
+ hint: 'publish the CPU mesh binding before recording residual casters',
905
+ });
906
+ }
907
+ if (gpuRecorded) pass.setBindGroup(1, materialBg, [0]);
908
+ const recordedKeys = recordShadowCasterDraws(
390
909
  c,
391
910
  pass,
392
911
  pipeline,
393
- c.meshBindGroup,
394
- buildMatchedRenderableIndices(c.dispatch, { LightMode: ['ShadowCaster'] }),
912
+ legacyMeshBindGroup,
913
+ buildMatchedRenderableIndices(shadowDispatchEntries(c), { LightMode: ['ShadowCaster'] }),
914
+ buildMatchedMaterialHandlesByRenderable(shadowDispatchEntries(c), {
915
+ LightMode: ['ShadowCaster'],
916
+ }),
395
917
  shadowShaderMap(c),
918
+ claimedKeys,
396
919
  );
920
+ validateShadowOwnership(c, directionalIdentity, claimedKeys, recordedKeys);
397
921
  c.frameState.directionalShadowCacheRecorded =
398
- cascadeIndex === 0 ? complete : c.frameState.directionalShadowCacheRecorded && complete;
922
+ cascadeIndex === 0 ? true : c.frameState.directionalShadowCacheRecorded;
399
923
  }
400
924
 
401
925
  export function encodePointShadowPass(
@@ -403,35 +927,110 @@ export function encodePointShadowPass(
403
927
  pass: RhiRenderPassEncoder,
404
928
  snapshotIndex: number,
405
929
  face: number,
930
+ drawFeatures?: (view: BindGroup) => void,
406
931
  ): void {
407
932
  const snapshot = c.frameState.pointShadowSnapshots[snapshotIndex];
408
- if (snapshot === undefined || c.meshBindGroup === null) return;
409
- const pipeline = shadowPipeline(c);
933
+ if (snapshot === undefined) return;
934
+ const pipeline = shadowPipeline(c, true);
410
935
  const viewBg = ensureTypedShadowViewBg(
411
936
  c,
412
937
  pointShadowViewOffset(snapshot.shadowAtlasLayer, face),
413
938
  0,
414
939
  `view-shadow-point-${snapshot.shadowAtlasLayer}-${face}`,
415
940
  );
416
- const materialBg = ensureSpotShadowMaterialBg(c);
417
- if (pipeline === null || viewBg === null || materialBg === null) return;
418
- pass.setPipeline(pipeline);
941
+ const materialBg = viewBg === null ? null : ensureSpotShadowMaterialBg(c);
942
+ const pointIdentity = { kind: 'point' as const, index: snapshotIndex, face };
943
+ const capable = c.gpuDrivenShadowViews?.submission(pointIdentity) !== undefined;
944
+ if (viewBg === null || materialBg === null) {
945
+ if (capable || hasExpectedShadowCasters(c))
946
+ throw new RhiError({
947
+ code: 'rhi-not-available',
948
+ expected: 'point ShadowCaster bind groups',
949
+ hint: 'publish the view/material resources before recording the shadow lane',
950
+ });
951
+ return;
952
+ }
953
+ if (pipeline === null && !capable && !hasExpectedShadowCasters(c) && drawFeatures === undefined)
954
+ return;
955
+ drawFeatures?.(viewBg);
419
956
  pass.setBindGroup(0, viewBg, [0, 0]);
420
957
  pass.setBindGroup(1, materialBg, [0]);
421
- recordShadowCasterDraws(
958
+ const gpuRecord = recordGpuDrivenShadowIfPublished(c, pass, pointIdentity);
959
+ const gpuRecorded = gpuRecord !== undefined;
960
+ const claimedKeys = gpuRecord?.claimedKeys;
961
+ if (capable && !gpuRecorded) {
962
+ throw new RhiError({
963
+ code: 'rhi-not-available',
964
+ expected: 'capable point GPU-driven shadow projection',
965
+ hint: 'terminate the capable shadow frame instead of enumerating CPU casters',
966
+ });
967
+ }
968
+ if (!hasUnclaimedShadowCasters(c, claimedKeys)) {
969
+ if (gpuRecorded || (!capable && !hasExpectedShadowCasters(c))) {
970
+ validateShadowOwnership(c, pointIdentity, claimedKeys, new Set());
971
+ return;
972
+ }
973
+ }
974
+ if (pipeline === null) {
975
+ if (gpuRecorded && !hasUnclaimedShadowCasters(c, claimedKeys)) {
976
+ validateShadowOwnership(c, pointIdentity, claimedKeys, new Set());
977
+ return;
978
+ }
979
+ if (gpuRecorded) {
980
+ throw new RhiError({
981
+ code: 'rhi-not-available',
982
+ expected: 'CPU residual point shadow pipeline',
983
+ hint: 'publish the legacy residual caster pipeline alongside the partial GPU shadow lane',
984
+ });
985
+ }
986
+ throw new RhiError({
987
+ code: 'rhi-not-available',
988
+ expected: 'point ShadowCaster pipeline',
989
+ hint: 'publish the CPU ShadowCaster pipeline before recording residual casters',
990
+ });
991
+ }
992
+ pass.setPipeline(pipeline);
993
+ const legacyMeshBindGroup = c.meshBindGroup;
994
+ if (legacyMeshBindGroup === null) {
995
+ if (gpuRecorded && !hasUnclaimedShadowCasters(c, claimedKeys)) {
996
+ validateShadowOwnership(c, pointIdentity, claimedKeys, new Set());
997
+ return;
998
+ }
999
+ if (gpuRecorded) {
1000
+ throw new RhiError({
1001
+ code: 'rhi-not-available',
1002
+ expected: 'CPU residual point shadow mesh binding',
1003
+ hint: 'publish legacy mesh resources for the unclaimed ShadowCaster residual',
1004
+ });
1005
+ }
1006
+ throw new RhiError({
1007
+ code: 'rhi-not-available',
1008
+ expected: 'point ShadowCaster mesh binding',
1009
+ hint: 'publish the CPU mesh binding before recording residual casters',
1010
+ });
1011
+ }
1012
+ if (gpuRecorded) pass.setBindGroup(1, materialBg, [0]);
1013
+ const recordedKeys = recordShadowCasterDraws(
422
1014
  c,
423
1015
  pass,
424
1016
  pipeline,
425
- c.meshBindGroup,
426
- buildMatchedRenderableIndices(c.dispatch, { LightMode: ['ShadowCaster'] }),
1017
+ legacyMeshBindGroup,
1018
+ buildMatchedRenderableIndices(shadowDispatchEntries(c), { LightMode: ['ShadowCaster'] }),
1019
+ buildMatchedMaterialHandlesByRenderable(shadowDispatchEntries(c), {
1020
+ LightMode: ['ShadowCaster'],
1021
+ }),
427
1022
  shadowShaderMap(c),
1023
+ claimedKeys,
1024
+ true,
428
1025
  );
1026
+ validateShadowOwnership(c, pointIdentity, claimedKeys, recordedKeys);
429
1027
  }
430
1028
 
431
1029
  export function encodeSpotShadowPass(
432
1030
  c: _InternalRenderPipelineContext,
433
1031
  pass: RhiRenderPassEncoder,
434
1032
  snapshotIndex: number,
1033
+ drawFeatures?: (view: BindGroup) => void,
435
1034
  ): void {
436
1035
  const snapshot = c.frameState.spotShadowSnapshots.filter(
437
1036
  (candidate) => candidate.shadowAtlasTile >= 0 && candidate.lightViewProj !== undefined,
@@ -439,8 +1038,7 @@ export function encodeSpotShadowPass(
439
1038
  if (
440
1039
  snapshot === undefined ||
441
1040
  snapshot.shadowAtlasTile < 0 ||
442
- snapshot.lightViewProj === undefined ||
443
- c.meshBindGroup === null
1041
+ snapshot.lightViewProj === undefined
444
1042
  ) {
445
1043
  return;
446
1044
  }
@@ -451,8 +1049,20 @@ export function encodeSpotShadowPass(
451
1049
  spotShadowCasterOffset(snapshot.shadowAtlasTile),
452
1050
  `view-shadow-spot-${snapshot.shadowAtlasTile}`,
453
1051
  );
454
- const materialBg = ensureSpotShadowMaterialBg(c);
455
- if (pipeline === null || viewBg === null || materialBg === null) return;
1052
+ const materialBg = viewBg === null ? null : ensureSpotShadowMaterialBg(c);
1053
+ const spotIdentity = { kind: 'spot' as const, index: snapshotIndex };
1054
+ const capable = c.gpuDrivenShadowViews?.submission(spotIdentity) !== undefined;
1055
+ if (viewBg === null || materialBg === null) {
1056
+ if (capable || hasExpectedShadowCasters(c))
1057
+ throw new RhiError({
1058
+ code: 'rhi-not-available',
1059
+ expected: 'spot ShadowCaster bind groups',
1060
+ hint: 'publish the view/material resources before recording the shadow lane',
1061
+ });
1062
+ return;
1063
+ }
1064
+ if (pipeline === null && !capable && !hasExpectedShadowCasters(c) && drawFeatures === undefined)
1065
+ return;
456
1066
  const tileSize = c.pipelineState.perPassResources.shadowMapSize;
457
1067
  const tile = snapshot.shadowAtlasTile;
458
1068
  pass.setViewport(
@@ -463,21 +1073,81 @@ export function encodeSpotShadowPass(
463
1073
  0,
464
1074
  1,
465
1075
  );
466
- pass.setPipeline(pipeline);
1076
+ drawFeatures?.(viewBg);
467
1077
  pass.setBindGroup(0, viewBg, [0, 0]);
468
1078
  pass.setBindGroup(1, materialBg, [0]);
469
1079
  // Reuse the shared caster recorder. The compact spot-only loop used to
470
1080
  // depend on instance bindings created by a directional pass, so a scene
471
1081
  // containing only a SpotLight opened the atlas pass but emitted no caster
472
1082
  // draws.
473
- recordShadowCasterDraws(
1083
+ const gpuRecord = recordGpuDrivenShadowIfPublished(c, pass, spotIdentity);
1084
+ const gpuRecorded = gpuRecord !== undefined;
1085
+ const claimedKeys = gpuRecord?.claimedKeys;
1086
+ if (capable && !gpuRecorded) {
1087
+ throw new RhiError({
1088
+ code: 'rhi-not-available',
1089
+ expected: 'capable spot GPU-driven shadow projection',
1090
+ hint: 'terminate the capable shadow frame instead of enumerating CPU casters',
1091
+ });
1092
+ }
1093
+ if (!hasUnclaimedShadowCasters(c, claimedKeys)) {
1094
+ if (gpuRecorded || (!capable && !hasExpectedShadowCasters(c))) {
1095
+ validateShadowOwnership(c, spotIdentity, claimedKeys, new Set());
1096
+ return;
1097
+ }
1098
+ }
1099
+ if (pipeline === null) {
1100
+ if (gpuRecorded && !hasUnclaimedShadowCasters(c, claimedKeys)) {
1101
+ validateShadowOwnership(c, spotIdentity, claimedKeys, new Set());
1102
+ return;
1103
+ }
1104
+ if (gpuRecorded) {
1105
+ throw new RhiError({
1106
+ code: 'rhi-not-available',
1107
+ expected: 'CPU residual spot shadow pipeline',
1108
+ hint: 'publish the legacy residual caster pipeline alongside the partial GPU shadow lane',
1109
+ });
1110
+ }
1111
+ throw new RhiError({
1112
+ code: 'rhi-not-available',
1113
+ expected: 'spot ShadowCaster pipeline',
1114
+ hint: 'publish the CPU ShadowCaster pipeline before recording residual casters',
1115
+ });
1116
+ }
1117
+ pass.setPipeline(pipeline);
1118
+ const legacyMeshBindGroup = c.meshBindGroup;
1119
+ if (legacyMeshBindGroup === null) {
1120
+ if (gpuRecorded && !hasUnclaimedShadowCasters(c, claimedKeys)) {
1121
+ validateShadowOwnership(c, spotIdentity, claimedKeys, new Set());
1122
+ return;
1123
+ }
1124
+ if (gpuRecorded) {
1125
+ throw new RhiError({
1126
+ code: 'rhi-not-available',
1127
+ expected: 'CPU residual spot shadow mesh binding',
1128
+ hint: 'publish legacy mesh resources for the unclaimed ShadowCaster residual',
1129
+ });
1130
+ }
1131
+ throw new RhiError({
1132
+ code: 'rhi-not-available',
1133
+ expected: 'spot ShadowCaster mesh binding',
1134
+ hint: 'publish the CPU mesh binding before recording residual casters',
1135
+ });
1136
+ }
1137
+ if (gpuRecorded) pass.setBindGroup(1, materialBg, [0]);
1138
+ const recordedKeys = recordShadowCasterDraws(
474
1139
  c,
475
1140
  pass,
476
1141
  pipeline,
477
- c.meshBindGroup,
478
- buildMatchedRenderableIndices(c.dispatch, { LightMode: ['ShadowCaster'] }),
1142
+ legacyMeshBindGroup,
1143
+ buildMatchedRenderableIndices(shadowDispatchEntries(c), { LightMode: ['ShadowCaster'] }),
1144
+ buildMatchedMaterialHandlesByRenderable(shadowDispatchEntries(c), {
1145
+ LightMode: ['ShadowCaster'],
1146
+ }),
479
1147
  shadowShaderMap(c),
1148
+ claimedKeys,
480
1149
  );
1150
+ validateShadowOwnership(c, spotIdentity, claimedKeys, recordedKeys);
481
1151
  }
482
1152
 
483
1153
  /**
@@ -541,16 +1211,41 @@ export function buildMatchedRenderableIndices(
541
1211
  * filtering is insufficient for geometry recording: a Deferred graph pass
542
1212
  * must not draw the same renderable's Forward-only material. Keep the
543
1213
  * renderable key in the result so mixed-material meshes retain their
544
- * per-submesh routing.
1214
+ * per-submesh routing. An optional exclusion selector removes a material
1215
+ * handle when that same renderable/material pair also contributes to another
1216
+ * graph lane (for example Deferred Standard geometry after SSR composition).
1217
+ * An inclusion selector intersects membership by the same renderable/material
1218
+ * pair without replacing the selected shader pass or its authored entries.
545
1219
  */
546
1220
  export function buildMatchedMaterialHandlesByRenderable(
547
1221
  dispatch: readonly DispatchEntry[],
548
1222
  selector: PassSelector,
1223
+ excludeSelector?: PassSelector,
1224
+ includeSelector?: PassSelector,
549
1225
  ): ReadonlyMap<number, ReadonlySet<number>> | null {
550
1226
  if (dispatch.length === 0) return null;
551
1227
  const matched = filterDispatchBySelector(dispatch, selector);
1228
+ const included =
1229
+ includeSelector === undefined
1230
+ ? undefined
1231
+ : new Set(
1232
+ filterDispatchBySelector(dispatch, includeSelector).map(
1233
+ (entry) => `${entry.renderableIndex}:${entry.materialHandle}`,
1234
+ ),
1235
+ );
1236
+ const excluded =
1237
+ excludeSelector === undefined
1238
+ ? undefined
1239
+ : new Set(
1240
+ filterDispatchBySelector(dispatch, excludeSelector).map(
1241
+ (entry) => `${entry.renderableIndex}:${entry.materialHandle}`,
1242
+ ),
1243
+ );
552
1244
  const handlesByRenderable = new Map<number, Set<number>>();
553
1245
  for (const entry of matched) {
1246
+ if (included !== undefined && !included.has(`${entry.renderableIndex}:${entry.materialHandle}`))
1247
+ continue;
1248
+ if (excluded?.has(`${entry.renderableIndex}:${entry.materialHandle}`)) continue;
554
1249
  const handles = handlesByRenderable.get(entry.renderableIndex);
555
1250
  if (handles === undefined) {
556
1251
  handlesByRenderable.set(entry.renderableIndex, new Set([entry.materialHandle]));
@@ -569,25 +1264,36 @@ export function buildMatchedMaterialHandlesByRenderable(
569
1264
  * and issues the per-submesh depth draws. `shadowPass` view/material bind
570
1265
  * groups (@group 0/1) are already set by the caller; this loop owns @group
571
1266
  * 2/3 + the vertex/index/pipeline de-dup state. Receives the explicit
572
- * `_InternalRenderPipelineContext` (`c`) plus the caller-resolved shadow
573
- * pipeline, mesh bind group, pass-selector match set, and per-material
1267
+ * `_InternalRenderPipelineContext` (`c`) plus the caller-resolved
1268
+ * mesh bind group, pass-selector match set, and per-material
574
1269
  * ShadowCaster dispatch map so no cross-function mutable state is introduced.
575
1270
  */
576
- function recordShadowCasterDraws(
1271
+ export function recordShadowCasterDraws(
577
1272
  c: _InternalRenderPipelineContext,
578
1273
  shadowPass: RhiRenderPassEncoder,
579
1274
  shadowPipeline: RenderPipeline,
580
1275
  shadowMeshBindGroup: BindGroup,
581
1276
  matchedIndices: Set<number> | null,
1277
+ matchedMaterialHandles: ReadonlyMap<number, ReadonlySet<number>> | null,
582
1278
  shadowDispatchByRenderableIdx: ShadowDispatchMap,
583
- ): boolean {
584
- const { runtime, pipelineState, validatedOrdered } = c;
1279
+ gpuDrivenShadowDrawKeys: ReadonlySet<string> | undefined,
1280
+ reflected = false,
1281
+ ): ReadonlySet<string> {
1282
+ const {
1283
+ runtime,
1284
+ pipelineState,
1285
+ validatedOrdered: mainValidatedOrdered,
1286
+ shadowValidatedOrdered,
1287
+ } = c;
1288
+ const validatedOrdered = shadowValidatedOrdered ?? mainValidatedOrdered;
1289
+ const meshSsboBase = c.shadowMeshSsboBase ?? 0;
585
1290
  // M-3 / w12: vertexBuffer/indexBuffer state locals migrate to GpuBuffer
586
1291
  // (the wrapper) -- the de-dup compare uses wrapper identity (one wrapper
587
1292
  // per RHI handle from gpuStore), and `.handle` is passed to the RHI
588
1293
  // setVertexBuffer / setIndexBuffer call.
589
1294
  let shadowLastVertexBuffer: GpuBuffer | null = null;
590
1295
  let shadowLastIndexBuffer: GpuBuffer | null = null;
1296
+ const recordedKeys = new Set<string>();
591
1297
  // bug-20260619-csm RC-3 (D-3): track the currently-bound shadow PSO so
592
1298
  // per-entity setPipeline only fires on change (same de-dup discipline as
593
1299
  // vertex/index buffers above). The default-shadow-caster PSO is already
@@ -595,7 +1301,6 @@ function recordShadowCasterDraws(
595
1301
  // ShadowCaster PSO when a material supplies one.
596
1302
  let shadowLastPipeline: RenderPipeline = shadowPipeline;
597
1303
  let materialDeps: PerSubmeshMaterialBgDeps | undefined;
598
- let complete = true;
599
1304
 
600
1305
  for (let i = 0; i < validatedOrdered.length; i++) {
601
1306
  const entry = validatedOrdered[i];
@@ -604,17 +1309,30 @@ function recordShadowCasterDraws(
604
1309
  // feat-20260609 M2: skip entities that don't match the pass selector.
605
1310
  if (matchedIndices !== null && !matchedIndices.has(entry.renderableIndex)) continue;
606
1311
 
607
- if (entry.source.skin !== undefined) {
608
- const skinPipeline = skinnedShadowPipeline(c, entry);
609
- if (skinPipeline === null) {
610
- complete = false;
611
- continue;
1312
+ const drawIndices = shadowDrawIndicesBySubmesh(entry.source, entry.mesh);
1313
+ const shadowSubmeshes = entry.mesh.submeshes.flatMap((submesh, submeshIndex) => {
1314
+ if (submesh.topology !== 'triangle-list' && submesh.topology !== 'triangle-strip') {
1315
+ return [];
612
1316
  }
613
- if (skinPipeline !== shadowLastPipeline) {
614
- shadowPass.setPipeline(skinPipeline);
615
- shadowLastPipeline = skinPipeline;
1317
+ if (matchedMaterialHandles !== null) {
1318
+ const handles = matchedMaterialHandles.get(entry.renderableIndex);
1319
+ const material = entry.source.materials[submesh.materialSlot] ?? entry.source.material;
1320
+ if (handles === undefined || !handles.has(material.materialHandle ?? -1)) return [];
616
1321
  }
617
- }
1322
+ return isGpuShadowClaimedSubmesh(
1323
+ entry.source,
1324
+ entry.renderableIndex,
1325
+ submeshIndex,
1326
+ drawIndices,
1327
+ gpuDrivenShadowDrawKeys,
1328
+ shadowDispatchByRenderableIdx,
1329
+ c.shadowCasterMembership,
1330
+ c.shadowCasterWorldKeys,
1331
+ )
1332
+ ? []
1333
+ : [{ submesh, submeshIndex }];
1334
+ });
1335
+ if (shadowSubmeshes.length === 0) continue;
618
1336
  // feat-20260604-mesh-topology-debug-draw M5 / w14 (AC-09, D-A6): the
619
1337
  // shadow caster PSO is triangle-list; it only projects triangle faces.
620
1338
  // line-list / line-strip / point-list meshes have no surface to cast a
@@ -624,14 +1342,6 @@ function recordShadowCasterDraws(
624
1342
  //
625
1343
  // feat-20260608 M4 / w16: per-submesh shadow draw — iterate submeshes
626
1344
  // and skip non-triangle submeshes individually (each submesh may differ).
627
- const shadowSubmeshes = entry.mesh.submeshes;
628
- const hasAnyShadowSubmesh = shadowSubmeshes.some(
629
- (sm) => sm.topology === 'triangle-list' || sm.topology === 'triangle-strip',
630
- );
631
- if (!hasAnyShadowSubmesh) {
632
- continue;
633
- }
634
-
635
1345
  if (entry.mesh.vertexBuffer !== shadowLastVertexBuffer) {
636
1346
  shadowPass.setVertexBuffer(0, entry.mesh.vertexBuffer.handle);
637
1347
  shadowLastVertexBuffer = entry.mesh.vertexBuffer;
@@ -646,39 +1356,250 @@ function recordShadowCasterDraws(
646
1356
 
647
1357
  if (entry.source.skin !== undefined) {
648
1358
  const skinMeshBindGroup = skinnedShadowMeshBindGroup(c, entry);
649
- if (skinMeshBindGroup === null) continue;
1359
+ if (skinMeshBindGroup === null) {
1360
+ throw new RhiError({
1361
+ code: 'rhi-not-available',
1362
+ expected: 'CPU skinned ShadowCaster mesh binding',
1363
+ hint: 'publish the skin mesh/palette bind group before recording the residual',
1364
+ });
1365
+ }
650
1366
  shadowPass.setBindGroup(
651
1367
  2,
652
1368
  skinMeshBindGroup,
653
- pbrSkinMeshDynamicOffsets(i * MESH_PER_ENTITY_STRIDE, entry.source.skin.byteOffset),
1369
+ pbrSkinMeshDynamicOffsets(
1370
+ (meshSsboBase + i) * MESH_PER_ENTITY_STRIDE,
1371
+ entry.source.skin.byteOffset,
1372
+ ),
654
1373
  );
655
1374
  } else {
656
- shadowPass.setBindGroup(2, shadowMeshBindGroup, [i * MESH_PER_ENTITY_STRIDE]);
1375
+ shadowPass.setBindGroup(2, shadowMeshBindGroup, [
1376
+ (meshSsboBase + i) * MESH_PER_ENTITY_STRIDE,
1377
+ ]);
657
1378
  }
658
1379
 
659
- // Main and shadow passes share one instance residency owner and upload receipt.
660
- const shadowInstanceDraws = resolveGeometryInstanceBuffer(
661
- c,
662
- entry,
663
- [
664
- {
665
- instanceBuffer: pipelineState.identityInstanceBuffer,
666
- instanceBindGroup: resolveGeometryInstancesBindGroup(
667
- c,
668
- pipelineState.identityInstanceBuffer,
669
- ),
670
- instanceCount: 1,
671
- probeOffset: -1,
672
- },
673
- ],
674
- false,
675
- );
676
- if (shadowInstanceDraws === null) continue;
1380
+ // C1 + C2 (w12): per-entity instance buffer + instanceCount
1381
+ let shadowInstanceDraws: readonly { readonly buffer: Buffer; readonly count: number }[] = [
1382
+ { buffer: pipelineState.identityInstanceBuffer, count: 1 },
1383
+ ];
1384
+ const shadowInst = entry.source.instances;
1385
+ if (shadowInst?.instanceCount === 0) continue;
1386
+ if (shadowInst !== undefined) {
1387
+ const uniformFallback = runtime.device.caps.storageBuffer === false;
1388
+ // Uniform fallback uses resident chunks because the shader's uniform
1389
+ // array is capped at MAX_UNIFORM_INSTANCES entries.
1390
+ if (uniformFallback && shadowInst.instanceCount > MAX_UNIFORM_INSTANCES) {
1391
+ const ownerKey = instanceCollectionCacheKey(entry.source.worldId, shadowInst);
1392
+ const chunks = c.frameState.instanceBufferChunks;
1393
+ const activeKeys = new Set<string>();
1394
+ const draws: { buffer: Buffer; count: number }[] = [];
1395
+ const strideBytes = 16 * Float32Array.BYTES_PER_ELEMENT;
1396
+ const dirtyRanges = shadowInst.dirtyRanges ?? [{ start: 0, end: shadowInst.instanceCount }];
1397
+ const uploadedRanges: { start: number; end: number }[] = [];
1398
+ let uploadedBytes = 0;
1399
+ let failed = false;
1400
+ for (let start = 0; start < shadowInst.instanceCount; start += MAX_UNIFORM_INSTANCES) {
1401
+ const count = Math.min(MAX_UNIFORM_INSTANCES, shadowInst.instanceCount - start);
1402
+ const chunkKey = `${ownerKey}:${start}`;
1403
+ activeKeys.add(chunkKey);
1404
+ const payloadBytes = count * strideBytes;
1405
+ const previous = chunks?.get(chunkKey);
1406
+ let active =
1407
+ previous !== undefined &&
1408
+ previous.uploadedArchVersion === shadowInst.archVersion &&
1409
+ previous.uploadedByteLength === payloadBytes
1410
+ ? previous
1411
+ : undefined;
1412
+ let activeIsNew = false;
1413
+ if (active === undefined) {
1414
+ const created = runtime.device.createBuffer({
1415
+ size: INSTANCE_UBO_FULL_ARRAY_BYTES,
1416
+ usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
1417
+ mappedAtCreation: false,
1418
+ });
1419
+ if (!created.ok) {
1420
+ runtime.errorRegistry.fire(created.error);
1421
+ failed = true;
1422
+ break;
1423
+ }
1424
+ if (previous !== undefined && !previous.buffer.isDestroyed) {
1425
+ const destroyed = previous.buffer.destroy();
1426
+ if (!destroyed.ok) runtime.errorRegistry.fire(destroyed.error);
1427
+ }
1428
+ active = {
1429
+ buffer: new GpuBuffer(runtime.device, created.value),
1430
+ uploadedArchVersion: shadowInst.archVersion,
1431
+ uploadedByteLength: payloadBytes,
1432
+ };
1433
+ activeIsNew = true;
1434
+ chunks?.set(chunkKey, active);
1435
+ if (chunks === undefined) c.frameState.transientInstanceBuffers.push(active);
1436
+ }
1437
+ const localRanges = dirtyRanges
1438
+ .map((range) => ({
1439
+ start: Math.max(start, range.start) - start,
1440
+ end: Math.min(start + count, range.end) - start,
1441
+ }))
1442
+ .filter((range) => range.end > range.start);
1443
+ const rangesToUpload = instanceUploadRangesForResident(activeIsNew, localRanges, {
1444
+ start: 0,
1445
+ end: count,
1446
+ });
1447
+ const needsUpload =
1448
+ activeIsNew ||
1449
+ (shadowInst.revision !== undefined && active.uploadedRevision !== shadowInst.revision);
1450
+ if (needsUpload && rangesToUpload.length > 0) {
1451
+ for (const range of rangesToUpload) {
1452
+ const source = shadowInst.transforms.subarray(
1453
+ (start + range.start) * 16,
1454
+ (start + range.end) * 16,
1455
+ );
1456
+ const written = runtime.device.queue.writeBuffer(
1457
+ active.buffer.handle,
1458
+ range.start * strideBytes,
1459
+ source,
1460
+ );
1461
+ if (!written.ok) {
1462
+ runtime.errorRegistry.fire(written.error);
1463
+ failed = true;
1464
+ break;
1465
+ }
1466
+ uploadedRanges.push({ start: start + range.start, end: start + range.end });
1467
+ uploadedBytes += source.byteLength;
1468
+ }
1469
+ if (failed) break;
1470
+ }
1471
+ if (needsUpload && shadowInst.revision !== undefined) {
1472
+ active = { ...active, uploadedRevision: shadowInst.revision };
1473
+ chunks?.set(chunkKey, active);
1474
+ }
1475
+ draws.push({ buffer: active.buffer.handle, count });
1476
+ }
1477
+ shadowInstanceDraws = failed
1478
+ ? [{ buffer: pipelineState.identityInstanceBuffer, count: 1 }]
1479
+ : draws;
1480
+ if (chunks !== undefined) {
1481
+ const prefix = `${ownerKey}:`;
1482
+ for (const [key, stale] of chunks.entries()) {
1483
+ if (!key.startsWith(prefix) || activeKeys.has(key)) continue;
1484
+ if (!stale.buffer.isDestroyed) {
1485
+ const destroyed = stale.buffer.destroy();
1486
+ if (!destroyed.ok) runtime.errorRegistry.fire(destroyed.error);
1487
+ }
1488
+ chunks.delete(key);
1489
+ }
1490
+ }
1491
+ if (!failed) {
1492
+ reportInstanceResidency(c, shadowInst, {
1493
+ lane: 'chunked-uniform',
1494
+ requestedBytes: shadowInst.instanceCount * strideBytes,
1495
+ supportedBytes: MAX_UNIFORM_INSTANCES * strideBytes,
1496
+ uploadRanges: uploadedRanges,
1497
+ uploadedBytes,
1498
+ });
1499
+ }
1500
+ } else {
1501
+ const bufUsage = uniformFallback
1502
+ ? GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST
1503
+ : GPU_BUFFER_USAGE_STORAGE | GPU_BUFFER_USAGE_COPY_DST;
1504
+ const instancePayload = uniformFallback
1505
+ ? shadowInst.transforms
1506
+ : packInstanceStorageBuffer(shadowInst.transforms);
1507
+ const requestedBytes = instancePayload.byteLength;
1508
+ const cap = runtime.device.limits.maxStorageBufferBindingSize;
1509
+ if (!uniformFallback && typeof cap === 'number' && cap > 0 && requestedBytes > cap) {
1510
+ const chunked = resolveStorageInstanceChunks(c, entry, shadowInst, cap, undefined, -1);
1511
+ if (chunked !== null) {
1512
+ shadowInstanceDraws = chunked.map((draw) => ({
1513
+ buffer: draw.instanceBuffer,
1514
+ count: draw.instanceCount,
1515
+ }));
1516
+ }
1517
+ } else {
1518
+ const instanceCacheKey = instanceCollectionCacheKey(entry.source.worldId, shadowInst);
1519
+ const cached = c.frameState.instanceBuffers.get(instanceCacheKey);
1520
+ let active: InstanceBufferCacheEntry | null = null;
1521
+ let activeIsNew = false;
1522
+ if (
1523
+ cached !== undefined &&
1524
+ cached.uploadedArchVersion === shadowInst.archVersion &&
1525
+ cached.uploadedByteLength === requestedBytes &&
1526
+ (shadowInst.revision === undefined
1527
+ ? cached.uploadedRevision === undefined
1528
+ : cached.uploadedRevision === shadowInst.revision)
1529
+ ) {
1530
+ active = cached;
1531
+ } else if (requestedBytes > 0) {
1532
+ const bufRes = runtime.device.createBuffer({
1533
+ size: requestedBytes,
1534
+ usage: bufUsage,
1535
+ mappedAtCreation: false,
1536
+ });
1537
+ if (!bufRes.ok) {
1538
+ runtime.errorRegistry.fire(bufRes.error);
1539
+ } else {
1540
+ // feat-20260619 M4 / F12: destroy the old cached buffer
1541
+ // before replacing it with the new one (D-6).
1542
+ if (cached !== undefined && !cached.buffer.isDestroyed) {
1543
+ const r = cached.buffer.destroy();
1544
+ if (!r.ok) runtime.errorRegistry.fire(r.error);
1545
+ }
1546
+ const newBuffer = new GpuBuffer(runtime.device, bufRes.value);
1547
+ active = {
1548
+ buffer: newBuffer,
1549
+ uploadedArchVersion: shadowInst.archVersion,
1550
+ uploadedByteLength: requestedBytes,
1551
+ };
1552
+ activeIsNew = true;
1553
+ if (active !== null) {
1554
+ // Keep the collection identity in the worldEntityKey namespace.
1555
+ c.frameState.instanceBuffers.set(instanceCacheKey, active);
1556
+ }
1557
+ }
1558
+ }
1559
+ if (active !== null) {
1560
+ const needsUpload =
1561
+ activeIsNew ||
1562
+ (shadowInst.revision !== undefined &&
1563
+ active.uploadedRevision !== shadowInst.revision);
1564
+ if (needsUpload) {
1565
+ const writeRes = runtime.device.queue.writeBuffer(
1566
+ active.buffer.handle,
1567
+ 0,
1568
+ instancePayload,
1569
+ );
1570
+ if (!writeRes.ok) {
1571
+ runtime.errorRegistry.fire(writeRes.error);
1572
+ continue;
1573
+ }
1574
+ if (shadowInst.revision !== undefined) {
1575
+ const published = { ...active, uploadedRevision: shadowInst.revision };
1576
+ c.frameState.instanceBuffers.set(instanceCacheKey, published);
1577
+ active = published;
1578
+ }
1579
+ }
1580
+ reportInstanceResidency(c, shadowInst, {
1581
+ lane: uniformFallback ? 'direct-uniform' : 'direct-storage',
1582
+ requestedBytes,
1583
+ supportedBytes: uniformFallback
1584
+ ? MAX_UNIFORM_INSTANCES * Float32Array.BYTES_PER_ELEMENT * 16
1585
+ : typeof cap === 'number'
1586
+ ? cap
1587
+ : undefined,
1588
+ uploadRanges: needsUpload ? [{ start: 0, end: shadowInst.instanceCount }] : [],
1589
+ uploadedBytes: needsUpload ? instancePayload.byteLength : 0,
1590
+ });
1591
+ shadowInstanceDraws = [
1592
+ { buffer: active.buffer.handle, count: Math.max(1, shadowInst.instanceCount) },
1593
+ ];
1594
+ }
1595
+ }
1596
+ }
1597
+ }
677
1598
 
678
1599
  // feat-20260608 M4 / w16: per-submesh shadow draw loop.
679
1600
  // Only draw submeshes whose topology is triangle-list or triangle-strip
680
1601
  // (line-list / point-list submeshes cast no shadow and are skipped).
681
- for (const sm of shadowSubmeshes) {
1602
+ for (const { submesh: sm, submeshIndex } of shadowSubmeshes) {
682
1603
  if (sm.topology !== 'triangle-list' && sm.topology !== 'triangle-strip') {
683
1604
  continue;
684
1605
  }
@@ -689,115 +1610,199 @@ function recordShadowCasterDraws(
689
1610
  // two-sided casters (such as the Three.js teapot) populate the atlas
690
1611
  // from both faces. Custom ShadowCaster shaders retain their own path.
691
1612
  const submeshMaterial = entry.source.materials[sm.materialSlot] ?? entry.source.material;
692
- const shadowDispatch = shadowDispatchByRenderableIdx
693
- .get(entry.renderableIndex)
694
- ?.get(submeshMaterial.materialHandle ?? 0);
695
- const entryShadowShaderId = shadowDispatch?.materialShaderId;
696
- const entryShadowRenderState = shadowDispatch?.renderState;
697
- // Canonical skin casters retain their palette layout. Authored casters
698
- // select their own program below with the same geometry layout.
699
- let entryShadowPipeline: RenderPipeline | null =
700
- entry.source.skin !== undefined ? shadowLastPipeline : shadowPipeline;
701
- if (
702
- entryShadowShaderId !== undefined &&
703
- (entryShadowShaderId !== 'forgeax::default-shadow-caster' ||
704
- (entryShadowRenderState !== undefined && entry.source.skin === undefined))
705
- ) {
706
- // Pending or failed authored programs must not draw using a different
707
- // caster. Retry preparation on the next frame with the same program.
708
- entryShadowPipeline =
709
- runtime.getMaterialShaderPipeline?.(
710
- entryShadowShaderId,
711
- false, // isHdr — shadow depth pass is always LDR
712
- entryShadowRenderState,
713
- 'triangle-list', // topology — shadow PSO targets triangle-list
714
- undefined, // indexFormat
715
- undefined, // variantSet — shadow caster has no variant axes
716
- 'shadow-caster', // passKind
717
- undefined, // meshAttributes
718
- 1,
719
- undefined, // colorFormatOverride
720
- undefined, // shaderUvSetCount
721
- undefined, // depthFormatOverride
722
- undefined, // vertexLayout
723
- entry.mesh.layoutProjection,
724
- undefined, // shaderModuleMode
725
- entry.source.skin === undefined ? 'pbr' : 'pbr-skin',
726
- undefined, // additionalColorFormats
727
- shadowDispatch?.vertexEntry,
728
- shadowDispatch?.fragmentEntry,
729
- ) ?? null;
730
- }
731
- if (entryShadowPipeline === null) {
732
- complete = false;
733
- continue;
734
- }
735
- if (entryShadowPipeline !== shadowLastPipeline && entryShadowPipeline !== null) {
736
- shadowPass.setPipeline(entryShadowPipeline);
737
- shadowLastPipeline = entryShadowPipeline;
738
- }
739
- if (entryShadowShaderId !== undefined && entryShadowShaderId !== SHADOW_CASTER_SHADER_ID) {
740
- materialDeps ??= {
741
- runtime,
742
- pipelineState,
743
- world: c.world,
744
- store: c.store,
745
- materialSlice: STANDARD_PBR_UBO_SIZE,
746
- videoHighPerfAvailable: probeVideoHighPerfUpload(runtime.device),
747
- skylightResources: prepareMaterialSkylight(c).skylightResources,
748
- resolveRenderTargetTextureSource: runtime.resolveRenderTargetTextureSource,
749
- materialBgShared: c.frameState.materialBgShared,
750
- materialBgAssemblyCache: c.materialBgAssemblyCache,
751
- frameState: c.frameState,
752
- bindGroupCounts: c.bindGroupCounts,
753
- };
754
- const materialSlot =
755
- c.materialSlotIndices[i]?.[sm.materialSlot] ?? c.materialSlotIndices[i]?.[0];
756
- if (materialSlot === undefined) {
1613
+ const shadowDispatches =
1614
+ shadowDispatchByRenderableIdx
1615
+ .get(entry.renderableIndex)
1616
+ ?.get(submeshMaterial.materialHandle ?? 0) ?? [];
1617
+ const compactDrawIndex = drawIndices.get(submeshIndex);
1618
+ const draw =
1619
+ compactDrawIndex === undefined
1620
+ ? undefined
1621
+ : entry.source.gpuDrivenDraws?.[compactDrawIndex];
1622
+ const drawItemIndex = draw?.drawItemIndex ?? compactDrawIndex ?? submeshIndex;
1623
+ const worldEntity = worldEntityKey(
1624
+ c.shadowCasterWorldKeys?.[entry.source.worldId] ?? entry.source.worldId,
1625
+ entry.source.entityKey,
1626
+ );
1627
+ const residualDispatches =
1628
+ gpuDrivenShadowDrawKeys === undefined
1629
+ ? shadowDispatches
1630
+ : shadowDispatches.filter(
1631
+ (dispatch) =>
1632
+ !gpuDrivenShadowDrawKeys.has(
1633
+ gpuDrivenShadowDrawKey(
1634
+ worldEntity,
1635
+ submeshMaterial.materialHandle ?? -1,
1636
+ drawItemIndex,
1637
+ dispatch.passIndex,
1638
+ ),
1639
+ ),
1640
+ );
1641
+ const passes = residualDispatches.length === 0 ? [undefined] : residualDispatches;
1642
+ if (gpuDrivenShadowDrawKeys !== undefined && residualDispatches.length === 0) continue;
1643
+ // Deformation selects the mesh/palette layout, never the material program.
1644
+ // Each residual keeps its authored shader, entries, state and bindings.
1645
+ for (const shadowDispatch of passes) {
1646
+ const entryShadowShaderId = shadowDispatch?.materialShaderId;
1647
+ const entryShadowRenderState = shadowRasterState(shadowDispatch?.renderState, reflected);
1648
+ const skinned = entry.source.skin !== undefined;
1649
+ let entryShadowPipeline: RenderPipeline | null = shadowPipeline;
1650
+ const needsDedicatedPipeline =
1651
+ shadowDispatch !== undefined || skinned || sm.topology !== 'triangle-list';
1652
+ if (shadowDispatch !== undefined && entryShadowShaderId === undefined) {
757
1653
  throw new RhiError({
758
- code: 'rhi-descriptor-invalid',
759
- expected: 'a prepared material slot for each shadow submesh',
760
- hint: 'repair material slot preparation before recording shadows',
1654
+ code: 'rhi-not-available',
1655
+ expected: 'material shader identity for CPU ShadowCaster residual',
1656
+ hint: 'publish a producer-owned ShadowCaster shader identity before recording the residual',
761
1657
  });
762
1658
  }
763
- const materialGroup = buildPerSubmeshMaterialBg(
764
- materialDeps,
765
- submeshMaterial,
766
- entry.source.entityKey,
767
- entry.world ?? c.world,
768
- entryShadowShaderId,
769
- );
770
- shadowPass.setBindGroup(1, materialGroup, [materialSlot * MATERIAL_PER_ENTITY_STRIDE]);
771
- } else {
772
- const materialGroup = ensureSpotShadowMaterialBg(c);
773
- if (materialGroup === null) continue;
774
- shadowPass.setBindGroup(1, materialGroup, [0]);
775
- }
776
- for (const instanceDraw of shadowInstanceDraws) {
777
- shadowPass.setBindGroup(3, instanceDraw.instanceBindGroup);
778
- if (entry.mesh.indexed) {
779
- shadowPass.drawIndexed(sm.indexCount, instanceDraw.instanceCount, sm.indexOffset, 0, 0);
1659
+ if (needsDedicatedPipeline) {
1660
+ entryShadowPipeline =
1661
+ runtime.getMaterialShaderPipeline?.(
1662
+ entryShadowShaderId ?? SHADOW_CASTER_SHADER_ID,
1663
+ false,
1664
+ entryShadowRenderState,
1665
+ sm.topology,
1666
+ entry.mesh.indexFormat,
1667
+ shadowCasterVariantSet(
1668
+ runtime.device.caps.storageBuffer,
1669
+ skinned,
1670
+ false,
1671
+ Number(shadowDispatch?.paramSnapshot?.alphaCutoff ?? 0) > 0,
1672
+ entry.mesh.layoutProjection.attributes.some(
1673
+ (attribute) => attribute.key === 'color',
1674
+ ),
1675
+ ),
1676
+ 'shadow-caster',
1677
+ undefined, // meshAttributes
1678
+ 1, // sampleCount
1679
+ undefined,
1680
+ undefined,
1681
+ undefined,
1682
+ undefined,
1683
+ entry.mesh.layoutProjection,
1684
+ // Built-in variants reuse validated prewarm modules, including
1685
+ // adapters with no synchronous shader factory. Authored residual
1686
+ // programs use their synchronous manifest producer at first draw.
1687
+ entryShadowShaderId === undefined || entryShadowShaderId === SHADOW_CASTER_SHADER_ID
1688
+ ? 'validated'
1689
+ : 'immediate',
1690
+ skinned ? 'pbr-skin' : 'pbr',
1691
+ undefined, // vertexEntryPoint
1692
+ undefined, // additionalColorFormats
1693
+ shadowDispatch?.vertexEntry,
1694
+ shadowDispatch?.fragmentEntry,
1695
+ ) ?? null;
1696
+ }
1697
+ if (entryShadowPipeline === null) {
1698
+ throw new RhiError({
1699
+ code: 'rhi-not-available',
1700
+ expected: `CPU ShadowCaster pipeline ${entryShadowShaderId ?? '<missing>'}`,
1701
+ hint: 'publish the exact residual ShadowCaster pipeline; do not substitute the default shader',
1702
+ });
1703
+ }
1704
+ if (entryShadowPipeline !== shadowLastPipeline && entryShadowPipeline !== null) {
1705
+ shadowPass.setPipeline(entryShadowPipeline);
1706
+ shadowLastPipeline = entryShadowPipeline;
1707
+ }
1708
+ if (shadowDispatch !== undefined && entryShadowShaderId !== undefined) {
1709
+ materialDeps ??= {
1710
+ runtime,
1711
+ pipelineState,
1712
+ world: c.world,
1713
+ store: c.store,
1714
+ materialSlice: STANDARD_PBR_UBO_SIZE,
1715
+ videoHighPerfAvailable: probeVideoHighPerfUpload(runtime.device),
1716
+ skylightResources: prepareMaterialSkylight(c).skylightResources,
1717
+ resolveRenderTargetTextureSource: runtime.resolveRenderTargetTextureSource,
1718
+ materialBgShared: c.frameState.materialBgShared,
1719
+ materialBgAssemblyCache: c.materialBgAssemblyCache,
1720
+ frameState: c.frameState,
1721
+ bindGroupCounts: c.bindGroupCounts,
1722
+ };
1723
+ const materialSlot =
1724
+ shadowDispatch?.materialSlot ??
1725
+ c.materialSlotIndices[i]?.[sm.materialSlot] ??
1726
+ c.materialSlotIndices[i]?.[0];
1727
+ if (materialSlot === undefined) {
1728
+ throw new RhiError({
1729
+ code: 'rhi-descriptor-invalid',
1730
+ expected: 'a prepared material slot for each shadow submesh',
1731
+ hint: 'repair material slot preparation before recording shadows',
1732
+ });
1733
+ }
1734
+ const materialGroup = buildPerSubmeshMaterialBg(
1735
+ materialDeps,
1736
+ submeshMaterial,
1737
+ entry.source.entityKey,
1738
+ entry.world ?? c.world,
1739
+ entryShadowShaderId,
1740
+ );
1741
+ shadowPass.setBindGroup(1, materialGroup, [materialSlot * MATERIAL_PER_ENTITY_STRIDE]);
780
1742
  } else {
781
- shadowPass.draw(sm.vertexCount, instanceDraw.instanceCount, 0, 0);
1743
+ const materialGroup = ensureSpotShadowMaterialBg(c);
1744
+ if (materialGroup === null) {
1745
+ throw new RhiError({
1746
+ code: 'rhi-not-available',
1747
+ expected: 'CPU residual shadow material bind group',
1748
+ hint: 'publish the residual material resources before recording shadows',
1749
+ });
1750
+ }
1751
+ shadowPass.setBindGroup(1, materialGroup, [0]);
1752
+ }
1753
+ for (const instanceDraw of shadowInstanceDraws) {
1754
+ const shadowInstancesBg = resolveGeometryInstancesBindGroup(c, instanceDraw.buffer);
1755
+ shadowPass.setBindGroup(3, shadowInstancesBg);
1756
+ if (entry.mesh.indexed) {
1757
+ shadowPass.drawIndexed(sm.indexCount, instanceDraw.count, sm.indexOffset, 0, 0);
1758
+ } else {
1759
+ shadowPass.draw(sm.vertexCount, instanceDraw.count, 0, 0);
1760
+ }
1761
+ }
1762
+ if (shadowDispatch !== undefined) {
1763
+ recordedKeys.add(
1764
+ gpuDrivenShadowDrawKey(
1765
+ worldEntity,
1766
+ submeshMaterial.materialHandle ?? -1,
1767
+ drawItemIndex,
1768
+ shadowDispatch.passIndex,
1769
+ ),
1770
+ );
782
1771
  }
783
1772
  }
784
1773
  }
785
1774
  }
786
- return complete;
1775
+ return recordedKeys;
787
1776
  }
788
1777
 
789
1778
  /**
790
- * Build (or reuse) the dummy `shadow-material-singleton` @group(1) BG for the
791
- * spot shadow caster pass. The vertex-only shadow_caster shader never consumes
792
- * @group(1) but the PSO's BGL must validate. Reuses the same singleton Map
793
- * entry encodeDirectionalShadowPass / encodePointShadowPass write so the three paths share
794
- * one allocation per frame (D-6).
1779
+ * Build (or reuse) the neutral `shadow-material-singleton` @group(1) BG for
1780
+ * default casters without an authored material dispatch. Authored opaque and
1781
+ * masked residuals bind their own prepared material slots instead. Directional,
1782
+ * point, and spot passes share bindings only while every resource identity
1783
+ * matches; scene-table growth or recovery must create a fresh bind group.
795
1784
  */
796
1785
  function ensureSpotShadowMaterialBg(c: _InternalRenderPipelineContext): BindGroup | null {
797
1786
  const { runtime, frameState, pipelineState } = c;
798
- const cached = frameState.singletonMaterialCache.get('shadow-material-singleton');
799
- if (cached !== undefined) return cached;
800
1787
  const fb = pipelineState.skylightFallback;
1788
+ const sceneMaterialBuffer =
1789
+ c.gpuDrivenStandardPbrFrameResources?.sceneMaterialBuffer ??
1790
+ pipelineState.meshStorageBuffer.buffer;
1791
+ const handles = [
1792
+ pipelineState.materialBindGroupLayout,
1793
+ pipelineState.materialUniformBuffer.buffer,
1794
+ pipelineState.defaultSampler,
1795
+ pipelineState.defaultNormalTextureView,
1796
+ pipelineState.fallbackTextureView,
1797
+ sceneMaterialBuffer,
1798
+ ...(fb === null ? [] : [fb]),
1799
+ ];
1800
+ const cached = findFromChain(
1801
+ frameState.shadowMaterialBindGroups,
1802
+ handles,
1803
+ 'shadow-material-singleton',
1804
+ );
1805
+ if (cached !== undefined) return cached;
801
1806
  const fallbackEntries: BindGroupEntry[] = buildPbrMaterialUserRegionEntries().map((entry) => {
802
1807
  if (entry.buffer !== undefined) {
803
1808
  return {
@@ -831,27 +1836,181 @@ function ensureSpotShadowMaterialBg(c: _InternalRenderPipelineContext): BindGrou
831
1836
  });
832
1837
  const merged =
833
1838
  fb !== null
834
- ? assembleMaterialWithSkylightEntries(fallbackEntries, {
835
- irradianceView: fb.irradianceView,
836
- irradianceSampler: fb.sampler,
837
- prefilterView: fb.prefilterView,
838
- prefilterSampler: fb.sampler,
839
- brdfLutView: fb.brdfLutView,
840
- brdfLutSampler: fb.sampler,
841
- intensityBuffer: fb.intensityBuffer,
842
- })
1839
+ ? assembleMaterialWithSkylightEntries(
1840
+ fallbackEntries,
1841
+ {
1842
+ irradianceView: fb.irradianceView,
1843
+ irradianceSampler: fb.sampler,
1844
+ prefilterView: fb.prefilterView,
1845
+ prefilterSampler: fb.sampler,
1846
+ brdfLutView: fb.brdfLutView,
1847
+ brdfLutSampler: fb.sampler,
1848
+ intensityBuffer: fb.intensityBuffer,
1849
+ },
1850
+ transmissionBackdropAvailable(runtime.device.limits.maxSampledTexturesPerShaderStage)
1851
+ ? undefined
1852
+ : null,
1853
+ )
843
1854
  : fallbackEntries;
844
- const r = runtime.device.createBindGroup({
845
- label: 'shadow-material-bg',
846
- layout: pipelineState.materialBindGroupLayout,
847
- entries: merged,
848
- });
1855
+ // Binding(46) is the optional scene-material storage table. The material
1856
+ // BGL intentionally omits it on the uniform-only WebGL2 lane, so emitting
1857
+ // an entry unconditionally creates a deferred-invalid bind group in wgpu's
1858
+ // downlevel backend and only surfaces at Queue::submit. GPU-driven alpha
1859
+ // batches build their own receipt-backed group and are storage-capable by
1860
+ // admission; this singleton only needs the optional slot when that same
1861
+ // capability is present.
1862
+ if (runtime.device.caps.storageBuffer) {
1863
+ // Scene-index shadow variants read MaterialParameters by the projected
1864
+ // row selected in visibleItems. The legacy singleton used the mesh SSBO
1865
+ // here because the old shadow vertex path never consumed binding(46); on
1866
+ // the capable scene-index lane that buffer is a different struct and can
1867
+ // make the alpha/depth fragment discard every caster. Reuse the frame's
1868
+ // producer-owned scene material table, with the mesh buffer only for
1869
+ // direct/legacy callers that do not publish one.
1870
+ merged.push({
1871
+ binding: 46,
1872
+ resource: {
1873
+ kind: 'buffer',
1874
+ value: { buffer: sceneMaterialBuffer },
1875
+ },
1876
+ });
1877
+ }
1878
+ const r = getOrCreateFromChainResult(
1879
+ frameState.shadowMaterialBindGroups,
1880
+ handles,
1881
+ 'shadow-material-singleton',
1882
+ () =>
1883
+ runtime.device.createBindGroup({
1884
+ label: 'shadow-material-bg',
1885
+ layout: pipelineState.materialBindGroupLayout,
1886
+ entries: merged,
1887
+ }),
1888
+ c.bindGroupCounts,
1889
+ );
849
1890
  if (!r.ok) {
850
1891
  runtime.errorRegistry.fire(r.error);
851
1892
  return null;
852
1893
  }
853
- c.bindGroupCounts.createBindGroup += 1;
854
- c.bindGroupCounts.keys.push('shadow-material-singleton');
855
- frameState.singletonMaterialCache.set('shadow-material-singleton', r.value);
856
1894
  return r.value;
857
1895
  }
1896
+
1897
+ /**
1898
+ * Assemble the material group for a GPU-driven alpha-mask caster from the
1899
+ * same material snapshot/residency owner as the main PBR pass. The shadow
1900
+ * shader only consumes the base-color pair and the scene-material table, but
1901
+ * the complete merged layout is still required by the shared pipeline.
1902
+ */
1903
+ function ensureGpuDrivenAlphaMaskMaterialBg(
1904
+ c: _InternalRenderPipelineContext,
1905
+ projection: GpuDrivenShadowBatchProjection,
1906
+ ): BindGroup {
1907
+ const sceneMaterialBuffer = c.gpuDrivenStandardPbrFrameResources?.sceneMaterialBuffer;
1908
+ if (sceneMaterialBuffer === undefined) {
1909
+ throw new RhiError({
1910
+ code: 'rhi-not-available',
1911
+ expected: 'GPU Scene material buffer for alpha-mask shadow recording',
1912
+ hint: 'publish the receipt-derived scene material table before the capable shadow pass',
1913
+ });
1914
+ }
1915
+ const fallback = c.pipelineState.skylightFallback;
1916
+ if (fallback === null) {
1917
+ throw new RhiError({
1918
+ code: 'webgpu-runtime-error',
1919
+ expected: 'pipelineState.skylightFallback for alpha-mask shadow material layout',
1920
+ hint: 'create the merged Standard PBR material resources before recording shadows',
1921
+ });
1922
+ }
1923
+ const skylightResources = {
1924
+ irradianceView: fallback.irradianceView,
1925
+ irradianceSampler: fallback.sampler,
1926
+ prefilterView: fallback.prefilterView,
1927
+ prefilterSampler: fallback.sampler,
1928
+ brdfLutView: fallback.brdfLutView,
1929
+ brdfLutSampler: fallback.sampler,
1930
+ intensityBuffer: fallback.intensityBuffer,
1931
+ } satisfies import('../ibl/skylight-bind-group').SkylightBindGroupResources;
1932
+ const deps: PerSubmeshMaterialBgDeps = {
1933
+ runtime: c.runtime,
1934
+ pipelineState: c.pipelineState,
1935
+ world: c.world,
1936
+ store: c.store,
1937
+ materialSlice: STANDARD_PBR_UBO_SIZE,
1938
+ videoHighPerfAvailable: false,
1939
+ skylightResources,
1940
+ materialBgShared: c.frameState.materialBgShared,
1941
+ materialBgAssemblyCache: c.materialBgAssemblyCache,
1942
+ sceneMaterialBuffer,
1943
+ bindGroupCounts: c.bindGroupCounts,
1944
+ frameState: c.frameState,
1945
+ };
1946
+ const bindGroup = buildPerSubmeshMaterialBg(
1947
+ deps,
1948
+ projection.material,
1949
+ projection.materialEntityKey,
1950
+ c.world,
1951
+ );
1952
+ return bindGroup;
1953
+ }
1954
+
1955
+ /**
1956
+ * Build the group(1) binding for a receipt-backed custom shadow program. The
1957
+ * ordinary shadow singleton is Standard-only; binding it for a custom layout
1958
+ * would make the pipeline appear executable while deferring a BGL mismatch to
1959
+ * submit. Custom resources therefore use the same material producer as the
1960
+ * main pass, using the selected scene-index row when the published custom ABI
1961
+ * declares that resource. No Standard-only alpha or shader rows are
1962
+ * synthesized for the custom artifact.
1963
+ */
1964
+ function ensureGpuDrivenCustomShadowMaterialBg(
1965
+ c: _InternalRenderPipelineContext,
1966
+ projection: GpuDrivenShadowBatchProjection,
1967
+ ): BindGroup {
1968
+ const shaderId = projection.shadowArtifact.material;
1969
+ const fallback = c.pipelineState.skylightFallback;
1970
+ if (fallback === null) {
1971
+ throw new RhiError({
1972
+ code: 'rhi-not-available',
1973
+ expected: 'skylight resources for custom shadow material binding',
1974
+ hint: 'publish the custom material resource owner before recording the shadow lane',
1975
+ });
1976
+ }
1977
+ const sceneMaterialBuffer = c.gpuDrivenStandardPbrFrameResources?.sceneMaterialBuffer;
1978
+ if (sceneMaterialBuffer === undefined) {
1979
+ throw new RhiError({
1980
+ code: 'rhi-not-available',
1981
+ expected: 'producer-owned scene material buffer for custom shadow ABI',
1982
+ hint: 'publish the selected shadow MaterialProgramAbi scene-index resource before recording',
1983
+ });
1984
+ }
1985
+ const skylightResources = {
1986
+ irradianceView: fallback.irradianceView,
1987
+ irradianceSampler: fallback.sampler,
1988
+ prefilterView: fallback.prefilterView,
1989
+ prefilterSampler: fallback.sampler,
1990
+ brdfLutView: fallback.brdfLutView,
1991
+ brdfLutSampler: fallback.sampler,
1992
+ intensityBuffer: fallback.intensityBuffer,
1993
+ } satisfies import('../ibl/skylight-bind-group').SkylightBindGroupResources;
1994
+ const deps: PerSubmeshMaterialBgDeps = {
1995
+ runtime: c.runtime,
1996
+ pipelineState: c.pipelineState,
1997
+ world: c.world,
1998
+ store: c.store,
1999
+ materialSlice: STANDARD_PBR_UBO_SIZE,
2000
+ videoHighPerfAvailable: false,
2001
+ skylightResources,
2002
+ materialBgShared: c.frameState.materialBgShared,
2003
+ materialBgAssemblyCache: c.materialBgAssemblyCache,
2004
+ bindGroupCounts: c.bindGroupCounts,
2005
+ frameState: c.frameState,
2006
+ sceneMaterialBuffer,
2007
+ };
2008
+ const bindGroup = buildPerSubmeshMaterialBg(
2009
+ deps,
2010
+ projection.material,
2011
+ projection.materialEntityKey,
2012
+ c.world,
2013
+ shaderId,
2014
+ );
2015
+ return bindGroup;
2016
+ }