@flighthq/displayobject-gl 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.
Files changed (143) hide show
  1. package/dist/glBitmap.d.ts +4 -0
  2. package/dist/glBitmap.d.ts.map +1 -0
  3. package/dist/glBitmap.js +106 -0
  4. package/dist/glBitmap.js.map +1 -0
  5. package/dist/glCache.d.ts +30 -0
  6. package/dist/glCache.d.ts.map +1 -0
  7. package/dist/glCache.js +185 -0
  8. package/dist/glCache.js.map +1 -0
  9. package/dist/glClip.d.ts +3 -0
  10. package/dist/glClip.d.ts.map +1 -0
  11. package/dist/glClip.js +46 -0
  12. package/dist/glClip.js.map +1 -0
  13. package/dist/glClipContours.d.ts +4 -0
  14. package/dist/glClipContours.d.ts.map +1 -0
  15. package/dist/glClipContours.js +148 -0
  16. package/dist/glClipContours.js.map +1 -0
  17. package/dist/glClipRectangle.d.ts +4 -0
  18. package/dist/glClipRectangle.d.ts.map +1 -0
  19. package/dist/glClipRectangle.js +68 -0
  20. package/dist/glClipRectangle.js.map +1 -0
  21. package/dist/glColorTransformMaterial.d.ts +4 -0
  22. package/dist/glColorTransformMaterial.d.ts.map +1 -0
  23. package/dist/glColorTransformMaterial.js +116 -0
  24. package/dist/glColorTransformMaterial.js.map +1 -0
  25. package/dist/glDefaultMaterial.d.ts +4 -0
  26. package/dist/glDefaultMaterial.d.ts.map +1 -0
  27. package/dist/glDefaultMaterial.js +20 -0
  28. package/dist/glDefaultMaterial.js.map +1 -0
  29. package/dist/glDisplayObject.d.ts +5 -0
  30. package/dist/glDisplayObject.d.ts.map +1 -0
  31. package/dist/glDisplayObject.js +41 -0
  32. package/dist/glDisplayObject.js.map +1 -0
  33. package/dist/glDisplayObjectRegistration.d.ts +25 -0
  34. package/dist/glDisplayObjectRegistration.d.ts.map +1 -0
  35. package/dist/glDisplayObjectRegistration.js +51 -0
  36. package/dist/glDisplayObjectRegistration.js.map +1 -0
  37. package/dist/glMaterials.d.ts +4 -0
  38. package/dist/glMaterials.d.ts.map +1 -0
  39. package/dist/glMaterials.js +97 -0
  40. package/dist/glMaterials.js.map +1 -0
  41. package/dist/glParticleEmitter.d.ts +4 -0
  42. package/dist/glParticleEmitter.d.ts.map +1 -0
  43. package/dist/glParticleEmitter.js +258 -0
  44. package/dist/glParticleEmitter.js.map +1 -0
  45. package/dist/glQuadBatch.d.ts +3 -0
  46. package/dist/glQuadBatch.d.ts.map +1 -0
  47. package/dist/glQuadBatch.js +88 -0
  48. package/dist/glQuadBatch.js.map +1 -0
  49. package/dist/glRichText.d.ts +9 -0
  50. package/dist/glRichText.d.ts.map +1 -0
  51. package/dist/glRichText.js +149 -0
  52. package/dist/glRichText.js.map +1 -0
  53. package/dist/glScale9Mapper.d.ts +3 -0
  54. package/dist/glScale9Mapper.d.ts.map +1 -0
  55. package/dist/glScale9Mapper.js +37 -0
  56. package/dist/glScale9Mapper.js.map +1 -0
  57. package/dist/glScale9Shape.d.ts +9 -0
  58. package/dist/glScale9Shape.d.ts.map +1 -0
  59. package/dist/glScale9Shape.js +118 -0
  60. package/dist/glScale9Shape.js.map +1 -0
  61. package/dist/glShape.d.ts +4 -0
  62. package/dist/glShape.d.ts.map +1 -0
  63. package/dist/glShape.js +125 -0
  64. package/dist/glShape.js.map +1 -0
  65. package/dist/glShapeMesh.d.ts +9 -0
  66. package/dist/glShapeMesh.d.ts.map +1 -0
  67. package/dist/glShapeMesh.js +90 -0
  68. package/dist/glShapeMesh.js.map +1 -0
  69. package/dist/glSprite.d.ts +3 -0
  70. package/dist/glSprite.d.ts.map +1 -0
  71. package/dist/glSprite.js +28 -0
  72. package/dist/glSprite.js.map +1 -0
  73. package/dist/glSpriteBatch.d.ts +11 -0
  74. package/dist/glSpriteBatch.d.ts.map +1 -0
  75. package/dist/glSpriteBatch.js +241 -0
  76. package/dist/glSpriteBatch.js.map +1 -0
  77. package/dist/glSpriteRenderer.d.ts +3 -0
  78. package/dist/glSpriteRenderer.d.ts.map +1 -0
  79. package/dist/glSpriteRenderer.js +50 -0
  80. package/dist/glSpriteRenderer.js.map +1 -0
  81. package/dist/glTestHelper.d.ts +9 -0
  82. package/dist/glTestHelper.d.ts.map +1 -0
  83. package/dist/glTestHelper.js +17 -0
  84. package/dist/glTestHelper.js.map +1 -0
  85. package/dist/glTextInput.d.ts +4 -0
  86. package/dist/glTextInput.d.ts.map +1 -0
  87. package/dist/glTextInput.js +43 -0
  88. package/dist/glTextInput.js.map +1 -0
  89. package/dist/glTextLabel.d.ts +4 -0
  90. package/dist/glTextLabel.d.ts.map +1 -0
  91. package/dist/glTextLabel.js +130 -0
  92. package/dist/glTextLabel.js.map +1 -0
  93. package/dist/glTilemap.d.ts +3 -0
  94. package/dist/glTilemap.d.ts.map +1 -0
  95. package/dist/glTilemap.js +74 -0
  96. package/dist/glTilemap.js.map +1 -0
  97. package/dist/glUniformColorTransformMaterial.d.ts +4 -0
  98. package/dist/glUniformColorTransformMaterial.d.ts.map +1 -0
  99. package/dist/glUniformColorTransformMaterial.js +56 -0
  100. package/dist/glUniformColorTransformMaterial.js.map +1 -0
  101. package/dist/glVelocity.d.ts +22 -0
  102. package/dist/glVelocity.d.ts.map +1 -0
  103. package/dist/glVelocity.js +319 -0
  104. package/dist/glVelocity.js.map +1 -0
  105. package/dist/glVideo.d.ts +6 -0
  106. package/dist/glVideo.d.ts.map +1 -0
  107. package/dist/glVideo.js +60 -0
  108. package/dist/glVideo.js.map +1 -0
  109. package/dist/index.d.ts +28 -0
  110. package/dist/index.d.ts.map +1 -0
  111. package/dist/index.js +29 -0
  112. package/dist/index.js.map +1 -0
  113. package/dist/moduleMockTestHelper.d.ts +15 -0
  114. package/dist/moduleMockTestHelper.d.ts.map +1 -0
  115. package/dist/moduleMockTestHelper.js +25 -0
  116. package/dist/moduleMockTestHelper.js.map +1 -0
  117. package/package.json +51 -0
  118. package/src/glBitmap.test.ts +128 -0
  119. package/src/glCache.test.ts +230 -0
  120. package/src/glClip.test.ts +101 -0
  121. package/src/glClipContours.test.ts +61 -0
  122. package/src/glClipRectangle.test.ts +47 -0
  123. package/src/glColorTransformMaterial.test.ts +45 -0
  124. package/src/glDefaultMaterial.test.ts +19 -0
  125. package/src/glDisplayObject.test.ts +85 -0
  126. package/src/glDisplayObjectRegistration.test.ts +130 -0
  127. package/src/glMaterials.test.ts +91 -0
  128. package/src/glParticleEmitter.test.ts +98 -0
  129. package/src/glQuadBatch.test.ts +132 -0
  130. package/src/glRichText.test.ts +135 -0
  131. package/src/glScale9Mapper.test.ts +20 -0
  132. package/src/glScale9Shape.test.ts +90 -0
  133. package/src/glShape.test.ts +115 -0
  134. package/src/glShapeMesh.test.ts +64 -0
  135. package/src/glSprite.test.ts +79 -0
  136. package/src/glSpriteBatch.test.ts +153 -0
  137. package/src/glSpriteRenderer.test.ts +75 -0
  138. package/src/glTextInput.test.ts +51 -0
  139. package/src/glTextLabel.test.ts +151 -0
  140. package/src/glTilemap.test.ts +127 -0
  141. package/src/glUniformColorTransformMaterial.test.ts +24 -0
  142. package/src/glVelocity.test.ts +155 -0
  143. package/src/glVideo.test.ts +65 -0
@@ -0,0 +1,47 @@
1
+ import { createMatrix, createRectangle } from '@flighthq/geometry';
2
+ import { getGlRenderStateRuntime } from '@flighthq/render-gl';
3
+
4
+ import { popGlClipRectangle, pushGlClipRectangle } from './glClipRectangle';
5
+ import { createGlState } from './glTestHelper';
6
+
7
+ describe('popGlClipRectangle', () => {
8
+ it('restores the previous scissor rectangle', () => {
9
+ const { state, gl } = createGlState();
10
+ pushGlClipRectangle(state, createRectangle(0, 0, 100, 50), createMatrix());
11
+ pushGlClipRectangle(state, createRectangle(10, 10, 10, 10), createMatrix());
12
+
13
+ popGlClipRectangle(state);
14
+
15
+ expect(gl.scissor).toHaveBeenLastCalledWith(0, 50, 100, 50);
16
+ });
17
+
18
+ it('disables scissor testing when the stack becomes empty', () => {
19
+ const { state, gl } = createGlState();
20
+ pushGlClipRectangle(state, createRectangle(0, 0, 100, 50), createMatrix());
21
+
22
+ popGlClipRectangle(state);
23
+
24
+ expect(gl.disable).toHaveBeenLastCalledWith(gl.SCISSOR_TEST);
25
+ expect(getGlRenderStateRuntime(state).currentScissorRect).toBeNull();
26
+ });
27
+ });
28
+
29
+ describe('pushGlClipRectangle', () => {
30
+ it('enables scissor testing with a transformed rectangle', () => {
31
+ const { state, gl } = createGlState();
32
+
33
+ pushGlClipRectangle(state, createRectangle(10, 20, 30, 40), createMatrix(1, 0, 0, 1, 5, 6));
34
+
35
+ expect(gl.enable).toHaveBeenLastCalledWith(gl.SCISSOR_TEST);
36
+ expect(gl.scissor).toHaveBeenLastCalledWith(15, 34, 30, 40);
37
+ });
38
+
39
+ it('intersects nested scissor rectangles', () => {
40
+ const { state, gl } = createGlState();
41
+
42
+ pushGlClipRectangle(state, createRectangle(0, 0, 100, 100), createMatrix());
43
+ pushGlClipRectangle(state, createRectangle(50, 25, 100, 50), createMatrix());
44
+
45
+ expect(gl.scissor).toHaveBeenLastCalledWith(50, 25, 50, 50);
46
+ });
47
+ });
@@ -0,0 +1,45 @@
1
+ import { getGlMaterialRenderer } from '@flighthq/render-gl';
2
+ import { ColorTransformMaterialKind } from '@flighthq/types';
3
+
4
+ import { colorTransformGlMaterialRenderer, registerGlColorTransformMaterial } from './glColorTransformMaterial';
5
+ import { createGlState } from './glTestHelper';
6
+
7
+ function makeColorTransform(redMultiplier: number) {
8
+ return {
9
+ redMultiplier,
10
+ greenMultiplier: 1,
11
+ blueMultiplier: 1,
12
+ alphaMultiplier: 1,
13
+ redOffset: 0,
14
+ greenOffset: 0,
15
+ blueOffset: 0,
16
+ alphaOffset: 0,
17
+ };
18
+ }
19
+
20
+ describe('colorTransformGlMaterialRenderer', () => {
21
+ it('declares 8 per-instance floats', () => {
22
+ expect(colorTransformGlMaterialRenderer.instanceFloatCount).toBe(8);
23
+ });
24
+
25
+ it('packs the supplied material data color transform', () => {
26
+ const out = new Float32Array(8);
27
+ colorTransformGlMaterialRenderer.packInstance!(null as any, makeColorTransform(0.5) as any, out, 0);
28
+ expect(out[0]).toBe(0.5);
29
+ expect(out[3]).toBe(1);
30
+ });
31
+
32
+ it('packs identity when material data is null', () => {
33
+ const out = new Float32Array(8);
34
+ colorTransformGlMaterialRenderer.packInstance!(null as any, null, out, 0);
35
+ expect(Array.from(out)).toEqual([1, 1, 1, 1, 0, 0, 0, 0]);
36
+ });
37
+ });
38
+
39
+ describe('registerGlColorTransformMaterial', () => {
40
+ it('registers the per-instance color transform material renderer', () => {
41
+ const { state } = createGlState();
42
+ registerGlColorTransformMaterial(state);
43
+ expect(getGlMaterialRenderer(state, ColorTransformMaterialKind)).toBe(colorTransformGlMaterialRenderer);
44
+ });
45
+ });
@@ -0,0 +1,19 @@
1
+ import { getGlMaterialRenderer } from '@flighthq/render-gl';
2
+ import { DefaultMaterialKind } from '@flighthq/types';
3
+
4
+ import { defaultGlMaterialRenderer, registerDefaultGlMaterial } from './glDefaultMaterial';
5
+ import { createGlState } from './glTestHelper';
6
+
7
+ describe('defaultGlMaterialRenderer', () => {
8
+ it('declares no per-instance float data', () => {
9
+ expect(defaultGlMaterialRenderer.instanceFloatCount).toBe(0);
10
+ });
11
+ });
12
+
13
+ describe('registerDefaultGlMaterial', () => {
14
+ it('registers the default renderer under DefaultMaterialKind', () => {
15
+ const { state } = createGlState();
16
+ registerDefaultGlMaterial(state);
17
+ expect(getGlMaterialRenderer(state, DefaultMaterialKind)).toBe(defaultGlMaterialRenderer);
18
+ });
19
+ });
@@ -0,0 +1,85 @@
1
+ import { createDisplayObject } from '@flighthq/displayobject';
2
+ import { addNodeChild } from '@flighthq/node';
3
+ import { getOrCreateRenderProxy2D, prepareDisplayObjectRender, registerRenderer } from '@flighthq/render';
4
+ import { createGlRenderState } from '@flighthq/render-gl';
5
+ import type { GlRenderState } from '@flighthq/types';
6
+ import { DisplayObjectKind } from '@flighthq/types';
7
+
8
+ import { defaultGlDisplayObjectRenderer, drawGlDisplayObject, renderGlDisplayObject } from './glDisplayObject';
9
+
10
+ function makeState(): GlRenderState {
11
+ const canvas = document.createElement('canvas');
12
+ canvas.width = 200;
13
+ canvas.height = 100;
14
+ return createGlRenderState(canvas);
15
+ }
16
+
17
+ function makeRenderer() {
18
+ return {
19
+ createData: () => null,
20
+ submit: vi.fn(),
21
+ } as any;
22
+ }
23
+
24
+ describe('defaultGlDisplayObjectRenderer', () => {
25
+ it('has draw, and createData functions', () => {
26
+ expect(defaultGlDisplayObjectRenderer.createData({} as any, {} as any)).toBeNull();
27
+ expect(defaultGlDisplayObjectRenderer.submit).toBe(drawGlDisplayObject);
28
+ });
29
+ });
30
+
31
+ describe('drawGlDisplayObject', () => {
32
+ it('does not draw plain display object geometry', () => {
33
+ const state = makeState();
34
+ expect(() => drawGlDisplayObject(state, {} as any)).not.toThrow();
35
+ });
36
+ });
37
+
38
+ describe('renderGlDisplayObject', () => {
39
+ it('does not throw for an empty display object', () => {
40
+ const state = makeState();
41
+ const obj = createDisplayObject();
42
+ prepareDisplayObjectRender(state, obj);
43
+ expect(() => renderGlDisplayObject(state, obj)).not.toThrow();
44
+ });
45
+
46
+ it('calls renderer.submit for a visible object with a renderer', () => {
47
+ const state = makeState();
48
+ const renderer = makeRenderer();
49
+ registerRenderer(state, DisplayObjectKind, renderer);
50
+ const obj = createDisplayObject();
51
+ const data = getOrCreateRenderProxy2D(state, obj);
52
+ prepareDisplayObjectRender(state, obj);
53
+
54
+ renderGlDisplayObject(state, obj);
55
+
56
+ expect(renderer.submit).toHaveBeenCalledWith(state, data);
57
+ });
58
+
59
+ it('skips objects with zero alpha', () => {
60
+ const state = makeState();
61
+ const renderer = makeRenderer();
62
+ registerRenderer(state, DisplayObjectKind, renderer);
63
+ const obj = createDisplayObject();
64
+ obj.alpha = 0;
65
+ prepareDisplayObjectRender(state, obj);
66
+
67
+ renderGlDisplayObject(state, obj);
68
+
69
+ expect(renderer.submit).not.toHaveBeenCalled();
70
+ });
71
+
72
+ it('traverses children and draws visible ones', () => {
73
+ const state = makeState();
74
+ const renderer = makeRenderer();
75
+ registerRenderer(state, DisplayObjectKind, renderer);
76
+ const parent = createDisplayObject();
77
+ const child = createDisplayObject();
78
+ addNodeChild(parent, child);
79
+ prepareDisplayObjectRender(state, parent);
80
+
81
+ renderGlDisplayObject(state, parent);
82
+
83
+ expect(renderer.submit).toHaveBeenCalledTimes(2);
84
+ });
85
+ });
@@ -0,0 +1,130 @@
1
+ import { getRenderStateRuntime } from '@flighthq/render';
2
+ import { createGlRenderState } from '@flighthq/render-gl';
3
+ import type { GlRenderState } from '@flighthq/types';
4
+ import {
5
+ BitmapKind,
6
+ DisplayObjectKind,
7
+ ParticleEmitterKind,
8
+ QuadBatchKind,
9
+ RenderCacheKind,
10
+ RichTextKind,
11
+ Scale9ShapeKind,
12
+ ShapeKind,
13
+ SpriteKind,
14
+ TextLabelKind,
15
+ TilemapKind,
16
+ VideoKind,
17
+ } from '@flighthq/types';
18
+
19
+ import { defaultGlBitmapRenderer } from './glBitmap';
20
+ import { defaultGlRenderCacheRenderer } from './glCache';
21
+ import { defaultGlDisplayObjectRenderer } from './glDisplayObject';
22
+ import { registerGlDisplayObjectRenderers } from './glDisplayObjectRegistration';
23
+ import { defaultGlParticleEmitterRenderer } from './glParticleEmitter';
24
+ import { defaultGlQuadBatchRenderer } from './glQuadBatch';
25
+ import { defaultGlRichTextRenderer } from './glRichText';
26
+ import { defaultGlScale9ShapeRenderer } from './glScale9Shape';
27
+ import { defaultGlShapeRenderer } from './glShape';
28
+ import { defaultGlSpriteRenderer } from './glSpriteRenderer';
29
+ import { defaultGlTextLabelRenderer } from './glTextLabel';
30
+ import { defaultGlTilemapRenderer } from './glTilemap';
31
+ import { defaultGlVideoRenderer } from './glVideo';
32
+
33
+ function makeState(): GlRenderState {
34
+ const canvas = document.createElement('canvas');
35
+ canvas.width = 100;
36
+ canvas.height = 100;
37
+ return createGlRenderState(canvas);
38
+ }
39
+
40
+ describe('registerGlDisplayObjectRenderers', () => {
41
+ it('registers all twelve display-object renderer kinds in one call', () => {
42
+ const state = makeState();
43
+ registerGlDisplayObjectRenderers(state);
44
+ const { rendererMap } = getRenderStateRuntime(state);
45
+ expect(rendererMap.get(BitmapKind)).toBe(defaultGlBitmapRenderer);
46
+ expect(rendererMap.get(DisplayObjectKind)).toBe(defaultGlDisplayObjectRenderer);
47
+ expect(rendererMap.get(ParticleEmitterKind)).toBe(defaultGlParticleEmitterRenderer);
48
+ expect(rendererMap.get(QuadBatchKind)).toBe(defaultGlQuadBatchRenderer);
49
+ expect(rendererMap.get(RenderCacheKind)).toBe(defaultGlRenderCacheRenderer);
50
+ expect(rendererMap.get(RichTextKind)).toBe(defaultGlRichTextRenderer);
51
+ expect(rendererMap.get(Scale9ShapeKind)).toBe(defaultGlScale9ShapeRenderer);
52
+ expect(rendererMap.get(ShapeKind)).toBe(defaultGlShapeRenderer);
53
+ expect(rendererMap.get(SpriteKind)).toBe(defaultGlSpriteRenderer);
54
+ expect(rendererMap.get(TextLabelKind)).toBe(defaultGlTextLabelRenderer);
55
+ expect(rendererMap.get(TilemapKind)).toBe(defaultGlTilemapRenderer);
56
+ expect(rendererMap.get(VideoKind)).toBe(defaultGlVideoRenderer);
57
+ });
58
+
59
+ it('registers BitmapKind with the default bitmap renderer', () => {
60
+ const state = makeState();
61
+ registerGlDisplayObjectRenderers(state);
62
+ expect(getRenderStateRuntime(state).rendererMap.get(BitmapKind)).toBe(defaultGlBitmapRenderer);
63
+ });
64
+
65
+ it('registers DisplayObjectKind with the default display object renderer', () => {
66
+ const state = makeState();
67
+ registerGlDisplayObjectRenderers(state);
68
+ expect(getRenderStateRuntime(state).rendererMap.get(DisplayObjectKind)).toBe(defaultGlDisplayObjectRenderer);
69
+ });
70
+
71
+ it('registers ParticleEmitterKind with the default particle emitter renderer', () => {
72
+ const state = makeState();
73
+ registerGlDisplayObjectRenderers(state);
74
+ expect(getRenderStateRuntime(state).rendererMap.get(ParticleEmitterKind)).toBe(defaultGlParticleEmitterRenderer);
75
+ });
76
+
77
+ it('registers QuadBatchKind with the default quad batch renderer', () => {
78
+ const state = makeState();
79
+ registerGlDisplayObjectRenderers(state);
80
+ expect(getRenderStateRuntime(state).rendererMap.get(QuadBatchKind)).toBe(defaultGlQuadBatchRenderer);
81
+ });
82
+
83
+ it('registers RenderCacheKind with the default render cache renderer', () => {
84
+ const state = makeState();
85
+ registerGlDisplayObjectRenderers(state);
86
+ expect(getRenderStateRuntime(state).rendererMap.get(RenderCacheKind)).toBe(defaultGlRenderCacheRenderer);
87
+ });
88
+
89
+ it('registers RichTextKind with the default rich text renderer', () => {
90
+ const state = makeState();
91
+ registerGlDisplayObjectRenderers(state);
92
+ expect(getRenderStateRuntime(state).rendererMap.get(RichTextKind)).toBe(defaultGlRichTextRenderer);
93
+ });
94
+
95
+ it('registers Scale9ShapeKind with the default scale9 shape renderer', () => {
96
+ const state = makeState();
97
+ registerGlDisplayObjectRenderers(state);
98
+ expect(getRenderStateRuntime(state).rendererMap.get(Scale9ShapeKind)).toBe(defaultGlScale9ShapeRenderer);
99
+ });
100
+
101
+ it('registers ShapeKind with the default shape renderer', () => {
102
+ const state = makeState();
103
+ registerGlDisplayObjectRenderers(state);
104
+ expect(getRenderStateRuntime(state).rendererMap.get(ShapeKind)).toBe(defaultGlShapeRenderer);
105
+ });
106
+
107
+ it('registers SpriteKind with the default sprite renderer', () => {
108
+ const state = makeState();
109
+ registerGlDisplayObjectRenderers(state);
110
+ expect(getRenderStateRuntime(state).rendererMap.get(SpriteKind)).toBe(defaultGlSpriteRenderer);
111
+ });
112
+
113
+ it('registers TextLabelKind with the default text label renderer', () => {
114
+ const state = makeState();
115
+ registerGlDisplayObjectRenderers(state);
116
+ expect(getRenderStateRuntime(state).rendererMap.get(TextLabelKind)).toBe(defaultGlTextLabelRenderer);
117
+ });
118
+
119
+ it('registers TilemapKind with the default tilemap renderer', () => {
120
+ const state = makeState();
121
+ registerGlDisplayObjectRenderers(state);
122
+ expect(getRenderStateRuntime(state).rendererMap.get(TilemapKind)).toBe(defaultGlTilemapRenderer);
123
+ });
124
+
125
+ it('registers VideoKind with the default video renderer', () => {
126
+ const state = makeState();
127
+ registerGlDisplayObjectRenderers(state);
128
+ expect(getRenderStateRuntime(state).rendererMap.get(VideoKind)).toBe(defaultGlVideoRenderer);
129
+ });
130
+ });
@@ -0,0 +1,91 @@
1
+ import { getGlRenderStateRuntime } from '@flighthq/render-gl';
2
+ import { ColorTransformMaterialKind, UniformColorTransformMaterialKind } from '@flighthq/types';
3
+
4
+ import { getGlRenderProxyColorTransform, registerGlColorTransformShader } from './glMaterials';
5
+ import { createGlState } from './glTestHelper';
6
+
7
+ describe('getGlRenderProxyColorTransform', () => {
8
+ it('returns null when the node has no material', () => {
9
+ expect(getGlRenderProxyColorTransform({ material: null } as never)).toBeNull();
10
+ });
11
+
12
+ it('returns the value carried on a uniform color transform material', () => {
13
+ const colorTransform = { redMultiplier: 0.5 };
14
+ const node = { material: { kind: UniformColorTransformMaterialKind, colorTransform } } as never;
15
+ expect(getGlRenderProxyColorTransform(node)).toBe(colorTransform);
16
+ });
17
+
18
+ it('returns the per-node material data for a color transform material', () => {
19
+ const colorTransform = { redMultiplier: 0.5 };
20
+ const node = { material: { kind: ColorTransformMaterialKind }, materialData: colorTransform } as never;
21
+ expect(getGlRenderProxyColorTransform(node)).toBe(colorTransform);
22
+ });
23
+ });
24
+
25
+ describe('registerGlColorTransformShader', () => {
26
+ it('registers a color transform shader on the render state', () => {
27
+ const { state } = createGlState();
28
+
29
+ registerGlColorTransformShader(state);
30
+
31
+ expect(getGlRenderStateRuntime(state).colorTransformBitmapShader).toBeDefined();
32
+ });
33
+
34
+ it('does not make the color transform shader the state default shader', () => {
35
+ const { state } = createGlState();
36
+
37
+ registerGlColorTransformShader(state);
38
+
39
+ const runtime = getGlRenderStateRuntime(state);
40
+ expect(runtime.defaultBitmapShader).not.toBe(runtime.colorTransformBitmapShader);
41
+ });
42
+
43
+ it('includes color transform uniform locations', () => {
44
+ const { state } = createGlState();
45
+
46
+ registerGlColorTransformShader(state);
47
+
48
+ const runtime = getGlRenderStateRuntime(state);
49
+ expect(runtime.colorTransformBitmapShader?.locations.locColorMultiplier).toBeDefined();
50
+ expect(runtime.colorTransformBitmapShader?.locations.locColorOffset).toBeDefined();
51
+ expect(runtime.colorTransformBitmapShader?.locations.locHasColorTransform).toBeDefined();
52
+ });
53
+
54
+ it('binds color transform uniforms from the node material data', () => {
55
+ const { state, gl } = createGlState();
56
+ const renderProxy = {
57
+ alpha: 0.75,
58
+ material: { kind: ColorTransformMaterialKind },
59
+ materialData: {
60
+ redMultiplier: 0.5,
61
+ greenMultiplier: 0.25,
62
+ blueMultiplier: 1.5,
63
+ alphaMultiplier: 0.8,
64
+ redOffset: 10,
65
+ greenOffset: 20,
66
+ blueOffset: 30,
67
+ alphaOffset: 40,
68
+ },
69
+ transform2D: { a: 1, b: 0, c: 0, d: 1, tx: 0, ty: 0 },
70
+ };
71
+
72
+ registerGlColorTransformShader(state);
73
+ const runtime = getGlRenderStateRuntime(state);
74
+ runtime.colorTransformBitmapShader!.bind(gl, state, renderProxy as never);
75
+
76
+ const loc = runtime.colorTransformBitmapShader!.locations;
77
+ expect(gl.uniform1i).toHaveBeenCalledWith(loc.locHasColorTransform, 1);
78
+ expect(gl.uniform4f).toHaveBeenCalledWith(loc.locColorMultiplier, 0.5, 0.25, 1.5, 0.8);
79
+ expect(gl.uniform4f).toHaveBeenCalledWith(loc.locColorOffset, 10 / 255, 20 / 255, 30 / 255, 40 / 255);
80
+ });
81
+
82
+ it('does not replace an existing color transform shader', () => {
83
+ const { state } = createGlState();
84
+
85
+ registerGlColorTransformShader(state);
86
+ const first = getGlRenderStateRuntime(state).colorTransformBitmapShader;
87
+ registerGlColorTransformShader(state);
88
+
89
+ expect(getGlRenderStateRuntime(state).colorTransformBitmapShader).toBe(first);
90
+ });
91
+ });
@@ -0,0 +1,98 @@
1
+ import type { RenderProxy2D } from '@flighthq/types';
2
+
3
+ import { defaultGlParticleEmitterRenderer, drawGlParticleEmitter } from './glParticleEmitter';
4
+ import { createGlState } from './glTestHelper';
5
+
6
+ function makeAtlas() {
7
+ const img = document.createElement('img');
8
+ return {
9
+ image: { source: img, width: 64, height: 64 },
10
+ regions: [{ id: 0, x: 0, y: 0, width: 32, height: 32 }],
11
+ };
12
+ }
13
+
14
+ function makeParticleEmitterNode(data: Record<string, unknown> = {}): RenderProxy2D {
15
+ return {
16
+ source: {
17
+ data: {
18
+ atlas: makeAtlas(),
19
+ particleCount: 1,
20
+ ids: new Uint16Array([0]),
21
+ transforms: new Float32Array([0, 0, 0, 1]),
22
+ alphas: new Float32Array([1]),
23
+ colors: new Float32Array([1, 1, 1]),
24
+ ...data,
25
+ },
26
+ },
27
+ blendMode: 0,
28
+ alpha: 1,
29
+ transform2D: { a: 1, b: 0, c: 0, d: 1, tx: 0, ty: 0 },
30
+ } as unknown as RenderProxy2D;
31
+ }
32
+
33
+ describe('defaultGlParticleEmitterRenderer', () => {
34
+ it('has a createData function', () => {
35
+ expect(typeof defaultGlParticleEmitterRenderer.createData).toBe('function');
36
+ });
37
+
38
+ it('has a submit function', () => {
39
+ expect(typeof defaultGlParticleEmitterRenderer.submit).toBe('function');
40
+ });
41
+ });
42
+
43
+ describe('drawGlParticleEmitter', () => {
44
+ it('returns early without drawing when atlas is null', () => {
45
+ const { state, gl } = createGlState();
46
+ drawGlParticleEmitter(state, makeParticleEmitterNode({ atlas: null }));
47
+ expect(gl.drawElements).not.toHaveBeenCalled();
48
+ });
49
+
50
+ it('returns early without drawing when atlas.image is null', () => {
51
+ const { state, gl } = createGlState();
52
+ drawGlParticleEmitter(state, makeParticleEmitterNode({ atlas: { image: null, regions: [] } }));
53
+ expect(gl.drawElements).not.toHaveBeenCalled();
54
+ });
55
+
56
+ it('returns early without drawing when atlas.image.source is null', () => {
57
+ const { state, gl } = createGlState();
58
+ drawGlParticleEmitter(state, makeParticleEmitterNode({ atlas: { image: { source: null }, regions: [] } }));
59
+ expect(gl.drawElements).not.toHaveBeenCalled();
60
+ });
61
+
62
+ it('returns early without drawing when particleCount is 0', () => {
63
+ const { state, gl } = createGlState();
64
+ drawGlParticleEmitter(state, makeParticleEmitterNode({ particleCount: 0 }));
65
+ expect(gl.drawElementsInstanced).not.toHaveBeenCalled();
66
+ });
67
+
68
+ it('draws all live particles in a single instanced draw call', () => {
69
+ const { state, gl } = createGlState();
70
+ drawGlParticleEmitter(
71
+ state,
72
+ makeParticleEmitterNode({
73
+ particleCount: 3,
74
+ ids: new Uint16Array([0, 0, 0]),
75
+ transforms: new Float32Array([0, 0, 0, 1, 10, 10, 0, 1, 20, 20, 0, 1]),
76
+ alphas: new Float32Array([1, 0.5, 0.25]),
77
+ colors: new Float32Array([1, 1, 1, 1, 1, 1, 1, 1, 1]),
78
+ }),
79
+ );
80
+ expect(gl.drawElementsInstanced).toHaveBeenCalledTimes(1);
81
+ expect(gl.drawElementsInstanced).toHaveBeenCalledWith(expect.anything(), 6, expect.anything(), 0, 3);
82
+ });
83
+
84
+ it('skips out-of-range region ids and draws only valid particles', () => {
85
+ const { state, gl } = createGlState();
86
+ drawGlParticleEmitter(
87
+ state,
88
+ makeParticleEmitterNode({
89
+ particleCount: 3,
90
+ ids: new Uint16Array([0, 99, 0]),
91
+ transforms: new Float32Array([0, 0, 0, 1, 10, 10, 0, 1, 20, 20, 0, 1]),
92
+ alphas: new Float32Array([1, 1, 1]),
93
+ colors: new Float32Array([1, 1, 1, 1, 1, 1, 1, 1, 1]),
94
+ }),
95
+ );
96
+ expect(gl.drawElementsInstanced).toHaveBeenCalledWith(expect.anything(), 6, expect.anything(), 0, 2);
97
+ });
98
+ });
@@ -0,0 +1,132 @@
1
+ import type { RenderProxy2D } from '@flighthq/types';
2
+
3
+ import { registerDefaultGlMaterial } from './glDefaultMaterial';
4
+ import { defaultGlQuadBatchRenderer } from './glQuadBatch';
5
+ import { flushGlSpriteBatch } from './glSpriteBatch';
6
+ import { createGlState } from './glTestHelper';
7
+
8
+ function makeAtlas() {
9
+ const img = document.createElement('img');
10
+ return {
11
+ image: { source: img, width: 64, height: 64 },
12
+ regions: [{ x: 0, y: 0, width: 32, height: 32 }],
13
+ };
14
+ }
15
+
16
+ function makeQuadBatchNode(data: Record<string, unknown> = {}): RenderProxy2D {
17
+ return {
18
+ source: {
19
+ data: {
20
+ atlas: makeAtlas(),
21
+ instanceCount: 1,
22
+ ids: new Int32Array([0]),
23
+ transforms: new Float32Array([0, 0]),
24
+ transformType: 'vector2',
25
+ ...data,
26
+ },
27
+ },
28
+ blendMode: 0,
29
+ alpha: 1,
30
+ material: null,
31
+ materialData: null,
32
+ renderer: null,
33
+ traverseChildren: false,
34
+ transform2D: { a: 1, b: 0, c: 0, d: 1, tx: 0, ty: 0 },
35
+ } as unknown as RenderProxy2D;
36
+ }
37
+
38
+ describe('defaultGlQuadBatchRenderer', () => {
39
+ it('has a createData function', () => {
40
+ expect(typeof defaultGlQuadBatchRenderer.createData).toBe('function');
41
+ });
42
+
43
+ it('has a submit function', () => {
44
+ expect(typeof defaultGlQuadBatchRenderer.submit).toBe('function');
45
+ });
46
+ });
47
+
48
+ describe('defaultGlQuadBatchRenderer.submit', () => {
49
+ it('returns early without drawing when atlas is null', () => {
50
+ const { state, gl } = createGlState();
51
+ registerDefaultGlMaterial(state);
52
+ defaultGlQuadBatchRenderer.submit(state, makeQuadBatchNode({ atlas: null }));
53
+ flushGlSpriteBatch(state as any);
54
+ expect(gl.drawElementsInstanced).not.toHaveBeenCalled();
55
+ });
56
+
57
+ it('returns early without drawing when atlas.image is null', () => {
58
+ const { state, gl } = createGlState();
59
+ registerDefaultGlMaterial(state);
60
+ defaultGlQuadBatchRenderer.submit(state, makeQuadBatchNode({ atlas: { image: null, regions: [] } }));
61
+ flushGlSpriteBatch(state as any);
62
+ expect(gl.drawElementsInstanced).not.toHaveBeenCalled();
63
+ });
64
+
65
+ it('returns early without drawing when atlas.image.source is null', () => {
66
+ const { state, gl } = createGlState();
67
+ registerDefaultGlMaterial(state);
68
+ defaultGlQuadBatchRenderer.submit(state, makeQuadBatchNode({ atlas: { image: { source: null }, regions: [] } }));
69
+ flushGlSpriteBatch(state as any);
70
+ expect(gl.drawElementsInstanced).not.toHaveBeenCalled();
71
+ });
72
+
73
+ it('returns early without drawing when instanceCount is 0', () => {
74
+ const { state, gl } = createGlState();
75
+ registerDefaultGlMaterial(state);
76
+ defaultGlQuadBatchRenderer.submit(state, makeQuadBatchNode({ instanceCount: 0 }));
77
+ flushGlSpriteBatch(state as any);
78
+ expect(gl.drawElementsInstanced).not.toHaveBeenCalled();
79
+ });
80
+
81
+ it('draws all valid instances in a single instanced call with vector2 transform type', () => {
82
+ const { state, gl } = createGlState();
83
+ registerDefaultGlMaterial(state);
84
+ defaultGlQuadBatchRenderer.submit(
85
+ state,
86
+ makeQuadBatchNode({
87
+ instanceCount: 2,
88
+ ids: new Int32Array([0, 0]),
89
+ transforms: new Float32Array([0, 0, 10, 20]),
90
+ transformType: 'vector2',
91
+ }),
92
+ );
93
+ flushGlSpriteBatch(state as any);
94
+ expect(gl.drawElementsInstanced).toHaveBeenCalledTimes(1);
95
+ expect(gl.drawElementsInstanced).toHaveBeenCalledWith(expect.anything(), 6, expect.anything(), 0, 2);
96
+ });
97
+
98
+ it('excludes out-of-range ids from the instanced draw count', () => {
99
+ const { state, gl } = createGlState();
100
+ registerDefaultGlMaterial(state);
101
+ defaultGlQuadBatchRenderer.submit(
102
+ state,
103
+ makeQuadBatchNode({
104
+ instanceCount: 3,
105
+ ids: new Int32Array([0, 99, 0]),
106
+ transforms: new Float32Array([0, 0, 0, 0, 0, 0]),
107
+ transformType: 'vector2',
108
+ }),
109
+ );
110
+ flushGlSpriteBatch(state as any);
111
+ expect(gl.drawElementsInstanced).toHaveBeenCalledTimes(1);
112
+ expect(gl.drawElementsInstanced).toHaveBeenCalledWith(expect.anything(), 6, expect.anything(), 0, 2);
113
+ });
114
+
115
+ it('draws all valid instances in a single instanced call with full matrix transform type', () => {
116
+ const { state, gl } = createGlState();
117
+ registerDefaultGlMaterial(state);
118
+ const transforms = new Float32Array([1, 0, 0, 1, 0, 0]);
119
+ defaultGlQuadBatchRenderer.submit(
120
+ state,
121
+ makeQuadBatchNode({
122
+ instanceCount: 1,
123
+ ids: new Int32Array([0]),
124
+ transforms,
125
+ transformType: 'matrix',
126
+ }),
127
+ );
128
+ flushGlSpriteBatch(state as any);
129
+ expect(gl.drawElementsInstanced).toHaveBeenCalledTimes(1);
130
+ expect(gl.drawElementsInstanced).toHaveBeenCalledWith(expect.anything(), 6, expect.anything(), 0, 1);
131
+ });
132
+ });