@flighthq/displayobject-wgpu 0.1.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/index.d.ts +29 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +29 -0
- package/dist/index.js.map +1 -0
- package/dist/moduleMockTestHelper.d.ts +15 -0
- package/dist/moduleMockTestHelper.d.ts.map +1 -0
- package/dist/moduleMockTestHelper.js +25 -0
- package/dist/moduleMockTestHelper.js.map +1 -0
- package/dist/wgpuBitmap.d.ts +4 -0
- package/dist/wgpuBitmap.d.ts.map +1 -0
- package/dist/wgpuBitmap.js +80 -0
- package/dist/wgpuBitmap.js.map +1 -0
- package/dist/wgpuCache.d.ts +31 -0
- package/dist/wgpuCache.d.ts.map +1 -0
- package/dist/wgpuCache.js +232 -0
- package/dist/wgpuCache.js.map +1 -0
- package/dist/wgpuClip.d.ts +3 -0
- package/dist/wgpuClip.d.ts.map +1 -0
- package/dist/wgpuClip.js +44 -0
- package/dist/wgpuClip.js.map +1 -0
- package/dist/wgpuClipContours.d.ts +4 -0
- package/dist/wgpuClipContours.d.ts.map +1 -0
- package/dist/wgpuClipContours.js +174 -0
- package/dist/wgpuClipContours.js.map +1 -0
- package/dist/wgpuClipRectangle.d.ts +4 -0
- package/dist/wgpuClipRectangle.d.ts.map +1 -0
- package/dist/wgpuClipRectangle.js +80 -0
- package/dist/wgpuClipRectangle.js.map +1 -0
- package/dist/wgpuColorTransformMaterial.d.ts +5 -0
- package/dist/wgpuColorTransformMaterial.d.ts.map +1 -0
- package/dist/wgpuColorTransformMaterial.js +97 -0
- package/dist/wgpuColorTransformMaterial.js.map +1 -0
- package/dist/wgpuDefaultMaterial.d.ts +4 -0
- package/dist/wgpuDefaultMaterial.d.ts.map +1 -0
- package/dist/wgpuDefaultMaterial.js +46 -0
- package/dist/wgpuDefaultMaterial.js.map +1 -0
- package/dist/wgpuDisplayObject.d.ts +5 -0
- package/dist/wgpuDisplayObject.d.ts.map +1 -0
- package/dist/wgpuDisplayObject.js +41 -0
- package/dist/wgpuDisplayObject.js.map +1 -0
- package/dist/wgpuMaterials.d.ts +15 -0
- package/dist/wgpuMaterials.d.ts.map +1 -0
- package/dist/wgpuMaterials.js +34 -0
- package/dist/wgpuMaterials.js.map +1 -0
- package/dist/wgpuParticleEmitter.d.ts +4 -0
- package/dist/wgpuParticleEmitter.d.ts.map +1 -0
- package/dist/wgpuParticleEmitter.js +288 -0
- package/dist/wgpuParticleEmitter.js.map +1 -0
- package/dist/wgpuQuadBatch.d.ts +7 -0
- package/dist/wgpuQuadBatch.d.ts.map +1 -0
- package/dist/wgpuQuadBatch.js +85 -0
- package/dist/wgpuQuadBatch.js.map +1 -0
- package/dist/wgpuRegistration.d.ts +19 -0
- package/dist/wgpuRegistration.d.ts.map +1 -0
- package/dist/wgpuRegistration.js +53 -0
- package/dist/wgpuRegistration.js.map +1 -0
- package/dist/wgpuRenderStats.d.ts +20 -0
- package/dist/wgpuRenderStats.d.ts.map +1 -0
- package/dist/wgpuRenderStats.js +50 -0
- package/dist/wgpuRenderStats.js.map +1 -0
- package/dist/wgpuRendererData.d.ts +26 -0
- package/dist/wgpuRendererData.d.ts.map +1 -0
- package/dist/wgpuRendererData.js +29 -0
- package/dist/wgpuRendererData.js.map +1 -0
- package/dist/wgpuRichText.d.ts +9 -0
- package/dist/wgpuRichText.d.ts.map +1 -0
- package/dist/wgpuRichText.js +163 -0
- package/dist/wgpuRichText.js.map +1 -0
- package/dist/wgpuScale9Mapper.d.ts +3 -0
- package/dist/wgpuScale9Mapper.d.ts.map +1 -0
- package/dist/wgpuScale9Mapper.js +42 -0
- package/dist/wgpuScale9Mapper.js.map +1 -0
- package/dist/wgpuScale9Shape.d.ts +9 -0
- package/dist/wgpuScale9Shape.d.ts.map +1 -0
- package/dist/wgpuScale9Shape.js +123 -0
- package/dist/wgpuScale9Shape.js.map +1 -0
- package/dist/wgpuShape.d.ts +4 -0
- package/dist/wgpuShape.d.ts.map +1 -0
- package/dist/wgpuShape.js +157 -0
- package/dist/wgpuShape.js.map +1 -0
- package/dist/wgpuShapeMesh.d.ts +9 -0
- package/dist/wgpuShapeMesh.d.ts.map +1 -0
- package/dist/wgpuShapeMesh.js +204 -0
- package/dist/wgpuShapeMesh.js.map +1 -0
- package/dist/wgpuSprite.d.ts +3 -0
- package/dist/wgpuSprite.d.ts.map +1 -0
- package/dist/wgpuSprite.js +28 -0
- package/dist/wgpuSprite.js.map +1 -0
- package/dist/wgpuSpriteBatch.d.ts +18 -0
- package/dist/wgpuSpriteBatch.d.ts.map +1 -0
- package/dist/wgpuSpriteBatch.js +320 -0
- package/dist/wgpuSpriteBatch.js.map +1 -0
- package/dist/wgpuSpriteRenderer.d.ts +3 -0
- package/dist/wgpuSpriteRenderer.d.ts.map +1 -0
- package/dist/wgpuSpriteRenderer.js +51 -0
- package/dist/wgpuSpriteRenderer.js.map +1 -0
- package/dist/wgpuTextInput.d.ts +4 -0
- package/dist/wgpuTextInput.d.ts.map +1 -0
- package/dist/wgpuTextInput.js +44 -0
- package/dist/wgpuTextInput.js.map +1 -0
- package/dist/wgpuTextLabel.d.ts +4 -0
- package/dist/wgpuTextLabel.d.ts.map +1 -0
- package/dist/wgpuTextLabel.js +157 -0
- package/dist/wgpuTextLabel.js.map +1 -0
- package/dist/wgpuTilemap.d.ts +3 -0
- package/dist/wgpuTilemap.d.ts.map +1 -0
- package/dist/wgpuTilemap.js +76 -0
- package/dist/wgpuTilemap.js.map +1 -0
- package/dist/wgpuVelocity.d.ts +23 -0
- package/dist/wgpuVelocity.d.ts.map +1 -0
- package/dist/wgpuVelocity.js +365 -0
- package/dist/wgpuVelocity.js.map +1 -0
- package/dist/wgpuVideo.d.ts +6 -0
- package/dist/wgpuVideo.d.ts.map +1 -0
- package/dist/wgpuVideo.js +63 -0
- package/dist/wgpuVideo.js.map +1 -0
- package/package.json +52 -0
- package/src/wgpuBitmap.test.ts +40 -0
- package/src/wgpuCache.test.ts +230 -0
- package/src/wgpuClip.test.ts +15 -0
- package/src/wgpuClipContours.test.ts +101 -0
- package/src/wgpuClipRectangle.test.ts +45 -0
- package/src/wgpuColorTransformMaterial.test.ts +62 -0
- package/src/wgpuDefaultMaterial.test.ts +21 -0
- package/src/wgpuDisplayObject.test.ts +36 -0
- package/src/wgpuMaterials.test.ts +44 -0
- package/src/wgpuParticleEmitter.test.ts +34 -0
- package/src/wgpuQuadBatch.test.ts +69 -0
- package/src/wgpuRegistration.test.ts +115 -0
- package/src/wgpuRenderStats.test.ts +89 -0
- package/src/wgpuRendererData.test.ts +29 -0
- package/src/wgpuRichText.test.ts +100 -0
- package/src/wgpuScale9Mapper.test.ts +20 -0
- package/src/wgpuScale9Shape.test.ts +97 -0
- package/src/wgpuShape.test.ts +95 -0
- package/src/wgpuShapeMesh.test.ts +124 -0
- package/src/wgpuSprite.test.ts +19 -0
- package/src/wgpuSpriteBatch.test.ts +180 -0
- package/src/wgpuSpriteRenderer.test.ts +38 -0
- package/src/wgpuTextInput.test.ts +37 -0
- package/src/wgpuTextLabel.test.ts +113 -0
- package/src/wgpuTilemap.test.ts +47 -0
- package/src/wgpuVelocity.test.ts +147 -0
- package/src/wgpuVideo.test.ts +59 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { getRenderStateRuntime } from '@flighthq/render';
|
|
2
|
+
import { createWgpuRenderStateForTest, installWgpuMock } from '@flighthq/render-wgpu';
|
|
3
|
+
import {
|
|
4
|
+
BitmapKind,
|
|
5
|
+
DisplayObjectKind,
|
|
6
|
+
ParticleEmitterKind,
|
|
7
|
+
QuadBatchKind,
|
|
8
|
+
RichTextKind,
|
|
9
|
+
Scale9ShapeKind,
|
|
10
|
+
ShapeKind,
|
|
11
|
+
SpriteKind,
|
|
12
|
+
TextLabelKind,
|
|
13
|
+
TilemapKind,
|
|
14
|
+
VideoKind,
|
|
15
|
+
} from '@flighthq/types';
|
|
16
|
+
|
|
17
|
+
import { registerWgpuDisplayObjectRenderers, registerWgpuSpriteRenderers } from './wgpuRegistration';
|
|
18
|
+
|
|
19
|
+
beforeAll(() => {
|
|
20
|
+
installWgpuMock();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe('registerWgpuDisplayObjectRenderers', () => {
|
|
24
|
+
it('registers bitmap renderer', async () => {
|
|
25
|
+
const state = await createWgpuRenderStateForTest();
|
|
26
|
+
registerWgpuDisplayObjectRenderers(state);
|
|
27
|
+
expect(getRenderStateRuntime(state).rendererMap.get(BitmapKind)).not.toBeUndefined();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('registers display object renderer', async () => {
|
|
31
|
+
const state = await createWgpuRenderStateForTest();
|
|
32
|
+
registerWgpuDisplayObjectRenderers(state);
|
|
33
|
+
expect(getRenderStateRuntime(state).rendererMap.get(DisplayObjectKind)).not.toBeUndefined();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('registers particle emitter renderer', async () => {
|
|
37
|
+
const state = await createWgpuRenderStateForTest();
|
|
38
|
+
registerWgpuDisplayObjectRenderers(state);
|
|
39
|
+
expect(getRenderStateRuntime(state).rendererMap.get(ParticleEmitterKind)).not.toBeUndefined();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('registers quad batch renderer', async () => {
|
|
43
|
+
const state = await createWgpuRenderStateForTest();
|
|
44
|
+
registerWgpuDisplayObjectRenderers(state);
|
|
45
|
+
expect(getRenderStateRuntime(state).rendererMap.get(QuadBatchKind)).not.toBeUndefined();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('registers rich text renderer', async () => {
|
|
49
|
+
const state = await createWgpuRenderStateForTest();
|
|
50
|
+
registerWgpuDisplayObjectRenderers(state);
|
|
51
|
+
expect(getRenderStateRuntime(state).rendererMap.get(RichTextKind)).not.toBeUndefined();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('registers scale9 shape renderer', async () => {
|
|
55
|
+
const state = await createWgpuRenderStateForTest();
|
|
56
|
+
registerWgpuDisplayObjectRenderers(state);
|
|
57
|
+
expect(getRenderStateRuntime(state).rendererMap.get(Scale9ShapeKind)).not.toBeUndefined();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('registers shape renderer', async () => {
|
|
61
|
+
const state = await createWgpuRenderStateForTest();
|
|
62
|
+
registerWgpuDisplayObjectRenderers(state);
|
|
63
|
+
expect(getRenderStateRuntime(state).rendererMap.get(ShapeKind)).not.toBeUndefined();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('registers sprite renderer', async () => {
|
|
67
|
+
const state = await createWgpuRenderStateForTest();
|
|
68
|
+
registerWgpuDisplayObjectRenderers(state);
|
|
69
|
+
expect(getRenderStateRuntime(state).rendererMap.get(SpriteKind)).not.toBeUndefined();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('registers text label renderer', async () => {
|
|
73
|
+
const state = await createWgpuRenderStateForTest();
|
|
74
|
+
registerWgpuDisplayObjectRenderers(state);
|
|
75
|
+
expect(getRenderStateRuntime(state).rendererMap.get(TextLabelKind)).not.toBeUndefined();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('registers tilemap renderer', async () => {
|
|
79
|
+
const state = await createWgpuRenderStateForTest();
|
|
80
|
+
registerWgpuDisplayObjectRenderers(state);
|
|
81
|
+
expect(getRenderStateRuntime(state).rendererMap.get(TilemapKind)).not.toBeUndefined();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('registers video renderer', async () => {
|
|
85
|
+
const state = await createWgpuRenderStateForTest();
|
|
86
|
+
registerWgpuDisplayObjectRenderers(state);
|
|
87
|
+
expect(getRenderStateRuntime(state).rendererMap.get(VideoKind)).not.toBeUndefined();
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe('registerWgpuSpriteRenderers', () => {
|
|
92
|
+
it('registers particle emitter renderer', async () => {
|
|
93
|
+
const state = await createWgpuRenderStateForTest();
|
|
94
|
+
registerWgpuSpriteRenderers(state);
|
|
95
|
+
expect(getRenderStateRuntime(state).rendererMap.get(ParticleEmitterKind)).not.toBeUndefined();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('registers quad batch renderer', async () => {
|
|
99
|
+
const state = await createWgpuRenderStateForTest();
|
|
100
|
+
registerWgpuSpriteRenderers(state);
|
|
101
|
+
expect(getRenderStateRuntime(state).rendererMap.get(QuadBatchKind)).not.toBeUndefined();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('registers sprite renderer', async () => {
|
|
105
|
+
const state = await createWgpuRenderStateForTest();
|
|
106
|
+
registerWgpuSpriteRenderers(state);
|
|
107
|
+
expect(getRenderStateRuntime(state).rendererMap.get(SpriteKind)).not.toBeUndefined();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('registers tilemap renderer', async () => {
|
|
111
|
+
const state = await createWgpuRenderStateForTest();
|
|
112
|
+
registerWgpuSpriteRenderers(state);
|
|
113
|
+
expect(getRenderStateRuntime(state).rendererMap.get(TilemapKind)).not.toBeUndefined();
|
|
114
|
+
});
|
|
115
|
+
});
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { createWgpuRenderStateForTest, installWgpuMock } from '@flighthq/render-wgpu';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
getWgpuRenderStats,
|
|
5
|
+
recordWgpuBatchFlush,
|
|
6
|
+
recordWgpuTextureUpload,
|
|
7
|
+
resetWgpuRenderStats,
|
|
8
|
+
} from './wgpuRenderStats';
|
|
9
|
+
|
|
10
|
+
beforeAll(() => {
|
|
11
|
+
installWgpuMock();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
describe('getWgpuRenderStats', () => {
|
|
15
|
+
it('returns zeroed stats for a fresh state', async () => {
|
|
16
|
+
const state = await createWgpuRenderStateForTest();
|
|
17
|
+
const stats = getWgpuRenderStats(state);
|
|
18
|
+
expect(stats.drawCallCount).toBe(0);
|
|
19
|
+
expect(stats.instanceCount).toBe(0);
|
|
20
|
+
expect(stats.batchFlushCount).toBe(0);
|
|
21
|
+
expect(stats.textureUploadCount).toBe(0);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('returns the same object reference on repeated calls', async () => {
|
|
25
|
+
const state = await createWgpuRenderStateForTest();
|
|
26
|
+
const a = getWgpuRenderStats(state);
|
|
27
|
+
const b = getWgpuRenderStats(state);
|
|
28
|
+
expect(a).toBe(b);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe('recordWgpuBatchFlush', () => {
|
|
33
|
+
it('increments draw call, instance, and batch flush counts', async () => {
|
|
34
|
+
const state = await createWgpuRenderStateForTest();
|
|
35
|
+
resetWgpuRenderStats(state);
|
|
36
|
+
recordWgpuBatchFlush(state, 10);
|
|
37
|
+
const stats = getWgpuRenderStats(state);
|
|
38
|
+
expect(stats.drawCallCount).toBe(1);
|
|
39
|
+
expect(stats.instanceCount).toBe(10);
|
|
40
|
+
expect(stats.batchFlushCount).toBe(1);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('accumulates across multiple flushes', async () => {
|
|
44
|
+
const state = await createWgpuRenderStateForTest();
|
|
45
|
+
resetWgpuRenderStats(state);
|
|
46
|
+
recordWgpuBatchFlush(state, 5);
|
|
47
|
+
recordWgpuBatchFlush(state, 3);
|
|
48
|
+
const stats = getWgpuRenderStats(state);
|
|
49
|
+
expect(stats.drawCallCount).toBe(2);
|
|
50
|
+
expect(stats.instanceCount).toBe(8);
|
|
51
|
+
expect(stats.batchFlushCount).toBe(2);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('is a no-op when stats have not been initialized via getWgpuRenderStats or resetWgpuRenderStats', async () => {
|
|
55
|
+
const state = await createWgpuRenderStateForTest();
|
|
56
|
+
// Calling recordWgpuBatchFlush before getWgpuRenderStats / resetWgpuRenderStats should not throw.
|
|
57
|
+
expect(() => recordWgpuBatchFlush(state, 10)).not.toThrow();
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe('recordWgpuTextureUpload', () => {
|
|
62
|
+
it('increments texture upload count', async () => {
|
|
63
|
+
const state = await createWgpuRenderStateForTest();
|
|
64
|
+
resetWgpuRenderStats(state);
|
|
65
|
+
recordWgpuTextureUpload(state);
|
|
66
|
+
recordWgpuTextureUpload(state);
|
|
67
|
+
expect(getWgpuRenderStats(state).textureUploadCount).toBe(2);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('is a no-op when stats have not been initialized', async () => {
|
|
71
|
+
const state = await createWgpuRenderStateForTest();
|
|
72
|
+
expect(() => recordWgpuTextureUpload(state)).not.toThrow();
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe('resetWgpuRenderStats', () => {
|
|
77
|
+
it('zeroes all counts', async () => {
|
|
78
|
+
const state = await createWgpuRenderStateForTest();
|
|
79
|
+
resetWgpuRenderStats(state);
|
|
80
|
+
recordWgpuBatchFlush(state, 100);
|
|
81
|
+
recordWgpuTextureUpload(state);
|
|
82
|
+
resetWgpuRenderStats(state);
|
|
83
|
+
const stats = getWgpuRenderStats(state);
|
|
84
|
+
expect(stats.drawCallCount).toBe(0);
|
|
85
|
+
expect(stats.instanceCount).toBe(0);
|
|
86
|
+
expect(stats.batchFlushCount).toBe(0);
|
|
87
|
+
expect(stats.textureUploadCount).toBe(0);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { createWgpuRendererData, getWgpuRendererData } from './wgpuRendererData';
|
|
2
|
+
|
|
3
|
+
describe('createWgpuRendererData', () => {
|
|
4
|
+
it('returns the input data cast to RendererData', () => {
|
|
5
|
+
const data = { x: 1, y: 2 };
|
|
6
|
+
const result = createWgpuRendererData(data);
|
|
7
|
+
expect(result).toBe(data);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('round-trips through getWgpuRendererData', () => {
|
|
11
|
+
const data = { value: 42 };
|
|
12
|
+
const rendererData = createWgpuRendererData(data);
|
|
13
|
+
const recovered = getWgpuRendererData<{ value: number }>(rendererData);
|
|
14
|
+
expect(recovered).toBe(data);
|
|
15
|
+
expect(recovered?.value).toBe(42);
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe('getWgpuRendererData', () => {
|
|
20
|
+
it('returns null when data is null', () => {
|
|
21
|
+
expect(getWgpuRendererData(null)).toBeNull();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('returns the original object', () => {
|
|
25
|
+
const data = { canvas: null, lastContentId: -1 };
|
|
26
|
+
const result = getWgpuRendererData(createWgpuRendererData(data));
|
|
27
|
+
expect(result).toBe(data);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { getOrCreateRenderProxy2D, prepareDisplayObjectRender } from '@flighthq/render';
|
|
2
|
+
import { renderWgpuBackground, submitWgpuRenderPass } from '@flighthq/render-wgpu';
|
|
3
|
+
import { createWgpuRenderStateForTest, installWgpuMock } from '@flighthq/render-wgpu';
|
|
4
|
+
import { createRichText } from '@flighthq/text';
|
|
5
|
+
import { enableTextInput } from '@flighthq/textinput';
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
createWgpuRichTextData,
|
|
9
|
+
defaultWgpuRichTextRenderer,
|
|
10
|
+
destroyWgpuRichTextData,
|
|
11
|
+
drawWgpuRichText,
|
|
12
|
+
drawWgpuRichTextWithOverlay,
|
|
13
|
+
registerWgpuTextInputOverlay,
|
|
14
|
+
} from './wgpuRichText';
|
|
15
|
+
|
|
16
|
+
beforeAll(() => {
|
|
17
|
+
installWgpuMock();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe('createWgpuRichTextData', () => {
|
|
21
|
+
it('allocates per-node data with no texture entry yet', () => {
|
|
22
|
+
const data = createWgpuRichTextData({} as never, {} as never) as unknown as { entry: unknown };
|
|
23
|
+
expect(data.entry).toBeNull();
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe('defaultWgpuRichTextRenderer', () => {
|
|
28
|
+
it('has createData and draw functions', () => {
|
|
29
|
+
expect(typeof defaultWgpuRichTextRenderer.createData).toBe('function');
|
|
30
|
+
expect(typeof defaultWgpuRichTextRenderer.submit).toBe('function');
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe('destroyWgpuRichTextData', () => {
|
|
35
|
+
it('destroys the GPU texture the node owns', () => {
|
|
36
|
+
const destroy = vi.fn();
|
|
37
|
+
destroyWgpuRichTextData({} as never, { entry: { texture: { destroy } } } as never);
|
|
38
|
+
expect(destroy).toHaveBeenCalled();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('is a no-op when no texture entry was allocated', () => {
|
|
42
|
+
expect(() => destroyWgpuRichTextData({} as never, { entry: null } as never)).not.toThrow();
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe('drawWgpuRichText', () => {
|
|
47
|
+
it('does not throw for empty rich text', async () => {
|
|
48
|
+
const state = await createWgpuRenderStateForTest();
|
|
49
|
+
renderWgpuBackground(state);
|
|
50
|
+
|
|
51
|
+
const richText = createRichText();
|
|
52
|
+
prepareDisplayObjectRender(state, richText);
|
|
53
|
+
const renderProxy = getOrCreateRenderProxy2D(state, richText);
|
|
54
|
+
|
|
55
|
+
expect(() => drawWgpuRichText(state, renderProxy)).not.toThrow();
|
|
56
|
+
submitWgpuRenderPass(state);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('does not throw when renderPass is null', async () => {
|
|
60
|
+
const state = await createWgpuRenderStateForTest();
|
|
61
|
+
const richText = createRichText();
|
|
62
|
+
prepareDisplayObjectRender(state, richText);
|
|
63
|
+
const renderProxy = getOrCreateRenderProxy2D(state, richText);
|
|
64
|
+
|
|
65
|
+
expect(() => drawWgpuRichText(state, renderProxy)).not.toThrow();
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe('drawWgpuRichTextWithOverlay', () => {
|
|
70
|
+
it('does not throw for empty rich text', async () => {
|
|
71
|
+
const state = await createWgpuRenderStateForTest();
|
|
72
|
+
renderWgpuBackground(state);
|
|
73
|
+
const richText = createRichText();
|
|
74
|
+
prepareDisplayObjectRender(state, richText);
|
|
75
|
+
const renderProxy = getOrCreateRenderProxy2D(state, richText);
|
|
76
|
+
expect(() => drawWgpuRichTextWithOverlay(state, renderProxy)).not.toThrow();
|
|
77
|
+
submitWgpuRenderPass(state);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe('registerWgpuTextInputOverlay', () => {
|
|
82
|
+
it('invokes the registered overlay only for a RichText with an input slot', async () => {
|
|
83
|
+
const overlay = vi.fn();
|
|
84
|
+
registerWgpuTextInputOverlay(overlay);
|
|
85
|
+
const state = await createWgpuRenderStateForTest();
|
|
86
|
+
renderWgpuBackground(state);
|
|
87
|
+
|
|
88
|
+
const plain = createRichText({ data: { height: 40, text: 'x', width: 100 } });
|
|
89
|
+
prepareDisplayObjectRender(state, plain);
|
|
90
|
+
drawWgpuRichText(state, getOrCreateRenderProxy2D(state, plain));
|
|
91
|
+
expect(overlay).not.toHaveBeenCalled();
|
|
92
|
+
|
|
93
|
+
const editable = createRichText({ data: { height: 40, text: 'x', width: 100 } });
|
|
94
|
+
enableTextInput(editable);
|
|
95
|
+
prepareDisplayObjectRender(state, editable);
|
|
96
|
+
drawWgpuRichText(state, getOrCreateRenderProxy2D(state, editable));
|
|
97
|
+
expect(overlay).toHaveBeenCalled();
|
|
98
|
+
submitWgpuRenderPass(state);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { createRectangle } from '@flighthq/geometry';
|
|
2
|
+
|
|
3
|
+
import { buildWgpuScale9Mapper } from './wgpuScale9Mapper';
|
|
4
|
+
|
|
5
|
+
describe('buildWgpuScale9Mapper', () => {
|
|
6
|
+
it('returns null for invalid scale or bounds', () => {
|
|
7
|
+
expect(buildWgpuScale9Mapper(createRectangle(0, 0, 0, 100), createRectangle(10, 10, 20, 20), 1, 1)).toBeNull();
|
|
8
|
+
expect(buildWgpuScale9Mapper(createRectangle(0, 0, 100, 100), createRectangle(10, 10, 20, 20), 0, 1)).toBeNull();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('maps edge and center coordinates using 9-slice scaling', () => {
|
|
12
|
+
const mapper = buildWgpuScale9Mapper(createRectangle(0, 0, 100, 100), createRectangle(10, 10, 20, 20), 2, 3)!;
|
|
13
|
+
|
|
14
|
+
expect(mapper.mapX(5)).toBe(5);
|
|
15
|
+
expect(mapper.mapX(20)).toBe(70);
|
|
16
|
+
expect(mapper.mapX(40)).toBe(140);
|
|
17
|
+
expect(mapper.mapY(20)).toBe(120);
|
|
18
|
+
expect(mapper.mapY(40)).toBe(240);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { getOrCreateRenderProxy2D, prepareDisplayObjectRender } from '@flighthq/render';
|
|
2
|
+
import { renderWgpuBackground, submitWgpuRenderPass } from '@flighthq/render-wgpu';
|
|
3
|
+
import { createWgpuRenderStateForTest, installWgpuMock } from '@flighthq/render-wgpu';
|
|
4
|
+
import { appendShapeBeginFill, appendShapeRectangle, createScale9Shape } from '@flighthq/shape';
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
createWgpuScale9ShapeData,
|
|
8
|
+
defaultWgpuScale9ShapeRenderer,
|
|
9
|
+
destroyWgpuScale9ShapeData,
|
|
10
|
+
drawWgpuScale9Shape,
|
|
11
|
+
drawWgpuScale9ShapeMask,
|
|
12
|
+
remapWgpuScale9Commands,
|
|
13
|
+
} from './wgpuScale9Shape';
|
|
14
|
+
|
|
15
|
+
const grid = { height: 80, width: 80, x: 10, y: 10 };
|
|
16
|
+
|
|
17
|
+
beforeAll(() => {
|
|
18
|
+
installWgpuMock();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe('createWgpuScale9ShapeData', () => {
|
|
22
|
+
it('creates renderer data with no texture entry yet', () => {
|
|
23
|
+
const data = createWgpuScale9ShapeData({} as never, createScale9Shape(grid)) as unknown as { entry: unknown };
|
|
24
|
+
expect(data.entry).toBeNull();
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe('defaultWgpuScale9ShapeRenderer', () => {
|
|
29
|
+
it('wires createData, destroyData, and submit', () => {
|
|
30
|
+
expect(defaultWgpuScale9ShapeRenderer.createData).toBe(createWgpuScale9ShapeData);
|
|
31
|
+
expect(defaultWgpuScale9ShapeRenderer.destroyData).toBe(destroyWgpuScale9ShapeData);
|
|
32
|
+
expect(defaultWgpuScale9ShapeRenderer.submit).toBe(drawWgpuScale9Shape);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe('destroyWgpuScale9ShapeData', () => {
|
|
37
|
+
it('destroys the texture it owns', () => {
|
|
38
|
+
const destroy = vi.fn();
|
|
39
|
+
destroyWgpuScale9ShapeData({} as never, { entry: { texture: { destroy } } } as never);
|
|
40
|
+
expect(destroy).toHaveBeenCalled();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('is a no-op when no texture entry was allocated', () => {
|
|
44
|
+
expect(() => destroyWgpuScale9ShapeData({} as never, { entry: null } as never)).not.toThrow();
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe('drawWgpuScale9Shape', () => {
|
|
49
|
+
it('returns early when commands are empty', async () => {
|
|
50
|
+
const state = await createWgpuRenderStateForTest();
|
|
51
|
+
renderWgpuBackground(state);
|
|
52
|
+
const shape = createScale9Shape(grid);
|
|
53
|
+
prepareDisplayObjectRender(state, shape);
|
|
54
|
+
const renderProxy = getOrCreateRenderProxy2D(state, shape);
|
|
55
|
+
|
|
56
|
+
expect(() => drawWgpuScale9Shape(state, renderProxy)).not.toThrow();
|
|
57
|
+
submitWgpuRenderPass(state);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('rasterizes and draws a filled shape without throwing', async () => {
|
|
61
|
+
const state = await createWgpuRenderStateForTest();
|
|
62
|
+
renderWgpuBackground(state);
|
|
63
|
+
const shape = createScale9Shape(grid);
|
|
64
|
+
appendShapeBeginFill(shape, 0xff0000);
|
|
65
|
+
appendShapeRectangle(shape, 0, 0, 100, 100);
|
|
66
|
+
prepareDisplayObjectRender(state, shape);
|
|
67
|
+
const renderProxy = getOrCreateRenderProxy2D(state, shape);
|
|
68
|
+
|
|
69
|
+
expect(() => drawWgpuScale9Shape(state, renderProxy)).not.toThrow();
|
|
70
|
+
submitWgpuRenderPass(state);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe('drawWgpuScale9ShapeMask', () => {
|
|
75
|
+
it('delegates to the Scale9 draw path', async () => {
|
|
76
|
+
const state = await createWgpuRenderStateForTest();
|
|
77
|
+
renderWgpuBackground(state);
|
|
78
|
+
const shape = createScale9Shape(grid);
|
|
79
|
+
prepareDisplayObjectRender(state, shape);
|
|
80
|
+
const renderProxy = getOrCreateRenderProxy2D(state, shape);
|
|
81
|
+
|
|
82
|
+
expect(() => drawWgpuScale9ShapeMask(state, renderProxy)).not.toThrow();
|
|
83
|
+
submitWgpuRenderPass(state);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
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
|
+
});
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type * as FlightNodeModule from '@flighthq/node';
|
|
2
|
+
import { renderWgpuBackground, submitWgpuRenderPass } from '@flighthq/render-wgpu';
|
|
3
|
+
import { getWgpuRenderStateRuntime } from '@flighthq/render-wgpu';
|
|
4
|
+
import { createWgpuRenderStateForTest, installWgpuMock } from '@flighthq/render-wgpu';
|
|
5
|
+
import type { RenderProxy2D } from '@flighthq/types';
|
|
6
|
+
import { BatchFormat } from '@flighthq/types';
|
|
7
|
+
|
|
8
|
+
import { scopeModuleMocks } from './moduleMockTestHelper';
|
|
9
|
+
import { registerDefaultWgpuMaterial } from './wgpuDefaultMaterial';
|
|
10
|
+
import type * as WgpuShapeModule from './wgpuShape';
|
|
11
|
+
|
|
12
|
+
// @flighthq/node's bounds/revision queries expect a real BoundsNode; these tests drive drawWgpuShape
|
|
13
|
+
// with lightweight fake proxies, so the two queries are stubbed. scopeModuleMocks scopes the stub to
|
|
14
|
+
// this file (registry reset before the mock applies, unmock + reset after), so under a shared
|
|
15
|
+
// (isolate:false) worker it never leaks into the many real consumers of these functions (node,
|
|
16
|
+
// interaction, shape, text) — and a sibling that pre-evaluated ./wgpuShape still picks up the stub.
|
|
17
|
+
let defaultWgpuShapeRenderer: typeof WgpuShapeModule.defaultWgpuShapeRenderer;
|
|
18
|
+
let drawWgpuShape: typeof WgpuShapeModule.drawWgpuShape;
|
|
19
|
+
|
|
20
|
+
scopeModuleMocks(['@flighthq/node']);
|
|
21
|
+
|
|
22
|
+
beforeAll(async () => {
|
|
23
|
+
vi.doMock('@flighthq/node', async (importOriginal) => ({
|
|
24
|
+
...(await importOriginal<typeof FlightNodeModule>()),
|
|
25
|
+
getNodeLocalBoundsRectangle: () => ({ x: 0, y: 0, width: 64, height: 48 }),
|
|
26
|
+
getNodeLocalContentRevision: (source: any) => source?.data?.version ?? 0,
|
|
27
|
+
}));
|
|
28
|
+
({ defaultWgpuShapeRenderer, drawWgpuShape } = await import('./wgpuShape'));
|
|
29
|
+
installWgpuMock();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
function makeShapeData() {
|
|
33
|
+
const canvas = document.createElement('canvas');
|
|
34
|
+
canvas.width = 1;
|
|
35
|
+
canvas.height = 1;
|
|
36
|
+
const ctx = canvas.getContext('2d')!;
|
|
37
|
+
return { canvas, ctx, lastContentId: -1, lastW: 0, lastH: 0 };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function makeShapeProxy(data: Record<string, unknown> = {}, rendererData: unknown = null): RenderProxy2D {
|
|
41
|
+
return {
|
|
42
|
+
source: { data: { commands: [], version: 0, ...data } },
|
|
43
|
+
blendMode: 0,
|
|
44
|
+
alpha: 1,
|
|
45
|
+
material: null,
|
|
46
|
+
materialData: null,
|
|
47
|
+
transform2D: { a: 1, b: 0, c: 0, d: 1, tx: 0, ty: 0 },
|
|
48
|
+
rendererData,
|
|
49
|
+
} as unknown as RenderProxy2D;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
describe('defaultWgpuShapeRenderer', () => {
|
|
53
|
+
it('declares BatchFormat.Quad', () => {
|
|
54
|
+
expect(defaultWgpuShapeRenderer.format).toBe(BatchFormat.Quad);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('has createData and submit functions', () => {
|
|
58
|
+
expect(typeof defaultWgpuShapeRenderer.createData).toBe('function');
|
|
59
|
+
expect(typeof defaultWgpuShapeRenderer.submit).toBe('function');
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe('drawWgpuShape', () => {
|
|
64
|
+
it('returns early without writing to batch when commands are empty', async () => {
|
|
65
|
+
const state = await createWgpuRenderStateForTest();
|
|
66
|
+
renderWgpuBackground(state);
|
|
67
|
+
registerDefaultWgpuMaterial(state);
|
|
68
|
+
drawWgpuShape(state, makeShapeProxy({ commands: [] }, makeShapeData()));
|
|
69
|
+
expect(getWgpuRenderStateRuntime(state).spriteBatchCount).toBe(0);
|
|
70
|
+
submitWgpuRenderPass(state);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('returns early without writing to batch when rendererData is null', async () => {
|
|
74
|
+
const state = await createWgpuRenderStateForTest();
|
|
75
|
+
renderWgpuBackground(state);
|
|
76
|
+
registerDefaultWgpuMaterial(state);
|
|
77
|
+
drawWgpuShape(state, makeShapeProxy({ commands: [{}] }, null));
|
|
78
|
+
expect(getWgpuRenderStateRuntime(state).spriteBatchCount).toBe(0);
|
|
79
|
+
submitWgpuRenderPass(state);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('does not throw when renderPass is null', async () => {
|
|
83
|
+
const state = await createWgpuRenderStateForTest();
|
|
84
|
+
expect(() => drawWgpuShape(state, makeShapeProxy({ commands: [{}] }, makeShapeData()))).not.toThrow();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('writes one instance to the sprite batch when shape has valid commands and bounds', async () => {
|
|
88
|
+
const state = await createWgpuRenderStateForTest();
|
|
89
|
+
renderWgpuBackground(state);
|
|
90
|
+
registerDefaultWgpuMaterial(state);
|
|
91
|
+
drawWgpuShape(state, makeShapeProxy({ commands: [{}], version: 1 }, makeShapeData()));
|
|
92
|
+
expect(getWgpuRenderStateRuntime(state).spriteBatchCount).toBe(1);
|
|
93
|
+
submitWgpuRenderPass(state);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { createMatrix } from '@flighthq/geometry';
|
|
2
|
+
import { getWgpuRenderStateRuntime } from '@flighthq/render-wgpu';
|
|
3
|
+
import { createWgpuRenderStateForTest, installWgpuMock } from '@flighthq/render-wgpu';
|
|
4
|
+
import type { RenderProxy2D, WgpuRenderState, WgpuShapeMeshBuffers } from '@flighthq/types';
|
|
5
|
+
|
|
6
|
+
import type { WgpuShapeMesh } from './wgpuShapeMesh';
|
|
7
|
+
import { drawWgpuShapeMeshes } from './wgpuShapeMesh';
|
|
8
|
+
|
|
9
|
+
beforeAll(() => {
|
|
10
|
+
installWgpuMock();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const TRIANGLE: WgpuShapeMesh = {
|
|
14
|
+
vertices: new Float32Array([0, 0, 10, 0, 0, 10]),
|
|
15
|
+
indices: new Uint16Array([0, 1, 2]),
|
|
16
|
+
color: 0xff8040,
|
|
17
|
+
alpha: 1,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
function makeBuffers(): WgpuShapeMeshBuffers {
|
|
21
|
+
return {
|
|
22
|
+
vertexBuffer: null,
|
|
23
|
+
vertexCapacity: 0,
|
|
24
|
+
indexBuffer: null,
|
|
25
|
+
indexCapacity: 0,
|
|
26
|
+
uniformBuffer: null,
|
|
27
|
+
bindGroup: null,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function makePassSpy(): GPURenderPassEncoder {
|
|
32
|
+
return {
|
|
33
|
+
setPipeline: vi.fn(),
|
|
34
|
+
setBindGroup: vi.fn(),
|
|
35
|
+
setVertexBuffer: vi.fn(),
|
|
36
|
+
setIndexBuffer: vi.fn(),
|
|
37
|
+
setStencilReference: vi.fn(),
|
|
38
|
+
draw: vi.fn(),
|
|
39
|
+
drawIndexed: vi.fn(),
|
|
40
|
+
} as unknown as GPURenderPassEncoder;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function makeProxy(matrix = createMatrix(), alpha = 1): RenderProxy2D {
|
|
44
|
+
return { alpha, blendMode: null, transform2D: matrix } as unknown as RenderProxy2D;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function makeState(): Promise<WgpuRenderState> {
|
|
48
|
+
const state = await createWgpuRenderStateForTest();
|
|
49
|
+
getWgpuRenderStateRuntime(state).renderPass = makePassSpy();
|
|
50
|
+
return state;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
describe('drawWgpuShapeMeshes', () => {
|
|
54
|
+
it('sets the shape-mesh pipeline and draws each mesh', async () => {
|
|
55
|
+
const state = await makeState();
|
|
56
|
+
const runtime = getWgpuRenderStateRuntime(state);
|
|
57
|
+
const pass = runtime.renderPass as unknown as {
|
|
58
|
+
setPipeline: ReturnType<typeof vi.fn>;
|
|
59
|
+
drawIndexed: ReturnType<typeof vi.fn>;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
drawWgpuShapeMeshes(state, makeProxy(), [TRIANGLE, TRIANGLE], makeBuffers());
|
|
63
|
+
|
|
64
|
+
expect(pass.setPipeline).toHaveBeenCalled();
|
|
65
|
+
expect(runtime.shapeMeshPipelines?.size ?? 0).toBeGreaterThan(0);
|
|
66
|
+
expect(pass.drawIndexed).toHaveBeenCalledTimes(2);
|
|
67
|
+
expect(pass.drawIndexed).toHaveBeenCalledWith(3);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('gates the fill by the active contour-clip stencil reference', async () => {
|
|
71
|
+
const state = await makeState();
|
|
72
|
+
const runtime = getWgpuRenderStateRuntime(state);
|
|
73
|
+
runtime.currentMaskDepth = 2;
|
|
74
|
+
const pass = runtime.renderPass as unknown as { setStencilReference: ReturnType<typeof vi.fn> };
|
|
75
|
+
|
|
76
|
+
drawWgpuShapeMeshes(state, makeProxy(), [TRIANGLE], makeBuffers());
|
|
77
|
+
|
|
78
|
+
expect(pass.setStencilReference).toHaveBeenCalledWith(2);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('writes premultiplied color (color * alpha) into the uniform buffer', async () => {
|
|
82
|
+
const state = await makeState();
|
|
83
|
+
const buffers = makeBuffers();
|
|
84
|
+
const writes = new Map<GPUBuffer, Float32Array>();
|
|
85
|
+
state.device.queue.writeBuffer = vi.fn((buffer: GPUBuffer, _offset: number, data: BufferSource) => {
|
|
86
|
+
const view = ArrayBuffer.isView(data) ? data : new Uint8Array(data as ArrayBuffer);
|
|
87
|
+
// Only the uniform write is inspected; record a copy keyed by its destination buffer.
|
|
88
|
+
if (view.byteLength % 4 === 0) {
|
|
89
|
+
writes.set(buffer, new Float32Array(view.buffer.slice(view.byteOffset, view.byteOffset + view.byteLength)));
|
|
90
|
+
}
|
|
91
|
+
}) as unknown as GPUQueue['writeBuffer'];
|
|
92
|
+
|
|
93
|
+
drawWgpuShapeMeshes(state, makeProxy(createMatrix(), 0.5), [{ ...TRIANGLE, color: 0xffffff }], buffers);
|
|
94
|
+
|
|
95
|
+
const uniformData = writes.get(buffers.uniformBuffer!);
|
|
96
|
+
expect(uniformData).toBeDefined();
|
|
97
|
+
// Color occupies floats 12..15 (after the mat3x3f columns): premultiplied (0.5, 0.5, 0.5, 0.5).
|
|
98
|
+
const color = uniformData!.slice(12, 16);
|
|
99
|
+
expect(color[0]).toBeCloseTo(0.5);
|
|
100
|
+
expect(color[1]).toBeCloseTo(0.5);
|
|
101
|
+
expect(color[2]).toBeCloseTo(0.5);
|
|
102
|
+
expect(color[3]).toBeCloseTo(0.5);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('skips fully transparent meshes', async () => {
|
|
106
|
+
const state = await makeState();
|
|
107
|
+
const pass = getWgpuRenderStateRuntime(state).renderPass as unknown as { drawIndexed: ReturnType<typeof vi.fn> };
|
|
108
|
+
|
|
109
|
+
drawWgpuShapeMeshes(state, makeProxy(), [{ ...TRIANGLE, alpha: 0 }], makeBuffers());
|
|
110
|
+
|
|
111
|
+
expect(pass.drawIndexed).not.toHaveBeenCalled();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('is a no-op for an empty mesh list', async () => {
|
|
115
|
+
const state = await makeState();
|
|
116
|
+
const runtime = getWgpuRenderStateRuntime(state);
|
|
117
|
+
const pass = runtime.renderPass as unknown as { setPipeline: ReturnType<typeof vi.fn> };
|
|
118
|
+
|
|
119
|
+
drawWgpuShapeMeshes(state, makeProxy(), [], makeBuffers());
|
|
120
|
+
|
|
121
|
+
expect(pass.setPipeline).not.toHaveBeenCalled();
|
|
122
|
+
expect(runtime.shapeMeshPipelines?.size ?? 0).toBe(0);
|
|
123
|
+
});
|
|
124
|
+
});
|