@flighthq/render-wgpu 0.3.0-next.906.07cea63 → 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/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 +15 -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.d.ts +2 -0
- package/dist/wgpuBackground.d.ts.map +1 -1
- package/dist/wgpuBackground.js +30 -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 +52 -13
- 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 +150 -69
- 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 +17 -12
- 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 +15 -3
- package/dist/wgpuRenderState.d.ts.map +1 -1
- package/dist/wgpuRenderState.js +253 -33
- 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 +125 -8
- 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 +68 -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 +17 -7
- package/src/enableWgpuTextureResolverGuards.test.ts +65 -0
- package/src/explainWgpuTextureResolution.test.ts +36 -0
- package/src/wgpuBackground.test.ts +34 -1
- package/src/wgpuCompressedTexture.test.ts +35 -40
- package/src/wgpuDraw.test.ts +180 -62
- package/src/wgpuExternalImageSource.test.ts +89 -0
- package/src/wgpuExternalTexture.test.ts +35 -0
- package/src/wgpuMaterialRegistry.test.ts +44 -3
- package/src/wgpuMipmap.test.ts +10 -2
- package/src/wgpuRenderState.test.ts +462 -12
- 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/wgpuTestHelper.test.ts +64 -0
- package/src/wgpuTextureResolver.test.ts +212 -0
- package/src/wgpuTextureUpload.test.ts +28 -13
|
@@ -1,18 +1,21 @@
|
|
|
1
|
+
import { addLogSink, createMemoryLogSink, getMemoryLogSinkEntries, removeLogSink } from '@flighthq/log/contract';
|
|
2
|
+
import { enableRenderRegistryGuards, explainRenderRegistryMisses } from '@flighthq/render/contract';
|
|
1
3
|
import type { Material, WgpuMaterialRenderer, WgpuRenderState } from '@flighthq/types/contract';
|
|
2
|
-
import { StandardMaterialKind, EntityRuntimeKey } from '@flighthq/types/contract';
|
|
4
|
+
import { StandardMaterialKind, EntityRuntimeKey, RenderRegistry } from '@flighthq/types/contract';
|
|
3
5
|
|
|
4
6
|
import {
|
|
5
7
|
getWgpuMaterialRenderer,
|
|
6
8
|
registerWgpuMaterialRenderer,
|
|
7
9
|
resolveWgpuMaterialRenderer,
|
|
8
10
|
} from './wgpuMaterialRegistry';
|
|
9
|
-
import { createWgpuRenderStateRuntime } from './wgpuRenderState';
|
|
11
|
+
import { createWgpuRenderStateRuntime, getWgpuRenderStateRuntime } from './wgpuRenderState';
|
|
10
12
|
|
|
11
13
|
const TestKind = 'TestMaterial';
|
|
12
14
|
const testRenderer: WgpuMaterialRenderer = { instanceFloatCount: 0, getShaderModule: () => ({}) as GPUShaderModule };
|
|
13
15
|
|
|
14
16
|
function makeState(): WgpuRenderState {
|
|
15
|
-
|
|
17
|
+
// `device` is what the guard's message table dispatches on to name the wgpu registrar.
|
|
18
|
+
const state = { device: {} } as WgpuRenderState;
|
|
16
19
|
state[EntityRuntimeKey] = createWgpuRenderStateRuntime();
|
|
17
20
|
return state;
|
|
18
21
|
}
|
|
@@ -30,8 +33,23 @@ describe('getWgpuMaterialRenderer', () => {
|
|
|
30
33
|
describe('registerWgpuMaterialRenderer', () => {
|
|
31
34
|
it('registers a renderer retrievable by kind', () => {
|
|
32
35
|
const state = makeState();
|
|
36
|
+
const before = getWgpuRenderStateRuntime(state).registries.materialRenderers;
|
|
33
37
|
registerWgpuMaterialRenderer(state, TestKind, testRenderer);
|
|
34
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 });
|
|
35
53
|
});
|
|
36
54
|
});
|
|
37
55
|
|
|
@@ -53,4 +71,27 @@ describe('resolveWgpuMaterialRenderer', () => {
|
|
|
53
71
|
expect(resolveWgpuMaterialRenderer(state, makeMaterial('Other'))).toBe(testRenderer);
|
|
54
72
|
expect(resolveWgpuMaterialRenderer(state, null)).toBe(testRenderer);
|
|
55
73
|
});
|
|
74
|
+
|
|
75
|
+
it('reports the missing kind against the wgpu registrar, not the gl one', () => {
|
|
76
|
+
const state = makeState();
|
|
77
|
+
enableRenderRegistryGuards(state);
|
|
78
|
+
const sink = createMemoryLogSink(4);
|
|
79
|
+
addLogSink(sink.sink);
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
resolveWgpuMaterialRenderer(state, makeMaterial(TestKind));
|
|
83
|
+
|
|
84
|
+
expect(explainRenderRegistryMisses(state).misses).toEqual([
|
|
85
|
+
{ kind: TestKind, registry: RenderRegistry.MaterialRenderer },
|
|
86
|
+
]);
|
|
87
|
+
expect(getMemoryLogSinkEntries(sink)[0]?.data).toMatchObject({
|
|
88
|
+
kind: TestKind,
|
|
89
|
+
message:
|
|
90
|
+
'resolveWgpuMaterialRenderer: material kind has no registered renderer, so nodes using it do not draw — call registerWgpuMaterialRenderer(state, kind, renderer)',
|
|
91
|
+
registry: RenderRegistry.MaterialRenderer,
|
|
92
|
+
});
|
|
93
|
+
} finally {
|
|
94
|
+
removeLogSink(sink.sink);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
56
97
|
});
|
package/src/wgpuMipmap.test.ts
CHANGED
|
@@ -53,10 +53,18 @@ describe('generateWgpuMipmaps', () => {
|
|
|
53
53
|
const state = await createWgpuRenderStateForTest();
|
|
54
54
|
spyEncoderPasses(state);
|
|
55
55
|
generateWgpuMipmaps(state, makeTextureSpy(), 4, 4, 'rgba8unorm');
|
|
56
|
-
const pipeline = getWgpuRenderStateRuntime(state).
|
|
56
|
+
const pipeline = getWgpuRenderStateRuntime(state).mipmapPipelineCache.get('rgba8unorm')?.pipeline;
|
|
57
57
|
expect(pipeline).toBeDefined();
|
|
58
58
|
generateWgpuMipmaps(state, makeTextureSpy(), 8, 8, 'rgba8unorm');
|
|
59
|
-
expect(getWgpuRenderStateRuntime(state).
|
|
59
|
+
expect(getWgpuRenderStateRuntime(state).mipmapPipelineCache.get('rgba8unorm')?.pipeline).toBe(pipeline);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('keeps linear and sRGB downsample pipelines distinct', async () => {
|
|
63
|
+
const state = await createWgpuRenderStateForTest();
|
|
64
|
+
spyEncoderPasses(state);
|
|
65
|
+
generateWgpuMipmaps(state, makeTextureSpy(), 4, 4, 'rgba8unorm');
|
|
66
|
+
generateWgpuMipmaps(state, makeTextureSpy(), 4, 4, 'rgba8unorm-srgb');
|
|
67
|
+
expect(getWgpuRenderStateRuntime(state).mipmapPipelineCache.size).toBe(2);
|
|
60
68
|
});
|
|
61
69
|
});
|
|
62
70
|
|
|
@@ -1,16 +1,338 @@
|
|
|
1
|
+
import { createEntity } from '@flighthq/entity/contract';
|
|
1
2
|
import {
|
|
3
|
+
createKeyedTable,
|
|
4
|
+
getRegistryTableEntry,
|
|
5
|
+
hasRegistryTableEntry,
|
|
6
|
+
withRegistryTableEntry,
|
|
7
|
+
} from '@flighthq/registry/contract';
|
|
8
|
+
import {
|
|
9
|
+
copyAllRenderersFromRenderState,
|
|
10
|
+
enableColorAdjustmentGuards,
|
|
11
|
+
enableColorAdjustments,
|
|
12
|
+
getColorAdjustmentUnsupportedGuard,
|
|
13
|
+
getRenderStateRuntime,
|
|
14
|
+
prepareScene2DRender,
|
|
15
|
+
registerRenderer,
|
|
16
|
+
} from '@flighthq/render/contract';
|
|
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';
|
|
26
|
+
|
|
27
|
+
import { beginWgpuFrame } from './wgpuBackground';
|
|
28
|
+
import { registerWgpuCompressedTextureDecoder, registerWgpuCompressedTextureUpload } from './wgpuCompressedTexture';
|
|
29
|
+
import { registerWgpuMaterialRenderer } from './wgpuMaterialRegistry';
|
|
30
|
+
import {
|
|
31
|
+
copyWgpuRenderStateRegistrations,
|
|
32
|
+
createWgpuOffscreenRenderState,
|
|
2
33
|
createWgpuRenderStateRuntime,
|
|
3
34
|
destroyWgpuRenderState,
|
|
35
|
+
getWgpuColorAdjustmentMaterialFeature,
|
|
36
|
+
getWgpuColorAdjustmentMaterialFeatureGuard,
|
|
4
37
|
getWgpuRenderStateRuntime,
|
|
5
38
|
getWgpuSampler,
|
|
6
39
|
isWgpuSupported,
|
|
40
|
+
resolveWgpuApplyBlendMode,
|
|
7
41
|
} from './wgpuRenderState';
|
|
8
42
|
import { createWgpuRenderStateForTest, installWgpuMock } from './wgpuTestHelper';
|
|
43
|
+
import { registerWgpuTextureResolver } from './wgpuTextureResolver';
|
|
9
44
|
|
|
10
45
|
beforeAll(() => {
|
|
11
46
|
installWgpuMock();
|
|
12
47
|
});
|
|
13
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
|
+
|
|
64
|
+
describe('copyWgpuRenderStateRegistrations', () => {
|
|
65
|
+
it('copies late Wgpu registrations only when explicitly requested', async () => {
|
|
66
|
+
const screen = await createWgpuRenderStateForTest();
|
|
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));
|
|
71
|
+
const resolver = vi.fn(() => null);
|
|
72
|
+
registerWgpuCompressedTextureDecoder(screen, decoder);
|
|
73
|
+
registerWgpuCompressedTextureUpload(screen);
|
|
74
|
+
registerWgpuMaterialRenderer(screen, 'acme.LateMaterial', materialRenderer);
|
|
75
|
+
registerWgpuTextureResolver(screen, 'acme.LateTexture', resolver);
|
|
76
|
+
|
|
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();
|
|
85
|
+
copyWgpuRenderStateRegistrations(offscreen, screen);
|
|
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
|
+
);
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
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
|
+
|
|
156
|
+
it('shares device resources while snapshotting independent registration policy', async () => {
|
|
157
|
+
const screen = await createWgpuRenderStateForTest();
|
|
158
|
+
const renderer = { createData: () => null, submit: vi.fn() };
|
|
159
|
+
const materialRenderer = { instanceFloatCount: 0, getShaderModule: vi.fn() } as never;
|
|
160
|
+
const paddingResolver = vi.fn(() => ({ bottom: 1, left: 1, right: 1, top: 1 }));
|
|
161
|
+
const textureResolver = vi.fn(() => null);
|
|
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();
|
|
171
|
+
registerRenderer(screen, 'acme.Node', renderer);
|
|
172
|
+
registerWgpuMaterialRenderer(screen, 'acme.Material', materialRenderer);
|
|
173
|
+
registerWgpuTextureResolver(screen, 'acme.Texture', textureResolver);
|
|
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
|
+
);
|
|
200
|
+
getWgpuRenderStateRuntime(screen).wgpuRenderTextureCache = new WeakMap();
|
|
201
|
+
|
|
202
|
+
const offscreen = createWgpuOffscreenRenderState(screen);
|
|
203
|
+
const screenRuntime = getWgpuRenderStateRuntime(screen);
|
|
204
|
+
const offscreenRuntime = getWgpuRenderStateRuntime(offscreen);
|
|
205
|
+
|
|
206
|
+
expect(offscreen.device).toBe(screen.device);
|
|
207
|
+
expect(offscreen.canvas).toBe(screen.canvas);
|
|
208
|
+
expect(offscreenRuntime.pipelineCache).toBe(screenRuntime.pipelineCache);
|
|
209
|
+
expect(offscreenRuntime.textureCache).toBe(screenRuntime.textureCache);
|
|
210
|
+
expect(offscreenRuntime.wgpuRenderTextureCache).toBe(screenRuntime.wgpuRenderTextureCache);
|
|
211
|
+
expect(offscreenRuntime.uniformBindGroupLayout).toBe(screenRuntime.uniformBindGroupLayout);
|
|
212
|
+
expect(offscreenRuntime.uniformBuffer).not.toBe(screenRuntime.uniformBuffer);
|
|
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,
|
|
258
|
+
);
|
|
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);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it('owns an independent encoder and proxy tree', async () => {
|
|
281
|
+
const screen = await createWgpuRenderStateForTest();
|
|
282
|
+
const offscreen = createWgpuOffscreenRenderState(screen);
|
|
283
|
+
const root = createDisplayObject();
|
|
284
|
+
registerRenderer(offscreen, root.kind, { createData: () => createEntity({}), submit: vi.fn() });
|
|
285
|
+
prepareScene2DRender(offscreen, root);
|
|
286
|
+
beginWgpuFrame(screen);
|
|
287
|
+
beginWgpuFrame(offscreen);
|
|
288
|
+
|
|
289
|
+
expect(getRenderStateRuntime(offscreen).renderProxyMap).not.toBe(getRenderStateRuntime(screen).renderProxyMap);
|
|
290
|
+
expect(getWgpuRenderStateRuntime(offscreen).commandEncoder).not.toBe(
|
|
291
|
+
getWgpuRenderStateRuntime(screen).commandEncoder,
|
|
292
|
+
);
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it('destroys derived renderer data and its own uniform ring without freeing the screen ring', async () => {
|
|
296
|
+
const screen = await createWgpuRenderStateForTest();
|
|
297
|
+
const root = createDisplayObject();
|
|
298
|
+
const destroyData = vi.fn();
|
|
299
|
+
registerRenderer(screen, root.kind, {
|
|
300
|
+
createData: () => createEntity({}),
|
|
301
|
+
destroyData,
|
|
302
|
+
submit: vi.fn(),
|
|
303
|
+
});
|
|
304
|
+
const offscreen = createWgpuOffscreenRenderState(screen);
|
|
305
|
+
prepareScene2DRender(offscreen, root);
|
|
306
|
+
const screenDestroy = vi.spyOn(getWgpuRenderStateRuntime(screen).uniformBuffer, 'destroy');
|
|
307
|
+
const offscreenDestroy = vi.spyOn(getWgpuRenderStateRuntime(offscreen).uniformBuffer, 'destroy');
|
|
308
|
+
|
|
309
|
+
destroyWgpuRenderState(offscreen);
|
|
310
|
+
|
|
311
|
+
expect(destroyData).toHaveBeenCalledOnce();
|
|
312
|
+
expect(offscreenDestroy).toHaveBeenCalledOnce();
|
|
313
|
+
expect(screenDestroy).not.toHaveBeenCalled();
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
it('requires explicit re-copy for renderers and padding registered after derivation', async () => {
|
|
317
|
+
const screen = await createWgpuRenderStateForTest();
|
|
318
|
+
const offscreen = createWgpuOffscreenRenderState(screen);
|
|
319
|
+
const renderer = { createData: () => null, submit: vi.fn() };
|
|
320
|
+
const paddingResolver = vi.fn(() => ({ bottom: 2, left: 2, right: 2, top: 2 }));
|
|
321
|
+
registerRenderer(screen, 'acme.LateNode', renderer);
|
|
322
|
+
registerPaddingResolver(screen, 'acme.LateEffect', paddingResolver);
|
|
323
|
+
|
|
324
|
+
expect(hasRegistryTableEntry(getRenderStateRuntime(offscreen).registries.renderers, 'acme.LateNode')).toBe(false);
|
|
325
|
+
expect(getPaddingResolver(offscreen, 'acme.LateEffect')).toBeNull();
|
|
326
|
+
|
|
327
|
+
copyAllRenderersFromRenderState(offscreen, screen);
|
|
328
|
+
copyWgpuRenderStateRegistrations(offscreen, screen);
|
|
329
|
+
expect(getRegistryTableEntry(getRenderStateRuntime(offscreen).registries.renderers, 'acme.LateNode')).toBe(
|
|
330
|
+
renderer,
|
|
331
|
+
);
|
|
332
|
+
expect(getPaddingResolver(offscreen, 'acme.LateEffect')).toBe(paddingResolver);
|
|
333
|
+
});
|
|
334
|
+
});
|
|
335
|
+
|
|
14
336
|
describe('createWgpuRenderState', () => {
|
|
15
337
|
it('returns a render state with device and context', async () => {
|
|
16
338
|
const state = await createWgpuRenderStateForTest();
|
|
@@ -45,9 +367,70 @@ describe('createWgpuRenderState', () => {
|
|
|
45
367
|
});
|
|
46
368
|
|
|
47
369
|
describe('createWgpuRenderStateRuntime', () => {
|
|
48
|
-
it('returns a runtime
|
|
370
|
+
it('returns a runtime with the base binding slot and empty named registration tables', () => {
|
|
49
371
|
const runtime = createWgpuRenderStateRuntime();
|
|
50
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);
|
|
51
434
|
});
|
|
52
435
|
});
|
|
53
436
|
|
|
@@ -68,6 +451,55 @@ describe('destroyWgpuRenderState', () => {
|
|
|
68
451
|
});
|
|
69
452
|
});
|
|
70
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
|
+
|
|
71
503
|
describe('getWgpuRenderStateRuntime', () => {
|
|
72
504
|
it('returns the runtime attached by createWgpuRenderState', async () => {
|
|
73
505
|
const state = await createWgpuRenderStateForTest();
|
|
@@ -85,8 +517,8 @@ describe('getWgpuRenderStateRuntime', () => {
|
|
|
85
517
|
describe('getWgpuSampler', () => {
|
|
86
518
|
it('caches a sampler per filter+wrap+mip+anisotropy config and reuses it (one createSampler)', async () => {
|
|
87
519
|
const state = await createWgpuRenderStateForTest();
|
|
88
|
-
const a = getWgpuSampler(state, 'linear', 'repeat', 'repeat');
|
|
89
|
-
const b = getWgpuSampler(state, 'linear', 'repeat', 'repeat');
|
|
520
|
+
const a = getWgpuSampler(state, 'linear', 'linear', 'repeat', 'repeat');
|
|
521
|
+
const b = getWgpuSampler(state, 'linear', 'linear', 'repeat', 'repeat');
|
|
90
522
|
expect(a).toBe(b);
|
|
91
523
|
// The cache is keyed by a packed NUMBER (no per-call string allocation), and one config caches once.
|
|
92
524
|
expect([...getWgpuRenderStateRuntime(state).samplerCache.keys()].every((k) => typeof k === 'number')).toBe(true);
|
|
@@ -95,17 +527,17 @@ describe('getWgpuSampler', () => {
|
|
|
95
527
|
|
|
96
528
|
it('returns a distinct sampler for a different wrap or filter', async () => {
|
|
97
529
|
const state = await createWgpuRenderStateForTest();
|
|
98
|
-
const repeat = getWgpuSampler(state, 'linear', 'repeat', 'repeat');
|
|
99
|
-
const clamp = getWgpuSampler(state, 'linear', 'clamp-to-edge', 'clamp-to-edge');
|
|
100
|
-
const nearest = getWgpuSampler(state, 'nearest', 'repeat', 'repeat');
|
|
530
|
+
const repeat = getWgpuSampler(state, 'linear', 'linear', 'repeat', 'repeat');
|
|
531
|
+
const clamp = getWgpuSampler(state, 'linear', 'linear', 'clamp-to-edge', 'clamp-to-edge');
|
|
532
|
+
const nearest = getWgpuSampler(state, 'nearest', 'nearest', 'repeat', 'repeat');
|
|
101
533
|
expect(repeat).not.toBe(clamp);
|
|
102
534
|
expect(repeat).not.toBe(nearest);
|
|
103
535
|
});
|
|
104
536
|
|
|
105
537
|
it('keys the mip filter separately so a trilinear sampler differs from a non-mip one', async () => {
|
|
106
538
|
const state = await createWgpuRenderStateForTest();
|
|
107
|
-
const noMip = getWgpuSampler(state, 'linear', 'repeat', 'repeat');
|
|
108
|
-
const trilinear = getWgpuSampler(state, 'linear', 'repeat', 'repeat', 'linear');
|
|
539
|
+
const noMip = getWgpuSampler(state, 'linear', 'linear', 'repeat', 'repeat');
|
|
540
|
+
const trilinear = getWgpuSampler(state, 'linear', 'linear', 'repeat', 'repeat', 'linear');
|
|
109
541
|
expect(noMip).not.toBe(trilinear);
|
|
110
542
|
expect(getWgpuRenderStateRuntime(state).samplerCache.size).toBe(2);
|
|
111
543
|
});
|
|
@@ -114,18 +546,27 @@ describe('getWgpuSampler', () => {
|
|
|
114
546
|
// WebGPU rejects maxAnisotropy > 1 unless min/mag/mip are all linear, so a nearest+aniso request
|
|
115
547
|
// collapses to the SAME sampler as the explicit linear/trilinear anisotropic request.
|
|
116
548
|
const state = await createWgpuRenderStateForTest();
|
|
117
|
-
const collapsed = getWgpuSampler(state, 'nearest', 'clamp-to-edge', 'clamp-to-edge', undefined, 8);
|
|
118
|
-
const explicit = getWgpuSampler(state, 'linear', 'clamp-to-edge', 'clamp-to-edge', 'linear', 8);
|
|
549
|
+
const collapsed = getWgpuSampler(state, 'nearest', 'nearest', 'clamp-to-edge', 'clamp-to-edge', undefined, 8);
|
|
550
|
+
const explicit = getWgpuSampler(state, 'linear', 'linear', 'clamp-to-edge', 'clamp-to-edge', 'linear', 8);
|
|
119
551
|
expect(collapsed).toBe(explicit);
|
|
120
552
|
});
|
|
121
553
|
|
|
122
554
|
it('floors and clamps the anisotropy level into the cache key', async () => {
|
|
123
555
|
const state = await createWgpuRenderStateForTest();
|
|
124
|
-
const a = getWgpuSampler(state, 'linear', 'repeat', 'repeat', 'linear', 4.9);
|
|
125
|
-
const b = getWgpuSampler(state, 'linear', 'repeat', 'repeat', 'linear', 4);
|
|
556
|
+
const a = getWgpuSampler(state, 'linear', 'linear', 'repeat', 'repeat', 'linear', 4.9);
|
|
557
|
+
const b = getWgpuSampler(state, 'linear', 'linear', 'repeat', 'repeat', 'linear', 4);
|
|
126
558
|
expect(a).toBe(b);
|
|
127
559
|
expect(getWgpuRenderStateRuntime(state).samplerCache.size).toBe(1);
|
|
128
560
|
});
|
|
561
|
+
|
|
562
|
+
it('keeps independent minification and magnification filters', async () => {
|
|
563
|
+
const state = await createWgpuRenderStateForTest();
|
|
564
|
+
const createSampler = vi.spyOn(state.device, 'createSampler');
|
|
565
|
+
getWgpuSampler(state, 'nearest', 'linear', 'repeat', 'repeat');
|
|
566
|
+
expect(createSampler).toHaveBeenLastCalledWith(
|
|
567
|
+
expect.objectContaining({ minFilter: 'nearest', magFilter: 'linear' }),
|
|
568
|
+
);
|
|
569
|
+
});
|
|
129
570
|
});
|
|
130
571
|
|
|
131
572
|
describe('isWgpuSupported', () => {
|
|
@@ -133,3 +574,12 @@ describe('isWgpuSupported', () => {
|
|
|
133
574
|
expect(isWgpuSupported()).toBe(true);
|
|
134
575
|
});
|
|
135
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
|
+
});
|
|
@@ -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);
|