@forgeax/engine-render 0.1.23 → 0.1.25

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 (322) hide show
  1. package/README.md +231 -10
  2. package/dist/assembly/bundler-contract.d.ts +9 -0
  3. package/dist/assembly/bundler-contract.d.ts.map +1 -0
  4. package/dist/assembly/device-feature-admission.d.ts +8 -0
  5. package/dist/assembly/device-feature-admission.d.ts.map +1 -1
  6. package/dist/assembly/extended-lighting-fallback.d.ts +13 -0
  7. package/dist/assembly/extended-lighting-fallback.d.ts.map +1 -0
  8. package/dist/assembly/factory.d.ts +7 -243
  9. package/dist/assembly/factory.d.ts.map +1 -1
  10. package/dist/assembly/material/assembly.d.ts +3 -2
  11. package/dist/assembly/material/assembly.d.ts.map +1 -1
  12. package/dist/assembly/material/standard-layer-projection.d.ts +10 -0
  13. package/dist/assembly/material/standard-layer-projection.d.ts.map +1 -0
  14. package/dist/assembly/material/surface-projection.d.ts +24 -0
  15. package/dist/assembly/material/surface-projection.d.ts.map +1 -0
  16. package/dist/assembly/material-shader-policy.d.ts +188 -0
  17. package/dist/assembly/material-shader-policy.d.ts.map +1 -0
  18. package/dist/assembly/material-shader-prewarm.d.ts +21 -0
  19. package/dist/assembly/material-shader-prewarm.d.ts.map +1 -0
  20. package/dist/assembly/mesh-ssbo-grow.d.ts +88 -0
  21. package/dist/assembly/mesh-ssbo-grow.d.ts.map +1 -0
  22. package/dist/assembly/recovery/device-loss-fanout.d.ts +14 -0
  23. package/dist/assembly/recovery/device-loss-fanout.d.ts.map +1 -0
  24. package/dist/assembly/recovery/generation.d.ts +46 -0
  25. package/dist/assembly/recovery/generation.d.ts.map +1 -0
  26. package/dist/assembly/recovery/recovery-attempt.d.ts +63 -0
  27. package/dist/assembly/recovery/recovery-attempt.d.ts.map +1 -0
  28. package/dist/assembly/recovery/renderer-recover.d.ts +115 -0
  29. package/dist/assembly/recovery/renderer-recover.d.ts.map +1 -0
  30. package/dist/assembly/renderer-facade.d.ts +10 -0
  31. package/dist/assembly/renderer-facade.d.ts.map +1 -0
  32. package/dist/assembly/renderer-frame-transaction.d.ts +17 -0
  33. package/dist/assembly/renderer-frame-transaction.d.ts.map +1 -1
  34. package/dist/assembly/renderer-helpers.d.ts +19 -0
  35. package/dist/assembly/renderer-helpers.d.ts.map +1 -0
  36. package/dist/assembly/renderer-lifecycle.d.ts +34 -0
  37. package/dist/assembly/renderer-lifecycle.d.ts.map +1 -1
  38. package/dist/assembly/shader-prewarm-policy.d.ts +15 -0
  39. package/dist/assembly/shader-prewarm-policy.d.ts.map +1 -0
  40. package/dist/assembly/webgpu-ready-contract.d.ts +9 -0
  41. package/dist/assembly/webgpu-ready-contract.d.ts.map +1 -0
  42. package/dist/assembly/webgpu-ready.d.ts +55 -0
  43. package/dist/assembly/webgpu-ready.d.ts.map +1 -0
  44. package/dist/assembly/webgpu-renderer.d.ts +152 -0
  45. package/dist/assembly/webgpu-renderer.d.ts.map +1 -0
  46. package/dist/assembly/webgpu-vertex-layouts.d.ts +89 -0
  47. package/dist/assembly/webgpu-vertex-layouts.d.ts.map +1 -0
  48. package/dist/assets/asset-decoders.d.ts.map +1 -1
  49. package/dist/authoring.mjs +1 -1
  50. package/dist/{chunk-FF7K3FSO.mjs → chunk-ADHHVYLW.mjs} +89 -89
  51. package/dist/chunk-ADHHVYLW.mjs.map +1 -0
  52. package/dist/{chunk-YALPS44A.mjs → chunk-E34VL5VI.mjs} +47 -47
  53. package/dist/chunk-E34VL5VI.mjs.map +1 -0
  54. package/dist/{chunk-4L3P6DT5.mjs → chunk-GE3SDD72.mjs} +37 -5
  55. package/dist/chunk-GE3SDD72.mjs.map +1 -0
  56. package/dist/{chunk-HZUPSYW7.mjs → chunk-HKXTW355.mjs} +105 -15
  57. package/dist/chunk-HKXTW355.mjs.map +1 -0
  58. package/dist/chunk-JSEDTZYS.mjs +413 -0
  59. package/dist/chunk-JSEDTZYS.mjs.map +1 -0
  60. package/dist/{chunk-FBC4R44J.mjs → chunk-KM2NOX2I.mjs} +57 -57
  61. package/dist/chunk-KM2NOX2I.mjs.map +1 -0
  62. package/dist/{chunk-DIYW3M5D.mjs → chunk-N3RT2EUR.mjs} +2288 -2238
  63. package/dist/chunk-N3RT2EUR.mjs.map +1 -0
  64. package/dist/{chunk-ZUWKAZ2I.mjs → chunk-TCLDX7SZ.mjs} +1167 -368
  65. package/dist/chunk-TCLDX7SZ.mjs.map +1 -0
  66. package/dist/{chunk-QODA5UKU.mjs → chunk-ZZ4YQ474.mjs} +2 -2
  67. package/dist/chunk-ZZ4YQ474.mjs.map +1 -0
  68. package/dist/components/instances.d.ts +13 -31
  69. package/dist/components/instances.d.ts.map +1 -1
  70. package/dist/construct-renderer.mjs +20688 -16314
  71. package/dist/construct-renderer.mjs.map +1 -1
  72. package/dist/device/device-scope.d.ts +2 -0
  73. package/dist/device/device-scope.d.ts.map +1 -1
  74. package/dist/device/gpu-residency.d.ts +24 -4
  75. package/dist/device/gpu-residency.d.ts.map +1 -1
  76. package/dist/environment/frame.d.ts +5 -0
  77. package/dist/environment/frame.d.ts.map +1 -1
  78. package/dist/environment/inspection.d.ts +7 -0
  79. package/dist/environment/inspection.d.ts.map +1 -1
  80. package/dist/environment/lifecycle.d.ts +20 -2
  81. package/dist/environment/lifecycle.d.ts.map +1 -1
  82. package/dist/errors/recover.d.ts +58 -2
  83. package/dist/errors/recover.d.ts.map +1 -1
  84. package/dist/errors/render.d.ts +13 -0
  85. package/dist/errors/render.d.ts.map +1 -1
  86. package/dist/extract/contracts.d.ts +899 -0
  87. package/dist/extract/contracts.d.ts.map +1 -0
  88. package/dist/features/host.d.ts +2 -0
  89. package/dist/features/host.d.ts.map +1 -1
  90. package/dist/features/prepared-gpu-work.d.ts +3 -0
  91. package/dist/features/prepared-gpu-work.d.ts.map +1 -1
  92. package/dist/features/prepared-graphics-store.d.ts +2 -0
  93. package/dist/features/prepared-graphics-store.d.ts.map +1 -1
  94. package/dist/gpu-driven/production-raster.d.ts +34 -1
  95. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  96. package/dist/gpu-scene.d.ts +5 -0
  97. package/dist/gpu-scene.d.ts.map +1 -1
  98. package/dist/ibl/IblPipelineCache.d.ts +7 -6
  99. package/dist/ibl/IblPipelineCache.d.ts.map +1 -1
  100. package/dist/ibl/skylight-bind-group.d.ts +7 -1
  101. package/dist/ibl/skylight-bind-group.d.ts.map +1 -1
  102. package/dist/index.d.ts +2 -1
  103. package/dist/index.d.ts.map +1 -1
  104. package/dist/index.mjs +27 -9
  105. package/dist/index.mjs.map +1 -1
  106. package/dist/instance-buffer-cache.d.ts +7 -1
  107. package/dist/instance-buffer-cache.d.ts.map +1 -1
  108. package/dist/instances.d.ts +101 -0
  109. package/dist/instances.d.ts.map +1 -0
  110. package/dist/internal.mjs +6 -6
  111. package/dist/light-buffer-layout.d.ts +3 -0
  112. package/dist/light-buffer-layout.d.ts.map +1 -1
  113. package/dist/materials.d.ts +96 -11
  114. package/dist/materials.d.ts.map +1 -1
  115. package/dist/mesh-material-bindings.d.ts +56 -0
  116. package/dist/mesh-material-bindings.d.ts.map +1 -1
  117. package/dist/occlusion-proxy.d.ts +4 -0
  118. package/dist/occlusion-proxy.d.ts.map +1 -0
  119. package/dist/pbr-pipeline.d.ts +32 -9
  120. package/dist/pbr-pipeline.d.ts.map +1 -1
  121. package/dist/pipeline-builder.d.ts.map +1 -1
  122. package/dist/pipeline-spec.d.ts.map +1 -1
  123. package/dist/points-lines/prepare.d.ts +3 -0
  124. package/dist/points-lines/prepare.d.ts.map +1 -1
  125. package/dist/points-lines/standard-owner.d.ts +33 -0
  126. package/dist/points-lines/standard-owner.d.ts.map +1 -0
  127. package/dist/record/frame-snapshot.d.ts +18 -2
  128. package/dist/record/frame-snapshot.d.ts.map +1 -1
  129. package/dist/record/frame-targets.d.ts.map +1 -1
  130. package/dist/record/frame.d.ts +14 -2
  131. package/dist/record/frame.d.ts.map +1 -1
  132. package/dist/record/main-pass-geometry.d.ts +36 -4
  133. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  134. package/dist/record/main-pass-material.d.ts +7 -17
  135. package/dist/record/main-pass-material.d.ts.map +1 -1
  136. package/dist/record/main-pass.d.ts.map +1 -1
  137. package/dist/record/mesh-ssbo.d.ts +11 -3
  138. package/dist/record/mesh-ssbo.d.ts.map +1 -1
  139. package/dist/record/recovery-pipeline.d.ts +30 -0
  140. package/dist/record/recovery-pipeline.d.ts.map +1 -0
  141. package/dist/record/render-context.d.ts +28 -5
  142. package/dist/record/render-context.d.ts.map +1 -1
  143. package/dist/record/shadow-pass.d.ts +11 -1
  144. package/dist/record/shadow-pass.d.ts.map +1 -1
  145. package/dist/record/typed-frame-graph.d.ts +1 -1
  146. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  147. package/dist/recovery/render-system-candidate.d.ts +65 -0
  148. package/dist/recovery/render-system-candidate.d.ts.map +1 -0
  149. package/dist/render-contract.d.ts +68 -7
  150. package/dist/render-contract.d.ts.map +1 -1
  151. package/dist/render-system-extract-tail.d.ts +16 -0
  152. package/dist/render-system-extract-tail.d.ts.map +1 -0
  153. package/dist/render-system-extract.d.ts +180 -22
  154. package/dist/render-system-extract.d.ts.map +1 -1
  155. package/dist/render-system.d.ts +131 -4
  156. package/dist/render-system.d.ts.map +1 -1
  157. package/dist/scene/render-scene-types.d.ts +2 -0
  158. package/dist/scene/render-scene-types.d.ts.map +1 -1
  159. package/dist/scene/render-scene.d.ts +31 -1
  160. package/dist/scene/render-scene.d.ts.map +1 -1
  161. package/dist/systems/skin-palette-allocator.d.ts +6 -0
  162. package/dist/systems/skin-palette-allocator.d.ts.map +1 -1
  163. package/dist/temporal/frame-coordinator.d.ts.map +1 -1
  164. package/dist/temporal/index.mjs +6 -6
  165. package/dist/temporal/taa-history-store.d.ts +15 -1
  166. package/dist/temporal/taa-history-store.d.ts.map +1 -1
  167. package/dist/temporal/transaction.d.ts +5 -0
  168. package/dist/temporal/transaction.d.ts.map +1 -1
  169. package/package.json +20 -20
  170. package/src/__tests__/device-feature-admission.unit.test.ts +6 -0
  171. package/src/__tests__/device-loss-fanout.unit.test.ts +107 -0
  172. package/src/__tests__/device-scope-lifecycle.unit.test.ts +21 -0
  173. package/src/__tests__/environment-generation.integration.test.ts +23 -0
  174. package/src/__tests__/expose-renderer-errors.unit.test.ts +34 -0
  175. package/src/__tests__/factory-contract.integration.test.ts +122 -7
  176. package/src/__tests__/fallback-row-stride-surface.unit.test.ts +4 -1
  177. package/src/__tests__/gpu-scene.unit.test.ts +38 -2
  178. package/src/__tests__/ibl/face-uniforms.unit.test.ts +2 -0
  179. package/src/__tests__/ibl-residency-publication.unit.test.ts +113 -0
  180. package/src/__tests__/instances-store.unit.test.ts +46 -0
  181. package/src/__tests__/instances-world-ownership.integration.test.ts +62 -0
  182. package/src/__tests__/material-contract-inventory.unit.test.ts +29 -22
  183. package/src/__tests__/material-cooked-projection.unit.test.ts +19 -1
  184. package/src/__tests__/material-pass-policy.unit.test.ts +69 -0
  185. package/src/__tests__/material-snapshot-mutation.integration.test.ts +69 -0
  186. package/src/__tests__/material-static-dynamic-route.unit.test.ts +29 -0
  187. package/src/__tests__/material-stride-surface.unit.test.ts +7 -3
  188. package/src/__tests__/material-surface-authoring.test-d.ts +62 -0
  189. package/src/__tests__/materials-standard-contract.unit.test.ts +104 -0
  190. package/src/__tests__/materials-transmission-validation.unit.test.ts +24 -0
  191. package/src/__tests__/mesh-buffer-usage-surface.unit.test.ts +1 -5
  192. package/src/__tests__/physical-clearcoat.integration.test.ts +89 -0
  193. package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +1 -1
  194. package/src/__tests__/prepared-graphics-pipeline-warmup.unit.test.ts +1 -1
  195. package/src/__tests__/recover-error-policy-owner.unit.test.ts +6 -3
  196. package/src/__tests__/recovery-candidate-prepare.contract.test.ts +140 -0
  197. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +127 -18
  198. package/src/__tests__/renderer-frame-generation-fence.integration.test.ts +47 -0
  199. package/src/__tests__/renderer-generation-assembly.unit.test.ts +77 -0
  200. package/src/__tests__/renderer-generation-failure.unit.test.ts +108 -0
  201. package/src/__tests__/renderer-producer-root-matrix.integration.test.ts +33 -0
  202. package/src/__tests__/renderer-recovery-capability-matrix.unit.test.ts +59 -0
  203. package/src/__tests__/renderer-recovery-contract.unit.test.ts +73 -0
  204. package/src/__tests__/renderer-recovery-lifecycle.unit.test.ts +216 -0
  205. package/src/__tests__/renderer-target-history-recovery.integration.test.ts +80 -0
  206. package/src/__tests__/shader-manifest-fixture.ts +13 -16
  207. package/src/__tests__/shader-stage-surface.unit.test.ts +1 -1
  208. package/src/__tests__/skin-motion-regression.unit.test.ts +33 -0
  209. package/src/__tests__/skinned-shadow-caster.test.ts +30 -2
  210. package/src/__tests__/standard-layer-projection.unit.test.ts +23 -0
  211. package/src/__tests__/surface-adjacent-regressions.integration.test.ts +60 -0
  212. package/src/__tests__/surface-standard-pipeline.browser.test.ts +20 -0
  213. package/src/__tests__/surface-standard-pipeline.dawn.test.ts +20 -0
  214. package/src/__tests__/surface-standard-pipeline.fixture.ts +132 -0
  215. package/src/__tests__/surface-variant-provenance.integration.test.ts +33 -0
  216. package/src/__tests__/transmission-prewarm.unit.test.ts +95 -85
  217. package/src/__tests__/volumetric-fog-budget-recovery.integration.test.ts +1 -1
  218. package/src/__tests__/volumetric-fog-topology.unit.test.ts +3 -1
  219. package/src/assembly/bundler-contract.ts +9 -0
  220. package/src/assembly/device-feature-admission.ts +15 -5
  221. package/src/assembly/extended-lighting-fallback.ts +216 -0
  222. package/src/assembly/factory.ts +83 -10088
  223. package/src/assembly/material/__tests__/standard-layer-plan-projection.integration.test.ts +18 -0
  224. package/src/assembly/material/__tests__/surface-pass-projection.integration.test.ts +62 -0
  225. package/src/assembly/material/assembly.ts +18 -2
  226. package/src/assembly/material/standard-layer-projection.ts +16 -0
  227. package/src/assembly/material/surface-projection.ts +90 -0
  228. package/src/assembly/material-shader-policy.ts +932 -0
  229. package/src/assembly/material-shader-prewarm.ts +88 -0
  230. package/src/assembly/mesh-ssbo-grow.ts +270 -0
  231. package/src/assembly/recovery/device-loss-fanout.ts +89 -0
  232. package/src/assembly/recovery/generation.ts +141 -0
  233. package/src/assembly/recovery/recovery-attempt.ts +800 -0
  234. package/src/assembly/recovery/renderer-recover.ts +906 -0
  235. package/src/assembly/renderer-facade.ts +342 -0
  236. package/src/assembly/renderer-frame-transaction.ts +45 -0
  237. package/src/assembly/renderer-helpers.ts +99 -0
  238. package/src/assembly/renderer-lifecycle.ts +159 -0
  239. package/src/assembly/shader-prewarm-policy.ts +157 -0
  240. package/src/assembly/webgpu-ready-contract.ts +14 -0
  241. package/src/assembly/webgpu-ready.ts +4066 -0
  242. package/src/assembly/webgpu-renderer.ts +4027 -0
  243. package/src/assembly/webgpu-vertex-layouts.ts +132 -0
  244. package/src/assets/asset-decoders.ts +27 -4
  245. package/src/components/camera.ts +1 -1
  246. package/src/components/instances.ts +14 -197
  247. package/src/debug-draw-glue.ts +1 -1
  248. package/src/device/device-scope.ts +35 -6
  249. package/src/device/gpu-residency.ts +194 -26
  250. package/src/environment/frame.ts +6 -0
  251. package/src/environment/inspection.ts +10 -0
  252. package/src/environment/lifecycle.ts +114 -8
  253. package/src/errors/recover.ts +76 -5
  254. package/src/errors/render.ts +17 -0
  255. package/src/extract/contracts.ts +974 -0
  256. package/src/features/host.ts +16 -0
  257. package/src/features/prepared-gpu-work.ts +20 -2
  258. package/src/features/prepared-graphics-store.ts +17 -0
  259. package/src/gpu-driven/production-raster.ts +131 -3
  260. package/src/gpu-scene.ts +87 -31
  261. package/src/ibl/IblPipelineCache.ts +134 -50
  262. package/src/ibl/skylight-bind-group.ts +26 -0
  263. package/src/index.ts +10 -1
  264. package/src/instance-buffer-cache.ts +14 -1
  265. package/src/instances.ts +288 -0
  266. package/src/light-buffer-layout.ts +3 -0
  267. package/src/materials.ts +365 -53
  268. package/src/mesh-material-bindings.ts +59 -0
  269. package/src/occlusion-proxy.ts +64 -0
  270. package/src/pbr-pipeline.ts +139 -15
  271. package/src/pipeline-builder.ts +14 -10
  272. package/src/pipeline-spec.ts +6 -14
  273. package/src/points-lines/__tests__/cache-recovery.unit.test.ts +22 -0
  274. package/src/points-lines/prepare.ts +9 -0
  275. package/src/points-lines/standard-owner.ts +403 -0
  276. package/src/record/__tests__/frame-targets-recovery.unit.test.ts +60 -0
  277. package/src/record/__tests__/main-pass-geometry.unit.test.ts +26 -1
  278. package/src/record/__tests__/mesh-ssbo.unit.test.ts +38 -0
  279. package/src/record/__tests__/recovery-pipeline.unit.test.ts +124 -0
  280. package/src/record/__tests__/sprite-material-abi.unit.test.ts +28 -13
  281. package/src/record/__tests__/standard-pbr-ubo-layout.unit.test.ts +47 -4
  282. package/src/record/__tests__/temporal-payload.unit.test.ts +22 -43
  283. package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +1 -1
  284. package/src/record/frame-snapshot.ts +34 -2
  285. package/src/record/frame-targets.ts +11 -1
  286. package/src/record/frame.ts +37 -4
  287. package/src/record/helpers.ts +1 -1
  288. package/src/record/main-pass-geometry.ts +727 -63
  289. package/src/record/main-pass-material.ts +316 -209
  290. package/src/record/main-pass-sprite-draws.ts +7 -8
  291. package/src/record/main-pass.ts +94 -6
  292. package/src/record/mesh-ssbo.ts +32 -10
  293. package/src/record/recovery-pipeline.ts +695 -0
  294. package/src/record/render-context.ts +39 -4
  295. package/src/record/shadow-pass.ts +46 -99
  296. package/src/record/typed-frame-graph.ts +45 -1
  297. package/src/record/view-ubo.ts +3 -3
  298. package/src/recovery/render-system-candidate.ts +876 -0
  299. package/src/render-contract.ts +69 -7
  300. package/src/render-system-extract-tail.ts +3039 -0
  301. package/src/render-system-extract.ts +177 -3003
  302. package/src/render-system.ts +906 -662
  303. package/src/scene/render-scene-types.ts +2 -0
  304. package/src/scene/render-scene.ts +703 -37
  305. package/src/systems/skin-palette-allocator.ts +129 -7
  306. package/src/temporal/frame-coordinator.ts +4 -1
  307. package/src/temporal/taa-history-store.ts +46 -2
  308. package/src/temporal/temporal-view.ts +1 -1
  309. package/src/temporal/transaction.ts +9 -0
  310. package/src/tilemap-chunk-extract-system.ts +1 -1
  311. package/src/transmission/__tests__/standard-transmission.dawn.test.ts +19 -12
  312. package/src/transmission/__tests__/surface-compatibility.integration.test.ts +32 -0
  313. package/dist/chunk-4L3P6DT5.mjs.map +0 -1
  314. package/dist/chunk-4NPUASZR.mjs +0 -203
  315. package/dist/chunk-4NPUASZR.mjs.map +0 -1
  316. package/dist/chunk-DIYW3M5D.mjs.map +0 -1
  317. package/dist/chunk-FBC4R44J.mjs.map +0 -1
  318. package/dist/chunk-FF7K3FSO.mjs.map +0 -1
  319. package/dist/chunk-HZUPSYW7.mjs.map +0 -1
  320. package/dist/chunk-QODA5UKU.mjs.map +0 -1
  321. package/dist/chunk-YALPS44A.mjs.map +0 -1
  322. package/dist/chunk-ZUWKAZ2I.mjs.map +0 -1
@@ -1,10 +1,27 @@
1
1
  import type { Buffer, Sampler, TextureView } from '@forgeax/engine-rhi';
2
2
  import { describe, expect, it } from 'vitest';
3
3
  import { assembleMaterialWithSkylightEntries } from '../../ibl/skylight-bind-group';
4
+ import { defaultViewForUserRegionField } from '../main-pass-material';
4
5
  import { buildSpritePassBaseMaterialEntries } from '../main-pass-sprite-draws';
5
6
  import type { PipelineState } from '../render-context';
6
7
 
7
8
  describe('sprite Standard material ABI', () => {
9
+ it('uses the dedicated neutral anisotropy fallback instead of white', () => {
10
+ const white = {} as TextureView;
11
+ const anisotropy = {} as TextureView;
12
+ const pipelineState = {
13
+ defaultWhiteTextureView: white,
14
+ defaultAnisotropyTextureView: anisotropy,
15
+ defaultNormalTextureView: {} as TextureView,
16
+ fallbackTextureView: {} as TextureView,
17
+ } as PipelineState;
18
+
19
+ expect(defaultViewForUserRegionField('anisotropyTexture', pipelineState)).toBe(anisotropy);
20
+ expect(defaultViewForUserRegionField('baseColorTexture', pipelineState)).toBe(
21
+ pipelineState.fallbackTextureView,
22
+ );
23
+ });
24
+
8
25
  it('covers transmission and thickness before IBL injection', () => {
9
26
  const sampler = {} as Sampler;
10
27
  const white = {} as TextureView;
@@ -21,14 +38,12 @@ describe('sprite Standard material ABI', () => {
21
38
  const entries = buildSpritePassBaseMaterialEntries(pipelineState, spriteTexture);
22
39
 
23
40
  expect(entries.map((entry) => entry.binding)).toEqual([
24
- 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
41
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
25
42
  ]);
26
43
  expect(entries[13]).toMatchObject({ binding: 13, resource: { kind: 'sampler' } });
27
44
  expect(entries[14]).toMatchObject({ binding: 14, resource: { kind: 'textureView' } });
28
- expect(entries[15]).toMatchObject({ binding: 15, resource: { kind: 'sampler' } });
29
- expect(entries[16]).toMatchObject({ binding: 16, resource: { kind: 'textureView' } });
30
45
  expect(entries[2]?.resource).toMatchObject({ kind: 'textureView', value: spriteTexture });
31
- expect(entries).toHaveLength(17);
46
+ expect(entries).toHaveLength(15);
32
47
 
33
48
  const merged = assembleMaterialWithSkylightEntries(entries, {
34
49
  irradianceView: {} as TextureView,
@@ -39,19 +54,19 @@ describe('sprite Standard material ABI', () => {
39
54
  brdfLutSampler: sampler,
40
55
  intensityBuffer: {} as Buffer,
41
56
  });
42
- expect(merged[17]).toMatchObject({
43
- binding: 17,
57
+ expect(merged[15]).toMatchObject({
58
+ binding: 15,
44
59
  resource: { kind: 'textureView' },
45
60
  });
46
- expect(merged[24]).toMatchObject({
47
- binding: 24,
61
+ expect(merged[22]).toMatchObject({
62
+ binding: 22,
48
63
  resource: { kind: 'sampler', value: sampler },
49
64
  });
50
- expect(merged[25]).toMatchObject({
51
- binding: 25,
65
+ expect(merged[23]).toMatchObject({
66
+ binding: 23,
52
67
  resource: { kind: 'textureView' },
53
68
  });
54
- expect(merged).toHaveLength(26);
69
+ expect(merged).toHaveLength(24);
55
70
 
56
71
  const backdrop = {} as TextureView;
57
72
  const active = assembleMaterialWithSkylightEntries(
@@ -67,8 +82,8 @@ describe('sprite Standard material ABI', () => {
67
82
  },
68
83
  { sampler, backdropView: backdrop },
69
84
  );
70
- expect(active[25]).toMatchObject({
71
- binding: 25,
85
+ expect(active[23]).toMatchObject({
86
+ binding: 23,
72
87
  resource: { kind: 'textureView', value: backdrop },
73
88
  });
74
89
  });
@@ -14,7 +14,7 @@ describe('Standard PBR UBO layout', () => {
14
14
  derived.coordinateRecords.map((record) => [record.parameter, record.offset]),
15
15
  );
16
16
 
17
- expect(derived.totalBytes).toBe(384);
17
+ expect(derived.totalBytes).toBe(736);
18
18
  expect(numeric.get('normalScale')).toBe(92);
19
19
  expect(numeric.get('transmission')).toBe(96);
20
20
  expect(numeric.get('ior')).toBe(100);
@@ -26,6 +26,26 @@ describe('Standard PBR UBO layout', () => {
26
26
  expect(coordinates.get('thicknessTexture')).toBe(352);
27
27
  });
28
28
 
29
+ it('reserves the physical-layer tail required by the clearcoat shader ABI', () => {
30
+ const material = {
31
+ baseColor: vec3.create(0.7, 0.7, 0.7),
32
+ metallic: 0,
33
+ roughness: 0.4,
34
+ materialShaderId: 'forgeax::default-standard-pbr',
35
+ materialParamSchema: DEFAULT_STANDARD_PBR_PARAM_SCHEMA,
36
+ paramSnapshot: {
37
+ clearcoat: 0.8,
38
+ clearcoatRoughness: 0.2,
39
+ clearcoatNormalScale: 0.75,
40
+ },
41
+ } satisfies MaterialSnapshot;
42
+ const payload = buildPbrMaterialUboPayload(material);
43
+ const f32 = new Float32Array(payload.buffer);
44
+
45
+ expect(payload.byteLength).toBe(736);
46
+ expect(f32.slice(108, 111)).toEqual(new Float32Array([0.8, 0.2, 0.75]));
47
+ });
48
+
29
49
  it('writes transmission scalars and defaults at the reflected offsets', () => {
30
50
  const material = {
31
51
  baseColor: vec3.create(0.7, 0.7, 0.7),
@@ -33,6 +53,7 @@ describe('Standard PBR UBO layout', () => {
33
53
  roughness: 0.4,
34
54
  normalScale: 0.25,
35
55
  materialShaderId: 'forgeax::default-standard-pbr',
56
+ materialParamSchema: DEFAULT_STANDARD_PBR_PARAM_SCHEMA,
36
57
  paramSnapshot: {
37
58
  transmission: 0.75,
38
59
  ior: 1.6,
@@ -48,7 +69,7 @@ describe('Standard PBR UBO layout', () => {
48
69
  );
49
70
  });
50
71
 
51
- it('uses all eight identity coordinate records in the no-snapshot fallback', () => {
72
+ it('uses all seven user-region identity coordinate records in the no-snapshot fallback', () => {
52
73
  const material = {
53
74
  baseColor: vec3.create(0.7, 0.7, 0.7),
54
75
  metallic: 0,
@@ -60,8 +81,30 @@ describe('Standard PBR UBO layout', () => {
60
81
  applyMaterialTextureUvScales(payload, material, new World());
61
82
 
62
83
  const f32 = new Float32Array(payload.buffer);
63
- expect(f32.slice(32, 96)).toEqual(
64
- new Float32Array(Array(8).fill([0, 0, 1, 1, 0, 0, 1, 1]).flat()),
84
+ expect(f32.slice(32, 88)).toEqual(
85
+ new Float32Array(Array(7).fill([0, 0, 1, 1, 0, 0, 1, 1]).flat()),
65
86
  );
66
87
  });
88
+
89
+ it('keeps the physical tail intact when standard coordinates are projected', () => {
90
+ const material = {
91
+ baseColor: vec3.create(0.7, 0.7, 0.7),
92
+ metallic: 0,
93
+ roughness: 0.4,
94
+ materialShaderId: 'forgeax::default-standard-pbr',
95
+ materialParamSchema: DEFAULT_STANDARD_PBR_PARAM_SCHEMA,
96
+ paramSnapshot: {
97
+ clearcoat: 1,
98
+ clearcoatRoughness: 0.18,
99
+ clearcoatNormalScale: 0.75,
100
+ },
101
+ } satisfies MaterialSnapshot;
102
+ const payload = buildPbrMaterialUboPayload(material);
103
+
104
+ applyMaterialTextureUvScales(payload, material, new World());
105
+
106
+ const f32 = new Float32Array(payload.buffer);
107
+ expect(f32.slice(108, 111)).toEqual(new Float32Array([1, 0.18, 0.75]));
108
+ expect(f32.slice(32, 40)).toEqual(new Float32Array([0, 0, 1, 1, 0, 0, 1, 1]));
109
+ });
67
110
  });
@@ -2,7 +2,7 @@ import { vec3 } from '@forgeax/engine-math';
2
2
  import type { Buffer, RhiQueue } from '@forgeax/engine-rhi';
3
3
  import { describe, expect, it } from 'vitest';
4
4
  import type { CameraSnapshot } from '../../render-contract';
5
- import type { RenderableSnapshot, SpotLightSnapshot } from '../../render-system-extract';
5
+ import type { RenderableSnapshot } from '../../render-system-extract';
6
6
  import type { TemporalView } from '../../temporal/temporal-view';
7
7
  import type { ValidatedRenderable } from '../frame-snapshot';
8
8
  import { uploadMeshSsboBatch } from '../mesh-ssbo';
@@ -98,27 +98,8 @@ function temporalView(): TemporalView {
98
98
  }
99
99
 
100
100
  describe('temporal GPU payloads', () => {
101
- it('publishes a projector-only SpotLight matrix into the explicit lane-0 contract', () => {
101
+ it('keeps the zero-directional fallback direction finite and unscaled', () => {
102
102
  const { queue, uploads } = recordingQueue();
103
- const projectorMatrix = translatedMatrix(9);
104
- const projector: SpotLightSnapshot = {
105
- kind: 'spot',
106
- entity: 4,
107
- position: vec3.create(0, 2, 0),
108
- direction: vec3.create(0, -1, 0),
109
- color: vec3.create(1, 1, 1),
110
- intensity: 1,
111
- invRangeSquared: 0.04,
112
- cosInner: 0.95,
113
- cosOuter: 0.8,
114
- castShadow: false,
115
- lightViewProj: projectorMatrix,
116
- mapSize: 512,
117
- nearPlane: 0.1,
118
- farPlane: 25,
119
- shadowAtlasTile: -1,
120
- projectorAsset: { kind: 'texture' } as never,
121
- };
122
103
  writeViewUbo(
123
104
  queue,
124
105
  {} as Buffer,
@@ -126,19 +107,20 @@ describe('temporal GPU payloads', () => {
126
107
  {
127
108
  kind: 'directional',
128
109
  direction: vec3.create(0, -1, 0),
129
- color: vec3.create(1, 1, 1),
130
- intensity: 1,
110
+ color: vec3.create(0, 0, 0),
111
+ intensity: 0,
131
112
  },
132
- { point: [], spot: [projector] } as never,
133
- [projector],
134
- undefined,
135
- 0,
113
+ { point: [], spot: [] } as never,
114
+ [],
136
115
  );
116
+
137
117
  const payload = new Float32Array(uploads[0]?.buffer ?? new ArrayBuffer(0));
138
- expect(payload[132 + 12]).toBe(9);
118
+ expect(Array.from(payload.slice(16, 19))).toEqual([0, -1, 0]);
119
+ expect(Array.from(payload.slice(20, 23))).toEqual([0, 0, 0]);
120
+ expect(Array.from(payload.slice(16, 23)).every(Number.isFinite)).toBe(true);
139
121
  });
140
122
 
141
- it('writes temporal current/previous projection after the spot-light lanes', () => {
123
+ it('keeps directional orientation independent from light intensity', () => {
142
124
  const { queue, uploads } = recordingQueue();
143
125
  writeViewUbo(
144
126
  queue,
@@ -146,28 +128,20 @@ describe('temporal GPU payloads', () => {
146
128
  camera(),
147
129
  {
148
130
  kind: 'directional',
149
- direction: vec3.create(0, -1, 0),
131
+ direction: vec3.create(0.25, -0.5, 0.75),
150
132
  color: vec3.create(1, 1, 1),
151
- intensity: 1,
133
+ intensity: 4,
152
134
  },
153
135
  { point: [], spot: [] } as never,
154
136
  [],
155
- temporalView(),
156
137
  );
157
138
 
158
139
  const payload = new Float32Array(uploads[0]?.buffer ?? new ArrayBuffer(0));
159
- expect(payload.length).toBe(240);
160
- expect(payload[196]).toBeCloseTo(Math.sqrt(3));
161
- expect(payload[212]).toBe(3);
162
- expect(payload[228]).toBeCloseTo(0.1);
163
- expect(payload[229]).toBe(100);
164
- expect(payload[230]).toBe(0);
140
+ expect(Array.from(payload.slice(16, 19))).toEqual([0.25, -0.5, 0.75]);
165
141
  });
166
142
 
167
- it('keeps the accepted previous camera in the temporal ABI', () => {
143
+ it('writes temporal current/previous projection after the spot-light lanes', () => {
168
144
  const { queue, uploads } = recordingQueue();
169
- const acceptedCamera = [7, 8, 9] as const;
170
- const temporal = { ...temporalView(), previousCameraPosition: acceptedCamera };
171
145
  writeViewUbo(
172
146
  queue,
173
147
  {} as Buffer,
@@ -180,11 +154,16 @@ describe('temporal GPU payloads', () => {
180
154
  },
181
155
  { point: [], spot: [] } as never,
182
156
  [],
183
- temporal as never,
157
+ temporalView(),
184
158
  );
185
159
 
186
160
  const payload = new Float32Array(uploads[0]?.buffer ?? new ArrayBuffer(0));
187
- expect(Array.from(payload.slice(232, 236))).toEqual([...acceptedCamera, 1]);
161
+ expect(payload.length).toBe(240);
162
+ expect(payload[196]).toBeCloseTo(Math.sqrt(3));
163
+ expect(payload[212]).toBe(3);
164
+ expect(payload[228]).toBeCloseTo(0.1);
165
+ expect(payload[229]).toBe(100);
166
+ expect(payload[230]).toBe(0);
188
167
  });
189
168
 
190
169
  it('seeds previousWorld on the first frame and uses the submitted snapshot later', () => {
@@ -52,7 +52,7 @@ function executeCapture(overrides: CaptureOverrides = {}) {
52
52
  usage: GPU_TEXTURE_USAGE_COPY_SRC,
53
53
  identity: overrides.identity ?? {
54
54
  graphGeneration: 7,
55
- frameId: 12,
55
+ frameId: 13,
56
56
  textureIdentity: getTextureIdentity(texture),
57
57
  },
58
58
  },
@@ -18,6 +18,8 @@ import type { DirectionalShadowQuality } from '../components/directional-shadow-
18
18
  import type { EnvironmentGeneration } from '../environment/generation';
19
19
  import type { EnvironmentLifecycle } from '../environment/lifecycle';
20
20
  import type { GpuBuffer } from '../gpu-resource';
21
+ import type { InstanceCollectionId, InstanceResidency } from '../instances';
22
+ import type { IblBindingInspection } from '../mesh-material-bindings';
21
23
  import type { StandardLightingInspection } from '../pipeline/standard-lighting/inspection';
22
24
  import type { PointShadowInspection } from '../point-shadow-inspection';
23
25
  import type { TemporalGpuState } from '../temporal/gpu';
@@ -51,6 +53,17 @@ export function worldEntityKey(worldId: number, entityKey: number): number {
51
53
  return worldId * 4294967296 + entityKey; // 2^32 = 4294967296
52
54
  }
53
55
 
56
+ /** Per-renderer projection identity, with entity keys for sprite/fold snapshots. */
57
+ export function instanceCollectionCacheKey(
58
+ worldId: number,
59
+ instance: { readonly cacheKey: number; readonly collectionId?: number },
60
+ ): number {
61
+ // Keep collection entries in a disjoint numeric namespace from the shared
62
+ // legacy sprite/fold cache. Fold keys occupy -1..-(2^32), while ordinary
63
+ // world/entity keys are non-negative; this range starts below both.
64
+ return -(0x1_0000_0000 + worldEntityKey(worldId, instance.collectionId ?? instance.cacheKey));
65
+ }
66
+
54
67
  /**
55
68
  * Cross-frame cache entry for a material bind group whose source material and
56
69
  * all resolved GPU resources are stable. The material snapshot identity is
@@ -138,6 +151,18 @@ export interface CurrentGraphTarget {
138
151
  const textureIdentities = new WeakMap<object, number>();
139
152
  let nextTextureIdentity = 1;
140
153
 
154
+ const opaqueResourceIdentities = new WeakMap<object, number>();
155
+ let nextOpaqueResourceIdentity = 1;
156
+
157
+ /** Stable detached identity for a renderer-owned opaque resource handle. */
158
+ export function getOpaqueResourceIdentity(resource: object): number {
159
+ const existing = opaqueResourceIdentities.get(resource);
160
+ if (existing !== undefined) return existing;
161
+ const identity = nextOpaqueResourceIdentity++;
162
+ opaqueResourceIdentities.set(resource, identity);
163
+ return identity;
164
+ }
165
+
141
166
  /**
142
167
  * Project an opaque RHI texture handle to a stable diagnostic identity.
143
168
  * RHI handles intentionally expose no serializable fields; object identity is
@@ -276,8 +301,9 @@ export function makeZeroCameraFallbackSnapshot(): CameraSnapshot {
276
301
  * Owned by the `createRenderSystem` closure; advanced once per
277
302
  * `recordFrame` invocation. The `instanceBuffers` map holds the per-entity
278
303
  * GPU storage buffers for Instances-bearing entities (cache key = the
279
- * packed Entity u32 surfaced via `InstancesSnapshot.cacheKey`); entries
280
- * are recreated when the `archVersion` bumps or `byteLength` changes.
304
+ * renderer-owned collection id, with the packed Entity u32 retained only for
305
+ * legacy snapshots); entries are recreated when the `archVersion` bumps or
306
+ * `byteLength` changes.
281
307
  *
282
308
  * feat-20260518-pbr-direct-lighting-mvp M3 / w14 (AC-17 a): the
283
309
  * `warnedZeroLightStandard` flag latches the first-frame warning that
@@ -289,6 +315,8 @@ export function makeZeroCameraFallbackSnapshot(): CameraSnapshot {
289
315
  */
290
316
  export interface RenderFrameState {
291
317
  frameNumber: number;
318
+ /** Final-submit Standard IBL binding-chain receipt for this frame. */
319
+ iblBindingInspection?: IblBindingInspection;
292
320
  /** Demand for the frame-local Standard fallback MRT. */
293
321
  reflectionFallbackDemand?: boolean;
294
322
  /** Monotonic compiled-graph identity used by diagnostic readbacks. */
@@ -375,6 +403,10 @@ export interface RenderFrameState {
375
403
  currentDirectionalShadowView: TextureView | null;
376
404
  currentSpotShadowView: TextureView | null;
377
405
  readonly instanceBuffers: Map<number, InstanceBufferCacheEntry>;
406
+ /** Renderer-owned collection owner used for record-stage residency facts. */
407
+ readonly instanceResidency?: Map<InstanceCollectionId, InstanceResidency>;
408
+ /** Stable buffers for renderer-owned collections split by device cap. */
409
+ readonly instanceBufferChunks?: Map<string, InstanceBufferCacheEntry>;
378
410
  /** Per-object ProbeBlendRecord buffers; upload is one atomic 160B write. */
379
411
  readonly probeBlendBuffers: Map<number, ProbeBlendBufferCacheEntry>;
380
412
  /** One renderer-owned record array; each object selects its 256B-aligned lane. */
@@ -75,9 +75,19 @@ export function acquireSwapChainTarget(
75
75
 
76
76
  let currentTextureResult = canvasContext.getCurrentTexture();
77
77
  if (!currentTextureResult.ok) {
78
+ const configuredDevice = internals.resolveSurfaceDevice?.(internals.device);
79
+ if (configuredDevice !== undefined && !configuredDevice.ok) {
80
+ internals.errorRegistry.fire(configuredDevice.error);
81
+ internals.healthRegistry.fire({
82
+ reason: 'internal-fault',
83
+ detail: { message: 'surface device resolution failed; current frame skipped' },
84
+ recoverable: true,
85
+ });
86
+ return null;
87
+ }
78
88
  const configured = configureSurface(
79
89
  canvasContext,
80
- internals.device,
90
+ configuredDevice?.value ?? internals.device,
81
91
  pipelineState.format,
82
92
  pipelineState.colorAttachmentFormat,
83
93
  );
@@ -86,6 +86,7 @@ import {
86
86
  type DispatchCounts,
87
87
  type FrameObservationSource,
88
88
  getTextureIdentity,
89
+ instanceCollectionCacheKey,
89
90
  makeZeroCameraFallbackSnapshot,
90
91
  type ReflectionFallbackReadbackRequest,
91
92
  type RenderFrameState,
@@ -2484,7 +2485,7 @@ function prepareDirectionalShadowCache(
2484
2485
  *
2485
2486
  * @internal
2486
2487
  */
2487
- function validateRenderables(
2488
+ export function validateRenderables(
2488
2489
  internals: RenderSystemInternals,
2489
2490
  world: World,
2490
2491
  // feat-20260709-editor-world-partition ENGINE-fix-round2 (defect 2): the full
@@ -2714,8 +2715,12 @@ function prepareMorphMesh(
2714
2715
  frameState.morphBuffers.delete(cacheKey);
2715
2716
  return undefined;
2716
2717
  }
2718
+ const buffer = new GpuBuffer(internals.device, created.value);
2719
+ internals.deviceScope._adopt('buffer', buffer, (owned) => {
2720
+ if (!owned.isDestroyed) owned.destroy();
2721
+ });
2717
2722
  cached = {
2718
- buffer: new GpuBuffer(internals.device, created.value),
2723
+ buffer,
2719
2724
  byteLength: vertices.byteLength,
2720
2725
  };
2721
2726
  frameState.morphBuffers.set(cacheKey, cached);
@@ -2989,8 +2994,14 @@ function cleanPerFrameCaches(
2989
2994
  // composites matching the write-side keys of #1-#3 — cross-world false eviction
2990
2995
  // is prevented because worldEntityKey(0, k) !== worldEntityKey(1, k).
2991
2996
  const validatedEntityKeys = new Set<number>(retainedEntityKeys);
2997
+ const validatedInstanceKeys = new Set<number>(retainedEntityKeys);
2992
2998
  for (const v of validated) {
2993
- validatedEntityKeys.add(worldEntityKey(v.source.worldId, v.source.entityKey));
2999
+ const entityKey = worldEntityKey(v.source.worldId, v.source.entityKey);
3000
+ validatedEntityKeys.add(entityKey);
3001
+ validatedInstanceKeys.add(entityKey);
3002
+ if (v.source.instances !== undefined) {
3003
+ validatedInstanceKeys.add(instanceCollectionCacheKey(v.source.worldId, v.source.instances));
3004
+ }
2994
3005
  }
2995
3006
 
2996
3007
  // Clean per-entity material BG cache: drop outer-Map entries whose
@@ -3014,7 +3025,7 @@ function cleanPerFrameCaches(
3014
3025
  // material-handle-based and cross-world collision is semantically
3015
3026
  // correct (same material renders in same fold bucket).
3016
3027
  for (const [key, entry] of frameState.instanceBuffers.entries()) {
3017
- if (!validatedEntityKeys.has(key)) {
3028
+ if (!validatedInstanceKeys.has(key)) {
3018
3029
  if (!entry.buffer.isDestroyed) {
3019
3030
  const r = entry.buffer.destroy();
3020
3031
  if (!r.ok) internals.errorRegistry.fire(r.error);
@@ -3023,6 +3034,28 @@ function cleanPerFrameCaches(
3023
3034
  }
3024
3035
  }
3025
3036
 
3037
+ if (frameState.instanceResidency !== undefined) {
3038
+ const liveCollections = new Set(validated.map((entry) => entry.source.instances?.collectionId));
3039
+ for (const id of frameState.instanceResidency.keys()) {
3040
+ if (!liveCollections.has(id)) frameState.instanceResidency.delete(id);
3041
+ }
3042
+ }
3043
+
3044
+ // Chunk keys are `${worldEntityKey}:${chunkStart}`. Keep the key opaque to
3045
+ // the authoring API, but retain the numeric owner prefix for eviction.
3046
+ if (frameState.instanceBufferChunks !== undefined) {
3047
+ for (const [key, entry] of frameState.instanceBufferChunks.entries()) {
3048
+ const separator = key.indexOf(':');
3049
+ const ownerKey = separator < 0 ? Number.NaN : Number(key.slice(0, separator));
3050
+ if (validatedInstanceKeys.has(ownerKey)) continue;
3051
+ if (!entry.buffer.isDestroyed) {
3052
+ const r = entry.buffer.destroy();
3053
+ if (!r.ok) internals.errorRegistry.fire(r.error);
3054
+ }
3055
+ frameState.instanceBufferChunks.delete(key);
3056
+ }
3057
+ }
3058
+
3026
3059
  if (frameState.morphBuffers !== undefined) {
3027
3060
  for (const [key, entry] of frameState.morphBuffers.entries()) {
3028
3061
  if (validatedEntityKeys.has(key)) continue;
@@ -214,8 +214,8 @@ export function computeProjectionMatrix(camera: CameraSnapshot): Mat4 {
214
214
  proj,
215
215
  camera.orthoLeft,
216
216
  camera.orthoRight,
217
- camera.orthoBottom,
218
217
  camera.orthoTop,
218
+ camera.orthoBottom,
219
219
  camera.near,
220
220
  camera.far,
221
221
  );