@flighthq/effects-wgpu 0.3.1-next.1055.12501ae → 0.3.1-next.1076.4f48950

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flighthq/effects-wgpu",
3
- "version": "0.3.1-next.1055.12501ae",
3
+ "version": "0.3.1-next.1076.4f48950",
4
4
  "author": "Joshua Granick and other contributors",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -38,14 +38,14 @@
38
38
  "clean:dist": "tsx ../../scripts/clean-package-dist.ts"
39
39
  },
40
40
  "dependencies": {
41
- "@flighthq/adjustments": "0.3.1-next.1055.12501ae",
42
- "@flighthq/color": "0.3.1-next.1055.12501ae",
43
- "@flighthq/effects": "0.3.1-next.1055.12501ae",
44
- "@flighthq/geometry": "0.3.1-next.1055.12501ae",
45
- "@flighthq/log": "0.3.1-next.1055.12501ae",
46
- "@flighthq/render-wgpu": "0.3.1-next.1055.12501ae",
47
- "@flighthq/registry": "0.3.1-next.1055.12501ae",
48
- "@flighthq/types": "0.3.1-next.1055.12501ae"
41
+ "@flighthq/adjustments": "0.3.1-next.1076.4f48950",
42
+ "@flighthq/color": "0.3.1-next.1076.4f48950",
43
+ "@flighthq/effects": "0.3.1-next.1076.4f48950",
44
+ "@flighthq/geometry": "0.3.1-next.1076.4f48950",
45
+ "@flighthq/log": "0.3.1-next.1076.4f48950",
46
+ "@flighthq/render-wgpu": "0.3.1-next.1076.4f48950",
47
+ "@flighthq/registry": "0.3.1-next.1076.4f48950",
48
+ "@flighthq/types": "0.3.1-next.1076.4f48950"
49
49
  },
50
50
  "peerDependencies": {
51
51
  "@webgpu/types": "^0.1.70"
@@ -13,26 +13,177 @@ import {
13
13
  beforeAll(() => installWgpuMock());
14
14
 
15
15
  describe('beginWgpuRenderEffectPipeline', () => {
16
- it('is a function', () => {
17
- expect(typeof beginWgpuRenderEffectPipeline).toBe('function');
16
+ it('allocates the scene target on first begin and reuses the same one afterwards', async () => {
17
+ const state = await createWgpuRenderStateForTest();
18
+ const pipeline = createWgpuRenderEffectPipeline(state);
19
+
20
+ beginWgpuFrame(state);
21
+ beginWgpuRenderEffectPipeline(state, pipeline);
22
+ const first = pipeline.sceneTarget;
23
+ endWgpuRenderEffectPipeline(state, pipeline, []);
24
+
25
+ beginWgpuFrame(state);
26
+ beginWgpuRenderEffectPipeline(state, pipeline);
27
+
28
+ expect(first).not.toBeNull();
29
+ // The retained-target contract: begin resizes rather than reallocating, so a per-frame pipeline
30
+ // does not churn GPU memory. A new object here would be a leak the pool cannot see.
31
+ expect(pipeline.sceneTarget).toBe(first);
32
+ endWgpuRenderEffectPipeline(state, pipeline, []);
33
+ });
34
+
35
+ it('stamps the requested color space onto the scene target before the pass opens', async () => {
36
+ const state = await createWgpuRenderStateForTest();
37
+ const pipeline = createWgpuRenderEffectPipeline(state);
38
+
39
+ beginWgpuFrame(state);
40
+ beginWgpuRenderEffectPipeline(state, pipeline, 'linear');
41
+
42
+ expect(pipeline.sceneTarget?.colorSpace).toBe('linear');
43
+ endWgpuRenderEffectPipeline(state, pipeline, []);
44
+ });
45
+
46
+ it('packs the background quadruple into one 0xRRGGBBAA clear color', async () => {
47
+ const state = await createWgpuRenderStateForTest();
48
+ const pipeline = createWgpuRenderEffectPipeline(state);
49
+ // Quarter/half/three-quarter channels, chosen so a wrong byte order cannot coincide with the
50
+ // right one — the ground truth is arithmetic here, not a value read back out of the source.
51
+ state.backgroundColorRgba.splice(0, state.backgroundColorRgba.length, 0x40 / 255, 0x80 / 255, 0xc0 / 255, 1);
52
+
53
+ beginWgpuFrame(state);
54
+ beginWgpuRenderEffectPipeline(state, pipeline);
55
+
56
+ expect(pipeline.sceneTarget?.clearColors).toEqual([0x4080c0ff]);
57
+ endWgpuRenderEffectPipeline(state, pipeline, []);
58
+ });
59
+
60
+ it('leaves the clear list empty when the background is not a full quadruple', async () => {
61
+ const state = await createWgpuRenderStateForTest();
62
+ const pipeline = createWgpuRenderEffectPipeline(state);
63
+ state.backgroundColorRgba.splice(0, state.backgroundColorRgba.length, 0.5, 0.5, 0.5);
64
+
65
+ beginWgpuFrame(state);
66
+ beginWgpuRenderEffectPipeline(state, pipeline);
67
+
68
+ // Empty means "no background to composite" — a different frame than a transparent clear, and the
69
+ // reason the target falls back to the render state's own background instead.
70
+ expect(pipeline.sceneTarget?.clearColors).toEqual([]);
71
+ endWgpuRenderEffectPipeline(state, pipeline, []);
18
72
  });
19
73
  });
20
74
 
21
75
  describe('createWgpuRenderEffectPipeline', () => {
22
- it('is a function', () => {
23
- expect(typeof createWgpuRenderEffectPipeline).toBe('function');
76
+ it('returns a pipeline with nothing allocated yet', async () => {
77
+ const state = await createWgpuRenderStateForTest();
78
+
79
+ const pipeline = createWgpuRenderEffectPipeline(state);
80
+
81
+ expect(pipeline.sceneTarget).toBeNull();
82
+ expect(pipeline.velocityTexture).toBeNull();
83
+ expect(pipeline.lutTexture).toEqual({ texture: null, size: 0, lut: null });
84
+ expect(pipeline.lutCache.signature).toBeNull();
85
+ });
86
+
87
+ it('copies the options rather than aliasing the caller object', async () => {
88
+ const state = await createWgpuRenderStateForTest();
89
+ const options = { format: 'rgba16f' as const };
90
+
91
+ const pipeline = createWgpuRenderEffectPipeline(state, options);
92
+ options.format = 'rgba8' as unknown as 'rgba16f';
93
+
94
+ // The pipeline outlives the call; an alias would let a caller retune the format of a pipeline
95
+ // whose scene target was already allocated against the old one.
96
+ expect(pipeline.options.format).toBe('rgba16f');
97
+ });
98
+
99
+ it('gives each pipeline its own pool and caches', async () => {
100
+ const state = await createWgpuRenderStateForTest();
101
+
102
+ const a = createWgpuRenderEffectPipeline(state);
103
+ const b = createWgpuRenderEffectPipeline(state);
104
+
105
+ expect(a.pool).not.toBe(b.pool);
106
+ expect(a.lutCache).not.toBe(b.lutCache);
107
+ expect(a.lutTexture).not.toBe(b.lutTexture);
24
108
  });
25
109
  });
26
110
 
27
111
  describe('destroyWgpuRenderEffectPipeline', () => {
28
- it('is a function', () => {
29
- expect(typeof destroyWgpuRenderEffectPipeline).toBe('function');
112
+ it('releases the scene target and clears the LUT caches', async () => {
113
+ const state = await createWgpuRenderStateForTest();
114
+ const pipeline = createWgpuRenderEffectPipeline(state);
115
+ beginWgpuFrame(state);
116
+ beginWgpuRenderEffectPipeline(state, pipeline);
117
+ endWgpuRenderEffectPipeline(state, pipeline, []);
118
+ expect(pipeline.sceneTarget).not.toBeNull();
119
+
120
+ destroyWgpuRenderEffectPipeline(state, pipeline);
121
+
122
+ expect(pipeline.sceneTarget).toBeNull();
123
+ expect(pipeline.lutTexture).toEqual({ texture: null, size: 0, lut: null });
124
+ expect(pipeline.lutCache.signature).toBeNull();
125
+ expect(pipeline.lutCache.lut).toBeNull();
126
+ });
127
+
128
+ it('is safe on a pipeline that never began a frame', async () => {
129
+ const state = await createWgpuRenderStateForTest();
130
+ const pipeline = createWgpuRenderEffectPipeline(state);
131
+
132
+ expect(() => destroyWgpuRenderEffectPipeline(state, pipeline)).not.toThrow();
133
+ expect(pipeline.sceneTarget).toBeNull();
134
+ });
135
+
136
+ it('is idempotent — a second destroy neither throws nor resurrects state', async () => {
137
+ const state = await createWgpuRenderStateForTest();
138
+ const pipeline = createWgpuRenderEffectPipeline(state);
139
+ beginWgpuFrame(state);
140
+ beginWgpuRenderEffectPipeline(state, pipeline);
141
+ endWgpuRenderEffectPipeline(state, pipeline, []);
142
+
143
+ destroyWgpuRenderEffectPipeline(state, pipeline);
144
+ expect(() => destroyWgpuRenderEffectPipeline(state, pipeline)).not.toThrow();
145
+ expect(pipeline.sceneTarget).toBeNull();
30
146
  });
31
147
  });
32
148
 
33
149
  describe('endWgpuRenderEffectPipeline', () => {
34
- it('is a function', () => {
35
- expect(typeof endWgpuRenderEffectPipeline).toBe('function');
150
+ it('returns without touching the pipeline when no scene target was begun', async () => {
151
+ const state = await createWgpuRenderStateForTest();
152
+ const pipeline = createWgpuRenderEffectPipeline(state);
153
+ const pool = pipeline.pool;
154
+
155
+ // The early return is the only thing standing between an unbegun pipeline and endWgpuRenderPass
156
+ // popping a pass that was never pushed, so it is asserted rather than assumed.
157
+ expect(() => endWgpuRenderEffectPipeline(state, pipeline, [])).not.toThrow();
158
+ expect(pipeline.sceneTarget).toBeNull();
159
+ expect(pipeline.pool).toBe(pool);
160
+ });
161
+
162
+ it('runs an empty operation list without allocating scratch targets', async () => {
163
+ const state = await createWgpuRenderStateForTest();
164
+ const pipeline = createWgpuRenderEffectPipeline(state);
165
+
166
+ beginWgpuFrame(state);
167
+ beginWgpuRenderEffectPipeline(state, pipeline);
168
+ endWgpuRenderEffectPipeline(state, pipeline, []);
169
+
170
+ // Every acquire must be matched by a release before end returns; a pool holding entries after an
171
+ // empty chain would mean the ping-pong leaked a target on the no-op path.
172
+ expect(pipeline.pool.free.length).toBe(0);
173
+ });
174
+
175
+ it('returns pooled scratch targets after running a chain', async () => {
176
+ const state = await createWgpuRenderStateForTest();
177
+ const pipeline = createWgpuRenderEffectPipeline(state);
178
+ const chain = [{ kind: 'test.wgpu-pipeline-unregistered' } as RenderEffect];
179
+
180
+ beginWgpuFrame(state);
181
+ beginWgpuRenderEffectPipeline(state, pipeline);
182
+ endWgpuRenderEffectPipeline(state, pipeline, chain);
183
+
184
+ // An unregistered kind is skipped, so the chain does no work — but the bracketing still has to
185
+ // balance. This is the acquire/release invariant measured at its cheapest observable point.
186
+ expect(pipeline.pool.free.every((target) => target !== pipeline.sceneTarget)).toBe(true);
36
187
  });
37
188
  });
38
189