‫HTML ב-WebGPU canvas

רינדור HTML בתוך קנבס של WebGPU מציע את כל היתרונות של HTML בקנבס של WebGL, כמו החלת אפקטים של הצללה על ה-DOM או שילוב של HTML אינטראקטיבי בסצנות תלת-ממדיות. עם זאת, ל-WebGPU יש יתרונות משמעותיים בהשוואה ל-WebGL.

במדריך הזה מוסבר איך לעבד HTML ב-canvas של WebGPU. מידע נוסף על HTML-in-Canvas בסקירה הכללית על ה-API

כדי לעבד HTML ב-canvas של WebGPU, צריך לבצע את השלבים הבאים:

  1. מכינים את הקנבס. מגדירים באופן הצהרתי את המאפיינים הנכונים כדי שהדפדפן ידע שהוא צריך לעבד HTML לתוך קנבס. ההגדרה הזו זהה בהקשרים של 2D,‏ WebGL ו-WebGPU. איך מגדירים את ה-canvas ואיך מגדירים רכיבי HTML שיאוירו באופן עצמאי.
  2. מחילים את ה-HTML כטקסטורה:
    1. הקצאה מראש של אחסון טקסטורות ב-GPU.
    2. מעלים את תמונת המצב של ה-DOM כמרקם.
  3. סנכרון ה-DOM והפיקסלים.

ציור של רכיב HTML כטקסטורה של WebGPU

ב-WebGPU, רכיבי HTML מועלים ל-GPU כטקסטורות דו-ממדיות באמצעות drawElementImageToTexture().

הקצאה מראש של אחסון טקסטורות

ב-WebGPU נדרשת הקצאת זיכרון מפורשת. לפני שמעתיקים פיקסלים של DOM לטקסטורה, צריך להקצות VRAM של GPU באמצעות השיטה 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,
});

ציור תמונה של רכיב 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 מגדיר את טקסטורת היעד של ה-GPU שמקבלת את תמונת המצב שעברה רינדור של ה-DOM, יחד עם היסטים אופציונליים של היעד, רמות mip וממדי העתקה כדי למקם את הפיקסלים באזור ספציפי של הטקסטורה:

נכס סוג תיאור
הדרישה היא texture GPUTexture הטקסטורה של WebGPU ביעד שהוקצה מראש, שתקבל את תמונת המצב של הפיקסלים של ה-DOM שעברו רינדור.
מספר השאלות האופציונליות: size GPUExtent3D הרוחב והגובה (בפיקסלים של טקסטורה פיזית, לדוגמה, { width, height }) כדי לשנות את קנה המידה ולבצע רסטריזציה של הרכיב בתוך טקסטורת היעד.
מספר השאלות האופציונליות: origin GPUOrigin3D ההיסט של יעד התלת-ממד (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 בוליאני ההגדרה הזו קובעת אם להכפיל מראש את האלפא בערוצי הצבע במהלך ההעלאה. ברירת המחדל היא false.

מעלים את הרכיב לטקסטורה בתוך האירוע paint באמצעות קריאה ל-method‏ 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);
});

סנכרון הגיאומטריה

כדי להתאים את בדיקת מיקום המצביע ב-DOM ואת טבעות המיקוד של הנגישות לפלט ה-canvas שעבר רינדור, יוצרים טרנספורמציה של 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 });

השלבים הבאים