Les cartes graphiques/Les Render Output Target
Pour rappel, les étapes précédentes du pipeline graphiques manipulaient non pas des pixels, mais des fragments. Pour rappel, la distinction entre fragment et pixel est pertinente quand plusieurs objets sont l'un derrière l'autre. Si vous tracez une demi-droite dont l'origine est la caméra, et qui passe par le pixel, il arrive qu'elle intersecte la géométrie en plusieurs points. La couleur finale dépend de la couleur de tous ces points d'intersection. Intuitivement, l'objet le plus proche est censé cacher les autres et c'est donc lui qui décide de la couleur du pixel, mais cela demande de déterminer quel est l'objet le plus proche. De plus, certains objets sont transparents et la couleur finale est un mélange de la couleur de plusieurs points d'intersection.
Tout demande de calculer un pseudo-pixel pour chaque point d'intersection et de combiner leurs couleurs pour obtenir le résultat final. Les pseudo-pixels en question sont des fragments. Chaque fragment possède une position à l'écran, une coordonnée de profondeur, une couleur, ainsi que quelques autres informations potentiellement utiles. Les fragments attribués à un même pixel, qui sont à la même position sur l'écran, sont donc combinés pour obtenir la couleur finale de ce pixel. Pour résumer, la profondeur des fragments doit être gérée, de même que la transparence, etc.
Et c'est justement le rôle de l'étage du pipeline que nous allons voir maintenant. Ces opérations sont réalisées dans un circuit qu'on nomme le Raster Operations Pipeline (ROP), aussi appelé Render Output Target, situé à la toute fin du pipeline graphique. Dans ce qui suit, nous utiliserons l'abréviation ROP pour simplifier les explications. Le ROP effectue quelques traitements sur les fragments, avant d'enregistrer l'image finale dans la mémoire vidéo.
Les fonctions des ROP
[modifier | modifier le wikicode]Les ROP incorporent plusieurs fonctionnalités qui sont assez diverses. Leur seul lien est qu'il est préférable de les implémenter en matériel plutôt qu'en logiciel, et en dehors des unités de textures. Il s'agit de fonctionnalités assez simples, basiques, mais nécessaires au fonctionnement de tout rendu 3D. Elles ont aussi pour particularité de beaucoup accéder à la mémoire vidéo. C'est la raison pour laquelle le ROP est situé en fin de pipeline, proche de la mémoire vidéo. Voyons quelles sont ces fonctionnalités.
Le mélange alpha et le z-buffer
[modifier | modifier le wikicode]Sa fonction la plus importante est l'élimination des pixels cachés, grâce au tampon de profondeur. Pour chaque fragment, il lit le pixel correspondant dans le tampon de profondeur, fait la comparaison de profondeur, et met à jour le tampon de profondeur. Nous en avons déjà beaucoup parlé dans les chapitres précédents, notamment dans le chapitre sur les bases du rendu 3D et dans celui sur le rastériseur (avec l'élimination précoce des pixels cachés).
Une autre fonction est le mélange alpha, pour gérer la transparence, qu'on a là encore vu dans le chapitre sur les bases du rendu 3D. Là encore, les ROPs lisent, pour chaque fragment, le pixel correspondant dans le framebuffer, font le mélange alpha, et enregistrent le résultat dans le framebuffer.
Le mélange alpha est supporté sur tous les ROPs, depuis les premières cartes graphiques, et est encore supporté jusqu'à ce jour. Par contre, ce n'est pas le cas qui est du test alpha. Ce dernier était pris en charge dans les ROPs jusqu'à DirectX 9, mais est maintenant émulé dans les pixel shaders depuis DirectX 10.
Il en est de même pour les effets de brouillard. Ils impliquent à la fois du mélange alpha mais aussi la coordonnée de profondeur, ce qui en fait que leur implémentation dans les ROPs parait logique. Aussi, les premières cartes graphiques calculaient le brouillard dans les ROP, en fonction de la coordonnée de profondeur du fragment. De nos jours, il est calculé par les pixel shaders et les ROP n'incorporent plus de technique de brouillard spécialisée.
Les ROPs ont d'autres fonctions, plus méconnues, qu'on n'a pas abordé dans les chapitres précédents.
Le tampon de stencil
[modifier | modifier le wikicode]Le stencil est une fonctionnalité des API graphiques qui existe depuis très longtemps. Il sert pour générer des effets graphiques très variés, qu'il serait vain de lister ici. Il a notamment été utilisé pour calculer des ombres volumétriques (le moteur de DOOM 3 en faisait grand usage à la base), des réflexions simples, des shadowmaps, et bien d'autres.
Pour le résumer, on peut le voir comme une sorte de tampon de profondeur où la coordonnée z est remplacée par u octet dont le programmeur peut faire ce qu'il veut. L'idée est que chaque pixel/fragment se voit attribuer une valeur entière, généralement codée sur un octet, que les programmeurs peuvent faire varier à loisir. L'octet ajouté est appelé l'octet de stencil. Il a une certaine valeur, qui est calculée par la carte graphique, généralement par les shaders. Il ne remplace pas la coordonnée de profondeur, mais s'ajoute à celle-ci.
Les octets de stencil sont placés dans le tampon de profondeur. L'ensemble forme un tableau qui associe 32 bits à chaque" pixel : 24 bits pour une coordonnée z, 8 pour l'octet de stencil. Lors du passage d'un fragment les ROPs, la carte graphique lit le pixel correspondant, dans le tampon de profondeur. Il récupère la coordonnée z, mais aussi l'octet de stencil. Puis il compare l'octet de stencil avec celui du fragment traité. Si le test échoue, le fragment ne passe pas à l'étape de test de profondeur et est abandonné. S'il passe, le tampon de stencil est mis à jour. Par mis à jour, on veut dire que le ROP peut faire diverses manipulations dessus : l'incrémenter, le décrémenter, le mettre à 0, inverser ses bits, remplacer par l'octet de stencil du fragment, etc. Les opérations possibles sont bien plus nombreuses qu'avec le tampon de profondeur, qui se contente de remplacer la coordonnée z par celle du fragment.
Les fonctions héritées des blitters 2D
[modifier | modifier le wikicode]Les ROPS implémentent aussi des techniques utilisées sur les blitters des anciennes cartes d'affichage 2D, comme l'application d'opérations logiques sur chaque pixel enregistré dans le framebuffer. Les opérations logiques en question peuvent prendre une à deux opérandes. Les opérandes sont soit un pixel lu dans le framebuffer, soit un fragment envoyé au ROP. Les opérations logiques à un opérande peuvent inverser, mettre à 0 ou à 1 le pixel dans le framebuffer, ou faire la même chose sur le fragment envoyé en opérande. Les opérations à deux opérandes lisent un pixel dans le framebuffer, et font un ET/OU/XOR avec le fragment opérande (un opérande peut être inversé). Elles sont utilisées pour faire du traitement d'image ou du rendu 2D, rarement pour du rendu 3D.
Les ROPs gèrent aussi des masques d'écritures, qui permettent de décider si un pixel doit être écrit ou non en mémoire. Il est possible d'inhiber certaines écritures dans le framebuffer, le tampon de profondeur ou le tampon de stencil. Inhiber la mise à jour d'un pixel dans le tampon de profondeur est utile pour gérer la transparence. Si un pixel est transparent, même partiellement, il ne faut pas mettre à jour le tampon de profondeur, et cela peut être géré par ce système de masquage. Les masquages des couleurs permettent de ne modifier qu'une seule composante R/G/B au lieu de modifier les trois en même temps, pour faire certains effets visuels.
L'architecture matérielle d'un ROP
[modifier | modifier le wikicode]Les ROP contiennent des circuits pour gérer la profondeur des fragments. Ils effectuent un test de profondeur, à savoir que les fragments correspondant à un même pixel sont comparés pour savoir lequel est devant l'autre. Ils contiennent aussi des circuits pour gérer la transparence des fragments. Le ROP gère aussi l'antialiasing, de concert avec l'unité de rastérisation. D'autres fonctionnalités annexes sont parfois implémentées dans les ROP. Par exemple, les vielles cartes graphiques implémentaient les effets de brouillards dans les ROPs. Le tout est suivi d'une unité qui enregistre le résultat final en mémoire, où masques et opérations logiques sont appliqués.
Les différentes opérations du ROP doivent se faire dans un certain ordre. Par exemple, gérer la transparence demande que les calculs de profondeur se fassent globalement après ou pendant le mélange alpha. Ou encore, les masques et opérations logiques se font à la toute fin du rendu. L'ordre des opérations est censé être le suivant : test alpha, test du stencil, test de profondeur, mélange alpha. Du moins, la carte graphique doit donner l'impression que c'est le cas. Elle peut optimiser le tout en traitant le tampon de profondeur, de couleur et de stencil en même temps, mais donner les résultats adéquats.
Un ROP est typiquement organisé comme illustré ci-dessous. Notons que les circuits de gestion de la profondeur et de la transparence sont séparés dans les schémas, mais il s'agit là d'une commodité qui ne reflète pas forcément l'implémentation matérielle. Et si ces deux circuits sont séparés, ils communiquent entre eux, notamment pour gérer la profondeur des fragments transparents.

Les ROPs récupèrent les fragments calculés par les pixels shaders et/ou les unités de texture, via un circuit d'interconnexion spécialisé. Chaque ROP est connecté à toutes les unités de shader, même si la connexion n'est pas forcément directe. Toute unité de shader peut envoyer des pixels à n'importe quel ROP. Les circuits d'interconnexion sont généralement des réseaux d'interconnexion de type crossbar, comme illustré ci-contre (le premier rectangle rouge).
Le ROP effectue beaucoup de lectures et écritures en mémoire vidéo. Or, la bande passante mémoire est limitée, ce qui fait que le ROP est un goulot d'étranglement assez important pour le rendu 3D. Heureusement, de nombreuses optimisations permettent d'optimiser le tout. Elles agissent sur la lecture du tampon de profondeur, mais aussi sur le framebuffer.
Le fast clear du framebuffer
[modifier | modifier le wikicode]Une première optimisation porte sur le framebuffer. Le framebufferest souvent réutilisé d'une image sur l'autre. Quand une image a été envoyée à l'écran, le framebuffer est remis à zéro pour accueillir une nouvelle image. Et ce avec ou sans double buffering. La mise à zéro est censée se faire en remettant réellement le framebuffer à zéro, en écrivant des 0 pour chaque pixel du framebuffer. Mais il y a moyen de s'en passer.
Pour cela, l'idée est que le framebuffer est découpé en tiles, des carrés de 4, 8, 16 pixels de côté. Les tiles ont généralement la même taille que les tiles utilisées pour la rastérisation, mais passons sur ce détail. L'idée est de mémoriser, pour chaque tile, si elle est mise à 0 ou non. Il suffit de cela d'un seul bit par tile, appelé le bit RESET. L'ensemble des bits RESET est mémorisé dans une petite mémoire SRAM, intégrée aux ROPs.
Lorsqu'on souhaite remettre à zéro le framebuffer, il suffit de mettre à 0 tous les bits RESET dans cette SRAM, pas besoin d’accéder à la mémoire vidéo. Avant toute lecture dans le framebuffer, le ROP lit cette SRAM pour vérifier si la tile en question a été remise à 0. Si ce n'est pas le cas, il lit le pixel voulu depuis le framebuffer. Mais si c'est le cas, alors le ROP ne fait pas la lecture et fournit un pixel à zéro à la place, qui est utilisé pour le mélange alpha ou autre. La moindre écriture dans une tile met le bit RESET à 0 : la tile entière est considérée comme non-remise à zéro, même si un seul pixel a été modifié dedans.
Notons que l'usage d'une granularité par tile est un compromis. On peut ne peut pas utiliser un bit par pixel, car cela demanderait d'utiliser une SRAM énorme. De même, utiliser un seul bit pour tout le framebuffer ruinerait totalement l'optimisation : le framebuffer entier serait considéré comme non-RESET dès la première écriture d'un pixel dedans, on ne sauverait qu'un nombre trop limité d'accès mémoire.
La z-compression
[modifier | modifier le wikicode]La technique de z-compression compresse le tampon de profondeur. Plus précisément, elle découpe le tampon de profondeur en tiles, en blocs carrés, qui sont compressés séparément les uns des autres. La taille des tiles est souvent la même que celle utilisée par le rastériseur pour la rastérisation grossière. Par exemple, la z-compression des cartes graphiques ATI radeon 9800, découpait le tampon de profondeur en tiles de 8 * 8 fragments, et les encodait avec un algorithme nommé DDPCM (Differential differential pulse code modulation).
Précisons que cette compression ne change pas la taille occupée par le tampon de profondeur, mais seulement la quantité de données lue/écrite. La raison est que les tiles doivent avoir une place fixe en mémoire. Par exemple, si une tile non-compressée prend 64 octets, on trouvera une tile tous les 64 octets en mémoire vidéo, afin de simplifier les calculs d'adresse, afin que le ROP sache facilement où se trouve la tile à lire/écrire. Avec une vraie compression, les tiles se trouveraient à des endroits très variables d'une image à l'autre.
Par contre, la z-compression réduit la quantité de données écrite dans le tampon de profondeur. Par exemple, au lieu d'écrire une tile non-compressée de 64 octets, on écrira une tile de seulement 6 octets, les 58 octets restants étant pas lus ou écrits. On obtient un gain en performance, pas en mémoire.

Le format de compression ajoute un bit par tile, qui indique si elle est compressée ou non. Le bit qui indique si la tile est compressée permet de laisser certaines tiles non-compressés, dans le cas où la compression ne permet pas de gagner de la place. La compression ajoute souvent un second bit, qui indique si la tile est à zéro ou non, sur le même modèle que pour le framebuffer. Il accélère la remise à zéro du tampon de profondeur. Au lieu de réellement remettre tout le tampon de profondeur à 0, il suffit de réécrire un bit par tile. Le gain en nombre d'accès mémoire peut se révéler assez impressionnant.
Les deux bits en question peuvent être placés à deux endroits différents. La première solution serait d'utiliser une portion de la mémoire vidéo, mais cela demanderait de faire deux lectures par accès au tampon de profondeur. La vraie solution est d'utiliser une SRAM reliée aux ROPs, qui est assez grande pour mémoriser tout le tampon de profondeur, du moins avec deux bits par tile.
Le cache de profondeur
[modifier | modifier le wikicode]Une optimisation complémentaire ajoute une ou plusieurs mémoires caches dans le ROP, dans le circuit de profondeur. Ce cache de profondeur stocke des portions du tampon de profondeur qui ont été lues ou écrite récemment. Comme cela, pas besoin de les recharger plusieurs fois : on charge un bloc une fois pour toutes, et on le conserve pour gérer les fragments qui suivent.
Sur certaines cartes graphiques, les données dans le cache de profondeur sont stockées sous forme compressées dans le cache de profondeur, là encore pour augmenter la taille effective du cache. D'autres cartes graphiques ont un cache qui stocke des données décompressées dans le cache de profondeur. Tout est question de compromis entre accès rapide au cache et augmentation de la taille du cache.
Il faut savoir que les autres unités de la carte graphique peuvent lire le tampon de profondeur, en théorie. Cela peut servir pour certaines techniques de rendu, comme pour le shadowmapping. De ce fait, il arrive que le cache de profondeur contienne des données qui sont copiées dans d'autres caches, comme les caches des processeurs de shaders. Le cache de profondeur n'est pas gardé cohérent avec les autres caches du GPU, ce qui signifie que les écritures dans le cache de profondeur ne sont pas propagées dans les autres caches du GPU. Si on modifie des données dans ce cache, les autres caches qui ont une copie de ces données auront une version périmée de la donnée. C'est souvent un problème, sauf dans le cas du cache de profondeur, pour lequel ce n'est pas nécessaire. Cela évite d'implémenter des techniques de cohérence des caches couteuses en circuits et en performance, alors qu'elles n'auraient pas d'intérêt dans ce cas précis.
Le z-fast pass
[modifier | modifier le wikicode]Le z-fast pass améliore la performance des prépasses z, une technique utilisée par de nombreux moteurs de jeux vidéo. L'idée est que le moteur de jeu effectue plusieurs passes, chacune faisant un truc précis, la prépasse z étant l'une de ces passes. Lors d'une prépasse z, le moteur de jeu calcule la scène 3D, rastérise l'image, et remplit le tampon de profondeur uniquement. Il ne place pas de textures, ne calcule pas de pixels shaders, il se préoccupe uniquement des coordonnées de profondeur des pixels. Au final, le rendu ne donne que le tampon de profondeur, qui est utilisé par les passes suivantes.
L'utilité est très variable, mais il y a deux raisons pour effectuer une prépasse z : la performance, mais aussi certains effets graphiques. Par exemple, les effets d'occlusion ambiante "screen space" utilisent le tampon de profondeur pour faire leur travail. Il en est de même pour les shadowmaps, qui effectuent une prépasse z par ombre à afficher. Une autre utilisation est que cela permet d'utiliser élimination des pixels cachés très performante. On effectue une prépasse z pour calculer le tampon de profondeur final, qui est ensuite utilisé par les passes suivantes pour éliminer les pixels cachés. Ainsi, les pixels cachés ne sont pas texturés et pixel shadés, avec certitude.
Toujours est-il qu'une prépasse z utilise les ROP "à moitié", dans le sens où seul le tampon de profondeur est utilisé, par la gestion des couleurs. Mais il se trouve que les circuits qui servent pour le mélange alpha peuvent être réutilisés pour faire les comparaisons de profondeur ! Le résultat est que les ROP peuvent fonctionner à double vitesse lors d'une prépasse z ! Cela demande cependant de concevoir les circuits du ROP pour en profiter. L'optimisation est parfois appelée le z-fast pass.
Tous les GPU depuis la Geforce FX en sont capables. Il y a cependant quelques contraintes. Premièrement, le ROP doit être configuré de manière à n’accéder qu'au tampon de profondeur, ils ne doivent pas dessiner dans le framebuffer. Le mélange 'alpha doit être désactivé, de même que l'alpha-test. D'autres contraintes supplémentaires sont parfois présentes, surtout sur les vieux GPUs. Par exemple, l'antialiasing doit être désactivé lors de la prépasse z. Et mine de rien, cela ne marche que pour les prépasses z pures. Par exemple, certaines techniques de rendu différé augmentent la prépasse z pour que celle-ci ne calcule pas que le tampon de profondeur, mais aussi d'autres informations comme les normales : elles ne profitent pas de cette optimisation.
Pourquoi ne pas émuler les ROPs dans les pixel shader ?
[modifier | modifier le wikicode]Les ROPs effectuent plusieurs opérations basiques, mais les deux plus importantes sont la gestion du tampon de profondeur et de la transparence. Par transparence, on veut parler du mélange alpha. Pour la gestion du tampon de profondeur, on veut parler du z-test, qui compare la profondeur de deux pixels/fragments. Il s'agit d'opérations simples, qu'un processeur de shader peut faire sans problèmes.
Par exemple, le z-test demande de faire plusieurs étapes :
- calculer l'adresse du pixel dans le tampon de profondeur ;
- lire le pixel dans le tampon de profondeur ;
- Faire la comparaison entre profondeurs ;
- Si le résultat de la comparaison est okay :
- écrire la nouvelle valeur z dans le tampon de profondeur, et écrire le nouveau pixel dedans.
Le mélange alpha demande lui de :
- calculer l'adresse du pixel dans le framebuffer ;
- lire le pixel dans le framebuffer ;
- faire des additions et multiplications pour le mélange alpha :
- écrire le nouveau pixel dans le framebuffer.
Pour résumer il faut pouvoir faire : calcul d'adresse, lecture, écriture, addition, multiplication et comparaisons. Et toutes ces opérations sont supportées nativement par les processeurs de shaders, ce sont des instructions communes. Il est donc possible d'émuler les ROPs dans les pixels shaders. En pratique, c'est assez rare, et il y a une bonne explication à cela.
Les GPU de type sort-last doivent "trier les pixels"
[modifier | modifier le wikicode]Émuler les ROPs dans un pixel shader est trivial, comme on vient de le voir. Sauf que cela ne marche que si le GPU fait le rendu un pixel à la fois. Le tampon de profondeur est conçu pour traiter un pixel à la fois, idem pour le mélange alpha. Mais si on ne traite pas l'image pixel par pixel, alors les deux algorithmes dysfonctionnent. Donc, tout va bien s'il n'y a qu'un seul processeur de pixel shader, et que celui-ci est conçu pour ne traiter qu'un pixel à la fois, qu'une seule instance de shader. Mais cela ne marche pas sur les GPU modernes, qui ont non seulement près d'une centaine de processeurs de shaders, chacun étant conçu pour traiter une centaine de fragments/pixels en même temps !
Pour donner un exemple, imaginons la situation illustrée ci-dessous. Supposons que l'on ait assez de processeurs de shaders pour traiter plusieurs triangles en même temps. Par malchance, les processeurs rendent en même temps deux triangles opaques qui se recouvrent à l'écran. Là où ils se recouvrent, les deux triangles vont générer deux fragments par pixel, et un seul sera le bon. Pas de chance, les deux fragments sont rendus en parallèle dans deux processeurs séparés. Les deux processeurs lisent la même donnée dans le tampon de profondeur et les deux fragments passent le z-test, car ils n'ont aucun moyen de savoir la coordonnée z en cours de traitement dans l'autre processeur. Les deux processeurs vont alors écrire leur résultat en mémoire et c'est premier arrivé, premier servi. Le résultat n'est pas forcément celui attendu : le pixel le plus proche peut être écrit avant le plus lointain, ou inversement.

Pour obtenir un bon rendu, le GPU doit forcer le z-test à se faire fragment par fragment, du moins quand on regarde un pixel individuel. Il reste possible de traiter des pixels différents en parallèle, mais pas deux fragments d'un même pixel. En utilisant des processeurs de shaders qui travaillent en parallèle, cette contrainte est parfois brisée et le rendu donne des résultats incorrects. Le tampon de profondeur n'est pas conçu pour être parallélisé, idem pour le mélange alpha. Il faut donc une sorte de point de synchronisation dans le pipeline pour éviter tout problème. Et c'est à ça que servent les ROPs.
Une explication alternative est la suivante. Les fragments doivent être rendus dans l'ordre de soumission. C'est à dire que des pixels qui sortent du rastériseur dans un certain ordre doivent être enregistré en mémoire dans ce même ordre. Et les pixels ne quittent pas le rastériseur dans le désordre non plus : des triangles arrivant au rastériseur dans un certain ordre seront traité dans ce même ordre.
Le problème est que les GPU étant massivement parallèle, les triangles et pixels seront traités dans le désordre, avant que leurs résultats soient remis en ordre. Pour les triangles/sommets, la remise en ordre se fait au niveau de l'assemblage de primitives, juste avant le rastériseur. Pour les fragments, la remise en ordre se fait dans les ROPs, en sortie des shaders, à la fin du pipeline. Du moins, sur les GPU de type sort-last. Les ROPs ne font donc pas que faire le z-test et le mélange alpha, ils ont des circuits de remise en ordre pour ça, qui sont de plus fortement mélés au tampon de profondeur et au framebuffer.
Le traitement parallèle des fragments sur les GPU sort-last
[modifier | modifier le wikicode]Plus haut, j'ai dit qu'il doit être impossible de traiter en même temps deux fragments d'un même pixel. Mais il reste possible de traiter des pixels différents en parallèle, mais pas deux fragments d'un même pixel. Reste à faire en sorte de ne pas envoyer deux fragments d'un même pixel dans les processeurs de shaders.
Une solution pour cela serait de mémoriser, pour chaque pixel, si un pixel shader est en train de le traiter. Il suffit de mémoriser un bit par pixel pour cela, dans une table d'utilisation, concrètement une petite mémoire. Elle serait mise à jour par les processeurs de shaders, et consultée par le rastériseur. Quand le rastériseur génère un fragment, il consulte cette table, pour vérifier s'il y a conflit avec les fragments en cours de traitement. Il attend si c'est le cas, le pixel shader finira par finir de traiter le pixel au bout d'un moment. Mais l'inconvénient de cette solution est qu'elle a besoin d'une mémoire partagée par tous les processeurs de shaders, qui est difficile à concevoir sans faire des concessions en termes de performances.
Une autre solution serait de mémoriser tous les pixels en cours de traitement. Quand le rastériseur génère un fragment, il mémorise les coordonnées x,y de ce fragment à l'écran, dans une table des pixels occupés. Dès qu'un pixel shader se termine, la table des pixels occupés est mise à jour. Le rastériseur consulte cette table quand il génère un fragment, afin de détecter les conflits. S'il y a conflit, le rastériseur attend que le fragment conflictuel, en cours de traitement dans le pixel shader, soit traité.
L’inconvénient de la solution précédente est que la table des pixels occupés est techniquement une mémoire associative, une sorte de mémoire cache, qui est plus complexe qu'une simple RAM. Il est très difficile de créer une mémoire de ce genre qui soit capable de mémoriser plusieurs dizaines ou centaine de milliers de pixels, pour gérer une centaine de processeurs de shaders. Par contre, elle fonctionne pas trop mal pour un petit nombre de processeurs de shaders, qui fonctionnent à basse fréquence. Cela explique que les GPU pour PC ont des ROPs séparés des processeurs de shaders : ces GPU ont beaucoup trop de processeurs de shaders.
Par contre, quelques cartes graphiques destinées les smartphones et autres appareils mobiles émulent les ROPs dans les pixel shaders. Mais il y a une bonne raison à cela : non seulement, ils n'ont que très peu de processeurs de shader, mais ce sont aussi des GPU en rendu à tuiles. L'avantage est qu'ils rendent une tile à la fois, ce qui fait qu'il y a besoin de tester les conflits entre fragments à l'intérieur d'une tile, pas pour l'écran complet. Et cela simplifie grandement les circuits de test, notamment la table des pixels occupés, qui est bien plus petite.