Förderjahr 2023 / Projekt Call #18 / ProjektID: 6745 / Projekt: weBIGeo
Die NVIDIA RTX4090 GPU verfügt über 24GB Grafikspeicher. Dadurch kann eine ganze Menge Daten für schnellen Zugriff auf der Grafikkarte gespeichert werden. Aber: Können Webapplikationen dieses Limit überhaupt nutzen?
Verschiedene GPUs (oder genauer: Grafiktreiber) haben verschiedene Features und Ressourcen. Zum Beispiel könnte eine GPU Texture-Compression und Buffer mit maximaler Größe von 2GB unterstützen, während eine andere GPU Half-Precision Floats und bis zu 4GB große Buffer unterstützt.
Ähnlich wie bei Vulkan muss in WebGPU eingangs ganz genau festgelegt werden, welche GPU-Features und Ressourcen (im WebGPU-Jargon "Limits" genannt) notwendig sind, damit die Applikation ordnungsgemäß funktionieren kann. Ein Programm könnte zum Beispiel sagen “Ich werde Texture-Compression im ETC2-Format und Buffer mit einer Größe von 2GB benötigen". Anschließend wird diese Anfrage (vom Grafiktreiber) gewährt oder abgelehnt. Der große Vorteil davon ist, dass im weiteren Programmverlauf immer davon ausgegangen werden kann, dass diese Features und Limits auf jeden Fall unterstützt werden.
Dies unterscheidet sich von OpenGL, wo Fehler erst dann auftreten, wenn versucht wird, nicht unterstützte Features oder Limits zu nutzen. Im Programmverlauf daher potenziell auf beliebige Kombinationen von unterstützten Features und Limits eingegangen werden muss.
Beim Portieren von AlpineMaps von OpenGL auf WebGPU haben wir das Anfragen von Features und Limits eingeführt und stießen daher gleich auf zwei unerwartete Probleme im Zusammenhang mit Dawn (Google’s WebGPU-Implementierung) und Emscripten (WebAssmebly-Compiler). Mehr zu diesen Technologien und wie wir sie in unserem Projekt verwenden könnt ihr in diesem Blogpost nachlesen.
Texture-Arrays und Tiering
In AlpineMaps werden Höhendaten und Orthofotos auf der GPU pro Tile in je einer Textur gespeichert. Diese Texturen sind als Array auf der GPU abgelegt. Bisher wurden Textur-Arrays mit einer Länge von 2048 verwendet, was im nativen Build (OpenGL) sowie im Web-Build (WebGL) von AlpineMaps funktioniert.
In WebGPU muss die maximale Länge von Textur-Arrays als Limit (maxTextureArrayLayers) explizit angefordert werden. Im nativen Build funktionierte dies ohne Probleme. Im Web-Build allerdings bekamen wir eine Fehlermeldung, dass dieses Limit nicht unterstützt sei. Nach einiger Recherche fanden wir heraus, dass Dawn für maxTextureArrayLayers das Vulkan-Limit maxImageArrayLayer verwendet. Laut gpuinfo.org unterstützt der überwiegende Großteil der Devices jeder Plattform einen Wert von 2048.
Wieso bekommen wir also trotz aktueller Hardware diesen Fehler? Wie oftmals im Web-Umfeld hat die Antwort mit Privacy zu tun.
Browser-Fingerprinting ist eine Technik, um Browser (und damit User) eindeutig zu identifizieren. Die grundlegende Idee ist, mithilfe von JavaScript alle möglichen Merkmale auszulesen, wie beispielsweise genaue Browser-Version, Sprache, Zeitzone, aber auch installierte Schriftarten oder Browser-Plugins. Darunter fällt auch beispielsweise verfügbarer GPU-Memory oder benötigte Zeit zum Rendern einer Testszene. Das Auslesen der Merkmale und Durchführen etwaiger Tests passiert normerweise im Hintergrund oder auf unsichtbaren HTML-Elementen. Die spezifische Kombination aller dieser Merkmale erlaubt es schließlich, den Client in den meisten Fällen eindeutig zu identifizieren.
Für Interessierte: Habt ihr einen eindeutigen Fingerprint? Auf coveryourtracks.eff.org könnt ihr euren eigenen Fingerprint überprüfen. Dabei wird auch gezeigt welche verschiedenen Daten dabei verwendet werden und ob euer Fingerprint unter allen Benutzer:innen der Seite in den letzten 45 Tagen eindeutig war.
Was können Browser nun gegen Fingerprinting tun? Eine Möglichkeit ist das sogenannte Tiering (engl. "Tier": Rang, Stufe). Dabei wird versucht, die Menge der möglichen Werte für bestimmten Merkmale wie Hardware-Limits einzuschränken. Für ein bestimmtes Limit werden verschiedene mögliche Werte vom Browser vorgegeben und aufgrund der tatsächlichen, physikalisch unterstützten Limits der beste Wert ausgewählt. Ein Beispiel: Nehmen wir an, die Hardware lässt eine maximale Buffer-Größe von 6GB zu. Der Browser hat 4 Tiers für die maximale Buffer-Größe mit den Werten 1GB, 2GB, 4GB und 8GB. Ausgewählt wird dann der Wert 4GB. Somit könnte bspw. nicht mehr zwischen Browsern unterschieden werden, deren echtes Hardware-Limit zwischen 4, 5, 6, oder 7GB ist. Allerdings hat dies auch den großen Nachteil, dass der Browser eventuell die vorhandene Hardware nicht komplett ausnutzt.
Zurück zu WebGPU. Da Dawn ein Open-Source-Projekt ist, konnten wir die relevanten Code-Stellen finden und drei wichtige Dinge in Erfahrung bringen:
- Für WebGPU-Limits wird Tiering verwendet.
- Tiering ist für native Builds deaktiviert.
- Es gibt zwei Tiers für maxTextureArrayLayers, beide mit den Wert 256.
Damit ist die Ursache des Fehlers geklärt: Dawn (und damit alle Chromium-basierten Browser) erlauben kein höheres Limit für maxTextureArrayLayers als 256. Wir eröffneten daher ein Ticket im Bug-Tracker von Dawn um den Wert des zweite Tiers auf 2048 zu erhöhen.
Die Dawn-Entwickler:innen waren dabei auf Zack: Bereits am nächsten Tag wurde eine Code-Änderung hochgeladen, die eine Erhöhung des Limits enthielt. Drei Tage später war die neue Änderung bereits im Chrome Nightly Release (Google Chrome Canary) verfügbar und wir konnten das neue Limit auf unseren Geräten verifizieren.
Unser nicht-ganz-so-kurzer Ausflug in das Gebiet der Web-Privacy und in den Source-Code von Dawn hatte sich damit schlussendlich ausgezahlt und unser Problem war behoben.
Emscripten und gleichzeitig schreibbare Bilddaten
AlpineMaps verwendet für das Rendering ein Paradigma namens Deferred Shading. Die Berechnung eines einzelnen Bildes erfolgt dabei in zwei Schritten. Zuerst werden getrennte Bilder für alle Daten gerendert, die für Beleuchtung und Effekte relevant sind, darunter zum Beispiel ein Bild, das die grundlegende Farbe der Oberfläche (Albedo) oder ein Bild, welches kodierte Oberflächennormalen (Normals) enthält. In einem zweiten Schritt werden die getrennten Bilder zu einem finalen Bild mit korrekter Beleuchtung und Effekten kombiniert.
Für den ersten Schritt im Deferred Shading muss in WebGPU ein Limit angegeben werden, das festlegt wie viele Daten gleichzeitig in die getrennten Bilder geschrieben werden können (maxColorAttachmentBytesPerSample). Der Test unseres Web-Builds schlug wieder als einziges fehl. Der Browser ignorierte den von uns angegebenen Wert und verwendete immer den Standardwert (der für AlpineMaps nicht ausreicht). Um das Problem zu finden, sahen wir uns Emscripten näher an.
Emscripten stellt eine Implementierung von webgpu.h bereit. Diese Implementierung leitet jeden Aufruf der WebGPU C API auf den entsprechenden Aufruf der WebGPU JavaScript API weiter, die im Browser implementiert ist. Da Emscripten ein Open-Source-Projekt ist, konnten wir die entsprechende Stelle im Code finden. Die C-Funktion zum Setzen der Limits nimmt ein C-Struct entgegen. Dieses wird anschießend in ein JavaScript-Objekt umgewandelt, um damit die WebGPU JavaScript API aufzurufen. Dabei wurde jedoch maxColorAttachmentBytesPerSample aus dem C-Struct nicht in das JavaScript-Objekt übernommen. Aus diesem Grund wurde bei jedem Aufruf der C-API zum Setzen der Limits für dieses spezielle Limit immer der Standardwert verwendet.
Wir eröffneten ein Ticket im Emscripten-Projekt auf GitHub sowie eine Pull-Request, die das entsprechende Problem behebt und das korrekte Verhalten mit einem neuen Test-Case absichert. Die zuständigen Entwickler:innen meldeten sich wenig später und überahmen schließlich unsere Änderungen. Somit war auch dieses Problem gelöst.
Fazit
WebGPU ist eine neue Technologie und noch wenig verbreitet. Es war deshalb klar, dass Implementierungen noch nicht perfekt und Problemen dieser Art (buchstäblich) vorprogrammiert sind. Glücklicherweise sind Dawn und Emscripten beides Open-Source-Projekte, wodurch wir diese Fehler finden und beheben konnten. Außerdem freut es uns, dass unsere Änderungsvorschläge angenommen wurden und wir dadurch einen Beitrag leisten konnten, der der gesamten WebGPU-Community zugutekommt.