מ-WebGL אל WebGPU

François Beaufort
François Beaufort

מפתחי WebGL עשויים להרגיש גם רתיעה וגם התלהבות מהאפשרות להתחיל להשתמש ב-WebGPU, יורשו של WebGL שמביא את ההתקדמות של ממשקי ה-API המודרניים לעיבוד גרפיקה לאינטרנט.

מרגיע לדעת ש-WebGL ו-WebGPU חולקים הרבה מושגי ליבה. שני ממשקי ה-API מאפשרים להריץ ב-GPU תוכנות קטנות שנקראות 'שידרוגים (shaders)'. ‏WebGL תומך בשיידרים של קודקודים ושל שברי קוד, בעוד ש-WebGPU תומך גם בשיידרים של מחשוב. ב-WebGL נעשה שימוש בשפת ההצללה של OpenGL‏ (GLSL), וב-WebGPU נעשה שימוש בשפת ההצללה של WebGPU‏ (WGSL). שתי השפות שונות, אבל העקרונות הבסיסיים שלהן זהים ברובם.

לכן, במאמר הזה נדגיש כמה הבדלים בין WebGL ל-WebGPU, כדי לעזור לכם להתחיל.

מצב גלובלי

ל-WebGL יש הרבה מצב גלובלי. חלק מההגדרות חלות על כל פעולות הרינדור, כמו הטקסטורות והמאגרים שמקושרים. מגדירים את המצב הגלובלי הזה באמצעות קריאה לפונקציות API שונות, והוא נשאר בתוקף עד שמשתנים אותו. המצב הגלובלי ב-WebGL הוא מקור עיקרי לשגיאות, כי קל לשכוח לשנות הגדרה גלובלית. בנוסף, מצב גלובלי מקשה על שיתוף הקוד, כי המפתחים צריכים להיזהר שלא לשנות בטעות את המצב הגלובלי באופן שמשפיע על חלקים אחרים בקוד.

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

לסיכום, מודל המצב הגלובלי של WebGL הקשיח והשברירי את היצירה של ספריות ואפליקציות חזקות וניתנות לשימוש חוזר, אבל WebGPU הפחית באופן משמעותי את כמות המצבים שהמפתחים צריכים לעקוב אחריהם בזמן שליחת פקודות ל-GPU.

סיום הסנכרון

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

לדוגמה, ב-WebGL, כדי לקרוא ל-gl.getError() נדרש IPC סינכרוני מתהליך JavaScript לתהליך ה-GPU ובחזרה. זה עלול לגרום לבועה בצד המעבד בזמן שהשניים מתקשרים.

כדי למנוע את הבועות האלה, WebGPU תוכנן להיות אסינכרוני לחלוטין. מודל השגיאה וכל הפעולות האחרות מתרחשות באופן אסינכרוני. לדוגמה, כשיוצרים טקסטורה, נראה שהפעולה מצליחה באופן מיידי, גם אם הטקסטורה היא למעשה שגיאה. אפשר לגלות את השגיאה רק באופן אסינכרוני. כך אפשר לשמור על תקשורת בין תהליכים ללא בועות, ולספק לאפליקציות ביצועים מהימנים.

חישוב של תוכנות הצללה (shader)

Compute shaders הם תוכנות שפועלות ב-GPU כדי לבצע חישובים למטרות כלליות. הם זמינים רק ב-WebGPU, ולא ב-WebGL.

בניגוד לשיפועי קודקודים ושיפועי שברי קוד, הם לא מוגבלים לעיבוד גרפיקה, ואפשר להשתמש בהם למגוון רחב של משימות, כמו למידת מכונה, סימולציה של פיזיקה ומחשוב מדעי. עיבוד הנתונים של Shaders מחשוב מתבצע במקביל על ידי מאות או אלפי חוטים, ולכן הם יעילים מאוד לעיבוד של מערכי נתונים גדולים. ניתן לקבל מידע נוסף על מחשוב GPU ופרטים נוספים במאמר המקיף הזה על WebGPU.

עיבוד של פריים בסרטון

לעיבוד פריימים של סרטונים באמצעות JavaScript ו-WebAssembly יש כמה חסרונות: העלות של העתקת הנתונים מזיכרון ה-GPU לזיכרון ה-CPU, והמקביליות המוגבלת שניתן להשיג באמצעות עובדים ושרשראות של מעבדים (CPU). ל-WebGPU אין את המגבלות האלה, ולכן הוא מתאים במיוחד לעיבוד של פריימים של וידאו, בזכות השילוב ההדוק שלו עם ממשק ה-API של WebCodecs.

קטע הקוד הבא מראה איך לייבא VideoFrame כטקסטורה חיצונית ב-WebGPU ולעבד אותו. אתם יכולים לנסות את הדגמה הזו.

// 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
}

ברירת מחדל של ניידות אפליקציות

WebGPU מאלץ אתכם לבקש את limits. כברירת מחדל, הפונקציה requestDevice() מחזירה GPUDevice שעשוי שלא להתאים ליכולות החומרה של המכשיר הפיזי, אלא לשותף הנמוך ביותר של כל ה-GPU. כשהמפתחים דורשים לבקש מגבלות על המכשיר, WebGPU מבטיח שהאפליקציות יפעלו בכמה שיותר מכשירים.

טיפול בקנבס

WebGL מנהל באופן אוטומטי את אזור העריכה אחרי שיוצרים הקשר של WebGL ומספק מאפייני הקשר כמו alpha , antialias , colorSpace , עומק, trainDrawingBuffer או סטנסיל.

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

כדאי לעיין בהדגמה של WebGPU עם כמה משטחי ציור.

דרך אגב, בדפדפנים יש כרגע מגבלה על מספר משטחי הקנבס של WebGL בכל דף. נכון למועד כתיבת המאמר, ב-Chrome וב-Safari אפשר להשתמש בו-זמנית רק ב-16 משטחי WebGL. ב-Firefox אפשר ליצור עד 200 משטחים כאלה. לעומת זאת, אין הגבלה על מספר משטחי הקנבס של WebGPU בכל דף.

צילום מסך שבו מוצג מספר המרבצים המקסימלי של WebGL בדפדפני Safari,‏ Chrome ו-Firefox
המספר המקסימלי של משטחי WebGL ב-Safari,‏ Chrome ו-Firefox (משמאל לימין) – דוגמה.

הודעות שגיאה מועילות

‏WebGPU מספק סטאק קריאות לכל הודעה שמוחזרת מה-API. כך תוכלו לראות במהירות איפה השגיאה התרחשה בקוד, וזה עוזר לניפוי באגים ולתיקון שגיאות.

מעבר למערך השיחות, קל להבין הודעות שגיאה של WebGPU וגם לפעול לפיהן. הודעות השגיאה כוללות בדרך כלל תיאור של השגיאה והצעות לתיקון שלה.

ב-WebGPU אפשר גם לספק label בהתאמה אישית לכל אובייקט WebGPU. לאחר מכן, הדפדפן משתמש בתווית הזו בהודעות GPUError, באזהרות במסוף ובכלים למפתחים בדפדפן.

משמות לאינדקסים

ב-WebGL, הרבה דברים מקושרים באמצעות שמות. לדוגמה, אפשר להצהיר על משתנה אחיד בשם myUniform ב-GLSL ולקבל את המיקום שלו באמצעות gl.getUniformLocation(program, 'myUniform'). האפשרות הזו שימושית כי אם מקלידים בטעות את השם של המשתנה היוניפורמי, מופיעה הודעת שגיאה.

לעומת זאת, ב-WebGPU, הכול מחובר באמצעות אופסט בייט או אינדקס (שנקראים לרוב מיקום). באחריותכם לוודא שהמיקומים של הקוד ב-WGSL וב-JavaScript מסונכרנים.

יצירת Mipmap

ב-WebGL, אפשר ליצור מיפוי מיפי ברמה 0 של טקסטורה ואז לבצע קריאה ל-gl.generateMipmap(). לאחר מכן, WebGL תיצור בשבילכם את כל שאר רמות ה-mip.

ב-WebGPU, אתם צריכים ליצור את המיפויים בעצמכם. אין פונקציה מובנית לביצוע הפעולה הזו. מידע נוסף על ההחלטה זמין בדיון בנושא המפרט. אפשר להשתמש בספריות שימושיות כמו webgpu-utils כדי ליצור מפות מיפוי רזולוציה (mipmap), או ללמוד איך לעשות זאת בעצמכם.

מאגרי אחסון וטקסטורות אחסון

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

  • מאגרי נתונים לאחסון שיעברו לשכבות השיזוף יכולים להיות גדולים בהרבה ממאגרי נתונים אחידים. לפי המפרט, הגודל המקסימלי של קישורי מאגרי נתונים אחידים הוא 64KB (ראו maxUniformBufferBindingSize), אבל הגודל המקסימלי של קישור של מאגר נתונים לאחסון הוא לפחות 128MB ב-WebGPU (ראו maxStorageBufferBindingSize).

  • אפשר לכתוב במאגרי שטחי האחסון, והם תומכים בחלק מהפעולות האטומיות. לעומת זאת, מאגרי שטחי אחסון אחידים הם לקריאה בלבד. כך אפשר להטמיע סוגים חדשים של אלגוריתמים.

  • קישורים של מאגרי נתונים זמניים באחסון תומכים במערכים בגודל זמן ריצה לאלגוריתמים גמישים יותר, אבל צריך לספק גדלים אחידים של מערכי מאגר נתונים זמני בכלי ההצללה.

יש תמיכה בטקסטורות אחסון רק ב-WebGPU, והן למעשה מה שמאגרי אחסון הם למאגרי אחסון אחידים. הן גמישות יותר מטקסטורות רגילות, ותומכות בכתיבת גישה אקראית (וגם בקריאה בעתיד).

שינויים במאגר ובטקסטורה

ב-WebGL, אפשר ליצור מאגר נתונים זמני או טקסטורה ואז לשנות את הגודל שלו בכל שלב באמצעות gl.bufferData() ו-gl.texImage2D() בהתאמה.

ב-WebGPU, מאגרים וטקסטורות הם בלתי ניתנים לשינוי. המשמעות היא שלא תוכלו לשנות את הגודל, השימוש או הפורמט שלהם אחרי שייווצרו. אפשר לשנות רק את התוכן שלהם.

הבדלים בין המרחבים המשותפים

ב-WebGL, הטווח של מרחב החיתוך של Z הוא מ--1 עד 1. ב-WebGPU, טווח השטח של הקליפ Z הוא בין 0 ל-1. כלומר, אובייקטים עם ערך Z שהוא 0 הם הקרובים ביותר למצלמה, ואובייקטים עם ערך Z של 1 הם הרחוקים ביותר.

איור של טווחי מרחב החיתוך Z ב-WebGL וב-WebGPU.
טווחי מרחב החיתוך Z ב-WebGL וב-WebGPU.

ב-WebGL נעשה שימוש במוסכמה של OpenGL, שבה ציר Y הוא למעלה וציר Z הוא לכיוון הצופה. ב-WebGPU נעשה שימוש במוסכמה מתכת, שבה ציר ה-Y נמצא למטה וציר ה-Z מחוץ למסך. חשוב לשים לב שכיוון ציר ה-Y הוא למטה בקואורדינטות של מאגר התמונות, בקואורדינטות של אזור התצוגה ובקואורדינטות של הפיקסלים/השברים. בשטח הקליפ, הכיוון של ציר ה-Y עדיין למעלה כמו ב-WebGL.

תודות

תודה ל-Corentin Wallez,‏ Gregg Tavares,‏ Stephen White,‏ Ken Russell ו-Rachel Andrew על בדיקת המאמר הזה.

מומלץ גם להיכנס לאתר WebGPUFundamentals.org כדי לקבל הסבר מעמיק על ההבדלים בין WebGPU לבין WebGL.