Từ WebGL sang WebGPU

François Beaufort
François Beaufort

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

Bạn sẽ yên tâm khi biết rằng WebGL và WebGPU 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ỏ được gọi là chương trình đổ bóng trên GPU. WebGL hỗ trợ 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ô bóng WebGPU (WGSL). Mặc dù hai ngôn ngữ là khác nhau, nhưng các khái niệm cơ bản hầu hết đều giống nhau.

Vì lý do đó, bài viết này 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 chung

WebGL có nhiều trạng thái trên toàn cục. Một số chế độ cài đặt áp dụng cho mọi hoạt động 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à một nguồn lỗi chính, vì rất dễ quên thay đổi cài đặt chung. Ngoài ra, trạng thái chung khiến việc chia sẻ mã trở nên khó khăn vì các 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 độc lập (stateless) và không duy trì trạng thái toàn cầu. Thay vào đó, công cụ này sử dụng khái niệm quy trình để đóng gói tất cả trạng thái kết xuất chung trong WebGL. Một quy trình chứa các thông tin như cách kết hợp, cấu trúc liên kết và các thuộc tính sẽ sử dụng. 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 bộ mã hoá lệnh để nhóm các lệnh với nhau và thực thi các lệnh theo thứ tự được ghi lại. Điều này rất hữu ích trong việc ánh xạ bóng đổ, chẳng hạn như khi 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ột luồng cho mỗi bản đồ bóng của ánh sáng.

Tóm lại, vì mô hình trạng thái chung 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ễ bị tổn thương, nên WebGPU đã giảm đáng kể lượng trạng thái mà các nhà phát triển cần theo dõi trong khi gửi lệnh tới GPU.

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

Trên GPU, việc gửi các lệnh và chờ chúng một cách đồng bộ thường không hiệu quả, vì điều này có thể làm nổ quy trình và gây ra bong bóng trò chuyện. Điều này đặc biệt đúng trong WebGPU và WebGL, những thiết bị 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 quy trình riêng biệt với JavaScript.

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

Để 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 hoạ tiết, thao tác có vẻ thành công ngay lập tức, ngay cả khi hoạ tiết đó thực sự là lỗi. Bạn chỉ có thể phát hiện lỗi một cách không đồng bộ. Thiết kế này giúp hoạt động giao tiếp giữa các quá trình không có bong bóng 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 cho mục đích chung. Các định dạng này chỉ có trong WebGPU, không có sẵn trong WebGL.

Không giống như chương trình đổ bóng đỉnh (vertex) và mảnh (vertex), chúng không giới hạn ở việc xử lý đồ hoạ và có thể dùng cho nhiều loại nhiệm vụ, chẳng hạn như máy học, 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 hàng trăm hoặc thậm chí hàng nghìn luồng thực thi song song nên rất hiệu quả trong việc xử lý các tập dữ liệu lớn. Tìm hiểu về điện toán GPU và thông tin chi tiết hơn trong bài viết mở rộng này về WebGPU.

Xử lý khung hình video

Việc xử lý khung 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à khả năng tải 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ế đó, vì vậy, nó rất phù hợp để xử lý khung hình video nhờ được tích hợp chặt chẽ với API WebCodecs.

Đoạn mã sau đây cho biết cách nhập một VideoFrame làm hoạ tiết bên ngoài trong WebGPU và xử lý nó. 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 yêu cầu limits. Theo mặc định, requestDevice() trả về một GPUDevice có thể không khớp với chức năng phần cứng của thiết bị thực tế, mà là mẫu số chung hợp lý và thấp nhất trong tất cả GPU. Bằng cách yêu cầu các 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 ngữ cảnh WebGL và cung cấp các thuộc tính ngữ cảnh như alpha, tính năng khử răng cưa, colorSpace, độ sâu, keepDrawingBuffer hoặc stencil.

Mặt khác, WebGPU yêu cầu bạn tự quản lý canvas. Ví dụ: để 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 đa mẫu thành hoạ tiết thông thường và vẽ hoạ tiết đó lên canvas. Cách quản lý thủ công này cho phép bạn xuất ra số lượng canvas tuỳ thích từ một đối tượng GPUDevice (thiết bị GPU). 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 nhiều Canvas.

Xin lưu ý rằng 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, 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 trong số đó. 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 WebGL tối đa trong Safari, Chrome và Firefox (từ trái sang phải) – bản minh họa.

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

WebGPU cung cấp 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 thấy lỗi xảy ra trong mã của mình. Điều này hữu ích khi gỡ lỗi và sửa lỗi.

Bên cạnh việc cung cấp ngăn xếp lệnh gọi, các thông báo lỗi WebGPU cũng dễ hiểu và dễ xử lý. Các thông báo lỗi thường bao gồm nội dung mô tả về lỗi và các đề xuất về cách sửa lỗi.

WebGPU cũng cho phép bạn cung cấp label tuỳ chỉnh cho từng đối tượng WebGPU. Sau đó, trình duyệt sử dụng nhãn này trong thông báo lỗi GPU, cảnh báo trên bảng điều khiển và công cụ cho nhà phát triển của 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 vị trí của biến đó bằng cách sử dụ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 theo độ lệch byte hoặc chỉ mục (thường được gọi là vị trí). 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 mip cấp 0 của hoạ tiết 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 nào để thực hiện việc này. Hãy xem nội dung 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ự làm.

Vùng đệm lưu trữ và kết cấu bộ nhớ

Cả WebGL và WebGPU đều hỗ trợ vùng đệm đồng nhất và cho phép bạn truyền các tham số không đổi có kích thước giới hạn đến chương trình đổ bóng. Các vùng đệm lưu trữ trông giống như các vùng đệm đồng nhất, chỉ được WebGPU hỗ trợ và mạnh mẽ và linh hoạt hơn các 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ù quy cách 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 64 KB (xem maxUniformBufferBindingSize) , nhưng kích thước tối đa của một liên kết vùng đệm lưu trữ trong WebGPU tối thiểu là 128 MB (xem maxStorageBufferBindingSize).

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

  • Các 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 để 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.

Kết cấu lưu trữ chỉ được hỗ trợ trong WebGPU và tạo kết cấu cho vùng đệm lưu trữ để thống nhất vùng đệm. Các API này linh hoạt hơn so với hoạ tiết thông thường, hỗ trợ khả năng ghi truy cập ngẫu nhiên (và cũng có thể đọc trong tương lai).

Thay đổi vùng đệm và hoạ tiết

Trong WebGL, bạn có thể tạo vùng đệm hoặc hoạ tiết, sau đó thay đổi kích thước của vùng đệm hoặc hoạ tiết đó 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à bất biến. Đ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 sau khi tạo. Bạn chỉ có thể thay đổi nội dung của các lời nhắc đó.

Điểm 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 là 1 ở xa nhất.

Hình minh hoạ phạm vi không gian đoạn video Z trong WebGL và WebGPU.
Phạm vi không gian đoạn video 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 và trục Z nằm ngoài màn hình. Lưu ý rằng hướng trục Y thấp hơn trong toạ độ vùng đệm khung, toạ độ khung nhìn và toạ độ mảnh/pixel. Trong không gian cắt, hướng trục Y vẫn hướng lên như trong WebGL.

Xác nhận

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

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