@flighthq/render-wgpu 0.5.0-next.1944.cd6b6e7 → 0.5.1-edge.638.d4dbd4e

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 (80) hide show
  1. package/README.md +2 -2
  2. package/dist/contract.d.ts +3 -0
  3. package/dist/contract.d.ts.map +1 -1
  4. package/dist/contract.js +3 -0
  5. package/dist/contract.js.map +1 -1
  6. package/dist/index.d.ts +1 -1
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +1 -1
  9. package/dist/index.js.map +1 -1
  10. package/dist/wgpuAntialias.d.ts +8 -4
  11. package/dist/wgpuAntialias.d.ts.map +1 -1
  12. package/dist/wgpuAntialias.js +23 -4
  13. package/dist/wgpuAntialias.js.map +1 -1
  14. package/dist/wgpuBackground.d.ts +10 -4
  15. package/dist/wgpuBackground.d.ts.map +1 -1
  16. package/dist/wgpuBackground.js +87 -3
  17. package/dist/wgpuBackground.js.map +1 -1
  18. package/dist/wgpuCompressedTexture.js +9 -9
  19. package/dist/wgpuCompressedTexture.js.map +1 -1
  20. package/dist/wgpuDeviceLoss.d.ts +21 -0
  21. package/dist/wgpuDeviceLoss.d.ts.map +1 -0
  22. package/dist/wgpuDeviceLoss.js +62 -0
  23. package/dist/wgpuDeviceLoss.js.map +1 -0
  24. package/dist/wgpuDraw.d.ts +0 -1
  25. package/dist/wgpuDraw.d.ts.map +1 -1
  26. package/dist/wgpuDraw.js +73 -96
  27. package/dist/wgpuDraw.js.map +1 -1
  28. package/dist/wgpuElement.d.ts +5 -0
  29. package/dist/wgpuElement.d.ts.map +1 -1
  30. package/dist/wgpuElement.js +17 -6
  31. package/dist/wgpuElement.js.map +1 -1
  32. package/dist/wgpuExternalTexture.d.ts.map +1 -1
  33. package/dist/wgpuExternalTexture.js +12 -11
  34. package/dist/wgpuExternalTexture.js.map +1 -1
  35. package/dist/wgpuFullscreenPass.js +1 -1
  36. package/dist/wgpuFullscreenPass.js.map +1 -1
  37. package/dist/wgpuHost.d.ts +7 -0
  38. package/dist/wgpuHost.d.ts.map +1 -0
  39. package/dist/wgpuHost.js +80 -0
  40. package/dist/wgpuHost.js.map +1 -0
  41. package/dist/wgpuMipmap.js +3 -3
  42. package/dist/wgpuMipmap.js.map +1 -1
  43. package/dist/wgpuPipeline.d.ts +5 -0
  44. package/dist/wgpuPipeline.d.ts.map +1 -0
  45. package/dist/wgpuPipeline.js +29 -0
  46. package/dist/wgpuPipeline.js.map +1 -0
  47. package/dist/wgpuRenderState.d.ts +13 -6
  48. package/dist/wgpuRenderState.d.ts.map +1 -1
  49. package/dist/wgpuRenderState.js +220 -271
  50. package/dist/wgpuRenderState.js.map +1 -1
  51. package/dist/wgpuRenderTarget.d.ts.map +1 -1
  52. package/dist/wgpuRenderTarget.js +6 -5
  53. package/dist/wgpuRenderTarget.js.map +1 -1
  54. package/dist/wgpuRenderTexture.js +4 -4
  55. package/dist/wgpuRenderTexture.js.map +1 -1
  56. package/dist/wgpuShader.d.ts.map +1 -1
  57. package/dist/wgpuShader.js +6 -4
  58. package/dist/wgpuShader.js.map +1 -1
  59. package/dist/wgpuSurface.d.ts +5 -5
  60. package/dist/wgpuSurface.d.ts.map +1 -1
  61. package/dist/wgpuSurface.js +2 -2
  62. package/dist/wgpuSurface.js.map +1 -1
  63. package/dist/wgpuTestHelper.d.ts +2 -2
  64. package/dist/wgpuTestHelper.d.ts.map +1 -1
  65. package/dist/wgpuTestHelper.js +6 -2
  66. package/dist/wgpuTestHelper.js.map +1 -1
  67. package/package.json +11 -11
  68. package/src/wgpuAntialias.test.ts +26 -4
  69. package/src/wgpuBackground.test.ts +43 -73
  70. package/src/wgpuDeviceLoss.test.ts +240 -0
  71. package/src/wgpuDraw.test.ts +99 -24
  72. package/src/wgpuElement.test.ts +127 -13
  73. package/src/wgpuHost.test.ts +227 -0
  74. package/src/wgpuMaterialRegistry.test.ts +6 -3
  75. package/src/wgpuMipmap.test.ts +3 -3
  76. package/src/wgpuPipeline.test.ts +52 -0
  77. package/src/wgpuRenderState.test.ts +679 -88
  78. package/src/wgpuRenderTarget.test.ts +12 -8
  79. package/src/wgpuRenderTexture.test.ts +1 -1
  80. package/src/wgpuShader.test.ts +2 -2
@@ -1,21 +1,135 @@
1
- import { createWgpuCanvasElement } from './wgpuElement';
1
+ import {
2
+ createWgpuCanvasElement,
3
+ createWgpuRenderSurface,
4
+ getWgpuRenderSurfaceProvider,
5
+ resetWgpuRenderSurfaceProviderForTest,
6
+ setWgpuRenderSurfaceProvider,
7
+ } from './wgpuElement';
2
8
 
3
9
  describe('createWgpuCanvasElement', () => {
4
- it('sets CSS dimensions from width and height', () => {
5
- const canvas = createWgpuCanvasElement(400, 300);
6
- expect(canvas.style.width).toBe('400px');
7
- expect(canvas.style.height).toBe('300px');
10
+ afterEach(() => resetWgpuRenderSurfaceProviderForTest());
11
+
12
+ it('preserves the canonical provider result identity', () => {
13
+ const surface = {} as HTMLCanvasElement;
14
+ setWgpuRenderSurfaceProvider({ createRenderSurface: () => surface });
15
+ expect(createWgpuCanvasElement(100, 200, 2)).toBe(surface);
16
+ });
17
+
18
+ it('throws an actionable Web-only setup error when the provider is absent', () => {
19
+ expect(() => createWgpuCanvasElement(100, 200)).toThrowError(
20
+ /enableHostWebWgpuRenderSurface\(\).*inject a WgpuRenderSurfaceProvider/,
21
+ );
22
+ });
23
+
24
+ it('throws the same setup error when the provider refuses', () => {
25
+ setWgpuRenderSurfaceProvider({ createRenderSurface: () => null });
26
+ expect(() => createWgpuCanvasElement(100, 200)).toThrowError(/enableHostWebWgpuRenderSurface\(\)/);
27
+ });
28
+ });
29
+
30
+ describe('createWgpuRenderSurface', () => {
31
+ afterEach(() => resetWgpuRenderSurfaceProviderForTest());
32
+
33
+ it('returns null when no provider is selected', () => {
34
+ expect(createWgpuRenderSurface(100, 200)).toBeNull();
35
+ });
36
+
37
+ it('passes exact dimensions and the default pixel ratio to the selected provider', () => {
38
+ const surface = {} as HTMLCanvasElement;
39
+ const createRenderSurface = vi.fn(() => surface);
40
+ setWgpuRenderSurfaceProvider({ createRenderSurface });
41
+ expect(createWgpuRenderSurface(100, 200)).toBe(surface);
42
+ expect(createRenderSurface).toHaveBeenCalledOnce();
43
+ expect(createRenderSurface).toHaveBeenCalledWith(100, 200, 1);
44
+ });
45
+
46
+ it('passes an explicit pixel ratio and preserves the provider result identity', () => {
47
+ const surface = {} as HTMLCanvasElement;
48
+ const createRenderSurface = vi.fn(() => surface);
49
+ setWgpuRenderSurfaceProvider({ createRenderSurface });
50
+ expect(createWgpuRenderSurface(300, 150, 2.5)).toBe(surface);
51
+ expect(createRenderSurface).toHaveBeenCalledWith(300, 150, 2.5);
52
+ });
53
+
54
+ it('selects replacements and reset restores omission', () => {
55
+ const first = {} as HTMLCanvasElement;
56
+ const second = {} as HTMLCanvasElement;
57
+ const firstProvider = { createRenderSurface: vi.fn(() => first) };
58
+ const secondProvider = { createRenderSurface: vi.fn(() => second) };
59
+ setWgpuRenderSurfaceProvider(firstProvider);
60
+ expect(getWgpuRenderSurfaceProvider()).toBe(firstProvider);
61
+ expect(createWgpuRenderSurface(1, 2)).toBe(first);
62
+ setWgpuRenderSurfaceProvider(secondProvider);
63
+ expect(getWgpuRenderSurfaceProvider()).toBe(secondProvider);
64
+ expect(createWgpuRenderSurface(3, 4)).toBe(second);
65
+ resetWgpuRenderSurfaceProviderForTest();
66
+ expect(getWgpuRenderSurfaceProvider()).toBeNull();
67
+ expect(createWgpuRenderSurface(5, 6)).toBeNull();
8
68
  });
9
69
 
10
- it('scales pixel dimensions by pixelRatio', () => {
11
- const canvas = createWgpuCanvasElement(200, 100, 2);
12
- expect(canvas.width).toBe(400);
13
- expect(canvas.height).toBe(200);
70
+ it('preserves provider refusal as null', () => {
71
+ setWgpuRenderSurfaceProvider({ createRenderSurface: () => null });
72
+ expect(createWgpuRenderSurface(100, 200)).toBeNull();
14
73
  });
15
74
 
16
- it('defaults pixelRatio to 1', () => {
17
- const canvas = createWgpuCanvasElement(300, 200);
18
- expect(canvas.width).toBe(300);
19
- expect(canvas.height).toBe(200);
75
+ it('lets a native host provide a surface without reading document', () => {
76
+ const surface = {} as HTMLCanvasElement;
77
+ setWgpuRenderSurfaceProvider({ createRenderSurface: () => surface });
78
+ withThrowingDocument(() => expect(createWgpuRenderSurface(100, 200)).toBe(surface));
79
+ });
80
+
81
+ it('returns null for omission and refusal without reading document', () => {
82
+ withThrowingDocument(() => {
83
+ expect(createWgpuRenderSurface(100, 200)).toBeNull();
84
+ setWgpuRenderSurfaceProvider({ createRenderSurface: () => null });
85
+ expect(createWgpuRenderSurface(100, 200)).toBeNull();
86
+ });
87
+ });
88
+ });
89
+
90
+ describe('getWgpuRenderSurfaceProvider', () => {
91
+ afterEach(() => resetWgpuRenderSurfaceProviderForTest());
92
+
93
+ it('returns the exact selected provider', () => {
94
+ const provider = { createRenderSurface: () => ({}) as HTMLCanvasElement };
95
+ setWgpuRenderSurfaceProvider(provider);
96
+ expect(getWgpuRenderSurfaceProvider()).toBe(provider);
20
97
  });
21
98
  });
99
+
100
+ describe('resetWgpuRenderSurfaceProviderForTest', () => {
101
+ it('restores provider omission', () => {
102
+ setWgpuRenderSurfaceProvider({ createRenderSurface: () => ({}) as HTMLCanvasElement });
103
+ resetWgpuRenderSurfaceProviderForTest();
104
+ expect(getWgpuRenderSurfaceProvider()).toBeNull();
105
+ expect(createWgpuRenderSurface(1, 1)).toBeNull();
106
+ });
107
+ });
108
+
109
+ describe('setWgpuRenderSurfaceProvider', () => {
110
+ afterEach(() => resetWgpuRenderSurfaceProviderForTest());
111
+
112
+ it('replaces the selected provider', () => {
113
+ const first = { createRenderSurface: () => ({}) as HTMLCanvasElement };
114
+ const second = { createRenderSurface: () => ({}) as HTMLCanvasElement };
115
+ setWgpuRenderSurfaceProvider(first);
116
+ setWgpuRenderSurfaceProvider(second);
117
+ expect(getWgpuRenderSurfaceProvider()).toBe(second);
118
+ });
119
+ });
120
+
121
+ function withThrowingDocument(run: () => void): void {
122
+ const descriptor = Object.getOwnPropertyDescriptor(globalThis, 'document');
123
+ Object.defineProperty(globalThis, 'document', {
124
+ configurable: true,
125
+ get(): never {
126
+ throw new Error('portable WGPU code read document');
127
+ },
128
+ });
129
+ try {
130
+ run();
131
+ } finally {
132
+ if (descriptor === undefined) delete (globalThis as { document?: Document }).document;
133
+ else Object.defineProperty(globalThis, 'document', descriptor);
134
+ }
135
+ }
@@ -0,0 +1,227 @@
1
+ import type { WgpuHostBackend } from '@flighthq/types/contract';
2
+ import { afterEach, beforeAll, describe, expect, it, vi } from 'vitest';
3
+
4
+ import {
5
+ createWebWgpuHostBackend,
6
+ getWgpuHostBackend,
7
+ installWgpuHostBackend,
8
+ resetWgpuHostBackendForTest,
9
+ setWgpuHostBackend,
10
+ } from './wgpuHost';
11
+ import { createEmptyWgpuRegistries, createWgpuPipeline } from './wgpuPipeline';
12
+ import {
13
+ createWgpuRenderState,
14
+ createWgpuRenderStateFromCanvasElement,
15
+ destroyWgpuRenderState,
16
+ } from './wgpuRenderState';
17
+ import { installWgpuMock } from './wgpuTestHelper';
18
+
19
+ function fakeBackend(): WgpuHostBackend {
20
+ return {
21
+ acquire: vi.fn(),
22
+ isSupported: vi.fn(() => true),
23
+ release: vi.fn(),
24
+ } as WgpuHostBackend;
25
+ }
26
+
27
+ beforeAll(installWgpuMock);
28
+ afterEach(resetWgpuHostBackendForTest);
29
+
30
+ const _pipeline = createWgpuPipeline(createEmptyWgpuRegistries());
31
+
32
+ describe('createWebWgpuHostBackend', () => {
33
+ it('acquires Flight-owned browser handles and releases each native handle', async () => {
34
+ const backend = createWebWgpuHostBackend();
35
+ const canvas = document.createElement('canvas');
36
+ const acquisition = await backend.acquire(canvas, {});
37
+ const unconfigure = vi.spyOn(acquisition.context, 'unconfigure');
38
+ const destroy = vi.spyOn(acquisition.device, 'destroy');
39
+
40
+ expect(acquisition.ownership).toBe('flight');
41
+ backend.release(acquisition);
42
+ expect(unconfigure).toHaveBeenCalledOnce();
43
+ expect(destroy).toHaveBeenCalledOnce();
44
+ });
45
+
46
+ // ★ THE BACKEND NO LONGER DECIDES OWNERSHIP. It used to return early for `caller`, which meant the whole
47
+ // contract lived in one implementation and any other backend could violate it unseen — and it also made
48
+ // the caller's own release verb a no-op. `release` now tears down whatever it is handed; Flight simply
49
+ // never hands it a caller-owned acquisition (see O1/O2), and `releaseWgpuAcquisition` hands it one only
50
+ // because the caller asked.
51
+ it('tears down whatever it is handed, leaving the ownership decision to the caller', async () => {
52
+ const backend = createWebWgpuHostBackend();
53
+ const acquired = await backend.acquire(document.createElement('canvas'), {});
54
+ const acquisition = { ...acquired, ownership: 'caller' } as const;
55
+ const unconfigure = vi.spyOn(acquisition.context, 'unconfigure');
56
+ const destroy = vi.spyOn(acquisition.device, 'destroy');
57
+
58
+ backend.release(acquisition);
59
+ expect(unconfigure).toHaveBeenCalledOnce();
60
+ expect(destroy).toHaveBeenCalledOnce();
61
+ });
62
+
63
+ it('reports unsupported instead of propagating a throwing navigator.gpu getter', () => {
64
+ Object.defineProperty(globalThis.navigator, 'gpu', {
65
+ configurable: true,
66
+ get(): never {
67
+ throw new Error('host getter failed');
68
+ },
69
+ });
70
+ try {
71
+ expect(createWebWgpuHostBackend().isSupported()).toBe(false);
72
+ } finally {
73
+ installWgpuMock();
74
+ }
75
+ });
76
+ });
77
+
78
+ describe('getWgpuHostBackend', () => {
79
+ it('returns one stable explicit web fallback', () => {
80
+ const first = getWgpuHostBackend();
81
+ const second = getWgpuHostBackend();
82
+ expect(first).toBe(second);
83
+ });
84
+ });
85
+
86
+ describe('installWgpuHostBackend', () => {
87
+ it('preserves the first installed host identity', () => {
88
+ const first = fakeBackend();
89
+ installWgpuHostBackend(first);
90
+ installWgpuHostBackend(fakeBackend());
91
+ expect(getWgpuHostBackend()).toBe(first);
92
+ });
93
+ });
94
+
95
+ describe('resetWgpuHostBackendForTest', () => {
96
+ it('clears custom and host slots back to the web fallback', () => {
97
+ const fallback = getWgpuHostBackend();
98
+ const host = fakeBackend();
99
+ const custom = fakeBackend();
100
+ installWgpuHostBackend(host);
101
+ setWgpuHostBackend(custom);
102
+
103
+ resetWgpuHostBackendForTest();
104
+ expect(getWgpuHostBackend()).toBe(fallback);
105
+ });
106
+ });
107
+
108
+ describe('setWgpuHostBackend', () => {
109
+ it('routes acquisition and release through the same selected backend', async () => {
110
+ const web = createWebWgpuHostBackend();
111
+ const acquired = await web.acquire(document.createElement('canvas'), {});
112
+ // Flight-owned: the state acquired these, so destroying it releases them. The caller-owned case is the
113
+ // opposite assertion and lives in its own test below.
114
+ const acquisition = { ...acquired, ownership: 'flight' } as const;
115
+ const backend: WgpuHostBackend = {
116
+ acquire: vi.fn(async () => acquisition),
117
+ isSupported: vi.fn(() => true),
118
+ release: vi.fn(),
119
+ };
120
+ const canvas = document.createElement('canvas');
121
+ setWgpuHostBackend(backend);
122
+
123
+ const state = await createWgpuRenderStateFromCanvasElement(canvas, _pipeline);
124
+ expect(backend.acquire).toHaveBeenCalledWith(canvas, { format: undefined, powerPreference: undefined });
125
+ expect(state.context).toBe(acquisition.context);
126
+ expect(state.device).toBe(acquisition.device);
127
+ expect(state.format).toBe(acquisition.format);
128
+ destroyWgpuRenderState(state);
129
+ expect(backend.release).toHaveBeenCalledOnce();
130
+ expect(backend.release).toHaveBeenCalledWith(acquisition);
131
+
132
+ web.release(acquired);
133
+ });
134
+
135
+ it('releases flight-owned handles through the selected backend when initialization fails', async () => {
136
+ const web = createWebWgpuHostBackend();
137
+ const acquired = await web.acquire(document.createElement('canvas'), {});
138
+ const acquisition = { ...acquired, ownership: 'flight' } as const;
139
+ const configure = vi.spyOn(acquisition.context, 'configure').mockImplementationOnce(() => {
140
+ throw new Error('configure failed');
141
+ });
142
+ const backend: WgpuHostBackend = {
143
+ acquire: vi.fn(async () => acquisition),
144
+ isSupported: vi.fn(() => true),
145
+ release: vi.fn(),
146
+ };
147
+ setWgpuHostBackend(backend);
148
+
149
+ expect(() => createWgpuRenderState(acquisition, _pipeline)).toThrow('configure failed');
150
+ expect(backend.release).toHaveBeenCalledOnce();
151
+ expect(backend.release).toHaveBeenCalledWith(acquisition);
152
+
153
+ configure.mockRestore();
154
+ web.release(acquired);
155
+ });
156
+
157
+ // ★ O2. The previous shape of this test used a CALLER-owned acquisition and asserted release WAS called,
158
+ // with a `vi.fn()` release that could not destroy anything — so it passed whether or not ownership was
159
+ // honoured. Flight now refuses to release borrowed handles even while unwinding a failure, and the
160
+ // backend here is deliberately one that would destroy them if asked.
161
+ it('O2: never releases caller-owned handles when initialization fails, whatever the backend does', async () => {
162
+ const web = createWebWgpuHostBackend();
163
+ const acquired = await web.acquire(document.createElement('canvas'), {});
164
+ const acquisition = { ...acquired, ownership: 'caller' } as const;
165
+ const configure = vi.spyOn(acquisition.context, 'configure').mockImplementationOnce(() => {
166
+ throw new Error('configure failed');
167
+ });
168
+ const destroy = vi.spyOn(acquisition.device, 'destroy');
169
+ const unconfigure = vi.spyOn(acquisition.context, 'unconfigure');
170
+ const backend: WgpuHostBackend = {
171
+ acquire: vi.fn(async () => acquisition),
172
+ isSupported: vi.fn(() => true),
173
+ release: vi.fn((held) => {
174
+ held.context.unconfigure();
175
+ held.device.destroy();
176
+ }),
177
+ };
178
+ setWgpuHostBackend(backend);
179
+
180
+ expect(() => createWgpuRenderState(acquisition, _pipeline)).toThrow('configure failed');
181
+ expect(backend.release).not.toHaveBeenCalled();
182
+ expect(destroy).not.toHaveBeenCalled();
183
+ expect(unconfigure).not.toHaveBeenCalled();
184
+
185
+ configure.mockRestore();
186
+ destroy.mockRestore();
187
+ unconfigure.mockRestore();
188
+ web.release(acquired);
189
+ });
190
+
191
+ // ★ O1. The destroy path, against a backend whose release really destroys.
192
+ it('O1: never releases caller-owned handles when the state is destroyed', async () => {
193
+ const web = createWebWgpuHostBackend();
194
+ const acquired = await web.acquire(document.createElement('canvas'), {});
195
+ const acquisition = { ...acquired, ownership: 'caller' } as const;
196
+ const destroy = vi.spyOn(acquisition.device, 'destroy');
197
+ const backend: WgpuHostBackend = {
198
+ acquire: vi.fn(async () => acquisition),
199
+ isSupported: vi.fn(() => true),
200
+ release: vi.fn((held) => {
201
+ held.device.destroy();
202
+ }),
203
+ };
204
+ setWgpuHostBackend(backend);
205
+
206
+ const state = createWgpuRenderState(acquisition, _pipeline);
207
+ destroyWgpuRenderState(state);
208
+
209
+ expect(backend.release).not.toHaveBeenCalled();
210
+ expect(destroy).not.toHaveBeenCalled();
211
+ // The handles are still usable, which is the property the caller actually cares about.
212
+ expect(acquisition.device.createBuffer({ size: 4, usage: GPUBufferUsage.COPY_DST }).size).toBe(4);
213
+
214
+ destroy.mockRestore();
215
+ web.release(acquired);
216
+ });
217
+
218
+ it('takes precedence over host installation and reveals that host when cleared', () => {
219
+ const host = fakeBackend();
220
+ const custom = fakeBackend();
221
+ installWgpuHostBackend(host);
222
+ setWgpuHostBackend(custom);
223
+ expect(getWgpuHostBackend()).toBe(custom);
224
+ setWgpuHostBackend(null);
225
+ expect(getWgpuHostBackend()).toBe(host);
226
+ });
227
+ });
@@ -8,15 +8,18 @@ import {
8
8
  registerWgpuMaterialRenderer,
9
9
  resolveWgpuMaterialRenderer,
10
10
  } from './wgpuMaterialRegistry';
11
- import { createWgpuRenderStateRuntime, getWgpuRenderStateRuntime } from './wgpuRenderState';
11
+ import { createEmptyWgpuRegistries, createWgpuPipeline } from './wgpuPipeline';
12
+ import { createWgpuDeviceState, createWgpuRenderStateRuntime, getWgpuRenderStateRuntime } from './wgpuRenderState';
12
13
 
13
14
  const TestKind = 'TestMaterial';
14
15
  const testRenderer: WgpuMaterialRenderer = { instanceFloatCount: 0, getShaderModule: () => ({}) as GPUShaderModule };
16
+ const _pipeline = createWgpuPipeline(createEmptyWgpuRegistries());
15
17
 
16
18
  function makeState(): WgpuRenderState {
17
19
  // `device` is what the guard's message table dispatches on to name the wgpu registrar.
18
- const state = { device: {} } as WgpuRenderState;
19
- state[EntityRuntimeKey] = createWgpuRenderStateRuntime();
20
+ const device = {} as GPUDevice;
21
+ const state = { device } as WgpuRenderState;
22
+ state[EntityRuntimeKey] = createWgpuRenderStateRuntime(createWgpuDeviceState(device), _pipeline);
20
23
  return state;
21
24
  }
22
25
 
@@ -53,10 +53,10 @@ describe('generateWgpuMipmaps', () => {
53
53
  const state = await createWgpuRenderStateForTest();
54
54
  spyEncoderPasses(state);
55
55
  generateWgpuMipmaps(state, makeTextureSpy(), 4, 4, 'rgba8unorm');
56
- const pipeline = getWgpuRenderStateRuntime(state).mipmapPipelineCache.get('rgba8unorm')?.pipeline;
56
+ const pipeline = getWgpuRenderStateRuntime(state).context.mipmapPipelineCache.get('rgba8unorm')?.pipeline;
57
57
  expect(pipeline).toBeDefined();
58
58
  generateWgpuMipmaps(state, makeTextureSpy(), 8, 8, 'rgba8unorm');
59
- expect(getWgpuRenderStateRuntime(state).mipmapPipelineCache.get('rgba8unorm')?.pipeline).toBe(pipeline);
59
+ expect(getWgpuRenderStateRuntime(state).context.mipmapPipelineCache.get('rgba8unorm')?.pipeline).toBe(pipeline);
60
60
  });
61
61
 
62
62
  it('keeps linear and sRGB downsample pipelines distinct', async () => {
@@ -64,7 +64,7 @@ describe('generateWgpuMipmaps', () => {
64
64
  spyEncoderPasses(state);
65
65
  generateWgpuMipmaps(state, makeTextureSpy(), 4, 4, 'rgba8unorm');
66
66
  generateWgpuMipmaps(state, makeTextureSpy(), 4, 4, 'rgba8unorm-srgb');
67
- expect(getWgpuRenderStateRuntime(state).mipmapPipelineCache.size).toBe(2);
67
+ expect(getWgpuRenderStateRuntime(state).context.mipmapPipelineCache.size).toBe(2);
68
68
  });
69
69
  });
70
70
 
@@ -0,0 +1,52 @@
1
+ import type { WgpuPipeline } from '@flighthq/types/contract';
2
+ import { EntityRuntimeKey } from '@flighthq/types/contract';
3
+
4
+ import { createEmptyWgpuRegistries, createWgpuPipeline, getWgpuPipelineRegistries } from './wgpuPipeline';
5
+
6
+ describe('createEmptyWgpuRegistries', () => {
7
+ it('creates empty tables for every required WGPU policy seam', () => {
8
+ const registries = createEmptyWgpuRegistries();
9
+ expect(registries.renderers.shape).toBe('keyed');
10
+ expect(registries.compressedTextureDecoder.shape).toBe('slot');
11
+ expect(registries.compressedTextureUpload.shape).toBe('slot');
12
+ expect(registries.customMaterialShaders.shape).toBe('keyed');
13
+ expect(registries.materialRenderers.shape).toBe('keyed');
14
+ expect(registries.meshMaterialRenderers.shape).toBe('keyed');
15
+ expect(registries.modifierSnippets.shape).toBe('keyed');
16
+ expect(registries.renderEffects.shape).toBe('keyed');
17
+ expect(registries.shapeRasterizer.shape).toBe('slot');
18
+ expect(registries.strokeTessellator.shape).toBe('slot');
19
+ expect(registries.textureResolvers.shape).toBe('keyed');
20
+ expect(registries.velocityWriters.shape).toBe('keyed');
21
+ });
22
+ });
23
+
24
+ describe('createWgpuPipeline', () => {
25
+ it('returns an Entity-backed pipeline with its own runtime identity', () => {
26
+ const registries = createEmptyWgpuRegistries();
27
+ const first = createWgpuPipeline(registries);
28
+ const second = createWgpuPipeline(registries);
29
+
30
+ expect(EntityRuntimeKey in first).toBe(true);
31
+ expect(first).not.toBe(second);
32
+ expect(first[EntityRuntimeKey]).not.toBe(second[EntityRuntimeKey]);
33
+ });
34
+
35
+ it('rejects a plain literal at the Entity boundary', () => {
36
+ const literal = { registries: createEmptyWgpuRegistries() } as unknown as WgpuPipeline;
37
+ expect(EntityRuntimeKey in literal).toBe(false);
38
+ });
39
+
40
+ it('carries the supplied immutable registration snapshot', () => {
41
+ const registries = createEmptyWgpuRegistries();
42
+ const pipeline = createWgpuPipeline(registries);
43
+ expect(getWgpuPipelineRegistries(pipeline)).toBe(registries);
44
+ });
45
+ });
46
+
47
+ describe('getWgpuPipelineRegistries', () => {
48
+ it('returns the registries captured by the explicit pipeline', () => {
49
+ const registries = createEmptyWgpuRegistries();
50
+ expect(getWgpuPipelineRegistries(createWgpuPipeline(registries))).toBe(registries);
51
+ });
52
+ });