@flighthq/render-wgpu 0.5.1-next.1069.554a2ac → 0.5.1-next.1201.9b49dee
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.
- package/README.md +2 -2
- package/dist/contract.d.ts +9 -6
- package/dist/contract.d.ts.map +1 -1
- package/dist/contract.js +9 -6
- package/dist/contract.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/wgpuDraw.js +1 -1
- package/dist/wgpuDraw.js.map +1 -1
- package/dist/wgpuFrame.d.ts +23 -0
- package/dist/wgpuFrame.d.ts.map +1 -0
- package/dist/{wgpuBackground.js → wgpuFrame.js} +67 -121
- package/dist/wgpuFrame.js.map +1 -0
- package/dist/wgpuFullscreenPass.d.ts +2 -2
- package/dist/wgpuFullscreenPass.d.ts.map +1 -1
- package/dist/wgpuFullscreenPass.js +1 -2
- package/dist/wgpuFullscreenPass.js.map +1 -1
- package/dist/wgpuHost.d.ts +6 -6
- package/dist/wgpuHost.d.ts.map +1 -1
- package/dist/wgpuHost.js +17 -3
- package/dist/wgpuHost.js.map +1 -1
- package/dist/wgpuRenderPass.d.ts +9 -0
- package/dist/wgpuRenderPass.d.ts.map +1 -0
- package/dist/wgpuRenderPass.js +222 -0
- package/dist/wgpuRenderPass.js.map +1 -0
- package/dist/wgpuRenderState.d.ts +3 -4
- package/dist/wgpuRenderState.d.ts.map +1 -1
- package/dist/wgpuRenderState.js +17 -88
- package/dist/wgpuRenderState.js.map +1 -1
- package/dist/wgpuRenderTarget.d.ts +12 -29
- package/dist/wgpuRenderTarget.d.ts.map +1 -1
- package/dist/wgpuRenderTarget.js +19 -252
- package/dist/wgpuRenderTarget.js.map +1 -1
- package/dist/wgpuRenderTargetPool.d.ts +5 -5
- package/dist/wgpuRenderTargetPool.d.ts.map +1 -1
- package/dist/wgpuRenderTargetPool.js +7 -7
- package/dist/wgpuRenderTargetPool.js.map +1 -1
- package/dist/wgpuRenderTexture.d.ts +5 -4
- package/dist/wgpuRenderTexture.d.ts.map +1 -1
- package/dist/wgpuRenderTexture.js +14 -16
- package/dist/wgpuRenderTexture.js.map +1 -1
- package/dist/wgpuRenderTexturePool.js +1 -3
- package/dist/wgpuRenderTexturePool.js.map +1 -1
- package/dist/wgpuScissor.d.ts +1 -1
- package/dist/wgpuScissor.d.ts.map +1 -1
- package/dist/wgpuScissor.js +7 -4
- package/dist/wgpuScissor.js.map +1 -1
- package/dist/wgpuScreenAntialias.d.ts +3 -0
- package/dist/wgpuScreenAntialias.d.ts.map +1 -0
- package/dist/wgpuScreenAntialias.js +119 -0
- package/dist/wgpuScreenAntialias.js.map +1 -0
- package/dist/wgpuScreenCapture.d.ts +5 -0
- package/dist/wgpuScreenCapture.d.ts.map +1 -0
- package/dist/{wgpuSurface.js → wgpuScreenCapture.js} +55 -73
- package/dist/wgpuScreenCapture.js.map +1 -0
- package/dist/wgpuScreenRenderTarget.d.ts +9 -0
- package/dist/wgpuScreenRenderTarget.d.ts.map +1 -0
- package/dist/wgpuScreenRenderTarget.js +123 -0
- package/dist/wgpuScreenRenderTarget.js.map +1 -0
- package/dist/wgpuShader.js +2 -2
- package/dist/wgpuShader.js.map +1 -1
- package/dist/wgpuTestHelper.d.ts +8 -2
- package/dist/wgpuTestHelper.d.ts.map +1 -1
- package/dist/wgpuTestHelper.js +38 -3
- package/dist/wgpuTestHelper.js.map +1 -1
- package/dist/wgpuTextureRenderTarget.d.ts +18 -0
- package/dist/wgpuTextureRenderTarget.d.ts.map +1 -0
- package/dist/wgpuTextureRenderTarget.js +122 -0
- package/dist/wgpuTextureRenderTarget.js.map +1 -0
- package/package.json +11 -11
- package/src/wgpuDraw.test.ts +8 -8
- package/src/{wgpuBackground.test.ts → wgpuFrame.test.ts} +34 -20
- package/src/wgpuFullscreenPass.test.ts +7 -7
- package/src/wgpuHost.test.ts +28 -82
- package/src/wgpuRenderPass.test.ts +258 -0
- package/src/wgpuRenderState.test.ts +34 -313
- package/src/wgpuRenderTarget.test.ts +44 -219
- package/src/wgpuRenderTargetPool.test.ts +21 -21
- package/src/wgpuRenderTexture.test.ts +17 -15
- package/src/wgpuScissor.test.ts +43 -13
- package/src/wgpuScreenAntialias.test.ts +60 -0
- package/src/wgpuScreenCapture.test.ts +156 -0
- package/src/wgpuScreenRenderTarget.test.ts +152 -0
- package/src/wgpuShader.test.ts +5 -6
- package/src/wgpuTextureRenderTarget.test.ts +129 -0
- package/src/wgpuTextureResolver.test.ts +5 -5
- package/dist/wgpuAntialias.d.ts +0 -14
- package/dist/wgpuAntialias.d.ts.map +0 -1
- package/dist/wgpuAntialias.js +0 -155
- package/dist/wgpuAntialias.js.map +0 -1
- package/dist/wgpuBackground.d.ts +0 -20
- package/dist/wgpuBackground.d.ts.map +0 -1
- package/dist/wgpuBackground.js.map +0 -1
- package/dist/wgpuElement.d.ts +0 -7
- package/dist/wgpuElement.d.ts.map +0 -1
- package/dist/wgpuElement.js +0 -20
- package/dist/wgpuElement.js.map +0 -1
- package/dist/wgpuSurface.d.ts +0 -6
- package/dist/wgpuSurface.d.ts.map +0 -1
- package/dist/wgpuSurface.js.map +0 -1
- package/src/wgpuAntialias.test.ts +0 -239
- package/src/wgpuElement.test.ts +0 -145
- package/src/wgpuSurface.test.ts +0 -141
|
@@ -22,27 +22,23 @@ import type {
|
|
|
22
22
|
WgpuColorAdjustmentMaterialFeature,
|
|
23
23
|
WgpuColorAdjustmentMaterialFeatureGuard,
|
|
24
24
|
WgpuHostAcquisition,
|
|
25
|
-
|
|
25
|
+
HostWgpuProvider,
|
|
26
26
|
WgpuPipeline,
|
|
27
|
-
WgpuPresentationRenderState,
|
|
28
|
-
WgpuPresentationSurface,
|
|
29
27
|
WgpuRenderOptions,
|
|
30
28
|
WgpuRenderState,
|
|
31
29
|
} from '@flighthq/types/contract';
|
|
32
30
|
import { EntityRuntimeKey, RegistryEntryState } from '@flighthq/types/contract';
|
|
33
31
|
|
|
34
|
-
import { getWgpuSurfaceRenderExtent } from './wgpuAntialias';
|
|
35
|
-
import { beginWgpuFrame, withWgpuFrameBorrow } from './wgpuBackground';
|
|
36
32
|
import { registerWgpuCompressedTextureDecoder, registerWgpuCompressedTextureUpload } from './wgpuCompressedTexture';
|
|
33
|
+
import { beginWgpuFrame, withWgpuFrameBorrow } from './wgpuFrame';
|
|
37
34
|
import { setWgpuHostBackend } from './wgpuHost';
|
|
38
35
|
import { registerWgpuMaterialRenderer } from './wgpuMaterialRegistry';
|
|
39
36
|
import { createEmptyWgpuRegistries, createWgpuPipeline } from './wgpuPipeline';
|
|
40
37
|
import {
|
|
41
|
-
|
|
38
|
+
createWgpuAcquisition,
|
|
42
39
|
createWgpuDeviceState,
|
|
43
40
|
createWgpuOffscreenRenderState as createDeviceOnlyWgpuRenderState,
|
|
44
41
|
createWgpuRenderState as createWgpuRenderStateWithPipeline,
|
|
45
|
-
createWgpuRenderStateFromCanvasElement as createWgpuRenderStateFromCanvasElementWithPipeline,
|
|
46
42
|
createWgpuRenderStateRuntime as createWgpuRenderStateRuntimeWithPipeline,
|
|
47
43
|
destroyWgpuRenderState,
|
|
48
44
|
getWgpuColorAdjustmentMaterialFeature,
|
|
@@ -77,22 +73,15 @@ beforeAll(() => {
|
|
|
77
73
|
|
|
78
74
|
const _testPipeline = createWgpuPipeline(createEmptyWgpuRegistries());
|
|
79
75
|
|
|
80
|
-
function createWgpuRenderState(
|
|
81
|
-
return createWgpuRenderStateWithPipeline(
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function createWgpuRenderStateFromCanvasElement(
|
|
85
|
-
canvas: HTMLCanvasElement,
|
|
86
|
-
options: Readonly<WgpuRenderOptions & { powerPreference?: GPUPowerPreference }> = {},
|
|
87
|
-
) {
|
|
88
|
-
return createWgpuRenderStateFromCanvasElementWithPipeline(canvas, _testPipeline, options);
|
|
76
|
+
function createWgpuRenderState(device: GPUDevice, options: Readonly<WgpuRenderOptions> = {}) {
|
|
77
|
+
return createWgpuRenderStateWithPipeline(device, _testPipeline, options);
|
|
89
78
|
}
|
|
90
79
|
|
|
91
80
|
function createWgpuRenderStateRuntime(deviceState: ReturnType<typeof createWgpuDeviceState>) {
|
|
92
81
|
return createWgpuRenderStateRuntimeWithPipeline(deviceState, _testPipeline);
|
|
93
82
|
}
|
|
94
83
|
|
|
95
|
-
function entityHostBackend(fields: Omit<
|
|
84
|
+
function entityHostBackend(fields: Omit<HostWgpuProvider, keyof Entity>): HostWgpuProvider {
|
|
96
85
|
return (() => {
|
|
97
86
|
const out = allocateEntity<any>();
|
|
98
87
|
Object.assign(out, fields);
|
|
@@ -103,7 +92,6 @@ function entityHostBackend(fields: Omit<WgpuHostBackend, keyof Entity>): WgpuHos
|
|
|
103
92
|
function createWgpuOffscreenRenderState(source: WgpuRenderState): WgpuRenderState {
|
|
104
93
|
const pipeline: WgpuPipeline = createWgpuPipeline(getWgpuRenderStateRuntime(source).registries);
|
|
105
94
|
const state = createDeviceOnlyWgpuRenderState(source.deviceState, pipeline, {
|
|
106
|
-
backgroundColor: source.backgroundColor,
|
|
107
95
|
format: source.format,
|
|
108
96
|
imageSmoothingEnabled: source.allowSmoothing,
|
|
109
97
|
pixelRatio: source.pixelRatio,
|
|
@@ -136,9 +124,9 @@ function registerPaddingResolver(state: RenderState, kind: string, resolver: Ren
|
|
|
136
124
|
);
|
|
137
125
|
}
|
|
138
126
|
|
|
139
|
-
describe('
|
|
127
|
+
describe('createWgpuAcquisition', () => {
|
|
140
128
|
it('hands back handles the CALLER owns, so no state teardown can release them', async () => {
|
|
141
|
-
const acquisition = await
|
|
129
|
+
const acquisition = await createWgpuAcquisition(document.createElement('canvas'));
|
|
142
130
|
|
|
143
131
|
expect(acquisition).not.toBeNull();
|
|
144
132
|
expect(acquisition!.ownership).toBe('caller');
|
|
@@ -154,12 +142,13 @@ describe('createWgpuAcquisitionFromCanvasElement', () => {
|
|
|
154
142
|
acquire: vi.fn(async () => {
|
|
155
143
|
throw new Error('no adapter');
|
|
156
144
|
}),
|
|
145
|
+
attachSurface: vi.fn(() => null),
|
|
157
146
|
isSupported: vi.fn(() => false),
|
|
158
147
|
release: vi.fn(),
|
|
159
148
|
}),
|
|
160
149
|
);
|
|
161
150
|
|
|
162
|
-
await expect(
|
|
151
|
+
await expect(createWgpuAcquisition(document.createElement('canvas'))).resolves.toBeNull();
|
|
163
152
|
setWgpuHostBackend(null);
|
|
164
153
|
});
|
|
165
154
|
});
|
|
@@ -195,14 +184,6 @@ describe('createWgpuDeviceState', () => {
|
|
|
195
184
|
});
|
|
196
185
|
|
|
197
186
|
describe('createWgpuOffscreenRenderState', () => {
|
|
198
|
-
it('rejects a device-only state at presentation boundaries', () => {
|
|
199
|
-
const deviceOnlyState = null as WgpuRenderState | null;
|
|
200
|
-
if (deviceOnlyState === null) return;
|
|
201
|
-
|
|
202
|
-
// @ts-expect-error — a device-only state has no presentation surface or canvas context.
|
|
203
|
-
beginWgpuFrame(deviceOnlyState);
|
|
204
|
-
});
|
|
205
|
-
|
|
206
187
|
it('resolves late screen blend-mode wiring explicitly until locally overridden', async () => {
|
|
207
188
|
const screen = await createWgpuRenderStateForTest();
|
|
208
189
|
const offscreen = createWgpuOffscreenRenderState(screen);
|
|
@@ -382,7 +363,6 @@ describe('createWgpuOffscreenRenderState', () => {
|
|
|
382
363
|
|
|
383
364
|
withWgpuFrameBorrow(screen, offscreen, () => {
|
|
384
365
|
expect(offscreenRuntime.commandEncoder).toBe(encoder);
|
|
385
|
-
expect(getWgpuSurfaceRenderExtent(offscreen)).toEqual({ height: 600, width: 800 });
|
|
386
366
|
offscreenRuntime.uniformOffset = offscreenRuntime.uniformStride;
|
|
387
367
|
});
|
|
388
368
|
|
|
@@ -403,7 +383,6 @@ describe('createWgpuOffscreenRenderState', () => {
|
|
|
403
383
|
expect.anything(),
|
|
404
384
|
);
|
|
405
385
|
expect(submit).not.toHaveBeenCalled();
|
|
406
|
-
expect(() => getWgpuSurfaceRenderExtent(offscreen)).toThrow(/outside an explicit frame borrow/);
|
|
407
386
|
});
|
|
408
387
|
|
|
409
388
|
it('submits a standalone borrow and restores both states when the callback throws', async () => {
|
|
@@ -421,7 +400,6 @@ describe('createWgpuOffscreenRenderState', () => {
|
|
|
421
400
|
expect(submit).toHaveBeenCalledOnce();
|
|
422
401
|
expect(getWgpuRenderStateRuntime(screen).commandEncoder).toBeNull();
|
|
423
402
|
expect(getWgpuRenderStateRuntime(offscreen).commandEncoder).toBeNull();
|
|
424
|
-
expect(() => getWgpuSurfaceRenderExtent(offscreen)).toThrow(/outside an explicit frame borrow/);
|
|
425
403
|
});
|
|
426
404
|
|
|
427
405
|
it('rejects nested borrowing by the same offscreen state', async () => {
|
|
@@ -474,73 +452,20 @@ describe('createWgpuOffscreenRenderState', () => {
|
|
|
474
452
|
});
|
|
475
453
|
|
|
476
454
|
describe('createWgpuRenderState', () => {
|
|
477
|
-
it('
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
let deviceUsable = true;
|
|
489
|
-
const originalCreateBuffer = acquisition.device.createBuffer.bind(acquisition.device);
|
|
490
|
-
const originalGetCurrentTexture = acquisition.context.getCurrentTexture.bind(acquisition.context);
|
|
491
|
-
const createBuffer = vi.spyOn(acquisition.device, 'createBuffer').mockImplementation((descriptor) => {
|
|
492
|
-
if (!deviceUsable) throw new Error('caller device was destroyed');
|
|
493
|
-
return originalCreateBuffer(descriptor);
|
|
494
|
-
});
|
|
495
|
-
const getCurrentTexture = vi.spyOn(acquisition.context, 'getCurrentTexture').mockImplementation(() => {
|
|
496
|
-
if (!contextUsable) throw new Error('caller context was unconfigured');
|
|
497
|
-
return originalGetCurrentTexture();
|
|
498
|
-
});
|
|
499
|
-
const unconfigure = vi.spyOn(acquisition.context, 'unconfigure').mockImplementation(() => {
|
|
500
|
-
contextUsable = false;
|
|
501
|
-
});
|
|
502
|
-
const destroy = vi.spyOn(acquisition.device, 'destroy').mockImplementation(() => {
|
|
503
|
-
deviceUsable = false;
|
|
504
|
-
});
|
|
505
|
-
Object.defineProperty(globalThis.navigator, 'gpu', {
|
|
506
|
-
configurable: true,
|
|
507
|
-
get(): never {
|
|
508
|
-
throw new Error('host getter failed');
|
|
509
|
-
},
|
|
510
|
-
});
|
|
511
|
-
|
|
512
|
-
try {
|
|
513
|
-
const state = createWgpuRenderState(acquisition);
|
|
514
|
-
const offscreen = createWgpuOffscreenRenderState(state);
|
|
515
|
-
expect(state.context).toBe(acquisition.context);
|
|
516
|
-
expect(state.device).toBe(acquisition.device);
|
|
517
|
-
expect(state.format).toBe(acquisition.format);
|
|
518
|
-
expect('context' in offscreen).toBe(false);
|
|
519
|
-
expect(offscreen.device).toBe(acquisition.device);
|
|
520
|
-
expect(offscreen.format).toBe(acquisition.format);
|
|
521
|
-
const createBufferCallsBeforeTeardown = createBuffer.mock.calls.length;
|
|
522
|
-
const getCurrentTextureCallsBeforeTeardown = getCurrentTexture.mock.calls.length;
|
|
523
|
-
destroyWgpuRenderState(state);
|
|
524
|
-
destroyWgpuRenderState(offscreen);
|
|
525
|
-
expect(unconfigure).not.toHaveBeenCalled();
|
|
526
|
-
expect(destroy).not.toHaveBeenCalled();
|
|
527
|
-
|
|
528
|
-
const buffer = acquisition.device.createBuffer({ size: 4, usage: GPUBufferUsage.COPY_DST });
|
|
529
|
-
expect(buffer.size).toBe(4);
|
|
530
|
-
expect(acquisition.context.getCurrentTexture().createView()).toBeDefined();
|
|
531
|
-
expect(createBuffer).toHaveBeenCalledTimes(createBufferCallsBeforeTeardown + 1);
|
|
532
|
-
expect(getCurrentTexture).toHaveBeenCalledTimes(getCurrentTextureCallsBeforeTeardown + 1);
|
|
533
|
-
buffer.destroy();
|
|
534
|
-
} finally {
|
|
535
|
-
installWgpuMock();
|
|
536
|
-
destroyWgpuRenderState(owner);
|
|
537
|
-
}
|
|
538
|
-
});
|
|
455
|
+
it('takes the device alone: no surface, no context, nothing that belongs to a screen', async () => {
|
|
456
|
+
// The state is "how to talk to the GPU" and the screen target is "which surface a frame lands on".
|
|
457
|
+
// Keeping them apart is what lets one state render to several windows, and lets an offscreen state
|
|
458
|
+
// exist without inventing a canvas for it.
|
|
459
|
+
const acquisition = await createWgpuAcquisition(document.createElement('canvas'));
|
|
460
|
+
const state = createWgpuRenderState(acquisition!.device, { format: acquisition!.format });
|
|
461
|
+
|
|
462
|
+
expect(state.device).toBe(acquisition!.device);
|
|
463
|
+
expect(state.format).toBe(acquisition!.format);
|
|
464
|
+
expect('context' in state).toBe(false);
|
|
465
|
+
expect('surface' in state).toBe(false);
|
|
539
466
|
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
expect(state.device).toBeDefined();
|
|
543
|
-
expect(state.context).toBeDefined();
|
|
467
|
+
destroyWgpuRenderState(state);
|
|
468
|
+
releaseWgpuAcquisition(acquisition!);
|
|
544
469
|
});
|
|
545
470
|
|
|
546
471
|
it('sets allowSmoothing to true by default', async () => {
|
|
@@ -556,60 +481,13 @@ describe('createWgpuRenderState', () => {
|
|
|
556
481
|
expect(runtime.uniformOffset).toBe(0);
|
|
557
482
|
});
|
|
558
483
|
|
|
559
|
-
|
|
560
|
-
// re-impose the DOM dependency the surface type exists to remove — that the web backend's surface happens
|
|
561
|
-
// to be a canvas is a property of that backend, pinned in wgpuHost.test.ts, not of the state contract.
|
|
562
|
-
it('exposes the presentation surface as live size, carrying no DOM member', async () => {
|
|
563
|
-
const state = await createWgpuRenderStateForTest();
|
|
564
|
-
expect(typeof state.surface.width).toBe('number');
|
|
565
|
-
expect(typeof state.surface.height).toBe('number');
|
|
566
|
-
expect(Object.keys(state.surface as object).filter((key) => key !== 'width' && key !== 'height')).toEqual([]);
|
|
567
|
-
});
|
|
568
|
-
|
|
569
|
-
it('starts with null renderPass and commandEncoder', async () => {
|
|
484
|
+
it('starts with no open frame, pass, or target', async () => {
|
|
570
485
|
const state = await createWgpuRenderStateForTest();
|
|
571
486
|
const runtime = getWgpuRenderStateRuntime(state);
|
|
572
487
|
expect(runtime.renderPass).toBeNull();
|
|
573
488
|
expect(runtime.commandEncoder).toBeNull();
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
describe('createWgpuRenderStateFromCanvasElement', () => {
|
|
578
|
-
it('acquires flight-owned handles and releases them when the state is destroyed', async () => {
|
|
579
|
-
const owner = await createWgpuRenderStateForTest();
|
|
580
|
-
const acquisition = { ...ownerAcquisition(owner), ownership: 'flight' } as const;
|
|
581
|
-
const released: Readonly<WgpuHostAcquisition>[] = [];
|
|
582
|
-
setWgpuHostBackend(
|
|
583
|
-
entityHostBackend({
|
|
584
|
-
acquire: vi.fn(async () => acquisition),
|
|
585
|
-
isSupported: vi.fn(() => true),
|
|
586
|
-
release: vi.fn((held: Readonly<WgpuHostAcquisition>) => released.push(held)),
|
|
587
|
-
}),
|
|
588
|
-
);
|
|
589
|
-
|
|
590
|
-
const state = await createWgpuRenderStateFromCanvasElement(document.createElement('canvas'));
|
|
591
|
-
destroyWgpuRenderState(state);
|
|
592
|
-
|
|
593
|
-
expect(released).toEqual([acquisition]);
|
|
594
|
-
setWgpuHostBackend(null);
|
|
595
|
-
destroyWgpuRenderState(owner);
|
|
596
|
-
});
|
|
597
|
-
|
|
598
|
-
it('forwards the acquisition options, which the render options no longer carry', async () => {
|
|
599
|
-
const owner = await createWgpuRenderStateForTest();
|
|
600
|
-
const acquire = vi.fn(async () => ({ ...ownerAcquisition(owner), ownership: 'flight' }) as const);
|
|
601
|
-
setWgpuHostBackend(entityHostBackend({ acquire, isSupported: vi.fn(() => true), release: vi.fn() }));
|
|
602
|
-
const canvas = document.createElement('canvas');
|
|
603
|
-
|
|
604
|
-
const state = await createWgpuRenderStateFromCanvasElement(canvas, {
|
|
605
|
-
format: 'rgba8unorm',
|
|
606
|
-
powerPreference: 'low-power',
|
|
607
|
-
});
|
|
608
|
-
|
|
609
|
-
expect(acquire).toHaveBeenCalledWith(canvas, { format: 'rgba8unorm', powerPreference: 'low-power' });
|
|
610
|
-
destroyWgpuRenderState(state);
|
|
611
|
-
setWgpuHostBackend(null);
|
|
612
|
-
destroyWgpuRenderState(owner);
|
|
489
|
+
expect(runtime.passStack).toEqual([]);
|
|
490
|
+
expect(runtime.currentRenderTarget).toBeNull();
|
|
613
491
|
});
|
|
614
492
|
});
|
|
615
493
|
|
|
@@ -704,21 +582,18 @@ describe('createWgpuRenderStateRuntime', () => {
|
|
|
704
582
|
});
|
|
705
583
|
|
|
706
584
|
describe('destroyWgpuRenderState', () => {
|
|
707
|
-
it('
|
|
585
|
+
it('never destroys the device, however many of its states are torn down', async () => {
|
|
586
|
+
// The device came from the caller and outlives every state built on it — including the last one.
|
|
708
587
|
const state = await createWgpuRenderStateForTest();
|
|
709
588
|
const offscreen = createWgpuOffscreenRenderState(state);
|
|
710
|
-
const unconfigure = vi.spyOn(state.context, 'unconfigure');
|
|
711
589
|
const destroy = vi.spyOn(state.device, 'destroy');
|
|
712
590
|
|
|
713
591
|
destroyWgpuRenderState(state);
|
|
714
592
|
destroyWgpuRenderState(state);
|
|
715
|
-
expect(unconfigure).not.toHaveBeenCalled();
|
|
716
|
-
expect(destroy).not.toHaveBeenCalled();
|
|
717
|
-
|
|
718
593
|
destroyWgpuRenderState(offscreen);
|
|
719
594
|
destroyWgpuRenderState(offscreen);
|
|
720
|
-
|
|
721
|
-
expect(destroy).
|
|
595
|
+
|
|
596
|
+
expect(destroy).not.toHaveBeenCalled();
|
|
722
597
|
});
|
|
723
598
|
|
|
724
599
|
it('destroys the state-owned uniform buffer', async () => {
|
|
@@ -992,15 +867,6 @@ describe('registerWgpuDeviceTeardown', () => {
|
|
|
992
867
|
});
|
|
993
868
|
});
|
|
994
869
|
|
|
995
|
-
function ownerAcquisition(owner: WgpuPresentationRenderState): Omit<WgpuHostAcquisition, 'ownership'> {
|
|
996
|
-
const out = allocateEntity<any>();
|
|
997
|
-
out.context = owner.context;
|
|
998
|
-
out.device = owner.device;
|
|
999
|
-
out.format = owner.format;
|
|
1000
|
-
out.surface = owner.surface;
|
|
1001
|
-
return finishEntity(out);
|
|
1002
|
-
}
|
|
1003
|
-
|
|
1004
870
|
describe('registerWgpuRenderStateTeardown', () => {
|
|
1005
871
|
it('runs a state-owned callback exactly once on teardown', async () => {
|
|
1006
872
|
const state = await createWgpuRenderStateForTest();
|
|
@@ -1019,12 +885,13 @@ describe('releaseWgpuAcquisition', () => {
|
|
|
1019
885
|
// Unconditional on purpose: this is the CALLER asking. Flight's own paths refuse to release caller-owned
|
|
1020
886
|
// handles, so if this verb deferred to the same policy the caller would have no way to end their life.
|
|
1021
887
|
it('releases caller-owned handles, which Flight itself never does', async () => {
|
|
1022
|
-
const
|
|
1023
|
-
const acquisition =
|
|
888
|
+
const acquired = await createWgpuAcquisition(document.createElement('canvas'));
|
|
889
|
+
const acquisition = acquired!;
|
|
1024
890
|
const released: Readonly<WgpuHostAcquisition>[] = [];
|
|
1025
891
|
setWgpuHostBackend(
|
|
1026
892
|
entityHostBackend({
|
|
1027
893
|
acquire: vi.fn(async () => acquisition),
|
|
894
|
+
attachSurface: vi.fn(() => null),
|
|
1028
895
|
isSupported: vi.fn(() => true),
|
|
1029
896
|
release: vi.fn((held: Readonly<WgpuHostAcquisition>) => released.push(held)),
|
|
1030
897
|
}),
|
|
@@ -1034,9 +901,9 @@ describe('releaseWgpuAcquisition', () => {
|
|
|
1034
901
|
|
|
1035
902
|
expect(released).toEqual([acquisition]);
|
|
1036
903
|
setWgpuHostBackend(null);
|
|
1037
|
-
destroyWgpuRenderState(owner);
|
|
1038
904
|
});
|
|
1039
905
|
});
|
|
906
|
+
|
|
1040
907
|
describe('resolveWgpuApplyBlendMode', () => {
|
|
1041
908
|
it('returns a hook installed directly on the state', async () => {
|
|
1042
909
|
const state = await createWgpuRenderStateForTest();
|
|
@@ -1046,104 +913,6 @@ describe('resolveWgpuApplyBlendMode', () => {
|
|
|
1046
913
|
});
|
|
1047
914
|
});
|
|
1048
915
|
|
|
1049
|
-
// ★ OWNERSHIP LIFECYCLE. Flight decides whether an acquisition is released; the backend only carries out
|
|
1050
|
-
// the teardown. Each backend here would really destroy the handles if asked, so "not released" is a fact
|
|
1051
|
-
// about Flight's decision rather than about a spy that could not have destroyed anything.
|
|
1052
|
-
describe('wgpu acquisition lifecycle', () => {
|
|
1053
|
-
const destroyingBackend = (
|
|
1054
|
-
acquisition: Readonly<WgpuHostAcquisition>,
|
|
1055
|
-
): { backend: WgpuHostBackend; released: Readonly<WgpuHostAcquisition>[] } => {
|
|
1056
|
-
const released: Readonly<WgpuHostAcquisition>[] = [];
|
|
1057
|
-
return {
|
|
1058
|
-
backend: entityHostBackend({
|
|
1059
|
-
acquire: vi.fn(async () => acquisition),
|
|
1060
|
-
isSupported: vi.fn(() => true),
|
|
1061
|
-
release: vi.fn((held: Readonly<WgpuHostAcquisition>) => {
|
|
1062
|
-
released.push(held);
|
|
1063
|
-
held.device.destroy();
|
|
1064
|
-
}),
|
|
1065
|
-
}),
|
|
1066
|
-
released,
|
|
1067
|
-
};
|
|
1068
|
-
};
|
|
1069
|
-
|
|
1070
|
-
it('L1: releases a flight-owned acquisition exactly once, after the last sharer, in either order', async () => {
|
|
1071
|
-
for (const destroyOffscreenFirst of [false, true]) {
|
|
1072
|
-
const owner = await createWgpuRenderStateForTest();
|
|
1073
|
-
const acquisition = { ...ownerAcquisition(owner), ownership: 'flight' } as const;
|
|
1074
|
-
const { backend, released } = destroyingBackend(acquisition);
|
|
1075
|
-
setWgpuHostBackend(backend);
|
|
1076
|
-
|
|
1077
|
-
const state = createWgpuRenderState(acquisition);
|
|
1078
|
-
const offscreen = createWgpuOffscreenRenderState(state);
|
|
1079
|
-
const first = destroyOffscreenFirst ? offscreen : state;
|
|
1080
|
-
const second = destroyOffscreenFirst ? state : offscreen;
|
|
1081
|
-
|
|
1082
|
-
destroyWgpuRenderState(first);
|
|
1083
|
-
expect(released, 'released before the last sharer was destroyed').toEqual([]);
|
|
1084
|
-
destroyWgpuRenderState(second);
|
|
1085
|
-
expect(released).toEqual([acquisition]);
|
|
1086
|
-
|
|
1087
|
-
setWgpuHostBackend(null);
|
|
1088
|
-
destroyWgpuRenderState(owner);
|
|
1089
|
-
}
|
|
1090
|
-
});
|
|
1091
|
-
|
|
1092
|
-
it('L2: never releases a caller-owned acquisition, in either destroy order', async () => {
|
|
1093
|
-
for (const destroyOffscreenFirst of [false, true]) {
|
|
1094
|
-
const owner = await createWgpuRenderStateForTest();
|
|
1095
|
-
const acquisition = { ...ownerAcquisition(owner), ownership: 'caller' } as const;
|
|
1096
|
-
const { backend, released } = destroyingBackend(acquisition);
|
|
1097
|
-
setWgpuHostBackend(backend);
|
|
1098
|
-
|
|
1099
|
-
const state = createWgpuRenderState(acquisition);
|
|
1100
|
-
const offscreen = createWgpuOffscreenRenderState(state);
|
|
1101
|
-
destroyWgpuRenderState(destroyOffscreenFirst ? offscreen : state);
|
|
1102
|
-
destroyWgpuRenderState(destroyOffscreenFirst ? state : offscreen);
|
|
1103
|
-
|
|
1104
|
-
expect(released).toEqual([]);
|
|
1105
|
-
// L5: the handles still work, which is what the caller actually depends on.
|
|
1106
|
-
expect(acquisition.device.createBuffer({ size: 4, usage: GPUBufferUsage.COPY_DST }).size).toBe(4);
|
|
1107
|
-
|
|
1108
|
-
setWgpuHostBackend(null);
|
|
1109
|
-
destroyWgpuRenderState(owner);
|
|
1110
|
-
}
|
|
1111
|
-
});
|
|
1112
|
-
|
|
1113
|
-
it('L3: destroying the same state twice releases once', async () => {
|
|
1114
|
-
const owner = await createWgpuRenderStateForTest();
|
|
1115
|
-
const acquisition = { ...ownerAcquisition(owner), ownership: 'flight' } as const;
|
|
1116
|
-
const { backend, released } = destroyingBackend(acquisition);
|
|
1117
|
-
setWgpuHostBackend(backend);
|
|
1118
|
-
|
|
1119
|
-
const state = createWgpuRenderState(acquisition);
|
|
1120
|
-
destroyWgpuRenderState(state);
|
|
1121
|
-
destroyWgpuRenderState(state);
|
|
1122
|
-
|
|
1123
|
-
expect(released).toEqual([acquisition]);
|
|
1124
|
-
setWgpuHostBackend(null);
|
|
1125
|
-
destroyWgpuRenderState(owner);
|
|
1126
|
-
});
|
|
1127
|
-
|
|
1128
|
-
it('L4: routes release to the backend that acquired, not whichever is installed at destroy time', async () => {
|
|
1129
|
-
const owner = await createWgpuRenderStateForTest();
|
|
1130
|
-
const acquisition = { ...ownerAcquisition(owner), ownership: 'flight' } as const;
|
|
1131
|
-
const acquiring = destroyingBackend(acquisition);
|
|
1132
|
-
const replacement = destroyingBackend(acquisition);
|
|
1133
|
-
setWgpuHostBackend(acquiring.backend);
|
|
1134
|
-
|
|
1135
|
-
const state = createWgpuRenderState(acquisition);
|
|
1136
|
-
setWgpuHostBackend(replacement.backend);
|
|
1137
|
-
destroyWgpuRenderState(state);
|
|
1138
|
-
|
|
1139
|
-
expect(acquiring.released).toEqual([acquisition]);
|
|
1140
|
-
expect(replacement.released, 'released through the backend installed later').toEqual([]);
|
|
1141
|
-
|
|
1142
|
-
setWgpuHostBackend(null);
|
|
1143
|
-
destroyWgpuRenderState(owner);
|
|
1144
|
-
});
|
|
1145
|
-
});
|
|
1146
|
-
|
|
1147
916
|
describe('WgpuPipeline snapshots', () => {
|
|
1148
917
|
it('captures late Wgpu registrations only when an explicit pipeline is created', async () => {
|
|
1149
918
|
const screen = await createWgpuRenderStateForTest();
|
|
@@ -1205,51 +974,3 @@ describe('WgpuPipeline snapshots', () => {
|
|
|
1205
974
|
// would satisfy every construction-time assertion in this repo and fail only on a resize, so liveness is
|
|
1206
975
|
// tested here through a NON-DOM provider whose values change behind getters — which also proves the second
|
|
1207
976
|
// half of the contract, that nothing on the path reads a DOM member.
|
|
1208
|
-
describe('WgpuPresentationSurface', () => {
|
|
1209
|
-
it('C1/C2: drives every size consumer from a live, non-DOM, size-only provider', async () => {
|
|
1210
|
-
const owner = await createWgpuRenderStateForTest();
|
|
1211
|
-
let width = 800;
|
|
1212
|
-
let height = 600;
|
|
1213
|
-
const surface: WgpuPresentationSurface = {
|
|
1214
|
-
get height() {
|
|
1215
|
-
return height;
|
|
1216
|
-
},
|
|
1217
|
-
get width() {
|
|
1218
|
-
return width;
|
|
1219
|
-
},
|
|
1220
|
-
};
|
|
1221
|
-
const acquisition = (() => {
|
|
1222
|
-
const out = allocateEntity<any>();
|
|
1223
|
-
out.context = owner.context;
|
|
1224
|
-
out.device = owner.device;
|
|
1225
|
-
out.format = owner.format;
|
|
1226
|
-
out.ownership = 'caller' as const;
|
|
1227
|
-
out.surface = surface;
|
|
1228
|
-
return finishEntity(out);
|
|
1229
|
-
})();
|
|
1230
|
-
const canvas = document.createElement('canvas');
|
|
1231
|
-
|
|
1232
|
-
const state = createWgpuRenderState(acquisition);
|
|
1233
|
-
expect(getWgpuSurfaceRenderExtent(state)).toEqual({ width: 800, height: 600 });
|
|
1234
|
-
|
|
1235
|
-
width = 320;
|
|
1236
|
-
height = 240;
|
|
1237
|
-
|
|
1238
|
-
// Every consumer re-reads: a captured `{ width, height }` would still answer 800x600 here.
|
|
1239
|
-
expect(state.surface.width).toBe(320);
|
|
1240
|
-
expect(state.surface.height).toBe(240);
|
|
1241
|
-
expect(getWgpuSurfaceRenderExtent(state)).toEqual({ width: 320, height: 240 });
|
|
1242
|
-
|
|
1243
|
-
destroyWgpuRenderState(state);
|
|
1244
|
-
destroyWgpuRenderState(owner);
|
|
1245
|
-
});
|
|
1246
|
-
|
|
1247
|
-
it('C4: accepts an HTMLCanvasElement structurally, so the web path needs no wrapper', () => {
|
|
1248
|
-
const canvas = document.createElement('canvas');
|
|
1249
|
-
canvas.width = 128;
|
|
1250
|
-
canvas.height = 64;
|
|
1251
|
-
const surface: WgpuPresentationSurface = canvas;
|
|
1252
|
-
expect(surface.width).toBe(128);
|
|
1253
|
-
expect(surface.height).toBe(64);
|
|
1254
|
-
});
|
|
1255
|
-
});
|