@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.
Files changed (50) hide show
  1. package/dist/enableGlRenderStateGuards.d.ts.map +1 -1
  2. package/dist/enableGlRenderStateGuards.js +12 -3
  3. package/dist/enableGlRenderStateGuards.js.map +1 -1
  4. package/dist/explainGlTextureResolution.d.ts.map +1 -1
  5. package/dist/explainGlTextureResolution.js +4 -1
  6. package/dist/explainGlTextureResolution.js.map +1 -1
  7. package/dist/glCompressedTexture.d.ts.map +1 -1
  8. package/dist/glCompressedTexture.js +13 -2
  9. package/dist/glCompressedTexture.js.map +1 -1
  10. package/dist/glDraw.d.ts.map +1 -1
  11. package/dist/glDraw.js +21 -14
  12. package/dist/glDraw.js.map +1 -1
  13. package/dist/glFullscreenPass.d.ts.map +1 -1
  14. package/dist/glFullscreenPass.js +2 -1
  15. package/dist/glFullscreenPass.js.map +1 -1
  16. package/dist/glMaterialRegistry.d.ts.map +1 -1
  17. package/dist/glMaterialRegistry.js +13 -8
  18. package/dist/glMaterialRegistry.js.map +1 -1
  19. package/dist/glRenderState.d.ts +3 -1
  20. package/dist/glRenderState.d.ts.map +1 -1
  21. package/dist/glRenderState.js +55 -37
  22. package/dist/glRenderState.js.map +1 -1
  23. package/dist/glRenderStateBracket.d.ts.map +1 -1
  24. package/dist/glRenderStateBracket.js +54 -0
  25. package/dist/glRenderStateBracket.js.map +1 -1
  26. package/dist/glRenderTarget.js.map +1 -1
  27. package/dist/glShader.d.ts +2 -1
  28. package/dist/glShader.d.ts.map +1 -1
  29. package/dist/glShader.js +10 -0
  30. package/dist/glShader.js.map +1 -1
  31. package/dist/glShaderBinding.d.ts.map +1 -1
  32. package/dist/glShaderBinding.js +2 -1
  33. package/dist/glShaderBinding.js.map +1 -1
  34. package/dist/glSkinPaletteTexture.d.ts +1 -1
  35. package/dist/glSkinPaletteTexture.d.ts.map +1 -1
  36. package/dist/glSkinPaletteTexture.js +6 -2
  37. package/dist/glSkinPaletteTexture.js.map +1 -1
  38. package/dist/glTextureResolver.d.ts.map +1 -1
  39. package/dist/glTextureResolver.js +13 -11
  40. package/dist/glTextureResolver.js.map +1 -1
  41. package/package.json +11 -10
  42. package/src/enableGlRenderStateGuards.test.ts +10 -0
  43. package/src/glCompressedTexture.test.ts +29 -12
  44. package/src/glDraw.test.ts +25 -6
  45. package/src/glMaterialRegistry.test.ts +16 -0
  46. package/src/glRenderState.test.ts +338 -29
  47. package/src/glRenderStateBracket.test.ts +170 -0
  48. package/src/glShader.test.ts +55 -1
  49. package/src/glTestHelper.test.ts +51 -0
  50. 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 { BlendMode } from '@flighthq/types/contract';
10
-
11
- import { enableGlRenderStateGuards } from './enableGlRenderStateGuards';
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(getGlRenderStateRuntime(offscreen).glTextureResolverRegistry?.has('acme.LateTexture')).not.toBe(true);
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(getGlRenderStateRuntime(offscreen).glTextureResolverRegistry?.get('acme.LateTexture')).toBe(resolver);
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 while snapshotting registration policy into independent maps', () => {
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
- getRenderStateRuntime(screen).renderEffectPaddingResolverRegistry = new Map([['acme.Effect', paddingResolver]]);
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.rendererMap).not.toBe(screenRuntime.rendererMap);
75
- expect(offscreenRuntime.materialRendererMap).not.toBe(screenRuntime.materialRendererMap);
76
- expect(offscreenRuntime.glTextureResolverRegistry).not.toBe(screenRuntime.glTextureResolverRegistry);
77
- expect(offscreenRuntime.renderEffectPaddingResolverRegistry).not.toBe(
78
- screenRuntime.renderEffectPaddingResolverRegistry,
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(offscreenRuntime.rendererMap.get('acme.Node')).toBe(renderer);
81
- expect(offscreenRuntime.materialRendererMap?.get('acme.Material')).toBe(materialRenderer);
82
- expect(offscreenRuntime.glTextureResolverRegistry?.get('acme.Texture')).toBe(textureResolver);
83
- expect(offscreenRuntime.renderEffectPaddingResolverRegistry?.get('acme.Effect')).toBe(paddingResolver);
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
- getRenderStateRuntime(screen).renderEffectPaddingResolverRegistry = new Map([['acme.LateEffect', paddingResolver]]);
271
+ registerPaddingResolver(screen, 'acme.LateEffect', paddingResolver);
99
272
 
100
- expect(getRenderStateRuntime(offscreen).rendererMap.has('acme.LateNode')).toBe(false);
101
- expect(getGlRenderStateRuntime(offscreen).glTextureResolverRegistry?.has('acme.LateTexture')).not.toBe(true);
102
- expect(getRenderStateRuntime(offscreen).renderEffectPaddingResolverRegistry?.has('acme.LateEffect')).not.toBe(true);
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).rendererMap.get('acme.LateNode')).toBe(renderer);
107
- expect(getGlRenderStateRuntime(offscreen).glTextureResolverRegistry?.get('acme.LateTexture')).toBe(resolver);
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', () => {
@@ -129,7 +306,7 @@ describe('createGlOffscreenRenderState', () => {
129
306
  expect(gl.deleteProgram).not.toHaveBeenCalled();
130
307
 
131
308
  destroyGlRenderState(screen);
132
- expect(gl.deleteProgram).toHaveBeenCalled();
309
+ expect(gl.deleteBuffer).toHaveBeenCalled();
133
310
  });
134
311
  });
135
312
 
@@ -229,14 +406,94 @@ describe('createGlRenderState', () => {
229
406
  });
230
407
 
231
408
  describe('createGlRenderStateRuntime', () => {
232
- it('returns a runtime carrying the base entity-runtime binding slot', () => {
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
 
238
495
  describe('destroyGlRenderState', () => {
239
- it('deletes the state-owned shader programs and buffers', () => {
496
+ it('deletes buffers on destroy even when no shader was compiled', () => {
240
497
  const { canvas, gl } = makeCanvas();
241
498
  const state = createGlRenderState(canvas);
242
499
  const deleteProgram = vi.spyOn(gl, 'deleteProgram');
@@ -244,7 +501,7 @@ describe('destroyGlRenderState', () => {
244
501
 
245
502
  destroyGlRenderState(state);
246
503
 
247
- expect(deleteProgram).toHaveBeenCalled();
504
+ expect(deleteProgram).not.toHaveBeenCalled();
248
505
  expect(deleteBuffer).toHaveBeenCalled();
249
506
  });
250
507
 
@@ -275,13 +532,65 @@ 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();
281
590
  const state = createGlRenderState(canvas);
282
591
  const runtime = getGlRenderStateRuntime(state);
283
592
  expect(runtime).toBeDefined();
284
- expect(runtime.defaultBitmapShader).toBeDefined();
593
+ expect(runtime.defaultBitmapShader).toBeNull();
285
594
  });
286
595
 
287
596
  it('resolves the same runtime object on repeated calls', () => {