@flighthq/render-wgpu 0.3.0-next.906.07cea63 → 0.3.1-next.209.43ef1bc
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/dist/contract.d.ts +7 -1
- package/dist/contract.d.ts.map +1 -1
- package/dist/contract.js +7 -1
- package/dist/contract.js.map +1 -1
- package/dist/enableWgpuTextureResolverGuards.d.ts +4 -0
- package/dist/enableWgpuTextureResolverGuards.d.ts.map +1 -0
- package/dist/enableWgpuTextureResolverGuards.js +8 -0
- package/dist/enableWgpuTextureResolverGuards.js.map +1 -0
- package/dist/explainWgpuTextureResolution.d.ts +3 -0
- package/dist/explainWgpuTextureResolution.d.ts.map +1 -0
- package/dist/explainWgpuTextureResolution.js +14 -0
- package/dist/explainWgpuTextureResolution.js.map +1 -0
- 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/wgpuBackground.js +2 -2
- package/dist/wgpuBackground.js.map +1 -1
- package/dist/wgpuCompressedTexture.d.ts +2 -2
- package/dist/wgpuCompressedTexture.d.ts.map +1 -1
- package/dist/wgpuCompressedTexture.js +40 -12
- package/dist/wgpuCompressedTexture.js.map +1 -1
- package/dist/wgpuDraw.d.ts +6 -4
- package/dist/wgpuDraw.d.ts.map +1 -1
- package/dist/wgpuDraw.js +139 -67
- package/dist/wgpuDraw.js.map +1 -1
- package/dist/wgpuExternalImageSource.d.ts +3 -0
- package/dist/wgpuExternalImageSource.d.ts.map +1 -0
- package/dist/wgpuExternalImageSource.js +51 -0
- package/dist/wgpuExternalImageSource.js.map +1 -0
- package/dist/wgpuExternalTexture.d.ts +4 -0
- package/dist/wgpuExternalTexture.d.ts.map +1 -0
- package/dist/wgpuExternalTexture.js +62 -0
- package/dist/wgpuExternalTexture.js.map +1 -0
- package/dist/wgpuMaterialRegistry.d.ts.map +1 -1
- package/dist/wgpuMaterialRegistry.js +10 -9
- package/dist/wgpuMaterialRegistry.js.map +1 -1
- package/dist/wgpuMipmap.d.ts.map +1 -1
- package/dist/wgpuMipmap.js +11 -9
- package/dist/wgpuMipmap.js.map +1 -1
- package/dist/wgpuRenderState.d.ts +11 -2
- package/dist/wgpuRenderState.d.ts.map +1 -1
- package/dist/wgpuRenderState.js +217 -30
- package/dist/wgpuRenderState.js.map +1 -1
- package/dist/wgpuRenderTarget.d.ts.map +1 -1
- package/dist/wgpuRenderTarget.js +2 -0
- package/dist/wgpuRenderTarget.js.map +1 -1
- package/dist/wgpuRenderTexture.d.ts +15 -0
- package/dist/wgpuRenderTexture.d.ts.map +1 -0
- package/dist/wgpuRenderTexture.js +130 -0
- package/dist/wgpuRenderTexture.js.map +1 -0
- package/dist/wgpuRenderTexturePool.d.ts +7 -0
- package/dist/wgpuRenderTexturePool.d.ts.map +1 -0
- package/dist/wgpuRenderTexturePool.js +86 -0
- package/dist/wgpuRenderTexturePool.js.map +1 -0
- package/dist/wgpuShader.d.ts.map +1 -1
- package/dist/wgpuShader.js +5 -1
- package/dist/wgpuShader.js.map +1 -1
- package/dist/wgpuSurface.d.ts +2 -2
- package/dist/wgpuSurface.d.ts.map +1 -1
- package/dist/wgpuSurface.js +61 -13
- package/dist/wgpuSurface.js.map +1 -1
- package/dist/wgpuTestHelper.d.ts +1 -0
- package/dist/wgpuTestHelper.d.ts.map +1 -1
- package/dist/wgpuTestHelper.js +15 -1
- package/dist/wgpuTestHelper.js.map +1 -1
- package/dist/wgpuTextureResolver.d.ts +9 -0
- package/dist/wgpuTextureResolver.d.ts.map +1 -0
- package/dist/wgpuTextureResolver.js +67 -0
- package/dist/wgpuTextureResolver.js.map +1 -0
- package/dist/wgpuTextureUpload.d.ts +2 -2
- package/dist/wgpuTextureUpload.d.ts.map +1 -1
- package/dist/wgpuTextureUpload.js +5 -10
- package/dist/wgpuTextureUpload.js.map +1 -1
- package/package.json +13 -7
- package/src/enableWgpuTextureResolverGuards.test.ts +65 -0
- package/src/explainWgpuTextureResolution.test.ts +36 -0
- package/src/wgpuCompressedTexture.test.ts +18 -35
- package/src/wgpuDraw.test.ts +164 -62
- package/src/wgpuExternalImageSource.test.ts +89 -0
- package/src/wgpuExternalTexture.test.ts +35 -0
- package/src/wgpuMaterialRegistry.test.ts +28 -2
- package/src/wgpuMipmap.test.ts +10 -2
- package/src/wgpuRenderState.test.ts +143 -11
- package/src/wgpuRenderTarget.test.ts +14 -0
- package/src/wgpuRenderTexture.test.ts +165 -0
- package/src/wgpuRenderTexturePool.test.ts +146 -0
- package/src/wgpuShaderBinding.test.ts +6 -6
- package/src/wgpuSurface.test.ts +95 -3
- package/src/wgpuTextureResolver.test.ts +209 -0
- package/src/wgpuTextureUpload.test.ts +28 -13
package/src/wgpuSurface.test.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import type { WgpuRenderState } from '@flighthq/types/contract';
|
|
1
2
|
import { beforeAll, describe, expect, it } from 'vitest';
|
|
2
3
|
|
|
3
4
|
import { getWgpuRenderStateRuntime } from './wgpuRenderState';
|
|
4
5
|
import {
|
|
5
6
|
acquireWgpuFrameCaptureTexture,
|
|
6
|
-
|
|
7
|
+
createBitmapFromWgpuRenderState,
|
|
7
8
|
enableWgpuFrameCapture,
|
|
8
9
|
encodeWgpuFrameCapture,
|
|
9
10
|
} from './wgpuSurface';
|
|
@@ -22,13 +23,62 @@ describe('acquireWgpuFrameCaptureTexture', () => {
|
|
|
22
23
|
});
|
|
23
24
|
});
|
|
24
25
|
|
|
25
|
-
describe('
|
|
26
|
+
describe('createBitmapFromWgpuRenderState', () => {
|
|
26
27
|
it('throws when frame capture was never enabled (no buffer to read)', async () => {
|
|
27
28
|
const state = await createWgpuRenderStateForTest();
|
|
28
|
-
await expect(
|
|
29
|
+
await expect(createBitmapFromWgpuRenderState(state)).rejects.toThrow(/enableWgpuFrameCapture/);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('reads the mapped buffer within the bound', async () => {
|
|
33
|
+
const state = await createWgpuRenderStateForTest();
|
|
34
|
+
installCaptureBuffer(state, () => Promise.resolve());
|
|
35
|
+
|
|
36
|
+
const bitmap = await createBitmapFromWgpuRenderState(state, 1_000);
|
|
37
|
+
|
|
38
|
+
expect(bitmap.width).toBe(1);
|
|
39
|
+
expect(bitmap.height).toBe(1);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('fails by name when the buffer never maps, instead of awaiting forever', async () => {
|
|
43
|
+
// The driver failure this bounds: mapAsync neither resolves nor rejects. Before the bound, the
|
|
44
|
+
// caller sat here until something outside it gave up and reported a stall with no location.
|
|
45
|
+
const state = await createWgpuRenderStateForTest();
|
|
46
|
+
installCaptureBuffer(state, () => new Promise<void>(() => {}));
|
|
47
|
+
|
|
48
|
+
await expect(createBitmapFromWgpuRenderState(state, 25)).rejects.toThrow(/did not map within 25ms/);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('leaves the wait unbounded when the caller passes 0', async () => {
|
|
52
|
+
const state = await createWgpuRenderStateForTest();
|
|
53
|
+
let settle = (): void => {};
|
|
54
|
+
installCaptureBuffer(state, () => new Promise<void>((resolve) => (settle = resolve)));
|
|
55
|
+
|
|
56
|
+
let done = false;
|
|
57
|
+
const pending = createBitmapFromWgpuRenderState(state, 0).then(() => (done = true));
|
|
58
|
+
await new Promise((resolve) => setTimeout(resolve, 40));
|
|
59
|
+
expect(done).toBe(false);
|
|
60
|
+
|
|
61
|
+
settle();
|
|
62
|
+
await pending;
|
|
63
|
+
expect(done).toBe(true);
|
|
29
64
|
});
|
|
30
65
|
});
|
|
31
66
|
|
|
67
|
+
// Installs a 1×1 retained capture buffer whose map step the caller controls, so the map bound can be
|
|
68
|
+
// exercised without a GPU. The mock device's buffers carry no map surface of their own.
|
|
69
|
+
function installCaptureBuffer(state: WgpuRenderState, mapAsync: () => Promise<void>): void {
|
|
70
|
+
const runtime = getWgpuRenderStateRuntime(state);
|
|
71
|
+
runtime.frameCaptureBuffer = {
|
|
72
|
+
mapAsync,
|
|
73
|
+
getMappedRange: () => new ArrayBuffer(256),
|
|
74
|
+
unmap: () => {},
|
|
75
|
+
destroy: () => {},
|
|
76
|
+
} as unknown as GPUBuffer;
|
|
77
|
+
runtime.frameCaptureWidth = 1;
|
|
78
|
+
runtime.frameCaptureHeight = 1;
|
|
79
|
+
runtime.frameCaptureBytesPerRow = 256;
|
|
80
|
+
}
|
|
81
|
+
|
|
32
82
|
describe('enableWgpuFrameCapture', () => {
|
|
33
83
|
it('sets the capture flag on the render state runtime', async () => {
|
|
34
84
|
const state = await createWgpuRenderStateForTest();
|
|
@@ -46,4 +96,46 @@ describe('encodeWgpuFrameCapture', () => {
|
|
|
46
96
|
encodeWgpuFrameCapture(state, encoder);
|
|
47
97
|
expect(copied).toBe(false);
|
|
48
98
|
});
|
|
99
|
+
|
|
100
|
+
it('skips the copy while a readback holds the buffer, and resumes once it lets go', async () => {
|
|
101
|
+
// The writer/reader handshake over the single retained buffer: an animating scene must not keep
|
|
102
|
+
// enqueueing copies into a buffer a readback has mapped or is waiting to map.
|
|
103
|
+
const state = await createWgpuRenderStateForTest();
|
|
104
|
+
enableWgpuFrameCapture(state);
|
|
105
|
+
acquireWgpuFrameCaptureTexture(state);
|
|
106
|
+
const runtime = getWgpuRenderStateRuntime(state);
|
|
107
|
+
let mapState: GPUBufferMapState = 'unmapped';
|
|
108
|
+
installCaptureBuffer(state, () => Promise.resolve());
|
|
109
|
+
Object.defineProperty(runtime.frameCaptureBuffer, 'mapState', { get: () => mapState });
|
|
110
|
+
|
|
111
|
+
let copies = 0;
|
|
112
|
+
const encoder = { copyTextureToBuffer: () => copies++ } as unknown as GPUCommandEncoder;
|
|
113
|
+
|
|
114
|
+
mapState = 'pending';
|
|
115
|
+
encodeWgpuFrameCapture(state, encoder);
|
|
116
|
+
mapState = 'mapped';
|
|
117
|
+
encodeWgpuFrameCapture(state, encoder);
|
|
118
|
+
expect(copies).toBe(0);
|
|
119
|
+
|
|
120
|
+
mapState = 'unmapped';
|
|
121
|
+
encodeWgpuFrameCapture(state, encoder);
|
|
122
|
+
expect(copies).toBe(1);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('still copies when the implementation reports no map state at all', async () => {
|
|
126
|
+
// Fails safe in the direction that keeps capturing: an undefined mapState must not read as "busy"
|
|
127
|
+
// and silence every copy for the rest of the run.
|
|
128
|
+
const state = await createWgpuRenderStateForTest();
|
|
129
|
+
enableWgpuFrameCapture(state);
|
|
130
|
+
acquireWgpuFrameCaptureTexture(state);
|
|
131
|
+
installCaptureBuffer(state, () => Promise.resolve());
|
|
132
|
+
Object.defineProperty(getWgpuRenderStateRuntime(state).frameCaptureBuffer, 'mapState', {
|
|
133
|
+
get: () => undefined,
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
let copies = 0;
|
|
137
|
+
encodeWgpuFrameCapture(state, { copyTextureToBuffer: () => copies++ } as unknown as GPUCommandEncoder);
|
|
138
|
+
|
|
139
|
+
expect(copies).toBe(1);
|
|
140
|
+
});
|
|
49
141
|
});
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { createEntity } from '@flighthq/entity/contract';
|
|
2
|
+
import type { Image, RenderTexture, Texture, TextureSource } from '@flighthq/types/contract';
|
|
3
|
+
import {
|
|
4
|
+
BitmapTextureSourceKind,
|
|
5
|
+
CompressedImageTextureSourceKind,
|
|
6
|
+
ImageTextureSourceKind,
|
|
7
|
+
RenderTargetTextureSourceKind,
|
|
8
|
+
} from '@flighthq/types/contract';
|
|
9
|
+
|
|
10
|
+
import { renderWgpuBackground, submitWgpuRenderPass } from './wgpuBackground';
|
|
11
|
+
import { getWgpuRenderStateRuntime } from './wgpuRenderState';
|
|
12
|
+
import { renderIntoWgpuRenderTexture } from './wgpuRenderTexture';
|
|
13
|
+
import { createWgpuRenderStateForTest, installWgpuMock } from './wgpuTestHelper';
|
|
14
|
+
import {
|
|
15
|
+
registerWgpuImageTextureResolver,
|
|
16
|
+
registerWgpuBitmapTextureResolver,
|
|
17
|
+
registerWgpuCompressedImageTextureResolver,
|
|
18
|
+
registerWgpuRenderTextureResolver,
|
|
19
|
+
registerStandardWgpuTextureResolvers,
|
|
20
|
+
registerWgpuTextureResolver,
|
|
21
|
+
resolveWgpuTexture,
|
|
22
|
+
} from './wgpuTextureResolver';
|
|
23
|
+
|
|
24
|
+
beforeAll(() => {
|
|
25
|
+
installWgpuMock();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
function imageResource(source: CanvasImageSource = document.createElement('canvas')): Image {
|
|
29
|
+
return {
|
|
30
|
+
height: 4,
|
|
31
|
+
alphaType: 'straight',
|
|
32
|
+
gamut: 'srgb',
|
|
33
|
+
kind: ImageTextureSourceKind,
|
|
34
|
+
source,
|
|
35
|
+
version: 0,
|
|
36
|
+
width: 4,
|
|
37
|
+
} as unknown as Image;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function textureSource(kind: string): TextureSource {
|
|
41
|
+
return {
|
|
42
|
+
height: 4,
|
|
43
|
+
kind,
|
|
44
|
+
version: 0,
|
|
45
|
+
width: 4,
|
|
46
|
+
} as unknown as TextureSource;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function textureWithImage(image: TextureSource | null): Texture {
|
|
50
|
+
return {
|
|
51
|
+
colorSpace: 'srgb',
|
|
52
|
+
flipX: false,
|
|
53
|
+
flipY: false,
|
|
54
|
+
sampler: {
|
|
55
|
+
anisotropy: 1,
|
|
56
|
+
magFilter: 'linear',
|
|
57
|
+
minFilter: 'linear',
|
|
58
|
+
mipmaps: false,
|
|
59
|
+
wrapU: 'clamp-to-edge',
|
|
60
|
+
wrapV: 'clamp-to-edge',
|
|
61
|
+
},
|
|
62
|
+
dimension: '2d',
|
|
63
|
+
source: image,
|
|
64
|
+
uvOffset: { x: 0, y: 0 },
|
|
65
|
+
uvRotation: 0,
|
|
66
|
+
uvScale: { x: 1, y: 1 },
|
|
67
|
+
version: 0,
|
|
68
|
+
} as unknown as Texture;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function renderTexture(): RenderTexture {
|
|
72
|
+
const texture = textureWithImage(null);
|
|
73
|
+
texture.colorSpace = 'linear';
|
|
74
|
+
if (texture.dimension !== '2d') throw new Error('test texture must be 2d');
|
|
75
|
+
texture.source = createEntity({
|
|
76
|
+
alphaType: 'straight' as const,
|
|
77
|
+
gamut: 'srgb' as const,
|
|
78
|
+
height: 8,
|
|
79
|
+
kind: RenderTargetTextureSourceKind,
|
|
80
|
+
version: 0,
|
|
81
|
+
width: 8,
|
|
82
|
+
});
|
|
83
|
+
return texture as RenderTexture;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
describe('registerStandardWgpuTextureResolvers', () => {
|
|
87
|
+
it('registers bitmap, image, and render texture sources without compressed images', async () => {
|
|
88
|
+
const state = await createWgpuRenderStateForTest();
|
|
89
|
+
registerStandardWgpuTextureResolvers(state);
|
|
90
|
+
expect([...getWgpuRenderStateRuntime(state).wgpuTextureResolverRegistry!.keys()]).toEqual([
|
|
91
|
+
BitmapTextureSourceKind,
|
|
92
|
+
ImageTextureSourceKind,
|
|
93
|
+
RenderTargetTextureSourceKind,
|
|
94
|
+
]);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe('registerWgpuBitmapTextureResolver', () => {
|
|
99
|
+
it('registers only the Bitmap source key', async () => {
|
|
100
|
+
const state = await createWgpuRenderStateForTest();
|
|
101
|
+
registerWgpuBitmapTextureResolver(state);
|
|
102
|
+
expect([...getWgpuRenderStateRuntime(state).wgpuTextureResolverRegistry!.keys()]).toEqual([
|
|
103
|
+
BitmapTextureSourceKind,
|
|
104
|
+
]);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
describe('registerWgpuCompressedImageTextureResolver', () => {
|
|
109
|
+
it('registers only the CompressedImage source key', async () => {
|
|
110
|
+
const state = await createWgpuRenderStateForTest();
|
|
111
|
+
registerWgpuCompressedImageTextureResolver(state);
|
|
112
|
+
expect([...getWgpuRenderStateRuntime(state).wgpuTextureResolverRegistry!.keys()]).toEqual([
|
|
113
|
+
CompressedImageTextureSourceKind,
|
|
114
|
+
]);
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
describe('registerWgpuImageTextureResolver', () => {
|
|
119
|
+
it('resolves and caches a declared still-image source', async () => {
|
|
120
|
+
const state = await createWgpuRenderStateForTest();
|
|
121
|
+
const texture = textureWithImage(imageResource());
|
|
122
|
+
registerWgpuImageTextureResolver(state);
|
|
123
|
+
|
|
124
|
+
const first = resolveWgpuTexture(state, texture);
|
|
125
|
+
expect(first).not.toBeNull();
|
|
126
|
+
expect(resolveWgpuTexture(state, texture)).toBe(first);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// The sample format comes from the PAIR (what the content is, what the destination composites in), not
|
|
130
|
+
// from an override. One sRGB texture therefore realizes two ways: decoded for a linear-working path
|
|
131
|
+
// (3D), byte-through for an encoded-working one (2D), cached apart so both can be live at once.
|
|
132
|
+
it('derives the sample format from the texture and working color spaces', async () => {
|
|
133
|
+
const state = await createWgpuRenderStateForTest();
|
|
134
|
+
const createTexture = vi.spyOn(state.device, 'createTexture');
|
|
135
|
+
const texture = textureWithImage(imageResource());
|
|
136
|
+
registerWgpuImageTextureResolver(state);
|
|
137
|
+
|
|
138
|
+
const decoded = resolveWgpuTexture(state, texture, false, 'linear');
|
|
139
|
+
expect(createTexture).toHaveBeenLastCalledWith(expect.objectContaining({ format: 'rgba8unorm-srgb' }));
|
|
140
|
+
|
|
141
|
+
const byteThrough = resolveWgpuTexture(state, texture, false, 'srgb');
|
|
142
|
+
expect(byteThrough).not.toBe(decoded);
|
|
143
|
+
expect(createTexture).toHaveBeenLastCalledWith(expect.objectContaining({ format: 'rgba8unorm' }));
|
|
144
|
+
|
|
145
|
+
// Linear content never decodes, whichever space the destination works in.
|
|
146
|
+
const linearTexture = textureWithImage(imageResource());
|
|
147
|
+
(linearTexture as { colorSpace: string }).colorSpace = 'linear';
|
|
148
|
+
resolveWgpuTexture(state, linearTexture, false, 'linear');
|
|
149
|
+
expect(createTexture).toHaveBeenLastCalledWith(expect.objectContaining({ format: 'rgba8unorm' }));
|
|
150
|
+
|
|
151
|
+
expect(resolveWgpuTexture(state, texture, false, 'linear')).toBe(decoded);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('resolves a host video through the image source kind', async () => {
|
|
155
|
+
const state = await createWgpuRenderStateForTest();
|
|
156
|
+
const source = document.createElement('video');
|
|
157
|
+
Object.defineProperties(source, {
|
|
158
|
+
readyState: { value: 4 },
|
|
159
|
+
videoHeight: { value: 8 },
|
|
160
|
+
videoWidth: { value: 8 },
|
|
161
|
+
});
|
|
162
|
+
const image = imageResource(source);
|
|
163
|
+
const texture = textureWithImage(image);
|
|
164
|
+
registerWgpuImageTextureResolver(state);
|
|
165
|
+
expect(resolveWgpuTexture(state, texture)).not.toBeNull();
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
describe('registerWgpuRenderTextureResolver', () => {
|
|
170
|
+
it('returns a render target only after it has been rendered', async () => {
|
|
171
|
+
const state = await createWgpuRenderStateForTest();
|
|
172
|
+
const texture = renderTexture();
|
|
173
|
+
registerWgpuRenderTextureResolver(state);
|
|
174
|
+
expect(resolveWgpuTexture(state, texture)).toBeNull();
|
|
175
|
+
|
|
176
|
+
renderWgpuBackground(state);
|
|
177
|
+
renderIntoWgpuRenderTexture(state, texture, () => {});
|
|
178
|
+
expect(resolveWgpuTexture(state, texture)).not.toBeNull();
|
|
179
|
+
submitWgpuRenderPass(state);
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
describe('registerWgpuTextureResolver', () => {
|
|
184
|
+
it('is state-scoped, last-write-wins by string kind, and removable', async () => {
|
|
185
|
+
const a = await createWgpuRenderStateForTest();
|
|
186
|
+
const b = await createWgpuRenderStateForTest();
|
|
187
|
+
const image = textureSource('acme.generated');
|
|
188
|
+
const texture = textureWithImage(image);
|
|
189
|
+
const first = vi.fn(() => ({ first: true }) as never);
|
|
190
|
+
const second = vi.fn(() => ({ second: true }) as never);
|
|
191
|
+
|
|
192
|
+
registerWgpuTextureResolver(a, image.kind, first);
|
|
193
|
+
expect(resolveWgpuTexture(a, texture)).toEqual({ first: true });
|
|
194
|
+
expect(resolveWgpuTexture(b, texture)).toBeNull();
|
|
195
|
+
|
|
196
|
+
registerWgpuTextureResolver(a, image.kind, second);
|
|
197
|
+
expect(resolveWgpuTexture(a, texture)).toEqual({ second: true });
|
|
198
|
+
|
|
199
|
+
registerWgpuTextureResolver(a, image.kind, null);
|
|
200
|
+
expect(resolveWgpuTexture(a, texture)).toBeNull();
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
describe('resolveWgpuTexture', () => {
|
|
205
|
+
it('returns null without an exact registered source kind', async () => {
|
|
206
|
+
const state = await createWgpuRenderStateForTest();
|
|
207
|
+
expect(resolveWgpuTexture(state, textureWithImage(null))).toBeNull();
|
|
208
|
+
});
|
|
209
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Image } from '@flighthq/types/contract';
|
|
2
2
|
|
|
3
3
|
import { uploadWgpuTextureData, uploadWgpuTextureElement, uploadWgpuTextureImageResource } from './wgpuTextureUpload';
|
|
4
4
|
|
|
@@ -28,7 +28,9 @@ describe('uploadWgpuTextureElement', () => {
|
|
|
28
28
|
it('drives copyExternalImageToTexture with the external source', () => {
|
|
29
29
|
const device = makeDevice();
|
|
30
30
|
const texture = {} as GPUTexture;
|
|
31
|
-
const source =
|
|
31
|
+
const source = document.createElement('canvas');
|
|
32
|
+
source.width = 4;
|
|
33
|
+
source.height = 4;
|
|
32
34
|
uploadWgpuTextureElement(device, texture, [0, 0, 0], 4, 4, source);
|
|
33
35
|
expect(device.queue.copyExternalImageToTexture).toHaveBeenCalledWith(
|
|
34
36
|
{ source },
|
|
@@ -36,25 +38,38 @@ describe('uploadWgpuTextureElement', () => {
|
|
|
36
38
|
[4, 4, 1],
|
|
37
39
|
);
|
|
38
40
|
});
|
|
41
|
+
|
|
42
|
+
it('skips a copy with a zero-sized extent', () => {
|
|
43
|
+
const device = makeDevice();
|
|
44
|
+
const source = document.createElement('canvas');
|
|
45
|
+
source.width = 4;
|
|
46
|
+
source.height = 4;
|
|
47
|
+
uploadWgpuTextureElement(device, {} as GPUTexture, [0, 0, 0], 0, 4, source);
|
|
48
|
+
expect(device.queue.copyExternalImageToTexture).not.toHaveBeenCalled();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('skips a copy when the browser cannot snapshot the source', () => {
|
|
52
|
+
const device = makeDevice();
|
|
53
|
+
const canvas = document.createElement('canvas');
|
|
54
|
+
canvas.width = 4;
|
|
55
|
+
canvas.height = 4;
|
|
56
|
+
vi.mocked(device.queue.copyExternalImageToTexture).mockImplementation(() => {
|
|
57
|
+
throw new TypeError('Failed to copy content from external image.');
|
|
58
|
+
});
|
|
59
|
+
expect(() => uploadWgpuTextureElement(device, {} as GPUTexture, [0, 0, 0], 4, 4, canvas)).not.toThrow();
|
|
60
|
+
});
|
|
39
61
|
});
|
|
40
62
|
|
|
41
63
|
describe('uploadWgpuTextureImageResource', () => {
|
|
42
64
|
it('takes the element path when the resource carries a source', () => {
|
|
43
65
|
const device = makeDevice();
|
|
44
66
|
const texture = {} as GPUTexture;
|
|
45
|
-
const
|
|
67
|
+
const source = document.createElement('canvas');
|
|
68
|
+
source.width = 4;
|
|
69
|
+
source.height = 4;
|
|
70
|
+
const image = { source, width: 4, height: 4 } as unknown as Image;
|
|
46
71
|
uploadWgpuTextureImageResource(device, texture, [0, 0, 0], image);
|
|
47
72
|
expect(device.queue.copyExternalImageToTexture).toHaveBeenCalledTimes(1);
|
|
48
73
|
expect(device.queue.writeTexture).not.toHaveBeenCalled();
|
|
49
74
|
});
|
|
50
|
-
|
|
51
|
-
it('takes the raw-pixel path when the resource is data-only', () => {
|
|
52
|
-
const device = makeDevice();
|
|
53
|
-
const texture = {} as GPUTexture;
|
|
54
|
-
const data = new Uint8ClampedArray(4 * 4 * 4);
|
|
55
|
-
const image = { source: null, data, width: 4, height: 4 } as ImageResource;
|
|
56
|
-
uploadWgpuTextureImageResource(device, texture, [0, 0, 0], image);
|
|
57
|
-
expect(device.queue.writeTexture).toHaveBeenCalledTimes(1);
|
|
58
|
-
expect(device.queue.copyExternalImageToTexture).not.toHaveBeenCalled();
|
|
59
|
-
});
|
|
60
75
|
});
|