AVIF Texture Loading in WebGL and WebGPU

AVIF offers superior compression efficiency and high dynamic range support, making it an attractive asset format for real-time 3D web applications. Integrating AVIF into WebGL and WebGPU pipelines involves decoding the AV1-compressed bitstream into an uncompressed pixel representation using browser APIs like createImageBitmap or the WebCodecs API before uploading that data into GPU memory. This article explains the technical mechanics, modern upload pathways, color space management, and performance trade-offs when using AVIF for graphics programming on the web.

The Role of AVIF in Graphics Pipelines

AVIF is an image delivery format rather than a GPU texture format. Unlike block-compressed formats designed for hardware (such as ASTC, BCn, or Basis Universal), AVIF cannot remain compressed inside GPU VRAM.

Instead, AVIF acts as an ultra-lightweight transmission format over the network. Once fetched by the client, the browser decodes the AV1 bitstream into raw, uncompressed RGBA pixel buffers in system memory. These raw pixels are subsequently copied into GPU texture memory for rendering.

Decoding AVIF Asynchronously

To prevent frame drops on the main thread, AVIF images must be decoded asynchronously. Modern web architectures avoid using traditional HTMLImageElement instances in favor of fetch combined with createImageBitmap.

// Fetch the AVIF file as a Blob
const response = await fetch('texture.avif');
const blob = await response.blob();

// Decode off-thread into an ImageBitmap
const imageBitmap = await createImageBitmap(blob, {
  colorSpaceConversion: 'none',
  premultiplyAlpha: 'none'
});

Using createImageBitmap allows the browser’s underlying AV1 decoder to process the image on a worker thread. Specifying decoding options ensures the raw data is not prematurely converted or blended before reaching the shader.

WebGL Texture Upload Pipeline

In WebGL 1 and WebGL 2, the pipeline receives the decoded ImageBitmap and uploads it via the CPU-to-GPU bridge.

  1. Configure Pixel Storage: Define alignment and unpacking flags to match your asset’s layout:
    gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
    gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
  2. Allocate and Upload: Bind a texture target and call texImage2D or texSubImage2D:
    gl.bindTexture(gl.TEXTURE_2D, texture);
    gl.texImage2D(
      gl.TEXTURE_2D,
      0,
      gl.RGBA,
      gl.RGBA,
      gl.UNSIGNED_BYTE,
      imageBitmap
    );
    gl.generateMipmap(gl.TEXTURE_2D);

Because AVIF does not store precomputed mipmaps, mip generation must occur dynamically on the GPU using gl.generateMipmap(), adding a small runtime overhead during scene initialization.

WebGPU Texture Upload Pipeline

WebGPU modernizes texture handling by introducing copyExternalImageToTexture(), which coordinates the transfer of image sources directly to GPU textures via the GPUQueue.

// Create target texture
const texture = device.createTexture({
  size: [imageBitmap.width, imageBitmap.height, 1],
  format: 'rgba8unorm',
  usage: GPUTextureUsage.TEXTURE_BINDING | 
         GPUTextureUsage.COPY_DST | 
         GPUTextureUsage.RENDER_ATTACHMENT,
});

// Direct transfer from decoded image source to GPU texture
device.queue.copyExternalImageToTexture(
  { source: imageBitmap, flipY: false },
  { texture: texture },
  [imageBitmap.width, imageBitmap.height]
);

This method is optimized inside browser engines to bypass unnecessary intermediate memory copies, achieving near-zero-copy transfers depending on the underlying OS compositor and hardware decoder capabilities.

High Dynamic Range (HDR) and Wide Color Gamut

One of AVIF's greatest advantages over older formats like WebP or JPEG is native support for 10-bit and 12-bit color depths, as well as wide color gamuts (BT.2020) and transfer functions like PQ and HLG.

Performance Trade-Offs

When designing a production pipeline, consider the following trade-offs between AVIF and GPU-native formats: