gpuFormats.ts

src/gpu/gpuFormats.ts · raw · all examples · site home

Exports: GpuFormatInfogpuFormatInfo

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
import * as THREE from "three";
import { ArrayFormats, type ArrayFormat, type ScalarType } from "../core/formats";

// How a GPU format (1, 2 or 4 components) becomes a texture on each backend.
// Three derives the sampled type (f32 / u32 / i32) only from texture.type
// being UnsignedIntType or IntType, so all integer formats use those types,
// and the real storage format goes in texture.internalFormat per backend.
export interface GpuFormatInfo {
  format: ArrayFormat;
  components: number;
  scalar: ScalarType;
  kind: "uint" | "sint" | "float";
  webgpu: GPUTextureFormat;
  // WebGL2 internal format and texSubImage format / type (gl constant names)
  gl: string;
  glFormat: string;
  glType: string;
  threeFormat: THREE.PixelFormat;
  threeType: THREE.TextureDataType;
  // TSL type of one element
  node: string;
  // snorm8 is not renderable on either backend (WebGL2 EXT_render_snorm: 80%)
  renderable: boolean;
  // Compute shaders can write it as a storage texture. Three declares the
  // WGSL storage format from format / type, not internalFormat, so 8 and
  // 16-bit integers (typed UnsignedIntType / IntType) would be declared
  // 32-bit; that leaves 32-bit formats, rgba8unorm, rgba8snorm, rgba16float.
  storable: boolean;
}

const SCALAR_GPU: Record<ScalarType, { kind: GpuFormatInfo["kind"]; webgpu: string; gl: string; glType: string; threeType: THREE.TextureDataType }> = {
  uint8: { kind: "uint", webgpu: "8uint", gl: "8UI", glType: "UNSIGNED_BYTE", threeType: THREE.UnsignedIntType },
  sint8: { kind: "sint", webgpu: "8sint", gl: "8I", glType: "BYTE", threeType: THREE.IntType },
  unorm8: { kind: "float", webgpu: "8unorm", gl: "8", glType: "UNSIGNED_BYTE", threeType: THREE.UnsignedByteType },
  snorm8: { kind: "float", webgpu: "8snorm", gl: "8_SNORM", glType: "BYTE", threeType: THREE.ByteType },
  uint16: { kind: "uint", webgpu: "16uint", gl: "16UI", glType: "UNSIGNED_SHORT", threeType: THREE.UnsignedIntType },
  sint16: { kind: "sint", webgpu: "16sint", gl: "16I", glType: "SHORT", threeType: THREE.IntType },
  float16: { kind: "float", webgpu: "16float", gl: "16F", glType: "HALF_FLOAT", threeType: THREE.HalfFloatType },
  uint32: { kind: "uint", webgpu: "32uint", gl: "32UI", glType: "UNSIGNED_INT", threeType: THREE.UnsignedIntType },
  sint32: { kind: "sint", webgpu: "32sint", gl: "32I", glType: "INT", threeType: THREE.IntType },
  float32: { kind: "float", webgpu: "32float", gl: "32F", glType: "FLOAT", threeType: THREE.FloatType },
};

const CHANNELS: Record<number, { webgpu: string; gl: string; glFormat: string; three: THREE.PixelFormat; threeInteger: THREE.PixelFormat }> = {
  1: { webgpu: "r", gl: "R", glFormat: "RED", three: THREE.RedFormat, threeInteger: THREE.RedIntegerFormat },
  2: { webgpu: "rg", gl: "RG", glFormat: "RG", three: THREE.RGFormat, threeInteger: THREE.RGIntegerFormat },
  4: { webgpu: "rgba", gl: "RGBA", glFormat: "RGBA", three: THREE.RGBAFormat, threeInteger: THREE.RGBAIntegerFormat },
};

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

export function gpuFormatInfo(format: ArrayFormat): GpuFormatInfo {
  const cached = cache.get(format);
  if (cached) {
    return cached;
  }

  const { scalar, components, gpu } = ArrayFormats.info(format);
  if (!gpu) {
    throw new Error(`Format ${format} is CPU-only (GPU formats have 1, 2 or 4 components)`);
  }

  const s = SCALAR_GPU[scalar];
  const channels = CHANNELS[components];
  const integer = s.kind !== "float";
  const vectorPrefix = { uint: "u", sint: "i", float: "" }[s.kind];
  const scalarNode = { uint: "uint", sint: "int", float: "float" }[s.kind];

  const info: GpuFormatInfo = {
    format,
    components,
    scalar,
    kind: s.kind,
    webgpu: `${channels.webgpu}${s.webgpu}` as GPUTextureFormat,
    gl: `${channels.gl}${s.gl}`,
    glFormat: `${channels.glFormat}${integer ? "_INTEGER" : ""}`,
    glType: s.glType,
    threeFormat: integer ? channels.threeInteger : channels.three,
    threeType: s.threeType,
    node: components === 1 ? scalarNode : `${vectorPrefix}vec${components}`,
    renderable: scalar !== "snorm8",
    storable: ArrayFormats.info(format).scalarBytes === 4 || (components === 4 && (scalar === "unorm8" || scalar === "snorm8" || scalar === "float16")),
  };

  cache.set(format, info);
  return info;
}