webgpuCreateTextureFromPixels function

TextureHandle? webgpuCreateTextureFromPixels(
  1. GPUDevice gpu,
  2. List<WebGpuTexture> tracked, {
  3. required int width,
  4. required int height,
  5. required TextureFormat format,
  6. required ByteData pixels,
  7. List<ByteData>? mipLevels,
})

A texture already holding pixels, and mipLevels below it. See GraphicsDevice.createTextureFromPixels.

Null where the bytes are not the size the description says, which is the contract's answer for a decoder that disagreed about the dimensions.

A block-compressed format goes to _webgpuCreateCompressedTextureFromPixels, which measures in blocks rather than texels. It is still a null here for a family the device was not granted, because gpuTextureFormat has a spelling for all of them and only supportsTextureFormat knows which were asked for — so the split is: a spelling that exists and a feature that was not granted is a texture the browser would refuse, and the caller is meant to have asked first.

Implementation

TextureHandle? webgpuCreateTextureFromPixels(
  GPUDevice gpu,
  List<WebGpuTexture> tracked, {
  required int width,
  required int height,
  required TextureFormat format,
  required ByteData pixels,
  List<ByteData>? mipLevels,
}) {
  final spelling = gpuTextureFormat(format);
  if (spelling == null) return null;
  if (format.isCompressed) {
    return _webgpuCreateCompressedTextureFromPixels(
      gpu,
      tracked,
      spelling: spelling,
      width: width,
      height: height,
      format: format,
      pixels: pixels,
      mipLevels: mipLevels,
    );
  }
  final texelBytes = webgpuTexelBytes(format);
  if (texelBytes == null) return null;
  if (pixels.lengthInBytes != width * height * texelBytes) return null;

  // Every level measured before a single byte is uploaded, the way the WebGL2
  // backend measures its chain: `writeTexture` reads as many bytes as the
  // rectangle needs and ignores the rest, so a level built with the wrong
  // arithmetic is a plausible, wrong picture the moment anything minifies.
  final levels = mipLevels ?? const <ByteData>[];
  var w = width;
  var h = height;
  for (final level in levels) {
    w = w > 1 ? w >> 1 : 1;
    h = h > 1 ? h >> 1 : 1;
    if (level.lengthInBytes != w * h * texelBytes) return null;
  }

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

  final backend = WebGpuTexture(
    texture: texture,
    dimension: WebGpuTextureDimension.twoDimensional,
    sampleable: true,
  );
  tracked.add(backend);
  return TextureHandle(
    backend: backend,
    width: width,
    height: height,
    format: format,
  );
}