@flighthq/scene2d-gl 0.3.0-next.906.07cea63 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/dist/contract.d.ts +9 -6
  2. package/dist/contract.d.ts.map +1 -1
  3. package/dist/contract.js +9 -6
  4. package/dist/contract.js.map +1 -1
  5. package/dist/enableGlColorAdjustmentGuards.js +2 -2
  6. package/dist/enableGlColorAdjustmentGuards.js.map +1 -1
  7. package/dist/enableGlStrokePathTessellation.d.ts +3 -0
  8. package/dist/enableGlStrokePathTessellation.d.ts.map +1 -0
  9. package/dist/enableGlStrokePathTessellation.js +8 -0
  10. package/dist/enableGlStrokePathTessellation.js.map +1 -0
  11. package/dist/explainGlScene2DCoverage.d.ts +4 -0
  12. package/dist/explainGlScene2DCoverage.d.ts.map +1 -0
  13. package/dist/explainGlScene2DCoverage.js +63 -0
  14. package/dist/explainGlScene2DCoverage.js.map +1 -0
  15. package/dist/glBitmapText.d.ts.map +1 -1
  16. package/dist/glBitmapText.js +20 -15
  17. package/dist/glBitmapText.js.map +1 -1
  18. package/dist/glCache.d.ts +2 -2
  19. package/dist/glCache.d.ts.map +1 -1
  20. package/dist/glCache.js +24 -92
  21. package/dist/glCache.js.map +1 -1
  22. package/dist/glClipContours.js +3 -3
  23. package/dist/glClipContours.js.map +1 -1
  24. package/dist/glClipRectangle.js +10 -8
  25. package/dist/glClipRectangle.js.map +1 -1
  26. package/dist/glColorAdjustmentMaterialFeature.d.ts.map +1 -1
  27. package/dist/glColorAdjustmentMaterialFeature.js +79 -76
  28. package/dist/glColorAdjustmentMaterialFeature.js.map +1 -1
  29. package/dist/glMeshShapeRenderer.d.ts +4 -0
  30. package/dist/glMeshShapeRenderer.d.ts.map +1 -0
  31. package/dist/glMeshShapeRenderer.js +89 -0
  32. package/dist/glMeshShapeRenderer.js.map +1 -0
  33. package/dist/glNode2D.js +2 -2
  34. package/dist/glNode2D.js.map +1 -1
  35. package/dist/glParticleEmitter2D.js +10 -9
  36. package/dist/glParticleEmitter2D.js.map +1 -1
  37. package/dist/glQuadBatch.d.ts.map +1 -1
  38. package/dist/glQuadBatch.js +18 -12
  39. package/dist/glQuadBatch.js.map +1 -1
  40. package/dist/{glSpriteBatch.d.ts → glQuadBatchWriter.d.ts} +6 -6
  41. package/dist/glQuadBatchWriter.d.ts.map +1 -0
  42. package/dist/{glSpriteBatch.js → glQuadBatchWriter.js} +84 -73
  43. package/dist/glQuadBatchWriter.js.map +1 -0
  44. package/dist/glRasterShapeRenderer.d.ts +4 -0
  45. package/dist/glRasterShapeRenderer.d.ts.map +1 -0
  46. package/dist/glRasterShapeRenderer.js +103 -0
  47. package/dist/glRasterShapeRenderer.js.map +1 -0
  48. package/dist/glRichText.js +2 -2
  49. package/dist/glRichText.js.map +1 -1
  50. package/dist/glScale9Shape.d.ts +1 -3
  51. package/dist/glScale9Shape.d.ts.map +1 -1
  52. package/dist/glScale9Shape.js +27 -29
  53. package/dist/glScale9Shape.js.map +1 -1
  54. package/dist/glShape.d.ts +2 -1
  55. package/dist/glShape.d.ts.map +1 -1
  56. package/dist/glShape.js +19 -135
  57. package/dist/glShape.js.map +1 -1
  58. package/dist/glShapeData.d.ts +7 -0
  59. package/dist/glShapeData.d.ts.map +1 -0
  60. package/dist/glShapeData.js +57 -0
  61. package/dist/glShapeData.js.map +1 -0
  62. package/dist/glShapeMesh.js +5 -5
  63. package/dist/glShapeMesh.js.map +1 -1
  64. package/dist/glShapeRasterizer.d.ts +4 -0
  65. package/dist/glShapeRasterizer.d.ts.map +1 -0
  66. package/dist/glShapeRasterizer.js +11 -0
  67. package/dist/glShapeRasterizer.js.map +1 -0
  68. package/dist/glSprite.d.ts +3 -2
  69. package/dist/glSprite.d.ts.map +1 -1
  70. package/dist/glSprite.js +64 -24
  71. package/dist/glSprite.js.map +1 -1
  72. package/dist/glStandardMaterial.d.ts +1 -1
  73. package/dist/glStandardMaterial.js +2 -2
  74. package/dist/glStandardMaterial.js.map +1 -1
  75. package/dist/glTestHelper.d.ts +1 -0
  76. package/dist/glTestHelper.d.ts.map +1 -1
  77. package/dist/glTestHelper.js +1 -0
  78. package/dist/glTestHelper.js.map +1 -1
  79. package/dist/glTextLabel.d.ts.map +1 -1
  80. package/dist/glTextLabel.js +14 -12
  81. package/dist/glTextLabel.js.map +1 -1
  82. package/dist/glTilemap.d.ts.map +1 -1
  83. package/dist/glTilemap.js +19 -17
  84. package/dist/glTilemap.js.map +1 -1
  85. package/dist/glVelocity.js +2 -2
  86. package/dist/glVelocity.js.map +1 -1
  87. package/dist/index.d.ts +2 -1
  88. package/dist/index.d.ts.map +1 -1
  89. package/dist/index.js +2 -1
  90. package/dist/index.js.map +1 -1
  91. package/package.json +22 -19
  92. package/src/enableGlColorAdjustmentGuards.test.ts +4 -4
  93. package/src/enableGlStrokePathTessellation.test.ts +16 -0
  94. package/src/explainGlScene2DCoverage.test.ts +99 -0
  95. package/src/glBitmapText.test.ts +18 -17
  96. package/src/glCache.test.ts +24 -11
  97. package/src/glClipContours.test.ts +57 -2
  98. package/src/glClipRectangle.test.ts +9 -0
  99. package/src/glColorAdjustmentMaterialFeature.test.ts +74 -71
  100. package/src/glMeshShapeRenderer.test.ts +112 -0
  101. package/src/glParticleEmitter2D.test.ts +38 -22
  102. package/src/glQuadBatch.test.ts +40 -28
  103. package/src/{glSpriteBatch.test.ts → glQuadBatchWriter.test.ts} +74 -53
  104. package/src/glRasterShapeRenderer.test.ts +109 -0
  105. package/src/glScale9Shape.test.ts +0 -13
  106. package/src/glShape.test.ts +166 -21
  107. package/src/glShapeData.test.ts +74 -0
  108. package/src/glShapeMesh.test.ts +1 -1
  109. package/src/glShapeRasterizer.test.ts +32 -0
  110. package/src/glSprite.test.ts +71 -62
  111. package/src/glStandardMaterial.test.ts +3 -3
  112. package/src/glTextLabel.test.ts +16 -16
  113. package/src/glTilemap.test.ts +44 -47
  114. package/src/glVelocity.test.ts +4 -4
  115. package/dist/glBitmap.d.ts +0 -4
  116. package/dist/glBitmap.d.ts.map +0 -1
  117. package/dist/glBitmap.js +0 -93
  118. package/dist/glBitmap.js.map +0 -1
  119. package/dist/glRenderTargetNode2D.d.ts +0 -11
  120. package/dist/glRenderTargetNode2D.d.ts.map +0 -1
  121. package/dist/glRenderTargetNode2D.js +0 -78
  122. package/dist/glRenderTargetNode2D.js.map +0 -1
  123. package/dist/glSpriteBatch.d.ts.map +0 -1
  124. package/dist/glSpriteBatch.js.map +0 -1
  125. package/dist/glSpriteRenderer.d.ts +0 -3
  126. package/dist/glSpriteRenderer.d.ts.map +0 -1
  127. package/dist/glSpriteRenderer.js +0 -54
  128. package/dist/glSpriteRenderer.js.map +0 -1
  129. package/dist/glVideo.d.ts +0 -6
  130. package/dist/glVideo.d.ts.map +0 -1
  131. package/dist/glVideo.js +0 -63
  132. package/dist/glVideo.js.map +0 -1
  133. package/src/glBitmap.test.ts +0 -136
  134. package/src/glRenderTargetNode2D.test.ts +0 -328
  135. package/src/glSpriteRenderer.test.ts +0 -89
  136. package/src/glVideo.test.ts +0 -99
@@ -1,14 +1,14 @@
1
- import { createImageResource } from '@flighthq/image/contract';
2
1
  import { getGlRenderStateRuntime } from '@flighthq/render-gl/contract';
3
- import type { ColorScaleBias, ImageResource } from '@flighthq/types/contract';
2
+ import { areColorAdjustmentsEnabled } from '@flighthq/render/contract';
3
+ import type { ColorScaleBias } from '@flighthq/types/contract';
4
4
 
5
5
  import { registerGlColorAdjustmentMaterialFeature } from './glColorAdjustmentMaterialFeature';
6
- import { flushGlSpriteBatch, prepareGlSpriteBatchWrite, recordGlSpriteBatchColorScaleBias } from './glSpriteBatch';
6
+ import { flushGlQuadBatchWriter, prepareGlQuadBatchWrite, recordGlQuadBatchColorScaleBias } from './glQuadBatchWriter';
7
7
  import { standardGlMaterialRenderer } from './glStandardMaterial';
8
8
  import { createGlState } from './glTestHelper';
9
9
 
10
- function makeTexture(): ImageResource {
11
- return createImageResource(document.createElement('img'));
10
+ function makeTexture(): WebGLTexture {
11
+ return {} as WebGLTexture;
12
12
  }
13
13
 
14
14
  function ct(
@@ -45,6 +45,7 @@ describe('registerGlColorAdjustmentMaterialFeature', () => {
45
45
  const { state } = createGlState();
46
46
  const runtime = getGlRenderStateRuntime(state);
47
47
  registerGlColorAdjustmentMaterialFeature(state);
48
+ expect(areColorAdjustmentsEnabled(state)).toBe(true);
48
49
  expect(runtime.glColorAdjustmentMaterialFeature).not.toBeNull();
49
50
  expect(runtime.glColorAdjustmentMaterialFeature).toBeDefined();
50
51
  });
@@ -62,11 +63,11 @@ describe('registerGlColorAdjustmentMaterialFeature', () => {
62
63
  const { state } = createGlState();
63
64
  const runtime = getGlRenderStateRuntime(state);
64
65
  registerGlColorAdjustmentMaterialFeature(state);
65
- recordGlSpriteBatchColorScaleBias(state, null, 0);
66
- recordGlSpriteBatchColorScaleBias(state, null, 1);
67
- expect(runtime.spriteBatchColorScaleBiasMode).toBe(CT_MODE_NONE);
68
- expect(runtime.spriteBatchColorTintData).toBeUndefined();
69
- expect(runtime.spriteBatchColorScaleBiasData).toBeUndefined();
66
+ recordGlQuadBatchColorScaleBias(state, null, 0);
67
+ recordGlQuadBatchColorScaleBias(state, null, 1);
68
+ expect(runtime.quadBatchWriterColorScaleBiasMode).toBe(CT_MODE_NONE);
69
+ expect(runtime.quadBatchWriterColorTintData).toBeUndefined();
70
+ expect(runtime.quadBatchWriterColorScaleBiasData).toBeUndefined();
70
71
  });
71
72
 
72
73
  it('uses one whole-batch uniform when every instance shares one tint', () => {
@@ -74,43 +75,43 @@ describe('registerGlColorAdjustmentMaterialFeature', () => {
74
75
  const runtime = getGlRenderStateRuntime(state);
75
76
  registerGlColorAdjustmentMaterialFeature(state);
76
77
  const tint = ct(0.5);
77
- recordGlSpriteBatchColorScaleBias(state, tint, 0);
78
- recordGlSpriteBatchColorScaleBias(state, tint, 1);
79
- recordGlSpriteBatchColorScaleBias(state, tint, 2);
80
- expect(runtime.spriteBatchColorScaleBiasMode).toBe(CT_MODE_UNIFORM);
81
- expect(runtime.spriteBatchUniformColorScaleBias).toBe(tint);
78
+ recordGlQuadBatchColorScaleBias(state, tint, 0);
79
+ recordGlQuadBatchColorScaleBias(state, tint, 1);
80
+ recordGlQuadBatchColorScaleBias(state, tint, 2);
81
+ expect(runtime.quadBatchWriterColorScaleBiasMode).toBe(CT_MODE_UNIFORM);
82
+ expect(runtime.quadBatchWriterUniformColorScaleBias).toBe(tint);
82
83
  });
83
84
 
84
85
  it('keeps the uniform path for a distinct-but-equal tint', () => {
85
86
  const { state } = createGlState();
86
87
  const runtime = getGlRenderStateRuntime(state);
87
88
  registerGlColorAdjustmentMaterialFeature(state);
88
- recordGlSpriteBatchColorScaleBias(state, ct(0.5), 0);
89
- recordGlSpriteBatchColorScaleBias(state, ct(0.5), 1);
90
- expect(runtime.spriteBatchColorScaleBiasMode).toBe(CT_MODE_UNIFORM);
89
+ recordGlQuadBatchColorScaleBias(state, ct(0.5), 0);
90
+ recordGlQuadBatchColorScaleBias(state, ct(0.5), 1);
91
+ expect(runtime.quadBatchWriterColorScaleBiasMode).toBe(CT_MODE_UNIFORM);
91
92
  });
92
93
 
93
94
  it('promotes varying multiply-only tints to packed RGBA8 without splitting', () => {
94
95
  const { state } = createGlState();
95
96
  const runtime = getGlRenderStateRuntime(state);
96
97
  registerGlColorAdjustmentMaterialFeature(state);
97
- recordGlSpriteBatchColorScaleBias(state, ct(0.5), 0);
98
- recordGlSpriteBatchColorScaleBias(state, ct(0.25), 1);
99
- expect(runtime.spriteBatchColorScaleBiasMode).toBe(CT_MODE_PACKED_TINT);
100
- expect(Array.from(new Uint8Array(runtime.spriteBatchColorTintData!.buffer, 0, 8))).toEqual([
98
+ recordGlQuadBatchColorScaleBias(state, ct(0.5), 0);
99
+ recordGlQuadBatchColorScaleBias(state, ct(0.25), 1);
100
+ expect(runtime.quadBatchWriterColorScaleBiasMode).toBe(CT_MODE_PACKED_TINT);
101
+ expect(Array.from(new Uint8Array(runtime.quadBatchWriterColorTintData!.buffer, 0, 8))).toEqual([
101
102
  128, 255, 255, 255, 64, 255, 255, 255,
102
103
  ]);
103
- expect(runtime.spriteBatchColorScaleBiasData).toBeUndefined();
104
+ expect(runtime.quadBatchWriterColorScaleBiasData).toBeUndefined();
104
105
  });
105
106
 
106
107
  it('promotes with identity fill when a tinted instance follows an untinted one', () => {
107
108
  const { state } = createGlState();
108
109
  const runtime = getGlRenderStateRuntime(state);
109
110
  registerGlColorAdjustmentMaterialFeature(state);
110
- recordGlSpriteBatchColorScaleBias(state, null, 0);
111
- recordGlSpriteBatchColorScaleBias(state, ct(0.5), 1);
112
- expect(runtime.spriteBatchColorScaleBiasMode).toBe(CT_MODE_PACKED_TINT);
113
- expect(Array.from(new Uint8Array(runtime.spriteBatchColorTintData!.buffer, 0, 8))).toEqual([
111
+ recordGlQuadBatchColorScaleBias(state, null, 0);
112
+ recordGlQuadBatchColorScaleBias(state, ct(0.5), 1);
113
+ expect(runtime.quadBatchWriterColorScaleBiasMode).toBe(CT_MODE_PACKED_TINT);
114
+ expect(Array.from(new Uint8Array(runtime.quadBatchWriterColorTintData!.buffer, 0, 8))).toEqual([
114
115
  255, 255, 255, 255, 128, 255, 255, 255,
115
116
  ]);
116
117
  });
@@ -119,43 +120,45 @@ describe('registerGlColorAdjustmentMaterialFeature', () => {
119
120
  const { state } = createGlState();
120
121
  const runtime = getGlRenderStateRuntime(state);
121
122
  registerGlColorAdjustmentMaterialFeature(state);
122
- recordGlSpriteBatchColorScaleBias(state, ct(0.5), 0);
123
- recordGlSpriteBatchColorScaleBias(state, ct(0.25), 1);
124
- recordGlSpriteBatchColorScaleBias(state, null, 2);
125
- expect(Array.from(new Uint8Array(runtime.spriteBatchColorTintData!.buffer, 8, 4))).toEqual([255, 255, 255, 255]);
123
+ recordGlQuadBatchColorScaleBias(state, ct(0.5), 0);
124
+ recordGlQuadBatchColorScaleBias(state, ct(0.25), 1);
125
+ recordGlQuadBatchColorScaleBias(state, null, 2);
126
+ expect(Array.from(new Uint8Array(runtime.quadBatchWriterColorTintData!.buffer, 8, 4))).toEqual([
127
+ 255, 255, 255, 255,
128
+ ]);
126
129
  });
127
130
 
128
131
  it('stores normalized-linear color biases without conversion', () => {
129
132
  const { state } = createGlState();
130
133
  const runtime = getGlRenderStateRuntime(state);
131
134
  registerGlColorAdjustmentMaterialFeature(state);
132
- recordGlSpriteBatchColorScaleBias(state, ct(1, 1, 1, 1, 1, 0, 0, 0), 0);
133
- recordGlSpriteBatchColorScaleBias(state, ct(0.5), 1);
134
- expect(runtime.spriteBatchColorScaleBiasData![4]).toBe(1);
135
- expect(runtime.spriteBatchColorScaleBiasMode).toBe(CT_MODE_PER_INSTANCE);
135
+ recordGlQuadBatchColorScaleBias(state, ct(1, 1, 1, 1, 1, 0, 0, 0), 0);
136
+ recordGlQuadBatchColorScaleBias(state, ct(0.5), 1);
137
+ expect(runtime.quadBatchWriterColorScaleBiasData![4]).toBe(1);
138
+ expect(runtime.quadBatchWriterColorScaleBiasMode).toBe(CT_MODE_PER_INSTANCE);
136
139
  });
137
140
 
138
141
  it('widens to a full matrix stream only when channel mixing appears', () => {
139
142
  const { state } = createGlState();
140
143
  const runtime = getGlRenderStateRuntime(state);
141
144
  registerGlColorAdjustmentMaterialFeature(state);
142
- recordGlSpriteBatchColorScaleBias(state, { tint: 0x808080ff }, 0);
143
- recordGlSpriteBatchColorScaleBias(state, MIX_RED_GREEN, 1);
144
- expect(runtime.spriteBatchColorScaleBiasMode).toBe(CT_MODE_MATRIX);
145
- expect(runtime.spriteBatchColorMatrixData![0]).toBeCloseTo(128 / 255);
146
- expect(runtime.spriteBatchColorMatrixData![20]).toBe(1);
147
- expect(runtime.spriteBatchColorMatrixData![21]).toBe(0.5);
148
- expect(runtime.spriteBatchColorMatrixData![36]).toBe(0.25);
145
+ recordGlQuadBatchColorScaleBias(state, { tint: 0x808080ff }, 0);
146
+ recordGlQuadBatchColorScaleBias(state, MIX_RED_GREEN, 1);
147
+ expect(runtime.quadBatchWriterColorScaleBiasMode).toBe(CT_MODE_MATRIX);
148
+ expect(runtime.quadBatchWriterColorMatrixData![0]).toBeCloseTo(128 / 255);
149
+ expect(runtime.quadBatchWriterColorMatrixData![20]).toBe(1);
150
+ expect(runtime.quadBatchWriterColorMatrixData![21]).toBe(0.5);
151
+ expect(runtime.quadBatchWriterColorMatrixData![36]).toBe(0.25);
149
152
  });
150
153
 
151
154
  it('records compact per-item tint data directly as four bytes', () => {
152
155
  const { state } = createGlState();
153
156
  const runtime = getGlRenderStateRuntime(state);
154
157
  registerGlColorAdjustmentMaterialFeature(state);
155
- recordGlSpriteBatchColorScaleBias(state, null, 0);
156
- recordGlSpriteBatchColorScaleBias(state, { tint: 0x12345678 }, 1);
157
- expect(runtime.spriteBatchColorScaleBiasMode).toBe(CT_MODE_PACKED_TINT);
158
- expect(Array.from(new Uint8Array(runtime.spriteBatchColorTintData!.buffer, 4, 4))).toEqual([
158
+ recordGlQuadBatchColorScaleBias(state, null, 0);
159
+ recordGlQuadBatchColorScaleBias(state, { tint: 0x12345678 }, 1);
160
+ expect(runtime.quadBatchWriterColorScaleBiasMode).toBe(CT_MODE_PACKED_TINT);
161
+ expect(Array.from(new Uint8Array(runtime.quadBatchWriterColorTintData!.buffer, 4, 4))).toEqual([
159
162
  0x12, 0x34, 0x56, 0x78,
160
163
  ]);
161
164
  });
@@ -164,10 +167,10 @@ describe('registerGlColorAdjustmentMaterialFeature', () => {
164
167
  const { state, gl } = createGlState();
165
168
  const runtime = getGlRenderStateRuntime(state);
166
169
  registerGlColorAdjustmentMaterialFeature(state);
167
- prepareGlSpriteBatchWrite(state, makeTexture(), null, null, standardGlMaterialRenderer, 1);
168
- recordGlSpriteBatchColorScaleBias(state, ct(0.5), 0);
169
- runtime.spriteBatchCount = 1;
170
- flushGlSpriteBatch(state);
170
+ prepareGlQuadBatchWrite(state, makeTexture(), false, null, null, null, standardGlMaterialRenderer, 1);
171
+ recordGlQuadBatchColorScaleBias(state, ct(0.5), 0);
172
+ runtime.quadBatchWriterCount = 1;
173
+ flushGlQuadBatchWriter(state);
171
174
  expect(gl.uniform4f).toHaveBeenCalled();
172
175
  expect(gl.drawElementsInstanced).toHaveBeenCalled();
173
176
  });
@@ -176,12 +179,12 @@ describe('registerGlColorAdjustmentMaterialFeature', () => {
176
179
  const { state, gl } = createGlState();
177
180
  const runtime = getGlRenderStateRuntime(state);
178
181
  registerGlColorAdjustmentMaterialFeature(state);
179
- prepareGlSpriteBatchWrite(state, makeTexture(), null, null, standardGlMaterialRenderer, 2);
180
- recordGlSpriteBatchColorScaleBias(state, ct(0.5), 0);
181
- recordGlSpriteBatchColorScaleBias(state, ct(0.25), 1);
182
- runtime.spriteBatchCount = 2;
183
- flushGlSpriteBatch(state);
184
- expect(runtime.spriteBatchColorScaleBiasBuffer).not.toBeNull();
182
+ prepareGlQuadBatchWrite(state, makeTexture(), false, null, null, null, standardGlMaterialRenderer, 2);
183
+ recordGlQuadBatchColorScaleBias(state, ct(0.5), 0);
184
+ recordGlQuadBatchColorScaleBias(state, ct(0.25), 1);
185
+ runtime.quadBatchWriterCount = 2;
186
+ flushGlQuadBatchWriter(state);
187
+ expect(runtime.quadBatchWriterColorScaleBiasBuffer).not.toBeNull();
185
188
  expect(gl.vertexAttribPointer).toHaveBeenCalledWith(7, 4, gl.UNSIGNED_BYTE, true, 4, 0);
186
189
  expect(gl.drawElementsInstanced).toHaveBeenCalledWith(expect.anything(), 6, expect.anything(), 0, 2);
187
190
  });
@@ -190,11 +193,11 @@ describe('registerGlColorAdjustmentMaterialFeature', () => {
190
193
  const { state, gl } = createGlState();
191
194
  const runtime = getGlRenderStateRuntime(state);
192
195
  registerGlColorAdjustmentMaterialFeature(state);
193
- prepareGlSpriteBatchWrite(state, makeTexture(), null, null, standardGlMaterialRenderer, 2);
194
- recordGlSpriteBatchColorScaleBias(state, null, 0);
195
- recordGlSpriteBatchColorScaleBias(state, MIX_RED_GREEN, 1);
196
- runtime.spriteBatchCount = 2;
197
- flushGlSpriteBatch(state);
196
+ prepareGlQuadBatchWrite(state, makeTexture(), false, null, null, null, standardGlMaterialRenderer, 2);
197
+ recordGlQuadBatchColorScaleBias(state, null, 0);
198
+ recordGlQuadBatchColorScaleBias(state, MIX_RED_GREEN, 1);
199
+ runtime.quadBatchWriterCount = 2;
200
+ flushGlQuadBatchWriter(state);
198
201
  expect(gl.vertexAttribPointer).toHaveBeenCalledWith(7, 4, gl.FLOAT, false, 80, 0);
199
202
  expect(gl.vertexAttribPointer).toHaveBeenCalledWith(11, 4, gl.FLOAT, false, 80, 64);
200
203
  });
@@ -203,22 +206,22 @@ describe('registerGlColorAdjustmentMaterialFeature', () => {
203
206
  const { state } = createGlState();
204
207
  const runtime = getGlRenderStateRuntime(state);
205
208
  registerGlColorAdjustmentMaterialFeature(state);
206
- prepareGlSpriteBatchWrite(state, makeTexture(), null, null, standardGlMaterialRenderer, 1);
207
- recordGlSpriteBatchColorScaleBias(state, MIX_RED_GREEN, 0);
208
- runtime.spriteBatchCount = 1;
209
- flushGlSpriteBatch(state);
210
- expect(runtime.spriteBatchColorScaleBiasMode).toBe(CT_MODE_NONE);
209
+ prepareGlQuadBatchWrite(state, makeTexture(), false, null, null, null, standardGlMaterialRenderer, 1);
210
+ recordGlQuadBatchColorScaleBias(state, MIX_RED_GREEN, 0);
211
+ runtime.quadBatchWriterCount = 1;
212
+ flushGlQuadBatchWriter(state);
213
+ expect(runtime.quadBatchWriterColorScaleBiasMode).toBe(CT_MODE_NONE);
211
214
  });
212
215
 
213
216
  it('leaves the lean base shader untouched for an untinted batch on flush', () => {
214
217
  const { state, gl } = createGlState();
215
218
  const runtime = getGlRenderStateRuntime(state);
216
219
  registerGlColorAdjustmentMaterialFeature(state);
217
- prepareGlSpriteBatchWrite(state, makeTexture(), null, null, standardGlMaterialRenderer, 1);
218
- recordGlSpriteBatchColorScaleBias(state, null, 0);
219
- runtime.spriteBatchCount = 1;
220
- flushGlSpriteBatch(state);
221
- expect(runtime.spriteBatchColorScaleBiasBuffer ?? null).toBeNull();
220
+ prepareGlQuadBatchWrite(state, makeTexture(), false, null, null, null, standardGlMaterialRenderer, 1);
221
+ recordGlQuadBatchColorScaleBias(state, null, 0);
222
+ runtime.quadBatchWriterCount = 1;
223
+ flushGlQuadBatchWriter(state);
224
+ expect(runtime.quadBatchWriterColorScaleBiasBuffer ?? null).toBeNull();
222
225
  expect(gl.drawElementsInstanced).toHaveBeenCalled();
223
226
  });
224
227
  });
@@ -0,0 +1,112 @@
1
+ import type * as FlightNodeModule from '@flighthq/node/contract';
2
+ import { enableRenderRegistryGuards, explainRenderRegistryMisses } from '@flighthq/render/contract';
3
+ import {
4
+ appendShapeBeginFill,
5
+ appendShapeBeginGradientFill,
6
+ appendShapeEndFill,
7
+ appendShapeRectangle,
8
+ createShape,
9
+ } from '@flighthq/shape/contract';
10
+ import type { RenderProxy2D } from '@flighthq/types/contract';
11
+ import { BatchFormat, RenderRegistry } from '@flighthq/types/contract';
12
+
13
+ import type * as GlMeshShapeModule from './glMeshShapeRenderer';
14
+ import { registerGlShapeRasterizer } from './glShapeRasterizer';
15
+ import { createGlState } from './glTestHelper';
16
+ import { scopeModuleMocks } from './moduleMockTestHelper';
17
+
18
+ let defaultGlMeshShapeRenderer: typeof GlMeshShapeModule.defaultGlMeshShapeRenderer;
19
+ let drawGlMeshShape: typeof GlMeshShapeModule.drawGlMeshShape;
20
+
21
+ scopeModuleMocks(['@flighthq/node']);
22
+
23
+ beforeAll(async () => {
24
+ vi.doMock('@flighthq/node/contract', async (importOriginal) => ({
25
+ ...(await importOriginal<typeof FlightNodeModule>()),
26
+ getNodeLocalBoundsRectangle: () => ({ x: 0, y: 0, width: 64, height: 48 }),
27
+ getNodeLocalContentRevision: (source: any) => source?.data?.version ?? 0,
28
+ }));
29
+ ({ defaultGlMeshShapeRenderer, drawGlMeshShape } = await import('./glMeshShapeRenderer'));
30
+ });
31
+
32
+ function makeShapeData() {
33
+ return { surface: null, lastContentId: -1, lastPixelRatio: 0, lastW: 0, lastH: 0, meshVersion: -1, meshes: null };
34
+ }
35
+
36
+ function makeShapeNode(data: Record<string, unknown>, rendererData: unknown = makeShapeData()): RenderProxy2D {
37
+ return {
38
+ source: { data: { commands: [], version: 0, ...data } },
39
+ rendererData,
40
+ blendMode: 0,
41
+ alpha: 1,
42
+ material: null,
43
+ materialData: null,
44
+ transform2D: { a: 1, b: 0, c: 0, d: 1, tx: 0, ty: 0 },
45
+ } as unknown as RenderProxy2D;
46
+ }
47
+
48
+ function solidShape() {
49
+ const shape = createShape();
50
+ appendShapeBeginFill(shape, 0x00cc00);
51
+ appendShapeRectangle(shape, 8, 8, 32, 24);
52
+ appendShapeEndFill(shape);
53
+ return shape;
54
+ }
55
+
56
+ function gradientShape() {
57
+ const shape = createShape();
58
+ appendShapeBeginGradientFill(shape, 'linear', [0xff0000, 0x0000ff], [1, 1], [0, 255], null);
59
+ appendShapeRectangle(shape, 8, 8, 32, 24);
60
+ appendShapeEndFill(shape);
61
+ return shape;
62
+ }
63
+
64
+ describe('defaultGlMeshShapeRenderer', () => {
65
+ it('declares BatchFormat.Quad and the shared shape data lifecycle', () => {
66
+ expect(defaultGlMeshShapeRenderer.format).toBe(BatchFormat.Quad);
67
+ expect(typeof defaultGlMeshShapeRenderer.createData).toBe('function');
68
+ expect(typeof defaultGlMeshShapeRenderer.destroyData).toBe('function');
69
+ });
70
+
71
+ it('never rasterizes, even with a rasterizer registered and a fill that cannot tessellate', () => {
72
+ // The whole point of pinning this strategy: a registered rasterizer is not consulted, so an app can
73
+ // choose the GPU path and know the canvas replay is dead weight it did not ship.
74
+ const { state } = createGlState();
75
+ const rasterizer = vi.fn();
76
+ registerGlShapeRasterizer(state, rasterizer);
77
+ defaultGlMeshShapeRenderer.submit!(state, makeShapeNode({ commands: gradientShape().data.commands, version: 1 }));
78
+ expect(rasterizer).not.toHaveBeenCalled();
79
+ });
80
+
81
+ it('reports a ShapeRasterizer miss rather than silently dropping an untessellatable fill', () => {
82
+ const { state } = createGlState();
83
+ enableRenderRegistryGuards(state);
84
+ defaultGlMeshShapeRenderer.submit!(state, makeShapeNode({ commands: gradientShape().data.commands, version: 1 }));
85
+ expect(explainRenderRegistryMisses(state).misses).toContainEqual({
86
+ kind: 'Shape',
87
+ registry: RenderRegistry.ShapeRasterizer,
88
+ });
89
+ });
90
+ });
91
+
92
+ describe('drawGlMeshShape', () => {
93
+ it('draws a solid fill as a GPU mesh and reports that it drew', () => {
94
+ const { state, gl } = createGlState();
95
+ const drew = drawGlMeshShape(state, makeShapeNode({ commands: solidShape().data.commands, version: 1 }));
96
+ expect(drew).toBe(true);
97
+ expect(gl.drawElements).toHaveBeenCalled();
98
+ });
99
+
100
+ it('reports false for a fill with no tessellated form, which is the hybrid fall-through signal', () => {
101
+ const { state } = createGlState();
102
+ expect(drawGlMeshShape(state, makeShapeNode({ commands: gradientShape().data.commands, version: 1 }))).toBe(false);
103
+ });
104
+
105
+ it('reports false for an empty command list and for absent renderer data', () => {
106
+ const { state } = createGlState();
107
+ expect(drawGlMeshShape(state, makeShapeNode({ commands: [], version: 1 }))).toBe(false);
108
+ expect(drawGlMeshShape(state, makeShapeNode({ commands: solidShape().data.commands, version: 1 }, null))).toBe(
109
+ false,
110
+ );
111
+ });
112
+ });
@@ -1,17 +1,33 @@
1
- import { getGlRenderStateRuntime } from '@flighthq/render-gl/contract';
2
- import type { RenderProxy2D } from '@flighthq/types/contract';
1
+ import { createImageResource } from '@flighthq/image/contract';
2
+ import {
3
+ getGlRenderStateRuntime,
4
+ registerGlCompressedImageTextureResolver,
5
+ registerGlImageTextureResolver,
6
+ } from '@flighthq/render-gl/contract';
7
+ import { createTexture } from '@flighthq/texture/contract';
8
+ import type { CompressedImage, RenderProxy2D } from '@flighthq/types/contract';
9
+ import { CompressedImageTextureSourceKind } from '@flighthq/types/contract';
3
10
 
4
11
  import { defaultGlParticleEmitter2DRenderer, drawGlParticleEmitter2D } from './glParticleEmitter2D';
5
12
  import { createGlState } from './glTestHelper';
6
13
 
7
14
  function makeAtlas() {
8
15
  const img = document.createElement('img');
16
+ const image = createImageResource(img);
17
+ image.width = 64;
18
+ image.height = 64;
9
19
  return {
10
- image: { source: img, width: 64, height: 64 },
11
20
  regions: [{ id: 0, x: 0, y: 0, width: 32, height: 32 }],
21
+ texture: createTexture({ dimension: '2d', source: image }),
12
22
  };
13
23
  }
14
24
 
25
+ function createAtlasGlState() {
26
+ const result = createGlState();
27
+ registerGlImageTextureResolver(result.state);
28
+ return result;
29
+ }
30
+
15
31
  function makeParticleEmitter2DNode(data: Record<string, unknown> = {}): RenderProxy2D {
16
32
  return {
17
33
  source: {
@@ -43,34 +59,31 @@ describe('defaultGlParticleEmitter2DRenderer', () => {
43
59
 
44
60
  describe('drawGlParticleEmitter2D', () => {
45
61
  it('returns early without drawing when atlas is null', () => {
46
- const { state, gl } = createGlState();
62
+ const { state, gl } = createAtlasGlState();
47
63
  drawGlParticleEmitter2D(state, makeParticleEmitter2DNode({ atlas: null }));
48
64
  expect(gl.drawElements).not.toHaveBeenCalled();
49
65
  });
50
66
 
51
- it('returns early without drawing when atlas.image is null', () => {
52
- const { state, gl } = createGlState();
53
- drawGlParticleEmitter2D(state, makeParticleEmitter2DNode({ atlas: { image: null, regions: [] } }));
67
+ it('returns early without drawing when atlas.texture is null', () => {
68
+ const { state, gl } = createAtlasGlState();
69
+ drawGlParticleEmitter2D(state, makeParticleEmitter2DNode({ atlas: { regions: [], texture: null } }));
54
70
  expect(gl.drawElements).not.toHaveBeenCalled();
55
71
  });
56
72
 
57
- it('returns early without drawing when atlas.image.source is null', () => {
58
- const { state, gl } = createGlState();
59
- drawGlParticleEmitter2D(
60
- state,
61
- makeParticleEmitter2DNode({ atlas: { image: { source: null, data: null, compressed: null }, regions: [] } }),
62
- );
73
+ it('returns early without drawing when atlas Texture is unbound', () => {
74
+ const { state, gl } = createAtlasGlState();
75
+ drawGlParticleEmitter2D(state, makeParticleEmitter2DNode({ atlas: { regions: [], texture: createTexture() } }));
63
76
  expect(gl.drawElements).not.toHaveBeenCalled();
64
77
  });
65
78
 
66
79
  it('returns early without drawing when particleCount is 0', () => {
67
- const { state, gl } = createGlState();
80
+ const { state, gl } = createAtlasGlState();
68
81
  drawGlParticleEmitter2D(state, makeParticleEmitter2DNode({ particleCount: 0 }));
69
82
  expect(gl.drawElementsInstanced).not.toHaveBeenCalled();
70
83
  });
71
84
 
72
85
  it('draws all live particles in a single instanced draw call', () => {
73
- const { state, gl } = createGlState();
86
+ const { state, gl } = createAtlasGlState();
74
87
  drawGlParticleEmitter2D(
75
88
  state,
76
89
  makeParticleEmitter2DNode({
@@ -86,7 +99,7 @@ describe('drawGlParticleEmitter2D', () => {
86
99
  });
87
100
 
88
101
  it('skips out-of-range region ids and draws only valid particles', () => {
89
- const { state, gl } = createGlState();
102
+ const { state, gl } = createAtlasGlState();
90
103
  drawGlParticleEmitter2D(
91
104
  state,
92
105
  makeParticleEmitter2DNode({
@@ -101,21 +114,24 @@ describe('drawGlParticleEmitter2D', () => {
101
114
  });
102
115
 
103
116
  it('uploads the straight-alpha flag for a compressed particle atlas', () => {
104
- const { state, gl } = createGlState();
117
+ const { state, gl } = createAtlasGlState();
118
+ registerGlCompressedImageTextureResolver(state);
119
+ getGlRenderStateRuntime(state).compressedTextureUpload = () => true;
105
120
  const image = {
106
- alphaType: 'straight',
107
121
  compressed: { container: {}, payload: new Uint8Array() },
108
- data: null,
109
122
  height: 4,
110
- source: null,
123
+ kind: CompressedImageTextureSourceKind,
111
124
  version: 1,
112
125
  width: 4,
113
- };
126
+ } as unknown as CompressedImage;
114
127
 
115
128
  drawGlParticleEmitter2D(
116
129
  state,
117
130
  makeParticleEmitter2DNode({
118
- atlas: { image, regions: [{ id: 0, x: 0, y: 0, width: 4, height: 4 }] },
131
+ atlas: {
132
+ regions: [{ id: 0, x: 0, y: 0, width: 4, height: 4 }],
133
+ texture: createTexture({ dimension: '2d', source: image }),
134
+ },
119
135
  }),
120
136
  );
121
137
 
@@ -1,18 +1,27 @@
1
1
  import type { RenderProxy2D } from '@flighthq/types/contract';
2
2
 
3
3
  import { defaultGlQuadBatchRenderer } from './glQuadBatch';
4
- import { flushGlSpriteBatch } from './glSpriteBatch';
5
- import { registerStandardGlMaterial } from './glStandardMaterial';
4
+ import { flushGlQuadBatchWriter } from './glQuadBatchWriter';
5
+ import { registerGlStandardMaterial } from './glStandardMaterial';
6
6
  import { createGlState } from './glTestHelper';
7
7
 
8
8
  function makeAtlas() {
9
9
  const img = document.createElement('img');
10
+ const image = createImageResource(img);
11
+ image.width = 64;
12
+ image.height = 64;
10
13
  return {
11
- image: { source: img, width: 64, height: 64 },
12
14
  regions: [{ x: 0, y: 0, width: 32, height: 32 }],
15
+ texture: createTexture({ dimension: '2d', source: image }),
13
16
  };
14
17
  }
15
18
 
19
+ function createAtlasGlState() {
20
+ const result = createGlState();
21
+ registerGlImageTextureResolver(result.state);
22
+ return result;
23
+ }
24
+
16
25
  function makeQuadBatchNode(data: Record<string, unknown> = {}): RenderProxy2D {
17
26
  return {
18
27
  source: {
@@ -47,40 +56,40 @@ describe('defaultGlQuadBatchRenderer', () => {
47
56
 
48
57
  describe('defaultGlQuadBatchRenderer.submit', () => {
49
58
  it('returns early without drawing when atlas is null', () => {
50
- const { state, gl } = createGlState();
51
- registerStandardGlMaterial(state);
59
+ const { state, gl } = createAtlasGlState();
60
+ registerGlStandardMaterial(state);
52
61
  defaultGlQuadBatchRenderer.submit(state, makeQuadBatchNode({ atlas: null }));
53
- flushGlSpriteBatch(state as any);
62
+ flushGlQuadBatchWriter(state as any);
54
63
  expect(gl.drawElementsInstanced).not.toHaveBeenCalled();
55
64
  });
56
65
 
57
- it('returns early without drawing when atlas.image is null', () => {
58
- const { state, gl } = createGlState();
59
- registerStandardGlMaterial(state);
60
- defaultGlQuadBatchRenderer.submit(state, makeQuadBatchNode({ atlas: { image: null, regions: [] } }));
61
- flushGlSpriteBatch(state as any);
66
+ it('returns early without drawing when atlas.texture is null', () => {
67
+ const { state, gl } = createAtlasGlState();
68
+ registerGlStandardMaterial(state);
69
+ defaultGlQuadBatchRenderer.submit(state, makeQuadBatchNode({ atlas: { regions: [], texture: null } }));
70
+ flushGlQuadBatchWriter(state as any);
62
71
  expect(gl.drawElementsInstanced).not.toHaveBeenCalled();
63
72
  });
64
73
 
65
- it('returns early without drawing when atlas.image.source is null', () => {
66
- const { state, gl } = createGlState();
67
- registerStandardGlMaterial(state);
68
- defaultGlQuadBatchRenderer.submit(state, makeQuadBatchNode({ atlas: { image: { source: null }, regions: [] } }));
69
- flushGlSpriteBatch(state as any);
74
+ it('returns early without drawing when atlas Texture is unbound', () => {
75
+ const { state, gl } = createAtlasGlState();
76
+ registerGlStandardMaterial(state);
77
+ defaultGlQuadBatchRenderer.submit(state, makeQuadBatchNode({ atlas: { regions: [], texture: createTexture() } }));
78
+ flushGlQuadBatchWriter(state as any);
70
79
  expect(gl.drawElementsInstanced).not.toHaveBeenCalled();
71
80
  });
72
81
 
73
82
  it('returns early without drawing when instanceCount is 0', () => {
74
- const { state, gl } = createGlState();
75
- registerStandardGlMaterial(state);
83
+ const { state, gl } = createAtlasGlState();
84
+ registerGlStandardMaterial(state);
76
85
  defaultGlQuadBatchRenderer.submit(state, makeQuadBatchNode({ instanceCount: 0 }));
77
- flushGlSpriteBatch(state as any);
86
+ flushGlQuadBatchWriter(state as any);
78
87
  expect(gl.drawElementsInstanced).not.toHaveBeenCalled();
79
88
  });
80
89
 
81
90
  it('draws all valid instances in a single instanced call with vector2 transform type', () => {
82
- const { state, gl } = createGlState();
83
- registerStandardGlMaterial(state);
91
+ const { state, gl } = createAtlasGlState();
92
+ registerGlStandardMaterial(state);
84
93
  defaultGlQuadBatchRenderer.submit(
85
94
  state,
86
95
  makeQuadBatchNode({
@@ -90,14 +99,14 @@ describe('defaultGlQuadBatchRenderer.submit', () => {
90
99
  transformType: 'vector2',
91
100
  }),
92
101
  );
93
- flushGlSpriteBatch(state as any);
102
+ flushGlQuadBatchWriter(state as any);
94
103
  expect(gl.drawElementsInstanced).toHaveBeenCalledTimes(1);
95
104
  expect(gl.drawElementsInstanced).toHaveBeenCalledWith(expect.anything(), 6, expect.anything(), 0, 2);
96
105
  });
97
106
 
98
107
  it('excludes out-of-range ids from the instanced draw count', () => {
99
- const { state, gl } = createGlState();
100
- registerStandardGlMaterial(state);
108
+ const { state, gl } = createAtlasGlState();
109
+ registerGlStandardMaterial(state);
101
110
  defaultGlQuadBatchRenderer.submit(
102
111
  state,
103
112
  makeQuadBatchNode({
@@ -107,14 +116,14 @@ describe('defaultGlQuadBatchRenderer.submit', () => {
107
116
  transformType: 'vector2',
108
117
  }),
109
118
  );
110
- flushGlSpriteBatch(state as any);
119
+ flushGlQuadBatchWriter(state as any);
111
120
  expect(gl.drawElementsInstanced).toHaveBeenCalledTimes(1);
112
121
  expect(gl.drawElementsInstanced).toHaveBeenCalledWith(expect.anything(), 6, expect.anything(), 0, 2);
113
122
  });
114
123
 
115
124
  it('draws all valid instances in a single instanced call with full matrix transform type', () => {
116
- const { state, gl } = createGlState();
117
- registerStandardGlMaterial(state);
125
+ const { state, gl } = createAtlasGlState();
126
+ registerGlStandardMaterial(state);
118
127
  const transforms = new Float32Array([1, 0, 0, 1, 0, 0]);
119
128
  defaultGlQuadBatchRenderer.submit(
120
129
  state,
@@ -125,8 +134,11 @@ describe('defaultGlQuadBatchRenderer.submit', () => {
125
134
  transformType: 'matrix',
126
135
  }),
127
136
  );
128
- flushGlSpriteBatch(state as any);
137
+ flushGlQuadBatchWriter(state as any);
129
138
  expect(gl.drawElementsInstanced).toHaveBeenCalledTimes(1);
130
139
  expect(gl.drawElementsInstanced).toHaveBeenCalledWith(expect.anything(), 6, expect.anything(), 0, 1);
131
140
  });
132
141
  });
142
+ import { createImageResource } from '@flighthq/image/contract';
143
+ import { registerGlImageTextureResolver } from '@flighthq/render-gl/contract';
144
+ import { createTexture } from '@flighthq/texture/contract';