@forgeax/engine-render 0.1.19 → 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 (452) 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 +34 -20
  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-RJRUS55L.mjs → chunk-5YADUCA7.mjs} +1896 -1624
  21. package/dist/chunk-5YADUCA7.mjs.map +1 -0
  22. package/dist/{chunk-LOSO4CTV.mjs → chunk-6OPXDQ2E.mjs} +423 -46
  23. package/dist/chunk-6OPXDQ2E.mjs.map +1 -0
  24. package/dist/{chunk-IPXCABEP.mjs → chunk-BXBQQKFQ.mjs} +3 -9
  25. package/dist/chunk-BXBQQKFQ.mjs.map +1 -0
  26. package/dist/chunk-FS2KNUQH.mjs +1294 -0
  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/components/spot-light.d.ts +12 -0
  42. package/dist/components/spot-light.d.ts.map +1 -1
  43. package/dist/construct-renderer.mjs +8552 -1612
  44. package/dist/construct-renderer.mjs.map +1 -1
  45. package/dist/device/gpu-residency.d.ts +19 -2
  46. package/dist/device/gpu-residency.d.ts.map +1 -1
  47. package/dist/environment/frame.d.ts.map +1 -1
  48. package/dist/environment/lifecycle.d.ts +5 -0
  49. package/dist/environment/lifecycle.d.ts.map +1 -1
  50. package/dist/errors/render.d.ts +209 -93
  51. package/dist/errors/render.d.ts.map +1 -1
  52. package/dist/extract/directional-shadow-projection.d.ts +23 -0
  53. package/dist/extract/directional-shadow-projection.d.ts.map +1 -0
  54. package/dist/extract/environment.d.ts +14 -0
  55. package/dist/extract/environment.d.ts.map +1 -1
  56. package/dist/extract/visibility.d.ts.map +1 -1
  57. package/dist/features/fullscreen.d.ts.map +1 -1
  58. package/dist/features/targets.d.ts.map +1 -1
  59. package/dist/features/types.d.ts +10 -0
  60. package/dist/features/types.d.ts.map +1 -1
  61. package/dist/fullscreen-post-process-pass.d.ts +13 -2
  62. package/dist/fullscreen-post-process-pass.d.ts.map +1 -1
  63. package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
  64. package/dist/gpu-driven/production-raster.d.ts +0 -1
  65. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  66. package/dist/gpu-scene.d.ts.map +1 -1
  67. package/dist/gpu-usage.d.ts +2 -0
  68. package/dist/gpu-usage.d.ts.map +1 -1
  69. package/dist/hdrp-buffers.d.ts +19 -17
  70. package/dist/hdrp-buffers.d.ts.map +1 -1
  71. package/dist/ibl/IblPipelineCache.d.ts +4 -0
  72. package/dist/ibl/IblPipelineCache.d.ts.map +1 -1
  73. package/dist/ibl/kernel-cache.d.ts +36 -0
  74. package/dist/ibl/kernel-cache.d.ts.map +1 -0
  75. package/dist/index.d.ts +35 -4
  76. package/dist/index.d.ts.map +1 -1
  77. package/dist/index.mjs +4 -4
  78. package/dist/index.mjs.map +1 -1
  79. package/dist/inspection-types.d.ts +51 -0
  80. package/dist/inspection-types.d.ts.map +1 -1
  81. package/dist/instances-derived-bounds.d.ts +50 -0
  82. package/dist/instances-derived-bounds.d.ts.map +1 -0
  83. package/dist/internal.mjs +1 -1
  84. package/dist/light-buffer-layout.d.ts +30 -13
  85. package/dist/light-buffer-layout.d.ts.map +1 -1
  86. package/dist/mesh-material-bindings.d.ts +54 -0
  87. package/dist/mesh-material-bindings.d.ts.map +1 -1
  88. package/dist/pbr-pipeline.d.ts +20 -7
  89. package/dist/pbr-pipeline.d.ts.map +1 -1
  90. package/dist/pipeline/standard-forward-lane.d.ts +2 -1
  91. package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
  92. package/dist/pipeline/standard-lighting/errors.d.ts +4 -0
  93. package/dist/pipeline/standard-lighting/errors.d.ts.map +1 -0
  94. package/dist/pipeline/standard-lighting/graph.d.ts +24 -0
  95. package/dist/pipeline/standard-lighting/graph.d.ts.map +1 -0
  96. package/dist/pipeline/standard-lighting/inspection.d.ts +38 -0
  97. package/dist/pipeline/standard-lighting/inspection.d.ts.map +1 -0
  98. package/dist/pipeline/standard-lighting/layout.d.ts +28 -0
  99. package/dist/pipeline/standard-lighting/layout.d.ts.map +1 -0
  100. package/dist/pipeline/standard-lighting/light-frame.d.ts +33 -0
  101. package/dist/pipeline/standard-lighting/light-frame.d.ts.map +1 -0
  102. package/dist/pipeline/standard-lighting/membership.d.ts +14 -0
  103. package/dist/pipeline/standard-lighting/membership.d.ts.map +1 -0
  104. package/dist/pipeline/standard-lighting/prepare.d.ts +16 -0
  105. package/dist/pipeline/standard-lighting/prepare.d.ts.map +1 -0
  106. package/dist/pipeline/standard-lighting/topology.d.ts +45 -0
  107. package/dist/pipeline/standard-lighting/topology.d.ts.map +1 -0
  108. package/dist/pipeline/standard-lighting/transport.d.ts +36 -0
  109. package/dist/pipeline/standard-lighting/transport.d.ts.map +1 -0
  110. package/dist/pipeline/standard-pipeline.d.ts +4 -13
  111. package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
  112. package/dist/pipeline/standard-post.d.ts.map +1 -1
  113. package/dist/pipeline/standard-profile.d.ts +12 -20
  114. package/dist/pipeline/standard-profile.d.ts.map +1 -1
  115. package/dist/pipeline-spec.d.ts +40 -7
  116. package/dist/pipeline-spec.d.ts.map +1 -1
  117. package/dist/point-shadow-inspection.d.ts +21 -0
  118. package/dist/point-shadow-inspection.d.ts.map +1 -0
  119. package/dist/record/frame-lighting.d.ts +29 -32
  120. package/dist/record/frame-lighting.d.ts.map +1 -1
  121. package/dist/record/frame-snapshot.d.ts +65 -15
  122. package/dist/record/frame-snapshot.d.ts.map +1 -1
  123. package/dist/record/frame-targets.d.ts +5 -0
  124. package/dist/record/frame-targets.d.ts.map +1 -1
  125. package/dist/record/frame.d.ts +12 -5
  126. package/dist/record/frame.d.ts.map +1 -1
  127. package/dist/record/gpu-pass-timing/contract.d.ts +96 -0
  128. package/dist/record/gpu-pass-timing/contract.d.ts.map +1 -0
  129. package/dist/record/gpu-pass-timing/errors.d.ts +18 -0
  130. package/dist/record/gpu-pass-timing/errors.d.ts.map +1 -0
  131. package/dist/record/gpu-pass-timing/index.d.ts +8 -0
  132. package/dist/record/gpu-pass-timing/index.d.ts.map +1 -0
  133. package/dist/record/gpu-pass-timing/parser.d.ts +11 -0
  134. package/dist/record/gpu-pass-timing/parser.d.ts.map +1 -0
  135. package/dist/record/gpu-pass-timing/session.d.ts +43 -0
  136. package/dist/record/gpu-pass-timing/session.d.ts.map +1 -0
  137. package/dist/record/gpu-timing.d.ts +37 -0
  138. package/dist/record/gpu-timing.d.ts.map +1 -0
  139. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  140. package/dist/record/main-pass-material.d.ts +23 -2
  141. package/dist/record/main-pass-material.d.ts.map +1 -1
  142. package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
  143. package/dist/record/main-pass.d.ts +2 -0
  144. package/dist/record/main-pass.d.ts.map +1 -1
  145. package/dist/record/render-context.d.ts +48 -3
  146. package/dist/record/render-context.d.ts.map +1 -1
  147. package/dist/record/shadow-pass.d.ts.map +1 -1
  148. package/dist/record/skybox-post-pass.d.ts.map +1 -1
  149. package/dist/record/typed-frame-graph.d.ts +81 -5
  150. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  151. package/dist/record/view-ubo.d.ts +7 -4
  152. package/dist/record/view-ubo.d.ts.map +1 -1
  153. package/dist/reflection/filter.d.ts +22 -0
  154. package/dist/reflection/filter.d.ts.map +1 -0
  155. package/dist/reflection/gpu-table.d.ts +25 -0
  156. package/dist/reflection/gpu-table.d.ts.map +1 -0
  157. package/dist/reflection/projection.d.ts +91 -0
  158. package/dist/reflection/projection.d.ts.map +1 -0
  159. package/dist/render-contract.d.ts +114 -7
  160. package/dist/render-contract.d.ts.map +1 -1
  161. package/dist/render-data.d.ts +10 -6
  162. package/dist/render-data.d.ts.map +1 -1
  163. package/dist/render-graph-primitives.d.ts +1 -0
  164. package/dist/render-graph-primitives.d.ts.map +1 -1
  165. package/dist/render-pipeline.d.ts +78 -2
  166. package/dist/render-pipeline.d.ts.map +1 -1
  167. package/dist/render-system-extract.d.ts +113 -20
  168. package/dist/render-system-extract.d.ts.map +1 -1
  169. package/dist/render-system.d.ts +24 -3
  170. package/dist/render-system.d.ts.map +1 -1
  171. package/dist/renderer-inspect.d.ts +29 -0
  172. package/dist/renderer-inspect.d.ts.map +1 -0
  173. package/dist/renderer.d.ts +43 -0
  174. package/dist/renderer.d.ts.map +1 -0
  175. package/dist/scene/render-scene.d.ts +14 -0
  176. package/dist/scene/render-scene.d.ts.map +1 -1
  177. package/dist/shadow-atlas.d.ts +3 -3
  178. package/dist/shadow-atlas.d.ts.map +1 -1
  179. package/dist/ssao-buffers.d.ts +3 -0
  180. package/dist/ssao-buffers.d.ts.map +1 -1
  181. package/dist/targets/contracts.d.ts +66 -0
  182. package/dist/targets/contracts.d.ts.map +1 -0
  183. package/dist/targets/graph-projection.d.ts +29 -0
  184. package/dist/targets/graph-projection.d.ts.map +1 -0
  185. package/dist/targets/material-source.d.ts +34 -0
  186. package/dist/targets/material-source.d.ts.map +1 -0
  187. package/dist/targets/owner.d.ts +32 -0
  188. package/dist/targets/owner.d.ts.map +1 -0
  189. package/dist/targets/physical.d.ts +18 -0
  190. package/dist/targets/physical.d.ts.map +1 -0
  191. package/dist/targets/readback.d.ts +39 -0
  192. package/dist/targets/readback.d.ts.map +1 -0
  193. package/dist/temporal/frame.d.ts +44 -0
  194. package/dist/temporal/frame.d.ts.map +1 -0
  195. package/dist/temporal/index.mjs +3 -3
  196. package/dist/typed-render-graph-primitives.d.ts +6 -1
  197. package/dist/typed-render-graph-primitives.d.ts.map +1 -1
  198. package/dist/typed-shadow-atlas.d.ts +10 -0
  199. package/dist/typed-shadow-atlas.d.ts.map +1 -0
  200. package/dist/typed-shadow-passes.d.ts +1 -1
  201. package/dist/typed-shadow-passes.d.ts.map +1 -1
  202. package/dist/volume/capability.d.ts +57 -0
  203. package/dist/volume/capability.d.ts.map +1 -0
  204. package/dist/volume/component.d.ts +61 -0
  205. package/dist/volume/component.d.ts.map +1 -0
  206. package/dist/volume/extract.d.ts +11 -0
  207. package/dist/volume/extract.d.ts.map +1 -0
  208. package/dist/volume/inspection.d.ts +38 -0
  209. package/dist/volume/inspection.d.ts.map +1 -0
  210. package/dist/volume/passes.d.ts +38 -0
  211. package/dist/volume/passes.d.ts.map +1 -0
  212. package/dist/volume/recovery.d.ts +38 -0
  213. package/dist/volume/recovery.d.ts.map +1 -0
  214. package/dist/volume/resources.d.ts +57 -0
  215. package/dist/volume/resources.d.ts.map +1 -0
  216. package/dist/volume/temporal.d.ts +35 -0
  217. package/dist/volume/temporal.d.ts.map +1 -0
  218. package/package.json +20 -20
  219. package/src/__tests__/bloom-exact-zero.unit.test.ts +48 -19
  220. package/src/__tests__/bloom-lifecycle.integration.test.ts +41 -13
  221. package/src/__tests__/camera-target-consumption.unit.test.ts +53 -0
  222. package/src/__tests__/cluster-binner.unit.test.ts +35 -0
  223. package/src/__tests__/cluster-grid-default-surface.unit.test.ts +7 -2
  224. package/src/__tests__/cube-camera-scheduler.unit.test.ts +70 -0
  225. package/src/__tests__/cube-camera-single-submit.perf.test.ts +75 -0
  226. package/src/__tests__/cube-camera-view-matrices.unit.test.ts +41 -0
  227. package/src/__tests__/cube-camera-vocabulary.unit.test.ts +47 -0
  228. package/src/__tests__/cube-camera.integration.test.ts +42 -0
  229. package/src/__tests__/device-feature-admission.unit.test.ts +73 -0
  230. package/src/__tests__/direct-light-snapshot.unit.test.ts +3 -3
  231. package/src/__tests__/docs-gate-green.unit.test.ts +38 -0
  232. package/src/__tests__/factory-contract.integration.test.ts +255 -29
  233. package/src/__tests__/fog-component.unit.test.ts +67 -0
  234. package/src/__tests__/fog.integration.test.ts +86 -0
  235. package/src/__tests__/fullscreen-feature-plan.unit.test.ts +37 -0
  236. package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +10 -9
  237. package/src/__tests__/gpu-driven-indirect-raster.dawn.test.ts +17 -1
  238. package/src/__tests__/gpu-driven-production.integration.test.ts +16 -17
  239. package/src/__tests__/gpu-driven-view-graph.integration.test.ts +12 -4
  240. package/src/__tests__/gpu-pass-timing-browser-runner.ts +281 -0
  241. package/src/__tests__/gpu-pass-timing-coexistence.integration.test.ts +80 -0
  242. package/src/__tests__/gpu-pass-timing-contract.unit.test.ts +243 -0
  243. package/src/__tests__/gpu-pass-timing-exact-zero.unit.test.ts +131 -0
  244. package/src/__tests__/gpu-pass-timing-lifecycle.integration.test.ts +230 -0
  245. package/src/__tests__/gpu-pass-timing-observation.unit.test.ts +233 -0
  246. package/src/__tests__/gpu-pass-timing-session.unit.test.ts +438 -0
  247. package/src/__tests__/gpu-pass-timing.browser.test.ts +26 -0
  248. package/src/__tests__/gpu-pass-timing.rhi-null.unit.test.ts +125 -0
  249. package/src/__tests__/hdrp-group2-visibility.unit.test.ts +24 -0
  250. package/src/__tests__/instances-culling.integration.test.ts +94 -0
  251. package/src/__tests__/instances-derived-bounds.unit.test.ts +118 -0
  252. package/src/__tests__/ktx2-basis-gpu-consumer.dawn.test.ts +14 -14
  253. package/src/__tests__/material-observation.integration.test.ts +101 -0
  254. package/src/__tests__/mesh-buffer-usage-surface.unit.test.ts +1 -1
  255. package/src/__tests__/pbr-view-layout.unit.test.ts +16 -0
  256. package/src/__tests__/point-shadow-inspection.unit.test.ts +60 -0
  257. package/src/__tests__/public-surface.test-d.ts +10 -0
  258. package/src/__tests__/reflection-probe-authoring.unit.test.ts +71 -0
  259. package/src/__tests__/reflection-probe-filter.unit.test.ts +54 -0
  260. package/src/__tests__/reflection-probe-ibl-topology.unit.test.ts +45 -0
  261. package/src/__tests__/reflection-probe-pass-encode.integration.test.ts +101 -0
  262. package/src/__tests__/reflection-probe-performance.perf.test.ts +60 -0
  263. package/src/__tests__/reflection-probe-recovery.integration.test.ts +55 -0
  264. package/src/__tests__/reflection-probe-selection.unit.test.ts +46 -0
  265. package/src/__tests__/render-error-code-owner.test-d.ts +23 -3
  266. package/src/__tests__/render-feature-docs.unit.test.ts +1 -1
  267. package/src/__tests__/render-target-admission.unit.test.ts +80 -0
  268. package/src/__tests__/render-target-graph-projection.unit.test.ts +102 -0
  269. package/src/__tests__/render-target-inspection.unit.test.ts +52 -0
  270. package/src/__tests__/render-target-material-source.test-d.ts +30 -0
  271. package/src/__tests__/render-target-material-source.unit.test.ts +97 -0
  272. package/src/__tests__/render-target-owner-placement.unit.test.ts +203 -0
  273. package/src/__tests__/render-target-owner.unit.test.ts +122 -0
  274. package/src/__tests__/render-target-probe-soak.perf.test.ts +94 -0
  275. package/src/__tests__/render-target-public-schema.json +107 -0
  276. package/src/__tests__/render-target-public-schema.unit.test.ts +91 -0
  277. package/src/__tests__/render-target-public-surface.test-d.ts +83 -0
  278. package/src/__tests__/render-target-readback.integration.test.ts +90 -0
  279. package/src/__tests__/render-target-recovery.integration.test.ts +85 -0
  280. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +76 -0
  281. package/src/__tests__/renderer-frame-transaction.integration.test.ts +1 -1
  282. package/src/__tests__/renderer-lifecycle.integration.test.ts +14 -0
  283. package/src/__tests__/shader-stage-surface.unit.test.ts +19 -4
  284. package/src/__tests__/skin-motion-regression.unit.test.ts +132 -0
  285. package/src/__tests__/skinned-shadow-caster.test.ts +32 -2
  286. package/src/__tests__/standard-cluster-capability.integration.test.ts +63 -0
  287. package/src/__tests__/standard-cluster-transport.unit.test.ts +121 -0
  288. package/src/__tests__/standard-cluster-variant-resolution.unit.test.ts +232 -0
  289. package/src/__tests__/standard-clustered-pipeline.unit.test.ts +8 -5
  290. package/src/__tests__/standard-lighting-inspection.integration.test.ts +138 -0
  291. package/src/__tests__/standard-lighting-ssot.unit.test.ts +18 -0
  292. package/src/__tests__/standard-lighting-write.unit.test.ts +377 -0
  293. package/src/__tests__/standard-lighting.integration.test.ts +67 -0
  294. package/src/__tests__/standard-pipeline.dawn.test.ts +206 -5
  295. package/src/__tests__/standard-pipeline.integration.test.ts +181 -150
  296. package/src/__tests__/standard-probe-binding.unit.test.ts +25 -0
  297. package/src/__tests__/standard-profile-oracle.unit.test.ts +23 -36
  298. package/src/__tests__/typed-pipeline-topology.unit.test.ts +395 -14
  299. package/src/__tests__/urp-pipeline-targets.unit.test.ts +2 -2
  300. package/src/__tests__/visibility-resolver.unit.test.ts +15 -0
  301. package/src/__tests__/volume-optics-oracle.unit.test.ts +85 -0
  302. package/src/__tests__/volumetric-fog-budget-recovery.integration.test.ts +655 -0
  303. package/src/__tests__/volumetric-fog-component.unit.test.ts +71 -0
  304. package/src/__tests__/volumetric-fog-composite-stage-readback.dawn.test.ts +376 -0
  305. package/src/__tests__/volumetric-fog-errors.unit.test.ts +51 -0
  306. package/src/__tests__/volumetric-fog-inspection.unit.test.ts +77 -0
  307. package/src/__tests__/volumetric-fog-light-capability.unit.test.ts +19 -0
  308. package/src/__tests__/volumetric-fog-light-consumers.integration.test.ts +18 -0
  309. package/src/__tests__/volumetric-fog-light-errors.unit.test.ts +17 -0
  310. package/src/__tests__/volumetric-fog-light-topology.unit.test.ts +21 -0
  311. package/src/__tests__/volumetric-fog-light-transaction.integration.test.ts +53 -0
  312. package/src/__tests__/volumetric-fog-m9-watcher.integration.test.ts +27 -0
  313. package/src/__tests__/volumetric-fog-point-light-owner.integration.test.ts +44 -0
  314. package/src/__tests__/volumetric-fog-point-spot-accumulation.integration.test.ts +49 -0
  315. package/src/__tests__/volumetric-fog-point-spot-contract.unit.test.ts +28 -0
  316. package/src/__tests__/volumetric-fog-point-spot-lkg.integration.test.ts +65 -0
  317. package/src/__tests__/volumetric-fog-point-spot-selection.integration.test.ts +49 -0
  318. package/src/__tests__/volumetric-fog-point-spot-temporal.unit.test.ts +55 -0
  319. package/src/__tests__/volumetric-fog-projector-contract.unit.test.ts +22 -0
  320. package/src/__tests__/volumetric-fog-projector-inspection.unit.test.ts +12 -0
  321. package/src/__tests__/volumetric-fog-projector-surface-volume.integration.test.ts +11 -0
  322. package/src/__tests__/volumetric-fog-projector-transaction.integration.test.ts +26 -0
  323. package/src/__tests__/volumetric-fog-recovery.integration.test.ts +47 -0
  324. package/src/__tests__/volumetric-fog-selected-light-resolution.integration.test.ts +63 -0
  325. package/src/__tests__/volumetric-fog-spot-closure.integration.test.ts +26 -0
  326. package/src/__tests__/volumetric-fog-spot-contract.test-d.ts +21 -0
  327. package/src/__tests__/volumetric-fog-spot-contract.unit.test.ts +27 -0
  328. package/src/__tests__/volumetric-fog-spot-frame-optics.integration.test.ts +14 -0
  329. package/src/__tests__/volumetric-fog-spot-optics.integration.test.ts +29 -0
  330. package/src/__tests__/volumetric-fog-stage-readback.dawn.test.ts +550 -0
  331. package/src/__tests__/volumetric-fog-temporal-selected-light.unit.test.ts +33 -0
  332. package/src/__tests__/volumetric-fog-temporal.unit.test.ts +45 -0
  333. package/src/__tests__/volumetric-fog-topology.unit.test.ts +237 -0
  334. package/src/__tests__/volumetric-fog-transparent.integration.test.ts +27 -0
  335. package/src/assembly/device-feature-admission.ts +73 -0
  336. package/src/assembly/directional-shadow-inspection.ts +122 -0
  337. package/src/assembly/factory.ts +1093 -246
  338. package/src/assembly/host-contract.ts +25 -0
  339. package/src/assembly/render-target-host.ts +847 -0
  340. package/src/assets/asset-decoders.ts +1 -1
  341. package/src/assets/render-asset-port.ts +7 -1
  342. package/src/capture/cube-views.ts +69 -0
  343. package/src/capture/scheduler.ts +265 -0
  344. package/src/components/__tests__/schema-contract.unit.test.ts +12 -0
  345. package/src/components/__tests__/schema-parity.unit.test.ts +9 -0
  346. package/src/components/camera.ts +4 -0
  347. package/src/components/cube-camera.ts +52 -0
  348. package/src/components/directional-light.ts +22 -2
  349. package/src/components/directional-shadow-filter.ts +57 -0
  350. package/src/components/index.ts +2 -0
  351. package/src/components/light-helpers.ts +85 -7
  352. package/src/components/reflection-probe.ts +49 -0
  353. package/src/components/spot-light.ts +22 -3
  354. package/src/device/gpu-residency.ts +168 -58
  355. package/src/environment/frame.ts +6 -24
  356. package/src/environment/lifecycle.ts +12 -0
  357. package/src/errors/render.ts +405 -143
  358. package/src/extract/directional-shadow-projection.ts +61 -0
  359. package/src/extract/environment.ts +57 -0
  360. package/src/extract/visibility.ts +25 -1
  361. package/src/features/fullscreen.ts +3 -0
  362. package/src/features/targets.ts +4 -1
  363. package/src/features/types.ts +10 -0
  364. package/src/fullscreen-post-process-pass.ts +43 -2
  365. package/src/gpu-driven/batch-topology.ts +1 -0
  366. package/src/gpu-driven/production-raster.ts +1 -3
  367. package/src/gpu-scene.ts +8 -1
  368. package/src/gpu-usage.ts +2 -0
  369. package/src/hdrp-buffers.ts +165 -59
  370. package/src/ibl/IblPipelineCache.ts +11 -0
  371. package/src/ibl/kernel-cache.ts +88 -0
  372. package/src/index.ts +186 -1
  373. package/src/inspection-types.ts +56 -0
  374. package/src/instances-derived-bounds.ts +171 -0
  375. package/src/light-buffer-layout.ts +74 -26
  376. package/src/mesh-material-bindings.ts +132 -0
  377. package/src/pbr-pipeline.ts +139 -27
  378. package/src/pipeline/standard-forward-lane.ts +65 -15
  379. package/src/pipeline/standard-lighting/errors.ts +6 -0
  380. package/src/pipeline/standard-lighting/graph.ts +157 -0
  381. package/src/pipeline/standard-lighting/inspection.ts +88 -0
  382. package/src/pipeline/standard-lighting/layout.ts +31 -0
  383. package/src/pipeline/standard-lighting/light-frame.ts +34 -0
  384. package/src/pipeline/standard-lighting/membership.ts +43 -0
  385. package/src/pipeline/standard-lighting/prepare.ts +37 -0
  386. package/src/pipeline/standard-lighting/topology.ts +120 -0
  387. package/src/pipeline/standard-lighting/transport.ts +77 -0
  388. package/src/pipeline/standard-pipeline.ts +141 -192
  389. package/src/pipeline/standard-post.ts +26 -7
  390. package/src/pipeline/standard-profile.ts +23 -30
  391. package/src/pipeline-spec.ts +84 -21
  392. package/src/point-shadow-inspection.ts +55 -0
  393. package/src/record/__tests__/temporal-payload.unit.test.ts +42 -1
  394. package/src/record/__tests__/transparent-group2-selection.unit.test.ts +73 -0
  395. package/src/record/__tests__/view-ubo-layout.unit.test.ts +40 -0
  396. package/src/record/frame-lighting.ts +470 -516
  397. package/src/record/frame-snapshot.ts +76 -28
  398. package/src/record/frame-targets.ts +23 -2
  399. package/src/record/frame.ts +1169 -79
  400. package/src/record/gpu-pass-timing/contract.ts +180 -0
  401. package/src/record/gpu-pass-timing/errors.ts +34 -0
  402. package/src/record/gpu-pass-timing/index.ts +37 -0
  403. package/src/record/gpu-pass-timing/parser.ts +97 -0
  404. package/src/record/gpu-pass-timing/session.ts +938 -0
  405. package/src/record/gpu-timing.ts +233 -0
  406. package/src/record/main-pass-geometry.ts +120 -80
  407. package/src/record/main-pass-material.ts +101 -11
  408. package/src/record/main-pass-sprite-draws.ts +54 -25
  409. package/src/record/main-pass.ts +71 -15
  410. package/src/record/render-context.ts +77 -0
  411. package/src/record/shadow-pass.ts +104 -66
  412. package/src/record/skybox-post-pass.ts +20 -4
  413. package/src/record/typed-frame-graph.ts +1016 -32
  414. package/src/record/view-ubo.ts +63 -28
  415. package/src/reflection/filter.ts +81 -0
  416. package/src/reflection/gpu-table.ts +44 -0
  417. package/src/reflection/projection.ts +281 -0
  418. package/src/render-contract.ts +147 -4
  419. package/src/render-data.ts +50 -22
  420. package/src/render-graph-primitives.ts +9 -4
  421. package/src/render-pipeline.ts +161 -2
  422. package/src/render-system-extract.ts +886 -103
  423. package/src/render-system.ts +1174 -66
  424. package/src/renderer-inspect.ts +66 -0
  425. package/src/renderer.ts +222 -0
  426. package/src/scene/render-scene.ts +117 -9
  427. package/src/shadow-atlas.ts +5 -5
  428. package/src/ssao-buffers.ts +9 -2
  429. package/src/targets/contracts.ts +182 -0
  430. package/src/targets/graph-projection.ts +169 -0
  431. package/src/targets/material-source.ts +143 -0
  432. package/src/targets/owner.ts +214 -0
  433. package/src/targets/physical.ts +18 -0
  434. package/src/targets/readback.ts +153 -0
  435. package/src/temporal/frame.ts +128 -0
  436. package/src/transmission/__tests__/standard-transmission.dawn.test.ts +61 -23
  437. package/src/typed-render-graph-primitives.ts +34 -2
  438. package/src/typed-shadow-atlas.ts +17 -0
  439. package/src/typed-shadow-passes.ts +47 -40
  440. package/src/volume/capability.ts +173 -0
  441. package/src/volume/component.ts +110 -0
  442. package/src/volume/extract.ts +21 -0
  443. package/src/volume/inspection.ts +78 -0
  444. package/src/volume/passes.ts +686 -0
  445. package/src/volume/recovery.ts +86 -0
  446. package/src/volume/resources.ts +236 -0
  447. package/src/volume/temporal.ts +89 -0
  448. package/dist/chunk-IPXCABEP.mjs.map +0 -1
  449. package/dist/chunk-LOSO4CTV.mjs.map +0 -1
  450. package/dist/chunk-RJRUS55L.mjs.map +0 -1
  451. package/dist/chunk-YTQ5C7YQ.mjs +0 -584
  452. package/dist/chunk-YTQ5C7YQ.mjs.map +0 -1
@@ -0,0 +1,233 @@
1
+ import type { Buffer, QuerySet, RhiCommandEncoder, RhiDevice, RhiError } from '@forgeax/engine-rhi';
2
+ import { ok, type Result } from '@forgeax/engine-types';
3
+ import {
4
+ GPU_BUFFER_USAGE_COPY_DST,
5
+ GPU_BUFFER_USAGE_COPY_SRC,
6
+ GPU_BUFFER_USAGE_MAP_READ,
7
+ GPU_BUFFER_USAGE_QUERY_RESOLVE,
8
+ } from '../gpu-usage';
9
+
10
+ const MAX_VOLUME_PASSES = 4;
11
+ const QUERY_COUNT = MAX_VOLUME_PASSES * 2;
12
+ const QUERY_STRIDE_BYTES = 256;
13
+ const READBACK_BYTES = QUERY_COUNT * QUERY_STRIDE_BYTES;
14
+ const PASS_NAMES = [
15
+ 'volume-inject',
16
+ 'volume-integrate',
17
+ 'volume-temporal',
18
+ 'volume-composite',
19
+ ] as const;
20
+
21
+ export type VolumeTimingObservation =
22
+ | {
23
+ readonly status: 'ready';
24
+ readonly unit: 'ms';
25
+ readonly totalMs: number;
26
+ readonly passes: readonly { readonly name: string; readonly milliseconds: number }[];
27
+ }
28
+ | {
29
+ readonly status: 'unavailable';
30
+ readonly reason: string;
31
+ };
32
+
33
+ function unavailable(reason: string): VolumeTimingObservation {
34
+ return Object.freeze({ status: 'unavailable', reason });
35
+ }
36
+
37
+ export interface VolumeTimingTimestampWrites {
38
+ readonly querySet: QuerySet;
39
+ readonly beginningOfPassWriteIndex: number;
40
+ readonly endOfPassWriteIndex: number;
41
+ }
42
+
43
+ /**
44
+ * Renderer-owned timestamp capture for the authored volume graph. Query
45
+ * resources are private to one accepted submit and never cross the receipt
46
+ * boundary. This publishes raw renderer timings; a benchmark runner owns
47
+ * runner class, frame-count, and threshold qualification.
48
+ */
49
+ export class GpuTimingCapture {
50
+ readonly #device: RhiDevice;
51
+ readonly #querySet: QuerySet;
52
+ readonly #resolveBuffer: Buffer;
53
+ readonly #readbackBuffer: Buffer;
54
+ readonly #periodNanoseconds: number;
55
+ readonly #started = new Map<string, number>();
56
+ readonly #finished = new Map<string, number>();
57
+ #resolved = false;
58
+ #submitted = false;
59
+ #discarded = false;
60
+ #disposed = false;
61
+ #observation: Promise<VolumeTimingObservation> | undefined;
62
+
63
+ private constructor(
64
+ device: RhiDevice,
65
+ querySet: QuerySet,
66
+ resolveBuffer: Buffer,
67
+ readbackBuffer: Buffer,
68
+ periodNanoseconds: number,
69
+ ) {
70
+ this.#device = device;
71
+ this.#querySet = querySet;
72
+ this.#resolveBuffer = resolveBuffer;
73
+ this.#readbackBuffer = readbackBuffer;
74
+ this.#periodNanoseconds = periodNanoseconds;
75
+ }
76
+
77
+ static create(device: RhiDevice): Result<GpuTimingCapture | undefined, RhiError> {
78
+ const period = device.caps.timestampPeriodNanoseconds;
79
+ if (!device.caps.timestampQuery || period === null || !Number.isFinite(period) || period <= 0) {
80
+ return ok(undefined);
81
+ }
82
+ const querySet = device.createQuerySet({
83
+ label: 'volume-timing',
84
+ type: 'timestamp',
85
+ count: QUERY_COUNT,
86
+ });
87
+ if (!querySet.ok) return querySet;
88
+ const resolveBuffer = device.createBuffer({
89
+ label: 'volume-timing.resolve',
90
+ size: READBACK_BYTES,
91
+ usage: GPU_BUFFER_USAGE_QUERY_RESOLVE | GPU_BUFFER_USAGE_COPY_SRC,
92
+ });
93
+ if (!resolveBuffer.ok) {
94
+ device.destroyQuerySet(querySet.value);
95
+ return resolveBuffer;
96
+ }
97
+ const readbackBuffer = device.createBuffer({
98
+ label: 'volume-timing.readback',
99
+ size: READBACK_BYTES,
100
+ usage: GPU_BUFFER_USAGE_MAP_READ | GPU_BUFFER_USAGE_COPY_DST,
101
+ });
102
+ if (!readbackBuffer.ok) {
103
+ device.destroyBuffer(resolveBuffer.value);
104
+ device.destroyQuerySet(querySet.value);
105
+ return readbackBuffer;
106
+ }
107
+ return ok(
108
+ new GpuTimingCapture(
109
+ device,
110
+ querySet.value,
111
+ resolveBuffer.value,
112
+ readbackBuffer.value,
113
+ period,
114
+ ),
115
+ );
116
+ }
117
+
118
+ recordPass(name: string): VolumeTimingTimestampWrites | undefined {
119
+ const index = PASS_NAMES.indexOf(name as (typeof PASS_NAMES)[number]);
120
+ if (index < 0 || this.#started.has(name) || this.#discarded) return undefined;
121
+ this.#started.set(name, index);
122
+ this.#finished.set(name, index);
123
+ return {
124
+ querySet: this.#querySet,
125
+ beginningOfPassWriteIndex: index * 2,
126
+ endOfPassWriteIndex: index * 2 + 1,
127
+ };
128
+ }
129
+
130
+ skipPass(name: string): void {
131
+ this.#started.delete(name);
132
+ this.#finished.delete(name);
133
+ }
134
+
135
+ resolve(encoder: RhiCommandEncoder): Result<void, RhiError> {
136
+ if (this.#started.size === 0) {
137
+ this.#resolved = true;
138
+ return ok(undefined);
139
+ }
140
+ const resolved = encoder.resolveQuerySet(
141
+ this.#querySet,
142
+ 0,
143
+ QUERY_COUNT,
144
+ this.#resolveBuffer,
145
+ 0,
146
+ );
147
+ if (!resolved.ok) return resolved;
148
+ encoder.copyBufferToBuffer(this.#resolveBuffer, this.#readbackBuffer, READBACK_BYTES);
149
+ this.#resolved = true;
150
+ return ok(undefined);
151
+ }
152
+
153
+ markSubmitted(): void {
154
+ if (!this.#discarded) this.#submitted = true;
155
+ }
156
+
157
+ discard(): void {
158
+ if (this.#discarded) return;
159
+ this.#discarded = true;
160
+ this.#resolved = true;
161
+ this.#submitted = false;
162
+ this.#dispose();
163
+ }
164
+
165
+ observation(): Promise<VolumeTimingObservation> {
166
+ if (this.#observation !== undefined) return this.#observation;
167
+ if (this.#discarded || !this.#resolved || !this.#submitted || this.#started.size === 0) {
168
+ this.#observation = Promise.resolve(
169
+ unavailable('timestamp capture did not reach an accepted volume submit'),
170
+ );
171
+ this.#dispose();
172
+ return this.#observation;
173
+ }
174
+ this.#observation = this.#readbackBuffer
175
+ .mapAsync(GPU_BUFFER_USAGE_MAP_READ)
176
+ .then((mapped) => {
177
+ if (!mapped.ok) return unavailable(`timestamp readback failed: ${mapped.error.code}`);
178
+ const range = mapped.value.getMappedRange();
179
+ if (!range.ok) {
180
+ mapped.value.unmap();
181
+ return unavailable(`timestamp mapping failed: ${range.error.code}`);
182
+ }
183
+ const values = new DataView(range.value);
184
+ const decoded = [...this.#finished.entries()]
185
+ .sort((a, b) => a[1] - b[1])
186
+ .map(([name, index]) => ({
187
+ name,
188
+ begin: values.getBigUint64(index * 2 * QUERY_STRIDE_BYTES, true),
189
+ end: values.getBigUint64((index * 2 + 1) * QUERY_STRIDE_BYTES, true),
190
+ }));
191
+ if (decoded.some(({ end, begin }) => end < begin)) {
192
+ mapped.value.unmap();
193
+ this.#dispose();
194
+ return unavailable('timestamp query returned an end before its begin');
195
+ }
196
+ const passes = decoded.map(({ name, begin, end }) => ({
197
+ name,
198
+ milliseconds: (Number(end - begin) * this.#periodNanoseconds) / 1_000_000,
199
+ }));
200
+ const firstBegin = decoded.reduce(
201
+ (first, sample) => (sample.begin < first ? sample.begin : first),
202
+ decoded[0]?.begin ?? 0n,
203
+ );
204
+ const lastEnd = decoded.reduce(
205
+ (last, sample) => (sample.end > last ? sample.end : last),
206
+ decoded[0]?.end ?? 0n,
207
+ );
208
+ const totalMs = (Number(lastEnd - firstBegin) * this.#periodNanoseconds) / 1_000_000;
209
+ mapped.value.unmap();
210
+ this.#dispose();
211
+ if (
212
+ !Number.isFinite(totalMs) ||
213
+ passes.some((pass) => !Number.isFinite(pass.milliseconds))
214
+ ) {
215
+ return unavailable('timestamp query returned a non-finite duration');
216
+ }
217
+ return Object.freeze({ status: 'ready', unit: 'ms', totalMs, passes });
218
+ })
219
+ .catch((cause: unknown) => {
220
+ this.#dispose();
221
+ return unavailable(`timestamp readback failed: ${String(cause)}`);
222
+ });
223
+ return this.#observation;
224
+ }
225
+
226
+ #dispose(): void {
227
+ if (this.#disposed) return;
228
+ this.#disposed = true;
229
+ this.#device.destroyBuffer(this.#resolveBuffer);
230
+ this.#device.destroyBuffer(this.#readbackBuffer);
231
+ this.#device.destroyQuerySet(this.#querySet);
232
+ }
233
+ }
@@ -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
  {