@flighthq/texture 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.
@@ -1,5 +1,6 @@
1
- import { createMatrix3, createVector2, inverseMatrix3 } from '@flighthq/geometry';
2
- import type { ImageResource } from '@flighthq/types';
1
+ import { createMatrix3, createVector2, inverseMatrix3 } from '@flighthq/geometry/contract';
2
+ import type { ImageResource } from '@flighthq/types/contract';
3
+ import { ImageTextureBackingKind, RenderTextureBackingKind } from '@flighthq/types/contract';
3
4
 
4
5
  import { createSampler, equalsSampler } from './sampler';
5
6
  import {
@@ -7,22 +8,25 @@ import {
7
8
  copyTexture,
8
9
  createTexture,
9
10
  equalsTexture,
11
+ getTextureBackingKind,
10
12
  getTextureHeight,
11
13
  getTextureInverseUvMatrix,
12
14
  getTextureUvMatrix,
13
15
  getTextureWidth,
16
+ hasTextureBacking,
14
17
  hasTextureUvTransform,
15
18
  isTextureReady,
16
19
  resetTextureUvTransform,
17
20
  setTextureFlip,
18
21
  setTextureImage,
19
22
  setTextureUvOffset,
23
+ setTextureUvFromPixelRect,
20
24
  setTextureUvRotation,
21
25
  setTextureUvScale,
22
26
  transformTextureUv,
23
27
  } from './texture';
24
28
 
25
- const fakeImage = { width: 32, height: 64 } as ImageResource;
29
+ const fakeImage = { height: 64, kind: ImageTextureBackingKind, width: 32 } as ImageResource;
26
30
 
27
31
  // Applies a column-major 3×3 uv matrix to (u, v, 1), writing the transformed coordinate into out.
28
32
  function multiplyMatrix3Uv(out: { x: number; y: number }, matrix: { m: ArrayLike<number> }, u: number, v: number) {
@@ -35,7 +39,7 @@ describe('cloneTexture', () => {
35
39
  it('shares the image but deep-clones the sampler and uv vectors', () => {
36
40
  const source = createTexture({
37
41
  colorSpace: 'linear',
38
- image: fakeImage,
42
+ storage: { dimension: '2d', image: fakeImage },
39
43
  uvRotation: 0.5,
40
44
  });
41
45
  source.uvOffset.x = 0.25;
@@ -44,7 +48,8 @@ describe('cloneTexture', () => {
44
48
  const copy = cloneTexture(source);
45
49
 
46
50
  expect(copy).not.toBe(source);
47
- expect(copy.image).toBe(fakeImage);
51
+ expect(copy.storage).not.toBe(source.storage);
52
+ expect(copy.storage.image).toBe(fakeImage);
48
53
  expect(copy.colorSpace).toStrictEqual('linear');
49
54
  expect(copy.uvRotation).toStrictEqual(0.5);
50
55
  expect(copy.sampler).not.toBe(source.sampler);
@@ -56,34 +61,60 @@ describe('cloneTexture', () => {
56
61
  copy.uvOffset.x = 0.9;
57
62
  expect(source.uvOffset.x).toStrictEqual(0.25);
58
63
  });
64
+
65
+ it('preserves a render-target descriptor while cloning sampling state', () => {
66
+ const target = {
67
+ colorSpace: 'linear' as const,
68
+ height: 32,
69
+ kind: RenderTextureBackingKind,
70
+ width: 64,
71
+ };
72
+ const source = createTexture({ storage: { dimension: '2d', image: null, target } });
73
+
74
+ const copy = cloneTexture(source);
75
+
76
+ expect(copy.storage.target).toBe(target);
77
+ expect(copy.storage).not.toBe(source.storage);
78
+ expect(copy.sampler).not.toBe(source.sampler);
79
+ });
59
80
  });
60
81
 
61
82
  describe('copyTexture', () => {
62
83
  it('writes every field from source into a distinct out, preserving out entity identities', () => {
63
- const source = createTexture({ colorSpace: 'linear', image: fakeImage, uvRotation: 1 });
84
+ const source = createTexture({
85
+ colorSpace: 'linear',
86
+ storage: { dimension: '2d', image: fakeImage },
87
+ uvRotation: 1,
88
+ });
64
89
  source.uvScale.x = 4;
65
90
  const out = createTexture();
66
91
  const outSampler = out.sampler;
67
92
  const outOffset = out.uvOffset;
93
+ const outStorage = out.storage;
68
94
 
69
95
  copyTexture(out, source);
70
96
 
71
- expect(out.image).toBe(fakeImage);
97
+ expect(out.storage.image).toBe(fakeImage);
72
98
  expect(out.colorSpace).toStrictEqual('linear');
73
99
  expect(out.uvRotation).toStrictEqual(1);
74
100
  expect(out.uvScale.x).toStrictEqual(4);
75
101
  expect(out.sampler).toBe(outSampler);
102
+ expect(out.storage).toBe(outStorage);
76
103
  expect(out.uvOffset).toBe(outOffset);
77
104
  });
78
105
 
79
106
  it('is safe when out aliases source', () => {
80
- const source = createTexture({ colorSpace: 'linear', image: fakeImage, uvRotation: 2 });
107
+ const source = createTexture({
108
+ colorSpace: 'linear',
109
+ storage: { dimension: '2d', image: fakeImage },
110
+ uvRotation: 2,
111
+ });
81
112
  source.uvScale.x = 7;
82
113
 
83
114
  copyTexture(source, source);
84
115
 
85
116
  expect(source.colorSpace).toStrictEqual('linear');
86
- expect(source.image).toBe(fakeImage);
117
+ expect(source.storage.image).toBe(fakeImage);
87
118
  expect(source.uvRotation).toStrictEqual(2);
88
119
  expect(source.uvScale.x).toStrictEqual(7);
89
120
  });
@@ -93,8 +124,10 @@ describe('createTexture', () => {
93
124
  it('applies the default unbound, srgb, identity-transform state', () => {
94
125
  const texture = createTexture();
95
126
 
96
- expect(texture.image).toBeNull();
127
+ expect(texture.storage.image).toBeNull();
128
+ expect(texture.storage.dimension).toStrictEqual('2d');
97
129
  expect(texture.colorSpace).toStrictEqual('srgb');
130
+ expect(texture.version).toStrictEqual(0);
98
131
  expect(texture.uvRotation).toStrictEqual(0);
99
132
  expect(texture.uvOffset.x).toStrictEqual(0);
100
133
  expect(texture.uvOffset.y).toStrictEqual(0);
@@ -114,8 +147,8 @@ describe('createTexture', () => {
114
147
 
115
148
  describe('equalsTexture', () => {
116
149
  it('is true for textures with identical state and same image reference', () => {
117
- const a = createTexture({ image: fakeImage, colorSpace: 'linear' });
118
- const b = createTexture({ image: fakeImage, colorSpace: 'linear' });
150
+ const a = createTexture({ colorSpace: 'linear', storage: { dimension: '2d', image: fakeImage } });
151
+ const b = createTexture({ colorSpace: 'linear', storage: { dimension: '2d', image: fakeImage } });
119
152
 
120
153
  expect(equalsTexture(a, b)).toBe(true);
121
154
  expect(equalsTexture(a, a)).toBe(true);
@@ -123,8 +156,8 @@ describe('equalsTexture', () => {
123
156
 
124
157
  it('is false when the image reference differs', () => {
125
158
  const other = { width: 4, height: 4 } as ImageResource;
126
- const a = createTexture({ image: fakeImage });
127
- const b = createTexture({ image: other });
159
+ const a = createTexture({ storage: { dimension: '2d', image: fakeImage } });
160
+ const b = createTexture({ storage: { dimension: '2d', image: other } });
128
161
 
129
162
  expect(equalsTexture(a, b)).toBe(false);
130
163
  });
@@ -175,9 +208,29 @@ describe('equalsTexture', () => {
175
208
  });
176
209
  });
177
210
 
211
+ describe('getTextureBackingKind', () => {
212
+ it('reads the key from the active image or target backing', () => {
213
+ expect(getTextureBackingKind(createTexture())).toBeNull();
214
+ expect(getTextureBackingKind(createTexture({ storage: { dimension: '2d', image: fakeImage } }))).toBe(
215
+ ImageTextureBackingKind,
216
+ );
217
+ expect(
218
+ getTextureBackingKind(
219
+ createTexture({
220
+ storage: {
221
+ dimension: '2d',
222
+ image: null,
223
+ target: { height: 8, kind: RenderTextureBackingKind, width: 8 },
224
+ },
225
+ }),
226
+ ),
227
+ ).toBe(RenderTextureBackingKind);
228
+ });
229
+ });
230
+
178
231
  describe('getTextureHeight', () => {
179
232
  it('returns the image height when an image is bound', () => {
180
- const texture = createTexture({ image: fakeImage });
233
+ const texture = createTexture({ storage: { dimension: '2d', image: fakeImage } });
181
234
 
182
235
  expect(getTextureHeight(texture)).toStrictEqual(64);
183
236
  });
@@ -187,6 +240,18 @@ describe('getTextureHeight', () => {
187
240
 
188
241
  expect(getTextureHeight(texture)).toStrictEqual(-1);
189
242
  });
243
+
244
+ it('returns the render target height when there is no CPU image', () => {
245
+ const texture = createTexture({
246
+ storage: {
247
+ dimension: '2d',
248
+ image: null,
249
+ target: { height: 48, kind: RenderTextureBackingKind, width: 96 },
250
+ },
251
+ });
252
+
253
+ expect(getTextureHeight(texture)).toStrictEqual(48);
254
+ });
190
255
  });
191
256
 
192
257
  describe('getTextureInverseUvMatrix', () => {
@@ -320,7 +385,7 @@ describe('getTextureUvMatrix', () => {
320
385
 
321
386
  describe('getTextureWidth', () => {
322
387
  it('returns the image width when an image is bound', () => {
323
- const texture = createTexture({ image: fakeImage });
388
+ const texture = createTexture({ storage: { dimension: '2d', image: fakeImage } });
324
389
 
325
390
  expect(getTextureWidth(texture)).toStrictEqual(32);
326
391
  });
@@ -330,6 +395,36 @@ describe('getTextureWidth', () => {
330
395
 
331
396
  expect(getTextureWidth(texture)).toStrictEqual(-1);
332
397
  });
398
+
399
+ it('returns the render target width when there is no CPU image', () => {
400
+ const texture = createTexture({
401
+ storage: {
402
+ dimension: '2d',
403
+ image: null,
404
+ target: { height: 48, kind: RenderTextureBackingKind, width: 96 },
405
+ },
406
+ });
407
+
408
+ expect(getTextureWidth(texture)).toStrictEqual(96);
409
+ });
410
+ });
411
+
412
+ describe('hasTextureBacking', () => {
413
+ it('detects image and target backings without duplicating their discriminant', () => {
414
+ expect(hasTextureBacking(createTexture())).toBe(false);
415
+ expect(hasTextureBacking(createTexture({ storage: { dimension: '2d', image: fakeImage } }))).toBe(true);
416
+ expect(
417
+ hasTextureBacking(
418
+ createTexture({
419
+ storage: {
420
+ dimension: '2d',
421
+ image: null,
422
+ target: { height: 8, kind: RenderTextureBackingKind, width: 8 },
423
+ },
424
+ }),
425
+ ),
426
+ ).toBe(true);
427
+ });
333
428
  });
334
429
 
335
430
  describe('hasTextureUvTransform', () => {
@@ -378,14 +473,14 @@ describe('isTextureReady', () => {
378
473
 
379
474
  expect(isTextureReady(texture)).toBe(false);
380
475
 
381
- texture.image = fakeImage;
476
+ texture.storage.image = fakeImage;
382
477
  expect(isTextureReady(texture)).toBe(true);
383
478
  });
384
479
  });
385
480
 
386
481
  describe('resetTextureUvTransform', () => {
387
482
  it('restores the identity transform while leaving image, color space, and sampler untouched', () => {
388
- const texture = createTexture({ colorSpace: 'linear', image: fakeImage });
483
+ const texture = createTexture({ colorSpace: 'linear', storage: { dimension: '2d', image: fakeImage } });
389
484
  const sampler = texture.sampler;
390
485
  setTextureUvOffset(texture, 0.4, 0.6);
391
486
  setTextureUvRotation(texture, Math.PI);
@@ -399,7 +494,7 @@ describe('resetTextureUvTransform', () => {
399
494
  expect(texture.uvScale.x).toStrictEqual(1);
400
495
  expect(texture.uvScale.y).toStrictEqual(1);
401
496
  expect(texture.colorSpace).toStrictEqual('linear');
402
- expect(texture.image).toBe(fakeImage);
497
+ expect(texture.storage.image).toBe(fakeImage);
403
498
  expect(texture.sampler).toBe(sampler);
404
499
  });
405
500
  });
@@ -418,14 +513,39 @@ describe('setTextureFlip', () => {
418
513
  });
419
514
 
420
515
  describe('setTextureImage', () => {
421
- it('binds and clears the image in place', () => {
516
+ it('binds and clears the image in place and advances the u32 version', () => {
422
517
  const texture = createTexture();
423
518
 
424
519
  setTextureImage(texture, fakeImage);
425
- expect(texture.image).toBe(fakeImage);
520
+ expect(texture.storage.image).toBe(fakeImage);
521
+ expect(texture.version).toStrictEqual(1);
522
+
523
+ setTextureImage(texture, fakeImage);
524
+ expect(texture.version).toStrictEqual(1);
426
525
 
427
526
  setTextureImage(texture, null);
428
- expect(texture.image).toBeNull();
527
+ expect(texture.storage.image).toBeNull();
528
+ expect(texture.version).toStrictEqual(2);
529
+ });
530
+ });
531
+
532
+ describe('setTextureUvFromPixelRect', () => {
533
+ it('normalizes a pixel rectangle against the texture dimensions', () => {
534
+ const texture = createTexture({
535
+ storage: { dimension: '2d', image: { height: 100, width: 200 } as ImageResource },
536
+ });
537
+ setTextureUvFromPixelRect(texture, 20, 10, 50, 25);
538
+ expect(texture.uvOffset.x).toBeCloseTo(0.1);
539
+ expect(texture.uvOffset.y).toBeCloseTo(0.1);
540
+ expect(texture.uvScale.x).toBeCloseTo(0.25);
541
+ expect(texture.uvScale.y).toBeCloseTo(0.25);
542
+ });
543
+
544
+ it('sets an empty window when the texture has no dimensions', () => {
545
+ const texture = createTexture();
546
+ setTextureUvFromPixelRect(texture, 20, 10, 50, 25);
547
+ expect(texture.uvOffset).toMatchObject({ x: 0, y: 0 });
548
+ expect(texture.uvScale).toMatchObject({ x: 0, y: 0 });
429
549
  });
430
550
  });
431
551
 
@@ -1,5 +1,5 @@
1
- import { createMatrix3, createVector2 } from '@flighthq/geometry';
2
- import type { VideoResource } from '@flighthq/types';
1
+ import { createMatrix3, createVector2 } from '@flighthq/geometry/contract';
2
+ import type { ImageResource, VideoResource } from '@flighthq/types/contract';
3
3
 
4
4
  import {
5
5
  advanceVideoTexture,
@@ -15,7 +15,7 @@ import {
15
15
  setVideoTextureSource,
16
16
  } from './videoTexture';
17
17
 
18
- // A minimal element carrier: only the fields the VideoTexture accessors read.
18
+ // A minimal borrowed host element: only the fields the video-backed Texture accessors read.
19
19
  function makeVideoResource(readyState = 4, videoWidth = 320, videoHeight = 240): VideoResource {
20
20
  return {
21
21
  element: { readyState, videoWidth, videoHeight } as unknown as HTMLVideoElement,
@@ -23,26 +23,27 @@ function makeVideoResource(readyState = 4, videoWidth = 320, videoHeight = 240):
23
23
  }
24
24
 
25
25
  describe('advanceVideoTexture', () => {
26
- it('bumps frameId and returns the new value', () => {
26
+ it('bumps the backing and Texture versions and returns the new value', () => {
27
27
  const vt = createVideoTexture(makeVideoResource());
28
- expect(vt.frameId).toBe(-1);
28
+ expect(vt.version).toBe(0xffffffff);
29
29
  expect(advanceVideoTexture(vt)).toBe(0);
30
30
  expect(advanceVideoTexture(vt)).toBe(1);
31
- expect(vt.frameId).toBe(1);
31
+ expect(vt.version).toBe(1);
32
+ expect(vt.storage.image?.version).toBe(1);
32
33
  });
33
34
  });
34
35
 
35
36
  describe('cloneVideoTexture', () => {
36
- it('shares the source but deep-clones sampler and uv vectors and resets frameId', () => {
37
+ it('shares the backing but deep-clones sampler and uv vectors', () => {
37
38
  const source = makeVideoResource();
38
39
  const vt = createVideoTexture(source, { uvOffset: createVector2(2, 3) });
39
40
  advanceVideoTexture(vt);
40
41
  const clone = cloneVideoTexture(vt);
41
- expect(clone.source).toBe(source);
42
+ expect(clone.storage.image).toBe(vt.storage.image);
42
43
  expect(clone.sampler).not.toBe(vt.sampler);
43
44
  expect(clone.uvOffset).not.toBe(vt.uvOffset);
44
45
  expect(clone.uvOffset.x).toBe(2);
45
- expect(clone.frameId).toBe(-1);
46
+ expect(clone.version).toBe(0);
46
47
  });
47
48
  });
48
49
 
@@ -53,8 +54,8 @@ describe('copyVideoTexture', () => {
53
54
  const b = createVideoTexture(makeVideoResource());
54
55
  copyVideoTexture(b, a);
55
56
  expect(b.colorSpace).toBe('linear');
56
- expect(b.frameId).toBe(0);
57
- expect(b.source).toBe(a.source);
57
+ expect(b.version).toBe(0);
58
+ expect(b.storage.image).toBe(a.storage.image);
58
59
  expect(b.uvScale.x).toBe(4);
59
60
  copyVideoTexture(a, a);
60
61
  expect(a.uvScale.x).toBe(4);
@@ -62,12 +63,12 @@ describe('copyVideoTexture', () => {
62
63
  });
63
64
 
64
65
  describe('createVideoTexture', () => {
65
- it('defaults to srgb, identity uv-transform, and frameId -1', () => {
66
+ it('returns a universal srgb Texture with a borrowed host-image backing', () => {
66
67
  const source = makeVideoResource();
67
68
  const vt = createVideoTexture(source);
68
69
  expect(vt.colorSpace).toBe('srgb');
69
- expect(vt.source).toBe(source);
70
- expect(vt.frameId).toBe(-1);
70
+ expect((vt.storage.image as ImageResource).source).toBe(source.element);
71
+ expect(vt.version).toBe(0xffffffff);
71
72
  expect(vt.uvOffset.x).toBe(0);
72
73
  expect(vt.uvScale.x).toBe(1);
73
74
  expect(vt.uvRotation).toBe(0);
@@ -135,23 +136,24 @@ describe('isVideoTextureFrameReady', () => {
135
136
  });
136
137
 
137
138
  describe('resetVideoTextureFrame', () => {
138
- it('sets frameId back to -1', () => {
139
+ it('sets the shared revision to the u32 pre-first-frame sentinel', () => {
139
140
  const vt = createVideoTexture(makeVideoResource());
140
141
  advanceVideoTexture(vt);
141
142
  advanceVideoTexture(vt);
142
143
  resetVideoTextureFrame(vt);
143
- expect(vt.frameId).toBe(-1);
144
+ expect(vt.version).toBe(0xffffffff);
145
+ expect(vt.storage.image?.version).toBe(0xffffffff);
144
146
  });
145
147
  });
146
148
 
147
149
  describe('setVideoTextureSource', () => {
148
- it('swaps the source and resets frameId', () => {
150
+ it('swaps the borrowed host handle and resets the version', () => {
149
151
  const vt = createVideoTexture(makeVideoResource());
150
152
  advanceVideoTexture(vt);
151
153
  const next = makeVideoResource(4, 640, 480);
152
154
  setVideoTextureSource(vt, next);
153
- expect(vt.source).toBe(next);
154
- expect(vt.frameId).toBe(-1);
155
+ expect((vt.storage.image as ImageResource).source).toBe(next.element);
156
+ expect(vt.version).toBe(0xffffffff);
155
157
  expect(getVideoTextureWidth(vt)).toBe(640);
156
158
  });
157
159
  });