WebGPU Cheat Sheet
Referensi cepat WebGPU API. Adapter, device, pipelines, buffers, textures, bind groups, shaders WGSL, dan compute passes. Perfect buat developer yang eksplor graphics programming modern.
JavaScript9 min read1.692 kata Silakan
login atau
daftar untuk membaca cheat sheet ini.
Baca Cheat Sheet Lengkap
Login atau daftar akun gratis untuk membaca cheat sheet ini.
WebGPU Cheat Sheet - BelajarKoding | BelajarKoding#Inisialisasi
#Adapter dan Device
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 };
}
#Buffers
#Create Buffer
// Vertex buffer (static data)
const vertexData = new Float32Array([
// position (x, y, z), color (r, g, b, a)
0.0, 0.5,
#Update Buffer
// 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.
#Textures
#Create Texture
const texture = device.createTexture({
size: [512, 512, 1],
format: 'rgba8unorm',
usage: GPUTextureUsage.TEXTURE_BINDING |
GPUTextureUsage.
#Sampler
const sampler = device.createSampler({
addressModeU: 'repeat', // 'clamp-to-edge' | 'repeat' | 'mirror-repeat'
addressModeV: 'repeat',
addressModeW: 'repeat',
magFilter: 'linear', // 'nearest' | 'linear'
minFilter: 'linear',
#WGSL (WebGPU Shading Language)
#Basic Vertex + Fragment Shader
// Vertex shader output struct
struct VertexOutput {
@builtin(position) position: vec4f,
@location(0) color: vec3f,
};
// Vertex shader
@vertex
fn vs_main
#Uniform Buffer di Shader
// Define uniform struct
struct Uniforms {
modelMatrix: mat4x4f,
viewMatrix: mat4x4f,
projectionMatrix: mat4x4f,
};
@group(0) @binding(0)
#Texture Sampling di Shader
@group(0) @binding(1) var tex: texture_2d<f32>;
@group(0) @binding(2) var samp: sampler;
@fragment
fn
#Compute Shader
@group(0) @binding(0) var<storage, read> inputData: array<f32>;
@group(0) @binding(
#Bind Group Layout dan Bind Group
// Define bind group layout
const bindGroupLayout = device.createBindGroupLayout({
entries: [
{
binding: 0,
visibility: GPUShaderStage.VERTEX | GPUShaderStage.
#Render Pipeline
const shaderModule = device.createShaderModule({
code: wgslCode, // WGSL string from above
});
#Render Pass
function render() {
// Create command encoder
const encoder = device.createCommandEncoder
#Compute Pipeline
const computePipeline = device.createComputePipeline({
layout: device.createPipelineLayout({
bindGroupLayouts: [computeBindGroupLayout],
}),
compute: {
module: shaderModule,
entryPoint: 'cs_main',
#Full Triangle Example
#Canvas Resize
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.
#WebGPU vs WebGL
| 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 |
#Debugging
// Error scope
device.pushErrorScope('validation');
// ... GPU operations ...
const error = await device.popErrorScope();
if (error) {
console.error(
#Glossary
- Adapter: Representasi physical GPU. Dipilih via requestAdapter().
- Device: Logical GPU context. Setiap app butuh device sendiri.
- Queue: Tempat submit GPU commands. device.queue untuk operasi write.
- Pipeline: Pre-compiled state object (render atau compute). Mahal di-create, murah di-use.
- Bind Group: Binding antara shader resources (buffers, textures) dan actual GPU resources.
- Buffer: Blok memory di GPU. Punya usage flags (vertex, uniform, storage, dst).
- Texture: 2D/3D image data di GPU. Punya format dan usage flags.
- Sampler: Object yang define cara sample texture (filtering, wrapping).
- WGSL: WebGPU Shading Language. Bahasa shader untuk WebGPU.
- Workgroup: Unit eksekusi di compute shader. workgroup_size define threads per workgroup.
- Command Encoder: Object yang record GPU commands untuk kemudian di-submit sekaligus.
- Render Pass: Session yang render ke texture targets (color attachments, depth attachment).
- Compute Pass: Session yang run compute shaders tanpa rendering output.
- Bind Group Layout: Template untuk bind group. Define tipe resource per binding slot.
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();
writeBuffer
(dynamicBuffer, dynamicOffset, data);
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]
);
mipmapFilter: 'linear',
lodMinClamp: 0,
lodMaxClamp: 32,
compare: 'less', // untuk shadow maps
});
(
@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);
}
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;
}
fs_main
(
@
location
(
0
) uv:
vec2f
)
->
@
location
(
0
)
vec4f
{
return textureSample(tex, samp, uv);
}
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;
}
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 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',
},
});
();
// 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);
},
});
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()]);
}
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();
addEventListener
(
'resize'
, handleResize);
handleResize(); // initial
'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})`);
});
}