I. Introduction▲
Valve Software, le studio de développement à l'origine de jeux phares comme Half Life ou encore Team Fortress, a annoncé le portage de son moteur « Source » sur Linux. John McDonald (NVIDIA), Rich Geldreich et Mike Satrain (Valve Software) reviennent sur ce portage et ont présenté à la GPU Technology Conference à San Jose, en Californie, les raisons de ce portage et les difficultés rencontrées.
II. Conférence▲
Cliquez pour lire la vidéo
III. Pourquoi avoir porté Source ?▲
Valve avance plusieurs arguments quant à la nécessité de ce portage :
- Linux est ouvert ;
- Linux se tourne vers les jeux (lentement) ;
- c'est une étape avant un portage vers les mobiles (OpenGL est très proche de OpenGL ES) ;
- les performances ;
- avoir Steam sur Linux.
III-A. OpenGL▲
III-A-1. Fonctionnalités▲
Un avantage d'OpenGL se trouve dans son fonctionnement. En effet, les fonctionnalités sont exposées suivant les capacités du matériel et non du système d'exploitation. Ainsi, les possesseurs d'ordinateurs fonctionnant sous Windows XP (encore très présent en Chine) sont bloqués à utiliser DirectX 9 alors qu'OpenGL peut permettre des fonctionnalités similaires à ce que l'on retrouve dans DirectX 10 et 11.
III-A-2. Gouvernance▲
Les spécifications sont ouvertes et consultables par tous (ici). OpenGL est géré par un comité et tout le monde peut y participer (avec un coût, mais qui reste léger).
IV. Quels sont les points à connaître entre Windows et Linux▲
IV-A. SDL▲
Tous ceux qui souhaitent porter un programme Windows vers Linux devraient penser à la SDL. La SDL est une bibliothèque C, qui vous permettra de gérer tous les problèmes de fenêtrage et cela de manière indépendante à la plateforme (même pour les mobiles). La SDL est utilisée dans tous les portages de Valve, même dans Steam.
IV-B. Système de fichiers▲
Le premier point à noter est la sensibilité à la casse de Linux. En effet, Windows ne fait pas de différence entre majuscules et minuscules, mais Linux si. La première solution est de mettre en minuscules tous les noms des ressources et des répertoires. La seconde est de construire un cache de fichiers et de chercher pour les noms similaires.
IV-C. Autres problèmes▲
IV-C-1. #define▲
Valve possédaient quelques #define problématiques, notamment certains programmeurs assimilent Linux à un serveur dédié.
IV-C-2. Internationalisation▲
De plus, le studio a rencontré des problèmes liés à l'internationalisation cassant certains printf()/scanf(). Leur solution a été de définir la locale à en_US.utf8 puis de gérer l'internationalisation en interne. Toutefois, chez les utilisateurs n'ayant pas la locale en_US.utf8, un avertissement est affiché.
IV-C-3. Police de caractères▲
Valve conseille de se renseigner sur freetype et fontconfig. Toutefois, la taille des polices n'est pas la même que sous Windows.
IV-C-4. RDTSC▲
Au lieu d'utiliser RDTSC pour obtenir le temps CPU, le studio conseille d'utiliser clock_gettime(CLOCK_MONOTONIC) à la place.
IV-C-5. Entrées souris▲
Le support des entrées utlisateur est normalement bon, mais quelques gestionnaires de fenêtrages capturent aussi le focus clavier faisant que Alt-Tab interrompt les événements provenant de la souris.
IV-C-6. Gestion des configurations multiécrans▲
Même si les configurations multiécrans sont moins bien gérées sous les systèmes d'exploitation Linux, SDL le gère pour vous et vous n'en avez donc pas à vous en soucier.
V. Quels sont les outils sous Linux▲
Valve propose des binaires compatibles avec la majorité des distributions. Le SDK possède tout ce qui est utile pour compiler dans un ensemble d'outils. De plus, des versions de débogage sont disponibles : https://github.com/ValveSoftware/steam-runtime.
V-A. CPU compilation et débogage▲
Valve utilise :
- GCC (compilateur) ;
- GDB (débogage) ;
- CGDB (débogage) ;
- LDD (équivalent de dumpbin) ;
- NM (informations sur les symboles) ;
- objdump (désassembleur et informations sur les binaires) ;
- readelf (plus de détails sur les binaires) ;
- make.
V-B. Analyse de performance sur CPU▲
Valve utilise :
V-B-1. Telemetry▲
Telemetry est un outil payant de RAD Game Tools permettant la visualisation des performances. Celui-ci a un très bas surcoût permettant de le laisser actif durant tout le développement. Il permet d'identifier rapidement les images longues à afficher et donne toutes les informations nécessaires sur cette image.
V-B-2. Résolution de symboles▲
Linux possède un système magique de résolution des symboles (équivalent à _NT_SYMBOL_PATH) :
- dans ~/.gbdinit :
- définir debug-file-directory /usr/lib/debug:/mnt/symstore/debug
- /mnt/symstore/debug est partagé et les symboles doivent être transférés dessus
Cela ne fonctionne actuellement qu'avec GDB, mais devrait aussi fonctionner avec l'outil « perf » de Google.
VI. Quels sont les points à connaître entre Direct3D et OpenGL▲
VI-A. Correspondance des versions▲
DirectX 9 | OpenGL 2 | Shaders |
DirectX 10 | OpenGL 3 | Bibliothèque orientée flux Geometry shaders |
DirectX 11 | OpenGL 4 | Tesselation Compute shaders |
VI-B. Portage▲
L'idée de Valve est de faire une implémentation de Direct3D 9/10/11 en utilisant OpenGL. Pour cela, une nouvelle DLL serait mise en place dans l'application et le code ne connaîtrait pas quelle bibliothèque est utilisée, même pour le code du rendu.
VI-B-1. Performance▲
Les performances ont été un point important, mais finalement, cela n'a pas été un problème. La nouvelle pile de Valve est plus rapide que la pile Direct3D d'environ 20 %.
VI-B-2. Pièces importantes▲
Les points importants durant cette transition ont été :
- les textures ;
- les tampons de sommets (« vertex buffers ») ;
- les tampons d'index (« index buffers ») ;
- la création du contexte OpenGL ;
- les shaders.
VI-B-3. Différences▲
OpenGL possède des données locales aux threads. Ainsi :
- un thread ne peut avoir qu'un seul contexte actif ;
- un contexte ne peut être courant que dans un seul thread à la fois ;
- les appels OpenGL ne font rien lorsqu'il n'y a aucun contexte ;
- MakeCurrent affecte les relations entre le thread courant et le contexte.
OpenGL est une bibliothèque C. Chaque objet possède un identifiant. Beaucoup de fonctions n'utilisent pas d'identifiant et agissent sur les objets courants. Normalement, un identifiant est un GLuint.
OpenGL supporte les extensions alors que Direct3D n'en a officiellement pas. OpenGL est bavard, mais très efficace : ne jugez pas un morceau de code au nombre de lignes, donc profilez.
VI-B-3-a. Objets OpenGL▲
OpenGL possède de multiples objets : textures, tampons, FBO…
Un objet est défini comme courant à l'aide d'un sélecteur. Les modifications suivantes sont alors appliquées à celui-ci. La plupart des objets possèdent un objet par défaut : 0.
VI-B-3-b. Extensions▲
Il est conseillé d'utiliser GLEW. Il existe des extensions par plateforme et par constructeur (mais qui peuvent être supportées par d'autres).
VI-B-3-c. Extensions utiles▲
Les extensions utiles d'après Valve sont :
- EXT_direct_state_access (la plupart des fonctions prennent un nom d'objet directement, sans avoir besoin de liaison. Le code devient plus facile à lire et nécessite moins de transitions. Similaire aux modèles d'utilisation de Direct3D. Sachant que l'extension est implémentée du côté du pilote, vous pouvez la réimplémenter lorsqu'elle n'est pas présente.) ;
- EXT_swap_interval (permet le changement de synchronisation verticale) ;
- EXT_swap_control_tear (permet le tearing lorsque l'image n'est pas prête à temps. Extension demandée par John Carmack et implémentée rapidement par la suite) ;
- ARB_debug_output (gestion d'erreur par callback permettant de parcourir la pile d'appels) ;
- ARB_texture_storage ;
- ARB_sampler_objects.
VI-B-3-d. Autres extensions intéressantes▲
- NVX_gpu_memory_info/GL_ATI_meminfo (récupère des informations sur le GPU) ;
- GL_GREMEDY_string_marker (équivalent à D3DPERF) ;
- GL_ARB_vertex_array_bgra (correspond mieux aux UINT de D3D) ;
- GL_APPLE_client_storage/GL_APPLE_texture_range (pas pour Linux, mais utile pour Mac).
VI-B-4. Astuces▲
Lors d'une recherche Google, cherchez avec et sans GL_.
Lisez les spécifications (même si cela est difficile), cela vous permettra de mieux comprendre le fonctionnement des cartes graphiques et du pipeline de rendu.
VI-C. Lacunes d'OpenGL▲
Valve a aussi rencontré quelques problèmes avec OpenGL :
- fonctionnels :
- états des textures → peut être résolu avec ARB_sampler_objects,
- différence d'ordre des matrices et sens des textures,
- différences d'origine des textures,
- convention du pixel central (OpenGL correspond à DirectX10) ;
- performance :
- problèmes de MakeCurrent (préférer un seul thread, ne pas tenter de l'appeler à chaque frame) ;
- sérialisation du pilote (les pilotes récents sont multithreads et la synchronisation des données est coûteuse, il faut dont éviter glGet*, glGetError*, les fonctions qui retournent une valeur, les fonctions qui copient un nombre non déterminable de données du client. ARB_debug_output peut vous aider).
De plus, chaque vendeur possède des différences dans l'implémentation et il faut tester au moins une fois pour chacun d'entre eux.
VI-C-1. Création d'un contexte OpenGL▲
La création d'un contexte OpenGL est simple. Il suffit de créer une fenêtre, puis le contexte, mais cela ne vous donnera pas nécessairement le contexte spécifié. La création de contexte « robuste » est plus difficile et nécessite des extensions WGL/GLX :
- créer une fenêtre (à ne pas afficher) ;
- créer un contexte ;
- faire une requête pour récupérer les extensions spécifiques aux fenêtres (WGL/GLX) ;
- créer une autre fenêtre (fenêtre de l'application) ;
- créer un contexte en utilisant les fonctions des extensions ;
- détruire le premier contexte ;
- détruire la première fenêtre.
Avec la SDL 2, cela se résume à SDL_GL_SetAttribute et SDL_CreateWindow.
VI-D. Divers▲
VI-D-1. Attributs de sommets▲
Il est nécessaire de relier le tampon avant son utilisation. Si cela vous déplaît, vous pouvez utiliser les Vertex Attribute Objects (VAO), mais cela est plus lent sur toutes les implémentations, donc Valve le déconseille. À la place, l'extension ARB_vertex_attrib_binding pourra vous aider et rend le code plus facile à lire.
VI-D-2. Mise à jour des tampons▲
Il est déconseillé d'utiliser MapBuffer qui retourne un pointeur, car cela provoque la sérialisation du pilote ou pire, cela peut provoquer une nécessité de synchronisation entre le CPU et le GPU. À la place, il faut utiliser BufferSubData, ou BufferData lorsque vous voulez supprimer tous les éléments que vous aviez.
VI-D-3. Rendu dans une texture▲
Les Frame Buffer Objects (FBO) permettent le rendu dans une texture en OpenGL. Il faut toutefois noter qu'ils ne sont pas partagés entre les contextes. Valve utilise un cache de FBO permettant de toujours récupérer un FBO correspondant à la cible de rendu voulue. Ainsi, les échanges d'attachements sont évités. En effet, le pilote effectue de nombreuses vérifications lors de ces échanges et donc, vous fait perdre du temps.
VI-D-4. Shaders▲
Il est nécessaire d'être précautionneux lors d'un changement de shaders, car les variables uniformes définies pour celui-ci sont aussi changés. Une solution est d'utiliser les Uniform Buffer Object (UBO), mais vous pouvez aussi utiliser une trace globale de vos variables uniformes et les redéfinir avant l'affichage.
De plus, Valve utilise une table de hachage pour les program shaders (notamment pour ne pas les traduire du HLSL plusieurs fois).
VI-D-5. Performances▲
Les fonctions les plus appelées peuvent être « inline » pour améliorer les performances, mais n'oubliez pas de profiler l'application pour vérifier le gain de cette pratique.
De plus, il est quelques fois nécessaire d'écrire du code spécifique au constructeur de la carte graphique afin de l'optimiser pour celle-ci. Vous avez aussi l'avantage de la spécification publique, vous permettant de vous retourner auprès du constructeur et de lui rapporter des bogues.
VI-D-6. Outils▲
Voici une liste d'outils utilisés par Valve :
- NVIDIA Nsight ;
- PerfStudio et gDEBugger ;
- CodeXL ;
- Apitrace.
VII. Remerciements▲
Merci à ClaudeLELOUP pour sa relecture orthographique.