@flighthq/render-gl 0.3.0 → 0.3.1-next.1041.35b950c
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 +19 -12
- package/dist/glDraw.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 +53 -32
- 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/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 +20 -1
- package/src/glMaterialRegistry.test.ts +16 -0
- package/src/glRenderState.test.ts +334 -25
- package/src/glRenderStateBracket.test.ts +170 -0
- package/src/glShader.test.ts +28 -0
- package/src/glTestHelper.test.ts +51 -0
- package/src/glTextureResolver.test.ts +11 -6
|
@@ -1,14 +1,31 @@
|
|
|
1
1
|
import { addLogSink, createMemoryLogSink, getMemoryLogSinkEntries, removeLogSink } from '@flighthq/log/contract';
|
|
2
|
+
import {
|
|
3
|
+
createKeyedTable,
|
|
4
|
+
getRegistryTableEntry,
|
|
5
|
+
hasRegistryTableEntry,
|
|
6
|
+
withRegistryTableEntry,
|
|
7
|
+
} from '@flighthq/registry/contract';
|
|
2
8
|
import {
|
|
3
9
|
copyAllRenderersFromRenderState,
|
|
10
|
+
enableColorAdjustmentGuards,
|
|
11
|
+
enableColorAdjustments,
|
|
12
|
+
getColorAdjustmentUnsupportedGuard,
|
|
4
13
|
getRenderStateRuntime,
|
|
5
14
|
prepareScene2DRender,
|
|
6
15
|
registerRenderer,
|
|
7
16
|
} from '@flighthq/render/contract';
|
|
8
17
|
import { createDisplayObject } from '@flighthq/scene2d/contract';
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
18
|
+
import type {
|
|
19
|
+
GlColorAdjustmentMaterialFeature,
|
|
20
|
+
GlColorAdjustmentMaterialFeatureGuard,
|
|
21
|
+
RenderEffectPaddingResolver,
|
|
22
|
+
RenderState,
|
|
23
|
+
} from '@flighthq/types/contract';
|
|
24
|
+
import { BlendMode, RegistryEntryState } from '@flighthq/types/contract';
|
|
25
|
+
|
|
26
|
+
import { areGlRenderStateGuardsEnabled, enableGlRenderStateGuards } from './enableGlRenderStateGuards';
|
|
27
|
+
import { registerGlCompressedTextureDecoder, registerGlCompressedTextureUpload } from './glCompressedTexture';
|
|
28
|
+
import { isBlendModeSupported, registerGlBlendMode } from './glDraw';
|
|
12
29
|
import { registerGlMaterialRenderer } from './glMaterialRegistry';
|
|
13
30
|
import {
|
|
14
31
|
copyGlRenderStateRegistrations,
|
|
@@ -16,6 +33,8 @@ import {
|
|
|
16
33
|
createGlRenderState,
|
|
17
34
|
createGlRenderStateRuntime,
|
|
18
35
|
destroyGlRenderState,
|
|
36
|
+
getGlColorAdjustmentMaterialFeature,
|
|
37
|
+
getGlColorAdjustmentMaterialFeatureGuard,
|
|
19
38
|
getGlRenderStateRuntime,
|
|
20
39
|
invalidateGlRenderStateCache,
|
|
21
40
|
} from './glRenderState';
|
|
@@ -31,32 +50,135 @@ function makeCanvas() {
|
|
|
31
50
|
return { canvas, gl };
|
|
32
51
|
}
|
|
33
52
|
|
|
53
|
+
function getPaddingResolver(state: RenderState, kind: string): RenderEffectPaddingResolver | null {
|
|
54
|
+
const table = getRenderStateRuntime(state).registries.effectPaddingResolvers;
|
|
55
|
+
return table === undefined ? null : getRegistryTableEntry(table, kind);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function registerPaddingResolver(state: RenderState, kind: string, resolver: RenderEffectPaddingResolver): void {
|
|
59
|
+
const runtime = getRenderStateRuntime(state);
|
|
60
|
+
runtime.registries.effectPaddingResolvers = withRegistryTableEntry(
|
|
61
|
+
runtime.registries.effectPaddingResolvers ??
|
|
62
|
+
createKeyedTable<RenderEffectPaddingResolver>('RenderEffectPaddingResolver', 'Zero'),
|
|
63
|
+
kind,
|
|
64
|
+
resolver,
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
34
68
|
describe('copyGlRenderStateRegistrations', () => {
|
|
35
69
|
it('copies late GL registrations only when explicitly requested', () => {
|
|
36
70
|
const { canvas } = makeCanvas();
|
|
37
71
|
const screen = createGlRenderState(canvas);
|
|
38
72
|
const offscreen = createGlOffscreenRenderState(screen);
|
|
73
|
+
const materialRenderer = { instanceFloatCount: 0, bind: vi.fn() } as never;
|
|
74
|
+
const offscreenMaterialRenderer = { instanceFloatCount: 0, bind: vi.fn() } as never;
|
|
75
|
+
const decoder = vi.fn(() => new Uint8ClampedArray(4));
|
|
39
76
|
const resolver = vi.fn(() => null);
|
|
77
|
+
registerGlBlendMode(screen, 'acme.LateBlend', { src: 'ONE', dst: 'ZERO' });
|
|
78
|
+
registerGlCompressedTextureDecoder(screen, decoder);
|
|
79
|
+
registerGlCompressedTextureUpload(screen);
|
|
80
|
+
registerGlMaterialRenderer(screen, 'acme.LateMaterial', materialRenderer);
|
|
40
81
|
registerGlTextureResolver(screen, 'acme.LateTexture', resolver);
|
|
41
82
|
|
|
42
|
-
expect(
|
|
83
|
+
expect(isBlendModeSupported(offscreen, 'acme.LateBlend')).toBe(false);
|
|
84
|
+
expect(
|
|
85
|
+
hasRegistryTableEntry(getGlRenderStateRuntime(offscreen).registries.materialRenderers, 'acme.LateMaterial'),
|
|
86
|
+
).toBe(false);
|
|
87
|
+
expect(
|
|
88
|
+
hasRegistryTableEntry(getGlRenderStateRuntime(offscreen).registries.textureResolvers, 'acme.LateTexture'),
|
|
89
|
+
).toBe(false);
|
|
90
|
+
expect(getGlRenderStateRuntime(offscreen).registries.compressedTextureDecoder.entry).toBeNull();
|
|
91
|
+
expect(getGlRenderStateRuntime(offscreen).registries.compressedTextureUpload.entry).toBeNull();
|
|
43
92
|
copyGlRenderStateRegistrations(offscreen, screen);
|
|
44
|
-
expect(
|
|
93
|
+
expect(isBlendModeSupported(offscreen, 'acme.LateBlend')).toBe(true);
|
|
94
|
+
expect(
|
|
95
|
+
getRegistryTableEntry(getGlRenderStateRuntime(offscreen).registries.materialRenderers, 'acme.LateMaterial'),
|
|
96
|
+
).toBe(materialRenderer);
|
|
97
|
+
expect(
|
|
98
|
+
getRegistryTableEntry(getGlRenderStateRuntime(offscreen).registries.textureResolvers, 'acme.LateTexture'),
|
|
99
|
+
).toBe(resolver);
|
|
100
|
+
expect(getGlRenderStateRuntime(offscreen).registries.compressedTextureDecoder).toBe(
|
|
101
|
+
getGlRenderStateRuntime(screen).registries.compressedTextureDecoder,
|
|
102
|
+
);
|
|
103
|
+
expect(getGlRenderStateRuntime(offscreen).registries.compressedTextureDecoder.entry).toEqual({
|
|
104
|
+
state: RegistryEntryState.Bound,
|
|
105
|
+
value: decoder,
|
|
106
|
+
});
|
|
107
|
+
expect(getGlRenderStateRuntime(offscreen).registries.compressedTextureUpload).toBe(
|
|
108
|
+
getGlRenderStateRuntime(screen).registries.compressedTextureUpload,
|
|
109
|
+
);
|
|
110
|
+
registerGlMaterialRenderer(offscreen, 'acme.LateMaterial', offscreenMaterialRenderer);
|
|
111
|
+
registerGlBlendMode(offscreen, 'acme.LateBlend', { src: 'ZERO', dst: 'ONE' });
|
|
112
|
+
registerGlCompressedTextureDecoder(offscreen, null);
|
|
113
|
+
registerGlCompressedTextureUpload(offscreen, null);
|
|
114
|
+
registerGlTextureResolver(offscreen, 'acme.LateTexture', null);
|
|
115
|
+
expect(
|
|
116
|
+
getRegistryTableEntry(getGlRenderStateRuntime(offscreen).registries.materialRenderers, 'acme.LateMaterial'),
|
|
117
|
+
).toBe(offscreenMaterialRenderer);
|
|
118
|
+
expect(
|
|
119
|
+
getRegistryTableEntry(getGlRenderStateRuntime(screen).registries.materialRenderers, 'acme.LateMaterial'),
|
|
120
|
+
).toBe(materialRenderer);
|
|
121
|
+
expect(
|
|
122
|
+
getRegistryTableEntry(getGlRenderStateRuntime(screen).registries.blendRealizations, 'acme.LateBlend'),
|
|
123
|
+
).toEqual({ src: 'ONE', dst: 'ZERO' });
|
|
124
|
+
expect(
|
|
125
|
+
hasRegistryTableEntry(getGlRenderStateRuntime(offscreen).registries.textureResolvers, 'acme.LateTexture'),
|
|
126
|
+
).toBe(false);
|
|
127
|
+
expect(getRegistryTableEntry(getGlRenderStateRuntime(screen).registries.textureResolvers, 'acme.LateTexture')).toBe(
|
|
128
|
+
resolver,
|
|
129
|
+
);
|
|
130
|
+
expect(getGlRenderStateRuntime(offscreen).registries.compressedTextureDecoder.entry).toBeNull();
|
|
131
|
+
expect(getGlRenderStateRuntime(screen).registries.compressedTextureDecoder.entry?.state).toBe(
|
|
132
|
+
RegistryEntryState.Bound,
|
|
133
|
+
);
|
|
134
|
+
expect(getGlRenderStateRuntime(offscreen).registries.compressedTextureUpload.entry).toBeNull();
|
|
135
|
+
expect(getGlRenderStateRuntime(screen).registries.compressedTextureUpload.entry?.state).toBe(
|
|
136
|
+
RegistryEntryState.Bound,
|
|
137
|
+
);
|
|
45
138
|
});
|
|
46
139
|
});
|
|
47
140
|
|
|
48
141
|
describe('createGlOffscreenRenderState', () => {
|
|
49
|
-
it('shares context resources
|
|
142
|
+
it('shares context resources and persistent registration snapshots through independent aggregates', () => {
|
|
50
143
|
const { canvas } = makeCanvas();
|
|
51
144
|
const screen = createGlRenderState(canvas);
|
|
52
145
|
const renderer = { createData: () => null, submit: vi.fn() };
|
|
53
146
|
const materialRenderer = { getBatchData: vi.fn(), getBatchFloats: vi.fn() } as never;
|
|
54
147
|
const paddingResolver = vi.fn(() => ({ bottom: 1, left: 1, right: 1, top: 1 }));
|
|
55
148
|
const textureResolver = vi.fn(() => null);
|
|
149
|
+
const effectRunner = vi.fn();
|
|
150
|
+
const colorAdjustmentFeature: GlColorAdjustmentMaterialFeature = {
|
|
151
|
+
drawShapeMeshes: vi.fn(),
|
|
152
|
+
flush: vi.fn(() => false),
|
|
153
|
+
fragmentShaderChunk: '',
|
|
154
|
+
matrixFragmentShaderChunk: '',
|
|
155
|
+
record: vi.fn(),
|
|
156
|
+
};
|
|
157
|
+
const colorAdjustmentFeatureGuard: GlColorAdjustmentMaterialFeatureGuard = vi.fn();
|
|
56
158
|
registerRenderer(screen, 'acme.Node', renderer);
|
|
57
159
|
registerGlMaterialRenderer(screen, 'acme.Material', materialRenderer);
|
|
58
160
|
registerGlTextureResolver(screen, 'acme.Texture', textureResolver);
|
|
59
|
-
|
|
161
|
+
enableColorAdjustments(screen);
|
|
162
|
+
enableColorAdjustmentGuards(screen);
|
|
163
|
+
enableGlRenderStateGuards(screen);
|
|
164
|
+
getGlRenderStateRuntime(screen).registries.renderEffects = withRegistryTableEntry(
|
|
165
|
+
getGlRenderStateRuntime(screen).registries.renderEffects,
|
|
166
|
+
'acme.Effect',
|
|
167
|
+
{ runner: effectRunner as never },
|
|
168
|
+
);
|
|
169
|
+
registerPaddingResolver(screen, 'acme.Effect', paddingResolver);
|
|
170
|
+
getGlRenderStateRuntime(screen).registries.colorAdjustmentFeature = {
|
|
171
|
+
entry: { state: RegistryEntryState.Bound, value: colorAdjustmentFeature },
|
|
172
|
+
onMiss: 'Disabled',
|
|
173
|
+
registry: 'GlColorAdjustmentFeature',
|
|
174
|
+
shape: 'slot',
|
|
175
|
+
};
|
|
176
|
+
getGlRenderStateRuntime(screen).registries.colorAdjustmentFeatureGuard = {
|
|
177
|
+
entry: { state: RegistryEntryState.Bound, value: colorAdjustmentFeatureGuard },
|
|
178
|
+
onMiss: 'Disabled',
|
|
179
|
+
registry: 'GlColorAdjustmentFeatureGuard',
|
|
180
|
+
shape: 'slot',
|
|
181
|
+
};
|
|
60
182
|
getGlRenderStateRuntime(screen).glRenderTextureCache = new WeakMap();
|
|
61
183
|
|
|
62
184
|
const offscreen = createGlOffscreenRenderState(screen);
|
|
@@ -71,16 +193,67 @@ describe('createGlOffscreenRenderState', () => {
|
|
|
71
193
|
);
|
|
72
194
|
expect(offscreenRuntime.glRenderTextureCache).toBe(screenRuntime.glRenderTextureCache);
|
|
73
195
|
expect(offscreenRuntime.quadIndexBuffer).toBe(screenRuntime.quadIndexBuffer);
|
|
74
|
-
expect(offscreenRuntime.
|
|
75
|
-
expect(offscreenRuntime.
|
|
76
|
-
expect(offscreenRuntime.
|
|
77
|
-
expect(offscreenRuntime.
|
|
78
|
-
|
|
196
|
+
expect(offscreenRuntime.registries.renderers).toBe(screenRuntime.registries.renderers);
|
|
197
|
+
expect(offscreenRuntime.registries).not.toBe(screenRuntime.registries);
|
|
198
|
+
expect(offscreenRuntime.registries.blendRealizations).toBe(screenRuntime.registries.blendRealizations);
|
|
199
|
+
expect(offscreenRuntime.registries.colorAdjustmentFeature).toBe(screenRuntime.registries.colorAdjustmentFeature);
|
|
200
|
+
expect(getGlColorAdjustmentMaterialFeature(offscreen)).toBe(colorAdjustmentFeature);
|
|
201
|
+
expect(offscreenRuntime.registries.colorAdjustmentFeatureGuard).toBe(
|
|
202
|
+
screenRuntime.registries.colorAdjustmentFeatureGuard,
|
|
203
|
+
);
|
|
204
|
+
expect(getGlColorAdjustmentMaterialFeatureGuard(offscreen)).toBe(colorAdjustmentFeatureGuard);
|
|
205
|
+
const sharedColorFeatureGuard = offscreenRuntime.registries.colorAdjustmentFeatureGuard;
|
|
206
|
+
screenRuntime.registries.colorAdjustmentFeatureGuard = undefined;
|
|
207
|
+
expect(getGlColorAdjustmentMaterialFeatureGuard(screen)).toBeNull();
|
|
208
|
+
expect(offscreenRuntime.registries.colorAdjustmentFeatureGuard).toBe(sharedColorFeatureGuard);
|
|
209
|
+
expect(getGlColorAdjustmentMaterialFeatureGuard(offscreen)).toBe(colorAdjustmentFeatureGuard);
|
|
210
|
+
const sharedColorFeature = offscreenRuntime.registries.colorAdjustmentFeature;
|
|
211
|
+
screenRuntime.registries.colorAdjustmentFeature = undefined;
|
|
212
|
+
expect(getGlColorAdjustmentMaterialFeature(screen)).toBeNull();
|
|
213
|
+
expect(offscreenRuntime.registries.colorAdjustmentFeature).toBe(sharedColorFeature);
|
|
214
|
+
expect(getGlColorAdjustmentMaterialFeature(offscreen)).toBe(colorAdjustmentFeature);
|
|
215
|
+
expect(offscreenRuntime.registries.colorAdjustments).toBe(screenRuntime.registries.colorAdjustments);
|
|
216
|
+
expect(offscreenRuntime.registries.colorAdjustmentUnsupportedGuard).toBe(
|
|
217
|
+
screenRuntime.registries.colorAdjustmentUnsupportedGuard,
|
|
79
218
|
);
|
|
80
|
-
expect(
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
expect(
|
|
219
|
+
expect(getColorAdjustmentUnsupportedGuard(offscreen)).not.toBeNull();
|
|
220
|
+
const sharedUnsupportedGuard = offscreenRuntime.registries.colorAdjustmentUnsupportedGuard;
|
|
221
|
+
screenRuntime.registries.colorAdjustmentUnsupportedGuard = undefined;
|
|
222
|
+
expect(getColorAdjustmentUnsupportedGuard(screen)).toBeNull();
|
|
223
|
+
expect(offscreenRuntime.registries.colorAdjustmentUnsupportedGuard).toBe(sharedUnsupportedGuard);
|
|
224
|
+
expect(getColorAdjustmentUnsupportedGuard(offscreen)).not.toBeNull();
|
|
225
|
+
expect(offscreenRuntime.registries.renderRootGuard).toBe(screenRuntime.registries.renderRootGuard);
|
|
226
|
+
expect(areGlRenderStateGuardsEnabled(offscreen)).toBe(true);
|
|
227
|
+
const sharedRenderRootGuard = offscreenRuntime.registries.renderRootGuard;
|
|
228
|
+
screenRuntime.registries.renderRootGuard = undefined;
|
|
229
|
+
expect(screenRuntime.registries.renderRootGuard).toBeUndefined();
|
|
230
|
+
expect(offscreenRuntime.registries.renderRootGuard).toBe(sharedRenderRootGuard);
|
|
231
|
+
expect(areGlRenderStateGuardsEnabled(offscreen)).toBe(true);
|
|
232
|
+
expect(offscreenRuntime.registries.compressedTextureDecoder).toBe(
|
|
233
|
+
screenRuntime.registries.compressedTextureDecoder,
|
|
234
|
+
);
|
|
235
|
+
expect(offscreenRuntime.registries.compressedTextureUpload).toBe(screenRuntime.registries.compressedTextureUpload);
|
|
236
|
+
expect(offscreenRuntime.registries.customEffectShaders).toBe(screenRuntime.registries.customEffectShaders);
|
|
237
|
+
expect(offscreenRuntime.registries.customMaterialShaders).toBe(screenRuntime.registries.customMaterialShaders);
|
|
238
|
+
expect(offscreenRuntime.registries.materialRenderers).toBe(screenRuntime.registries.materialRenderers);
|
|
239
|
+
expect(offscreenRuntime.registries.meshMaterialRenderers).toBe(screenRuntime.registries.meshMaterialRenderers);
|
|
240
|
+
expect(offscreenRuntime.registries.modifierSnippets).toBe(screenRuntime.registries.modifierSnippets);
|
|
241
|
+
expect(offscreenRuntime.registries.modifierSnippetRevision).toBe(screenRuntime.registries.modifierSnippetRevision);
|
|
242
|
+
expect(offscreenRuntime.registries.pbrExtensions).toBe(screenRuntime.registries.pbrExtensions);
|
|
243
|
+
expect(offscreenRuntime.registries.pbrExtensionRevision).toBe(screenRuntime.registries.pbrExtensionRevision);
|
|
244
|
+
expect(offscreenRuntime.registries.renderEffects).toBe(screenRuntime.registries.renderEffects);
|
|
245
|
+
expect(offscreenRuntime.registries.shapeRasterizer).toBe(screenRuntime.registries.shapeRasterizer);
|
|
246
|
+
expect(offscreenRuntime.registries.strokeTessellator).toBe(screenRuntime.registries.strokeTessellator);
|
|
247
|
+
expect(offscreenRuntime.registries.textureResolvers).toBe(screenRuntime.registries.textureResolvers);
|
|
248
|
+
expect(offscreenRuntime.registries.velocityWriters).toBe(screenRuntime.registries.velocityWriters);
|
|
249
|
+
expect(offscreenRuntime.registries.effectPaddingResolvers).toBe(screenRuntime.registries.effectPaddingResolvers);
|
|
250
|
+
expect(getRegistryTableEntry(offscreenRuntime.registries.renderers, 'acme.Node')).toBe(renderer);
|
|
251
|
+
expect(getRegistryTableEntry(offscreenRuntime.registries.materialRenderers, 'acme.Material')).toBe(
|
|
252
|
+
materialRenderer,
|
|
253
|
+
);
|
|
254
|
+
expect(getRegistryTableEntry(offscreenRuntime.registries.textureResolvers, 'acme.Texture')).toBe(textureResolver);
|
|
255
|
+
expect(getRegistryTableEntry(offscreenRuntime.registries.renderEffects, 'acme.Effect')?.runner).toBe(effectRunner);
|
|
256
|
+
expect(getPaddingResolver(offscreen, 'acme.Effect')).toBe(paddingResolver);
|
|
84
257
|
|
|
85
258
|
screenRuntime.currentProgram = {} as WebGLProgram;
|
|
86
259
|
expect(offscreenRuntime.currentProgram).toBe(screenRuntime.currentProgram);
|
|
@@ -95,19 +268,23 @@ describe('createGlOffscreenRenderState', () => {
|
|
|
95
268
|
const resolver = vi.fn(() => null);
|
|
96
269
|
registerRenderer(screen, 'acme.LateNode', renderer);
|
|
97
270
|
registerGlTextureResolver(screen, 'acme.LateTexture', resolver);
|
|
98
|
-
|
|
271
|
+
registerPaddingResolver(screen, 'acme.LateEffect', paddingResolver);
|
|
99
272
|
|
|
100
|
-
expect(getRenderStateRuntime(offscreen).
|
|
101
|
-
expect(
|
|
102
|
-
|
|
273
|
+
expect(hasRegistryTableEntry(getRenderStateRuntime(offscreen).registries.renderers, 'acme.LateNode')).toBe(false);
|
|
274
|
+
expect(
|
|
275
|
+
hasRegistryTableEntry(getGlRenderStateRuntime(offscreen).registries.textureResolvers, 'acme.LateTexture'),
|
|
276
|
+
).toBe(false);
|
|
277
|
+
expect(getPaddingResolver(offscreen, 'acme.LateEffect')).toBeNull();
|
|
103
278
|
|
|
104
279
|
copyAllRenderersFromRenderState(offscreen, screen);
|
|
105
280
|
copyGlRenderStateRegistrations(offscreen, screen);
|
|
106
|
-
expect(getRenderStateRuntime(offscreen).
|
|
107
|
-
|
|
108
|
-
expect(getRenderStateRuntime(offscreen).renderEffectPaddingResolverRegistry?.get('acme.LateEffect')).toBe(
|
|
109
|
-
paddingResolver,
|
|
281
|
+
expect(getRegistryTableEntry(getRenderStateRuntime(offscreen).registries.renderers, 'acme.LateNode')).toBe(
|
|
282
|
+
renderer,
|
|
110
283
|
);
|
|
284
|
+
expect(
|
|
285
|
+
getRegistryTableEntry(getGlRenderStateRuntime(offscreen).registries.textureResolvers, 'acme.LateTexture'),
|
|
286
|
+
).toBe(resolver);
|
|
287
|
+
expect(getPaddingResolver(offscreen, 'acme.LateEffect')).toBe(paddingResolver);
|
|
111
288
|
});
|
|
112
289
|
|
|
113
290
|
it('keeps proxy trees independent and destroys derived renderer data without freeing shared context resources', () => {
|
|
@@ -229,9 +406,89 @@ describe('createGlRenderState', () => {
|
|
|
229
406
|
});
|
|
230
407
|
|
|
231
408
|
describe('createGlRenderStateRuntime', () => {
|
|
232
|
-
it('returns a runtime
|
|
409
|
+
it('returns a runtime with the base binding slot and empty named registration tables', () => {
|
|
233
410
|
const runtime = createGlRenderStateRuntime();
|
|
234
411
|
expect(runtime.binding).toBeNull();
|
|
412
|
+
expect(runtime.registries.blendRealizations).toMatchObject({
|
|
413
|
+
onMiss: 'Normal',
|
|
414
|
+
registry: 'GlBlendRealization',
|
|
415
|
+
shape: 'keyed',
|
|
416
|
+
});
|
|
417
|
+
expect(runtime.registries.blendRealizations.entries.size).toBe(0);
|
|
418
|
+
expect(runtime.registries.customEffectShaders).toMatchObject({
|
|
419
|
+
onMiss: 'Unregistered',
|
|
420
|
+
registry: 'GlCustomEffectShader',
|
|
421
|
+
shape: 'keyed',
|
|
422
|
+
});
|
|
423
|
+
expect(runtime.registries.customEffectShaders.entries.size).toBe(0);
|
|
424
|
+
expect(runtime.registries.customMaterialShaders).toMatchObject({
|
|
425
|
+
onMiss: 'Unregistered',
|
|
426
|
+
registry: 'GlCustomMaterialShader',
|
|
427
|
+
shape: 'keyed',
|
|
428
|
+
});
|
|
429
|
+
expect(runtime.registries.customMaterialShaders.entries.size).toBe(0);
|
|
430
|
+
expect(runtime.registries.materialRenderers).toMatchObject({
|
|
431
|
+
onMiss: 'StandardMaterial',
|
|
432
|
+
registry: 'GlMaterialRenderer',
|
|
433
|
+
shape: 'keyed',
|
|
434
|
+
});
|
|
435
|
+
expect(runtime.registries.materialRenderers.entries.size).toBe(0);
|
|
436
|
+
expect(runtime.registries.meshMaterialRenderers).toMatchObject({
|
|
437
|
+
onMiss: 'StandardMaterial',
|
|
438
|
+
registry: 'GlMeshMaterialRenderer',
|
|
439
|
+
shape: 'keyed',
|
|
440
|
+
});
|
|
441
|
+
expect(runtime.registries.meshMaterialRenderers.entries.size).toBe(0);
|
|
442
|
+
expect(runtime.registries.modifierSnippets).toMatchObject({
|
|
443
|
+
onMiss: 'Unregistered',
|
|
444
|
+
registry: 'GlModifierSnippet',
|
|
445
|
+
shape: 'keyed',
|
|
446
|
+
});
|
|
447
|
+
expect(runtime.registries.modifierSnippets.entries.size).toBe(0);
|
|
448
|
+
expect(runtime.registries.modifierSnippetRevision).toBe(0);
|
|
449
|
+
expect(runtime.registries.pbrExtensions).toMatchObject({
|
|
450
|
+
onMiss: 'Unregistered',
|
|
451
|
+
registry: 'GlPbrExtension',
|
|
452
|
+
shape: 'keyed',
|
|
453
|
+
});
|
|
454
|
+
expect(runtime.registries.pbrExtensions.entries.size).toBe(0);
|
|
455
|
+
expect(runtime.registries.pbrExtensionRevision).toBe(0);
|
|
456
|
+
expect(runtime.registries.renderEffects).toMatchObject({
|
|
457
|
+
onMiss: 'Unregistered',
|
|
458
|
+
registry: 'GlRenderEffect',
|
|
459
|
+
shape: 'keyed',
|
|
460
|
+
});
|
|
461
|
+
expect(runtime.registries.compressedTextureDecoder).toEqual({
|
|
462
|
+
entry: null,
|
|
463
|
+
onMiss: 'Unregistered',
|
|
464
|
+
registry: 'GlCompressedTextureDecoder',
|
|
465
|
+
shape: 'slot',
|
|
466
|
+
});
|
|
467
|
+
expect(runtime.registries.colorAdjustments).toBeUndefined();
|
|
468
|
+
expect(runtime.registries.compressedTextureUpload).toEqual({
|
|
469
|
+
entry: null,
|
|
470
|
+
onMiss: 'Unregistered',
|
|
471
|
+
registry: 'GlCompressedTextureUpload',
|
|
472
|
+
shape: 'slot',
|
|
473
|
+
});
|
|
474
|
+
expect(runtime.registries.shapeRasterizer).toEqual({
|
|
475
|
+
entry: null,
|
|
476
|
+
onMiss: 'Unregistered',
|
|
477
|
+
registry: 'GlShapeRasterizer',
|
|
478
|
+
shape: 'slot',
|
|
479
|
+
});
|
|
480
|
+
expect(runtime.registries.strokeTessellator).toEqual({
|
|
481
|
+
entry: null,
|
|
482
|
+
onMiss: 'Rasterize',
|
|
483
|
+
registry: 'StrokeTessellator',
|
|
484
|
+
shape: 'slot',
|
|
485
|
+
});
|
|
486
|
+
expect(runtime.registries.velocityWriters).toMatchObject({
|
|
487
|
+
onMiss: 'Unregistered',
|
|
488
|
+
registry: 'GlVelocityWriter',
|
|
489
|
+
shape: 'keyed',
|
|
490
|
+
});
|
|
491
|
+
expect(runtime.registries.velocityWriters.entries.size).toBe(0);
|
|
235
492
|
});
|
|
236
493
|
});
|
|
237
494
|
|
|
@@ -275,6 +532,58 @@ describe('enableGlRenderStateGuards', () => {
|
|
|
275
532
|
});
|
|
276
533
|
});
|
|
277
534
|
|
|
535
|
+
describe('getGlColorAdjustmentMaterialFeature', () => {
|
|
536
|
+
it('resolves only a bound feature entry', () => {
|
|
537
|
+
const { canvas } = makeCanvas();
|
|
538
|
+
const state = createGlRenderState(canvas);
|
|
539
|
+
const feature: GlColorAdjustmentMaterialFeature = {
|
|
540
|
+
drawShapeMeshes: vi.fn(),
|
|
541
|
+
flush: vi.fn(() => false),
|
|
542
|
+
fragmentShaderChunk: '',
|
|
543
|
+
matrixFragmentShaderChunk: '',
|
|
544
|
+
record: vi.fn(),
|
|
545
|
+
};
|
|
546
|
+
const runtime = getGlRenderStateRuntime(state);
|
|
547
|
+
|
|
548
|
+
expect(getGlColorAdjustmentMaterialFeature(state)).toBeNull();
|
|
549
|
+
runtime.registries.colorAdjustmentFeature = {
|
|
550
|
+
entry: { state: RegistryEntryState.Bound, value: feature },
|
|
551
|
+
onMiss: 'Disabled',
|
|
552
|
+
registry: 'GlColorAdjustmentFeature',
|
|
553
|
+
shape: 'slot',
|
|
554
|
+
};
|
|
555
|
+
expect(getGlColorAdjustmentMaterialFeature(state)).toBe(feature);
|
|
556
|
+
runtime.registries.colorAdjustmentFeature = {
|
|
557
|
+
...runtime.registries.colorAdjustmentFeature,
|
|
558
|
+
entry: { state: RegistryEntryState.Tombstoned },
|
|
559
|
+
};
|
|
560
|
+
expect(getGlColorAdjustmentMaterialFeature(state)).toBeNull();
|
|
561
|
+
});
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
describe('getGlColorAdjustmentMaterialFeatureGuard', () => {
|
|
565
|
+
it('resolves only a bound guard entry', () => {
|
|
566
|
+
const { canvas } = makeCanvas();
|
|
567
|
+
const state = createGlRenderState(canvas);
|
|
568
|
+
const guard: GlColorAdjustmentMaterialFeatureGuard = vi.fn();
|
|
569
|
+
const runtime = getGlRenderStateRuntime(state);
|
|
570
|
+
|
|
571
|
+
expect(getGlColorAdjustmentMaterialFeatureGuard(state)).toBeNull();
|
|
572
|
+
runtime.registries.colorAdjustmentFeatureGuard = {
|
|
573
|
+
entry: { state: RegistryEntryState.Bound, value: guard },
|
|
574
|
+
onMiss: 'Disabled',
|
|
575
|
+
registry: 'GlColorAdjustmentFeatureGuard',
|
|
576
|
+
shape: 'slot',
|
|
577
|
+
};
|
|
578
|
+
expect(getGlColorAdjustmentMaterialFeatureGuard(state)).toBe(guard);
|
|
579
|
+
runtime.registries.colorAdjustmentFeatureGuard = {
|
|
580
|
+
...runtime.registries.colorAdjustmentFeatureGuard,
|
|
581
|
+
entry: { state: RegistryEntryState.Tombstoned },
|
|
582
|
+
};
|
|
583
|
+
expect(getGlColorAdjustmentMaterialFeatureGuard(state)).toBeNull();
|
|
584
|
+
});
|
|
585
|
+
});
|
|
586
|
+
|
|
278
587
|
describe('getGlRenderStateRuntime', () => {
|
|
279
588
|
it('returns the runtime attached by createGlRenderState', () => {
|
|
280
589
|
const { canvas } = makeCanvas();
|
|
@@ -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;
|