Etude comparative des différents HLSL
Ensuite parce que les différents langages sont très proches : inspirés directement du C, ils en reprennent la syntaxe et la sémantique, offrent le même type d’ajouts provenant du C++ (les constructeurs, la surcharge de fonctions, la définition de variables n’importe où avant leur utilisation ou le type booléen par exemple) et présentent les mêmes restrictions (pas de pointeurs, pas de support pour les switch ou les goto, les enums ou les unions…).
Ce n’est donc pas à ce niveau que les différences importent et nous allons donc explorer ici les différences de philosophie entre les HLSL concurrents qui sont bien plus instructives. Intéressons nous en particulier à la compilation de ces fameux shaders de haut niveau.
Voilà un schéma résumant le fonctionnement du Cg :

Le shader est donc compilé en fonction du profil choisi vers l’assembleur intermédiaire que l’on retrouve dans les API. Ensuite ce shader est géré comme s’il avait été écrit directement en assembleur : comme le Cg n’est lié à aucune API son implication doit être transparente vis-à-vis de celle-ci. Il y a donc trois passes : une passe qui transforme le langage de haut niveau en assembleur (à la charge du compilateur), une passe qui assemble le shader dans un format binaire (au niveau de l’API) et une passe qui se charge d’interpréter cet assembleur en byte code spécifique au hardware (à la charge du driver).
DirectX HLSL fonctionne d’une manière à peu près similaire à une différence près : étant lié à l’API le compilateur peut compiler directement en assembleur binaire qui sera envoyé au driver de la carte 3D, évitant ainsi une étape. En d’autres termes par défaut on ne voit pas le résultat du code généré par le compilateur contrairement au Cg qui génère un shader en mode texte qu’il faut ensuite assembler pour obtenir la représentation binaire qui sera transmise au driver. La compilation se fait soit à l’exécution de l’application (sur la machine cliente donc) par l’intermédiaire de la bibliothèque D3DX, soit lors du développement des shaders par l’intermédiaire du compilateur en ligne de commande fxc.
L’avantage de compiler à l’exécution est que cela permet d’examiner la machine cliente et d’en déduire un compile target approprié (il faut toujours garder à l’esprit que le shader en question DOIT pouvoir être compilé pour ce compile target, s’il excède les ressources disponibles il ne compilera pas) mais en contrepartie les sources des shaders sont disponibles avec l’application. En pré compilant à la création les shaders seule la version asm binaire est fournie, cela permet aussi d’examiner le code fourni par le compilateur et de l’optimiser le cas échéant.

La compilation se divise donc en deux passes : une passe qui compile le shader de haut niveau en assembleur binaire (à la charge du compilateur si le shader est pré compilé ou de l’API si c’est à l’exécution) et une passe qui se charge d’interpréter cet assembleur en byte code spécifique au hardware (à la charge du driver).
Dans les deux cas présentés on remarque donc que les HLSL sont compilés vers un assembleur intermédiaire qu’il soit en mode texte (Cg) ou binaire (HLSL). GLSLANG utilise une autre approche qui n’expose pas un assembleur intermédiaire commun à tous les chips au dessus du hardware.

Contrairement à DirectX HLSL, GLSLANG fournit directement le code de haut niveau au driver de la carte 3D lequel contient un compilateur qui produira directement un byte code spécifique au hardware. La compilation est donc effectué en une seule passe : du langage de haut niveau au byte code directement sans étape intermédiaire.
Tout ceci c’est bien joli mais quels sont les avantages et les inconvénients de ces deux méthodes ? Tout d’abord examinons les avantages de la solution de GLSLANG : intégrer le compilateur au driver permet d’assurer que les anciennes applications bénéficieront automatiquement des mises à jour du compilateur inclues dans les drivers récents. Cela évite aussi les fabricants de hardware de se conformer au moule d’un assembleur commun qui empêche de tirer le meilleur profit du hardware. Par exemple le NV30 dispose de fonctions comme sin et cos câblées. En compilant vers un assembleur commun comme celui de DirectX 9 ces fonctions seront inutilisées car elles ne sont pas supportées. Elles seront donc remplacées par une macro réalisant une approximation via les séries de Taylor. Le fabricant de hardware connaissant mieux son matériel il est plus à même d’écrire un compilateur optimisant pour son architecture et d’en exploiter toutes les ressources. Enfin cela permet des optimisations de haut niveau que la double traduction pourrait éliminer.
Mais cette méthode présente aussi des inconvénients. Le principal étant qu’écrire un compilateur est quelque chose de délicat, et que ce n’est pas du tout le même travail qu’écrire un driver. Certaines sociétés pourraient donc ne pas avoir les compétences ou tout simplement ne pas souhaiter dépenser les ressources nécessaires à ce genre de tâche, l’avantage de passer par un assembleur intermédiaire est que l’écriture du compilateur est à la charge du développeur de l’API. L’autre avantage de fournir un accès à l’assembleur est que cela permet ensuite de passer derrière le compilateur pour optimiser le code généré. Enfin le développeur perd une certaine forme de contrôle et ne peut plus être assuré qu’une version particulière du compilateur qui aura été préalablement testée va être utilisée : le driver pourra donc être une nouvelle source de bugs.
On peut donc constater qu’il y a du bon dans les deux approches et qu’il est bien difficile de se prononcer sur la meilleure surtout que les HLSL sont extrêmement récents et que par conséquent l’on manque de recul sur leur utilisation. Au final il y a fort à parier que comme pour les API aujourd’hui le choix sera plus basé sur des préférences personnelles que sur de véritables considérations techniques.