از WebGL تا WebGPU

فرانسوا بوفور
François Beaufort

به‌عنوان یک توسعه‌دهنده WebGL، ممکن است از شروع استفاده از WebGPU، جانشین WebGL که پیشرفت‌های APIهای گرافیکی مدرن را به وب می‌آورد، هراسان و هیجان‌زده باشید.

دانستن اینکه WebGL و WebGPU مفاهیم اصلی بسیاری را به اشتراک می گذارند، اطمینان بخش است. هر دو API به شما این امکان را می دهند که برنامه های کوچکی به نام shader را روی GPU اجرا کنید. WebGL از سایه زن های راس و قطعه پشتیبانی می کند، در حالی که WebGPU از سایه زن های محاسباتی نیز پشتیبانی می کند. WebGL از OpenGL Shading Language (GLSL) استفاده می کند در حالی که WebGPU از WebGPU Shading Language (WGSL) استفاده می کند. اگرچه این دو زبان متفاوت هستند، مفاهیم اساسی عمدتاً یکسان هستند.

با در نظر گرفتن این موضوع، این مقاله برخی از تفاوت‌های بین WebGL و WebGPU را برجسته می‌کند تا به شما در شروع کار کمک کند.

دولت جهانی

WebGL حالت جهانی زیادی دارد. برخی از تنظیمات برای همه عملیات رندر اعمال می‌شود، مانند بافت‌ها و بافرها. شما این حالت جهانی را با فراخوانی توابع مختلف API تنظیم می کنید و تا زمانی که آن را تغییر ندهید فعال باقی می ماند. حالت جهانی در WebGL منبع اصلی خطاها است، زیرا فراموش کردن تغییر یک تنظیمات جهانی آسان است. به‌علاوه، حالت جهانی اشتراک‌گذاری کد را دشوار می‌کند، زیرا توسعه‌دهندگان باید مراقب باشند که به‌طور تصادفی وضعیت جهانی را طوری تغییر ندهند که بر سایر قسمت‌های کد تأثیر بگذارد.

WebGPU یک API بدون حالت است و حالت جهانی را حفظ نمی کند. در عوض، از مفهوم خط لوله برای کپسوله کردن تمام حالت رندر که در WebGL جهانی بود استفاده می کند. خط لوله حاوی اطلاعاتی از قبیل ترکیب، توپولوژی و ویژگی هایی است که باید استفاده شود. یک خط لوله تغییر ناپذیر است. اگر می خواهید برخی از تنظیمات را تغییر دهید، باید خط لوله دیگری ایجاد کنید. WebGPU همچنین از رمزگذارهای فرمان برای دسته بندی دستورات با هم و اجرای آنها به ترتیبی که ضبط شده اند استفاده می کند. این برای مثال در نگاشت سایه مفید است، جایی که در یک عبور از روی اشیا، برنامه می تواند چندین جریان فرمان را ضبط کند، یکی برای نقشه سایه هر نور.

به طور خلاصه، از آنجایی که مدل حالت جهانی WebGL ایجاد کتابخانه ها و برنامه های کاربردی قوی و قابل ترکیب را دشوار و شکننده می کرد، WebGPU به طور قابل توجهی میزان وضعیتی را که توسعه دهندگان نیاز داشتند هنگام ارسال دستورات به GPU پیگیری کنند، کاهش داد.

دیگر همگام سازی نکنید

در پردازنده‌های گرافیکی، ارسال فرمان‌ها و منتظر ماندن همزمان برای آن‌ها معمولاً ناکارآمد است، زیرا این امر می‌تواند خط لوله را تمیز کند و باعث ایجاد حباب شود. این امر به ویژه در WebGPU و WebGL که از معماری چند فرآیندی با درایور GPU در فرآیندی مجزا از جاوا اسکریپت اجرا می شود، صادق است.

به عنوان مثال، در WebGL، فراخوانی gl.getError() به یک IPC همزمان از فرآیند جاوا اسکریپت به فرآیند GPU و بازگشت نیاز دارد. این می تواند باعث ایجاد حباب در سمت CPU در هنگام ارتباط بین دو فرآیند شود.

برای جلوگیری از این حباب ها، WebGPU کاملاً ناهمزمان طراحی شده است. مدل خطا و تمام عملیات های دیگر به صورت ناهمزمان اتفاق می افتد. به عنوان مثال، هنگامی که یک بافت ایجاد می کنید، به نظر می رسد که عملیات بلافاصله با موفقیت انجام می شود، حتی اگر بافت در واقع یک خطا باشد. شما فقط می توانید خطا را به صورت ناهمزمان کشف کنید. این طراحی ارتباطات متقابل فرآیند را بدون حباب نگه می دارد و به برنامه ها عملکرد قابل اعتمادی می دهد.

شیدرها را محاسبه کنید

شیدرهای محاسباتی برنامه هایی هستند که بر روی GPU برای انجام محاسبات همه منظوره اجرا می شوند. آنها فقط در WebGPU در دسترس هستند، نه WebGL.

برخلاف سایه‌زن‌های راس و قطعه، آنها به پردازش گرافیکی محدود نمی‌شوند و می‌توانند برای کارهای مختلف مانند یادگیری ماشین، شبیه‌سازی فیزیک و محاسبات علمی استفاده شوند. شیدرهای محاسباتی به صورت موازی توسط صدها یا حتی هزاران رشته اجرا می شوند که آنها را برای پردازش مجموعه داده های بزرگ بسیار کارآمد می کند. در این مقاله گسترده درباره WebGPU، با محاسبات GPU و جزئیات بیشتر آشنا شوید.

پردازش فریم ویدیو

پردازش فریم های ویدئویی با استفاده از جاوا اسکریپت و 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 را برمی‌گرداند که ممکن است با قابلیت‌های سخت‌افزاری دستگاه فیزیکی مطابقت نداشته باشد، بلکه معقول‌ترین و پایین‌ترین مخرج مشترک همه GPUها است. WebGPU با الزام توسعه‌دهندگان به درخواست محدودیت‌های دستگاه، تضمین می‌کند که برنامه‌ها تا حد امکان در دستگاه‌های بیشتری اجرا می‌شوند.

دست زدن به بوم

WebGL به طور خودکار بوم را پس از ایجاد یک زمینه WebGL و ارائه ویژگی های زمینه مانند alpha، antialias، colorSpace، depth، saveDrawingBuffer یا استنسیل مدیریت می کند.

از طرف دیگر، WebGPU از شما می خواهد که بوم را خودتان مدیریت کنید. به عنوان مثال، برای دستیابی به antialiasing در WebGPU، می توانید یک بافت چند نمونه ایجاد کنید و به آن رندر دهید. سپس، بافت چند نمونه را به یک بافت معمولی تبدیل می‌کنید و آن بافت را روی بوم می‌کشید. این مدیریت دستی به شما امکان می دهد از یک شیء واحد GPUDevice به هر تعداد بوم که می خواهید خروجی داشته باشید. در مقابل، WebGL تنها می تواند یک زمینه در هر بوم ایجاد کند.

نسخه نمایشی WebGPU Multiple Canvases را بررسی کنید.

در یک یادداشت جانبی، مرورگرها در حال حاضر محدودیتی در تعداد بوم های WebGL در هر صفحه دارند. در زمان نگارش، کروم و سافاری تنها می‌توانند از 16 بوم WebGL به طور همزمان استفاده کنند. فایرفاکس می تواند تا 200 عدد از آنها را ایجاد کند. از سوی دیگر، محدودیتی در تعداد بوم های WebGPU در هر صفحه وجود ندارد.

اسکرین شات دارای حداکثر تعداد بوم های WebGL در مرورگرهای Safari، Chrome و Firefox
حداکثر تعداد بوم های WebGL در سافاری، کروم و فایرفاکس (از چپ به راست) - نسخه نمایشی .

پیام های خطای مفید

WebGPU یک پشته تماس برای هر پیامی که از API برگردانده می شود فراهم می کند. این بدان معنی است که شما می توانید به سرعت ببینید که خطا در کد شما کجا رخ داده است، که برای اشکال زدایی و رفع خطاها مفید است.

علاوه بر ارائه پشته تماس، پیام های خطای WebGPU نیز به راحتی قابل درک و عمل هستند. پیام‌های خطا معمولاً شامل شرح خطا و پیشنهاداتی برای رفع خطا می‌شوند.

WebGPU همچنین به شما امکان می دهد یک label سفارشی برای هر شی WebGPU ارائه دهید. این برچسب سپس توسط مرورگر در پیام‌های خطای GPUE، هشدارهای کنسول و ابزارهای توسعه‌دهنده مرورگر استفاده می‌شود.

از نام ها تا فهرست ها

در WebGL، بسیاری از چیزها با نام به هم متصل می شوند. به عنوان مثال، می توانید یک متغیر یکنواخت به نام myUniform را در GLSL اعلام کنید و مکان آن را با استفاده از gl.getUniformLocation(program, 'myUniform') دریافت کنید. اگر نام متغیر یکنواخت را اشتباه تایپ کنید، با خطا مواجه می شوید.

از سوی دیگر، در WebGPU، همه چیز به طور کامل توسط بایت افست یا شاخص (که اغلب مکان نامیده می شود) به هم متصل می شود. مسئولیت همگام نگه داشتن مکان های کد در WGSL و جاوا اسکریپت بر عهده شماست.

تولید Mipmap

در WebGL، می توانید سطح 0 mip یک بافت ایجاد کنید و سپس gl.generateMipmap() را فراخوانی کنید. سپس WebGL تمام سطوح mip دیگر را برای شما ایجاد می کند.

در WebGPU باید خودتان mipmaps تولید کنید. هیچ عملکرد داخلی برای انجام این کار وجود ندارد. برای کسب اطلاعات بیشتر در مورد تصمیم، بحث مشخصات را ببینید. می‌توانید از کتابخانه‌های مفیدی مانند webgpu-utils برای تولید mipmaps استفاده کنید یا یاد بگیرید که چگونه این کار را خودتان انجام دهید.

بافرهای ذخیره سازی و بافت های ذخیره سازی

بافرهای یکنواخت توسط WebGL و WebGPU پشتیبانی می شوند و به شما امکان می دهند پارامترهای ثابت با اندازه محدود را به سایه زن ها منتقل کنید. بافرهای ذخیره سازی که بسیار شبیه بافرهای یکنواخت هستند، فقط توسط WebGPU پشتیبانی می شوند و از بافرهای یکنواخت قدرتمندتر و انعطاف پذیرتر هستند.

  • داده‌های بافرهای ذخیره‌سازی ارسال شده به سایه‌زن‌ها می‌تواند بسیار بزرگ‌تر از بافرهای یکنواخت باشد. در حالی که مشخصات می‌گوید اتصالات بافر یکنواخت می‌تواند تا 64 کیلوبایت باشد (به maxUniformBufferBindingSize مراجعه کنید)، حداکثر اندازه اتصال بافر ذخیره‌سازی حداقل 128 مگابایت در WebGPU است (به maxStorageBufferBindingSize مراجعه کنید).

  • بافرهای ذخیره سازی قابل نوشتن هستند و برخی از عملیات اتمی را پشتیبانی می کنند در حالی که بافرهای یکنواخت فقط خواندنی هستند. این اجازه می دهد تا کلاس های جدیدی از الگوریتم ها پیاده سازی شوند.

  • اتصالات بافرهای ذخیره‌سازی از آرایه‌های اندازه زمان اجرا برای الگوریتم‌های انعطاف‌پذیرتر پشتیبانی می‌کنند، در حالی که اندازه‌های آرایه بافر یکنواخت باید در سایه‌زن ارائه شوند.

بافت‌های ذخیره‌سازی فقط در WebGPU پشتیبانی می‌شوند، و برای بافت‌ها مانند بافرهای ذخیره‌سازی برای بافرهای یکنواخت هستند. آنها نسبت به بافت های معمولی انعطاف پذیرتر هستند و از نوشتن با دسترسی تصادفی (و همچنین خواندن در آینده) پشتیبانی می کنند.

بافر و بافت تغییر می کند

در WebGL، می توانید یک بافر یا بافت ایجاد کنید و سپس اندازه آن را در هر زمان به ترتیب با gl.bufferData() و gl.texImage2D() تغییر دهید.

در WebGPU، بافرها و بافت ها تغییر ناپذیر هستند. این بدان معنی است که شما نمی توانید اندازه، استفاده یا قالب آنها را پس از ایجاد تغییر دهید. شما فقط می توانید محتوای آنها را تغییر دهید.

تفاوت قراردادهای فضایی

در WebGL، محدوده فضای کلیپ Z از -1 تا 1 است. در WebGPU، محدوده فضای کلیپ Z از 0 تا 1 است. این بدان معنی است که اشیاء با مقدار az 0 نزدیکترین به دوربین هستند، در حالی که اشیاء با مقدار az. از 1 دورترین فاصله دارند.

تصویر محدوده فضای کلیپ Z در WebGL و WebGPU.
محدوده فضای کلیپ Z در WebGL و WebGPU است.

WebGL از قرارداد OpenGL استفاده می کند که در آن محور Y به سمت بالا و محور Z به سمت بیننده است. WebGPU از قرارداد Metal استفاده می کند، که در آن محور Y پایین است و محور Z خارج از صفحه است. توجه داشته باشید که جهت محور Y در مختصات بافر فریم، مختصات درگاه دید و مختصات قطعه/پیکسل پایین است. در فضای کلیپ، جهت محور Y همچنان مانند WebGL بالاست.

قدردانی ها

با تشکر از کورنتین والز، گرگ تاوارس، استفن وایت، کن راسل و ریچل اندرو برای بررسی این مقاله.

من همچنین WebGPUFundamentals.org را برای بررسی عمیق تفاوت های WebGPU و WebGL توصیه می کنم.