2 points par GN⁺ 2024-02-15 | 1 commentaires | Partager sur WhatsApp
  • Le M1 est resté bloqué pendant des années sur OpenGL 4.1, mais les tout derniers pilotes M1/M2 de Fedora Asahi Remix prennent désormais entièrement en charge OpenGL 4.6 et OpenGL ES 3.2
  • Le pilote Linux open source d’Asahi, contrairement au pilote 4.1 non conforme du fournisseur, est un pilote certifié conforme figurant dans les listes de Khronos, ce qui élargit la compatibilité avec des charges de travail OpenGL modernes comme Blender
  • OpenGL 4.6 exige robustness, SPIR-V, clip control, cull distance, compute shaders et un transform feedback amélioré, mais le matériel du M1 ne correspond pas naturellement aux standards graphiques récents
  • Les fonctions absentes du matériel sont compensées par des techniques de pilote et de compilateur : geometry shaders, tessellation et transform feedback sont traités via des compute shaders, tandis que cull distance et clip control sont gérés par des transformations de shader
  • Pour réussir plus de 100 000 tests de conformité, la robustness des buffers et des images est implémentée en logiciel, et des optimisations de clamp, de preamble et de contournement des mipmaps réduisent le surcoût

OpenGL 4.6/ES 3.2 désormais disponible sur Fedora Asahi Remix

  • Le M1 ne prenait jusqu’ici en charge qu’OpenGL 4.1, mais il prend maintenant en charge OpenGL 4.6 et OpenGL ES 3.2
  • Les derniers pilotes pour les séries M1/M2 sont disponibles via l’installation de Fedora Asahi Remix
  • Pour les utilisateurs l’ayant déjà installé, la mise à jour se fait avec la commande suivante
    • dnf upgrade --refresh
  • Le pilote Mesa open source d’Asahi, contrairement au pilote 4.1 non conforme du fournisseur, dispose de la conformité aux versions récentes d’OpenGL
  • Un pilote 4.6/3.2 conforme doit réussir plus de 100 000 tests pour garantir l’exactitude
  • Six mois après la sortie du premier pilote conforme du M1 pour une API graphique standard avec OpenGL ES 3.1, c’est désormais OpenGL 4.6 qui est finalisé
  • La prise en charge de Vulkan est également en cours

Implémentation des fonctionnalités pour dépasser la barrière d’OpenGL 4.1

  • OpenGL 4.6 ajoute plusieurs fonctionnalités obligatoires par rapport à 4.1
    • Robustness
    • SPIR-V
    • Clip control
    • Cull distance
    • Compute shaders
    • Transform feedback amélioré
  • Le M1 s’adapte moins bien aux standards graphiques plus récents qu’à OpenGL ES 3.1
    • Vulkan rend certaines fonctionnalités optionnelles, mais ces fonctions manquantes sont nécessaires pour y superposer DirectX et OpenGL
    • La solution existante du M1 ne pouvait pas dépasser l’ensemble de fonctionnalités d’OpenGL 4.1
  • Les nouvelles fonctions sans prise en charge matérielle sont implémentées par des techniques de pilote
    • Les geometry shaders, la tessellation et le transform feedback sont gérés via des compute shaders
    • Cull distance est traité avec des valeurs interpolées transformées
    • Clip control est implémenté dans l’épilogue du vertex shader

Robustness des buffers et compensation logicielle sur M1

  • Les GPU privilégient traditionnellement les performances à la sûreté, si bien qu’un shader contenant du code incorrect lisant hors des limites d’un buffer peut provoquer un comportement indéfini
  • Pour les applications qui manipulent des shaders non fiables, comme les navigateurs web, ce compromis n’est pas souhaitable
    • L’API graphique elle-même n’est pas une frontière de sécurité, donc une certaine sanitization reste nécessaire
    • Réduire les comportements indéfinis de l’API aide à renforcer la défense en profondeur
  • Lorsque robustness est activé, l’application peut choisir un comportement défini pour les accès hors limites en échange d’une partie des performances
  • Le résultat d’un chargement de buffer hors limites diffère selon l’API
    • Direct3D et Vulkan robustBufferAccess2 : retour de 0
    • OpenGL et Vulkan robustBufferAccess : retour de 0 ou d’une partie des données présentes dans le buffer
    • OpenGL ES : une valeur arbitraire est possible, mais sans crash
  • Comme OpenGL exige qu’un accès hors limites renvoie 0 ou des données situées dans le buffer, il est possible de calculer le minimum non signé entre le dernier index valide et l’index demandé, puis de charger via cet index sûr
    • chargement de uniform buffer sans robustness : load.i32 result, buffer, index
    • après application de robustness : umin idx, index, last puis load.i32 result, buffer, idx
  • Le preamble du M1 calcule une fois des valeurs communes à tous les threads au lieu de les recalculer à répétition
    • La taille d’un uniform buffer étant fixe, l’arithmétique supplémentaire liée à robustness peut aussi être déplacée dans le preamble
    • Même lorsque le load/store lui-même ne peut pas être déplacé pour un storage buffer robuste, le calcul du clamp peut l’être

Implémentation de la robustness des vertex buffers

  • Dans les API graphiques, l’application configure l’adresse de base GPU du vertex buffer et le layout des attributs
    • Chaque attribut possède un offset et un format
    • Le buffer possède un stride indiquant le nombre d’octets par vertex
    • Le vertex shader lit les attributs en les indexant implicitement par vertex
  • Certains matériels implémentent nativement le robust vertex fetch ou accélèrent les fetchs logiciels via des buffers avec vérification de limites, mais le M1 ne dispose d’aucune de ces deux options
  • Les chargements mémoire du GPU M1 prennent une adresse de base 64 bits et un offset en unités d’élément, et il fournit aussi l’instruction imad de multiplication-addition entière
    • Un chargement d’attribut 32 bits peut être implémenté avec deux instructions : imad idx, stride/4, vertex, offset/4 puis load.i32 result, base, idx
    • Un attribut vectoriel de 4 valeurs 32 bits placées de façon compacte peut être chargé en une seule instruction : load.v4i32 result, base, vertex << 2
  • Robustness exige un clamp, mais la taille du vertex buffer est exprimée en octets tandis que le chargement optimisé utilise un index en unités de vertex
  • Le problème est résolu en réinterprétant les multiples attributs et offsets d’un même buffer comme des adresses de base distinctes par attribut
    • Au lieu d’ajouter l’offset dans le shader, une base par attribut est transmise
    • La taille du buffer en octets peut alors être convertie en taille en unités de vertex pour chaque attribut
    • On applique le clamp à l’index de vertex plutôt qu’à l’offset
  • Le pilote précalcule l’index du dernier vertex valide à partir de la taille du format de chaque attribut et le transmet au shader
  • Si le buffer est trop petit pour permettre le moindre chargement, le clamp ne suffit pas ; le buffer de cet attribut est alors remplacé par un petit zero buffer
    • Comme des adresses de base par attribut sont utilisées, cette décision peut aussi être prise attribut par attribut
  • Au final, un vertex buffer robuste est obtenu au prix d’un léger calcul côté pilote et d’un seul umin

Robustness des images et contournement des mipmaps

  • En plus de la robustness des buffers, la robustness des images est nécessaire, et un image load hors limites doit renvoyer 0
  • Une image avec mipmaps contient plusieurs levels of detail
    • Le niveau de base est l’image d’origine
    • Chaque niveau suivant est une version réduite du précédent
    • Au rendu, le matériel sélectionne le niveau le plus proche de la taille affichée pour améliorer l’efficacité et la qualité visuelle
  • La spécification impose qu’avec robustness, un image load renvoie 0 dans les cas suivants
    • lorsque les coordonnées X ou Y sont hors limites
    • lorsque le level est hors limites
  • Le comportement des image loads sur le GPU M1 diffère de cette exigence
    • si X ou Y est hors limites, il renvoie 0
    • si le level est hors limites, il renvoie la valeur du dernier level
  • Le fournisseur ne publiant pas la documentation matérielle, il est impossible de savoir si ce comportement est intentionnel ou s’il s’agit d’un bug matériel ; un contournement est donc nécessaire pour réussir la conformité
  • Le contournement le plus simple consiste à charger uniquement si le level est valide, sinon à renvoyer 0 via une branche, mais les branches sont inefficaces
  • Une meilleure approche consiste à tirer parti du fait qu’un load avec un level hors limites ne provoque pas de crash, afin d’effectuer d’abord le chargement puis de sélectionner 0 avec un compare-select
    • Mais le jeu d’instructions du GPU M1 est scalaire, alors qu’un image load renvoie un vecteur à 4 composantes red/green/blue/alpha
    • Un ulesel est donc nécessaire pour chaque composante, ce qui alourdit l’assembleur
  • Le contournement final exploite le fait que le matériel renvoie 0 si X ou Y est hors limites
    • Comme la largeur maximale d’une image est de 16384 px, remplacer X par une valeur comme 20000 le place forcément hors limites
    • Si le level est valide, on conserve le X d’origine ; sinon, on remplace X par 20000 pour que l’image load renvoie 0
  • Cette méthode se compile en assembleur compact, car elle modifie un seul scalaire au lieu de sélectionner tout le vecteur
    • Si la constante est préchargée dans un registre uniforme, le coût du contournement se limite à une seule instruction
    • Cette approche permet de réussir les tests de conformité

1 commentaires

 
GN⁺ 2024-02-15
Commentaires sur Hacker News
  • Alyssa Rosenzweig semble être un immense cadeau pour la communauté
    À chaque billet de blog, j’apprends forcément quelque chose que j’ignorais sur les entrailles du matériel graphique moderne

  • C’est le genre de travail qui montre à chaque fois que la compétence l’emporte sur les paroles
    Rien qu’en lisant le blog, il y a déjà de quoi se faire chauffer les neurones tant il y a de choses à décortiquer, et même si la conclusion est dans la deuxième phrase plutôt que dans la dernière, on finit quand même par suivre bit à bit le terrier du lapin
    S’il existait un benchmark du nombre de révélations par paragraphe, Alyssa serait sans doute première partout

  • Un jour, si Apple abandonne OpenGL 3.3 core, tout le monde finira peut-être par suivre
    On entend souvent dire qu’OpenGL est plus simple à utiliser que Vulkan, et des API trop complexes peuvent rendre l’exploitation du GPU difficile pour les développeurs peu expérimentés, créant une barrière à l’entrée qui écarte les développeurs de jeux indépendants
    Aujourd’hui, tout le monde utilise Unity et Unreal, donc partir de zéro ou choisir un autre moteur paraît bizarre, et même s’il est intéressant de voir le développement de jeux se réveiller après que Unity a tenté de verrouiller davantage son écosystème, c’est aussi agaçant
    L’open source a toujours été à l’étroit dans le développement de jeux, et même s’il y a Godot, il semble difficile qu’il rivalise sérieusement avec Unity et Unreal
    Même si Godot en est tout à fait capable, les développeurs indés sont plus familiers avec Unity et Unreal et risquent donc d’y rester
    L’état de l’open source dans le développement de jeux paraît parfois désespérant, et l’arrivée des API graphiques de nouvelle génération ne facilite pas les choses

    • Dire qu’OpenGL est plus simple que Vulkan est assez défendable
      L’exemple de rendu d’un triangle en OpenGL fait environ 200 lignes, tandis que l’exemple de rendu d’un triangle en Vulkan en fait environ 1000
    • Personnellement, je trouve Metal plus simple à utiliser que Vulkan
      Vulkan a été conçu pour être très flexible, mais n’offre pas beaucoup de commodités
      Dans les deux cas, OpenGL était trop haut niveau pour être exposé comme API directe du pilote, et il est plus cohérent avec le fonctionnement réel du matériel GPU d’avoir une couche de base basse niveau comme Vulkan, avec quelque chose comme OpenGL au-dessus
      Et tout le monde n’utilise pas Unity et Unreal
      Les six nommés au titre de jeu de l’année aux Game Awards 2023 ont tous été développés avec des moteurs maison, et même dans l’indé, il existe encore des développeurs qui créent leur propre moteur, comme pour Hades
      Cela dit, pour la majorité, utiliser un moteur existant reste le bon choix
    • OpenGL n’a pas été abandonné ; il est simplement plus simple, et continue d’être utilisé là où Vulkan est excessif
      S’il couvre toutes les fonctionnalités nécessaires et qu’on peut accepter un pipeline de rendu à états, c’est aussi un bon choix pour de nouveaux projets
    • Sur macOS et iOS, OpenGL est déjà déprécié depuis plusieurs années
      Ça fonctionne encore, et aujourd’hui cela tourne comme une couche au-dessus de Metal, mais si l’on compile du code GL pour macOS ou iOS, des avertissements de dépréciation apparaissent en permanence
      On peut les désactiver avec un define
    • WGPU cherche plutôt à résoudre ce problème en proposant une API multiplateforme plus simple à utiliser que Vulkan
      Le problème d’OpenGL, c’est qu’il est trop éloigné du mode de fonctionnement réel des GPU pour qu’il soit facile d’obtenir de bonnes performances
  • Je me demande quelle part de ce travail est liée au code GPU du M1, et dans quelle mesure ces implémentations de fonctionnalités par-dessus d’autres fonctionnalités peuvent être réutilisées ailleurs
    Cela ressemble beaucoup à la façon dont Zink exécute des fonctionnalités OpenGL complexes au-dessus de Vulkan plus brut, sauf qu’il n’existe pas encore de backend Vulkan ciblable pour le M1

    • Plus généralement, il est possible d’exécuter des fonctionnalités OpenGL ou Vulkan complexes au-dessus d’une combinaison arbitraire de rendu logiciel CPU et d’accélération native spécifique au matériel
      Au final, c’est une question de quantité de travail, et cela peut être réutilisé sur différents matériels
      Cela peut aussi aider des matériels anciens, bien compris, mais difficiles à utiliser seuls pour des charges de travail modernes
  • Je suis très curieux de l’impact sur les performances que cela entraîne, surtout sur macOS, par rapport à l’utilisation directe de Metal
    La réponse est évidemment « ça dépend », mais je reste curieux
    Il y a peut-être une réponse dans le billet, mais je n’en ai pas compris la majeure partie

    • Implémenter une fonctionnalité dans le code compute du pilote n’implique pas forcément une grande différence par rapport à une implémentation avec prise en charge matérielle du GPU
      La « prise en charge matérielle » est elle aussi souvent implémentée en microcode GPU, en passant fréquemment par le même silicium
      N’importe quelle fonctionnalité peut devenir un goulet d’étranglement en termes de performances, et il est difficile de savoir à l’avance où ça bloquera avant de l’essayer pour de vrai
    • La formulation d’Alyssa me paraît un peu étrange
      Il est vrai que les GPU Apple ne prennent pas en charge nativement les geometry shaders, mais les geometry shaders sont mal conçus et s’adaptent mal au matériel GPU
      Ils sont connus pour être lents même sur les matériels qui les prennent réellement en charge, et si Nvidia a conçu le mesh shading, ce n’est pas pour rien
      On cite aussi souvent le transform feedback, mais les GPU Apple peuvent écrire à des emplacements mémoire arbitraires depuis n’importe quelle étape de shader, ce qui rend en pratique le transform feedback inutile
      L’essentiel, c’est qu’Apple a mis en œuvre une architecture compute concise en supprimant beaucoup d’anciens vestiges et de fonctionnalités connues pour mal fonctionner
      Dire que « le M1 est bloqué à OpenGL 4.1 » ne me semble pas approprié
      Apple ayant cessé de suivre OpenGL depuis longtemps, je ne sais même pas de quelles fonctionnalités postérieures à 4.1 il s’agirait, mais il serait très surprenant qu’OpenGL sache faire quelque chose que Metal ne peut pas faire
      À l’inverse, il y a beaucoup de choses possibles avec Metal et totalement impossibles avec OpenGL, et le simple fait que le langage de shading de Metal dispose de pointeurs complets en est déjà un exemple
  • C’est pour le Fedora sur M1
    Ce serait surprenant si cela devenait aussi possible sur macOS, mais je me demande ce qu’il faudrait pour y parvenir.

    • Au final, la structure consiste à créer des tampons de commandes et à les envoyer au GPU, donc il faut un moyen de faire cela sur macOS.
      Le pilote Mesa initial pour le GPU M1 a lui aussi été bootstrapé en envoyant des tampons de commandes au pilote AGX de macOS via IOKit.
      https://rosenzweig.io/blog/asahi-gpu-part-2.html
      https://github.com/AsahiLinux/gpu/blob/main/demo/iokit.c
      Il faut donc encore un peu de code de glue côté Mesa pour transmettre la surface du GPU à une cible pouvant être composée sur l’écran macOS.
    • D’après les développeurs, c’est en pratique difficile parce qu’Apple n’a pas d’API kernel publique stable : https://social.treehouse.systems/@AsahiLinux/111930744188229065
    • Je me demande si ce ne serait pas déjà possible via la chaîne MoltenVK → Vulkan → Zink.
    • Apple semble interdire les pilotes kernel tiers.
      Si je comprends bien, pour réaliser une véritable implémentation de Vulkan ou d’OpenGL, il faut un pendant côté kernel chargé du traitement GPU.
      C’est probablement pour cela que personne ne semble vouloir implémenter Vulkan nativement sur macOS.
      Cela dit, si c’est possible au-dessus des pilotes Apple, je ne sais pas.
    • Il est possible d’implémenter un pilote OpenGL au-dessus de Metal.
      Mais je ne sais pas s’il y a une raison d’y consacrer autant de ressources pour une API legacy qui n’est pas optimale.
  • C’est assez drôle d’appeler robustesse le fait de remplacer un trap sur accès hors limites par un retour de données arbitraires.
    La programmation graphique est vraiment étrange.

    • Du point de vue de l’écriture d’un pilote graphique, cela se tient, et c’est aussi cohérent avec la loi de Postel, c’est-à-dire le principe de robustesse.
      Le cœur du travail d’un pilote GPU consiste à faire tourner des applications cassées, ou à les faire tourner plus vite.
      Rendre le comportement par défaut plus strict ne corrigera pas le problème structurel d’une industrie du jeu vidéo qui publie du code cassé ; cela fera seulement fuir les utilisateurs.
      Sur un matériel où les branchements sont en général très coûteux, un flag indiquant au système de gérer silencieusement les cas limites de la manière la plus efficace semble utile.
      Il existe aussi probablement beaucoup de cas d’usage valides où le programmeur peut raisonnablement être certain que ces cas limites n’auront pratiquement aucun effet sur l’image finale rendue.
    • L’absence de vérifications de robustesse ne signifie pas forcément qu’un accès hors limites provoquera un trap ; ici, la robustesse veut dire qu’un résultat connu est fourni même dans ce genre de cas étrange.
      Cela a du sens si l’on considère que les GPU, de manière générale, n’aiment pas beaucoup les traps.
      Carmack a aussi déjà dit que faire accepter aux fabricants les idées de mémoire virtuelle lors de la conception de megatexture avait été pénible.
    • C’est pour cela que l’avenir de C et C++ reste radieux dans les domaines du graphisme, du calcul haute performance, de la physique des hautes énergies et du trading à haute fréquence.
      Parler d’autres langages de programmation dans des domaines dominés par une culture où « les performances passent avant la sécurité » revient un peu à parler à un mur.
  • C’est clairement très intéressant, mais je me demande pourquoi ils n’ont pas d’abord visé Vulkan.
    Cela semble être une cible plus importante aujourd’hui, et il existe déjà des implémentations OpenGL par-dessus.

    • Une couche de compatibilité OpenGL sur Vulkan n’est pas magique.
      Pour prendre en charge certaines fonctionnalités OpenGL, le pilote Vulkan doit supporter les fonctionnalités correspondantes, ce qui nécessite généralement des extensions.
      Autrement dit, le simple fait d’implémenter un pilote Vulkan de base ne donne pas gratuitement le support d’OGL 4.6, et pour que Mesa puisse traduire OGL 4.6 vers Vulkan, le pilote Vulkan doit implémenter l’ensemble des fonctionnalités d’OGL 4.6.
      En plus, Alyssa est quelqu’un qui a déjà fait de la rétro-ingénierie et travaillé sur des projets de pilotes OpenGL.
      Je ne connais pas tous les détails, mais il est très probable qu’il soit bien plus simple et rapide de créer un pilote pour une API familière que pour une API qui ne l’est pas.
    • Ils ont d’abord visé un ancien OpenGL pour faire fonctionner le jeu de fonctionnalités de base.
      À partir de là, il est probable que monter vers un OpenGL plus moderne ait demandé moins de travail qu’une implémentation complète de Vulkan, et qu’ils aient aussi beaucoup appris des éléments nécessaires à Vulkan.
    • J’ai eu une réflexion similaire, mais pour prendre en charge OpenGL au-dessus de Vulkan, il faut de toute façon une version plus élevée de Vulkan et la charge de travail reste importante.
      Ils ont donc probablement choisi de commencer par une version plus basse d’OpenGL afin d’obtenir plus vite quelque chose de fonctionnel.
  • Il est assez surprenant de se dire que, si John Carmack n’avait pas tenu mordicus à utiliser OpenGL dans Quake II dans les années 90, OpenGL n’aurait peut-être jamais acquis une vraie présence dans les jeux 3D

    • Quake n’en est sans doute qu’une petite partie dans l’histoire
      Ce qui a finalement fait d’OpenGL ce qu’il est, c’est SGI et l’effort colossal pour créer des implémentations compatibles sur de nombreux systèmes et architectures
    • Fait amusant : le plus ancien site OpenGL conservé dans les archives affichait une énorme bannière FAST GAMES GRAPHICS avec des graphismes animés de Quake 1 et un menu différent :-P
      https://web.archive.org/web/19970707113513/http://www.opengl.org/
    • Je ne sais pas si c’était l’unique raison, mais le fait que Carmack ait poussé OpenGL a clairement aidé
      Beaucoup de choses liées aux jeux 3D doivent beaucoup à Doom et Quake
    • Quelques années plus tard, en 2011, John Carmack a déclaré que Direct3D était alors une meilleure API
      Selon lui, Microsoft avait eu le courage de continuer à introduire de grands changements incompatibles pour améliorer l’API, tandis qu’OpenGL était freiné par des préoccupations de compatibilité
      Il disait aussi que Direct3D gérait mieux le multithreading et que les versions récentes étaient meilleures pour la gestion d’état
      Il ajoutait cependant qu’id Software restait sur OpenGL par inertie et n’avait pas l’intention de migrer vers Direct3D malgré ses avantages
      Source : https://www.bit-tech.net/news/gaming/pc/carmack-directx-better-opengl/1/
    • Référence de contexte : https://www.chrishecker.com/OpenGL/Press_Release