@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
@@ -0,0 +1,74 @@
1
+ import { getGlRenderStateRuntime } from '@flighthq/render-gl/contract';
2
+ import type { GlShapeRendererData } from '@flighthq/types/contract';
3
+
4
+ import {
5
+ acquireGlShapeRasterSurface,
6
+ createGlShapeData,
7
+ destroyGlShapeData,
8
+ getGlShapeData,
9
+ toGlShapeRendererData,
10
+ } from './glShapeData';
11
+ import { createGlState } from './glTestHelper';
12
+
13
+ function emptyData(): GlShapeRendererData {
14
+ return { surface: null, lastContentId: -1, lastPixelRatio: 0, lastW: 0, lastH: 0, meshVersion: -1, meshes: null };
15
+ }
16
+
17
+ describe('acquireGlShapeRasterSurface', () => {
18
+ it('allocates once and returns the same surface thereafter', () => {
19
+ const data = emptyData();
20
+ const first = acquireGlShapeRasterSurface(data);
21
+ expect(data.surface).toBe(first);
22
+ expect(acquireGlShapeRasterSurface(data)).toBe(first);
23
+ });
24
+
25
+ it('wraps the canvas as an Image so the quad batch treats it like any other texture source', () => {
26
+ const surface = acquireGlShapeRasterSurface(emptyData());
27
+ expect(surface.image.source).toBe(surface.canvas);
28
+ });
29
+ });
30
+
31
+ describe('createGlShapeData', () => {
32
+ it('starts with neither cache populated, so a shape allocates nothing until a strategy needs it', () => {
33
+ const { state } = createGlState();
34
+ const data = getGlShapeData(createGlShapeData(state, {} as never)!);
35
+ expect(data.surface).toBeNull();
36
+ expect(data.meshes).toBeNull();
37
+ expect(data.meshVersion).toBe(-1);
38
+ });
39
+ });
40
+
41
+ describe('destroyGlShapeData', () => {
42
+ it('does nothing when the shape only ever tessellated, since there is no surface to free', () => {
43
+ const { state, gl } = createGlState();
44
+ destroyGlShapeData(state, toGlShapeRendererData(emptyData()));
45
+ expect(gl.deleteTexture).not.toHaveBeenCalled();
46
+ });
47
+
48
+ it('frees the cached GPU texture keyed on the raster surface', () => {
49
+ const { state, gl } = createGlState();
50
+ const data = emptyData();
51
+ const surface = acquireGlShapeRasterSurface(data);
52
+ const texture = {} as WebGLTexture;
53
+ getGlRenderStateRuntime(state).textureSourcePremultipliedTextureCache.set(surface.image, { texture } as never);
54
+
55
+ destroyGlShapeData(state, toGlShapeRendererData(data));
56
+
57
+ expect(gl.deleteTexture).toHaveBeenCalledWith(texture);
58
+ expect(getGlRenderStateRuntime(state).textureSourcePremultipliedTextureCache.has(surface.image)).toBe(false);
59
+ });
60
+ });
61
+
62
+ describe('getGlShapeData', () => {
63
+ it('round-trips through the opaque RendererData slot', () => {
64
+ const data = emptyData();
65
+ expect(getGlShapeData(toGlShapeRendererData(data))).toBe(data);
66
+ });
67
+ });
68
+
69
+ describe('toGlShapeRendererData', () => {
70
+ it('is the inverse of getGlShapeData', () => {
71
+ const data = emptyData();
72
+ expect(toGlShapeRendererData(getGlShapeData(toGlShapeRendererData(data)))).toBe(toGlShapeRendererData(data));
73
+ });
74
+ });
@@ -127,7 +127,7 @@ describe('drawGlShapeMeshes', () => {
127
127
  const shader = getGlRenderStateRuntime(state).shapeMeshColorScaleBiasShader!;
128
128
  expect(shader).toBeDefined();
129
129
  // Scale and bias are uploaded verbatim — identical to glColorAdjustmentMaterialFeature's
130
- // bindGlSpriteBatchUniformColorScaleBias.
130
+ // bindGlQuadBatchWriterUniformColorScaleBias.
131
131
  expect(gl.uniform4f).toHaveBeenCalledWith(shader.colorScaleLocation, 0.5, 0.5, 0.5, 1);
132
132
  expect(gl.uniform4f).toHaveBeenCalledWith(shader.colorBiasLocation, 0.5, 0, 0, 0);
133
133
  // Flat mesh color still uploaded premultiplied (white, alpha 1); the shader un/re-premultiplies.
@@ -0,0 +1,32 @@
1
+ import { createRenderState, enableRenderRegistryGuards, explainRenderRegistryMisses } from '@flighthq/render/contract';
2
+ import { RenderRegistry } from '@flighthq/types/contract';
3
+
4
+ import { getGlShapeRasterizer, registerGlShapeRasterizer } from './glShapeRasterizer';
5
+ import { createGlState } from './glTestHelper';
6
+
7
+ describe('getGlShapeRasterizer', () => {
8
+ it('reports none until one is registered', () => {
9
+ const state = createGlState().state;
10
+ expect(getGlShapeRasterizer(state)).toBeNull();
11
+ });
12
+ });
13
+
14
+ describe('registerGlShapeRasterizer', () => {
15
+ it('installs the rasterizer the shape path draws non-solid fills through', () => {
16
+ const state = createGlState().state;
17
+ const rasterizer = (): void => {};
18
+
19
+ registerGlShapeRasterizer(state, rasterizer);
20
+
21
+ expect(getGlShapeRasterizer(state)).toBe(rasterizer);
22
+ });
23
+
24
+ it('removes one again, so a state can drop back to tessellation only', () => {
25
+ const state = createGlState().state;
26
+ registerGlShapeRasterizer(state, (): void => {});
27
+
28
+ registerGlShapeRasterizer(state, null);
29
+
30
+ expect(getGlShapeRasterizer(state)).toBeNull();
31
+ });
32
+ });
@@ -1,79 +1,88 @@
1
- import { addNodeChild } from '@flighthq/node/contract';
2
- import { getOrCreateRenderProxy2D, prepareScene2DRender, registerRenderer } from '@flighthq/render/contract';
3
- import { createSprite } from '@flighthq/sprite/contract';
4
- import { SpriteKind } from '@flighthq/types/contract';
5
-
6
- import { renderGlSprite } from './glSprite';
1
+ import {
2
+ getGlRenderStateRuntime,
3
+ registerGlBitmapTextureResolver,
4
+ registerGlRenderTextureResolver,
5
+ renderIntoGlRenderTexture,
6
+ } from '@flighthq/render-gl/contract';
7
+ import { createRenderTexture, createTexture, setTextureUvFromPixelRect } from '@flighthq/texture/contract';
8
+ import type { Bitmap, RenderProxy2D, Sprite } from '@flighthq/types/contract';
9
+ import { BatchFormat, BitmapTextureSourceKind } from '@flighthq/types/contract';
10
+
11
+ import { flushGlQuadBatchWriter } from './glQuadBatchWriter';
12
+ import { defaultGlSpriteRenderer, drawGlSprite } from './glSprite';
13
+ import { registerGlStandardMaterial } from './glStandardMaterial';
7
14
  import { createGlState } from './glTestHelper';
8
15
 
9
- function makeRenderer() {
10
- return { createData: () => null, submit: vi.fn() } as any;
16
+ function makeSprite(width = 64, height = 48): Sprite {
17
+ const image = {
18
+ alphaType: 'straight',
19
+ colorSpace: 'srgb',
20
+ data: new Uint8ClampedArray(width * height * 4),
21
+ format: 'rgba8unorm',
22
+ height,
23
+ kind: BitmapTextureSourceKind,
24
+ version: 1,
25
+ width,
26
+ } as unknown as Bitmap;
27
+ return {
28
+ data: { texture: createTexture({ dimension: '2d', source: image }) },
29
+ } as Sprite;
11
30
  }
12
31
 
13
- describe('renderGlSprite', () => {
14
- it('does not throw for an empty sprite node', () => {
15
- const { state } = createGlState();
16
- const sprite = createSprite();
17
- expect(() => {
18
- prepareScene2DRender(state, sprite);
19
- renderGlSprite(state, sprite);
20
- }).not.toThrow();
21
- });
22
-
23
- it('calls renderer.submit for a visible sprite with a registered renderer', () => {
24
- const { state } = createGlState();
25
- const renderer = makeRenderer();
26
- registerRenderer(state, SpriteKind, renderer);
27
-
28
- const sprite = createSprite();
29
- const data = getOrCreateRenderProxy2D(state, sprite);
30
-
31
- prepareScene2DRender(state, sprite);
32
- renderGlSprite(state, sprite);
32
+ function makeRenderProxy(sprite: Sprite): RenderProxy2D {
33
+ return {
34
+ alpha: 1,
35
+ blendMode: 0,
36
+ colorMatrix: null,
37
+ colorScaleBias: null,
38
+ material: null,
39
+ materialData: null,
40
+ source: sprite,
41
+ transform2D: { a: 1, b: 0, c: 0, d: 1, tx: 10, ty: 20 },
42
+ } as unknown as RenderProxy2D;
43
+ }
33
44
 
34
- expect(renderer.submit).toHaveBeenCalledWith(state, data);
45
+ describe('defaultGlSpriteRenderer', () => {
46
+ it('declares the quad format and submit function', () => {
47
+ expect(defaultGlSpriteRenderer.format).toBe(BatchFormat.Quad);
48
+ expect(typeof defaultGlSpriteRenderer.isDirty).toBe('function');
49
+ expect(defaultGlSpriteRenderer.submit).toBe(drawGlSprite);
35
50
  });
51
+ });
36
52
 
37
- it('skips a sprite node with visible set to false', () => {
53
+ describe('drawGlSprite', () => {
54
+ it('writes natural size and uv window into the shared batch', () => {
38
55
  const { state } = createGlState();
39
- const renderer = makeRenderer();
40
- registerRenderer(state, SpriteKind, renderer);
41
-
42
- const sprite = createSprite();
43
- sprite.visible = false;
44
-
45
- prepareScene2DRender(state, sprite);
46
- renderGlSprite(state, sprite);
47
-
48
- expect(renderer.submit).not.toHaveBeenCalled();
56
+ registerGlBitmapTextureResolver(state);
57
+ registerGlStandardMaterial(state);
58
+ const sprite = makeSprite(128, 64);
59
+ setTextureUvFromPixelRect(sprite.data.texture!, 16, 8, 32, 16);
60
+ drawGlSprite(state, makeRenderProxy(sprite));
61
+ const data = getGlRenderStateRuntime(state).quadBatchWriterInstanceData;
62
+ expect(Array.from(data.slice(6, 12))).toEqual([32, 16, 0.125, 0.125, 0.375, 0.375]);
49
63
  });
50
64
 
51
- it('skips a sprite node with alpha at or below 0', () => {
65
+ it('uses the physical slab once and reflects a render-target sub-view into GL coordinates', () => {
52
66
  const { state } = createGlState();
53
- const renderer = makeRenderer();
54
- registerRenderer(state, SpriteKind, renderer);
55
-
56
- const sprite = createSprite();
57
- sprite.alpha = 0;
67
+ registerGlRenderTextureResolver(state);
68
+ registerGlStandardMaterial(state);
69
+ const renderTexture = createRenderTexture({ height: 480, width: 720 });
70
+ setTextureUvFromPixelRect(renderTexture, 140, 160, 100, 80);
71
+ renderIntoGlRenderTexture(state, renderTexture, () => {});
72
+ const sprite = { data: { texture: renderTexture } } as Sprite;
58
73
 
59
- prepareScene2DRender(state, sprite);
60
- renderGlSprite(state, sprite);
74
+ drawGlSprite(state, makeRenderProxy(sprite));
61
75
 
62
- expect(renderer.submit).not.toHaveBeenCalled();
76
+ const data = getGlRenderStateRuntime(state).quadBatchWriterInstanceData;
77
+ expect(data.slice(6, 12)).toEqual(new Float32Array([100, 80, 140 / 720, 1 - 160 / 480, 240 / 720, 1 - 240 / 480]));
63
78
  });
64
79
 
65
- it('recurses into children and renders visible ones', () => {
66
- const { state } = createGlState();
67
- const renderer = makeRenderer();
68
- registerRenderer(state, SpriteKind, renderer);
69
-
70
- const parent = createSprite();
71
- const child = createSprite();
72
- addNodeChild(parent, child);
73
-
74
- prepareScene2DRender(state, parent);
75
- renderGlSprite(state, parent);
76
-
77
- expect(renderer.submit).toHaveBeenCalledTimes(2);
80
+ it('resolves and draws the texture on flush', () => {
81
+ const { gl, state } = createGlState();
82
+ registerGlBitmapTextureResolver(state);
83
+ registerGlStandardMaterial(state);
84
+ drawGlSprite(state, makeRenderProxy(makeSprite()));
85
+ flushGlQuadBatchWriter(state);
86
+ expect(gl.drawElementsInstanced).toHaveBeenCalledOnce();
78
87
  });
79
88
  });
@@ -1,13 +1,13 @@
1
1
  import { getGlMaterialRenderer } from '@flighthq/render-gl/contract';
2
2
  import { StandardMaterialKind } from '@flighthq/types/contract';
3
3
 
4
- import { registerStandardGlMaterial, standardGlMaterialRenderer } from './glStandardMaterial';
4
+ import { registerGlStandardMaterial, standardGlMaterialRenderer } from './glStandardMaterial';
5
5
  import { createGlState } from './glTestHelper';
6
6
 
7
- describe('registerStandardGlMaterial', () => {
7
+ describe('registerGlStandardMaterial', () => {
8
8
  it('registers the default renderer under StandardMaterialKind', () => {
9
9
  const { state } = createGlState();
10
- registerStandardGlMaterial(state);
10
+ registerGlStandardMaterial(state);
11
11
  expect(getGlMaterialRenderer(state, StandardMaterialKind)).toBe(standardGlMaterialRenderer);
12
12
  });
13
13
  });
@@ -4,8 +4,8 @@ import { createTextLabel, setTextLabelString } from '@flighthq/text/contract';
4
4
  import type { RenderProxy2D, TextLabel } from '@flighthq/types/contract';
5
5
  import { BatchFormat } from '@flighthq/types/contract';
6
6
 
7
- import { flushGlSpriteBatch } from './glSpriteBatch';
8
- import { registerStandardGlMaterial } from './glStandardMaterial';
7
+ import { flushGlQuadBatchWriter } from './glQuadBatchWriter';
8
+ import { registerGlStandardMaterial } from './glStandardMaterial';
9
9
  import { createGlState } from './glTestHelper';
10
10
  import type * as GlTextLabelModule from './glTextLabel';
11
11
  import { scopeModuleMocks } from './moduleMockTestHelper';
@@ -83,46 +83,46 @@ describe('defaultGlTextLabelRenderer', () => {
83
83
  describe('drawGlTextLabel', () => {
84
84
  it('returns early without writing to batch when text is empty', () => {
85
85
  const { state } = createGlState();
86
- registerStandardGlMaterial(state);
86
+ registerGlStandardMaterial(state);
87
87
  drawGlTextLabel(state, makeTextProxy('', makeTextData()));
88
- expect(getGlRenderStateRuntime(state).spriteBatchCount).toBe(0);
88
+ expect(getGlRenderStateRuntime(state).quadBatchWriterCount).toBe(0);
89
89
  });
90
90
 
91
91
  it('returns early without writing to batch when rendererData is null', () => {
92
92
  const { state } = createGlState();
93
- registerStandardGlMaterial(state);
93
+ registerGlStandardMaterial(state);
94
94
  drawGlTextLabel(state, makeTextProxy('hello', null));
95
- expect(getGlRenderStateRuntime(state).spriteBatchCount).toBe(0);
95
+ expect(getGlRenderStateRuntime(state).quadBatchWriterCount).toBe(0);
96
96
  });
97
97
 
98
98
  it('returns early without writing to batch when no material renderer is registered', () => {
99
99
  const { state } = createGlState();
100
100
  drawGlTextLabel(state, makeTextProxy('hello', makeTextData()));
101
- expect(getGlRenderStateRuntime(state).spriteBatchCount).toBe(0);
101
+ expect(getGlRenderStateRuntime(state).quadBatchWriterCount).toBe(0);
102
102
  });
103
103
 
104
- it('writes one instance to the sprite batch when text has content', () => {
104
+ it('writes one instance to the quad-batch writer when text has content', () => {
105
105
  const { state } = createGlState();
106
- registerStandardGlMaterial(state);
106
+ registerGlStandardMaterial(state);
107
107
  drawGlTextLabel(state, makeTextProxy('hello', makeTextData()));
108
- expect(getGlRenderStateRuntime(state).spriteBatchCount).toBe(1);
108
+ expect(getGlRenderStateRuntime(state).quadBatchWriterCount).toBe(1);
109
109
  });
110
110
 
111
111
  it('draws via drawElementsInstanced after flush', () => {
112
112
  const { state, gl } = createGlState();
113
- registerStandardGlMaterial(state);
113
+ registerGlStandardMaterial(state);
114
114
  drawGlTextLabel(state, makeTextProxy('hello', makeTextData()));
115
- flushGlSpriteBatch(state);
115
+ flushGlQuadBatchWriter(state);
116
116
  expect(gl.drawElementsInstanced).toHaveBeenCalled();
117
117
  });
118
118
 
119
119
  it('skips layout and rasterization on repeated calls when the content version is unchanged', () => {
120
120
  const { state } = createGlState();
121
- registerStandardGlMaterial(state);
121
+ registerGlStandardMaterial(state);
122
122
  const data = makeTextData();
123
123
  const proxy = makeTextProxy('hello', data);
124
124
  drawGlTextLabel(state, proxy);
125
- // Rasterization bumps the canvas resource's version (setImageResourceSource); a skipped raster leaves
125
+ // Rasterization bumps the canvas resource's version (invalidateImageResource); a skipped raster leaves
126
126
  // it untouched. First draw rasterizes (version → 1); the repeat is skipped.
127
127
  const rasterized = data.image.version;
128
128
  drawGlTextLabel(state, proxy);
@@ -131,7 +131,7 @@ describe('drawGlTextLabel', () => {
131
131
 
132
132
  it('re-rasterizes when the content version is bumped', () => {
133
133
  const { state } = createGlState();
134
- registerStandardGlMaterial(state);
134
+ registerGlStandardMaterial(state);
135
135
  const data = makeTextData();
136
136
  const proxy = makeTextProxy('hello', data);
137
137
  drawGlTextLabel(state, proxy);
@@ -143,7 +143,7 @@ describe('drawGlTextLabel', () => {
143
143
 
144
144
  it('does not re-rasterize when only alpha changes (version unchanged)', () => {
145
145
  const { state } = createGlState();
146
- registerStandardGlMaterial(state);
146
+ registerGlStandardMaterial(state);
147
147
  const data = makeTextData();
148
148
  const proxy = makeTextProxy('hello', data);
149
149
  drawGlTextLabel(state, proxy);
@@ -1,33 +1,36 @@
1
1
  import type { RenderProxy2D } from '@flighthq/types/contract';
2
2
 
3
- import { flushGlSpriteBatch } from './glSpriteBatch';
4
- import { registerStandardGlMaterial } from './glStandardMaterial';
3
+ import { flushGlQuadBatchWriter } from './glQuadBatchWriter';
4
+ import { registerGlStandardMaterial } from './glStandardMaterial';
5
5
  import { createGlState } from './glTestHelper';
6
6
  import { defaultGlTilemapRenderer } from './glTilemap';
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: 16, height: 16 }],
15
+ texture: createTexture({ dimension: '2d', source: image }),
13
16
  };
14
17
  }
15
18
 
16
- function makeTileset(atlasOverride?: unknown) {
17
- return {
18
- atlas: atlasOverride !== undefined ? atlasOverride : makeAtlas(),
19
- tileWidth: 16,
20
- tileHeight: 16,
21
- };
19
+ function createAtlasGlState() {
20
+ const result = createGlState();
21
+ registerGlImageTextureResolver(result.state);
22
+ return result;
22
23
  }
23
24
 
24
25
  function makeTilemapNode(data: Record<string, unknown> = {}): RenderProxy2D {
25
26
  return {
26
27
  source: {
27
28
  data: {
28
- tileset: makeTileset(),
29
+ atlas: makeAtlas(),
29
30
  columns: 2,
30
31
  rows: 2,
32
+ tileHeight: 16,
33
+ tileWidth: 16,
31
34
  tiles: [0, 0, 0, 0],
32
35
  ...data,
33
36
  },
@@ -53,75 +56,69 @@ describe('defaultGlTilemapRenderer', () => {
53
56
  });
54
57
 
55
58
  describe('defaultGlTilemapRenderer.submit', () => {
56
- it('returns early without drawing when tileset is null', () => {
57
- const { state, gl } = createGlState();
58
- registerStandardGlMaterial(state);
59
- defaultGlTilemapRenderer.submit(state, makeTilemapNode({ tileset: null }));
60
- flushGlSpriteBatch(state);
61
- expect(gl.drawElementsInstanced).not.toHaveBeenCalled();
62
- });
63
-
64
59
  it('returns early without drawing when atlas is null', () => {
65
- const { state, gl } = createGlState();
66
- registerStandardGlMaterial(state);
67
- defaultGlTilemapRenderer.submit(state, makeTilemapNode({ tileset: makeTileset(null) }));
68
- flushGlSpriteBatch(state);
60
+ const { state, gl } = createAtlasGlState();
61
+ registerGlStandardMaterial(state);
62
+ defaultGlTilemapRenderer.submit(state, makeTilemapNode({ atlas: null }));
63
+ flushGlQuadBatchWriter(state);
69
64
  expect(gl.drawElementsInstanced).not.toHaveBeenCalled();
70
65
  });
71
66
 
72
- it('returns early without drawing when atlas.image is null', () => {
73
- const { state, gl } = createGlState();
74
- registerStandardGlMaterial(state);
75
- defaultGlTilemapRenderer.submit(
76
- state,
77
- makeTilemapNode({ tileset: { atlas: { image: null, regions: [] }, tileWidth: 16, tileHeight: 16 } }),
78
- );
79
- flushGlSpriteBatch(state);
67
+ it('returns early without drawing when atlas.texture is null', () => {
68
+ const { state, gl } = createAtlasGlState();
69
+ registerGlStandardMaterial(state);
70
+ defaultGlTilemapRenderer.submit(state, makeTilemapNode({ atlas: { regions: [], texture: null } }));
71
+ flushGlQuadBatchWriter(state);
80
72
  expect(gl.drawElementsInstanced).not.toHaveBeenCalled();
81
73
  });
82
74
 
83
- it('returns early without drawing when atlas.image.source is null', () => {
84
- const { state, gl } = createGlState();
85
- registerStandardGlMaterial(state);
75
+ it('returns early without drawing when atlas Texture is unbound', () => {
76
+ const { state, gl } = createAtlasGlState();
77
+ registerGlStandardMaterial(state);
86
78
  defaultGlTilemapRenderer.submit(
87
79
  state,
88
- makeTilemapNode({ tileset: { atlas: { image: { source: null }, regions: [] }, tileWidth: 16, tileHeight: 16 } }),
80
+ makeTilemapNode({
81
+ atlas: { regions: [], texture: createTexture() },
82
+ }),
89
83
  );
90
- flushGlSpriteBatch(state);
84
+ flushGlQuadBatchWriter(state);
91
85
  expect(gl.drawElementsInstanced).not.toHaveBeenCalled();
92
86
  });
93
87
 
94
88
  it('returns early without drawing when columns is 0', () => {
95
- const { state, gl } = createGlState();
96
- registerStandardGlMaterial(state);
89
+ const { state, gl } = createAtlasGlState();
90
+ registerGlStandardMaterial(state);
97
91
  defaultGlTilemapRenderer.submit(state, makeTilemapNode({ columns: 0 }));
98
- flushGlSpriteBatch(state);
92
+ flushGlQuadBatchWriter(state);
99
93
  expect(gl.drawElementsInstanced).not.toHaveBeenCalled();
100
94
  });
101
95
 
102
96
  it('returns early without drawing when rows is 0', () => {
103
- const { state, gl } = createGlState();
104
- registerStandardGlMaterial(state);
97
+ const { state, gl } = createAtlasGlState();
98
+ registerGlStandardMaterial(state);
105
99
  defaultGlTilemapRenderer.submit(state, makeTilemapNode({ rows: 0 }));
106
- flushGlSpriteBatch(state);
100
+ flushGlQuadBatchWriter(state);
107
101
  expect(gl.drawElementsInstanced).not.toHaveBeenCalled();
108
102
  });
109
103
 
110
104
  it('draws all valid tiles in a single instanced call', () => {
111
- const { state, gl } = createGlState();
112
- registerStandardGlMaterial(state);
105
+ const { state, gl } = createAtlasGlState();
106
+ registerGlStandardMaterial(state);
113
107
  defaultGlTilemapRenderer.submit(state, makeTilemapNode());
114
- flushGlSpriteBatch(state);
108
+ flushGlQuadBatchWriter(state);
115
109
  expect(gl.drawElementsInstanced).toHaveBeenCalledTimes(1);
116
110
  expect(gl.drawElementsInstanced).toHaveBeenCalledWith(expect.anything(), 6, expect.anything(), 0, 4);
117
111
  });
118
112
 
119
113
  it('excludes out-of-range tile ids from the instanced draw count', () => {
120
- const { state, gl } = createGlState();
121
- registerStandardGlMaterial(state);
114
+ const { state, gl } = createAtlasGlState();
115
+ registerGlStandardMaterial(state);
122
116
  defaultGlTilemapRenderer.submit(state, makeTilemapNode({ tiles: [0, 99, 99, 0] }));
123
- flushGlSpriteBatch(state);
117
+ flushGlQuadBatchWriter(state);
124
118
  expect(gl.drawElementsInstanced).toHaveBeenCalledTimes(1);
125
119
  expect(gl.drawElementsInstanced).toHaveBeenCalledWith(expect.anything(), 6, expect.anything(), 0, 2);
126
120
  });
127
121
  });
122
+ import { createImageResource } from '@flighthq/image/contract';
123
+ import { registerGlImageTextureResolver } from '@flighthq/render-gl/contract';
124
+ import { createTexture } from '@flighthq/texture/contract';
@@ -1,6 +1,6 @@
1
1
  import { createParticleEmitter2D, reserveParticleEmitter2D } from '@flighthq/particleemitter/contract';
2
+ import { createQuadBatch, getQuadBatchRuntime } from '@flighthq/quadbatch/contract';
2
3
  import { createDisplayObject } from '@flighthq/scene2d/contract';
3
- import { createQuadBatch, getQuadBatchRuntime } from '@flighthq/sprite/contract';
4
4
  import type { QuadBatchRuntime, TextureAtlas, TextureAtlasRegion } from '@flighthq/types/contract';
5
5
  import { QuadBatchKind } from '@flighthq/types/contract';
6
6
  import { beginVelocityFrame, contributeVelocity, createVelocityField } from '@flighthq/velocity/contract';
@@ -42,7 +42,7 @@ describe('defaultGlParticleEmitter2DVelocityWriter', () => {
42
42
  const { state } = createGlState();
43
43
  const target = createGlVelocityTarget(state, 128, 64);
44
44
  const region = { id: 0, x: 0, y: 0, width: 16, height: 16, pivotX: null, pivotY: null } as TextureAtlasRegion;
45
- const atlas = { image: null, regions: [region] } as TextureAtlas;
45
+ const atlas = { texture: null, regions: [region] } as TextureAtlas;
46
46
  const emitter = createParticleEmitter2D();
47
47
  reserveParticleEmitter2D(emitter, 2);
48
48
  emitter.data.atlas = atlas;
@@ -69,7 +69,7 @@ describe('defaultGlQuadBatchVelocityWriter', () => {
69
69
  const { state } = createGlState();
70
70
  const target = createGlVelocityTarget(state, 128, 64);
71
71
  const region = { id: 0, x: 0, y: 0, width: 32, height: 32, pivotX: null, pivotY: null } as TextureAtlasRegion;
72
- const atlas = { image: null, regions: [region] } as TextureAtlas;
72
+ const atlas = { texture: null, regions: [region] } as TextureAtlas;
73
73
  // Two vector2 instances at distinct positions, each moving independently.
74
74
  const batch = createQuadBatch({
75
75
  data: {
@@ -94,7 +94,7 @@ describe('defaultGlQuadBatchVelocityWriter', () => {
94
94
  const { state } = createGlState();
95
95
  const target = createGlVelocityTarget(state, 128, 64);
96
96
  const region = { id: 0, x: 0, y: 0, width: 32, height: 32, pivotX: null, pivotY: null } as TextureAtlasRegion;
97
- const atlas = { image: null, regions: [region] } as TextureAtlas;
97
+ const atlas = { texture: null, regions: [region] } as TextureAtlas;
98
98
  const batch = createQuadBatch({
99
99
  data: { atlas, ids: new Uint16Array([0]), instanceCount: 1, transforms: new Float32Array([0, 0]) },
100
100
  });
@@ -1,4 +0,0 @@
1
- import type { Scene2DRenderer, GlRenderState, RenderProxy2D } from '@flighthq/types/contract';
2
- export declare function drawGlBitmap(state: GlRenderState, renderProxy: RenderProxy2D): void;
3
- export declare const defaultGlBitmapRenderer: Scene2DRenderer;
4
- //# sourceMappingURL=glBitmap.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"glBitmap.d.ts","sourceRoot":"","sources":["../src/glBitmap.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAEV,eAAe,EACf,aAAa,EAIb,aAAa,EACd,MAAM,0BAA0B,CAAC;AAgClC,wBAAgB,YAAY,CAAC,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,aAAa,GAAG,IAAI,CAwEnF;AAED,eAAO,MAAM,uBAAuB,EAAE,eAKrC,CAAC"}