@flighthq/scene2d-wgpu 0.2.1-next.840.857425c

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.
Files changed (144) hide show
  1. package/dist/enableWgpuColorAdjustmentGuards.d.ts +4 -0
  2. package/dist/enableWgpuColorAdjustmentGuards.d.ts.map +1 -0
  3. package/dist/enableWgpuColorAdjustmentGuards.js +21 -0
  4. package/dist/enableWgpuColorAdjustmentGuards.js.map +1 -0
  5. package/dist/index.d.ts +29 -0
  6. package/dist/index.d.ts.map +1 -0
  7. package/dist/index.js +29 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/moduleMockTestHelper.d.ts +15 -0
  10. package/dist/moduleMockTestHelper.d.ts.map +1 -0
  11. package/dist/moduleMockTestHelper.js +25 -0
  12. package/dist/moduleMockTestHelper.js.map +1 -0
  13. package/dist/wgpuBitmap.d.ts +4 -0
  14. package/dist/wgpuBitmap.d.ts.map +1 -0
  15. package/dist/wgpuBitmap.js +84 -0
  16. package/dist/wgpuBitmap.js.map +1 -0
  17. package/dist/wgpuCache.d.ts +31 -0
  18. package/dist/wgpuCache.d.ts.map +1 -0
  19. package/dist/wgpuCache.js +244 -0
  20. package/dist/wgpuCache.js.map +1 -0
  21. package/dist/wgpuClip.d.ts +3 -0
  22. package/dist/wgpuClip.d.ts.map +1 -0
  23. package/dist/wgpuClip.js +44 -0
  24. package/dist/wgpuClip.js.map +1 -0
  25. package/dist/wgpuClipContours.d.ts +4 -0
  26. package/dist/wgpuClipContours.d.ts.map +1 -0
  27. package/dist/wgpuClipContours.js +174 -0
  28. package/dist/wgpuClipContours.js.map +1 -0
  29. package/dist/wgpuClipRectangle.d.ts +4 -0
  30. package/dist/wgpuClipRectangle.d.ts.map +1 -0
  31. package/dist/wgpuClipRectangle.js +80 -0
  32. package/dist/wgpuClipRectangle.js.map +1 -0
  33. package/dist/wgpuColorAdjustment.d.ts +3 -0
  34. package/dist/wgpuColorAdjustment.d.ts.map +1 -0
  35. package/dist/wgpuColorAdjustment.js +201 -0
  36. package/dist/wgpuColorAdjustment.js.map +1 -0
  37. package/dist/wgpuDefaultMaterial.d.ts +4 -0
  38. package/dist/wgpuDefaultMaterial.d.ts.map +1 -0
  39. package/dist/wgpuDefaultMaterial.js +49 -0
  40. package/dist/wgpuDefaultMaterial.js.map +1 -0
  41. package/dist/wgpuNode2D.d.ts +5 -0
  42. package/dist/wgpuNode2D.d.ts.map +1 -0
  43. package/dist/wgpuNode2D.js +41 -0
  44. package/dist/wgpuNode2D.js.map +1 -0
  45. package/dist/wgpuParticleEmitter2D.d.ts +4 -0
  46. package/dist/wgpuParticleEmitter2D.d.ts.map +1 -0
  47. package/dist/wgpuParticleEmitter2D.js +293 -0
  48. package/dist/wgpuParticleEmitter2D.js.map +1 -0
  49. package/dist/wgpuQuadBatch.d.ts +5 -0
  50. package/dist/wgpuQuadBatch.d.ts.map +1 -0
  51. package/dist/wgpuQuadBatch.js +90 -0
  52. package/dist/wgpuQuadBatch.js.map +1 -0
  53. package/dist/wgpuRegistration.d.ts +19 -0
  54. package/dist/wgpuRegistration.d.ts.map +1 -0
  55. package/dist/wgpuRegistration.js +53 -0
  56. package/dist/wgpuRegistration.js.map +1 -0
  57. package/dist/wgpuRenderStats.d.ts +20 -0
  58. package/dist/wgpuRenderStats.d.ts.map +1 -0
  59. package/dist/wgpuRenderStats.js +50 -0
  60. package/dist/wgpuRenderStats.js.map +1 -0
  61. package/dist/wgpuRendererData.d.ts +26 -0
  62. package/dist/wgpuRendererData.d.ts.map +1 -0
  63. package/dist/wgpuRendererData.js +29 -0
  64. package/dist/wgpuRendererData.js.map +1 -0
  65. package/dist/wgpuRichText.d.ts +8 -0
  66. package/dist/wgpuRichText.d.ts.map +1 -0
  67. package/dist/wgpuRichText.js +175 -0
  68. package/dist/wgpuRichText.js.map +1 -0
  69. package/dist/wgpuScale9Mapper.d.ts +3 -0
  70. package/dist/wgpuScale9Mapper.d.ts.map +1 -0
  71. package/dist/wgpuScale9Mapper.js +42 -0
  72. package/dist/wgpuScale9Mapper.js.map +1 -0
  73. package/dist/wgpuScale9Shape.d.ts +9 -0
  74. package/dist/wgpuScale9Shape.d.ts.map +1 -0
  75. package/dist/wgpuScale9Shape.js +123 -0
  76. package/dist/wgpuScale9Shape.js.map +1 -0
  77. package/dist/wgpuShape.d.ts +4 -0
  78. package/dist/wgpuShape.d.ts.map +1 -0
  79. package/dist/wgpuShape.js +164 -0
  80. package/dist/wgpuShape.js.map +1 -0
  81. package/dist/wgpuShapeMesh.d.ts +3 -0
  82. package/dist/wgpuShapeMesh.d.ts.map +1 -0
  83. package/dist/wgpuShapeMesh.js +224 -0
  84. package/dist/wgpuShapeMesh.js.map +1 -0
  85. package/dist/wgpuSprite.d.ts +3 -0
  86. package/dist/wgpuSprite.d.ts.map +1 -0
  87. package/dist/wgpuSprite.js +28 -0
  88. package/dist/wgpuSprite.js.map +1 -0
  89. package/dist/wgpuSpriteBatch.d.ts +12 -0
  90. package/dist/wgpuSpriteBatch.d.ts.map +1 -0
  91. package/dist/wgpuSpriteBatch.js +341 -0
  92. package/dist/wgpuSpriteBatch.js.map +1 -0
  93. package/dist/wgpuSpriteRenderer.d.ts +3 -0
  94. package/dist/wgpuSpriteRenderer.d.ts.map +1 -0
  95. package/dist/wgpuSpriteRenderer.js +55 -0
  96. package/dist/wgpuSpriteRenderer.js.map +1 -0
  97. package/dist/wgpuTextInput.d.ts +4 -0
  98. package/dist/wgpuTextInput.d.ts.map +1 -0
  99. package/dist/wgpuTextInput.js +44 -0
  100. package/dist/wgpuTextInput.js.map +1 -0
  101. package/dist/wgpuTextLabel.d.ts +4 -0
  102. package/dist/wgpuTextLabel.d.ts.map +1 -0
  103. package/dist/wgpuTextLabel.js +149 -0
  104. package/dist/wgpuTextLabel.js.map +1 -0
  105. package/dist/wgpuTilemap.d.ts +3 -0
  106. package/dist/wgpuTilemap.d.ts.map +1 -0
  107. package/dist/wgpuTilemap.js +81 -0
  108. package/dist/wgpuTilemap.js.map +1 -0
  109. package/dist/wgpuVelocity.d.ts +23 -0
  110. package/dist/wgpuVelocity.d.ts.map +1 -0
  111. package/dist/wgpuVelocity.js +365 -0
  112. package/dist/wgpuVelocity.js.map +1 -0
  113. package/dist/wgpuVideo.d.ts +6 -0
  114. package/dist/wgpuVideo.d.ts.map +1 -0
  115. package/dist/wgpuVideo.js +63 -0
  116. package/dist/wgpuVideo.js.map +1 -0
  117. package/package.json +60 -0
  118. package/src/enableWgpuColorAdjustmentGuards.test.ts +82 -0
  119. package/src/wgpuBitmap.test.ts +40 -0
  120. package/src/wgpuCache.test.ts +272 -0
  121. package/src/wgpuClip.test.ts +15 -0
  122. package/src/wgpuClipContours.test.ts +101 -0
  123. package/src/wgpuClipRectangle.test.ts +45 -0
  124. package/src/wgpuColorAdjustment.test.ts +120 -0
  125. package/src/wgpuDefaultMaterial.test.ts +21 -0
  126. package/src/wgpuNode2D.test.ts +36 -0
  127. package/src/wgpuParticleEmitter2D.test.ts +85 -0
  128. package/src/wgpuQuadBatch.test.ts +69 -0
  129. package/src/wgpuRegistration.test.ts +115 -0
  130. package/src/wgpuRenderStats.test.ts +89 -0
  131. package/src/wgpuRendererData.test.ts +29 -0
  132. package/src/wgpuRichText.test.ts +100 -0
  133. package/src/wgpuScale9Mapper.test.ts +20 -0
  134. package/src/wgpuScale9Shape.test.ts +97 -0
  135. package/src/wgpuShape.test.ts +96 -0
  136. package/src/wgpuShapeMesh.test.ts +164 -0
  137. package/src/wgpuSprite.test.ts +19 -0
  138. package/src/wgpuSpriteBatch.test.ts +272 -0
  139. package/src/wgpuSpriteRenderer.test.ts +63 -0
  140. package/src/wgpuTextInput.test.ts +37 -0
  141. package/src/wgpuTextLabel.test.ts +124 -0
  142. package/src/wgpuTilemap.test.ts +47 -0
  143. package/src/wgpuVelocity.test.ts +148 -0
  144. package/src/wgpuVideo.test.ts +59 -0
@@ -0,0 +1,124 @@
1
+ import { createImageResource } from '@flighthq/image';
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 { createTextLabel } from '@flighthq/text';
6
+ import type { RenderProxy2D } from '@flighthq/types';
7
+ import { BatchFormat } from '@flighthq/types';
8
+
9
+ import { scopeModuleMocks } from './moduleMockTestHelper';
10
+ import { registerDefaultWgpuMaterial } from './wgpuDefaultMaterial';
11
+ import type * as WgpuTextLabelModule from './wgpuTextLabel';
12
+
13
+ // @flighthq/textlayout.computeTextLayout is stubbed to emit one deterministic glyph group.
14
+ // scopeModuleMocks scopes the stub to this file (registry reset before the mock applies, unmock +
15
+ // reset after), so under a shared (isolate:false) worker it never leaks into the real text-layout
16
+ // consumers (text package) — and a sibling that pre-evaluated ./wgpuTextLabel still picks up the stub.
17
+ let defaultWgpuTextLabelRenderer: typeof WgpuTextLabelModule.defaultWgpuTextLabelRenderer;
18
+ let drawWgpuTextLabel: typeof WgpuTextLabelModule.drawWgpuTextLabel;
19
+
20
+ scopeModuleMocks(['@flighthq/textlayout']);
21
+
22
+ beforeAll(async () => {
23
+ vi.doMock('@flighthq/textlayout', async (importOriginal) => {
24
+ const actual = (await importOriginal()) as Record<string, unknown>;
25
+ return {
26
+ ...actual,
27
+ computeTextLayout: vi.fn((result: { groups: object[] }) => {
28
+ result.groups.push({
29
+ offsetX: 0,
30
+ offsetY: 0,
31
+ width: 50,
32
+ ascent: 12,
33
+ descent: 4,
34
+ format: {},
35
+ startIndex: 0,
36
+ endIndex: 5,
37
+ });
38
+ }),
39
+ };
40
+ });
41
+ ({ defaultWgpuTextLabelRenderer, drawWgpuTextLabel } = await import('./wgpuTextLabel'));
42
+ installWgpuMock();
43
+ });
44
+
45
+ function makeTextData() {
46
+ const canvas = document.createElement('canvas');
47
+ canvas.width = 1;
48
+ canvas.height = 1;
49
+ const ctx = canvas.getContext('2d')!;
50
+ return {
51
+ canvas,
52
+ ctx,
53
+ image: createImageResource(canvas),
54
+ lastContentId: -1,
55
+ lastPixelRatio: 0,
56
+ logW: 0,
57
+ logH: 0,
58
+ lastPW: 0,
59
+ lastPH: 0,
60
+ };
61
+ }
62
+
63
+ function makeTextProxy(text = '', rendererData: unknown = null): RenderProxy2D {
64
+ const source = createTextLabel();
65
+ source.data.text = text;
66
+ source.data.textFormat = {};
67
+ source.data.width = 200;
68
+ source.data.height = 100;
69
+ return {
70
+ source,
71
+ blendMode: 0,
72
+ alpha: 1,
73
+ material: null,
74
+ materialData: null,
75
+ transform2D: { a: 1, b: 0, c: 0, d: 1, tx: 0, ty: 0 },
76
+ rendererData,
77
+ } as unknown as RenderProxy2D;
78
+ }
79
+
80
+ describe('defaultWgpuTextLabelRenderer', () => {
81
+ it('declares BatchFormat.Quad', () => {
82
+ expect(defaultWgpuTextLabelRenderer.format).toBe(BatchFormat.Quad);
83
+ });
84
+
85
+ it('has createData and submit functions', () => {
86
+ expect(typeof defaultWgpuTextLabelRenderer.createData).toBe('function');
87
+ expect(typeof defaultWgpuTextLabelRenderer.submit).toBe('function');
88
+ });
89
+ });
90
+
91
+ describe('drawWgpuTextLabel', () => {
92
+ it('returns early when text is empty', async () => {
93
+ const state = await createWgpuRenderStateForTest();
94
+ renderWgpuBackground(state);
95
+ registerDefaultWgpuMaterial(state);
96
+ expect(() => drawWgpuTextLabel(state, makeTextProxy('', makeTextData()))).not.toThrow();
97
+ expect(getWgpuRenderStateRuntime(state).spriteBatchCount).toBe(0);
98
+ submitWgpuRenderPass(state);
99
+ });
100
+
101
+ it('writes one instance to the sprite batch when text has content', async () => {
102
+ const state = await createWgpuRenderStateForTest();
103
+ renderWgpuBackground(state);
104
+ registerDefaultWgpuMaterial(state);
105
+ drawWgpuTextLabel(state, makeTextProxy('hello', makeTextData()));
106
+ expect(getWgpuRenderStateRuntime(state).spriteBatchCount).toBe(1);
107
+ submitWgpuRenderPass(state);
108
+ });
109
+
110
+ it('does not re-rasterize when only alpha changes (content version unchanged)', async () => {
111
+ const state = await createWgpuRenderStateForTest();
112
+ renderWgpuBackground(state);
113
+ registerDefaultWgpuMaterial(state);
114
+ const proxy = makeTextProxy('hello', makeTextData());
115
+ drawWgpuTextLabel(state, proxy);
116
+ const updateSpy = vi.spyOn(getWgpuRenderStateRuntime(state).textureCache, 'get');
117
+ proxy.alpha = 0.5;
118
+ drawWgpuTextLabel(state, proxy);
119
+ // Version is unchanged, so the rasterization block is skipped entirely on the second draw.
120
+ expect((proxy.rendererData as any).lastContentId).toBe(0);
121
+ submitWgpuRenderPass(state);
122
+ updateSpy.mockRestore();
123
+ });
124
+ });
@@ -0,0 +1,47 @@
1
+ import { getRenderProxy2D, prepareScene2DRender } from '@flighthq/render';
2
+ import { renderWgpuBackground, submitWgpuRenderPass } from '@flighthq/render-wgpu';
3
+ import { createWgpuRenderStateForTest, installWgpuMock } from '@flighthq/render-wgpu';
4
+ import { createTilemap } from '@flighthq/sprite';
5
+
6
+ import { flushWgpuSpriteBatch } from './wgpuSpriteBatch';
7
+ import { defaultWgpuTilemapRenderer } from './wgpuTilemap';
8
+
9
+ beforeAll(() => {
10
+ installWgpuMock();
11
+ });
12
+
13
+ describe('defaultWgpuTilemapRenderer', () => {
14
+ it('has a createData function', () => {
15
+ expect(typeof defaultWgpuTilemapRenderer.createData).toBe('function');
16
+ });
17
+
18
+ it('has a submit function', () => {
19
+ expect(typeof defaultWgpuTilemapRenderer.submit).toBe('function');
20
+ });
21
+ });
22
+
23
+ describe('defaultWgpuTilemapRenderer.submit', () => {
24
+ it('does not throw when tileset is null', async () => {
25
+ const state = await createWgpuRenderStateForTest();
26
+ renderWgpuBackground(state);
27
+
28
+ const tilemap = createTilemap();
29
+ prepareScene2DRender(state, tilemap);
30
+ const renderProxy = getRenderProxy2D(state, tilemap)!;
31
+
32
+ expect(() => {
33
+ defaultWgpuTilemapRenderer.submit(state, renderProxy);
34
+ flushWgpuSpriteBatch(state as any);
35
+ }).not.toThrow();
36
+ submitWgpuRenderPass(state);
37
+ });
38
+
39
+ it('does not throw when renderPass is null', async () => {
40
+ const state = await createWgpuRenderStateForTest();
41
+ const tilemap = createTilemap();
42
+ prepareScene2DRender(state, tilemap);
43
+ const renderProxy = getRenderProxy2D(state, tilemap)!;
44
+
45
+ expect(() => defaultWgpuTilemapRenderer.submit(state, renderProxy)).not.toThrow();
46
+ });
47
+ });
@@ -0,0 +1,148 @@
1
+ import { createParticleEmitter2D, reserveParticleEmitter2D } from '@flighthq/particleemitter';
2
+ import { renderWgpuBackground } from '@flighthq/render-wgpu';
3
+ import { createWgpuRenderStateForTest, installWgpuMock } from '@flighthq/render-wgpu';
4
+ import { createDisplayObject } from '@flighthq/scene2d';
5
+ import { createQuadBatch, getQuadBatchRuntime } from '@flighthq/sprite';
6
+ import type { QuadBatchRuntime, TextureAtlas, TextureAtlasRegion } from '@flighthq/types';
7
+ import { QuadBatchKind } from '@flighthq/types';
8
+ import { beginVelocityFrame, contributeVelocity, createVelocityField } from '@flighthq/velocity';
9
+ import { beforeAll, describe, expect, it } from 'vitest';
10
+
11
+ import {
12
+ createWgpuVelocityTarget,
13
+ defaultWgpuNode2DVelocityWriter,
14
+ defaultWgpuParticleEmitter2DVelocityWriter,
15
+ defaultWgpuQuadBatchVelocityWriter,
16
+ drawWgpuVelocityQuad,
17
+ getWgpuVelocityWriter,
18
+ registerWgpuVelocityWriter,
19
+ renderWgpuVelocity,
20
+ } from './wgpuVelocity';
21
+
22
+ beforeAll(() => {
23
+ installWgpuMock();
24
+ });
25
+
26
+ describe('createWgpuVelocityTarget', () => {
27
+ it('allocates an rgba16float target at the requested size', async () => {
28
+ const state = await createWgpuRenderStateForTest();
29
+ const target = createWgpuVelocityTarget(state, 128, 64);
30
+ expect(target.format).toBe('rgba16float');
31
+ expect(target.width).toBe(128);
32
+ expect(target.height).toBe(64);
33
+ });
34
+ });
35
+
36
+ describe('defaultWgpuNode2DVelocityWriter', () => {
37
+ it('is a velocity writer function', () => {
38
+ expect(typeof defaultWgpuNode2DVelocityWriter).toBe('function');
39
+ });
40
+ });
41
+
42
+ describe('defaultWgpuParticleEmitter2DVelocityWriter', () => {
43
+ it('is a velocity writer function', () => {
44
+ expect(typeof defaultWgpuParticleEmitter2DVelocityWriter).toBe('function');
45
+ });
46
+
47
+ it('emits per-particle velocity for an emitter with a velocities array without throwing', async () => {
48
+ const state = await createWgpuRenderStateForTest();
49
+ const target = createWgpuVelocityTarget(state, 128, 64);
50
+ const region = { id: 0, x: 0, y: 0, width: 16, height: 16, pivotX: null, pivotY: null } as TextureAtlasRegion;
51
+ const atlas = { image: null, regions: [region] } as TextureAtlas;
52
+ const emitter = createParticleEmitter2D();
53
+ reserveParticleEmitter2D(emitter, 2);
54
+ emitter.data.atlas = atlas;
55
+ emitter.data.particleCount = 2;
56
+ emitter.data.ids[0] = 0;
57
+ emitter.data.ids[1] = 0;
58
+ emitter.data.transforms.set([10, 10, 0, 1, 40, 20, 0, 1]);
59
+ emitter.data.velocities.set([3, -2, -1, 4]);
60
+
61
+ registerWgpuVelocityWriter(state, emitter.kind, defaultWgpuParticleEmitter2DVelocityWriter);
62
+ const field = createVelocityField();
63
+ beginVelocityFrame(field);
64
+
65
+ renderWgpuBackground(state);
66
+ expect(() => renderWgpuVelocity(state, emitter, field, target)).not.toThrow();
67
+ });
68
+ });
69
+
70
+ describe('defaultWgpuQuadBatchVelocityWriter', () => {
71
+ it('is a velocity writer function', () => {
72
+ expect(typeof defaultWgpuQuadBatchVelocityWriter).toBe('function');
73
+ });
74
+
75
+ it('emits per-instance velocity for a batch with an instanceVelocities array without throwing', async () => {
76
+ const state = await createWgpuRenderStateForTest();
77
+ const target = createWgpuVelocityTarget(state, 128, 64);
78
+ const region = { id: 0, x: 0, y: 0, width: 32, height: 32, pivotX: null, pivotY: null } as TextureAtlasRegion;
79
+ const atlas = { image: null, regions: [region] } as TextureAtlas;
80
+ const batch = createQuadBatch({
81
+ data: {
82
+ atlas,
83
+ ids: new Uint16Array([0, 0]),
84
+ instanceCount: 2,
85
+ transforms: new Float32Array([0, 0, 40, 10]),
86
+ transformType: 'vector2',
87
+ },
88
+ });
89
+ (getQuadBatchRuntime(batch) as QuadBatchRuntime).instanceVelocities = new Float32Array([3, -2, -1, 4]);
90
+
91
+ registerWgpuVelocityWriter(state, QuadBatchKind, defaultWgpuQuadBatchVelocityWriter);
92
+ const field = createVelocityField();
93
+ beginVelocityFrame(field);
94
+
95
+ renderWgpuBackground(state);
96
+ expect(() => renderWgpuVelocity(state, batch, field, target)).not.toThrow();
97
+ });
98
+ });
99
+
100
+ describe('drawWgpuVelocityQuad', () => {
101
+ it('is a no-op outside an active velocity pass', async () => {
102
+ const state = await createWgpuRenderStateForTest();
103
+ const ctx = { state, field: createVelocityField(), width: 128, height: 64, pixelRatio: 1 };
104
+ expect(() => drawWgpuVelocityQuad(ctx, 0, 0, 10, 10, 1, 0)).not.toThrow();
105
+ });
106
+ });
107
+
108
+ describe('getWgpuVelocityWriter', () => {
109
+ it('returns null for an unregistered kind', async () => {
110
+ const state = await createWgpuRenderStateForTest();
111
+ expect(getWgpuVelocityWriter(state, 'unregistered')).toBeNull();
112
+ });
113
+ });
114
+
115
+ describe('registerWgpuVelocityWriter', () => {
116
+ it('registers a writer dispatched by kind', async () => {
117
+ const state = await createWgpuRenderStateForTest();
118
+ const root = createDisplayObject();
119
+ registerWgpuVelocityWriter(state, root.kind, defaultWgpuNode2DVelocityWriter);
120
+ expect(getWgpuVelocityWriter(state, root.kind)).toBe(defaultWgpuNode2DVelocityWriter);
121
+ });
122
+ });
123
+
124
+ describe('renderWgpuVelocity', () => {
125
+ it('dispatches the registered writer for a moving node without throwing', async () => {
126
+ const state = await createWgpuRenderStateForTest();
127
+ const target = createWgpuVelocityTarget(state, 128, 64);
128
+ const root = createDisplayObject();
129
+ registerWgpuVelocityWriter(state, root.kind, defaultWgpuNode2DVelocityWriter);
130
+
131
+ const field = createVelocityField();
132
+ beginVelocityFrame(field);
133
+ contributeVelocity(field, root, 3, -2);
134
+
135
+ renderWgpuBackground(state);
136
+ expect(() => renderWgpuVelocity(state, root, field, target)).not.toThrow();
137
+ });
138
+
139
+ it('throws when no command encoder is open (renderWgpuBackground not called)', async () => {
140
+ const state = await createWgpuRenderStateForTest();
141
+ const target = createWgpuVelocityTarget(state, 128, 64);
142
+ const root = createDisplayObject();
143
+ const field = createVelocityField();
144
+ beginVelocityFrame(field);
145
+
146
+ expect(() => renderWgpuVelocity(state, root, field, target)).toThrow(/command encoder/);
147
+ });
148
+ });
@@ -0,0 +1,59 @@
1
+ import { getOrCreateRenderProxy2D, prepareScene2DRender } from '@flighthq/render';
2
+ import { renderWgpuBackground, submitWgpuRenderPass } from '@flighthq/render-wgpu';
3
+ import { createWgpuRenderStateForTest, installWgpuMock } from '@flighthq/render-wgpu';
4
+ import { createVideo } from '@flighthq/scene2d';
5
+
6
+ import { createWgpuVideoData, defaultWgpuVideoRenderer, destroyWgpuVideoData, drawWgpuVideo } from './wgpuVideo';
7
+
8
+ beforeAll(() => {
9
+ installWgpuMock();
10
+ });
11
+
12
+ describe('createWgpuVideoData', () => {
13
+ it('allocates per-node data with no texture entry yet', () => {
14
+ const data = createWgpuVideoData({} as never, {} as never) as unknown as { entry: unknown };
15
+ expect(data.entry).toBeNull();
16
+ });
17
+ });
18
+
19
+ describe('defaultWgpuVideoRenderer', () => {
20
+ it('has createData and submit functions', () => {
21
+ expect(typeof defaultWgpuVideoRenderer.createData).toBe('function');
22
+ expect(typeof defaultWgpuVideoRenderer.submit).toBe('function');
23
+ });
24
+ });
25
+
26
+ describe('destroyWgpuVideoData', () => {
27
+ it('destroys the GPU texture the node owns', () => {
28
+ const destroy = vi.fn();
29
+ destroyWgpuVideoData({} as never, { entry: { texture: { destroy } } } as never);
30
+ expect(destroy).toHaveBeenCalled();
31
+ });
32
+
33
+ it('is a no-op when no texture entry was allocated', () => {
34
+ expect(() => destroyWgpuVideoData({} as never, { entry: null } as never)).not.toThrow();
35
+ });
36
+ });
37
+
38
+ describe('drawWgpuVideo', () => {
39
+ it('does not throw when video source is null', async () => {
40
+ const state = await createWgpuRenderStateForTest();
41
+ renderWgpuBackground(state);
42
+
43
+ const video = createVideo();
44
+ prepareScene2DRender(state, video);
45
+ const renderProxy = getOrCreateRenderProxy2D(state, video);
46
+
47
+ expect(() => drawWgpuVideo(state, renderProxy)).not.toThrow();
48
+ submitWgpuRenderPass(state);
49
+ });
50
+
51
+ it('does not throw when renderPass is null', async () => {
52
+ const state = await createWgpuRenderStateForTest();
53
+ const video = createVideo();
54
+ prepareScene2DRender(state, video);
55
+ const renderProxy = getOrCreateRenderProxy2D(state, video);
56
+
57
+ expect(() => drawWgpuVideo(state, renderProxy)).not.toThrow();
58
+ });
59
+ });