Từ WebGL sang WebGPU

François Beaufort
François Beaufort

Là nhà phát triển WebGL, có thể bạn sẽ vừa sợ hãi vừa thích thú khi bắt đầu sử dụng WebGPU, sản phẩm kế thừa của WebGL mang những tiến bộ của API đồ hoạ hiện đại lên 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ữ tạo bóng OpenGL (GLSL) trong khi WebGPU sử dụng Ngôn ngữ tạo 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.

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

Tiểu bang trên toàn cầu

WebGL có nhiều trạng thái chung. Một số chế độ cài đặt áp dụng cho tất 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 chung trong WebGL là nguyên nhân chính gây ra lỗi, vì người dùng dễ quên khi thay đổi chế độ cài đặt chung. 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 đó, nó sử dụng khái niệm quy trình để đóng gói tất cả trạng thái kết xuất có chung trong WebGL. Quy trình chứa thông tin như cách kết hợp, cấu trúc liên kết và thuộc tính nào 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 phải tạo một quy trình khác. WebGPU cũng sử dụng bộ mã hoá lệnh để phân nhóm các lệnh với nhau và thực thi chúng theo thứ tự được ghi lại. Điều này rất hữu ích trong việc lập bản đồ bóng, chẳng hạn như trong một lần 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 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, thường không hiệu quả khi gửi lệnh và đợi lệnh một cách đồng bộ, vì điều này có thể làm xoá quy trình và gây ra bong bóng trò chuyện. Điều này đặc biệt đúng với WebGPU và WebGL, vốn sử dụng kiến trúc đa tiến trình với trình điều khiển GPU chạy trong một tiến 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 2 quy trình giao tiếp với nhau.

Để tránh các 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 hoạ tiết, thao tác sẽ 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 không có bong bóng giao tiếp giữa các quy trình và mang lại hiệu suất đáng tin cậy cho ứ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 đa năng. Chúng chỉ có trong WebGPU, không có trong WebGL.

Không giống như chương trình đổ bóng mảnh và đỉnh (vertex), không chỉ xử lý đồ hoạ, mà còn có thể dùng cho nhiều công việc, chẳng hạn như học máy, mô phỏng vật lý và điện 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ả. Bạn có thể 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ế đó nên phù hợp để xử lý khung hình video nhờ 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ý khung hình đó. 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à trả về mẫu số chung thấp nhất và hợp lý trong tất cả các GPU. Bằng cách đòi hỏi các nhà phát triển phải 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 ngữ cảnh WebGL và cung cấp các thuộc tính ngữ cảnh như alpha, khử răng cưa, colorSpace, depth, keepDrawingBuffer 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 cần tạo một hoạ tiết đa 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 rồi vẽ hoạ tiết đó vào canvas. Việc quản lý thủ công này cho phép bạn xuất ra số lượng canvas tuỳ ý 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.

Xem bản minh hoạ về nhiều Canvas của WebGPU.

Ngoài ra, các 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 này, Chrome và Safari chỉ có thể sử dụng tối đa 16 canvas WebGL cùng lúc; 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 tối đa WebGL 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 họa.

Các 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 API trả về. Điều này có nghĩa là bạn có thể nhanh chóng biết được vị trí xảy ra lỗi trong mã của mình, điều này hữu ích cho việc gỡ lỗi và sửa lỗi.

Bên cạnh việc cung cấp ngăn xếp cuộc gọi, các thông báo lỗi WebGPU cũng rất dễ hiểu và dễ xử lý. 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 một label tuỳ chỉnh cho từng đối tượng WebGPU. Sau đó, trình duyệt sẽ sử dụng nhãn này trong các thông báo GPUError, cảnh báo trên bảng điều khiển và công cụ 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 theo 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ứ được kết nối hoàn toàn bằng độ lệch hoặc chỉ mục byte (thường được gọi là location). Bạn có trách nhiệm đồng bộ hoá các vị trí cho mã trong WGSL và JavaScript.

Tạo Mipmap

Trong WebGL, bạn có thể tạo cấp độ hoạ tiết 0 mip rồi 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 sẵn nào để thực hiện việc nà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. 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à kết cấu 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ữ, trông khá giống vùng đệm đồng nhất, chỉ được WebGPU hỗ trợ và mạnh mẽ và linh hoạt hơn so với vùng đệm đồng nhất.

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

  • Vùng đệm lưu trữ có thể ghi và hỗ trợ một số hoạt động nguyên tử trong khi các vùng đệm đồng nhất chỉ ở chế độ chỉ đọ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 vùng đệm lưu trữ hỗ trợ các mảng có kích thước thời gian chạy cho các thuật toán linh hoạt hơn, trong khi kích thước mảng vùng đệm đồng nhất phải được cung cấp trong chương trình đổ bóng.

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

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

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

Trong WebGPU, vùng đệm và kết cấu 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 chúng sau khi đã tạo. Bạn chỉ có thể thay đổi nội dung của các thư mục đó.

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

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

Hình minh hoạ dải không gian đoạn video Z trong WebGL và WebGPU.
Dải 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ề người xem. WebGPU sử dụng quy ước Kim loại, trong đó trục Y hướng xuống còn trục Z nằm ngoài màn hình. Lưu ý rằng hướng trục Y hướng xuống trong toạ độ vùng đệm khung, toạ độ khung nhìn và toạ độ mảnh/pixel. Trong không gian clip, hướng trục Y vẫn ở trên như trong WebGL.

Xác nhậ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 sâu hơn về sự khác biệt giữa WebGPU và WebGL.