This programming guide is only available in Vietnamese. Switch the language toggle to Vietnamese to read the full article.
Sau 17 bài, chúng ta đã đi từ tam giác đầu tiên đến PBR, đổ bóng, IBL và tối ưu hiệu năng — tất cả trên nền WebGL. Nhưng WebGL, dù mạnh mẽ, vẫn mang trên mình di sản của một thiết kế từ thập niên 1990. Bài cuối cùng của series này giới thiệu WebGPU — API đồ họa thế hệ mới của web, được thiết kế lại từ đầu để khai thác kiến trúc GPU hiện đại, mở khóa compute shader thực thụ và đặt nền móng cho đồ họa, GPGPU và machine learning trong trình duyệt suốt thập kỷ tới.
1. Vì sao cần WebGPU? Giới hạn cố hữu của WebGL
WebGL là một ánh xạ gần như trực tiếp của OpenGL ES 2.0/3.0 — bản thân nó là một API
đã hơn hai thập kỷ tuổi. Mô hình lập trình của nó dựa trên một
state machine (máy trạng thái) toàn cục khổng lồ: bạn liên tục gọi
gl.bindBuffer, gl.bindTexture, gl.enable… để bật/tắt trạng thái
ẩn, rồi mới gl.draw*. Cách thiết kế này sinh ra ba vấn đề lớn:
- Chi phí điều khiển (CPU overhead) cao: Mỗi lệnh vẽ phải kiểm tra và xác thực lại toàn bộ trạng thái ẩn ngay khi gọi. Điều này khiến driver tốn nhiều CPU, gây nghẽn cổ chai khi cảnh có hàng nghìn draw call.
- Khó song song hóa: State machine toàn cục buộc mọi thứ chạy tuần tự trên một luồng. Không có khái niệm ghi sẵn lệnh (command buffer) để gửi hàng loạt như các API gốc hiện đại.
- Không có compute shader thực thụ: WebGL 2.0 thiếu hẳn compute shader. Mọi tính toán tổng quát (GPGPU) phải "gian lận" qua fragment shader và texture — vừa rườm rà vừa kém hiệu quả.
Trong khi đó, các API gốc hiện đại — Vulkan (Khronos), Metal (Apple) và Direct3D 12 (Microsoft) — đã chuyển sang mô hình explicit: lập trình viên ghi sẵn lệnh vào command buffer, quản lý tài nguyên tường minh và gửi cả khối công việc lên GPU. WebGPU chính là một lớp trừu tượng an toàn, đa nền tảng nằm trên ba API này — viết một lần, chạy trên Vulkan ở Linux, Metal trên macOS/iOS và D3D12 trên Windows.
WebGPU → Vulkan / Metal / D3D12 (explicit, command buffer, có compute)
// WebGL: state machine — mỗi lệnh thay đổi trạng thái ẩn toàn cục
gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
gl.useProgram(program);
gl.enableVertexAttribArray(0);
gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0);
gl.drawArrays(gl.TRIANGLES, 0, 3); // driver xác thực lại mọi thứ ngay tại đây
// WebGPU: ghi sẵn lệnh vào command buffer rồi gửi cả khối lên Queue
const encoder = device.createCommandEncoder();
const pass = encoder.beginRenderPass({ /* ... */ });
pass.setPipeline(pipeline); // pipeline đã gói sẵn toàn bộ trạng thái
pass.setVertexBuffer(0, vbo);
pass.draw(3);
pass.end();
device.queue.submit([encoder.finish()]); // gửi 1 lần, song song hóa tốt
2. Kiến trúc WebGPU: Adapter, Device, Queue, Pipeline
WebGPU tổ chức công việc quanh một chuỗi đối tượng rõ ràng, mỗi đối tượng có trách nhiệm riêng. Hiểu các khối này là chìa khóa để đọc bất kỳ đoạn code WebGPU nào:
-
Adapter: Đại diện cho một GPU vật lý trên máy (ví dụ card rời, card tích hợp). Bạn
xin adapter từ
navigator.gpu.requestAdapter()và có thể chọn ưu tiên hiệu năng cao hay tiết kiệm pin. - Device: Một kết nối logic tới adapter — là "cánh cổng" để tạo mọi tài nguyên (buffer, texture, pipeline, shader). Mọi lỗi cấp thiết bị đều được báo qua device.
- Queue: Hàng đợi để gửi command buffer và ghi dữ liệu lên GPU. Đây là điểm duy nhất nơi công việc thực sự được giao cho GPU thực thi.
-
Command Encoder: Đối tượng ghi lại một chuỗi lệnh (render pass, compute pass, sao
chép buffer) vào một command buffer, sau đó
finish()để gửi vào Queue. - Render / Compute Pipeline: Gói toàn bộ trạng thái đồ họa (shader, định dạng vertex, blend, depth…) thành một đối tượng bất biến (immutable). Khác hẳn WebGL — không còn thay đổi trạng thái rời rạc nữa.
- Bind Group: Nhóm tài nguyên (uniform buffer, texture, sampler) được "gắn" vào shader theo từng nhóm. Thay cho cơ chế bind từng uniform rời rạc của WebGL.
So với WebGL nơi gl vừa là context vừa là kho trạng thái toàn cục, WebGPU phân tách trách
nhiệm rạch ròi. Bước đầu tiên của mọi ứng dụng là xin adapter và device — và đây là code bất đồng bộ
(asynchronous) vì việc dò GPU cần thời gian:
async function initWebGPU(canvas) {
// 0. Kiểm tra hỗ trợ — WebGPU có thể chưa tồn tại trên trình duyệt cũ
if (!navigator.gpu) throw new Error('Trình duyệt chưa hỗ trợ WebGPU');
// 1. Xin Adapter (GPU vật lý)
const adapter = await navigator.gpu.requestAdapter({
powerPreference: 'high-performance',
});
if (!adapter) throw new Error('Không tìm thấy GPU adapter phù hợp');
// 2. Xin Device (kết nối logic — cổng tạo mọi tài nguyên)
const device = await adapter.requestDevice();
// 3. Cấu hình context của canvas
const context = canvas.getContext('webgpu');
const format = navigator.gpu.getPreferredCanvasFormat();
context.configure({ device, format, alphaMode: 'premultiplied' });
return { device, context, format };
}
Có device rồi, ta dựng một render pipeline. Toàn bộ trạng thái — shader nào, vertex/fragment entry point, định dạng màu đầu ra, topology — được khai báo một lần và đóng băng thành đối tượng bất biến. GPU nhờ vậy không phải xác thực lại mỗi lệnh vẽ:
const module = device.createShaderModule({ code: wgslSource });
const pipeline = device.createRenderPipeline({
layout: 'auto',
vertex: {
module,
entryPoint: 'vs_main', // hàm gắn @vertex trong WGSL
},
fragment: {
module,
entryPoint: 'fs_main', // hàm gắn @fragment
targets: [{ format }], // định dạng màu khớp với canvas
},
primitive: { topology: 'triangle-list' },
});
// pipeline giờ là bất biến — tái dùng cho mọi frame, không xác thực lại
3. WGSL — ngôn ngữ shader mới của web
WebGL dùng GLSL. WebGPU giới thiệu một ngôn ngữ shader hoàn toàn mới: WGSL (WebGPU Shading Language). Lý do không chỉ là sở thích: WGSL được thiết kế để biên dịch an toàn và xác định (deterministic) sang SPIR-V (Vulkan), MSL (Metal) và HLSL (D3D12), đồng thời tránh các hành vi không xác định vốn gây lỗ hổng bảo mật trong GLSL.
Khác biệt chính so với GLSL: WGSL dùng cú pháp kiểu Rust với
thuộc tính (attribute) bắt đầu bằng @. Hàm vertex được đánh dấu
@vertex, fragment là @fragment; vị trí vertex và đầu ra màu khai báo tường
minh qua @builtin(position) và @location(0). Tài nguyên (uniform, texture)
gắn qua @group và @binding — khớp trực tiếp với khái niệm Bind Group ở phần
2. Biến cũng khai báo tường minh kiểu với var/let và
struct thay cho các vector ngầm định của GLSL.
// Struct mô tả dữ liệu truyền từ vertex sang fragment
struct VertexOut {
@builtin(position) position : vec4<f32>,
@location(0) color : vec3<f32>,
};
// Uniform gắn qua @group/@binding — tương ứng Bind Group ở phía JS
@group(0) @binding(0) var<uniform> uAngle : f32;
@vertex
fn vs_main(@builtin(vertex_index) vIndex : u32) -> VertexOut {
// Ba đỉnh của tam giác, định nghĩa ngay trong shader
var positions = array<vec2<f32>, 3>(
vec2<f32>( 0.0, 0.6),
vec2<f32>(-0.6, -0.6),
vec2<f32>( 0.6, -0.6),
);
var colors = array<vec3<f32>, 3>(
vec3<f32>(1.0, 0.2, 0.3),
vec3<f32>(0.2, 0.9, 0.4),
vec3<f32>(0.2, 0.4, 1.0),
);
let s = sin(uAngle);
let c = cos(uAngle);
let p = positions[vIndex];
let rotated = vec2<f32>(p.x * c - p.y * s, p.x * s + p.y * c);
var out : VertexOut;
out.position = vec4<f32>(rotated, 0.0, 1.0);
out.color = colors[vIndex];
return out;
}
@fragment
fn fs_main(in : VertexOut) -> @location(0) vec4<f32> {
return vec4<f32>(in.color, 1.0);
}
4. Demo tương tác: tam giác xoay bằng WebGPU thật
Demo dưới đây cố gắng khởi tạo một pipeline WebGPU thật và vẽ một tam giác gradient xoay
tròn, dùng đúng shader WGSL ở phần 3. Vì WebGPU còn mới, demo được viết
phòng thủ tuyệt đối: nếu trình duyệt của bạn chưa hỗ trợ
navigator.gpu hoặc gặp lỗi khởi tạo, nó sẽ tự động chuyển sang vẽ cùng tam giác đó bằng
Canvas 2D — bạn vẫn thấy hình, không bao giờ gặp màn hình trắng:
5. Compute Shaders & tương lai của web GPU
Tính năng "đổi cuộc chơi" lớn nhất của WebGPU là compute shader — thứ WebGL chưa bao giờ có. Compute shader cho phép chạy code song song khối lượng lớn trên GPU mà không liên quan gì đến vẽ hình: đây là GPGPU (General-Purpose GPU) thực thụ ngay trong trình duyệt. Ứng dụng mở ra cực rộng:
- Hệ hạt (particle systems) hàng triệu phần tử: Cập nhật vị trí, vận tốc của hàng triệu hạt trực tiếp trên GPU mỗi frame — điều bất khả thi mượt mà với WebGL.
- Machine Learning trong trình duyệt: Các thư viện như TensorFlow.js và ONNX Runtime Web đã có backend WebGPU, tăng tốc suy luận mạng nơ-ron gấp nhiều lần so với backend WebGL.
- Mô phỏng vật lý, xử lý ảnh, sắp xếp, giảm (reduction): Mọi bài toán dữ liệu song song đều có thể đẩy lên GPU qua một compute pass.
Compute shader trong WGSL được đánh dấu bằng @compute và khai báo kích thước nhóm làm
việc qua @workgroup_size. Mỗi lần thực thi (invocation) xử lý một phần tử dữ liệu, định
danh bằng @builtin(global_invocation_id):
struct Particle {
pos : vec2<f32>,
vel : vec2<f32>,
};
// storage buffer đọc-ghi: dữ liệu hạt nằm trên GPU
@group(0) @binding(0) var<storage, read_write> particles : array<Particle>;
@group(0) @binding(1) var<uniform> dt : f32;
@compute @workgroup_size(64)
fn cs_main(@builtin(global_invocation_id) gid : vec3<u32>) {
let i = gid.x;
if (i >= arrayLength(&particles)) { return; } // chống tràn chỉ số
var p = particles[i];
p.pos = p.pos + p.vel * dt; // tích phân Euler đơn giản
// nảy lại khi chạm biên [-1, 1]
if (p.pos.x < -1.0 || p.pos.x > 1.0) { p.vel.x = -p.vel.x; }
if (p.pos.y < -1.0 || p.pos.y > 1.0) { p.vel.y = -p.vel.y; }
particles[i] = p; // ghi thẳng lại storage buffer trên GPU
}
Lộ trình hỗ trợ trình duyệt: WebGPU đã chính thức ra mắt ổn định trên Chrome và Edge (từ phiên bản 113, năm 2023) trên Windows, macOS và ChromeOS, sau đó mở rộng sang Android và Linux. Safari đã bật WebGPU mặc định từ Safari 18 (2024–2025), còn Firefox đang triển khai dần (bật mặc định trên Windows, lan ra các nền tảng khác). Tóm lại: hỗ trợ đã rộng nhưng chưa phổ cập 100% như WebGL — đó là lý do mã sản phẩm vẫn cần feature detection và fallback như demo phía trên.
Khi nào dùng WebGPU, khi nào dùng WebGL? Hãy chọn WebGL khi bạn cần tương thích tối đa với mọi thiết bị cũ, hoặc dự án nhỏ chỉ vẽ 2D/3D cơ bản và đã có sẵn hệ sinh thái (Three.js cổ điển). Hãy chọn WebGPU khi bạn cần compute shader (GPGPU/ML), hiệu năng cao với hàng nghìn draw call, hoặc muốn đầu tư cho tương lai dài hạn. Đáng mừng là Three.js, Babylon.js và nhiều engine đã có renderer WebGPU, nên bạn có thể chuyển dần mà không phải viết lại từ đầu. Đây cũng là điểm khép lại hành trình 18 bài của chúng ta: bạn đã nắm vững WebGL và sẵn sàng bước sang kỷ nguyên WebGPU.
6. Câu hỏi trắc nghiệm ôn tập
Trắc nghiệm 1: Động lực ra đời WebGPU
Đâu là khả năng quan trọng mà WebGPU mang lại nhưng WebGL 2.0 hoàn toàn thiếu?
Trắc nghiệm 2: Kiến trúc WebGPU
Trong WebGPU, đối tượng nào là "cánh cổng" để tạo ra mọi tài nguyên như buffer, texture và pipeline?
Trắc nghiệm 3: Ngôn ngữ WGSL
Trong WGSL, cặp thuộc tính @group(0) @binding(0) đặt trước một biến uniform có vai trò
gì?
Tải mã nguồn thực hành
File chứa khởi tạo WebGPU đầy đủ, shader WGSL tam giác xoay và bộ khung compute shader để bạn tự thử nghiệm:
Tải về mã nguồn WebGPU mẫu
Comments
Bình luận