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

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

const webGpuUtils = function() {
  const outputFormat = 'bgra8unorm';

  const wgslShaders = {
    vertex: `
var<private> quadPos : array<vec4<f32>, 4> = array<vec4<f32>, 4>(
    vec4<f32>(-1.0,  1.0, 0.0, 1.0),
    vec4<f32>(-1.0, -1.0, 0.0, 1.0),
    vec4<f32>( 1.0,  1.0, 0.0, 1.0),
    vec4<f32>( 1.0, -1.0, 0.0, 1.0));

var<private> quadUV : array<vec2<f32>, 4> = array<vec2<f32>, 4>(
    vec2<f32>(0.0, 0.0),
    vec2<f32>(0.0, 1.0),
    vec2<f32>(1.0, 0.0),
    vec2<f32>(1.0, 1.0));

struct VertexOutput {
  @builtin(position) Position : vec4<f32>,
  @location(0) fragUV : vec2<f32>,
}

@vertex
fn main(@builtin(vertex_index) VertexIndex : u32) -> VertexOutput {
  var output: VertexOutput;
  output.Position = quadPos[VertexIndex];
  output.fragUV = quadUV[VertexIndex];
  return output;
}
`,

    fragmentBlit: `
@group(0) @binding(0) var mySampler: sampler;
@group(0) @binding(1) var myTexture: texture_2d<f32>;

@fragment
fn main(@location(0) fragUV : vec2<f32>) -> @location(0) vec4<f32> {
  return textureSample(myTexture, mySampler, fragUV);
}
`,

    fragmentClear: `
@fragment
fn main(@location(0) fragUV : vec2<f32>) -> @location(0) vec4<f32> {
  return vec4<f32>(1.0, 1.0, 1.0, 1.0);
}
`,

    fragmentImport: `
@group(0) @binding(0) var mySampler: sampler;
@group(0) @binding(1) var myTexture: texture_external;

@fragment
fn main(@location(0) fragUV : vec2<f32>) -> @location(0) vec4<f32> {
  return textureSampleBaseClampToEdge(myTexture, mySampler, fragUV);
}
`,
  };

  return {
    init: async function(gpuCanvas, has_alpha = true) {
      const adapter = navigator.gpu && await navigator.gpu.requestAdapter();
      if (!adapter) {
        console.error('navigator.gpu && navigator.gpu.requestAdapter failed');
        return null;
      }

      const device = await adapter.requestDevice();
      if (!device) {
        console.error('adapter.requestDevice() failed');
        return null;
      }

      const context = gpuCanvas.getContext('webgpu');
      if (!context) {
        console.error('getContext(webgpu) failed');
        return null;
      }

      context.configure({
        device: device,
        format: outputFormat,
        usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC,
        alphaMode: has_alpha ? "premultiplied" : "opaque",
      });

      return [device, context];
    },

    importExternalTextureTest: function(
      device, context, video) {
        const blitPipeline = device.createRenderPipeline({
          layout: 'auto',
          vertex: {
            module: device.createShaderModule({
              code: wgslShaders.vertex,
            }),
            entryPoint: 'main',
          },
          fragment: {
            module: device.createShaderModule({
              code: wgslShaders.fragmentImport,
            }),
            entryPoint: 'main',
            targets: [
              {
                format: outputFormat,
              },
            ],
          },
          primitive: {
            topology: 'triangle-strip',
            stripIndexFormat: 'uint16',
          },
        });

        const sampler = device.createSampler({
          magFilter: 'linear',
          minFilter: 'linear',
        });
        const externalTextureDescriptor = { source: video };
        const externalTexture =
            device.importExternalTexture(externalTextureDescriptor);

        const bindGroup = device.createBindGroup({
          layout: blitPipeline.getBindGroupLayout(0),
          entries: [
            {
              binding: 0,
              resource: sampler,
            },
            {
              binding: 1,
              resource: externalTexture,
            },
          ],
        });

        const renderPassDescriptor = {
          colorAttachments: [
            {
              view: context.getCurrentTexture().createView(),
              loadOp: 'clear',
              clearValue: {r: 0.0, g: 0.0, b: 0.0, a: 1.0},
              storeOp: 'store',
            },
          ],
        };

        const commandEncoder = device.createCommandEncoder();
        const passEncoder =
            commandEncoder.beginRenderPass(renderPassDescriptor);
        passEncoder.setPipeline(blitPipeline);
        passEncoder.setBindGroup(0, bindGroup);
        passEncoder.draw(4, 1, 0, 0);
        passEncoder.end();

        device.queue.submit([commandEncoder.finish()]);
    },

    uploadToGPUTextureTest: function(
      device, context, canvasImageSource) {
      const blitPipeline = device.createRenderPipeline({
        layout: 'auto',
        vertex: {
          module: device.createShaderModule({
            code: wgslShaders.vertex,
          }),
          entryPoint: 'main',
        },
        fragment: {
          module: device.createShaderModule({
            code: wgslShaders.fragmentBlit,
          }),
          entryPoint: 'main',
          targets: [
            {
              format: outputFormat,
            },
          ],
        },
        primitive: {
          topology: 'triangle-strip',
          stripIndexFormat: 'uint16',
        },
      });

      const sampler = device.createSampler({
        magFilter: 'linear',
        minFilter: 'linear',
      });

      let texture;

      texture = device.createTexture({
        size: [canvasImageSource.width, canvasImageSource.height],
        format: 'rgba8unorm',
        usage: GPUTextureUsage.COPY_DST |
               GPUTextureUsage.RENDER_ATTACHMENT |
               GPUTextureUsage.TEXTURE_BINDING
      });

      device.queue.copyExternalImageToTexture(
        {source: canvasImageSource, origin: [0, 0]},
        {texture},
        [canvasImageSource.width, canvasImageSource.height]
      );

      const bindGroup = device.createBindGroup({
        layout: blitPipeline.getBindGroupLayout(0),
        entries: [
          {
            binding: 0,
            resource: sampler,
          },
          {
            binding: 1,
            resource: texture.createView(),
          },
        ],
      });

      const renderPassDescriptor = {
        colorAttachments: [
          {
            view: context.getCurrentTexture().createView(),
            loadOp: 'clear',
            clearValue: {r: 0.0, g: 0.0, b: 0.0, a: 1.0},
            storeOp: 'store',
          },
        ],
      };

      const commandEncoder = device.createCommandEncoder();
      const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);
      passEncoder.setPipeline(blitPipeline);
      passEncoder.setBindGroup(0, bindGroup);
      passEncoder.draw(4, 1, 0, 0);
      passEncoder.end();

      device.queue.submit([commandEncoder.finish()]);
    },

    fourColorsTest: function(device, context, width, height) {
      const clearPipeline = device.createRenderPipeline({
        layout: 'auto',
        vertex: {
          module: device.createShaderModule({
            code: wgslShaders.vertex,
          }),
          entryPoint: 'main',
        },
        fragment: {
          module: device.createShaderModule({
            code: wgslShaders.fragmentClear,
          }),
          entryPoint: 'main',
          targets: [{
            format: outputFormat,
            blend: {
              color: {
                srcFactor: 'constant',
                dstFactor: 'zero'
              },
              alpha: {
                srcFactor: 'constant',
                dstFactor: 'zero'
              },
            }
          }],
        },
        primitive: {
          topology: 'triangle-strip',
          stripIndexFormat: 'uint16',
        },
      });

      const renderPassDescriptor = {
        colorAttachments: [
          {
            view: context.getCurrentTexture().createView(),
            loadOp: 'clear',
            clearValue: {r: 0.0, g: 0.0, b: 0.0, a: 1.0},
            storeOp: 'store',
          },
        ],
      };

      const commandEncoder = device.createCommandEncoder();
      const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);
      passEncoder.setPipeline(clearPipeline);

      passEncoder.setBlendConstant([0.0, 1.0, 0.0, 1.0]);
      passEncoder.setScissorRect(0, 0, width / 2, height / 2);
      passEncoder.draw(4, 1, 0, 0);

      passEncoder.setBlendConstant([1.0, 0.0, 0.0, 1.0]);
      passEncoder.setScissorRect(0, height / 2, width / 2, height / 2);
      passEncoder.draw(4, 1, 0, 0);

      passEncoder.setBlendConstant([1.0, 1.0, 0.0, 1.0]);
      passEncoder.setScissorRect(width / 2, height / 2, width / 2, height / 2);
      passEncoder.draw(4, 1, 0, 0);

      passEncoder.setBlendConstant([0.0, 0.0, 1.0, 1.0]);
      passEncoder.setScissorRect(width / 2, 0, width / 2, height / 2);
      passEncoder.draw(4, 1, 0, 0);

      passEncoder.end();
      device.queue.submit([commandEncoder.finish()]);
    },
  };
}();