Enabling Support

WebGL P3 support is available by default in Chrome 104 and later!

Detecting Support

To detect support for WebGL, check if the drawingBufferColorSpace attribute is present. The following example will draw green if the feature is present and red if it is not.

  var gl = document.getElementById('detectExample').getContext('webgl2');
  if (`drawingBufferColorSpace` in gl) {
    console.log('Color space support is present');
    gl.clearColor(0, 1, 0, 1);
  } else {
    console.log('Color space support is NOT present');
    gl.clearColor(0, 1, 0, 1);
  }
  gl.clear(gl.COLOR_BUFFER_BIT);

The following canvas runs that example.

Setting the Drawing Buffer Color Space

To set the drawing buffer color space, set the aforementioned drawingBufferColorSpace. The valid values for this to be set to are 'srgb' and 'display-p3'. If an invalid value is specified, then the value of drawingBufferColorSpace will remain unchanged.

The following example draws an sRGB WebGL canvas on the left and a Display P3 canvas on the right. The left canvas is cleared to sRGB red. The center canvas is cleared to Display P3 red. The right canvas has an invalid color space set, and remains sRGB. Note that the drawingBufferColorSpace is set before drawing. The contents of the canvas are cleared when it is set.

  const glSRGB = document.getElementById('setExampleSRGB').getContext('webgl2');
  glSRGB.clearColor(1, 0, 0, 1);
  glSRGB.clear(glSRGB.COLOR_BUFFER_BIT);

  const glP3 = document.getElementById('setExampleP3').getContext('webgl2');
  if (`drawingBufferColorSpace` in glP3) {
    glP3.drawingBufferColorSpace = 'display-p3';
    console.log(glP3.drawingBufferColorSpace);
    glP3.clearColor(1, 0, 0, 1);
    glP3.clear(glP3.COLOR_BUFFER_BIT);
  }

  const glInvalid = document.getElementById('setExampleInvalid').getContext('webgl2');
  if (`drawingBufferColorSpace` in glInvalid) {
    glInvalid.drawingBufferColorSpace = 'not-a-valid-color-space';
    console.log(glInvalid.drawingBufferColorSpace);
    glInvalid.clearColor(1, 0, 0, 1);
    glInvalid.clear(glInvalid.COLOR_BUFFER_BIT);
  }

Uploading Data

Content is imported to a WebGL texture using texImage2D and similar functions.

If this content is a TexImageSource (ImageBitmap, ImageData, HTMLImageElement, HTMLCanvasElement, HTMLVideoElement, OffscreenCanvas, or VideoFrame), then the content will be converted to the unpackColorSpace during import. The one exception to this is if the content is a HTMLImageElement and UNPACK_COLORSPACE_CONVERSION_WEBGL is set to NONE.

If this content is an ArrayBufferView or a pixel buffer object, then no color conversion is performed.

In the following example a Uint8ClampedArray is populated with the color (255, 0, 0, 255). It is imported directly on the left, no conversion is done, and the result appears as Display P3 red. It is imported on the right as an sRGB ImageData, converted to Display P3, and the result appears as sRGB red. The drawing buffer color space is set the Display P3.

  const drawTexImage = function(gl, image_source, width, height, x) {
    let tex = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, tex);
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, image_source);
    // ... draw tex at the specified x coordinate.
  }

  const glTexImageExample = document.getElementById('texImageExample').getContext('webgl2');
  glTexImageExample.drawingBufferColorSpace = 'display-p3';
  glTexImageExample.unpackColorSpace = 'display-p3';

  // Populate a Uint8ClampedArray with (255, 0, 0, 255).
  const data = new Uint8ClampedArray(4 * 32 * 32);
  for (var offset = 0; offset < data.length; offset += 4)
    data[offset + 0] = data[offset + 3] = 255;

  // Draw it as a texture imported from a Uint8ClampedArray. The data will be
  // imported directly.
  drawTexImage(glTexImageExample, data, 32, 32, -0.5);

  // Draw it via an ImageData (which is sRGB unless otherwise specified). The
  // data will be converted from sRGB to Display P3.
  const imageData = new ImageData(data, 32, 32);
  drawTexImage(glTexImageExample, imageData, 32, 32, 0.5);

Performance Notes

It is always best to perform color conversion on the GPU. If the application has data in its own custom color space, the most performant path is to upload this data without conversion (e.g, via Uint8ClampedArray or similar), and then perform color conversion to the drawingBufferColorSpace via a shader.