Referensi cepat WebGPU API. Adapter, device, pipelines, buffers, textures, bind groups, shaders WGSL, dan compute passes. Perfect buat developer yang eksplor graphics programming modern.
async function initWebGPU() {
// 1. Check support
if (!navigator.gpu) {
throw new Error('WebGPU not supported in this browser');
}
// 2. Request adapter (GPU physical device)
const adapter = await navigator.gpu.requestAdapter({
powerPreference: 'high-performance', // atau 'low-power'
});
if (!adapter) {
throw new Error('No suitable GPU adapter found');
}
// 3. Request device (logical GPU context)
const device = await adapter.requestDevice({
requiredFeatures: ['texture-compression-bc'],
requiredLimits: {
maxBufferSize: 1 << 28,
maxStorageBufferBindingSize: 1 << 27,
},
});
// 4. Configure canvas context
const canvas = document.querySelector('canvas');
const context = canvas.getContext('webgpu');
const format = navigator.gpu.getPreferredCanvasFormat();
context.configure({
device,
format,
alphaMode: 'premultiplied', // atau 'opaque'
});
return { device, context, format, canvas };
}// Vertex buffer (static data)
const vertexData = new Float32Array([
// position (x, y, z), color (r, g, b, a)
0.0, 0.5, 0.0, 1, 0, 0, 1,
-0.5, -0.5, 0.0, 0, 1, 0, 1,
0.5, -0.5, 0.0, 0, 0, 1, 1,
]);
const vertexBuffer = device.createBuffer({
size: vertexData.byteLength,
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
});
// Write data ke buffer
device.queue.writeBuffer(vertexBuffer, 0, vertexData);
// Uniform buffer (updated each frame)
const uniformBuffer = device.createBuffer({
size: 64, // 4x4 matrix (16 floats * 4 bytes)
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
});
// Storage buffer (GPU read/write)
const storageBuffer = device.createBuffer({
size: 1000 * 4, // 1000 floats
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
});
// Read buffer back to CPU
const readBuffer = device.createBuffer({
size: 1000 * 4,
usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST,
});
// Copy GPU buffer ke read buffer
encoder.copyBufferToBuffer(storageBuffer, 0, readBuffer, 0, 1000 * 4);
// Map untuk read
await readBuffer.mapAsync(GPUMapMode.READ);
const result = new Float32Array(readBuffer.getMappedRange());
console.log(result);
readBuffer.unmap();// Update uniform setiap frame
const transformMatrix = new Float32Array(16);
// ... compute matrix ...
device.queue.writeBuffer(uniformBuffer, 0, transformMatrix);
// Dynamic buffer (aligned)
const dynamicOffset = 256; // harus multiple of 256
device.queue.writeBuffer(dynamicBuffer, dynamicOffset, data);const texture = device.createTexture({
size: [512, 512, 1],
format: 'rgba8unorm',
usage: GPUTextureUsage.TEXTURE_BINDING |
GPUTextureUsage.COPY_DST |
GPUTextureUsage.RENDER_ATTACHMENT,
});
// Write texture data dari image
const imageData = new Uint8Array(512 * 512 * 4);
// ... fill imageData ...
device.queue.writeTexture(
{ texture },
imageData,
{ bytesPerRow: 512 * 4, rowsPerImage: 512 },
[512, 512]
);
// Texture from image element
const img = new Image();
img.src = 'texture.jpg';
await img.decode();
const imageBitmap = await createImageBitmap(img);
device.queue.copyExternalImageToTexture(
{ source: imageBitmap },
{ texture },
[img.width, img.height]
);const sampler = device.createSampler({
addressModeU: 'repeat', // 'clamp-to-edge' | 'repeat' | 'mirror-repeat'
addressModeV: 'repeat',
addressModeW: 'repeat',
magFilter: 'linear', // 'nearest' | 'linear'
minFilter: 'linear',
mipmapFilter: 'linear',
lodMinClamp: 0,
lodMaxClamp: 32,
compare: 'less', // untuk shadow maps
});// Vertex shader output struct
struct VertexOutput {
@builtin(position) position: vec4f,
@location(0) color: vec3f,
};
// Vertex shader
@vertex
fn vs_main(
@location(0) pos: vec3f,
@location(1) inColor: vec3f,
) -> VertexOutput {
var output: VertexOutput;
output.position = vec4f(pos, 1.0);
output.color = inColor;
return output;
}
// Fragment shader
@fragment
fn fs_main(input: VertexOutput) -> @location(0) vec4f {
return vec4f(input.color, 1.0);
}// Define uniform struct
struct Uniforms {
modelMatrix: mat4x4f,
viewMatrix: mat4x4f,
projectionMatrix: mat4x4f,
};
@group(0) @binding(0) var<uniform> uniforms: Uniforms;
@vertex
fn vs_main(@location(0) pos: vec3f) -> @builtin(position) vec4f {
let world = uniforms.modelMatrix * vec4f(pos, 1.0);
let view = uniforms.viewMatrix * world;
return uniforms.projectionMatrix * view;
}@group(0) @binding(1) var tex: texture_2d<f32>;
@group(0) @binding(2) var samp: sampler;
@fragment
fn fs_main(@location(0) uv: vec2f) -> @location(0) vec4f {
return textureSample(tex, samp, uv);
}@group(0) @binding(0) var<storage, read> inputData: array<f32>;
@group(0) @binding(1) var<storage, read_write> outputData: array<f32>;
@compute @workgroup_size(64)
fn cs_main(@builtin(global_invocation_id) id: vec3u) {
let index = id.x;
if (index >= arrayLength(&inputData)) {
return;
}
// Simple: double each value
outputData[index] = inputData[index] * 2.0;
}// Define bind group layout
const bindGroupLayout = device.createBindGroupLayout({
entries: [
{
binding: 0,
visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
buffer: { type: 'uniform' },
},
{
binding: 1,
visibility: GPUShaderStage.FRAGMENT,
texture: { sampleType: 'float' },
},
{
binding: 2,
visibility: GPUShaderStage.FRAGMENT,
sampler: { type: 'filtering' },
},
],
});
// Create bind group (assign actual resources)
const bindGroup = device.createBindGroup({
layout: bindGroupLayout,
entries: [
{ binding: 0, resource: { buffer: uniformBuffer } },
{ binding: 1, resource: texture.createView() },
{ binding: 2, resource: sampler },
],
});
// Pipeline layout
const pipelineLayout = device.createPipelineLayout({
bindGroupLayouts: [bindGroupLayout],
});const shaderModule = device.createShaderModule({
code: wgslCode, // WGSL string from above
});
const renderPipeline = device.createRenderPipeline({
layout: pipelineLayout,
vertex: {
module: shaderModule,
entryPoint: 'vs_main',
buffers: [
{
arrayStride: 6 * 4, // 6 floats * 4 bytes
attributes: [
{
shaderLocation: 0,
offset: 0,
format: 'float32x3', // position
},
{
shaderLocation: 1,
offset: 3 * 4,
format: 'float32x3', // color
},
],
},
],
},
fragment: {
module: shaderModule,
entryPoint: 'fs_main',
targets: [
{
format: presentationFormat,
blend: {
color: {
srcFactor: 'src-alpha',
dstFactor: 'one-minus-src-alpha',
operation: 'add',
},
alpha: {
srcFactor: 'one',
dstFactor: 'one-minus-src-alpha',
operation: 'add',
},
},
},
],
},
primitive: {
topology: 'triangle-list', // 'point-list' | 'line-list' | 'line-strip' | 'triangle-list' | 'triangle-strip'
cullMode: 'back', // 'none' | 'front' | 'back'
frontFace: 'ccw', // 'ccw' | 'cw'
},
depthStencil: {
format: 'depth24plus',
depthWriteEnabled: true,
depthCompare: 'less',
},
});function render() {
// Create command encoder
const encoder = device.createCommandEncoder();
// Create depth texture (per frame atau persistent)
const depthTexture = device.createTexture({
size: [canvas.width, canvas.height],
format: 'depth24plus',
usage: GPUTextureUsage.RENDER_ATTACHMENT,
});
// Begin render pass
const renderPass = encoder.beginRenderPass({
colorAttachments: [
{
view: context.getCurrentTexture().createView(),
clearValue: { r: 0.1, g: 0.1, b: 0.1, a: 1.0 },
loadOp: 'clear', // 'clear' | 'load'
storeOp: 'store', // 'store' | 'discard'
},
],
depthStencilAttachment: {
view: depthTexture.createView(),
depthClearValue: 1.0,
depthLoadOp: 'clear',
depthStoreOp: 'store',
},
});
// Set pipeline dan resources
renderPass.setPipeline(renderPipeline);
renderPass.setBindGroup(0, bindGroup);
renderPass.setVertexBuffer(0, vertexBuffer);
renderPass.setIndexBuffer(indexBuffer, 'uint16');
// Draw
renderPass.drawIndexed(3); // indexed draw
// atau
renderPass.draw(3, 1, 0, 0); // vertexCount, instanceCount, firstVertex, firstInstance
// Multiple instances (instanced rendering)
renderPass.draw(3, 100); // 100 instances
// End pass
renderPass.end();
// Submit
device.queue.submit([encoder.finish()]);
// Cleanup depth texture
depthTexture.destroy();
}
requestAnimationFrame(render);const computePipeline = device.createComputePipeline({
layout: device.createPipelineLayout({
bindGroupLayouts: [computeBindGroupLayout],
}),
compute: {
module: shaderModule,
entryPoint: 'cs_main',
},
});
function compute() {
const encoder = device.createCommandEncoder();
const computePass = encoder.beginComputePass();
computePass.setPipeline(computePipeline);
computePass.setBindGroup(0, computeBindGroup);
// Dispatch workgroups
// workgroup_size(64) di shader = 64 threads per workgroup
// total threads = workgroupCountX * 64
const dataSize = 10000;
const workgroupCount = Math.ceil(dataSize / 64);
computePass.dispatchWorkgroups(workgroupCount);
computePass.end();
device.queue.submit([encoder.finish()]);
}async function main() {
const { device, context, format, canvas } = await initWebGPU();
// Vertex data
const vertices = new Float32Array([
0.0, 0.5, 1, 0, 0,
-0.5, -0.5, 0, 1, 0,
0.5, -0.5, 0, 0, 1,
]);
const vertexBuffer = device.createBuffer({
size: vertices.byteLength,
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
});
device.queue.writeBuffer(vertexBuffer, 0, vertices);
// Shader
const shader = device.createShaderModule({
code: `
struct VOut {
@builtin(position) pos: vec4f,
@location(0) color: vec3f,
};
@vertex
fn vs(@location(0) p: vec2f, @location(1) c: vec3f) -> VOut {
var o: VOut;
o.pos = vec4f(p, 0.0, 1.0);
o.color = c;
return o;
}
@fragment
fn fs(@location(0) c: vec3f) -> @location(0) vec4f {
return vec4f(c, 1.0);
}
`,
});
// Pipeline
const pipeline = device.createRenderPipeline({
layout: 'auto',
vertex: {
module: shader,
entryPoint: 'vs',
buffers: [{
arrayStride: 20,
attributes: [
{ shaderLocation: 0, offset: 0, format: 'float32x2' },
{ shaderLocation: 1, offset: 8, format: 'float32x3' },
],
}],
},
fragment: {
module: shader,
entryPoint: 'fs',
targets: [{ format }],
},
primitive: { topology: 'triangle-list' },
});
// Render
function frame() {
const encoder = device.createCommandEncoder();
const pass = encoder.beginRenderPass({
colorAttachments: [{
view: context.getCurrentTexture().createView(),
clearValue: { r: 0, g: 0, b: 0, a: 1 },
loadOp: 'clear',
storeOp: 'store',
}],
});
pass.setPipeline(pipeline);
pass.setVertexBuffer(0, vertexBuffer);
pass.draw(3);
pass.end();
device.queue.submit([encoder.finish()]);
requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
}
main();function handleResize() {
const dpr = Math.min(window.devicePixelRatio, 2);
canvas.width = canvas.clientWidth * dpr;
canvas.height = canvas.clientHeight * dpr;
// Context auto-reconfigure dengan size baru
}
window.addEventListener('resize', handleResize);
handleResize(); // initial| Aspek | WebGPU | WebGL 2.0 |
|---|---|---|
| Shading Language | WGSL | GLSL |
| API | Modern, explicit | Legacy, stateful |
| Compute Shaders | Ya (native) | Tidak (webgl-compute hack) |
| Performance | Lebih baik | Baik |
| Bind Model | Bind Groups (explicit) | Global state |
| Threading | Ya (Web Workers) | Tidak |
| Browser Support | Chrome 113+, Edge, Safari 18+ | Semua modern browsers |
| Maturity | Emerging | Mature |
// Error scope
device.pushErrorScope('validation');
// ... GPU operations ...
const error = await device.popErrorScope();
if (error) {
console.error('GPU error:', error.message);
}
// Uncaptured error handler
device.addEventListener('uncapturederror', (event) => {
console.error('Uncaptured GPU error:', event.error.message);
});
// Label objects untuk debugging
const buffer = device.createBuffer({
label: 'Vertex Buffer',
// ...
});
// Validate shader compilation
const shader = device.createShaderModule({ code });
const info = await shader.getCompilationInfo();
if (info.messages.length > 0) {
info.messages.forEach(msg => {
console.log(`${msg.type}: ${msg.message} (line ${msg.lineNum})`);
});
}Login atau daftar akun gratis untuk membaca cheat sheet ini.