@flighthq/scene2d-wgpu 0.3.0 → 0.3.1-next.1041.35b950c

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/dist/enableWgpuColorAdjustmentGuards.d.ts.map +1 -1
  2. package/dist/enableWgpuColorAdjustmentGuards.js +13 -5
  3. package/dist/enableWgpuColorAdjustmentGuards.js.map +1 -1
  4. package/dist/enableWgpuStrokePathTessellation.d.ts.map +1 -1
  5. package/dist/enableWgpuStrokePathTessellation.js +7 -1
  6. package/dist/enableWgpuStrokePathTessellation.js.map +1 -1
  7. package/dist/wgpuCache.d.ts.map +1 -1
  8. package/dist/wgpuCache.js +30 -10
  9. package/dist/wgpuCache.js.map +1 -1
  10. package/dist/wgpuClipContours.d.ts.map +1 -1
  11. package/dist/wgpuClipContours.js +3 -2
  12. package/dist/wgpuClipContours.js.map +1 -1
  13. package/dist/wgpuColorAdjustmentMaterialFeature.d.ts.map +1 -1
  14. package/dist/wgpuColorAdjustmentMaterialFeature.js +9 -1
  15. package/dist/wgpuColorAdjustmentMaterialFeature.js.map +1 -1
  16. package/dist/wgpuMeshShapeRenderer.d.ts.map +1 -1
  17. package/dist/wgpuMeshShapeRenderer.js +3 -2
  18. package/dist/wgpuMeshShapeRenderer.js.map +1 -1
  19. package/dist/wgpuParticleEmitter2D.d.ts.map +1 -1
  20. package/dist/wgpuParticleEmitter2D.js +3 -3
  21. package/dist/wgpuParticleEmitter2D.js.map +1 -1
  22. package/dist/wgpuQuadBatchWriter.d.ts.map +1 -1
  23. package/dist/wgpuQuadBatchWriter.js +7 -5
  24. package/dist/wgpuQuadBatchWriter.js.map +1 -1
  25. package/dist/wgpuRichText.d.ts.map +1 -1
  26. package/dist/wgpuRichText.js +9 -6
  27. package/dist/wgpuRichText.js.map +1 -1
  28. package/dist/wgpuScale9Shape.d.ts.map +1 -1
  29. package/dist/wgpuScale9Shape.js +5 -2
  30. package/dist/wgpuScale9Shape.js.map +1 -1
  31. package/dist/wgpuShapeMesh.d.ts.map +1 -1
  32. package/dist/wgpuShapeMesh.js +6 -10
  33. package/dist/wgpuShapeMesh.js.map +1 -1
  34. package/dist/wgpuShapeRasterizer.d.ts.map +1 -1
  35. package/dist/wgpuShapeRasterizer.js +9 -2
  36. package/dist/wgpuShapeRasterizer.js.map +1 -1
  37. package/dist/wgpuSprite.d.ts.map +1 -1
  38. package/dist/wgpuSprite.js +2 -2
  39. package/dist/wgpuSprite.js.map +1 -1
  40. package/dist/wgpuTextLabel.js +2 -2
  41. package/dist/wgpuTextLabel.js.map +1 -1
  42. package/dist/wgpuVelocity.d.ts.map +1 -1
  43. package/dist/wgpuVelocity.js +11 -12
  44. package/dist/wgpuVelocity.js.map +1 -1
  45. package/package.json +22 -18
  46. package/src/enableWgpuColorAdjustmentGuards.test.ts +13 -8
  47. package/src/enableWgpuStrokePathTessellation.test.ts +10 -3
  48. package/src/wgpuCache.test.ts +154 -7
  49. package/src/wgpuColorAdjustmentMaterialFeature.test.ts +7 -6
  50. package/src/wgpuParticleEmitter2D.test.ts +20 -14
  51. package/src/wgpuShapeRasterizer.test.ts +11 -1
  52. package/src/wgpuTextLabel.test.ts +18 -2
  53. package/src/wgpuVelocity.test.ts +23 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flighthq/scene2d-wgpu",
3
- "version": "0.3.0",
3
+ "version": "0.3.1-next.1041.35b950c",
4
4
  "author": "Joshua Granick and other contributors",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -38,23 +38,27 @@
38
38
  "clean:dist": "tsx ../../scripts/clean-package-dist.ts"
39
39
  },
40
40
  "dependencies": {
41
- "@flighthq/color": "0.3.0",
42
- "@flighthq/scene2d": "0.3.0",
43
- "@flighthq/scene2d-canvas": "0.3.0",
44
- "@flighthq/geometry": "0.3.0",
45
- "@flighthq/image": "0.3.0",
46
- "@flighthq/log": "0.3.0",
47
- "@flighthq/node": "0.3.0",
48
- "@flighthq/path": "0.3.0",
49
- "@flighthq/render": "0.3.0",
50
- "@flighthq/render-wgpu": "0.3.0",
51
- "@flighthq/shape": "0.3.0",
52
- "@flighthq/text": "0.3.0",
53
- "@flighthq/textinput": "0.3.0",
54
- "@flighthq/textlayout": "0.3.0",
55
- "@flighthq/texture": "0.3.0",
56
- "@flighthq/types": "0.3.0",
57
- "@flighthq/velocity": "0.3.0"
41
+ "@flighthq/color": "0.3.1-next.1041.35b950c",
42
+ "@flighthq/scene2d": "0.3.1-next.1041.35b950c",
43
+ "@flighthq/scene2d-canvas": "0.3.1-next.1041.35b950c",
44
+ "@flighthq/geometry": "0.3.1-next.1041.35b950c",
45
+ "@flighthq/image": "0.3.1-next.1041.35b950c",
46
+ "@flighthq/log": "0.3.1-next.1041.35b950c",
47
+ "@flighthq/node": "0.3.1-next.1041.35b950c",
48
+ "@flighthq/path": "0.3.1-next.1041.35b950c",
49
+ "@flighthq/registry": "0.3.1-next.1041.35b950c",
50
+ "@flighthq/render": "0.3.1-next.1041.35b950c",
51
+ "@flighthq/render-wgpu": "0.3.1-next.1041.35b950c",
52
+ "@flighthq/shape": "0.3.1-next.1041.35b950c",
53
+ "@flighthq/text": "0.3.1-next.1041.35b950c",
54
+ "@flighthq/textinput": "0.3.1-next.1041.35b950c",
55
+ "@flighthq/textlayout": "0.3.1-next.1041.35b950c",
56
+ "@flighthq/texture": "0.3.1-next.1041.35b950c",
57
+ "@flighthq/types": "0.3.1-next.1041.35b950c",
58
+ "@flighthq/velocity": "0.3.1-next.1041.35b950c"
59
+ },
60
+ "peerDependencies": {
61
+ "@webgpu/types": "^0.1.70"
58
62
  },
59
63
  "devDependencies": {
60
64
  "@flighthq/bitmaptext": "*",
@@ -1,12 +1,14 @@
1
1
  import { addLogSink, createMemoryLogSink, getMemoryLogSinkEntries, removeLogSink } from '@flighthq/log/contract';
2
- import { getWgpuRenderStateRuntime } from '@flighthq/render-wgpu/contract';
3
2
  import { createWgpuRenderStateForTest, installWgpuMock } from '@flighthq/render-wgpu/contract';
3
+ import { getWgpuRenderStateRuntime } from '@flighthq/render-wgpu/contract';
4
4
  import type { ColorScaleBias } from '@flighthq/types/contract';
5
+ import { RegistryEntryState } from '@flighthq/types/contract';
5
6
 
6
7
  import {
7
8
  areWgpuColorAdjustmentGuardsEnabled,
8
9
  enableWgpuColorAdjustmentGuards,
9
10
  } from './enableWgpuColorAdjustmentGuards';
11
+ import { registerWgpuColorAdjustmentMaterialFeature } from './wgpuColorAdjustmentMaterialFeature';
10
12
  import { recordWgpuQuadBatchColorScaleBias } from './wgpuQuadBatchWriter';
11
13
 
12
14
  beforeAll(() => {
@@ -32,6 +34,15 @@ describe('areWgpuColorAdjustmentGuardsEnabled', () => {
32
34
  expect(areWgpuColorAdjustmentGuardsEnabled(state)).toBe(false);
33
35
  enableWgpuColorAdjustmentGuards(state);
34
36
  expect(areWgpuColorAdjustmentGuardsEnabled(state)).toBe(true);
37
+ const table = getWgpuRenderStateRuntime(state).registries.colorAdjustmentFeatureGuard;
38
+ expect(table).toMatchObject({
39
+ entry: { state: RegistryEntryState.Bound },
40
+ onMiss: 'Disabled',
41
+ registry: 'WgpuColorAdjustmentFeatureGuard',
42
+ shape: 'slot',
43
+ });
44
+ enableWgpuColorAdjustmentGuards(state);
45
+ expect(getWgpuRenderStateRuntime(state).registries.colorAdjustmentFeatureGuard).toBe(table);
35
46
  });
36
47
  });
37
48
 
@@ -67,17 +78,11 @@ describe('enableWgpuColorAdjustmentGuards', () => {
67
78
 
68
79
  it('does not warn when the guard slot is present but color adjustment is also enabled', async () => {
69
80
  const state = await createWgpuRenderStateForTest();
70
- const runtime = getWgpuRenderStateRuntime(state);
71
81
  const sink = createMemoryLogSink(8);
72
82
  addLogSink(sink.sink);
73
83
  try {
74
84
  enableWgpuColorAdjustmentGuards(state);
75
- runtime.wgpuColorAdjustmentMaterialFeature = {
76
- fragmentShaderChunk: '',
77
- matrixFragmentShaderChunk: '',
78
- record: () => {},
79
- resolveFlush: () => null,
80
- };
85
+ registerWgpuColorAdjustmentMaterialFeature(state);
81
86
  recordWgpuQuadBatchColorScaleBias(state, ct(), 0);
82
87
  expect(getMemoryLogSinkEntries(sink).length).toBe(0);
83
88
  } finally {
@@ -4,6 +4,7 @@ import {
4
4
  getWgpuRenderStateRuntime,
5
5
  installWgpuMock,
6
6
  } from '@flighthq/render-wgpu/contract';
7
+ import { RegistryEntryState } from '@flighthq/types/contract';
7
8
 
8
9
  import { enableWgpuStrokePathTessellation } from './enableWgpuStrokePathTessellation';
9
10
 
@@ -12,12 +13,18 @@ beforeAll(() => {
12
13
  });
13
14
 
14
15
  describe('enableWgpuStrokePathTessellation', () => {
15
- it('installs the full stroke tessellator on the render state', async () => {
16
+ it('replaces the full stroke-tessellator policy slot', async () => {
16
17
  const state = await createWgpuRenderStateForTest();
17
- expect(getWgpuRenderStateRuntime(state).strokeTessellator).toBeNull();
18
+ const runtime = getWgpuRenderStateRuntime(state);
19
+ const before = runtime.registries.strokeTessellator;
20
+ expect(before.entry).toBeNull();
18
21
 
19
22
  enableWgpuStrokePathTessellation(state);
20
23
 
21
- expect(getWgpuRenderStateRuntime(state).strokeTessellator).toBe(tessellateStrokePath);
24
+ expect(runtime.registries.strokeTessellator).not.toBe(before);
25
+ expect(runtime.registries.strokeTessellator.entry).toEqual({
26
+ state: RegistryEntryState.Bound,
27
+ value: tessellateStrokePath,
28
+ });
22
29
  });
23
30
  });
@@ -1,9 +1,24 @@
1
1
  import { createMatrix } from '@flighthq/geometry/contract';
2
+ import { getRegistryTableEntry } from '@flighthq/registry/contract';
2
3
  import type * as WgpuRenderWgpuModule from '@flighthq/render-wgpu/contract';
3
- import { createRenderCache, createRenderState, RenderCacheKind, useRenderCache } from '@flighthq/render/contract';
4
+ import {
5
+ createRenderCache,
6
+ createRenderState,
7
+ enableColorAdjustmentGuards,
8
+ getColorAdjustmentUnsupportedGuard,
9
+ RenderCacheKind,
10
+ useRenderCache,
11
+ } from '@flighthq/render/contract';
4
12
  import { createDisplayObject } from '@flighthq/scene2d/contract';
5
- import type { WgpuRenderState, WgpuRenderTarget } from '@flighthq/types/contract';
6
- import { EntityRuntimeKey } from '@flighthq/types/contract';
13
+ import type {
14
+ WgpuColorAdjustmentMaterialFeature,
15
+ WgpuColorAdjustmentMaterialFeatureGuard,
16
+ WgpuMaterialRenderer,
17
+ WgpuRenderState,
18
+ RenderRootGuard,
19
+ WgpuRenderTarget,
20
+ } from '@flighthq/types/contract';
21
+ import { EntityRuntimeKey, RegistryEntryState } from '@flighthq/types/contract';
7
22
 
8
23
  import { scopeModuleMocks } from './moduleMockTestHelper';
9
24
  import type * as WgpuCacheModule from './wgpuCache';
@@ -28,6 +43,9 @@ let refreshWgpuRenderCache: typeof WgpuCacheModule.refreshWgpuRenderCache;
28
43
  let releaseWgpuRenderCache: typeof WgpuCacheModule.releaseWgpuRenderCache;
29
44
  let createWgpuRenderStateRuntime: typeof WgpuRenderWgpuModule.createWgpuRenderStateRuntime;
30
45
  let getWgpuRenderStateRuntime: typeof WgpuRenderWgpuModule.getWgpuRenderStateRuntime;
46
+ let getWgpuMaterialRenderer: typeof WgpuRenderWgpuModule.getWgpuMaterialRenderer;
47
+ let registerWgpuMaterialRenderer: typeof WgpuRenderWgpuModule.registerWgpuMaterialRenderer;
48
+ let resolveWgpuApplyBlendMode: typeof WgpuRenderWgpuModule.resolveWgpuApplyBlendMode;
31
49
  let beginWgpuFrame: typeof WgpuRenderWgpuModule.beginWgpuFrame;
32
50
  let destroyWgpuRenderTarget: typeof WgpuRenderWgpuModule.destroyWgpuRenderTarget;
33
51
  let drawWgpuRenderTargetResult: typeof WgpuRenderWgpuModule.drawWgpuRenderTargetResult;
@@ -92,7 +110,10 @@ beforeAll(async () => {
92
110
  createWgpuRenderStateRuntime,
93
111
  destroyWgpuRenderTarget,
94
112
  drawWgpuRenderTargetResult,
113
+ getWgpuMaterialRenderer,
95
114
  getWgpuRenderStateRuntime,
115
+ registerWgpuMaterialRenderer,
116
+ resolveWgpuApplyBlendMode,
96
117
  submitWgpuRenderPass,
97
118
  } = await import('@flighthq/render-wgpu/contract'));
98
119
  ({ flushWgpuQuadBatchWriter } = await import('./wgpuQuadBatchWriter'));
@@ -124,18 +145,141 @@ function makeCacheNode(source: unknown): any {
124
145
  }
125
146
 
126
147
  describe('createWgpuCacheState', () => {
148
+ it('resolves late screen blend-mode wiring explicitly until locally overridden', () => {
149
+ const screen = fakeScreen();
150
+ screen.applyBlendMode = null;
151
+ const cacheState = createWgpuCacheState(screen);
152
+ const screenHook = vi.fn();
153
+ const laterScreenHook = vi.fn();
154
+ const cacheHook = vi.fn();
155
+
156
+ expect(cacheState.applyBlendMode).toBeNull();
157
+ expect(Object.getOwnPropertyDescriptor(cacheState, 'applyBlendMode')?.get).toBeUndefined();
158
+ expect(resolveWgpuApplyBlendMode(cacheState)).toBeNull();
159
+ screen.applyBlendMode = screenHook;
160
+ expect(cacheState.applyBlendMode).toBeNull();
161
+ expect(resolveWgpuApplyBlendMode(cacheState)).toBe(screenHook);
162
+
163
+ cacheState.applyBlendMode = cacheHook;
164
+ screen.applyBlendMode = laterScreenHook;
165
+ expect(resolveWgpuApplyBlendMode(cacheState)).toBe(cacheHook);
166
+ });
167
+
127
168
  it('copies renderers and shares the GPU device but keeps its own node map', () => {
128
169
  const screen = fakeScreen();
129
170
  const resolver = vi.fn();
130
- getWgpuRenderStateRuntime(screen).colorAdjustmentResolver = resolver;
171
+ const colorAdjustmentFeature: WgpuColorAdjustmentMaterialFeature = {
172
+ fragmentShaderChunk: '',
173
+ matrixFragmentShaderChunk: '',
174
+ record: vi.fn(),
175
+ resolveFlush: vi.fn(() => null),
176
+ };
177
+ const colorAdjustmentFeatureGuard: WgpuColorAdjustmentMaterialFeatureGuard = vi.fn();
178
+ const renderRootGuard: RenderRootGuard = vi.fn();
179
+ const screenRuntime = getWgpuRenderStateRuntime(screen);
180
+ screenRuntime.registries.colorAdjustments = {
181
+ entry: { state: RegistryEntryState.Bound, value: resolver },
182
+ onMiss: 'Disabled',
183
+ registry: 'ColorAdjustments',
184
+ shape: 'slot',
185
+ };
186
+ screenRuntime.registries.colorAdjustmentFeature = {
187
+ entry: { state: RegistryEntryState.Bound, value: colorAdjustmentFeature },
188
+ onMiss: 'Disabled',
189
+ registry: 'WgpuColorAdjustmentFeature',
190
+ shape: 'slot',
191
+ };
192
+ screenRuntime.registries.colorAdjustmentFeatureGuard = {
193
+ entry: { state: RegistryEntryState.Bound, value: colorAdjustmentFeatureGuard },
194
+ onMiss: 'Disabled',
195
+ registry: 'WgpuColorAdjustmentFeatureGuard',
196
+ shape: 'slot',
197
+ };
198
+ screenRuntime.registries.renderRootGuard = {
199
+ entry: { state: RegistryEntryState.Bound, value: renderRootGuard },
200
+ onMiss: 'Disabled',
201
+ registry: 'RenderRootGuard',
202
+ shape: 'slot',
203
+ };
204
+ enableColorAdjustmentGuards(screen);
131
205
  enableWgpuRenderCache(screen);
132
206
  const cacheState = createWgpuCacheState(screen);
133
- expect(getWgpuRenderStateRuntime(cacheState).rendererMap.get(RenderCacheKind)).toBe(defaultWgpuRenderCacheRenderer);
207
+ expect(getWgpuRenderStateRuntime(cacheState).registries.renderers.entries.get(RenderCacheKind)).toEqual({
208
+ state: 'bound',
209
+ value: defaultWgpuRenderCacheRenderer,
210
+ });
134
211
  expect((cacheState as any).device).toBe((screen as any).device);
135
212
  expect(getWgpuRenderStateRuntime(cacheState).renderProxyMap).not.toBe(
136
213
  getWgpuRenderStateRuntime(screen).renderProxyMap,
137
214
  );
138
- expect(getWgpuRenderStateRuntime(cacheState).colorAdjustmentResolver).toBe(resolver);
215
+ expect(getWgpuRenderStateRuntime(cacheState).registries.colorAdjustments).toBe(
216
+ screenRuntime.registries.colorAdjustments,
217
+ );
218
+ expect(getWgpuRenderStateRuntime(cacheState).registries.colorAdjustmentFeature).toBe(
219
+ screenRuntime.registries.colorAdjustmentFeature,
220
+ );
221
+ const cacheRuntime = getWgpuRenderStateRuntime(cacheState);
222
+ expect(cacheRuntime.registries.colorAdjustmentFeatureGuard).toBe(
223
+ screenRuntime.registries.colorAdjustmentFeatureGuard,
224
+ );
225
+ const sharedColorFeatureGuard = cacheRuntime.registries.colorAdjustmentFeatureGuard;
226
+ screenRuntime.registries.colorAdjustmentFeatureGuard = undefined;
227
+ expect(cacheRuntime.registries.colorAdjustmentFeatureGuard).toBe(sharedColorFeatureGuard);
228
+ expect(cacheRuntime.registries.colorAdjustmentUnsupportedGuard).toBe(
229
+ screenRuntime.registries.colorAdjustmentUnsupportedGuard,
230
+ );
231
+ expect(getColorAdjustmentUnsupportedGuard(cacheState)).not.toBeNull();
232
+ const sharedUnsupportedGuard = cacheRuntime.registries.colorAdjustmentUnsupportedGuard;
233
+ screenRuntime.registries.colorAdjustmentUnsupportedGuard = undefined;
234
+ expect(getColorAdjustmentUnsupportedGuard(screen)).toBeNull();
235
+ expect(cacheRuntime.registries.colorAdjustmentUnsupportedGuard).toBe(sharedUnsupportedGuard);
236
+ expect(getColorAdjustmentUnsupportedGuard(cacheState)).not.toBeNull();
237
+ expect(cacheRuntime.registries.renderRootGuard).toBe(screenRuntime.registries.renderRootGuard);
238
+ expect(
239
+ getRegistryTableEntry(
240
+ cacheRuntime.registries.renderRootGuard!,
241
+ cacheRuntime.registries.renderRootGuard!.registry,
242
+ ),
243
+ ).toBe(renderRootGuard);
244
+ const sharedRootGuard = cacheRuntime.registries.renderRootGuard;
245
+ screenRuntime.registries.renderRootGuard = undefined;
246
+ expect(screenRuntime.registries.renderRootGuard).toBeUndefined();
247
+ expect(cacheRuntime.registries.renderRootGuard).toBe(sharedRootGuard);
248
+ expect(
249
+ getRegistryTableEntry(
250
+ cacheRuntime.registries.renderRootGuard!,
251
+ cacheRuntime.registries.renderRootGuard!.registry,
252
+ ),
253
+ ).toBe(renderRootGuard);
254
+ });
255
+
256
+ it('shares persistent registration snapshots through a distinct aggregate and then diverges', () => {
257
+ const screen = fakeScreen();
258
+ const first: WgpuMaterialRenderer = { instanceFloatCount: 0, getShaderModule: vi.fn() };
259
+ const replacement: WgpuMaterialRenderer = { instanceFloatCount: 0, getShaderModule: vi.fn() };
260
+ registerWgpuMaterialRenderer(screen, 'acme.Material', first);
261
+
262
+ const cacheState = createWgpuCacheState(screen);
263
+ const screenRuntime = getWgpuRenderStateRuntime(screen);
264
+ const cacheRuntime = getWgpuRenderStateRuntime(cacheState);
265
+ expect(cacheRuntime.registries).not.toBe(screenRuntime.registries);
266
+ expect(cacheRuntime.registries.colorAdjustments).toBe(screenRuntime.registries.colorAdjustments);
267
+ expect(cacheRuntime.registries.compressedTextureDecoder).toBe(screenRuntime.registries.compressedTextureDecoder);
268
+ expect(cacheRuntime.registries.compressedTextureUpload).toBe(screenRuntime.registries.compressedTextureUpload);
269
+ expect(cacheRuntime.registries.customMaterialShaders).toBe(screenRuntime.registries.customMaterialShaders);
270
+ expect(cacheRuntime.registries.materialRenderers).toBe(screenRuntime.registries.materialRenderers);
271
+ expect(cacheRuntime.registries.meshMaterialRenderers).toBe(screenRuntime.registries.meshMaterialRenderers);
272
+ expect(cacheRuntime.registries.renderEffects).toBe(screenRuntime.registries.renderEffects);
273
+ expect(cacheRuntime.registries.shapeRasterizer).toBe(screenRuntime.registries.shapeRasterizer);
274
+ expect(cacheRuntime.registries.strokeTessellator).toBe(screenRuntime.registries.strokeTessellator);
275
+ expect(cacheRuntime.registries.textureResolvers).toBe(screenRuntime.registries.textureResolvers);
276
+ expect(cacheRuntime.registries.velocityWriters).toBe(screenRuntime.registries.velocityWriters);
277
+ expect(getWgpuMaterialRenderer(cacheState, 'acme.Material')).toBe(first);
278
+
279
+ registerWgpuMaterialRenderer(screen, 'acme.Material', replacement);
280
+
281
+ expect(getWgpuMaterialRenderer(screen, 'acme.Material')).toBe(replacement);
282
+ expect(getWgpuMaterialRenderer(cacheState, 'acme.Material')).toBe(first);
139
283
  });
140
284
  });
141
285
 
@@ -177,7 +321,10 @@ describe('enableWgpuRenderCache', () => {
177
321
  it('registers the renderer for the render cache kind', () => {
178
322
  const state = fakeScreen();
179
323
  enableWgpuRenderCache(state);
180
- expect(getWgpuRenderStateRuntime(state).rendererMap.get(RenderCacheKind)).toBe(defaultWgpuRenderCacheRenderer);
324
+ expect(getWgpuRenderStateRuntime(state).registries.renderers.entries.get(RenderCacheKind)).toEqual({
325
+ state: 'bound',
326
+ value: defaultWgpuRenderCacheRenderer,
327
+ });
181
328
  });
182
329
  });
183
330
 
@@ -1,5 +1,5 @@
1
1
  import { renderWgpuBackground, submitWgpuRenderPass } from '@flighthq/render-wgpu/contract';
2
- import { getWgpuRenderStateRuntime } from '@flighthq/render-wgpu/contract';
2
+ import { getWgpuColorAdjustmentMaterialFeature, getWgpuRenderStateRuntime } from '@flighthq/render-wgpu/contract';
3
3
  import { createWgpuRenderStateForTest, installWgpuMock } from '@flighthq/render-wgpu/contract';
4
4
  import { areColorAdjustmentsEnabled } from '@flighthq/render/contract';
5
5
  import type { ColorScaleBias } from '@flighthq/types/contract';
@@ -51,18 +51,19 @@ describe('registerWgpuColorAdjustmentMaterialFeature', () => {
51
51
  const runtime = getWgpuRenderStateRuntime(state);
52
52
  registerWgpuColorAdjustmentMaterialFeature(state);
53
53
  expect(areColorAdjustmentsEnabled(state)).toBe(true);
54
- expect(runtime.wgpuColorAdjustmentMaterialFeature).toBeDefined();
55
- expect(runtime.wgpuColorAdjustmentMaterialFeature).not.toBeNull();
56
- expect(runtime.wgpuColorAdjustmentMaterialFeature?.drawShapeMeshes).toBeTypeOf('function');
54
+ expect(runtime.registries.colorAdjustmentFeature).toBeDefined();
55
+ expect(getWgpuColorAdjustmentMaterialFeature(state)?.drawShapeMeshes).toBeTypeOf('function');
57
56
  });
58
57
 
59
58
  it('is idempotent', async () => {
60
59
  const state = await createWgpuRenderStateForTest();
61
60
  const runtime = getWgpuRenderStateRuntime(state);
62
61
  registerWgpuColorAdjustmentMaterialFeature(state);
63
- const fold = runtime.wgpuColorAdjustmentMaterialFeature;
62
+ const table = runtime.registries.colorAdjustmentFeature;
63
+ const fold = getWgpuColorAdjustmentMaterialFeature(state);
64
64
  registerWgpuColorAdjustmentMaterialFeature(state);
65
- expect(runtime.wgpuColorAdjustmentMaterialFeature).toBe(fold);
65
+ expect(runtime.registries.colorAdjustmentFeature).toBe(table);
66
+ expect(getWgpuColorAdjustmentMaterialFeature(state)).toBe(fold);
66
67
  });
67
68
 
68
69
  it('stays untinted (mode NONE) when no instance carries a color adjustment', async () => {
@@ -9,7 +9,7 @@ import { createWgpuRenderStateForTest, installWgpuMock } from '@flighthq/render-
9
9
  import { getRenderProxy2D, prepareScene2DRender } from '@flighthq/render/contract';
10
10
  import { createTexture } from '@flighthq/texture/contract';
11
11
  import type { CompressedImage, RenderProxy2D } from '@flighthq/types/contract';
12
- import { CompressedImageTextureSourceKind } from '@flighthq/types/contract';
12
+ import { CompressedImageTextureSourceKind, RegistryEntryState } from '@flighthq/types/contract';
13
13
 
14
14
  import { defaultWgpuParticleEmitter2DRenderer, drawWgpuParticleEmitter2D } from './wgpuParticleEmitter2D';
15
15
 
@@ -53,19 +53,25 @@ describe('drawWgpuParticleEmitter2D', () => {
53
53
  version: 1,
54
54
  width: 4,
55
55
  } as unknown as CompressedImage;
56
- runtime.compressedTextureUpload = () => {
57
- const texture = state.device.createTexture({
58
- size: [4, 4],
59
- format: 'bc3-rgba-unorm',
60
- usage: GPUTextureUsage.TEXTURE_BINDING,
61
- });
62
- const view = texture.createView();
63
- return {
64
- bindGroup: state.device.createBindGroup({ layout: runtime.textureBindGroupLayout, entries: [] }),
65
- straightAlpha: true,
66
- texture,
67
- view,
68
- };
56
+ runtime.registries.compressedTextureUpload = {
57
+ ...runtime.registries.compressedTextureUpload,
58
+ entry: {
59
+ state: RegistryEntryState.Bound,
60
+ value: () => {
61
+ const texture = state.device.createTexture({
62
+ size: [4, 4],
63
+ format: 'bc3-rgba-unorm',
64
+ usage: GPUTextureUsage.TEXTURE_BINDING,
65
+ });
66
+ const view = texture.createView();
67
+ return {
68
+ bindGroup: state.device.createBindGroup({ layout: runtime.textureBindGroupLayout, entries: [] }),
69
+ straightAlpha: true,
70
+ texture,
71
+ view,
72
+ };
73
+ },
74
+ },
69
75
  };
70
76
  const renderProxy = {
71
77
  alpha: 1,
@@ -1,4 +1,8 @@
1
- import { createWgpuRenderStateForTest, installWgpuMock } from '@flighthq/render-wgpu/contract';
1
+ import {
2
+ createWgpuRenderStateForTest,
3
+ getWgpuRenderStateRuntime,
4
+ installWgpuMock,
5
+ } from '@flighthq/render-wgpu/contract';
2
6
  import { createRenderState, enableRenderRegistryGuards, explainRenderRegistryMisses } from '@flighthq/render/contract';
3
7
  import { RenderRegistry } from '@flighthq/types/contract';
4
8
 
@@ -19,18 +23,24 @@ describe('registerWgpuShapeRasterizer', () => {
19
23
  it('installs the rasterizer the shape path draws non-solid fills through', async () => {
20
24
  const state = await createWgpuRenderStateForTest();
21
25
  const rasterizer = (): void => {};
26
+ const before = getWgpuRenderStateRuntime(state).registries.shapeRasterizer;
22
27
 
23
28
  registerWgpuShapeRasterizer(state, rasterizer);
24
29
 
25
30
  expect(getWgpuShapeRasterizer(state)).toBe(rasterizer);
31
+ expect(getWgpuRenderStateRuntime(state).registries.shapeRasterizer).not.toBe(before);
32
+ expect(before.entry).toBeNull();
26
33
  });
27
34
 
28
35
  it('removes one again, so a state can drop back to tessellation only', async () => {
29
36
  const state = await createWgpuRenderStateForTest();
30
37
  registerWgpuShapeRasterizer(state, (): void => {});
38
+ const before = getWgpuRenderStateRuntime(state).registries.shapeRasterizer;
31
39
 
32
40
  registerWgpuShapeRasterizer(state, null);
33
41
 
34
42
  expect(getWgpuShapeRasterizer(state)).toBeNull();
43
+ expect(getWgpuRenderStateRuntime(state).registries.shapeRasterizer).not.toBe(before);
44
+ expect(before.entry?.state).toBe('bound');
35
45
  });
36
46
  });
@@ -24,14 +24,14 @@ beforeAll(async () => {
24
24
  const actual = (await importOriginal()) as Record<string, unknown>;
25
25
  return {
26
26
  ...actual,
27
- computeTextLayout: vi.fn((result: { groups: object[] }) => {
27
+ computeTextLayout: vi.fn((result: { groups: object[] }, params: { formatRanges: Array<{ format: object }> }) => {
28
28
  result.groups.push({
29
29
  offsetX: 0,
30
30
  offsetY: 0,
31
31
  width: 50,
32
32
  ascent: 12,
33
33
  descent: 4,
34
- format: {},
34
+ format: params.formatRanges[0]?.format ?? {},
35
35
  startIndex: 0,
36
36
  endIndex: 5,
37
37
  });
@@ -107,6 +107,22 @@ describe('drawWgpuTextLabel', () => {
107
107
  submitWgpuRenderPass(state);
108
108
  });
109
109
 
110
+ it('rasterizes packed run alpha into the canvas color', async () => {
111
+ const state = await createWgpuRenderStateForTest();
112
+ renderWgpuBackground(state);
113
+ registerWgpuStandardMaterial(state);
114
+ const data = makeTextData();
115
+ const proxy = makeTextProxy('hello', data);
116
+ (proxy.source as ReturnType<typeof createTextLabel>).data.textFormat = { color: 0xff000080 };
117
+ const styles: Array<string | CanvasGradient | CanvasPattern> = [];
118
+ vi.spyOn(data.ctx, 'fillText').mockImplementation(() => styles.push(data.ctx.fillStyle));
119
+
120
+ drawWgpuTextLabel(state, proxy);
121
+
122
+ expect(styles).toEqual(['rgba(255, 0, 0, 0.5019607843137255)']);
123
+ submitWgpuRenderPass(state);
124
+ });
125
+
110
126
  it('does not re-rasterize when only alpha changes (content version unchanged)', async () => {
111
127
  const state = await createWgpuRenderStateForTest();
112
128
  renderWgpuBackground(state);
@@ -1,6 +1,10 @@
1
1
  import { createParticleEmitter2D, reserveParticleEmitter2D } from '@flighthq/particleemitter/contract';
2
2
  import { createQuadBatch, getQuadBatchRuntime } from '@flighthq/quadbatch/contract';
3
- import { renderWgpuBackground } from '@flighthq/render-wgpu/contract';
3
+ import {
4
+ createWgpuOffscreenRenderState,
5
+ getWgpuRenderStateRuntime,
6
+ renderWgpuBackground,
7
+ } from '@flighthq/render-wgpu/contract';
4
8
  import { createWgpuRenderStateForTest, installWgpuMock } from '@flighthq/render-wgpu/contract';
5
9
  import { createDisplayObject } from '@flighthq/scene2d/contract';
6
10
  import type { QuadBatchRuntime, TextureAtlas, TextureAtlasRegion } from '@flighthq/types/contract';
@@ -119,6 +123,24 @@ describe('registerWgpuVelocityWriter', () => {
119
123
  registerWgpuVelocityWriter(state, root.kind, defaultWgpuNode2DVelocityWriter);
120
124
  expect(getWgpuVelocityWriter(state, root.kind)).toBe(defaultWgpuNode2DVelocityWriter);
121
125
  });
126
+
127
+ it('replaces persistent snapshots and preserves a derived pipeline snapshot', async () => {
128
+ const state = await createWgpuRenderStateForTest();
129
+ const kind = 'acme.Velocity';
130
+ const replacement = vi.fn();
131
+ const before = getWgpuRenderStateRuntime(state).registries.velocityWriters;
132
+
133
+ registerWgpuVelocityWriter(state, kind, defaultWgpuNode2DVelocityWriter);
134
+ const registered = getWgpuRenderStateRuntime(state).registries.velocityWriters;
135
+ const offscreen = createWgpuOffscreenRenderState(state);
136
+ registerWgpuVelocityWriter(state, kind, replacement);
137
+
138
+ expect(registered).not.toBe(before);
139
+ expect(before.entries.has(kind)).toBe(false);
140
+ expect(registered.entries.get(kind)).toEqual({ state: 'bound', value: defaultWgpuNode2DVelocityWriter });
141
+ expect(getWgpuVelocityWriter(state, kind)).toBe(replacement);
142
+ expect(getWgpuVelocityWriter(offscreen, kind)).toBe(defaultWgpuNode2DVelocityWriter);
143
+ });
122
144
  });
123
145
 
124
146
  describe('renderWgpuVelocity', () => {