@flighthq/render-gl 0.3.0 → 0.3.1-next.1050.9114695
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/enableGlRenderStateGuards.d.ts.map +1 -1
- package/dist/enableGlRenderStateGuards.js +12 -3
- package/dist/enableGlRenderStateGuards.js.map +1 -1
- package/dist/explainGlTextureResolution.d.ts.map +1 -1
- package/dist/explainGlTextureResolution.js +4 -1
- package/dist/explainGlTextureResolution.js.map +1 -1
- package/dist/glCompressedTexture.d.ts.map +1 -1
- package/dist/glCompressedTexture.js +13 -2
- package/dist/glCompressedTexture.js.map +1 -1
- package/dist/glDraw.d.ts.map +1 -1
- package/dist/glDraw.js +21 -14
- package/dist/glDraw.js.map +1 -1
- package/dist/glFullscreenPass.d.ts.map +1 -1
- package/dist/glFullscreenPass.js +2 -1
- package/dist/glFullscreenPass.js.map +1 -1
- package/dist/glMaterialRegistry.d.ts.map +1 -1
- package/dist/glMaterialRegistry.js +13 -8
- package/dist/glMaterialRegistry.js.map +1 -1
- package/dist/glRenderState.d.ts +3 -1
- package/dist/glRenderState.d.ts.map +1 -1
- package/dist/glRenderState.js +55 -37
- package/dist/glRenderState.js.map +1 -1
- package/dist/glRenderStateBracket.d.ts.map +1 -1
- package/dist/glRenderStateBracket.js +54 -0
- package/dist/glRenderStateBracket.js.map +1 -1
- package/dist/glRenderTarget.js.map +1 -1
- package/dist/glShader.d.ts +2 -1
- package/dist/glShader.d.ts.map +1 -1
- package/dist/glShader.js +10 -0
- package/dist/glShader.js.map +1 -1
- package/dist/glShaderBinding.d.ts.map +1 -1
- package/dist/glShaderBinding.js +2 -1
- package/dist/glShaderBinding.js.map +1 -1
- package/dist/glSkinPaletteTexture.d.ts +1 -1
- package/dist/glSkinPaletteTexture.d.ts.map +1 -1
- package/dist/glSkinPaletteTexture.js +6 -2
- package/dist/glSkinPaletteTexture.js.map +1 -1
- package/dist/glTextureResolver.d.ts.map +1 -1
- package/dist/glTextureResolver.js +13 -11
- package/dist/glTextureResolver.js.map +1 -1
- package/package.json +11 -10
- package/src/enableGlRenderStateGuards.test.ts +10 -0
- package/src/glCompressedTexture.test.ts +29 -12
- package/src/glDraw.test.ts +25 -6
- package/src/glMaterialRegistry.test.ts +16 -0
- package/src/glRenderState.test.ts +338 -29
- package/src/glRenderStateBracket.test.ts +170 -0
- package/src/glShader.test.ts +55 -1
- package/src/glTestHelper.test.ts +51 -0
- package/src/glTextureResolver.test.ts +11 -6
|
@@ -23,6 +23,9 @@ type TestGlState = {
|
|
|
23
23
|
blendSrcRgb: number;
|
|
24
24
|
cullFace: boolean;
|
|
25
25
|
cullFaceMode: number;
|
|
26
|
+
frontFace: number;
|
|
27
|
+
stencilTest: boolean;
|
|
28
|
+
stencilWriteMask: number;
|
|
26
29
|
currentRenderTarget: GlRenderTarget | null;
|
|
27
30
|
depthFunc: number;
|
|
28
31
|
depthMask: boolean;
|
|
@@ -58,6 +61,9 @@ const OUTER_STATE: TestGlState = {
|
|
|
58
61
|
blendSrcRgb: 1,
|
|
59
62
|
cullFace: false,
|
|
60
63
|
cullFaceMode: 0x0405,
|
|
64
|
+
frontFace: 0x0900, // GL_CW — a host that does not use Flight's default
|
|
65
|
+
stencilTest: true, // enabled on purpose: a fixture that starts disabled cannot tell 'restored' from 'left off'
|
|
66
|
+
stencilWriteMask: 0x0f,
|
|
61
67
|
currentRenderTarget: { name: 'outer-target' } as unknown as GlRenderTarget,
|
|
62
68
|
depthFunc: 0x0203,
|
|
63
69
|
depthMask: false,
|
|
@@ -86,6 +92,9 @@ const MIDDLE_STATE: TestGlState = {
|
|
|
86
92
|
blendSrcRgb: 1,
|
|
87
93
|
cullFace: true,
|
|
88
94
|
cullFaceMode: 0x0404,
|
|
95
|
+
frontFace: 0x0901, // GL_CCW
|
|
96
|
+
stencilTest: false,
|
|
97
|
+
stencilWriteMask: 0xff,
|
|
89
98
|
currentRenderTarget: { name: 'middle-target' } as unknown as GlRenderTarget,
|
|
90
99
|
depthFunc: 0x0201,
|
|
91
100
|
depthMask: true,
|
|
@@ -136,6 +145,57 @@ describe('popGlRenderState', () => {
|
|
|
136
145
|
expectTestGlState(fixture, OUTER_STATE);
|
|
137
146
|
});
|
|
138
147
|
|
|
148
|
+
it('restores a two-sided stencil configuration without collapsing the back face onto the front', () => {
|
|
149
|
+
const fixture = createStatefulGl();
|
|
150
|
+
const gl = fixture.gl;
|
|
151
|
+
applyTestGlState(fixture, OUTER_STATE);
|
|
152
|
+
// A host driving two-sided stencil — the case stencilOpSeparate exists for. Every back-face value
|
|
153
|
+
// differs from its front counterpart, so a restore that writes both faces from the front's saved
|
|
154
|
+
// values cannot pass by coincidence: it would report the front correct and the back overwritten.
|
|
155
|
+
gl.stencilOpSeparate(gl.FRONT, 0x1e00, 0x1e00, 0x1e01); // KEEP, KEEP, REPLACE
|
|
156
|
+
gl.stencilOpSeparate(gl.BACK, 0x1e02, 0x1e03, 0x150a); // INCR, DECR, INVERT
|
|
157
|
+
gl.stencilFuncSeparate(gl.FRONT, 0x0201, 1, 0xf0); // LESS
|
|
158
|
+
gl.stencilFuncSeparate(gl.BACK, 0x0204, 2, 0x0f); // GREATER
|
|
159
|
+
gl.stencilMaskSeparate(gl.FRONT, 0xaa);
|
|
160
|
+
gl.stencilMaskSeparate(gl.BACK, 0x55);
|
|
161
|
+
const host = new Map(fixture.parameters);
|
|
162
|
+
pushGlRenderState(fixture.state);
|
|
163
|
+
|
|
164
|
+
// What Flight's own 2D clip pass writes: separate wrap ops per face, then a symmetric gate.
|
|
165
|
+
gl.stencilOpSeparate(gl.FRONT, 0x1e00, 0x1e00, 0x8507); // INCR_WRAP
|
|
166
|
+
gl.stencilOpSeparate(gl.BACK, 0x1e00, 0x1e00, 0x8508); // DECR_WRAP
|
|
167
|
+
gl.stencilFunc(0x0207, 0, 0xff); // ALWAYS, both faces
|
|
168
|
+
gl.stencilMask(0xff);
|
|
169
|
+
popGlRenderState(fixture.state);
|
|
170
|
+
|
|
171
|
+
for (const parameter of TWO_SIDED_STENCIL_PARAMETERS) {
|
|
172
|
+
expect([parameter, fixture.parameters.get(gl[parameter])]).toEqual([parameter, host.get(gl[parameter])]);
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('restores colour-write, clear-colour, and upload-premultiply state Flight leaves behind', () => {
|
|
177
|
+
const fixture = createStatefulGl();
|
|
178
|
+
const gl = fixture.gl;
|
|
179
|
+
applyTestGlState(fixture, OUTER_STATE);
|
|
180
|
+
// A host whose own settings all differ from both the GL defaults and from what Flight will set,
|
|
181
|
+
// so neither 'never restored' nor 'reset to the default' can pass as restoration.
|
|
182
|
+
gl.colorMask(true, false, true, false);
|
|
183
|
+
gl.clearColor(0.25, 0.5, 0.75, 1);
|
|
184
|
+
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
|
|
185
|
+
pushGlRenderState(fixture.state);
|
|
186
|
+
|
|
187
|
+
// What Flight's own passes do: mask colour off while rasterizing clip coverage, clear a target,
|
|
188
|
+
// and premultiply a texture upload. None of these puts the previous value back.
|
|
189
|
+
gl.colorMask(false, false, false, false);
|
|
190
|
+
gl.clearColor(0, 0, 0, 0);
|
|
191
|
+
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);
|
|
192
|
+
popGlRenderState(fixture.state);
|
|
193
|
+
|
|
194
|
+
expect(fixture.parameters.get(gl.COLOR_WRITEMASK)).toEqual([true, false, true, false]);
|
|
195
|
+
expect(fixture.parameters.get(gl.COLOR_CLEAR_VALUE)).toEqual([0.25, 0.5, 0.75, 1]);
|
|
196
|
+
expect(fixture.parameters.get(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL)).toBe(false);
|
|
197
|
+
});
|
|
198
|
+
|
|
139
199
|
it('restores a non-active texture unit binding', () => {
|
|
140
200
|
const fixture = createStatefulGl();
|
|
141
201
|
applyTestGlState(fixture, OUTER_STATE);
|
|
@@ -207,6 +267,9 @@ function applyTestGlState(fixture: StatefulGl, values: Readonly<TestGlState>): v
|
|
|
207
267
|
gl.depthFunc(values.depthFunc);
|
|
208
268
|
setCapability(gl, gl.CULL_FACE, values.cullFace);
|
|
209
269
|
gl.cullFace(values.cullFaceMode);
|
|
270
|
+
gl.frontFace(values.frontFace);
|
|
271
|
+
setCapability(gl, gl.STENCIL_TEST, values.stencilTest);
|
|
272
|
+
gl.stencilMask(values.stencilWriteMask);
|
|
210
273
|
setCapability(gl, gl.BLEND, values.blend);
|
|
211
274
|
gl.blendFuncSeparate(values.blendSrcRgb, values.blendDstRgb, values.blendSrcAlpha, values.blendDstAlpha);
|
|
212
275
|
gl.blendEquationSeparate(values.blendEquationRgb, values.blendEquationAlpha);
|
|
@@ -244,6 +307,27 @@ function createStatefulGl(): StatefulGl & ReturnType<typeof createGlState> {
|
|
|
244
307
|
DEPTH_FUNC: 0x0b74,
|
|
245
308
|
DEPTH_WRITEMASK: 0x0b72,
|
|
246
309
|
FRAMEBUFFER_BINDING: 0x8ca6,
|
|
310
|
+
FRONT_FACE: 0x0b46,
|
|
311
|
+
BACK: 0x0405,
|
|
312
|
+
COLOR_CLEAR_VALUE: 0x0c22,
|
|
313
|
+
COLOR_WRITEMASK: 0x0c23,
|
|
314
|
+
UNPACK_PREMULTIPLY_ALPHA_WEBGL: 0x9241,
|
|
315
|
+
FRONT: 0x0404,
|
|
316
|
+
STENCIL_BACK_FAIL: 0x8801,
|
|
317
|
+
STENCIL_BACK_FUNC: 0x8800,
|
|
318
|
+
STENCIL_BACK_PASS_DEPTH_FAIL: 0x8802,
|
|
319
|
+
STENCIL_BACK_PASS_DEPTH_PASS: 0x8803,
|
|
320
|
+
STENCIL_BACK_REF: 0x8ca3,
|
|
321
|
+
STENCIL_BACK_VALUE_MASK: 0x8ca4,
|
|
322
|
+
STENCIL_BACK_WRITEMASK: 0x8ca5,
|
|
323
|
+
STENCIL_FAIL: 0x0b94,
|
|
324
|
+
STENCIL_FUNC: 0x0b92,
|
|
325
|
+
STENCIL_PASS_DEPTH_FAIL: 0x0b95,
|
|
326
|
+
STENCIL_PASS_DEPTH_PASS: 0x0b96,
|
|
327
|
+
STENCIL_REF: 0x0b97,
|
|
328
|
+
STENCIL_TEST: 0x0b90,
|
|
329
|
+
STENCIL_VALUE_MASK: 0x0b93,
|
|
330
|
+
STENCIL_WRITEMASK: 0x0b98,
|
|
247
331
|
SCISSOR_BOX: 0x0c10,
|
|
248
332
|
TEXTURE_BINDING_2D: 0x8069,
|
|
249
333
|
VERTEX_ARRAY_BINDING: 0x85b5,
|
|
@@ -271,6 +355,65 @@ function createStatefulGl(): StatefulGl & ReturnType<typeof createGlState> {
|
|
|
271
355
|
(gl.cullFace as ReturnType<typeof vi.fn>).mockImplementation((value: number) =>
|
|
272
356
|
parameters.set(gl.CULL_FACE_MODE, value),
|
|
273
357
|
);
|
|
358
|
+
(gl.frontFace as ReturnType<typeof vi.fn>).mockImplementation((value: number) =>
|
|
359
|
+
parameters.set(gl.FRONT_FACE, value),
|
|
360
|
+
);
|
|
361
|
+
(gl.colorMask as ReturnType<typeof vi.fn>).mockImplementation((r: boolean, g: boolean, b: boolean, a: boolean) =>
|
|
362
|
+
parameters.set(gl.COLOR_WRITEMASK, [r, g, b, a]),
|
|
363
|
+
);
|
|
364
|
+
(gl.clearColor as ReturnType<typeof vi.fn>).mockImplementation((r: number, g: number, b: number, a: number) =>
|
|
365
|
+
parameters.set(gl.COLOR_CLEAR_VALUE, [r, g, b, a]),
|
|
366
|
+
);
|
|
367
|
+
(gl.pixelStorei as ReturnType<typeof vi.fn>).mockImplementation((parameter: number, value: unknown) =>
|
|
368
|
+
parameters.set(parameter, value),
|
|
369
|
+
);
|
|
370
|
+
// Stencil is two-faced state: the unsuffixed setters write FRONT_AND_BACK while the Separate ones
|
|
371
|
+
// write only the named face. That rule was measured against a real WebGL2 context rather than assumed
|
|
372
|
+
// here — a fake cannot be the oracle for the API it fakes. `null` is the unsuffixed, both-faces form.
|
|
373
|
+
const writeStencilMask = (face: number | null, value: number): void => {
|
|
374
|
+
if (face !== gl.BACK) parameters.set(gl.STENCIL_WRITEMASK, value);
|
|
375
|
+
if (face !== gl.FRONT) parameters.set(gl.STENCIL_BACK_WRITEMASK, value);
|
|
376
|
+
};
|
|
377
|
+
const writeStencilFunc = (face: number | null, func: number, ref: number, mask: number): void => {
|
|
378
|
+
if (face !== gl.BACK) {
|
|
379
|
+
parameters.set(gl.STENCIL_FUNC, func);
|
|
380
|
+
parameters.set(gl.STENCIL_REF, ref);
|
|
381
|
+
parameters.set(gl.STENCIL_VALUE_MASK, mask);
|
|
382
|
+
}
|
|
383
|
+
if (face !== gl.FRONT) {
|
|
384
|
+
parameters.set(gl.STENCIL_BACK_FUNC, func);
|
|
385
|
+
parameters.set(gl.STENCIL_BACK_REF, ref);
|
|
386
|
+
parameters.set(gl.STENCIL_BACK_VALUE_MASK, mask);
|
|
387
|
+
}
|
|
388
|
+
};
|
|
389
|
+
const writeStencilOp = (face: number | null, fail: number, depthFail: number, depthPass: number): void => {
|
|
390
|
+
if (face !== gl.BACK) {
|
|
391
|
+
parameters.set(gl.STENCIL_FAIL, fail);
|
|
392
|
+
parameters.set(gl.STENCIL_PASS_DEPTH_FAIL, depthFail);
|
|
393
|
+
parameters.set(gl.STENCIL_PASS_DEPTH_PASS, depthPass);
|
|
394
|
+
}
|
|
395
|
+
if (face !== gl.FRONT) {
|
|
396
|
+
parameters.set(gl.STENCIL_BACK_FAIL, fail);
|
|
397
|
+
parameters.set(gl.STENCIL_BACK_PASS_DEPTH_FAIL, depthFail);
|
|
398
|
+
parameters.set(gl.STENCIL_BACK_PASS_DEPTH_PASS, depthPass);
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
(gl.stencilMask as ReturnType<typeof vi.fn>).mockImplementation((value: number) => writeStencilMask(null, value));
|
|
402
|
+
(gl.stencilMaskSeparate as ReturnType<typeof vi.fn>).mockImplementation((face: number, value: number) =>
|
|
403
|
+
writeStencilMask(face, value),
|
|
404
|
+
);
|
|
405
|
+
(gl.stencilFunc as ReturnType<typeof vi.fn>).mockImplementation((func: number, ref: number, mask: number) =>
|
|
406
|
+
writeStencilFunc(null, func, ref, mask),
|
|
407
|
+
);
|
|
408
|
+
(gl.stencilFuncSeparate as ReturnType<typeof vi.fn>).mockImplementation(
|
|
409
|
+
(face: number, func: number, ref: number, mask: number) => writeStencilFunc(face, func, ref, mask),
|
|
410
|
+
);
|
|
411
|
+
(gl.stencilOp as ReturnType<typeof vi.fn>).mockImplementation((fail: number, dFail: number, dPass: number) =>
|
|
412
|
+
writeStencilOp(null, fail, dFail, dPass),
|
|
413
|
+
);
|
|
414
|
+
(gl.stencilOpSeparate as ReturnType<typeof vi.fn>).mockImplementation(
|
|
415
|
+
(face: number, fail: number, dFail: number, dPass: number) => writeStencilOp(face, fail, dFail, dPass),
|
|
416
|
+
);
|
|
274
417
|
(gl.blendFuncSeparate as ReturnType<typeof vi.fn>).mockImplementation(
|
|
275
418
|
(srcRgb: number, dstRgb: number, srcAlpha: number, dstAlpha: number) => {
|
|
276
419
|
parameters.set(gl.BLEND_SRC_RGB, srcRgb);
|
|
@@ -315,6 +458,14 @@ function expectTestGlState(fixture: StatefulGl, expected: Readonly<TestGlState>)
|
|
|
315
458
|
expect(parameters.get(gl.DEPTH_FUNC)).toBe(expected.depthFunc);
|
|
316
459
|
expect(gl.isEnabled(gl.CULL_FACE)).toBe(expected.cullFace);
|
|
317
460
|
expect(parameters.get(gl.CULL_FACE_MODE)).toBe(expected.cullFaceMode);
|
|
461
|
+
// The 3D mesh path picks a front face per draw from the model determinant, so this is state Flight
|
|
462
|
+
// mutates and must hand back untouched — a host set to CW must not get CCW returned.
|
|
463
|
+
expect(parameters.get(gl.FRONT_FACE)).toBe(expected.frontFace);
|
|
464
|
+
// Flight's 2D clip system rasterizes stencil masks; its internal restore is scoped to Flight's own
|
|
465
|
+
// nesting and disables the test outright when Flight had no clip, so the host-facing preservation
|
|
466
|
+
// has to happen here. The outer fixture starts ENABLED so 'restored' is distinguishable from 'left off'.
|
|
467
|
+
expect(gl.isEnabled(gl.STENCIL_TEST)).toBe(expected.stencilTest);
|
|
468
|
+
expect(parameters.get(gl.STENCIL_WRITEMASK)).toBe(expected.stencilWriteMask);
|
|
318
469
|
expect(gl.isEnabled(gl.BLEND)).toBe(expected.blend);
|
|
319
470
|
expect(parameters.get(gl.BLEND_SRC_RGB)).toBe(expected.blendSrcRgb);
|
|
320
471
|
expect(parameters.get(gl.BLEND_DST_RGB)).toBe(expected.blendDstRgb);
|
|
@@ -347,3 +498,22 @@ function setCapability(gl: WebGL2RenderingContext, capability: number, enabled:
|
|
|
347
498
|
gl.disable(capability);
|
|
348
499
|
}
|
|
349
500
|
}
|
|
501
|
+
|
|
502
|
+
// Both faces of every stencil parameter the bracket saves. Naming them here rather than asserting a
|
|
503
|
+
// handful keeps the list checkable against the saved-state fields by eye.
|
|
504
|
+
const TWO_SIDED_STENCIL_PARAMETERS = [
|
|
505
|
+
'STENCIL_FAIL',
|
|
506
|
+
'STENCIL_BACK_FAIL',
|
|
507
|
+
'STENCIL_PASS_DEPTH_FAIL',
|
|
508
|
+
'STENCIL_BACK_PASS_DEPTH_FAIL',
|
|
509
|
+
'STENCIL_PASS_DEPTH_PASS',
|
|
510
|
+
'STENCIL_BACK_PASS_DEPTH_PASS',
|
|
511
|
+
'STENCIL_FUNC',
|
|
512
|
+
'STENCIL_BACK_FUNC',
|
|
513
|
+
'STENCIL_REF',
|
|
514
|
+
'STENCIL_BACK_REF',
|
|
515
|
+
'STENCIL_VALUE_MASK',
|
|
516
|
+
'STENCIL_BACK_VALUE_MASK',
|
|
517
|
+
'STENCIL_WRITEMASK',
|
|
518
|
+
'STENCIL_BACK_WRITEMASK',
|
|
519
|
+
] as const;
|
package/src/glShader.test.ts
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import { EntityRuntimeKey } from '@flighthq/types/contract';
|
|
2
2
|
|
|
3
|
+
import { getGlRenderStateRuntime } from './glRenderState';
|
|
3
4
|
import {
|
|
4
5
|
compileDefaultGlProgram,
|
|
5
6
|
compileGlBitmapProgram,
|
|
6
7
|
createDefaultGlBitmapShader,
|
|
7
8
|
createGlBitmapShader,
|
|
9
|
+
ensureDefaultGlBitmapShader,
|
|
8
10
|
setGlAttributes,
|
|
9
11
|
setGlBaseUniforms,
|
|
10
12
|
setGlMatrixFromTransform,
|
|
11
13
|
setGlMatrixFromValues,
|
|
12
14
|
} from './glShader';
|
|
13
|
-
import { makeGL, makeShaderLoc } from './glTestHelper';
|
|
15
|
+
import { createGlState, makeGL, makeShaderLoc } from './glTestHelper';
|
|
14
16
|
|
|
15
17
|
describe('compileDefaultGlProgram', () => {
|
|
16
18
|
it('returns shader locations with all required fields', () => {
|
|
@@ -150,6 +152,30 @@ describe('createGlBitmapShader', () => {
|
|
|
150
152
|
});
|
|
151
153
|
});
|
|
152
154
|
|
|
155
|
+
describe('ensureDefaultGlBitmapShader', () => {
|
|
156
|
+
it('compiles and caches the default shader on first call', () => {
|
|
157
|
+
const { state } = createGlState();
|
|
158
|
+
const runtime = getGlRenderStateRuntime(state);
|
|
159
|
+
runtime.defaultBitmapShader = null;
|
|
160
|
+
runtime.shaderLoc = null;
|
|
161
|
+
const shader = ensureDefaultGlBitmapShader(state);
|
|
162
|
+
expect(shader).toBeDefined();
|
|
163
|
+
expect(shader.program).toBeDefined();
|
|
164
|
+
expect(runtime.defaultBitmapShader).toBe(shader);
|
|
165
|
+
expect(runtime.shaderLoc).not.toBeNull();
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('returns the cached shader on subsequent calls', () => {
|
|
169
|
+
const { state } = createGlState();
|
|
170
|
+
const runtime = getGlRenderStateRuntime(state);
|
|
171
|
+
runtime.defaultBitmapShader = null;
|
|
172
|
+
runtime.shaderLoc = null;
|
|
173
|
+
const first = ensureDefaultGlBitmapShader(state);
|
|
174
|
+
const second = ensureDefaultGlBitmapShader(state);
|
|
175
|
+
expect(second).toBe(first);
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
153
179
|
describe('setGlAttributes', () => {
|
|
154
180
|
it('enables the position vertex attrib array', () => {
|
|
155
181
|
const gl = makeGL();
|
|
@@ -233,6 +259,34 @@ describe('setGlBaseUniforms', () => {
|
|
|
233
259
|
});
|
|
234
260
|
|
|
235
261
|
describe('setGlMatrixFromTransform', () => {
|
|
262
|
+
it('projects the quad corner order to a clockwise triangle, which is why 2D draws need culling off', () => {
|
|
263
|
+
// Measured in a real WebGL2 context, not argued: with CULL_FACE enabled and the CCW default, this
|
|
264
|
+
// quad renders 0 lit pixels where it otherwise renders 2304 — a single-sided 3D draw earlier in the
|
|
265
|
+
// frame therefore erases all 2D content. `renderGlScene2D` disables CULL_FACE because of THIS, and
|
|
266
|
+
// without an assertion the reason lives only in a comment: someone who later made the winding CCW
|
|
267
|
+
// would find the disable looking superfluous. The corner order below mirrors the quadVertexData
|
|
268
|
+
// fill in glDraw.ts and must be changed with it.
|
|
269
|
+
const gl = makeGL();
|
|
270
|
+
const m = new Float32Array(9);
|
|
271
|
+
const canvas = document.createElement('canvas');
|
|
272
|
+
canvas.width = 200;
|
|
273
|
+
canvas.height = 100;
|
|
274
|
+
setGlMatrixFromTransform(gl, makeShaderLoc(), m, { a: 1, b: 0, c: 0, d: 1, tx: 0, ty: 0 }, canvas);
|
|
275
|
+
|
|
276
|
+
const project = (x: number, y: number): [number, number] => [
|
|
277
|
+
m[0] * x + m[3] * y + m[6],
|
|
278
|
+
m[1] * x + m[4] * y + m[7],
|
|
279
|
+
];
|
|
280
|
+
const [x0, y0, x1, y1] = [20, 30, 120, 80];
|
|
281
|
+
const [ax, ay] = project(x0, y0);
|
|
282
|
+
const [bx, by] = project(x1, y0);
|
|
283
|
+
const [cx, cy] = project(x1, y1);
|
|
284
|
+
|
|
285
|
+
// Signed area of the first indexed triangle (indices [0, 1, 2]); negative is clockwise.
|
|
286
|
+
const signedArea = (bx - ax) * (cy - ay) - (by - ay) * (cx - ax);
|
|
287
|
+
expect(signedArea).toBeLessThan(0);
|
|
288
|
+
});
|
|
289
|
+
|
|
236
290
|
it('computes matrix scaled by canvas dimensions for identity transform', () => {
|
|
237
291
|
const gl = makeGL();
|
|
238
292
|
const loc = makeShaderLoc();
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { makeGL } from './glTestHelper';
|
|
2
|
+
|
|
3
|
+
describe('makeGL', () => {
|
|
4
|
+
it('keeps draw validation outside a test-installed mock implementation', () => {
|
|
5
|
+
const gl = makeGL();
|
|
6
|
+
const draw = vi.fn();
|
|
7
|
+
(gl.drawElements as ReturnType<typeof vi.fn>).mockImplementation(draw);
|
|
8
|
+
|
|
9
|
+
gl.drawElements(gl.TRIANGLES, 1, gl.UNSIGNED_SHORT, 1);
|
|
10
|
+
|
|
11
|
+
expect(draw).not.toHaveBeenCalled();
|
|
12
|
+
expect(gl.getError()).toBe(gl.INVALID_OPERATION);
|
|
13
|
+
|
|
14
|
+
gl.drawElements(gl.TRIANGLES, 1, gl.UNSIGNED_SHORT, 0);
|
|
15
|
+
expect(draw).toHaveBeenCalledOnce();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('sets and consumes the first GL draw error without throwing', () => {
|
|
19
|
+
const gl = makeGL();
|
|
20
|
+
|
|
21
|
+
expect(() => gl.drawElements(gl.TRIANGLES, -1, gl.UNSIGNED_SHORT, 0)).not.toThrow();
|
|
22
|
+
gl.drawElements(0xffff, 1, gl.UNSIGNED_SHORT, 0);
|
|
23
|
+
|
|
24
|
+
expect(gl.drawElements).not.toHaveBeenCalled();
|
|
25
|
+
expect(gl.getError()).toBe(gl.INVALID_VALUE);
|
|
26
|
+
expect(gl.getError()).toBe(gl.NO_ERROR);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('reports a type-misaligned index offset as INVALID_OPERATION', () => {
|
|
30
|
+
const gl = makeGL();
|
|
31
|
+
|
|
32
|
+
gl.drawElements(gl.TRIANGLES, 1, gl.UNSIGNED_SHORT, 1);
|
|
33
|
+
|
|
34
|
+
expect(gl.getError()).toBe(gl.INVALID_OPERATION);
|
|
35
|
+
expect(gl.getError()).toBe(gl.NO_ERROR);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('validates non-indexed and instanced draw entry points', () => {
|
|
39
|
+
const gl = makeGL();
|
|
40
|
+
|
|
41
|
+
gl.drawArrays(0xffff, 0, 1);
|
|
42
|
+
expect(gl.getError()).toBe(gl.INVALID_ENUM);
|
|
43
|
+
|
|
44
|
+
gl.drawArraysInstanced(gl.TRIANGLES, 0, 1, -1);
|
|
45
|
+
expect(gl.getError()).toBe(gl.INVALID_VALUE);
|
|
46
|
+
|
|
47
|
+
gl.drawElementsInstanced(gl.TRIANGLES, 1, gl.FLOAT, 0, 1);
|
|
48
|
+
expect(gl.getError()).toBe(gl.INVALID_ENUM);
|
|
49
|
+
expect(gl.getError()).toBe(gl.NO_ERROR);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createEntity } from '@flighthq/entity/contract';
|
|
2
|
+
import { getRegistryTableKeys } from '@flighthq/registry/contract';
|
|
2
3
|
import type { Image, RenderTexture, TextureLike, TextureSource } from '@flighthq/types/contract';
|
|
3
4
|
import {
|
|
4
5
|
BitmapTextureSourceKind,
|
|
@@ -80,11 +81,17 @@ function textureWithTarget(): TextureLike {
|
|
|
80
81
|
return texture;
|
|
81
82
|
}
|
|
82
83
|
|
|
84
|
+
function registeredTextureSourceKinds(state: Parameters<typeof getGlRenderStateRuntime>[0]): string[] {
|
|
85
|
+
const kinds: string[] = [];
|
|
86
|
+
getRegistryTableKeys(kinds, getGlRenderStateRuntime(state).registries.textureResolvers);
|
|
87
|
+
return kinds;
|
|
88
|
+
}
|
|
89
|
+
|
|
83
90
|
describe('registerGlBitmapTextureResolver', () => {
|
|
84
91
|
it('registers only the Bitmap source key', () => {
|
|
85
92
|
const { state } = createGlState();
|
|
86
93
|
registerGlBitmapTextureResolver(state);
|
|
87
|
-
expect(
|
|
94
|
+
expect(registeredTextureSourceKinds(state)).toEqual([BitmapTextureSourceKind]);
|
|
88
95
|
});
|
|
89
96
|
});
|
|
90
97
|
|
|
@@ -92,9 +99,7 @@ describe('registerGlCompressedImageTextureResolver', () => {
|
|
|
92
99
|
it('registers only the CompressedImage source key', () => {
|
|
93
100
|
const { state } = createGlState();
|
|
94
101
|
registerGlCompressedImageTextureResolver(state);
|
|
95
|
-
expect(
|
|
96
|
-
CompressedImageTextureSourceKind,
|
|
97
|
-
]);
|
|
102
|
+
expect(registeredTextureSourceKinds(state)).toEqual([CompressedImageTextureSourceKind]);
|
|
98
103
|
});
|
|
99
104
|
});
|
|
100
105
|
|
|
@@ -225,7 +230,7 @@ describe('registerGlTextureResolver', () => {
|
|
|
225
230
|
|
|
226
231
|
registerGlTextureResolver(a, sourceKind, second);
|
|
227
232
|
expect(resolveGlTexture(a, texture)).toEqual({ second: true });
|
|
228
|
-
expect(getGlRenderStateRuntime(a).
|
|
233
|
+
expect(getGlRenderStateRuntime(a).registries.textureResolvers.entries.size).toBe(1);
|
|
229
234
|
|
|
230
235
|
registerGlTextureResolver(a, sourceKind, null);
|
|
231
236
|
expect(resolveGlTexture(a, texture)).toBeNull();
|
|
@@ -244,7 +249,7 @@ describe('registerStandardGlTextureResolvers', () => {
|
|
|
244
249
|
it('registers bitmap, image, and render texture sources without compressed images', () => {
|
|
245
250
|
const { state } = createGlState();
|
|
246
251
|
registerStandardGlTextureResolvers(state);
|
|
247
|
-
expect(
|
|
252
|
+
expect(registeredTextureSourceKinds(state)).toEqual([
|
|
248
253
|
BitmapTextureSourceKind,
|
|
249
254
|
ImageTextureSourceKind,
|
|
250
255
|
RenderTargetTextureSourceKind,
|