HTML trong canvas WebGPU

Việc kết xuất HTML bên trong canvas WebGPU mang lại tất cả lợi ích mà HTML trong canvas WebGL mang lại, chẳng hạn như áp dụng hiệu ứng chương trình đổ bóng cho DOM hoặc tích hợp HTML tương tác vào cảnh 3D. Tuy nhiên, WebGPU mang lại nhiều lợi ích đáng kể so với WebGL.

Hướng dẫn này minh hoạ cách kết xuất HTML vào canvas WebGPU. Tìm hiểu thêm về HTML-in-Canvas trong phần tổng quan về API.

Để kết xuất HTML trong canvas WebGPU, bạn cần thực hiện các bước sau:

  1. Chuẩn bị canvas. Đặt các thuộc tính phù hợp một cách rõ ràng để cho trình duyệt biết rằng trình duyệt nên kết xuất HTML vào một canvas. Cách thiết lập này giống hệt nhau trong các bối cảnh 2D, WebGL và WebGPU. Tìm hiểu cách thiết lập canvas và cách khai báo các phần tử HTML cần được vẽ độc lập.
  2. Áp dụng HTML làm hoạ tiết:
    1. Phân bổ trước bộ nhớ kết cấu GPU.
    2. Tải ảnh chụp nhanh DOM lên dưới dạng hoạ tiết.
  3. Đồng bộ hoá DOM và pixel.

Vẽ một phần tử HTML dưới dạng kết cấu WebGPU

Trong WebGPU, các phần tử HTML được tải lên GPU dưới dạng kết cấu 2D bằng drawElementImageToTexture().

Phân bổ trước bộ nhớ hoạ tiết

WebGPU yêu cầu phân bổ bộ nhớ rõ ràng. Trước khi sao chép pixel DOM vào hoạ tiết, bạn phải đặt trước VRAM GPU bằng phương thức device.createTexture():

const context = canvas.getContext("webgpu");

// Pre-allocate the texture size as needed
// For example: HTML element CSS size × devicePixelRatio
const size = canvas.getElementImageDefaultSize(myElement);
const myElementWidth = Math.ceil(size.width);
const myElementHeight = Math.ceil(size.height);

// Pre-allocate the GPUTexture backing (run once during setup)
const myTexture = device.createTexture({
  size: {
    width: myElementWidth,
    height: myElementHeight,
    depthOrArrayLayers: 1, // optional (defaults to 1)
  },
  format: "rgba8unorm",
  usage:
    GPUTextureUsage.TEXTURE_BINDING |
    GPUTextureUsage.COPY_DST |
    GPUTextureUsage.RENDER_ATTACHMENT,
});

Vẽ hình ảnh phần tử HTML

Trong WebGPU, phương thức drawElementImageToTexture() sẽ vẽ ảnh chụp nhanh của phần tử HTML. Phương thức này nhận hai đối tượng từ điển: source và destination.

source xác định phần tử DOM nguồn cần tạo điểm ảnh và toạ độ cắt hình chữ nhật phụ không bắt buộc, cho phép bạn chỉ sao chép một phần của phần tử vào hoạ tiết thay vì toàn bộ nút DOM:

Thuộc tính Loại Mô tả
Cần có source Element hoặc ElementImage Phần tử DOM mục tiêu (hoặc thực thể ElementImage được chụp trước) để vẽ vào hoạ tiết.
sourceX không bắt buộc (tất cả bốn hoặc không có) số thực dấu phẩy động Độ lệch toạ độ X theo chiều ngang (tính bằng pixel CSS) xác định góc trên cùng bên trái của lát cắt hình chữ nhật phụ nguồn.
sourceY không bắt buộc (tất cả bốn hoặc không có) số thực dấu phẩy động Độ lệch toạ độ Y dọc (tính bằng pixel CSS) xác định góc trên cùng bên trái của lát cắt hình chữ nhật phụ nguồn.
sourceWidth không bắt buộc (tất cả bốn hoặc không có) số thực dấu phẩy động Chiều rộng (tính bằng pixel CSS) của hình chữ nhật con nguồn để cắt và tạo điểm ảnh.
sourceHeight không bắt buộc (tất cả bốn hoặc không có) số thực dấu phẩy động Chiều cao (tính bằng pixel CSS) của hình chữ nhật con nguồn cần cắt và tạo điểm ảnh.

destination xác định hoạ tiết GPU mục tiêu nhận ảnh chụp nhanh DOM được kết xuất, cùng với độ lệch đích, cấp độ mip và kích thước sao chép không bắt buộc để đặt các pixel vào một vùng cụ thể của hoạ tiết:

Thuộc tính Loại Mô tả
Cần có texture GPUTexture Kết cấu WebGPU đích được phân bổ trước sẽ nhận được ảnh chụp nhanh pixel DOM được kết xuất.
size câu hỏi không bắt buộc GPUExtent3D Chiều rộng và chiều cao (tính bằng pixel kết cấu thực, ví dụ: { width, height }) để điều chỉnh tỷ lệ và tạo điểm ảnh cho phần tử trong kết cấu đích.
origin câu hỏi không bắt buộc GPUOrigin3D Độ lệch đích 3D (x, y, z) trong kết cấu nơi các pixel được tạo điểm ảnh sẽ được ghi. Giá trị mặc định là [0, 0, 0].
mipLevel câu hỏi không bắt buộc GPUIntegerCoordinate Cấp mipmap cụ thể của hoạ tiết đích để ghi vào. Giá trị mặc định là 0.
aspect câu hỏi không bắt buộc GPUTextureAspect Chỉ định khía cạnh kết cấu cần sao chép vào ("all", "stencil-only" hoặc "depth-only"). Giá trị mặc định là "all".
colorSpace câu hỏi không bắt buộc PredefinedColorSpace Không gian màu sắc đích (ví dụ: "srgb", "display-p3") để chuyển đổi màu khi tải lên. Mặc định là "srgb".
premultipliedAlpha câu hỏi không bắt buộc boolean Liệu alpha có nên được nhân trước vào các kênh màu trong quá trình tải lên hay không. Giá trị mặc định là false.

Tải phần tử lên hoạ tiết bên trong sự kiện paint bằng cách gọi phương thức drawElementImageToTexture():

canvas.addEventListener("paint", (event) => {
  const sourceDict = {
    source: myElement,
    // Optional sub-rectangle crop in CSS pixels
    // (defaults to full element if omitted):
    sourceX: 0,
    sourceY: 0,
    sourceWidth: myElement.offsetWidth,
    sourceHeight: myElement.offsetHeight,
  };

  const destDict = {
    texture: myTexture,
    size: { width: myElementWidth, height: myElementHeight },
  };

  // Upload the latest DOM snapshot into the pre-allocated GPUTexture
  device.queue.drawElementImageToTexture(sourceDict, destDict);
});

Đồng bộ hoá hình học

Để điều chỉnh vòng lấy tiêu điểm hỗ trợ tiếp cận và thử nghiệm nhấn DOM cho phù hợp với kết quả canvas được kết xuất, hãy tạo một phép biến đổi DOMMatrix và áp dụng phép biến đổi đó bằng phương thức updateElementGeometry():

// Construct a transform as needed in CSS pixels
// relative to the canvas, for example:
// 1/8 of the canvas width from the left edge, vertically centered.
// scaleX, scaleY: canvas grid pixels per CSS pixel (from resize observer)
const canvasCssWidth = canvas.width / scaleX;
const canvasCssHeight = canvas.height / scaleY;
const elementCssHeight = canvas.getElementImageDefaultSize(myElement).height / scaleY;
const x = canvasCssWidth / 8;
const y = (canvasCssHeight - elementCssHeight) / 2;
const transform = new DOMMatrix().translate(x, y);

// Synchronize the element's DOM hit-testing and accessibility geometry
canvas.updateElementGeometry(myElement, { canvasTransform: transform });

Các bước tiếp theo