chromium/content/test/data/gpu/webgpu-context-lost.html

<html>

<head>
  <script type='module'>
    let lostJustHappened = false;
    const initTextureColor = [1.0, 0.0, 0.0];
    const reinitTextureColor = [0.0, 1.0, 0.0];
    const reinitTextureColorArray = new Uint8Array([0x00, 0xff, 0x00, 0xff]);

    function contextLostTest(kind) {
      switch (kind) {
        case 'kill_after_notification':
          // The browser test waits for notification from the page that it
          // has been loaded before navigating to about:gpucrash.
          window.domAutomationController.send('LOADED');
          break;
      }
    }

    async function init(canvas, color) {
      let adapter = await navigator.gpu.requestAdapter();
      if (!adapter) {
        console.warn('Failed to get WebGPU adapter');
      }

      let device = await adapter.requestDevice();
      device.lost.then(async value => {
        // After device lost, re-initialize for new adapter and device.
        // We choose a different texture color from when we first
        // init to be sure that the texture is coming from here
        device = null;
        adapter = null;
        lostJustHappened = true;
        await init(canvas, reinitTextureColor);
      });

      const context = canvas.getContext('webgpu');
      if (!context)
        return;

      const swapChainFormat = 'bgra8unorm';
      context.configure({
        device,
        format: swapChainFormat,
        usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT
      });

      async function checkTextureColor(
        src,
        format,
        { x, y },
        exp
      ) {
        const byteLength = 4;
        const bytesPerRow = 256;
        const rowsPerImage = 1;
        const mipSize = [1, 1, 1];
        const buffer = device.createBuffer({
          size: byteLength,
          usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST,
        });

        const commandEncoder = device.createCommandEncoder();
        commandEncoder.copyTextureToBuffer(
          { texture: src, mipLevel: 0, arrayLayer: 0, origin: { x, y, z: 0 } },
          { buffer, bytesPerRow, rowsPerImage },
          mipSize
        );
        device.queue.submit([commandEncoder.finish()]);
        await buffer.mapAsync(GPUMapMode.READ);
        const actual = new Uint8Array(buffer.getMappedRange());

        // check that the actual and expected buffers are the same
        if (actual.byteLength !== exp.byteLength) {
          window.domAutomationController.send('FAILED');
        }
        for (var i = 0; i !== actual.byteLength; i++) {
          if (actual[i] != exp[i]) {
            window.domAutomationController.send('FAILED');
            return;
          }
        }
        // if we did not fail in the previous checks, the context successfully restored
        window.domAutomationController.send('SUCCESS');
      }

      function drawFrame() {
        const commandEncoder = device.createCommandEncoder();
        const texture = context ? context.getCurrentTexture() : null;
        const textureView = texture ? texture.createView() : null;

        const renderPassDescriptor = {
          colorAttachments: [{
            view: textureView,
            loadOp: 'clear',
            clearValue: { r: color[0], g: color[1], b: color[2], a: 1.0 },
            storeOp: 'store',
          }],
        };

        const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);
        passEncoder.end();

        device.queue.submit([commandEncoder.finish()]);
        if (lostJustHappened) {
          // We don't want to check for the texture color until device and adapter have been reinitialized
          // Checking prematurely was causing hangs in buffer.mapAync to never return (crbug.com/dawn/556)
          if (device == null || adapter == null) {
            return;
          }
          // We want to check if it's been restored to the reinit texture color.
          checkTextureColor(texture, swapChainFormat, { x: 0, y: 0 }, reinitTextureColorArray);
          lostJustHappened = false;
        }
      }

      function doFrame(timestamp) {
        drawFrame(timestamp);
        requestAnimationFrame(doFrame);
      }
      requestAnimationFrame(doFrame);
    }

    export async function onLoad() {
      const container = document.getElementById('container');
      const canvas = document.createElement('canvas');
      canvas.width = 100;
      canvas.height = 100;
      container.appendChild(canvas);

      if (!navigator.gpu) {
        console.warn('webgpu not supported');
        return;
      }

      await init(canvas, initTextureColor);

      const query = new URLSearchParams(window.location.search).get('query');
      if (query)
        contextLostTest(query);
    }

    document.addEventListener('DOMContentLoaded', onLoad);
  </script>
</head>

<body>
  <div id='container'></div>
</body>

</html>