Toute cette puissance juste pour dessiner des pixels?

Je regardais les caractéristiques des cartes graphiques récentes et je me suis posé une question : toute cette puissance sert vraiment juste à dessiner des pixels plus beaux? En 2005, le GPU reste construit pour le rendu, mais son organisation suggère déjà une autre façon de calculer.

Le CPU possède quelques unités très flexibles, de gros caches et une logique élaborée pour les branches. Le GPU consacre davantage de transistors à répéter des opérations semblables sur beaucoup de sommets ou fragments. Le pipeline graphique fournit naturellement des milliers d’éléments indépendants : transformer chaque sommet et calculer chaque pixel.

CPU : préparer données et commandes
  ↓ bus AGP/PCI Express
GPU : vertex shaders → rasterisation → pixel shaders
  ↓
mémoire vidéo : textures, tampons, résultat

Pour détourner ce pipeline, les chercheurs représentent un tableau de nombres comme une texture. Chaque texel contient une ou plusieurs valeurs. Un pixel shader lit les textures d’entrée, effectue le même calcul pour chaque coordonnée et écrit dans une texture de sortie grâce au rendu hors écran.

Une addition de deux vecteurs devient conceptuellement :

pour chaque pixel i en parallèle
    sortie[i] = textureA[i] + textureB[i]

Dans un langage de shader, l’opération reste minuscule :

float4 main(float2 uv : TEXCOORD0) : COLOR
{
    return tex2D(A, uv) + tex2D(B, uv);
}

Le problème est tout ce qui entoure ces trois lignes. Le programme CPU doit convertir les données vers un format de texture accepté, les transférer dans la mémoire vidéo, configurer une surface de rendu, dessiner un rectangle qui lance un fragment par élément, puis relire le résultat. La lecture GPU vers CPU est particulièrement coûteuse parce qu’elle synchronise le pipeline. Pour additionner seulement 100 nombres, le transfert prend plus de temps que le calcul.

Le GPU gagne lorsque le travail est massivement parallèle et assez dense pour amortir les déplacements : traitement d’images, simulation sur une grille ou algèbre linéaire. Il perd sur une liste chaînée, beaucoup de branches différentes ou un algorithme où chaque élément dépend immédiatement du précédent. Les premiers shaders ont aussi des limites de longueur, de précision flottante, de formats de sortie et de lectures indirectes.

Je ferais un benchmark honnête :

  1. produire un tableau de 1 024 × 1 024 valeurs;
  2. mesurer la conversion et le transfert vers la carte;
  3. mesurer plusieurs itérations qui restent entièrement sur le GPU;
  4. mesurer la lecture finale vers le CPU;
  5. comparer au CPU avec le même type et la même tolérance numérique;
  6. répéter sur deux cartes et deux pilotes.

La tolérance compte : deux chemins flottants peuvent arrondir différemment. Une image ne révèle pas toujours cette différence, mais un calcul scientifique doit la mesurer. Le code doit aussi détecter les capacités de la carte et refuser proprement ou fournir un chemin CPU si le profil de shader ou le format flottant manque.

Le parallèle ne retire pas les dépendances. Un pilote bogué peut donner un résultat faux ou faire tomber l’application. Le GPU partage sa mémoire avec les textures de l’affichage. Le refroidissement et l’alimentation limitent la fréquence : une carte qui consomme beaucoup rejette de la chaleur et peut devenir instable si le boîtier respire mal.

Une panne du chemin GPU ne doit pas corrompre le résultat silencieusement. Pour un calcul important, je compare périodiquement un petit échantillon au chemin CPU et je conserve assez de contexte pour reprendre tout le lot sur le processeur.

Je ne remplacerais pas encore un calcul de production par un shader détourné. Les outils exigent de penser comme un moteur graphique et le débogage reste spécialisé. Mais l’expérience enseigne une pratique solide : mesurer séparément transfert, calcul et retour, puis garder les données près de l’unité qui les traite. La puissance du GPU devient intéressante quand on lui donne beaucoup du même travail et qu’on évite de traverser le bus à chaque étape.