@flighthq/scene2d-gl 0.2.1-next.840.857425c
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/enableGlColorAdjustmentGuards.d.ts +4 -0
- package/dist/enableGlColorAdjustmentGuards.d.ts.map +1 -0
- package/dist/enableGlColorAdjustmentGuards.js +21 -0
- package/dist/enableGlColorAdjustmentGuards.js.map +1 -0
- package/dist/glBitmap.d.ts +4 -0
- package/dist/glBitmap.d.ts.map +1 -0
- package/dist/glBitmap.js +93 -0
- package/dist/glBitmap.js.map +1 -0
- package/dist/glCache.d.ts +30 -0
- package/dist/glCache.d.ts.map +1 -0
- package/dist/glCache.js +196 -0
- package/dist/glCache.js.map +1 -0
- package/dist/glClip.d.ts +3 -0
- package/dist/glClip.d.ts.map +1 -0
- package/dist/glClip.js +46 -0
- package/dist/glClip.js.map +1 -0
- package/dist/glClipContours.d.ts +4 -0
- package/dist/glClipContours.d.ts.map +1 -0
- package/dist/glClipContours.js +148 -0
- package/dist/glClipContours.js.map +1 -0
- package/dist/glClipRectangle.d.ts +4 -0
- package/dist/glClipRectangle.d.ts.map +1 -0
- package/dist/glClipRectangle.js +68 -0
- package/dist/glClipRectangle.js.map +1 -0
- package/dist/glColorAdjustment.d.ts +3 -0
- package/dist/glColorAdjustment.d.ts.map +1 -0
- package/dist/glColorAdjustment.js +361 -0
- package/dist/glColorAdjustment.js.map +1 -0
- package/dist/glDefaultMaterial.d.ts +4 -0
- package/dist/glDefaultMaterial.d.ts.map +1 -0
- package/dist/glDefaultMaterial.js +20 -0
- package/dist/glDefaultMaterial.js.map +1 -0
- package/dist/glNode2D.d.ts +5 -0
- package/dist/glNode2D.d.ts.map +1 -0
- package/dist/glNode2D.js +41 -0
- package/dist/glNode2D.js.map +1 -0
- package/dist/glNode2DRegistration.d.ts +25 -0
- package/dist/glNode2DRegistration.d.ts.map +1 -0
- package/dist/glNode2DRegistration.js +51 -0
- package/dist/glNode2DRegistration.js.map +1 -0
- package/dist/glParticleEmitter2D.d.ts +4 -0
- package/dist/glParticleEmitter2D.d.ts.map +1 -0
- package/dist/glParticleEmitter2D.js +263 -0
- package/dist/glParticleEmitter2D.js.map +1 -0
- package/dist/glQuadBatch.d.ts +3 -0
- package/dist/glQuadBatch.d.ts.map +1 -0
- package/dist/glQuadBatch.js +92 -0
- package/dist/glQuadBatch.js.map +1 -0
- package/dist/glRichText.d.ts +8 -0
- package/dist/glRichText.d.ts.map +1 -0
- package/dist/glRichText.js +161 -0
- package/dist/glRichText.js.map +1 -0
- package/dist/glScale9Mapper.d.ts +3 -0
- package/dist/glScale9Mapper.d.ts.map +1 -0
- package/dist/glScale9Mapper.js +37 -0
- package/dist/glScale9Mapper.js.map +1 -0
- package/dist/glScale9Shape.d.ts +9 -0
- package/dist/glScale9Shape.d.ts.map +1 -0
- package/dist/glScale9Shape.js +118 -0
- package/dist/glScale9Shape.js.map +1 -0
- package/dist/glShape.d.ts +4 -0
- package/dist/glShape.d.ts.map +1 -0
- package/dist/glShape.js +146 -0
- package/dist/glShape.js.map +1 -0
- package/dist/glShapeMesh.d.ts +5 -0
- package/dist/glShapeMesh.d.ts.map +1 -0
- package/dist/glShapeMesh.js +114 -0
- package/dist/glShapeMesh.js.map +1 -0
- package/dist/glSprite.d.ts +3 -0
- package/dist/glSprite.d.ts.map +1 -0
- package/dist/glSprite.js +28 -0
- package/dist/glSprite.js.map +1 -0
- package/dist/glSpriteBatch.d.ts +11 -0
- package/dist/glSpriteBatch.d.ts.map +1 -0
- package/dist/glSpriteBatch.js +277 -0
- package/dist/glSpriteBatch.js.map +1 -0
- package/dist/glSpriteRenderer.d.ts +3 -0
- package/dist/glSpriteRenderer.d.ts.map +1 -0
- package/dist/glSpriteRenderer.js +54 -0
- package/dist/glSpriteRenderer.js.map +1 -0
- package/dist/glTestHelper.d.ts +9 -0
- package/dist/glTestHelper.d.ts.map +1 -0
- package/dist/glTestHelper.js +17 -0
- package/dist/glTestHelper.js.map +1 -0
- package/dist/glTextInput.d.ts +4 -0
- package/dist/glTextInput.d.ts.map +1 -0
- package/dist/glTextInput.js +43 -0
- package/dist/glTextInput.js.map +1 -0
- package/dist/glTextLabel.d.ts +4 -0
- package/dist/glTextLabel.d.ts.map +1 -0
- package/dist/glTextLabel.js +141 -0
- package/dist/glTextLabel.js.map +1 -0
- package/dist/glTilemap.d.ts +3 -0
- package/dist/glTilemap.d.ts.map +1 -0
- package/dist/glTilemap.js +79 -0
- package/dist/glTilemap.js.map +1 -0
- package/dist/glVelocity.d.ts +22 -0
- package/dist/glVelocity.d.ts.map +1 -0
- package/dist/glVelocity.js +319 -0
- package/dist/glVelocity.js.map +1 -0
- package/dist/glVideo.d.ts +6 -0
- package/dist/glVideo.d.ts.map +1 -0
- package/dist/glVideo.js +62 -0
- package/dist/glVideo.js.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/dist/moduleMockTestHelper.d.ts +15 -0
- package/dist/moduleMockTestHelper.d.ts.map +1 -0
- package/dist/moduleMockTestHelper.js +25 -0
- package/dist/moduleMockTestHelper.js.map +1 -0
- package/package.json +60 -0
- package/src/enableGlColorAdjustmentGuards.test.ts +76 -0
- package/src/glBitmap.test.ts +136 -0
- package/src/glCache.test.ts +234 -0
- package/src/glClip.test.ts +101 -0
- package/src/glClipContours.test.ts +61 -0
- package/src/glClipRectangle.test.ts +47 -0
- package/src/glColorAdjustment.test.ts +166 -0
- package/src/glDefaultMaterial.test.ts +19 -0
- package/src/glNode2D.test.ts +85 -0
- package/src/glNode2DRegistration.test.ts +132 -0
- package/src/glParticleEmitter2D.test.ts +122 -0
- package/src/glQuadBatch.test.ts +132 -0
- package/src/glRichText.test.ts +140 -0
- package/src/glScale9Mapper.test.ts +20 -0
- package/src/glScale9Shape.test.ts +90 -0
- package/src/glShape.test.ts +116 -0
- package/src/glShapeMesh.test.ts +164 -0
- package/src/glSprite.test.ts +79 -0
- package/src/glSpriteBatch.test.ts +249 -0
- package/src/glSpriteRenderer.test.ts +89 -0
- package/src/glTextInput.test.ts +51 -0
- package/src/glTextLabel.test.ts +156 -0
- package/src/glTilemap.test.ts +127 -0
- package/src/glVelocity.test.ts +156 -0
- package/src/glVideo.test.ts +98 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import type { RenderProxy2D } from '@flighthq/types';
|
|
2
|
+
|
|
3
|
+
import { registerDefaultGlMaterial } from './glDefaultMaterial';
|
|
4
|
+
import { flushGlSpriteBatch } from './glSpriteBatch';
|
|
5
|
+
import { createGlState } from './glTestHelper';
|
|
6
|
+
import { defaultGlTilemapRenderer } from './glTilemap';
|
|
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: 16, height: 16 }],
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function makeTileset(atlasOverride?: unknown) {
|
|
17
|
+
return {
|
|
18
|
+
atlas: atlasOverride !== undefined ? atlasOverride : makeAtlas(),
|
|
19
|
+
tileWidth: 16,
|
|
20
|
+
tileHeight: 16,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function makeTilemapNode(data: Record<string, unknown> = {}): RenderProxy2D {
|
|
25
|
+
return {
|
|
26
|
+
source: {
|
|
27
|
+
data: {
|
|
28
|
+
tileset: makeTileset(),
|
|
29
|
+
columns: 2,
|
|
30
|
+
rows: 2,
|
|
31
|
+
tiles: [0, 0, 0, 0],
|
|
32
|
+
...data,
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
blendMode: 0,
|
|
36
|
+
alpha: 1,
|
|
37
|
+
material: null,
|
|
38
|
+
materialData: null,
|
|
39
|
+
renderer: null,
|
|
40
|
+
traverseChildren: false,
|
|
41
|
+
transform2D: { a: 1, b: 0, c: 0, d: 1, tx: 0, ty: 0 },
|
|
42
|
+
} as unknown as RenderProxy2D;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
describe('defaultGlTilemapRenderer', () => {
|
|
46
|
+
it('has a createData function', () => {
|
|
47
|
+
expect(typeof defaultGlTilemapRenderer.createData).toBe('function');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('has a submit function', () => {
|
|
51
|
+
expect(typeof defaultGlTilemapRenderer.submit).toBe('function');
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe('defaultGlTilemapRenderer.submit', () => {
|
|
56
|
+
it('returns early without drawing when tileset is null', () => {
|
|
57
|
+
const { state, gl } = createGlState();
|
|
58
|
+
registerDefaultGlMaterial(state);
|
|
59
|
+
defaultGlTilemapRenderer.submit(state, makeTilemapNode({ tileset: null }));
|
|
60
|
+
flushGlSpriteBatch(state);
|
|
61
|
+
expect(gl.drawElementsInstanced).not.toHaveBeenCalled();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('returns early without drawing when atlas is null', () => {
|
|
65
|
+
const { state, gl } = createGlState();
|
|
66
|
+
registerDefaultGlMaterial(state);
|
|
67
|
+
defaultGlTilemapRenderer.submit(state, makeTilemapNode({ tileset: makeTileset(null) }));
|
|
68
|
+
flushGlSpriteBatch(state);
|
|
69
|
+
expect(gl.drawElementsInstanced).not.toHaveBeenCalled();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('returns early without drawing when atlas.image is null', () => {
|
|
73
|
+
const { state, gl } = createGlState();
|
|
74
|
+
registerDefaultGlMaterial(state);
|
|
75
|
+
defaultGlTilemapRenderer.submit(
|
|
76
|
+
state,
|
|
77
|
+
makeTilemapNode({ tileset: { atlas: { image: null, regions: [] }, tileWidth: 16, tileHeight: 16 } }),
|
|
78
|
+
);
|
|
79
|
+
flushGlSpriteBatch(state);
|
|
80
|
+
expect(gl.drawElementsInstanced).not.toHaveBeenCalled();
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('returns early without drawing when atlas.image.source is null', () => {
|
|
84
|
+
const { state, gl } = createGlState();
|
|
85
|
+
registerDefaultGlMaterial(state);
|
|
86
|
+
defaultGlTilemapRenderer.submit(
|
|
87
|
+
state,
|
|
88
|
+
makeTilemapNode({ tileset: { atlas: { image: { source: null }, regions: [] }, tileWidth: 16, tileHeight: 16 } }),
|
|
89
|
+
);
|
|
90
|
+
flushGlSpriteBatch(state);
|
|
91
|
+
expect(gl.drawElementsInstanced).not.toHaveBeenCalled();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('returns early without drawing when columns is 0', () => {
|
|
95
|
+
const { state, gl } = createGlState();
|
|
96
|
+
registerDefaultGlMaterial(state);
|
|
97
|
+
defaultGlTilemapRenderer.submit(state, makeTilemapNode({ columns: 0 }));
|
|
98
|
+
flushGlSpriteBatch(state);
|
|
99
|
+
expect(gl.drawElementsInstanced).not.toHaveBeenCalled();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('returns early without drawing when rows is 0', () => {
|
|
103
|
+
const { state, gl } = createGlState();
|
|
104
|
+
registerDefaultGlMaterial(state);
|
|
105
|
+
defaultGlTilemapRenderer.submit(state, makeTilemapNode({ rows: 0 }));
|
|
106
|
+
flushGlSpriteBatch(state);
|
|
107
|
+
expect(gl.drawElementsInstanced).not.toHaveBeenCalled();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('draws all valid tiles in a single instanced call', () => {
|
|
111
|
+
const { state, gl } = createGlState();
|
|
112
|
+
registerDefaultGlMaterial(state);
|
|
113
|
+
defaultGlTilemapRenderer.submit(state, makeTilemapNode());
|
|
114
|
+
flushGlSpriteBatch(state);
|
|
115
|
+
expect(gl.drawElementsInstanced).toHaveBeenCalledTimes(1);
|
|
116
|
+
expect(gl.drawElementsInstanced).toHaveBeenCalledWith(expect.anything(), 6, expect.anything(), 0, 4);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('excludes out-of-range tile ids from the instanced draw count', () => {
|
|
120
|
+
const { state, gl } = createGlState();
|
|
121
|
+
registerDefaultGlMaterial(state);
|
|
122
|
+
defaultGlTilemapRenderer.submit(state, makeTilemapNode({ tiles: [0, 99, 99, 0] }));
|
|
123
|
+
flushGlSpriteBatch(state);
|
|
124
|
+
expect(gl.drawElementsInstanced).toHaveBeenCalledTimes(1);
|
|
125
|
+
expect(gl.drawElementsInstanced).toHaveBeenCalledWith(expect.anything(), 6, expect.anything(), 0, 2);
|
|
126
|
+
});
|
|
127
|
+
});
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { createParticleEmitter2D, reserveParticleEmitter2D } from '@flighthq/particleemitter';
|
|
2
|
+
import { createDisplayObject } from '@flighthq/scene2d';
|
|
3
|
+
import { createQuadBatch, getQuadBatchRuntime } from '@flighthq/sprite';
|
|
4
|
+
import type { QuadBatchRuntime, TextureAtlas, TextureAtlasRegion } from '@flighthq/types';
|
|
5
|
+
import { QuadBatchKind } from '@flighthq/types';
|
|
6
|
+
import { beginVelocityFrame, contributeVelocity, createVelocityField } from '@flighthq/velocity';
|
|
7
|
+
|
|
8
|
+
import { createGlState } from './glTestHelper';
|
|
9
|
+
import {
|
|
10
|
+
createGlVelocityTarget,
|
|
11
|
+
defaultGlNode2DVelocityWriter,
|
|
12
|
+
defaultGlParticleEmitter2DVelocityWriter,
|
|
13
|
+
defaultGlQuadBatchVelocityWriter,
|
|
14
|
+
drawGlVelocityQuad,
|
|
15
|
+
getGlVelocityWriter,
|
|
16
|
+
registerGlVelocityWriter,
|
|
17
|
+
renderGlVelocity,
|
|
18
|
+
} from './glVelocity';
|
|
19
|
+
|
|
20
|
+
describe('createGlVelocityTarget', () => {
|
|
21
|
+
it('allocates an rgba16f target at the requested size', () => {
|
|
22
|
+
const { state } = createGlState();
|
|
23
|
+
const target = createGlVelocityTarget(state, 128, 64);
|
|
24
|
+
expect(target.format).toBe('rgba16f');
|
|
25
|
+
expect(target.width).toBe(128);
|
|
26
|
+
expect(target.height).toBe(64);
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe('defaultGlNode2DVelocityWriter', () => {
|
|
31
|
+
it('is a velocity writer function', () => {
|
|
32
|
+
expect(typeof defaultGlNode2DVelocityWriter).toBe('function');
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe('defaultGlParticleEmitter2DVelocityWriter', () => {
|
|
37
|
+
it('is a velocity writer function', () => {
|
|
38
|
+
expect(typeof defaultGlParticleEmitter2DVelocityWriter).toBe('function');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('emits per-particle velocity for an emitter with a velocities array without throwing', () => {
|
|
42
|
+
const { state } = createGlState();
|
|
43
|
+
const target = createGlVelocityTarget(state, 128, 64);
|
|
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;
|
|
46
|
+
const emitter = createParticleEmitter2D();
|
|
47
|
+
reserveParticleEmitter2D(emitter, 2);
|
|
48
|
+
emitter.data.atlas = atlas;
|
|
49
|
+
emitter.data.particleCount = 2;
|
|
50
|
+
emitter.data.ids[0] = 0;
|
|
51
|
+
emitter.data.ids[1] = 0;
|
|
52
|
+
emitter.data.transforms.set([10, 10, 0, 1, 40, 20, 0, 1]);
|
|
53
|
+
emitter.data.velocities.set([3, -2, -1, 4]);
|
|
54
|
+
|
|
55
|
+
registerGlVelocityWriter(state, emitter.kind, defaultGlParticleEmitter2DVelocityWriter);
|
|
56
|
+
const field = createVelocityField();
|
|
57
|
+
beginVelocityFrame(field);
|
|
58
|
+
|
|
59
|
+
expect(() => renderGlVelocity(state, emitter, field, target)).not.toThrow();
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe('defaultGlQuadBatchVelocityWriter', () => {
|
|
64
|
+
it('is a velocity writer function', () => {
|
|
65
|
+
expect(typeof defaultGlQuadBatchVelocityWriter).toBe('function');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('emits per-instance velocity for a batch with an instanceVelocities array without throwing', () => {
|
|
69
|
+
const { state } = createGlState();
|
|
70
|
+
const target = createGlVelocityTarget(state, 128, 64);
|
|
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;
|
|
73
|
+
// Two vector2 instances at distinct positions, each moving independently.
|
|
74
|
+
const batch = createQuadBatch({
|
|
75
|
+
data: {
|
|
76
|
+
atlas,
|
|
77
|
+
ids: new Uint16Array([0, 0]),
|
|
78
|
+
instanceCount: 2,
|
|
79
|
+
transforms: new Float32Array([0, 0, 40, 10]),
|
|
80
|
+
transformType: 'vector2',
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
// The batch owns its per-instance velocity (NOT the VelocityField); whatever drives the quads fills it.
|
|
84
|
+
(getQuadBatchRuntime(batch) as QuadBatchRuntime).instanceVelocities = new Float32Array([3, -2, -1, 4]);
|
|
85
|
+
|
|
86
|
+
registerGlVelocityWriter(state, QuadBatchKind, defaultGlQuadBatchVelocityWriter);
|
|
87
|
+
const field = createVelocityField();
|
|
88
|
+
beginVelocityFrame(field);
|
|
89
|
+
|
|
90
|
+
expect(() => renderGlVelocity(state, batch, field, target)).not.toThrow();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('falls back to coarse batch velocity when no instanceVelocities array is present', () => {
|
|
94
|
+
const { state } = createGlState();
|
|
95
|
+
const target = createGlVelocityTarget(state, 128, 64);
|
|
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;
|
|
98
|
+
const batch = createQuadBatch({
|
|
99
|
+
data: { atlas, ids: new Uint16Array([0]), instanceCount: 1, transforms: new Float32Array([0, 0]) },
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
registerGlVelocityWriter(state, QuadBatchKind, defaultGlQuadBatchVelocityWriter);
|
|
103
|
+
const field = createVelocityField();
|
|
104
|
+
beginVelocityFrame(field);
|
|
105
|
+
contributeVelocity(field, batch, 5, 1);
|
|
106
|
+
|
|
107
|
+
expect(() => renderGlVelocity(state, batch, field, target)).not.toThrow();
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe('drawGlVelocityQuad', () => {
|
|
112
|
+
it('is callable', () => {
|
|
113
|
+
expect(typeof drawGlVelocityQuad).toBe('function');
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe('getGlVelocityWriter', () => {
|
|
118
|
+
it('returns null for an unregistered kind', () => {
|
|
119
|
+
const { state } = createGlState();
|
|
120
|
+
expect(getGlVelocityWriter(state, 'unregistered')).toBeNull();
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe('registerGlVelocityWriter', () => {
|
|
125
|
+
it('registers a writer dispatched by kind', () => {
|
|
126
|
+
const { state } = createGlState();
|
|
127
|
+
const root = createDisplayObject();
|
|
128
|
+
registerGlVelocityWriter(state, root.kind, defaultGlNode2DVelocityWriter);
|
|
129
|
+
expect(getGlVelocityWriter(state, root.kind)).toBe(defaultGlNode2DVelocityWriter);
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
describe('renderGlVelocity', () => {
|
|
134
|
+
it('dispatches the registered writer for a moving node without throwing', () => {
|
|
135
|
+
const { state } = createGlState();
|
|
136
|
+
const target = createGlVelocityTarget(state, 128, 64);
|
|
137
|
+
const root = createDisplayObject();
|
|
138
|
+
registerGlVelocityWriter(state, root.kind, defaultGlNode2DVelocityWriter);
|
|
139
|
+
|
|
140
|
+
const field = createVelocityField();
|
|
141
|
+
beginVelocityFrame(field);
|
|
142
|
+
contributeVelocity(field, root, 3, -2);
|
|
143
|
+
|
|
144
|
+
expect(() => renderGlVelocity(state, root, field, target)).not.toThrow();
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('runs without throwing when no writer is registered', () => {
|
|
148
|
+
const { state } = createGlState();
|
|
149
|
+
const target = createGlVelocityTarget(state, 128, 64);
|
|
150
|
+
const root = createDisplayObject();
|
|
151
|
+
const field = createVelocityField();
|
|
152
|
+
beginVelocityFrame(field);
|
|
153
|
+
|
|
154
|
+
expect(() => renderGlVelocity(state, root, field, target)).not.toThrow();
|
|
155
|
+
});
|
|
156
|
+
});
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { getGlRenderStateRuntime } from '@flighthq/render-gl';
|
|
2
|
+
import type { RendererData, RenderProxy2D, VideoTexture } from '@flighthq/types';
|
|
3
|
+
|
|
4
|
+
import { createGlState } from './glTestHelper';
|
|
5
|
+
import { createGlVideoData, defaultGlVideoRenderer, destroyGlVideoData, drawGlVideo } from './glVideo';
|
|
6
|
+
|
|
7
|
+
function makeVideoNode(element: HTMLVideoElement | null = null): RenderProxy2D {
|
|
8
|
+
return {
|
|
9
|
+
source: { data: { source: element !== null ? { element } : null } },
|
|
10
|
+
blendMode: 0,
|
|
11
|
+
alpha: 1,
|
|
12
|
+
transform2D: { a: 1, b: 0, c: 0, d: 1, tx: 0, ty: 0 },
|
|
13
|
+
rendererData: { source: null, videoTexture: null },
|
|
14
|
+
} as unknown as RenderProxy2D;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function makeReadyElement(): HTMLVideoElement {
|
|
18
|
+
const element = document.createElement('video');
|
|
19
|
+
Object.defineProperty(element, 'readyState', { value: 4, configurable: true });
|
|
20
|
+
Object.defineProperty(element, 'videoWidth', { value: 320, configurable: true });
|
|
21
|
+
Object.defineProperty(element, 'videoHeight', { value: 240, configurable: true });
|
|
22
|
+
return element;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
describe('createGlVideoData', () => {
|
|
26
|
+
it('allocates per-node data with no stream or texture recorded yet', () => {
|
|
27
|
+
const { state } = createGlState();
|
|
28
|
+
const data = createGlVideoData(state, {} as never) as unknown as {
|
|
29
|
+
source: unknown;
|
|
30
|
+
videoTexture: VideoTexture | null;
|
|
31
|
+
};
|
|
32
|
+
expect(data.source).toBeNull();
|
|
33
|
+
expect(data.videoTexture).toBeNull();
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe('defaultGlVideoRenderer', () => {
|
|
38
|
+
it('has submit, and createData functions', () => {
|
|
39
|
+
expect(typeof defaultGlVideoRenderer.submit).toBe('function');
|
|
40
|
+
expect(typeof defaultGlVideoRenderer.createData).toBe('function');
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe('destroyGlVideoData', () => {
|
|
45
|
+
it('deletes the cached GPU texture for the recorded VideoTexture', () => {
|
|
46
|
+
const { state, gl } = createGlState();
|
|
47
|
+
const runtime = getGlRenderStateRuntime(state);
|
|
48
|
+
const videoTexture = { frameId: -1 } as unknown as VideoTexture;
|
|
49
|
+
const texture = gl.createTexture() as WebGLTexture;
|
|
50
|
+
runtime.videoTextureCache = new WeakMap();
|
|
51
|
+
runtime.videoTextureCache.set(videoTexture, { texture, uploadedFrameId: -1 });
|
|
52
|
+
const deleteSpy = vi.spyOn(gl, 'deleteTexture');
|
|
53
|
+
destroyGlVideoData(state, { source: null, videoTexture } as unknown as RendererData);
|
|
54
|
+
expect(deleteSpy).toHaveBeenCalledWith(texture);
|
|
55
|
+
expect(runtime.videoTextureCache.has(videoTexture)).toBe(false);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('is a no-op when no VideoTexture was recorded', () => {
|
|
59
|
+
const { state, gl } = createGlState();
|
|
60
|
+
const deleteSpy = vi.spyOn(gl, 'deleteTexture');
|
|
61
|
+
destroyGlVideoData(state, { source: null, videoTexture: null } as unknown as RendererData);
|
|
62
|
+
expect(deleteSpy).not.toHaveBeenCalled();
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe('drawGlVideo', () => {
|
|
67
|
+
it('returns early when source is null', () => {
|
|
68
|
+
const { state, gl } = createGlState();
|
|
69
|
+
drawGlVideo(state, makeVideoNode(null));
|
|
70
|
+
expect(gl.drawElements).not.toHaveBeenCalled();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('returns early when element has not loaded', () => {
|
|
74
|
+
const { state, gl } = createGlState();
|
|
75
|
+
drawGlVideo(state, makeVideoNode(document.createElement('video')));
|
|
76
|
+
expect(gl.drawElements).not.toHaveBeenCalled();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('builds a VideoTexture and uploads the ready frame through the gated bind', () => {
|
|
80
|
+
const { state, gl } = createGlState();
|
|
81
|
+
const node = makeVideoNode(makeReadyElement());
|
|
82
|
+
drawGlVideo(state, node);
|
|
83
|
+
const data = node.rendererData as unknown as { videoTexture: VideoTexture | null };
|
|
84
|
+
expect(data.videoTexture).not.toBeNull();
|
|
85
|
+
expect(gl.texImage2D).toHaveBeenCalled();
|
|
86
|
+
expect(gl.drawElements).toHaveBeenCalled();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('reuses the VideoTexture across draws of the same stream', () => {
|
|
90
|
+
const { state } = createGlState();
|
|
91
|
+
const node = makeVideoNode(makeReadyElement());
|
|
92
|
+
drawGlVideo(state, node);
|
|
93
|
+
const first = (node.rendererData as unknown as { videoTexture: VideoTexture | null }).videoTexture;
|
|
94
|
+
drawGlVideo(state, node);
|
|
95
|
+
const second = (node.rendererData as unknown as { videoTexture: VideoTexture | null }).videoTexture;
|
|
96
|
+
expect(second).toBe(first);
|
|
97
|
+
});
|
|
98
|
+
});
|