@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.
Files changed (82) hide show
  1. package/README.md +2 -2
  2. package/dist/contract.d.ts +1 -3
  3. package/dist/contract.d.ts.map +1 -1
  4. package/dist/contract.js +1 -3
  5. package/dist/contract.js.map +1 -1
  6. package/dist/enableGlRenderStateGuards.js +2 -2
  7. package/dist/enableGlRenderStateGuards.js.map +1 -1
  8. package/dist/glCubeRenderTarget.d.ts +5 -5
  9. package/dist/glCubeRenderTarget.d.ts.map +1 -1
  10. package/dist/glCubeRenderTarget.js +16 -13
  11. package/dist/glCubeRenderTarget.js.map +1 -1
  12. package/dist/glFullscreenPass.d.ts +4 -3
  13. package/dist/glFullscreenPass.d.ts.map +1 -1
  14. package/dist/glFullscreenPass.js +152 -23
  15. package/dist/glFullscreenPass.js.map +1 -1
  16. package/dist/glLinearToSrgbPass.d.ts +2 -2
  17. package/dist/glLinearToSrgbPass.d.ts.map +1 -1
  18. package/dist/glLinearToSrgbPass.js.map +1 -1
  19. package/dist/glPresentRenderTarget.d.ts +2 -2
  20. package/dist/glPresentRenderTarget.d.ts.map +1 -1
  21. package/dist/glPresentRenderTarget.js.map +1 -1
  22. package/dist/glReadback.d.ts +2 -2
  23. package/dist/glReadback.d.ts.map +1 -1
  24. package/dist/glReadback.js +1 -1
  25. package/dist/glReadback.js.map +1 -1
  26. package/dist/glRenderPass.d.ts +6 -3
  27. package/dist/glRenderPass.d.ts.map +1 -1
  28. package/dist/glRenderPass.js +105 -86
  29. package/dist/glRenderPass.js.map +1 -1
  30. package/dist/glRenderState.d.ts +1 -2
  31. package/dist/glRenderState.d.ts.map +1 -1
  32. package/dist/glRenderState.js +53 -46
  33. package/dist/glRenderState.js.map +1 -1
  34. package/dist/glRenderTarget.d.ts +17 -16
  35. package/dist/glRenderTarget.d.ts.map +1 -1
  36. package/dist/glRenderTarget.js +38 -26
  37. package/dist/glRenderTarget.js.map +1 -1
  38. package/dist/glRenderTargetPool.d.ts +9 -9
  39. package/dist/glRenderTargetPool.d.ts.map +1 -1
  40. package/dist/glRenderTargetPool.js +10 -12
  41. package/dist/glRenderTargetPool.js.map +1 -1
  42. package/dist/glRenderTexture.d.ts +2 -2
  43. package/dist/glRenderTexture.d.ts.map +1 -1
  44. package/dist/glRenderTexture.js +11 -13
  45. package/dist/glRenderTexture.js.map +1 -1
  46. package/dist/glRenderTexturePool.js +3 -5
  47. package/dist/glRenderTexturePool.js.map +1 -1
  48. package/dist/glSkinPaletteTexture.js +8 -2
  49. package/dist/glSkinPaletteTexture.js.map +1 -1
  50. package/dist/glTestHelper.d.ts +0 -1
  51. package/dist/glTestHelper.d.ts.map +1 -1
  52. package/dist/glTestHelper.js +0 -1
  53. package/dist/glTestHelper.js.map +1 -1
  54. package/dist/index.d.ts +1 -1
  55. package/dist/index.d.ts.map +1 -1
  56. package/dist/index.js +1 -1
  57. package/dist/index.js.map +1 -1
  58. package/package.json +11 -11
  59. package/src/enableGlRenderStateGuards.test.ts +2 -4
  60. package/src/glContext.test.ts +3 -52
  61. package/src/glCubeRenderTarget.test.ts +10 -8
  62. package/src/glDraw.test.ts +4 -16
  63. package/src/glFullscreenPass.test.ts +145 -41
  64. package/src/glLinearToSrgbPass.test.ts +8 -5
  65. package/src/glPresentRenderTarget.test.ts +3 -5
  66. package/src/glReadback.test.ts +3 -3
  67. package/src/glRenderPass.test.ts +228 -55
  68. package/src/glRenderState.test.ts +106 -165
  69. package/src/glRenderTarget.test.ts +134 -99
  70. package/src/glRenderTargetPool.test.ts +69 -76
  71. package/src/glRenderTexture.test.ts +58 -31
  72. package/src/glSkinPaletteTexture.test.ts +9 -0
  73. package/src/glTextureResolver.test.ts +5 -1
  74. package/dist/glBackground.d.ts +0 -3
  75. package/dist/glBackground.d.ts.map +0 -1
  76. package/dist/glBackground.js +0 -19
  77. package/dist/glBackground.js.map +0 -1
  78. package/dist/glContext.d.ts +0 -3
  79. package/dist/glContext.d.ts.map +0 -1
  80. package/dist/glContext.js +0 -16
  81. package/dist/glContext.js.map +0 -1
  82. package/src/glBackground.test.ts +0 -68
@@ -1,17 +1,17 @@
1
1
  import {
2
- acquireGlRenderTarget,
3
- createGlRenderTargetPool,
4
- destroyGlRenderTargetPool,
5
- initializeGlRenderTargetPool,
6
- releaseGlRenderTarget,
2
+ acquireGlTextureRenderTarget,
3
+ createGlTextureRenderTargetPool,
4
+ destroyGlTextureRenderTargetPool,
5
+ initializeGlTextureRenderTargetPool,
6
+ releaseGlTextureRenderTarget,
7
7
  } from './glRenderTargetPool';
8
8
  import { createGlState } from './glTestHelper';
9
9
 
10
- describe('acquireGlRenderTarget', () => {
10
+ describe('acquireGlTextureRenderTarget', () => {
11
11
  it('allocates a new target when the pool is empty', () => {
12
12
  const { state } = createGlState();
13
- const pool = createGlRenderTargetPool();
14
- const target = acquireGlRenderTarget(state, pool, { width: 64, height: 48 });
13
+ const pool = createGlTextureRenderTargetPool();
14
+ const target = acquireGlTextureRenderTarget(state, pool, { width: 64, height: 48 });
15
15
  expect(target.width).toBe(64);
16
16
  expect(target.height).toBe(48);
17
17
  expect(pool.free.length).toBe(0);
@@ -19,39 +19,39 @@ describe('acquireGlRenderTarget', () => {
19
19
 
20
20
  it('reuses a matching released target instead of allocating', () => {
21
21
  const { state } = createGlState();
22
- const pool = createGlRenderTargetPool();
23
- const first = acquireGlRenderTarget(state, pool, { width: 64, height: 48 });
24
- releaseGlRenderTarget(pool, first);
22
+ const pool = createGlTextureRenderTargetPool();
23
+ const first = acquireGlTextureRenderTarget(state, pool, { width: 64, height: 48 });
24
+ releaseGlTextureRenderTarget(pool, first);
25
25
 
26
- const reused = acquireGlRenderTarget(state, pool, { width: 64, height: 48 });
26
+ const reused = acquireGlTextureRenderTarget(state, pool, { width: 64, height: 48 });
27
27
  expect(reused).toBe(first);
28
28
  expect(pool.free.length).toBe(0);
29
29
  });
30
30
 
31
31
  it('does not reuse a target with a different color space', () => {
32
32
  const { state } = createGlState();
33
- const pool = createGlRenderTargetPool();
34
- const first = acquireGlRenderTarget(state, pool, { width: 64, height: 48, colorSpace: 'linear' });
33
+ const pool = createGlTextureRenderTargetPool();
34
+ const first = acquireGlTextureRenderTarget(state, pool, { width: 64, height: 48, colorSpace: 'linear' });
35
35
  expect(first.colorSpace).toBe('linear');
36
- releaseGlRenderTarget(pool, first);
36
+ releaseGlTextureRenderTarget(pool, first);
37
37
 
38
- const other = acquireGlRenderTarget(state, pool, { width: 64, height: 48, colorSpace: 'srgb' });
38
+ const other = acquireGlTextureRenderTarget(state, pool, { width: 64, height: 48, colorSpace: 'srgb' });
39
39
  expect(other).not.toBe(first);
40
40
  expect(pool.free).toContain(first);
41
41
  });
42
42
 
43
43
  it('does not reuse a target with different heterogeneous color formats', () => {
44
44
  const { state } = createGlState();
45
- const pool = createGlRenderTargetPool();
46
- const first = acquireGlRenderTarget(state, pool, {
45
+ const pool = createGlTextureRenderTargetPool();
46
+ const first = acquireGlTextureRenderTarget(state, pool, {
47
47
  width: 64,
48
48
  height: 48,
49
49
  colorAttachments: 2,
50
50
  colorFormats: ['rgba8', 'rgba8'],
51
51
  });
52
- releaseGlRenderTarget(pool, first);
52
+ releaseGlTextureRenderTarget(pool, first);
53
53
 
54
- const other = acquireGlRenderTarget(state, pool, {
54
+ const other = acquireGlTextureRenderTarget(state, pool, {
55
55
  width: 64,
56
56
  height: 48,
57
57
  colorAttachments: 2,
@@ -64,11 +64,11 @@ describe('acquireGlRenderTarget', () => {
64
64
  it('reuses the effective rgba8 target when a preferred float format is unsupported', () => {
65
65
  const { state, gl } = createGlState();
66
66
  vi.spyOn(gl, 'getExtension').mockReturnValue(null);
67
- const pool = createGlRenderTargetPool();
68
- const first = acquireGlRenderTarget(state, pool, { width: 64, height: 48, format: 'rgba8' });
69
- releaseGlRenderTarget(pool, first);
67
+ const pool = createGlTextureRenderTargetPool();
68
+ const first = acquireGlTextureRenderTarget(state, pool, { width: 64, height: 48, format: 'rgba8' });
69
+ releaseGlTextureRenderTarget(pool, first);
70
70
 
71
- const reused = acquireGlRenderTarget(state, pool, { width: 64, height: 48, format: 'rgba32f' }, 'preferred');
71
+ const reused = acquireGlTextureRenderTarget(state, pool, { width: 64, height: 48, format: 'rgba32f' }, 'preferred');
72
72
  expect(reused).toBe(first);
73
73
  expect(reused.format).toBe('rgba8');
74
74
  expect(reused.requestedAxes.format).toBe('rgba32f');
@@ -78,15 +78,15 @@ describe('acquireGlRenderTarget', () => {
78
78
  it('refuses an unsupported required float format without consuming a fallback target', () => {
79
79
  const { state, gl } = createGlState();
80
80
  vi.spyOn(gl, 'getExtension').mockReturnValue(null);
81
- const pool = createGlRenderTargetPool();
82
- const fallback = acquireGlRenderTarget(state, pool, { width: 64, height: 48, format: 'rgba8' });
83
- releaseGlRenderTarget(pool, fallback);
81
+ const pool = createGlTextureRenderTargetPool();
82
+ const fallback = acquireGlTextureRenderTarget(state, pool, { width: 64, height: 48, format: 'rgba8' });
83
+ releaseGlTextureRenderTarget(pool, fallback);
84
84
  const createFramebufferSpy = vi.spyOn(gl, 'createFramebuffer');
85
85
  const clearSpy = vi.spyOn(gl, 'clear');
86
86
  createFramebufferSpy.mockClear();
87
87
  clearSpy.mockClear();
88
88
 
89
- const refused = acquireGlRenderTarget(state, pool, { width: 64, height: 48, format: 'rgba32f' }, 'required');
89
+ const refused = acquireGlTextureRenderTarget(state, pool, { width: 64, height: 48, format: 'rgba32f' }, 'required');
90
90
  expect(refused).toBeNull();
91
91
  expect(pool.free).toEqual([fallback]);
92
92
  expect(createFramebufferSpy).not.toHaveBeenCalled();
@@ -96,9 +96,9 @@ describe('acquireGlRenderTarget', () => {
96
96
  it('allocates a supported required float format', () => {
97
97
  const { state, gl } = createGlState();
98
98
  vi.spyOn(gl, 'getExtension').mockReturnValue({} as never);
99
- const pool = createGlRenderTargetPool();
99
+ const pool = createGlTextureRenderTargetPool();
100
100
 
101
- const target = acquireGlRenderTarget(state, pool, { width: 64, height: 48, format: 'rgba16f' }, 'required');
101
+ const target = acquireGlTextureRenderTarget(state, pool, { width: 64, height: 48, format: 'rgba16f' }, 'required');
102
102
  expect(target).not.toBeNull();
103
103
  expect(target?.format).toBe('rgba16f');
104
104
  expect(target?.requestedAxes.format).toBe('rgba16f');
@@ -106,35 +106,29 @@ describe('acquireGlRenderTarget', () => {
106
106
 
107
107
  it('does not reuse a target with a different depth mode', () => {
108
108
  const { state } = createGlState();
109
- const pool = createGlRenderTargetPool();
110
- const first = acquireGlRenderTarget(state, pool, { width: 64, height: 48, depth: 'none' });
111
- releaseGlRenderTarget(pool, first);
109
+ const pool = createGlTextureRenderTargetPool();
110
+ const first = acquireGlTextureRenderTarget(state, pool, { width: 64, height: 48, depth: 'none' });
111
+ releaseGlTextureRenderTarget(pool, first);
112
112
 
113
- const other = acquireGlRenderTarget(state, pool, { width: 64, height: 48, depth: 'depth-stencil' });
113
+ const other = acquireGlTextureRenderTarget(state, pool, { width: 64, height: 48, depth: 'depth-stencil' });
114
114
  expect(other).not.toBe(first);
115
115
  expect(pool.free).toContain(first);
116
116
  });
117
117
 
118
- it('re-stamps clear policy and requested axes on a compatible target', () => {
118
+ it('re-stamps requested axes on a compatible target', () => {
119
119
  const { state } = createGlState();
120
- const pool = createGlRenderTargetPool();
121
- const first = acquireGlRenderTarget(state, pool, {
120
+ const pool = createGlTextureRenderTargetPool();
121
+ const first = acquireGlTextureRenderTarget(state, pool, {
122
122
  width: 64,
123
123
  height: 48,
124
- clearColors: [0xff0000ff],
125
- clearDepth: 0.25,
126
124
  });
127
- releaseGlRenderTarget(pool, first);
125
+ releaseGlTextureRenderTarget(pool, first);
128
126
 
129
- const reused = acquireGlRenderTarget(state, pool, {
127
+ const reused = acquireGlTextureRenderTarget(state, pool, {
130
128
  width: 64,
131
129
  height: 48,
132
- clearColors: [0x00ff00ff],
133
- clearDepth: 0.75,
134
130
  });
135
131
  expect(reused).toBe(first);
136
- expect(reused.clearColors).toEqual([0x00ff00ff]);
137
- expect(reused.clearDepth).toBe(0.75);
138
132
  expect(reused.requestedAxes).toEqual({
139
133
  width: 64,
140
134
  height: 48,
@@ -149,11 +143,11 @@ describe('acquireGlRenderTarget', () => {
149
143
 
150
144
  it('allocates a new target when the free target has different dimensions', () => {
151
145
  const { state } = createGlState();
152
- const pool = createGlRenderTargetPool();
153
- const first = acquireGlRenderTarget(state, pool, { width: 64, height: 48 });
154
- releaseGlRenderTarget(pool, first);
146
+ const pool = createGlTextureRenderTargetPool();
147
+ const first = acquireGlTextureRenderTarget(state, pool, { width: 64, height: 48 });
148
+ releaseGlTextureRenderTarget(pool, first);
155
149
 
156
- const other = acquireGlRenderTarget(state, pool, { width: 128, height: 48 });
150
+ const other = acquireGlTextureRenderTarget(state, pool, { width: 128, height: 48 });
157
151
  expect(other).not.toBe(first);
158
152
  // The mismatched target stays parked in the free list.
159
153
  expect(pool.free).toContain(first);
@@ -161,47 +155,46 @@ describe('acquireGlRenderTarget', () => {
161
155
 
162
156
  it('matches the ceiled, clamped descriptor dimensions', () => {
163
157
  const { state } = createGlState();
164
- const pool = createGlRenderTargetPool();
165
- const first = acquireGlRenderTarget(state, pool, { width: 10.2, height: 0 });
166
- releaseGlRenderTarget(pool, first);
158
+ const pool = createGlTextureRenderTargetPool();
159
+ const first = acquireGlTextureRenderTarget(state, pool, { width: 10.2, height: 0 });
160
+ releaseGlTextureRenderTarget(pool, first);
167
161
 
168
162
  // 10.2 -> 11, 0 -> 1; an equivalent descriptor must hit the same parked target.
169
- const reused = acquireGlRenderTarget(state, pool, { width: 10.9, height: 0.4 });
163
+ const reused = acquireGlTextureRenderTarget(state, pool, { width: 10.9, height: 0.4 });
170
164
  expect(reused).toBe(first);
171
165
  });
172
166
 
173
167
  it('clears a reused target so it is handed back clean', () => {
174
168
  const { state, gl } = createGlState();
175
- const pool = createGlRenderTargetPool();
176
- const first = acquireGlRenderTarget(state, pool, { width: 64, height: 48 });
177
- releaseGlRenderTarget(pool, first);
169
+ const pool = createGlTextureRenderTargetPool();
170
+ const first = acquireGlTextureRenderTarget(state, pool, { width: 64, height: 48 });
171
+ releaseGlTextureRenderTarget(pool, first);
178
172
 
179
- // Only the reuse path clears — spy after the first (fresh) acquire so we measure just the reuse.
180
- const clearSpy = vi.spyOn(gl, 'clear');
181
- const reused = acquireGlRenderTarget(state, pool, { width: 64, height: 48 });
173
+ const clearSpy = vi.spyOn(gl, 'clearBufferfv');
174
+ const reused = acquireGlTextureRenderTarget(state, pool, { width: 64, height: 48 });
182
175
  expect(reused).toBe(first);
183
176
  expect(clearSpy).toHaveBeenCalled();
184
177
  });
185
178
  });
186
179
 
187
- describe('createGlRenderTargetPool', () => {
180
+ describe('createGlTextureRenderTargetPool', () => {
188
181
  it('returns a pool with an empty free list', () => {
189
- const pool = createGlRenderTargetPool();
182
+ const pool = createGlTextureRenderTargetPool();
190
183
  expect(pool.free).toEqual([]);
191
184
  });
192
185
  });
193
186
 
194
- describe('destroyGlRenderTargetPool', () => {
187
+ describe('destroyGlTextureRenderTargetPool', () => {
195
188
  it('destroys every parked target and empties the free list', () => {
196
189
  const { state, gl } = createGlState();
197
- const pool = createGlRenderTargetPool();
198
- const a = acquireGlRenderTarget(state, pool, { width: 32, height: 32 });
199
- const b = acquireGlRenderTarget(state, pool, { width: 16, height: 16 });
200
- releaseGlRenderTarget(pool, a);
201
- releaseGlRenderTarget(pool, b);
190
+ const pool = createGlTextureRenderTargetPool();
191
+ const a = acquireGlTextureRenderTarget(state, pool, { width: 32, height: 32 });
192
+ const b = acquireGlTextureRenderTarget(state, pool, { width: 16, height: 16 });
193
+ releaseGlTextureRenderTarget(pool, a);
194
+ releaseGlTextureRenderTarget(pool, b);
202
195
 
203
196
  const deleteSpy = vi.spyOn(gl, 'deleteFramebuffer');
204
- destroyGlRenderTargetPool(state, pool);
197
+ destroyGlTextureRenderTargetPool(pool);
205
198
 
206
199
  expect(deleteSpy).toHaveBeenCalledWith(a.framebuffer);
207
200
  expect(deleteSpy).toHaveBeenCalledWith(b.framebuffer);
@@ -210,24 +203,24 @@ describe('destroyGlRenderTargetPool', () => {
210
203
 
211
204
  it('is a no-op on an empty pool', () => {
212
205
  const { state } = createGlState();
213
- const pool = createGlRenderTargetPool();
214
- expect(() => destroyGlRenderTargetPool(state, pool)).not.toThrow();
206
+ const pool = createGlTextureRenderTargetPool();
207
+ expect(() => destroyGlTextureRenderTargetPool(pool)).not.toThrow();
215
208
  });
216
209
  });
217
210
 
218
- describe('initializeGlRenderTargetPool', () => {
211
+ describe('initializeGlTextureRenderTargetPool', () => {
219
212
  it('is the construction initializer of createGlRenderTargetPool', () => {
220
- expect(typeof initializeGlRenderTargetPool).toBe('function');
213
+ expect(typeof initializeGlTextureRenderTargetPool).toBe('function');
221
214
  });
222
215
  });
223
- describe('releaseGlRenderTarget', () => {
216
+ describe('releaseGlTextureRenderTarget', () => {
224
217
  it('returns the target to the free list without destroying it', () => {
225
218
  const { state, gl } = createGlState();
226
- const pool = createGlRenderTargetPool();
227
- const target = acquireGlRenderTarget(state, pool, { width: 64, height: 64 });
219
+ const pool = createGlTextureRenderTargetPool();
220
+ const target = acquireGlTextureRenderTarget(state, pool, { width: 64, height: 64 });
228
221
 
229
222
  const deleteSpy = vi.spyOn(gl, 'deleteFramebuffer');
230
- releaseGlRenderTarget(pool, target);
223
+ releaseGlTextureRenderTarget(pool, target);
231
224
 
232
225
  expect(pool.free).toContain(target);
233
226
  expect(deleteSpy).not.toHaveBeenCalled();
@@ -1,5 +1,5 @@
1
1
  import { addLogSink, createMemoryLogSink, getMemoryLogSinkEntries, removeLogSink } from '@flighthq/log/contract';
2
- import type { RenderTexture } from '@flighthq/types/contract';
2
+ import type { RenderTargetClear, RenderTexture } from '@flighthq/types/contract';
3
3
  import { RenderTargetTextureSourceKind } from '@flighthq/types/contract';
4
4
 
5
5
  import { enableGlRenderTextureGuards } from './enableGlRenderTextureGuards';
@@ -23,7 +23,7 @@ describe('bindGlRenderTexture', () => {
23
23
  it('binds the existing target texture without uploading pixels', () => {
24
24
  const { state, gl } = createRenderTextureState();
25
25
  const renderTexture = createRenderTexture({ height: 8, width: 8 });
26
- renderIntoGlRenderTexture(state, renderTexture, () => {});
26
+ renderIntoGlRenderTexture(state, renderTexture, () => {}, TRANSPARENT_CLEAR);
27
27
  const uploads = vi.mocked(gl.texImage2D).mock.calls.length;
28
28
 
29
29
  const texture = bindGlRenderTexture(state, renderTexture);
@@ -47,7 +47,7 @@ describe('clearGlRenderTexture', () => {
47
47
 
48
48
  clearGlRenderTexture(state, renderTexture);
49
49
 
50
- expect(gl.clear).toHaveBeenCalledWith(gl.COLOR_BUFFER_BIT);
50
+ expect(gl.clearBufferfv).toHaveBeenCalledWith(gl.COLOR, 0, expect.anything());
51
51
  expect(isGlRenderTextureReady(state, renderTexture)).toBe(true);
52
52
  expect(renderTexture.version).toBe(1);
53
53
  });
@@ -57,7 +57,7 @@ describe('destroyGlRenderTexture', () => {
57
57
  it('deletes the hidden target and returns the source to unrendered state', () => {
58
58
  const { state, gl } = createRenderTextureState();
59
59
  const renderTexture = createRenderTexture({ height: 8, width: 8 });
60
- renderIntoGlRenderTexture(state, renderTexture, () => {});
60
+ renderIntoGlRenderTexture(state, renderTexture, () => {}, TRANSPARENT_CLEAR);
61
61
 
62
62
  destroyGlRenderTexture(state, renderTexture);
63
63
 
@@ -76,9 +76,14 @@ describe('enableGlRenderTextureGuards', () => {
76
76
  enableGlRenderTextureGuards(state);
77
77
  try {
78
78
  bindGlRenderTexture(state, unrendered);
79
- renderIntoGlRenderTexture(state, writing, () => {
80
- bindGlRenderTexture(state, writing);
81
- });
79
+ renderIntoGlRenderTexture(
80
+ state,
81
+ writing,
82
+ () => {
83
+ bindGlRenderTexture(state, writing);
84
+ },
85
+ TRANSPARENT_CLEAR,
86
+ );
82
87
 
83
88
  const entries = getMemoryLogSinkEntries(sink);
84
89
  expect(entries).toHaveLength(2);
@@ -108,7 +113,7 @@ describe('getGlRenderTextureColorSpace', () => {
108
113
  const renderTexture = createRenderTexture({ height: 8, width: 8 });
109
114
 
110
115
  expect(getGlRenderTextureColorSpace(state, renderTexture)).toBe('linear');
111
- renderIntoGlRenderTexture(state, renderTexture, () => {});
116
+ renderIntoGlRenderTexture(state, renderTexture, () => {}, TRANSPARENT_CLEAR);
112
117
  expect(getGlRenderTextureColorSpace(state, renderTexture)).toBe('linear');
113
118
  });
114
119
  });
@@ -145,7 +150,7 @@ describe('isGlRenderTextureReady', () => {
145
150
  const renderTexture = createRenderTexture({ height: 8, width: 8 });
146
151
 
147
152
  expect(isGlRenderTextureReady(state, renderTexture)).toBe(false);
148
- renderIntoGlRenderTexture(state, renderTexture, () => {});
153
+ renderIntoGlRenderTexture(state, renderTexture, () => {}, TRANSPARENT_CLEAR);
149
154
  expect(isGlRenderTextureReady(state, renderTexture)).toBe(true);
150
155
  });
151
156
  });
@@ -154,8 +159,8 @@ describe('renderIntoGlRenderTexture', () => {
154
159
  it('lazily creates a target and marks it ready after the callback completes', () => {
155
160
  const { state } = createRenderTextureState();
156
161
  const renderTexture = createRenderTexture({ depth: true, height: 24, width: 32 });
157
- const callback = vi.fn((callbackState) => {
158
- expect(callbackState).toBe(state);
162
+ const callback = vi.fn((pass) => {
163
+ expect(pass.state).toBe(state);
159
164
  expect(explainGlRenderTexture(state, renderTexture).status).toBe('writing');
160
165
  });
161
166
 
@@ -166,7 +171,7 @@ describe('renderIntoGlRenderTexture', () => {
166
171
  });
167
172
  expect(renderTexture.version).toBe(0);
168
173
 
169
- renderIntoGlRenderTexture(state, renderTexture, callback);
174
+ renderIntoGlRenderTexture(state, renderTexture, callback, TRANSPARENT_CLEAR);
170
175
 
171
176
  expect(callback).toHaveBeenCalledOnce();
172
177
  expect(renderTexture.version).toBe(1);
@@ -183,9 +188,14 @@ describe('renderIntoGlRenderTexture', () => {
183
188
  const renderTexture = createRenderTexture({ height: 8, width: 8 });
184
189
 
185
190
  expect(() =>
186
- renderIntoGlRenderTexture(state, renderTexture, () => {
187
- throw new Error('producer failed');
188
- }),
191
+ renderIntoGlRenderTexture(
192
+ state,
193
+ renderTexture,
194
+ () => {
195
+ throw new Error('producer failed');
196
+ },
197
+ TRANSPARENT_CLEAR,
198
+ ),
189
199
  ).toThrow('producer failed');
190
200
 
191
201
  expect(explainGlRenderTexture(state, renderTexture).status).toBe('unrendered');
@@ -195,12 +205,17 @@ describe('renderIntoGlRenderTexture', () => {
195
205
  it('invalidates a previously-ready target when a replacement render throws', () => {
196
206
  const { state } = createRenderTextureState();
197
207
  const renderTexture = createRenderTexture({ height: 8, width: 8 });
198
- renderIntoGlRenderTexture(state, renderTexture, () => {});
208
+ renderIntoGlRenderTexture(state, renderTexture, () => {}, TRANSPARENT_CLEAR);
199
209
 
200
210
  expect(() =>
201
- renderIntoGlRenderTexture(state, renderTexture, () => {
202
- throw new Error('replacement failed');
203
- }),
211
+ renderIntoGlRenderTexture(
212
+ state,
213
+ renderTexture,
214
+ () => {
215
+ throw new Error('replacement failed');
216
+ },
217
+ TRANSPARENT_CLEAR,
218
+ ),
204
219
  ).toThrow('replacement failed');
205
220
 
206
221
  expect(explainGlRenderTexture(state, renderTexture).status).toBe('unrendered');
@@ -211,14 +226,19 @@ describe('renderIntoGlRenderTexture', () => {
211
226
  const { state } = createRenderTextureState();
212
227
  const renderTexture = createRenderTexture({ height: 8, width: 8 });
213
228
 
214
- renderIntoGlRenderTexture(state, renderTexture, () => {
215
- getGlRenderStateRuntime(state).currentMaskDepth = 1;
229
+ renderIntoGlRenderTexture(
230
+ state,
231
+ renderTexture,
232
+ () => {
233
+ getGlRenderStateRuntime(state).currentMaskDepth = 1;
216
234
 
217
- expect(() => renderIntoGlRenderTexture(state, renderTexture, () => {})).toThrow(
218
- 'cannot nest the active framebuffer while a contour clip is live',
219
- );
220
- expect(explainGlRenderTexture(state, renderTexture).status).toBe('writing');
221
- });
235
+ expect(() => renderIntoGlRenderTexture(state, renderTexture, () => {}, TRANSPARENT_CLEAR)).toThrow(
236
+ 'cannot nest the active framebuffer while a contour clip is live',
237
+ );
238
+ expect(explainGlRenderTexture(state, renderTexture).status).toBe('writing');
239
+ },
240
+ TRANSPARENT_CLEAR,
241
+ );
222
242
 
223
243
  expect(explainGlRenderTexture(state, renderTexture).status).toBe('ready');
224
244
  });
@@ -226,11 +246,11 @@ describe('renderIntoGlRenderTexture', () => {
226
246
  it('resizes the retained target when the source dimensions change', () => {
227
247
  const { state } = createRenderTextureState();
228
248
  const renderTexture = createRenderTexture({ height: 16, width: 32 });
229
- renderIntoGlRenderTexture(state, renderTexture, () => {});
249
+ renderIntoGlRenderTexture(state, renderTexture, () => {}, TRANSPARENT_CLEAR);
230
250
 
231
251
  renderTexture.source.width = 12;
232
252
  renderTexture.source.height = 10;
233
- renderIntoGlRenderTexture(state, renderTexture, () => {});
253
+ renderIntoGlRenderTexture(state, renderTexture, () => {}, TRANSPARENT_CLEAR);
234
254
 
235
255
  expect(explainGlRenderTexture(state, renderTexture)).toEqual({
236
256
  height: 10,
@@ -244,9 +264,14 @@ describe('renderIntoGlRenderTexture', () => {
244
264
  const renderTexture = createRenderTexture({ height: 8, width: 8 });
245
265
  let whileWriting: WebGLTexture | null = {} as WebGLTexture;
246
266
 
247
- renderIntoGlRenderTexture(state, renderTexture, () => {
248
- whileWriting = bindGlRenderTexture(state, renderTexture);
249
- });
267
+ renderIntoGlRenderTexture(
268
+ state,
269
+ renderTexture,
270
+ () => {
271
+ whileWriting = bindGlRenderTexture(state, renderTexture);
272
+ },
273
+ TRANSPARENT_CLEAR,
274
+ );
250
275
 
251
276
  expect(whileWriting).toBeNull();
252
277
  expect(bindGlRenderTexture(state, renderTexture)).not.toBeNull();
@@ -324,3 +349,5 @@ function createRenderTexture(options: { depth?: boolean; height: number; width:
324
349
  version: 0,
325
350
  } as unknown as RenderTexture;
326
351
  }
352
+
353
+ const TRANSPARENT_CLEAR: Readonly<RenderTargetClear> = { color: [0, 0, 0, 0], depth: 1.0, stencil: 0 };
@@ -159,6 +159,15 @@ describe('uploadGlSkinPaletteTexture', () => {
159
159
  expect(palette.jointCapacity).toBe(6);
160
160
  });
161
161
 
162
+ it('throws when MAX_TEXTURE_SIZE is not positive', () => {
163
+ const { gl } = makeGl(0);
164
+ const palette = createGlSkinPaletteTexture(gl);
165
+
166
+ expect(() => uploadGlSkinPaletteTexture(gl, palette, makePalette(1), 1)).toThrow(
167
+ 'WebGL MAX_TEXTURE_SIZE must be greater than zero',
168
+ );
169
+ });
170
+
162
171
  it('wraps into multiple rows when totalTexels exceeds MAX_TEXTURE_SIZE', () => {
163
172
  const { gl, calls } = makeGl(16);
164
173
  const palette = createGlSkinPaletteTexture(gl);
@@ -206,7 +206,11 @@ describe('registerGlRenderTextureResolver', () => {
206
206
  return previous?.(parameter);
207
207
  });
208
208
  const texture = textureWithTarget();
209
- renderIntoGlRenderTexture(state, texture as RenderTexture, () => {});
209
+ renderIntoGlRenderTexture(state, texture as RenderTexture, () => {}, {
210
+ color: [0, 0, 0, 0],
211
+ depth: 1.0,
212
+ stencil: 0,
213
+ });
210
214
  registerGlRenderTextureResolver(state);
211
215
  const uploads = vi.mocked(gl.texImage2D).mock.calls.length;
212
216
 
@@ -1,3 +0,0 @@
1
- import type { GlRenderState } from '@flighthq/types/contract';
2
- export declare function renderGlBackground(state: GlRenderState): void;
3
- //# sourceMappingURL=glBackground.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"glBackground.d.ts","sourceRoot":"","sources":["../src/glBackground.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAI9D,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI,CAwB7D"}
@@ -1,19 +0,0 @@
1
- import { srgbChannelToLinear } from '@flighthq/color/contract';
2
- import { getGlRenderStateRuntime } from './glRenderState';
3
- export function renderGlBackground(state) {
4
- const runtime = getGlRenderStateRuntime(state);
5
- const gl = state.gl;
6
- const viewport = runtime.renderTargetViewport;
7
- gl.viewport(viewport?.x ?? 0, viewport?.y ?? 0, viewport?.width ?? gl.drawingBufferWidth, viewport?.height ?? gl.drawingBufferHeight);
8
- const rgba = state.backgroundColorRgba;
9
- if (rgba.length >= 4 && rgba[3] > 0) {
10
- const linear = runtime.currentRenderTarget?.colorSpace === 'linear';
11
- gl.clearColor(linear ? srgbChannelToLinear(rgba[0]) : rgba[0], linear ? srgbChannelToLinear(rgba[1]) : rgba[1], linear ? srgbChannelToLinear(rgba[2]) : rgba[2], rgba[3]);
12
- }
13
- else {
14
- gl.clearColor(0, 0, 0, 0);
15
- }
16
- gl.clear(gl.COLOR_BUFFER_BIT);
17
- runtime.context.currentBlendSignature = null;
18
- }
19
- //# sourceMappingURL=glBackground.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"glBackground.js","sourceRoot":"","sources":["../src/glBackground.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAG/D,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAE1D,MAAM,UAAU,kBAAkB,CAAC,KAAoB;IACrD,MAAM,OAAO,GAAG,uBAAuB,CAAC,KAAK,CAAC,CAAC;IAC/C,MAAM,EAAE,GAAG,KAAK,CAAC,EAAE,CAAC;IACpB,MAAM,QAAQ,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAC9C,EAAE,CAAC,QAAQ,CACT,QAAQ,EAAE,CAAC,IAAI,CAAC,EAChB,QAAQ,EAAE,CAAC,IAAI,CAAC,EAChB,QAAQ,EAAE,KAAK,IAAI,EAAE,CAAC,kBAAkB,EACxC,QAAQ,EAAE,MAAM,IAAI,EAAE,CAAC,mBAAmB,CAC3C,CAAC;IACF,MAAM,IAAI,GAAG,KAAK,CAAC,mBAAmB,CAAC;IACvC,IAAI,IAAI,CAAC,MAAM,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;QACpC,MAAM,MAAM,GAAG,OAAO,CAAC,mBAAmB,EAAE,UAAU,KAAK,QAAQ,CAAC;QACpE,EAAE,CAAC,UAAU,CACX,MAAM,CAAC,CAAC,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAC/C,MAAM,CAAC,CAAC,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAC/C,MAAM,CAAC,CAAC,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAC/C,IAAI,CAAC,CAAC,CAAC,CACR,CAAC;IACJ,CAAC;SAAM,CAAC;QACN,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IAC5B,CAAC;IACD,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,gBAAgB,CAAC,CAAC;IAC9B,OAAO,CAAC,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC;AAC/C,CAAC"}
@@ -1,3 +0,0 @@
1
- import type { GlContext, GlContextOptions } from '@flighthq/types/contract';
2
- export declare function createGlContextFromCanvasElement(canvas: HTMLCanvasElement, options?: Readonly<GlContextOptions>): GlContext;
3
- //# sourceMappingURL=glContext.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"glContext.d.ts","sourceRoot":"","sources":["../src/glContext.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAI5E,wBAAgB,gCAAgC,CAC9C,MAAM,EAAE,iBAAiB,EACzB,OAAO,GAAE,QAAQ,CAAC,gBAAgB,CAAM,GACvC,SAAS,CAWX"}
package/dist/glContext.js DELETED
@@ -1,16 +0,0 @@
1
- // Web-only acquisition constructor. Context-first/native callers do not import this module, keeping
2
- // HTMLCanvasElement.getContext out of their bundles.
3
- export function createGlContextFromCanvasElement(canvas, options = {}) {
4
- const contextAttributes = {
5
- alpha: true,
6
- antialias: options.antialias ?? true,
7
- powerPreference: options.powerPreference ?? 'default',
8
- stencil: true,
9
- ...options.contextAttributes,
10
- };
11
- const context = canvas.getContext('webgl2', contextAttributes);
12
- if (context === null)
13
- throw new Error('Failed to get WebGL2 context.');
14
- return context;
15
- }
16
- //# sourceMappingURL=glContext.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"glContext.js","sourceRoot":"","sources":["../src/glContext.ts"],"names":[],"mappings":"AAEA,oGAAoG;AACpG,qDAAqD;AACrD,MAAM,UAAU,gCAAgC,CAC9C,MAAyB,EACzB,UAAsC,EAAE;IAExC,MAAM,iBAAiB,GAA2B;QAChD,KAAK,EAAE,IAAI;QACX,SAAS,EAAE,OAAO,CAAC,SAAS,IAAI,IAAI;QACpC,eAAe,EAAE,OAAO,CAAC,eAAe,IAAI,SAAS;QACrD,OAAO,EAAE,IAAI;QACb,GAAG,OAAO,CAAC,iBAAiB;KAC7B,CAAC;IACF,MAAM,OAAO,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,EAAE,iBAAiB,CAAC,CAAC;IAC/D,IAAI,OAAO,KAAK,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC;IACvE,OAAO,OAAO,CAAC;AACjB,CAAC"}
@@ -1,68 +0,0 @@
1
- import { srgbChannelToLinear } from '@flighthq/color/contract';
2
-
3
- import { renderGlBackground } from './glBackground';
4
- import { getGlRenderStateRuntime } from './glRenderState';
5
- import { createGlState } from './glTestHelper';
6
-
7
- describe('renderGlBackground', () => {
8
- it('calls clearColor with zeros when background alpha is 0', () => {
9
- const { state, gl } = createGlState({ backgroundColorRgba: [0, 0, 0, 0] });
10
- renderGlBackground(state);
11
- expect(gl.clearColor).toHaveBeenCalledWith(0, 0, 0, 0);
12
- });
13
-
14
- it('calls clearColor with the RGBA values when background is non-transparent', () => {
15
- const { state, gl } = createGlState({ backgroundColorRgba: [1, 0, 0, 1] });
16
- renderGlBackground(state);
17
- expect(gl.clearColor).toHaveBeenCalledWith(1, 0, 0, 1);
18
- });
19
-
20
- it('calls clearColor with zeros when RGBA array has fewer than 4 elements', () => {
21
- const { state, gl } = createGlState({ backgroundColorRgba: [] });
22
- renderGlBackground(state);
23
- expect(gl.clearColor).toHaveBeenCalledWith(0, 0, 0, 0);
24
- });
25
-
26
- it('always calls gl.clear with COLOR_BUFFER_BIT', () => {
27
- const { state, gl } = createGlState();
28
- renderGlBackground(state);
29
- expect(gl.clear).toHaveBeenCalledWith((gl as unknown as { COLOR_BUFFER_BIT: number }).COLOR_BUFFER_BIT);
30
- });
31
-
32
- it('preserves an active partial-target viewport origin', () => {
33
- const { state, gl } = createGlState();
34
- getGlRenderStateRuntime(state).renderTargetViewport = { height: 40, width: 30, x: 10, y: 20 };
35
-
36
- renderGlBackground(state);
37
-
38
- expect(gl.viewport).toHaveBeenLastCalledWith(10, 20, 30, 40);
39
- });
40
-
41
- it('invalidates currentBlendSignature', () => {
42
- const { state } = createGlState();
43
- const runtime = getGlRenderStateRuntime(state);
44
- runtime.context.currentBlendSignature = { dst: 0, equation: 0, src: 0 };
45
- renderGlBackground(state);
46
- expect(runtime.context.currentBlendSignature).toBeNull();
47
- });
48
-
49
- it('passes fractional RGBA values through unchanged', () => {
50
- const { state, gl } = createGlState({ backgroundColorRgba: [0.5, 0.25, 0.75, 0.8] });
51
- renderGlBackground(state);
52
- expect(gl.clearColor).toHaveBeenCalledWith(0.5, 0.25, 0.75, 0.8);
53
- });
54
-
55
- it('decodes an sRGB background when the active target stores linear scene color', () => {
56
- const { state, gl } = createGlState({ backgroundColorRgba: [0.5, 0.25, 0.75, 0.8] });
57
- getGlRenderStateRuntime(state).currentRenderTarget = { colorSpace: 'linear' } as never;
58
-
59
- renderGlBackground(state);
60
-
61
- expect(gl.clearColor).toHaveBeenCalledWith(
62
- srgbChannelToLinear(0.5),
63
- srgbChannelToLinear(0.25),
64
- srgbChannelToLinear(0.75),
65
- 0.8,
66
- );
67
- });
68
- });