@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,85 @@
1
+ import { createParticleEmitter2D } from '@flighthq/particleemitter';
2
+ import { getRenderProxy2D, prepareScene2DRender } from '@flighthq/render';
3
+ import { getWgpuRenderStateRuntime, renderWgpuBackground, submitWgpuRenderPass } from '@flighthq/render-wgpu';
4
+ import { createWgpuRenderStateForTest, installWgpuMock } from '@flighthq/render-wgpu';
5
+ import type { ImageResource, RenderProxy2D } from '@flighthq/types';
6
+
7
+ import { defaultWgpuParticleEmitter2DRenderer, drawWgpuParticleEmitter2D } from './wgpuParticleEmitter2D';
8
+
9
+ beforeAll(() => {
10
+ installWgpuMock();
11
+ });
12
+
13
+ describe('defaultWgpuParticleEmitter2DRenderer', () => {
14
+ it('has a createData function', () => {
15
+ expect(typeof defaultWgpuParticleEmitter2DRenderer.createData).toBe('function');
16
+ });
17
+
18
+ it('has a submit function', () => {
19
+ expect(typeof defaultWgpuParticleEmitter2DRenderer.submit).toBe('function');
20
+ });
21
+ });
22
+
23
+ describe('drawWgpuParticleEmitter2D', () => {
24
+ it('does not throw when atlas is null', async () => {
25
+ const state = await createWgpuRenderStateForTest();
26
+ renderWgpuBackground(state);
27
+
28
+ const emitter = createParticleEmitter2D();
29
+ prepareScene2DRender(state, emitter);
30
+ const renderProxy = getRenderProxy2D(state, emitter)!;
31
+
32
+ expect(() => drawWgpuParticleEmitter2D(state, renderProxy)).not.toThrow();
33
+ submitWgpuRenderPass(state);
34
+ });
35
+
36
+ it('threads a native compressed atlas straight-alpha flag through the particle uniform', async () => {
37
+ const state = await createWgpuRenderStateForTest();
38
+ const runtime = getWgpuRenderStateRuntime(state);
39
+ renderWgpuBackground(state);
40
+ const before = runtime.uniformOffset;
41
+ const image = {
42
+ alphaType: 'straight',
43
+ compressed: { container: {}, payload: new Uint8Array() },
44
+ data: null,
45
+ height: 4,
46
+ source: null,
47
+ version: 1,
48
+ width: 4,
49
+ } as unknown as ImageResource;
50
+ runtime.compressedTextureUpload = () => {
51
+ const texture = state.device.createTexture({
52
+ size: [4, 4],
53
+ format: 'bc3-rgba-unorm',
54
+ usage: GPUTextureUsage.TEXTURE_BINDING,
55
+ });
56
+ const view = texture.createView();
57
+ return {
58
+ bindGroup: state.device.createBindGroup({ layout: runtime.textureBindGroupLayout, entries: [] }),
59
+ straightAlpha: true,
60
+ texture,
61
+ view,
62
+ };
63
+ };
64
+ const renderProxy = {
65
+ alpha: 1,
66
+ blendMode: null,
67
+ source: {
68
+ data: {
69
+ alphas: new Float32Array([1]),
70
+ atlas: { image, regions: [{ height: 4, width: 4, x: 0, y: 0 }] },
71
+ colors: new Float32Array([1, 1, 1]),
72
+ ids: new Uint16Array([0]),
73
+ particleCount: 1,
74
+ transforms: new Float32Array([0, 0, 0, 1]),
75
+ worldSpace: false,
76
+ },
77
+ },
78
+ transform2D: { a: 1, b: 0, c: 0, d: 1, tx: 0, ty: 0 },
79
+ } as unknown as RenderProxy2D;
80
+
81
+ drawWgpuParticleEmitter2D(state, renderProxy);
82
+
83
+ expect(runtime.uniformDataU32[(before >> 2) + 14]).toBe(1);
84
+ });
85
+ });
@@ -0,0 +1,69 @@
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 { createQuadBatch } from '@flighthq/sprite';
5
+
6
+ import { defaultWgpuQuadBatchRenderer, ensureWgpuQuadBatchResources, getWgpuQuadBatchPipeline } from './wgpuQuadBatch';
7
+ import { flushWgpuSpriteBatch } from './wgpuSpriteBatch';
8
+
9
+ beforeAll(() => {
10
+ installWgpuMock();
11
+ });
12
+
13
+ describe('defaultWgpuQuadBatchRenderer', () => {
14
+ it('has a createData function', () => {
15
+ expect(typeof defaultWgpuQuadBatchRenderer.createData).toBe('function');
16
+ });
17
+
18
+ it('has a submit function', () => {
19
+ expect(typeof defaultWgpuQuadBatchRenderer.submit).toBe('function');
20
+ });
21
+ });
22
+
23
+ describe('defaultWgpuQuadBatchRenderer.submit', () => {
24
+ it('does not throw when atlas is null', async () => {
25
+ const state = await createWgpuRenderStateForTest();
26
+ renderWgpuBackground(state);
27
+
28
+ const batch = createQuadBatch();
29
+ prepareScene2DRender(state, batch);
30
+ const renderProxy = getRenderProxy2D(state, batch)!;
31
+
32
+ expect(() => {
33
+ defaultWgpuQuadBatchRenderer.submit(state, renderProxy);
34
+ flushWgpuSpriteBatch(state as any);
35
+ }).not.toThrow();
36
+ submitWgpuRenderPass(state);
37
+ });
38
+ });
39
+
40
+ describe('ensureWgpuQuadBatchResources', () => {
41
+ it('returns resources with bind group layouts and a pipelines WeakMap', async () => {
42
+ const state = await createWgpuRenderStateForTest();
43
+ const resources = ensureWgpuQuadBatchResources(state);
44
+ expect(resources.instanceBindGroupLayout).toBeDefined();
45
+ expect(resources.materialBindGroupLayout).toBeDefined();
46
+ expect(resources.basePipelineLayout).toBeDefined();
47
+ expect(resources.materialPipelineLayout).toBeDefined();
48
+ expect(resources.pipelines).toBeInstanceOf(WeakMap);
49
+ });
50
+
51
+ it('is idempotent — returns the same resources on repeated calls', async () => {
52
+ const state = await createWgpuRenderStateForTest();
53
+ const r1 = ensureWgpuQuadBatchResources(state);
54
+ const r2 = ensureWgpuQuadBatchResources(state);
55
+ expect(r1).toBe(r2);
56
+ });
57
+ });
58
+
59
+ describe('getWgpuQuadBatchPipeline', () => {
60
+ it('creates and caches a pipeline for a given shader module', async () => {
61
+ const state = await createWgpuRenderStateForTest();
62
+ const resources = ensureWgpuQuadBatchResources(state);
63
+ const module = state.device.createShaderModule({ code: '' });
64
+ const pipeline = getWgpuQuadBatchPipeline(state, resources, module, false, null);
65
+ expect(pipeline).toBeDefined();
66
+ const pipeline2 = getWgpuQuadBatchPipeline(state, resources, module, false, null);
67
+ expect(pipeline2).toBe(pipeline);
68
+ });
69
+ });
@@ -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
+ ParticleEmitter2DKind,
7
+ QuadBatchKind,
8
+ RichTextKind,
9
+ Scale9ShapeKind,
10
+ ShapeKind,
11
+ SpriteKind,
12
+ TextLabelKind,
13
+ TilemapKind,
14
+ VideoKind,
15
+ } from '@flighthq/types';
16
+
17
+ import { registerWgpuScene2DRenderers, registerWgpuSpriteRenderers } from './wgpuRegistration';
18
+
19
+ beforeAll(() => {
20
+ installWgpuMock();
21
+ });
22
+
23
+ describe('registerWgpuScene2DRenderers', () => {
24
+ it('registers bitmap renderer', async () => {
25
+ const state = await createWgpuRenderStateForTest();
26
+ registerWgpuScene2DRenderers(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
+ registerWgpuScene2DRenderers(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
+ registerWgpuScene2DRenderers(state);
39
+ expect(getRenderStateRuntime(state).rendererMap.get(ParticleEmitter2DKind)).not.toBeUndefined();
40
+ });
41
+
42
+ it('registers quad batch renderer', async () => {
43
+ const state = await createWgpuRenderStateForTest();
44
+ registerWgpuScene2DRenderers(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
+ registerWgpuScene2DRenderers(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
+ registerWgpuScene2DRenderers(state);
57
+ expect(getRenderStateRuntime(state).rendererMap.get(Scale9ShapeKind)).not.toBeUndefined();
58
+ });
59
+
60
+ it('registers shape renderer', async () => {
61
+ const state = await createWgpuRenderStateForTest();
62
+ registerWgpuScene2DRenderers(state);
63
+ expect(getRenderStateRuntime(state).rendererMap.get(ShapeKind)).not.toBeUndefined();
64
+ });
65
+
66
+ it('registers sprite renderer', async () => {
67
+ const state = await createWgpuRenderStateForTest();
68
+ registerWgpuScene2DRenderers(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
+ registerWgpuScene2DRenderers(state);
75
+ expect(getRenderStateRuntime(state).rendererMap.get(TextLabelKind)).not.toBeUndefined();
76
+ });
77
+
78
+ it('registers tilemap renderer', async () => {
79
+ const state = await createWgpuRenderStateForTest();
80
+ registerWgpuScene2DRenderers(state);
81
+ expect(getRenderStateRuntime(state).rendererMap.get(TilemapKind)).not.toBeUndefined();
82
+ });
83
+
84
+ it('registers video renderer', async () => {
85
+ const state = await createWgpuRenderStateForTest();
86
+ registerWgpuScene2DRenderers(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(ParticleEmitter2DKind)).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, prepareScene2DRender } 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
+ prepareScene2DRender(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
+ prepareScene2DRender(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
+ prepareScene2DRender(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
+ prepareScene2DRender(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
+ prepareScene2DRender(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, prepareScene2DRender } 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
+ prepareScene2DRender(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
+ prepareScene2DRender(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
+ prepareScene2DRender(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
+ });