@forgeax/engine-render 0.1.20 → 0.1.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (372) hide show
  1. package/README.md +215 -0
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/assembly/device-feature-admission.d.ts +18 -0
  4. package/dist/assembly/device-feature-admission.d.ts.map +1 -0
  5. package/dist/assembly/directional-shadow-inspection.d.ts +38 -0
  6. package/dist/assembly/directional-shadow-inspection.d.ts.map +1 -0
  7. package/dist/assembly/factory.d.ts +21 -18
  8. package/dist/assembly/factory.d.ts.map +1 -1
  9. package/dist/assembly/host-contract.d.ts +9 -0
  10. package/dist/assembly/host-contract.d.ts.map +1 -1
  11. package/dist/assembly/render-target-host.d.ts +46 -0
  12. package/dist/assembly/render-target-host.d.ts.map +1 -0
  13. package/dist/assets/render-asset-port.d.ts.map +1 -1
  14. package/dist/authoring.mjs +3 -0
  15. package/dist/authoring.mjs.map +1 -1
  16. package/dist/capture/cube-views.d.ts +21 -0
  17. package/dist/capture/cube-views.d.ts.map +1 -0
  18. package/dist/capture/scheduler.d.ts +54 -0
  19. package/dist/capture/scheduler.d.ts.map +1 -0
  20. package/dist/{chunk-4XINDLAY.mjs → chunk-5YADUCA7.mjs} +1552 -1361
  21. package/dist/chunk-5YADUCA7.mjs.map +1 -0
  22. package/dist/{chunk-TBQJOK2L.mjs → chunk-6OPXDQ2E.mjs} +364 -51
  23. package/dist/chunk-6OPXDQ2E.mjs.map +1 -0
  24. package/dist/{chunk-M2MP4ZOU.mjs → chunk-BXBQQKFQ.mjs} +3 -9
  25. package/dist/chunk-BXBQQKFQ.mjs.map +1 -0
  26. package/dist/{chunk-CDTWTHZR.mjs → chunk-FS2KNUQH.mjs} +548 -6
  27. package/dist/chunk-FS2KNUQH.mjs.map +1 -0
  28. package/dist/components/camera.d.ts +1 -0
  29. package/dist/components/camera.d.ts.map +1 -1
  30. package/dist/components/cube-camera.d.ts +20 -0
  31. package/dist/components/cube-camera.d.ts.map +1 -0
  32. package/dist/components/directional-light.d.ts +11 -2
  33. package/dist/components/directional-light.d.ts.map +1 -1
  34. package/dist/components/directional-shadow-filter.d.ts +25 -0
  35. package/dist/components/directional-shadow-filter.d.ts.map +1 -0
  36. package/dist/components/index.d.ts +2 -0
  37. package/dist/components/index.d.ts.map +1 -1
  38. package/dist/components/light-helpers.d.ts.map +1 -1
  39. package/dist/components/reflection-probe.d.ts +17 -0
  40. package/dist/components/reflection-probe.d.ts.map +1 -0
  41. package/dist/construct-renderer.mjs +5826 -1003
  42. package/dist/construct-renderer.mjs.map +1 -1
  43. package/dist/environment/frame.d.ts.map +1 -1
  44. package/dist/environment/lifecycle.d.ts +5 -0
  45. package/dist/environment/lifecycle.d.ts.map +1 -1
  46. package/dist/errors/render.d.ts +141 -93
  47. package/dist/errors/render.d.ts.map +1 -1
  48. package/dist/extract/directional-shadow-projection.d.ts +23 -0
  49. package/dist/extract/directional-shadow-projection.d.ts.map +1 -0
  50. package/dist/extract/environment.d.ts +14 -0
  51. package/dist/extract/environment.d.ts.map +1 -1
  52. package/dist/extract/visibility.d.ts.map +1 -1
  53. package/dist/features/fullscreen.d.ts.map +1 -1
  54. package/dist/features/targets.d.ts.map +1 -1
  55. package/dist/features/types.d.ts +10 -0
  56. package/dist/features/types.d.ts.map +1 -1
  57. package/dist/fullscreen-post-process-pass.d.ts +13 -2
  58. package/dist/fullscreen-post-process-pass.d.ts.map +1 -1
  59. package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
  60. package/dist/gpu-driven/production-raster.d.ts +0 -1
  61. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  62. package/dist/gpu-scene.d.ts.map +1 -1
  63. package/dist/hdrp-buffers.d.ts +19 -17
  64. package/dist/hdrp-buffers.d.ts.map +1 -1
  65. package/dist/ibl/IblPipelineCache.d.ts +4 -0
  66. package/dist/ibl/IblPipelineCache.d.ts.map +1 -1
  67. package/dist/ibl/kernel-cache.d.ts +36 -0
  68. package/dist/ibl/kernel-cache.d.ts.map +1 -0
  69. package/dist/index.d.ts +28 -3
  70. package/dist/index.d.ts.map +1 -1
  71. package/dist/index.mjs +4 -4
  72. package/dist/index.mjs.map +1 -1
  73. package/dist/inspection-types.d.ts +51 -0
  74. package/dist/inspection-types.d.ts.map +1 -1
  75. package/dist/instances-derived-bounds.d.ts +50 -0
  76. package/dist/instances-derived-bounds.d.ts.map +1 -0
  77. package/dist/internal.mjs +1 -1
  78. package/dist/light-buffer-layout.d.ts +10 -1
  79. package/dist/light-buffer-layout.d.ts.map +1 -1
  80. package/dist/mesh-material-bindings.d.ts +54 -0
  81. package/dist/mesh-material-bindings.d.ts.map +1 -1
  82. package/dist/pbr-pipeline.d.ts +14 -4
  83. package/dist/pbr-pipeline.d.ts.map +1 -1
  84. package/dist/pipeline/standard-forward-lane.d.ts +2 -1
  85. package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
  86. package/dist/pipeline/standard-lighting/errors.d.ts +4 -0
  87. package/dist/pipeline/standard-lighting/errors.d.ts.map +1 -0
  88. package/dist/pipeline/standard-lighting/graph.d.ts +24 -0
  89. package/dist/pipeline/standard-lighting/graph.d.ts.map +1 -0
  90. package/dist/pipeline/standard-lighting/inspection.d.ts +38 -0
  91. package/dist/pipeline/standard-lighting/inspection.d.ts.map +1 -0
  92. package/dist/pipeline/standard-lighting/layout.d.ts +28 -0
  93. package/dist/pipeline/standard-lighting/layout.d.ts.map +1 -0
  94. package/dist/pipeline/standard-lighting/light-frame.d.ts +33 -0
  95. package/dist/pipeline/standard-lighting/light-frame.d.ts.map +1 -0
  96. package/dist/pipeline/standard-lighting/membership.d.ts +14 -0
  97. package/dist/pipeline/standard-lighting/membership.d.ts.map +1 -0
  98. package/dist/pipeline/standard-lighting/prepare.d.ts +16 -0
  99. package/dist/pipeline/standard-lighting/prepare.d.ts.map +1 -0
  100. package/dist/pipeline/standard-lighting/topology.d.ts +45 -0
  101. package/dist/pipeline/standard-lighting/topology.d.ts.map +1 -0
  102. package/dist/pipeline/standard-lighting/transport.d.ts +36 -0
  103. package/dist/pipeline/standard-lighting/transport.d.ts.map +1 -0
  104. package/dist/pipeline/standard-pipeline.d.ts +4 -13
  105. package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
  106. package/dist/pipeline/standard-post.d.ts.map +1 -1
  107. package/dist/pipeline/standard-profile.d.ts +5 -26
  108. package/dist/pipeline/standard-profile.d.ts.map +1 -1
  109. package/dist/pipeline-spec.d.ts +40 -7
  110. package/dist/pipeline-spec.d.ts.map +1 -1
  111. package/dist/point-shadow-inspection.d.ts +21 -0
  112. package/dist/point-shadow-inspection.d.ts.map +1 -0
  113. package/dist/record/frame-lighting.d.ts +27 -32
  114. package/dist/record/frame-lighting.d.ts.map +1 -1
  115. package/dist/record/frame-snapshot.d.ts +28 -14
  116. package/dist/record/frame-snapshot.d.ts.map +1 -1
  117. package/dist/record/frame-targets.d.ts +5 -0
  118. package/dist/record/frame-targets.d.ts.map +1 -1
  119. package/dist/record/frame.d.ts +10 -4
  120. package/dist/record/frame.d.ts.map +1 -1
  121. package/dist/record/gpu-pass-timing/contract.d.ts +96 -0
  122. package/dist/record/gpu-pass-timing/contract.d.ts.map +1 -0
  123. package/dist/record/gpu-pass-timing/errors.d.ts +18 -0
  124. package/dist/record/gpu-pass-timing/errors.d.ts.map +1 -0
  125. package/dist/record/gpu-pass-timing/index.d.ts +8 -0
  126. package/dist/record/gpu-pass-timing/index.d.ts.map +1 -0
  127. package/dist/record/gpu-pass-timing/parser.d.ts +11 -0
  128. package/dist/record/gpu-pass-timing/parser.d.ts.map +1 -0
  129. package/dist/record/gpu-pass-timing/session.d.ts +43 -0
  130. package/dist/record/gpu-pass-timing/session.d.ts.map +1 -0
  131. package/dist/record/gpu-timing.d.ts +8 -3
  132. package/dist/record/gpu-timing.d.ts.map +1 -1
  133. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  134. package/dist/record/main-pass-material.d.ts +22 -1
  135. package/dist/record/main-pass-material.d.ts.map +1 -1
  136. package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
  137. package/dist/record/main-pass.d.ts +2 -0
  138. package/dist/record/main-pass.d.ts.map +1 -1
  139. package/dist/record/render-context.d.ts +43 -2
  140. package/dist/record/render-context.d.ts.map +1 -1
  141. package/dist/record/shadow-pass.d.ts.map +1 -1
  142. package/dist/record/skybox-post-pass.d.ts.map +1 -1
  143. package/dist/record/typed-frame-graph.d.ts +72 -4
  144. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  145. package/dist/record/view-ubo.d.ts +7 -4
  146. package/dist/record/view-ubo.d.ts.map +1 -1
  147. package/dist/reflection/filter.d.ts +22 -0
  148. package/dist/reflection/filter.d.ts.map +1 -0
  149. package/dist/reflection/gpu-table.d.ts +25 -0
  150. package/dist/reflection/gpu-table.d.ts.map +1 -0
  151. package/dist/reflection/projection.d.ts +91 -0
  152. package/dist/reflection/projection.d.ts.map +1 -0
  153. package/dist/render-contract.d.ts +49 -7
  154. package/dist/render-contract.d.ts.map +1 -1
  155. package/dist/render-graph-primitives.d.ts +1 -0
  156. package/dist/render-graph-primitives.d.ts.map +1 -1
  157. package/dist/render-pipeline.d.ts +42 -2
  158. package/dist/render-pipeline.d.ts.map +1 -1
  159. package/dist/render-system-extract.d.ts +58 -17
  160. package/dist/render-system-extract.d.ts.map +1 -1
  161. package/dist/render-system.d.ts +17 -2
  162. package/dist/render-system.d.ts.map +1 -1
  163. package/dist/renderer.d.ts +43 -0
  164. package/dist/renderer.d.ts.map +1 -0
  165. package/dist/scene/render-scene.d.ts +14 -0
  166. package/dist/scene/render-scene.d.ts.map +1 -1
  167. package/dist/shadow-atlas.d.ts +3 -3
  168. package/dist/shadow-atlas.d.ts.map +1 -1
  169. package/dist/ssao-buffers.d.ts +3 -0
  170. package/dist/ssao-buffers.d.ts.map +1 -1
  171. package/dist/targets/contracts.d.ts +66 -0
  172. package/dist/targets/contracts.d.ts.map +1 -0
  173. package/dist/targets/graph-projection.d.ts +29 -0
  174. package/dist/targets/graph-projection.d.ts.map +1 -0
  175. package/dist/targets/material-source.d.ts +34 -0
  176. package/dist/targets/material-source.d.ts.map +1 -0
  177. package/dist/targets/owner.d.ts +32 -0
  178. package/dist/targets/owner.d.ts.map +1 -0
  179. package/dist/targets/physical.d.ts +18 -0
  180. package/dist/targets/physical.d.ts.map +1 -0
  181. package/dist/targets/readback.d.ts +39 -0
  182. package/dist/targets/readback.d.ts.map +1 -0
  183. package/dist/temporal/index.mjs +3 -3
  184. package/dist/typed-render-graph-primitives.d.ts +1 -1
  185. package/dist/typed-render-graph-primitives.d.ts.map +1 -1
  186. package/dist/typed-shadow-passes.d.ts +1 -1
  187. package/dist/typed-shadow-passes.d.ts.map +1 -1
  188. package/dist/volume/passes.d.ts +2 -2
  189. package/dist/volume/passes.d.ts.map +1 -1
  190. package/package.json +20 -20
  191. package/src/__tests__/bloom-exact-zero.unit.test.ts +48 -19
  192. package/src/__tests__/bloom-lifecycle.integration.test.ts +41 -13
  193. package/src/__tests__/camera-target-consumption.unit.test.ts +53 -0
  194. package/src/__tests__/cluster-binner.unit.test.ts +35 -0
  195. package/src/__tests__/cluster-grid-default-surface.unit.test.ts +7 -2
  196. package/src/__tests__/cube-camera-scheduler.unit.test.ts +70 -0
  197. package/src/__tests__/cube-camera-single-submit.perf.test.ts +75 -0
  198. package/src/__tests__/cube-camera-view-matrices.unit.test.ts +41 -0
  199. package/src/__tests__/cube-camera-vocabulary.unit.test.ts +47 -0
  200. package/src/__tests__/cube-camera.integration.test.ts +42 -0
  201. package/src/__tests__/device-feature-admission.unit.test.ts +73 -0
  202. package/src/__tests__/direct-light-snapshot.unit.test.ts +3 -3
  203. package/src/__tests__/docs-gate-green.unit.test.ts +38 -0
  204. package/src/__tests__/factory-contract.integration.test.ts +255 -29
  205. package/src/__tests__/fog-component.unit.test.ts +67 -0
  206. package/src/__tests__/fog.integration.test.ts +86 -0
  207. package/src/__tests__/fullscreen-feature-plan.unit.test.ts +37 -0
  208. package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +10 -9
  209. package/src/__tests__/gpu-driven-indirect-raster.dawn.test.ts +17 -1
  210. package/src/__tests__/gpu-driven-production.integration.test.ts +16 -17
  211. package/src/__tests__/gpu-driven-view-graph.integration.test.ts +12 -4
  212. package/src/__tests__/gpu-pass-timing-browser-runner.ts +281 -0
  213. package/src/__tests__/gpu-pass-timing-coexistence.integration.test.ts +80 -0
  214. package/src/__tests__/gpu-pass-timing-contract.unit.test.ts +243 -0
  215. package/src/__tests__/gpu-pass-timing-exact-zero.unit.test.ts +131 -0
  216. package/src/__tests__/gpu-pass-timing-lifecycle.integration.test.ts +230 -0
  217. package/src/__tests__/gpu-pass-timing-observation.unit.test.ts +233 -0
  218. package/src/__tests__/gpu-pass-timing-session.unit.test.ts +438 -0
  219. package/src/__tests__/gpu-pass-timing.browser.test.ts +26 -0
  220. package/src/__tests__/gpu-pass-timing.rhi-null.unit.test.ts +125 -0
  221. package/src/__tests__/hdrp-group2-visibility.unit.test.ts +24 -0
  222. package/src/__tests__/instances-culling.integration.test.ts +94 -0
  223. package/src/__tests__/instances-derived-bounds.unit.test.ts +118 -0
  224. package/src/__tests__/material-observation.integration.test.ts +101 -0
  225. package/src/__tests__/pbr-view-layout.unit.test.ts +16 -0
  226. package/src/__tests__/point-shadow-inspection.unit.test.ts +60 -0
  227. package/src/__tests__/public-surface.test-d.ts +10 -0
  228. package/src/__tests__/reflection-probe-authoring.unit.test.ts +71 -0
  229. package/src/__tests__/reflection-probe-filter.unit.test.ts +54 -0
  230. package/src/__tests__/reflection-probe-ibl-topology.unit.test.ts +45 -0
  231. package/src/__tests__/reflection-probe-pass-encode.integration.test.ts +101 -0
  232. package/src/__tests__/reflection-probe-performance.perf.test.ts +60 -0
  233. package/src/__tests__/reflection-probe-recovery.integration.test.ts +55 -0
  234. package/src/__tests__/reflection-probe-selection.unit.test.ts +46 -0
  235. package/src/__tests__/render-error-code-owner.test-d.ts +18 -3
  236. package/src/__tests__/render-feature-docs.unit.test.ts +1 -1
  237. package/src/__tests__/render-target-admission.unit.test.ts +80 -0
  238. package/src/__tests__/render-target-graph-projection.unit.test.ts +102 -0
  239. package/src/__tests__/render-target-inspection.unit.test.ts +52 -0
  240. package/src/__tests__/render-target-material-source.test-d.ts +30 -0
  241. package/src/__tests__/render-target-material-source.unit.test.ts +97 -0
  242. package/src/__tests__/render-target-owner-placement.unit.test.ts +203 -0
  243. package/src/__tests__/render-target-owner.unit.test.ts +122 -0
  244. package/src/__tests__/render-target-probe-soak.perf.test.ts +94 -0
  245. package/src/__tests__/render-target-public-schema.json +107 -0
  246. package/src/__tests__/render-target-public-schema.unit.test.ts +91 -0
  247. package/src/__tests__/render-target-public-surface.test-d.ts +83 -0
  248. package/src/__tests__/render-target-readback.integration.test.ts +90 -0
  249. package/src/__tests__/render-target-recovery.integration.test.ts +85 -0
  250. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +48 -0
  251. package/src/__tests__/renderer-lifecycle.integration.test.ts +14 -0
  252. package/src/__tests__/shader-stage-surface.unit.test.ts +19 -4
  253. package/src/__tests__/skin-motion-regression.unit.test.ts +132 -0
  254. package/src/__tests__/skinned-shadow-caster.test.ts +11 -2
  255. package/src/__tests__/standard-cluster-capability.integration.test.ts +63 -0
  256. package/src/__tests__/standard-cluster-transport.unit.test.ts +121 -0
  257. package/src/__tests__/standard-cluster-variant-resolution.unit.test.ts +232 -0
  258. package/src/__tests__/standard-clustered-pipeline.unit.test.ts +8 -5
  259. package/src/__tests__/standard-lighting-inspection.integration.test.ts +138 -0
  260. package/src/__tests__/standard-lighting-ssot.unit.test.ts +18 -0
  261. package/src/__tests__/standard-lighting-write.unit.test.ts +377 -0
  262. package/src/__tests__/standard-lighting.integration.test.ts +67 -0
  263. package/src/__tests__/standard-pipeline.dawn.test.ts +206 -5
  264. package/src/__tests__/standard-pipeline.integration.test.ts +181 -150
  265. package/src/__tests__/standard-probe-binding.unit.test.ts +25 -0
  266. package/src/__tests__/standard-profile-oracle.unit.test.ts +23 -38
  267. package/src/__tests__/typed-pipeline-topology.unit.test.ts +395 -14
  268. package/src/__tests__/urp-pipeline-targets.unit.test.ts +2 -2
  269. package/src/__tests__/visibility-resolver.unit.test.ts +15 -0
  270. package/src/assembly/device-feature-admission.ts +73 -0
  271. package/src/assembly/directional-shadow-inspection.ts +122 -0
  272. package/src/assembly/factory.ts +877 -286
  273. package/src/assembly/host-contract.ts +25 -0
  274. package/src/assembly/render-target-host.ts +847 -0
  275. package/src/assets/asset-decoders.ts +1 -1
  276. package/src/assets/render-asset-port.ts +7 -1
  277. package/src/capture/cube-views.ts +69 -0
  278. package/src/capture/scheduler.ts +265 -0
  279. package/src/components/__tests__/schema-contract.unit.test.ts +12 -0
  280. package/src/components/__tests__/schema-parity.unit.test.ts +9 -0
  281. package/src/components/camera.ts +4 -0
  282. package/src/components/cube-camera.ts +52 -0
  283. package/src/components/directional-light.ts +22 -2
  284. package/src/components/directional-shadow-filter.ts +57 -0
  285. package/src/components/index.ts +2 -0
  286. package/src/components/light-helpers.ts +81 -7
  287. package/src/components/reflection-probe.ts +49 -0
  288. package/src/environment/frame.ts +6 -24
  289. package/src/environment/lifecycle.ts +12 -0
  290. package/src/errors/render.ts +286 -142
  291. package/src/extract/directional-shadow-projection.ts +61 -0
  292. package/src/extract/environment.ts +57 -0
  293. package/src/extract/visibility.ts +25 -1
  294. package/src/features/fullscreen.ts +3 -0
  295. package/src/features/targets.ts +4 -1
  296. package/src/features/types.ts +10 -0
  297. package/src/fullscreen-post-process-pass.ts +43 -2
  298. package/src/gpu-driven/batch-topology.ts +1 -0
  299. package/src/gpu-driven/production-raster.ts +1 -3
  300. package/src/gpu-scene.ts +8 -1
  301. package/src/hdrp-buffers.ts +165 -59
  302. package/src/ibl/IblPipelineCache.ts +11 -0
  303. package/src/ibl/kernel-cache.ts +88 -0
  304. package/src/index.ts +148 -0
  305. package/src/inspection-types.ts +56 -0
  306. package/src/instances-derived-bounds.ts +171 -0
  307. package/src/light-buffer-layout.ts +20 -5
  308. package/src/mesh-material-bindings.ts +132 -0
  309. package/src/pbr-pipeline.ts +106 -17
  310. package/src/pipeline/standard-forward-lane.ts +46 -16
  311. package/src/pipeline/standard-lighting/errors.ts +6 -0
  312. package/src/pipeline/standard-lighting/graph.ts +157 -0
  313. package/src/pipeline/standard-lighting/inspection.ts +88 -0
  314. package/src/pipeline/standard-lighting/layout.ts +31 -0
  315. package/src/pipeline/standard-lighting/light-frame.ts +34 -0
  316. package/src/pipeline/standard-lighting/membership.ts +43 -0
  317. package/src/pipeline/standard-lighting/prepare.ts +37 -0
  318. package/src/pipeline/standard-lighting/topology.ts +120 -0
  319. package/src/pipeline/standard-lighting/transport.ts +77 -0
  320. package/src/pipeline/standard-pipeline.ts +122 -193
  321. package/src/pipeline/standard-post.ts +26 -7
  322. package/src/pipeline/standard-profile.ts +17 -47
  323. package/src/pipeline-spec.ts +84 -21
  324. package/src/point-shadow-inspection.ts +55 -0
  325. package/src/record/__tests__/temporal-payload.unit.test.ts +42 -1
  326. package/src/record/__tests__/transparent-group2-selection.unit.test.ts +73 -0
  327. package/src/record/__tests__/view-ubo-layout.unit.test.ts +40 -0
  328. package/src/record/frame-lighting.ts +367 -427
  329. package/src/record/frame-snapshot.ts +35 -27
  330. package/src/record/frame-targets.ts +17 -0
  331. package/src/record/frame.ts +261 -74
  332. package/src/record/gpu-pass-timing/contract.ts +180 -0
  333. package/src/record/gpu-pass-timing/errors.ts +34 -0
  334. package/src/record/gpu-pass-timing/index.ts +37 -0
  335. package/src/record/gpu-pass-timing/parser.ts +97 -0
  336. package/src/record/gpu-pass-timing/session.ts +938 -0
  337. package/src/record/gpu-timing.ts +21 -12
  338. package/src/record/main-pass-geometry.ts +120 -80
  339. package/src/record/main-pass-material.ts +95 -9
  340. package/src/record/main-pass-sprite-draws.ts +54 -25
  341. package/src/record/main-pass.ts +71 -15
  342. package/src/record/render-context.ts +72 -0
  343. package/src/record/shadow-pass.ts +17 -28
  344. package/src/record/skybox-post-pass.ts +20 -4
  345. package/src/record/typed-frame-graph.ts +893 -44
  346. package/src/record/view-ubo.ts +63 -28
  347. package/src/reflection/filter.ts +81 -0
  348. package/src/reflection/gpu-table.ts +44 -0
  349. package/src/reflection/projection.ts +281 -0
  350. package/src/render-contract.ts +76 -5
  351. package/src/render-graph-primitives.ts +9 -4
  352. package/src/render-pipeline.ts +117 -2
  353. package/src/render-system-extract.ts +557 -100
  354. package/src/render-system.ts +1095 -66
  355. package/src/renderer.ts +222 -0
  356. package/src/scene/render-scene.ts +115 -9
  357. package/src/shadow-atlas.ts +5 -5
  358. package/src/ssao-buffers.ts +9 -2
  359. package/src/targets/contracts.ts +182 -0
  360. package/src/targets/graph-projection.ts +169 -0
  361. package/src/targets/material-source.ts +143 -0
  362. package/src/targets/owner.ts +214 -0
  363. package/src/targets/physical.ts +18 -0
  364. package/src/targets/readback.ts +153 -0
  365. package/src/transmission/__tests__/standard-transmission.dawn.test.ts +59 -20
  366. package/src/typed-render-graph-primitives.ts +5 -3
  367. package/src/typed-shadow-passes.ts +46 -40
  368. package/src/volume/passes.ts +15 -9
  369. package/dist/chunk-4XINDLAY.mjs.map +0 -1
  370. package/dist/chunk-CDTWTHZR.mjs.map +0 -1
  371. package/dist/chunk-M2MP4ZOU.mjs.map +0 -1
  372. package/dist/chunk-TBQJOK2L.mjs.map +0 -1
@@ -9,8 +9,8 @@ import {
9
9
 
10
10
  const MAX_VOLUME_PASSES = 4;
11
11
  const QUERY_COUNT = MAX_VOLUME_PASSES * 2;
12
- const QUERY_RESULT_BYTES = 8;
13
- const READBACK_BYTES = QUERY_COUNT * QUERY_RESULT_BYTES;
12
+ const QUERY_STRIDE_BYTES = 256;
13
+ const READBACK_BYTES = QUERY_COUNT * QUERY_STRIDE_BYTES;
14
14
  const PASS_NAMES = [
15
15
  'volume-inject',
16
16
  'volume-integrate',
@@ -34,6 +34,12 @@ function unavailable(reason: string): VolumeTimingObservation {
34
34
  return Object.freeze({ status: 'unavailable', reason });
35
35
  }
36
36
 
37
+ export interface VolumeTimingTimestampWrites {
38
+ readonly querySet: QuerySet;
39
+ readonly beginningOfPassWriteIndex: number;
40
+ readonly endOfPassWriteIndex: number;
41
+ }
42
+
37
43
  /**
38
44
  * Renderer-owned timestamp capture for the authored volume graph. Query
39
45
  * resources are private to one accepted submit and never cross the receipt
@@ -109,18 +115,21 @@ export class GpuTimingCapture {
109
115
  );
110
116
  }
111
117
 
112
- beforePass(name: string, encoder: RhiCommandEncoder): void {
118
+ recordPass(name: string): VolumeTimingTimestampWrites | undefined {
113
119
  const index = PASS_NAMES.indexOf(name as (typeof PASS_NAMES)[number]);
114
- if (index < 0 || this.#started.has(name)) return;
120
+ if (index < 0 || this.#started.has(name) || this.#discarded) return undefined;
115
121
  this.#started.set(name, index);
116
- encoder.writeTimestamp(this.#querySet, index * 2);
122
+ this.#finished.set(name, index);
123
+ return {
124
+ querySet: this.#querySet,
125
+ beginningOfPassWriteIndex: index * 2,
126
+ endOfPassWriteIndex: index * 2 + 1,
127
+ };
117
128
  }
118
129
 
119
- afterPass(name: string, encoder: RhiCommandEncoder): void {
120
- const index = this.#started.get(name);
121
- if (index === undefined || this.#finished.has(name)) return;
122
- this.#finished.set(name, index);
123
- encoder.writeTimestamp(this.#querySet, index * 2 + 1);
130
+ skipPass(name: string): void {
131
+ this.#started.delete(name);
132
+ this.#finished.delete(name);
124
133
  }
125
134
 
126
135
  resolve(encoder: RhiCommandEncoder): Result<void, RhiError> {
@@ -176,8 +185,8 @@ export class GpuTimingCapture {
176
185
  .sort((a, b) => a[1] - b[1])
177
186
  .map(([name, index]) => ({
178
187
  name,
179
- begin: values.getBigUint64(index * 2 * QUERY_RESULT_BYTES, true),
180
- end: values.getBigUint64((index * 2 + 1) * QUERY_RESULT_BYTES, true),
188
+ begin: values.getBigUint64(index * 2 * QUERY_STRIDE_BYTES, true),
189
+ end: values.getBigUint64((index * 2 + 1) * QUERY_STRIDE_BYTES, true),
181
190
  }));
182
191
  if (decoded.some(({ end, begin }) => end < begin)) {
183
192
  mapped.value.unmap();
@@ -17,10 +17,20 @@ import {
17
17
  GPU_BUFFER_USAGE_STORAGE,
18
18
  GPU_BUFFER_USAGE_UNIFORM,
19
19
  } from '../gpu-usage';
20
+ import { createHdrpSkinUnifiedBindGroup, getOrCreateHdrpBuffers } from '../hdrp-buffers';
20
21
  import type { InstanceBufferCacheEntry } from '../instance-buffer-cache';
21
22
  import { SPRITE_PREMULTIPLIED_ALPHA_BLEND } from '../materials';
22
- import { SKIN_MATERIAL_SHADER_ID } from '../pbr-pipeline';
23
- import { renderStateHash, variantSetFromVertexLayoutProjection } from '../pipeline-spec';
23
+ import {
24
+ createPbrSkinMeshBindGroupEntries,
25
+ pbrSkinMeshDynamicOffsets,
26
+ SKIN_MATERIAL_SHADER_ID,
27
+ } from '../pbr-pipeline';
28
+ import {
29
+ renderStateHash,
30
+ standardStorageVariantSet,
31
+ standardTopologyVariantSet,
32
+ variantSetFromVertexLayoutProjection,
33
+ } from '../pipeline-spec';
24
34
  import type { PointsLinesRecordPlan } from '../points-lines/record';
25
35
  import { POINTS_LINES_MATERIAL_SHADER_ID } from '../points-lines/record';
26
36
  import type { RenderRecordPhase } from '../render-contract';
@@ -30,6 +40,7 @@ import {
30
40
  geometryRenderStateForTopology,
31
41
  isEntityFullyTransparent,
32
42
  selectGeometryPipeline,
43
+ selectMaterialGroup2,
33
44
  } from './main-pass-material';
34
45
  import {
35
46
  getOrCreateFromChain,
@@ -40,7 +51,7 @@ import {
40
51
  MESH_UBO_FULL_ARRAY_BYTES,
41
52
  packInstanceStorageBuffer,
42
53
  } from './mesh-ssbo';
43
- import type { _InternalRenderPipelineContext } from './render-context';
54
+ import type { _InternalRenderPipelineContext, MaterialShaderPipelineEntry } from './render-context';
44
55
  import { MATERIAL_PER_ENTITY_STRIDE } from './render-context';
45
56
  import { POINTS_LINES_VIEW_SLOT_STRIDE, writePointsLinesViewUbo } from './view-ubo';
46
57
 
@@ -66,7 +77,7 @@ type MaterialPipelineLookup = {
66
77
  readonly layoutProjection: import('@forgeax/engine-geometry').VertexLayoutProjection;
67
78
  readonly sampleCount: number;
68
79
  readonly colorFormatOverride: GPUTextureFormat | undefined;
69
- readonly handle: RenderPipeline | null;
80
+ readonly handle: MaterialShaderPipelineEntry | null;
70
81
  };
71
82
 
72
83
  type GeometryRecordProfileSegment = 'material-bind-groups' | 'pipeline-selection' | 'draw-submit';
@@ -197,8 +208,13 @@ export function recordGeometryDraws(
197
208
  validatedOrdered,
198
209
  splitLdrSprite,
199
210
  viewBindGroup,
211
+ viewBindGroupDynamicOffset = 0,
200
212
  } = c;
201
213
  const colorFormatOverride = c.transparentColorFormat as GPUTextureFormat | undefined;
214
+ // The prepared Standard topology is the frame authority. A bind-group
215
+ // identity is only a resource result; using it to infer the shader lane
216
+ // would silently fall back to URP when the clustered resource is missing.
217
+ const clusteredLighting = c.standardLighting?.kind === 'clustered';
202
218
  // A no-tone frame may still render into the graph-owned rgba16float target
203
219
  // (the linear-LDR route). Shader variants must follow the attachment's
204
220
  // numeric domain rather than the camera's tone-map switch, otherwise the
@@ -222,7 +238,6 @@ export function recordGeometryDraws(
222
238
  const profilePhase = c.profilePhase;
223
239
  const materialGroup1DynamicOffsets = new Uint32Array(1);
224
240
  const meshGroup2DynamicOffsets = [0];
225
- const skinGroup2DynamicOffsets = [0, 0];
226
241
  let lastStencilReference: number | null = null;
227
242
  // feat-20260625-refactor-sprite-as-transparent-mesh M3 / w14 (D-7):
228
243
  // sprite PSO selection no longer maintains a dedicated tag — the
@@ -246,7 +261,7 @@ export function recordGeometryDraws(
246
261
  variantSet: string | undefined,
247
262
  colorFormat: GPUTextureFormat | undefined,
248
263
  layoutProjection: import('@forgeax/engine-geometry').VertexLayoutProjection,
249
- ): RenderPipeline | null => {
264
+ ): MaterialShaderPipelineEntry | null => {
250
265
  const renderStateKey = renderStateHash(renderState);
251
266
  const shaderLookupCache = materialPipelineLookupCache.get(materialShaderId);
252
267
  const cachedLookups = shaderLookupCache?.get(renderStateKey);
@@ -278,7 +293,7 @@ export function recordGeometryDraws(
278
293
  }
279
294
  const resolvedVariantSet = resolvedVariantSetResult.value;
280
295
  const resolvePipeline = () =>
281
- runtime.getMaterialShaderPipeline?.(
296
+ runtime.getMaterialShaderPipelineEntry?.(
282
297
  materialShaderId,
283
298
  isHdrTarget,
284
299
  renderState,
@@ -340,7 +355,7 @@ export function recordGeometryDraws(
340
355
  // Points/Lines share the Standard geometry pass. Their retained source
341
356
  // is prepared once by the renderer owner, while this loop remains the
342
357
  // sole pass/draw submission owner and preserves the authored topology.
343
- const pointsLinesSubmission = c.pointsLines?.prepare(entry, frameState.isHdrpActive);
358
+ const pointsLinesSubmission = c.pointsLines?.prepare(entry, clusteredLighting);
344
359
  if (entry.source.pointsLines !== undefined && pointsLinesSubmission?.plan.drawCount !== 1)
345
360
  continue;
346
361
 
@@ -399,7 +414,7 @@ export function recordGeometryDraws(
399
414
  entry.source.entityKey,
400
415
  entry.world ?? c.world,
401
416
  );
402
- const pointsLinesPipeline = resolveMaterialPipeline(
417
+ const pointsLinesPipelineEntry = resolveMaterialPipeline(
403
418
  POINTS_LINES_MATERIAL_SHADER_ID,
404
419
  {
405
420
  ...pointsLinesMaterial.renderState,
@@ -411,7 +426,8 @@ export function recordGeometryDraws(
411
426
  colorFormatOverride,
412
427
  pointsLinesSubmission.layoutProjection,
413
428
  );
414
- if (pointsLinesPipeline === null) continue;
429
+ if (pointsLinesPipelineEntry === null) continue;
430
+ const pointsLinesPipeline = pointsLinesPipelineEntry.pipeline;
415
431
  if (pipelineState.pointsLinesViewBuffer === undefined) continue;
416
432
  writePointsLinesViewUbo(
417
433
  runtime.device.queue,
@@ -426,9 +442,9 @@ export function recordGeometryDraws(
426
442
  pass.setBindGroup(
427
443
  0,
428
444
  viewBindGroup as BindGroup,
429
- new Uint32Array([i * POINTS_LINES_VIEW_SLOT_STRIDE]),
445
+ new Uint32Array([0, i * POINTS_LINES_VIEW_SLOT_STRIDE]),
430
446
  0,
431
- 1,
447
+ 2,
432
448
  );
433
449
  pass.setPipeline(pointsLinesPipeline);
434
450
  pass.setVertexBuffer(0, pointsLinesSubmission.vertexBuffer);
@@ -444,7 +460,7 @@ export function recordGeometryDraws(
444
460
  resolveGeometryInstancesBindGroup(c, identityInstanceBuffer),
445
461
  );
446
462
  recordPointsLinesDraw(pass, pointsLinesSubmission.plan);
447
- pass.setBindGroup(0, viewBindGroup as BindGroup, [0]);
463
+ pass.setBindGroup(0, viewBindGroup as BindGroup, [viewBindGroupDynamicOffset, 0]);
448
464
  continue;
449
465
  }
450
466
 
@@ -474,11 +490,12 @@ export function recordGeometryDraws(
474
490
  const instanceDraws = resolveGeometryInstanceBuffer(c, entry, identityInstanceDraws);
475
491
  if (instanceDraws === null) continue;
476
492
 
477
- // feat-20260611 R2 / M8 / w28 (IS-14): skin entries need a 2-binding
493
+ // feat-20260611 R2 / M8 / w28 (IS-14): skin entries need a 3-binding
478
494
  // group(2) BG matching `pbr-skin-pl` (binding 0 mesh-array UBO +
479
- // binding 1 palette UBO). Building this here -- not in the
495
+ // binding 1 current palette UBO + binding 2 previous palette UBO).
496
+ // Building this here -- not in the
480
497
  // `meshBindGroup` factory above -- because the binding-shape
481
- // (1-entry vs 2-entry) is per-entry, not per-frame. URP / HDRP
498
+ // (1-entry vs 3-entry) is per-entry, not per-frame. URP / HDRP
482
499
  // entries keep using `meshGroup2` (1-entry mesh-array or HDRP
483
500
  // unified). The skin-variant cache key includes both buffer
484
501
  // identities so a future allocator-driven palette buffer rotation
@@ -489,7 +506,7 @@ export function recordGeometryDraws(
489
506
  // BGL-build failure), AND (b) the skin PSO cache returns a non-null
490
507
  // pipeline for this entry. Without (b), the per-submesh selector
491
508
  // below falls back to URP `standardPipeline` (`pbr-pl` layout,
492
- // 1-entry mesh-array BGL); binding the 2-entry skin BG against
509
+ // 1-entry mesh-array BGL); binding the 3-entry skin BG against
493
510
  // that pipeline reproduces the exact `pbr-mesh-array-bgl ... does
494
511
  // not match layout pbr-skin-mesh-array-bgl` device error R1
495
512
  // captured. Mirrors the uniform null skip-draw pattern (M6-T1).
@@ -517,28 +534,40 @@ export function recordGeometryDraws(
517
534
  // and walks 0, 1536, 3072, ... on the storage path.
518
535
  const skinAllocator = pipelineState.skinPaletteAllocator;
519
536
  const skinSlice = entry.source.skin;
537
+ const clusteredSkin = clusteredLighting;
538
+ const skinMeshBindGroupLayout = clusteredSkin
539
+ ? pipelineState.hdrpSkinMeshBindGroupLayout
540
+ : pipelineState.pbrSkinMeshBindGroupLayout;
541
+ const hdrpSkinBuffers =
542
+ isSkinEntry && clusteredSkin
543
+ ? getOrCreateHdrpBuffers(runtime, frameState.installedPipelineConfig?.clusterGrid)
544
+ : null;
520
545
  const skinResources =
521
546
  isSkinEntry &&
522
- pipelineState.pbrSkinMeshBindGroupLayout !== null &&
547
+ skinMeshBindGroupLayout !== null &&
523
548
  skinAllocator !== null &&
524
- skinSlice !== undefined
549
+ skinSlice !== undefined &&
550
+ (!clusteredSkin || hdrpSkinBuffers !== null)
525
551
  ? {
526
- meshArrayBgl: pipelineState.pbrSkinMeshBindGroupLayout,
552
+ meshArrayBgl: skinMeshBindGroupLayout,
527
553
  paletteBuffer: skinSlice.buffer,
528
554
  paletteBindingWindowBytes: skinAllocator.bindingWindowBytes,
555
+ hdrpBuffers: hdrpSkinBuffers,
529
556
  }
530
557
  : null;
531
558
  // Probe the skin PSO cache up front so we can decide whether to swap
532
- // to the 2-binding skin BG. The same probe + selector is repeated in
559
+ // to the skin BG. The same probe + selector is repeated in
533
560
  // the per-submesh loop below (the loop's variantSet derivation is
534
561
  // identical -- skin shader registers a single all-true variant so
535
562
  // the canonical empty-key rule applies on HDRP and the URP key is
536
563
  // the explicit expanded form, mirroring the standard PBR path).
537
564
  const skinVariantSetResult = variantSetFromVertexLayoutProjection(
538
565
  entry.mesh.layoutProjection,
539
- frameState.isHdrpActive
540
- ? ''
541
- : 'CLUSTER_FORWARD_AVAILABLE=false+STORAGE_BUFFER_AVAILABLE=true',
566
+ standardTopologyVariantSet(
567
+ c.standardLighting,
568
+ runtime.device.caps.storageBuffer,
569
+ entry.mesh.layoutProjection.attributes.some((attribute) => attribute.key === 'color'),
570
+ ),
542
571
  );
543
572
  if (!skinVariantSetResult.ok) runtime.errorRegistry.fire(skinVariantSetResult.error);
544
573
  const skinVariantSet = skinVariantSetResult.ok ? skinVariantSetResult.value : undefined;
@@ -579,45 +608,47 @@ export function recordGeometryDraws(
579
608
  const skinMissesBefore = bindGroupCounts.createBindGroup;
580
609
  const skinBindGroup: BindGroup = getOrCreateFromChain(
581
610
  frameState.meshBindGroupCache,
582
- [pipelineState.meshStorageBuffer.buffer, skinResources.paletteBuffer],
611
+ [
612
+ pipelineState.meshStorageBuffer.buffer,
613
+ skinResources.paletteBuffer,
614
+ ...(skinResources.hdrpBuffers === null
615
+ ? []
616
+ : [
617
+ skinResources.hdrpBuffers.lightDataBuffer,
618
+ skinResources.hdrpBuffers.clusterGridBuffer,
619
+ skinResources.hdrpBuffers.lightIndexListBuffer,
620
+ skinResources.hdrpBuffers.clusterUniformBuffer,
621
+ ]),
622
+ ],
583
623
  'pbr-skin-mesh',
584
624
  () => {
625
+ if (skinResources.hdrpBuffers !== null) {
626
+ const result = createHdrpSkinUnifiedBindGroup(
627
+ runtime,
628
+ skinResources.hdrpBuffers,
629
+ skinResources.meshArrayBgl,
630
+ pipelineState.meshStorageBuffer.buffer,
631
+ skinResources.paletteBuffer,
632
+ skinResources.paletteBindingWindowBytes,
633
+ );
634
+ if (result === null) {
635
+ throw new RhiError({
636
+ code: 'webgpu-runtime-error',
637
+ expected: 'HDRP clustered skin BindGroup creation succeeds',
638
+ hint: 'inspect prior errorRegistry events for createBindGroup failure detail',
639
+ });
640
+ }
641
+ return result;
642
+ }
585
643
  const result = runtime.device.createBindGroup({
586
644
  label: 'pbr-skin-mesh-bg',
587
645
  layout: skinResources.meshArrayBgl,
588
- entries: [
589
- {
590
- binding: 0,
591
- resource: {
592
- kind: 'buffer',
593
- value: {
594
- buffer: pipelineState.meshStorageBuffer.buffer,
595
- offset: 0,
596
- size: meshBindSize,
597
- },
598
- },
599
- },
600
- {
601
- binding: 1,
602
- resource: {
603
- kind: 'buffer',
604
- value: {
605
- buffer: skinResources.paletteBuffer,
606
- offset: 0,
607
- // M6 SSOT: static BG entry size (= MAX_JOINTS * 64 =
608
- // 16320 B) sourced from the allocator. The per-draw
609
- // window slides via `group2DynamicOffsets[1]`
610
- // (= entry.source.skin.byteOffset) so this size
611
- // stays at the worst case across all skin entries
612
- // -- one BG covers every skinned draw in the frame
613
- // (m3-1b miss=1 + hit=N-1 contract). The allocator
614
- // guarantees `buffer.size >= byteOffset + this size`
615
- // so dynOffset[1] passes WebGPU validation.
616
- size: skinResources.paletteBindingWindowBytes,
617
- },
618
- },
619
- },
620
- ],
646
+ entries: createPbrSkinMeshBindGroupEntries(
647
+ pipelineState.meshStorageBuffer.buffer,
648
+ meshBindSize,
649
+ skinResources.paletteBuffer,
650
+ skinResources.paletteBindingWindowBytes,
651
+ ),
621
652
  });
622
653
  if (!result.ok) throw result.error;
623
654
  return result.value;
@@ -634,9 +665,10 @@ export function recordGeometryDraws(
634
665
  // Replaces the prior PR #353 hard-coded `0` second slot -- every
635
666
  // skin entry now points the palette window at its own slice while
636
667
  // sharing the worst-case BG entry size above.
637
- skinGroup2DynamicOffsets[0] = i * MESH_PER_ENTITY_STRIDE;
638
- skinGroup2DynamicOffsets[1] = entry.source.skin?.byteOffset ?? 0;
639
- group2DynamicOffsets = skinGroup2DynamicOffsets;
668
+ group2DynamicOffsets = pbrSkinMeshDynamicOffsets(
669
+ i * MESH_PER_ENTITY_STRIDE,
670
+ entry.source.skin?.byteOffset ?? 0,
671
+ );
640
672
  } else if (isSkinEntry) {
641
673
  // Skin entry but skin PSO not ready (cache miss / async build pending,
642
674
  // or skin pipeline layout failed at boot). Skip the draw rather than
@@ -778,6 +810,8 @@ export function recordGeometryDraws(
778
810
  : geometryRenderStateForTopology(smTopology, submeshMaterial.renderState);
779
811
  const pipelineRenderState = geometryRenderStateForPass(basePipelineRenderState, passKind);
780
812
  let smPipelineHandle: typeof pipelineState.unlitPipeline;
813
+ let materialGroup2Contract: 'mesh' | 'skin' | 'cluster' | 'skin-cluster' | undefined;
814
+ let materialPipelineEntry: MaterialShaderPipelineEntry | null = null;
781
815
  if (smMaterialShaderId === undefined || smMaterialShaderId === 'forgeax::default-unlit') {
782
816
  const unlitShaderId = smMaterialShaderId ?? 'forgeax::default-unlit';
783
817
  const unlitProjectionVariantResult = variantSetFromVertexLayoutProjection(
@@ -789,9 +823,10 @@ export function recordGeometryDraws(
789
823
  continue;
790
824
  }
791
825
  const unlitHasColor = unlitProjectionVariantResult.value === 'VERTEX_COLOR_AVAILABLE=true';
792
- const unlitVariantSet = unlitHasColor
793
- ? ''
794
- : 'STORAGE_BUFFER_AVAILABLE=true+VERTEX_COLOR_AVAILABLE=false';
826
+ const unlitVariantSet = standardStorageVariantSet(
827
+ runtime.device.caps.storageBuffer,
828
+ unlitHasColor,
829
+ );
795
830
  const unlitRsp = resolveMaterialPipeline(
796
831
  unlitShaderId,
797
832
  pipelineRenderState,
@@ -801,8 +836,9 @@ export function recordGeometryDraws(
801
836
  colorFormatOverride,
802
837
  entry.mesh.layoutProjection,
803
838
  );
839
+ materialPipelineEntry = unlitRsp;
804
840
  smPipelineHandle =
805
- unlitRsp ??
841
+ unlitRsp?.pipeline ??
806
842
  (colorFormatOverride === undefined
807
843
  ? selectGeometryPipeline(pipelineState, isHdrTarget, msaaActive)
808
844
  : null);
@@ -829,11 +865,11 @@ export function recordGeometryDraws(
829
865
  // VERTEX_COLOR_AVAILABLE=false axis, so retain the two capability
830
866
  // axes explicitly or the selector would silently choose the URP
831
867
  // group(2) layout for an HDRP cluster bind group.
832
- const capabilityVariantSet = frameState.isHdrpActive
833
- ? hasVertexColor
834
- ? ''
835
- : 'CLUSTER_FORWARD_AVAILABLE=true+STORAGE_BUFFER_AVAILABLE=true'
836
- : 'CLUSTER_FORWARD_AVAILABLE=false+STORAGE_BUFFER_AVAILABLE=true';
868
+ const capabilityVariantSet = standardTopologyVariantSet(
869
+ c.standardLighting,
870
+ runtime.device.caps.storageBuffer,
871
+ hasVertexColor,
872
+ );
837
873
  // Sprite's base request must retain the boot-selected artifact. Only
838
874
  // SpriteInstances uses the explicit empty key for the
839
875
  // PER_INSTANCE_REGION=true variant; synthesizing the generic PBR
@@ -877,7 +913,8 @@ export function recordGeometryDraws(
877
913
  // build resolves. The pre-loop skin-PSO probe still skips the
878
914
  // entire entry on first-submesh probe miss; this site only
879
915
  // fires on per-submesh topology variance miss.
880
- smPipelineHandle = cachedPipeline ?? null;
916
+ materialPipelineEntry = cachedPipeline;
917
+ smPipelineHandle = cachedPipeline?.pipeline ?? null;
881
918
  } else {
882
919
  smPipelineHandle = selectGeometryPipeline(pipelineState, isHdrTarget, msaaActive);
883
920
  }
@@ -890,16 +927,16 @@ export function recordGeometryDraws(
890
927
  // ordinary mesh group instead; choosing one group for the whole pass
891
928
  // makes Dawn reject an otherwise valid unlit pipeline and invalidates
892
929
  // the command buffer before it reaches the surface.
893
- const usesStandardClusterBindings = smMaterialShaderId === 'forgeax::default-standard-pbr';
894
930
  if (!isSkinEntry) {
895
- if (usesStandardClusterBindings) {
896
- if (meshGroup2 === null) continue;
897
- group2BindGroup = meshGroup2;
898
- } else {
899
- const fallbackGroup = meshBindGroup ?? meshGroup2;
900
- if (fallbackGroup === null) continue;
901
- group2BindGroup = fallbackGroup;
902
- }
931
+ materialGroup2Contract = materialPipelineEntry?.group2Contract;
932
+ if (materialGroup2Contract === undefined) continue;
933
+ const selectedGroup = selectMaterialGroup2(
934
+ meshGroup2,
935
+ meshBindGroup,
936
+ materialGroup2Contract,
937
+ );
938
+ if (selectedGroup === null) continue;
939
+ group2BindGroup = selectedGroup;
903
940
  }
904
941
  pass.setBindGroup(2, group2BindGroup, group2DynamicOffsets);
905
942
 
@@ -945,6 +982,9 @@ function resolveGeometryInstanceBuffer(
945
982
  const { runtime, pipelineState, frameState } = c;
946
983
  const inst = entry.source.instances;
947
984
  if (inst === undefined) return identityInstanceDraws;
985
+ // A present, empty Instances array means zero draws. Do not fall back to
986
+ // the shared identity buffer, which would silently render one instance.
987
+ if (inst.instanceCount === 0) return [];
948
988
  let instanceBuffer: Buffer = pipelineState.identityInstanceBuffer;
949
989
  let instanceCount = 1;
950
990
  {
@@ -40,9 +40,16 @@ import {
40
40
  type SkylightBindGroupResources,
41
41
  type TransmissionBindGroupResources,
42
42
  } from '../ibl/skylight-bind-group';
43
+ import type { PipelineGroup2Contract } from '../pbr-pipeline';
43
44
  import { isStandardPbrMaterialShader } from '../pbr-pipeline';
44
45
  import { deriveTextureExtent } from '../render-data';
45
46
  import type { MaterialSnapshot } from '../render-system-extract';
47
+ import type { RenderTargetTextureSource } from '../targets/contracts';
48
+ import {
49
+ type RenderTargetMaterialSourceBinding,
50
+ resolveRenderTargetMaterialSource,
51
+ } from '../targets/material-source';
52
+ import type { StandardReflectionProbeBinding } from './frame-lighting';
46
53
  import type { BindGroupCounts, MaterialBgAssemblyCacheEntry } from './frame-snapshot';
47
54
  import { extractEntryResourceHandle, getOrCreatePerEntity } from './mesh-ssbo';
48
55
  import {
@@ -51,6 +58,23 @@ import {
51
58
  STANDARD_PBR_UBO_SIZE,
52
59
  } from './render-context';
53
60
 
61
+ /**
62
+ * Resolve the group(2) resource layout from the material shader contract.
63
+ * Standard clustered PBR consumes the unified cluster bind group; every other
64
+ * material consumes the ordinary mesh bind group. Missing ordinary resources
65
+ * remain missing so the caller can skip the draw rather than violate layout
66
+ * compatibility by binding a cluster group.
67
+ */
68
+ export function selectMaterialGroup2(
69
+ clusterGroup: BindGroup | null,
70
+ meshGroup: BindGroup | null,
71
+ group2Contract: PipelineGroup2Contract,
72
+ ): BindGroup | null {
73
+ if (group2Contract === 'cluster') return clusterGroup;
74
+ if (group2Contract === 'mesh') return meshGroup;
75
+ return null;
76
+ }
77
+
54
78
  // Param schemas are immutable runtime contracts: the shader registry installs
55
79
  // them once and material snapshots only retain the same array reference. Keep
56
80
  // the expensive pure derivation on that identity so the per-submesh record
@@ -412,6 +436,21 @@ const DEFAULT_STANDARD_TEXTURE_COORDINATES = new Float32Array([
412
436
  0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1,
413
437
  ]);
414
438
 
439
+ export interface StandardReflectionProbeMaterialProjection {
440
+ readonly probeIndex: number | undefined;
441
+ readonly fallbackToSkylight: boolean;
442
+ }
443
+
444
+ /** Projects the selected scene row into the Standard material lane. */
445
+ export function projectReflectionProbeMaterialBinding(
446
+ binding: StandardReflectionProbeBinding,
447
+ ): StandardReflectionProbeMaterialProjection {
448
+ return {
449
+ probeIndex: binding.probeIndex,
450
+ fallbackToSkylight: binding.useSkylight,
451
+ };
452
+ }
453
+
415
454
  /** Derives the logical-content UV scale for an uploaded material texture. */
416
455
  export function materialTextureUvScale(
417
456
  texture: Pick<TextureAsset, 'shape' | 'format'> | undefined,
@@ -433,6 +472,18 @@ function materialTextureForField(
433
472
  return material.textureHandles?.get(field);
434
473
  }
435
474
 
475
+ /** Resolve a renderer-local target source without entering AssetRegistry. */
476
+ export function materialRenderTargetSourceForField(
477
+ material: MaterialSnapshot,
478
+ field: string,
479
+ resolveSource?: RenderSystemRuntime['resolveRenderTargetTextureSource'],
480
+ ): RenderTargetMaterialSourceBinding | undefined {
481
+ const source = material.textureSources?.get(field) as RenderTargetTextureSource | undefined;
482
+ return source === undefined
483
+ ? undefined
484
+ : (resolveSource?.(source) ?? resolveRenderTargetMaterialSource(source));
485
+ }
486
+
436
487
  /**
437
488
  * Appends the engine-owned scale for every built-in material texture binding.
438
489
  * Asset dimensions remain logical; this is the sole record-stage projection
@@ -583,7 +634,15 @@ export function userRegionTextureFieldOrder(
583
634
  export function defaultViewForUserRegionField(
584
635
  field: string,
585
636
  pipelineState: PipelineState,
637
+ schema?: readonly ParamSchemaEntry[],
586
638
  ): TextureView {
639
+ const parameter = schema?.find((entry) => entry.name === field);
640
+ if (parameter?.type === 'texture_cube') {
641
+ // The renderer already owns a valid white cube for the Skylight fallback.
642
+ // Use it until a receipt-promoted RenderTarget source becomes available;
643
+ // binding a 2D view here would invalidate the custom cube material BGL.
644
+ return pipelineState.skylightFallback?.prefilterView ?? pipelineState.defaultWhiteTextureView;
645
+ }
587
646
  if (field === 'normalTexture') return pipelineState.defaultNormalTextureView;
588
647
  if (field === 'baseColorTexture') return pipelineState.fallbackTextureView;
589
648
  return pipelineState.defaultWhiteTextureView;
@@ -885,6 +944,11 @@ export interface PerSubmeshMaterialBgDeps {
885
944
  readonly materialSlice: number;
886
945
  readonly videoHighPerfAvailable: boolean;
887
946
  readonly skylightResources: SkylightBindGroupResources;
947
+ readonly resolveRenderTargetTextureSource?: RenderSystemRuntime['resolveRenderTargetTextureSource'];
948
+ readonly resolveReflectionProbeResources?: (
949
+ materialWorld: World,
950
+ entityKey: number,
951
+ ) => SkylightBindGroupResources | undefined;
888
952
  readonly materialBgShared: Map<string, WeakMap<object, unknown>>;
889
953
  /** Cross-frame final BG reuse keyed by the stable source material handle. */
890
954
  readonly materialBgAssemblyCache: Map<string, MaterialBgAssemblyCacheEntry>;
@@ -932,11 +996,15 @@ export function buildPerSubmeshMaterialBg(
932
996
  materialSlice,
933
997
  videoHighPerfAvailable,
934
998
  skylightResources,
999
+ resolveRenderTargetTextureSource,
1000
+ resolveReflectionProbeResources,
935
1001
  materialBgShared,
936
1002
  materialBgAssemblyCache,
937
1003
  transmissionBackdropView,
938
1004
  bindGroupCounts,
939
1005
  } = deps;
1006
+ const effectiveSkylightResources =
1007
+ resolveReflectionProbeResources?.(materialWorld, entityKey) ?? skylightResources;
940
1008
  const smMaterialHandle = submeshMaterial.materialHandle;
941
1009
  const materialCacheKey =
942
1010
  smMaterialHandle === undefined ? undefined : `${materialWorld.identity}:${smMaterialHandle}`;
@@ -955,7 +1023,7 @@ export function buildPerSubmeshMaterialBg(
955
1023
  submeshMaterial,
956
1024
  smMaterialBgl,
957
1025
  pipelineState.materialUniformBuffer.buffer,
958
- skylightResources,
1026
+ effectiveSkylightResources,
959
1027
  store.materialResourceEpoch,
960
1028
  transmissionBackdropView,
961
1029
  )
@@ -1003,11 +1071,25 @@ export function buildPerSubmeshMaterialBg(
1003
1071
  const textureBinding = samplerBinding + 1;
1004
1072
  let smView: TextureView =
1005
1073
  field !== undefined
1006
- ? defaultViewForUserRegionField(field, pipelineState)
1074
+ ? defaultViewForUserRegionField(field, pipelineState, smSchema)
1007
1075
  : pipelineState.defaultWhiteTextureView;
1008
1076
  const smVideoClip =
1009
1077
  field !== undefined ? submeshMaterial.videoTextureFields?.get(field) : undefined;
1010
- if (smVideoClip !== undefined) {
1078
+ const smTargetSource =
1079
+ field === undefined
1080
+ ? undefined
1081
+ : materialRenderTargetSourceForField(
1082
+ submeshMaterial,
1083
+ field,
1084
+ resolveRenderTargetTextureSource,
1085
+ );
1086
+ if (smTargetSource !== undefined) {
1087
+ // The Renderer host resolves the generation-checked physical view. A
1088
+ // candidate is never exposed here; until its matching FrameReceipt
1089
+ // completes, the material remains on the neutral fallback.
1090
+ if (smTargetSource.textureView !== undefined) smView = smTargetSource.textureView;
1091
+ else materialResourcesResident = false;
1092
+ } else if (smVideoClip !== undefined) {
1011
1093
  const view = videoTextureView(
1012
1094
  materialWorld,
1013
1095
  runtime.dynamicTextureStore,
@@ -1049,12 +1131,16 @@ export function buildPerSubmeshMaterialBg(
1049
1131
  // authored UV textures and is not a backdrop sampler.
1050
1132
  const transmissionSampler =
1051
1133
  transmissionBackdropView != null
1052
- ? skylightResources.prefilterSampler
1134
+ ? effectiveSkylightResources.prefilterSampler
1053
1135
  : pipelineState.defaultSampler;
1054
- const smMergedEntries = assembleMaterialWithSkylightEntries(smBaseEntries, skylightResources, {
1055
- sampler: transmissionSampler,
1056
- ...(transmissionBackdropView === undefined ? {} : { backdropView: transmissionBackdropView }),
1057
- } satisfies TransmissionBindGroupResources);
1136
+ const smMergedEntries = assembleMaterialWithSkylightEntries(
1137
+ smBaseEntries,
1138
+ effectiveSkylightResources,
1139
+ {
1140
+ sampler: transmissionSampler,
1141
+ ...(transmissionBackdropView === undefined ? {} : { backdropView: transmissionBackdropView }),
1142
+ } satisfies TransmissionBindGroupResources,
1143
+ );
1058
1144
  const createBindGroup = (): BindGroup => {
1059
1145
  const result = runtime.device.createBindGroup({
1060
1146
  label: 'pbr-material-skylight-bg',
@@ -1086,7 +1172,7 @@ export function buildPerSubmeshMaterialBg(
1086
1172
  materialResourceEpoch: store.materialResourceEpoch,
1087
1173
  materialBgl: smMaterialBgl,
1088
1174
  materialBuffer: pipelineState.materialUniformBuffer.buffer,
1089
- skylightResources,
1175
+ skylightResources: effectiveSkylightResources,
1090
1176
  bindGroup: smBindGroup,
1091
1177
  });
1092
1178
  }