@flighthq/render-wgpu 0.2.1-next.841.373388f → 0.3.0-next.1032.e2b99fc
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 +22 -0
- package/dist/contract.d.ts.map +1 -0
- package/dist/contract.js +22 -0
- package/dist/contract.js.map +1 -0
- package/dist/index.d.ts +1 -18
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -18
- package/dist/index.js.map +1 -1
- package/dist/wgpuAdapterCapabilities.d.ts +1 -1
- package/dist/wgpuAdapterCapabilities.d.ts.map +1 -1
- package/dist/wgpuBackground.d.ts +1 -1
- package/dist/wgpuBackground.d.ts.map +1 -1
- package/dist/wgpuBackground.js +2 -2
- package/dist/wgpuBackground.js.map +1 -1
- package/dist/wgpuCompressedTexture.d.ts +1 -1
- package/dist/wgpuCompressedTexture.d.ts.map +1 -1
- package/dist/wgpuCompressedTexture.js +4 -7
- package/dist/wgpuCompressedTexture.js.map +1 -1
- package/dist/wgpuDraw.d.ts +7 -5
- package/dist/wgpuDraw.d.ts.map +1 -1
- package/dist/wgpuDraw.js +76 -56
- package/dist/wgpuDraw.js.map +1 -1
- package/dist/wgpuExternalTexture.d.ts +4 -0
- package/dist/wgpuExternalTexture.d.ts.map +1 -0
- package/dist/wgpuExternalTexture.js +58 -0
- package/dist/wgpuExternalTexture.js.map +1 -0
- package/dist/wgpuFullscreenPass.d.ts +1 -1
- package/dist/wgpuFullscreenPass.d.ts.map +1 -1
- package/dist/wgpuMaterialRegistry.d.ts +1 -1
- package/dist/wgpuMaterialRegistry.d.ts.map +1 -1
- package/dist/wgpuMaterialRegistry.js +3 -3
- package/dist/wgpuMaterialRegistry.js.map +1 -1
- package/dist/wgpuMipmap.d.ts +1 -1
- package/dist/wgpuMipmap.d.ts.map +1 -1
- package/dist/wgpuRenderState.d.ts +1 -1
- package/dist/wgpuRenderState.d.ts.map +1 -1
- package/dist/wgpuRenderState.js +22 -21
- package/dist/wgpuRenderState.js.map +1 -1
- package/dist/wgpuRenderTarget.d.ts +1 -1
- package/dist/wgpuRenderTarget.d.ts.map +1 -1
- package/dist/wgpuRenderTarget.js +4 -2
- package/dist/wgpuRenderTarget.js.map +1 -1
- package/dist/wgpuRenderTargetPool.d.ts +1 -1
- package/dist/wgpuRenderTargetPool.d.ts.map +1 -1
- package/dist/wgpuRenderTargetPool.js.map +1 -1
- package/dist/wgpuRenderTexture.d.ts +10 -0
- package/dist/wgpuRenderTexture.d.ts.map +1 -0
- package/dist/wgpuRenderTexture.js +73 -0
- package/dist/wgpuRenderTexture.js.map +1 -0
- package/dist/wgpuScissor.d.ts +1 -1
- package/dist/wgpuScissor.d.ts.map +1 -1
- package/dist/wgpuShader.d.ts +3 -3
- package/dist/wgpuShader.d.ts.map +1 -1
- package/dist/wgpuShader.js +28 -28
- package/dist/wgpuShader.js.map +1 -1
- package/dist/wgpuShaderBinding.d.ts +1 -1
- package/dist/wgpuShaderBinding.d.ts.map +1 -1
- package/dist/wgpuShaderBinding.js +1 -1
- package/dist/wgpuShaderBinding.js.map +1 -1
- package/dist/wgpuShaderRegistry.d.ts +1 -1
- package/dist/wgpuShaderRegistry.d.ts.map +1 -1
- package/dist/wgpuShaderRegistry.js +1 -1
- package/dist/wgpuShaderRegistry.js.map +1 -1
- package/dist/wgpuSurface.d.ts +2 -2
- package/dist/wgpuSurface.d.ts.map +1 -1
- package/dist/wgpuSurface.js +12 -12
- package/dist/wgpuSurface.js.map +1 -1
- package/dist/wgpuTestHelper.d.ts +1 -1
- package/dist/wgpuTestHelper.d.ts.map +1 -1
- package/dist/wgpuTextureResolver.d.ts +9 -0
- package/dist/wgpuTextureResolver.d.ts.map +1 -0
- package/dist/wgpuTextureResolver.js +61 -0
- package/dist/wgpuTextureResolver.js.map +1 -0
- package/dist/wgpuTextureUpload.d.ts +1 -1
- package/dist/wgpuTextureUpload.d.ts.map +1 -1
- package/dist/wgpuTextureUpload.js +3 -9
- package/dist/wgpuTextureUpload.js.map +1 -1
- package/package.json +12 -7
- package/src/wgpuCompressedTexture.test.ts +18 -35
- package/src/wgpuDraw.test.ts +111 -65
- package/src/wgpuExternalTexture.test.ts +35 -0
- package/src/wgpuMaterialRegistry.test.ts +4 -4
- package/src/wgpuMipmap.test.ts +1 -1
- package/src/wgpuRenderTarget.test.ts +15 -1
- package/src/wgpuRenderTexture.test.ts +99 -0
- package/src/wgpuShader.test.ts +8 -8
- package/src/wgpuShaderBinding.test.ts +8 -8
- package/src/wgpuShaderRegistry.test.ts +1 -1
- package/src/wgpuSurface.test.ts +3 -3
- package/src/wgpuTextureResolver.test.ts +156 -0
- package/src/wgpuTextureUpload.test.ts +2 -12
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { CompressedImage, TextureContainer, WgpuCompressedTextureSupport } from '@flighthq/types/contract';
|
|
2
|
+
import { CompressedImageTextureBackingKind } from '@flighthq/types/contract';
|
|
2
3
|
|
|
3
4
|
import {
|
|
4
5
|
detectWgpuCompressedTextureSupport,
|
|
@@ -8,7 +9,7 @@ import {
|
|
|
8
9
|
registerWgpuCompressedTextureUpload,
|
|
9
10
|
uploadWgpuCompressedTextureContainer,
|
|
10
11
|
} from './wgpuCompressedTexture';
|
|
11
|
-
import {
|
|
12
|
+
import { bindWgpuCompressedImageTexture } from './wgpuDraw';
|
|
12
13
|
import { getWgpuRenderStateRuntime } from './wgpuRenderState';
|
|
13
14
|
import { createWgpuRenderStateForTest, installWgpuMock } from './wgpuTestHelper';
|
|
14
15
|
|
|
@@ -29,6 +30,16 @@ function container(overrides: Partial<TextureContainer> = {}): TextureContainer
|
|
|
29
30
|
};
|
|
30
31
|
}
|
|
31
32
|
|
|
33
|
+
function compressedImage(): CompressedImage {
|
|
34
|
+
return {
|
|
35
|
+
compressed: { container: container(), payload: new Uint8Array(8) },
|
|
36
|
+
height: 4,
|
|
37
|
+
kind: CompressedImageTextureBackingKind,
|
|
38
|
+
version: 1,
|
|
39
|
+
width: 4,
|
|
40
|
+
} as unknown as CompressedImage;
|
|
41
|
+
}
|
|
42
|
+
|
|
32
43
|
describe('detectWgpuCompressedTextureSupport', () => {
|
|
33
44
|
it('reads the three WebGPU compression features', async () => {
|
|
34
45
|
const state = await createWgpuRenderStateForTest();
|
|
@@ -69,7 +80,7 @@ describe('registerWgpuCompressedTextureDecoder', () => {
|
|
|
69
80
|
});
|
|
70
81
|
|
|
71
82
|
describe('registerWgpuCompressedTextureUpload', () => {
|
|
72
|
-
it('installs and clears the
|
|
83
|
+
it('installs and clears the CompressedImage upload seam', async () => {
|
|
73
84
|
const state = await createWgpuRenderStateForTest();
|
|
74
85
|
registerWgpuCompressedTextureUpload(state);
|
|
75
86
|
expect(getWgpuRenderStateRuntime(state).compressedTextureUpload).toBeTypeOf('function');
|
|
@@ -77,23 +88,14 @@ describe('registerWgpuCompressedTextureUpload', () => {
|
|
|
77
88
|
expect(getWgpuRenderStateRuntime(state).compressedTextureUpload).toBeNull();
|
|
78
89
|
});
|
|
79
90
|
|
|
80
|
-
it('lets the
|
|
91
|
+
it('lets the compressed-image binder consume its backing', async () => {
|
|
81
92
|
const state = await createWgpuRenderStateForTest();
|
|
82
93
|
registerWgpuCompressedTextureUpload(state);
|
|
83
94
|
registerWgpuCompressedTextureDecoder(state, (_format, width, height) => {
|
|
84
95
|
return new Uint8ClampedArray(width * height * 4);
|
|
85
96
|
});
|
|
86
|
-
const image = {
|
|
87
|
-
alphaType: 'straight',
|
|
88
|
-
compressed: { container: container(), payload: new Uint8Array(8) },
|
|
89
|
-
data: null,
|
|
90
|
-
height: 4,
|
|
91
|
-
source: null,
|
|
92
|
-
version: 1,
|
|
93
|
-
width: 4,
|
|
94
|
-
} as unknown as ImageResource;
|
|
95
97
|
const writeTexture = vi.spyOn(state.device.queue, 'writeTexture');
|
|
96
|
-
expect(
|
|
98
|
+
expect(bindWgpuCompressedImageTexture(state, compressedImage())?.view).toBeDefined();
|
|
97
99
|
expect(writeTexture).toHaveBeenCalledTimes(1);
|
|
98
100
|
});
|
|
99
101
|
|
|
@@ -109,18 +111,9 @@ describe('registerWgpuCompressedTextureUpload', () => {
|
|
|
109
111
|
}
|
|
110
112
|
return rgba;
|
|
111
113
|
});
|
|
112
|
-
const image = {
|
|
113
|
-
alphaType: 'straight',
|
|
114
|
-
compressed: { container: container(), payload: new Uint8Array(8) },
|
|
115
|
-
data: null,
|
|
116
|
-
height: 4,
|
|
117
|
-
source: null,
|
|
118
|
-
version: 1,
|
|
119
|
-
width: 4,
|
|
120
|
-
} as unknown as ImageResource;
|
|
121
114
|
const writeTexture = vi.spyOn(state.device.queue, 'writeTexture');
|
|
122
115
|
|
|
123
|
-
const entry =
|
|
116
|
+
const entry = bindWgpuCompressedImageTexture(state, compressedImage())!;
|
|
124
117
|
|
|
125
118
|
expect(entry.straightAlpha).toBe(false);
|
|
126
119
|
expect(Array.from(writeTexture.mock.calls[0][1] as Uint8ClampedArray).slice(0, 4)).toEqual([128, 32, 0, 128]);
|
|
@@ -130,17 +123,7 @@ describe('registerWgpuCompressedTextureUpload', () => {
|
|
|
130
123
|
const state = await createWgpuRenderStateForTest();
|
|
131
124
|
(state.device.features as Set<GPUFeatureName>).add('texture-compression-bc');
|
|
132
125
|
registerWgpuCompressedTextureUpload(state);
|
|
133
|
-
|
|
134
|
-
alphaType: 'straight',
|
|
135
|
-
compressed: { container: container(), payload: new Uint8Array(8) },
|
|
136
|
-
data: null,
|
|
137
|
-
height: 4,
|
|
138
|
-
source: null,
|
|
139
|
-
version: 1,
|
|
140
|
-
width: 4,
|
|
141
|
-
} as unknown as ImageResource;
|
|
142
|
-
|
|
143
|
-
expect(bindWgpuImageResourceTexture(state, image).straightAlpha).toBe(true);
|
|
126
|
+
expect(bindWgpuCompressedImageTexture(state, compressedImage())?.straightAlpha).toBe(true);
|
|
144
127
|
});
|
|
145
128
|
});
|
|
146
129
|
|
package/src/wgpuDraw.test.ts
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
|
-
import { getOrCreateRenderProxy2D, prepareScene2DRender } from '@flighthq/render';
|
|
2
|
-
import {
|
|
3
|
-
import type { ImageResource,
|
|
4
|
-
import {
|
|
1
|
+
import { getOrCreateRenderProxy2D, prepareScene2DRender } from '@flighthq/render/contract';
|
|
2
|
+
import { createSprite } from '@flighthq/scene2d/contract';
|
|
3
|
+
import type { Bitmap, CompressedImage, ImageResource, Texture } from '@flighthq/types/contract';
|
|
4
|
+
import {
|
|
5
|
+
BitmapTextureBackingKind,
|
|
6
|
+
BlendMode,
|
|
7
|
+
CompressedImageTextureBackingKind,
|
|
8
|
+
ImageTextureBackingKind,
|
|
9
|
+
} from '@flighthq/types/contract';
|
|
5
10
|
|
|
6
11
|
import { renderWgpuBackground, submitWgpuRenderPass } from './wgpuBackground';
|
|
12
|
+
import { registerWgpuCompressedTextureDecoder, registerWgpuCompressedTextureUpload } from './wgpuCompressedTexture';
|
|
7
13
|
import {
|
|
8
14
|
applyWgpuBlendMode,
|
|
15
|
+
bindWgpuBitmapTexture,
|
|
16
|
+
bindWgpuCompressedImageTexture,
|
|
9
17
|
bindWgpuImageResourceTexture,
|
|
10
18
|
bindWgpuTexture,
|
|
11
19
|
bindWgpuVideoTexture,
|
|
@@ -15,7 +23,7 @@ import {
|
|
|
15
23
|
drawWgpuQuadWithTransform,
|
|
16
24
|
destroyWgpuVideoTexture,
|
|
17
25
|
enableWgpuBlendModeSupport,
|
|
18
|
-
|
|
26
|
+
getWgpuRenderProxyColorScaleBias,
|
|
19
27
|
resolveWgpuSmoothingBindGroup,
|
|
20
28
|
submitWgpuQuadDraw,
|
|
21
29
|
updateWgpuTextureEntry,
|
|
@@ -28,6 +36,42 @@ beforeAll(() => {
|
|
|
28
36
|
installWgpuMock();
|
|
29
37
|
});
|
|
30
38
|
|
|
39
|
+
function bitmap(size: number, version: number): Bitmap {
|
|
40
|
+
return {
|
|
41
|
+
alphaType: 'straight',
|
|
42
|
+
colorSpace: 'srgb',
|
|
43
|
+
data: new Uint8ClampedArray(size * size * 4),
|
|
44
|
+
format: 'rgba8unorm',
|
|
45
|
+
height: size,
|
|
46
|
+
kind: BitmapTextureBackingKind,
|
|
47
|
+
version,
|
|
48
|
+
width: size,
|
|
49
|
+
} as unknown as Bitmap;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function compressedBc3Image(): CompressedImage {
|
|
53
|
+
return {
|
|
54
|
+
compressed: {
|
|
55
|
+
container: {
|
|
56
|
+
depth: 1,
|
|
57
|
+
faces: 1,
|
|
58
|
+
format: 'bc3',
|
|
59
|
+
height: 4,
|
|
60
|
+
layers: 1,
|
|
61
|
+
levels: [{ byteLength: 16, byteOffset: 0, height: 4, width: 4 }],
|
|
62
|
+
mipLevels: 1,
|
|
63
|
+
supercompression: 'None',
|
|
64
|
+
width: 4,
|
|
65
|
+
},
|
|
66
|
+
payload: new Uint8Array(16),
|
|
67
|
+
},
|
|
68
|
+
height: 4,
|
|
69
|
+
kind: CompressedImageTextureBackingKind,
|
|
70
|
+
version: 1,
|
|
71
|
+
width: 4,
|
|
72
|
+
} as unknown as CompressedImage;
|
|
73
|
+
}
|
|
74
|
+
|
|
31
75
|
describe('applyWgpuBlendMode', () => {
|
|
32
76
|
it('updates currentBlendMode on the state', async () => {
|
|
33
77
|
const state = await createWgpuRenderStateForTest();
|
|
@@ -42,27 +86,46 @@ describe('applyWgpuBlendMode', () => {
|
|
|
42
86
|
});
|
|
43
87
|
});
|
|
44
88
|
|
|
45
|
-
describe('
|
|
46
|
-
|
|
47
|
-
return {
|
|
48
|
-
source: null,
|
|
49
|
-
data: new Uint8ClampedArray(size * size * 4),
|
|
50
|
-
width: size,
|
|
51
|
-
height: size,
|
|
52
|
-
version,
|
|
53
|
-
alphaType: 'straight',
|
|
54
|
-
} as unknown as ImageResource;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
it('uploads a data-only ImageResource (a generated Surface) via writeTexture', async () => {
|
|
89
|
+
describe('bindWgpuBitmapTexture', () => {
|
|
90
|
+
it('uploads CPU-readable pixels via writeTexture', async () => {
|
|
58
91
|
const state = await createWgpuRenderStateForTest();
|
|
59
92
|
const writeTexture = vi.spyOn(state.device.queue, 'writeTexture');
|
|
60
|
-
const entry =
|
|
93
|
+
const entry = bindWgpuBitmapTexture(state, bitmap(4, 1));
|
|
61
94
|
expect(entry.texture).toBeDefined();
|
|
62
95
|
expect(writeTexture).toHaveBeenCalled();
|
|
63
96
|
});
|
|
64
97
|
|
|
65
|
-
it('
|
|
98
|
+
it('caches by resource identity and re-uploads only when the version changes', async () => {
|
|
99
|
+
const state = await createWgpuRenderStateForTest();
|
|
100
|
+
const image = bitmap(2, 1);
|
|
101
|
+
const first = bindWgpuBitmapTexture(state, image);
|
|
102
|
+
expect(bindWgpuBitmapTexture(state, image)).toBe(first);
|
|
103
|
+
const createTexture = vi.spyOn(state.device, 'createTexture');
|
|
104
|
+
image.version = 2;
|
|
105
|
+
bindWgpuBitmapTexture(state, image);
|
|
106
|
+
expect(createTexture).toHaveBeenCalled();
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
describe('bindWgpuCompressedImageTexture', () => {
|
|
111
|
+
it('routes through the registered compressed upload seam', async () => {
|
|
112
|
+
const state = await createWgpuRenderStateForTest();
|
|
113
|
+
const decode = vi.fn(() => new Uint8ClampedArray(4 * 4 * 4));
|
|
114
|
+
registerWgpuCompressedTextureUpload(state);
|
|
115
|
+
registerWgpuCompressedTextureDecoder(state, decode);
|
|
116
|
+
const entry = bindWgpuCompressedImageTexture(state, compressedBc3Image());
|
|
117
|
+
expect(entry?.texture).toBeDefined();
|
|
118
|
+
expect(decode).toHaveBeenCalledWith('bc3', 4, 4, expect.any(Uint8Array));
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('returns null when no compressed uploader is registered', async () => {
|
|
122
|
+
const state = await createWgpuRenderStateForTest();
|
|
123
|
+
expect(bindWgpuCompressedImageTexture(state, compressedBc3Image())).toBeNull();
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
describe('bindWgpuImageResourceTexture', () => {
|
|
128
|
+
it('uploads a host-backed image via copyExternalImageToTexture', async () => {
|
|
66
129
|
const state = await createWgpuRenderStateForTest();
|
|
67
130
|
const copy = vi.spyOn(state.device.queue, 'copyExternalImageToTexture');
|
|
68
131
|
const canvas = document.createElement('canvas');
|
|
@@ -70,26 +133,14 @@ describe('bindWgpuImageResourceTexture', () => {
|
|
|
70
133
|
canvas.height = 4;
|
|
71
134
|
const image = {
|
|
72
135
|
source: canvas,
|
|
73
|
-
data: null,
|
|
74
136
|
width: 4,
|
|
75
137
|
height: 4,
|
|
138
|
+
kind: ImageTextureBackingKind,
|
|
76
139
|
version: 1,
|
|
77
|
-
alphaType: 'straight',
|
|
78
140
|
} as unknown as ImageResource;
|
|
79
141
|
bindWgpuImageResourceTexture(state, image);
|
|
80
142
|
expect(copy).toHaveBeenCalled();
|
|
81
143
|
});
|
|
82
|
-
|
|
83
|
-
it('caches by resource identity and re-uploads only when the version changes', async () => {
|
|
84
|
-
const state = await createWgpuRenderStateForTest();
|
|
85
|
-
const image = dataResource(2, 1);
|
|
86
|
-
const first = bindWgpuImageResourceTexture(state, image);
|
|
87
|
-
expect(bindWgpuImageResourceTexture(state, image)).toBe(first);
|
|
88
|
-
const createTexture = vi.spyOn(state.device, 'createTexture');
|
|
89
|
-
image.version = 2;
|
|
90
|
-
bindWgpuImageResourceTexture(state, image);
|
|
91
|
-
expect(createTexture).toHaveBeenCalled();
|
|
92
|
-
});
|
|
93
144
|
});
|
|
94
145
|
|
|
95
146
|
describe('bindWgpuTexture', () => {
|
|
@@ -129,9 +180,8 @@ describe('bindWgpuTexture', () => {
|
|
|
129
180
|
});
|
|
130
181
|
|
|
131
182
|
describe('bindWgpuVideoTexture', () => {
|
|
132
|
-
function videoTexture(
|
|
183
|
+
function videoTexture(version: number, readyState = 4, width = 320, height = 240): Texture {
|
|
133
184
|
return {
|
|
134
|
-
frameId,
|
|
135
185
|
sampler: {
|
|
136
186
|
anisotropy: 1,
|
|
137
187
|
magFilter: 'linear',
|
|
@@ -140,11 +190,18 @@ describe('bindWgpuVideoTexture', () => {
|
|
|
140
190
|
wrapU: 'clamp-to-edge',
|
|
141
191
|
wrapV: 'clamp-to-edge',
|
|
142
192
|
},
|
|
143
|
-
|
|
144
|
-
|
|
193
|
+
storage: {
|
|
194
|
+
dimension: '2d',
|
|
195
|
+
image: {
|
|
196
|
+
source: { readyState, videoHeight: height, videoWidth: width } as HTMLVideoElement,
|
|
197
|
+
version,
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
version,
|
|
201
|
+
} as unknown as Texture;
|
|
145
202
|
}
|
|
146
203
|
|
|
147
|
-
it('uploads once per
|
|
204
|
+
it('uploads once per backing version and caches the texture by backing identity', async () => {
|
|
148
205
|
const state = await createWgpuRenderStateForTest();
|
|
149
206
|
const copy = vi.spyOn(state.device.queue, 'copyExternalImageToTexture');
|
|
150
207
|
const video = videoTexture(1);
|
|
@@ -153,7 +210,7 @@ describe('bindWgpuVideoTexture', () => {
|
|
|
153
210
|
expect(copy).toHaveBeenCalledTimes(1);
|
|
154
211
|
expect(bindWgpuVideoTexture(state, video)).toBe(first);
|
|
155
212
|
expect(copy).toHaveBeenCalledTimes(1);
|
|
156
|
-
video.
|
|
213
|
+
video.storage.image!.version = 2;
|
|
157
214
|
expect(bindWgpuVideoTexture(state, video)).toBe(first);
|
|
158
215
|
expect(copy).toHaveBeenCalledTimes(2);
|
|
159
216
|
});
|
|
@@ -162,7 +219,7 @@ describe('bindWgpuVideoTexture', () => {
|
|
|
162
219
|
const state = await createWgpuRenderStateForTest();
|
|
163
220
|
const video = videoTexture(1, 1, 0, 0);
|
|
164
221
|
expect(bindWgpuVideoTexture(state, video)).toBeNull();
|
|
165
|
-
const element = video.source
|
|
222
|
+
const element = (video.storage.image as ImageResource).source as HTMLVideoElement;
|
|
166
223
|
Object.assign(element, { readyState: 4, videoHeight: 120, videoWidth: 160 });
|
|
167
224
|
const first = bindWgpuVideoTexture(state, video)!;
|
|
168
225
|
const destroy = vi.spyOn(first.texture, 'destroy');
|
|
@@ -214,7 +271,7 @@ describe('drawWgpuQuad', () => {
|
|
|
214
271
|
it('does not throw when render pass is open', async () => {
|
|
215
272
|
const state = await createWgpuRenderStateForTest();
|
|
216
273
|
renderWgpuBackground(state);
|
|
217
|
-
const bitmap =
|
|
274
|
+
const bitmap = createSprite();
|
|
218
275
|
prepareScene2DRender(state, bitmap);
|
|
219
276
|
const renderProxy = getOrCreateRenderProxy2D(state, bitmap);
|
|
220
277
|
const canvas = document.createElement('canvas');
|
|
@@ -230,7 +287,7 @@ describe('drawWgpuQuadWithTransform', () => {
|
|
|
230
287
|
it('does not throw when render pass is open', async () => {
|
|
231
288
|
const state = await createWgpuRenderStateForTest();
|
|
232
289
|
renderWgpuBackground(state);
|
|
233
|
-
const bitmap =
|
|
290
|
+
const bitmap = createSprite();
|
|
234
291
|
prepareScene2DRender(state, bitmap);
|
|
235
292
|
const renderProxy = getOrCreateRenderProxy2D(state, bitmap);
|
|
236
293
|
const canvas = document.createElement('canvas');
|
|
@@ -251,33 +308,22 @@ describe('enableWgpuBlendModeSupport', () => {
|
|
|
251
308
|
});
|
|
252
309
|
});
|
|
253
310
|
|
|
254
|
-
describe('
|
|
255
|
-
it('returns null when the node has no color
|
|
256
|
-
expect(
|
|
257
|
-
expect(
|
|
311
|
+
describe('getWgpuRenderProxyColorScaleBias', () => {
|
|
312
|
+
it('returns null when the node has no color adjustment', () => {
|
|
313
|
+
expect(getWgpuRenderProxyColorScaleBias({} as never)).toBeNull();
|
|
314
|
+
expect(getWgpuRenderProxyColorScaleBias({ colorScaleBias: null } as never)).toBeNull();
|
|
258
315
|
});
|
|
259
316
|
|
|
260
|
-
it('returns the resolved node-level color
|
|
261
|
-
const
|
|
262
|
-
expect(
|
|
317
|
+
it('returns the resolved node-level color adjustment trait', () => {
|
|
318
|
+
const colorScaleBias = { redScale: 0.5 };
|
|
319
|
+
expect(getWgpuRenderProxyColorScaleBias({ colorScaleBias } as never)).toBe(colorScaleBias);
|
|
263
320
|
});
|
|
264
321
|
});
|
|
265
322
|
|
|
266
323
|
describe('resolveWgpuSmoothingBindGroup', () => {
|
|
267
|
-
function dataResource(size: number, version: number): ImageResource {
|
|
268
|
-
return {
|
|
269
|
-
source: null,
|
|
270
|
-
data: new Uint8ClampedArray(size * size * 4),
|
|
271
|
-
width: size,
|
|
272
|
-
height: size,
|
|
273
|
-
version,
|
|
274
|
-
alphaType: 'straight',
|
|
275
|
-
} as unknown as ImageResource;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
324
|
it('returns the default bind group for a null smoothing (no variant built)', async () => {
|
|
279
325
|
const state = await createWgpuRenderStateForTest();
|
|
280
|
-
const entry =
|
|
326
|
+
const entry = bindWgpuBitmapTexture(state, bitmap(4, 1));
|
|
281
327
|
expect(resolveWgpuSmoothingBindGroup(state, entry, null)).toBe(entry.bindGroup);
|
|
282
328
|
expect(entry.bindGroupLinear).toBeUndefined();
|
|
283
329
|
expect(entry.bindGroupNearest).toBeUndefined();
|
|
@@ -285,7 +331,7 @@ describe('resolveWgpuSmoothingBindGroup', () => {
|
|
|
285
331
|
|
|
286
332
|
it('builds and caches distinct LINEAR and NEAREST variants for true/false', async () => {
|
|
287
333
|
const state = await createWgpuRenderStateForTest();
|
|
288
|
-
const entry =
|
|
334
|
+
const entry = bindWgpuBitmapTexture(state, bitmap(4, 1));
|
|
289
335
|
const createBindGroup = vi.spyOn(state.device, 'createBindGroup');
|
|
290
336
|
|
|
291
337
|
resolveWgpuSmoothingBindGroup(state, entry, true);
|
|
@@ -304,13 +350,13 @@ describe('resolveWgpuSmoothingBindGroup', () => {
|
|
|
304
350
|
|
|
305
351
|
it('drops the cached variants when the texture re-uploads (version bump)', async () => {
|
|
306
352
|
const state = await createWgpuRenderStateForTest();
|
|
307
|
-
const image =
|
|
308
|
-
const entry =
|
|
353
|
+
const image = bitmap(4, 1);
|
|
354
|
+
const entry = bindWgpuBitmapTexture(state, image);
|
|
309
355
|
resolveWgpuSmoothingBindGroup(state, entry, true);
|
|
310
356
|
resolveWgpuSmoothingBindGroup(state, entry, false);
|
|
311
357
|
expect(entry.bindGroupLinear).toBeDefined();
|
|
312
358
|
image.version = 2;
|
|
313
|
-
|
|
359
|
+
bindWgpuBitmapTexture(state, image);
|
|
314
360
|
expect(entry.bindGroupLinear).toBeUndefined();
|
|
315
361
|
expect(entry.bindGroupNearest).toBeUndefined();
|
|
316
362
|
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { getTextureBackingKind } from '@flighthq/texture/contract';
|
|
2
|
+
import { ExternalTextureBackingKind } from '@flighthq/types/contract';
|
|
3
|
+
|
|
4
|
+
import { createExternalWgpuTexture, disposeExternalWgpuTexture } from './wgpuExternalTexture';
|
|
5
|
+
import { createWgpuRenderStateForTest, installWgpuMock } from './wgpuTestHelper';
|
|
6
|
+
import { resolveWgpuTexture } from './wgpuTextureResolver';
|
|
7
|
+
|
|
8
|
+
beforeAll(() => {
|
|
9
|
+
installWgpuMock();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
describe('createExternalWgpuTexture', () => {
|
|
13
|
+
it('resolves a borrowed handle and forgets it without destroying the allocation', async () => {
|
|
14
|
+
const state = await createWgpuRenderStateForTest();
|
|
15
|
+
const destroy = vi.fn();
|
|
16
|
+
const handle = { createView: vi.fn(() => ({})), destroy } as unknown as GPUTexture;
|
|
17
|
+
const texture = createExternalWgpuTexture(state, handle, { height: 16, width: 32 });
|
|
18
|
+
|
|
19
|
+
expect(getTextureBackingKind(texture)).toBe(ExternalTextureBackingKind);
|
|
20
|
+
expect(resolveWgpuTexture(state, texture)?.texture).toBe(handle);
|
|
21
|
+
expect(disposeExternalWgpuTexture(state, texture)).toBe(true);
|
|
22
|
+
expect(resolveWgpuTexture(state, texture)).toBeNull();
|
|
23
|
+
expect(destroy).not.toHaveBeenCalled();
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe('disposeExternalWgpuTexture', () => {
|
|
28
|
+
it('returns false after the borrowed handle has already been forgotten', async () => {
|
|
29
|
+
const state = await createWgpuRenderStateForTest();
|
|
30
|
+
const handle = { createView: () => ({}) } as unknown as GPUTexture;
|
|
31
|
+
const texture = createExternalWgpuTexture(state, handle, { height: 1, width: 1 });
|
|
32
|
+
expect(disposeExternalWgpuTexture(state, texture)).toBe(true);
|
|
33
|
+
expect(disposeExternalWgpuTexture(state, texture)).toBe(false);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { Material, WgpuMaterialRenderer, WgpuRenderState } from '@flighthq/types';
|
|
2
|
-
import {
|
|
1
|
+
import type { Material, WgpuMaterialRenderer, WgpuRenderState } from '@flighthq/types/contract';
|
|
2
|
+
import { StandardMaterialKind, EntityRuntimeKey } from '@flighthq/types/contract';
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
getWgpuMaterialRenderer,
|
|
@@ -47,9 +47,9 @@ describe('resolveWgpuMaterialRenderer', () => {
|
|
|
47
47
|
expect(resolveWgpuMaterialRenderer(state, makeMaterial(TestKind))).toBe(testRenderer);
|
|
48
48
|
});
|
|
49
49
|
|
|
50
|
-
it('falls back to the renderer registered for
|
|
50
|
+
it('falls back to the renderer registered for StandardMaterialKind', () => {
|
|
51
51
|
const state = makeState();
|
|
52
|
-
registerWgpuMaterialRenderer(state,
|
|
52
|
+
registerWgpuMaterialRenderer(state, StandardMaterialKind, testRenderer);
|
|
53
53
|
expect(resolveWgpuMaterialRenderer(state, makeMaterial('Other'))).toBe(testRenderer);
|
|
54
54
|
expect(resolveWgpuMaterialRenderer(state, null)).toBe(testRenderer);
|
|
55
55
|
});
|
package/src/wgpuMipmap.test.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createMatrix } from '@flighthq/geometry';
|
|
1
|
+
import { createMatrix } from '@flighthq/geometry/contract';
|
|
2
2
|
|
|
3
3
|
import { renderWgpuBackground, submitWgpuRenderPass } from './wgpuBackground';
|
|
4
4
|
import { getWgpuRenderStateRuntime } from './wgpuRenderState';
|
|
@@ -155,6 +155,20 @@ describe('endWgpuRenderPass', () => {
|
|
|
155
155
|
});
|
|
156
156
|
|
|
157
157
|
describe('resizeWgpuRenderTarget', () => {
|
|
158
|
+
it('preserves the allocation when dimensions are unchanged', async () => {
|
|
159
|
+
const state = await createWgpuRenderStateForTest();
|
|
160
|
+
const target = createWgpuRenderTarget(state, 64, 64);
|
|
161
|
+
const texture = target.texture;
|
|
162
|
+
const depthStencilTexture = target.depthStencilTexture;
|
|
163
|
+
const bindGroup = target.bindGroup;
|
|
164
|
+
|
|
165
|
+
resizeWgpuRenderTarget(state, target, 64, 64);
|
|
166
|
+
|
|
167
|
+
expect(target.texture).toBe(texture);
|
|
168
|
+
expect(target.depthStencilTexture).toBe(depthStencilTexture);
|
|
169
|
+
expect(target.bindGroup).toBe(bindGroup);
|
|
170
|
+
});
|
|
171
|
+
|
|
158
172
|
it('updates width, height, and bind group', async () => {
|
|
159
173
|
const state = await createWgpuRenderStateForTest();
|
|
160
174
|
const target = createWgpuRenderTarget(state, 64, 64);
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import type { RenderTexture } from '@flighthq/types/contract';
|
|
2
|
+
import { RenderTextureBackingKind } from '@flighthq/types/contract';
|
|
3
|
+
|
|
4
|
+
import { renderWgpuBackground, submitWgpuRenderPass } from './wgpuBackground';
|
|
5
|
+
import { getWgpuRenderStateRuntime } from './wgpuRenderState';
|
|
6
|
+
import {
|
|
7
|
+
bindWgpuRenderTexture,
|
|
8
|
+
destroyWgpuRenderTexture,
|
|
9
|
+
isWgpuRenderTextureReady,
|
|
10
|
+
renderIntoWgpuRenderTexture,
|
|
11
|
+
} from './wgpuRenderTexture';
|
|
12
|
+
import { createWgpuRenderStateForTest, installWgpuMock } from './wgpuTestHelper';
|
|
13
|
+
|
|
14
|
+
beforeAll(() => {
|
|
15
|
+
installWgpuMock();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
function texture(): RenderTexture {
|
|
19
|
+
return {
|
|
20
|
+
colorSpace: 'linear',
|
|
21
|
+
flipX: false,
|
|
22
|
+
flipY: false,
|
|
23
|
+
sampler: {
|
|
24
|
+
anisotropy: 1,
|
|
25
|
+
magFilter: 'linear',
|
|
26
|
+
minFilter: 'linear',
|
|
27
|
+
mipmaps: false,
|
|
28
|
+
wrapU: 'clamp-to-edge',
|
|
29
|
+
wrapV: 'clamp-to-edge',
|
|
30
|
+
},
|
|
31
|
+
storage: {
|
|
32
|
+
dimension: '2d',
|
|
33
|
+
image: null,
|
|
34
|
+
target: { height: 8, kind: RenderTextureBackingKind, width: 8 },
|
|
35
|
+
},
|
|
36
|
+
uvOffset: { x: 0, y: 0 },
|
|
37
|
+
uvRotation: 0,
|
|
38
|
+
uvScale: { x: 1, y: 1 },
|
|
39
|
+
version: 0,
|
|
40
|
+
} as RenderTexture;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
describe('bindWgpuRenderTexture', () => {
|
|
44
|
+
it('returns null before render and the state-owned target after render', async () => {
|
|
45
|
+
const state = await createWgpuRenderStateForTest();
|
|
46
|
+
const renderTexture = texture();
|
|
47
|
+
expect(bindWgpuRenderTexture(state, renderTexture)).toBeNull();
|
|
48
|
+
|
|
49
|
+
renderWgpuBackground(state);
|
|
50
|
+
renderIntoWgpuRenderTexture(state, renderTexture, () => {});
|
|
51
|
+
expect(bindWgpuRenderTexture(state, renderTexture)).not.toBeNull();
|
|
52
|
+
submitWgpuRenderPass(state);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
describe('destroyWgpuRenderTexture', () => {
|
|
57
|
+
it('destroys and removes the hidden target', async () => {
|
|
58
|
+
const state = await createWgpuRenderStateForTest();
|
|
59
|
+
const renderTexture = texture();
|
|
60
|
+
renderWgpuBackground(state);
|
|
61
|
+
renderIntoWgpuRenderTexture(state, renderTexture, () => {});
|
|
62
|
+
const entry = getWgpuRenderStateRuntime(state).wgpuRenderTextureCache!.get(renderTexture)!;
|
|
63
|
+
const destroy = vi.spyOn(entry.target.texture, 'destroy');
|
|
64
|
+
|
|
65
|
+
destroyWgpuRenderTexture(state, renderTexture);
|
|
66
|
+
expect(destroy).toHaveBeenCalled();
|
|
67
|
+
expect(bindWgpuRenderTexture(state, renderTexture)).toBeNull();
|
|
68
|
+
submitWgpuRenderPass(state);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
describe('isWgpuRenderTextureReady', () => {
|
|
73
|
+
it('tracks the successful rendered-content transition', async () => {
|
|
74
|
+
const state = await createWgpuRenderStateForTest();
|
|
75
|
+
const renderTexture = texture();
|
|
76
|
+
expect(isWgpuRenderTextureReady(state, renderTexture)).toBe(false);
|
|
77
|
+
renderWgpuBackground(state);
|
|
78
|
+
renderIntoWgpuRenderTexture(state, renderTexture, () => {});
|
|
79
|
+
expect(isWgpuRenderTextureReady(state, renderTexture)).toBe(true);
|
|
80
|
+
submitWgpuRenderPass(state);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe('renderIntoWgpuRenderTexture', () => {
|
|
85
|
+
it('restores the enclosing pass and bumps version after success', async () => {
|
|
86
|
+
const state = await createWgpuRenderStateForTest();
|
|
87
|
+
const renderTexture = texture();
|
|
88
|
+
renderWgpuBackground(state);
|
|
89
|
+
const enclosingPass = getWgpuRenderStateRuntime(state).renderPass;
|
|
90
|
+
|
|
91
|
+
renderIntoWgpuRenderTexture(state, renderTexture, () => {});
|
|
92
|
+
|
|
93
|
+
expect(renderTexture.version).toBe(1);
|
|
94
|
+
expect(renderTexture.colorSpace).toBe('linear');
|
|
95
|
+
expect(getWgpuRenderStateRuntime(state).renderPass).not.toBeNull();
|
|
96
|
+
expect(getWgpuRenderStateRuntime(state).renderPass).not.toBe(enclosingPass);
|
|
97
|
+
submitWgpuRenderPass(state);
|
|
98
|
+
});
|
|
99
|
+
});
|
package/src/wgpuShader.test.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AdvancedBlendMode, BlendMode } from '@flighthq/types';
|
|
1
|
+
import { AdvancedBlendMode, BlendMode } from '@flighthq/types/contract';
|
|
2
2
|
|
|
3
3
|
import { renderWgpuBackground } from './wgpuBackground';
|
|
4
4
|
import { getWgpuRenderStateRuntime } from './wgpuRenderState';
|
|
@@ -159,7 +159,7 @@ describe('writeWgpuMatrixOnlyUniforms', () => {
|
|
|
159
159
|
const runtime = getWgpuRenderStateRuntime(state);
|
|
160
160
|
renderWgpuBackground(state);
|
|
161
161
|
const before = runtime.uniformOffset;
|
|
162
|
-
const fakeNode = { alpha: 1,
|
|
162
|
+
const fakeNode = { alpha: 1, useColorScaleBias: false, colorScaleBias: null };
|
|
163
163
|
const t = { a: 1, b: 0, c: 0, d: 1, tx: 0, ty: 0 };
|
|
164
164
|
writeWgpuMatrixOnlyUniforms(state, fakeNode as never, t, 0, 0, 10, 10, 0, 0, 1, 1);
|
|
165
165
|
expect(runtime.uniformOffset).toBe(before + runtime.uniformStride);
|
|
@@ -174,8 +174,8 @@ describe('writeWgpuQuadUniforms', () => {
|
|
|
174
174
|
const before = runtime.uniformOffset;
|
|
175
175
|
const fakeNode = {
|
|
176
176
|
alpha: 1,
|
|
177
|
-
|
|
178
|
-
|
|
177
|
+
useColorScaleBias: false,
|
|
178
|
+
colorScaleBias: null,
|
|
179
179
|
transform2D: { a: 1, b: 0, c: 0, d: 1, tx: 0, ty: 0 },
|
|
180
180
|
};
|
|
181
181
|
writeWgpuQuadUniforms(state, fakeNode, null, 0, 0, 100, 100, 0, 0, 1, 1);
|
|
@@ -188,8 +188,8 @@ describe('writeWgpuQuadUniforms', () => {
|
|
|
188
188
|
renderWgpuBackground(state);
|
|
189
189
|
const fakeNode = {
|
|
190
190
|
alpha: 0.5,
|
|
191
|
-
|
|
192
|
-
|
|
191
|
+
useColorScaleBias: false,
|
|
192
|
+
colorScaleBias: null,
|
|
193
193
|
transform2D: { a: 1, b: 0, c: 0, d: 1, tx: 0, ty: 0 },
|
|
194
194
|
};
|
|
195
195
|
const offset = writeWgpuQuadUniforms(state, fakeNode, null, 10, 20, 30, 40, 0.1, 0.2, 0.9, 0.8);
|
|
@@ -208,8 +208,8 @@ describe('writeWgpuQuadUniforms', () => {
|
|
|
208
208
|
renderWgpuBackground(state);
|
|
209
209
|
const fakeNode = {
|
|
210
210
|
alpha: 1,
|
|
211
|
-
|
|
212
|
-
|
|
211
|
+
useColorScaleBias: false,
|
|
212
|
+
colorScaleBias: null,
|
|
213
213
|
transform2D: { a: 1, b: 0, c: 0, d: 1, tx: 0, ty: 0 },
|
|
214
214
|
};
|
|
215
215
|
|