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łków i fragmentów, 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 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 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 mieszanie, 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 na przykład w przypadku mapowania cieni, gdzie 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óry 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 nawiązują komunikację.

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 błędna. Błąd można wykryć tylko asynchronicznie. Dzięki temu komunikacji między procesami nie towarzyszy żadna bańka i aplikacje działają niezawodnie.

Programy do cieniowania

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 CPU 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 ś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ć to demo.

// 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 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 kanwą. Aby na przykład 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. 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 ograniczoną liczbę 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 Firefoxie (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 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 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 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ą zapisy z losowym dostępem (a w przyszłości także odczyty).

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 ograniczania 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 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.