@flighthq/scene2d-wgpu 0.3.0-next.906.07cea63 → 0.3.0
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 +8 -6
- package/dist/contract.d.ts.map +1 -1
- package/dist/contract.js +8 -6
- package/dist/contract.js.map +1 -1
- package/dist/enableWgpuColorAdjustmentGuards.js +2 -2
- package/dist/enableWgpuColorAdjustmentGuards.js.map +1 -1
- package/dist/enableWgpuStrokePathTessellation.d.ts +3 -0
- package/dist/enableWgpuStrokePathTessellation.d.ts.map +1 -0
- package/dist/enableWgpuStrokePathTessellation.js +8 -0
- package/dist/enableWgpuStrokePathTessellation.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/wgpuBitmapText.d.ts.map +1 -1
- package/dist/wgpuBitmapText.js +20 -16
- package/dist/wgpuBitmapText.js.map +1 -1
- package/dist/wgpuCache.d.ts.map +1 -1
- package/dist/wgpuCache.js +19 -16
- package/dist/wgpuCache.js.map +1 -1
- package/dist/wgpuClipContours.js +3 -3
- package/dist/wgpuClipContours.js.map +1 -1
- package/dist/wgpuClipRectangle.js +3 -3
- package/dist/wgpuClipRectangle.js.map +1 -1
- package/dist/wgpuColorAdjustmentMaterialFeature.d.ts.map +1 -1
- package/dist/wgpuColorAdjustmentMaterialFeature.js +156 -67
- package/dist/wgpuColorAdjustmentMaterialFeature.js.map +1 -1
- package/dist/wgpuMeshShapeRenderer.d.ts +4 -0
- package/dist/wgpuMeshShapeRenderer.d.ts.map +1 -0
- package/dist/wgpuMeshShapeRenderer.js +92 -0
- package/dist/wgpuMeshShapeRenderer.js.map +1 -0
- package/dist/wgpuNode2D.js +2 -2
- package/dist/wgpuNode2D.js.map +1 -1
- package/dist/wgpuParticleEmitter2D.d.ts.map +1 -1
- package/dist/wgpuParticleEmitter2D.js +26 -10
- package/dist/wgpuParticleEmitter2D.js.map +1 -1
- package/dist/wgpuQuadBatch.d.ts +1 -1
- package/dist/wgpuQuadBatch.d.ts.map +1 -1
- package/dist/wgpuQuadBatch.js +18 -13
- package/dist/wgpuQuadBatch.js.map +1 -1
- package/dist/wgpuQuadBatchWriter.d.ts +12 -0
- package/dist/wgpuQuadBatchWriter.d.ts.map +1 -0
- package/dist/{wgpuSpriteBatch.js → wgpuQuadBatchWriter.js} +97 -72
- package/dist/wgpuQuadBatchWriter.js.map +1 -0
- package/dist/wgpuRasterShapeRenderer.d.ts +4 -0
- package/dist/wgpuRasterShapeRenderer.d.ts.map +1 -0
- package/dist/wgpuRasterShapeRenderer.js +110 -0
- package/dist/wgpuRasterShapeRenderer.js.map +1 -0
- package/dist/wgpuRichText.d.ts.map +1 -1
- package/dist/wgpuRichText.js +7 -4
- package/dist/wgpuRichText.js.map +1 -1
- package/dist/wgpuScale9Shape.d.ts +1 -3
- package/dist/wgpuScale9Shape.d.ts.map +1 -1
- package/dist/wgpuScale9Shape.js +36 -33
- package/dist/wgpuScale9Shape.js.map +1 -1
- package/dist/wgpuShape.d.ts +2 -1
- package/dist/wgpuShape.d.ts.map +1 -1
- package/dist/wgpuShape.js +19 -153
- package/dist/wgpuShape.js.map +1 -1
- package/dist/wgpuShapeData.d.ts +6 -0
- package/dist/wgpuShapeData.d.ts.map +1 -0
- package/dist/wgpuShapeData.js +81 -0
- package/dist/wgpuShapeData.js.map +1 -0
- package/dist/wgpuShapeMesh.d.ts +2 -1
- package/dist/wgpuShapeMesh.d.ts.map +1 -1
- package/dist/wgpuShapeMesh.js +74 -62
- package/dist/wgpuShapeMesh.js.map +1 -1
- package/dist/wgpuShapeRasterizer.d.ts +4 -0
- package/dist/wgpuShapeRasterizer.d.ts.map +1 -0
- package/dist/wgpuShapeRasterizer.js +11 -0
- package/dist/wgpuShapeRasterizer.js.map +1 -0
- package/dist/wgpuSprite.d.ts +3 -2
- package/dist/wgpuSprite.d.ts.map +1 -1
- package/dist/wgpuSprite.js +67 -25
- package/dist/wgpuSprite.js.map +1 -1
- package/dist/wgpuStandardMaterial.d.ts +1 -1
- package/dist/wgpuStandardMaterial.js +3 -3
- package/dist/wgpuStandardMaterial.js.map +1 -1
- package/dist/wgpuTextLabel.d.ts.map +1 -1
- package/dist/wgpuTextLabel.js +15 -12
- package/dist/wgpuTextLabel.js.map +1 -1
- package/dist/wgpuTilemap.d.ts.map +1 -1
- package/dist/wgpuTilemap.js +19 -18
- package/dist/wgpuTilemap.js.map +1 -1
- package/package.json +22 -18
- package/src/enableWgpuColorAdjustmentGuards.test.ts +4 -4
- package/src/enableWgpuStrokePathTessellation.test.ts +23 -0
- package/src/wgpuBitmapText.test.ts +2 -2
- package/src/wgpuCache.test.ts +14 -11
- package/src/wgpuColorAdjustmentMaterialFeature.test.ts +52 -46
- package/src/wgpuMeshShapeRenderer.test.ts +167 -0
- package/src/wgpuParticleEmitter2D.test.ts +16 -7
- package/src/wgpuQuadBatch.test.ts +3 -3
- package/src/{wgpuSpriteBatch.test.ts → wgpuQuadBatchWriter.test.ts} +79 -70
- package/src/wgpuRasterShapeRenderer.test.ts +156 -0
- package/src/wgpuScale9Shape.test.ts +0 -13
- package/src/wgpuShape.test.ts +169 -15
- package/src/wgpuShapeData.test.ts +97 -0
- package/src/wgpuShapeMesh.test.ts +170 -15
- package/src/wgpuShapeRasterizer.test.ts +36 -0
- package/src/wgpuSprite.test.ts +45 -10
- package/src/wgpuStandardMaterial.test.ts +3 -3
- package/src/wgpuTextLabel.test.ts +7 -7
- package/src/wgpuTilemap.test.ts +4 -4
- package/src/wgpuVelocity.test.ts +3 -3
- package/dist/wgpuBitmap.d.ts +0 -4
- package/dist/wgpuBitmap.d.ts.map +0 -1
- package/dist/wgpuBitmap.js +0 -84
- package/dist/wgpuBitmap.js.map +0 -1
- package/dist/wgpuRenderTargetNode2D.d.ts +0 -11
- package/dist/wgpuRenderTargetNode2D.d.ts.map +0 -1
- package/dist/wgpuRenderTargetNode2D.js +0 -100
- package/dist/wgpuRenderTargetNode2D.js.map +0 -1
- package/dist/wgpuSpriteBatch.d.ts +0 -12
- package/dist/wgpuSpriteBatch.d.ts.map +0 -1
- package/dist/wgpuSpriteBatch.js.map +0 -1
- package/dist/wgpuSpriteRenderer.d.ts +0 -3
- package/dist/wgpuSpriteRenderer.d.ts.map +0 -1
- package/dist/wgpuSpriteRenderer.js +0 -55
- package/dist/wgpuSpriteRenderer.js.map +0 -1
- package/dist/wgpuVideo.d.ts +0 -6
- package/dist/wgpuVideo.d.ts.map +0 -1
- package/dist/wgpuVideo.js +0 -63
- package/dist/wgpuVideo.js.map +0 -1
- package/src/wgpuBitmap.test.ts +0 -40
- package/src/wgpuRenderTargetNode2D.test.ts +0 -267
- package/src/wgpuSpriteRenderer.test.ts +0 -63
- package/src/wgpuVideo.test.ts +0 -59
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
destroyWgpuScale9ShapeData,
|
|
10
10
|
drawWgpuScale9Shape,
|
|
11
11
|
drawWgpuScale9ShapeMask,
|
|
12
|
-
remapWgpuScale9Commands,
|
|
13
12
|
} from './wgpuScale9Shape';
|
|
14
13
|
|
|
15
14
|
const grid = { height: 80, width: 80, x: 10, y: 10 };
|
|
@@ -83,15 +82,3 @@ describe('drawWgpuScale9ShapeMask', () => {
|
|
|
83
82
|
submitWgpuRenderPass(state);
|
|
84
83
|
});
|
|
85
84
|
});
|
|
86
|
-
|
|
87
|
-
describe('remapWgpuScale9Commands', () => {
|
|
88
|
-
it('remaps coordinates with a compatible mapper', () => {
|
|
89
|
-
const out: unknown[] = [];
|
|
90
|
-
remapWgpuScale9Commands(out, ['drawRectangle', 4, 10, 20, 50, 30], {
|
|
91
|
-
mapX: (x: number) => x * 2,
|
|
92
|
-
mapY: (y: number) => y * 3,
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
expect(out).toEqual(['drawRectangle', 4, 20, 60, 100, 90]);
|
|
96
|
-
});
|
|
97
|
-
});
|
package/src/wgpuShape.test.ts
CHANGED
|
@@ -3,18 +3,33 @@ import type * as FlightNodeModule 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';
|
|
6
|
+
import {
|
|
7
|
+
appendShapeBeginFill,
|
|
8
|
+
appendShapeEndFill,
|
|
9
|
+
appendShapePath,
|
|
10
|
+
appendShapeLineStyle,
|
|
11
|
+
appendShapeLineTo,
|
|
12
|
+
appendShapeMoveTo,
|
|
13
|
+
appendShapeRectangle,
|
|
14
|
+
createShape,
|
|
15
|
+
} from '@flighthq/shape/contract';
|
|
6
16
|
import type { RenderProxy2D } from '@flighthq/types/contract';
|
|
7
|
-
import { BatchFormat } from '@flighthq/types/contract';
|
|
17
|
+
import { BatchFormat, PathCommand } from '@flighthq/types/contract';
|
|
8
18
|
|
|
19
|
+
import { enableWgpuStrokePathTessellation } from './enableWgpuStrokePathTessellation';
|
|
9
20
|
import { scopeModuleMocks } from './moduleMockTestHelper';
|
|
10
21
|
import type * as WgpuShapeModule from './wgpuShape';
|
|
11
|
-
import {
|
|
22
|
+
import { registerWgpuShapeRasterizer } from './wgpuShapeRasterizer';
|
|
23
|
+
import { registerWgpuStandardMaterial } from './wgpuStandardMaterial';
|
|
24
|
+
|
|
25
|
+
const noopRasterizer = (): void => {};
|
|
12
26
|
|
|
13
27
|
// @flighthq/node's bounds/revision queries expect a real BoundsNode; these tests drive drawWgpuShape
|
|
14
28
|
// with lightweight fake proxies, so the two queries are stubbed. scopeModuleMocks scopes the stub to
|
|
15
29
|
// this file (registry reset before the mock applies, unmock + reset after), so under a shared
|
|
16
30
|
// (isolate:false) worker it never leaks into the many real consumers of these functions (node,
|
|
17
31
|
// interaction, shape, text) — and a sibling that pre-evaluated ./wgpuShape still picks up the stub.
|
|
32
|
+
let defaultWgpuMorphShapeRenderer: typeof WgpuShapeModule.defaultWgpuMorphShapeRenderer;
|
|
18
33
|
let defaultWgpuShapeRenderer: typeof WgpuShapeModule.defaultWgpuShapeRenderer;
|
|
19
34
|
let drawWgpuShape: typeof WgpuShapeModule.drawWgpuShape;
|
|
20
35
|
|
|
@@ -26,16 +41,32 @@ beforeAll(async () => {
|
|
|
26
41
|
getNodeLocalBoundsRectangle: () => ({ x: 0, y: 0, width: 64, height: 48 }),
|
|
27
42
|
getNodeLocalContentRevision: (source: any) => source?.data?.version ?? 0,
|
|
28
43
|
}));
|
|
29
|
-
({ defaultWgpuShapeRenderer, drawWgpuShape } = await import('./wgpuShape'));
|
|
44
|
+
({ defaultWgpuMorphShapeRenderer, defaultWgpuShapeRenderer, drawWgpuShape } = await import('./wgpuShape'));
|
|
30
45
|
installWgpuMock();
|
|
31
46
|
});
|
|
32
47
|
|
|
48
|
+
// Mirrors createWgpuShapeData: the rasterization surface is absent until a shape actually needs one,
|
|
49
|
+
// so a scene drawn entirely through the mesh path carries no canvases.
|
|
33
50
|
function makeShapeData() {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
51
|
+
return {
|
|
52
|
+
surface: null,
|
|
53
|
+
lastContentId: -1,
|
|
54
|
+
lastPixelRatio: 0,
|
|
55
|
+
lastW: 0,
|
|
56
|
+
lastH: 0,
|
|
57
|
+
meshVersion: -1,
|
|
58
|
+
meshes: null,
|
|
59
|
+
meshBuffers: {
|
|
60
|
+
vertexBuffers: [],
|
|
61
|
+
vertexCapacities: [],
|
|
62
|
+
indexBuffers: [],
|
|
63
|
+
indexCapacities: [],
|
|
64
|
+
uniformBuffers: [],
|
|
65
|
+
bindGroups: [],
|
|
66
|
+
colorScaleBiasUniformBuffers: [],
|
|
67
|
+
colorScaleBiasBindGroups: [],
|
|
68
|
+
},
|
|
69
|
+
};
|
|
39
70
|
}
|
|
40
71
|
|
|
41
72
|
function makeShapeProxy(data: Record<string, unknown> = {}, rendererData: unknown = null): RenderProxy2D {
|
|
@@ -50,7 +81,24 @@ function makeShapeProxy(data: Record<string, unknown> = {}, rendererData: unknow
|
|
|
50
81
|
} as unknown as RenderProxy2D;
|
|
51
82
|
}
|
|
52
83
|
|
|
84
|
+
function makeMeshPassSpy(): GPURenderPassEncoder {
|
|
85
|
+
return {
|
|
86
|
+
setPipeline: vi.fn(),
|
|
87
|
+
setBindGroup: vi.fn(),
|
|
88
|
+
setVertexBuffer: vi.fn(),
|
|
89
|
+
setIndexBuffer: vi.fn(),
|
|
90
|
+
setStencilReference: vi.fn(),
|
|
91
|
+
draw: vi.fn(),
|
|
92
|
+
drawIndexed: vi.fn(),
|
|
93
|
+
end: vi.fn(),
|
|
94
|
+
} as unknown as GPURenderPassEncoder;
|
|
95
|
+
}
|
|
96
|
+
|
|
53
97
|
describe('defaultWgpuShapeRenderer', () => {
|
|
98
|
+
it('provides the MorphShape renderer alias', () => {
|
|
99
|
+
expect(defaultWgpuMorphShapeRenderer).toBe(defaultWgpuShapeRenderer);
|
|
100
|
+
});
|
|
101
|
+
|
|
54
102
|
it('declares BatchFormat.Quad', () => {
|
|
55
103
|
expect(defaultWgpuShapeRenderer.format).toBe(BatchFormat.Quad);
|
|
56
104
|
});
|
|
@@ -62,35 +110,141 @@ describe('defaultWgpuShapeRenderer', () => {
|
|
|
62
110
|
});
|
|
63
111
|
|
|
64
112
|
describe('drawWgpuShape', () => {
|
|
113
|
+
it('draws a solid fill and open solid stroke as GPU meshes in one shape', async () => {
|
|
114
|
+
const state = await createWgpuRenderStateForTest();
|
|
115
|
+
registerWgpuShapeRasterizer(state, noopRasterizer);
|
|
116
|
+
renderWgpuBackground(state);
|
|
117
|
+
getWgpuRenderStateRuntime(state).renderPass = makeMeshPassSpy();
|
|
118
|
+
const shape = createShape();
|
|
119
|
+
appendShapeBeginFill(shape, 0x00cc00);
|
|
120
|
+
appendShapeRectangle(shape, 8, 8, 32, 24);
|
|
121
|
+
appendShapeEndFill(shape);
|
|
122
|
+
appendShapeLineStyle(shape, 4, 0xff0000);
|
|
123
|
+
appendShapeMoveTo(shape, 8, 4);
|
|
124
|
+
appendShapeLineTo(shape, 40, 4);
|
|
125
|
+
const rendererData = defaultWgpuShapeRenderer.createData!(state, shape)!;
|
|
126
|
+
|
|
127
|
+
drawWgpuShape(state, makeShapeProxy({ commands: shape.data.commands, version: 1 }, rendererData));
|
|
128
|
+
|
|
129
|
+
const pass = getWgpuRenderStateRuntime(state).renderPass as unknown as {
|
|
130
|
+
drawIndexed: ReturnType<typeof vi.fn>;
|
|
131
|
+
};
|
|
132
|
+
expect(pass.drawIndexed).toHaveBeenCalledTimes(2);
|
|
133
|
+
expect(getWgpuRenderStateRuntime(state).quadBatchWriterCount).toBe(0);
|
|
134
|
+
submitWgpuRenderPass(state);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('draws a closed solid stroke ring as one GPU mesh', async () => {
|
|
138
|
+
const state = await createWgpuRenderStateForTest();
|
|
139
|
+
registerWgpuShapeRasterizer(state, noopRasterizer);
|
|
140
|
+
enableWgpuStrokePathTessellation(state);
|
|
141
|
+
renderWgpuBackground(state);
|
|
142
|
+
getWgpuRenderStateRuntime(state).renderPass = makeMeshPassSpy();
|
|
143
|
+
const shape = createShape();
|
|
144
|
+
appendShapeLineStyle(shape, 8, 0xff0000);
|
|
145
|
+
appendShapeRectangle(shape, 8, 8, 32, 24);
|
|
146
|
+
const rendererData = defaultWgpuShapeRenderer.createData!(state, shape)!;
|
|
147
|
+
|
|
148
|
+
drawWgpuShape(state, makeShapeProxy({ commands: shape.data.commands, version: 1 }, rendererData));
|
|
149
|
+
|
|
150
|
+
const pass = getWgpuRenderStateRuntime(state).renderPass as unknown as {
|
|
151
|
+
drawIndexed: ReturnType<typeof vi.fn>;
|
|
152
|
+
};
|
|
153
|
+
expect(pass.drawIndexed).toHaveBeenCalledTimes(1);
|
|
154
|
+
expect(getWgpuRenderStateRuntime(state).quadBatchWriterCount).toBe(0);
|
|
155
|
+
submitWgpuRenderPass(state);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('keeps a closed stroke on the raster lane until stroke-path tessellation is enabled', async () => {
|
|
159
|
+
const state = await createWgpuRenderStateForTest();
|
|
160
|
+
registerWgpuShapeRasterizer(state, noopRasterizer);
|
|
161
|
+
renderWgpuBackground(state);
|
|
162
|
+
registerWgpuStandardMaterial(state);
|
|
163
|
+
const pass = makeMeshPassSpy();
|
|
164
|
+
getWgpuRenderStateRuntime(state).renderPass = pass;
|
|
165
|
+
const shape = createShape();
|
|
166
|
+
appendShapeLineStyle(shape, 8, 0xff0000);
|
|
167
|
+
appendShapeRectangle(shape, 8, 8, 32, 24);
|
|
168
|
+
const rendererData = defaultWgpuShapeRenderer.createData!(state, shape)!;
|
|
169
|
+
|
|
170
|
+
drawWgpuShape(state, makeShapeProxy({ commands: shape.data.commands, version: 1 }, rendererData));
|
|
171
|
+
|
|
172
|
+
expect(pass.drawIndexed).not.toHaveBeenCalled();
|
|
173
|
+
expect(getWgpuRenderStateRuntime(state).quadBatchWriterCount).toBe(1);
|
|
174
|
+
submitWgpuRenderPass(state);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('falls back to the raster quad for a self-intersecting stroke centerline', async () => {
|
|
178
|
+
const state = await createWgpuRenderStateForTest();
|
|
179
|
+
registerWgpuShapeRasterizer(state, noopRasterizer);
|
|
180
|
+
enableWgpuStrokePathTessellation(state);
|
|
181
|
+
renderWgpuBackground(state);
|
|
182
|
+
registerWgpuStandardMaterial(state);
|
|
183
|
+
const pass = makeMeshPassSpy();
|
|
184
|
+
getWgpuRenderStateRuntime(state).renderPass = pass;
|
|
185
|
+
const shape = createShape();
|
|
186
|
+
appendShapeLineStyle(shape, 8, 0xff0000);
|
|
187
|
+
appendShapePath(
|
|
188
|
+
shape,
|
|
189
|
+
[PathCommand.MOVE_TO, PathCommand.LINE_TO, PathCommand.LINE_TO, PathCommand.LINE_TO, PathCommand.CLOSE],
|
|
190
|
+
[8, 8, 40, 40, 8, 40, 40, 8],
|
|
191
|
+
'nonZero',
|
|
192
|
+
);
|
|
193
|
+
const rendererData = defaultWgpuShapeRenderer.createData!(state, shape)!;
|
|
194
|
+
const proxy = makeShapeProxy({ commands: shape.data.commands, version: 1 }, rendererData);
|
|
195
|
+
|
|
196
|
+
drawWgpuShape(state, proxy);
|
|
197
|
+
// The first draw rasterized, so the surface exists by now — it is allocated on demand, not with the node.
|
|
198
|
+
const canvas = (rendererData as unknown as { surface: { canvas: HTMLCanvasElement } }).surface.canvas;
|
|
199
|
+
let canvasWidth = canvas.width;
|
|
200
|
+
let canvasHeight = canvas.height;
|
|
201
|
+
const setWidth = vi.fn((value: number) => (canvasWidth = value));
|
|
202
|
+
const setHeight = vi.fn((value: number) => (canvasHeight = value));
|
|
203
|
+
Object.defineProperty(canvas, 'width', { configurable: true, get: () => canvasWidth, set: setWidth });
|
|
204
|
+
Object.defineProperty(canvas, 'height', { configurable: true, get: () => canvasHeight, set: setHeight });
|
|
205
|
+
(proxy.source as unknown as { data: { version: number } }).data.version = 2;
|
|
206
|
+
drawWgpuShape(state, proxy);
|
|
207
|
+
|
|
208
|
+
expect(pass.drawIndexed).not.toHaveBeenCalled();
|
|
209
|
+
expect(getWgpuRenderStateRuntime(state).quadBatchWriterCount).toBe(2);
|
|
210
|
+
expect(setWidth).not.toHaveBeenCalled();
|
|
211
|
+
expect(setHeight).not.toHaveBeenCalled();
|
|
212
|
+
submitWgpuRenderPass(state);
|
|
213
|
+
});
|
|
214
|
+
|
|
65
215
|
it('returns early without writing to batch when commands are empty', async () => {
|
|
66
216
|
const state = await createWgpuRenderStateForTest();
|
|
217
|
+
registerWgpuShapeRasterizer(state, noopRasterizer);
|
|
67
218
|
renderWgpuBackground(state);
|
|
68
|
-
|
|
219
|
+
registerWgpuStandardMaterial(state);
|
|
69
220
|
drawWgpuShape(state, makeShapeProxy({ commands: [] }, makeShapeData()));
|
|
70
|
-
expect(getWgpuRenderStateRuntime(state).
|
|
221
|
+
expect(getWgpuRenderStateRuntime(state).quadBatchWriterCount).toBe(0);
|
|
71
222
|
submitWgpuRenderPass(state);
|
|
72
223
|
});
|
|
73
224
|
|
|
74
225
|
it('returns early without writing to batch when rendererData is null', async () => {
|
|
75
226
|
const state = await createWgpuRenderStateForTest();
|
|
227
|
+
registerWgpuShapeRasterizer(state, noopRasterizer);
|
|
76
228
|
renderWgpuBackground(state);
|
|
77
|
-
|
|
229
|
+
registerWgpuStandardMaterial(state);
|
|
78
230
|
drawWgpuShape(state, makeShapeProxy({ commands: [{}] }, null));
|
|
79
|
-
expect(getWgpuRenderStateRuntime(state).
|
|
231
|
+
expect(getWgpuRenderStateRuntime(state).quadBatchWriterCount).toBe(0);
|
|
80
232
|
submitWgpuRenderPass(state);
|
|
81
233
|
});
|
|
82
234
|
|
|
83
235
|
it('does not throw when renderPass is null', async () => {
|
|
84
236
|
const state = await createWgpuRenderStateForTest();
|
|
237
|
+
registerWgpuShapeRasterizer(state, noopRasterizer);
|
|
85
238
|
expect(() => drawWgpuShape(state, makeShapeProxy({ commands: [{}] }, makeShapeData()))).not.toThrow();
|
|
86
239
|
});
|
|
87
240
|
|
|
88
|
-
it('writes one instance to the
|
|
241
|
+
it('writes one instance to the quad-batch writer when shape has valid commands and bounds', async () => {
|
|
89
242
|
const state = await createWgpuRenderStateForTest();
|
|
243
|
+
registerWgpuShapeRasterizer(state, noopRasterizer);
|
|
90
244
|
renderWgpuBackground(state);
|
|
91
|
-
|
|
245
|
+
registerWgpuStandardMaterial(state);
|
|
92
246
|
drawWgpuShape(state, makeShapeProxy({ commands: [{}], version: 1 }, makeShapeData()));
|
|
93
|
-
expect(getWgpuRenderStateRuntime(state).
|
|
247
|
+
expect(getWgpuRenderStateRuntime(state).quadBatchWriterCount).toBe(1);
|
|
94
248
|
submitWgpuRenderPass(state);
|
|
95
249
|
});
|
|
96
250
|
});
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { getWgpuRenderStateRuntime } from '@flighthq/render-wgpu/contract';
|
|
2
|
+
import { createWgpuRenderStateForTest, installWgpuMock } from '@flighthq/render-wgpu/contract';
|
|
3
|
+
import type { WgpuShapeRendererData } from '@flighthq/types/contract';
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
acquireWgpuShapeRasterSurface,
|
|
7
|
+
createWgpuShapeData,
|
|
8
|
+
destroyWgpuShapeData,
|
|
9
|
+
getWgpuShapeData,
|
|
10
|
+
} from './wgpuShapeData';
|
|
11
|
+
|
|
12
|
+
beforeAll(() => {
|
|
13
|
+
installWgpuMock();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
function emptyData(): WgpuShapeRendererData {
|
|
17
|
+
return {
|
|
18
|
+
surface: null,
|
|
19
|
+
lastContentId: -1,
|
|
20
|
+
lastPixelRatio: 0,
|
|
21
|
+
lastW: 0,
|
|
22
|
+
lastH: 0,
|
|
23
|
+
meshVersion: -1,
|
|
24
|
+
meshes: null,
|
|
25
|
+
meshBuffers: {
|
|
26
|
+
vertexBuffers: [],
|
|
27
|
+
vertexCapacities: [],
|
|
28
|
+
indexBuffers: [],
|
|
29
|
+
indexCapacities: [],
|
|
30
|
+
uniformBuffers: [],
|
|
31
|
+
bindGroups: [],
|
|
32
|
+
colorScaleBiasUniformBuffers: [],
|
|
33
|
+
colorScaleBiasBindGroups: [],
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
describe('acquireWgpuShapeRasterSurface', () => {
|
|
39
|
+
it('allocates once and returns the same surface thereafter', () => {
|
|
40
|
+
const data = emptyData();
|
|
41
|
+
const first = acquireWgpuShapeRasterSurface(data);
|
|
42
|
+
expect(data.surface).toBe(first);
|
|
43
|
+
expect(acquireWgpuShapeRasterSurface(data)).toBe(first);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('wraps the canvas as an Image so the quad batch treats it like any other texture source', () => {
|
|
47
|
+
const surface = acquireWgpuShapeRasterSurface(emptyData());
|
|
48
|
+
expect(surface.image.source).toBe(surface.canvas);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe('createWgpuShapeData', () => {
|
|
53
|
+
it('allocates no canvas up front, so a mesh-only scene carries none', async () => {
|
|
54
|
+
const state = await createWgpuRenderStateForTest();
|
|
55
|
+
const data = getWgpuShapeData(createWgpuShapeData(state, {} as never))!;
|
|
56
|
+
expect(data.surface).toBeNull();
|
|
57
|
+
expect(data.meshes).toBeNull();
|
|
58
|
+
expect(data.meshVersion).toBe(-1);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe('destroyWgpuShapeData', () => {
|
|
63
|
+
it('does nothing when the shape only ever tessellated, since there is no surface to free', async () => {
|
|
64
|
+
const state = await createWgpuRenderStateForTest();
|
|
65
|
+
const data = createWgpuShapeData(state, {} as never);
|
|
66
|
+
expect(() => destroyWgpuShapeData(state, data)).not.toThrow();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('destroys the cached GPU texture keyed on the raster surface, and the mesh buffers', async () => {
|
|
70
|
+
const state = await createWgpuRenderStateForTest();
|
|
71
|
+
const data = createWgpuShapeData(state, {} as never);
|
|
72
|
+
const shapeData = getWgpuShapeData(data)!;
|
|
73
|
+
const surface = acquireWgpuShapeRasterSurface(shapeData);
|
|
74
|
+
const destroy = vi.fn();
|
|
75
|
+
getWgpuRenderStateRuntime(state).textureSourcePremultipliedTextureCache.set(surface.image, {
|
|
76
|
+
texture: { destroy },
|
|
77
|
+
} as never);
|
|
78
|
+
const bufferDestroy = vi.fn();
|
|
79
|
+
shapeData.meshBuffers.vertexBuffers.push({ destroy: bufferDestroy } as never);
|
|
80
|
+
shapeData.meshBuffers.colorScaleBiasUniformBuffers.push({ destroy: bufferDestroy } as never);
|
|
81
|
+
|
|
82
|
+
destroyWgpuShapeData(state, data);
|
|
83
|
+
|
|
84
|
+
expect(destroy).toHaveBeenCalled();
|
|
85
|
+
expect(bufferDestroy).toHaveBeenCalled();
|
|
86
|
+
expect(shapeData.meshBuffers.vertexBuffers).toHaveLength(0);
|
|
87
|
+
expect(shapeData.meshBuffers.colorScaleBiasUniformBuffers).toHaveLength(0);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe('getWgpuShapeData', () => {
|
|
92
|
+
it('reads the shape data back out of the opaque RendererData slot', async () => {
|
|
93
|
+
const state = await createWgpuRenderStateForTest();
|
|
94
|
+
const data = createWgpuShapeData(state, {} as never);
|
|
95
|
+
expect(getWgpuShapeData(data)?.meshVersion).toBe(-1);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
import { createMatrix } from '@flighthq/geometry/contract';
|
|
2
2
|
import { getWgpuRenderStateRuntime } from '@flighthq/render-wgpu/contract';
|
|
3
3
|
import { createWgpuRenderStateForTest, installWgpuMock } from '@flighthq/render-wgpu/contract';
|
|
4
|
-
import type {
|
|
4
|
+
import type {
|
|
5
|
+
ColorScaleBias,
|
|
6
|
+
RenderProxy2D,
|
|
7
|
+
WgpuRenderState,
|
|
8
|
+
WgpuShapeMeshBuffers,
|
|
9
|
+
WgpuShapeMeshPipeline,
|
|
10
|
+
} from '@flighthq/types/contract';
|
|
5
11
|
import type { WgpuShapeMesh } from '@flighthq/types/contract';
|
|
6
12
|
import { BlendMode } from '@flighthq/types/contract';
|
|
7
13
|
|
|
8
|
-
import {
|
|
14
|
+
import { registerWgpuColorAdjustmentMaterialFeature } from './wgpuColorAdjustmentMaterialFeature';
|
|
15
|
+
import { drawWgpuShapeMeshBatch, drawWgpuShapeMeshes } from './wgpuShapeMesh';
|
|
9
16
|
|
|
10
17
|
beforeAll(() => {
|
|
11
18
|
installWgpuMock();
|
|
@@ -20,12 +27,14 @@ const TRIANGLE: WgpuShapeMesh = {
|
|
|
20
27
|
|
|
21
28
|
function makeBuffers(): WgpuShapeMeshBuffers {
|
|
22
29
|
return {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
30
|
+
vertexBuffers: [],
|
|
31
|
+
vertexCapacities: [],
|
|
32
|
+
indexBuffers: [],
|
|
33
|
+
indexCapacities: [],
|
|
34
|
+
uniformBuffers: [],
|
|
35
|
+
bindGroups: [],
|
|
36
|
+
colorScaleBiasUniformBuffers: [],
|
|
37
|
+
colorScaleBiasBindGroups: [],
|
|
29
38
|
};
|
|
30
39
|
}
|
|
31
40
|
|
|
@@ -41,8 +50,37 @@ function makePassSpy(): GPURenderPassEncoder {
|
|
|
41
50
|
} as unknown as GPURenderPassEncoder;
|
|
42
51
|
}
|
|
43
52
|
|
|
44
|
-
function makeProxy(
|
|
45
|
-
return {
|
|
53
|
+
function makeProxy(overrides?: Partial<RenderProxy2D>): RenderProxy2D {
|
|
54
|
+
return {
|
|
55
|
+
alpha: 1,
|
|
56
|
+
blendMode: null,
|
|
57
|
+
colorMatrix: null,
|
|
58
|
+
colorScaleBias: null,
|
|
59
|
+
transform2D: createMatrix(),
|
|
60
|
+
...overrides,
|
|
61
|
+
} as unknown as RenderProxy2D;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function ct(
|
|
65
|
+
redScale = 1,
|
|
66
|
+
greenScale = 1,
|
|
67
|
+
blueScale = 1,
|
|
68
|
+
alphaScale = 1,
|
|
69
|
+
redBias = 0,
|
|
70
|
+
greenBias = 0,
|
|
71
|
+
blueBias = 0,
|
|
72
|
+
alphaBias = 0,
|
|
73
|
+
): ColorScaleBias {
|
|
74
|
+
return {
|
|
75
|
+
redScale,
|
|
76
|
+
greenScale,
|
|
77
|
+
blueScale,
|
|
78
|
+
alphaScale,
|
|
79
|
+
redBias,
|
|
80
|
+
greenBias,
|
|
81
|
+
blueBias,
|
|
82
|
+
alphaBias,
|
|
83
|
+
} as ColorScaleBias;
|
|
46
84
|
}
|
|
47
85
|
|
|
48
86
|
async function makeState(): Promise<WgpuRenderState> {
|
|
@@ -51,6 +89,42 @@ async function makeState(): Promise<WgpuRenderState> {
|
|
|
51
89
|
return state;
|
|
52
90
|
}
|
|
53
91
|
|
|
92
|
+
describe('drawWgpuShapeMeshBatch', () => {
|
|
93
|
+
it('writes only the shared matrix and color fields, preserving a feature-owned uniform tail', async () => {
|
|
94
|
+
const state = await makeState();
|
|
95
|
+
const buffers = makeBuffers();
|
|
96
|
+
const uniformBuffers: GPUBuffer[] = [];
|
|
97
|
+
const bindGroups: GPUBindGroup[] = [];
|
|
98
|
+
const uniformData = new Float32Array(24);
|
|
99
|
+
uniformData.set([0.25, 0.5, 0.75, 1, 0.1, 0.2, 0.3, 0.4], 16);
|
|
100
|
+
const pipelineEntry: WgpuShapeMeshPipeline = {
|
|
101
|
+
bindGroupLayout: {} as GPUBindGroupLayout,
|
|
102
|
+
pipeline: {} as GPURenderPipeline,
|
|
103
|
+
};
|
|
104
|
+
let upload: Float32Array | undefined;
|
|
105
|
+
state.device.queue.writeBuffer = vi.fn((buffer: GPUBuffer, _offset: number, data: BufferSource) => {
|
|
106
|
+
if (buffer !== uniformBuffers[0]) return;
|
|
107
|
+
const view = ArrayBuffer.isView(data) ? data : new Uint8Array(data as ArrayBuffer);
|
|
108
|
+
upload = new Float32Array(view.buffer.slice(view.byteOffset, view.byteOffset + view.byteLength));
|
|
109
|
+
}) as unknown as GPUQueue['writeBuffer'];
|
|
110
|
+
|
|
111
|
+
drawWgpuShapeMeshBatch(
|
|
112
|
+
state,
|
|
113
|
+
makeProxy({ alpha: 0.5 }),
|
|
114
|
+
[{ ...TRIANGLE, color: 0xffffff }],
|
|
115
|
+
buffers,
|
|
116
|
+
pipelineEntry,
|
|
117
|
+
uniformBuffers,
|
|
118
|
+
bindGroups,
|
|
119
|
+
uniformData,
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
expect(uniformBuffers).toHaveLength(1);
|
|
123
|
+
expect(upload?.slice(12, 16)).toEqual(new Float32Array([0.5, 0.5, 0.5, 0.5]));
|
|
124
|
+
expect(upload?.slice(16, 24)).toEqual(new Float32Array([0.25, 0.5, 0.75, 1, 0.1, 0.2, 0.3, 0.4]));
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
54
128
|
describe('drawWgpuShapeMeshes', () => {
|
|
55
129
|
it('sets the shape-mesh pipeline and draws each mesh', async () => {
|
|
56
130
|
const state = await makeState();
|
|
@@ -68,6 +142,20 @@ describe('drawWgpuShapeMeshes', () => {
|
|
|
68
142
|
expect(pass.drawIndexed).toHaveBeenCalledWith(3);
|
|
69
143
|
});
|
|
70
144
|
|
|
145
|
+
it('retains distinct GPU storage for every mesh recorded before submit', async () => {
|
|
146
|
+
const state = await makeState();
|
|
147
|
+
const buffers = makeBuffers();
|
|
148
|
+
|
|
149
|
+
drawWgpuShapeMeshes(state, makeProxy(), [TRIANGLE, { ...TRIANGLE, color: 0x0080ff }], buffers);
|
|
150
|
+
|
|
151
|
+
expect(buffers.vertexBuffers).toHaveLength(2);
|
|
152
|
+
expect(buffers.indexBuffers).toHaveLength(2);
|
|
153
|
+
expect(buffers.uniformBuffers).toHaveLength(2);
|
|
154
|
+
expect(buffers.vertexBuffers[0]).not.toBe(buffers.vertexBuffers[1]);
|
|
155
|
+
expect(buffers.indexBuffers[0]).not.toBe(buffers.indexBuffers[1]);
|
|
156
|
+
expect(buffers.uniformBuffers[0]).not.toBe(buffers.uniformBuffers[1]);
|
|
157
|
+
});
|
|
158
|
+
|
|
71
159
|
it('retires grown geometry buffers until the recorded frame is submitted', async () => {
|
|
72
160
|
const state = await makeState();
|
|
73
161
|
const buffers = makeBuffers();
|
|
@@ -83,7 +171,8 @@ describe('drawWgpuShapeMeshes', () => {
|
|
|
83
171
|
indices: new Uint16Array([0, 1, 2, 1, 3, 2]),
|
|
84
172
|
};
|
|
85
173
|
|
|
86
|
-
drawWgpuShapeMeshes(state, makeProxy(), [TRIANGLE
|
|
174
|
+
drawWgpuShapeMeshes(state, makeProxy(), [TRIANGLE], buffers);
|
|
175
|
+
drawWgpuShapeMeshes(state, makeProxy(), [larger], buffers);
|
|
87
176
|
|
|
88
177
|
const retired = getWgpuRenderStateRuntime(state).retiredBuffers!;
|
|
89
178
|
expect(retired).toHaveLength(2);
|
|
@@ -106,8 +195,8 @@ describe('drawWgpuShapeMeshes', () => {
|
|
|
106
195
|
it('bakes the node blend mode into a distinct shape pipeline', async () => {
|
|
107
196
|
const state = await makeState();
|
|
108
197
|
|
|
109
|
-
drawWgpuShapeMeshes(state, makeProxy(
|
|
110
|
-
drawWgpuShapeMeshes(state, makeProxy(
|
|
198
|
+
drawWgpuShapeMeshes(state, makeProxy({ blendMode: BlendMode.Normal }), [TRIANGLE], makeBuffers());
|
|
199
|
+
drawWgpuShapeMeshes(state, makeProxy({ blendMode: BlendMode.Add }), [TRIANGLE], makeBuffers());
|
|
111
200
|
|
|
112
201
|
const pipelines = [...getWgpuRenderStateRuntime(state).shapeMeshPipelines!.values()].map(
|
|
113
202
|
(entry) => entry.pipeline,
|
|
@@ -130,9 +219,9 @@ describe('drawWgpuShapeMeshes', () => {
|
|
|
130
219
|
}
|
|
131
220
|
}) as unknown as GPUQueue['writeBuffer'];
|
|
132
221
|
|
|
133
|
-
drawWgpuShapeMeshes(state, makeProxy(
|
|
222
|
+
drawWgpuShapeMeshes(state, makeProxy({ alpha: 0.5 }), [{ ...TRIANGLE, color: 0xffffff }], buffers);
|
|
134
223
|
|
|
135
|
-
const uniformData = writes.get(buffers.
|
|
224
|
+
const uniformData = writes.get(buffers.uniformBuffers[0]);
|
|
136
225
|
expect(uniformData).toBeDefined();
|
|
137
226
|
// Color occupies floats 12..15 (after the mat3x3f columns): premultiplied (0.5, 0.5, 0.5, 0.5).
|
|
138
227
|
const color = uniformData!.slice(12, 16);
|
|
@@ -161,4 +250,70 @@ describe('drawWgpuShapeMeshes', () => {
|
|
|
161
250
|
expect(pass.setPipeline).not.toHaveBeenCalled();
|
|
162
251
|
expect(runtime.shapeMeshPipelines?.size ?? 0).toBe(0);
|
|
163
252
|
});
|
|
253
|
+
|
|
254
|
+
it('ignores ColorScaleBias when the opt-in feature is not registered', async () => {
|
|
255
|
+
const state = await makeState();
|
|
256
|
+
const buffers = makeBuffers();
|
|
257
|
+
|
|
258
|
+
drawWgpuShapeMeshes(state, makeProxy({ colorScaleBias: ct(0.5) }), [TRIANGLE], buffers);
|
|
259
|
+
|
|
260
|
+
expect(buffers.uniformBuffers).toHaveLength(1);
|
|
261
|
+
expect(buffers.colorScaleBiasUniformBuffers).toHaveLength(0);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it('keeps registered but unadjusted meshes on the lean uniform path', async () => {
|
|
265
|
+
const state = await makeState();
|
|
266
|
+
const buffers = makeBuffers();
|
|
267
|
+
registerWgpuColorAdjustmentMaterialFeature(state);
|
|
268
|
+
|
|
269
|
+
drawWgpuShapeMeshes(state, makeProxy(), [TRIANGLE], buffers);
|
|
270
|
+
|
|
271
|
+
expect(buffers.uniformBuffers).toHaveLength(1);
|
|
272
|
+
expect(buffers.colorScaleBiasUniformBuffers).toHaveLength(0);
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it('folds ColorScaleBias through a separate 96-byte uniform and shader', async () => {
|
|
276
|
+
const state = await makeState();
|
|
277
|
+
const buffers = makeBuffers();
|
|
278
|
+
registerWgpuColorAdjustmentMaterialFeature(state);
|
|
279
|
+
const createShaderModule = vi.spyOn(state.device, 'createShaderModule');
|
|
280
|
+
const writes = new Map<GPUBuffer, Float32Array>();
|
|
281
|
+
state.device.queue.writeBuffer = vi.fn((buffer: GPUBuffer, _offset: number, data: BufferSource) => {
|
|
282
|
+
const view = ArrayBuffer.isView(data) ? data : new Uint8Array(data as ArrayBuffer);
|
|
283
|
+
if (view.byteLength % 4 === 0) {
|
|
284
|
+
writes.set(buffer, new Float32Array(view.buffer.slice(view.byteOffset, view.byteOffset + view.byteLength)));
|
|
285
|
+
}
|
|
286
|
+
}) as unknown as GPUQueue['writeBuffer'];
|
|
287
|
+
|
|
288
|
+
drawWgpuShapeMeshes(
|
|
289
|
+
state,
|
|
290
|
+
makeProxy({ alpha: 0.5, colorScaleBias: ct(0.25, 0.5, 0.75, 0.8, 0.1, 0.2, 0.3, 0.4) }),
|
|
291
|
+
[{ ...TRIANGLE, color: 0xffffff }],
|
|
292
|
+
buffers,
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
expect(buffers.uniformBuffers).toHaveLength(0);
|
|
296
|
+
expect(buffers.colorScaleBiasUniformBuffers).toHaveLength(1);
|
|
297
|
+
const uniform = writes.get(buffers.colorScaleBiasUniformBuffers[0]);
|
|
298
|
+
expect(uniform).toHaveLength(24);
|
|
299
|
+
expect(uniform?.slice(12, 16)).toEqual(new Float32Array([0.5, 0.5, 0.5, 0.5]));
|
|
300
|
+
expect(uniform?.slice(16, 24)).toEqual(new Float32Array([0.25, 0.5, 0.75, 0.8, 0.1, 0.2, 0.3, 0.4]));
|
|
301
|
+
const shader = createShaderModule.mock.calls
|
|
302
|
+
.map(([descriptor]) => descriptor.code)
|
|
303
|
+
.find((code) => code.includes('struct ShapeMeshUniforms'));
|
|
304
|
+
expect(shader).toContain('u.color.rgb / u.color.a');
|
|
305
|
+
expect(shader).toContain('applyFlightColorAdjustment(color, u.colorScale, u.colorBias)');
|
|
306
|
+
expect(shader).toContain('color.rgb * color.a');
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it('leaves the full color-matrix path outside the bounded solid-mesh fold', async () => {
|
|
310
|
+
const state = await makeState();
|
|
311
|
+
const buffers = makeBuffers();
|
|
312
|
+
registerWgpuColorAdjustmentMaterialFeature(state);
|
|
313
|
+
|
|
314
|
+
drawWgpuShapeMeshes(state, makeProxy({ colorMatrix: new Array(20).fill(0) }), [TRIANGLE], buffers);
|
|
315
|
+
|
|
316
|
+
expect(buffers.uniformBuffers).toHaveLength(1);
|
|
317
|
+
expect(buffers.colorScaleBiasUniformBuffers).toHaveLength(0);
|
|
318
|
+
});
|
|
164
319
|
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { createWgpuRenderStateForTest, installWgpuMock } from '@flighthq/render-wgpu/contract';
|
|
2
|
+
import { createRenderState, enableRenderRegistryGuards, explainRenderRegistryMisses } from '@flighthq/render/contract';
|
|
3
|
+
import { RenderRegistry } from '@flighthq/types/contract';
|
|
4
|
+
|
|
5
|
+
import { getWgpuShapeRasterizer, registerWgpuShapeRasterizer } from './wgpuShapeRasterizer';
|
|
6
|
+
|
|
7
|
+
beforeAll(() => {
|
|
8
|
+
installWgpuMock();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
describe('getWgpuShapeRasterizer', () => {
|
|
12
|
+
it('reports none until one is registered', async () => {
|
|
13
|
+
const state = await createWgpuRenderStateForTest();
|
|
14
|
+
expect(getWgpuShapeRasterizer(state)).toBeNull();
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
describe('registerWgpuShapeRasterizer', () => {
|
|
19
|
+
it('installs the rasterizer the shape path draws non-solid fills through', async () => {
|
|
20
|
+
const state = await createWgpuRenderStateForTest();
|
|
21
|
+
const rasterizer = (): void => {};
|
|
22
|
+
|
|
23
|
+
registerWgpuShapeRasterizer(state, rasterizer);
|
|
24
|
+
|
|
25
|
+
expect(getWgpuShapeRasterizer(state)).toBe(rasterizer);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('removes one again, so a state can drop back to tessellation only', async () => {
|
|
29
|
+
const state = await createWgpuRenderStateForTest();
|
|
30
|
+
registerWgpuShapeRasterizer(state, (): void => {});
|
|
31
|
+
|
|
32
|
+
registerWgpuShapeRasterizer(state, null);
|
|
33
|
+
|
|
34
|
+
expect(getWgpuShapeRasterizer(state)).toBeNull();
|
|
35
|
+
});
|
|
36
|
+
});
|