@flighthq/scene2d-wgpu 0.5.1-next.650.5d3df0b → 0.5.1-next.658.9cb44d7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/package.json +20 -20
- package/src/wgpuCache.test.ts +161 -182
- package/src/wgpuMeshShapeRenderer.test.ts +15 -13
- package/src/wgpuRasterShapeRenderer.test.ts +14 -13
- package/src/wgpuShape.test.ts +16 -17
- package/src/wgpuTextLabel.test.ts +21 -27
package/README.md
CHANGED
|
@@ -13,5 +13,5 @@ Import the supported application-facing API from `@flighthq/scene2d-wgpu`. The `
|
|
|
13
13
|
This package is part of the locked-version Flight SDK graph. Applications may instead install and import `@flighthq/sdk` when package-level tree shaking is sufficient.
|
|
14
14
|
|
|
15
15
|
- [Flight project](https://github.com/flighthq/flight)
|
|
16
|
-
- [Source for @flighthq/scene2d-wgpu@0.5.1-next.
|
|
17
|
-
- [License](https://github.com/flighthq/flight/blob/
|
|
16
|
+
- [Source for @flighthq/scene2d-wgpu@0.5.1-next.658.9cb44d7](https://github.com/flighthq/flight/tree/9cb44d76b981adad2503d5e62207be99adb35c55/packages/scene2d-wgpu)
|
|
17
|
+
- [License](https://github.com/flighthq/flight/blob/9cb44d76b981adad2503d5e62207be99adb35c55/LICENSE.md)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flighthq/scene2d-wgpu",
|
|
3
|
-
"version": "0.5.1-next.
|
|
3
|
+
"version": "0.5.1-next.658.9cb44d7",
|
|
4
4
|
"author": "Joshua Granick and other contributors",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -38,25 +38,25 @@
|
|
|
38
38
|
"clean:dist": "tsx ../../scripts/clean-package-dist.ts"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@flighthq/color": "0.5.1-next.
|
|
42
|
-
"@flighthq/entity": "0.5.1-next.
|
|
43
|
-
"@flighthq/scene2d": "0.5.1-next.
|
|
44
|
-
"@flighthq/scene2d-canvas": "0.5.1-next.
|
|
45
|
-
"@flighthq/geometry": "0.5.1-next.
|
|
46
|
-
"@flighthq/image": "0.5.1-next.
|
|
47
|
-
"@flighthq/log": "0.5.1-next.
|
|
48
|
-
"@flighthq/node": "0.5.1-next.
|
|
49
|
-
"@flighthq/path": "0.5.1-next.
|
|
50
|
-
"@flighthq/registry": "0.5.1-next.
|
|
51
|
-
"@flighthq/render": "0.5.1-next.
|
|
52
|
-
"@flighthq/render-wgpu": "0.5.1-next.
|
|
53
|
-
"@flighthq/shape": "0.5.1-next.
|
|
54
|
-
"@flighthq/text": "0.5.1-next.
|
|
55
|
-
"@flighthq/textinput": "0.5.1-next.
|
|
56
|
-
"@flighthq/textlayout": "0.5.1-next.
|
|
57
|
-
"@flighthq/texture": "0.5.1-next.
|
|
58
|
-
"@flighthq/types": "0.5.1-next.
|
|
59
|
-
"@flighthq/velocity": "0.5.1-next.
|
|
41
|
+
"@flighthq/color": "0.5.1-next.658.9cb44d7",
|
|
42
|
+
"@flighthq/entity": "0.5.1-next.658.9cb44d7",
|
|
43
|
+
"@flighthq/scene2d": "0.5.1-next.658.9cb44d7",
|
|
44
|
+
"@flighthq/scene2d-canvas": "0.5.1-next.658.9cb44d7",
|
|
45
|
+
"@flighthq/geometry": "0.5.1-next.658.9cb44d7",
|
|
46
|
+
"@flighthq/image": "0.5.1-next.658.9cb44d7",
|
|
47
|
+
"@flighthq/log": "0.5.1-next.658.9cb44d7",
|
|
48
|
+
"@flighthq/node": "0.5.1-next.658.9cb44d7",
|
|
49
|
+
"@flighthq/path": "0.5.1-next.658.9cb44d7",
|
|
50
|
+
"@flighthq/registry": "0.5.1-next.658.9cb44d7",
|
|
51
|
+
"@flighthq/render": "0.5.1-next.658.9cb44d7",
|
|
52
|
+
"@flighthq/render-wgpu": "0.5.1-next.658.9cb44d7",
|
|
53
|
+
"@flighthq/shape": "0.5.1-next.658.9cb44d7",
|
|
54
|
+
"@flighthq/text": "0.5.1-next.658.9cb44d7",
|
|
55
|
+
"@flighthq/textinput": "0.5.1-next.658.9cb44d7",
|
|
56
|
+
"@flighthq/textlayout": "0.5.1-next.658.9cb44d7",
|
|
57
|
+
"@flighthq/texture": "0.5.1-next.658.9cb44d7",
|
|
58
|
+
"@flighthq/types": "0.5.1-next.658.9cb44d7",
|
|
59
|
+
"@flighthq/velocity": "0.5.1-next.658.9cb44d7"
|
|
60
60
|
},
|
|
61
61
|
"peerDependencies": {
|
|
62
62
|
"@webgpu/types": "^0.1.70"
|
package/src/wgpuCache.test.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createMatrix } from '@flighthq/geometry/contract';
|
|
2
2
|
import { getRegistryTableEntry } from '@flighthq/registry/contract';
|
|
3
|
-
import
|
|
3
|
+
import * as renderWgpu from '@flighthq/render-wgpu/contract';
|
|
4
4
|
import {
|
|
5
5
|
createRenderCache,
|
|
6
6
|
createRenderState,
|
|
@@ -21,138 +21,6 @@ import type {
|
|
|
21
21
|
} from '@flighthq/types/contract';
|
|
22
22
|
import { EntityRuntimeKey, RegistryEntryState } from '@flighthq/types/contract';
|
|
23
23
|
|
|
24
|
-
import type * as WgpuNode2DModule from './wgpuNode2D';
|
|
25
|
-
import type * as WgpuQuadBatchWriterModule from './wgpuQuadBatchWriter';
|
|
26
|
-
|
|
27
|
-
// The GPU render-target lifecycle (@flighthq/render-wgpu) and the two local collaborators
|
|
28
|
-
// ./wgpuQuadBatchWriter and ./wgpuNode2D are stubbed so cache orchestration can be unit-tested without a
|
|
29
|
-
// real GPU pipeline: createWgpuRenderTarget returns a plain descriptor, and the composite, batch-flush
|
|
30
|
-
// and subtree-render calls become spies for the call and ordering assertions below.
|
|
31
|
-
//
|
|
32
|
-
// ★ HOISTED MOCKS, NOT HAND-ROLLED ONES. This file is in REGISTRY_ISOLATED_TESTS, so it already runs with
|
|
33
|
-
// its own module registry — the hermeticity the `scopeModuleMocks` + `vi.doMock` + dynamic-import dance
|
|
34
|
-
// bought by hand comes from the platform here, with no hook, and these stubs cannot reach the real
|
|
35
|
-
// render-wgpu / scene2d-wgpu consumers. The dance was not merely redundant: it rebuilt the subject's
|
|
36
|
-
// entire transitive module graph inside a FIXED `beforeAll` deadline — three modules plus everything they
|
|
37
|
-
// import — which is unbounded work against a fixed clock and the shape of flake tiering exists to remove.
|
|
38
|
-
//
|
|
39
|
-
// ★ THE FRAME STUBS REACH THROUGH `actual`, NOT THROUGH AN OUTER BINDING. A hoisted factory runs during
|
|
40
|
-
// this file's own import phase, so closing over a module-scope `const` declared below it would be a
|
|
41
|
-
// temporal-dead-zone trap that only fires once the import order changes. `actual` is in hand already.
|
|
42
|
-
vi.mock('./wgpuQuadBatchWriter', async (importOriginal) => {
|
|
43
|
-
const actual = await importOriginal<typeof WgpuQuadBatchWriterModule>();
|
|
44
|
-
return { ...actual, flushWgpuQuadBatchWriter: vi.fn() };
|
|
45
|
-
});
|
|
46
|
-
vi.mock('@flighthq/render-wgpu/contract', async (importOriginal) => {
|
|
47
|
-
const actual = await importOriginal<typeof WgpuRenderWgpuModule>();
|
|
48
|
-
const render = await import('@flighthq/render/contract');
|
|
49
|
-
const types = await import('@flighthq/types/contract');
|
|
50
|
-
const beginWgpuFrame = vi.fn((state: WgpuRenderState) => {
|
|
51
|
-
actual.getWgpuRenderStateRuntime(state).commandEncoder = {} as GPUCommandEncoder;
|
|
52
|
-
});
|
|
53
|
-
const submitWgpuRenderPass = vi.fn((state: WgpuRenderState) => {
|
|
54
|
-
actual.getWgpuRenderStateRuntime(state).commandEncoder = null;
|
|
55
|
-
});
|
|
56
|
-
return {
|
|
57
|
-
...actual,
|
|
58
|
-
beginWgpuFrame,
|
|
59
|
-
beginWgpuRenderPass: vi.fn(),
|
|
60
|
-
createWgpuOffscreenRenderState: vi.fn((deviceState, pipeline, options = {}) => {
|
|
61
|
-
const state = render.createRenderState({
|
|
62
|
-
allowSmoothing: options.imageSmoothingEnabled ?? true,
|
|
63
|
-
pixelRatio: options.pixelRatio ?? 1,
|
|
64
|
-
roundPixels: options.roundPixels ?? false,
|
|
65
|
-
sceneGraphSyncPolicy: options.sceneGraphSyncPolicy,
|
|
66
|
-
}) as WgpuRenderState;
|
|
67
|
-
Object.assign(state, {
|
|
68
|
-
applyBlendMode: null,
|
|
69
|
-
device: deviceState.device,
|
|
70
|
-
deviceState,
|
|
71
|
-
format: options.format ?? 'bgra8unorm',
|
|
72
|
-
pipeline,
|
|
73
|
-
});
|
|
74
|
-
const runtime = actual.createWgpuRenderStateRuntime(deviceState, pipeline);
|
|
75
|
-
state[types.EntityRuntimeKey] = runtime;
|
|
76
|
-
Object.assign(runtime, {
|
|
77
|
-
commandEncoder: null,
|
|
78
|
-
currentBlendMode: null,
|
|
79
|
-
currentMaskDepth: 0,
|
|
80
|
-
currentRenderTarget: null,
|
|
81
|
-
depthStencilTexture: null,
|
|
82
|
-
particleInstanceBuffer: null,
|
|
83
|
-
quadBatchWriterBufferPool: [],
|
|
84
|
-
renderPass: null,
|
|
85
|
-
retiredBuffers: [],
|
|
86
|
-
retiredTextures: [],
|
|
87
|
-
surfaceAntialiasTexture: null,
|
|
88
|
-
uniformBuffer: { destroy: vi.fn() } as unknown as GPUBuffer,
|
|
89
|
-
uniformData: new Float32Array(0),
|
|
90
|
-
uniformOffset: 0,
|
|
91
|
-
});
|
|
92
|
-
return state;
|
|
93
|
-
}),
|
|
94
|
-
setWgpuRenderTransform2D: vi.fn(),
|
|
95
|
-
createWgpuRenderTarget: vi.fn(
|
|
96
|
-
(_state: unknown, width: number, height: number): WgpuRenderTarget => ({
|
|
97
|
-
bindings: new Map(),
|
|
98
|
-
mipLevelCount: 1,
|
|
99
|
-
colorSpace: 'srgb',
|
|
100
|
-
depthStencilTexture: {} as GPUTexture,
|
|
101
|
-
depthStencilView: {} as GPUTextureView,
|
|
102
|
-
texture: {} as GPUTexture,
|
|
103
|
-
view: {} as GPUTextureView,
|
|
104
|
-
format: 'bgra8unorm',
|
|
105
|
-
sampleCount: 1,
|
|
106
|
-
clearColors: [],
|
|
107
|
-
clearDepth: 1,
|
|
108
|
-
width,
|
|
109
|
-
height,
|
|
110
|
-
}),
|
|
111
|
-
),
|
|
112
|
-
destroyWgpuRenderTarget: vi.fn(),
|
|
113
|
-
drawWgpuRenderTargetResult: vi.fn(),
|
|
114
|
-
endWgpuRenderPass: vi.fn(),
|
|
115
|
-
resizeWgpuRenderTarget: vi.fn((_state: unknown, target: WgpuRenderTarget, width: number, height: number) => {
|
|
116
|
-
target.width = width;
|
|
117
|
-
target.height = height;
|
|
118
|
-
}),
|
|
119
|
-
submitWgpuRenderPass,
|
|
120
|
-
withWgpuFrameBorrow: vi.fn((owner: WgpuRenderState, borrower: WgpuRenderState, callback: () => unknown) => {
|
|
121
|
-
const ownerRuntime = actual.getWgpuRenderStateRuntime(owner);
|
|
122
|
-
const borrowerRuntime = actual.getWgpuRenderStateRuntime(borrower);
|
|
123
|
-
const ownsFrame = ownerRuntime.commandEncoder === null;
|
|
124
|
-
if (ownsFrame) beginWgpuFrame(owner);
|
|
125
|
-
borrowerRuntime.commandEncoder = ownerRuntime.commandEncoder;
|
|
126
|
-
try {
|
|
127
|
-
return callback();
|
|
128
|
-
} finally {
|
|
129
|
-
borrowerRuntime.commandEncoder = null;
|
|
130
|
-
if (ownsFrame) submitWgpuRenderPass(owner);
|
|
131
|
-
}
|
|
132
|
-
}),
|
|
133
|
-
};
|
|
134
|
-
});
|
|
135
|
-
vi.mock('./wgpuNode2D', async (importOriginal) => {
|
|
136
|
-
const actual = await importOriginal<typeof WgpuNode2DModule>();
|
|
137
|
-
return { ...actual, renderWgpuScene2D: vi.fn() };
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
import {
|
|
141
|
-
beginWgpuFrame,
|
|
142
|
-
createEmptyWgpuRegistries,
|
|
143
|
-
createWgpuDeviceState,
|
|
144
|
-
createWgpuPipeline,
|
|
145
|
-
createWgpuRenderStateRuntime,
|
|
146
|
-
destroyWgpuRenderState,
|
|
147
|
-
destroyWgpuRenderTarget,
|
|
148
|
-
drawWgpuRenderTargetResult,
|
|
149
|
-
getWgpuMaterialRenderer,
|
|
150
|
-
getWgpuRenderStateRuntime,
|
|
151
|
-
registerWgpuMaterialRenderer,
|
|
152
|
-
resolveWgpuApplyBlendMode,
|
|
153
|
-
submitWgpuRenderPass,
|
|
154
|
-
} from '@flighthq/render-wgpu/contract';
|
|
155
|
-
|
|
156
24
|
import {
|
|
157
25
|
createWgpuCacheState,
|
|
158
26
|
defaultWgpuRenderCacheRenderer,
|
|
@@ -162,14 +30,120 @@ import {
|
|
|
162
30
|
refreshWgpuRenderCache,
|
|
163
31
|
releaseWgpuRenderCache,
|
|
164
32
|
} from './wgpuCache';
|
|
165
|
-
import
|
|
166
|
-
import
|
|
33
|
+
import * as wgpuNode2D from './wgpuNode2D';
|
|
34
|
+
import * as wgpuQuadBatchWriter from './wgpuQuadBatchWriter';
|
|
35
|
+
|
|
36
|
+
// The GPU render-target lifecycle (@flighthq/render-wgpu) and the two local collaborators
|
|
37
|
+
// ./wgpuQuadBatchWriter and ./wgpuNode2D are stubbed so cache orchestration can be unit-tested without a
|
|
38
|
+
// real GPU pipeline: createWgpuRenderTarget returns a plain descriptor, and the composite, batch-flush
|
|
39
|
+
// and subtree-render calls become spies for the call and ordering assertions below.
|
|
40
|
+
beforeEach(() => {
|
|
41
|
+
vi.spyOn(wgpuQuadBatchWriter, 'flushWgpuQuadBatchWriter').mockImplementation((() => {}) as never);
|
|
42
|
+
|
|
43
|
+
const beginWgpuFrameSpy = vi.spyOn(renderWgpu, 'beginWgpuFrame').mockImplementation(((state: WgpuRenderState) => {
|
|
44
|
+
renderWgpu.getWgpuRenderStateRuntime(state).commandEncoder = {} as GPUCommandEncoder;
|
|
45
|
+
}) as never);
|
|
46
|
+
const submitWgpuRenderPassSpy = vi.spyOn(renderWgpu, 'submitWgpuRenderPass').mockImplementation(((
|
|
47
|
+
state: WgpuRenderState,
|
|
48
|
+
) => {
|
|
49
|
+
renderWgpu.getWgpuRenderStateRuntime(state).commandEncoder = null;
|
|
50
|
+
}) as never);
|
|
51
|
+
vi.spyOn(renderWgpu, 'beginWgpuRenderPass').mockImplementation((() => {}) as never);
|
|
52
|
+
vi.spyOn(renderWgpu, 'createWgpuOffscreenRenderState').mockImplementation(((
|
|
53
|
+
deviceState: any,
|
|
54
|
+
pipeline: any,
|
|
55
|
+
options: any = {},
|
|
56
|
+
) => {
|
|
57
|
+
const state = createRenderState({
|
|
58
|
+
allowSmoothing: options.imageSmoothingEnabled ?? true,
|
|
59
|
+
pixelRatio: options.pixelRatio ?? 1,
|
|
60
|
+
roundPixels: options.roundPixels ?? false,
|
|
61
|
+
sceneGraphSyncPolicy: options.sceneGraphSyncPolicy,
|
|
62
|
+
}) as WgpuRenderState;
|
|
63
|
+
Object.assign(state, {
|
|
64
|
+
applyBlendMode: null,
|
|
65
|
+
device: deviceState.device,
|
|
66
|
+
deviceState,
|
|
67
|
+
format: options.format ?? 'bgra8unorm',
|
|
68
|
+
pipeline,
|
|
69
|
+
});
|
|
70
|
+
const runtime = renderWgpu.createWgpuRenderStateRuntime(deviceState, pipeline);
|
|
71
|
+
state[EntityRuntimeKey] = runtime;
|
|
72
|
+
Object.assign(runtime, {
|
|
73
|
+
commandEncoder: null,
|
|
74
|
+
currentBlendMode: null,
|
|
75
|
+
currentMaskDepth: 0,
|
|
76
|
+
currentRenderTarget: null,
|
|
77
|
+
depthStencilTexture: null,
|
|
78
|
+
particleInstanceBuffer: null,
|
|
79
|
+
quadBatchWriterBufferPool: [],
|
|
80
|
+
renderPass: null,
|
|
81
|
+
retiredBuffers: [],
|
|
82
|
+
retiredTextures: [],
|
|
83
|
+
surfaceAntialiasTexture: null,
|
|
84
|
+
uniformBuffer: { destroy: vi.fn() } as unknown as GPUBuffer,
|
|
85
|
+
uniformData: new Float32Array(0),
|
|
86
|
+
uniformOffset: 0,
|
|
87
|
+
});
|
|
88
|
+
return state;
|
|
89
|
+
}) as never);
|
|
90
|
+
vi.spyOn(renderWgpu, 'setWgpuRenderTransform2D').mockImplementation((() => {}) as never);
|
|
91
|
+
vi.spyOn(renderWgpu, 'createWgpuRenderTarget').mockImplementation(
|
|
92
|
+
((_state: unknown, width: number, height: number): WgpuRenderTarget => ({
|
|
93
|
+
bindings: new Map(),
|
|
94
|
+
mipLevelCount: 1,
|
|
95
|
+
colorSpace: 'srgb',
|
|
96
|
+
depthStencilTexture: {} as GPUTexture,
|
|
97
|
+
depthStencilView: {} as GPUTextureView,
|
|
98
|
+
texture: {} as GPUTexture,
|
|
99
|
+
view: {} as GPUTextureView,
|
|
100
|
+
format: 'bgra8unorm',
|
|
101
|
+
sampleCount: 1,
|
|
102
|
+
clearColors: [],
|
|
103
|
+
clearDepth: 1,
|
|
104
|
+
width,
|
|
105
|
+
height,
|
|
106
|
+
})) as never,
|
|
107
|
+
);
|
|
108
|
+
vi.spyOn(renderWgpu, 'destroyWgpuRenderTarget').mockImplementation((() => {}) as never);
|
|
109
|
+
vi.spyOn(renderWgpu, 'drawWgpuRenderTargetResult').mockImplementation((() => {}) as never);
|
|
110
|
+
vi.spyOn(renderWgpu, 'endWgpuRenderPass').mockImplementation((() => {}) as never);
|
|
111
|
+
vi.spyOn(renderWgpu, 'resizeWgpuRenderTarget').mockImplementation(((
|
|
112
|
+
_state: unknown,
|
|
113
|
+
target: WgpuRenderTarget,
|
|
114
|
+
width: number,
|
|
115
|
+
height: number,
|
|
116
|
+
) => {
|
|
117
|
+
target.width = width;
|
|
118
|
+
target.height = height;
|
|
119
|
+
}) as never);
|
|
120
|
+
vi.spyOn(renderWgpu, 'withWgpuFrameBorrow').mockImplementation(((
|
|
121
|
+
owner: WgpuPresentationRenderState,
|
|
122
|
+
borrower: WgpuRenderState,
|
|
123
|
+
callback: () => unknown,
|
|
124
|
+
) => {
|
|
125
|
+
const ownerRuntime = renderWgpu.getWgpuRenderStateRuntime(owner);
|
|
126
|
+
const borrowerRuntime = renderWgpu.getWgpuRenderStateRuntime(borrower);
|
|
127
|
+
const ownsFrame = ownerRuntime.commandEncoder === null;
|
|
128
|
+
if (ownsFrame) beginWgpuFrameSpy(owner);
|
|
129
|
+
borrowerRuntime.commandEncoder = ownerRuntime.commandEncoder;
|
|
130
|
+
try {
|
|
131
|
+
return callback();
|
|
132
|
+
} finally {
|
|
133
|
+
borrowerRuntime.commandEncoder = null;
|
|
134
|
+
if (ownsFrame) submitWgpuRenderPassSpy(owner);
|
|
135
|
+
}
|
|
136
|
+
}) as never);
|
|
137
|
+
vi.spyOn(wgpuNode2D, 'renderWgpuScene2D').mockImplementation((() => {}) as never);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
afterEach(() => vi.restoreAllMocks());
|
|
167
141
|
|
|
168
142
|
function fakeScreen(options = {}): WgpuPresentationRenderState {
|
|
169
143
|
const state = createRenderState(options) as unknown as WgpuPresentationRenderState;
|
|
170
144
|
const device = {} as GPUDevice;
|
|
171
|
-
const deviceState = createWgpuDeviceState(device);
|
|
172
|
-
const pipeline = createWgpuPipeline(createEmptyWgpuRegistries());
|
|
145
|
+
const deviceState = renderWgpu.createWgpuDeviceState(device);
|
|
146
|
+
const pipeline = renderWgpu.createWgpuPipeline(renderWgpu.createEmptyWgpuRegistries());
|
|
173
147
|
Object.assign(state, {
|
|
174
148
|
context: {} as GPUCanvasContext,
|
|
175
149
|
device,
|
|
@@ -178,8 +152,8 @@ function fakeScreen(options = {}): WgpuPresentationRenderState {
|
|
|
178
152
|
pipeline,
|
|
179
153
|
surface: { height: 600, width: 800 },
|
|
180
154
|
});
|
|
181
|
-
state[EntityRuntimeKey] = createWgpuRenderStateRuntime(deviceState, pipeline);
|
|
182
|
-
const runtime = getWgpuRenderStateRuntime(state);
|
|
155
|
+
state[EntityRuntimeKey] = renderWgpu.createWgpuRenderStateRuntime(deviceState, pipeline);
|
|
156
|
+
const runtime = renderWgpu.getWgpuRenderStateRuntime(state);
|
|
183
157
|
runtime.commandEncoder = null;
|
|
184
158
|
runtime.currentBlendMode = null;
|
|
185
159
|
return state;
|
|
@@ -189,7 +163,7 @@ function createCacheState(screen: WgpuPresentationRenderState): WgpuRenderState
|
|
|
189
163
|
return createWgpuCacheState(
|
|
190
164
|
screen,
|
|
191
165
|
screen.deviceState,
|
|
192
|
-
createWgpuPipeline(getWgpuRenderStateRuntime(screen).registries),
|
|
166
|
+
renderWgpu.createWgpuPipeline(renderWgpu.getWgpuRenderStateRuntime(screen).registries),
|
|
193
167
|
{
|
|
194
168
|
format: screen.format,
|
|
195
169
|
imageSmoothingEnabled: screen.allowSmoothing,
|
|
@@ -208,25 +182,25 @@ describe('createWgpuCacheState', () => {
|
|
|
208
182
|
it('does not destroy a uniform buffer owned by the screen state', () => {
|
|
209
183
|
const screen = fakeScreen();
|
|
210
184
|
const destroyUniformBuffer = vi.fn();
|
|
211
|
-
const screenRuntime = getWgpuRenderStateRuntime(screen);
|
|
185
|
+
const screenRuntime = renderWgpu.getWgpuRenderStateRuntime(screen);
|
|
212
186
|
screenRuntime.uniformBuffer = { destroy: destroyUniformBuffer } as unknown as GPUBuffer;
|
|
213
187
|
screenRuntime.quadBatchWriterBufferPool = [];
|
|
214
188
|
|
|
215
189
|
const cacheState = createCacheState(screen);
|
|
216
|
-
destroyWgpuRenderState(cacheState);
|
|
190
|
+
renderWgpu.destroyWgpuRenderState(cacheState);
|
|
217
191
|
|
|
218
192
|
expect(destroyUniformBuffer).not.toHaveBeenCalled();
|
|
219
193
|
});
|
|
220
194
|
|
|
221
195
|
it('does not retain the device tier when an owned cache state is left undisposed', () => {
|
|
222
196
|
const screen = fakeScreen();
|
|
223
|
-
const screenRuntime = getWgpuRenderStateRuntime(screen);
|
|
197
|
+
const screenRuntime = renderWgpu.getWgpuRenderStateRuntime(screen);
|
|
224
198
|
const teardown = vi.fn();
|
|
225
199
|
screenRuntime.context.teardowns.push(teardown);
|
|
226
200
|
screenRuntime.quadBatchWriterBufferPool = [];
|
|
227
201
|
|
|
228
202
|
createCacheState(screen);
|
|
229
|
-
destroyWgpuRenderState(screen);
|
|
203
|
+
renderWgpu.destroyWgpuRenderState(screen);
|
|
230
204
|
|
|
231
205
|
expect(teardown).toHaveBeenCalledOnce();
|
|
232
206
|
});
|
|
@@ -241,14 +215,14 @@ describe('createWgpuCacheState', () => {
|
|
|
241
215
|
|
|
242
216
|
expect(cacheState.applyBlendMode).toBeNull();
|
|
243
217
|
expect(Object.getOwnPropertyDescriptor(cacheState, 'applyBlendMode')?.get).toBeUndefined();
|
|
244
|
-
expect(resolveWgpuApplyBlendMode(cacheState)).toBeNull();
|
|
218
|
+
expect(renderWgpu.resolveWgpuApplyBlendMode(cacheState)).toBeNull();
|
|
245
219
|
screen.applyBlendMode = screenHook;
|
|
246
220
|
expect(cacheState.applyBlendMode).toBeNull();
|
|
247
|
-
expect(resolveWgpuApplyBlendMode(cacheState)).toBe(screenHook);
|
|
221
|
+
expect(renderWgpu.resolveWgpuApplyBlendMode(cacheState)).toBe(screenHook);
|
|
248
222
|
|
|
249
223
|
cacheState.applyBlendMode = cacheHook;
|
|
250
224
|
screen.applyBlendMode = laterScreenHook;
|
|
251
|
-
expect(resolveWgpuApplyBlendMode(cacheState)).toBe(cacheHook);
|
|
225
|
+
expect(renderWgpu.resolveWgpuApplyBlendMode(cacheState)).toBe(cacheHook);
|
|
252
226
|
});
|
|
253
227
|
|
|
254
228
|
it('copies renderers and shares the GPU device but keeps its own node map', () => {
|
|
@@ -262,7 +236,7 @@ describe('createWgpuCacheState', () => {
|
|
|
262
236
|
};
|
|
263
237
|
const colorAdjustmentFeatureGuard: WgpuColorAdjustmentMaterialFeatureGuard = vi.fn();
|
|
264
238
|
const renderRootGuard: RenderRootGuard = vi.fn();
|
|
265
|
-
const screenRuntime = getWgpuRenderStateRuntime(screen);
|
|
239
|
+
const screenRuntime = renderWgpu.getWgpuRenderStateRuntime(screen);
|
|
266
240
|
screenRuntime.registries.colorAdjustments = {
|
|
267
241
|
entry: { state: RegistryEntryState.Bound, value: resolver },
|
|
268
242
|
onMiss: 'Disabled',
|
|
@@ -290,21 +264,21 @@ describe('createWgpuCacheState', () => {
|
|
|
290
264
|
enableColorAdjustmentGuards(screen);
|
|
291
265
|
enableWgpuRenderCache(screen);
|
|
292
266
|
const cacheState = createCacheState(screen);
|
|
293
|
-
expect(getWgpuRenderStateRuntime(cacheState).registries.renderers.entries.get(RenderCacheKind)).toEqual({
|
|
267
|
+
expect(renderWgpu.getWgpuRenderStateRuntime(cacheState).registries.renderers.entries.get(RenderCacheKind)).toEqual({
|
|
294
268
|
state: 'bound',
|
|
295
269
|
value: defaultWgpuRenderCacheRenderer,
|
|
296
270
|
});
|
|
297
271
|
expect((cacheState as any).device).toBe((screen as any).device);
|
|
298
|
-
expect(getWgpuRenderStateRuntime(cacheState).renderProxyMap).not.toBe(
|
|
299
|
-
getWgpuRenderStateRuntime(screen).renderProxyMap,
|
|
272
|
+
expect(renderWgpu.getWgpuRenderStateRuntime(cacheState).renderProxyMap).not.toBe(
|
|
273
|
+
renderWgpu.getWgpuRenderStateRuntime(screen).renderProxyMap,
|
|
300
274
|
);
|
|
301
|
-
expect(getWgpuRenderStateRuntime(cacheState).registries.colorAdjustments).toBe(
|
|
275
|
+
expect(renderWgpu.getWgpuRenderStateRuntime(cacheState).registries.colorAdjustments).toBe(
|
|
302
276
|
screenRuntime.registries.colorAdjustments,
|
|
303
277
|
);
|
|
304
|
-
expect(getWgpuRenderStateRuntime(cacheState).registries.colorAdjustmentFeature).toBe(
|
|
278
|
+
expect(renderWgpu.getWgpuRenderStateRuntime(cacheState).registries.colorAdjustmentFeature).toBe(
|
|
305
279
|
screenRuntime.registries.colorAdjustmentFeature,
|
|
306
280
|
);
|
|
307
|
-
const cacheRuntime = getWgpuRenderStateRuntime(cacheState);
|
|
281
|
+
const cacheRuntime = renderWgpu.getWgpuRenderStateRuntime(cacheState);
|
|
308
282
|
expect(cacheRuntime.registries.colorAdjustmentFeatureGuard).toBe(
|
|
309
283
|
screenRuntime.registries.colorAdjustmentFeatureGuard,
|
|
310
284
|
);
|
|
@@ -343,11 +317,11 @@ describe('createWgpuCacheState', () => {
|
|
|
343
317
|
const screen = fakeScreen();
|
|
344
318
|
const first: WgpuMaterialRenderer = { instanceFloatCount: 0, getShaderModule: vi.fn() };
|
|
345
319
|
const replacement: WgpuMaterialRenderer = { instanceFloatCount: 0, getShaderModule: vi.fn() };
|
|
346
|
-
registerWgpuMaterialRenderer(screen, 'acme.Material', first);
|
|
320
|
+
renderWgpu.registerWgpuMaterialRenderer(screen, 'acme.Material', first);
|
|
347
321
|
|
|
348
322
|
const cacheState = createCacheState(screen);
|
|
349
|
-
const screenRuntime = getWgpuRenderStateRuntime(screen);
|
|
350
|
-
const cacheRuntime = getWgpuRenderStateRuntime(cacheState);
|
|
323
|
+
const screenRuntime = renderWgpu.getWgpuRenderStateRuntime(screen);
|
|
324
|
+
const cacheRuntime = renderWgpu.getWgpuRenderStateRuntime(cacheState);
|
|
351
325
|
expect(cacheRuntime.registries).not.toBe(screenRuntime.registries);
|
|
352
326
|
expect(cacheRuntime.registries.colorAdjustments).toBe(screenRuntime.registries.colorAdjustments);
|
|
353
327
|
expect(cacheRuntime.registries.compressedTextureDecoder).toBe(screenRuntime.registries.compressedTextureDecoder);
|
|
@@ -360,12 +334,12 @@ describe('createWgpuCacheState', () => {
|
|
|
360
334
|
expect(cacheRuntime.registries.strokeTessellator).toBe(screenRuntime.registries.strokeTessellator);
|
|
361
335
|
expect(cacheRuntime.registries.textureResolvers).toBe(screenRuntime.registries.textureResolvers);
|
|
362
336
|
expect(cacheRuntime.registries.velocityWriters).toBe(screenRuntime.registries.velocityWriters);
|
|
363
|
-
expect(getWgpuMaterialRenderer(cacheState, 'acme.Material')).toBe(first);
|
|
337
|
+
expect(renderWgpu.getWgpuMaterialRenderer(cacheState, 'acme.Material')).toBe(first);
|
|
364
338
|
|
|
365
|
-
registerWgpuMaterialRenderer(screen, 'acme.Material', replacement);
|
|
339
|
+
renderWgpu.registerWgpuMaterialRenderer(screen, 'acme.Material', replacement);
|
|
366
340
|
|
|
367
|
-
expect(getWgpuMaterialRenderer(screen, 'acme.Material')).toBe(replacement);
|
|
368
|
-
expect(getWgpuMaterialRenderer(cacheState, 'acme.Material')).toBe(first);
|
|
341
|
+
expect(renderWgpu.getWgpuMaterialRenderer(screen, 'acme.Material')).toBe(replacement);
|
|
342
|
+
expect(renderWgpu.getWgpuMaterialRenderer(cacheState, 'acme.Material')).toBe(first);
|
|
369
343
|
});
|
|
370
344
|
});
|
|
371
345
|
|
|
@@ -373,7 +347,7 @@ describe('defaultWgpuRenderCacheRenderer', () => {
|
|
|
373
347
|
it('does nothing when no cache is attached to the source', () => {
|
|
374
348
|
const state = fakeScreen();
|
|
375
349
|
defaultWgpuRenderCacheRenderer.submit(state, makeCacheNode(createDisplayObject()));
|
|
376
|
-
expect(drawWgpuRenderTargetResult).not.toHaveBeenCalled();
|
|
350
|
+
expect(renderWgpu.drawWgpuRenderTargetResult).not.toHaveBeenCalled();
|
|
377
351
|
});
|
|
378
352
|
|
|
379
353
|
it('composites the cache target attached to the source node', () => {
|
|
@@ -383,7 +357,12 @@ describe('defaultWgpuRenderCacheRenderer', () => {
|
|
|
383
357
|
useRenderCache(state, obj, cache);
|
|
384
358
|
const target = ensureWgpuRenderCacheTarget(state, cache, 16, 16);
|
|
385
359
|
defaultWgpuRenderCacheRenderer.submit(state, makeCacheNode(obj));
|
|
386
|
-
expect(drawWgpuRenderTargetResult).toHaveBeenCalledWith(
|
|
360
|
+
expect(renderWgpu.drawWgpuRenderTargetResult).toHaveBeenCalledWith(
|
|
361
|
+
state,
|
|
362
|
+
expect.anything(),
|
|
363
|
+
target,
|
|
364
|
+
expect.anything(),
|
|
365
|
+
);
|
|
387
366
|
});
|
|
388
367
|
|
|
389
368
|
it('flushes pending batched geometry before the immediate composite', () => {
|
|
@@ -396,9 +375,9 @@ describe('defaultWgpuRenderCacheRenderer', () => {
|
|
|
396
375
|
// The composite draws an immediate quad outside the quad-batch writer; geometry submitted earlier in
|
|
397
376
|
// the walk must be drained first, or the immediate quad interleaves with the un-flushed batch's
|
|
398
377
|
// instance buffer and bind-group state and corrupts it.
|
|
399
|
-
expect(flushWgpuQuadBatchWriter).toHaveBeenCalledWith(state);
|
|
400
|
-
expect((flushWgpuQuadBatchWriter as any).mock.invocationCallOrder[0]).toBeLessThan(
|
|
401
|
-
(drawWgpuRenderTargetResult as any).mock.invocationCallOrder[0],
|
|
378
|
+
expect(wgpuQuadBatchWriter.flushWgpuQuadBatchWriter).toHaveBeenCalledWith(state);
|
|
379
|
+
expect((wgpuQuadBatchWriter.flushWgpuQuadBatchWriter as any).mock.invocationCallOrder[0]).toBeLessThan(
|
|
380
|
+
(renderWgpu.drawWgpuRenderTargetResult as any).mock.invocationCallOrder[0],
|
|
402
381
|
);
|
|
403
382
|
});
|
|
404
383
|
});
|
|
@@ -407,7 +386,7 @@ describe('enableWgpuRenderCache', () => {
|
|
|
407
386
|
it('registers the renderer for the render cache kind', () => {
|
|
408
387
|
const state = fakeScreen();
|
|
409
388
|
enableWgpuRenderCache(state);
|
|
410
|
-
expect(getWgpuRenderStateRuntime(state).registries.renderers.entries.get(RenderCacheKind)).toEqual({
|
|
389
|
+
expect(renderWgpu.getWgpuRenderStateRuntime(state).registries.renderers.entries.get(RenderCacheKind)).toEqual({
|
|
411
390
|
state: 'bound',
|
|
412
391
|
value: defaultWgpuRenderCacheRenderer,
|
|
413
392
|
});
|
|
@@ -440,17 +419,17 @@ describe('ensureWgpuRenderCacheTarget', () => {
|
|
|
440
419
|
|
|
441
420
|
it('enumerates and destroys every owned target during state teardown', () => {
|
|
442
421
|
const state = fakeScreen();
|
|
443
|
-
const runtime = getWgpuRenderStateRuntime(state);
|
|
422
|
+
const runtime = renderWgpu.getWgpuRenderStateRuntime(state);
|
|
444
423
|
runtime.quadBatchWriterBufferPool = [];
|
|
445
424
|
const firstCache = createRenderCache();
|
|
446
425
|
const secondCache = createRenderCache();
|
|
447
426
|
const first = ensureWgpuRenderCacheTarget(state, firstCache, 8, 8);
|
|
448
427
|
const second = ensureWgpuRenderCacheTarget(state, secondCache, 16, 16);
|
|
449
428
|
|
|
450
|
-
destroyWgpuRenderState(state);
|
|
429
|
+
renderWgpu.destroyWgpuRenderState(state);
|
|
451
430
|
|
|
452
|
-
expect(destroyWgpuRenderTarget).toHaveBeenCalledWith(state, first);
|
|
453
|
-
expect(destroyWgpuRenderTarget).toHaveBeenCalledWith(state, second);
|
|
431
|
+
expect(renderWgpu.destroyWgpuRenderTarget).toHaveBeenCalledWith(state, first);
|
|
432
|
+
expect(renderWgpu.destroyWgpuRenderTarget).toHaveBeenCalledWith(state, second);
|
|
454
433
|
expect(getWgpuRenderCacheTarget(state, firstCache)).toBeNull();
|
|
455
434
|
expect(getWgpuRenderCacheTarget(state, secondCache)).toBeNull();
|
|
456
435
|
});
|
|
@@ -474,22 +453,22 @@ describe('refreshWgpuRenderCache', () => {
|
|
|
474
453
|
const screen = fakeScreen();
|
|
475
454
|
const cacheState = createCacheState(screen);
|
|
476
455
|
refreshWgpuRenderCache(screen, cacheState, createRenderCache(), createDisplayObject(), { padding: 5 });
|
|
477
|
-
expect(beginWgpuFrame).toHaveBeenCalledWith(screen);
|
|
478
|
-
expect(submitWgpuRenderPass).toHaveBeenCalledWith(screen);
|
|
479
|
-
expect(getWgpuRenderStateRuntime(screen).commandEncoder).toBeNull();
|
|
456
|
+
expect(renderWgpu.beginWgpuFrame).toHaveBeenCalledWith(screen);
|
|
457
|
+
expect(renderWgpu.submitWgpuRenderPass).toHaveBeenCalledWith(screen);
|
|
458
|
+
expect(renderWgpu.getWgpuRenderStateRuntime(screen).commandEncoder).toBeNull();
|
|
480
459
|
});
|
|
481
460
|
|
|
482
461
|
it('records into an active application frame without submitting it', () => {
|
|
483
462
|
const screen = fakeScreen();
|
|
484
463
|
const encoder = {} as GPUCommandEncoder;
|
|
485
|
-
getWgpuRenderStateRuntime(screen).commandEncoder = encoder;
|
|
464
|
+
renderWgpu.getWgpuRenderStateRuntime(screen).commandEncoder = encoder;
|
|
486
465
|
const cacheState = createCacheState(screen);
|
|
487
|
-
vi.mocked(beginWgpuFrame).mockClear();
|
|
488
|
-
vi.mocked(submitWgpuRenderPass).mockClear();
|
|
466
|
+
vi.mocked(renderWgpu.beginWgpuFrame).mockClear();
|
|
467
|
+
vi.mocked(renderWgpu.submitWgpuRenderPass).mockClear();
|
|
489
468
|
refreshWgpuRenderCache(screen, cacheState, createRenderCache(), createDisplayObject(), { padding: 5 });
|
|
490
|
-
expect(beginWgpuFrame).not.toHaveBeenCalled();
|
|
491
|
-
expect(submitWgpuRenderPass).not.toHaveBeenCalled();
|
|
492
|
-
expect(getWgpuRenderStateRuntime(screen).commandEncoder).toBe(encoder);
|
|
469
|
+
expect(renderWgpu.beginWgpuFrame).not.toHaveBeenCalled();
|
|
470
|
+
expect(renderWgpu.submitWgpuRenderPass).not.toHaveBeenCalled();
|
|
471
|
+
expect(renderWgpu.getWgpuRenderStateRuntime(screen).commandEncoder).toBe(encoder);
|
|
493
472
|
});
|
|
494
473
|
|
|
495
474
|
it('bakes on the first call and allocates the target on the screen state', () => {
|
|
@@ -499,7 +478,7 @@ describe('refreshWgpuRenderCache', () => {
|
|
|
499
478
|
const obj = createDisplayObject();
|
|
500
479
|
const rebaked = refreshWgpuRenderCache(screen, cacheState, cache, obj, { padding: 5 });
|
|
501
480
|
expect(rebaked).toBe(true);
|
|
502
|
-
expect(renderWgpuScene2D).toHaveBeenCalled();
|
|
481
|
+
expect(wgpuNode2D.renderWgpuScene2D).toHaveBeenCalled();
|
|
503
482
|
const target = getWgpuRenderCacheTarget(screen, cache);
|
|
504
483
|
expect(target).not.toBeNull();
|
|
505
484
|
expect(target!.width).toBe(10);
|
|
@@ -521,7 +500,7 @@ describe('releaseWgpuRenderCache', () => {
|
|
|
521
500
|
const cache = createRenderCache();
|
|
522
501
|
const target = ensureWgpuRenderCacheTarget(state, cache, 8, 8);
|
|
523
502
|
releaseWgpuRenderCache(state, cache);
|
|
524
|
-
expect(destroyWgpuRenderTarget).toHaveBeenCalledWith(state, target);
|
|
503
|
+
expect(renderWgpu.destroyWgpuRenderTarget).toHaveBeenCalledWith(state, target);
|
|
525
504
|
expect(getWgpuRenderCacheTarget(state, cache)).toBeNull();
|
|
526
505
|
});
|
|
527
506
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as flightNode from '@flighthq/node/contract';
|
|
2
2
|
import { getWgpuRenderStateRuntime } from '@flighthq/render-wgpu/contract';
|
|
3
3
|
import { createWgpuRenderStateForTest, installWgpuMock, renderWgpuBackground } from '@flighthq/render-wgpu/contract';
|
|
4
4
|
import { enableRenderRegistryGuards, explainRenderRegistryMisses } from '@flighthq/render/contract';
|
|
@@ -12,23 +12,25 @@ import {
|
|
|
12
12
|
import type { RenderProxy2D } from '@flighthq/types/contract';
|
|
13
13
|
import { BatchFormat, RenderRegistry } from '@flighthq/types/contract';
|
|
14
14
|
|
|
15
|
-
// ★ A HOISTED MOCK, NOT A HAND-ROLLED ONE. This file is in REGISTRY_ISOLATED_TESTS, so it already runs
|
|
16
|
-
// with its own module registry — the hermeticity the `scopeModuleMocks` + `vi.doMock` + dynamic-import
|
|
17
|
-
// dance bought by hand comes from the platform here, with no hook. The dance was not merely redundant:
|
|
18
|
-
// it rebuilt the subject's entire transitive module graph inside a FIXED `beforeAll` deadline, which is
|
|
19
|
-
// unbounded work against a fixed clock and the shape of flake that tiering exists to remove.
|
|
20
|
-
vi.mock('@flighthq/node/contract', async (importOriginal) => ({
|
|
21
|
-
...(await importOriginal<typeof FlightNodeModule>()),
|
|
22
|
-
getNodeLocalBoundsRectangle: () => ({ x: 0, y: 0, width: 64, height: 48 }),
|
|
23
|
-
getNodeLocalContentRevision: (source: { data?: { version?: number } } | null | undefined) =>
|
|
24
|
-
source?.data?.version ?? 0,
|
|
25
|
-
}));
|
|
26
|
-
|
|
27
15
|
import { defaultWgpuMeshShapeRenderer, drawWgpuMeshShape } from './wgpuMeshShapeRenderer';
|
|
28
16
|
import { registerWgpuShapeRasterizer } from './wgpuShapeRasterizer';
|
|
29
17
|
|
|
30
18
|
beforeAll(() => installWgpuMock());
|
|
31
19
|
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
vi.spyOn(flightNode, 'getNodeLocalBoundsRectangle').mockImplementation((() => ({
|
|
22
|
+
x: 0,
|
|
23
|
+
y: 0,
|
|
24
|
+
width: 64,
|
|
25
|
+
height: 48,
|
|
26
|
+
})) as never);
|
|
27
|
+
vi.spyOn(flightNode, 'getNodeLocalContentRevision').mockImplementation(
|
|
28
|
+
((source: { data?: { version?: number } } | null | undefined) => source?.data?.version ?? 0) as never,
|
|
29
|
+
);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
afterEach(() => vi.restoreAllMocks());
|
|
33
|
+
|
|
32
34
|
function makeShapeData() {
|
|
33
35
|
return {
|
|
34
36
|
surface: null,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createImageResource } from '@flighthq/image/contract';
|
|
2
|
-
import
|
|
2
|
+
import * as flightNode from '@flighthq/node/contract';
|
|
3
3
|
import { getWgpuRenderStateRuntime } from '@flighthq/render-wgpu/contract';
|
|
4
4
|
import { createWgpuRenderStateForTest, installWgpuMock, renderWgpuBackground } from '@flighthq/render-wgpu/contract';
|
|
5
5
|
import {
|
|
@@ -12,24 +12,24 @@ import { appendShapeBeginFill, appendShapeEndFill, appendShapeRectangle, createS
|
|
|
12
12
|
import type { RenderProxy2D } from '@flighthq/types/contract';
|
|
13
13
|
import { BatchFormat, RenderRegistry } from '@flighthq/types/contract';
|
|
14
14
|
|
|
15
|
-
// ★ A HOISTED MOCK, NOT A HAND-ROLLED ONE. This file is in REGISTRY_ISOLATED_TESTS, so it already runs
|
|
16
|
-
// with its own module registry — the hermeticity the `scopeModuleMocks` + `vi.doMock` + dynamic-import
|
|
17
|
-
// dance bought by hand comes from the platform here, with no hook. The dance was not merely redundant:
|
|
18
|
-
// it rebuilt the subject's entire transitive module graph inside a FIXED `beforeAll` deadline, which is
|
|
19
|
-
// unbounded work against a fixed clock and the shape of flake that tiering exists to remove.
|
|
20
|
-
vi.mock('@flighthq/node/contract', async (importOriginal) => ({
|
|
21
|
-
...(await importOriginal<typeof FlightNodeModule>()),
|
|
22
|
-
getNodeLocalBoundsRectangle: () => ({ x: 0, y: 0, width: 64, height: 48 }),
|
|
23
|
-
getNodeLocalContentRevision: (source: { data?: { version?: number } } | null | undefined) =>
|
|
24
|
-
source?.data?.version ?? 0,
|
|
25
|
-
}));
|
|
26
|
-
|
|
27
15
|
import { defaultWgpuRasterShapeRenderer, drawWgpuRasterShape } from './wgpuRasterShapeRenderer';
|
|
28
16
|
import { registerWgpuShapeRasterizer } from './wgpuShapeRasterizer';
|
|
29
17
|
import { registerWgpuStandardMaterial } from './wgpuStandardMaterial';
|
|
30
18
|
|
|
31
19
|
beforeAll(() => installWgpuMock());
|
|
32
20
|
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
vi.spyOn(flightNode, 'getNodeLocalBoundsRectangle').mockImplementation((() => ({
|
|
23
|
+
x: 0,
|
|
24
|
+
y: 0,
|
|
25
|
+
width: 64,
|
|
26
|
+
height: 48,
|
|
27
|
+
})) as never);
|
|
28
|
+
vi.spyOn(flightNode, 'getNodeLocalContentRevision').mockImplementation(
|
|
29
|
+
((source: { data?: { version?: number } } | null | undefined) => source?.data?.version ?? 0) as never,
|
|
30
|
+
);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
33
|
beforeEach(() => {
|
|
34
34
|
setRaster2DSurfaceProvider({
|
|
35
35
|
createRaster2DSurface(width, height) {
|
|
@@ -59,6 +59,7 @@ beforeEach(() => {
|
|
|
59
59
|
});
|
|
60
60
|
|
|
61
61
|
afterEach(() => {
|
|
62
|
+
vi.restoreAllMocks();
|
|
62
63
|
resetRaster2DSurfaceProviderForTest();
|
|
63
64
|
});
|
|
64
65
|
|
package/src/wgpuShape.test.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createImageResource } from '@flighthq/image/contract';
|
|
2
|
-
import
|
|
2
|
+
import * as flightNode from '@flighthq/node/contract';
|
|
3
3
|
import { renderWgpuBackground, submitWgpuRenderPass } from '@flighthq/render-wgpu/contract';
|
|
4
4
|
import { getWgpuRenderStateRuntime } from '@flighthq/render-wgpu/contract';
|
|
5
5
|
import { createWgpuRenderStateForTest, installWgpuMock } from '@flighthq/render-wgpu/contract';
|
|
@@ -18,22 +18,6 @@ import type { RenderProxy2D } from '@flighthq/types/contract';
|
|
|
18
18
|
import { BatchFormat, PathCommand } from '@flighthq/types/contract';
|
|
19
19
|
|
|
20
20
|
import { enableWgpuStrokePathTessellation } from './enableWgpuStrokePathTessellation';
|
|
21
|
-
// @flighthq/node's bounds/revision queries expect a real BoundsNode; these tests drive drawWgpuShape with
|
|
22
|
-
// lightweight fake proxies, so the two queries are stubbed.
|
|
23
|
-
//
|
|
24
|
-
// ★ A HOISTED MOCK, NOT A HAND-ROLLED ONE. This file is in REGISTRY_ISOLATED_TESTS, so it already runs
|
|
25
|
-
// with its own module registry — the hermeticity the `scopeModuleMocks` + `vi.doMock` + dynamic-import
|
|
26
|
-
// dance bought by hand comes from the platform here, with no hook, and the stub cannot reach the many
|
|
27
|
-
// real consumers of these functions (node, interaction, shape, text). The dance was not merely
|
|
28
|
-
// redundant: it rebuilt the subject's entire transitive module graph inside a FIXED `beforeAll`
|
|
29
|
-
// deadline, which is unbounded work against a fixed clock and the shape of flake tiering exists to remove.
|
|
30
|
-
vi.mock('@flighthq/node/contract', async (importOriginal) => ({
|
|
31
|
-
...(await importOriginal<typeof FlightNodeModule>()),
|
|
32
|
-
getNodeLocalBoundsRectangle: () => ({ x: 0, y: 0, width: 64, height: 48 }),
|
|
33
|
-
getNodeLocalContentRevision: (source: { data?: { version?: number } } | null | undefined) =>
|
|
34
|
-
source?.data?.version ?? 0,
|
|
35
|
-
}));
|
|
36
|
-
|
|
37
21
|
import { defaultWgpuMorphShapeRenderer, defaultWgpuShapeRenderer, drawWgpuShape } from './wgpuShape';
|
|
38
22
|
import { registerWgpuShapeRasterizer } from './wgpuShapeRasterizer';
|
|
39
23
|
import { registerWgpuStandardMaterial } from './wgpuStandardMaterial';
|
|
@@ -42,6 +26,20 @@ const noopRasterizer = (): void => {};
|
|
|
42
26
|
|
|
43
27
|
beforeAll(() => installWgpuMock());
|
|
44
28
|
|
|
29
|
+
// @flighthq/node's bounds/revision queries expect a real BoundsNode; these tests drive drawWgpuShape with
|
|
30
|
+
// lightweight fake proxies, so the two queries are stubbed.
|
|
31
|
+
beforeEach(() => {
|
|
32
|
+
vi.spyOn(flightNode, 'getNodeLocalBoundsRectangle').mockImplementation((() => ({
|
|
33
|
+
x: 0,
|
|
34
|
+
y: 0,
|
|
35
|
+
width: 64,
|
|
36
|
+
height: 48,
|
|
37
|
+
})) as never);
|
|
38
|
+
vi.spyOn(flightNode, 'getNodeLocalContentRevision').mockImplementation(
|
|
39
|
+
((source: { data?: { version?: number } } | null | undefined) => source?.data?.version ?? 0) as never,
|
|
40
|
+
);
|
|
41
|
+
});
|
|
42
|
+
|
|
45
43
|
beforeEach(() => {
|
|
46
44
|
setRaster2DSurfaceProvider({
|
|
47
45
|
createRaster2DSurface(width, height) {
|
|
@@ -71,6 +69,7 @@ beforeEach(() => {
|
|
|
71
69
|
});
|
|
72
70
|
|
|
73
71
|
afterEach(() => {
|
|
72
|
+
vi.restoreAllMocks();
|
|
74
73
|
resetRaster2DSurfaceProviderForTest();
|
|
75
74
|
});
|
|
76
75
|
|
|
@@ -4,42 +4,36 @@ import { getWgpuRenderStateRuntime } from '@flighthq/render-wgpu/contract';
|
|
|
4
4
|
import { createWgpuRenderStateForTest, installWgpuMock } from '@flighthq/render-wgpu/contract';
|
|
5
5
|
import { resetRaster2DSurfaceProviderForTest, setRaster2DSurfaceProvider } from '@flighthq/render/contract';
|
|
6
6
|
import { createTextLabel } from '@flighthq/text/contract';
|
|
7
|
+
import * as textlayout from '@flighthq/textlayout/contract';
|
|
7
8
|
import type { Raster2DSurface, RenderProxy2D } from '@flighthq/types/contract';
|
|
8
9
|
import { BatchFormat } from '@flighthq/types/contract';
|
|
9
10
|
|
|
10
|
-
// @flighthq/textlayout.computeTextLayout is stubbed to emit one deterministic glyph group.
|
|
11
|
-
//
|
|
12
|
-
// ★ A HOISTED MOCK, NOT A HAND-ROLLED ONE. This file is in REGISTRY_ISOLATED_TESTS, so it already runs
|
|
13
|
-
// with its own module registry — the hermeticity the `scopeModuleMocks` + `vi.doMock` + dynamic-import
|
|
14
|
-
// dance bought by hand comes from the platform here, with no hook, and the stub cannot reach the real
|
|
15
|
-
// text-layout consumers. The dance was not merely redundant: it rebuilt the subject's entire transitive
|
|
16
|
-
// module graph inside a FIXED `beforeAll` deadline, which is unbounded work against a fixed clock and
|
|
17
|
-
// the shape of flake tiering exists to remove.
|
|
18
|
-
vi.mock('@flighthq/textlayout/contract', async (importOriginal) => {
|
|
19
|
-
const actual = (await importOriginal()) as Record<string, unknown>;
|
|
20
|
-
return {
|
|
21
|
-
...actual,
|
|
22
|
-
computeTextLayout: vi.fn((result: { groups: object[] }, params: { formatRanges: Array<{ format: object }> }) => {
|
|
23
|
-
result.groups.push({
|
|
24
|
-
offsetX: 0,
|
|
25
|
-
offsetY: 0,
|
|
26
|
-
width: 50,
|
|
27
|
-
ascent: 12,
|
|
28
|
-
descent: 4,
|
|
29
|
-
format: params.formatRanges[0]?.format ?? {},
|
|
30
|
-
startIndex: 0,
|
|
31
|
-
endIndex: 5,
|
|
32
|
-
});
|
|
33
|
-
}),
|
|
34
|
-
};
|
|
35
|
-
});
|
|
36
|
-
|
|
37
11
|
import { registerWgpuStandardMaterial } from './wgpuStandardMaterial';
|
|
38
12
|
import { defaultWgpuTextLabelRenderer, drawWgpuTextLabel } from './wgpuTextLabel';
|
|
39
13
|
|
|
40
14
|
beforeAll(() => installWgpuMock());
|
|
41
15
|
|
|
16
|
+
// @flighthq/textlayout.computeTextLayout is stubbed to emit one deterministic glyph group.
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
vi.spyOn(textlayout, 'computeTextLayout').mockImplementation(((
|
|
19
|
+
result: { groups: object[] },
|
|
20
|
+
params: { formatRanges: Array<{ format: object }> },
|
|
21
|
+
) => {
|
|
22
|
+
result.groups.push({
|
|
23
|
+
offsetX: 0,
|
|
24
|
+
offsetY: 0,
|
|
25
|
+
width: 50,
|
|
26
|
+
ascent: 12,
|
|
27
|
+
descent: 4,
|
|
28
|
+
format: params.formatRanges[0]?.format ?? {},
|
|
29
|
+
startIndex: 0,
|
|
30
|
+
endIndex: 5,
|
|
31
|
+
});
|
|
32
|
+
}) as never);
|
|
33
|
+
});
|
|
34
|
+
|
|
42
35
|
afterEach(() => {
|
|
36
|
+
vi.restoreAllMocks();
|
|
43
37
|
resetRaster2DSurfaceProviderForTest();
|
|
44
38
|
});
|
|
45
39
|
|