@flighthq/render-gl 0.5.1-next.1069.554a2ac → 0.5.1-next.1201.9b49dee
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/README.md +2 -2
- package/dist/contract.d.ts +1 -3
- package/dist/contract.d.ts.map +1 -1
- package/dist/contract.js +1 -3
- package/dist/contract.js.map +1 -1
- package/dist/enableGlRenderStateGuards.js +2 -2
- package/dist/enableGlRenderStateGuards.js.map +1 -1
- package/dist/glCubeRenderTarget.d.ts +5 -5
- package/dist/glCubeRenderTarget.d.ts.map +1 -1
- package/dist/glCubeRenderTarget.js +16 -13
- package/dist/glCubeRenderTarget.js.map +1 -1
- package/dist/glFullscreenPass.d.ts +4 -3
- package/dist/glFullscreenPass.d.ts.map +1 -1
- package/dist/glFullscreenPass.js +152 -23
- package/dist/glFullscreenPass.js.map +1 -1
- package/dist/glLinearToSrgbPass.d.ts +2 -2
- package/dist/glLinearToSrgbPass.d.ts.map +1 -1
- package/dist/glLinearToSrgbPass.js.map +1 -1
- package/dist/glPresentRenderTarget.d.ts +2 -2
- package/dist/glPresentRenderTarget.d.ts.map +1 -1
- package/dist/glPresentRenderTarget.js.map +1 -1
- package/dist/glReadback.d.ts +2 -2
- package/dist/glReadback.d.ts.map +1 -1
- package/dist/glReadback.js +1 -1
- package/dist/glReadback.js.map +1 -1
- package/dist/glRenderPass.d.ts +6 -3
- package/dist/glRenderPass.d.ts.map +1 -1
- package/dist/glRenderPass.js +105 -86
- package/dist/glRenderPass.js.map +1 -1
- package/dist/glRenderState.d.ts +1 -2
- package/dist/glRenderState.d.ts.map +1 -1
- package/dist/glRenderState.js +53 -46
- package/dist/glRenderState.js.map +1 -1
- package/dist/glRenderTarget.d.ts +17 -16
- package/dist/glRenderTarget.d.ts.map +1 -1
- package/dist/glRenderTarget.js +38 -26
- package/dist/glRenderTarget.js.map +1 -1
- package/dist/glRenderTargetPool.d.ts +9 -9
- package/dist/glRenderTargetPool.d.ts.map +1 -1
- package/dist/glRenderTargetPool.js +10 -12
- package/dist/glRenderTargetPool.js.map +1 -1
- package/dist/glRenderTexture.d.ts +2 -2
- package/dist/glRenderTexture.d.ts.map +1 -1
- package/dist/glRenderTexture.js +11 -13
- package/dist/glRenderTexture.js.map +1 -1
- package/dist/glRenderTexturePool.js +3 -5
- package/dist/glRenderTexturePool.js.map +1 -1
- package/dist/glSkinPaletteTexture.js +8 -2
- package/dist/glSkinPaletteTexture.js.map +1 -1
- package/dist/glTestHelper.d.ts +0 -1
- package/dist/glTestHelper.d.ts.map +1 -1
- package/dist/glTestHelper.js +0 -1
- package/dist/glTestHelper.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +11 -11
- package/src/enableGlRenderStateGuards.test.ts +2 -4
- package/src/glContext.test.ts +3 -52
- package/src/glCubeRenderTarget.test.ts +10 -8
- package/src/glDraw.test.ts +4 -16
- package/src/glFullscreenPass.test.ts +145 -41
- package/src/glLinearToSrgbPass.test.ts +8 -5
- package/src/glPresentRenderTarget.test.ts +3 -5
- package/src/glReadback.test.ts +3 -3
- package/src/glRenderPass.test.ts +228 -55
- package/src/glRenderState.test.ts +106 -165
- package/src/glRenderTarget.test.ts +134 -99
- package/src/glRenderTargetPool.test.ts +69 -76
- package/src/glRenderTexture.test.ts +58 -31
- package/src/glSkinPaletteTexture.test.ts +9 -0
- package/src/glTextureResolver.test.ts +5 -1
- package/dist/glBackground.d.ts +0 -3
- package/dist/glBackground.d.ts.map +0 -1
- package/dist/glBackground.js +0 -19
- package/dist/glBackground.js.map +0 -1
- package/dist/glContext.d.ts +0 -3
- package/dist/glContext.d.ts.map +0 -1
- package/dist/glContext.js +0 -16
- package/dist/glContext.js.map +0 -1
- package/src/glBackground.test.ts +0 -68
package/src/glRenderPass.test.ts
CHANGED
|
@@ -2,16 +2,30 @@ import { allocateEntity, finishEntity } from '@flighthq/entity/contract';
|
|
|
2
2
|
import { createMatrix } from '@flighthq/geometry/contract';
|
|
3
3
|
import { getOrCreateRenderProxy2D } from '@flighthq/render/contract';
|
|
4
4
|
import { createDisplayObject } from '@flighthq/scene2d/contract';
|
|
5
|
-
import type {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
import type {
|
|
6
|
+
GlContext,
|
|
7
|
+
GlRenderPass,
|
|
8
|
+
GlRenderTarget,
|
|
9
|
+
GlTextureRenderTarget,
|
|
10
|
+
Viewport,
|
|
11
|
+
} from '@flighthq/types/contract';
|
|
12
|
+
|
|
13
|
+
import { clearGlRenderTarget, clearGlRenderTargetAttachments, fillGlRect } from './glFullscreenPass';
|
|
14
|
+
import {
|
|
15
|
+
acquireGlRenderPassHandle,
|
|
16
|
+
beginGlRenderPass,
|
|
17
|
+
endGlRenderPass,
|
|
18
|
+
getGlCurrentRenderPass,
|
|
19
|
+
releaseGlRenderPassHandle,
|
|
20
|
+
setGlRenderTransform2D,
|
|
21
|
+
} from './glRenderPass';
|
|
22
|
+
import { createGlRenderState, getGlRenderStateRuntime } from './glRenderState';
|
|
23
|
+
import { drawGlTextureRenderTargetResult } from './glRenderTarget';
|
|
10
24
|
import { createGlState } from './glTestHelper';
|
|
11
25
|
|
|
12
|
-
function makeTarget(overrides?: Partial<
|
|
26
|
+
function makeTarget(overrides?: Partial<GlTextureRenderTarget>): GlTextureRenderTarget {
|
|
13
27
|
const texture = { id: 'c0' } as unknown as WebGLTexture;
|
|
14
|
-
const out = allocateEntity<
|
|
28
|
+
const out = allocateEntity<GlTextureRenderTarget>();
|
|
15
29
|
out.requestedAxes = {
|
|
16
30
|
width: 32,
|
|
17
31
|
height: 16,
|
|
@@ -29,8 +43,6 @@ function makeTarget(overrides?: Partial<GlRenderTarget>): GlRenderTarget {
|
|
|
29
43
|
out.colorFormats = ['rgba8'];
|
|
30
44
|
out.depth = 'depth-stencil';
|
|
31
45
|
out.colorSpace = 'srgb';
|
|
32
|
-
out.clearColors = [];
|
|
33
|
-
out.clearDepth = 1;
|
|
34
46
|
out.sampleCount = 1;
|
|
35
47
|
out.framebuffer = {} as WebGLFramebuffer;
|
|
36
48
|
out.resolveFramebuffer = null;
|
|
@@ -43,57 +55,93 @@ function makeTarget(overrides?: Partial<GlRenderTarget>): GlRenderTarget {
|
|
|
43
55
|
return finishEntity(out);
|
|
44
56
|
}
|
|
45
57
|
|
|
58
|
+
describe('acquireGlRenderPassHandle', () => {
|
|
59
|
+
it('returns a pass with the given gl, state, and target', () => {
|
|
60
|
+
const { state, gl } = createGlState();
|
|
61
|
+
const target = makeTarget();
|
|
62
|
+
|
|
63
|
+
const handle = acquireGlRenderPassHandle(gl, state, target);
|
|
64
|
+
|
|
65
|
+
expect(handle.gl).toBe(gl);
|
|
66
|
+
expect(handle.state).toBe(state);
|
|
67
|
+
expect(handle.target).toBe(target);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('reuses a previously released handle from the same context', () => {
|
|
71
|
+
const { state, gl } = createGlState();
|
|
72
|
+
const target = makeTarget();
|
|
73
|
+
|
|
74
|
+
const first = acquireGlRenderPassHandle(gl, state, target);
|
|
75
|
+
releaseGlRenderPassHandle(gl, first);
|
|
76
|
+
const second = acquireGlRenderPassHandle(gl, state, target);
|
|
77
|
+
|
|
78
|
+
expect(second).toBe(first);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
46
82
|
describe('beginGlRenderPass', () => {
|
|
47
|
-
it('clears
|
|
83
|
+
it('clears color and depth when clear descriptor specifies both', () => {
|
|
48
84
|
const { state, gl } = createGlState();
|
|
49
85
|
const clearColor = vi.spyOn(gl, 'clearBufferfv');
|
|
50
86
|
const clearDepth = vi.spyOn(gl, 'clearBufferfi');
|
|
51
87
|
|
|
52
|
-
beginGlRenderPass(state, makeTarget());
|
|
88
|
+
beginGlRenderPass(state, makeTarget(), { color: [0, 0, 0, 0], depth: 1.0, stencil: 0 });
|
|
53
89
|
|
|
54
90
|
expect(clearColor).toHaveBeenCalledWith(gl.COLOR, 0, expect.anything());
|
|
55
91
|
expect(clearDepth).toHaveBeenCalledWith(gl.DEPTH_STENCIL, 0, 1, 0);
|
|
56
92
|
});
|
|
57
93
|
|
|
58
|
-
it('
|
|
94
|
+
it('preserves everything when no clear descriptor is given', () => {
|
|
59
95
|
const { state, gl } = createGlState();
|
|
60
96
|
const clearColor = vi.spyOn(gl, 'clearBufferfv');
|
|
61
97
|
const clearDepth = vi.spyOn(gl, 'clearBufferfi');
|
|
62
98
|
|
|
63
|
-
beginGlRenderPass(state, makeTarget()
|
|
99
|
+
beginGlRenderPass(state, makeTarget());
|
|
100
|
+
|
|
101
|
+
expect(clearColor).not.toHaveBeenCalled();
|
|
102
|
+
expect(clearDepth).not.toHaveBeenCalled();
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('preserves color when color is omitted from clear descriptor', () => {
|
|
106
|
+
const { state, gl } = createGlState();
|
|
107
|
+
const clearColor = vi.spyOn(gl, 'clearBufferfv');
|
|
108
|
+
const clearDepth = vi.spyOn(gl, 'clearBufferfi');
|
|
109
|
+
|
|
110
|
+
beginGlRenderPass(state, makeTarget(), { depth: 1.0, stencil: 0 });
|
|
64
111
|
|
|
65
112
|
expect(clearColor).not.toHaveBeenCalled();
|
|
66
113
|
expect(clearDepth).toHaveBeenCalled();
|
|
67
114
|
});
|
|
68
115
|
|
|
69
|
-
it('
|
|
116
|
+
it('preserves depth when depth is omitted from clear descriptor', () => {
|
|
70
117
|
const { state, gl } = createGlState();
|
|
71
118
|
const clearColor = vi.spyOn(gl, 'clearBufferfv');
|
|
72
119
|
const clearDepth = vi.spyOn(gl, 'clearBufferfi');
|
|
73
120
|
|
|
74
|
-
beginGlRenderPass(state, makeTarget(), {
|
|
121
|
+
beginGlRenderPass(state, makeTarget(), { color: [0, 0, 0, 0] });
|
|
75
122
|
|
|
76
123
|
expect(clearColor).toHaveBeenCalled();
|
|
77
124
|
expect(clearDepth).not.toHaveBeenCalled();
|
|
78
125
|
});
|
|
79
126
|
|
|
80
|
-
it('
|
|
127
|
+
it('clears per-attachment with colors array, skipping undefined entries', () => {
|
|
81
128
|
const { state, gl } = createGlState();
|
|
82
129
|
const c0 = { id: 'c0' } as unknown as WebGLTexture;
|
|
83
130
|
const c1 = { id: 'c1' } as unknown as WebGLTexture;
|
|
84
131
|
const clearColor = vi.spyOn(gl, 'clearBufferfv');
|
|
85
132
|
|
|
86
|
-
|
|
87
|
-
|
|
133
|
+
beginGlRenderPass(state, makeTarget({ textures: [c0, c1], texture: c0 }), {
|
|
134
|
+
colors: [undefined, [0, 0, 0, 0]],
|
|
135
|
+
});
|
|
88
136
|
|
|
89
137
|
expect(clearColor.mock.calls.map((c) => c[1])).toEqual([1]);
|
|
90
138
|
});
|
|
91
139
|
|
|
92
|
-
it(
|
|
93
|
-
const { state, gl } = createGlState(
|
|
140
|
+
it('writes float RGBA values directly to clearBufferfv', () => {
|
|
141
|
+
const { state, gl } = createGlState();
|
|
94
142
|
const clearColor = vi.spyOn(gl, 'clearBufferfv');
|
|
95
143
|
|
|
96
|
-
beginGlRenderPass(state, makeTarget({
|
|
144
|
+
beginGlRenderPass(state, makeTarget(), { color: [1, 0, 0, 1] });
|
|
97
145
|
|
|
98
146
|
const rgba = clearColor.mock.calls[0][2] as Float32Array;
|
|
99
147
|
expect(rgba[0]).toBeCloseTo(1);
|
|
@@ -117,7 +165,7 @@ describe('beginGlRenderPass', () => {
|
|
|
117
165
|
const target = makeTarget({ width: 100, height: 80 });
|
|
118
166
|
const viewport = makeViewport(10, 20, 30, 40);
|
|
119
167
|
|
|
120
|
-
beginGlRenderPass(state, target,
|
|
168
|
+
beginGlRenderPass(state, target, { color: [0, 0, 0, 0] }, viewport);
|
|
121
169
|
|
|
122
170
|
expect(gl.viewport).toHaveBeenLastCalledWith(10, 20, 30, 40);
|
|
123
171
|
expect(gl.enable).toHaveBeenCalledWith(gl.SCISSOR_TEST);
|
|
@@ -171,10 +219,10 @@ describe('beginGlRenderPass', () => {
|
|
|
171
219
|
const runtime = getGlRenderStateRuntime(state);
|
|
172
220
|
|
|
173
221
|
beginGlRenderPass(state, outer);
|
|
174
|
-
beginGlRenderPass(state, inner);
|
|
222
|
+
const innerPass = beginGlRenderPass(state, inner);
|
|
175
223
|
expect(runtime.currentRenderTarget).toBe(inner);
|
|
176
224
|
|
|
177
|
-
endGlRenderPass(
|
|
225
|
+
endGlRenderPass(innerPass);
|
|
178
226
|
expect(runtime.currentRenderTarget).toBe(outer);
|
|
179
227
|
expect(runtime.renderTargetViewport).toEqual({ height: 48, width: 64, x: 0, y: 0 });
|
|
180
228
|
});
|
|
@@ -188,14 +236,14 @@ describe('beginGlRenderPass', () => {
|
|
|
188
236
|
const outer = makeTarget({ width: 64, height: 48 });
|
|
189
237
|
const inner = makeTarget({ width: 32, height: 24 });
|
|
190
238
|
|
|
191
|
-
beginGlRenderPass(outerState, outer);
|
|
192
|
-
beginGlRenderPass(innerState, inner);
|
|
193
|
-
endGlRenderPass(
|
|
239
|
+
const outerPass = beginGlRenderPass(outerState, outer);
|
|
240
|
+
const innerPass = beginGlRenderPass(innerState, inner);
|
|
241
|
+
endGlRenderPass(innerPass);
|
|
194
242
|
|
|
195
243
|
expect(vi.mocked(gl.bindFramebuffer).mock.calls.at(-1)?.[1]).toBe(outer.framebuffer);
|
|
196
244
|
expect(getGlRenderStateRuntime(outerState).currentRenderTarget).toBe(outer);
|
|
197
245
|
expect(getGlRenderStateRuntime(innerState).currentRenderTarget).toBeNull();
|
|
198
|
-
endGlRenderPass(
|
|
246
|
+
endGlRenderPass(outerPass);
|
|
199
247
|
});
|
|
200
248
|
|
|
201
249
|
it('rejects a same-target contour pass across render states sharing one context', () => {
|
|
@@ -206,7 +254,7 @@ describe('beginGlRenderPass', () => {
|
|
|
206
254
|
(innerState as { gl: GlContext }).gl = gl;
|
|
207
255
|
const target = makeTarget({ width: 64, height: 48 });
|
|
208
256
|
|
|
209
|
-
beginGlRenderPass(outerState, target);
|
|
257
|
+
const outerPass = beginGlRenderPass(outerState, target);
|
|
210
258
|
getGlRenderStateRuntime(outerState).currentMaskDepth = 1;
|
|
211
259
|
|
|
212
260
|
expect(() => beginGlRenderPass(innerState, target)).toThrow(
|
|
@@ -214,7 +262,7 @@ describe('beginGlRenderPass', () => {
|
|
|
214
262
|
);
|
|
215
263
|
expect(getGlRenderStateRuntime(outerState).currentRenderTarget).toBe(target);
|
|
216
264
|
expect(getGlRenderStateRuntime(innerState).currentRenderTarget).toBeNull();
|
|
217
|
-
endGlRenderPass(
|
|
265
|
+
endGlRenderPass(outerPass);
|
|
218
266
|
});
|
|
219
267
|
});
|
|
220
268
|
|
|
@@ -224,8 +272,8 @@ describe('endGlRenderPass', () => {
|
|
|
224
272
|
const target = makeTarget();
|
|
225
273
|
const bindFramebuffer = vi.spyOn(gl, 'bindFramebuffer');
|
|
226
274
|
|
|
227
|
-
beginGlRenderPass(state, target);
|
|
228
|
-
endGlRenderPass(
|
|
275
|
+
const pass = beginGlRenderPass(state, target);
|
|
276
|
+
endGlRenderPass(pass);
|
|
229
277
|
|
|
230
278
|
// The pass began with the canvas default bound (null); ending restores it.
|
|
231
279
|
expect(bindFramebuffer.mock.calls.at(-1)?.[1]).toBe(null);
|
|
@@ -235,8 +283,8 @@ describe('endGlRenderPass', () => {
|
|
|
235
283
|
const { state } = createGlState();
|
|
236
284
|
const target = makeTarget();
|
|
237
285
|
|
|
238
|
-
beginGlRenderPass(state, target);
|
|
239
|
-
endGlRenderPass(
|
|
286
|
+
const pass = beginGlRenderPass(state, target);
|
|
287
|
+
endGlRenderPass(pass);
|
|
240
288
|
|
|
241
289
|
expect(getGlRenderStateRuntime(state).renderTargetViewport).toBeNull();
|
|
242
290
|
});
|
|
@@ -246,8 +294,8 @@ describe('endGlRenderPass', () => {
|
|
|
246
294
|
const target = makeTarget({ width: 100, height: 80 });
|
|
247
295
|
|
|
248
296
|
beginGlRenderPass(state, target, undefined, makeViewport(10, 20, 30, 40));
|
|
249
|
-
beginGlRenderPass(state, target, undefined, makeViewport(15, 25, 10, 10));
|
|
250
|
-
endGlRenderPass(
|
|
297
|
+
const innerPass = beginGlRenderPass(state, target, undefined, makeViewport(15, 25, 10, 10));
|
|
298
|
+
endGlRenderPass(innerPass);
|
|
251
299
|
|
|
252
300
|
expect(gl.viewport).toHaveBeenLastCalledWith(10, 20, 30, 40);
|
|
253
301
|
expect(gl.scissor).toHaveBeenLastCalledWith(10, 20, 30, 40);
|
|
@@ -265,12 +313,12 @@ describe('endGlRenderPass', () => {
|
|
|
265
313
|
runtime.scissorStack!.push(outerClip);
|
|
266
314
|
runtime.clipForms.push('rect');
|
|
267
315
|
|
|
268
|
-
beginGlRenderPass(state, makeTarget({ width: 100, height: 80 }));
|
|
316
|
+
const innerPass = beginGlRenderPass(state, makeTarget({ width: 100, height: 80 }));
|
|
269
317
|
expect(runtime.clipForms).toEqual([]);
|
|
270
318
|
expect(runtime.currentMaskDepth).toBe(0);
|
|
271
319
|
|
|
272
320
|
// An inner renderGlScene2D.finalize now has no enclosing entry to drain.
|
|
273
|
-
endGlRenderPass(
|
|
321
|
+
endGlRenderPass(innerPass);
|
|
274
322
|
expect(runtime.currentScissorRect).toBe(outerClip);
|
|
275
323
|
expect(runtime.scissorStack).toEqual([{ height: 40, width: 30, x: 10, y: 20 }, outerClip]);
|
|
276
324
|
expect(runtime.clipForms).toEqual(['rect']);
|
|
@@ -284,11 +332,11 @@ describe('endGlRenderPass', () => {
|
|
|
284
332
|
});
|
|
285
333
|
vi.mocked(gl.getParameter).mockImplementation((parameter) => (parameter === gl.DEPTH_WRITEMASK ? depthMask : null));
|
|
286
334
|
|
|
287
|
-
beginGlRenderPass(state, makeTarget(), {
|
|
335
|
+
beginGlRenderPass(state, makeTarget(), { color: [0, 0, 0, 0] });
|
|
288
336
|
gl.depthMask(false);
|
|
289
|
-
beginGlRenderPass(state, makeTarget());
|
|
337
|
+
const innerPass = beginGlRenderPass(state, makeTarget(), { depth: 1.0, stencil: 0 });
|
|
290
338
|
expect(depthMask).toBe(true);
|
|
291
|
-
endGlRenderPass(
|
|
339
|
+
endGlRenderPass(innerPass);
|
|
292
340
|
|
|
293
341
|
expect(depthMask).toBe(false);
|
|
294
342
|
});
|
|
@@ -299,12 +347,12 @@ describe('endGlRenderPass', () => {
|
|
|
299
347
|
runtime.clipForms = ['contour'];
|
|
300
348
|
runtime.currentMaskDepth = 1;
|
|
301
349
|
|
|
302
|
-
beginGlRenderPass(state, makeTarget());
|
|
350
|
+
const pass = beginGlRenderPass(state, makeTarget());
|
|
303
351
|
expect(runtime.clipForms).toEqual([]);
|
|
304
352
|
expect(runtime.currentMaskDepth).toBe(0);
|
|
305
353
|
expect(gl.disable).toHaveBeenCalledWith(gl.STENCIL_TEST);
|
|
306
354
|
|
|
307
|
-
endGlRenderPass(
|
|
355
|
+
endGlRenderPass(pass);
|
|
308
356
|
expect(runtime.clipForms).toEqual(['contour']);
|
|
309
357
|
expect(runtime.currentMaskDepth).toBe(1);
|
|
310
358
|
expect(gl.enable).toHaveBeenCalledWith(gl.STENCIL_TEST);
|
|
@@ -316,8 +364,8 @@ describe('endGlRenderPass', () => {
|
|
|
316
364
|
it('disables scissor when the outer partial pass returns to the canvas', () => {
|
|
317
365
|
const { state, gl } = createGlState();
|
|
318
366
|
|
|
319
|
-
beginGlRenderPass(state, makeTarget(), undefined, makeViewport(1, 2, 10, 8));
|
|
320
|
-
endGlRenderPass(
|
|
367
|
+
const pass = beginGlRenderPass(state, makeTarget(), undefined, makeViewport(1, 2, 10, 8));
|
|
368
|
+
endGlRenderPass(pass);
|
|
321
369
|
|
|
322
370
|
expect(gl.disable).toHaveBeenCalledWith(gl.SCISSOR_TEST);
|
|
323
371
|
expect(getGlRenderStateRuntime(state).currentScissorRect).toBeNull();
|
|
@@ -326,7 +374,8 @@ describe('endGlRenderPass', () => {
|
|
|
326
374
|
|
|
327
375
|
it('throws when there is no matching begin', () => {
|
|
328
376
|
const { state } = createGlState();
|
|
329
|
-
|
|
377
|
+
const mockPass = { gl: state.gl, state, target: {} as GlRenderTarget } as GlRenderPass;
|
|
378
|
+
expect(() => endGlRenderPass(mockPass)).toThrow('without a matching beginGlRenderPass');
|
|
330
379
|
});
|
|
331
380
|
});
|
|
332
381
|
|
|
@@ -334,6 +383,34 @@ function makeViewport(x: number, y: number, width: number, height: number): View
|
|
|
334
383
|
return { devicePixelRatio: 1, height, width, x, y } as Viewport;
|
|
335
384
|
}
|
|
336
385
|
|
|
386
|
+
describe('getGlCurrentRenderPass', () => {
|
|
387
|
+
it('returns null when no pass is active', () => {
|
|
388
|
+
const { state } = createGlState();
|
|
389
|
+
|
|
390
|
+
expect(getGlCurrentRenderPass(state)).toBeNull();
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
it('returns the active pass during a render pass', () => {
|
|
394
|
+
const { state } = createGlState();
|
|
395
|
+
const target = makeTarget();
|
|
396
|
+
|
|
397
|
+
const pass = beginGlRenderPass(state, target);
|
|
398
|
+
|
|
399
|
+
expect(getGlCurrentRenderPass(state)).toBe(pass);
|
|
400
|
+
endGlRenderPass(pass);
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
it('returns null after the pass ends', () => {
|
|
404
|
+
const { state } = createGlState();
|
|
405
|
+
const target = makeTarget();
|
|
406
|
+
|
|
407
|
+
const pass = beginGlRenderPass(state, target);
|
|
408
|
+
endGlRenderPass(pass);
|
|
409
|
+
|
|
410
|
+
expect(getGlCurrentRenderPass(state)).toBeNull();
|
|
411
|
+
});
|
|
412
|
+
});
|
|
413
|
+
|
|
337
414
|
describe('offscreen 2D projection basis', () => {
|
|
338
415
|
// Pins the property a downstream report claimed was broken: that an offscreen state's 2D pass projects
|
|
339
416
|
// into the BOUND TARGET's dimensions rather than the shared context's drawing buffer. Reading the
|
|
@@ -341,26 +418,26 @@ describe('offscreen 2D projection basis', () => {
|
|
|
341
418
|
// looking plausible, so this asserts the pass actually populates its explicit viewport.
|
|
342
419
|
it('derives the viewport from the target, not from the shared drawing buffer', () => {
|
|
343
420
|
const { state } = createGlState();
|
|
344
|
-
const offscreen =
|
|
421
|
+
const offscreen = createGlRenderState(state.gl, state.pipeline);
|
|
345
422
|
const runtime = getGlRenderStateRuntime(offscreen);
|
|
346
423
|
expect(offscreen.gl).toBe(state.gl);
|
|
347
424
|
expect([state.gl.drawingBufferWidth, state.gl.drawingBufferHeight]).toEqual([200, 100]);
|
|
348
425
|
|
|
349
|
-
beginGlRenderPass(offscreen, makeTarget({ width: 64, height: 32 }));
|
|
426
|
+
const pass = beginGlRenderPass(offscreen, makeTarget({ width: 64, height: 32 }));
|
|
350
427
|
|
|
351
428
|
expect(runtime.renderTargetViewport).toEqual({ height: 32, width: 64, x: 0, y: 0 });
|
|
352
|
-
endGlRenderPass(
|
|
429
|
+
endGlRenderPass(pass);
|
|
353
430
|
});
|
|
354
431
|
|
|
355
432
|
it('restores the previous basis when the pass ends', () => {
|
|
356
433
|
// Guards the guard: a pass that set the viewport and never restored it would satisfy the test above
|
|
357
434
|
// while leaving every later screen draw projecting into the offscreen target's dimensions.
|
|
358
435
|
const { state } = createGlState();
|
|
359
|
-
const offscreen =
|
|
436
|
+
const offscreen = createGlRenderState(state.gl, state.pipeline);
|
|
360
437
|
const runtime = getGlRenderStateRuntime(offscreen);
|
|
361
438
|
|
|
362
|
-
beginGlRenderPass(offscreen, makeTarget({ width: 64, height: 32 }));
|
|
363
|
-
endGlRenderPass(
|
|
439
|
+
const pass = beginGlRenderPass(offscreen, makeTarget({ width: 64, height: 32 }));
|
|
440
|
+
endGlRenderPass(pass);
|
|
364
441
|
|
|
365
442
|
expect(runtime.renderTargetViewport).toBe(null);
|
|
366
443
|
});
|
|
@@ -370,7 +447,7 @@ describe('offscreen 2D projection basis', () => {
|
|
|
370
447
|
// compensation. Identity is the correct neutral for both, and the two constructors agreeing is what
|
|
371
448
|
// makes an offscreen pass behave like a screen pass until a caller deliberately changes it.
|
|
372
449
|
const { state } = createGlState();
|
|
373
|
-
const offscreen =
|
|
450
|
+
const offscreen = createGlRenderState(state.gl, state.pipeline);
|
|
374
451
|
|
|
375
452
|
// Field-by-field, not toEqual: a Matrix is entity-backed and carries runtime identity beyond its
|
|
376
453
|
// public fields, so two structurally identical matrices are not deeply equal.
|
|
@@ -381,6 +458,30 @@ describe('offscreen 2D projection basis', () => {
|
|
|
381
458
|
});
|
|
382
459
|
});
|
|
383
460
|
|
|
461
|
+
describe('releaseGlRenderPassHandle', () => {
|
|
462
|
+
it('returns a handle to the pool for reuse', () => {
|
|
463
|
+
const { state, gl } = createGlState();
|
|
464
|
+
const target = makeTarget();
|
|
465
|
+
|
|
466
|
+
const handle = acquireGlRenderPassHandle(gl, state, target);
|
|
467
|
+
releaseGlRenderPassHandle(gl, handle);
|
|
468
|
+
const reused = acquireGlRenderPassHandle(gl, state, target);
|
|
469
|
+
|
|
470
|
+
expect(reused).toBe(handle);
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
it('creates the pool lazily on first release', () => {
|
|
474
|
+
const { state, gl } = createGlState();
|
|
475
|
+
const target = makeTarget();
|
|
476
|
+
const handle = acquireGlRenderPassHandle(gl, state, target);
|
|
477
|
+
|
|
478
|
+
releaseGlRenderPassHandle(gl, handle);
|
|
479
|
+
|
|
480
|
+
const second = acquireGlRenderPassHandle(gl, state, target);
|
|
481
|
+
expect(second).toBe(handle);
|
|
482
|
+
});
|
|
483
|
+
});
|
|
484
|
+
|
|
384
485
|
describe('setGlRenderTransform2D', () => {
|
|
385
486
|
it('installs a copy of the transform as the 2D root device transform', () => {
|
|
386
487
|
const { state } = createGlState();
|
|
@@ -401,10 +502,10 @@ describe('setGlRenderTransform2D', () => {
|
|
|
401
502
|
const cacheTransform = createMatrix();
|
|
402
503
|
cacheTransform.tx = 99;
|
|
403
504
|
|
|
404
|
-
beginGlRenderPass(state, target, {
|
|
505
|
+
const pass = beginGlRenderPass(state, target, { depth: 1.0, stencil: 0 });
|
|
405
506
|
setGlRenderTransform2D(state, cacheTransform);
|
|
406
507
|
expect(state.renderTransform2D?.tx).toBe(99);
|
|
407
|
-
endGlRenderPass(
|
|
508
|
+
endGlRenderPass(pass);
|
|
408
509
|
|
|
409
510
|
expect(state.renderTransform2D).toBe(original);
|
|
410
511
|
});
|
|
@@ -419,3 +520,75 @@ describe('setGlRenderTransform2D', () => {
|
|
|
419
520
|
expect(proxy.lastLocalTransformId).toBe(-1);
|
|
420
521
|
});
|
|
421
522
|
});
|
|
523
|
+
|
|
524
|
+
describe('zero-compat ratchet', () => {
|
|
525
|
+
it('clearGlRenderTarget takes (target, clear) — no state parameter', () => {
|
|
526
|
+
expect(clearGlRenderTarget).toHaveLength(2);
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
it('clearGlRenderTargetAttachments takes (target, clear) — no state parameter', () => {
|
|
530
|
+
expect(clearGlRenderTargetAttachments).toHaveLength(2);
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
it('fillGlRect first parameter is a pass, not state', () => {
|
|
534
|
+
const { state } = createGlState();
|
|
535
|
+
const target = makeTarget();
|
|
536
|
+
const pass = beginGlRenderPass(state, target);
|
|
537
|
+
expect(pass.gl).toBe(state.gl);
|
|
538
|
+
expect(pass.state).toBe(state);
|
|
539
|
+
expect(pass.target).toBe(target);
|
|
540
|
+
endGlRenderPass(pass);
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
it('drawGlTextureRenderTargetResult first parameter is a pass', () => {
|
|
544
|
+
expect(drawGlTextureRenderTargetResult).toHaveLength(4);
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
it('beginGlRenderPass returns a GlRenderPass with gl, state, and target', () => {
|
|
548
|
+
const { state } = createGlState();
|
|
549
|
+
const target = makeTarget();
|
|
550
|
+
const pass = beginGlRenderPass(state, target);
|
|
551
|
+
expect(pass).toHaveProperty('gl');
|
|
552
|
+
expect(pass).toHaveProperty('state');
|
|
553
|
+
expect(pass).toHaveProperty('target');
|
|
554
|
+
expect(pass.gl).toBe(state.gl);
|
|
555
|
+
expect(pass.state).toBe(state);
|
|
556
|
+
expect(pass.target).toBe(target);
|
|
557
|
+
endGlRenderPass(pass);
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
it('endGlRenderPass accepts GlRenderPass only', () => {
|
|
561
|
+
expect(endGlRenderPass).toHaveLength(1);
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
it('getGlCurrentRenderPass tracks the active pass on the state', () => {
|
|
565
|
+
const { state } = createGlState();
|
|
566
|
+
const target = makeTarget();
|
|
567
|
+
expect(getGlCurrentRenderPass(state)).toBeNull();
|
|
568
|
+
const pass = beginGlRenderPass(state, target);
|
|
569
|
+
expect(getGlCurrentRenderPass(state)).toBe(pass);
|
|
570
|
+
endGlRenderPass(pass);
|
|
571
|
+
expect(getGlCurrentRenderPass(state)).toBeNull();
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
it('pooled handles are reused across begin/end cycles', () => {
|
|
575
|
+
const { state, gl } = createGlState();
|
|
576
|
+
const target = makeTarget();
|
|
577
|
+
const first = beginGlRenderPass(state, target);
|
|
578
|
+
endGlRenderPass(first);
|
|
579
|
+
const second = beginGlRenderPass(state, target);
|
|
580
|
+
expect(second).toBe(first);
|
|
581
|
+
endGlRenderPass(second);
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
it('screen and offscreen states share the context pass pool', () => {
|
|
585
|
+
const { state, gl } = createGlState();
|
|
586
|
+
const offscreen = createGlRenderState(state.gl, state.pipeline);
|
|
587
|
+
const target = makeTarget();
|
|
588
|
+
const screenPass = beginGlRenderPass(state, target);
|
|
589
|
+
endGlRenderPass(screenPass);
|
|
590
|
+
const offscreenPass = beginGlRenderPass(offscreen, target);
|
|
591
|
+
expect(offscreenPass).toBe(screenPass);
|
|
592
|
+
endGlRenderPass(offscreenPass);
|
|
593
|
+
});
|
|
594
|
+
});
|