formats.ts

src/core/formats.ts · raw · all examples · site home

Exports: ScalarTypeArrayFormatTypedArrayTypedArrayConstructorArrayOfFormatInfoArrayFormats

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
// Format names follow WebGPU vertex formats: a scalar type, optionally
// followed by "x" and a component count ("uint32", "uint32x2", "unorm8x4").
// Only 1, 2 or 4 components can live on the GPU; other counts are CPU-only.

export type ScalarType =
  | "uint8" | "sint8" | "unorm8" | "snorm8"
  | "uint16" | "sint16" | "float16"
  | "uint32" | "sint32" | "float32";

export type ArrayFormat = ScalarType | `${ScalarType}x${number}`;

export type TypedArray =
  | Uint8Array<ArrayBuffer> | Int8Array<ArrayBuffer>
  | Uint16Array<ArrayBuffer> | Int16Array<ArrayBuffer>
  | Uint32Array<ArrayBuffer> | Int32Array<ArrayBuffer>
  | Float32Array<ArrayBuffer>;

export type TypedArrayConstructor =
  | Uint8ArrayConstructor | Int8ArrayConstructor
  | Uint16ArrayConstructor | Int16ArrayConstructor
  | Uint32ArrayConstructor | Int32ArrayConstructor
  | Float32ArrayConstructor;

interface ScalarArrays {
  uint8: Uint8Array<ArrayBuffer>;
  sint8: Int8Array<ArrayBuffer>;
  unorm8: Uint8Array<ArrayBuffer>;
  snorm8: Int8Array<ArrayBuffer>;
  uint16: Uint16Array<ArrayBuffer>;
  sint16: Int16Array<ArrayBuffer>;
  float16: Uint16Array<ArrayBuffer>;
  uint32: Uint32Array<ArrayBuffer>;
  sint32: Int32Array<ArrayBuffer>;
  float32: Float32Array<ArrayBuffer>;
}

type ScalarOf<F extends string> = F extends `${infer S}x${number}` ? S : F;

// The CPU array type of a format, e.g. ArrayOf<"uint32x4"> = Uint32Array
export type ArrayOf<F extends ArrayFormat> = ScalarArrays[ScalarOf<F> & ScalarType];

// float16 is stored as raw half-float bits
const SCALARS: Record<ScalarType, { bytes: number; array: TypedArrayConstructor }> = {
  uint8: { bytes: 1, array: Uint8Array },
  sint8: { bytes: 1, array: Int8Array },
  unorm8: { bytes: 1, array: Uint8Array },
  snorm8: { bytes: 1, array: Int8Array },
  uint16: { bytes: 2, array: Uint16Array },
  sint16: { bytes: 2, array: Int16Array },
  float16: { bytes: 2, array: Uint16Array },
  uint32: { bytes: 4, array: Uint32Array },
  sint32: { bytes: 4, array: Int32Array },
  float32: { bytes: 4, array: Float32Array },
};

export interface FormatInfo {
  format: ArrayFormat;
  scalar: ScalarType;
  components: number;
  scalarBytes: number;
  // Bytes per element
  bytes: number;
  array: TypedArrayConstructor;
  gpu: boolean;
}

const cache = new Map<string, FormatInfo>();

export class ArrayFormats {
  static info(format: string): FormatInfo {
    let info = cache.get(format);
    if (!info) {
      const [scalar, count] = format.split("x") as [ScalarType, string | undefined];
      const spec = SCALARS[scalar];
      const components = count === undefined ? 1 : Number(count);
      if (!spec || !Number.isInteger(components) || components < 1) {
        throw new Error(`Invalid array format "${format}"`);
      }

      info = {
        format: format as ArrayFormat,
        scalar,
        components,
        scalarBytes: spec.bytes,
        bytes: spec.bytes * components,
        array: spec.array,
        gpu: components === 1 || components === 2 || components === 4,
      };
      cache.set(format, info);
    }
    return info;
  }

  // Format of `scalar` with `components` components ("uint32", "uint32x4")
  static make(scalar: ScalarType, components: number): ArrayFormat {
    return components === 1 ? scalar : `${scalar}x${components}`;
  }
}