2 Punkte von GN⁺ 2024-02-15 | 1 Kommentare | Auf WhatsApp teilen
  • Der M1 hing jahrelang bei OpenGL 4.1 fest, doch die neuesten M1/M2-Treiber in Fedora Asahi Remix unterstützen OpenGL 4.6 und OpenGL ES 3.2 vollständig
  • Asahis Open-Source-Linux-Treiber sind im Gegensatz zu den inkompatiblen 4.1-Treibern des Herstellers konforme Treiber, die in der Khronos-Liste geführt werden, und erweitern die Kompatibilität mit modernen OpenGL-Workloads wie Blender
  • OpenGL 4.6 verlangt Robustness, SPIR-V, Clip Control, Cull Distance, Compute Shaders und verbessertes Transform Feedback, doch die M1-Hardware passt nicht unverändert zu aktuellen Grafikstandards
  • Funktionen, die in der Hardware fehlen, werden durch Treiber- und Compiler-Techniken ergänzt: Geometry Shaders, Tessellation und Transform Feedback werden über Compute Shaders abgewickelt, Cull Distance und Clip Control über Shader-Transformationen
  • Um mehr als 100.000 Konformitätstests zu bestehen, wurden Buffer- und Image-Robustness in Software implementiert; zusätzliche Kosten werden durch Clamp-, Preamble-Optimierungen und Mipmap-Workarounds reduziert

OpenGL 4.6/ES 3.2 in Fedora Asahi Remix verfügbar

  • Der M1 unterstützte bisher nur bis OpenGL 4.1, nun aber OpenGL 4.6 und OpenGL ES 3.2
  • Die neuesten Treiber für die M1/M2-Serie sind über eine Installation von Fedora Asahi Remix nutzbar
  • Bereits installierte Systeme lassen sich mit folgendem Befehl aktualisieren
    • dnf upgrade --refresh
  • Asahis Open-Source-Mesa-Treiber sind im Gegensatz zu den inkompatiblen 4.1-Treibern des Herstellers für die aktuellen OpenGL-Versionen konform (conformance)
  • Ein konformer 4.6/3.2-Treiber muss mehr als 100.000 Tests bestehen, um Korrektheit zu gewährleisten
  • Nachdem vor sechs Monaten mit dem OpenGL-ES-3.1-Treiber der erste konforme Treiber für eine Standard-Grafik-API auf dem M1 erschien, ist nun auch OpenGL 4.6 abgeschlossen
  • Vulkan-Unterstützung ist ebenfalls in Arbeit

Funktionsimplementierung, um die OpenGL-4.1-Grenze zu überwinden

  • OpenGL 4.6 fügt gegenüber 4.1 mehrere Pflichtfunktionen hinzu
    • Robustness
    • SPIR-V
    • Clip Control
    • Cull Distance
    • Compute Shaders
    • Aufgewertetes Transform Feedback
  • Der M1 passt weniger gut zu neueren Grafikstandards als zu OpenGL ES 3.1
    • Vulkan macht einige Funktionen optional, doch um DirectX und OpenGL darauf zu schichten, werden die fehlenden Funktionen benötigt
    • Die bisherigen Lösungen auf dem M1 kamen nicht über den OpenGL-4.1-Funktionsumfang hinaus
  • Neue Funktionen ohne Hardware-Unterstützung werden über Treibertechniken implementiert
    • Geometry Shaders, Tessellation und Transform Feedback werden mit Compute Shaders verarbeitet
    • Cull Distance wird über transformierte interpolierte Werte behandelt
    • Clip Control wird als Vertex-Shader-Epilog implementiert

Buffer-Robustness und Software-Korrekturen auf dem M1

  • GPUs priorisieren traditionell Performance gegenüber Sicherheit, sodass fehlerhafter Code, bei dem ein Shader außerhalb eines Buffer-Bereichs liest, zu undefiniertem Verhalten führen kann
  • Für Anwendungen, die wie Webbrowser nicht vertrauenswürdige Shader verarbeiten, ist dieser Kompromiss unerwünscht
    • Die Grafik-API selbst ist keine Sicherheitsgrenze, daher ist ein gewisses Sanitizing nötig
    • Weniger undefiniertes Verhalten in der API hilft bei Defense in Depth
  • Wenn Robustness aktiviert ist, kann eine Anwendung für Zugriffe außerhalb des gültigen Bereichs definiertes Verhalten wählen, verzichtet dafür aber auf etwas Performance
  • Die Ergebnisse von Buffer-Loads außerhalb des gültigen Bereichs unterscheiden sich je nach API
    • Direct3D und Vulkan robustBufferAccess2: Rückgabe von 0
    • OpenGL und Vulkan robustBufferAccess: Rückgabe von 0 oder einiger Daten aus dem Buffer
    • OpenGL ES: beliebiger Wert möglich, aber kein Crash
  • Da OpenGL verlangt, dass bei Zugriffen außerhalb des Bereichs 0 oder Daten innerhalb des Buffers zurückgegeben werden, kann man das unsigned minimum aus letztem gültigen Index und Zugriffsindex berechnen und mit einem sicheren Index laden
    • Uniform-Buffer-Load ohne Robustness: load.i32 result, buffer, index
    • Nach Anwendung von Robustness: umin idx, index, last gefolgt von load.i32 result, buffer, idx
  • Die Preamble des M1 berechnet Werte, die in allen Threads gleich sind, nur einmal und verwendet sie wieder
    • Da die Größe von Uniform Buffers fest ist, kann auch zusätzliche Robustness-Arithmetik in die Preamble verschoben werden
    • Auch bei robusten Storage Buffers kann zwar der Load/Store selbst nicht verschoben werden, die Clamp-Berechnung aber schon in die Preamble

Implementierung von Vertex-Buffer-Robustness

  • In Grafik-APIs legt die Anwendung die GPU-Basisadresse des Vertex Buffers und das Attribute-Layout fest
    • Jedes Attribute hat einen Offset und ein Format
    • Der Buffer hat einen Stride, der die Anzahl der Bytes pro Vertex angibt
    • Der Vertex Shader liest Attributes implizit indexiert anhand des Vertex
  • Manche Hardware implementiert robusten Vertex Fetch nativ oder beschleunigt Software-Fetch über Bounds-checked Buffers, doch der M1 hat keines von beidem
  • Speicher-Loads der M1-GPU nehmen eine 64-Bit-Basisadresse und einen Offset in Element-Einheiten entgegen; außerdem gibt es die Integer-Multiply-Add-Instruktion imad
    • Ein 32-Bit-Attribute-Load lässt sich mit zwei Instruktionen implementieren: imad idx, stride/4, vertex, offset/4 und load.i32 result, base, idx
    • Ein dicht gepacktes Vector Attribute mit vier 32-Bit-Werten kann mit einer Instruktion geladen werden: load.v4i32 result, base, vertex << 2
  • Für Robustness ist ein Clamp nötig, doch die Größe des Vertex Buffers ist in Bytes angegeben, während der optimierte Load einen Index in Vertex-Einheiten nutzt
  • Das Problem wird gelöst, indem mehrere Attributes und Offsets innerhalb eines Buffers so umgedeutet werden, als hätten sie jeweils eigene Basisadressen
    • Statt den Offset im Shader zu addieren, wird pro Attribute eine eigene Basis übergeben
    • Die Buffer-Größe in Bytes kann für jedes Attribute in eine Größe in Vertex-Einheiten umgerechnet werden
    • Nicht der Offset, sondern der Vertex-Index wird geclamped
  • Der Treiber berechnet mit der Größe des jeweiligen Attribute-Formats den letzten gültigen Vertex-Index vor und übergibt ihn an den Shader
  • Ist ein Buffer so klein, dass gar nichts geladen werden kann, reicht ein Clamp nicht aus; dann wird der Buffer dieses Attributes durch einen kleinen Zero Buffer ersetzt
    • Durch die Basisadressen pro Attribute ist auch diese Entscheidung pro Attribute möglich
  • Am Ende wird ein robuster Vertex Buffer mit etwas Treiberberechnung und den Kosten eines einzelnen umin implementiert

Image-Robustness und Mipmap-Workaround

  • Neben Buffer-Robustness wird auch Image-Robustness benötigt; Image Loads außerhalb des gültigen Bereichs müssen 0 zurückgeben
  • Ein mipmapped Image enthält mehrere Levels of Detail
    • Das Base Level ist das Originalbild
    • Jedes folgende Level ist eine verkleinerte Version des vorherigen Levels
    • Beim Rendering wählt die Hardware das Level, das der Bildschirmgröße nahekommt, und verbessert so Effizienz und visuelle Qualität
  • Die Spezifikation verlangt, dass ein Image Load bei Robustness in folgenden Fällen 0 zurückgibt
    • Wenn die X- oder Y-Koordinate außerhalb des Bereichs liegt
    • Wenn das Level außerhalb des Bereichs liegt
  • Das Verhalten von Image Loads auf der M1-GPU weicht von den Anforderungen ab
    • Liegt die X- oder Y-Koordinate außerhalb des Bereichs, wird 0 zurückgegeben
    • Liegt das Level außerhalb des Bereichs, wird der Wert des letzten Levels zurückgegeben
  • Da der Hersteller keine Hardwaredokumentation veröffentlicht, ist nicht bekannt, ob dieses Verhalten beabsichtigt oder ein Hardware-Bug ist; um die Konformität zu bestehen, ist ein Workaround nötig
  • Ein einfacher Workaround wäre ein Branch: nur laden, wenn das Level gültig ist, andernfalls 0 zurückgeben. Branches sind jedoch ineffizient
  • Besser ist es, auszunutzen, dass ein Load auch bei einem Level außerhalb des Bereichs nicht crasht: zuerst laden und dann per Compare-Select 0 auswählen
    • Allerdings ist der Befehlssatz der M1-GPU skalar, während ein Image Load einen Vektor aus vier Komponenten zurückgibt: Red/Green/Blue/Alpha
    • Pro Komponente wäre ein ulesel nötig, wodurch das Assembly wächst
  • Der finale Workaround nutzt aus, dass die Hardware 0 zurückgibt, wenn X oder Y außerhalb des Bereichs liegt
    • Da die maximale Image-Breite 16384 px beträgt, liegt ein X-Wert wie 20000 außerhalb des Bereichs
    • Ist das Level gültig, wird das ursprüngliche X verwendet; ist es ungültig, wird X auf 20000 geändert, sodass der Image Load 0 zurückgibt
  • Diese Methode ändert nur einen Skalar statt den gesamten Vektor auszuwählen und kompiliert daher zu kompaktem Assembly
    • Wenn die Konstante vorab in ein Uniform Register geladen wird, kostet der Workaround nur eine einzelne Instruktion
    • Auf diese Weise wurde die Conformance bestanden

1 Kommentare

 
GN⁺ 2024-02-15
Meinungen auf Hacker News
  • Alyssa Rosenzweig wirkt wie ein großes Geschenk, das der Community immer weiter etwas zurückgibt.
    In jedem Blogpost lernt man garantiert etwas über die interne Struktur moderner Grafik-Hardware, das man vorher nicht wusste.

  • Diese Arbeit zeigt immer wieder, dass Können Worte schlägt.
    Schon beim Lesen des Blogs gibt es so viel zu entwirren, dass einem der Kopf raucht; obwohl die Schlussfolgerung nicht im letzten, sondern im zweiten Satz steht, folgt man am Ende doch jeder einzelnen Bit-Manipulation tief in den Kaninchenbau.
    Wenn es einen Benchmark für Erkenntnisse pro Absatz gäbe, würde Alyssa vermutlich überall Platz 1 belegen.

  • Wenn Apple irgendwann OpenGL 3.3 core ausmustert, könnte es sein, dass letztlich alle nachziehen.
    Im Allgemeinen hört man, dass OpenGL einfacher zu verwenden sei als Vulkan; zu komplexe APIs können es weniger erfahrenen Entwicklern schwer machen, die GPU zu nutzen, und als Einstiegshürde Indie-Game-Entwickler verdrängen.
    Heutzutage nutzen alle Unity und Unreal, sodass es merkwürdig wirkt, etwas von Grund auf zu bauen oder eine andere Engine zu verwenden. Zu sehen, wie die Spieleentwicklung aufwacht, nachdem Unity versucht hat, die Daumenschrauben anzuziehen, ist interessant, aber auch frustrierend.
    Open Source war in der Spieleentwicklung immer knapp aufgestellt, und auch wenn es Godot gibt, scheint es schwer zu sein, Unity und Unreal ernsthaft die Stirn zu bieten.
    Selbst wenn Godot ausreichend leistungsfähig wäre, dürften Indie-Entwickler eher bei Unity und Unreal bleiben, weil sie damit vertrauter sind.
    Der Open-Source-Zustand der Spieleentwicklung fühlt sich manchmal hoffnungslos an, und das Aufkommen der Grafik-APIs der nächsten Generation macht die Sache nicht einfacher.

    • Dass OpenGL einfacher ist als Vulkan, klingt ziemlich plausibel.
      Das OpenGL-Beispiel zum Rendern eines Dreiecks hat etwa 200 Zeilen, das Vulkan-Beispiel zum Rendern eines Dreiecks etwa 1000 Zeilen.
    • Persönlich halte ich Metal für einfacher zu verwenden als Vulkan.
      Vulkan ist sehr flexibel ausgelegt, bietet dafür aber nicht viele Komfortfunktionen.
      So oder so war OpenGL zu High-Level, um direkt als Treiber-API exponiert zu werden; besser passt es zur Funktionsweise von GPU-Hardware, eine Low-Level-API wie Vulkan als Basisschicht zu verwenden und darauf etwas wie OpenGL zu setzen.
      Außerdem verwenden nicht alle Unity und Unreal.
      Die sechs Nominierten für das Spiel des Jahres bei den The Game Awards 2023 wurden alle mit eigenen Engines entwickelt, und auch im Indie-Bereich gibt es weiterhin Entwickler, die wie bei Hades eigene Engines bauen.
      Allerdings verwendet die große Mehrheit tatsächlich fertige Engines.
    • OpenGL ist nicht ausgemustert, sondern einfacher und wird dort weiter eingesetzt, wo Vulkan überdimensioniert ist.
      Wenn es alle benötigten Funktionen abdeckt und man mit einer zustandsbasierten Rendering-Pipeline leben kann, ist es auch für neue Projekte eine gute Wahl.
    • Auf macOS und iOS ist OpenGL schon seit einigen Jahren als veraltet markiert.
      Es funktioniert noch und läuft heutzutage als Schicht über Metal, aber wenn man GL-Code für macOS oder iOS baut, erscheinen ständig Deprecation-Warnungen.
      Per define kann man sie abschalten.
    • WGPU geht eher in die Richtung, dieses Problem als plattformübergreifende API zu lösen, die einfacher zu verwenden ist als Vulkan.
      Das Problem von OpenGL ist, dass es zu weit von der Arbeitsweise von GPUs entfernt ist, um gute Performance zu erzielen.
  • Ich frage mich, wie viel von dieser Arbeit an M1-GPU-Code gebunden ist und wie viel der Implementierung von Funktionen auf Basis anderer Funktionen auch anderswo wiederverwendet werden kann.
    Es sieht sehr ähnlich danach aus, wie Zink komplexe OpenGL-Funktionen auf dem primitiveren Vulkan ausführt, nur dass es für den M1 noch kein Vulkan-Backend als Ziel gibt.

    • Allgemeiner gesagt kann man komplexes OpenGL oder Vulkan auf beliebigen Kombinationen aus CPU-Software-Rendering und hardware-spezifischer nativer Beschleunigungsunterstützung ausführen.
      Am Ende ist es eine Frage des Arbeitsaufwands und lässt sich auf verschiedene Hardware wiederverwenden.
      Das kann auch bei Hardware helfen, die alt und gut verstanden ist, aber für moderne Workloads allein schwer einsetzbar ist.
  • Ich bin sehr neugierig, wie groß die Performance-Auswirkungen dadurch sind, insbesondere im Vergleich zur direkten Nutzung von Metal unter macOS.
    Die Antwort lautet sicher „es kommt darauf an“, aber neugierig bin ich trotzdem.
    Vielleicht steht die Antwort im Artikel, aber das meiste davon habe ich nicht verstanden.

    • Zwischen der Implementierung einer Funktion als Compute-Code im Treiber und ihrer Implementierung durch GPU-Hardware-Unterstützung muss nicht zwangsläufig ein großer Unterschied liegen.
      Auch „Hardware-Unterstützung“ ist häufig als GPU-Mikrocode implementiert und läuft oft durch dasselbe Silizium.
      Jede Funktion kann zum Performance-Flaschenhals werden, und bevor man es ausprobiert, lässt sich schwer sagen, wo es klemmt.
    • Alyssas Formulierung wirkt etwas seltsam.
      Es stimmt, dass Apple-GPUs Geometry Shader nicht nativ unterstützen, aber Geometry Shader sind schlecht designt und passen nicht gut zu GPU-Hardware.
      Selbst auf Hardware, die sie angeblich unterstützt, gelten sie als langsam, und Nvidia hatte Gründe, Mesh Shading zu entwickeln.
      Transform Feedback wird ebenfalls häufig genannt, aber Apple-GPUs können aus jeder Shader-Stufe an beliebige Speicherpositionen schreiben, sodass Transform Feedback im Grunde überflüssig ist.
      Der Kernpunkt ist, dass Apple eine schlanke Compute-Architektur implementiert und dabei viele alte Altlasten sowie Funktionen entfernt hat, von denen bekannt ist, dass sie schlecht funktionieren.
      Die Formulierung „M1 steckt bei OpenGL 4.1 fest“ wirkt nicht passend.
      Ich habe OpenGL schon vor langer Zeit nicht mehr weiterverfolgt und weiß daher nicht, welche Funktionen nach 4.1 gemeint sind, aber ich wäre sehr überrascht, wenn es etwas gäbe, das in OpenGL möglich ist, in Metal aber nicht.
      Umgekehrt gibt es vieles, was in Metal möglich ist, in OpenGL aber überhaupt nicht, angefangen damit, dass die Metal Shading Language vollständige Pointer hat.
  • Das ist für Fedora auf dem M1 gedacht.
    Es wäre überraschend, wenn das auch unter macOS möglich würde; ich frage mich, was nötig wäre, um so etwas zu bauen.

    • Am Ende geht es um eine Architektur, bei der Command Buffers erstellt und an die GPU geschickt werden; dafür bräuchte man unter macOS eine Möglichkeit.
      Der frühe Mesa-Treiber für die M1-GPU wurde ebenfalls gebootstrapped, indem er per IOKit Command Buffers an den AGX-Treiber von macOS schickte.
      https://rosenzweig.io/blog/asahi-gpu-part-2.html
      https://github.com/AsahiLinux/gpu/blob/main/demo/iokit.c
      Daher bräuchte es auf Mesa-Seite noch etwas Glue-Code, um eine Surface der GPU an ein Ziel weiterzugeben, das sich in den macOS-Bildschirm compositen lässt.
    • Den Entwicklern zufolge ist das in der Praxis schwierig, weil Apple keine stabile öffentliche Kernel-API hat: https://social.treehouse.systems/@AsahiLinux/111930744188229065
    • Ich frage mich, ob es nicht bereits über den Pfad MoltenVK → Vulkan → Zink möglich sein könnte.
    • Apple scheint Kernel-Treiber von Drittanbietern zu verbieten.
      Soweit ich es verstehe, braucht man für eine ordentliche Vulkan- oder OpenGL-Implementierung ein Gegenstück auf Kernel-Seite, das die GPU-Verarbeitung übernimmt.
      Vermutlich gibt es deshalb niemanden, der versucht, natives Vulkan für macOS zu implementieren.
      Ob es auf Basis des Apple-Treibers möglich wäre, weiß ich allerdings nicht.
    • Man kann einen OpenGL-Treiber auf Metal implementieren.
      Aber ich weiß nicht, ob es sich lohnt, so viele Ressourcen für eine suboptimale Legacy-API aufzuwenden.
  • Es ist ziemlich witzig, das Umstellen von Out-of-bounds-Zugriffen von Traps auf die Rückgabe beliebiger Daten Robustheit zu nennen.
    Grafikprogrammierung ist wirklich seltsam.

    • Aus Sicht des Schreibens von Grafiktreibern ergibt das Sinn und passt auch zu Postels Gesetz, also dem Robustheitsprinzip.
      Bei GPU-Treibern geht es im Kern darum, kaputte Anwendungen zum Laufen zu bringen oder schneller laufen zu lassen.
      Striktere Defaults beheben nicht die strukturellen Probleme der Videospielbranche, die kaputten Code ausliefert; sie treiben nur Nutzer weg.
      Auf Hardware, auf der Branches im Allgemeinen sehr teuer sind, wirkt ein Flag nützlich, das dem System sagt, Grenzfälle stillschweigend auf die effizienteste Weise zu behandeln.
      Es dürfte auch viele legitime Einsatzfälle geben, in denen Programmierer vernünftigerweise sicher sein können, dass solche Grenzfälle kaum Einfluss auf den final gerenderten Frame haben.
    • Ohne Robustheitsprüfungen heißt das nicht, dass Out-of-bounds-Zugriffe zwangsläufig eine Trap auslösen; Robustheit bedeutet hier, auch in solchen seltsamen Fällen ein bekanntes Ergebnis zu liefern.
      Zusammen mit der Tatsache, dass GPUs Traps im Allgemeinen nicht besonders mögen, ergibt das Sinn.
      Carmack hat auch einmal gesagt, dass es bei der Entwicklung von Megatexture schmerzhaft war, die Hersteller dazu zu bringen, Ideen aus dem Bereich des virtuellen Speichers zu akzeptieren.
    • Deshalb haben C und C++ in Bereichen wie Grafik, High-Performance Computing, Hochenergiephysik und Hochfrequenzhandel weiterhin gute Zukunftsaussichten.
      In Feldern, in denen eine Kultur von „Performance vor Sicherheit“ dominiert, über andere Programmiersprachen zu reden, ist ungefähr so, als würde man gegen eine Wand sprechen.
  • Das ist zweifellos sehr interessant, aber ich frage mich, warum nicht zuerst Vulkan ins Visier genommen wurde.
    Heutzutage scheint es das wichtigere Ziel zu sein, und darauf gibt es bereits OpenGL-Implementierungen.

    • Eine OpenGL-Kompatibilitätsschicht auf Vulkan ist keine Magie.
      Um bestimmte OpenGL-Funktionen zu unterstützen, muss der Vulkan-Treiber die entsprechenden Funktionen unterstützen, und normalerweise sind dafür Extensions nötig.
      Mit anderen Worten: Nur weil ein grundlegender Vulkan-Treiber implementiert wurde, bekommt man OGL-4.6-Unterstützung nicht gratis; damit Mesa OGL 4.6 nach Vulkan übersetzen kann, muss der Vulkan-Treiber sämtliche OGL-4.6-Funktionen implementieren.
      Außerdem hat Alyssa bereits Reverse-Engineering- und OpenGL-Treiberprojekte gemacht.
      Ich kenne die Details nicht, aber es ist sehr wahrscheinlich, dass das Schreiben eines Treibers für eine vertraute API deutlich einfacher und schneller ist als für eine unvertraute API.
    • Zuerst wurde altes OpenGL ins Visier genommen, um ein Basis-Feature-Set zum Laufen zu bringen.
      Von dort aus bis zu neuerem OpenGL zu kommen, war wahrscheinlich weniger Arbeit als eine vollständige Vulkan-Implementierung, und man dürfte dabei auch viel über das gelernt haben, was für Vulkan nötig ist.
    • Ich hatte einen ähnlichen Gedanken, aber um OpenGL auf Vulkan zu unterstützen, braucht man ohnehin eine höhere Vulkan-Version, und der Arbeitsaufwand ist groß.
      Deshalb scheint man sich zuerst für eine niedrigere OpenGL-Version entschieden zu haben, um schneller etwas Funktionsfähiges zu bekommen.
  • Es ist ziemlich erstaunlich, wenn man bedenkt, dass OpenGL in 3D-Spielen vielleicht nie diese Präsenz gehabt hätte, wenn John Carmack in den 90ern nicht darauf beharrt hätte, Quake II mit OpenGL zu nutzen

    • Quake ist in der Geschichte vermutlich nur ein kleiner Teil
      Was OpenGL letztlich zu dem gemacht hat, was es wurde, waren SGI und der enorme Aufwand, kompatible Implementierungen für verschiedene Systeme und Architekturen zu schaffen
    • Interessanter Fakt: Die älteste archivierte OpenGL-Seite hatte ein großes FAST GAMES GRAPHICS-Banner mit animierter Quake-1-Grafik und einem anderen Menü :-P
      https://web.archive.org/web/19970707113513/http://www.opengl.org/
    • Ob es der einzige Grund war, weiß ich nicht, aber Carmacks Unterstützung für OpenGL hat definitiv geholfen
      Viele Dinge rund um 3D-Spiele verdanken wir Doom und Quake
    • Einige Jahre später, 2011, sagte John Carmack, Direct3D sei damals die bessere API gewesen
      Er meinte, Microsoft habe den Mut gehabt, immer wieder große, inkompatible Änderungen zur Verbesserung der API vorzunehmen, während OpenGL durch Kompatibilitätsbedenken ausgebremst wurde
      Direct3D gehe besser mit Multithreading um, und neuere Versionen hätten auch ein besseres State-Management, sagte er
      Dennoch sagte er, id Software bleibe aus Trägheit bei OpenGL und habe trotz der Vorteile keine Pläne, zu Direct3D zu wechseln
      Quelle: https://www.bit-tech.net/news/gaming/pc/carmack-directx-better-opengl/1/
    • Kontextmaterial: https://www.chrishecker.com/OpenGL/Press_Release