@flighthq/render-wgpu 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 (105) hide show
  1. package/README.md +2 -2
  2. package/dist/contract.d.ts +9 -6
  3. package/dist/contract.d.ts.map +1 -1
  4. package/dist/contract.js +9 -6
  5. package/dist/contract.js.map +1 -1
  6. package/dist/index.d.ts +1 -1
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +1 -1
  9. package/dist/index.js.map +1 -1
  10. package/dist/wgpuDraw.js +1 -1
  11. package/dist/wgpuDraw.js.map +1 -1
  12. package/dist/wgpuFrame.d.ts +23 -0
  13. package/dist/wgpuFrame.d.ts.map +1 -0
  14. package/dist/{wgpuBackground.js → wgpuFrame.js} +67 -121
  15. package/dist/wgpuFrame.js.map +1 -0
  16. package/dist/wgpuFullscreenPass.d.ts +2 -2
  17. package/dist/wgpuFullscreenPass.d.ts.map +1 -1
  18. package/dist/wgpuFullscreenPass.js +1 -2
  19. package/dist/wgpuFullscreenPass.js.map +1 -1
  20. package/dist/wgpuHost.d.ts +6 -6
  21. package/dist/wgpuHost.d.ts.map +1 -1
  22. package/dist/wgpuHost.js +17 -3
  23. package/dist/wgpuHost.js.map +1 -1
  24. package/dist/wgpuRenderPass.d.ts +9 -0
  25. package/dist/wgpuRenderPass.d.ts.map +1 -0
  26. package/dist/wgpuRenderPass.js +222 -0
  27. package/dist/wgpuRenderPass.js.map +1 -0
  28. package/dist/wgpuRenderState.d.ts +3 -4
  29. package/dist/wgpuRenderState.d.ts.map +1 -1
  30. package/dist/wgpuRenderState.js +17 -88
  31. package/dist/wgpuRenderState.js.map +1 -1
  32. package/dist/wgpuRenderTarget.d.ts +12 -29
  33. package/dist/wgpuRenderTarget.d.ts.map +1 -1
  34. package/dist/wgpuRenderTarget.js +19 -252
  35. package/dist/wgpuRenderTarget.js.map +1 -1
  36. package/dist/wgpuRenderTargetPool.d.ts +5 -5
  37. package/dist/wgpuRenderTargetPool.d.ts.map +1 -1
  38. package/dist/wgpuRenderTargetPool.js +7 -7
  39. package/dist/wgpuRenderTargetPool.js.map +1 -1
  40. package/dist/wgpuRenderTexture.d.ts +5 -4
  41. package/dist/wgpuRenderTexture.d.ts.map +1 -1
  42. package/dist/wgpuRenderTexture.js +14 -16
  43. package/dist/wgpuRenderTexture.js.map +1 -1
  44. package/dist/wgpuRenderTexturePool.js +1 -3
  45. package/dist/wgpuRenderTexturePool.js.map +1 -1
  46. package/dist/wgpuScissor.d.ts +1 -1
  47. package/dist/wgpuScissor.d.ts.map +1 -1
  48. package/dist/wgpuScissor.js +7 -4
  49. package/dist/wgpuScissor.js.map +1 -1
  50. package/dist/wgpuScreenAntialias.d.ts +3 -0
  51. package/dist/wgpuScreenAntialias.d.ts.map +1 -0
  52. package/dist/wgpuScreenAntialias.js +119 -0
  53. package/dist/wgpuScreenAntialias.js.map +1 -0
  54. package/dist/wgpuScreenCapture.d.ts +5 -0
  55. package/dist/wgpuScreenCapture.d.ts.map +1 -0
  56. package/dist/{wgpuSurface.js → wgpuScreenCapture.js} +55 -73
  57. package/dist/wgpuScreenCapture.js.map +1 -0
  58. package/dist/wgpuScreenRenderTarget.d.ts +9 -0
  59. package/dist/wgpuScreenRenderTarget.d.ts.map +1 -0
  60. package/dist/wgpuScreenRenderTarget.js +123 -0
  61. package/dist/wgpuScreenRenderTarget.js.map +1 -0
  62. package/dist/wgpuShader.js +2 -2
  63. package/dist/wgpuShader.js.map +1 -1
  64. package/dist/wgpuTestHelper.d.ts +8 -2
  65. package/dist/wgpuTestHelper.d.ts.map +1 -1
  66. package/dist/wgpuTestHelper.js +38 -3
  67. package/dist/wgpuTestHelper.js.map +1 -1
  68. package/dist/wgpuTextureRenderTarget.d.ts +18 -0
  69. package/dist/wgpuTextureRenderTarget.d.ts.map +1 -0
  70. package/dist/wgpuTextureRenderTarget.js +122 -0
  71. package/dist/wgpuTextureRenderTarget.js.map +1 -0
  72. package/package.json +11 -11
  73. package/src/wgpuDraw.test.ts +8 -8
  74. package/src/{wgpuBackground.test.ts → wgpuFrame.test.ts} +34 -20
  75. package/src/wgpuFullscreenPass.test.ts +7 -7
  76. package/src/wgpuHost.test.ts +28 -82
  77. package/src/wgpuRenderPass.test.ts +258 -0
  78. package/src/wgpuRenderState.test.ts +34 -313
  79. package/src/wgpuRenderTarget.test.ts +44 -219
  80. package/src/wgpuRenderTargetPool.test.ts +21 -21
  81. package/src/wgpuRenderTexture.test.ts +17 -15
  82. package/src/wgpuScissor.test.ts +43 -13
  83. package/src/wgpuScreenAntialias.test.ts +60 -0
  84. package/src/wgpuScreenCapture.test.ts +156 -0
  85. package/src/wgpuScreenRenderTarget.test.ts +152 -0
  86. package/src/wgpuShader.test.ts +5 -6
  87. package/src/wgpuTextureRenderTarget.test.ts +129 -0
  88. package/src/wgpuTextureResolver.test.ts +5 -5
  89. package/dist/wgpuAntialias.d.ts +0 -14
  90. package/dist/wgpuAntialias.d.ts.map +0 -1
  91. package/dist/wgpuAntialias.js +0 -155
  92. package/dist/wgpuAntialias.js.map +0 -1
  93. package/dist/wgpuBackground.d.ts +0 -20
  94. package/dist/wgpuBackground.d.ts.map +0 -1
  95. package/dist/wgpuBackground.js.map +0 -1
  96. package/dist/wgpuElement.d.ts +0 -7
  97. package/dist/wgpuElement.d.ts.map +0 -1
  98. package/dist/wgpuElement.js +0 -20
  99. package/dist/wgpuElement.js.map +0 -1
  100. package/dist/wgpuSurface.d.ts +0 -6
  101. package/dist/wgpuSurface.d.ts.map +0 -1
  102. package/dist/wgpuSurface.js.map +0 -1
  103. package/src/wgpuAntialias.test.ts +0 -239
  104. package/src/wgpuElement.test.ts +0 -145
  105. package/src/wgpuSurface.test.ts +0 -141
@@ -1,170 +1,41 @@
1
- import { createMatrix } from '@flighthq/geometry/contract';
2
- import type { WgpuRenderTarget } from '@flighthq/types/contract';
1
+ import type { WgpuRenderTarget, WgpuScreenRenderTarget } from '@flighthq/types/contract';
3
2
 
4
- import { renderWgpuBackground, submitWgpuRenderPass } from './wgpuBackground';
5
- import { resolveWgpuSmoothingBindGroup } from './wgpuDraw';
6
- import { getWgpuRenderStateDeviceResources, getWgpuRenderStateRuntime } from './wgpuRenderState';
3
+ import { beginWgpuRenderPass, endWgpuRenderPass } from './wgpuRenderPass';
4
+ import { getWgpuRenderStateRuntime } from './wgpuRenderState';
5
+ import { declareWgpuRenderTargetColorSpace, getWgpuRenderTargetSupersampleScale } from './wgpuRenderTarget';
7
6
  import {
8
- beginWgpuRenderPass,
9
- createWgpuRenderTarget,
10
- declareWgpuRenderTargetColorSpace,
11
- destroyWgpuRenderTarget,
12
- drawWgpuRenderTargetResult,
13
- endWgpuRenderPass,
14
- getWgpuRenderTargetSupersampleScale,
15
- initializeWgpuRenderTarget,
16
- resizeWgpuRenderTarget,
17
- setWgpuRenderTransform2D,
18
- } from './wgpuRenderTarget';
19
- import { createWgpuRenderStateForTest, installWgpuMock } from './wgpuTestHelper';
7
+ beginWgpuScreenRenderPassForTest,
8
+ createWgpuRenderStateForTest,
9
+ createWgpuScreenRenderTargetForTest,
10
+ installWgpuMock,
11
+ } from './wgpuTestHelper';
12
+ import { createWgpuTextureRenderTarget } from './wgpuTextureRenderTarget';
20
13
 
21
14
  beforeAll(() => {
22
15
  installWgpuMock();
23
16
  });
24
17
 
25
- describe('beginWgpuRenderPass', () => {
26
- it('sets the render target viewport', async () => {
27
- const state = await createWgpuRenderStateForTest();
28
- renderWgpuBackground(state);
29
- const target = createWgpuRenderTarget(state, 64, 64);
30
- beginWgpuRenderPass(state, target);
31
- expect(getWgpuRenderStateRuntime(state).renderTargetViewport?.width).toBe(64);
32
- endWgpuRenderPass(state);
33
- submitWgpuRenderPass(state);
34
- });
35
-
36
- it('switches the render target and restores it on end', async () => {
37
- const state = await createWgpuRenderStateForTest();
38
- renderWgpuBackground(state);
39
-
40
- const target = createWgpuRenderTarget(state, 128, 128);
41
- beginWgpuRenderPass(state, target);
42
-
43
- const runtime = getWgpuRenderStateRuntime(state);
44
- expect(runtime.renderTargetViewport?.width).toBe(128);
45
-
46
- endWgpuRenderPass(state);
47
- expect(runtime.renderTargetViewport).toBeNull();
48
-
49
- submitWgpuRenderPass(state);
50
- });
51
- });
52
-
53
- describe('createWgpuRenderTarget', () => {
54
- it('returns a target with texture, view, binding cache, and depth-stencil', async () => {
55
- const state = await createWgpuRenderStateForTest();
56
- const target = createWgpuRenderTarget(state, 256, 256);
57
- expect(target.texture).toBeDefined();
58
- expect(target.view).toBeDefined();
59
- expect(target.bindings).toBeDefined();
60
- expect(target.depthStencilTexture).toBeDefined();
61
- expect(target.width).toBe(256);
62
- expect(target.height).toBe(256);
63
- });
64
-
65
- it('clamps to minimum 1×1', async () => {
66
- const state = await createWgpuRenderStateForTest();
67
- const target = createWgpuRenderTarget(state, 0, 0);
68
- expect(target.width).toBe(1);
69
- expect(target.height).toBe(1);
70
- });
71
-
72
- it('defaults to sRGB and accepts a linear content declaration', async () => {
73
- const state = await createWgpuRenderStateForTest();
74
- expect(createWgpuRenderTarget(state, 16, 16).colorSpace).toBe('srgb');
75
- expect(createWgpuRenderTarget(state, 16, 16, state.format, 'linear').colorSpace).toBe('linear');
76
- });
77
-
78
- it('realizes four coverage samples as a 2x extent in each axis', async () => {
79
- const state = await createWgpuRenderStateForTest();
80
- const target = createWgpuRenderTarget(state, 64, 48, state.format, 'srgb', 4);
81
- expect(target.width).toBe(128);
82
- expect(target.height).toBe(96);
83
- expect(target.sampleCount).toBe(4);
84
- expect(target.texture.width).toBe(128);
85
- expect(target.texture.height).toBe(96);
86
- });
87
- });
88
-
89
18
  describe('declareWgpuRenderTargetColorSpace', () => {
90
- it('decodes a nonblack packed sRGB clear when the target is created as linear', async () => {
91
- const state = await createWgpuRenderStateForTest();
92
- renderWgpuBackground(state);
93
- const runtime = getWgpuRenderStateRuntime(state);
94
- const beginRenderPass = vi.spyOn(runtime.commandEncoder!, 'beginRenderPass');
95
- const target = createWgpuRenderTarget(state, 32, 32, state.format, 'linear');
96
- target.clearColors = [0x0a0c10ff];
97
-
98
- beginWgpuRenderPass(state, target);
99
- const attachment = Array.from(beginRenderPass.mock.calls.at(-1)![0].colorAttachments)[0]!;
100
- const linearClear = attachment.clearValue as GPUColorDict;
101
- expect(beginRenderPass).toHaveBeenCalledTimes(1);
102
- expect(linearClear.r).toBeCloseTo(0.00303527);
103
- expect(linearClear.g).toBeCloseTo(0.00367651);
104
- expect(linearClear.b).toBeCloseTo(0.00518152);
105
- expect(linearClear.a).toBe(1);
106
-
107
- endWgpuRenderPass(state);
108
- submitWgpuRenderPass(state);
109
- });
110
-
111
- it('stamps the current target and restores the enclosing target on end', async () => {
19
+ it('stamps the innermost open target and reports false outside a pass', async () => {
112
20
  const state = await createWgpuRenderStateForTest();
113
- renderWgpuBackground(state);
114
- const outer = createWgpuRenderTarget(state, 32, 32);
115
- const inner = createWgpuRenderTarget(state, 16, 16);
21
+ const outer = createWgpuTextureRenderTarget(state, 32, 32);
22
+ const inner = createWgpuTextureRenderTarget(state, 16, 16);
116
23
 
117
24
  expect(declareWgpuRenderTargetColorSpace(state, 'linear')).toBe(false);
118
- beginWgpuRenderPass(state, outer);
25
+
26
+ const screenPass = beginWgpuScreenRenderPassForTest(state);
27
+ const outerPass = beginWgpuRenderPass(state, outer);
119
28
  expect(declareWgpuRenderTargetColorSpace(state, 'linear')).toBe(true);
120
- beginWgpuRenderPass(state, inner);
29
+ const innerPass = beginWgpuRenderPass(state, inner);
121
30
  expect(declareWgpuRenderTargetColorSpace(state, 'linear')).toBe(true);
122
- endWgpuRenderPass(state);
31
+
32
+ endWgpuRenderPass(innerPass);
123
33
  expect(getWgpuRenderStateRuntime(state).currentRenderTarget).toBe(outer);
124
- endWgpuRenderPass(state);
125
- expect(getWgpuRenderStateRuntime(state).currentRenderTarget).toBeNull();
34
+ endWgpuRenderPass(outerPass);
35
+ endWgpuRenderPass(screenPass);
36
+
126
37
  expect(outer.colorSpace).toBe('linear');
127
38
  expect(inner.colorSpace).toBe('linear');
128
- submitWgpuRenderPass(state);
129
- });
130
- });
131
-
132
- describe('destroyWgpuRenderTarget', () => {
133
- it('calls destroy on texture and depth-stencil', async () => {
134
- const state = await createWgpuRenderStateForTest();
135
- const target = createWgpuRenderTarget(state, 64, 64);
136
- const destroyTexture = vi.spyOn(target.texture, 'destroy');
137
- const destroyDepth = vi.spyOn(target.depthStencilTexture, 'destroy');
138
- destroyWgpuRenderTarget(state, target);
139
- expect(destroyTexture).toHaveBeenCalled();
140
- expect(destroyDepth).toHaveBeenCalled();
141
- });
142
- });
143
-
144
- describe('drawWgpuRenderTargetResult', () => {
145
- it('does not throw with an open render pass', async () => {
146
- const state = await createWgpuRenderStateForTest();
147
- renderWgpuBackground(state);
148
- const target = createWgpuRenderTarget(state, 64, 64);
149
- const fakeNode = {
150
- alpha: 1,
151
- material: null,
152
- transform2D: { a: 1, b: 0, c: 0, d: 1, tx: 0, ty: 0 },
153
- };
154
- expect(() => drawWgpuRenderTargetResult(state, fakeNode, target, createMatrix())).not.toThrow();
155
- submitWgpuRenderPass(state);
156
- });
157
- });
158
-
159
- describe('endWgpuRenderPass', () => {
160
- it('restores null renderTargetViewport', async () => {
161
- const state = await createWgpuRenderStateForTest();
162
- renderWgpuBackground(state);
163
- const target = createWgpuRenderTarget(state, 32, 32);
164
- beginWgpuRenderPass(state, target);
165
- endWgpuRenderPass(state);
166
- expect(getWgpuRenderStateRuntime(state).renderTargetViewport).toBeNull();
167
- submitWgpuRenderPass(state);
168
39
  });
169
40
  });
170
41
 
@@ -173,9 +44,22 @@ describe('getWgpuRenderTargetSupersampleScale', () => {
173
44
  // if the 2D projection divides by that physical width it maps logical x = 800 to NDC 0, and the scene
174
45
  // fills a quarter of its own target. Measured on effect-sepia before the repair: the WebGPU frame
175
46
  // equalled the WebGL frame sampled at (2x, 2y) on 12 of 12 grid points; after, 48 of 48 matched 1:1.
176
- it('reports 2 for a supersampled target and 1 for a single-sample one', () => {
177
- expect(getWgpuRenderTargetSupersampleScale({ sampleCount: 4 } as WgpuRenderTarget)).toBe(2);
178
- expect(getWgpuRenderTargetSupersampleScale({ sampleCount: 1 } as WgpuRenderTarget)).toBe(1);
47
+ it('reports 2 for a supersampled texture target and 1 for a single-sample one', () => {
48
+ expect(getWgpuRenderTargetSupersampleScale({ context: null, sampleCount: 4 } as WgpuRenderTarget)).toBe(2);
49
+ expect(getWgpuRenderTargetSupersampleScale({ context: null, sampleCount: 1 } as WgpuRenderTarget)).toBe(1);
50
+ });
51
+
52
+ // A screen target reaches supersampling by a different route — an antialias option rather than a sample
53
+ // count — and the two must resolve to one number, or the scissor and the projection disagree about which
54
+ // coordinate space the frame is in.
55
+ it('reads a screen target from its antialias option, not its sample count', async () => {
56
+ const state = await createWgpuRenderStateForTest();
57
+ const plain = createWgpuScreenRenderTargetForTest(state);
58
+ const antialiased = createWgpuScreenRenderTargetForTest(state, { antialias: true });
59
+
60
+ expect(getWgpuRenderTargetSupersampleScale(plain)).toBe(1);
61
+ expect(getWgpuRenderTargetSupersampleScale(antialiased)).toBe(2);
62
+ expect(antialiased.sampleCount).toBe(1);
179
63
  });
180
64
 
181
65
  // Dividing the physical extent by the scale has to land back on the logical extent exactly, or the
@@ -189,77 +73,18 @@ describe('getWgpuRenderTargetSupersampleScale', () => {
189
73
  [1, 4],
190
74
  ] as const) {
191
75
  const physical = Math.max(1, Math.ceil(logical)) * (sampleCount === 4 ? 2 : 1);
192
- const scale = getWgpuRenderTargetSupersampleScale({ sampleCount } as WgpuRenderTarget);
76
+ const scale = getWgpuRenderTargetSupersampleScale({ context: null, sampleCount } as WgpuRenderTarget);
193
77
 
194
78
  expect(physical / scale).toBe(logical);
195
79
  }
196
80
  });
197
- });
198
81
 
199
- describe('initializeWgpuRenderTarget', () => {
200
- it('is the construction initializer of createWgpuRenderTarget', () => {
201
- expect(typeof initializeWgpuRenderTarget).toBe('function');
202
- });
203
- });
204
-
205
- describe('resizeWgpuRenderTarget', () => {
206
- it('preserves the allocation when dimensions are unchanged', async () => {
207
- const state = await createWgpuRenderStateForTest();
208
- const target = createWgpuRenderTarget(state, 64, 64);
209
- const texture = target.texture;
210
- const depthStencilTexture = target.depthStencilTexture;
211
- const sampler = getWgpuRenderStateDeviceResources(state).linearSampler;
212
- const bindGroup = resolveWgpuSmoothingBindGroup(state, target, true);
213
-
214
- resizeWgpuRenderTarget(state, target, 64, 64);
215
-
216
- expect(target.texture).toBe(texture);
217
- expect(target.depthStencilTexture).toBe(depthStencilTexture);
218
- // The view survived, so its cached bindings must survive with it.
219
- expect(target.bindings.get(sampler)).toBe(bindGroup);
220
- });
221
-
222
- it('updates width, height, and drops bindings that referenced the old view', async () => {
223
- const state = await createWgpuRenderStateForTest();
224
- const target = createWgpuRenderTarget(state, 64, 64);
225
- const previousBindGroup = resolveWgpuSmoothingBindGroup(state, target, true);
226
- resizeWgpuRenderTarget(state, target, 200, 150);
227
- expect(target.width).toBe(200);
228
- expect(target.height).toBe(150);
229
- // Reallocation replaces the view, so every binding over the old one is dropped and rebuilt.
230
- expect(target.bindings.size).toBe(0);
231
- expect(resolveWgpuSmoothingBindGroup(state, target, true)).not.toBe(previousBindGroup);
232
- });
233
-
234
- it('reallocates when the effective sample count changes', async () => {
235
- const state = await createWgpuRenderStateForTest();
236
- const target = createWgpuRenderTarget(state, 64, 48);
237
- const previousTexture = target.texture;
238
-
239
- resizeWgpuRenderTarget(state, target, 64, 48, 4);
240
-
241
- expect(target.width).toBe(128);
242
- expect(target.height).toBe(96);
243
- expect(target.sampleCount).toBe(4);
244
- expect(target.texture).not.toBe(previousTexture);
245
- });
246
- });
247
- describe('setWgpuRenderTransform2D', () => {
248
- it('installs a copy of the transform, restored by the enclosing pass', async () => {
82
+ it('inverts the screen allocator exactly for a supersampled surface', async () => {
249
83
  const state = await createWgpuRenderStateForTest();
250
- renderWgpuBackground(state);
251
- const original = state.renderTransform2D;
252
- const target = createWgpuRenderTarget(state, 32, 32);
253
- const bake = createMatrix();
254
- bake.tx = 42;
255
-
256
- beginWgpuRenderPass(state, target);
257
- setWgpuRenderTransform2D(state, bake);
258
- expect(state.renderTransform2D?.tx).toBe(42);
259
- expect(state.renderTransform2D).not.toBe(bake);
260
- endWgpuRenderPass(state);
84
+ const screen: WgpuScreenRenderTarget = createWgpuScreenRenderTargetForTest(state, { antialias: true }, 801, 599);
261
85
 
262
- expect(state.renderTransform2D).toBe(original);
263
- submitWgpuRenderPass(state);
86
+ expect(screen.width).toBe(1602);
87
+ expect(screen.width / getWgpuRenderTargetSupersampleScale(screen)).toBe(801);
88
+ expect(screen.height / getWgpuRenderTargetSupersampleScale(screen)).toBe(599);
264
89
  });
265
90
  });
@@ -1,9 +1,9 @@
1
1
  import {
2
- acquireWgpuRenderTarget,
2
+ acquireWgpuTextureRenderTarget,
3
3
  createWgpuRenderTargetPool,
4
4
  destroyWgpuRenderTargetPool,
5
5
  initializeWgpuRenderTargetPool,
6
- releaseWgpuRenderTarget,
6
+ releaseWgpuTextureRenderTarget,
7
7
  } from './wgpuRenderTargetPool';
8
8
  import { createWgpuRenderStateForTest, installWgpuMock } from './wgpuTestHelper';
9
9
 
@@ -11,22 +11,22 @@ beforeAll(() => {
11
11
  installWgpuMock();
12
12
  });
13
13
 
14
- describe('acquireWgpuRenderTarget', () => {
14
+ describe('acquireWgpuTextureRenderTarget', () => {
15
15
  it('reuses a released target matching width, height, and format', async () => {
16
16
  const state = await createWgpuRenderStateForTest();
17
17
  const pool = createWgpuRenderTargetPool();
18
- const first = acquireWgpuRenderTarget(state, pool, { width: 64, height: 64 });
19
- releaseWgpuRenderTarget(pool, first);
20
- const second = acquireWgpuRenderTarget(state, pool, { width: 64, height: 64 });
18
+ const first = acquireWgpuTextureRenderTarget(state, pool, { width: 64, height: 64 });
19
+ releaseWgpuTextureRenderTarget(pool, first);
20
+ const second = acquireWgpuTextureRenderTarget(state, pool, { width: 64, height: 64 });
21
21
  expect(second).toBe(first);
22
22
  });
23
23
 
24
24
  it('allocates a distinct target when no free target matches the format', async () => {
25
25
  const state = await createWgpuRenderStateForTest();
26
26
  const pool = createWgpuRenderTargetPool();
27
- const eight = acquireWgpuRenderTarget(state, pool, { width: 64, height: 64 });
28
- releaseWgpuRenderTarget(pool, eight);
29
- const hdr = acquireWgpuRenderTarget(state, pool, { width: 64, height: 64, format: 'rgba16float' });
27
+ const eight = acquireWgpuTextureRenderTarget(state, pool, { width: 64, height: 64 });
28
+ releaseWgpuTextureRenderTarget(pool, eight);
29
+ const hdr = acquireWgpuTextureRenderTarget(state, pool, { width: 64, height: 64, format: 'rgba16float' });
30
30
  expect(hdr).not.toBe(eight);
31
31
  expect(hdr.format).toBe('rgba16float');
32
32
  });
@@ -34,26 +34,26 @@ describe('acquireWgpuRenderTarget', () => {
34
34
  it('realizes and pools a four-sample request at twice the extent in each axis', async () => {
35
35
  const state = await createWgpuRenderStateForTest();
36
36
  const pool = createWgpuRenderTargetPool();
37
- const supersampled = acquireWgpuRenderTarget(state, pool, { width: 64, height: 48, sampleCount: 4 });
37
+ const supersampled = acquireWgpuTextureRenderTarget(state, pool, { width: 64, height: 48, sampleCount: 4 });
38
38
  expect(supersampled.width).toBe(128);
39
39
  expect(supersampled.height).toBe(96);
40
40
  expect(supersampled.sampleCount).toBe(4);
41
41
 
42
- releaseWgpuRenderTarget(pool, supersampled);
43
- const singleSampled = acquireWgpuRenderTarget(state, pool, { width: 128, height: 96 });
42
+ releaseWgpuTextureRenderTarget(pool, supersampled);
43
+ const singleSampled = acquireWgpuTextureRenderTarget(state, pool, { width: 128, height: 96 });
44
44
  expect(singleSampled).not.toBe(supersampled);
45
45
 
46
- const reused = acquireWgpuRenderTarget(state, pool, { width: 64, height: 48, sampleCount: 4 });
46
+ const reused = acquireWgpuTextureRenderTarget(state, pool, { width: 64, height: 48, sampleCount: 4 });
47
47
  expect(reused).toBe(supersampled);
48
48
  });
49
49
 
50
50
  it('re-stamps the logical color space when reusing storage', async () => {
51
51
  const state = await createWgpuRenderStateForTest();
52
52
  const pool = createWgpuRenderTargetPool();
53
- const first = acquireWgpuRenderTarget(state, pool, { width: 64, height: 64, colorSpace: 'linear' });
53
+ const first = acquireWgpuTextureRenderTarget(state, pool, { width: 64, height: 64, colorSpace: 'linear' });
54
54
  expect(first.colorSpace).toBe('linear');
55
- releaseWgpuRenderTarget(pool, first);
56
- const reused = acquireWgpuRenderTarget(state, pool, { width: 64, height: 64 });
55
+ releaseWgpuTextureRenderTarget(pool, first);
56
+ const reused = acquireWgpuTextureRenderTarget(state, pool, { width: 64, height: 64 });
57
57
  expect(reused).toBe(first);
58
58
  expect(reused.colorSpace).toBe('srgb');
59
59
  });
@@ -69,8 +69,8 @@ describe('destroyWgpuRenderTargetPool', () => {
69
69
  it('clears the free list', async () => {
70
70
  const state = await createWgpuRenderStateForTest();
71
71
  const pool = createWgpuRenderTargetPool();
72
- releaseWgpuRenderTarget(pool, acquireWgpuRenderTarget(state, pool, { width: 32, height: 32 }));
73
- destroyWgpuRenderTargetPool(state, pool);
72
+ releaseWgpuTextureRenderTarget(pool, acquireWgpuTextureRenderTarget(state, pool, { width: 32, height: 32 }));
73
+ destroyWgpuRenderTargetPool(pool);
74
74
  expect(pool.free.length).toBe(0);
75
75
  });
76
76
  });
@@ -80,12 +80,12 @@ describe('initializeWgpuRenderTargetPool', () => {
80
80
  expect(typeof initializeWgpuRenderTargetPool).toBe('function');
81
81
  });
82
82
  });
83
- describe('releaseWgpuRenderTarget', () => {
83
+ describe('releaseWgpuTextureRenderTarget', () => {
84
84
  it('returns the target to the free list', async () => {
85
85
  const state = await createWgpuRenderStateForTest();
86
86
  const pool = createWgpuRenderTargetPool();
87
- const target = acquireWgpuRenderTarget(state, pool, { width: 16, height: 16 });
88
- releaseWgpuRenderTarget(pool, target);
87
+ const target = acquireWgpuTextureRenderTarget(state, pool, { width: 16, height: 16 });
88
+ releaseWgpuTextureRenderTarget(pool, target);
89
89
  expect(pool.free).toContain(target);
90
90
  });
91
91
  });
@@ -1,7 +1,7 @@
1
- import type { RenderTexture } from '@flighthq/types/contract';
1
+ import type { RenderTargetClear, RenderTexture } from '@flighthq/types/contract';
2
2
  import { RenderTargetTextureSourceKind } from '@flighthq/types/contract';
3
3
 
4
- import { renderWgpuBackground, submitWgpuRenderPass } from './wgpuBackground';
4
+ import { submitWgpuFrame } from './wgpuFrame';
5
5
  import { getWgpuRenderStateRuntime } from './wgpuRenderState';
6
6
  import {
7
7
  bindWgpuRenderTexture,
@@ -14,7 +14,7 @@ import {
14
14
  setWgpuRenderTextureGuard,
15
15
  writeWgpuRenderTextureTarget,
16
16
  } from './wgpuRenderTexture';
17
- import { createWgpuRenderStateForTest, installWgpuMock } from './wgpuTestHelper';
17
+ import { beginWgpuScreenRenderPassForTest, createWgpuRenderStateForTest, installWgpuMock } from './wgpuTestHelper';
18
18
 
19
19
  beforeAll(() => {
20
20
  installWgpuMock();
@@ -48,10 +48,10 @@ describe('bindWgpuRenderTexture', () => {
48
48
  const renderTexture = texture();
49
49
  expect(bindWgpuRenderTexture(state, renderTexture)).toBeNull();
50
50
 
51
- renderWgpuBackground(state);
52
- renderIntoWgpuRenderTexture(state, renderTexture, () => {});
51
+ beginWgpuScreenRenderPassForTest(state);
52
+ renderIntoWgpuRenderTexture(state, renderTexture, () => {}, TRANSPARENT_CLEAR);
53
53
  expect(bindWgpuRenderTexture(state, renderTexture)).not.toBeNull();
54
- submitWgpuRenderPass(state);
54
+ submitWgpuFrame(state);
55
55
  });
56
56
  });
57
57
 
@@ -59,15 +59,15 @@ describe('destroyWgpuRenderTexture', () => {
59
59
  it('destroys and removes the hidden target', async () => {
60
60
  const state = await createWgpuRenderStateForTest();
61
61
  const renderTexture = texture();
62
- renderWgpuBackground(state);
63
- renderIntoWgpuRenderTexture(state, renderTexture, () => {});
62
+ beginWgpuScreenRenderPassForTest(state);
63
+ renderIntoWgpuRenderTexture(state, renderTexture, () => {}, TRANSPARENT_CLEAR);
64
64
  const entry = getWgpuRenderStateRuntime(state).context.wgpuRenderTextureCache!.get(renderTexture)!;
65
65
  const destroy = vi.spyOn(entry.target.texture, 'destroy');
66
66
 
67
67
  destroyWgpuRenderTexture(state, renderTexture);
68
68
  expect(destroy).toHaveBeenCalled();
69
69
  expect(bindWgpuRenderTexture(state, renderTexture)).toBeNull();
70
- submitWgpuRenderPass(state);
70
+ submitWgpuFrame(state);
71
71
  });
72
72
  });
73
73
 
@@ -109,10 +109,10 @@ describe('isWgpuRenderTextureReady', () => {
109
109
  const state = await createWgpuRenderStateForTest();
110
110
  const renderTexture = texture();
111
111
  expect(isWgpuRenderTextureReady(state, renderTexture)).toBe(false);
112
- renderWgpuBackground(state);
113
- renderIntoWgpuRenderTexture(state, renderTexture, () => {});
112
+ beginWgpuScreenRenderPassForTest(state);
113
+ renderIntoWgpuRenderTexture(state, renderTexture, () => {}, TRANSPARENT_CLEAR);
114
114
  expect(isWgpuRenderTextureReady(state, renderTexture)).toBe(true);
115
- submitWgpuRenderPass(state);
115
+ submitWgpuFrame(state);
116
116
  });
117
117
  });
118
118
 
@@ -120,16 +120,16 @@ describe('renderIntoWgpuRenderTexture', () => {
120
120
  it('restores the enclosing pass and bumps version after success', async () => {
121
121
  const state = await createWgpuRenderStateForTest();
122
122
  const renderTexture = texture();
123
- renderWgpuBackground(state);
123
+ beginWgpuScreenRenderPassForTest(state);
124
124
  const enclosingPass = getWgpuRenderStateRuntime(state).renderPass;
125
125
 
126
- renderIntoWgpuRenderTexture(state, renderTexture, () => {});
126
+ renderIntoWgpuRenderTexture(state, renderTexture, () => {}, TRANSPARENT_CLEAR);
127
127
 
128
128
  expect(renderTexture.version).toBe(1);
129
129
  expect(renderTexture.colorSpace).toBe('linear');
130
130
  expect(getWgpuRenderStateRuntime(state).renderPass).not.toBeNull();
131
131
  expect(getWgpuRenderStateRuntime(state).renderPass).not.toBe(enclosingPass);
132
- submitWgpuRenderPass(state);
132
+ submitWgpuFrame(state);
133
133
  });
134
134
  });
135
135
 
@@ -176,3 +176,5 @@ describe('writeWgpuRenderTextureTarget', () => {
176
176
  expect(target.sampleCount).toBe(4);
177
177
  });
178
178
  });
179
+
180
+ const TRANSPARENT_CLEAR: Readonly<RenderTargetClear> = { color: [0, 0, 0, 0], depth: 1.0, stencil: 0 };
@@ -1,4 +1,4 @@
1
- import { renderWgpuBackground } from './wgpuBackground';
1
+ import { beginWgpuRenderPass, endWgpuRenderPass } from './wgpuRenderPass';
2
2
  import { getWgpuRenderStateRuntime } from './wgpuRenderState';
3
3
  import {
4
4
  applyWgpuScissorRect,
@@ -6,7 +6,8 @@ import {
6
6
  pushWgpuScissorRect,
7
7
  setWgpuRenderPassScissorRect,
8
8
  } from './wgpuScissor';
9
- import { createWgpuRenderStateForTest, installWgpuMock } from './wgpuTestHelper';
9
+ import { beginWgpuScreenRenderPassForTest, createWgpuRenderStateForTest, installWgpuMock } from './wgpuTestHelper';
10
+ import { createWgpuTextureRenderTarget } from './wgpuTextureRenderTarget';
10
11
 
11
12
  beforeAll(() => {
12
13
  installWgpuMock();
@@ -15,7 +16,7 @@ beforeAll(() => {
15
16
  describe('applyWgpuScissorRect', () => {
16
17
  it('calls setScissorRect on the pass when a rect is active', async () => {
17
18
  const state = await createWgpuRenderStateForTest();
18
- renderWgpuBackground(state);
19
+ beginWgpuScreenRenderPassForTest(state);
19
20
  const runtime = getWgpuRenderStateRuntime(state);
20
21
  runtime.currentScissorRect = { x: 10, y: 20, width: 100, height: 50 };
21
22
  const calls: number[][] = [];
@@ -27,7 +28,7 @@ describe('applyWgpuScissorRect', () => {
27
28
 
28
29
  it('is a no-op when currentScissorRect is null', async () => {
29
30
  const state = await createWgpuRenderStateForTest();
30
- renderWgpuBackground(state);
31
+ beginWgpuScreenRenderPassForTest(state);
31
32
  const calls: number[][] = [];
32
33
  const fakePass = { setScissorRect: (...args: number[]) => calls.push(args) } as unknown as GPURenderPassEncoder;
33
34
  applyWgpuScissorRect(state, fakePass);
@@ -36,7 +37,7 @@ describe('applyWgpuScissorRect', () => {
36
37
 
37
38
  it('clamps dimensions to at least 1×1', async () => {
38
39
  const state = await createWgpuRenderStateForTest();
39
- renderWgpuBackground(state);
40
+ beginWgpuScreenRenderPassForTest(state);
40
41
  const runtime = getWgpuRenderStateRuntime(state);
41
42
  runtime.currentScissorRect = { x: 0, y: 0, width: 0, height: 0 };
42
43
  const calls: number[][] = [];
@@ -49,14 +50,14 @@ describe('applyWgpuScissorRect', () => {
49
50
  describe('popWgpuScissorRect', () => {
50
51
  it('is a no-op when the stack is empty', async () => {
51
52
  const state = await createWgpuRenderStateForTest();
52
- renderWgpuBackground(state);
53
+ beginWgpuScreenRenderPassForTest(state);
53
54
  expect(() => popWgpuScissorRect(state)).not.toThrow();
54
55
  expect(getWgpuRenderStateRuntime(state).currentScissorRect).toBeNull();
55
56
  });
56
57
 
57
58
  it('restores the previous rect after a push/pop pair', async () => {
58
59
  const state = await createWgpuRenderStateForTest();
59
- renderWgpuBackground(state);
60
+ beginWgpuScreenRenderPassForTest(state);
60
61
  const r1 = { x: 0, y: 0, width: 100, height: 100 };
61
62
  const r2 = { x: 10, y: 10, width: 50, height: 50 };
62
63
  pushWgpuScissorRect(state, r1);
@@ -72,7 +73,7 @@ describe('popWgpuScissorRect', () => {
72
73
  describe('pushWgpuScissorRect', () => {
73
74
  it('sets currentScissorRect on first push', async () => {
74
75
  const state = await createWgpuRenderStateForTest();
75
- renderWgpuBackground(state);
76
+ beginWgpuScreenRenderPassForTest(state);
76
77
  const rect = { x: 5, y: 10, width: 80, height: 40 };
77
78
  pushWgpuScissorRect(state, rect);
78
79
  expect(getWgpuRenderStateRuntime(state).currentScissorRect).toEqual(rect);
@@ -80,7 +81,7 @@ describe('pushWgpuScissorRect', () => {
80
81
 
81
82
  it('stacks previous rect when pushing a second rect', async () => {
82
83
  const state = await createWgpuRenderStateForTest();
83
- renderWgpuBackground(state);
84
+ beginWgpuScreenRenderPassForTest(state);
84
85
  const r1 = { x: 0, y: 0, width: 100, height: 100 };
85
86
  const r2 = { x: 20, y: 20, width: 60, height: 60 };
86
87
  pushWgpuScissorRect(state, r1);
@@ -93,7 +94,7 @@ describe('pushWgpuScissorRect', () => {
93
94
 
94
95
  it('copies the rect so mutation of the original does not affect the pushed value', async () => {
95
96
  const state = await createWgpuRenderStateForTest();
96
- renderWgpuBackground(state);
97
+ beginWgpuScreenRenderPassForTest(state);
97
98
  const rect = { x: 0, y: 0, width: 50, height: 50 };
98
99
  pushWgpuScissorRect(state, rect);
99
100
  rect.width = 999;
@@ -102,13 +103,42 @@ describe('pushWgpuScissorRect', () => {
102
103
  });
103
104
 
104
105
  describe('setWgpuRenderPassScissorRect', () => {
105
- it('scales logical coordinates on the acquired supersample surface', async () => {
106
- const state = await createWgpuRenderStateForTest({ antialias: true });
107
- renderWgpuBackground(state);
106
+ it('scales logical coordinates onto a supersampled screen surface', async () => {
107
+ const state = await createWgpuRenderStateForTest();
108
+ beginWgpuScreenRenderPassForTest(state, { color: [0, 0, 0, 0] }, { antialias: true });
109
+ const setScissorRect = vi.fn();
110
+
111
+ setWgpuRenderPassScissorRect(state, { setScissorRect } as unknown as GPURenderPassEncoder, 3, 5, 7, 11);
112
+
113
+ expect(setScissorRect).toHaveBeenCalledWith(6, 10, 14, 22);
114
+ });
115
+
116
+ // Logical is one coordinate space, so a supersampled OFFSCREEN target scales its scissors by the same
117
+ // factor its storage was grown by. It previously passed them through unscaled while the screen scaled
118
+ // them, which left a clip inside a sampleCount-4 effect target covering a quarter of its intended area.
119
+ it('scales them onto a supersampled texture target by the same rule', async () => {
120
+ const state = await createWgpuRenderStateForTest();
121
+ const outer = beginWgpuScreenRenderPassForTest(state);
122
+ const pass = beginWgpuRenderPass(state, createWgpuTextureRenderTarget(state, 64, 64, state.format, 'srgb', 4));
108
123
  const setScissorRect = vi.fn();
109
124
 
110
125
  setWgpuRenderPassScissorRect(state, { setScissorRect } as unknown as GPURenderPassEncoder, 3, 5, 7, 11);
111
126
 
112
127
  expect(setScissorRect).toHaveBeenCalledWith(6, 10, 14, 22);
128
+ endWgpuRenderPass(pass);
129
+ endWgpuRenderPass(outer);
130
+ });
131
+
132
+ it('passes them through unscaled on a target with no supersample', async () => {
133
+ const state = await createWgpuRenderStateForTest();
134
+ const outer = beginWgpuScreenRenderPassForTest(state);
135
+ const pass = beginWgpuRenderPass(state, createWgpuTextureRenderTarget(state, 64, 64));
136
+ const setScissorRect = vi.fn();
137
+
138
+ setWgpuRenderPassScissorRect(state, { setScissorRect } as unknown as GPURenderPassEncoder, 3, 5, 7, 11);
139
+
140
+ expect(setScissorRect).toHaveBeenCalledWith(3, 5, 7, 11);
141
+ endWgpuRenderPass(pass);
142
+ endWgpuRenderPass(outer);
113
143
  });
114
144
  });