@flighthq/texture-formats 0.2.1-next.451.d9a4a4e → 0.2.1-next.530.a223ca9

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.
@@ -5,6 +5,7 @@ export interface ByteReader {
5
5
  export declare function createByteReader(bytes: Readonly<Uint8Array>, offset?: number): ByteReader;
6
6
  export declare function hasByteReaderBytes(reader: Readonly<ByteReader>, count: number): boolean;
7
7
  export declare function readByteReaderU16(reader: ByteReader): number;
8
+ export declare function readByteReaderU24(reader: ByteReader): number;
8
9
  export declare function readByteReaderU24BigEndian(reader: ByteReader): number;
9
10
  export declare function readByteReaderU32(reader: ByteReader): number;
10
11
  export declare function readByteReaderU32BigEndian(reader: ByteReader): number;
@@ -1 +1 @@
1
- {"version":3,"file":"byteReader.d.ts","sourceRoot":"","sources":["../src/byteReader.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,SAAI,GAAG,UAAU,CAEpF;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,QAAQ,CAAC,UAAU,CAAC,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAEvF;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAI5D;AAED,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAMrE;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAI5D;AAED,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAIrE;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAK5D;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAI3D;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAEtE"}
1
+ {"version":3,"file":"byteReader.d.ts","sourceRoot":"","sources":["../src/byteReader.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,SAAI,GAAG,UAAU,CAEpF;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,QAAQ,CAAC,UAAU,CAAC,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAEvF;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAI5D;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAM5D;AAED,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAMrE;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAI5D;AAED,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAIrE;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAK5D;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAI3D;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAEtE"}
@@ -17,6 +17,13 @@ export function readByteReaderU16(reader) {
17
17
  reader.offset += 2;
18
18
  return value;
19
19
  }
20
+ export function readByteReaderU24(reader) {
21
+ const view = reader.view;
22
+ const offset = reader.offset;
23
+ const value = view.getUint8(offset) | (view.getUint8(offset + 1) << 8) | (view.getUint8(offset + 2) << 16);
24
+ reader.offset += 3;
25
+ return value;
26
+ }
20
27
  export function readByteReaderU24BigEndian(reader) {
21
28
  const view = reader.view;
22
29
  const offset = reader.offset;
@@ -1 +1 @@
1
- {"version":3,"file":"byteReader.js","sourceRoot":"","sources":["../src/byteReader.ts"],"names":[],"mappings":"AAAA,kGAAkG;AAClG,oGAAoG;AACpG,sGAAsG;AACtG,mGAAmG;AACnG,uGAAuG;AACvG,wGAAwG;AACxG,wGAAwG;AACxG,sEAAsE;AAOtE,MAAM,UAAU,gBAAgB,CAAC,KAA2B,EAAE,MAAM,GAAG,CAAC;IACtE,OAAO,EAAE,IAAI,EAAE,IAAI,QAAQ,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;AAC1F,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,MAA4B,EAAE,KAAa;IAC5E,OAAO,KAAK,IAAI,CAAC,IAAI,MAAM,CAAC,MAAM,GAAG,KAAK,IAAI,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC;AACvE,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,MAAkB;IAClD,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IACzD,MAAM,CAAC,MAAM,IAAI,CAAC,CAAC;IACnB,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,0BAA0B,CAAC,MAAkB;IAC3D,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC;IACzB,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;IAC7B,MAAM,KAAK,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAC3G,MAAM,CAAC,MAAM,IAAI,CAAC,CAAC;IACnB,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,MAAkB;IAClD,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IACzD,MAAM,CAAC,MAAM,IAAI,CAAC,CAAC;IACnB,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,0BAA0B,CAAC,MAAkB;IAC3D,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAC1D,MAAM,CAAC,MAAM,IAAI,CAAC,CAAC;IACnB,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,MAAkB;IAClD,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IACvD,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,IAAI,CAAC,CAAC;IAC5D,MAAM,CAAC,MAAM,IAAI,CAAC,CAAC;IACnB,OAAO,IAAI,GAAG,aAAa,GAAG,GAAG,CAAC;AACpC,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,MAAkB;IACjD,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAClD,MAAM,CAAC,MAAM,IAAI,CAAC,CAAC;IACnB,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,MAAkB,EAAE,KAAa;IAC9D,MAAM,CAAC,MAAM,IAAI,KAAK,CAAC;AACzB,CAAC"}
1
+ {"version":3,"file":"byteReader.js","sourceRoot":"","sources":["../src/byteReader.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -4,4 +4,5 @@ export * from './parseBasis';
4
4
  export * from './parseDds';
5
5
  export * from './parseKtx2';
6
6
  export * from './selectTextureContainer';
7
+ export * from './textureLevelLayout';
7
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,0BAA0B,CAAC;AACzC,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,0BAA0B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,0BAA0B,CAAC;AACzC,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,0BAA0B,CAAC;AACzC,cAAc,sBAAsB,CAAC"}
package/dist/index.js CHANGED
@@ -4,4 +4,5 @@ export * from './parseBasis';
4
4
  export * from './parseDds';
5
5
  export * from './parseKtx2';
6
6
  export * from './selectTextureContainer';
7
+ export * from './textureLevelLayout';
7
8
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,0BAA0B,CAAC;AACzC,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,0BAA0B,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,0BAA0B,CAAC;AACzC,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,0BAA0B,CAAC;AACzC,cAAc,sBAAsB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"parseBasis.d.ts","sourceRoot":"","sources":["../src/parseBasis.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAwBxD,wBAAgB,UAAU,CAAC,KAAK,EAAE,QAAQ,CAAC,UAAU,CAAC,GAAG,gBAAgB,GAAG,IAAI,CAqD/E"}
1
+ {"version":3,"file":"parseBasis.d.ts","sourceRoot":"","sources":["../src/parseBasis.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AA0BxD,wBAAgB,UAAU,CAAC,KAAK,EAAE,QAAQ,CAAC,UAAU,CAAC,GAAG,gBAAgB,GAAG,IAAI,CAuD/E"}
@@ -1,4 +1,4 @@
1
- import { createByteReader, hasByteReaderBytes, readByteReaderU16, readByteReaderU32, readByteReaderU8, } from './byteReader';
1
+ import { createByteReader, hasByteReaderBytes, readByteReaderU16, readByteReaderU24, readByteReaderU32, readByteReaderU8, } from './byteReader';
2
2
  // Parses a Basis Universal `.basis` container into a `TextureContainer`, or returns `null` if the bytes
3
3
  // are not a `.basis` file, are truncated, or carry an unknown texture format.
4
4
  //
@@ -9,21 +9,25 @@ import { createByteReader, hasByteReaderBytes, readByteReaderU16, readByteReader
9
9
  // compressed — transcoding ETC1S/UASTC to a GPU format is the caller's / `flight-rs`'s job.
10
10
  //
11
11
  // The ETC1S codebook/selector/table sections and per-slice CRCs are not read (they are inputs to the
12
- // transcoder, not to locating a slice). Basis cube/video texture types are not distinguished — every
13
- // image is reported as an array layer (`faces` = 1).
12
+ // transcoder, not to locating a slice). 2D/array images become layers, cubemap-array images become six
13
+ // faces per layer, and volume images become depth slices. Video frames are rejected because the common
14
+ // container descriptor cannot preserve their temporal/conditional-replenishment semantics truthfully.
14
15
  export function parseBasis(bytes) {
15
16
  if (!hasBasisSignature(bytes))
16
17
  return null;
17
18
  if (bytes.byteLength < basisHeaderMinSize)
18
19
  return null;
19
20
  const header = createByteReader(bytes, basisTotalSlicesOffset);
20
- const totalSlices = readByteReaderU16(header); // m_total_slices (offset 12)
21
- const totalImages = readByteReaderU16(header); // m_total_images (offset 14)
22
- const format = basisTexFormat[bytes[basisTexFormatOffset]]; // m_tex_format (offset 16)
21
+ const totalSlices = readByteReaderU24(header); // m_total_slices (offset 14)
22
+ const totalImages = readByteReaderU24(header); // m_total_images (offset 17)
23
+ const format = basisTexFormat[bytes[basisTexFormatOffset]]; // m_tex_format (offset 20)
23
24
  if (format === undefined)
24
25
  return null;
25
26
  if (totalSlices === 0)
26
27
  return null;
28
+ const shape = getBasisTextureShape(bytes[basisTexTypeOffset], totalImages);
29
+ if (shape === null)
30
+ return null;
27
31
  const sliceDescReader = createByteReader(bytes, basisSliceDescOffsetField);
28
32
  const sliceDescOffset = readByteReaderU32(sliceDescReader);
29
33
  const table = createByteReader(bytes, sliceDescOffset);
@@ -34,7 +38,7 @@ export function parseBasis(bytes) {
34
38
  let baseHeight = 0;
35
39
  let maxLevel = 0;
36
40
  for (let slice = 0; slice < totalSlices; slice += 1) {
37
- const imageIndex = readByteReaderU16(table);
41
+ const imageIndex = readByteReaderU24(table);
38
42
  const levelIndex = readByteReaderU8(table);
39
43
  readByteReaderU8(table); // m_flags
40
44
  const width = readByteReaderU16(table);
@@ -55,26 +59,50 @@ export function parseBasis(bytes) {
55
59
  levels.push({ byteLength, byteOffset, height, width });
56
60
  }
57
61
  return {
58
- depth: 1,
59
- faces: 1,
62
+ depth: shape.depth,
63
+ faces: shape.faces,
60
64
  format,
61
65
  height: baseHeight || (levels[0]?.height ?? 0),
62
- layers: Math.max(1, totalImages),
66
+ layers: shape.layers,
63
67
  levels,
64
68
  mipLevels: Math.max(1, maxLevel),
65
69
  supercompression: 'None',
66
70
  width: baseWidth || (levels[0]?.width ?? 0),
67
71
  };
68
72
  }
73
+ function getBasisTextureShape(textureType, totalImages) {
74
+ const images = Math.max(1, totalImages);
75
+ switch (textureType) {
76
+ case basisTextureType2d:
77
+ case basisTextureType2dArray:
78
+ return { depth: 1, faces: 1, layers: images };
79
+ case basisTextureTypeCubemapArray:
80
+ if (totalImages === 0 || totalImages % 6 !== 0)
81
+ return null;
82
+ return { depth: 1, faces: 6, layers: totalImages / 6 };
83
+ case basisTextureTypeVideoFrames:
84
+ return null;
85
+ case basisTextureTypeVolume:
86
+ return { depth: images, faces: 1, layers: 1 };
87
+ default:
88
+ return null;
89
+ }
90
+ }
69
91
  function hasBasisSignature(bytes) {
70
92
  // m_sig = 0x4273, stored little-endian: byte 0 = 0x73 ('s'), byte 1 = 0x42 ('B').
71
93
  return bytes.byteLength >= 2 && bytes[0] === 0x73 && bytes[1] === 0x42;
72
94
  }
73
- const basisTotalSlicesOffset = 12;
74
- const basisTexFormatOffset = 16;
75
- const basisSliceDescOffsetField = 61;
76
- const basisHeaderMinSize = 65; // through m_slice_desc_file_ofs (offset 61, 4 bytes)
77
- const basisSliceDescSize = 22;
95
+ const basisTotalSlicesOffset = 14;
96
+ const basisTexFormatOffset = 20;
97
+ const basisTexTypeOffset = 23;
98
+ const basisSliceDescOffsetField = 63;
99
+ const basisHeaderMinSize = 67; // through m_slice_desc_file_ofs (offset 63, 4 bytes)
100
+ const basisSliceDescSize = 23;
101
+ const basisTextureType2d = 0;
102
+ const basisTextureType2dArray = 1;
103
+ const basisTextureTypeCubemapArray = 2;
104
+ const basisTextureTypeVideoFrames = 3;
105
+ const basisTextureTypeVolume = 4;
78
106
  const basisTexFormat = {
79
107
  0: 'etc1s',
80
108
  1: 'uastc',
@@ -1 +1 @@
1
- {"version":3,"file":"parseBasis.js","sourceRoot":"","sources":["../src/parseBasis.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"parseBasis.js","sourceRoot":"","sources":["../src/parseBasis.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flighthq/texture-formats",
3
- "version": "0.2.1-next.451.d9a4a4e",
3
+ "version": "0.2.1-next.530.a223ca9",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/flighthq/flight.git",
@@ -32,11 +32,11 @@
32
32
  "clean:dist": "tsx ../../scripts/clean-package-dist.ts"
33
33
  },
34
34
  "dependencies": {
35
- "@flighthq/types": "0.2.1-next.451.d9a4a4e"
35
+ "@flighthq/types": "0.2.1-next.530.a223ca9"
36
36
  },
37
37
  "devDependencies": {
38
38
  "typescript": "^5.3.0"
39
39
  },
40
- "description": "Tree-shakable GPU texture-container parsers (KTX2 / DDS / Basis) — identify format and locate per-level byte ranges, no transcode",
40
+ "description": "Tree-shakable GPU texture-container parsers (KTX2 / DDS / Basis / ATF), peer selection, and level layout — no transcode",
41
41
  "sideEffects": false
42
42
  }
@@ -4,6 +4,7 @@ import {
4
4
  createByteReader,
5
5
  hasByteReaderBytes,
6
6
  readByteReaderU16,
7
+ readByteReaderU24,
7
8
  readByteReaderU24BigEndian,
8
9
  readByteReaderU32,
9
10
  readByteReaderU32BigEndian,
@@ -43,6 +44,19 @@ describe('readByteReaderU16', () => {
43
44
  });
44
45
  });
45
46
 
47
+ describe('readByteReaderU24', () => {
48
+ it('reads a 3-byte little-endian value and advances 3', () => {
49
+ const reader = createByteReader(new Uint8Array([0x56, 0x34, 0x12]));
50
+ expect(readByteReaderU24(reader)).toBe(0x123456);
51
+ expect(reader.offset).toBe(3);
52
+ });
53
+
54
+ it('reads the full 24-bit range without sign issues', () => {
55
+ const reader = createByteReader(new Uint8Array([0xff, 0xff, 0xff]));
56
+ expect(readByteReaderU24(reader)).toBe(0xffffff);
57
+ });
58
+ });
59
+
46
60
  describe('readByteReaderU24BigEndian', () => {
47
61
  it('reads a 3-byte big-endian value and advances 3', () => {
48
62
  const reader = createByteReader(new Uint8Array([0x12, 0x34, 0x56]));
@@ -14,14 +14,21 @@ interface BasisSlice {
14
14
  interface BasisOptions {
15
15
  totalImages?: number;
16
16
  texFormat?: number;
17
+ texType?: number;
17
18
  slices: readonly BasisSlice[];
18
19
  }
19
20
 
20
21
  const basisHeaderSize = 77;
21
- const basisSliceDescSize = 22;
22
+ const basisSliceDescSize = 23;
23
+
24
+ function setU24(dv: DataView, offset: number, value: number): void {
25
+ dv.setUint8(offset, value & 0xff);
26
+ dv.setUint8(offset + 1, (value >>> 8) & 0xff);
27
+ dv.setUint8(offset + 2, (value >>> 16) & 0xff);
28
+ }
22
29
 
23
30
  function buildBasis(opts: BasisOptions): Uint8Array {
24
- const { totalImages = 1, texFormat = 0, slices } = opts;
31
+ const { totalImages = 1, texFormat = 0, texType = 0, slices } = opts;
25
32
  const sliceTableOffset = basisHeaderSize;
26
33
  const dataStart = sliceTableOffset + slices.length * basisSliceDescSize;
27
34
  const total = slices.reduce((max, s) => Math.max(max, s.fileOfs + s.fileSize), dataStart);
@@ -29,18 +36,19 @@ function buildBasis(opts: BasisOptions): Uint8Array {
29
36
  const dv = new DataView(bytes.buffer);
30
37
  bytes[0] = 0x73; // 's'
31
38
  bytes[1] = 0x42; // 'B'
32
- dv.setUint16(12, slices.length, true); // m_total_slices
33
- dv.setUint16(14, totalImages, true); // m_total_images
34
- bytes[16] = texFormat; // m_tex_format
35
- dv.setUint32(61, sliceTableOffset, true); // m_slice_desc_file_ofs
39
+ setU24(dv, 14, slices.length); // m_total_slices
40
+ setU24(dv, 17, totalImages); // m_total_images
41
+ bytes[20] = texFormat; // m_tex_format
42
+ bytes[23] = texType; // m_tex_type
43
+ dv.setUint32(63, sliceTableOffset, true); // m_slice_desc_file_ofs
36
44
  let off = sliceTableOffset;
37
45
  for (const slice of slices) {
38
- dv.setUint16(off + 0, slice.imageIndex, true);
39
- bytes[off + 2] = slice.levelIndex;
40
- dv.setUint16(off + 4, slice.width, true);
41
- dv.setUint16(off + 6, slice.height, true);
42
- dv.setUint32(off + 12, slice.fileOfs, true);
43
- dv.setUint32(off + 16, slice.fileSize, true);
46
+ setU24(dv, off, slice.imageIndex);
47
+ bytes[off + 3] = slice.levelIndex;
48
+ dv.setUint16(off + 5, slice.width, true);
49
+ dv.setUint16(off + 7, slice.height, true);
50
+ dv.setUint32(off + 13, slice.fileOfs, true);
51
+ dv.setUint32(off + 17, slice.fileSize, true);
44
52
  off += basisSliceDescSize;
45
53
  }
46
54
  return bytes;
@@ -49,7 +57,7 @@ function buildBasis(opts: BasisOptions): Uint8Array {
49
57
  describe('parseBasis', () => {
50
58
  it('parses a single-slice ETC1S image', () => {
51
59
  const container = parseBasis(
52
- buildBasis({ slices: [{ fileOfs: 99, fileSize: 8, height: 4, imageIndex: 0, levelIndex: 0, width: 4 }] }),
60
+ buildBasis({ slices: [{ fileOfs: 100, fileSize: 8, height: 4, imageIndex: 0, levelIndex: 0, width: 4 }] }),
53
61
  );
54
62
  expect(container).not.toBeNull();
55
63
  expect(container!.format).toBe('etc1s');
@@ -59,28 +67,28 @@ describe('parseBasis', () => {
59
67
  expect(container!.layers).toBe(1);
60
68
  expect(container!.faces).toBe(1);
61
69
  expect(container!.supercompression).toBe('None');
62
- expect(container!.levels).toEqual([{ byteLength: 8, byteOffset: 99, height: 4, width: 4 }]);
70
+ expect(container!.levels).toEqual([{ byteLength: 8, byteOffset: 100, height: 4, width: 4 }]);
63
71
  });
64
72
 
65
73
  it('reports a mip chain from the slice level indices', () => {
66
74
  const container = parseBasis(
67
75
  buildBasis({
68
76
  slices: [
69
- { fileOfs: 121, fileSize: 8, height: 4, imageIndex: 0, levelIndex: 0, width: 4 },
70
- { fileOfs: 129, fileSize: 4, height: 2, imageIndex: 0, levelIndex: 1, width: 2 },
77
+ { fileOfs: 123, fileSize: 8, height: 4, imageIndex: 0, levelIndex: 0, width: 4 },
78
+ { fileOfs: 131, fileSize: 4, height: 2, imageIndex: 0, levelIndex: 1, width: 2 },
71
79
  ],
72
80
  }),
73
81
  );
74
82
  expect(container).not.toBeNull();
75
83
  expect(container!.mipLevels).toBe(2);
76
84
  expect(container!.levels.map((l) => l.width)).toEqual([4, 2]);
77
- expect(container!.levels.map((l) => l.byteOffset)).toEqual([121, 129]);
85
+ expect(container!.levels.map((l) => l.byteOffset)).toEqual([123, 131]);
78
86
  });
79
87
 
80
88
  it('maps the UASTC texture format', () => {
81
89
  const container = parseBasis(
82
90
  buildBasis({
83
- slices: [{ fileOfs: 99, fileSize: 16, height: 4, imageIndex: 0, levelIndex: 0, width: 4 }],
91
+ slices: [{ fileOfs: 100, fileSize: 16, height: 4, imageIndex: 0, levelIndex: 0, width: 4 }],
84
92
  texFormat: 1,
85
93
  }),
86
94
  );
@@ -91,8 +99,8 @@ describe('parseBasis', () => {
91
99
  const container = parseBasis(
92
100
  buildBasis({
93
101
  slices: [
94
- { fileOfs: 121, fileSize: 8, height: 4, imageIndex: 0, levelIndex: 0, width: 4 },
95
- { fileOfs: 129, fileSize: 8, height: 4, imageIndex: 1, levelIndex: 0, width: 4 },
102
+ { fileOfs: 123, fileSize: 8, height: 4, imageIndex: 0, levelIndex: 0, width: 4 },
103
+ { fileOfs: 131, fileSize: 8, height: 4, imageIndex: 1, levelIndex: 0, width: 4 },
96
104
  ],
97
105
  totalImages: 2,
98
106
  }),
@@ -101,13 +109,54 @@ describe('parseBasis', () => {
101
109
  expect(container!.levels).toHaveLength(2);
102
110
  });
103
111
 
112
+ it('reads 24-bit image counts and slice image indices', () => {
113
+ const container = parseBasis(
114
+ buildBasis({
115
+ slices: [{ fileOfs: 100, fileSize: 8, height: 4, imageIndex: 70_000, levelIndex: 0, width: 4 }],
116
+ totalImages: 70_001,
117
+ }),
118
+ );
119
+ expect(container).not.toBeNull();
120
+ expect(container!.layers).toBe(70_001);
121
+ expect(container!.levels).toEqual([{ byteLength: 8, byteOffset: 100, height: 4, width: 4 }]);
122
+ });
123
+
124
+ it('preserves cubemap-array faces and layers', () => {
125
+ const slices: BasisSlice[] = [];
126
+ for (let imageIndex = 0; imageIndex < 12; imageIndex++) {
127
+ slices.push({ fileOfs: 353 + imageIndex * 8, fileSize: 8, height: 4, imageIndex, levelIndex: 0, width: 4 });
128
+ }
129
+ const container = parseBasis(buildBasis({ slices, texType: 2, totalImages: 12 }));
130
+ expect(container).not.toBeNull();
131
+ expect(container!.faces).toBe(6);
132
+ expect(container!.layers).toBe(2);
133
+ expect(container!.depth).toBe(1);
134
+ expect(container!.levels).toHaveLength(12);
135
+ });
136
+
137
+ it('preserves volume depth and rejects temporal or malformed cube shapes', () => {
138
+ const slices = [
139
+ { fileOfs: 146, fileSize: 8, height: 4, imageIndex: 0, levelIndex: 0, width: 4 },
140
+ { fileOfs: 154, fileSize: 8, height: 4, imageIndex: 1, levelIndex: 0, width: 4 },
141
+ { fileOfs: 162, fileSize: 8, height: 4, imageIndex: 2, levelIndex: 0, width: 4 },
142
+ ];
143
+ const volume = parseBasis(buildBasis({ slices, texType: 4, totalImages: 3 }));
144
+ expect(volume).not.toBeNull();
145
+ expect(volume!.depth).toBe(3);
146
+ expect(volume!.faces).toBe(1);
147
+ expect(volume!.layers).toBe(1);
148
+
149
+ expect(parseBasis(buildBasis({ slices, texType: 3, totalImages: 3 }))).toBeNull();
150
+ expect(parseBasis(buildBasis({ slices, texType: 2, totalImages: 3 }))).toBeNull();
151
+ });
152
+
104
153
  it('returns null for a non-Basis, truncated, or unknown-format buffer', () => {
105
154
  expect(parseBasis(new Uint8Array([0x44, 0x44, 0x53, 0x20]))).toBeNull();
106
155
  expect(parseBasis(new Uint8Array([0x73, 0x42, 0, 0]))).toBeNull();
107
156
  expect(
108
157
  parseBasis(
109
158
  buildBasis({
110
- slices: [{ fileOfs: 99, fileSize: 8, height: 4, imageIndex: 0, levelIndex: 0, width: 4 }],
159
+ slices: [{ fileOfs: 100, fileSize: 8, height: 4, imageIndex: 0, levelIndex: 0, width: 4 }],
111
160
  texFormat: 5,
112
161
  }),
113
162
  ),
@@ -116,9 +165,9 @@ describe('parseBasis', () => {
116
165
 
117
166
  it('returns null when the slice table runs past the buffer', () => {
118
167
  const bytes = buildBasis({
119
- slices: [{ fileOfs: 99, fileSize: 8, height: 4, imageIndex: 0, levelIndex: 0, width: 4 }],
168
+ slices: [{ fileOfs: 100, fileSize: 8, height: 4, imageIndex: 0, levelIndex: 0, width: 4 }],
120
169
  });
121
- new DataView(bytes.buffer).setUint32(61, 1_000_000, true); // corrupt m_slice_desc_file_ofs
170
+ new DataView(bytes.buffer).setUint32(63, 1_000_000, true); // corrupt m_slice_desc_file_ofs
122
171
  expect(parseBasis(bytes)).toBeNull();
123
172
  });
124
173
  });
@@ -131,6 +131,25 @@ describe('parseDds', () => {
131
131
  expect(container!.levels[0].byteOffset).toBe(148); // data follows the 20-byte DX10 header
132
132
  });
133
133
 
134
+ it('lays out every DX10 array layer as an independent subresource chain', () => {
135
+ const container = parseDds(
136
+ buildDds({
137
+ dx10: { arraySize: 3, dxgiFormat: 98 },
138
+ fourCC: fourCC('DX10'),
139
+ height: 4,
140
+ pfFlags: 0x4,
141
+ width: 4,
142
+ }),
143
+ );
144
+ expect(container).not.toBeNull();
145
+ expect(container!.layers).toBe(3);
146
+ expect(container!.levels).toEqual([
147
+ { byteLength: 16, byteOffset: 148, height: 4, width: 4 },
148
+ { byteLength: 16, byteOffset: 164, height: 4, width: 4 },
149
+ { byteLength: 16, byteOffset: 180, height: 4, width: 4 },
150
+ ]);
151
+ });
152
+
134
153
  it('returns null for a non-DDS, truncated, unknown-format, or volume texture', () => {
135
154
  expect(parseDds(new Uint8Array([0xab, 0x4b, 0x54, 0x58]))).toBeNull();
136
155
  expect(parseDds(new Uint8Array([0x44, 0x44, 0x53, 0x20, 0, 0, 0, 0]))).toBeNull();
@@ -74,6 +74,25 @@ describe('parseKtx2', () => {
74
74
  expect(container!.levels.map((l) => l.byteOffset)).toEqual([104, 168, 232, 296, 360, 424]);
75
75
  });
76
76
 
77
+ it('splits an uncompressed array level into one range per layer', () => {
78
+ const container = parseKtx2(
79
+ buildKtx2({
80
+ layerCount: 3,
81
+ levels: [{ byteLength: 192, byteOffset: 104 }],
82
+ vkFormat: 37,
83
+ width: 4,
84
+ height: 4,
85
+ }),
86
+ );
87
+ expect(container).not.toBeNull();
88
+ expect(container!.layers).toBe(3);
89
+ expect(container!.levels).toEqual([
90
+ { byteLength: 64, byteOffset: 104, height: 4, width: 4 },
91
+ { byteLength: 64, byteOffset: 168, height: 4, width: 4 },
92
+ { byteLength: 64, byteOffset: 232, height: 4, width: 4 },
93
+ ]);
94
+ });
95
+
77
96
  it('reports each mip of a mip chain from the level index', () => {
78
97
  const container = parseKtx2(
79
98
  buildKtx2({
@@ -112,6 +131,27 @@ describe('parseKtx2', () => {
112
131
  expect(container!.format).toBe('uastc');
113
132
  });
114
133
 
134
+ it('preserves Zstd and ZLIB supercompressed levels as indivisible blobs', () => {
135
+ for (const [scheme, supercompression] of [
136
+ [2, 'Zstd'],
137
+ [3, 'ZLIB'],
138
+ ] as const) {
139
+ const container = parseKtx2(
140
+ buildKtx2({
141
+ layerCount: 2,
142
+ levels: [{ byteLength: 48, byteOffset: 104 }],
143
+ scheme,
144
+ vkFormat: 37,
145
+ width: 4,
146
+ height: 4,
147
+ }),
148
+ );
149
+ expect(container).not.toBeNull();
150
+ expect(container!.supercompression).toBe(supercompression);
151
+ expect(container!.levels).toEqual([{ byteLength: 48, byteOffset: 104, height: 4, width: 4 }]);
152
+ }
153
+ });
154
+
115
155
  it('returns null for a non-KTX2 buffer', () => {
116
156
  expect(parseKtx2(new Uint8Array([0x44, 0x44, 0x53, 0x20]))).toBeNull();
117
157
  });