webgpuCreateCubeTextureFromPixels function

TextureHandle? webgpuCreateCubeTextureFromPixels(
  1. GPUDevice gpu,
  2. List<WebGpuTexture> tracked, {
  3. required int size,
  4. required TextureFormat format,
  5. required List<ByteData> faces,
  6. List<List<ByteData>>? mipLevels,
})

faces in +X, −X, +Y, −Y, +Z, −Z order as one cube texture. See GraphicsDevice.createCubeTextureFromPixels.

A cube is a six-layer 2D texture here, and a face is the z of an origin rather than a target constant of its own. There is no six-argument upload call and no face enumeration in this API, so the order the contract documents is honoured by the loop's own index and by nothing else — which is why the conformance check that draws six known directions against six known colours is the thing that holds it.

A block-compressed cube is a null, and that is a scope line rather than a limit of the API. WebGPU would take one — six layers of block rows is the same writeTexture the 2D path makes — but nothing in this repository uploads a compressed cube: the KTX2 loader hands over one 2D image and the engine's own cubes are rendered rather than decoded. A path with no caller is a path with no test, and the null is what webgpuTexelBytes already answers for a format it has no texel size for.

Implementation

TextureHandle? webgpuCreateCubeTextureFromPixels(
  GPUDevice gpu,
  List<WebGpuTexture> tracked, {
  required int size,
  required TextureFormat format,
  required List<ByteData> faces,
  List<List<ByteData>>? mipLevels,
}) {
  final spelling = gpuTextureFormat(format);
  final texelBytes = webgpuTexelBytes(format);
  if (spelling == null || texelBytes == null) return null;
  if (faces.length != 6) return null;
  for (final face in faces) {
    if (face.lengthInBytes != size * size * texelBytes) return null;
  }
  final levels = mipLevels ?? const <List<ByteData>>[];
  var side = size;
  for (final level in levels) {
    if (level.length != 6) return null;
    side = side > 1 ? side >> 1 : 1;
    for (final face in level) {
      if (face.lengthInBytes != side * side * texelBytes) return null;
    }
  }

  final texture = gpu.createTexture(
    GPUTextureDescriptor(
      size: GPUExtent3DDict(width: size, height: size, depthOrArrayLayers: 6),
      format: spelling,
      usage:
          GpuTextureUsage.textureBinding |
          GpuTextureUsage.copyDst |
          GpuTextureUsage.copySrc,
      sampleCount: 1,
      mipLevelCount: 1 + levels.length,
      dimension: '2d',
      label: 'cube ${size}x$size $spelling',
    ),
  );
  for (var face = 0; face < 6; face++) {
    _writeLevel(gpu, texture, 0, face, size, size, texelBytes, faces[face]);
  }
  side = size;
  for (var level = 0; level < levels.length; level++) {
    side = side > 1 ? side >> 1 : 1;
    for (var face = 0; face < 6; face++) {
      _writeLevel(
        gpu,
        texture,
        level + 1,
        face,
        side,
        side,
        texelBytes,
        levels[level][face],
      );
    }
  }

  final backend = WebGpuTexture(
    texture: texture,
    dimension: WebGpuTextureDimension.cube,
    sampleable: true,
  );
  tracked.add(backend);
  return TextureHandle(
    backend: backend,
    width: size,
    height: size,
    format: format,
    type: TextureType.textureCube,
  );
}