chromium/content/test/data/gpu/pixel_video_from_canvas.js

// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

function logOutput(s) {
  if (window.domAutomationController)
    window.domAutomationController.log(s);
  else
    console.log(s);
}

function sendResult(status) {
  if (window.domAutomationController) {
    window.domAutomationController.send(status);
  } else {
    console.log(status);
  }
}

// This is a very basic test for 4 corner pixels, with large tolerance.
function checkFourColorsFrame(video) {
  const width = video.videoWidth;
  const height = video.videoHeight;
  let test_cnv = new OffscreenCanvas(width, height);
  let ctx = test_cnv.getContext('2d');
  ctx.drawImage(video, 0, 0, width, height);

  const kYellow = [0xFF, 0xFF, 0x00, 0xFF];
  const kRed = [0xFF, 0x00, 0x00, 0xFF];
  const kBlue = [0x00, 0x00, 0xFF, 0xFF];
  const kGreen = [0x00, 0xFF, 0x00, 0xFF];

  function checkPixel(x, y, expected_rgba) {
    const settings = {colorSpaceConversion: 'none'};
    const rgba = ctx.getImageData(x, y, 1, 1, settings).data;
    const tolerance = 30;
    for (let i = 0; i < 4; i++) {
      if (Math.abs(rgba[i] - expected_rgba[i]) > tolerance) {
        return false;
      }
    }
    return true;
  }

  let m = 10;  // margin from the frame's edge
  if (!checkPixel(m, m, kYellow)) {
    logOutput('top left corner is not yellow');
    return false;
  }
  if (!checkPixel(width - m, m, kRed)) {
    logOutput('top right corner is not red');
    return false;
  }
  if (!checkPixel(m, height - m, kBlue)) {
    logOutput('bottom left corner is not blue');
    return false;
  }
  if (!checkPixel(width - m, height - m, kGreen)) {
    logOutput('bottom right corner is not green');
    return false;
  }
  return true;
}

function fourColorsFrame(ctx) {
  const width = ctx.canvas.width;
  const height = ctx.canvas.height;
  const kYellow = '#FFFF00';
  const kRed = '#FF0000';
  const kBlue = '#0000FF';
  const kGreen = '#00FF00';

  ctx.fillStyle = kYellow;
  ctx.fillRect(0, 0, width / 2, height / 2);

  ctx.fillStyle = kRed;
  ctx.fillRect(width / 2, 0, width / 2, height / 2);

  ctx.fillStyle = kBlue;
  ctx.fillRect(0, height / 2, width / 2, height / 2);

  ctx.fillStyle = kGreen;
  ctx.fillRect(width / 2, height / 2, width / 2, height / 2);
}

function waitForNextFrame() {
  return new Promise((resolve, _) => {
    window.requestAnimationFrame(resolve);
  });
}

function initGL(gl) {
  gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
  gl.enable(gl.SCISSOR_TEST);
}

function fourColorsFrameGL(gl) {
  const width = gl.canvas.width;
  const height = gl.canvas.height;
  gl.scissor(0, 0, width, height);
  gl.clearColor(0, 0, 0, 1);
  gl.clear(gl.COLOR_BUFFER_BIT);

  gl.scissor(width / 2, 0, width, height / 2);
  gl.clearColor(0, 1, 0, 1);
  gl.clear(gl.COLOR_BUFFER_BIT);

  gl.scissor(width / 2, height / 2, width, height);
  gl.clearColor(1, 0, 0, 1);
  gl.clear(gl.COLOR_BUFFER_BIT);

  gl.scissor(0, 0, width / 2, height / 2);
  gl.clearColor(0, 0, 1, 1);
  gl.clear(gl.COLOR_BUFFER_BIT);

  gl.scissor(0, height / 2, width / 2, height);
  gl.clearColor(1, 1, 0, 1);
  gl.clear(gl.COLOR_BUFFER_BIT);

  gl.finish();
}

function setupFrameCallback(video) {
  logOutput(`Expecting frames now`);
  let vf_counter = 5;
  function videoFrameCallback(now, md) {
    logOutput(`Got a video frame: ${now}`);
    if (vf_counter > 0) {
      vf_counter--;
      video.requestVideoFrameCallback(videoFrameCallback);
      return;
    }

    if (checkFourColorsFrame(video)) {
      logOutput('Test completed');
      sendResult('SUCCESS');
    } else {
      logOutput('Test failed. Result mismatch.');
      sendResult('FAILED');
    }
  }
  video.requestVideoFrameCallback(videoFrameCallback);
}

async function runTest(context_type, alpha) {
  const canvas = document.getElementById('cnv');
  const video = document.getElementById('vid');
  const ctx = canvas.getContext(context_type, {alpha: alpha});
  const fps = 60;

  let draw = null;
  if (context_type == 'webgl2') {
    initGL(ctx);
    draw = fourColorsFrameGL;
  } else {
    draw = fourColorsFrame;
  }

  draw(ctx);
  let stream = canvas.captureStream(fps);
  video.srcObject = stream;
  video.onerror = e => {
    logOutput(`Test failed: ${e.message}`);
    sendResult('FAIL');
  };
  setupFrameCallback(video);
  video.play();

  while (true) {
    await waitForNextFrame();
    draw(ctx);
  }
}