Get started with WebGPU

WonderIT·2022년 4월 13일
post-thumbnail

Background

WebGPU is available for now in Chrome Canary on desktop behind an experimental flag. You can enable it at chrome://flags/#enable-unsafe-webgpu. The API is constantly changing and currently unsafe. As GPU sandboxing isn't implemented yet for the WebGPU API, it is possible to read GPU data for other processes! Don't browse the web with it enabled.

Access the GPU

const adapter = await navigator.gpu.requestAdapter();
if (!adapter) { return; }
const device = await adapter.requestDevice();

Write buffer memory

  • device.createBuffer() : create buffer
    • mappedAtCreation: if true, GPU buffers can be read and written in JavaScript when creation.
  • GPUBufferUsage.MAP_WRITE: not required for this specific call, but let's be explicit that we want to write to this buffer.
  • getMappedRange(): raw binary data buffer can be retrieved.
  • mapped: owned by the CPU
  • unmapped: owned by the GPU
    • gpuBuffer.unmap() is required for GPU to access buffer.
// Get a GPU buffer in a mapped state and an arrayBuffer for writing.
const gpuBuffer = device.createBuffer({
  mappedAtCreation: true,
  size: 4,
  usage: GPUBufferUsage.MAP_WRITE
});
const arrayBuffer = gpuBuffer.getMappedRange();

// Write bytes to buffer.
new Uint8Array(arrayBuffer).set([0, 1, 2, 3]);

Read buffer memory

Let's copy a GPU buffer to another GPU buffer and read it back

  • GPUBufferUsage.COPY_SRC: for copying buffer.
  • GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ: this buffer will be used as destination for the first GPU buffer, and read in JavaScript once GPU copy commands have been executed.
  • device.createCommandEncoder(): JS object that builds a batch of buffered commands that will be sent to GPU.
    • GPU commands are executed asynchronously.
  • GPUBuffer: unbuffered commands that execute atomically at the time they are called.
  • copyEncoder.copyBufferToBuffer(): add command to the command queue for later execution.
  • copyEncoder.finish(): finish encoding commands and submit those to the GPU device.
  • device.queue.submit(): atomically execute all the commands stored in the array in order.
  • gpuReaderBuffer.mapAsync(GPUMapMode.READ): returns a promise that will resolve when the GPU buffer is mapped.
  • gpuReaderBuffer.getMappedRange(): get the mapped range that contains the same values as the first GPU buffer once all queued GPU commands have been executed.
// JS code 
(async () => {
  if (!("gpu" in navigator)) {
    console.log(
      "WebGPU is not supported. Enable chrome://flags/#enable-unsafe-webgpu flag."
    );
    return;
  }

  const adapter = await navigator.gpu.requestAdapter();
  if (!adapter) {
    console.log("Failed to get GPU adapter.");
    return;
  }
  const device = await adapter.requestDevice();

  // Get a GPU buffer in a mapped state and an arrayBuffer for writing.
  const gpuWriteBuffer = device.createBuffer({
    mappedAtCreation: true,
    size: 4,
    usage: GPUBufferUsage.MAP_WRITE | GPUBufferUsage.COPY_SRC
  });
  const arrayBuffer = gpuWriteBuffer.getMappedRange();

  // Write bytes to buffer.
  new Uint8Array(arrayBuffer).set([0, 1, 2, 3]);

  // Unmap buffer so that it can be used later for copy.
  gpuWriteBuffer.unmap();

  // Get a GPU buffer for reading in an unmapped state.
  const gpuReadBuffer = device.createBuffer({
    mappedAtCreation: false,
    size: 4,
    usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
  });

  // Encode commands for copying buffer to buffer.
  const copyEncoder = device.createCommandEncoder();
  copyEncoder.copyBufferToBuffer(
    gpuWriteBuffer /* source buffer */,
    0 /* source offset */,
    gpuReadBuffer /* destination buffer */,
    0 /* destination offset */,
    4 /* size */
  );

  // Submit copy commands.
  const copyCommands = copyEncoder.finish();
  device.queue.submit([copyCommands]);

  // Read buffer.
  await gpuReadBuffer.mapAsync(GPUMapMode.READ);
  const copyArrayBuffer = gpuReadBuffer.getMappedRange();

  console.log(new Uint8Array(copyArrayBuffer));
})();

Summary

  • GPU buffers have to be unmapped to be used in device queue submission.
  • When mapped, GPU buffers can be read and written in JS.
  • GPU buffers are mapped when mapAsync() and createBuffer(mappedAtCreation=true).

Shader programming

Create GPU Buffers

Bind group layout & bind group

Compute shader code

Pipeline setup

Commands submission

Read result matrix

One last trick

Reference

0개의 댓글