WebGPU キャンバス内の HTML

WebGPU キャンバス内で HTML をレンダリングすると、WebGL キャンバス内の HTML と同様に、シェーダー効果を DOM に適用したり、インタラクティブな HTML を 3D シーンに統合したりするなど、さまざまなメリットが得られます。しかし、WebGPU には WebGL と比較して大きなメリットがあります。

このガイドでは、HTML を WebGPU キャンバスにレンダリングする方法について説明します。詳しくは、API の概要で HTML-in-Canvas をご覧ください。

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 の 2 つの辞書オブジェクトを受け取ります。

source は、ラスタライズするソース DOM 要素と、オプションのサブ長方形クリッピング座標を定義します。これにより、DOM ノード全体ではなく、要素の一部のみをテクスチャにコピーできます。

プロパティ タイプ 説明
source が必要です Element または ElementImage テクスチャに描画するターゲット DOM 要素(または事前にキャプチャされた ElementImage インスタンス)。
sourceX は省略可(4 つすべてを指定するか、1 つも指定しない) float ソースのサブ長方形スライスの左上隅を定義する水平 X 座標オフセット(CSS ピクセル単位)。
sourceY は省略可(4 つすべてを指定するか、1 つも指定しない) float ソースのサブ長方形スライスの左上隅を定義する垂直 Y 座標オフセット(CSS ピクセル単位)。
sourceWidth(省略可、4 つすべてを指定するか、1 つも指定しない) float 切り抜いてラスタライズするソース サブレクタングルの幅(CSS ピクセル単位)。
sourceHeight は省略可(4 つすべてを指定するか、1 つも指定しない) float 切り抜いてラスタライズするソース サブレクタングルの高さ(CSS ピクセル単位)。

destination は、レンダリングされた DOM スナップショットを受け取るターゲット GPU テクスチャを定義します。また、オプションのデスティネーション オフセット、ミップレベル、コピー寸法を使用して、ピクセルをテクスチャの特定の領域に配置します。

プロパティ タイプ 説明
texture が必要です GPUTexture レンダリングされた DOM ピクセル スナップショットを受け取る、事前に割り当てられた宛先 WebGPU テクスチャ。
省略可: size 個 GPUExtent3D デスティネーション テクスチャ内で要素をスケーリングしてラスタライズするための幅と高さ(物理テクスチャ ピクセル単位、例: { width, height })。
省略可: origin 個 GPUOrigin3D ラスタライズされたピクセルが書き込まれるテクスチャ内の 3D 宛先オフセット(x、y、z)。デフォルトは [0, 0, 0] です。
省略可: mipLevel 個 GPUIntegerCoordinate 書き込み先のテクスチャの特定のミップマップ レベル。デフォルトは 0 です。
省略可: aspect 個 GPUTextureAspect コピー先のテクスチャ アスペクト("all"、"stencil-only"、"depth-only")を指定します。デフォルトは "all" です。
省略可: colorSpace 個 PredefinedColorSpace アップロード時の色変換の宛先色空間("srgb"、"display-p3" など)。デフォルト値は "srgb" です。
省略可: premultipliedAlpha 個 ブール値 アップロード中にアルファをカラー チャンネルに事前乗算するかどうか。デフォルトは 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 });

次のステップ