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

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

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

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

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

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

טיפול בקנבס

WebGL מנהל את הלוח באופן אוטומטי אחרי שיוצרים הקשר WebGL ומספקים מאפייני הקשר כמו alpha,‏ antialias,‏ colorSpace,‏ depth,‏ preserveDrawingBuffer או stencil.

לעומת זאת, ב-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 נעשה שימוש בהסכם של Metal, שבו ציר Y נמצא למטה וציר Z נמצא מחוץ למסך. חשוב לשים לב שכיוון ציר ה-Y הוא למטה בקואורדינטות של מאגר התמונות, בקואורדינטות של אזור התצוגה ובקואורדינטות של הפיקסלים/השברים. במרחב הקליפ, כיוון ציר ה-Y עדיין למעלה כמו ב-WebGL.

תודות

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

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