@flighthq/render-wgpu 0.2.1-next.841.373388f → 0.3.0-next.1032.e2b99fc

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 (91) hide show
  1. package/dist/contract.d.ts +22 -0
  2. package/dist/contract.d.ts.map +1 -0
  3. package/dist/contract.js +22 -0
  4. package/dist/contract.js.map +1 -0
  5. package/dist/index.d.ts +1 -18
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +1 -18
  8. package/dist/index.js.map +1 -1
  9. package/dist/wgpuAdapterCapabilities.d.ts +1 -1
  10. package/dist/wgpuAdapterCapabilities.d.ts.map +1 -1
  11. package/dist/wgpuBackground.d.ts +1 -1
  12. package/dist/wgpuBackground.d.ts.map +1 -1
  13. package/dist/wgpuBackground.js +2 -2
  14. package/dist/wgpuBackground.js.map +1 -1
  15. package/dist/wgpuCompressedTexture.d.ts +1 -1
  16. package/dist/wgpuCompressedTexture.d.ts.map +1 -1
  17. package/dist/wgpuCompressedTexture.js +4 -7
  18. package/dist/wgpuCompressedTexture.js.map +1 -1
  19. package/dist/wgpuDraw.d.ts +7 -5
  20. package/dist/wgpuDraw.d.ts.map +1 -1
  21. package/dist/wgpuDraw.js +76 -56
  22. package/dist/wgpuDraw.js.map +1 -1
  23. package/dist/wgpuExternalTexture.d.ts +4 -0
  24. package/dist/wgpuExternalTexture.d.ts.map +1 -0
  25. package/dist/wgpuExternalTexture.js +58 -0
  26. package/dist/wgpuExternalTexture.js.map +1 -0
  27. package/dist/wgpuFullscreenPass.d.ts +1 -1
  28. package/dist/wgpuFullscreenPass.d.ts.map +1 -1
  29. package/dist/wgpuMaterialRegistry.d.ts +1 -1
  30. package/dist/wgpuMaterialRegistry.d.ts.map +1 -1
  31. package/dist/wgpuMaterialRegistry.js +3 -3
  32. package/dist/wgpuMaterialRegistry.js.map +1 -1
  33. package/dist/wgpuMipmap.d.ts +1 -1
  34. package/dist/wgpuMipmap.d.ts.map +1 -1
  35. package/dist/wgpuRenderState.d.ts +1 -1
  36. package/dist/wgpuRenderState.d.ts.map +1 -1
  37. package/dist/wgpuRenderState.js +22 -21
  38. package/dist/wgpuRenderState.js.map +1 -1
  39. package/dist/wgpuRenderTarget.d.ts +1 -1
  40. package/dist/wgpuRenderTarget.d.ts.map +1 -1
  41. package/dist/wgpuRenderTarget.js +4 -2
  42. package/dist/wgpuRenderTarget.js.map +1 -1
  43. package/dist/wgpuRenderTargetPool.d.ts +1 -1
  44. package/dist/wgpuRenderTargetPool.d.ts.map +1 -1
  45. package/dist/wgpuRenderTargetPool.js.map +1 -1
  46. package/dist/wgpuRenderTexture.d.ts +10 -0
  47. package/dist/wgpuRenderTexture.d.ts.map +1 -0
  48. package/dist/wgpuRenderTexture.js +73 -0
  49. package/dist/wgpuRenderTexture.js.map +1 -0
  50. package/dist/wgpuScissor.d.ts +1 -1
  51. package/dist/wgpuScissor.d.ts.map +1 -1
  52. package/dist/wgpuShader.d.ts +3 -3
  53. package/dist/wgpuShader.d.ts.map +1 -1
  54. package/dist/wgpuShader.js +28 -28
  55. package/dist/wgpuShader.js.map +1 -1
  56. package/dist/wgpuShaderBinding.d.ts +1 -1
  57. package/dist/wgpuShaderBinding.d.ts.map +1 -1
  58. package/dist/wgpuShaderBinding.js +1 -1
  59. package/dist/wgpuShaderBinding.js.map +1 -1
  60. package/dist/wgpuShaderRegistry.d.ts +1 -1
  61. package/dist/wgpuShaderRegistry.d.ts.map +1 -1
  62. package/dist/wgpuShaderRegistry.js +1 -1
  63. package/dist/wgpuShaderRegistry.js.map +1 -1
  64. package/dist/wgpuSurface.d.ts +2 -2
  65. package/dist/wgpuSurface.d.ts.map +1 -1
  66. package/dist/wgpuSurface.js +12 -12
  67. package/dist/wgpuSurface.js.map +1 -1
  68. package/dist/wgpuTestHelper.d.ts +1 -1
  69. package/dist/wgpuTestHelper.d.ts.map +1 -1
  70. package/dist/wgpuTextureResolver.d.ts +9 -0
  71. package/dist/wgpuTextureResolver.d.ts.map +1 -0
  72. package/dist/wgpuTextureResolver.js +61 -0
  73. package/dist/wgpuTextureResolver.js.map +1 -0
  74. package/dist/wgpuTextureUpload.d.ts +1 -1
  75. package/dist/wgpuTextureUpload.d.ts.map +1 -1
  76. package/dist/wgpuTextureUpload.js +3 -9
  77. package/dist/wgpuTextureUpload.js.map +1 -1
  78. package/package.json +12 -7
  79. package/src/wgpuCompressedTexture.test.ts +18 -35
  80. package/src/wgpuDraw.test.ts +111 -65
  81. package/src/wgpuExternalTexture.test.ts +35 -0
  82. package/src/wgpuMaterialRegistry.test.ts +4 -4
  83. package/src/wgpuMipmap.test.ts +1 -1
  84. package/src/wgpuRenderTarget.test.ts +15 -1
  85. package/src/wgpuRenderTexture.test.ts +99 -0
  86. package/src/wgpuShader.test.ts +8 -8
  87. package/src/wgpuShaderBinding.test.ts +8 -8
  88. package/src/wgpuShaderRegistry.test.ts +1 -1
  89. package/src/wgpuSurface.test.ts +3 -3
  90. package/src/wgpuTextureResolver.test.ts +156 -0
  91. package/src/wgpuTextureUpload.test.ts +2 -12
@@ -1,4 +1,5 @@
1
- import type { ImageResource, TextureContainer, WgpuCompressedTextureSupport } from '@flighthq/types';
1
+ import type { CompressedImage, TextureContainer, WgpuCompressedTextureSupport } from '@flighthq/types/contract';
2
+ import { CompressedImageTextureBackingKind } from '@flighthq/types/contract';
2
3
 
3
4
  import {
4
5
  detectWgpuCompressedTextureSupport,
@@ -8,7 +9,7 @@ import {
8
9
  registerWgpuCompressedTextureUpload,
9
10
  uploadWgpuCompressedTextureContainer,
10
11
  } from './wgpuCompressedTexture';
11
- import { bindWgpuImageResourceTexture } from './wgpuDraw';
12
+ import { bindWgpuCompressedImageTexture } from './wgpuDraw';
12
13
  import { getWgpuRenderStateRuntime } from './wgpuRenderState';
13
14
  import { createWgpuRenderStateForTest, installWgpuMock } from './wgpuTestHelper';
14
15
 
@@ -29,6 +30,16 @@ function container(overrides: Partial<TextureContainer> = {}): TextureContainer
29
30
  };
30
31
  }
31
32
 
33
+ function compressedImage(): CompressedImage {
34
+ return {
35
+ compressed: { container: container(), payload: new Uint8Array(8) },
36
+ height: 4,
37
+ kind: CompressedImageTextureBackingKind,
38
+ version: 1,
39
+ width: 4,
40
+ } as unknown as CompressedImage;
41
+ }
42
+
32
43
  describe('detectWgpuCompressedTextureSupport', () => {
33
44
  it('reads the three WebGPU compression features', async () => {
34
45
  const state = await createWgpuRenderStateForTest();
@@ -69,7 +80,7 @@ describe('registerWgpuCompressedTextureDecoder', () => {
69
80
  });
70
81
 
71
82
  describe('registerWgpuCompressedTextureUpload', () => {
72
- it('installs and clears the ImageResource upload seam', async () => {
83
+ it('installs and clears the CompressedImage upload seam', async () => {
73
84
  const state = await createWgpuRenderStateForTest();
74
85
  registerWgpuCompressedTextureUpload(state);
75
86
  expect(getWgpuRenderStateRuntime(state).compressedTextureUpload).toBeTypeOf('function');
@@ -77,23 +88,14 @@ describe('registerWgpuCompressedTextureUpload', () => {
77
88
  expect(getWgpuRenderStateRuntime(state).compressedTextureUpload).toBeNull();
78
89
  });
79
90
 
80
- it('lets the normal image binder consume a compressed-only resource', async () => {
91
+ it('lets the compressed-image binder consume its backing', async () => {
81
92
  const state = await createWgpuRenderStateForTest();
82
93
  registerWgpuCompressedTextureUpload(state);
83
94
  registerWgpuCompressedTextureDecoder(state, (_format, width, height) => {
84
95
  return new Uint8ClampedArray(width * height * 4);
85
96
  });
86
- const image = {
87
- alphaType: 'straight',
88
- compressed: { container: container(), payload: new Uint8Array(8) },
89
- data: null,
90
- height: 4,
91
- source: null,
92
- version: 1,
93
- width: 4,
94
- } as unknown as ImageResource;
95
97
  const writeTexture = vi.spyOn(state.device.queue, 'writeTexture');
96
- expect(bindWgpuImageResourceTexture(state, image).view).toBeDefined();
98
+ expect(bindWgpuCompressedImageTexture(state, compressedImage())?.view).toBeDefined();
97
99
  expect(writeTexture).toHaveBeenCalledTimes(1);
98
100
  });
99
101
 
@@ -109,18 +111,9 @@ describe('registerWgpuCompressedTextureUpload', () => {
109
111
  }
110
112
  return rgba;
111
113
  });
112
- const image = {
113
- alphaType: 'straight',
114
- compressed: { container: container(), payload: new Uint8Array(8) },
115
- data: null,
116
- height: 4,
117
- source: null,
118
- version: 1,
119
- width: 4,
120
- } as unknown as ImageResource;
121
114
  const writeTexture = vi.spyOn(state.device.queue, 'writeTexture');
122
115
 
123
- const entry = bindWgpuImageResourceTexture(state, image);
116
+ const entry = bindWgpuCompressedImageTexture(state, compressedImage())!;
124
117
 
125
118
  expect(entry.straightAlpha).toBe(false);
126
119
  expect(Array.from(writeTexture.mock.calls[0][1] as Uint8ClampedArray).slice(0, 4)).toEqual([128, 32, 0, 128]);
@@ -130,17 +123,7 @@ describe('registerWgpuCompressedTextureUpload', () => {
130
123
  const state = await createWgpuRenderStateForTest();
131
124
  (state.device.features as Set<GPUFeatureName>).add('texture-compression-bc');
132
125
  registerWgpuCompressedTextureUpload(state);
133
- const image = {
134
- alphaType: 'straight',
135
- compressed: { container: container(), payload: new Uint8Array(8) },
136
- data: null,
137
- height: 4,
138
- source: null,
139
- version: 1,
140
- width: 4,
141
- } as unknown as ImageResource;
142
-
143
- expect(bindWgpuImageResourceTexture(state, image).straightAlpha).toBe(true);
126
+ expect(bindWgpuCompressedImageTexture(state, compressedImage())?.straightAlpha).toBe(true);
144
127
  });
145
128
  });
146
129
 
@@ -1,11 +1,19 @@
1
- import { getOrCreateRenderProxy2D, prepareScene2DRender } from '@flighthq/render';
2
- import { createBitmap } from '@flighthq/scene2d';
3
- import type { ImageResource, VideoTexture } from '@flighthq/types';
4
- import { BlendMode } from '@flighthq/types';
1
+ import { getOrCreateRenderProxy2D, prepareScene2DRender } from '@flighthq/render/contract';
2
+ import { createSprite } from '@flighthq/scene2d/contract';
3
+ import type { Bitmap, CompressedImage, ImageResource, Texture } from '@flighthq/types/contract';
4
+ import {
5
+ BitmapTextureBackingKind,
6
+ BlendMode,
7
+ CompressedImageTextureBackingKind,
8
+ ImageTextureBackingKind,
9
+ } from '@flighthq/types/contract';
5
10
 
6
11
  import { renderWgpuBackground, submitWgpuRenderPass } from './wgpuBackground';
12
+ import { registerWgpuCompressedTextureDecoder, registerWgpuCompressedTextureUpload } from './wgpuCompressedTexture';
7
13
  import {
8
14
  applyWgpuBlendMode,
15
+ bindWgpuBitmapTexture,
16
+ bindWgpuCompressedImageTexture,
9
17
  bindWgpuImageResourceTexture,
10
18
  bindWgpuTexture,
11
19
  bindWgpuVideoTexture,
@@ -15,7 +23,7 @@ import {
15
23
  drawWgpuQuadWithTransform,
16
24
  destroyWgpuVideoTexture,
17
25
  enableWgpuBlendModeSupport,
18
- getWgpuRenderProxyColorTransform,
26
+ getWgpuRenderProxyColorScaleBias,
19
27
  resolveWgpuSmoothingBindGroup,
20
28
  submitWgpuQuadDraw,
21
29
  updateWgpuTextureEntry,
@@ -28,6 +36,42 @@ beforeAll(() => {
28
36
  installWgpuMock();
29
37
  });
30
38
 
39
+ function bitmap(size: number, version: number): Bitmap {
40
+ return {
41
+ alphaType: 'straight',
42
+ colorSpace: 'srgb',
43
+ data: new Uint8ClampedArray(size * size * 4),
44
+ format: 'rgba8unorm',
45
+ height: size,
46
+ kind: BitmapTextureBackingKind,
47
+ version,
48
+ width: size,
49
+ } as unknown as Bitmap;
50
+ }
51
+
52
+ function compressedBc3Image(): CompressedImage {
53
+ return {
54
+ compressed: {
55
+ container: {
56
+ depth: 1,
57
+ faces: 1,
58
+ format: 'bc3',
59
+ height: 4,
60
+ layers: 1,
61
+ levels: [{ byteLength: 16, byteOffset: 0, height: 4, width: 4 }],
62
+ mipLevels: 1,
63
+ supercompression: 'None',
64
+ width: 4,
65
+ },
66
+ payload: new Uint8Array(16),
67
+ },
68
+ height: 4,
69
+ kind: CompressedImageTextureBackingKind,
70
+ version: 1,
71
+ width: 4,
72
+ } as unknown as CompressedImage;
73
+ }
74
+
31
75
  describe('applyWgpuBlendMode', () => {
32
76
  it('updates currentBlendMode on the state', async () => {
33
77
  const state = await createWgpuRenderStateForTest();
@@ -42,27 +86,46 @@ describe('applyWgpuBlendMode', () => {
42
86
  });
43
87
  });
44
88
 
45
- describe('bindWgpuImageResourceTexture', () => {
46
- function dataResource(size: number, version: number): ImageResource {
47
- return {
48
- source: null,
49
- data: new Uint8ClampedArray(size * size * 4),
50
- width: size,
51
- height: size,
52
- version,
53
- alphaType: 'straight',
54
- } as unknown as ImageResource;
55
- }
56
-
57
- it('uploads a data-only ImageResource (a generated Surface) via writeTexture', async () => {
89
+ describe('bindWgpuBitmapTexture', () => {
90
+ it('uploads CPU-readable pixels via writeTexture', async () => {
58
91
  const state = await createWgpuRenderStateForTest();
59
92
  const writeTexture = vi.spyOn(state.device.queue, 'writeTexture');
60
- const entry = bindWgpuImageResourceTexture(state, dataResource(4, 1));
93
+ const entry = bindWgpuBitmapTexture(state, bitmap(4, 1));
61
94
  expect(entry.texture).toBeDefined();
62
95
  expect(writeTexture).toHaveBeenCalled();
63
96
  });
64
97
 
65
- it('uploads an element-backed ImageResource via copyExternalImageToTexture', async () => {
98
+ it('caches by resource identity and re-uploads only when the version changes', async () => {
99
+ const state = await createWgpuRenderStateForTest();
100
+ const image = bitmap(2, 1);
101
+ const first = bindWgpuBitmapTexture(state, image);
102
+ expect(bindWgpuBitmapTexture(state, image)).toBe(first);
103
+ const createTexture = vi.spyOn(state.device, 'createTexture');
104
+ image.version = 2;
105
+ bindWgpuBitmapTexture(state, image);
106
+ expect(createTexture).toHaveBeenCalled();
107
+ });
108
+ });
109
+
110
+ describe('bindWgpuCompressedImageTexture', () => {
111
+ it('routes through the registered compressed upload seam', async () => {
112
+ const state = await createWgpuRenderStateForTest();
113
+ const decode = vi.fn(() => new Uint8ClampedArray(4 * 4 * 4));
114
+ registerWgpuCompressedTextureUpload(state);
115
+ registerWgpuCompressedTextureDecoder(state, decode);
116
+ const entry = bindWgpuCompressedImageTexture(state, compressedBc3Image());
117
+ expect(entry?.texture).toBeDefined();
118
+ expect(decode).toHaveBeenCalledWith('bc3', 4, 4, expect.any(Uint8Array));
119
+ });
120
+
121
+ it('returns null when no compressed uploader is registered', async () => {
122
+ const state = await createWgpuRenderStateForTest();
123
+ expect(bindWgpuCompressedImageTexture(state, compressedBc3Image())).toBeNull();
124
+ });
125
+ });
126
+
127
+ describe('bindWgpuImageResourceTexture', () => {
128
+ it('uploads a host-backed image via copyExternalImageToTexture', async () => {
66
129
  const state = await createWgpuRenderStateForTest();
67
130
  const copy = vi.spyOn(state.device.queue, 'copyExternalImageToTexture');
68
131
  const canvas = document.createElement('canvas');
@@ -70,26 +133,14 @@ describe('bindWgpuImageResourceTexture', () => {
70
133
  canvas.height = 4;
71
134
  const image = {
72
135
  source: canvas,
73
- data: null,
74
136
  width: 4,
75
137
  height: 4,
138
+ kind: ImageTextureBackingKind,
76
139
  version: 1,
77
- alphaType: 'straight',
78
140
  } as unknown as ImageResource;
79
141
  bindWgpuImageResourceTexture(state, image);
80
142
  expect(copy).toHaveBeenCalled();
81
143
  });
82
-
83
- it('caches by resource identity and re-uploads only when the version changes', async () => {
84
- const state = await createWgpuRenderStateForTest();
85
- const image = dataResource(2, 1);
86
- const first = bindWgpuImageResourceTexture(state, image);
87
- expect(bindWgpuImageResourceTexture(state, image)).toBe(first);
88
- const createTexture = vi.spyOn(state.device, 'createTexture');
89
- image.version = 2;
90
- bindWgpuImageResourceTexture(state, image);
91
- expect(createTexture).toHaveBeenCalled();
92
- });
93
144
  });
94
145
 
95
146
  describe('bindWgpuTexture', () => {
@@ -129,9 +180,8 @@ describe('bindWgpuTexture', () => {
129
180
  });
130
181
 
131
182
  describe('bindWgpuVideoTexture', () => {
132
- function videoTexture(frameId: number, readyState = 4, width = 320, height = 240): VideoTexture {
183
+ function videoTexture(version: number, readyState = 4, width = 320, height = 240): Texture {
133
184
  return {
134
- frameId,
135
185
  sampler: {
136
186
  anisotropy: 1,
137
187
  magFilter: 'linear',
@@ -140,11 +190,18 @@ describe('bindWgpuVideoTexture', () => {
140
190
  wrapU: 'clamp-to-edge',
141
191
  wrapV: 'clamp-to-edge',
142
192
  },
143
- source: { element: { readyState, videoHeight: height, videoWidth: width } as HTMLVideoElement },
144
- } as unknown as VideoTexture;
193
+ storage: {
194
+ dimension: '2d',
195
+ image: {
196
+ source: { readyState, videoHeight: height, videoWidth: width } as HTMLVideoElement,
197
+ version,
198
+ },
199
+ },
200
+ version,
201
+ } as unknown as Texture;
145
202
  }
146
203
 
147
- it('uploads once per frameId and caches the texture by VideoTexture identity', async () => {
204
+ it('uploads once per backing version and caches the texture by backing identity', async () => {
148
205
  const state = await createWgpuRenderStateForTest();
149
206
  const copy = vi.spyOn(state.device.queue, 'copyExternalImageToTexture');
150
207
  const video = videoTexture(1);
@@ -153,7 +210,7 @@ describe('bindWgpuVideoTexture', () => {
153
210
  expect(copy).toHaveBeenCalledTimes(1);
154
211
  expect(bindWgpuVideoTexture(state, video)).toBe(first);
155
212
  expect(copy).toHaveBeenCalledTimes(1);
156
- video.frameId = 2;
213
+ video.storage.image!.version = 2;
157
214
  expect(bindWgpuVideoTexture(state, video)).toBe(first);
158
215
  expect(copy).toHaveBeenCalledTimes(2);
159
216
  });
@@ -162,7 +219,7 @@ describe('bindWgpuVideoTexture', () => {
162
219
  const state = await createWgpuRenderStateForTest();
163
220
  const video = videoTexture(1, 1, 0, 0);
164
221
  expect(bindWgpuVideoTexture(state, video)).toBeNull();
165
- const element = video.source.element!;
222
+ const element = (video.storage.image as ImageResource).source as HTMLVideoElement;
166
223
  Object.assign(element, { readyState: 4, videoHeight: 120, videoWidth: 160 });
167
224
  const first = bindWgpuVideoTexture(state, video)!;
168
225
  const destroy = vi.spyOn(first.texture, 'destroy');
@@ -214,7 +271,7 @@ describe('drawWgpuQuad', () => {
214
271
  it('does not throw when render pass is open', async () => {
215
272
  const state = await createWgpuRenderStateForTest();
216
273
  renderWgpuBackground(state);
217
- const bitmap = createBitmap();
274
+ const bitmap = createSprite();
218
275
  prepareScene2DRender(state, bitmap);
219
276
  const renderProxy = getOrCreateRenderProxy2D(state, bitmap);
220
277
  const canvas = document.createElement('canvas');
@@ -230,7 +287,7 @@ describe('drawWgpuQuadWithTransform', () => {
230
287
  it('does not throw when render pass is open', async () => {
231
288
  const state = await createWgpuRenderStateForTest();
232
289
  renderWgpuBackground(state);
233
- const bitmap = createBitmap();
290
+ const bitmap = createSprite();
234
291
  prepareScene2DRender(state, bitmap);
235
292
  const renderProxy = getOrCreateRenderProxy2D(state, bitmap);
236
293
  const canvas = document.createElement('canvas');
@@ -251,33 +308,22 @@ describe('enableWgpuBlendModeSupport', () => {
251
308
  });
252
309
  });
253
310
 
254
- describe('getWgpuRenderProxyColorTransform', () => {
255
- it('returns null when the node has no color transform', () => {
256
- expect(getWgpuRenderProxyColorTransform({} as never)).toBeNull();
257
- expect(getWgpuRenderProxyColorTransform({ colorTransform: null } as never)).toBeNull();
311
+ describe('getWgpuRenderProxyColorScaleBias', () => {
312
+ it('returns null when the node has no color adjustment', () => {
313
+ expect(getWgpuRenderProxyColorScaleBias({} as never)).toBeNull();
314
+ expect(getWgpuRenderProxyColorScaleBias({ colorScaleBias: null } as never)).toBeNull();
258
315
  });
259
316
 
260
- it('returns the resolved node-level color transform trait', () => {
261
- const colorTransform = { redMultiplier: 0.5 };
262
- expect(getWgpuRenderProxyColorTransform({ colorTransform } as never)).toBe(colorTransform);
317
+ it('returns the resolved node-level color adjustment trait', () => {
318
+ const colorScaleBias = { redScale: 0.5 };
319
+ expect(getWgpuRenderProxyColorScaleBias({ colorScaleBias } as never)).toBe(colorScaleBias);
263
320
  });
264
321
  });
265
322
 
266
323
  describe('resolveWgpuSmoothingBindGroup', () => {
267
- function dataResource(size: number, version: number): ImageResource {
268
- return {
269
- source: null,
270
- data: new Uint8ClampedArray(size * size * 4),
271
- width: size,
272
- height: size,
273
- version,
274
- alphaType: 'straight',
275
- } as unknown as ImageResource;
276
- }
277
-
278
324
  it('returns the default bind group for a null smoothing (no variant built)', async () => {
279
325
  const state = await createWgpuRenderStateForTest();
280
- const entry = bindWgpuImageResourceTexture(state, dataResource(4, 1));
326
+ const entry = bindWgpuBitmapTexture(state, bitmap(4, 1));
281
327
  expect(resolveWgpuSmoothingBindGroup(state, entry, null)).toBe(entry.bindGroup);
282
328
  expect(entry.bindGroupLinear).toBeUndefined();
283
329
  expect(entry.bindGroupNearest).toBeUndefined();
@@ -285,7 +331,7 @@ describe('resolveWgpuSmoothingBindGroup', () => {
285
331
 
286
332
  it('builds and caches distinct LINEAR and NEAREST variants for true/false', async () => {
287
333
  const state = await createWgpuRenderStateForTest();
288
- const entry = bindWgpuImageResourceTexture(state, dataResource(4, 1));
334
+ const entry = bindWgpuBitmapTexture(state, bitmap(4, 1));
289
335
  const createBindGroup = vi.spyOn(state.device, 'createBindGroup');
290
336
 
291
337
  resolveWgpuSmoothingBindGroup(state, entry, true);
@@ -304,13 +350,13 @@ describe('resolveWgpuSmoothingBindGroup', () => {
304
350
 
305
351
  it('drops the cached variants when the texture re-uploads (version bump)', async () => {
306
352
  const state = await createWgpuRenderStateForTest();
307
- const image = dataResource(4, 1);
308
- const entry = bindWgpuImageResourceTexture(state, image);
353
+ const image = bitmap(4, 1);
354
+ const entry = bindWgpuBitmapTexture(state, image);
309
355
  resolveWgpuSmoothingBindGroup(state, entry, true);
310
356
  resolveWgpuSmoothingBindGroup(state, entry, false);
311
357
  expect(entry.bindGroupLinear).toBeDefined();
312
358
  image.version = 2;
313
- bindWgpuImageResourceTexture(state, image);
359
+ bindWgpuBitmapTexture(state, image);
314
360
  expect(entry.bindGroupLinear).toBeUndefined();
315
361
  expect(entry.bindGroupNearest).toBeUndefined();
316
362
  });
@@ -0,0 +1,35 @@
1
+ import { getTextureBackingKind } from '@flighthq/texture/contract';
2
+ import { ExternalTextureBackingKind } from '@flighthq/types/contract';
3
+
4
+ import { createExternalWgpuTexture, disposeExternalWgpuTexture } from './wgpuExternalTexture';
5
+ import { createWgpuRenderStateForTest, installWgpuMock } from './wgpuTestHelper';
6
+ import { resolveWgpuTexture } from './wgpuTextureResolver';
7
+
8
+ beforeAll(() => {
9
+ installWgpuMock();
10
+ });
11
+
12
+ describe('createExternalWgpuTexture', () => {
13
+ it('resolves a borrowed handle and forgets it without destroying the allocation', async () => {
14
+ const state = await createWgpuRenderStateForTest();
15
+ const destroy = vi.fn();
16
+ const handle = { createView: vi.fn(() => ({})), destroy } as unknown as GPUTexture;
17
+ const texture = createExternalWgpuTexture(state, handle, { height: 16, width: 32 });
18
+
19
+ expect(getTextureBackingKind(texture)).toBe(ExternalTextureBackingKind);
20
+ expect(resolveWgpuTexture(state, texture)?.texture).toBe(handle);
21
+ expect(disposeExternalWgpuTexture(state, texture)).toBe(true);
22
+ expect(resolveWgpuTexture(state, texture)).toBeNull();
23
+ expect(destroy).not.toHaveBeenCalled();
24
+ });
25
+ });
26
+
27
+ describe('disposeExternalWgpuTexture', () => {
28
+ it('returns false after the borrowed handle has already been forgotten', async () => {
29
+ const state = await createWgpuRenderStateForTest();
30
+ const handle = { createView: () => ({}) } as unknown as GPUTexture;
31
+ const texture = createExternalWgpuTexture(state, handle, { height: 1, width: 1 });
32
+ expect(disposeExternalWgpuTexture(state, texture)).toBe(true);
33
+ expect(disposeExternalWgpuTexture(state, texture)).toBe(false);
34
+ });
35
+ });
@@ -1,5 +1,5 @@
1
- import type { Material, WgpuMaterialRenderer, WgpuRenderState } from '@flighthq/types';
2
- import { DefaultMaterialKind, EntityRuntimeKey } from '@flighthq/types';
1
+ import type { Material, WgpuMaterialRenderer, WgpuRenderState } from '@flighthq/types/contract';
2
+ import { StandardMaterialKind, EntityRuntimeKey } from '@flighthq/types/contract';
3
3
 
4
4
  import {
5
5
  getWgpuMaterialRenderer,
@@ -47,9 +47,9 @@ describe('resolveWgpuMaterialRenderer', () => {
47
47
  expect(resolveWgpuMaterialRenderer(state, makeMaterial(TestKind))).toBe(testRenderer);
48
48
  });
49
49
 
50
- it('falls back to the renderer registered for DefaultMaterialKind', () => {
50
+ it('falls back to the renderer registered for StandardMaterialKind', () => {
51
51
  const state = makeState();
52
- registerWgpuMaterialRenderer(state, DefaultMaterialKind, testRenderer);
52
+ registerWgpuMaterialRenderer(state, StandardMaterialKind, testRenderer);
53
53
  expect(resolveWgpuMaterialRenderer(state, makeMaterial('Other'))).toBe(testRenderer);
54
54
  expect(resolveWgpuMaterialRenderer(state, null)).toBe(testRenderer);
55
55
  });
@@ -1,4 +1,4 @@
1
- import type { WgpuRenderState } from '@flighthq/types';
1
+ import type { WgpuRenderState } from '@flighthq/types/contract';
2
2
 
3
3
  import { generateWgpuMipmaps, getWgpuMipLevelCount } from './wgpuMipmap';
4
4
  import { getWgpuRenderStateRuntime } from './wgpuRenderState';
@@ -1,4 +1,4 @@
1
- import { createMatrix } from '@flighthq/geometry';
1
+ import { createMatrix } from '@flighthq/geometry/contract';
2
2
 
3
3
  import { renderWgpuBackground, submitWgpuRenderPass } from './wgpuBackground';
4
4
  import { getWgpuRenderStateRuntime } from './wgpuRenderState';
@@ -155,6 +155,20 @@ describe('endWgpuRenderPass', () => {
155
155
  });
156
156
 
157
157
  describe('resizeWgpuRenderTarget', () => {
158
+ it('preserves the allocation when dimensions are unchanged', async () => {
159
+ const state = await createWgpuRenderStateForTest();
160
+ const target = createWgpuRenderTarget(state, 64, 64);
161
+ const texture = target.texture;
162
+ const depthStencilTexture = target.depthStencilTexture;
163
+ const bindGroup = target.bindGroup;
164
+
165
+ resizeWgpuRenderTarget(state, target, 64, 64);
166
+
167
+ expect(target.texture).toBe(texture);
168
+ expect(target.depthStencilTexture).toBe(depthStencilTexture);
169
+ expect(target.bindGroup).toBe(bindGroup);
170
+ });
171
+
158
172
  it('updates width, height, and bind group', async () => {
159
173
  const state = await createWgpuRenderStateForTest();
160
174
  const target = createWgpuRenderTarget(state, 64, 64);
@@ -0,0 +1,99 @@
1
+ import type { RenderTexture } from '@flighthq/types/contract';
2
+ import { RenderTextureBackingKind } from '@flighthq/types/contract';
3
+
4
+ import { renderWgpuBackground, submitWgpuRenderPass } from './wgpuBackground';
5
+ import { getWgpuRenderStateRuntime } from './wgpuRenderState';
6
+ import {
7
+ bindWgpuRenderTexture,
8
+ destroyWgpuRenderTexture,
9
+ isWgpuRenderTextureReady,
10
+ renderIntoWgpuRenderTexture,
11
+ } from './wgpuRenderTexture';
12
+ import { createWgpuRenderStateForTest, installWgpuMock } from './wgpuTestHelper';
13
+
14
+ beforeAll(() => {
15
+ installWgpuMock();
16
+ });
17
+
18
+ function texture(): RenderTexture {
19
+ return {
20
+ colorSpace: 'linear',
21
+ flipX: false,
22
+ flipY: false,
23
+ sampler: {
24
+ anisotropy: 1,
25
+ magFilter: 'linear',
26
+ minFilter: 'linear',
27
+ mipmaps: false,
28
+ wrapU: 'clamp-to-edge',
29
+ wrapV: 'clamp-to-edge',
30
+ },
31
+ storage: {
32
+ dimension: '2d',
33
+ image: null,
34
+ target: { height: 8, kind: RenderTextureBackingKind, width: 8 },
35
+ },
36
+ uvOffset: { x: 0, y: 0 },
37
+ uvRotation: 0,
38
+ uvScale: { x: 1, y: 1 },
39
+ version: 0,
40
+ } as RenderTexture;
41
+ }
42
+
43
+ describe('bindWgpuRenderTexture', () => {
44
+ it('returns null before render and the state-owned target after render', async () => {
45
+ const state = await createWgpuRenderStateForTest();
46
+ const renderTexture = texture();
47
+ expect(bindWgpuRenderTexture(state, renderTexture)).toBeNull();
48
+
49
+ renderWgpuBackground(state);
50
+ renderIntoWgpuRenderTexture(state, renderTexture, () => {});
51
+ expect(bindWgpuRenderTexture(state, renderTexture)).not.toBeNull();
52
+ submitWgpuRenderPass(state);
53
+ });
54
+ });
55
+
56
+ describe('destroyWgpuRenderTexture', () => {
57
+ it('destroys and removes the hidden target', async () => {
58
+ const state = await createWgpuRenderStateForTest();
59
+ const renderTexture = texture();
60
+ renderWgpuBackground(state);
61
+ renderIntoWgpuRenderTexture(state, renderTexture, () => {});
62
+ const entry = getWgpuRenderStateRuntime(state).wgpuRenderTextureCache!.get(renderTexture)!;
63
+ const destroy = vi.spyOn(entry.target.texture, 'destroy');
64
+
65
+ destroyWgpuRenderTexture(state, renderTexture);
66
+ expect(destroy).toHaveBeenCalled();
67
+ expect(bindWgpuRenderTexture(state, renderTexture)).toBeNull();
68
+ submitWgpuRenderPass(state);
69
+ });
70
+ });
71
+
72
+ describe('isWgpuRenderTextureReady', () => {
73
+ it('tracks the successful rendered-content transition', async () => {
74
+ const state = await createWgpuRenderStateForTest();
75
+ const renderTexture = texture();
76
+ expect(isWgpuRenderTextureReady(state, renderTexture)).toBe(false);
77
+ renderWgpuBackground(state);
78
+ renderIntoWgpuRenderTexture(state, renderTexture, () => {});
79
+ expect(isWgpuRenderTextureReady(state, renderTexture)).toBe(true);
80
+ submitWgpuRenderPass(state);
81
+ });
82
+ });
83
+
84
+ describe('renderIntoWgpuRenderTexture', () => {
85
+ it('restores the enclosing pass and bumps version after success', async () => {
86
+ const state = await createWgpuRenderStateForTest();
87
+ const renderTexture = texture();
88
+ renderWgpuBackground(state);
89
+ const enclosingPass = getWgpuRenderStateRuntime(state).renderPass;
90
+
91
+ renderIntoWgpuRenderTexture(state, renderTexture, () => {});
92
+
93
+ expect(renderTexture.version).toBe(1);
94
+ expect(renderTexture.colorSpace).toBe('linear');
95
+ expect(getWgpuRenderStateRuntime(state).renderPass).not.toBeNull();
96
+ expect(getWgpuRenderStateRuntime(state).renderPass).not.toBe(enclosingPass);
97
+ submitWgpuRenderPass(state);
98
+ });
99
+ });
@@ -1,4 +1,4 @@
1
- import { AdvancedBlendMode, BlendMode } from '@flighthq/types';
1
+ import { AdvancedBlendMode, BlendMode } from '@flighthq/types/contract';
2
2
 
3
3
  import { renderWgpuBackground } from './wgpuBackground';
4
4
  import { getWgpuRenderStateRuntime } from './wgpuRenderState';
@@ -159,7 +159,7 @@ describe('writeWgpuMatrixOnlyUniforms', () => {
159
159
  const runtime = getWgpuRenderStateRuntime(state);
160
160
  renderWgpuBackground(state);
161
161
  const before = runtime.uniformOffset;
162
- const fakeNode = { alpha: 1, useColorTransform: false, colorTransform: null };
162
+ const fakeNode = { alpha: 1, useColorScaleBias: false, colorScaleBias: null };
163
163
  const t = { a: 1, b: 0, c: 0, d: 1, tx: 0, ty: 0 };
164
164
  writeWgpuMatrixOnlyUniforms(state, fakeNode as never, t, 0, 0, 10, 10, 0, 0, 1, 1);
165
165
  expect(runtime.uniformOffset).toBe(before + runtime.uniformStride);
@@ -174,8 +174,8 @@ describe('writeWgpuQuadUniforms', () => {
174
174
  const before = runtime.uniformOffset;
175
175
  const fakeNode = {
176
176
  alpha: 1,
177
- useColorTransform: false,
178
- colorTransform: null,
177
+ useColorScaleBias: false,
178
+ colorScaleBias: null,
179
179
  transform2D: { a: 1, b: 0, c: 0, d: 1, tx: 0, ty: 0 },
180
180
  };
181
181
  writeWgpuQuadUniforms(state, fakeNode, null, 0, 0, 100, 100, 0, 0, 1, 1);
@@ -188,8 +188,8 @@ describe('writeWgpuQuadUniforms', () => {
188
188
  renderWgpuBackground(state);
189
189
  const fakeNode = {
190
190
  alpha: 0.5,
191
- useColorTransform: false,
192
- colorTransform: null,
191
+ useColorScaleBias: false,
192
+ colorScaleBias: null,
193
193
  transform2D: { a: 1, b: 0, c: 0, d: 1, tx: 0, ty: 0 },
194
194
  };
195
195
  const offset = writeWgpuQuadUniforms(state, fakeNode, null, 10, 20, 30, 40, 0.1, 0.2, 0.9, 0.8);
@@ -208,8 +208,8 @@ describe('writeWgpuQuadUniforms', () => {
208
208
  renderWgpuBackground(state);
209
209
  const fakeNode = {
210
210
  alpha: 1,
211
- useColorTransform: false,
212
- colorTransform: null,
211
+ useColorScaleBias: false,
212
+ colorScaleBias: null,
213
213
  transform2D: { a: 1, b: 0, c: 0, d: 1, tx: 0, ty: 0 },
214
214
  };
215
215