@forgeax/engine-render 0.1.23 → 0.1.24

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 (271) hide show
  1. package/README.md +131 -9
  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-shader-policy.d.ts +168 -0
  15. package/dist/assembly/material-shader-policy.d.ts.map +1 -0
  16. package/dist/assembly/material-shader-prewarm.d.ts +21 -0
  17. package/dist/assembly/material-shader-prewarm.d.ts.map +1 -0
  18. package/dist/assembly/mesh-ssbo-grow.d.ts +88 -0
  19. package/dist/assembly/mesh-ssbo-grow.d.ts.map +1 -0
  20. package/dist/assembly/recovery/device-loss-fanout.d.ts +14 -0
  21. package/dist/assembly/recovery/device-loss-fanout.d.ts.map +1 -0
  22. package/dist/assembly/recovery/generation.d.ts +46 -0
  23. package/dist/assembly/recovery/generation.d.ts.map +1 -0
  24. package/dist/assembly/recovery/recovery-attempt.d.ts +63 -0
  25. package/dist/assembly/recovery/recovery-attempt.d.ts.map +1 -0
  26. package/dist/assembly/recovery/renderer-recover.d.ts +115 -0
  27. package/dist/assembly/recovery/renderer-recover.d.ts.map +1 -0
  28. package/dist/assembly/renderer-facade.d.ts +10 -0
  29. package/dist/assembly/renderer-facade.d.ts.map +1 -0
  30. package/dist/assembly/renderer-frame-transaction.d.ts +17 -0
  31. package/dist/assembly/renderer-frame-transaction.d.ts.map +1 -1
  32. package/dist/assembly/renderer-helpers.d.ts +19 -0
  33. package/dist/assembly/renderer-helpers.d.ts.map +1 -0
  34. package/dist/assembly/renderer-lifecycle.d.ts +34 -0
  35. package/dist/assembly/renderer-lifecycle.d.ts.map +1 -1
  36. package/dist/assembly/shader-prewarm-policy.d.ts +15 -0
  37. package/dist/assembly/shader-prewarm-policy.d.ts.map +1 -0
  38. package/dist/assembly/webgpu-ready-contract.d.ts +9 -0
  39. package/dist/assembly/webgpu-ready-contract.d.ts.map +1 -0
  40. package/dist/assembly/webgpu-ready.d.ts +55 -0
  41. package/dist/assembly/webgpu-ready.d.ts.map +1 -0
  42. package/dist/assembly/webgpu-renderer.d.ts +152 -0
  43. package/dist/assembly/webgpu-renderer.d.ts.map +1 -0
  44. package/dist/authoring.mjs +1 -1
  45. package/dist/{chunk-4L3P6DT5.mjs → chunk-GE3SDD72.mjs} +37 -5
  46. package/dist/chunk-GE3SDD72.mjs.map +1 -0
  47. package/dist/{chunk-HZUPSYW7.mjs → chunk-KZFTK6DO.mjs} +102 -15
  48. package/dist/chunk-KZFTK6DO.mjs.map +1 -0
  49. package/dist/{chunk-FF7K3FSO.mjs → chunk-Q752SHI5.mjs} +89 -89
  50. package/dist/{chunk-FF7K3FSO.mjs.map → chunk-Q752SHI5.mjs.map} +1 -1
  51. package/dist/{chunk-QODA5UKU.mjs → chunk-RR5L4SRS.mjs} +2 -2
  52. package/dist/chunk-RR5L4SRS.mjs.map +1 -0
  53. package/dist/{chunk-YALPS44A.mjs → chunk-TFH5I7JJ.mjs} +47 -47
  54. package/dist/chunk-TFH5I7JJ.mjs.map +1 -0
  55. package/dist/{chunk-FBC4R44J.mjs → chunk-UQYS7FRM.mjs} +57 -57
  56. package/dist/chunk-UQYS7FRM.mjs.map +1 -0
  57. package/dist/{chunk-4NPUASZR.mjs → chunk-YKB5DQV7.mjs} +161 -19
  58. package/dist/chunk-YKB5DQV7.mjs.map +1 -0
  59. package/dist/{chunk-ZUWKAZ2I.mjs → chunk-YRUYGA4A.mjs} +615 -237
  60. package/dist/chunk-YRUYGA4A.mjs.map +1 -0
  61. package/dist/{chunk-DIYW3M5D.mjs → chunk-Z3YYH2MB.mjs} +2210 -2213
  62. package/dist/chunk-Z3YYH2MB.mjs.map +1 -0
  63. package/dist/construct-renderer.mjs +19841 -16190
  64. package/dist/construct-renderer.mjs.map +1 -1
  65. package/dist/device/device-scope.d.ts +2 -0
  66. package/dist/device/device-scope.d.ts.map +1 -1
  67. package/dist/device/gpu-residency.d.ts +24 -4
  68. package/dist/device/gpu-residency.d.ts.map +1 -1
  69. package/dist/environment/frame.d.ts +5 -0
  70. package/dist/environment/frame.d.ts.map +1 -1
  71. package/dist/environment/inspection.d.ts +7 -0
  72. package/dist/environment/inspection.d.ts.map +1 -1
  73. package/dist/environment/lifecycle.d.ts +20 -2
  74. package/dist/environment/lifecycle.d.ts.map +1 -1
  75. package/dist/errors/recover.d.ts +58 -2
  76. package/dist/errors/recover.d.ts.map +1 -1
  77. package/dist/errors/render.d.ts +13 -0
  78. package/dist/errors/render.d.ts.map +1 -1
  79. package/dist/extract/contracts.d.ts +899 -0
  80. package/dist/extract/contracts.d.ts.map +1 -0
  81. package/dist/features/host.d.ts +2 -0
  82. package/dist/features/host.d.ts.map +1 -1
  83. package/dist/features/prepared-gpu-work.d.ts +3 -0
  84. package/dist/features/prepared-gpu-work.d.ts.map +1 -1
  85. package/dist/features/prepared-graphics-store.d.ts +2 -0
  86. package/dist/features/prepared-graphics-store.d.ts.map +1 -1
  87. package/dist/gpu-driven/production-raster.d.ts +28 -0
  88. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  89. package/dist/gpu-scene.d.ts +1 -0
  90. package/dist/gpu-scene.d.ts.map +1 -1
  91. package/dist/ibl/IblPipelineCache.d.ts +7 -6
  92. package/dist/ibl/IblPipelineCache.d.ts.map +1 -1
  93. package/dist/ibl/skylight-bind-group.d.ts +7 -1
  94. package/dist/ibl/skylight-bind-group.d.ts.map +1 -1
  95. package/dist/index.d.ts +1 -1
  96. package/dist/index.d.ts.map +1 -1
  97. package/dist/index.mjs +6 -6
  98. package/dist/internal.mjs +6 -6
  99. package/dist/light-buffer-layout.d.ts +3 -0
  100. package/dist/light-buffer-layout.d.ts.map +1 -1
  101. package/dist/materials.d.ts +34 -10
  102. package/dist/materials.d.ts.map +1 -1
  103. package/dist/mesh-material-bindings.d.ts +56 -0
  104. package/dist/mesh-material-bindings.d.ts.map +1 -1
  105. package/dist/occlusion-proxy.d.ts +4 -0
  106. package/dist/occlusion-proxy.d.ts.map +1 -0
  107. package/dist/pbr-pipeline.d.ts +32 -9
  108. package/dist/pbr-pipeline.d.ts.map +1 -1
  109. package/dist/points-lines/prepare.d.ts +3 -0
  110. package/dist/points-lines/prepare.d.ts.map +1 -1
  111. package/dist/points-lines/standard-owner.d.ts +33 -0
  112. package/dist/points-lines/standard-owner.d.ts.map +1 -0
  113. package/dist/record/frame-snapshot.d.ts +5 -0
  114. package/dist/record/frame-snapshot.d.ts.map +1 -1
  115. package/dist/record/frame-targets.d.ts.map +1 -1
  116. package/dist/record/frame.d.ts +14 -2
  117. package/dist/record/frame.d.ts.map +1 -1
  118. package/dist/record/main-pass-geometry.d.ts +14 -1
  119. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  120. package/dist/record/main-pass-material.d.ts +7 -17
  121. package/dist/record/main-pass-material.d.ts.map +1 -1
  122. package/dist/record/main-pass.d.ts.map +1 -1
  123. package/dist/record/mesh-ssbo.d.ts +11 -3
  124. package/dist/record/mesh-ssbo.d.ts.map +1 -1
  125. package/dist/record/recovery-pipeline.d.ts +30 -0
  126. package/dist/record/recovery-pipeline.d.ts.map +1 -0
  127. package/dist/record/render-context.d.ts +26 -4
  128. package/dist/record/render-context.d.ts.map +1 -1
  129. package/dist/record/typed-frame-graph.d.ts +1 -1
  130. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  131. package/dist/recovery/render-system-candidate.d.ts +65 -0
  132. package/dist/recovery/render-system-candidate.d.ts.map +1 -0
  133. package/dist/render-contract.d.ts +64 -7
  134. package/dist/render-contract.d.ts.map +1 -1
  135. package/dist/render-system-extract-tail.d.ts +12 -0
  136. package/dist/render-system-extract-tail.d.ts.map +1 -0
  137. package/dist/render-system-extract.d.ts +156 -22
  138. package/dist/render-system-extract.d.ts.map +1 -1
  139. package/dist/render-system.d.ts +128 -4
  140. package/dist/render-system.d.ts.map +1 -1
  141. package/dist/scene/render-scene.d.ts +19 -0
  142. package/dist/scene/render-scene.d.ts.map +1 -1
  143. package/dist/temporal/frame-coordinator.d.ts.map +1 -1
  144. package/dist/temporal/index.mjs +6 -6
  145. package/dist/temporal/taa-history-store.d.ts +15 -1
  146. package/dist/temporal/taa-history-store.d.ts.map +1 -1
  147. package/dist/temporal/transaction.d.ts +5 -0
  148. package/dist/temporal/transaction.d.ts.map +1 -1
  149. package/package.json +20 -20
  150. package/src/__tests__/device-feature-admission.unit.test.ts +6 -0
  151. package/src/__tests__/device-loss-fanout.unit.test.ts +107 -0
  152. package/src/__tests__/device-scope-lifecycle.unit.test.ts +21 -0
  153. package/src/__tests__/environment-generation.integration.test.ts +23 -0
  154. package/src/__tests__/expose-renderer-errors.unit.test.ts +34 -0
  155. package/src/__tests__/factory-contract.integration.test.ts +122 -7
  156. package/src/__tests__/fallback-row-stride-surface.unit.test.ts +4 -1
  157. package/src/__tests__/ibl/face-uniforms.unit.test.ts +2 -0
  158. package/src/__tests__/ibl-residency-publication.unit.test.ts +113 -0
  159. package/src/__tests__/material-contract-inventory.unit.test.ts +24 -9
  160. package/src/__tests__/material-cooked-projection.unit.test.ts +19 -1
  161. package/src/__tests__/material-static-dynamic-route.unit.test.ts +29 -0
  162. package/src/__tests__/material-stride-surface.unit.test.ts +7 -3
  163. package/src/__tests__/materials-standard-contract.unit.test.ts +104 -0
  164. package/src/__tests__/mesh-buffer-usage-surface.unit.test.ts +1 -1
  165. package/src/__tests__/physical-clearcoat.integration.test.ts +89 -0
  166. package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +1 -1
  167. package/src/__tests__/prepared-graphics-pipeline-warmup.unit.test.ts +1 -1
  168. package/src/__tests__/recover-error-policy-owner.unit.test.ts +6 -3
  169. package/src/__tests__/recovery-candidate-prepare.contract.test.ts +140 -0
  170. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +85 -18
  171. package/src/__tests__/renderer-frame-generation-fence.integration.test.ts +47 -0
  172. package/src/__tests__/renderer-generation-assembly.unit.test.ts +77 -0
  173. package/src/__tests__/renderer-generation-failure.unit.test.ts +108 -0
  174. package/src/__tests__/renderer-producer-root-matrix.integration.test.ts +33 -0
  175. package/src/__tests__/renderer-recovery-capability-matrix.unit.test.ts +59 -0
  176. package/src/__tests__/renderer-recovery-contract.unit.test.ts +73 -0
  177. package/src/__tests__/renderer-recovery-lifecycle.unit.test.ts +216 -0
  178. package/src/__tests__/renderer-target-history-recovery.integration.test.ts +80 -0
  179. package/src/__tests__/shader-manifest-fixture.ts +13 -16
  180. package/src/__tests__/shader-stage-surface.unit.test.ts +1 -1
  181. package/src/__tests__/standard-layer-projection.unit.test.ts +23 -0
  182. package/src/__tests__/transmission-prewarm.unit.test.ts +95 -85
  183. package/src/__tests__/volumetric-fog-topology.unit.test.ts +3 -1
  184. package/src/assembly/bundler-contract.ts +9 -0
  185. package/src/assembly/device-feature-admission.ts +15 -5
  186. package/src/assembly/extended-lighting-fallback.ts +216 -0
  187. package/src/assembly/factory.ts +81 -10088
  188. package/src/assembly/material/assembly.ts +18 -2
  189. package/src/assembly/material/standard-layer-projection.ts +16 -0
  190. package/src/assembly/material-shader-policy.ts +854 -0
  191. package/src/assembly/material-shader-prewarm.ts +88 -0
  192. package/src/assembly/mesh-ssbo-grow.ts +270 -0
  193. package/src/assembly/recovery/device-loss-fanout.ts +89 -0
  194. package/src/assembly/recovery/generation.ts +141 -0
  195. package/src/assembly/recovery/recovery-attempt.ts +800 -0
  196. package/src/assembly/recovery/renderer-recover.ts +906 -0
  197. package/src/assembly/renderer-facade.ts +342 -0
  198. package/src/assembly/renderer-frame-transaction.ts +45 -0
  199. package/src/assembly/renderer-helpers.ts +99 -0
  200. package/src/assembly/renderer-lifecycle.ts +159 -0
  201. package/src/assembly/shader-prewarm-policy.ts +157 -0
  202. package/src/assembly/webgpu-ready-contract.ts +14 -0
  203. package/src/assembly/webgpu-ready.ts +4079 -0
  204. package/src/assembly/webgpu-renderer.ts +4095 -0
  205. package/src/components/camera.ts +1 -1
  206. package/src/debug-draw-glue.ts +1 -1
  207. package/src/device/device-scope.ts +35 -6
  208. package/src/device/gpu-residency.ts +194 -26
  209. package/src/environment/frame.ts +6 -0
  210. package/src/environment/inspection.ts +10 -0
  211. package/src/environment/lifecycle.ts +114 -8
  212. package/src/errors/recover.ts +76 -5
  213. package/src/errors/render.ts +17 -0
  214. package/src/extract/contracts.ts +974 -0
  215. package/src/features/host.ts +16 -0
  216. package/src/features/prepared-gpu-work.ts +20 -2
  217. package/src/features/prepared-graphics-store.ts +17 -0
  218. package/src/gpu-driven/production-raster.ts +97 -2
  219. package/src/gpu-scene.ts +3 -2
  220. package/src/ibl/IblPipelineCache.ts +134 -50
  221. package/src/ibl/skylight-bind-group.ts +26 -0
  222. package/src/index.ts +9 -1
  223. package/src/light-buffer-layout.ts +3 -0
  224. package/src/materials.ts +262 -45
  225. package/src/mesh-material-bindings.ts +59 -0
  226. package/src/occlusion-proxy.ts +64 -0
  227. package/src/pbr-pipeline.ts +139 -15
  228. package/src/points-lines/__tests__/cache-recovery.unit.test.ts +22 -0
  229. package/src/points-lines/prepare.ts +9 -0
  230. package/src/points-lines/standard-owner.ts +403 -0
  231. package/src/record/__tests__/frame-targets-recovery.unit.test.ts +60 -0
  232. package/src/record/__tests__/main-pass-geometry.unit.test.ts +26 -1
  233. package/src/record/__tests__/mesh-ssbo.unit.test.ts +38 -0
  234. package/src/record/__tests__/recovery-pipeline.unit.test.ts +124 -0
  235. package/src/record/__tests__/sprite-material-abi.unit.test.ts +28 -13
  236. package/src/record/__tests__/standard-pbr-ubo-layout.unit.test.ts +47 -4
  237. package/src/record/__tests__/temporal-payload.unit.test.ts +22 -43
  238. package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +1 -1
  239. package/src/record/frame-snapshot.ts +15 -0
  240. package/src/record/frame-targets.ts +11 -1
  241. package/src/record/frame.ts +6 -2
  242. package/src/record/helpers.ts +1 -1
  243. package/src/record/main-pass-geometry.ts +230 -5
  244. package/src/record/main-pass-material.ts +316 -209
  245. package/src/record/main-pass-sprite-draws.ts +6 -7
  246. package/src/record/main-pass.ts +94 -6
  247. package/src/record/mesh-ssbo.ts +32 -10
  248. package/src/record/recovery-pipeline.ts +695 -0
  249. package/src/record/render-context.ts +35 -4
  250. package/src/record/typed-frame-graph.ts +45 -1
  251. package/src/record/view-ubo.ts +3 -3
  252. package/src/recovery/render-system-candidate.ts +839 -0
  253. package/src/render-contract.ts +65 -7
  254. package/src/render-system-extract-tail.ts +2983 -0
  255. package/src/render-system-extract.ts +124 -2977
  256. package/src/render-system.ts +868 -659
  257. package/src/scene/render-scene.ts +124 -1
  258. package/src/temporal/frame-coordinator.ts +4 -1
  259. package/src/temporal/taa-history-store.ts +46 -2
  260. package/src/temporal/temporal-view.ts +1 -1
  261. package/src/temporal/transaction.ts +9 -0
  262. package/src/tilemap-chunk-extract-system.ts +1 -1
  263. package/src/transmission/__tests__/standard-transmission.dawn.test.ts +19 -12
  264. package/dist/chunk-4L3P6DT5.mjs.map +0 -1
  265. package/dist/chunk-4NPUASZR.mjs.map +0 -1
  266. package/dist/chunk-DIYW3M5D.mjs.map +0 -1
  267. package/dist/chunk-FBC4R44J.mjs.map +0 -1
  268. package/dist/chunk-HZUPSYW7.mjs.map +0 -1
  269. package/dist/chunk-QODA5UKU.mjs.map +0 -1
  270. package/dist/chunk-YALPS44A.mjs.map +0 -1
  271. package/dist/chunk-ZUWKAZ2I.mjs.map +0 -1
@@ -0,0 +1,60 @@
1
+ import type { RhiCanvasContext, RhiDevice, Texture, TextureView } from '@forgeax/engine-rhi';
2
+ import { err, ok, RhiError } from '@forgeax/engine-rhi';
3
+ import { describe, expect, it, vi } from 'vitest';
4
+ import { acquireSwapChainTarget } from '../frame-targets';
5
+ import type { PipelineState, RenderSystemInternals } from '../render-context';
6
+
7
+ function device(): RhiDevice {
8
+ return {
9
+ caps: { backendKind: 'webgpu', storageBuffer: true },
10
+ limits: {},
11
+ createTextureView: vi.fn(() => ok({} as TextureView)),
12
+ } as unknown as RhiDevice;
13
+ }
14
+
15
+ function unavailableSurfaceError(): RhiError {
16
+ return new RhiError({
17
+ code: 'webgpu-runtime-error',
18
+ expected: 'the surface has a current texture',
19
+ hint: 'configure the surface and retry',
20
+ });
21
+ }
22
+
23
+ describe('acquireSwapChainTarget recovery', () => {
24
+ it('resolves the recorder device before reconfiguring a recovered surface', () => {
25
+ const recorderDevice = device();
26
+ const configuredDevice = device();
27
+ const texture = {} as Texture;
28
+ const getCurrentTexture = vi
29
+ .fn()
30
+ .mockReturnValueOnce(err(unavailableSurfaceError()))
31
+ .mockReturnValueOnce(ok(texture));
32
+ const configure = vi.fn(() => ok(undefined));
33
+ const context = { configure, getCurrentTexture } as unknown as RhiCanvasContext;
34
+ const resolveSurfaceDevice = vi.fn(() => ok(configuredDevice));
35
+ const internals = {
36
+ context,
37
+ device: recorderDevice,
38
+ canvas: { width: 800, height: 600 },
39
+ resolveSurfaceDevice,
40
+ errorRegistry: { fire: vi.fn() },
41
+ healthRegistry: { fire: vi.fn() },
42
+ } as unknown as RenderSystemInternals;
43
+ const pipelineState = {
44
+ device: recorderDevice,
45
+ format: 'bgra8unorm',
46
+ colorAttachmentFormat: 'bgra8unorm',
47
+ perPassResources: { configured: false },
48
+ } as unknown as PipelineState;
49
+
50
+ const result = acquireSwapChainTarget(internals, pipelineState);
51
+
52
+ expect(result).toMatchObject({ currentTexture: texture, targetW: 800, targetH: 600 });
53
+ expect(resolveSurfaceDevice).toHaveBeenCalledWith(recorderDevice);
54
+ expect(configure).toHaveBeenCalledWith(
55
+ expect.objectContaining({ device: configuredDevice, format: 'bgra8unorm' }),
56
+ );
57
+ expect(getCurrentTexture).toHaveBeenCalledTimes(2);
58
+ expect(pipelineState.perPassResources.configured).toBe(true);
59
+ });
60
+ });
@@ -1,5 +1,8 @@
1
1
  import { describe, expect, it } from 'vitest';
2
- import { geometryRenderStateForPass } from '../main-pass-geometry';
2
+ import {
3
+ geometryRenderStateForPass,
4
+ requestsStandardTransmissionVariant,
5
+ } from '../main-pass-geometry';
3
6
 
4
7
  describe('geometry pass render state', () => {
5
8
  it('keeps the main depth authority while admitting equal temporal depth', () => {
@@ -45,3 +48,25 @@ describe('geometry pass render state', () => {
45
48
  expect(accepted).toEqual([visibleDepth]);
46
49
  });
47
50
  });
51
+
52
+ describe('Standard transmission variant selection', () => {
53
+ it('uses authored transmission presence instead of the canonical reserved schema', () => {
54
+ const baseOnly = {
55
+ materialShaderId: 'forgeax::default-standard-pbr',
56
+ paramSnapshot: { alphaCutoff: 0.25 },
57
+ } as const;
58
+ const transmission = {
59
+ materialShaderId: 'forgeax::default-standard-pbr',
60
+ paramSnapshot: { transmission: 0 },
61
+ } as const;
62
+
63
+ expect(requestsStandardTransmissionVariant(baseOnly)).toBe(false);
64
+ expect(requestsStandardTransmissionVariant(transmission)).toBe(true);
65
+ expect(
66
+ requestsStandardTransmissionVariant({
67
+ materialShaderId: 'custom::standard',
68
+ paramSnapshot: { transmission: 1 },
69
+ }),
70
+ ).toBe(false);
71
+ });
72
+ });
@@ -0,0 +1,38 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { getOrCreateFromChain } from '../mesh-ssbo';
3
+
4
+ describe('mesh bind-group cache chains', () => {
5
+ it('keeps variable-depth material resource chains isolated', () => {
6
+ const root = new WeakMap<object, unknown>();
7
+ const shared = { shared: true };
8
+ const coatSampler = { coatSampler: true };
9
+ const coatTexture = { coatTexture: true };
10
+ const counts = { createBindGroup: 0, keys: [] as string[] };
11
+ const long = getOrCreateFromChain(
12
+ root,
13
+ [shared, coatSampler, coatTexture],
14
+ 'material-shared',
15
+ () => ({ id: 'long' }) as never,
16
+ counts,
17
+ );
18
+ const short = getOrCreateFromChain(
19
+ root,
20
+ [shared, coatSampler],
21
+ 'material-shared',
22
+ () => ({ id: 'short' }) as never,
23
+ counts,
24
+ );
25
+
26
+ expect(
27
+ getOrCreateFromChain(
28
+ root,
29
+ [shared, coatSampler],
30
+ 'material-shared',
31
+ () => ({ id: 'unexpected' }) as never,
32
+ counts,
33
+ ),
34
+ ).toBe(short);
35
+ expect(long).not.toBe(short);
36
+ expect(counts.createBindGroup).toBe(2);
37
+ });
38
+ });
@@ -0,0 +1,124 @@
1
+ import { deriveVertexLayoutProjection } from '@forgeax/engine-geometry';
2
+ import { describe, expect, it } from 'vitest';
3
+ import { makeZeroCameraFallbackSnapshot, type ValidatedRenderable } from '../frame-snapshot';
4
+ import {
5
+ createRecoveryColdWorkGuard,
6
+ prepareRecoveryPipelineReadiness,
7
+ } from '../recovery-pipeline';
8
+ import type { PipelineState, RenderSystemInternals } from '../render-context';
9
+
10
+ function probeRenderable(probe: boolean): ValidatedRenderable {
11
+ const material = {
12
+ baseColor: [1, 1, 1],
13
+ metallic: 0,
14
+ roughness: 1,
15
+ materialShaderId: 'forgeax::default-standard-pbr',
16
+ } as never;
17
+ return {
18
+ source: {
19
+ material,
20
+ materials: [material],
21
+ ...(probe ? { probeBlendRecord: {} } : {}),
22
+ },
23
+ mesh: {
24
+ layoutProjection: deriveVertexLayoutProjection({
25
+ position: new Float32Array(0),
26
+ normal: new Float32Array(0),
27
+ uv: new Float32Array(0),
28
+ tangent: new Float32Array(0),
29
+ }),
30
+ indexFormat: 'uint32',
31
+ submeshes: [
32
+ {
33
+ indexOffset: 0,
34
+ indexCount: 3,
35
+ vertexCount: 3,
36
+ topology: 'triangle-list',
37
+ materialSlot: 0,
38
+ },
39
+ ],
40
+ },
41
+ } as never;
42
+ }
43
+
44
+ function recoveryReadinessFor(
45
+ renderable: ValidatedRenderable,
46
+ reflectionFallbackAvailable = false,
47
+ ) {
48
+ const pipeline = {} as never;
49
+ const internals = {
50
+ device: { caps: { backendKind: 'webgpu', storageBuffer: true } },
51
+ getMaterialShaderPipelineEntry: (..._args: unknown[]) => ({
52
+ pipeline,
53
+ group2Contract: 'cluster' as const,
54
+ }),
55
+ } as unknown as RenderSystemInternals;
56
+ const pipelineState = {
57
+ colorAttachmentFormat: 'bgra8unorm',
58
+ } as unknown as PipelineState;
59
+ return prepareRecoveryPipelineReadiness({
60
+ internals,
61
+ pipelineState,
62
+ camera: makeZeroCameraFallbackSnapshot(),
63
+ standardLighting: { kind: 'no-local-lights' } as never,
64
+ validated: [renderable],
65
+ dispatch: [],
66
+ shadowCastersActive: false,
67
+ splitLdrSprite: false,
68
+ reflectionFallbackAvailable,
69
+ });
70
+ }
71
+
72
+ describe('recovery first-frame cold-work guard', () => {
73
+ it('prepares the probe-enabled Standard PBR variant used by the live record path', () => {
74
+ const readiness = recoveryReadinessFor(probeRenderable(true));
75
+ const standardPbr = readiness.pipelineSpecs.find(
76
+ (spec) => spec.shader.id === 'forgeax::default-standard-pbr',
77
+ );
78
+
79
+ expect(standardPbr?.shader.variantSet).toContain('PROBE_BLEND_AVAILABLE=true');
80
+ });
81
+
82
+ it('does not add the probe ABI to a Standard PBR renderable without a record', () => {
83
+ const readiness = recoveryReadinessFor(probeRenderable(false));
84
+ const standardPbr = readiness.pipelineSpecs.find(
85
+ (spec) => spec.shader.id === 'forgeax::default-standard-pbr',
86
+ );
87
+
88
+ expect(standardPbr?.shader.variantSet).not.toContain('PROBE_BLEND_AVAILABLE=true');
89
+ });
90
+
91
+ it('prepares the Standard PBR reflection fallback MRT variant', () => {
92
+ const readiness = recoveryReadinessFor(probeRenderable(false), true);
93
+ const standardPbr = readiness.pipelineSpecs.find(
94
+ (spec) => spec.shader.id === 'forgeax::default-standard-pbr',
95
+ );
96
+
97
+ expect(standardPbr?.shader.variantSet).toContain('REFLECTION_FALLBACK_AVAILABLE=true');
98
+ expect(standardPbr?.attachments.colorFormats).toEqual(['rgba16float', 'rgba16float']);
99
+ });
100
+
101
+ it('accepts a prepared candidate with no first-frame cold work', () => {
102
+ const guard = createRecoveryColdWorkGuard();
103
+
104
+ guard.arm();
105
+ expect(() => guard.finish()).not.toThrow();
106
+ expect(() => guard.finish()).not.toThrow();
107
+ });
108
+
109
+ it.each([
110
+ [
111
+ 'pipeline',
112
+ (guard: ReturnType<typeof createRecoveryColdWorkGuard>) => guard.notePipelineColdWork(),
113
+ ],
114
+ [
115
+ 'upload',
116
+ (guard: ReturnType<typeof createRecoveryColdWorkGuard>) => guard.noteUploadColdWork(),
117
+ ],
118
+ ] as const)('hard-fails a first-frame %s miss before submission', (_kind, note) => {
119
+ const guard = createRecoveryColdWorkGuard();
120
+
121
+ guard.arm();
122
+ expect(() => note(guard)).toThrow(/recovery first frame performed cold/);
123
+ });
124
+ });
@@ -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,7 @@ 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 { IblBindingInspection } from '../mesh-material-bindings';
21
22
  import type { StandardLightingInspection } from '../pipeline/standard-lighting/inspection';
22
23
  import type { PointShadowInspection } from '../point-shadow-inspection';
23
24
  import type { TemporalGpuState } from '../temporal/gpu';
@@ -138,6 +139,18 @@ export interface CurrentGraphTarget {
138
139
  const textureIdentities = new WeakMap<object, number>();
139
140
  let nextTextureIdentity = 1;
140
141
 
142
+ const opaqueResourceIdentities = new WeakMap<object, number>();
143
+ let nextOpaqueResourceIdentity = 1;
144
+
145
+ /** Stable detached identity for a renderer-owned opaque resource handle. */
146
+ export function getOpaqueResourceIdentity(resource: object): number {
147
+ const existing = opaqueResourceIdentities.get(resource);
148
+ if (existing !== undefined) return existing;
149
+ const identity = nextOpaqueResourceIdentity++;
150
+ opaqueResourceIdentities.set(resource, identity);
151
+ return identity;
152
+ }
153
+
141
154
  /**
142
155
  * Project an opaque RHI texture handle to a stable diagnostic identity.
143
156
  * RHI handles intentionally expose no serializable fields; object identity is
@@ -289,6 +302,8 @@ export function makeZeroCameraFallbackSnapshot(): CameraSnapshot {
289
302
  */
290
303
  export interface RenderFrameState {
291
304
  frameNumber: number;
305
+ /** Final-submit Standard IBL binding-chain receipt for this frame. */
306
+ iblBindingInspection?: IblBindingInspection;
292
307
  /** Demand for the frame-local Standard fallback MRT. */
293
308
  reflectionFallbackDemand?: boolean;
294
309
  /** Monotonic compiled-graph identity used by diagnostic readbacks. */
@@ -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
  );
@@ -2484,7 +2484,7 @@ function prepareDirectionalShadowCache(
2484
2484
  *
2485
2485
  * @internal
2486
2486
  */
2487
- function validateRenderables(
2487
+ export function validateRenderables(
2488
2488
  internals: RenderSystemInternals,
2489
2489
  world: World,
2490
2490
  // feat-20260709-editor-world-partition ENGINE-fix-round2 (defect 2): the full
@@ -2714,8 +2714,12 @@ function prepareMorphMesh(
2714
2714
  frameState.morphBuffers.delete(cacheKey);
2715
2715
  return undefined;
2716
2716
  }
2717
+ const buffer = new GpuBuffer(internals.device, created.value);
2718
+ internals.deviceScope._adopt('buffer', buffer, (owned) => {
2719
+ if (!owned.isDestroyed) owned.destroy();
2720
+ });
2717
2721
  cached = {
2718
- buffer: new GpuBuffer(internals.device, created.value),
2722
+ buffer,
2719
2723
  byteLength: vertices.byteLength,
2720
2724
  };
2721
2725
  frameState.morphBuffers.set(cacheKey, cached);
@@ -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
  );