Z WebGL do WebGPU

François Beaufort
François Beaufort

Jako programista WebGL możesz być zaskoczony i podekscytowany, że możesz zacząć używać WebGPU, czyli następcy WebGL, który wprowadza do sieci nowe funkcje interfejsów 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ż te dwa języki są różne, ich podstawy są w dużej mierze 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 dotyczą wszystkich operacji renderowania, np. 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 można zapomnieć o zmianie ustawienia globalnego. Stan globalny utrudnia też 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ć kolejny 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 np. w przypadku mapowania cieni, gdy w ramach jednego przejścia przez obiekty aplikacja może rejestrować wiele strumieni poleceń, po jednym dla każdej mapy cieni światła.

Podsumowując, model globalnego stanu WebGL utrudniał tworzenie niezawodnych, modułowych bibliotek i aplikacji, a WebGPU znacznie zmniejszył ilość stanu, którą deweloperzy musieli śledzić podczas wysyłania 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. Jest to szczególnie ważne w przypadku WebGPU i WebGL, które korzystają z wiele procesowej architektury, w której sterownik GPU działa w oddzielnym procesie od 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 bań, WebGPU zostało zaprojektowane tak, aby było całkowicie asynchroniczne. Model błędów i wszystkie inne operacje są wykonywane asynchronicznie. Na przykład podczas tworzenia tekstury operacja wydaje się być natychmiastowa, nawet jeśli tekstura jest w rzeczywistości błędna. Błąd można wykryć tylko asynchronicznie. Dzięki temu komunikacji między procesami nie towarzyszy żadna bańka, a aplikacje działają niezawodnie.

Shadery obliczeniowe

Shadery obliczeniowe to programy, które działają na GPU w celu wykonywania obliczeń ogólnego przeznaczenia. Są one dostępne tylko w WebGPU, a nie w 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 klatek 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ą wątków roboczych i procesora. WebGPU nie ma tych ograniczeń, dzięki czemu świetnie nadaje się do przetwarzania klatek wideo dzięki ścisłej integracji z interfejsem WebCodecs API.

Ten fragment kodu pokazuje, jak zaimportować ramkę wideo jako zewnętrzną teksturę w WebGPU i ją 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ślna przenośność aplikacji

WebGPU wymaga przesłania żądania limits. Domyślnie funkcja requestDevice() zwraca 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. Wymagając od deweloperów, aby prosili o limity urządzeń, WebGPU 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 renderuj ją. Następnie przekształcasz wielopróbkową teksturę w zwykłą i rysujesz ją na płótnie. Dzięki temu ręcznemu zarządzaniu możesz wyprowadzać dane na dowolną liczbę kanw 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. Dzięki temu 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 też podanie niestandardowego 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. Możesz na przykład zadeklarować w GLSL zmienną o nazwie myUniform i uzyskać jej lokalizację za pomocą funkcji gl.getUniformLocation(program, 'myUniform'). Jest to przydatne, ponieważ w przypadku błędnego wpisania nazwy zmiennej uniform wystąpi błąd.

Z kolei 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, aby lokalizacje kodu w WGSL i JavaScript były zsynchronizowane.

Generowanie mapy MIP-ów

W WebGL możesz utworzyć teksturę na poziomie 0 mip, a potem wywołać funkcję gl.generateMipmap(). WebGL wygeneruje dla Ciebie 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,

Zbiory 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, które wyglądają bardzo podobnie do buforów jednolitych, są obsługiwane tylko przez WebGPU. Są one bardziej wydajne i elastyczne niż bufory jednolite.

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

  • Bufory pamięci są buforami do zapisu i obsługują niektóre operacje atomowe, podczas gdy bufory jednolite 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ą zapis (a w przyszłości także odczyt) z dostępem losowym.

Zmiany bufora 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 zmienić ich zawartość.

Różnice w konwencji dotyczącej przestrzeni

W WebGL zakres przestrzeni klipu Z wynosi od –1 do 1. W WebGPU zakres przestrzeni ograniczeń Z wynosi 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.
Z zakresów przestrzeni klatki 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 – poza ekran. Pamiętaj, że oś Y w współrzędnych framebuffera, współrzędnych widocznego obszaru i współrzędnych fragmentu/piksela jest skierowana w dół. W przestrzeni klipu kierunek osi Y jest nadal skierowany 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.