Z WebGL do WebGPU

François Beaufort
François Beaufort

Jako programista WebGL możesz odczuwać strach i entuzjazm, gdy już zaczniesz korzystać z WebGPU – następcy WebGL, który wprowadza do internetu postępy w nowoczesnych interfejsach API do obsługi grafiki.

Cieszymy się, że WebGL i WebGPU mają wiele podstawowych koncepcji. Oba interfejsy API umożliwiają uruchamianie małych programów (tzw. cieniowania) na GPU. WebGL obsługuje cieniowanie wierzchołków i fragmentów, a WebGPU – także cieniowanie obliczeniowe. Interfejs WebGL korzysta z języka cieniowania OpenGL (GLSL), a WebGPU używa języka WGSL (WebGPU Shading Language). Chociaż te 2 języki są różne, podstawowe koncepcje są w większości takie same.

Mając to na uwadze, przedstawmy w tym artykule różnice między WebGL a WebGPU, by ułatwić Ci rozpoczęcie korzystania z tej usługi.

Stan globalny

WebGL ma duży stan globalny. Niektóre ustawienia mają zastosowanie do wszystkich operacji renderowania, takich jak informacje o tym, które tekstury i bufory są powiązane. Ten stan globalny możesz ustawić, wywołując różne funkcje interfejsu API, który będzie obowiązywać, dopóki go nie zmienisz. Stan globalny w WebGL jest poważnym źródłem błędów, ponieważ łatwo jest zapomnieć zmienić ustawienie globalne. Poza tym stan globalny utrudnia udostępnianie kodu, ponieważ deweloperzy muszą uważać, aby przypadkowo nie zmienić stanu globalnego w sposób, który wpłynie na inne części kodu.

WebGPU to bezstanowy interfejs API, który nie utrzymuje stanu globalnego. Zamiast tego wykorzystuje koncepcję potoku do obejmującego cały stan renderowania, który był globalny w WebGL. Potok zawiera informacje dotyczące mieszania, topologii i atrybutów, których należy użyć. Potok nie można zmienić. Jeśli chcesz zmienić niektóre ustawienia, musisz utworzyć inny potok. WebGPU korzysta też z koderów poleceń do grupowania poleceń i wykonywania ich w kolejności, w jakiej zostały zarejestrowane. Jest to przydatne w przypadku mapowania cieni, na przykład gdy w przypadku 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 stanu WebGL sprawił, że tworzenie solidnych, kompatybilnych bibliotek i aplikacji było trudne i kruche, dlatego WebGPU znacznie ograniczył stan, który programiści musieli śledzić przy wysyłaniu poleceń do GPU.

Koniec z synchronizacją

W przypadku procesorów graficznych zwykle nieefektywne jest wysyłanie poleceń i oczekiwanie na nie synchronicznie, ponieważ może to spowodować opróżnienie potoku i powstanie dymki. Szczególnie dotyczy to WebGPU i WebGL, które korzystają z architektury wieloprocesowej ze sterownikiem GPU działającym w innym procesie niż JavaScript.

Na przykład w przypadku interfejsu WebGL wywołanie gl.getError() wymaga synchronicznego IPC obejmującego proces JavaScript oraz proces GPU i odwrotny. Może to spowodować pojawienie się po stronie procesora pojawienie się dymka podczas komunikacji między tymi dwoma procesami.

Aby uniknąć wyświetlania tych dymków, interfejs WebGPU został w pełni asynchroniczny. Model błędu i wszystkie inne operacje odbywają się asynchronicznie. Na przykład, gdy tworzysz teksturę, operacja zaczyna się od razu zakończyć, nawet jeśli w rzeczywistości jest to błąd. Błąd jest wykrywany tylko asynchronicznie. Taka konstrukcja zapewnia brak dymków do komunikacji między procesami i zapewnia aplikacjom niezawodne działanie.

Sztuczne cieniowanie

Programy do cieniowania Compute to programy, które uruchamiają się na GPU i wykonują ogólne obliczenia. Są dostępne tylko w WebGPU, nie w WebGL.

W przeciwieństwie do cieniowania wierzchołków i fragmentów funkcje te nie ograniczają się do przetwarzania grafiki i można ich używać do różnego rodzaju zadań, takich jak systemy uczące się, symulacje fizyki i obliczenia naukowe. Programy do cieniowania obliczeniowego są uruchamiane równolegle przez setki, a nawet tysiące wątków, co czyni je bardzo wydajnymi przy przetwarzaniu dużych zbiorów danych. Więcej informacji o procesorach GPU i inne szczegóły znajdziesz w tym obszernym artykule o WebGPU.

Przetwarzanie klatki filmu

Przetwarzanie klatek wideo za pomocą JavaScriptu i WebAssembly ma pewne wady: koszt kopiowania danych z pamięci GPU do pamięci procesora oraz ograniczoną równoległość, którą można uzyskać w przypadku instancji roboczych i wątków procesora. WebGPU nie ma takich ograniczeń, dzięki czemu świetnie sprawdza się w przypadku przetwarzania klatek wideo dzięki ścisłej integracji z interfejsem API WebCodecs.

Poniższy fragment kodu pokazuje, jak zaimportować i przetworzyć klatkę wideo jako zewnętrzną teksturę w interfejsie WebGPU. Możesz wypróbować tę prezentację.

// 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ślna przenośność aplikacji

WebGPU wymusza wysłanie żądania limits. Domyślnie requestDevice() zwraca urządzenie GPU, które może nie odpowiadać możliwościom sprzętowym urządzenia, ale raczej zwraca rozsądny i najniższy wspólny mianownik spośród wszystkich GPU. Wymagając od programistów żądania limitów urządzeń, technologia WebGPU zapewnia, że aplikacje będą działać na jak największej liczbie urządzeń.

Obsługa odbitek na płótnie

WebGL automatycznie zarządza obszarem roboczym po utworzeniu kontekstu WebGL i dostarczaniu atrybutów kontekstowych, takich jak alfa, antialias, colorSpace, głębokość, zapisywanie obiektu RysunkiBuffer czy szablon.

Z kolei WebGPU wymaga samodzielnego zarządzania obszarem roboczym. Aby na przykład uzyskać antyaliasing w WebGPU, musisz utworzyć i renderować wielopróbkową teksturę. Następnie przekształcaj wielopróbkową teksturę w zwykłą teksturę i narysuj ją na obszarze roboczym. To ręczne zarządzanie pozwala na zapisywanie danych wyjściowych z jednego obiektu GPUDevice do dowolnej liczby obszarów roboczych. Natomiast WebGL może utworzyć tylko 1 kontekst dla każdego obszaru roboczego.

Sprawdź prezentację funkcji WebGPU Multiple Canvas.

Na marginesie w przeglądarkach obowiązuje obecnie limit liczby obszarów roboczych WebGL na stronie. W momencie pisania Chrome i Safari mogą jednocześnie korzystać z maksymalnie 16 obszarów roboczych WebGL, a Firefox może utworzyć do 200. Z drugiej strony nie ma limitu liczby obszarów roboczych WebGPU na stronie.

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

Przydatne komunikaty o błędach

WebGPU zapewnia stos wywołań dla każdej wiadomości zwracanej przez interfejs API. Dzięki temu możesz szybko sprawdzić, gdzie w kodzie wystąpił błąd, co przydaje się podczas debugowania i naprawiania błędów.

Komunikaty o błędach WebGPU nie tylko zapewniają stos wywołań, ale też są zrozumiałe i przydatne dla użytkowników. Komunikaty o błędach zwykle zawierają opis błędu i wskazówki, jak go naprawić.

WebGPU pozwala też udostępnić niestandardowy label dla każdego obiektu WebGPU. Ta etykieta jest następnie używana przez przeglądarkę w komunikatach o błędach GPU, ostrzeżeniach w konsoli i narzędziach dla programistów przeglądarki.

Z nazw do indeksów

W WebGL wiele elementów łączy nazwy. Na przykład możesz zadeklarować jednolitą zmienną o nazwie myUniform w GLSL i uzyskać jej lokalizację za pomocą parametru gl.getUniformLocation(program, 'myUniform'). Jest to przydatne, gdy w wyniku błędnego wpisania nazwy zmiennej jednolitej pojawi się błąd.

Z drugiej strony w WebGPU wszystko jest w całości połączone za pomocą przesunięcia bajtów, czyli indeksu (często nazywanego lokalizacją). To Ty odpowiadasz za synchronizację lokalizacji kodu w języku WGSL i JavaScript.

Generowanie mipmap

W WebGL możesz utworzyć mip na poziomie 0 tekstu i wywołać funkcję gl.generateMipmap(). Następnie WebGL wygeneruje wszystkie pozostałe poziomy mip.

W przypadku WebGPU musisz samodzielnie wygenerować mapy mipmap. Nie ma wbudowanej funkcji, która mogłaby to robić. Więcej informacji o decyzji znajdziesz w dyskusji na temat specyfikacji. Możesz użyć przydatnych bibliotek, takich jak webgpu-utils, aby wygenerować mipmapy, lub dowiedzieć się, jak zrobić to samodzielnie.

Bufory pamięci masowej i tekstury pamięci masowej

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

  • Bufori pamięci masowej przekazywane do cieniowania mogą być znacznie większe niż bufory jednolite. Specyfikacja określa, że rozmiar jednolitego wiązania bufora może mieć rozmiar do 64 KB (patrz maxUniformBufferBindingSize), ale maksymalny rozmiar powiązania bufora pamięci masowej w WebGPU to co najmniej 128 MB (patrz maxStorageBufferBindingSize).

  • Bufory pamięci masowej umożliwiają zapis i obsługują niektóre operacje atomowe, natomiast bufory jednolite są tylko do odczytu. Dzięki temu można wdrażać nowe klasy algorytmów.

  • Powiązania buforów pamięci masowej obsługują tablice o rozmiarze środowiska wykonawczego, co zapewnia bardziej elastyczne algorytmy, natomiast w cieniowaniu trzeba podać jednolite rozmiary tablic buforów.

Tekstury pamięci masowej są obsługiwane tylko przez WebGPU i stanowią tekstury, które pełnią funkcję buforów pamięci jednolitej. Są bardziej elastyczne niż zwykłe tekstury, obsługując zapisy z losowym dostępem (a także odczyty w przyszłości).

Zmiany buforów i tekstur

W WebGL możesz utworzyć bufor lub teksturę, a następnie w dowolnej chwili zmienić ich rozmiar, np. za pomocą odpowiednio gl.bufferData() i gl.texImage2D().

W WebGPU buforów i tekstur nie można zmienić. Oznacza to, że po utworzeniu nie będzie można zmienić ich rozmiaru, użycia ani formatu. Możesz tylko zmieniać ich treść.

Różnice w konwencji dotyczących pokoi

W WebGL zakres klipów Z wynosi od -1 do 1. W WebGPU obszar klipu Z mieści się w zakresie od 0 do 1. Oznacza to, że obiekty o wartości z są najbliżej kamery, a obiekty o wartości z są najbardziej oddalone.

Ilustracja przedstawiająca zakresy klipów Z w WebGL i WebGPU
Zakresy przycinania Z w WebGL i WebGPU.

WebGL korzysta z konwencji OpenGL, w której oś Y jest góra, a oś Z w kierunku widza. W WebGPU stosowana jest konwencja Metal, w której oś Y jest przesuwana w dół, a oś Z poza ekranem. Zwróć uwagę, że kierunek osi Y jest ustawiony w dół we współrzędnych bufora ramki, współrzędnych widocznego obszaru oraz współrzędnych fragmentu/piksela. W klipsie kierunek osi Y jest nadal wyższy, tak jak w WebGL.

Podziękowania

Dziękujemy Corentina Walleza, Gregga Tavaresa, Stephena White'a, Kena Russella i Rachel Andrew za przeczytanie tego artykułu.

Szczegółowo omawiamy różnice między WebGPU a WebGPU na stronie WebGPUFundamentals.org.