Z WebGL do WebGPU

François Beaufort
François Beaufort

Jako programista WebGL możesz mieć obawy, że zaczniesz używać WebGPU, następcy WebGL, który wprowadza do internetu zaawansowane interfejsy API do grafiki.

Świadomość, że WebGL i WebGPU mają takie same podstawowe koncepcje, daje spokój. Oba interfejsy API umożliwiają uruchamianie na GPU niewielkich programów zwanych cieniowaniem. WebGL obsługuje cieniowanie wierzchołków i fragmentów, a WebGPU obsługuje także algorytmy przetwarzania danych. WebGL używa języka cieniowania OpenGL (GLSL), a WebGPU korzysta z języka WGSL (WebGPU Shading Language). Chociaż te 2 języki się różnią, podstawowe pojęcia są w większości takie same.

Mając to na uwadze, w tym artykule przedstawiamy kilka różnic między WebGL a WebGPU, co może 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 ustawia się, wywołując różne funkcje interfejsu API. Pozostanie on aktywny, dopóki go nie zmienisz. Stan globalny w WebGL jest poważnym źródłem błędów, bo łatwo jest zapomnieć o zmianie ustawienia globalnego. Poza tym stan globalny utrudnia udostępnianie kodu, ponieważ deweloperzy muszą uważać, aby przypadkowo nie zmienić stanu globalnego w sposób wpływający na inne części kodu.

WebGPU to bezstanowy interfejs API i nie zachowuje stanu globalnego. Zamiast tego wykorzystano koncepcję potoku, aby uwzględnić wszystkie globalne stany renderowania w WebGL. Potok zawiera informacje dotyczące mieszania, topologii i atrybutów, które mają zostać użyte. Potoku nie można zmienić. Jeśli chcesz zmienić niektóre ustawienia, musisz utworzyć nowy potok. WebGPU korzysta też z koderów poleceń do łączenia poleceń wsadowych i wykonywanie 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 więcej

W przypadku procesorów graficznych wysyłanie poleceń i oczekiwanie na nie synchronicznie jest zwykle nieefektywne, ponieważ może to spowodować opróżnienie potoku i wywołać bąbelki. Dotyczy to zwłaszcza technologii WebGPU i WebGPU, które wykorzystują architekturę wieloprocesową ze sterownikiem GPU działającym w osobnym 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 powodować pojawienie się dymka po stronie procesora podczas komunikacji między dwoma procesami.

Aby uniknąć tych dymków, procesor WebGPU został zaprojektowany w sposób całkowicie asynchroniczny. Model błędu i wszystkie pozostałe operacje są realizowane asynchronicznie. Jeśli na przykład utworzysz teksturę, operacja wydaje się natychmiastowa, nawet jeśli tekstura jest błędem. Błąd możesz wykryć tylko asynchronicznie. Taka konstrukcja sprawia, że komunikacja między procesami jest pozbawiona bąbelków i zapewnia niezawodną wydajność aplikacji.

Ściemniacze Compute

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 cieniowania wierzchołków i fragmentów nie ograniczają się one do przetwarzania grafiki i można ich używać do wielu różnych zadań, takich jak uczenie maszynowe, symulacja fizyki czy obliczenia naukowe. Moduły do cieniowania Compute są wykonywane równolegle przez setki, a nawet tysiące wątków, dzięki czemu są bardzo wydajne do przetwarzania dużych zbiorów danych. Więcej informacji o obliczeniach GPU i więcej szczegółów znajdziesz w tym obszernym artykule o WebGPU.

Przetwarzanie klatki wideo

Przetwarzanie klatek wideo przy użyciu JavaScriptu i WebAssembly ma pewne wady: koszt kopiowania danych z pamięci GPU do pamięci procesora oraz ograniczony poziom równoległości, który można osiągnąć przez instancje robocze i wątki procesora. WebGPU nie ma tych ograniczeń, dzięki czemu świetnie nadaje się do przetwarzania klatek wideo dzięki ścisłej integracji z interfejsem API WebCodecs.

Poniższy fragment kodu pokazuje, jak zaimportować element VideoFrame jako zewnętrzną teksturę w WebGPU i go przetworzyć. 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ślne przenoszenie aplikacji

WebGPU wymusza żądanie limits. Domyślnie requestDevice() zwraca urządzenie GPU, które może nie odpowiadać możliwościom sprzętowym urządzenia fizycznego, ale raczej stanowić najniższy wspólny mianownik wszystkich układów GPU. Dzięki wymaganiu od programistów zgłaszania żądań limitów liczby urządzeń WebGPU zapewnia uruchamianie aplikacji na jak największej liczbie urządzeń.

Obsługa kanw

WebGL automatycznie zarządza obszarem roboczym po utworzeniu kontekstu WebGL i podaniu atrybutów kontekstowych, takich jak alfa, antyalias, kolorowa przestrzeń, głębia, stołówka drewnianyBuffer czy szablon.

Z drugiej strony WebGPU wymaga samodzielnego zarządzania obszarem roboczym. Aby na przykład zastosować antyaliasing w WebGPU, musisz utworzyć teksturę z wieloma próbkami i wyrenderować ją. Następnie musisz przekształcić teksturę w wielu próbkach w standardową teksturę i narysować ją na obszarze roboczym. Dzięki temu ręcznemu zarządzaniu możesz eksportować dane z jednego obiektu GPUDevice do dowolnej liczby obszarów roboczych. W przeciwieństwie do tego WebGL może utworzyć tylko 1 kontekst na obszar roboczy.

Zapoznaj się z demonstracją funkcji WebGPU wielu kanw.

Na marginesie warto zwrócić uwagę, że przeglądarki mają obecnie ograniczoną liczbę obszarów roboczych WebGL na stronie. W momencie pisania Chrome i Safari mogą używać maksymalnie 16 obszarów roboczych WebGL, a w Firefoksie – może ich utworzyć nawet 200. Z drugiej strony nie ma limitu liczby obszarów roboczych WebGPU na stronie.

Zrzut ekranu przedstawiający maksymalną liczbę kanw 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 udostępnia stos wywołań dla każdej wiadomości zwracanej przez interfejs API. Oznacza to, że możesz szybko zobaczyć, gdzie w kodzie wystąpił błąd, co jest przydatne przy debugowaniu i naprawianiu błędów.

Komunikaty o błędach WebGPU nie tylko zapewniają stos wywołań, ale są też łatwe do zrozumienia i łatwe do podjęcia działania. Komunikaty o błędach zazwyczaj zawierają opis błędu i sugestie dotyczące jego usunięcia.

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 komunikatach o błędach GPU, ostrzeżeniach w konsoli i narzędziach dla programistów przeglądarki.

Od nazw do indeksów

W WebGL wiele rzeczy łączy się własnymi 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, gdy w wyniku błędu podczas wpisywania nazwy jednolitej zmiennej wpiszesz błąd.

Z drugiej strony w WebGPU wszystko jest w całości połączone przesunięciem bajtów lub indeksem (często nazywamy je location). Odpowiedzialność za synchronizację lokalizacji kodu w języku WGSL i JavaScript spoczywa na użytkowniku.

Generowanie map AMP

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

W przypadku WebGPU musisz samodzielnie wygenerować mapy mipMap. Nie ma do tego żadnej wbudowanej funkcji. Więcej informacji o podjętej decyzji znajdziesz w dyskusji na temat specyfikacji. Do generowania mipmap możesz użyć przydatnych bibliotek, takich jak webgpu-utils, lub nauczyć się, jak zrobić to samodzielnie.

Bufory pamięci masowej i tekstury pamięci masowej

Jednolite bufory są obsługiwane przez WebGL oraz WebGPU i umożliwiają przekazywanie do cienioró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 masowej przekazywane do cieniorów mogą być znacznie większe niż w przypadku jednolitych buforów. 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. Dzięki temu można wdrażać nowe klasy algorytmów.

  • Powiązania buforów pamięci masowej obsługują tablice o rozmiarze działania (środowisko wykonawcze) na potrzeby bardziej elastycznych algorytmów, a jednolity rozmiar tablicy bufora należy podać w cieniowaniu.

Tekstury pamięci masowej są obsługiwane tylko w WebGPU i służą jako tekstury buforów pamięci masowej dla jednolitych buforów. Są bardziej elastyczne niż zwykłe tekstury i obsługują losowe zapisy (a także odczyty w przyszłości).

Zmiany buforów i tekstury

W WebGL możesz utworzyć bufor lub teksturę, a następnie w dowolnym momencie zmienić jego rozmiar, na przykład za pomocą odpowiednio gl.bufferData() i gl.texImage2D().

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

Różnice między konwencjami kosmicznymi

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 o wartości 0 znajdują się najbliżej kamery, a obiekty o wartości 1 – najdalej oddalone.

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

WebGL wykorzystuje konwencję OpenGL, w której oś Y jest skierowana do góry, a oś Z jest skierowana w stronę przeglądarki. W WebGPU stosowana jest konwencja Metal, w której oś Y jest skierowana w dół, a oś Z poza ekranem. Zwróć uwagę, że kierunek osi Y jest ustawiony w dół we współrzędnej bufora ramki, współrzędnej widocznego obszaru i współrzędnej fragmentu/piksela. W miejscu na klips kierunek osi Y jest nadal ustawiony w górnej części, tak jak w przypadku WebGL.

Podziękowania

Dziękujemy Corentinowi Wallezowi, Greggowi Tavaresowi, Stephenowi White'owi, Kenowi Russellowi i Rachel Andrew za zapoznanie się z tym artykułem.

Dokładne omówienie różnic między WebGPU a WebGPU i WebGPUFundamentals.org znajdziesz też na stronie WebGPUFundamentals.org.