# Les cartes graphiques/Le processeur de commandes

Dans ce chapitre, nous allons parler de tous les intermédiaires qui se placent entre une application de rendu 3D et les circuits de rendu 3D proprement dit. En premier lieu, on trouve le pilote de la carte graphique, aux fonctions multiples et variées. En second lieu, nous allons parler d'un circuit de la carte graphique qui fait l'interface entre le logiciel et les circuits de rendu 3D de la carte graphique : le processeur de commandes. Ce processeur de commandes est un circuit qui sert de chef d'orchestre, qui pilote le fonctionnement global de la carte graphique. API 3D, pilote de carte graphique et processeur de commande travaillent de concert pour que l'application communique avec la carte graphique. Nous allons d'abord voir le pilote de la carte graphique, avant de voir le fonctionnement du processeur de commande.

## Le pilote de carte graphique

Le pilote de la carte graphique est un logiciel qui s'occupe de faire l'interface entre le logiciel et la carte graphique. De manière générale, les pilotes de périphérique sont des intermédiaires entre les applications/APIs et le matériel. En théorie, le système d'exploitation est censé jouer ce rôle, mais il n'est pas programmé pour être compatible avec tous les périphériques vendus sur le marché. À la place, le système d'exploitation ne gère pas directement certains périphériques, mais fournit de quoi ajouter ce qui manque. Le pilote d'un périphérique sert justement à ajouter ce qui manque : ils ajoutent au système d'exploitation de quoi reconnaître le périphérique, de quoi l'utiliser au mieux. Pour une carte graphique, il a diverses fonctions que nous allons voir ci-dessous.
Avant toute chose, précisons que les systèmes d'exploitation usuels (Windows, Linux, MacOsX et autres) sont fournis avec un pilote de carte graphique générique, compatible avec la plupart des cartes graphiques existantes. Rien de magique derrière cela : toutes les cartes graphiques vendues depuis plusieurs décennies respectent des standards, comme le VGA, le VESA, et d'autres. Et le pilote de base fournit avec le système d'exploitation est justement compatible avec ces standards minimaux. Mais le pilote ne peut pas profiter des fonctionnalités qui sont au-delà de ce standard. L'accélération 3D est presque inexistante avec le pilote de base, qui ne sert qu'à faire du rendu 2D (de quoi afficher l’interface de base du système d'exploitation, comme le bureau de Windows). Et même pour du rendu 2D, la plupart des fonctionnalités de la carte graphique ne sont pas disponibles. Par exemple, certaines résolutions ne sont pas disponibles, notamment les grandes résolutions. Ou encore, les performances sont loin d'être excellentes. Si vous avez déjà utilisé un PC sans pilote de carte graphique installé, vous avez certainement remarqué qu'il était particulièrement lent.

### La gestion des interruptions

Une fonction particulièrement importante du pilote de carte graphique est la réponse aux interruptions lancées par la carte graphique. Pour rappel, les interruptions sont une fonctionnalité qui permet à un périphérique de communiquer avec le processeur, tout en économisant le temps de calcul de ce dernier. Typiquement, lorsqu'un périphérique lance une interruption, le processeur stoppe son travail et exécute automatiquement une routine d'interruption, un petit programme qui s'occupe de gérer le périphérique, de faire ce qu'il faut, ce que l'interruption a demandé.
Il y a plusieurs interruptions possibles, chacune ayant son propre numéro et sa fonction dédiée, chacune ayant sa propre routine d'interruption. Après tout, la routine qui gère un débordement de la mémoire vidéo n'est pas la même que celle qui gère un changement de fréquence du GPU ou la fin du calcul d'une image 3D. Le pilote fournit l'ensemble des routines d'interruptions nécessaires pour gérer la carte graphique.

### La compilation des shaders

Le pilote de carte graphique est aussi chargé de traduire les shaders en code machine. En soi, cette étape est assez complexe, et ressemble beaucoup à la compilation d'un programme informatique normal. Les shaders sont écrits dans un langage de programmation de haut niveau, comme le HLSL ou le GLSL, mais ce n'est pas ce code source qui est compilé par le pilote de carte graphique. À la place, les shaders sont pré-compilés vers un langage dit intermédiaire, avant d'être compilé par le pilote en code machine. Le langage intermédiaire, comme son nom l'indique, sert d'intermédiaire entre le code source écrit en HLSL/GLSL et le code machine exécuté par la carte graphique. Il ressemble à un langage assembleur, mais reste malgré tout assez générique pour ne pas être un véritable code machine. Par exemple, il y a peu de limitations quant au nombre de processeurs ou de registres. En clair, il y a deux passes de compilation : une passe de traduction du code source en langage intermédiaire, puis une passe de compilation du code intermédiaire vers le code machine. Notons que la première passe est réalisée par le programmeur des shaders, non pas par l'utilisateur. Par exemple, les shaders d'un jeu vidéo sont fournis déjà pré-compilés : les fichiers du jeu ne contiennent pas le code source GLSL/HLSL, mais du code intermédiaire.
L'avantage de cette méthode est que le travail du pilote est fortement simplifié. Le pilote de périphérique pourrait compiler directement du code HLSL/GLSL, mais le temps de compilation serait très long et cela aurait un impact sur les performances. Avec l'usage du langage intermédiaire, le gros du travail à été fait lors de la première passe de compilation et le pilote graphique ne fait que finir le travail. Les optimisations importantes ont déjà été réalisées lors de la première passe. Il doit bien faire la traduction, ajouter quelques optimisations de bas niveau par-ci par-là, mais rien de bien gourmand en processeur. Autant dire que cela économise plus le processeur que si on devait compiler complètement les shaders à chaque exécution.
Fait amusant, il faut savoir que le pilote peut parfois remplacer les shaders d'un jeu vidéo à la volée. Les pilotes récents embarquent en effet des shaders alternatifs pour les jeux les plus vendus et/ou les plus populaires. Lorsque vous lancez un de ces jeux vidéo et que le shader originel s'exécute, le pilote le détecte automatiquement et le remplace par la version améliorée, fournie par le pilote. Évidemment, le shader alternatif du pilote est optimisé pour le matériel adéquat. Cela permet de gagner en performance, voire en qualité d'image, sans pour autant que les concepteurs du jeu n'aient quoique ce soit à faire.
Enfin, certains shaders sont fournis par le pilote pour d'autres raisons. Par exemple, les cartes graphiques récentes n'ont pas de circuits fixes pour traiter la géométrie. Autant les anciennes cartes graphiques avaient des circuits de T&L qui s'en occupaient, autant tout cela doit être émulé sur les machines récentes. Sans cette émulation, les vieux jeux vidéo conçus pour exploiter le T&L et d'autres technologies du genre ne fonctionneraient plus du tout. Émuler les circuits fixes disparus sur les cartes récentes est justement le fait de shaders, présents dans le pilote de carte graphique.

### Les autres fonctions

Le pilote a aussi bien d'autres fonctions. Par exemple, il s'occupe d'initialiser la carte graphique, de fixer la résolution, d'allouer la mémoire vidéo, de gérer le curseur de souris matériel, etc.

## Les commandes graphiques/vidéo/autres : des ordres envoyés au GPU

Tous les traitements que la carte graphique doit effectuer, qu'il s'agisse de rendu 2D, de calculs 2D, du décodage matérielle d'un flux vidéo, ou de calculs généralistes, sont envoyés par le pilote de la carte graphique, sous la forme de commandes. Ces commandes demandent à la carte graphique d'effectuer une opération 2D, ou une opération 3D, ou encore le rendu d'une vidéo.
Les commandes graphiques en question varient beaucoup selon la carte graphique. Les commandes sont régulièrement revues et chaque nouvelle architecture a quelques changements par rapport aux modèles plus anciens. Des commandes peuvent apparaître, d'autres disparaître, d'autre voient leur fonctionnement légèrement altéré, etc.
Mais dans les grandes lignes, on peut classer ces commandes en quelques grands types. Les commandes les plus importantes s'occupent de l'affichage 3D : afficher une image à partir de paquets de sommets, préparer le passage d'une image à une autre, changer la résolution, etc. Plus rare, d'autres commandes servent à faire du rendu 2D : afficher un polygone, tracer une ligne, coloriser une surface, etc. Sur les cartes graphiques qui peuvent accélérer le rendu des vidéos, il existe des commandes spécialisées pour l’accélération des vidéos.
Pour donner quelques exemples, prenons les commandes 2D de la carte graphique AMD Radeon X1800.
| Commandes 2D | Fonction |
| PAINT | Peindre un rectangle d'une certaine couleur |
| PAINT_MULTI | Peindre des rectangles (pas les mêmes paramètres que PAINT) |
| BITBLT | Copie d'un bloc de mémoire dans un autre |
| BITBLT_MULTI | Plusieurs copies de blocs de mémoire dans d'autres |
| TRANS_BITBLT | Copie de blocs de mémoire avec un masque |
| NEXTCHAR | Afficher un caractère avec une certaine couleur |
| HOSTDATA_BLT | Écrire une chaîne de caractères à l'écran ou copier une série d'image bitmap dans la mémoire vidéo |
| POLYLINE | Afficher des lignes reliées entre elles |
| POLYSCANLINES | Afficher des lignes |
| PLY_NEXTSCAN | Afficher plusieurs lignes simples |
| SET_SCISSORS | Utiliser des coupes (ciseaux) |
| LOAD_PALETTE | Charger la palette pour affichage 2D |

### Le tampon de commandes

L'envoi des commandes à la carte graphique ne se fait pas directement. La carte graphique n'est pas toujours libre pour accepter une nouvelle commande, soit parce qu'elle est occupée par une commande précédente, soit parce qu'elle fait autre chose. Il faut alors faire patienter les données tant que la carte graphique est occupée. Les pilotes de la carte graphique vont les mettre en attente dans une file (une zone de mémoire dans laquelle on stocke des données dans l'ordre d'ajout) : le tampon de commandes.
Ce tampon de commandes est ce qu'on appelle une file, une zone de mémoire dans laquelle on stocke des données dans l'ordre d'ajout. Si le tampon de commandes est plein, le driver n'accepte plus de demandes en provenance des applications. Un tampon de commandes plein est généralement mauvais signe : cela signifie que la carte graphique est trop lente pour traiter les demandes qui lui sont faites. Par contre, il arrive que le tampon de commandes soit vide : dans ce cas, c'est simplement que la carte graphique est trop rapide comparé au processeur, qui n'arrive alors pas à donner assez de commandes à la carte graphique pour l'occuper suffisamment.

### Le processeur de commandes

Le processeur de commande est un circuit qui gère les commandes envoyées par le processeur. Il lit la commande la plus ancienne dans le tampon de commande, l’interprète et l’exécute, et recommence ainsi de suite. Le processeur de commande est chargé de piloter les circuits de la carte graphique. Un point important est qu'il répartit le travail entre les différents circuits de la carte graphique et assure que l'ordre du pipeline est respecté. En soi, le processeur de commande est un circuit assez compliqué. Sur les cartes graphiques anciennes, c'était un circuit séquentiel complexe, fabriqué à la main et était la partie la plus complexe du processeur graphique. Sur les cartes graphiques modernes, c'est un véritable microcontrôleur, avec un processeur, de la mémoire RAM, etc.
Le processeur de commandes récupère les commandes dans le tampon de commande, en mémoire RAM, pour les recopier dans la mémoire vidéo et/ou une mémoire interne. Cette copie se fait via la technologie DMA, une technologie de transfert de données entre mémoire RAM et périphérique qui n'utilise pas le processeur principal. Une fois la copie faite, le processeur de commande décode la commande et l’exécute sur la carte graphique. Il garde en mémoire l'état de traitement de chaque commande : est-ce qu'elle est en train de s’exécuter sur le processeur graphique, est-ce qu'elle est mise en pause, est-ce qu'elle attend une donnée en provenance de la mémoire, est-ce qu'elle est terminée, etc.
Une commande utilise divers circuits de la carte 3D, de la mémoire vidéo, et d'autres ressources au sens large : des processeurs de shader, notamment. La fonction principale du processeur de commande est de répartir le travail entre les différents circuits. Le processeur de commande décide à quel processeur ou circuit doit être envoyé une commande. C'est le processeur de commande qui s'occupe de la gestion des ressources et qui attribue telle ressource à telle commande.
A chaque instant, le processeur de commande répartit les shaders sur les processeurs de shaders, en faisant en sorte qu'ils soient utilisés le plus possible. Dans le cas le plus simple, les unités sont alimentées en vertex/pixels les unes après les autres (principe du round-robin), mais des stratégies plus optimisées sont la règle de nos jours. Cela est très important sur les cartes graphiques : répartir plusieurs commandes sur plusieurs processeurs est une tâche difficile.
Il envoie aussi certaines commandes aux circuits fixes, comme l’input assembler. Là encore, il faut en sorte que les circuits fixes soient utilisés le plus possible et évite au maximum les situations où ces circuits n'ont rien à faire. Sa tâche est compliquée par le fait que les cartes graphiques actuelles dupliquent leurs unités pour pouvoir faire beaucoup de calculs en parallèle. Elles ont plusieurs unités de traitement des sommets et des pixels, plusieurs ROP, plusieurs unités de textures, etc. Et c'est en partie le travail du processeur de commande que de répartir les calculs sur ces différentes unités.
C'est là un problème assez compliqué pour le processeur de commande et ce pour tout un tas de raisons que nous ne ferons que survoler. Les contraintes d'ordonnancement de l'API et les règles de celle-ci sont une partie de la réponse. Mais d'autres raisons sont intrinsèques au rendu 3D ou à la conception des circuits.

### L'ordonnancement et la gestion des ressources

Le processeur de commande doit souvent mettre en pause certains circuits du pipeline, ainsi que les circuits précédents. Par exemple, si tous les processeurs de vertex shader sont occupés, l’input assembler ne peut pas charger le paquet suivant car il n'y a aucun processeur de libre pour l’accueillir. Dans ce cas, l'étape d’input assembly est mise en pause en attendant qu'un processeur de shader soit libre.
La même chose a lieu pour l'étape de rastérisation : si aucun processeur de shader n'est libre, elle est mise en pause. Sauf que pour la rastérisation, cela a des conséquences sur les circuits précédents la rastérisation. Si un processeur de shader veut envoyer quelque chose au rastériseur alors qu'il est en pause, il devra lui aussi attendre que le rastériseur soit libre. On a la même chose quand les ROP sont occupés : les pixel shader ou l'unité de texture doivent parfois être mis en pause, ce qui peut entrainer la mise en pause des étapes précédentes, etc.
En clair : plus un étape est situé vers la fin du pipeline graphique, plus sa mise en pause a de chances de se répercuter sur les étapes précédentes et de les mettre en pause aussi. Le processeur de commande doit gérer ces situations.

### Le pipelining des commandes

Dans les cartes graphiques les plus rudimentaires, le processeur de commande exécute les commandes dans l'ordre. Il exécute une commande çà la fois et attend qu'une commande soit terminé pour en lancer une autre. Plusieurs commandes sont accumulées dans le tampon de commande, le processeur de commande les traite de la plus ancienne vers la plus récente. Du moins, les anciennes cartes graphiques faisaient comme cela, les cartes graphiques modernes sont un peu plus complexes. Elles n'attendent pas qu'une commande soit totalement terminée avant d'en lancer une nouvelle. Les processeurs de commande actuels sont capables d’exécuter plusieurs commandes en même temps.
L'intérêt est un gain en performance assez important. Pour ceux qui ont déjà eu un cours d'architecture des ordinateurs avancé, lancer plusieurs commandes en même temps permet une forme de pipeline, dont les gains sont substantiels. Par exemple, imaginez qu'une commande de rendu 3D soit bien avancée et n'utilise que les processeurs de shaders et les ROP, mais laisse les unités géométriques libres. Il est alors possible de démarrer la commande suivante en avance, afin qu'elle remplisse les unités géométriques inutilisées. On a alors deux commandes en cours de traitement : une commande qui utilise la fin du pipeline uniquement, une autre qui utilise seulement le début du pipeline graphique.
Le processeur de commande gère donc plusieurs commandes simultanées, à savoir plusieurs commandes qui en sont à des étapes différentes du pipeline. On peut avoir une commande qui est en cours dans l'étage de gestion de la géométrie, une autre dans le rastériseur, et une autre dans les unités de traitement des pixels. Le fait que les étapes du pipeline graphique sont à effectuer dans un ordre bien précis permet ce genre de chose assez facilement.
Une autre possibilité est de faire des rendus en parallèle. Par exemple, imaginez qu'on ait des circuits séparés pour le rendu 2D et 3D. Prenons l'exemple où le calcul d'une image finale demande de faire un rendu 3D suivi d'un rendu 2D, par exemple un jeu vidéo en 3D qui a un HUD dessiné en 2D. Dans ce cas, on peut démarrer le calcul de l'image suivante dans le pipeline 3D pendant que la précédente est dans les circuits 2D, au lieu de laisser inutilisé le pipeline 3D pendant le rendu 2D.
Mais c'est là un défi compliqué à relever pour le processeur de commande, qui donne lieu à son lot de problèmes. Le problème principal est le partage de ressources entre commandes. Par exemple, on peut prendre le cas où une commande n'utilise pas beaucoup les processeurs de shaders. On pourrait imaginer lancer une seconde commande en parallèle, afin que la seconde commande utiliser les processeurs de shaders inutilisés. Mais cela n'est possible que si les circuits fixes sont capables d'accepter une seconde commande. Si la première commande sature les circuits fixe, le lancement de la seconde commande sera empêché. Mais si ce n'est pas le cas, les deux commandes pourront être lancées en même temps. Pareil pour le débit mémoire, qui doit alors être partagé entre deux commandes simultanées, ce qui est à prendre en compte.

### La synchronisation de l’exécution des commandes avec le processeur

Il arrive que le processeur doive savoir où en est le traitement des commandes, ce qui est très utile pour la gestion de la mémoire vidéo.
Un premier exemple : comment éviter de saturer une carte graphique de commande, alors qu'on ne sait pas si elles traite les commandes rapidement ou non ? L'idéal est de regarder où en est la carte graphique dans l'exécution des commandes. Si elle n'en est qu'au début du tampon de commande et que celui-ci est bien remplit, alors il vaut mieux lever le pied et ne pas envoyer de nouvelles commandes. A l'inverse, si le tampon de commande est presque vide, envoyer des commandes est la meilleure idée possible.
Un autre exemple un peu moins parlant est celui de la gestion de la mémoire vidéo. Rappelons qu'elle est réalisée par le pilote de la carte graphique, sur le processeur principal, qui décide d'allouer et de libérer de la mémoire vidéo. Pour donner un exemple d'utilisation, imaginez que le pilote de la carte graphique doive libérer de la mémoire pour pouvoir ajouter une nouvelle commande. Comment éviter d'enlever une texture tant que les commandes qui l'utilisent ne sont pas terminées ? Ce problème ne se limite pas aux textures, mais vaut pour tout ce qui est placé en mémoire vidéo.
Il faut donc que la carte graphique trouve un moyen de prévenir le processeur que le traitement de telle commande est terminée, que telle commande en est rendue à tel ou tel point, etc. Pour cela, on a globalement deux grandes solutions. La première est celle des interruptions, abordées dans les chapitres précédents. Mais elles sont assez couteuses et ne sont utilisées que pour des évènements vraiment importants, critiques, qui demandent une réaction rapide du processeur. L'autre solution est celle du pooling, où le processeur monitore la carte graphique à intervalles réguliers. Deux solutions bien connues de ceux qui ont déjà lu un cours d'architecture des ordinateurs digne de ce nom, rien de bien neuf : la carte graphique communique avec le processeur comme tout périphérique.
Pour faciliter l'implémentation du pooling, la carte graphique contient des registres de statut, dans le processeur de commande, qui mémorisent tout ou partie de l'état de la carte graphique. Si un problème survient, certains bits du registre de statut seront mis à 1 pour indiquer que telle erreur bien précise a eu lieu. Il existe aussi un registre de statut qui mémorise le numéro de la commande en cours, ou de la dernière commande terminée, ce qui permet de savoir où en est la carte graphique dans l'exécution des commandes. Et certaines registres de statut sont dédiés à la gestion des commandes. Ils sont totalement programmable, la carte graphique peut écrire dedans sans problème.
Les cartes graphiques récentes incorporent des commandes pour modifier ces registres de statut au besoin. Elles sont appelées des commandes de synchronisation : les barrières (fences). Elles permettent d'écrire une valeur dans un registre de statut quand telle ou telle condition est remplie. Par exemple, si jamais une commande est terminée, on peut écrire la valeur 1 dans tel ou tel registre. La condition en question peut être assez complexe et se résumer à quelque chose du genre : "si jamais toutes les shaders ont fini de traiter tel ensemble de textures, alors écrit la valeur 1024 dans le registre de statut numéro 9".

### La synchronisation de l’exécution des commandes intra-GPU

Lancer plusieurs commandes en parallèle dans le pipeline permet de gagner en performance.Mais cela a un gros défaut : il n'est pas garantit que les commandes se terminent dans l'ordre. Si on démarre une seconde commande après la première, il arrive que la seconde finisse avant. Pire : il n'est pas garantit qu'elles s'exécutent dans l'ordre. Dans la plupart des cas, cela ne pose pas de problèmes. Mais dans d'autres, cela donne des résultats erronés.
Pour donner un exemple, utilisons les commandes de rendu 2D vues plus haut, histoire de simplifier le tout. Imaginez que vous codez un jeu vidéo et que le rendu se fait en 2D, partons sur un jeu de type FPS. Vous avez une série de commandes pour calculer l'image à rendre à l'écran, suivie par une série de commandes finales pour dessiner le HUB. Pour dessiner le HUD, vous utilisez des commandes HOSTDATA_BLT, dont le but est d'écrire une chaîne de caractères à l'écran ou copier une série d'image bitmap dans la mémoire vidéo. Et bien ces commandes HOSTDATA_BLT doivent démarrer une fois que le rendu de l'image est complétement terminé. Imaginez que vous dessiniez le HUD sur une portion de l'image pas encore terminée et que celui-ci est effacé par des écritures ultérieures !
Pour éviter tout problème, les GPU incorporent des commandes de synchronisation intra-GPU, destinées au processeur de commande, aussis appelées des commandes de sémaphore. Elles permettent de mettre en pause le lancement d'une nouvelle commande tant que les précédentes ne sont pas totalement ou partiellement terminées. Les plus simples empêchent le démarrage d'une commande tant que les précédentes ne sont pas terminées. D'autres permettent de démarrer une commande si et seulement si certaines commandes sont terminées. D'autres sont encore plus précises : elles empêchent le démarrage d'une nouvelle commande tant qu'une commande précédente n'a pas atteint un certain stade de son exécution. Là encore, de telles commandes sont des commandes du style "attend que le registre de statut numéro N contienne la valeur adéquate avant de poursuivre l'exécution des commandes".
Les commandes en question peuvent être sur le même modèle que précédemment, à savoir des commandes qui lisent les registres de statut. Mais elles peuvent aussi se baser sur le fait que telle ou telle ressource de rendu est libre ou non. Des commandes successives se partagent souvent des données, et que de nombreuses commandes différentes peuvent s'exécuter en même temps. Or, si une commande veut modifier les données utilisées par une autre commande, il faut que l'ordre des commandes soit maintenu : la commande la plus récente ne doit pas modifier les données utilisées par une commande plus ancienne. Avec ce modèle, les sémaphores bloquent une commande tant qu'une ressource (une texture) est utilisée par une autre commande.
| Commandes de synchronisation | Fonction |
| NOP | Ne rien faire |
| WAIT_SEMAPHORE | Attendre la synchronisation avec un sémaphore |
| WAIT_MEM | Attendre que la mémoire vidéo soit disponible et inoccupée par le CPU |

## L'arbitrage de l'accès à la carte graphique

Il n'est pas rare que plusieurs applications souhaitent accéder en même temps à la carte graphique. Imaginons que vous regardez une vidéo en streaming sur votre navigateur web, avec un programme de cloud computing de type Folding@Home qui tourne en arrière-plan, sur Windows. Le décodage de la vidéo est réalisé par la carte graphique, Windows s'occupe de l'affichage du bureau et des fenêtres, le navigateur web doit afficher tout ce qui est autour de la vidéo (la page web), le programme de cloud computing va lancer ses calculs sur la carte graphique, etc. Des situations de ce genre sont assez courantes et c'est soit le pilote qui s'en charge, soit la carte graphique elle-même.

### L'arbitrage logiciel du GPU

L'arbitrage logiciel est la méthode la plus simple. Concrètement, c'est le système d'exploitation et/ou le pilote de la carte graphique qui gèrent l'arbitrage du GPU. Dans les deux cas, tout est fait en logiciel. Les commandes sont accumulées dans un tampon de commande, voire plusieurs, et l'OS/pilote décide quelle commande envoyer en priorité.
Sur Windows, avant l'arrivée du modèle de driver dit Windows Display Driver Model, de telles situations étaient gérées simplement. Les applications ajoutaient des commandes dans une file de commande unique. Les commandes étaient exécutées dans l'ordre, en mode premier entrée dans la file premier sorti/exécuté. Il n'y avait pour ainsi dire pas d'arbitrage entre les applications. Et ce n'était pas un problème car, à l'époque, les seules applications qui utilisaient le GPU étaient des jeux vidéos fonctionnant en mode plein écran.
Avec le modèle de driver Windows Display Driver Model (WDDM), le GPU est maintenant arbitré, à savoir que les applications ont accès à tour de rôle au GPU, certaines ayant droit à plus de temps GPU que d'autres. Chaque programme a accès à la carte graphique durant quelques dizaines ou centaines de millisecondes, à tour de rôle. Si le programme finissait son travail en moins de temps que la durée impartie, il laissait la main au programme suivant. S’il atteignait la durée maximale allouée, il était interrompu pour laisser la place au programme suivant. Et chaque programme avait droit d'accès à la carte graphique chacun à son tour. Un tel algorithme en tourniquet est très simple, mais avait cependant quelques défauts. De nos jours, les algorithmes d'ordonnancement d'accès sont plus élaborés, bien qu'il soit difficile de trouver de la littérature ou des brevets sur le sujet.
Une autre fonctionnalité de WDDM est que les applications utilisant le GPU peuvent se partager la mémoire vidéo sans se marcher dessus. Avant, toutes les applications avaient accès à toute la mémoire vidéo, bien que ce soit par l’intermédiaire de commandes spécifiques. Mais avec WDDM, chaque application dispose de sa propre portion de mémoire vidéo, à laquelle est la seule à avoir accès. Une application ne peut plus lire le contenu réel de la mémoire vidéo, et encore moins lire le contenu des autres applications.
    Il s'agit d'un équivalent à l'isolation des processus pour les programmes Windows, mais appliqué à la mémoire vidéo et non la mémoire système. Et cette isolation est rendue d'autant plus simple que les GPU modernes implémentent un système de mémoire virtuelle à pagination très poussé, avec du swap en mémoire RAM système, une protection mémoire, et bien d'autres fonctionnalités. Nous en reparlerons dans les chapitres sur la mémoire vidéo.

### L'arbitrage accéléré par le GPU

Depuis 2020, avec l'arrivée de la Windows 10 May 2020 update, Windows est capable de déléguer l'arbitrage du GPU au GPU lui-même. Le GPU gère lui-même l'arbitrage, du moins en partie. En partie, car l'OS gére les priorité, à savoir quelle application a droit à plus de temps que les autres. Par contre, la gestion du quantum de temps ou les commutations de contexte (on stoppe une application pour laisser la place à une autre), est du fait du GPU lui-même.
L'avantage est que cela décharge le processeur d'une tâche assez lourde en calcul, pour la déporter sur le GPU. Un autre avantage est que le GPU peut gérer plus finement les quantum de temps et la commutation de contexte. Le CPU principal gère des durées assez importantes, de l'ordre de la milliseconde. De plus, il doit communiquer avec le GPU par des mécanismes temporellement imprécis, comme des interruptions. Avec l'arbitrage accéléré par le GPU, le GPU n'a plus besoin de communiquer avec le CPU pour l'arbitrage et peut le faire plus précisément, plus immédiatement. Un gain en performance théorique est donc possible.
L'arbitrage accéléré par le GPU est le fait soit du processeur de commandes, soit d'un processeur spécialisé, séparé.