Z WebGL do WebGPU

François Beaufort
François Beaufort

Jako programista WebGL możesz być podekscytowany, ale i trochę przerażony, że zaczynasz używać WebGPU, czyli następcy WebGL, który wprowadza do sieci nowoczesne interfejsy API grafiki.

Warto wiedzieć, że WebGL i WebGPU mają wiele wspólnych podstawowych koncepcji. Oba interfejsy API umożliwiają uruchamianie na GPU małych programów zwanych shaderami. WebGL obsługuje shadery wierzchołkowe i fragmentowe, a WebGPU także shadery obliczeniowe. WebGL używa języka cieniowania OpenGL (GLSL), a WebGPU – języka cieniowania WebGPU (WGSL). Chociaż są to dwa różne języki, ich podstawy są w większości takie same.

Z tego względu w tym artykule omówiliśmy różnice między WebGL a WebGPU, aby ułatwić Ci rozpoczęcie pracy.

Stan globalny

WebGL ma wiele stanów globalnych. Niektóre ustawienia mają zastosowanie do wszystkich operacji renderowania, na przykład do określania, które tekstury i bufory są powiązane. Ten stan globalny ustawiasz, wywołując różne funkcje interfejsu API. Stan ten pozostaje w zbiorze do czasu jego zmiany. Stan globalny w WebGL jest głównym źródłem błędów, ponieważ łatwo zapomnieć o zmianie ustawienia globalnego. Dodatkowo stan globalny utrudnia udostępnianie kodu, ponieważ deweloperzy muszą uważać, aby nie zmieniać go przypadkowo w sposób, który wpłynie na inne części kodu.

WebGPU to interfejs API bezstanowy, który nie obsługuje stanu globalnego. Zamiast tego używa ona koncepcji rury, aby ująć wszystkie stany renderowania, które były globalne w WebGL. Proces zawiera informacje o tym, jakiego rodzaju mieszania, topologii i atrybutów użyć. Pliku nie można zmienić. Jeśli chcesz zmienić niektóre ustawienia, musisz utworzyć inny potok. WebGPU używa też koderów poleceń do grupowania poleceń i wykonywaniu ich w kolejności, w jakiej zostały zarejestrowane. Jest to przydatne w przypadku mapowania cieni, gdy na przykład w ramach pojedynczego przejścia nad obiektami aplikacja może zarejestrować wiele strumieni poleceń, po jednym dla mapy cieni każdego światła.

Podsumowując, globalny model stanowy WebGL sprawił, że tworzenie solidnych, kompozycyjnych bibliotek i aplikacji stało się trudniejsze i bardziej delikatne, dlatego WebGPU znacznie zmniejszył ilość stanu, który deweloperzy muszą śledzić przy jednoczesnym wysyłaniu poleceń do GPU.

Nie synchronizuj

Na kartach graficznych wysyłanie poleceń i czekanie na nie w sposób synchroniczny jest zwykle nieefektywne, ponieważ może to spowodować wyczyszczenie potoku i wywołać bąble. Dotyczy to zwłaszcza technologii WebGPU i WebGL, które wykorzystują architekturę wieloprocesową ze sterownikiem GPU działającym w oddzielnym procesie niż JavaScript.

Na przykład w WebGL wywołanie gl.getError() wymaga synchronicznego IPC z procesu JavaScript do procesu GPU i z powrotem. Może to spowodować powstanie bąbla po stronie procesora, ponieważ 2 procesy będą ze sobą współpracować.

Aby uniknąć tych dymków, procesor WebGPU został zaprojektowany w taki sposób, aby był całkowicie asynchroniczny. Model błędów i wszystkie inne operacje są wykonywane asynchronicznie. Jeśli na przykład utworzysz teksturę, operacja wydaje się natychmiastowa, nawet jeśli tekstura jest w rzeczywistości błędną. Błąd możesz wykryć tylko asynchronicznie. Dzięki temu nie ma potrzeby tworzenia okna komunikacji między procesami, a aplikacje działają niezawodnie.

Programy do cieniowania

Moduły do cieniowania Compute to programy, które działają na procesorze graficznym, i wykonują ogólne obliczenia. Są dostępne tylko w WebGPU, a nie WebGL.

W przeciwieństwie do shaderów wierzchołkowych i fragmentowych nie są one ograniczone do przetwarzania grafiki. Można ich używać do wykonywania różnych zadań, takich jak uczenie maszynowe, symulacja fizyki i obliczenia naukowe. Shadery obliczeniowe są wykonywane równolegle przez setki, a nawet tysiące wątków, co sprawia, że są bardzo wydajne w przypadku przetwarzania dużych zbiorów danych. Więcej informacji o przetwarzaniu na procesorze graficznym znajdziesz w tym obszernym artykule o WebGPU.

Przetwarzanie klatki wideo

Przetwarzanie klatek wideo za pomocą JavaScript i WebAssembly ma pewne wady: koszt kopiowania danych z pamięci GPU do pamięci procesora oraz ograniczone równoległość, której można osiągnąć za pomocą procesów roboczych i wątków procesora. WebGPU nie ma tych ograniczeń, dzięki czemu doskonale nadaje się do przetwarzania klatek wideo dzięki ścisłej integracji z interfejsem WebCodecs API.

Poniższy fragment kodu pokazuje, jak zaimportować element VideoFrame jako zewnętrzną teksturę w WebGPU i go przetworzyć. Możesz wypróbować tę wersję demonstracyjną.

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

Domyślne przenoszenie aplikacji

WebGPU wymaga przesłania żądania limits. Domyślnie funkcja requestDevice() zwraca obiekt GPUDevice, który może nie odpowiadać możliwościom sprzętowym fizycznego urządzenia, ale raczej rozsądnemu i najniższemu wspólnemu mianownikowi wszystkich GPU. Wymaganie od deweloperów, aby prosili o limity urządzeń, zapewnia, że aplikacje będą działać na jak największej liczbie urządzeń.

Obsługa Canvas

WebGL automatycznie zarządza płótnem po utworzeniu kontekstu WebGL i podaniu atrybutów kontekstu takich jak alpha, antialias, ColorSpace, depth, preserveDrawingBuffer czy stencil.

Z drugiej strony WebGPU wymaga samodzielnego zarządzania płótnem. Na przykład, aby uzyskać wygładzanie krawędzi w WebGPU, utwórz teksturę wielopróbkową i wyrenderuj ją. Następnie przekształcasz teksturę wielopróbkową w zwykłą i rysujesz ją na płótnie. Ręczne zarządzanie pozwala eksportować pliki do dowolnej liczby obszarów roboczych z jednego obiektu GPUDevice. W przypadku WebGL można utworzyć tylko 1 kontekst na 1 płótno.

Obejrzyj prezentację WebGPU dotyczące wielu kanałów.

Pamiętaj, że przeglądarki mają obecnie limit liczby kanw WebGL na stronę. W momencie pisania tego artykułu Chrome i Safari mogą jednocześnie używać tylko 16 płót WebGL; Firefox może utworzyć ich do 200. Z drugiej strony nie ma limitu liczby kanw WebGPU na stronę.

Zrzut ekranu pokazujący maksymalną liczbę kanw WebGL w przeglądarkach Safari, Chrome i Firefox
Maksymalna liczba obszarów roboczych WebGL w Safari, Chrome i Firefox (od lewej do prawej) – demo.

Przydatne komunikaty o błędach

WebGPU udostępnia stos wywołań dla każdej wiadomości zwracanej przez interfejs API. Oznacza to, że możesz szybko sprawdzić, gdzie w kodzie wystąpił błąd, co jest przydatne podczas debugowania i naprawiania błędów.

Oprócz informacji o zbiorze wywołań komunikaty o błędach WebGPU są też łatwe do zrozumienia i działania. Komunikaty o błędach zwykle zawierają opis błędu i sugestie dotyczące jego naprawienia.

WebGPU umożliwia również udostępnienie niestandardowego obiektu label dla każdego obiektu WebGPU. Ta etykieta jest następnie używana przez przeglądarkę w wiadomościach GPUError, ostrzeżeniach w konsoli i narzędziach dla programistów przeglądarki.

Od nazw do indeksów

W WebGL wiele elementów jest połączonych nazwami. Na przykład możesz zadeklarować w GLSL jedną zmienną o nazwie myUniform i ustalić jej lokalizację za pomocą atrybutu gl.getUniformLocation(program, 'myUniform'). Jest to przydatne, ponieważ w przypadku błędnego wpisania nazwy zmiennej jednorodnej pojawi się błąd.

Z drugiej strony w WebGPU wszystko jest połączone za pomocą przesunięcia bajtów lub indeksu (często nazywanego lokalizacją). Twoim obowiązkiem jest dbanie o to, żeby lokalizacje kodu w WGSL i JavaScript były zsynchronizowane.

Generowanie map MIP-ów

W WebGL możesz utworzyć teksturę na poziomie 0 mip, a potem wywołać funkcję gl.generateMipmap(). WebGL wygeneruje wtedy wszystkie pozostałe poziomy mip.

W WebGPU musisz samodzielnie wygenerować mipmapy. Nie ma wbudowanej funkcji, która umożliwiałaby to zrobienie. Aby dowiedzieć się więcej o tej decyzji, zapoznaj się z dyskusją na temat specyfikacji. Aby wygenerować mapy MIP, możesz użyć przydatnych bibliotek takich jak webgpu-utils lub dowiedzieć się, jak zrobić to samodzielnie.

bufory i tekstury pamięci masowej,

Bufory jednolite są obsługiwane zarówno przez WebGL, jak i WebGPU i umożliwiają przekazywanie do shaderów stałych parametrów o ograniczonym rozmiarze. Bufory pamięci masowej, które wyglądają jak jednolite bufory, są obsługiwane tylko przez WebGPU i są bardziej wydajne i elastyczne niż jednolite bufory.

  • Dane buforów pamięci przekazywane do shaderów mogą być znacznie większe niż bufory jednolite. Według specyfikacji rozmiar powiązań buforów jednolitych może wynosić do 64 KB (patrz maxUniformBufferBindingSize) , ale maksymalny rozmiar powiązania bufora pamięci masowej w WebGPU (patrz maxStorageBufferBindingSize) wynosi co najmniej 128 MB.

  • Bufory pamięci masowej są dostępne do zapisu i obsługują niektóre operacje niepodzielne, a jednolite bufory są tylko do odczytu. Umożliwia to wdrażanie nowych klas algorytmów.

  • Powiązania buforów pamięci obsługują tablice o rozmiarze zdefiniowanym w czasie wykonywania, co umożliwia większą elastyczność algorytmów, podczas gdy rozmiary tablic jednolitych buforów muszą być podawane w shaderze.

Tekstury pamięci masowej są obsługiwane tylko w WebGPU i są dla tekstur tym, czym dla buforów jednolitych są bufory pamięci masowej. Są bardziej elastyczne niż zwykłe tekstury i obsługują zapisy z losowym dostępem (a w przyszłości także odczyty).

Zmiany buforów i tekstury

W WebGL możesz utworzyć bufor lub teksturę, a potem w dowolnym momencie zmienić ich rozmiar za pomocą funkcji gl.bufferData()gl.texImage2D().

W WebGPU bufory i tekstury są niezmienne. Oznacza to, że po utworzeniu nie można zmienić ich rozmiaru, sposobu użycia ani formatu. Możesz tylko zmieniać ich zawartość.

Różnice w konwencji dotyczącej przestrzeni

W WebGL obszar klipu mieści się w zakresie od -1 do 1. W WebGPU przestrzeń przycięcia Z mieści się w zakresie od 0 do 1. Oznacza to, że obiekty o wartości z równej 0 są najbliżej kamery, a obiekty o wartości z równej 1 są najdalej.

Ilustracja przedstawiająca zakresy przestrzeni ograniczeń Z w WebGL i WebGPU.
Zakresy przycięcia typu Z w WebGL i WebGPU.

WebGL używa konwencji OpenGL, w której oś Y jest skierowana w górę, a oś Z – w kierunku widza. WebGPU używa konwencji Metal, w której oś Y jest skierowana w dół, a oś Z jest skierowana poza ekran. Zwróć uwagę, że kierunek osi Y jest ustawiony w dół we współrzędnych bufora ramki, współrzędnej widocznego obszaru i współrzędnej fragmentu/piksela. W przestrzeni klipu oś Y jest nadal skierowana w górę, tak jak w WebGL.

Podziękowania

Dziękujemy Corentin Wallez, Gregg Tavares, Stephen White, Ken Russell i Rachel Andrew za sprawdzenie tego artykułu.

Aby dowiedzieć się więcej o różnicach między WebGPU a WebGL, warto też odwiedzić stronę WebGPUFundamentals.org.