@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,61 @@
|
|
|
1
|
+
import { createMatrix } from '@flighthq/geometry';
|
|
2
|
+
import { getGlRenderStateRuntime } from '@flighthq/render-gl';
|
|
3
|
+
|
|
4
|
+
import { popGlClipContours, pushGlClipContours } from './glClipContours';
|
|
5
|
+
import { createGlState } from './glTestHelper';
|
|
6
|
+
|
|
7
|
+
const SQUARE = [[0, 0, 50, 0, 50, 50, 0, 50]];
|
|
8
|
+
|
|
9
|
+
describe('popGlClipContours', () => {
|
|
10
|
+
it('decrements the stencil depth and disables the stencil test at depth 0', () => {
|
|
11
|
+
const { state, gl } = createGlState();
|
|
12
|
+
const runtime = getGlRenderStateRuntime(state);
|
|
13
|
+
pushGlClipContours(state, SQUARE, 'nonZero', createMatrix());
|
|
14
|
+
expect(runtime.currentMaskDepth).toBe(1);
|
|
15
|
+
|
|
16
|
+
popGlClipContours(state);
|
|
17
|
+
|
|
18
|
+
expect(runtime.currentMaskDepth).toBe(0);
|
|
19
|
+
expect(gl.disable).toHaveBeenCalledWith(gl.STENCIL_TEST);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe('pushGlClipContours', () => {
|
|
24
|
+
it('enables the stencil test and clears the buffer when opening the first clip', () => {
|
|
25
|
+
const { state, gl } = createGlState();
|
|
26
|
+
|
|
27
|
+
pushGlClipContours(state, SQUARE, 'nonZero', createMatrix());
|
|
28
|
+
|
|
29
|
+
expect(gl.enable).toHaveBeenCalledWith(gl.STENCIL_TEST);
|
|
30
|
+
expect(gl.clear).toHaveBeenCalledWith(gl.STENCIL_BUFFER_BIT);
|
|
31
|
+
expect(getGlRenderStateRuntime(state).currentMaskDepth).toBe(1);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('syncs state.currentProgram to the clip program so content draws re-bind their own program', () => {
|
|
35
|
+
const { state } = createGlState();
|
|
36
|
+
const runtime = getGlRenderStateRuntime(state);
|
|
37
|
+
expect(runtime.currentProgram).toBeNull();
|
|
38
|
+
|
|
39
|
+
pushGlClipContours(state, SQUARE, 'nonZero', createMatrix());
|
|
40
|
+
|
|
41
|
+
// The clip binds its own position-only program; recording it forces the next content draw to detect
|
|
42
|
+
// the change and re-bind, instead of setting its uniforms against the clip program.
|
|
43
|
+
expect(runtime.currentProgram).not.toBeNull();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('re-enables color writes after stencilling so gated content is drawn', () => {
|
|
47
|
+
const { state, gl } = createGlState();
|
|
48
|
+
|
|
49
|
+
pushGlClipContours(state, SQUARE, 'nonZero', createMatrix());
|
|
50
|
+
|
|
51
|
+
expect(gl.colorMask).toHaveBeenLastCalledWith(true, true, true, true);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('uses the even-odd stencil op for an evenOdd winding', () => {
|
|
55
|
+
const { state, gl } = createGlState();
|
|
56
|
+
|
|
57
|
+
pushGlClipContours(state, SQUARE, 'evenOdd', createMatrix());
|
|
58
|
+
|
|
59
|
+
expect(gl.stencilOp).toHaveBeenCalledWith(gl.KEEP, gl.KEEP, gl.INVERT);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { createMatrix, createRectangle } from '@flighthq/geometry';
|
|
2
|
+
import { getGlRenderStateRuntime } from '@flighthq/render-gl';
|
|
3
|
+
|
|
4
|
+
import { popGlClipRectangle, pushGlClipRectangle } from './glClipRectangle';
|
|
5
|
+
import { createGlState } from './glTestHelper';
|
|
6
|
+
|
|
7
|
+
describe('popGlClipRectangle', () => {
|
|
8
|
+
it('restores the previous scissor rectangle', () => {
|
|
9
|
+
const { state, gl } = createGlState();
|
|
10
|
+
pushGlClipRectangle(state, createRectangle(0, 0, 100, 50), createMatrix());
|
|
11
|
+
pushGlClipRectangle(state, createRectangle(10, 10, 10, 10), createMatrix());
|
|
12
|
+
|
|
13
|
+
popGlClipRectangle(state);
|
|
14
|
+
|
|
15
|
+
expect(gl.scissor).toHaveBeenLastCalledWith(0, 50, 100, 50);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('disables scissor testing when the stack becomes empty', () => {
|
|
19
|
+
const { state, gl } = createGlState();
|
|
20
|
+
pushGlClipRectangle(state, createRectangle(0, 0, 100, 50), createMatrix());
|
|
21
|
+
|
|
22
|
+
popGlClipRectangle(state);
|
|
23
|
+
|
|
24
|
+
expect(gl.disable).toHaveBeenLastCalledWith(gl.SCISSOR_TEST);
|
|
25
|
+
expect(getGlRenderStateRuntime(state).currentScissorRect).toBeNull();
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe('pushGlClipRectangle', () => {
|
|
30
|
+
it('enables scissor testing with a transformed rectangle', () => {
|
|
31
|
+
const { state, gl } = createGlState();
|
|
32
|
+
|
|
33
|
+
pushGlClipRectangle(state, createRectangle(10, 20, 30, 40), createMatrix(1, 0, 0, 1, 5, 6));
|
|
34
|
+
|
|
35
|
+
expect(gl.enable).toHaveBeenLastCalledWith(gl.SCISSOR_TEST);
|
|
36
|
+
expect(gl.scissor).toHaveBeenLastCalledWith(15, 34, 30, 40);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('intersects nested scissor rectangles', () => {
|
|
40
|
+
const { state, gl } = createGlState();
|
|
41
|
+
|
|
42
|
+
pushGlClipRectangle(state, createRectangle(0, 0, 100, 100), createMatrix());
|
|
43
|
+
pushGlClipRectangle(state, createRectangle(50, 25, 100, 50), createMatrix());
|
|
44
|
+
|
|
45
|
+
expect(gl.scissor).toHaveBeenLastCalledWith(50, 25, 50, 50);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { createImageResource } from '@flighthq/image';
|
|
2
|
+
import { getGlRenderStateRuntime } from '@flighthq/render-gl';
|
|
3
|
+
import type { ColorTransform, ImageResource } from '@flighthq/types';
|
|
4
|
+
|
|
5
|
+
import { enableGlColorAdjustment } from './glColorAdjustment';
|
|
6
|
+
import { defaultGlMaterialRenderer } from './glDefaultMaterial';
|
|
7
|
+
import { flushGlSpriteBatch, prepareGlSpriteBatchWrite, recordGlSpriteBatchColorTransform } from './glSpriteBatch';
|
|
8
|
+
import { createGlState } from './glTestHelper';
|
|
9
|
+
|
|
10
|
+
function makeTexture(): ImageResource {
|
|
11
|
+
return createImageResource(document.createElement('img'));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function ct(
|
|
15
|
+
redMultiplier = 1,
|
|
16
|
+
greenMultiplier = 1,
|
|
17
|
+
blueMultiplier = 1,
|
|
18
|
+
alphaMultiplier = 1,
|
|
19
|
+
redOffset = 0,
|
|
20
|
+
greenOffset = 0,
|
|
21
|
+
blueOffset = 0,
|
|
22
|
+
alphaOffset = 0,
|
|
23
|
+
): ColorTransform {
|
|
24
|
+
return {
|
|
25
|
+
redMultiplier,
|
|
26
|
+
greenMultiplier,
|
|
27
|
+
blueMultiplier,
|
|
28
|
+
alphaMultiplier,
|
|
29
|
+
redOffset,
|
|
30
|
+
greenOffset,
|
|
31
|
+
blueOffset,
|
|
32
|
+
alphaOffset,
|
|
33
|
+
} as ColorTransform;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const CT_MODE_NONE = 0;
|
|
37
|
+
const CT_MODE_UNIFORM = 1;
|
|
38
|
+
const CT_MODE_PER_INSTANCE = 2;
|
|
39
|
+
|
|
40
|
+
describe('enableGlColorAdjustment', () => {
|
|
41
|
+
it('installs the fold so recorded tints drive the color-adjustment state machine', () => {
|
|
42
|
+
const { state } = createGlState();
|
|
43
|
+
const runtime = getGlRenderStateRuntime(state);
|
|
44
|
+
enableGlColorAdjustment(state);
|
|
45
|
+
expect(runtime.glColorAdjustmentFold).not.toBeNull();
|
|
46
|
+
expect(runtime.glColorAdjustmentFold).toBeDefined();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('is idempotent', () => {
|
|
50
|
+
const { state } = createGlState();
|
|
51
|
+
const runtime = getGlRenderStateRuntime(state);
|
|
52
|
+
enableGlColorAdjustment(state);
|
|
53
|
+
const fold = runtime.glColorAdjustmentFold;
|
|
54
|
+
enableGlColorAdjustment(state);
|
|
55
|
+
expect(runtime.glColorAdjustmentFold).toBe(fold);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('stays untinted (mode NONE) when no instance carries a color transform', () => {
|
|
59
|
+
const { state } = createGlState();
|
|
60
|
+
const runtime = getGlRenderStateRuntime(state);
|
|
61
|
+
enableGlColorAdjustment(state);
|
|
62
|
+
recordGlSpriteBatchColorTransform(state, null, 0);
|
|
63
|
+
recordGlSpriteBatchColorTransform(state, null, 1);
|
|
64
|
+
expect(runtime.spriteBatchColorTransformMode).toBe(CT_MODE_NONE);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('uses one whole-batch uniform when every instance shares one tint', () => {
|
|
68
|
+
const { state } = createGlState();
|
|
69
|
+
const runtime = getGlRenderStateRuntime(state);
|
|
70
|
+
enableGlColorAdjustment(state);
|
|
71
|
+
const tint = ct(0.5);
|
|
72
|
+
recordGlSpriteBatchColorTransform(state, tint, 0);
|
|
73
|
+
recordGlSpriteBatchColorTransform(state, tint, 1);
|
|
74
|
+
recordGlSpriteBatchColorTransform(state, tint, 2);
|
|
75
|
+
expect(runtime.spriteBatchColorTransformMode).toBe(CT_MODE_UNIFORM);
|
|
76
|
+
expect(runtime.spriteBatchUniformColorTransform).toBe(tint);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('keeps the uniform path for a distinct-but-equal tint', () => {
|
|
80
|
+
const { state } = createGlState();
|
|
81
|
+
const runtime = getGlRenderStateRuntime(state);
|
|
82
|
+
enableGlColorAdjustment(state);
|
|
83
|
+
recordGlSpriteBatchColorTransform(state, ct(0.5), 0);
|
|
84
|
+
recordGlSpriteBatchColorTransform(state, ct(0.5), 1);
|
|
85
|
+
expect(runtime.spriteBatchColorTransformMode).toBe(CT_MODE_UNIFORM);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('promotes to per-instance (never splits) when tints diverge, back-filling the earlier tint', () => {
|
|
89
|
+
const { state } = createGlState();
|
|
90
|
+
const runtime = getGlRenderStateRuntime(state);
|
|
91
|
+
enableGlColorAdjustment(state);
|
|
92
|
+
recordGlSpriteBatchColorTransform(state, ct(0.5), 0);
|
|
93
|
+
recordGlSpriteBatchColorTransform(state, ct(0.25), 1);
|
|
94
|
+
expect(runtime.spriteBatchColorTransformMode).toBe(CT_MODE_PER_INSTANCE);
|
|
95
|
+
expect(runtime.spriteBatchColorTransformData![0]).toBe(0.5);
|
|
96
|
+
expect(runtime.spriteBatchColorTransformData![8]).toBe(0.25);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('promotes with identity fill when a tinted instance follows an untinted one', () => {
|
|
100
|
+
const { state } = createGlState();
|
|
101
|
+
const runtime = getGlRenderStateRuntime(state);
|
|
102
|
+
enableGlColorAdjustment(state);
|
|
103
|
+
recordGlSpriteBatchColorTransform(state, null, 0);
|
|
104
|
+
recordGlSpriteBatchColorTransform(state, ct(0.5), 1);
|
|
105
|
+
expect(runtime.spriteBatchColorTransformMode).toBe(CT_MODE_PER_INSTANCE);
|
|
106
|
+
expect(runtime.spriteBatchColorTransformData![0]).toBe(1);
|
|
107
|
+
expect(runtime.spriteBatchColorTransformData![8]).toBe(0.5);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('writes identity for an untinted instance once the batch is per-instance', () => {
|
|
111
|
+
const { state } = createGlState();
|
|
112
|
+
const runtime = getGlRenderStateRuntime(state);
|
|
113
|
+
enableGlColorAdjustment(state);
|
|
114
|
+
recordGlSpriteBatchColorTransform(state, ct(0.5), 0);
|
|
115
|
+
recordGlSpriteBatchColorTransform(state, ct(0.25), 1);
|
|
116
|
+
recordGlSpriteBatchColorTransform(state, null, 2);
|
|
117
|
+
expect(runtime.spriteBatchColorTransformData![16]).toBe(1);
|
|
118
|
+
expect(runtime.spriteBatchColorTransformData![20]).toBe(0);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('normalizes color offsets by 255', () => {
|
|
122
|
+
const { state } = createGlState();
|
|
123
|
+
const runtime = getGlRenderStateRuntime(state);
|
|
124
|
+
enableGlColorAdjustment(state);
|
|
125
|
+
recordGlSpriteBatchColorTransform(state, ct(1, 1, 1, 1, 255, 0, 0, 0), 0);
|
|
126
|
+
recordGlSpriteBatchColorTransform(state, ct(0.5), 1);
|
|
127
|
+
expect(runtime.spriteBatchColorTransformData![4]).toBe(1);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('drives the uniform color-transform shader on flush', () => {
|
|
131
|
+
const { state, gl } = createGlState();
|
|
132
|
+
const runtime = getGlRenderStateRuntime(state);
|
|
133
|
+
enableGlColorAdjustment(state);
|
|
134
|
+
prepareGlSpriteBatchWrite(state, makeTexture(), null, null, defaultGlMaterialRenderer, 1);
|
|
135
|
+
recordGlSpriteBatchColorTransform(state, ct(0.5), 0);
|
|
136
|
+
runtime.spriteBatchCount = 1;
|
|
137
|
+
flushGlSpriteBatch(state);
|
|
138
|
+
expect(gl.uniform4f).toHaveBeenCalled();
|
|
139
|
+
expect(gl.drawElementsInstanced).toHaveBeenCalled();
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('uploads a per-instance color-transform buffer on flush when tints vary', () => {
|
|
143
|
+
const { state, gl } = createGlState();
|
|
144
|
+
const runtime = getGlRenderStateRuntime(state);
|
|
145
|
+
enableGlColorAdjustment(state);
|
|
146
|
+
prepareGlSpriteBatchWrite(state, makeTexture(), null, null, defaultGlMaterialRenderer, 2);
|
|
147
|
+
recordGlSpriteBatchColorTransform(state, ct(0.5), 0);
|
|
148
|
+
recordGlSpriteBatchColorTransform(state, ct(0.25), 1);
|
|
149
|
+
runtime.spriteBatchCount = 2;
|
|
150
|
+
flushGlSpriteBatch(state);
|
|
151
|
+
expect(runtime.spriteBatchColorTransformBuffer).not.toBeNull();
|
|
152
|
+
expect(gl.drawElementsInstanced).toHaveBeenCalledWith(expect.anything(), 6, expect.anything(), 0, 2);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('leaves the lean base shader untouched for an untinted batch on flush', () => {
|
|
156
|
+
const { state, gl } = createGlState();
|
|
157
|
+
const runtime = getGlRenderStateRuntime(state);
|
|
158
|
+
enableGlColorAdjustment(state);
|
|
159
|
+
prepareGlSpriteBatchWrite(state, makeTexture(), null, null, defaultGlMaterialRenderer, 1);
|
|
160
|
+
recordGlSpriteBatchColorTransform(state, null, 0);
|
|
161
|
+
runtime.spriteBatchCount = 1;
|
|
162
|
+
flushGlSpriteBatch(state);
|
|
163
|
+
expect(runtime.spriteBatchColorTransformBuffer ?? null).toBeNull();
|
|
164
|
+
expect(gl.drawElementsInstanced).toHaveBeenCalled();
|
|
165
|
+
});
|
|
166
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { getGlMaterialRenderer } from '@flighthq/render-gl';
|
|
2
|
+
import { DefaultMaterialKind } from '@flighthq/types';
|
|
3
|
+
|
|
4
|
+
import { defaultGlMaterialRenderer, registerDefaultGlMaterial } from './glDefaultMaterial';
|
|
5
|
+
import { createGlState } from './glTestHelper';
|
|
6
|
+
|
|
7
|
+
describe('defaultGlMaterialRenderer', () => {
|
|
8
|
+
it('declares no per-instance float data', () => {
|
|
9
|
+
expect(defaultGlMaterialRenderer.instanceFloatCount).toBe(0);
|
|
10
|
+
});
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
describe('registerDefaultGlMaterial', () => {
|
|
14
|
+
it('registers the default renderer under DefaultMaterialKind', () => {
|
|
15
|
+
const { state } = createGlState();
|
|
16
|
+
registerDefaultGlMaterial(state);
|
|
17
|
+
expect(getGlMaterialRenderer(state, DefaultMaterialKind)).toBe(defaultGlMaterialRenderer);
|
|
18
|
+
});
|
|
19
|
+
});
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { addNodeChild } from '@flighthq/node';
|
|
2
|
+
import { getOrCreateRenderProxy2D, prepareScene2DRender, registerRenderer } from '@flighthq/render';
|
|
3
|
+
import { createGlRenderState } from '@flighthq/render-gl';
|
|
4
|
+
import { createDisplayObject } from '@flighthq/scene2d';
|
|
5
|
+
import type { GlRenderState } from '@flighthq/types';
|
|
6
|
+
import { DisplayObjectKind } from '@flighthq/types';
|
|
7
|
+
|
|
8
|
+
import { defaultGlScene2DRenderer, drawGlScene2D, renderGlScene2D } from './glNode2D';
|
|
9
|
+
|
|
10
|
+
function makeState(): GlRenderState {
|
|
11
|
+
const canvas = document.createElement('canvas');
|
|
12
|
+
canvas.width = 200;
|
|
13
|
+
canvas.height = 100;
|
|
14
|
+
return createGlRenderState(canvas);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function makeRenderer() {
|
|
18
|
+
return {
|
|
19
|
+
createData: () => null,
|
|
20
|
+
submit: vi.fn(),
|
|
21
|
+
} as any;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
describe('defaultGlScene2DRenderer', () => {
|
|
25
|
+
it('has draw, and createData functions', () => {
|
|
26
|
+
expect(defaultGlScene2DRenderer.createData({} as any, {} as any)).toBeNull();
|
|
27
|
+
expect(defaultGlScene2DRenderer.submit).toBe(drawGlScene2D);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe('drawGlScene2D', () => {
|
|
32
|
+
it('does not draw plain display object geometry', () => {
|
|
33
|
+
const state = makeState();
|
|
34
|
+
expect(() => drawGlScene2D(state, {} as any)).not.toThrow();
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe('renderGlScene2D', () => {
|
|
39
|
+
it('does not throw for an empty display object', () => {
|
|
40
|
+
const state = makeState();
|
|
41
|
+
const obj = createDisplayObject();
|
|
42
|
+
prepareScene2DRender(state, obj);
|
|
43
|
+
expect(() => renderGlScene2D(state, obj)).not.toThrow();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('calls renderer.submit for a visible object with a renderer', () => {
|
|
47
|
+
const state = makeState();
|
|
48
|
+
const renderer = makeRenderer();
|
|
49
|
+
registerRenderer(state, DisplayObjectKind, renderer);
|
|
50
|
+
const obj = createDisplayObject();
|
|
51
|
+
const data = getOrCreateRenderProxy2D(state, obj);
|
|
52
|
+
prepareScene2DRender(state, obj);
|
|
53
|
+
|
|
54
|
+
renderGlScene2D(state, obj);
|
|
55
|
+
|
|
56
|
+
expect(renderer.submit).toHaveBeenCalledWith(state, data);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('skips objects with zero alpha', () => {
|
|
60
|
+
const state = makeState();
|
|
61
|
+
const renderer = makeRenderer();
|
|
62
|
+
registerRenderer(state, DisplayObjectKind, renderer);
|
|
63
|
+
const obj = createDisplayObject();
|
|
64
|
+
obj.alpha = 0;
|
|
65
|
+
prepareScene2DRender(state, obj);
|
|
66
|
+
|
|
67
|
+
renderGlScene2D(state, obj);
|
|
68
|
+
|
|
69
|
+
expect(renderer.submit).not.toHaveBeenCalled();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('traverses children and draws visible ones', () => {
|
|
73
|
+
const state = makeState();
|
|
74
|
+
const renderer = makeRenderer();
|
|
75
|
+
registerRenderer(state, DisplayObjectKind, renderer);
|
|
76
|
+
const parent = createDisplayObject();
|
|
77
|
+
const child = createDisplayObject();
|
|
78
|
+
addNodeChild(parent, child);
|
|
79
|
+
prepareScene2DRender(state, parent);
|
|
80
|
+
|
|
81
|
+
renderGlScene2D(state, parent);
|
|
82
|
+
|
|
83
|
+
expect(renderer.submit).toHaveBeenCalledTimes(2);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { getRenderStateRuntime } from '@flighthq/render';
|
|
2
|
+
import { createGlRenderState } from '@flighthq/render-gl';
|
|
3
|
+
import type { GlRenderState } from '@flighthq/types';
|
|
4
|
+
import {
|
|
5
|
+
BitmapKind,
|
|
6
|
+
DisplayObjectKind,
|
|
7
|
+
ParticleEmitter2DKind,
|
|
8
|
+
QuadBatchKind,
|
|
9
|
+
RenderCacheKind,
|
|
10
|
+
RichTextKind,
|
|
11
|
+
Scale9ShapeKind,
|
|
12
|
+
ShapeKind,
|
|
13
|
+
SpriteKind,
|
|
14
|
+
TextLabelKind,
|
|
15
|
+
TilemapKind,
|
|
16
|
+
VideoKind,
|
|
17
|
+
} from '@flighthq/types';
|
|
18
|
+
|
|
19
|
+
import { defaultGlBitmapRenderer } from './glBitmap';
|
|
20
|
+
import { defaultGlRenderCacheRenderer } from './glCache';
|
|
21
|
+
import { defaultGlScene2DRenderer } from './glNode2D';
|
|
22
|
+
import { registerGlScene2DRenderers } from './glNode2DRegistration';
|
|
23
|
+
import { defaultGlParticleEmitter2DRenderer } from './glParticleEmitter2D';
|
|
24
|
+
import { defaultGlQuadBatchRenderer } from './glQuadBatch';
|
|
25
|
+
import { defaultGlRichTextRenderer } from './glRichText';
|
|
26
|
+
import { defaultGlScale9ShapeRenderer } from './glScale9Shape';
|
|
27
|
+
import { defaultGlShapeRenderer } from './glShape';
|
|
28
|
+
import { defaultGlSpriteRenderer } from './glSpriteRenderer';
|
|
29
|
+
import { defaultGlTextLabelRenderer } from './glTextLabel';
|
|
30
|
+
import { defaultGlTilemapRenderer } from './glTilemap';
|
|
31
|
+
import { defaultGlVideoRenderer } from './glVideo';
|
|
32
|
+
|
|
33
|
+
function makeState(): GlRenderState {
|
|
34
|
+
const canvas = document.createElement('canvas');
|
|
35
|
+
canvas.width = 100;
|
|
36
|
+
canvas.height = 100;
|
|
37
|
+
return createGlRenderState(canvas);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
describe('registerGlScene2DRenderers', () => {
|
|
41
|
+
it('registers all twelve display-object renderer kinds in one call', () => {
|
|
42
|
+
const state = makeState();
|
|
43
|
+
registerGlScene2DRenderers(state);
|
|
44
|
+
const { rendererMap } = getRenderStateRuntime(state);
|
|
45
|
+
expect(rendererMap.get(BitmapKind)).toBe(defaultGlBitmapRenderer);
|
|
46
|
+
expect(rendererMap.get(DisplayObjectKind)).toBe(defaultGlScene2DRenderer);
|
|
47
|
+
expect(rendererMap.get(ParticleEmitter2DKind)).toBe(defaultGlParticleEmitter2DRenderer);
|
|
48
|
+
expect(rendererMap.get(QuadBatchKind)).toBe(defaultGlQuadBatchRenderer);
|
|
49
|
+
expect(rendererMap.get(RenderCacheKind)).toBe(defaultGlRenderCacheRenderer);
|
|
50
|
+
expect(rendererMap.get(RichTextKind)).toBe(defaultGlRichTextRenderer);
|
|
51
|
+
expect(rendererMap.get(Scale9ShapeKind)).toBe(defaultGlScale9ShapeRenderer);
|
|
52
|
+
expect(rendererMap.get(ShapeKind)).toBe(defaultGlShapeRenderer);
|
|
53
|
+
expect(rendererMap.get(SpriteKind)).toBe(defaultGlSpriteRenderer);
|
|
54
|
+
expect(rendererMap.get(TextLabelKind)).toBe(defaultGlTextLabelRenderer);
|
|
55
|
+
expect(rendererMap.get(TilemapKind)).toBe(defaultGlTilemapRenderer);
|
|
56
|
+
expect(rendererMap.get(VideoKind)).toBe(defaultGlVideoRenderer);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('registers BitmapKind with the default bitmap renderer', () => {
|
|
60
|
+
const state = makeState();
|
|
61
|
+
registerGlScene2DRenderers(state);
|
|
62
|
+
expect(getRenderStateRuntime(state).rendererMap.get(BitmapKind)).toBe(defaultGlBitmapRenderer);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('registers DisplayObjectKind with the default display object renderer', () => {
|
|
66
|
+
const state = makeState();
|
|
67
|
+
registerGlScene2DRenderers(state);
|
|
68
|
+
expect(getRenderStateRuntime(state).rendererMap.get(DisplayObjectKind)).toBe(defaultGlScene2DRenderer);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('registers ParticleEmitter2DKind with the default particle emitter renderer', () => {
|
|
72
|
+
const state = makeState();
|
|
73
|
+
registerGlScene2DRenderers(state);
|
|
74
|
+
expect(getRenderStateRuntime(state).rendererMap.get(ParticleEmitter2DKind)).toBe(
|
|
75
|
+
defaultGlParticleEmitter2DRenderer,
|
|
76
|
+
);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('registers QuadBatchKind with the default quad batch renderer', () => {
|
|
80
|
+
const state = makeState();
|
|
81
|
+
registerGlScene2DRenderers(state);
|
|
82
|
+
expect(getRenderStateRuntime(state).rendererMap.get(QuadBatchKind)).toBe(defaultGlQuadBatchRenderer);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('registers RenderCacheKind with the default render cache renderer', () => {
|
|
86
|
+
const state = makeState();
|
|
87
|
+
registerGlScene2DRenderers(state);
|
|
88
|
+
expect(getRenderStateRuntime(state).rendererMap.get(RenderCacheKind)).toBe(defaultGlRenderCacheRenderer);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('registers RichTextKind with the default rich text renderer', () => {
|
|
92
|
+
const state = makeState();
|
|
93
|
+
registerGlScene2DRenderers(state);
|
|
94
|
+
expect(getRenderStateRuntime(state).rendererMap.get(RichTextKind)).toBe(defaultGlRichTextRenderer);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('registers Scale9ShapeKind with the default scale9 shape renderer', () => {
|
|
98
|
+
const state = makeState();
|
|
99
|
+
registerGlScene2DRenderers(state);
|
|
100
|
+
expect(getRenderStateRuntime(state).rendererMap.get(Scale9ShapeKind)).toBe(defaultGlScale9ShapeRenderer);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('registers ShapeKind with the default shape renderer', () => {
|
|
104
|
+
const state = makeState();
|
|
105
|
+
registerGlScene2DRenderers(state);
|
|
106
|
+
expect(getRenderStateRuntime(state).rendererMap.get(ShapeKind)).toBe(defaultGlShapeRenderer);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('registers SpriteKind with the default sprite renderer', () => {
|
|
110
|
+
const state = makeState();
|
|
111
|
+
registerGlScene2DRenderers(state);
|
|
112
|
+
expect(getRenderStateRuntime(state).rendererMap.get(SpriteKind)).toBe(defaultGlSpriteRenderer);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('registers TextLabelKind with the default text label renderer', () => {
|
|
116
|
+
const state = makeState();
|
|
117
|
+
registerGlScene2DRenderers(state);
|
|
118
|
+
expect(getRenderStateRuntime(state).rendererMap.get(TextLabelKind)).toBe(defaultGlTextLabelRenderer);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('registers TilemapKind with the default tilemap renderer', () => {
|
|
122
|
+
const state = makeState();
|
|
123
|
+
registerGlScene2DRenderers(state);
|
|
124
|
+
expect(getRenderStateRuntime(state).rendererMap.get(TilemapKind)).toBe(defaultGlTilemapRenderer);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('registers VideoKind with the default video renderer', () => {
|
|
128
|
+
const state = makeState();
|
|
129
|
+
registerGlScene2DRenderers(state);
|
|
130
|
+
expect(getRenderStateRuntime(state).rendererMap.get(VideoKind)).toBe(defaultGlVideoRenderer);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { getGlRenderStateRuntime } from '@flighthq/render-gl';
|
|
2
|
+
import type { RenderProxy2D } from '@flighthq/types';
|
|
3
|
+
|
|
4
|
+
import { defaultGlParticleEmitter2DRenderer, drawGlParticleEmitter2D } from './glParticleEmitter2D';
|
|
5
|
+
import { createGlState } from './glTestHelper';
|
|
6
|
+
|
|
7
|
+
function makeAtlas() {
|
|
8
|
+
const img = document.createElement('img');
|
|
9
|
+
return {
|
|
10
|
+
image: { source: img, width: 64, height: 64 },
|
|
11
|
+
regions: [{ id: 0, x: 0, y: 0, width: 32, height: 32 }],
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function makeParticleEmitter2DNode(data: Record<string, unknown> = {}): RenderProxy2D {
|
|
16
|
+
return {
|
|
17
|
+
source: {
|
|
18
|
+
data: {
|
|
19
|
+
atlas: makeAtlas(),
|
|
20
|
+
particleCount: 1,
|
|
21
|
+
ids: new Uint16Array([0]),
|
|
22
|
+
transforms: new Float32Array([0, 0, 0, 1]),
|
|
23
|
+
alphas: new Float32Array([1]),
|
|
24
|
+
colors: new Float32Array([1, 1, 1]),
|
|
25
|
+
...data,
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
blendMode: 0,
|
|
29
|
+
alpha: 1,
|
|
30
|
+
transform2D: { a: 1, b: 0, c: 0, d: 1, tx: 0, ty: 0 },
|
|
31
|
+
} as unknown as RenderProxy2D;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
describe('defaultGlParticleEmitter2DRenderer', () => {
|
|
35
|
+
it('has a createData function', () => {
|
|
36
|
+
expect(typeof defaultGlParticleEmitter2DRenderer.createData).toBe('function');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('has a submit function', () => {
|
|
40
|
+
expect(typeof defaultGlParticleEmitter2DRenderer.submit).toBe('function');
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe('drawGlParticleEmitter2D', () => {
|
|
45
|
+
it('returns early without drawing when atlas is null', () => {
|
|
46
|
+
const { state, gl } = createGlState();
|
|
47
|
+
drawGlParticleEmitter2D(state, makeParticleEmitter2DNode({ atlas: null }));
|
|
48
|
+
expect(gl.drawElements).not.toHaveBeenCalled();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('returns early without drawing when atlas.image is null', () => {
|
|
52
|
+
const { state, gl } = createGlState();
|
|
53
|
+
drawGlParticleEmitter2D(state, makeParticleEmitter2DNode({ atlas: { image: null, regions: [] } }));
|
|
54
|
+
expect(gl.drawElements).not.toHaveBeenCalled();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('returns early without drawing when atlas.image.source is null', () => {
|
|
58
|
+
const { state, gl } = createGlState();
|
|
59
|
+
drawGlParticleEmitter2D(state, makeParticleEmitter2DNode({ atlas: { image: { source: null }, regions: [] } }));
|
|
60
|
+
expect(gl.drawElements).not.toHaveBeenCalled();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('returns early without drawing when particleCount is 0', () => {
|
|
64
|
+
const { state, gl } = createGlState();
|
|
65
|
+
drawGlParticleEmitter2D(state, makeParticleEmitter2DNode({ particleCount: 0 }));
|
|
66
|
+
expect(gl.drawElementsInstanced).not.toHaveBeenCalled();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('draws all live particles in a single instanced draw call', () => {
|
|
70
|
+
const { state, gl } = createGlState();
|
|
71
|
+
drawGlParticleEmitter2D(
|
|
72
|
+
state,
|
|
73
|
+
makeParticleEmitter2DNode({
|
|
74
|
+
particleCount: 3,
|
|
75
|
+
ids: new Uint16Array([0, 0, 0]),
|
|
76
|
+
transforms: new Float32Array([0, 0, 0, 1, 10, 10, 0, 1, 20, 20, 0, 1]),
|
|
77
|
+
alphas: new Float32Array([1, 0.5, 0.25]),
|
|
78
|
+
colors: new Float32Array([1, 1, 1, 1, 1, 1, 1, 1, 1]),
|
|
79
|
+
}),
|
|
80
|
+
);
|
|
81
|
+
expect(gl.drawElementsInstanced).toHaveBeenCalledTimes(1);
|
|
82
|
+
expect(gl.drawElementsInstanced).toHaveBeenCalledWith(expect.anything(), 6, expect.anything(), 0, 3);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('skips out-of-range region ids and draws only valid particles', () => {
|
|
86
|
+
const { state, gl } = createGlState();
|
|
87
|
+
drawGlParticleEmitter2D(
|
|
88
|
+
state,
|
|
89
|
+
makeParticleEmitter2DNode({
|
|
90
|
+
particleCount: 3,
|
|
91
|
+
ids: new Uint16Array([0, 99, 0]),
|
|
92
|
+
transforms: new Float32Array([0, 0, 0, 1, 10, 10, 0, 1, 20, 20, 0, 1]),
|
|
93
|
+
alphas: new Float32Array([1, 1, 1]),
|
|
94
|
+
colors: new Float32Array([1, 1, 1, 1, 1, 1, 1, 1, 1]),
|
|
95
|
+
}),
|
|
96
|
+
);
|
|
97
|
+
expect(gl.drawElementsInstanced).toHaveBeenCalledWith(expect.anything(), 6, expect.anything(), 0, 2);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('uploads the straight-alpha flag for a compressed particle atlas', () => {
|
|
101
|
+
const { state, gl } = createGlState();
|
|
102
|
+
const image = {
|
|
103
|
+
alphaType: 'straight',
|
|
104
|
+
compressed: { container: {}, payload: new Uint8Array() },
|
|
105
|
+
data: null,
|
|
106
|
+
height: 4,
|
|
107
|
+
source: null,
|
|
108
|
+
version: 1,
|
|
109
|
+
width: 4,
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
drawGlParticleEmitter2D(
|
|
113
|
+
state,
|
|
114
|
+
makeParticleEmitter2DNode({
|
|
115
|
+
atlas: { image, regions: [{ id: 0, x: 0, y: 0, width: 4, height: 4 }] },
|
|
116
|
+
}),
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
const shader = getGlRenderStateRuntime(state).particleShader!;
|
|
120
|
+
expect(gl.uniform1i).toHaveBeenCalledWith(shader.locStraightTextureAlpha, 1);
|
|
121
|
+
});
|
|
122
|
+
});
|