Từ WebGL sang WebGPU

François Beaufort
François Beaufort

Là một nhà phát triển WebGL, bạn có thể vừa lo lắng vừa hào hứng khi bắt đầu sử dụng WebGPU, phiên bản kế nhiệm của WebGL, mang đến những tiến bộ của API đồ hoạ hiện đại cho web.

Bạn có thể yên tâm khi biết rằng WebGL và WebGPU đều có chung nhiều khái niệm cốt lõi. Cả hai API đều cho phép bạn chạy các chương trình nhỏ gọi là chương trình đổ bóng trên GPU. WebGL hỗ trợ chương trình đổ bóng đỉnh và mảnh, trong khi WebGPU cũng hỗ trợ chương trình đổ bóng điện toán. WebGL sử dụng Ngôn ngữ đổ bóng OpenGL (GLSL) còn WebGPU sử dụng Ngôn ngữ đổ bóng WebGPU (WGSL). Mặc dù hai ngôn ngữ này khác nhau, nhưng các khái niệm cơ bản hầu như giống nhau.

Do đó, bài viết này sẽ nêu bật một số điểm khác biệt giữa WebGL và WebGPU để giúp bạn bắt đầu.

Trạng thái toàn cục

WebGL có nhiều trạng thái chung. Một số chế độ cài đặt áp dụng cho tất cả các thao tác kết xuất, chẳng hạn như kết cấu và vùng đệm nào được liên kết. Bạn đặt trạng thái chung này bằng cách gọi nhiều hàm API và trạng thái này vẫn có hiệu lực cho đến khi bạn thay đổi. Trạng thái toàn cục trong WebGL là nguồn lỗi chính, vì bạn dễ quên thay đổi chế độ cài đặt toàn cục. Ngoài ra, trạng thái chung gây khó khăn cho việc chia sẻ mã, vì nhà phát triển cần phải cẩn thận để không vô tình thay đổi trạng thái chung theo cách ảnh hưởng đến các phần khác của mã.

WebGPU là một API phi trạng thái và không duy trì trạng thái chung. Thay vào đó, WebGL sử dụng khái niệm quy trình để đóng gói tất cả trạng thái kết xuất trên toàn cục. Quy trình chứa thông tin như kiểu kết hợp, cấu trúc liên kết và thuộc tính cần sử dụng. Một quy trình là bất biến. Nếu muốn thay đổi một số chế độ cài đặt, bạn cần tạo một quy trình khác. WebGPU cũng sử dụng trình mã hoá lệnh để kết hợp các lệnh với nhau và thực thi các lệnh đó theo thứ tự đã ghi. Điều này hữu ích trong việc lập bản đồ bóng, chẳng hạn như trong một lượt truyền qua các đối tượng, ứng dụng có thể ghi lại nhiều luồng lệnh, mỗi luồng lệnh cho một bản đồ bóng của ánh sáng.

Tóm lại, vì mô hình trạng thái toàn cầu của WebGL khiến việc tạo thư viện và ứng dụng mạnh mẽ, có khả năng kết hợp trở nên khó khăn và dễ vỡ, WebGPU đã giảm đáng kể lượng trạng thái mà các nhà phát triển cần theo dõi khi gửi lệnh tới GPU.

Không đồng bộ hoá nữa

Trên GPU, việc gửi lệnh và chờ lệnh một cách đồng bộ thường không hiệu quả vì điều này có thể làm sạch quy trình và gây ra bong bóng. Điều này đặc biệt đúng trong WebGPU và WebGL, sử dụng cấu trúc đa quy trình với trình điều khiển GPU chạy trong một quy trình riêng biệt với JavaScript.

Ví dụ: trong WebGL, việc gọi gl.getError() yêu cầu phải có một IPC đồng bộ từ quy trình JavaScript đến quy trình GPU và quay lại. Điều này có thể gây ra bong bóng ở phía CPU khi hai quy trình giao tiếp.

Để tránh những bong bóng này, WebGPU được thiết kế hoàn toàn không đồng bộ. Mô hình lỗi và tất cả các thao tác khác diễn ra không đồng bộ. Ví dụ: khi bạn tạo một hoạ tiết, thao tác này có vẻ như thành công ngay lập tức, ngay cả khi hoạ tiết đó thực sự là một lỗi. Bạn chỉ có thể khám phá lỗi không đồng bộ. Thiết kế này giúp bong bóng trò chuyện giao tiếp giữa các quy trình không bị gián đoạn và mang lại hiệu suất đáng tin cậy cho các ứng dụng.

Tính toán chương trình đổ bóng

Chương trình đổ bóng điện toán là các chương trình chạy trên GPU để thực hiện các phép tính chung. Các tính năng này chỉ có trong WebGPU, chứ không có trong WebGL.

Không giống như chương trình đổ bóng đỉnh và mảnh, chương trình đổ bóng này không chỉ giới hạn ở việc xử lý đồ hoạ mà còn có thể được dùng cho nhiều tác vụ, chẳng hạn như học máy, mô phỏng vật lý và tính toán khoa học. Chương trình đổ bóng điện toán được thực thi song song bởi hàng trăm hoặc thậm chí hàng nghìn luồng. Điều này giúp việc xử lý các tập dữ liệu lớn trở nên rất hiệu quả. Tìm hiểu về tính năng điện toán GPU và thông tin chi tiết hơn trong bài viết chuyên sâu này về WebGPU.

Xử lý khung hình video

Việc xử lý khung hình video bằng JavaScript và WebAssembly có một số hạn chế: chi phí sao chép dữ liệu từ bộ nhớ GPU sang bộ nhớ CPU và tính song song hạn chế có thể đạt được với worker và luồng CPU. WebGPU không có những hạn chế đó, nhờ đó, WebGPU rất phù hợp để xử lý khung hình video nhờ khả năng tích hợp chặt chẽ với API WebCodecs.

Đoạn mã sau đây cho biết cách nhập VideoFrame dưới dạng hoạ tiết bên ngoài trong WebGPU và xử lý video đó. Bạn có thể xem bản minh hoạ này.

// Init WebGPU device and pipeline...
// Configure canvas context...
// Feed camera stream to video...

(function render() {
  const videoFrame = new VideoFrame(video);
  applyFilter(videoFrame);
  requestAnimationFrame(render);
})();

function applyFilter(videoFrame) {
  const texture = device.importExternalTexture({ source: videoFrame });
  const bindgroup = device.createBindGroup({
    layout: pipeline.getBindGroupLayout(0),
    entries: [{ binding: 0, resource: texture }],
  });
  // Finally, submit commands to GPU
}

Khả năng di chuyển của ứng dụng theo mặc định

WebGPU buộc bạn phải yêu cầu limits. Theo mặc định, requestDevice() trả về một GPUDevice có thể không khớp với khả năng phần cứng của thiết bị thực, mà là một mẫu số chung hợp lý và thấp nhất của tất cả GPU. Bằng cách yêu cầu nhà phát triển yêu cầu giới hạn thiết bị, WebGPU đảm bảo rằng các ứng dụng sẽ chạy trên nhiều thiết bị nhất có thể.

Xử lý Canvas

WebGL tự động quản lý canvas sau khi bạn tạo một ngữ cảnh WebGL và cung cấp các thuộc tính ngữ cảnh như alpha, antialias, colorSpace, depth, preserveDrawingBuffer hoặc stencil.

Mặt khác, WebGPU yêu cầu bạn tự quản lý canvas. Ví dụ: để đạt được hiệu ứng khử răng cưa trong WebGPU, bạn sẽ tạo một hoạ tiết nhiều mẫu và kết xuất hoạ tiết đó. Sau đó, bạn sẽ phân giải hoạ tiết nhiều mẫu thành hoạ tiết thông thường và vẽ hoạ tiết đó lên canvas. Phương thức quản lý thủ công này cho phép bạn xuất ra nhiều canvas như bạn muốn từ một đối tượng GPUDevice. Ngược lại, WebGL chỉ có thể tạo một ngữ cảnh cho mỗi canvas.

Hãy xem bản minh hoạ WebGPU Multiple Canvases.

Ngoài ra, trình duyệt hiện có giới hạn về số lượng canvas WebGL trên mỗi trang. Tại thời điểm viết bài, Chrome và Safari chỉ có thể sử dụng tối đa 16 canvas WebGL đồng thời; Firefox có thể tạo tối đa 200 canvas. Mặt khác, không có giới hạn về số lượng canvas WebGPU trên mỗi trang.

Ảnh chụp màn hình cho thấy số lượng canvas WebGL tối đa trong trình duyệt Safari, Chrome và Firefox
Số lượng canvas tối đa WebGL trong Safari, Chrome và Firefox (từ trái sang phải) - bản minh hoạ.

Thông báo lỗi hữu ích

WebGPU cung cấp một ngăn xếp lệnh gọi cho mọi thông báo được trả về từ API. Điều này có nghĩa là bạn có thể nhanh chóng xem vị trí xảy ra lỗi trong mã, điều này rất hữu ích để gỡ lỗi và khắc phục lỗi.

Ngoài việc cung cấp ngăn xếp lệnh gọi, thông báo lỗi WebGPU cũng dễ hiểu và có thể hành động. Thông báo lỗi thường bao gồm nội dung mô tả lỗi và đề xuất cách khắc phục lỗi.

WebGPU cũng cho phép bạn cung cấp label tuỳ chỉnh cho mỗi đối tượng WebGPU. Sau đó, trình duyệt sẽ sử dụng nhãn này trong thông báo GPUError, cảnh báo bảng điều khiển và công cụ dành cho nhà phát triển trình duyệt.

Từ tên đến chỉ mục

Trong WebGL, nhiều thứ được kết nối bằng tên. Ví dụ: bạn có thể khai báo một biến đồng nhất có tên là myUniform trong GLSL và lấy thông tin vị trí của biến đó bằng gl.getUniformLocation(program, 'myUniform'). Điều này rất hữu ích khi bạn gặp lỗi nếu nhập sai tên của biến đồng nhất.

Mặt khác, trong WebGPU, mọi thứ đều được kết nối hoàn toàn bằng độ dời byte hoặc chỉ mục (thường được gọi là vị trí). Bạn có trách nhiệm đồng bộ hoá vị trí của mã trong WGSL và JavaScript.

Tạo mipmap

Trong WebGL, bạn có thể tạo mip cấp 0 của hoạ tiết, sau đó gọi gl.generateMipmap(). Sau đó, WebGL sẽ tạo tất cả các cấp mip khác cho bạn.

Trong WebGPU, bạn phải tự tạo mipmap. Không có hàm tích hợp nào để thực hiện việc này. Hãy xem cuộc thảo luận về thông số kỹ thuật để tìm hiểu thêm về quyết định này. Bạn có thể sử dụng các thư viện tiện dụng như webgpu-utils để tạo mipmap hoặc tìm hiểu cách tự thực hiện.

Vùng đệm lưu trữ và hoạ tiết lưu trữ

Cả WebGL và WebGPU đều hỗ trợ vùng đệm đồng nhất, đồng thời cho phép bạn truyền các tham số không đổi có kích thước giới hạn tới chương trình đổ bóng. Vùng đệm lưu trữ (storage buffer) trông rất giống vùng đệm đồng nhất (uniform buffer) và chỉ được WebGPU hỗ trợ. Vùng đệm lưu trữ mạnh mẽ và linh hoạt hơn vùng đệm đồng nhất.

  • Dữ liệu bộ đệm bộ nhớ được truyền đến chương trình đổ bóng có thể lớn hơn nhiều so với bộ đệm đồng nhất. Mặc dù thông số kỹ thuật cho biết các liên kết bộ đệm đồng nhất có thể có kích thước tối đa là 64 KB (xem maxUniformBufferBindingSize), nhưng kích thước tối đa của liên kết bộ đệm bộ nhớ phải ít nhất là 128 MB trong WebGPU (xem maxStorageBufferBindingSize).

  • Vùng đệm bộ nhớ có thể ghi và hỗ trợ một số thao tác nguyên tử, trong khi vùng đệm đồng nhất chỉ có thể đọc. Điều này cho phép triển khai các lớp thuật toán mới.

  • Liên kết bộ nhớ đệm lưu trữ hỗ trợ các mảng có kích thước thời gian chạy để các thuật toán linh hoạt hơn, trong khi kích thước mảng bộ nhớ đệm đồng nhất phải được cung cấp trong chương trình đổ bóng.

Kết cấu bộ nhớ chỉ được hỗ trợ trong WebGPU và là kết cấu của bộ đệm bộ nhớ đối với bộ đệm đồng nhất. Chúng linh hoạt hơn so với các hoạ tiết thông thường, hỗ trợ ghi truy cập ngẫu nhiên (và đọc trong tương lai).

Thay đổi về vùng đệm và kết cấu

Trong WebGL, bạn có thể tạo một vùng đệm hoặc kết cấu, sau đó thay đổi kích thước của vùng đệm hoặc kết cấu đó bất cứ lúc nào bằng gl.bufferData()gl.texImage2D() tương ứng.

Trong WebGPU, vùng đệm và hoạ tiết là không thể thay đổi. Điều này có nghĩa là bạn không thể thay đổi kích thước, cách sử dụng hoặc định dạng của các thẻ này sau khi tạo. Bạn chỉ có thể thay đổi nội dung của các thẻ này.

Sự khác biệt về quy ước không gian

Trong WebGL, phạm vi không gian cắt Z là từ -1 đến 1. Trong WebGPU, phạm vi không gian cắt Z là từ 0 đến 1. Điều này có nghĩa là các đối tượng có giá trị z là 0 sẽ ở gần máy ảnh nhất, trong khi các đối tượng có giá trị z là 1 sẽ ở xa nhất.

Hình minh hoạ các phạm vi không gian cắt Z trong WebGL và WebGPU.
Phạm vi không gian cắt Z trong WebGL và WebGPU.

WebGL sử dụng quy ước OpenGL, trong đó trục Y hướng lên và trục Z hướng về phía người xem. WebGPU sử dụng quy ước Metal, trong đó trục Y hướng xuống và trục Z hướng ra khỏi màn hình. Xin lưu ý rằng hướng trục Y là xuống trong toạ độ vùng đệm khung hình, toạ độ khung nhìn và toạ độ mảnh/pixel. Trong không gian clip, hướng trục Y vẫn hướng lên như trong WebGL.

Lời cảm ơn

Cảm ơn Corentin Wallez, Gregg Tavares, Stephen White, Ken Russell và Rachel Andrew đã đánh giá bài viết này.

Bạn cũng nên truy cập trang WebGPUFundamentals.org để tìm hiểu kỹ hơn về sự khác biệt giữa WebGPU và WebGL.