@flighthq/render-wgpu 0.3.0 → 0.3.1-next.1041.35b950c
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/explainWgpuTextureResolution.d.ts.map +1 -1
- package/dist/explainWgpuTextureResolution.js +2 -1
- package/dist/explainWgpuTextureResolution.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/wgpuBackground.d.ts +2 -0
- package/dist/wgpuBackground.d.ts.map +1 -1
- package/dist/wgpuBackground.js +28 -0
- package/dist/wgpuBackground.js.map +1 -1
- package/dist/wgpuCompressedTexture.d.ts.map +1 -1
- package/dist/wgpuCompressedTexture.js +13 -2
- package/dist/wgpuCompressedTexture.js.map +1 -1
- package/dist/wgpuDraw.d.ts.map +1 -1
- package/dist/wgpuDraw.js +12 -3
- package/dist/wgpuDraw.js.map +1 -1
- package/dist/wgpuMaterialRegistry.d.ts.map +1 -1
- package/dist/wgpuMaterialRegistry.js +13 -9
- package/dist/wgpuMaterialRegistry.js.map +1 -1
- package/dist/wgpuRenderState.d.ts +4 -1
- package/dist/wgpuRenderState.d.ts.map +1 -1
- package/dist/wgpuRenderState.js +66 -33
- package/dist/wgpuRenderState.js.map +1 -1
- package/dist/wgpuTestHelper.d.ts.map +1 -1
- package/dist/wgpuTestHelper.js +110 -7
- package/dist/wgpuTestHelper.js.map +1 -1
- package/dist/wgpuTextureResolver.d.ts.map +1 -1
- package/dist/wgpuTextureResolver.js +12 -11
- package/dist/wgpuTextureResolver.js.map +1 -1
- package/package.json +15 -11
- package/src/wgpuBackground.test.ts +34 -1
- package/src/wgpuCompressedTexture.test.ts +19 -7
- package/src/wgpuDraw.test.ts +16 -0
- package/src/wgpuMaterialRegistry.test.ts +16 -1
- package/src/wgpuRenderState.test.ts +340 -22
- package/src/wgpuTestHelper.test.ts +64 -0
- package/src/wgpuTextureResolver.test.ts +10 -7
package/src/wgpuDraw.test.ts
CHANGED
|
@@ -111,6 +111,22 @@ describe('bindWgpuBitmapTexture', () => {
|
|
|
111
111
|
bindWgpuBitmapTexture(state, image);
|
|
112
112
|
expect(createTexture).toHaveBeenCalled();
|
|
113
113
|
});
|
|
114
|
+
|
|
115
|
+
it('RETIRES the outgoing texture on a version bump rather than destroying it', async () => {
|
|
116
|
+
const state = await createWgpuRenderStateForTest();
|
|
117
|
+
const image = bitmap(2, 1);
|
|
118
|
+
const outgoing = bindWgpuBitmapTexture(state, image).texture;
|
|
119
|
+
|
|
120
|
+
image.version = 2;
|
|
121
|
+
bindWgpuBitmapTexture(state, image);
|
|
122
|
+
|
|
123
|
+
// A version bump rewrites the cached entry IN PLACE, so a bind group recorded earlier in the frame
|
|
124
|
+
// still points at `outgoing`. The frame's submit is deferred; destroying a texture a recorded command
|
|
125
|
+
// buffer references fails that submit and blanks the whole frame, so it has to wait for post-submit.
|
|
126
|
+
// This guards a hazard with no reproduction of its own — without the test there is nothing to stop
|
|
127
|
+
// the deferral being read as pointless indirection and reverted.
|
|
128
|
+
expect(getWgpuRenderStateRuntime(state).retiredTextures).toContain(outgoing);
|
|
129
|
+
});
|
|
114
130
|
});
|
|
115
131
|
|
|
116
132
|
describe('bindWgpuCompressedImageTexture', () => {
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
registerWgpuMaterialRenderer,
|
|
9
9
|
resolveWgpuMaterialRenderer,
|
|
10
10
|
} from './wgpuMaterialRegistry';
|
|
11
|
-
import { createWgpuRenderStateRuntime } from './wgpuRenderState';
|
|
11
|
+
import { createWgpuRenderStateRuntime, getWgpuRenderStateRuntime } from './wgpuRenderState';
|
|
12
12
|
|
|
13
13
|
const TestKind = 'TestMaterial';
|
|
14
14
|
const testRenderer: WgpuMaterialRenderer = { instanceFloatCount: 0, getShaderModule: () => ({}) as GPUShaderModule };
|
|
@@ -33,8 +33,23 @@ describe('getWgpuMaterialRenderer', () => {
|
|
|
33
33
|
describe('registerWgpuMaterialRenderer', () => {
|
|
34
34
|
it('registers a renderer retrievable by kind', () => {
|
|
35
35
|
const state = makeState();
|
|
36
|
+
const before = getWgpuRenderStateRuntime(state).registries.materialRenderers;
|
|
36
37
|
registerWgpuMaterialRenderer(state, TestKind, testRenderer);
|
|
37
38
|
expect(getWgpuMaterialRenderer(state, TestKind)).toBe(testRenderer);
|
|
39
|
+
expect(getWgpuRenderStateRuntime(state).registries.materialRenderers).not.toBe(before);
|
|
40
|
+
expect(before.entries.size).toBe(0);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('is last-write-wins without mutating the earlier snapshot', () => {
|
|
44
|
+
const state = makeState();
|
|
45
|
+
const replacement: WgpuMaterialRenderer = { instanceFloatCount: 0, getShaderModule: () => ({}) as GPUShaderModule };
|
|
46
|
+
registerWgpuMaterialRenderer(state, TestKind, testRenderer);
|
|
47
|
+
const before = getWgpuRenderStateRuntime(state).registries.materialRenderers;
|
|
48
|
+
|
|
49
|
+
registerWgpuMaterialRenderer(state, TestKind, replacement);
|
|
50
|
+
|
|
51
|
+
expect(getWgpuMaterialRenderer(state, TestKind)).toBe(replacement);
|
|
52
|
+
expect(before.entries.get(TestKind)).toEqual({ state: 'bound', value: testRenderer });
|
|
38
53
|
});
|
|
39
54
|
});
|
|
40
55
|
|
|
@@ -1,22 +1,43 @@
|
|
|
1
1
|
import { createEntity } from '@flighthq/entity/contract';
|
|
2
|
+
import {
|
|
3
|
+
createKeyedTable,
|
|
4
|
+
getRegistryTableEntry,
|
|
5
|
+
hasRegistryTableEntry,
|
|
6
|
+
withRegistryTableEntry,
|
|
7
|
+
} from '@flighthq/registry/contract';
|
|
2
8
|
import {
|
|
3
9
|
copyAllRenderersFromRenderState,
|
|
10
|
+
enableColorAdjustmentGuards,
|
|
11
|
+
enableColorAdjustments,
|
|
12
|
+
getColorAdjustmentUnsupportedGuard,
|
|
4
13
|
getRenderStateRuntime,
|
|
5
14
|
prepareScene2DRender,
|
|
6
15
|
registerRenderer,
|
|
7
16
|
} from '@flighthq/render/contract';
|
|
8
17
|
import { createDisplayObject } from '@flighthq/scene2d/contract';
|
|
18
|
+
import type {
|
|
19
|
+
RenderEffectPaddingResolver,
|
|
20
|
+
RenderRootGuard,
|
|
21
|
+
RenderState,
|
|
22
|
+
WgpuColorAdjustmentMaterialFeature,
|
|
23
|
+
WgpuColorAdjustmentMaterialFeatureGuard,
|
|
24
|
+
} from '@flighthq/types/contract';
|
|
25
|
+
import { RegistryEntryState } from '@flighthq/types/contract';
|
|
9
26
|
|
|
10
27
|
import { beginWgpuFrame } from './wgpuBackground';
|
|
28
|
+
import { registerWgpuCompressedTextureDecoder, registerWgpuCompressedTextureUpload } from './wgpuCompressedTexture';
|
|
11
29
|
import { registerWgpuMaterialRenderer } from './wgpuMaterialRegistry';
|
|
12
30
|
import {
|
|
13
31
|
copyWgpuRenderStateRegistrations,
|
|
14
32
|
createWgpuOffscreenRenderState,
|
|
15
33
|
createWgpuRenderStateRuntime,
|
|
16
34
|
destroyWgpuRenderState,
|
|
35
|
+
getWgpuColorAdjustmentMaterialFeature,
|
|
36
|
+
getWgpuColorAdjustmentMaterialFeatureGuard,
|
|
17
37
|
getWgpuRenderStateRuntime,
|
|
18
38
|
getWgpuSampler,
|
|
19
39
|
isWgpuSupported,
|
|
40
|
+
resolveWgpuApplyBlendMode,
|
|
20
41
|
} from './wgpuRenderState';
|
|
21
42
|
import { createWgpuRenderStateForTest, installWgpuMock } from './wgpuTestHelper';
|
|
22
43
|
import { registerWgpuTextureResolver } from './wgpuTextureResolver';
|
|
@@ -25,20 +46,113 @@ beforeAll(() => {
|
|
|
25
46
|
installWgpuMock();
|
|
26
47
|
});
|
|
27
48
|
|
|
49
|
+
function getPaddingResolver(state: RenderState, kind: string): RenderEffectPaddingResolver | null {
|
|
50
|
+
const table = getRenderStateRuntime(state).registries.effectPaddingResolvers;
|
|
51
|
+
return table === undefined ? null : getRegistryTableEntry(table, kind);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function registerPaddingResolver(state: RenderState, kind: string, resolver: RenderEffectPaddingResolver): void {
|
|
55
|
+
const runtime = getRenderStateRuntime(state);
|
|
56
|
+
runtime.registries.effectPaddingResolvers = withRegistryTableEntry(
|
|
57
|
+
runtime.registries.effectPaddingResolvers ??
|
|
58
|
+
createKeyedTable<RenderEffectPaddingResolver>('RenderEffectPaddingResolver', 'Zero'),
|
|
59
|
+
kind,
|
|
60
|
+
resolver,
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
28
64
|
describe('copyWgpuRenderStateRegistrations', () => {
|
|
29
65
|
it('copies late Wgpu registrations only when explicitly requested', async () => {
|
|
30
66
|
const screen = await createWgpuRenderStateForTest();
|
|
31
67
|
const offscreen = createWgpuOffscreenRenderState(screen);
|
|
68
|
+
const materialRenderer = { instanceFloatCount: 0, getShaderModule: vi.fn() } as never;
|
|
69
|
+
const offscreenMaterialRenderer = { instanceFloatCount: 0, getShaderModule: vi.fn() } as never;
|
|
70
|
+
const decoder = vi.fn(() => new Uint8ClampedArray(4));
|
|
32
71
|
const resolver = vi.fn(() => null);
|
|
72
|
+
registerWgpuCompressedTextureDecoder(screen, decoder);
|
|
73
|
+
registerWgpuCompressedTextureUpload(screen);
|
|
74
|
+
registerWgpuMaterialRenderer(screen, 'acme.LateMaterial', materialRenderer);
|
|
33
75
|
registerWgpuTextureResolver(screen, 'acme.LateTexture', resolver);
|
|
34
76
|
|
|
35
|
-
expect(
|
|
77
|
+
expect(
|
|
78
|
+
hasRegistryTableEntry(getWgpuRenderStateRuntime(offscreen).registries.materialRenderers, 'acme.LateMaterial'),
|
|
79
|
+
).toBe(false);
|
|
80
|
+
expect(
|
|
81
|
+
hasRegistryTableEntry(getWgpuRenderStateRuntime(offscreen).registries.textureResolvers, 'acme.LateTexture'),
|
|
82
|
+
).toBe(false);
|
|
83
|
+
expect(getWgpuRenderStateRuntime(offscreen).registries.compressedTextureDecoder.entry).toBeNull();
|
|
84
|
+
expect(getWgpuRenderStateRuntime(offscreen).registries.compressedTextureUpload.entry).toBeNull();
|
|
36
85
|
copyWgpuRenderStateRegistrations(offscreen, screen);
|
|
37
|
-
expect(
|
|
86
|
+
expect(
|
|
87
|
+
getRegistryTableEntry(getWgpuRenderStateRuntime(offscreen).registries.materialRenderers, 'acme.LateMaterial'),
|
|
88
|
+
).toBe(materialRenderer);
|
|
89
|
+
expect(
|
|
90
|
+
getRegistryTableEntry(getWgpuRenderStateRuntime(offscreen).registries.textureResolvers, 'acme.LateTexture'),
|
|
91
|
+
).toBe(resolver);
|
|
92
|
+
expect(getWgpuRenderStateRuntime(offscreen).registries.compressedTextureDecoder).toBe(
|
|
93
|
+
getWgpuRenderStateRuntime(screen).registries.compressedTextureDecoder,
|
|
94
|
+
);
|
|
95
|
+
expect(getWgpuRenderStateRuntime(offscreen).registries.compressedTextureDecoder.entry).toEqual({
|
|
96
|
+
state: RegistryEntryState.Bound,
|
|
97
|
+
value: decoder,
|
|
98
|
+
});
|
|
99
|
+
expect(getWgpuRenderStateRuntime(offscreen).registries.compressedTextureUpload).toBe(
|
|
100
|
+
getWgpuRenderStateRuntime(screen).registries.compressedTextureUpload,
|
|
101
|
+
);
|
|
102
|
+
registerWgpuMaterialRenderer(offscreen, 'acme.LateMaterial', offscreenMaterialRenderer);
|
|
103
|
+
registerWgpuCompressedTextureDecoder(offscreen, null);
|
|
104
|
+
registerWgpuCompressedTextureUpload(offscreen, null);
|
|
105
|
+
registerWgpuTextureResolver(offscreen, 'acme.LateTexture', null);
|
|
106
|
+
expect(
|
|
107
|
+
getRegistryTableEntry(getWgpuRenderStateRuntime(offscreen).registries.materialRenderers, 'acme.LateMaterial'),
|
|
108
|
+
).toBe(offscreenMaterialRenderer);
|
|
109
|
+
expect(
|
|
110
|
+
getRegistryTableEntry(getWgpuRenderStateRuntime(screen).registries.materialRenderers, 'acme.LateMaterial'),
|
|
111
|
+
).toBe(materialRenderer);
|
|
112
|
+
expect(
|
|
113
|
+
hasRegistryTableEntry(getWgpuRenderStateRuntime(offscreen).registries.textureResolvers, 'acme.LateTexture'),
|
|
114
|
+
).toBe(false);
|
|
115
|
+
expect(
|
|
116
|
+
getRegistryTableEntry(getWgpuRenderStateRuntime(screen).registries.textureResolvers, 'acme.LateTexture'),
|
|
117
|
+
).toBe(resolver);
|
|
118
|
+
expect(getWgpuRenderStateRuntime(offscreen).registries.compressedTextureDecoder.entry).toBeNull();
|
|
119
|
+
expect(getWgpuRenderStateRuntime(screen).registries.compressedTextureDecoder.entry?.state).toBe(
|
|
120
|
+
RegistryEntryState.Bound,
|
|
121
|
+
);
|
|
122
|
+
expect(getWgpuRenderStateRuntime(offscreen).registries.compressedTextureUpload.entry).toBeNull();
|
|
123
|
+
expect(getWgpuRenderStateRuntime(screen).registries.compressedTextureUpload.entry?.state).toBe(
|
|
124
|
+
RegistryEntryState.Bound,
|
|
125
|
+
);
|
|
38
126
|
});
|
|
39
127
|
});
|
|
40
128
|
|
|
41
129
|
describe('createWgpuOffscreenRenderState', () => {
|
|
130
|
+
it('resolves late screen blend-mode wiring explicitly until locally overridden', async () => {
|
|
131
|
+
const screen = await createWgpuRenderStateForTest();
|
|
132
|
+
const offscreen = createWgpuOffscreenRenderState(screen);
|
|
133
|
+
const nested = createWgpuOffscreenRenderState(offscreen);
|
|
134
|
+
const screenHook = vi.fn();
|
|
135
|
+
const laterScreenHook = vi.fn();
|
|
136
|
+
const offscreenHook = vi.fn();
|
|
137
|
+
|
|
138
|
+
expect(offscreen.applyBlendMode).toBeNull();
|
|
139
|
+
expect(Object.getOwnPropertyDescriptor(offscreen, 'applyBlendMode')?.get).toBeUndefined();
|
|
140
|
+
expect(resolveWgpuApplyBlendMode(offscreen)).toBeNull();
|
|
141
|
+
screen.applyBlendMode = screenHook;
|
|
142
|
+
expect(offscreen.applyBlendMode).toBeNull();
|
|
143
|
+
expect(resolveWgpuApplyBlendMode(offscreen)).toBe(screenHook);
|
|
144
|
+
expect(resolveWgpuApplyBlendMode(nested)).toBe(screenHook);
|
|
145
|
+
|
|
146
|
+
offscreen.applyBlendMode = offscreenHook;
|
|
147
|
+
screen.applyBlendMode = laterScreenHook;
|
|
148
|
+
expect(resolveWgpuApplyBlendMode(offscreen)).toBe(offscreenHook);
|
|
149
|
+
expect(resolveWgpuApplyBlendMode(nested)).toBe(offscreenHook);
|
|
150
|
+
|
|
151
|
+
copyWgpuRenderStateRegistrations(offscreen, screen);
|
|
152
|
+
expect(offscreen.applyBlendMode).toBeNull();
|
|
153
|
+
expect(resolveWgpuApplyBlendMode(offscreen)).toBe(laterScreenHook);
|
|
154
|
+
});
|
|
155
|
+
|
|
42
156
|
it('shares device resources while snapshotting independent registration policy', async () => {
|
|
43
157
|
const screen = await createWgpuRenderStateForTest();
|
|
44
158
|
const renderer = { createData: () => null, submit: vi.fn() };
|
|
@@ -46,11 +160,43 @@ describe('createWgpuOffscreenRenderState', () => {
|
|
|
46
160
|
const paddingResolver = vi.fn(() => ({ bottom: 1, left: 1, right: 1, top: 1 }));
|
|
47
161
|
const textureResolver = vi.fn(() => null);
|
|
48
162
|
const effectRunner = vi.fn();
|
|
163
|
+
const colorAdjustmentFeature: WgpuColorAdjustmentMaterialFeature = {
|
|
164
|
+
fragmentShaderChunk: '',
|
|
165
|
+
matrixFragmentShaderChunk: '',
|
|
166
|
+
record: vi.fn(),
|
|
167
|
+
resolveFlush: vi.fn(() => null),
|
|
168
|
+
};
|
|
169
|
+
const colorAdjustmentFeatureGuard: WgpuColorAdjustmentMaterialFeatureGuard = vi.fn();
|
|
170
|
+
const renderRootGuard: RenderRootGuard = vi.fn();
|
|
49
171
|
registerRenderer(screen, 'acme.Node', renderer);
|
|
50
172
|
registerWgpuMaterialRenderer(screen, 'acme.Material', materialRenderer);
|
|
51
173
|
registerWgpuTextureResolver(screen, 'acme.Texture', textureResolver);
|
|
52
|
-
|
|
53
|
-
|
|
174
|
+
enableColorAdjustments(screen);
|
|
175
|
+
enableColorAdjustmentGuards(screen);
|
|
176
|
+
registerPaddingResolver(screen, 'acme.Effect', paddingResolver);
|
|
177
|
+
getWgpuRenderStateRuntime(screen).registries.colorAdjustmentFeature = {
|
|
178
|
+
entry: { state: RegistryEntryState.Bound, value: colorAdjustmentFeature },
|
|
179
|
+
onMiss: 'Disabled',
|
|
180
|
+
registry: 'WgpuColorAdjustmentFeature',
|
|
181
|
+
shape: 'slot',
|
|
182
|
+
};
|
|
183
|
+
getWgpuRenderStateRuntime(screen).registries.colorAdjustmentFeatureGuard = {
|
|
184
|
+
entry: { state: RegistryEntryState.Bound, value: colorAdjustmentFeatureGuard },
|
|
185
|
+
onMiss: 'Disabled',
|
|
186
|
+
registry: 'WgpuColorAdjustmentFeatureGuard',
|
|
187
|
+
shape: 'slot',
|
|
188
|
+
};
|
|
189
|
+
getWgpuRenderStateRuntime(screen).registries.renderRootGuard = {
|
|
190
|
+
entry: { state: RegistryEntryState.Bound, value: renderRootGuard },
|
|
191
|
+
onMiss: 'Disabled',
|
|
192
|
+
registry: 'RenderRootGuard',
|
|
193
|
+
shape: 'slot',
|
|
194
|
+
};
|
|
195
|
+
getWgpuRenderStateRuntime(screen).registries.renderEffects = withRegistryTableEntry(
|
|
196
|
+
getWgpuRenderStateRuntime(screen).registries.renderEffects,
|
|
197
|
+
'acme.Effect',
|
|
198
|
+
effectRunner as never,
|
|
199
|
+
);
|
|
54
200
|
getWgpuRenderStateRuntime(screen).wgpuRenderTextureCache = new WeakMap();
|
|
55
201
|
|
|
56
202
|
const offscreen = createWgpuOffscreenRenderState(screen);
|
|
@@ -64,18 +210,71 @@ describe('createWgpuOffscreenRenderState', () => {
|
|
|
64
210
|
expect(offscreenRuntime.wgpuRenderTextureCache).toBe(screenRuntime.wgpuRenderTextureCache);
|
|
65
211
|
expect(offscreenRuntime.uniformBindGroupLayout).toBe(screenRuntime.uniformBindGroupLayout);
|
|
66
212
|
expect(offscreenRuntime.uniformBuffer).not.toBe(screenRuntime.uniformBuffer);
|
|
67
|
-
expect(offscreenRuntime.
|
|
68
|
-
expect(offscreenRuntime.
|
|
69
|
-
expect(offscreenRuntime.
|
|
70
|
-
expect(
|
|
71
|
-
expect(offscreenRuntime.
|
|
72
|
-
screenRuntime.
|
|
213
|
+
expect(offscreenRuntime.registries.renderers).toBe(screenRuntime.registries.renderers);
|
|
214
|
+
expect(offscreenRuntime.registries).not.toBe(screenRuntime.registries);
|
|
215
|
+
expect(offscreenRuntime.registries.colorAdjustmentFeature).toBe(screenRuntime.registries.colorAdjustmentFeature);
|
|
216
|
+
expect(getWgpuColorAdjustmentMaterialFeature(offscreen)).toBe(colorAdjustmentFeature);
|
|
217
|
+
expect(offscreenRuntime.registries.colorAdjustmentFeatureGuard).toBe(
|
|
218
|
+
screenRuntime.registries.colorAdjustmentFeatureGuard,
|
|
219
|
+
);
|
|
220
|
+
expect(getWgpuColorAdjustmentMaterialFeatureGuard(offscreen)).toBe(colorAdjustmentFeatureGuard);
|
|
221
|
+
const sharedColorFeatureGuard = offscreenRuntime.registries.colorAdjustmentFeatureGuard;
|
|
222
|
+
screenRuntime.registries.colorAdjustmentFeatureGuard = undefined;
|
|
223
|
+
expect(getWgpuColorAdjustmentMaterialFeatureGuard(screen)).toBeNull();
|
|
224
|
+
expect(offscreenRuntime.registries.colorAdjustmentFeatureGuard).toBe(sharedColorFeatureGuard);
|
|
225
|
+
expect(getWgpuColorAdjustmentMaterialFeatureGuard(offscreen)).toBe(colorAdjustmentFeatureGuard);
|
|
226
|
+
const sharedColorFeature = offscreenRuntime.registries.colorAdjustmentFeature;
|
|
227
|
+
screenRuntime.registries.colorAdjustmentFeature = undefined;
|
|
228
|
+
expect(getWgpuColorAdjustmentMaterialFeature(screen)).toBeNull();
|
|
229
|
+
expect(offscreenRuntime.registries.colorAdjustmentFeature).toBe(sharedColorFeature);
|
|
230
|
+
expect(getWgpuColorAdjustmentMaterialFeature(offscreen)).toBe(colorAdjustmentFeature);
|
|
231
|
+
expect(offscreenRuntime.registries.colorAdjustments).toBe(screenRuntime.registries.colorAdjustments);
|
|
232
|
+
expect(offscreenRuntime.registries.colorAdjustmentUnsupportedGuard).toBe(
|
|
233
|
+
screenRuntime.registries.colorAdjustmentUnsupportedGuard,
|
|
234
|
+
);
|
|
235
|
+
expect(getColorAdjustmentUnsupportedGuard(offscreen)).not.toBeNull();
|
|
236
|
+
const sharedUnsupportedGuard = offscreenRuntime.registries.colorAdjustmentUnsupportedGuard;
|
|
237
|
+
screenRuntime.registries.colorAdjustmentUnsupportedGuard = undefined;
|
|
238
|
+
expect(getColorAdjustmentUnsupportedGuard(screen)).toBeNull();
|
|
239
|
+
expect(offscreenRuntime.registries.colorAdjustmentUnsupportedGuard).toBe(sharedUnsupportedGuard);
|
|
240
|
+
expect(getColorAdjustmentUnsupportedGuard(offscreen)).not.toBeNull();
|
|
241
|
+
expect(offscreenRuntime.registries.renderRootGuard).toBe(screenRuntime.registries.renderRootGuard);
|
|
242
|
+
expect(offscreenRuntime.registries.renderRootGuard?.entry).toEqual({
|
|
243
|
+
state: RegistryEntryState.Bound,
|
|
244
|
+
value: renderRootGuard,
|
|
245
|
+
});
|
|
246
|
+
const sharedRenderRootGuard = offscreenRuntime.registries.renderRootGuard;
|
|
247
|
+
screenRuntime.registries.renderRootGuard = undefined;
|
|
248
|
+
expect(screenRuntime.registries.renderRootGuard).toBeUndefined();
|
|
249
|
+
expect(offscreenRuntime.registries.renderRootGuard).toBe(sharedRenderRootGuard);
|
|
250
|
+
expect(
|
|
251
|
+
getRegistryTableEntry(
|
|
252
|
+
offscreenRuntime.registries.renderRootGuard!,
|
|
253
|
+
offscreenRuntime.registries.renderRootGuard!.registry,
|
|
254
|
+
),
|
|
255
|
+
).toBe(renderRootGuard);
|
|
256
|
+
expect(offscreenRuntime.registries.compressedTextureDecoder).toBe(
|
|
257
|
+
screenRuntime.registries.compressedTextureDecoder,
|
|
73
258
|
);
|
|
74
|
-
expect(offscreenRuntime.
|
|
75
|
-
expect(offscreenRuntime.
|
|
76
|
-
expect(offscreenRuntime.
|
|
77
|
-
expect(offscreenRuntime.
|
|
78
|
-
expect(offscreenRuntime.
|
|
259
|
+
expect(offscreenRuntime.registries.compressedTextureUpload).toBe(screenRuntime.registries.compressedTextureUpload);
|
|
260
|
+
expect(offscreenRuntime.registries.customMaterialShaders).toBe(screenRuntime.registries.customMaterialShaders);
|
|
261
|
+
expect(offscreenRuntime.registries.materialRenderers).toBe(screenRuntime.registries.materialRenderers);
|
|
262
|
+
expect(offscreenRuntime.registries.meshMaterialRenderers).toBe(screenRuntime.registries.meshMaterialRenderers);
|
|
263
|
+
expect(offscreenRuntime.registries.modifierSnippets).toBe(screenRuntime.registries.modifierSnippets);
|
|
264
|
+
expect(offscreenRuntime.registries.modifierSnippetRevision).toBe(screenRuntime.registries.modifierSnippetRevision);
|
|
265
|
+
expect(offscreenRuntime.registries.renderEffects).toBe(screenRuntime.registries.renderEffects);
|
|
266
|
+
expect(offscreenRuntime.registries.shapeRasterizer).toBe(screenRuntime.registries.shapeRasterizer);
|
|
267
|
+
expect(offscreenRuntime.registries.strokeTessellator).toBe(screenRuntime.registries.strokeTessellator);
|
|
268
|
+
expect(offscreenRuntime.registries.textureResolvers).toBe(screenRuntime.registries.textureResolvers);
|
|
269
|
+
expect(offscreenRuntime.registries.velocityWriters).toBe(screenRuntime.registries.velocityWriters);
|
|
270
|
+
expect(offscreenRuntime.registries.effectPaddingResolvers).toBe(screenRuntime.registries.effectPaddingResolvers);
|
|
271
|
+
expect(getRegistryTableEntry(offscreenRuntime.registries.renderers, 'acme.Node')).toBe(renderer);
|
|
272
|
+
expect(getRegistryTableEntry(offscreenRuntime.registries.materialRenderers, 'acme.Material')).toBe(
|
|
273
|
+
materialRenderer,
|
|
274
|
+
);
|
|
275
|
+
expect(getRegistryTableEntry(offscreenRuntime.registries.textureResolvers, 'acme.Texture')).toBe(textureResolver);
|
|
276
|
+
expect(getRegistryTableEntry(offscreenRuntime.registries.renderEffects, 'acme.Effect')).toBe(effectRunner);
|
|
277
|
+
expect(getPaddingResolver(offscreen, 'acme.Effect')).toBe(paddingResolver);
|
|
79
278
|
});
|
|
80
279
|
|
|
81
280
|
it('owns an independent encoder and proxy tree', async () => {
|
|
@@ -120,17 +319,17 @@ describe('createWgpuOffscreenRenderState', () => {
|
|
|
120
319
|
const renderer = { createData: () => null, submit: vi.fn() };
|
|
121
320
|
const paddingResolver = vi.fn(() => ({ bottom: 2, left: 2, right: 2, top: 2 }));
|
|
122
321
|
registerRenderer(screen, 'acme.LateNode', renderer);
|
|
123
|
-
|
|
322
|
+
registerPaddingResolver(screen, 'acme.LateEffect', paddingResolver);
|
|
124
323
|
|
|
125
|
-
expect(getRenderStateRuntime(offscreen).
|
|
126
|
-
expect(
|
|
324
|
+
expect(hasRegistryTableEntry(getRenderStateRuntime(offscreen).registries.renderers, 'acme.LateNode')).toBe(false);
|
|
325
|
+
expect(getPaddingResolver(offscreen, 'acme.LateEffect')).toBeNull();
|
|
127
326
|
|
|
128
327
|
copyAllRenderersFromRenderState(offscreen, screen);
|
|
129
328
|
copyWgpuRenderStateRegistrations(offscreen, screen);
|
|
130
|
-
expect(getRenderStateRuntime(offscreen).
|
|
131
|
-
|
|
132
|
-
paddingResolver,
|
|
329
|
+
expect(getRegistryTableEntry(getRenderStateRuntime(offscreen).registries.renderers, 'acme.LateNode')).toBe(
|
|
330
|
+
renderer,
|
|
133
331
|
);
|
|
332
|
+
expect(getPaddingResolver(offscreen, 'acme.LateEffect')).toBe(paddingResolver);
|
|
134
333
|
});
|
|
135
334
|
});
|
|
136
335
|
|
|
@@ -168,9 +367,70 @@ describe('createWgpuRenderState', () => {
|
|
|
168
367
|
});
|
|
169
368
|
|
|
170
369
|
describe('createWgpuRenderStateRuntime', () => {
|
|
171
|
-
it('returns a runtime
|
|
370
|
+
it('returns a runtime with the base binding slot and empty named registration tables', () => {
|
|
172
371
|
const runtime = createWgpuRenderStateRuntime();
|
|
173
372
|
expect(runtime.binding).toBeNull();
|
|
373
|
+
expect(runtime.registries.customMaterialShaders).toMatchObject({
|
|
374
|
+
onMiss: 'Unregistered',
|
|
375
|
+
registry: 'WgpuCustomMaterialShader',
|
|
376
|
+
shape: 'keyed',
|
|
377
|
+
});
|
|
378
|
+
expect(runtime.registries.customMaterialShaders.entries.size).toBe(0);
|
|
379
|
+
expect(runtime.registries.materialRenderers).toMatchObject({
|
|
380
|
+
onMiss: 'StandardMaterial',
|
|
381
|
+
registry: 'WgpuMaterialRenderer',
|
|
382
|
+
shape: 'keyed',
|
|
383
|
+
});
|
|
384
|
+
expect(runtime.registries.materialRenderers.entries.size).toBe(0);
|
|
385
|
+
expect(runtime.registries.meshMaterialRenderers).toMatchObject({
|
|
386
|
+
onMiss: 'StandardMaterial',
|
|
387
|
+
registry: 'WgpuMeshMaterialRenderer',
|
|
388
|
+
shape: 'keyed',
|
|
389
|
+
});
|
|
390
|
+
expect(runtime.registries.meshMaterialRenderers.entries.size).toBe(0);
|
|
391
|
+
expect(runtime.registries.modifierSnippets).toMatchObject({
|
|
392
|
+
onMiss: 'Unregistered',
|
|
393
|
+
registry: 'WgpuModifierSnippet',
|
|
394
|
+
shape: 'keyed',
|
|
395
|
+
});
|
|
396
|
+
expect(runtime.registries.modifierSnippets.entries.size).toBe(0);
|
|
397
|
+
expect(runtime.registries.modifierSnippetRevision).toBe(0);
|
|
398
|
+
expect(runtime.registries.renderEffects).toMatchObject({
|
|
399
|
+
onMiss: 'Unregistered',
|
|
400
|
+
registry: 'WgpuRenderEffect',
|
|
401
|
+
shape: 'keyed',
|
|
402
|
+
});
|
|
403
|
+
expect(runtime.registries.compressedTextureDecoder).toEqual({
|
|
404
|
+
entry: null,
|
|
405
|
+
onMiss: 'Unregistered',
|
|
406
|
+
registry: 'WgpuCompressedTextureDecoder',
|
|
407
|
+
shape: 'slot',
|
|
408
|
+
});
|
|
409
|
+
expect(runtime.registries.colorAdjustments).toBeUndefined();
|
|
410
|
+
expect(runtime.registries.compressedTextureUpload).toEqual({
|
|
411
|
+
entry: null,
|
|
412
|
+
onMiss: 'Unregistered',
|
|
413
|
+
registry: 'WgpuCompressedTextureUpload',
|
|
414
|
+
shape: 'slot',
|
|
415
|
+
});
|
|
416
|
+
expect(runtime.registries.shapeRasterizer).toEqual({
|
|
417
|
+
entry: null,
|
|
418
|
+
onMiss: 'Unregistered',
|
|
419
|
+
registry: 'WgpuShapeRasterizer',
|
|
420
|
+
shape: 'slot',
|
|
421
|
+
});
|
|
422
|
+
expect(runtime.registries.strokeTessellator).toEqual({
|
|
423
|
+
entry: null,
|
|
424
|
+
onMiss: 'Rasterize',
|
|
425
|
+
registry: 'StrokeTessellator',
|
|
426
|
+
shape: 'slot',
|
|
427
|
+
});
|
|
428
|
+
expect(runtime.registries.velocityWriters).toMatchObject({
|
|
429
|
+
onMiss: 'Unregistered',
|
|
430
|
+
registry: 'WgpuVelocityWriter',
|
|
431
|
+
shape: 'keyed',
|
|
432
|
+
});
|
|
433
|
+
expect(runtime.registries.velocityWriters.entries.size).toBe(0);
|
|
174
434
|
});
|
|
175
435
|
});
|
|
176
436
|
|
|
@@ -191,6 +451,55 @@ describe('destroyWgpuRenderState', () => {
|
|
|
191
451
|
});
|
|
192
452
|
});
|
|
193
453
|
|
|
454
|
+
describe('getWgpuColorAdjustmentMaterialFeature', () => {
|
|
455
|
+
it('resolves only a bound feature entry', async () => {
|
|
456
|
+
const state = await createWgpuRenderStateForTest();
|
|
457
|
+
const feature: WgpuColorAdjustmentMaterialFeature = {
|
|
458
|
+
fragmentShaderChunk: '',
|
|
459
|
+
matrixFragmentShaderChunk: '',
|
|
460
|
+
record: vi.fn(),
|
|
461
|
+
resolveFlush: vi.fn(() => null),
|
|
462
|
+
};
|
|
463
|
+
const runtime = getWgpuRenderStateRuntime(state);
|
|
464
|
+
|
|
465
|
+
expect(getWgpuColorAdjustmentMaterialFeature(state)).toBeNull();
|
|
466
|
+
runtime.registries.colorAdjustmentFeature = {
|
|
467
|
+
entry: { state: RegistryEntryState.Bound, value: feature },
|
|
468
|
+
onMiss: 'Disabled',
|
|
469
|
+
registry: 'WgpuColorAdjustmentFeature',
|
|
470
|
+
shape: 'slot',
|
|
471
|
+
};
|
|
472
|
+
expect(getWgpuColorAdjustmentMaterialFeature(state)).toBe(feature);
|
|
473
|
+
runtime.registries.colorAdjustmentFeature = {
|
|
474
|
+
...runtime.registries.colorAdjustmentFeature,
|
|
475
|
+
entry: { state: RegistryEntryState.Tombstoned },
|
|
476
|
+
};
|
|
477
|
+
expect(getWgpuColorAdjustmentMaterialFeature(state)).toBeNull();
|
|
478
|
+
});
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
describe('getWgpuColorAdjustmentMaterialFeatureGuard', () => {
|
|
482
|
+
it('resolves only a bound guard entry', async () => {
|
|
483
|
+
const state = await createWgpuRenderStateForTest();
|
|
484
|
+
const guard: WgpuColorAdjustmentMaterialFeatureGuard = vi.fn();
|
|
485
|
+
const runtime = getWgpuRenderStateRuntime(state);
|
|
486
|
+
|
|
487
|
+
expect(getWgpuColorAdjustmentMaterialFeatureGuard(state)).toBeNull();
|
|
488
|
+
runtime.registries.colorAdjustmentFeatureGuard = {
|
|
489
|
+
entry: { state: RegistryEntryState.Bound, value: guard },
|
|
490
|
+
onMiss: 'Disabled',
|
|
491
|
+
registry: 'WgpuColorAdjustmentFeatureGuard',
|
|
492
|
+
shape: 'slot',
|
|
493
|
+
};
|
|
494
|
+
expect(getWgpuColorAdjustmentMaterialFeatureGuard(state)).toBe(guard);
|
|
495
|
+
runtime.registries.colorAdjustmentFeatureGuard = {
|
|
496
|
+
...runtime.registries.colorAdjustmentFeatureGuard,
|
|
497
|
+
entry: { state: RegistryEntryState.Tombstoned },
|
|
498
|
+
};
|
|
499
|
+
expect(getWgpuColorAdjustmentMaterialFeatureGuard(state)).toBeNull();
|
|
500
|
+
});
|
|
501
|
+
});
|
|
502
|
+
|
|
194
503
|
describe('getWgpuRenderStateRuntime', () => {
|
|
195
504
|
it('returns the runtime attached by createWgpuRenderState', async () => {
|
|
196
505
|
const state = await createWgpuRenderStateForTest();
|
|
@@ -265,3 +574,12 @@ describe('isWgpuSupported', () => {
|
|
|
265
574
|
expect(isWgpuSupported()).toBe(true);
|
|
266
575
|
});
|
|
267
576
|
});
|
|
577
|
+
|
|
578
|
+
describe('resolveWgpuApplyBlendMode', () => {
|
|
579
|
+
it('returns a hook installed directly on the state', async () => {
|
|
580
|
+
const state = await createWgpuRenderStateForTest();
|
|
581
|
+
const hook = vi.fn();
|
|
582
|
+
state.applyBlendMode = hook;
|
|
583
|
+
expect(resolveWgpuApplyBlendMode(state)).toBe(hook);
|
|
584
|
+
});
|
|
585
|
+
});
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { createWgpuRenderStateForTest, installWgpuMock } from './wgpuTestHelper';
|
|
2
|
+
|
|
3
|
+
beforeAll(() => {
|
|
4
|
+
installWgpuMock();
|
|
5
|
+
});
|
|
6
|
+
|
|
7
|
+
describe('installWgpuMock', () => {
|
|
8
|
+
it('accepts aligned writeBuffer data using typed-array element units', async () => {
|
|
9
|
+
const state = await createWgpuRenderStateForTest();
|
|
10
|
+
const buffer = state.device.createBuffer({ size: 8, usage: GPUBufferUsage.COPY_DST });
|
|
11
|
+
|
|
12
|
+
expect(() => state.device.queue.writeBuffer(buffer, 0, new Uint16Array([0, 1, 2]), 0, 2)).not.toThrow();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('rejects writeBuffer content whose byte length is not a multiple of four', async () => {
|
|
16
|
+
const state = await createWgpuRenderStateForTest();
|
|
17
|
+
const buffer = state.device.createBuffer({ size: 8, usage: GPUBufferUsage.COPY_DST });
|
|
18
|
+
|
|
19
|
+
expect(() => state.device.queue.writeBuffer(buffer, 0, new Uint16Array([0, 1, 2]))).toThrow(
|
|
20
|
+
expect.objectContaining({ name: 'OperationError' }),
|
|
21
|
+
);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('rejects writeBuffer source ranges and destination offsets outside the API contract', async () => {
|
|
25
|
+
const state = await createWgpuRenderStateForTest();
|
|
26
|
+
const buffer = state.device.createBuffer({ size: 8, usage: GPUBufferUsage.COPY_DST });
|
|
27
|
+
|
|
28
|
+
expect(() => state.device.queue.writeBuffer(buffer, 0, new Uint32Array([0]), 2)).toThrow(
|
|
29
|
+
/data range is out of bounds/,
|
|
30
|
+
);
|
|
31
|
+
expect(() => state.device.queue.writeBuffer(buffer, 2, new Uint32Array([0]))).toThrow(
|
|
32
|
+
/bufferOffset must be a non-negative multiple of 4/,
|
|
33
|
+
);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('rejects writeBuffer destinations without enough COPY_DST storage', async () => {
|
|
37
|
+
const state = await createWgpuRenderStateForTest();
|
|
38
|
+
const tooSmall = state.device.createBuffer({ size: 4, usage: GPUBufferUsage.COPY_DST });
|
|
39
|
+
const wrongUsage = state.device.createBuffer({ size: 8, usage: GPUBufferUsage.VERTEX });
|
|
40
|
+
|
|
41
|
+
expect(() => state.device.queue.writeBuffer(tooSmall, 0, new Uint32Array([0, 1]))).toThrow(
|
|
42
|
+
/destination range exceeds the buffer size/,
|
|
43
|
+
);
|
|
44
|
+
expect(() => state.device.queue.writeBuffer(wrongUsage, 0, new Uint32Array([0]))).toThrow(
|
|
45
|
+
/must have COPY_DST usage/,
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('rejects writeTexture data smaller than an rgba8 copy requires', async () => {
|
|
50
|
+
const state = await createWgpuRenderStateForTest();
|
|
51
|
+
const texture = state.device.createTexture({
|
|
52
|
+
size: [2, 2, 1],
|
|
53
|
+
format: 'rgba8unorm',
|
|
54
|
+
usage: GPUTextureUsage.COPY_DST,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
expect(() =>
|
|
58
|
+
state.device.queue.writeTexture({ texture }, new Uint8Array(15), { bytesPerRow: 8, rowsPerImage: 2 }, [2, 2, 1]),
|
|
59
|
+
).toThrow(expect.objectContaining({ name: 'OperationError' }));
|
|
60
|
+
expect(() =>
|
|
61
|
+
state.device.queue.writeTexture({ texture }, new Uint8Array(16), { bytesPerRow: 8, rowsPerImage: 2 }, [2, 2, 1]),
|
|
62
|
+
).not.toThrow();
|
|
63
|
+
});
|
|
64
|
+
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createEntity } from '@flighthq/entity/contract';
|
|
2
|
+
import { getRegistryTableKeys } from '@flighthq/registry/contract';
|
|
2
3
|
import type { Image, RenderTexture, Texture, TextureSource } from '@flighthq/types/contract';
|
|
3
4
|
import {
|
|
4
5
|
BitmapTextureSourceKind,
|
|
@@ -83,11 +84,17 @@ function renderTexture(): RenderTexture {
|
|
|
83
84
|
return texture as RenderTexture;
|
|
84
85
|
}
|
|
85
86
|
|
|
87
|
+
function registeredTextureSourceKinds(state: Parameters<typeof getWgpuRenderStateRuntime>[0]): string[] {
|
|
88
|
+
const kinds: string[] = [];
|
|
89
|
+
getRegistryTableKeys(kinds, getWgpuRenderStateRuntime(state).registries.textureResolvers);
|
|
90
|
+
return kinds;
|
|
91
|
+
}
|
|
92
|
+
|
|
86
93
|
describe('registerStandardWgpuTextureResolvers', () => {
|
|
87
94
|
it('registers bitmap, image, and render texture sources without compressed images', async () => {
|
|
88
95
|
const state = await createWgpuRenderStateForTest();
|
|
89
96
|
registerStandardWgpuTextureResolvers(state);
|
|
90
|
-
expect(
|
|
97
|
+
expect(registeredTextureSourceKinds(state)).toEqual([
|
|
91
98
|
BitmapTextureSourceKind,
|
|
92
99
|
ImageTextureSourceKind,
|
|
93
100
|
RenderTargetTextureSourceKind,
|
|
@@ -99,9 +106,7 @@ describe('registerWgpuBitmapTextureResolver', () => {
|
|
|
99
106
|
it('registers only the Bitmap source key', async () => {
|
|
100
107
|
const state = await createWgpuRenderStateForTest();
|
|
101
108
|
registerWgpuBitmapTextureResolver(state);
|
|
102
|
-
expect(
|
|
103
|
-
BitmapTextureSourceKind,
|
|
104
|
-
]);
|
|
109
|
+
expect(registeredTextureSourceKinds(state)).toEqual([BitmapTextureSourceKind]);
|
|
105
110
|
});
|
|
106
111
|
});
|
|
107
112
|
|
|
@@ -109,9 +114,7 @@ describe('registerWgpuCompressedImageTextureResolver', () => {
|
|
|
109
114
|
it('registers only the CompressedImage source key', async () => {
|
|
110
115
|
const state = await createWgpuRenderStateForTest();
|
|
111
116
|
registerWgpuCompressedImageTextureResolver(state);
|
|
112
|
-
expect(
|
|
113
|
-
CompressedImageTextureSourceKind,
|
|
114
|
-
]);
|
|
117
|
+
expect(registeredTextureSourceKinds(state)).toEqual([CompressedImageTextureSourceKind]);
|
|
115
118
|
});
|
|
116
119
|
});
|
|
117
120
|
|