@forgeax/engine-render 0.1.21 → 0.1.23

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 (426) hide show
  1. package/README.md +150 -29
  2. package/dist/assembly/backend-contract.d.ts +13 -0
  3. package/dist/assembly/backend-contract.d.ts.map +1 -1
  4. package/dist/assembly/device-feature-admission.d.ts.map +1 -1
  5. package/dist/assembly/factory.d.ts +14 -6
  6. package/dist/assembly/factory.d.ts.map +1 -1
  7. package/dist/assembly/host-contract.d.ts +10 -0
  8. package/dist/assembly/host-contract.d.ts.map +1 -1
  9. package/dist/authoring.mjs +1 -1
  10. package/dist/{chunk-PCHBPYML.mjs → chunk-4L3P6DT5.mjs} +2 -2
  11. package/dist/chunk-4L3P6DT5.mjs.map +1 -0
  12. package/dist/{chunk-VFPLTKCU.mjs → chunk-4NPUASZR.mjs} +15 -4
  13. package/dist/chunk-4NPUASZR.mjs.map +1 -0
  14. package/dist/chunk-DIYW3M5D.mjs +5085 -0
  15. package/dist/chunk-DIYW3M5D.mjs.map +1 -0
  16. package/dist/chunk-FBC4R44J.mjs +1003 -0
  17. package/dist/chunk-FBC4R44J.mjs.map +1 -0
  18. package/dist/{chunk-FS2KNUQH.mjs → chunk-FF7K3FSO.mjs} +253 -489
  19. package/dist/chunk-FF7K3FSO.mjs.map +1 -0
  20. package/dist/chunk-HZUPSYW7.mjs +1104 -0
  21. package/dist/chunk-HZUPSYW7.mjs.map +1 -0
  22. package/dist/{chunk-6OPXDQ2E.mjs → chunk-QODA5UKU.mjs} +33 -294
  23. package/dist/chunk-QODA5UKU.mjs.map +1 -0
  24. package/dist/chunk-YALPS44A.mjs +296 -0
  25. package/dist/chunk-YALPS44A.mjs.map +1 -0
  26. package/dist/{chunk-5YADUCA7.mjs → chunk-ZUWKAZ2I.mjs} +585 -991
  27. package/dist/chunk-ZUWKAZ2I.mjs.map +1 -0
  28. package/dist/cluster-binner.d.ts +5 -0
  29. package/dist/cluster-binner.d.ts.map +1 -1
  30. package/dist/components/index.d.ts +3 -0
  31. package/dist/components/index.d.ts.map +1 -1
  32. package/dist/components/light-helpers.d.ts +4 -0
  33. package/dist/components/light-helpers.d.ts.map +1 -1
  34. package/dist/components/light-probe.d.ts +9 -0
  35. package/dist/components/light-probe.d.ts.map +1 -0
  36. package/dist/components/rect-area-light.d.ts +12 -0
  37. package/dist/components/rect-area-light.d.ts.map +1 -0
  38. package/dist/components/scene-instance.d.ts +4 -0
  39. package/dist/components/scene-instance.d.ts.map +1 -1
  40. package/dist/components/spot-light.d.ts +3 -0
  41. package/dist/components/spot-light.d.ts.map +1 -1
  42. package/dist/construct-renderer.mjs +10437 -10044
  43. package/dist/construct-renderer.mjs.map +1 -1
  44. package/dist/device/gpu-residency.d.ts +8 -1
  45. package/dist/device/gpu-residency.d.ts.map +1 -1
  46. package/dist/device/lifecycle-transaction.d.ts +2 -0
  47. package/dist/device/lifecycle-transaction.d.ts.map +1 -0
  48. package/dist/environment/lifecycle.d.ts.map +1 -1
  49. package/dist/errors/gpu-driven.d.ts +26 -0
  50. package/dist/errors/gpu-driven.d.ts.map +1 -0
  51. package/dist/errors/index.d.ts +1 -0
  52. package/dist/errors/index.d.ts.map +1 -1
  53. package/dist/errors/recover.d.ts +9 -1
  54. package/dist/errors/recover.d.ts.map +1 -1
  55. package/dist/errors/render.d.ts +26 -1
  56. package/dist/errors/render.d.ts.map +1 -1
  57. package/dist/extract/gpu-driven.d.ts +50 -0
  58. package/dist/extract/gpu-driven.d.ts.map +1 -0
  59. package/dist/extract/visibility.d.ts.map +1 -1
  60. package/dist/gpu-driven/batch-topology.d.ts +34 -0
  61. package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
  62. package/dist/gpu-driven/prepared-draw.d.ts +63 -0
  63. package/dist/gpu-driven/prepared-draw.d.ts.map +1 -0
  64. package/dist/gpu-driven/production-raster.d.ts +98 -2
  65. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  66. package/dist/gpu-driven/view-gpu.d.ts +64 -1
  67. package/dist/gpu-driven/view-gpu.d.ts.map +1 -1
  68. package/dist/gpu-scene-schema.d.ts +14 -0
  69. package/dist/gpu-scene-schema.d.ts.map +1 -1
  70. package/dist/gpu-scene.d.ts +8 -1
  71. package/dist/gpu-scene.d.ts.map +1 -1
  72. package/dist/gpu-usage.d.ts +0 -1
  73. package/dist/gpu-usage.d.ts.map +1 -1
  74. package/dist/hdrp-buffers.d.ts +3 -3
  75. package/dist/hdrp-buffers.d.ts.map +1 -1
  76. package/dist/index.d.ts +12 -1
  77. package/dist/index.d.ts.map +1 -1
  78. package/dist/index.mjs +90 -5
  79. package/dist/index.mjs.map +1 -1
  80. package/dist/inspection-types.d.ts +163 -1
  81. package/dist/inspection-types.d.ts.map +1 -1
  82. package/dist/internal.d.ts +9 -0
  83. package/dist/internal.d.ts.map +1 -1
  84. package/dist/internal.mjs +23 -2
  85. package/dist/internal.mjs.map +1 -1
  86. package/dist/light-buffer-layout.d.ts +58 -149
  87. package/dist/light-buffer-layout.d.ts.map +1 -1
  88. package/dist/materials.d.ts.map +1 -1
  89. package/dist/pbr-pipeline.d.ts +26 -12
  90. package/dist/pbr-pipeline.d.ts.map +1 -1
  91. package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
  92. package/dist/pipeline/standard-lighting/graph.d.ts.map +1 -1
  93. package/dist/pipeline/standard-lighting/light-frame.d.ts +3 -3
  94. package/dist/pipeline/standard-lighting/light-frame.d.ts.map +1 -1
  95. package/dist/pipeline/standard-lighting/topology.d.ts +1 -1
  96. package/dist/pipeline/standard-lighting/topology.d.ts.map +1 -1
  97. package/dist/pipeline/standard-lighting/transport.d.ts.map +1 -1
  98. package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
  99. package/dist/pipeline-builder.d.ts +2 -0
  100. package/dist/pipeline-builder.d.ts.map +1 -1
  101. package/dist/pipeline-spec.d.ts +1 -1
  102. package/dist/pipeline-spec.d.ts.map +1 -1
  103. package/dist/plugin.d.ts.map +1 -1
  104. package/dist/prepare/extended-lighting/ltc-resources.d.ts +31 -0
  105. package/dist/prepare/extended-lighting/ltc-resources.d.ts.map +1 -0
  106. package/dist/prepare/extended-lighting/resources.d.ts +72 -0
  107. package/dist/prepare/extended-lighting/resources.d.ts.map +1 -0
  108. package/dist/prepare/extended-lighting/spot-modifiers.d.ts +42 -0
  109. package/dist/prepare/extended-lighting/spot-modifiers.d.ts.map +1 -0
  110. package/dist/prepare/extended-lighting/state.d.ts +24 -0
  111. package/dist/prepare/extended-lighting/state.d.ts.map +1 -0
  112. package/dist/record/frame-lighting.d.ts +8 -6
  113. package/dist/record/frame-lighting.d.ts.map +1 -1
  114. package/dist/record/frame-snapshot.d.ts +50 -10
  115. package/dist/record/frame-snapshot.d.ts.map +1 -1
  116. package/dist/record/frame.d.ts +7 -3
  117. package/dist/record/frame.d.ts.map +1 -1
  118. package/dist/record/gpu-timing.d.ts +19 -7
  119. package/dist/record/gpu-timing.d.ts.map +1 -1
  120. package/dist/record/helpers.d.ts +0 -20
  121. package/dist/record/helpers.d.ts.map +1 -1
  122. package/dist/record/main-pass-geometry.d.ts +6 -1
  123. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  124. package/dist/record/main-pass-material.d.ts +3 -1
  125. package/dist/record/main-pass-material.d.ts.map +1 -1
  126. package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
  127. package/dist/record/mesh-ssbo.d.ts.map +1 -1
  128. package/dist/record/render-context.d.ts +57 -6
  129. package/dist/record/render-context.d.ts.map +1 -1
  130. package/dist/record/shadow-pass.d.ts.map +1 -1
  131. package/dist/record/typed-frame-graph.d.ts +49 -2
  132. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  133. package/dist/record/view-ubo.d.ts +1 -2
  134. package/dist/record/view-ubo.d.ts.map +1 -1
  135. package/dist/reflection/inspection.d.ts +10 -0
  136. package/dist/reflection/inspection.d.ts.map +1 -0
  137. package/dist/reflection/projection.d.ts +49 -0
  138. package/dist/reflection/projection.d.ts.map +1 -1
  139. package/dist/reflection/record-owner.d.ts +60 -0
  140. package/dist/reflection/record-owner.d.ts.map +1 -0
  141. package/dist/render-contract.d.ts +14 -8
  142. package/dist/render-contract.d.ts.map +1 -1
  143. package/dist/render-pipeline.d.ts +7 -0
  144. package/dist/render-pipeline.d.ts.map +1 -1
  145. package/dist/render-system-extract.d.ts +104 -16
  146. package/dist/render-system-extract.d.ts.map +1 -1
  147. package/dist/render-system.d.ts +12 -1
  148. package/dist/render-system.d.ts.map +1 -1
  149. package/dist/scene/probe-blend-record.d.ts +22 -0
  150. package/dist/scene/probe-blend-record.d.ts.map +1 -0
  151. package/dist/scene/probe-blend.d.ts +142 -0
  152. package/dist/scene/probe-blend.d.ts.map +1 -0
  153. package/dist/scene/render-scene-types.d.ts +0 -6
  154. package/dist/scene/render-scene-types.d.ts.map +1 -1
  155. package/dist/scene/render-scene.d.ts +103 -9
  156. package/dist/scene/render-scene.d.ts.map +1 -1
  157. package/dist/scene/visibility/bounds.d.ts +20 -0
  158. package/dist/scene/visibility/bounds.d.ts.map +1 -0
  159. package/dist/scene/visibility/budget.d.ts +16 -0
  160. package/dist/scene/visibility/budget.d.ts.map +1 -0
  161. package/dist/scene/visibility/facet.d.ts +73 -0
  162. package/dist/scene/visibility/facet.d.ts.map +1 -0
  163. package/dist/scene/visibility/gpu-frame-timing.d.ts +41 -0
  164. package/dist/scene/visibility/gpu-frame-timing.d.ts.map +1 -0
  165. package/dist/scene/visibility/gpu-lod.d.ts +46 -0
  166. package/dist/scene/visibility/gpu-lod.d.ts.map +1 -0
  167. package/dist/scene/visibility/inspection.d.ts +132 -0
  168. package/dist/scene/visibility/inspection.d.ts.map +1 -0
  169. package/dist/scene/visibility/lod-selector.d.ts +25 -0
  170. package/dist/scene/visibility/lod-selector.d.ts.map +1 -0
  171. package/dist/scene/visibility/occlusion-confidence.d.ts +81 -0
  172. package/dist/scene/visibility/occlusion-confidence.d.ts.map +1 -0
  173. package/dist/scene/visibility/occlusion-pass.d.ts +57 -0
  174. package/dist/scene/visibility/occlusion-pass.d.ts.map +1 -0
  175. package/dist/scene/visibility/occlusion-query-pool.d.ts +57 -0
  176. package/dist/scene/visibility/occlusion-query-pool.d.ts.map +1 -0
  177. package/dist/scene/visibility/occlusion-runtime.d.ts +96 -0
  178. package/dist/scene/visibility/occlusion-runtime.d.ts.map +1 -0
  179. package/dist/scene/visibility/types.d.ts +34 -0
  180. package/dist/scene/visibility/types.d.ts.map +1 -0
  181. package/dist/ssr/admission.d.ts +122 -0
  182. package/dist/ssr/admission.d.ts.map +1 -0
  183. package/dist/ssr/composition.d.ts +32 -0
  184. package/dist/ssr/composition.d.ts.map +1 -0
  185. package/dist/ssr/errors.d.ts +39 -0
  186. package/dist/ssr/errors.d.ts.map +1 -0
  187. package/dist/ssr/identity.d.ts +13 -0
  188. package/dist/ssr/identity.d.ts.map +1 -0
  189. package/dist/systems/skin-palette-allocator.d.ts +1 -1
  190. package/dist/temporal/frame-coordinator.d.ts +13 -0
  191. package/dist/temporal/frame-coordinator.d.ts.map +1 -1
  192. package/dist/temporal/frame.d.ts +2 -0
  193. package/dist/temporal/frame.d.ts.map +1 -1
  194. package/dist/temporal/index.mjs +6 -5
  195. package/dist/temporal/view.d.ts +7 -0
  196. package/dist/temporal/view.d.ts.map +1 -1
  197. package/dist/tilemap-chunk-extract-system.d.ts +1 -1
  198. package/dist/typed-render-graph-primitives.d.ts +4 -0
  199. package/dist/typed-render-graph-primitives.d.ts.map +1 -1
  200. package/dist/volume/passes.d.ts +8 -4
  201. package/dist/volume/passes.d.ts.map +1 -1
  202. package/package.json +20 -20
  203. package/src/__tests__/cluster-grid-default-surface.unit.test.ts +5 -0
  204. package/src/__tests__/detached-inspection.integration.test.ts +6 -0
  205. package/src/__tests__/device-feature-admission.unit.test.ts +13 -1
  206. package/src/__tests__/direct-light-snapshot.unit.test.ts +59 -3
  207. package/src/__tests__/environment-generation.integration.test.ts +16 -0
  208. package/src/__tests__/evidence-identity.ts +13 -0
  209. package/src/__tests__/evidence-identity.unit.test.ts +24 -0
  210. package/src/__tests__/extended-lighting-recovery.browser.test.ts +14 -0
  211. package/src/__tests__/extended-lighting-recovery.dawn.test.ts +14 -0
  212. package/src/__tests__/extended-lighting-recovery.integration.test.ts +81 -0
  213. package/src/__tests__/extended-lighting-resources.unit.test.ts +51 -0
  214. package/src/__tests__/extended-lighting-zero-feature.integration.test.ts +52 -0
  215. package/src/__tests__/factory-contract.integration.test.ts +33 -0
  216. package/src/__tests__/frame-observation.contract.test.ts +15 -0
  217. package/src/__tests__/gpu-driven-batch-topology-pbr.unit.test.ts +146 -0
  218. package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +1 -0
  219. package/src/__tests__/gpu-driven-indirect-raster.dawn.test.ts +1 -0
  220. package/src/__tests__/gpu-driven-production.integration.test.ts +572 -9
  221. package/src/__tests__/gpu-driven-scaling.unit.test.ts +66 -2
  222. package/src/__tests__/gpu-driven-view-gpu-evidence.ts +26 -1
  223. package/src/__tests__/gpu-driven-view-graph.integration.test.ts +89 -2
  224. package/src/__tests__/gpu-driven-view.browser.test.ts +3 -3
  225. package/src/__tests__/gpu-driven-view.dawn.test.ts +3 -3
  226. package/src/__tests__/gpu-driven-view.unit.test.ts +50 -0
  227. package/src/__tests__/gpu-lod-evidence.ts +188 -0
  228. package/src/__tests__/gpu-lod-fallback.integration.test.ts +10 -0
  229. package/src/__tests__/gpu-lod-indirect.integration.test.ts +40 -0
  230. package/src/__tests__/gpu-lod-parity.dawn.test.ts +29 -0
  231. package/src/__tests__/gpu-lod-readiness.integration.test.ts +52 -0
  232. package/src/__tests__/gpu-lod-schema.unit.test.ts +66 -0
  233. package/src/__tests__/gpu-lod-selector.unit.test.ts +28 -0
  234. package/src/__tests__/gpu-lod-silhouette.browser.test.ts +13 -0
  235. package/src/__tests__/gpu-resource-store-stride.unit.test.ts +66 -1
  236. package/src/__tests__/gpu-scene.dawn.test.ts +8 -1
  237. package/src/__tests__/gpu-scene.unit.test.ts +49 -3
  238. package/src/__tests__/light-buffer-layout.unit.test.ts +113 -3
  239. package/src/__tests__/light-inspection.unit.test.ts +55 -0
  240. package/src/__tests__/light-resource-error.unit.test.ts +43 -0
  241. package/src/__tests__/lighting-scene-roundtrip.integration.test.ts +109 -0
  242. package/src/__tests__/lod-bounds.unit.test.ts +42 -0
  243. package/src/__tests__/lod-cpu-lane.integration.test.ts +55 -0
  244. package/src/__tests__/lod-cpu-no-change.perf.test.ts +19 -0
  245. package/src/__tests__/lod-gpu-frame-timing.unit.test.ts +49 -0
  246. package/src/__tests__/lod-multi-view.integration.test.ts +63 -0
  247. package/src/__tests__/lod-occlusion-device-loss.integration.test.ts +27 -0
  248. package/src/__tests__/lod-occlusion-final-decision.integration.test.ts +47 -0
  249. package/src/__tests__/lod-occlusion-inspection.integration.test.ts +217 -0
  250. package/src/__tests__/lod-occlusion-inspection.schema.test-d.ts +14 -0
  251. package/src/__tests__/lod-occlusion-inspection.unit.test.ts +72 -0
  252. package/src/__tests__/lod-occlusion-visual-evidence.ts +91 -0
  253. package/src/__tests__/lod-occlusion.browser.test.ts +15 -0
  254. package/src/__tests__/lod-occlusion.dawn.test.ts +20 -0
  255. package/src/__tests__/lod-selector.profile.test.ts +35 -0
  256. package/src/__tests__/lod-selector.unit.test.ts +68 -0
  257. package/src/__tests__/lod-visibility-identity.unit.test.ts +198 -0
  258. package/src/__tests__/lod-visibility-lifecycle.integration.test.ts +132 -0
  259. package/src/__tests__/ltc-resources.unit.test.ts +32 -0
  260. package/src/__tests__/material-contract-inventory.unit.test.ts +17 -0
  261. package/src/__tests__/occlusion-confidence.unit.test.ts +168 -0
  262. package/src/__tests__/occlusion-invalidation.integration.test.ts +37 -0
  263. package/src/__tests__/occlusion-pass-routing.unit.test.ts +16 -0
  264. package/src/__tests__/occlusion-pass.dawn.test.ts +20 -0
  265. package/src/__tests__/occlusion-pass.integration.test.ts +43 -0
  266. package/src/__tests__/occlusion-pass.rhi-null.test.ts +22 -0
  267. package/src/__tests__/occlusion-query-pool.rhi-null.test.ts +21 -0
  268. package/src/__tests__/occlusion-query-pool.unit.test.ts +95 -0
  269. package/src/__tests__/occlusion-query-ticket.integration.test.ts +47 -0
  270. package/src/__tests__/occlusion-query.dawn.test.ts +16 -0
  271. package/src/__tests__/occlusion-query.evidence.ts +178 -0
  272. package/src/__tests__/occlusion-query.rhi-null.test.ts +31 -0
  273. package/src/__tests__/occlusion-runtime.unit.test.ts +120 -0
  274. package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +196 -0
  275. package/src/__tests__/probe-bindings.unit.test.ts +200 -0
  276. package/src/__tests__/probe-blend-numeric.unit.test.ts +67 -0
  277. package/src/__tests__/probe-blend-oracle.unit.test.ts +136 -0
  278. package/src/__tests__/probe-capacity.integration.test.ts +97 -0
  279. package/src/__tests__/probe-recovery.integration.test.ts +104 -0
  280. package/src/__tests__/probe-scene-dirty.integration.test.ts +211 -0
  281. package/src/__tests__/profiler-phase-catalog.test.ts +4 -1
  282. package/src/__tests__/profiler-phase-token.test.ts +1 -0
  283. package/src/__tests__/rect-area-boundary.unit.test.ts +24 -0
  284. package/src/__tests__/reflection-fallback-graph.unit.test.ts +67 -0
  285. package/src/__tests__/reflection-fallback-inspection.unit.test.ts +77 -0
  286. package/src/__tests__/reflection-fallback-lifecycle.integration.test.ts +21 -0
  287. package/src/__tests__/reflection-fallback-projection.unit.test.ts +90 -0
  288. package/src/__tests__/reflection-fallback-source.unit.test.ts +32 -0
  289. package/src/__tests__/reflection-probe-owner-placement.unit.test.ts +39 -0
  290. package/src/__tests__/reflection-probe-record-owner.characterization.unit.test.ts +120 -0
  291. package/src/__tests__/render-error-code-owner.test-d.ts +1 -0
  292. package/src/__tests__/render-feature-docs.unit.test.ts +12 -7
  293. package/src/__tests__/render-graph-fallback.integration.test.ts +24 -0
  294. package/src/__tests__/render-perf/extended-lighting.bench.test.ts +66 -0
  295. package/src/__tests__/render-scene-projection.unit.test.ts +447 -18
  296. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +76 -0
  297. package/src/__tests__/skin-motion-regression.unit.test.ts +22 -0
  298. package/src/__tests__/skinned-shadow-caster.test.ts +2 -2
  299. package/src/__tests__/spot-modifier-projection.unit.test.ts +70 -0
  300. package/src/__tests__/ssr-admission-matrix.integration.test.ts +206 -0
  301. package/src/__tests__/ssr-composition.unit.test.ts +46 -0
  302. package/src/__tests__/ssr-fallback-zero-work.bench.test.ts +94 -0
  303. package/src/__tests__/ssr-recovery-errors.unit.test.ts +58 -0
  304. package/src/__tests__/standard-cluster-variant-resolution.unit.test.ts +67 -0
  305. package/src/__tests__/standard-clustered-pipeline.unit.test.ts +7 -0
  306. package/src/__tests__/standard-lighting-inspection.integration.test.ts +1 -1
  307. package/src/__tests__/standard-lighting-write.unit.test.ts +66 -2
  308. package/src/__tests__/temporal-fallback-generation.integration.test.ts +16 -0
  309. package/src/__tests__/temporal-view.unit.test.ts +3 -3
  310. package/src/__tests__/transmission-prewarm.unit.test.ts +69 -0
  311. package/src/__tests__/visibility-budget.integration.test.ts +140 -0
  312. package/src/__tests__/visibility-budget.unit.test.ts +70 -0
  313. package/src/__tests__/visibility-extract.integration.test.ts +11 -6
  314. package/src/__tests__/volumetric-fog-point-spot-accumulation.integration.test.ts +6 -3
  315. package/src/__tests__/volumetric-fog-stage-readback.dawn.test.ts +31 -28
  316. package/src/assembly/backend-contract.ts +13 -0
  317. package/src/assembly/device-feature-admission.ts +16 -9
  318. package/src/assembly/factory.ts +547 -93
  319. package/src/assembly/host-contract.ts +10 -0
  320. package/src/cluster-binner.ts +31 -0
  321. package/src/components/__tests__/lighting-authoring.unit.test.ts +39 -0
  322. package/src/components/__tests__/rect-area-light.unit.test.ts +33 -0
  323. package/src/components/index.ts +3 -0
  324. package/src/components/light-helpers.ts +52 -1
  325. package/src/components/light-probe.ts +13 -0
  326. package/src/components/rect-area-light.ts +15 -0
  327. package/src/components/scene-instance.ts +6 -0
  328. package/src/components/spot-light.ts +3 -0
  329. package/src/device/__tests__/extended-lighting-recovery.unit.test.ts +24 -0
  330. package/src/device/gpu-residency.ts +140 -9
  331. package/src/device/lifecycle-transaction.ts +12 -0
  332. package/src/environment/lifecycle.ts +8 -1
  333. package/src/errors/gpu-driven.ts +92 -0
  334. package/src/errors/index.ts +1 -0
  335. package/src/errors/recover.ts +32 -0
  336. package/src/errors/render.ts +55 -1
  337. package/src/extract/camera.ts +3 -3
  338. package/src/extract/gpu-driven.ts +108 -0
  339. package/src/extract/visibility.ts +0 -23
  340. package/src/gpu-driven/batch-topology.ts +206 -28
  341. package/src/gpu-driven/prepared-draw.ts +208 -0
  342. package/src/gpu-driven/production-raster.ts +953 -41
  343. package/src/gpu-driven/view-gpu.ts +411 -29
  344. package/src/gpu-scene-schema.ts +13 -0
  345. package/src/gpu-scene.ts +134 -65
  346. package/src/gpu-usage.ts +0 -1
  347. package/src/hdrp-buffers.ts +6 -6
  348. package/src/index.ts +74 -0
  349. package/src/inspection-types.ts +220 -1
  350. package/src/internal.ts +38 -0
  351. package/src/light-buffer-layout.ts +154 -324
  352. package/src/materials.ts +16 -2
  353. package/src/pbr-pipeline.ts +151 -45
  354. package/src/pipeline/standard-forward-lane.ts +47 -0
  355. package/src/pipeline/standard-lighting/graph.ts +2 -1
  356. package/src/pipeline/standard-lighting/light-frame.ts +3 -2
  357. package/src/pipeline/standard-lighting/topology.ts +1 -1
  358. package/src/pipeline/standard-lighting/transport.ts +2 -1
  359. package/src/pipeline/standard-pipeline.ts +2 -0
  360. package/src/pipeline-builder.ts +5 -2
  361. package/src/pipeline-spec.ts +12 -1
  362. package/src/plugin.ts +4 -0
  363. package/src/prepare/__tests__/extended-lighting-capability.unit.test.ts +56 -0
  364. package/src/prepare/extended-lighting/ltc-resources.ts +136 -0
  365. package/src/prepare/extended-lighting/resources.ts +389 -0
  366. package/src/prepare/extended-lighting/spot-modifiers.ts +194 -0
  367. package/src/prepare/extended-lighting/state.ts +92 -0
  368. package/src/record/__tests__/gpu-timing.unit.test.ts +177 -0
  369. package/src/record/__tests__/temporal-payload.unit.test.ts +23 -1
  370. package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +20 -1
  371. package/src/record/frame-lighting.ts +252 -53
  372. package/src/record/frame-snapshot.ts +57 -12
  373. package/src/record/frame.ts +410 -93
  374. package/src/record/gpu-timing.ts +108 -44
  375. package/src/record/helpers.ts +0 -58
  376. package/src/record/main-pass-geometry.ts +204 -39
  377. package/src/record/main-pass-material.ts +8 -1
  378. package/src/record/main-pass-sprite-draws.ts +52 -85
  379. package/src/record/mesh-ssbo.ts +47 -36
  380. package/src/record/render-context.ts +65 -4
  381. package/src/record/shadow-pass.ts +66 -40
  382. package/src/record/typed-frame-graph.ts +225 -83
  383. package/src/record/view-ubo.ts +14 -24
  384. package/src/reflection/inspection.ts +67 -0
  385. package/src/reflection/projection.ts +196 -1
  386. package/src/reflection/record-owner.ts +1283 -0
  387. package/src/render-contract.ts +17 -5
  388. package/src/render-pipeline.ts +5 -0
  389. package/src/render-system-extract.ts +493 -134
  390. package/src/render-system.ts +811 -615
  391. package/src/scene/__tests__/changed-block-summary.candidate.test.ts +36 -0
  392. package/src/scene/probe-blend-record.ts +54 -0
  393. package/src/scene/probe-blend.ts +813 -0
  394. package/src/scene/render-scene-types.ts +0 -7
  395. package/src/scene/render-scene.ts +848 -183
  396. package/src/scene/visibility/bounds.ts +57 -0
  397. package/src/scene/visibility/budget.ts +40 -0
  398. package/src/scene/visibility/facet.ts +400 -0
  399. package/src/scene/visibility/gpu-frame-timing.ts +101 -0
  400. package/src/scene/visibility/gpu-lod.ts +119 -0
  401. package/src/scene/visibility/inspection.ts +313 -0
  402. package/src/scene/visibility/lod-selector.ts +84 -0
  403. package/src/scene/visibility/occlusion-confidence.ts +478 -0
  404. package/src/scene/visibility/occlusion-pass.ts +165 -0
  405. package/src/scene/visibility/occlusion-query-pool.ts +178 -0
  406. package/src/scene/visibility/occlusion-runtime.ts +672 -0
  407. package/src/scene/visibility/types.ts +50 -0
  408. package/src/ssr/admission.ts +451 -0
  409. package/src/ssr/composition.ts +73 -0
  410. package/src/ssr/errors.ts +76 -0
  411. package/src/ssr/identity.ts +12 -0
  412. package/src/systems/skin-palette-allocator.ts +1 -1
  413. package/src/temporal/frame-coordinator.ts +26 -0
  414. package/src/temporal/frame.ts +14 -1
  415. package/src/temporal/view.ts +11 -0
  416. package/src/tilemap-chunk-extract-system.ts +4 -4
  417. package/src/typed-render-graph-primitives.ts +48 -0
  418. package/src/volume/passes.ts +46 -49
  419. package/dist/.tsbuildinfo +0 -1
  420. package/dist/chunk-5YADUCA7.mjs.map +0 -1
  421. package/dist/chunk-6OPXDQ2E.mjs.map +0 -1
  422. package/dist/chunk-BXBQQKFQ.mjs +0 -67
  423. package/dist/chunk-BXBQQKFQ.mjs.map +0 -1
  424. package/dist/chunk-FS2KNUQH.mjs.map +0 -1
  425. package/dist/chunk-PCHBPYML.mjs.map +0 -1
  426. package/dist/chunk-VFPLTKCU.mjs.map +0 -1
@@ -22,6 +22,7 @@ import type { InstanceBufferCacheEntry } from '../instance-buffer-cache';
22
22
  import { SPRITE_PREMULTIPLIED_ALPHA_BLEND } from '../materials';
23
23
  import {
24
24
  createPbrSkinMeshBindGroupEntries,
25
+ isStandardPbrMaterialShader,
25
26
  pbrSkinMeshDynamicOffsets,
26
27
  SKIN_MATERIAL_SHADER_ID,
27
28
  } from '../pbr-pipeline';
@@ -35,6 +36,11 @@ import type { PointsLinesRecordPlan } from '../points-lines/record';
35
36
  import { POINTS_LINES_MATERIAL_SHADER_ID } from '../points-lines/record';
36
37
  import type { RenderRecordPhase } from '../render-contract';
37
38
  import type { MaterialSnapshot } from '../render-system-extract';
39
+ import {
40
+ PROBE_BLEND_RECORD_BYTE_SIZE,
41
+ PROBE_BLEND_RECORD_STRIDE,
42
+ probeBlendRecordOffset,
43
+ } from '../scene/probe-blend-record';
38
44
  import { worldEntityKey } from './frame-snapshot';
39
45
  import {
40
46
  geometryRenderStateForTopology,
@@ -58,12 +64,15 @@ import { POINTS_LINES_VIEW_SLOT_STRIDE, writePointsLinesViewUbo } from './view-u
58
64
  type GeometryInstanceDraw = {
59
65
  readonly instanceBuffer: Buffer;
60
66
  readonly instanceBindGroup: BindGroup;
67
+ /** -1 means the no-probe layout and therefore no dynamic offset. */
68
+ readonly probeOffset: number;
61
69
  readonly instanceCount: number;
62
70
  };
63
71
 
64
72
  type GeometryBindingState = {
65
73
  pipeline: RenderPipeline | null;
66
74
  instancesBuffer: Buffer | null;
75
+ probeOffset: number;
67
76
  };
68
77
 
69
78
  type MaterialPipelineLookup = {
@@ -77,6 +86,7 @@ type MaterialPipelineLookup = {
77
86
  readonly layoutProjection: import('@forgeax/engine-geometry').VertexLayoutProjection;
78
87
  readonly sampleCount: number;
79
88
  readonly colorFormatOverride: GPUTextureFormat | undefined;
89
+ readonly additionalColorFormats: readonly GPUTextureFormat[] | undefined;
80
90
  readonly handle: MaterialShaderPipelineEntry | null;
81
91
  };
82
92
 
@@ -138,9 +148,17 @@ function submitSubmeshDraws(
138
148
  state.pipeline = pipeline;
139
149
  }
140
150
  for (const instanceDraw of instanceDraws) {
141
- if (instanceDraw.instanceBuffer !== state.instancesBuffer) {
142
- pass.setBindGroup(3, instanceDraw.instanceBindGroup);
151
+ if (
152
+ instanceDraw.instanceBuffer !== state.instancesBuffer ||
153
+ instanceDraw.probeOffset !== state.probeOffset
154
+ ) {
155
+ if (instanceDraw.probeOffset < 0) {
156
+ pass.setBindGroup(3, instanceDraw.instanceBindGroup);
157
+ } else {
158
+ pass.setBindGroup(3, instanceDraw.instanceBindGroup, [instanceDraw.probeOffset]);
159
+ }
143
160
  state.instancesBuffer = instanceDraw.instanceBuffer;
161
+ state.probeOffset = instanceDraw.probeOffset;
144
162
  }
145
163
  if (indexed) {
146
164
  pass.drawIndexed(indexCount, instanceDraw.instanceCount, indexOffset, 0, 0);
@@ -226,12 +244,14 @@ export function recordGeometryDraws(
226
244
  const bindingState: GeometryBindingState = {
227
245
  pipeline: null,
228
246
  instancesBuffer: null,
247
+ probeOffset: -1,
229
248
  };
230
249
  const identityInstanceBuffer = pipelineState.identityInstanceBuffer;
231
250
  const identityInstanceDraws: readonly GeometryInstanceDraw[] = [
232
251
  {
233
252
  instanceBuffer: identityInstanceBuffer,
234
- instanceBindGroup: resolveGeometryInstancesBindGroup(c, identityInstanceBuffer),
253
+ instanceBindGroup: resolveGeometryInstancesBindGroup(c, identityInstanceBuffer, undefined),
254
+ probeOffset: -1,
235
255
  instanceCount: 1,
236
256
  },
237
257
  ];
@@ -253,6 +273,9 @@ export function recordGeometryDraws(
253
273
  // renderer's authoritative cache still owns lifetime/invalidation, while
254
274
  // this frame-local table removes the hot call/lookup overhead.
255
275
  const materialPipelineLookupCache = new Map<string, Map<string, MaterialPipelineLookup[]>>();
276
+ const recordOcclusionCandidate = (draw: () => void): void => {
277
+ draw();
278
+ };
256
279
  const resolveMaterialPipeline = (
257
280
  materialShaderId: string,
258
281
  renderState: MaterialSnapshot['renderState'],
@@ -262,6 +285,19 @@ export function recordGeometryDraws(
262
285
  colorFormat: GPUTextureFormat | undefined,
263
286
  layoutProjection: import('@forgeax/engine-geometry').VertexLayoutProjection,
264
287
  ): MaterialShaderPipelineEntry | null => {
288
+ // The fallback MRT is an extension of the built-in Standard PBR output
289
+ // contract only. The render pass may carry a second attachment whenever
290
+ // another renderable needs S_fallback, but shaders such as unlit, sprite,
291
+ // and the skin PBR variant still expose only @location(0). Passing that
292
+ // attachment into their PSO would make WebGPU reject the pipeline before
293
+ // the first draw ("Color target has no corresponding fragment stage
294
+ // output"). Keep the graph attachment shared while deriving pipeline
295
+ // targets from the shader's actual output contract.
296
+ const reflectionFallbackFormats =
297
+ materialShaderId === 'forgeax::default-standard-pbr' &&
298
+ c.reflectionFallbackColorFormat !== undefined
299
+ ? [c.reflectionFallbackColorFormat]
300
+ : undefined;
265
301
  const renderStateKey = renderStateHash(renderState);
266
302
  const shaderLookupCache = materialPipelineLookupCache.get(materialShaderId);
267
303
  const cachedLookups = shaderLookupCache?.get(renderStateKey);
@@ -276,7 +312,8 @@ export function recordGeometryDraws(
276
312
  cached.passKind === passKind &&
277
313
  cached.layoutProjection.digest === layoutProjection.digest &&
278
314
  cached.sampleCount === sampleCount &&
279
- cached.colorFormatOverride === colorFormat
315
+ cached.colorFormatOverride === colorFormat &&
316
+ cached.additionalColorFormats?.join(',') === reflectionFallbackFormats?.join(',')
280
317
  ) {
281
318
  return cached.handle;
282
319
  }
@@ -292,6 +329,12 @@ export function recordGeometryDraws(
292
329
  return null;
293
330
  }
294
331
  const resolvedVariantSet = resolvedVariantSetResult.value;
332
+ const fallbackVariantSet =
333
+ materialShaderId !== 'forgeax::default-standard-pbr'
334
+ ? resolvedVariantSet
335
+ : c.reflectionFallbackColorFormat !== undefined && resolvedVariantSet === ''
336
+ ? ''
337
+ : `${resolvedVariantSet === undefined || resolvedVariantSet === '' ? '' : `${resolvedVariantSet}+`}REFLECTION_FALLBACK_AVAILABLE=${c.reflectionFallbackColorFormat !== undefined}`;
295
338
  const resolvePipeline = () =>
296
339
  runtime.getMaterialShaderPipelineEntry?.(
297
340
  materialShaderId,
@@ -299,7 +342,7 @@ export function recordGeometryDraws(
299
342
  renderState,
300
343
  topology,
301
344
  indexFormat,
302
- resolvedVariantSet,
345
+ fallbackVariantSet,
303
346
  passKind,
304
347
  undefined,
305
348
  sampleCount,
@@ -308,6 +351,9 @@ export function recordGeometryDraws(
308
351
  undefined,
309
352
  undefined,
310
353
  layoutProjection,
354
+ undefined,
355
+ undefined,
356
+ reflectionFallbackFormats,
311
357
  ) ?? null;
312
358
  const handle =
313
359
  profilePhase === undefined
@@ -332,6 +378,7 @@ export function recordGeometryDraws(
332
378
  layoutProjection,
333
379
  sampleCount,
334
380
  colorFormatOverride: colorFormat,
381
+ additionalColorFormats: reflectionFallbackFormats,
335
382
  handle,
336
383
  };
337
384
  if (shaderLookupCache === undefined) {
@@ -361,7 +408,12 @@ export function recordGeometryDraws(
361
408
 
362
409
  if (
363
410
  passKind === 'forward' &&
364
- c.gpuDrivenEntityKeys.has(worldEntityKey(entry.source.worldId, entry.source.entityKey))
411
+ c.gpuDrivenEntityKeys.has(
412
+ worldEntityKey(
413
+ c.gpuDrivenWorldKeys?.[entry.source.worldId] ?? entry.source.worldId,
414
+ entry.source.entityKey,
415
+ ),
416
+ )
365
417
  ) {
366
418
  continue;
367
419
  }
@@ -454,12 +506,11 @@ export function recordGeometryDraws(
454
506
  const pointsLinesMeshGroup = meshBindGroup ?? meshGroup2;
455
507
  if (pointsLinesMeshGroup === null) continue;
456
508
  pass.setBindGroup(2, pointsLinesMeshGroup, meshGroup2DynamicOffsets);
457
- pass.setBindGroup(
458
- 3,
509
+ const pointsLinesInstancesBg =
459
510
  identityInstanceDraws[0]?.instanceBindGroup ??
460
- resolveGeometryInstancesBindGroup(c, identityInstanceBuffer),
461
- );
462
- recordPointsLinesDraw(pass, pointsLinesSubmission.plan);
511
+ resolveGeometryInstancesBindGroup(c, identityInstanceBuffer, undefined);
512
+ pass.setBindGroup(3, pointsLinesInstancesBg);
513
+ recordOcclusionCandidate(() => recordPointsLinesDraw(pass, pointsLinesSubmission.plan));
463
514
  pass.setBindGroup(0, viewBindGroup as BindGroup, [viewBindGroupDynamicOffset, 0]);
464
515
  continue;
465
516
  }
@@ -487,7 +538,7 @@ export function recordGeometryDraws(
487
538
  // length instead.
488
539
  if (pipelineTag === 'unlit') dispatchCounts.unlit += 1;
489
540
 
490
- const instanceDraws = resolveGeometryInstanceBuffer(c, entry, identityInstanceDraws);
541
+ const instanceDraws = resolveGeometryInstanceBuffer(c, entry, identityInstanceDraws, false);
491
542
  if (instanceDraws === null) continue;
492
543
 
493
544
  // feat-20260611 R2 / M8 / w28 (IS-14): skin entries need a 3-binding
@@ -518,6 +569,8 @@ export function recordGeometryDraws(
518
569
  meshGroup2DynamicOffsets[0] = i * MESH_PER_ENTITY_STRIDE;
519
570
  let group2DynamicOffsets: readonly number[] = meshGroup2DynamicOffsets;
520
571
  const isSkinEntry = entry.source.skin !== undefined;
572
+ const probeBlendRecordAvailable =
573
+ runtime.device.caps.storageBuffer && entry.source.probeBlendRecord !== undefined;
521
574
  // feat-20260612-skin-palette-per-frame-upload M1 / m1-3 + M6: the
522
575
  // record stage reads the GPU buffer reference through
523
576
  // `entry.source.skin.buffer` (per-slice carrier set at extract time
@@ -567,6 +620,7 @@ export function recordGeometryDraws(
567
620
  c.standardLighting,
568
621
  runtime.device.caps.storageBuffer,
569
622
  entry.mesh.layoutProjection.attributes.some((attribute) => attribute.key === 'color'),
623
+ probeBlendRecordAvailable,
570
624
  ),
571
625
  );
572
626
  if (!skinVariantSetResult.ok) runtime.errorRegistry.fire(skinVariantSetResult.error);
@@ -588,6 +642,9 @@ export function recordGeometryDraws(
588
642
  undefined,
589
643
  undefined,
590
644
  entry.mesh.layoutProjection,
645
+ undefined,
646
+ undefined,
647
+ undefined,
591
648
  ) ?? null)
592
649
  : null;
593
650
  if (skinResources !== null && skinPsoProbe !== null) {
@@ -794,6 +851,8 @@ export function recordGeometryDraws(
794
851
  entry.source.skin !== undefined
795
852
  ? SKIN_MATERIAL_SHADER_ID
796
853
  : submeshMaterial.materialShaderId;
854
+ const probeBlendAvailable =
855
+ probeBlendRecordAvailable && isStandardPbrMaterialShader(smMaterialShaderId);
797
856
  const isSpriteShader =
798
857
  smMaterialShaderId === 'forgeax::sprite' || smMaterialShaderId === 'forgeax::sprite-lit';
799
858
  // Preserve the dedicated sprite-pass state when the linear-LDR graph
@@ -869,6 +928,7 @@ export function recordGeometryDraws(
869
928
  c.standardLighting,
870
929
  runtime.device.caps.storageBuffer,
871
930
  hasVertexColor,
931
+ probeBlendAvailable,
872
932
  );
873
933
  // Sprite's base request must retain the boot-selected artifact. Only
874
934
  // SpriteInstances uses the explicit empty key for the
@@ -884,19 +944,12 @@ export function recordGeometryDraws(
884
944
  : capabilityVariantSet === ''
885
945
  ? entry.variantSet
886
946
  : `${capabilityVariantSet}+${entry.variantSet}`;
887
- const projectionVariantSetResult = isSpriteShader
888
- ? ({ ok: true, value: variantSet } as const)
889
- : variantSetFromVertexLayoutProjection(entry.mesh.layoutProjection, variantSet);
890
- if (!projectionVariantSetResult.ok) {
891
- runtime.errorRegistry.fire(projectionVariantSetResult.error);
892
- continue;
893
- }
894
947
  const cachedPipeline = resolveMaterialPipeline(
895
948
  smMaterialShaderId,
896
949
  pipelineRenderState,
897
950
  smTopology,
898
951
  entry.mesh.indexFormat,
899
- projectionVariantSetResult.value,
952
+ variantSet,
900
953
  colorFormatOverride,
901
954
  entry.mesh.layoutProjection,
902
955
  );
@@ -922,6 +975,18 @@ export function recordGeometryDraws(
922
975
  if (smPipelineHandle === null) {
923
976
  continue;
924
977
  }
978
+ // The probe record is an ABI extension of the built-in Standard PBR
979
+ // shaders only. RenderScene may retain a record on every projected
980
+ // renderable, but unlit/custom shaders still use the one-binding
981
+ // instances BGL and must never receive the probe bind group. Resolve
982
+ // this per submesh so mixed-material meshes cannot cross the layouts.
983
+ const instanceDraws = resolveGeometryInstanceBuffer(
984
+ c,
985
+ entry,
986
+ identityInstanceDraws,
987
+ probeBlendAvailable,
988
+ );
989
+ if (instanceDraws === null) continue;
925
990
  // Standard clustered PBR variants declare the unified cluster/SSAO
926
991
  // group(2) layout. Unlit and other URP-layout materials must bind the
927
992
  // ordinary mesh group instead; choosing one group for the whole pass
@@ -941,18 +1006,7 @@ export function recordGeometryDraws(
941
1006
  pass.setBindGroup(2, group2BindGroup, group2DynamicOffsets);
942
1007
 
943
1008
  if (profilePhase === undefined) {
944
- submitSubmeshDraws(
945
- pass,
946
- bindingState,
947
- smPipelineHandle,
948
- instanceDraws,
949
- entry.mesh.indexed,
950
- sm.indexCount,
951
- sm.vertexCount,
952
- sm.indexOffset,
953
- );
954
- } else {
955
- profileGeometrySegment(c, passKind, 'draw-submit', () =>
1009
+ recordOcclusionCandidate(() =>
956
1010
  submitSubmeshDraws(
957
1011
  pass,
958
1012
  bindingState,
@@ -964,6 +1018,21 @@ export function recordGeometryDraws(
964
1018
  sm.indexOffset,
965
1019
  ),
966
1020
  );
1021
+ } else {
1022
+ profileGeometrySegment(c, passKind, 'draw-submit', () =>
1023
+ recordOcclusionCandidate(() =>
1024
+ submitSubmeshDraws(
1025
+ pass,
1026
+ bindingState,
1027
+ smPipelineHandle,
1028
+ instanceDraws,
1029
+ entry.mesh.indexed,
1030
+ sm.indexCount,
1031
+ sm.vertexCount,
1032
+ sm.indexOffset,
1033
+ ),
1034
+ ),
1035
+ );
967
1036
  }
968
1037
  }
969
1038
  }
@@ -978,10 +1047,35 @@ function resolveGeometryInstanceBuffer(
978
1047
  c: _InternalRenderPipelineContext,
979
1048
  entry: _InternalRenderPipelineContext['validatedOrdered'][number],
980
1049
  identityInstanceDraws: readonly GeometryInstanceDraw[],
1050
+ useProbeBlend: boolean,
981
1051
  ): readonly GeometryInstanceDraw[] | null {
982
1052
  const { runtime, pipelineState, frameState } = c;
983
1053
  const inst = entry.source.instances;
984
- if (inst === undefined) return identityInstanceDraws;
1054
+ const probeBinding =
1055
+ !useProbeBlend ||
1056
+ !runtime.device.caps.storageBuffer ||
1057
+ entry.source.probeBlendRecord === undefined
1058
+ ? undefined
1059
+ : resolveProbeBlendBuffer(
1060
+ c,
1061
+ entry.source.probeBlendRecord,
1062
+ worldEntityKey(entry.source.worldId, entry.source.entityKey),
1063
+ );
1064
+ if (inst === undefined) {
1065
+ const identity = identityInstanceDraws[0];
1066
+ if (identity === undefined) return null;
1067
+ return [
1068
+ {
1069
+ ...identity,
1070
+ instanceBindGroup: resolveGeometryInstancesBindGroup(
1071
+ c,
1072
+ pipelineState.identityInstanceBuffer,
1073
+ probeBinding?.buffer,
1074
+ ),
1075
+ probeOffset: probeBinding?.offset ?? -1,
1076
+ },
1077
+ ];
1078
+ }
985
1079
  // A present, empty Instances array means zero draws. Do not fall back to
986
1080
  // the shared identity buffer, which would silently render one instance.
987
1081
  if (inst.instanceCount === 0) return [];
@@ -1022,7 +1116,12 @@ function resolveGeometryInstanceBuffer(
1022
1116
  }
1023
1117
  draws.push({
1024
1118
  instanceBuffer: buffer.handle,
1025
- instanceBindGroup: resolveGeometryInstancesBindGroup(c, buffer.handle),
1119
+ instanceBindGroup: resolveGeometryInstancesBindGroup(
1120
+ c,
1121
+ buffer.handle,
1122
+ probeBinding?.buffer,
1123
+ ),
1124
+ probeOffset: probeBinding?.offset ?? -1,
1026
1125
  instanceCount: count,
1027
1126
  });
1028
1127
  }
@@ -1114,25 +1213,80 @@ function resolveGeometryInstanceBuffer(
1114
1213
  return [
1115
1214
  {
1116
1215
  instanceBuffer,
1117
- instanceBindGroup: resolveGeometryInstancesBindGroup(c, instanceBuffer),
1216
+ instanceBindGroup: resolveGeometryInstancesBindGroup(c, instanceBuffer, probeBinding?.buffer),
1217
+ probeOffset: probeBinding?.offset ?? -1,
1118
1218
  instanceCount,
1119
1219
  },
1120
1220
  ];
1121
1221
  }
1122
1222
 
1123
- function resolveGeometryInstancesBindGroup(
1223
+ export function resolveProbeBlendBuffer(
1224
+ c: _InternalRenderPipelineContext,
1225
+ record: import('../scene/probe-blend-record').ProbeBlendRecord | undefined,
1226
+ entityKey?: number,
1227
+ ): { readonly buffer: Buffer; readonly offset: number } {
1228
+ const { runtime, frameState } = c;
1229
+ const usage = runtime.device.caps.storageBuffer
1230
+ ? GPU_BUFFER_USAGE_STORAGE | GPU_BUFFER_USAGE_COPY_DST
1231
+ : GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST;
1232
+ if (record === undefined) throw new Error('probe blend record is required for probe allocation');
1233
+ if (entityKey === undefined) throw new Error('probe blend record requires an entity key');
1234
+ const offset = probeBlendRecordOffset(record.objectKey);
1235
+ const requiredCapacity = record.objectKey + 2;
1236
+ let buffer = frameState.probeBlendRecordBuffer;
1237
+ if (buffer === undefined || frameState.probeBlendRecordBufferCapacity < requiredCapacity) {
1238
+ const created = runtime.device.createBuffer({
1239
+ label: 'probe-blend-records',
1240
+ size: requiredCapacity * PROBE_BLEND_RECORD_STRIDE,
1241
+ usage,
1242
+ mappedAtCreation: false,
1243
+ });
1244
+ if (!created.ok) throw created.error;
1245
+ buffer = new GpuBuffer(runtime.device, created.value);
1246
+ frameState.probeBlendRecordBuffer = buffer;
1247
+ frameState.probeBlendRecordBufferCapacity = requiredCapacity;
1248
+ // The cache is tied to the previous backing allocation. A new buffer has
1249
+ // no valid record contents, so every later object must upload its full
1250
+ // 160B payload even when generation/bytes match the old allocation.
1251
+ frameState.probeBlendBuffers.clear();
1252
+ }
1253
+ const cached = frameState.probeBlendBuffers.get(entityKey);
1254
+ const unchanged =
1255
+ cached !== undefined &&
1256
+ cached.generation === record.generation &&
1257
+ cached.bytes.length === record.bytes.length &&
1258
+ cached.bytes.every((value, index) => value === record.bytes[index]);
1259
+ if (!unchanged) {
1260
+ const uploaded = runtime.device.queue.writeBuffer(buffer.handle, offset, record.bytes);
1261
+ if (!uploaded.ok) throw uploaded.error;
1262
+ // Publish the generation only after the complete 160B queue write succeeds.
1263
+ frameState.probeBlendBuffers.set(entityKey, {
1264
+ generation: record.generation,
1265
+ bytes: new Uint8Array(record.bytes),
1266
+ });
1267
+ }
1268
+ return { buffer: buffer.handle, offset };
1269
+ }
1270
+
1271
+ export function resolveGeometryInstancesBindGroup(
1124
1272
  c: _InternalRenderPipelineContext,
1125
1273
  instanceBuffer: Buffer,
1274
+ probeBuffer?: Buffer,
1126
1275
  ): BindGroup {
1127
1276
  const { runtime, pipelineState, frameState, bindGroupCounts } = c;
1277
+ const layout =
1278
+ probeBuffer === undefined
1279
+ ? pipelineState.instancesBindGroupLayout
1280
+ : pipelineState.probeInstancesBindGroupLayout;
1281
+ if (layout === undefined) throw new Error('probe instances bind-group layout is unavailable');
1128
1282
  return getOrCreateFromChain(
1129
1283
  frameState.instancesBgShared,
1130
- [pipelineState.instancesBindGroupLayout, instanceBuffer],
1131
- 'instances',
1284
+ probeBuffer === undefined ? [layout, instanceBuffer] : [layout, instanceBuffer, probeBuffer],
1285
+ probeBuffer === undefined ? 'instances-no-probe' : 'instances-probe',
1132
1286
  () => {
1133
1287
  const result = runtime.device.createBindGroup({
1134
1288
  label: 'pbr-instances-bg',
1135
- layout: pipelineState.instancesBindGroupLayout,
1289
+ layout,
1136
1290
  entries: [
1137
1291
  {
1138
1292
  binding: 0,
@@ -1141,6 +1295,17 @@ function resolveGeometryInstancesBindGroup(
1141
1295
  value: { buffer: instanceBuffer },
1142
1296
  },
1143
1297
  },
1298
+ ...(probeBuffer === undefined
1299
+ ? []
1300
+ : [
1301
+ {
1302
+ binding: 1,
1303
+ resource: {
1304
+ kind: 'buffer' as const,
1305
+ value: { buffer: probeBuffer, offset: 0, size: PROBE_BLEND_RECORD_BYTE_SIZE },
1306
+ },
1307
+ },
1308
+ ]),
1144
1309
  ],
1145
1310
  });
1146
1311
  if (!result.ok) throw result.error;
@@ -441,6 +441,13 @@ export interface StandardReflectionProbeMaterialProjection {
441
441
  readonly fallbackToSkylight: boolean;
442
442
  }
443
443
 
444
+ /** Keeps the fallback output as a same-pass, linear-HDR material projection. */
445
+ export function standardPbrFallbackDemand(
446
+ projection: StandardReflectionProbeMaterialProjection,
447
+ ): boolean {
448
+ return projection.probeIndex !== undefined || projection.fallbackToSkylight;
449
+ }
450
+
444
451
  /** Projects the selected scene row into the Standard material lane. */
445
452
  export function projectReflectionProbeMaterialBinding(
446
453
  binding: StandardReflectionProbeBinding,
@@ -662,7 +669,7 @@ export function defaultViewForUserRegionField(
662
669
  * `renderableIndex` per RenderSystem lifetime via the `seenIndices` Set
663
670
  * (the same warn-once anchor pattern used for missing-texture sprites).
664
671
  *
665
- * @param transformWorld The entity's resolved Transform.world mat4 (16 floats column-major).
672
+ * @param transformWorld The entity's resolved GlobalTransform.world mat4 (16 floats column-major).
666
673
  * @param slices The four anchor distances `[left, top, right, bottom]`
667
674
  * (sentinel `bottom < 0` for tile mode is consumed via abs()).
668
675
  * @param renderableIndex The entity index into the validated renderables list.
@@ -25,7 +25,10 @@ import {
25
25
  } from '../ibl/skylight-bind-group';
26
26
  import type { InstanceBufferCacheEntry } from '../instance-buffer-cache';
27
27
  import { SPRITE_PREMULTIPLIED_ALPHA_BLEND } from '../materials';
28
- import { SPRITE_PASS_PER_INSTANCE_REGION_VARIANT_SET } from '../pbr-pipeline';
28
+ import {
29
+ isStandardPbrMaterialShader,
30
+ SPRITE_PASS_PER_INSTANCE_REGION_VARIANT_SET,
31
+ } from '../pbr-pipeline';
29
32
  import {
30
33
  buildBeginRenderPassDescriptor,
31
34
  standardTopologyVariantSet,
@@ -33,6 +36,7 @@ import {
33
36
  } from '../pipeline-spec';
34
37
  import type { MaterialSnapshot, SpriteInstancesSnapshot } from '../render-system-extract';
35
38
  import { worldEntityKey } from './frame-snapshot';
39
+ import { resolveGeometryInstancesBindGroup, resolveProbeBlendBuffer } from './main-pass-geometry';
36
40
  import {
37
41
  BUILTIN_USER_REGION_TEXTURE_FIELDS,
38
42
  defaultViewForUserRegionField,
@@ -463,7 +467,7 @@ function recordSpriteTransparentPbrDraws(
463
467
  ) => BindGroup,
464
468
  meshGroup2: BindGroup | null,
465
469
  ): void {
466
- const { runtime, pipelineState, frameState, bindGroupCounts, validatedOrdered } = c;
470
+ const { runtime, pipelineState, validatedOrdered } = c;
467
471
  const isHdrTarget =
468
472
  c.tonemapActive || transparentPassColorFormat(c, pipelineState) === 'rgba16float';
469
473
  let lastPbrSubPipelineHandle: typeof pipelineState.unlitPipeline = null;
@@ -497,48 +501,6 @@ function recordSpriteTransparentPbrDraws(
497
501
  spritePass.setIndexBuffer(entry.mesh.indexBuffer.handle, entry.mesh.indexFormat);
498
502
  lastPbrSubIndexBuffer = entry.mesh.indexBuffer;
499
503
  }
500
- // Identity instance BG (instanceCount=1); reuse the per-entity cache.
501
- const identityInstBg: BindGroup = getOrCreatePerEntity(
502
- frameState.instancesBgPerEntity,
503
- worldEntityKey(entry.source.worldId, entry.source.entityKey),
504
- [pipelineState.identityInstanceBuffer],
505
- 'instances',
506
- () => {
507
- const result = runtime.device.createBindGroup({
508
- label: 'pbr-transparent-sub-instances-bg',
509
- layout: pipelineState.instancesBindGroupLayout,
510
- entries: [
511
- {
512
- binding: 0,
513
- resource: {
514
- kind: 'buffer',
515
- value: { buffer: pipelineState.identityInstanceBuffer },
516
- },
517
- },
518
- ],
519
- });
520
- if (!result.ok) throw result.error;
521
- return result.value;
522
- },
523
- bindGroupCounts,
524
- );
525
- spritePass.setBindGroup(3, identityInstBg);
526
-
527
- const subVariantSet = standardTopologyVariantSet(
528
- c.standardLighting,
529
- runtime.device.caps.storageBuffer,
530
- entry.mesh.layoutProjection.attributes.some((attribute) => attribute.key === 'color'),
531
- );
532
- const projectionVariantSetResult = variantSetFromVertexLayoutProjection(
533
- entry.mesh.layoutProjection,
534
- subVariantSet,
535
- );
536
- if (!projectionVariantSetResult.ok) {
537
- runtime.errorRegistry.fire(projectionVariantSetResult.error);
538
- continue;
539
- }
540
- const projectionVariantSet = projectionVariantSetResult.value;
541
-
542
504
  for (let smIdx = 0; smIdx < entry.mesh.submeshes.length; smIdx++) {
543
505
  const sm = entry.mesh.submeshes[smIdx];
544
506
  if (sm === undefined) continue;
@@ -549,6 +511,39 @@ function recordSpriteTransparentPbrDraws(
549
511
  if (submeshMaterial.transparent !== true) continue;
550
512
  const smShaderId = submeshMaterial.materialShaderId;
551
513
  if (smShaderId === undefined) continue;
514
+ const probeBlendAvailable =
515
+ runtime.device.caps.storageBuffer &&
516
+ entry.source.probeBlendRecord !== undefined &&
517
+ isStandardPbrMaterialShader(smShaderId);
518
+ const probeBinding = probeBlendAvailable
519
+ ? resolveProbeBlendBuffer(
520
+ c,
521
+ entry.source.probeBlendRecord,
522
+ worldEntityKey(entry.source.worldId, entry.source.entityKey),
523
+ )
524
+ : undefined;
525
+ const identityInstBg = resolveGeometryInstancesBindGroup(
526
+ c,
527
+ pipelineState.identityInstanceBuffer,
528
+ probeBinding?.buffer,
529
+ );
530
+ if (probeBinding === undefined) spritePass.setBindGroup(3, identityInstBg);
531
+ else spritePass.setBindGroup(3, identityInstBg, [probeBinding.offset]);
532
+ const subVariantSet = standardTopologyVariantSet(
533
+ c.standardLighting,
534
+ runtime.device.caps.storageBuffer,
535
+ entry.mesh.layoutProjection.attributes.some((attribute) => attribute.key === 'color'),
536
+ probeBlendAvailable,
537
+ );
538
+ const projectionVariantSetResult = variantSetFromVertexLayoutProjection(
539
+ entry.mesh.layoutProjection,
540
+ subVariantSet,
541
+ );
542
+ if (!projectionVariantSetResult.ok) {
543
+ runtime.errorRegistry.fire(projectionVariantSetResult.error);
544
+ continue;
545
+ }
546
+ const projectionVariantSet = projectionVariantSetResult.value;
552
547
  const subPipelineEntry =
553
548
  c.runtime.getMaterialShaderPipelineEntry?.(
554
549
  smShaderId,
@@ -837,21 +832,15 @@ function recordSpriteEntityDraws(
837
832
  );
838
833
  spriteInstanceCount = spriteInst.instanceCount;
839
834
  spriteInstanceBuffer = pipelineState.identityInstanceBuffer;
840
- const spriteInstBgResult = runtime.device.createBindGroup({
841
- label: 'sprite-pass-instances-bg',
842
- layout: pipelineState.instancesBindGroupLayout,
843
- entries: [
844
- {
845
- binding: 0,
846
- resource: {
847
- kind: 'buffer',
848
- value: { buffer: spriteInstanceBuffer },
849
- },
850
- },
851
- ],
852
- });
853
- if (!spriteInstBgResult.ok) throw spriteInstBgResult.error;
854
- spritePass.setBindGroup(3, spriteInstBgResult.value as BindGroup);
835
+ // Sprite shaders do not declare the Standard PBR Probe ABI. A
836
+ // retained scene record must therefore not change their group(3)
837
+ // bind-group shape.
838
+ const spriteInstBg = resolveGeometryInstancesBindGroup(
839
+ c,
840
+ spriteInstanceBuffer,
841
+ undefined,
842
+ );
843
+ spritePass.setBindGroup(3, spriteInstBg);
855
844
  spritePass.drawIndexed(spriteEntry.mesh.indexCount, spriteInstanceCount, 0, 0, 0);
856
845
  continue;
857
846
  }
@@ -959,32 +948,10 @@ function recordSpriteEntityDraws(
959
948
  ? c.hdrpClusterBindGroup
960
949
  : meshBindGroup;
961
950
 
962
- // M3 / w12: LDR sprite split pass per-entity instances BG cache.
963
- // Same pattern as #7.
964
- const spriteInstancesBg: BindGroup = getOrCreatePerEntity(
965
- frameState.instancesBgPerEntity,
966
- worldEntityKey(spriteEntry.source.worldId, spriteEntry.source.entityKey),
967
- [spriteInstanceBuffer],
968
- 'instances',
969
- () => {
970
- const result = runtime.device.createBindGroup({
971
- label: 'sprite-pass-instances-bg',
972
- layout: pipelineState.instancesBindGroupLayout,
973
- entries: [
974
- {
975
- binding: 0,
976
- resource: {
977
- kind: 'buffer',
978
- value: { buffer: spriteInstanceBuffer },
979
- },
980
- },
981
- ],
982
- });
983
- if (!result.ok) throw result.error;
984
- return result.value;
985
- },
986
- bindGroupCounts,
987
- );
951
+ // M3 / w12: LDR sprite split pass per-entity instances BG cache. Sprite
952
+ // shaders do not declare the Standard PBR Probe ABI, so retained probe
953
+ // records stay out of this bind-group path.
954
+ const spriteInstancesBg = resolveGeometryInstancesBindGroup(c, spriteInstanceBuffer, undefined);
988
955
 
989
956
  spritePass.setBindGroup(2, spriteGroup2 as BindGroup, [i * MESH_PER_ENTITY_STRIDE]);
990
957