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.
const adapter = await navigator.gpu.requestAdapter();
if (!adapter) { return; }
const device = await adapter.requestDevice();
device.createBuffer() : create buffermappedAtCreation: 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 CPUunmapped: owned by the GPUgpuBuffer.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]);
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. 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));
})();
mapAsync() and createBuffer(mappedAtCreation=true).Reference