מ-WebGL אל WebGPU

François Beaufort
François Beaufort

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

חשוב לדעת של-WebGL ול-WebGPU יש הרבה מושגי ליבה משותפים. שני ממשקי ה-API מאפשרים להריץ תוכניות קטנות שנקראות shaders ב-GPU. ‫WebGL תומך ב-vertex וב-fragment shaders, ואילו WebGPU תומך גם ב-compute shaders. ‫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

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

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

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

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

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

טיפול בקנבס

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

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

כדאי לעיין בהדגמה של WebGPU Multiple Canvases.

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

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

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

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

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

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

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

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

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

יצירת Mipmap

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

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

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

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

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

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

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

מרקמי אחסון נתמכים רק ב-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.