WebGPU 画布中的 HTML

在 WebGPU 画布中渲染 HTML 可提供在 WebGL 画布中渲染 HTML 的所有优势,例如将着色器效果应用于 DOM 或将交互式 HTML 集成到 3D 场景中。不过,与 WebGL 相比,WebGPU 具有显著优势。

本指南演示了如何将 HTML 渲染到 WebGPU 画布中。如需详细了解画布中的 HTML,请参阅 API 概览。

在 WebGPU 画布中渲染 HTML 需要执行以下步骤:

  1. 准备画布。以声明方式设置正确的属性,让浏览器知道它应该将 HTML 渲染到画布中。此设置在 2D、WebGL 和 WebGPU 上下文中完全相同。了解如何设置画布以及如何声明要独立绘制的 HTML 元素。
  2. 将 HTML 作为纹理应用:
    1. 预分配 GPU 纹理存储空间。
    2. 将 DOM 快照作为纹理上传。
  3. 同步 DOM 和像素。

将 HTML 元素绘制为 WebGPU 纹理

在 WebGPU 中,HTML 元素会使用 drawElementImageToTexture() 作为 2D 纹理上传到 GPU。

预分配纹理存储空间

WebGPU 需要显式内存分配。在将 DOM 像素复制到纹理之前,您必须使用 device.createTexture() 方法预留 GPU VRAM:

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,
});

绘制 HTML 元素图片

在 WebGPU 中,drawElementImageToTexture() 方法会绘制 HTML 元素的快照。该方法接受两个字典对象:source 和 destination。

source 定义了要光栅化的源 DOM 元素和可选的子矩形裁剪坐标,以便您仅将元素的一部分(而非整个 DOM 节点)复制到纹理中:

属性 类型 说明
必须source Element 或 ElementImage 要绘制到纹理中的目标 DOM 元素(或预先捕获的 ElementImage 实例)。
sourceX 可选(全部四个或一个都没有) float 水平 X 坐标偏移量(以 CSS 像素为单位),用于定义源子矩形切片的左上角。
sourceY 可选(全部四个或一个都没有) float 垂直 Y 坐标偏移量(以 CSS 像素为单位),用于定义源子矩形切片的左上角。
sourceWidth 可选(全部四个或一个都没有) float 要剪裁和光栅化的源子矩形的宽度(以 CSS 像素为单位)。
sourceHeight 可选(全部四个或一个都没有) float 要剪裁和光栅化的源子矩形的高度(以 CSS 像素为单位)。

destination 定义了接收渲染 DOM 快照的目标 GPU 纹理,以及可选的目标偏移量、Mip 级别和复制尺寸,以便将像素放置在纹理的特定区域中:

属性 类型 说明
必须texture GPUTexture 预分配的目标 WebGPU 纹理,用于接收渲染的 DOM 像素快照。
可选的 size GPUExtent3D 在目标纹理中缩放和光栅化元素的宽度和高度(以物理纹理像素为单位,例如 { width, height })。
可选的 origin GPUOrigin3D 纹理中将写入光栅化像素的 3D 目标偏移量 (x, y, z)。默认为 [0, 0, 0]。
可选的 mipLevel GPUIntegerCoordinate 要写入的目标纹理的特定 mipmap 级别。默认为 0。
可选的 aspect GPUTextureAspect 指定要复制到哪个纹理方面("all"、"stencil-only" 或 "depth-only")。默认值为 "all"。
可选的 colorSpace PredefinedColorSpace 上传时进行颜色转换的目标色彩空间(例如 "srgb"、"display-p3")。默认值为 "srgb"。
可选的 premultipliedAlpha 布尔值 在上传期间,是否应将 Alpha 预乘到颜色通道中。默认为 false。

通过调用 drawElementImageToTexture() 方法,在 paint 事件中将元素上传到纹理中:

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);
});

同步几何图形

如需使 DOM 点击测试和无障碍功能焦点环与渲染的画布输出保持一致,请构建 DOMMatrix 转换,并使用 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 });

后续步骤