Quand un calcul calculé est-il plus efficace qu'un pixel shader pour le filtrage d'images?


37

Les opérations de filtrage d’image telles que flou, SSAO, bloom, etc. sont généralement effectuées à l’aide de shaders de pixels et d’opérations de "regroupement", dans lesquelles chaque invocation de shader de pixels génère un certain nombre d’améliorations de texture pour accéder aux valeurs de pixel voisines, et calcule la valeur d’un pixel unique. le résultat. Cette approche présente une inefficacité théorique en ce sens que de nombreux extractions redondantes sont effectuées: les invocations de shader à proximité permettront de récupérer plusieurs des mêmes texels.

Une autre façon de le faire est d'utiliser des calcul shaders. Celles-ci présentent l'avantage potentiel de pouvoir partager une petite quantité de mémoire sur un groupe d'invocations de shader. Par exemple, vous pouvez faire en sorte que chaque appel récupère un texel et le stocke dans la mémoire partagée, puis calcule les résultats à partir de là. Cela pourrait être ou ne pas être plus rapide.

La question est de savoir dans quelles circonstances (le cas échéant) la méthode Compute-Shader est-elle réellement plus rapide que la méthode Pixel Shader? Cela dépend-il de la taille du noyau, du type d’opération de filtrage, etc.? Il est clair que la réponse variera d’un modèle de GPU à un autre, mais j’aimerais savoir s’il existe des tendances générales.


Je pense que la réponse est "toujours" si le calcul est fait correctement. Ce n'est pas trivial à réaliser. Un compute shader est également une meilleure correspondance qu'un pixel shader conceptuellement pour les algorithmes de traitement d'image. Un pixel shader offre toutefois moins de marge de manœuvre pour écrire des filtres peu performants.
Bernie

@bernie Pouvez-vous clarifier ce qui est nécessaire pour que le calcul soit "fait correctement"? Peut-être écrire une réponse? Toujours bon d'avoir plus de perspectives sur le sujet. :)
Nathan Reed

2
Maintenant regarde ce que tu m'as fait faire! :)
bernie

Outre le partage du travail entre les threads, la possibilité d'utiliser le calcul asynchrone est l'une des principales raisons d'utiliser les shaders de calcul.
JarkkoL

Réponses:


23

L'un des avantages architecturaux des calculeurs de nuage pour le traitement des images est qu'ils ignorent l' étape ROP . Il est très probable que les écritures de pixel shaders passent par tout le matériel de fusion habituel, même si vous ne l'utilisez pas. En règle générale, les calcul shaders suivent un chemin différent (et souvent plus direct) vers la mémoire. Vous éviterez ainsi un goulot d'étranglement. J'ai entendu parler de gains de performances assez considérables attribués à cela.

L’un des désavantages architecturaux des calculeurs de shaders est que le processeur graphique ne sait plus quels éléments de travail s’appliquent à quels pixels. Si vous utilisez le pipeline de pixel shading, le processeur graphique a la possibilité d’intégrer le travail dans un front de warp / wave qui écrit dans une zone de la cible de rendu contiguë en mémoire (qui peut être en mosaïque dans l’ordre Z ou similaire). les raisons). Si vous utilisez un pipeline de calcul, il est possible que le processeur graphique ne fonctionne plus en lots optimaux, ce qui entraîne une utilisation accrue de la bande passante.

Vous pourrez peut-être utiliser à nouveau cette compression de chaîne / front d'onde modifiée en un avantage, si vous savez que votre opération particulière possède une sous-structure que vous pouvez exploiter en regroupant les tâches connexes dans le même groupe de threads. Comme vous l'avez dit, vous pouvez théoriquement donner une pause au matériel d'échantillonnage en échantillonnant une valeur par voie et en plaçant le résultat dans une mémoire partagée par groupe afin que les autres voies puissent y accéder sans échantillonnage. La réussite de cette opération dépend du coût de la mémoire partagée par votre groupe: s'il est meilleur marché que le cache de texture de niveau le plus bas, il peut s'agir d'une solution gagnante, mais rien ne le garantit. Les GPU gèrent déjà assez bien les récupérations de texture très locales (par nécessité).

Si vous souhaitez partager les résultats à une étape intermédiaire de l'opération, il peut être plus judicieux d'utiliser la mémoire groupée (car vous ne pouvez pas vous rabattre sur le matériel d'échantillonnage de texture sans avoir écrit votre résultat intermédiaire dans la mémoire). Malheureusement, vous ne pouvez pas non plus vous fier aux résultats de tout autre groupe de threads. Par conséquent, la deuxième étape devrait se limiter à ce qui est disponible dans la même vignette. Je pense que l'exemple canonique ici calcule la luminance moyenne de l'écran pour l'exposition automatique. Je pourrais aussi imaginer combiner le suréchantillonnage de texture avec une autre opération (car le suréchantillonnage, contrairement au sous-échantillonnage et au flou, ne dépend d'aucune valeur en dehors d'une mosaïque donnée).


Je doute sérieusement que la POR ajoute une surcharge de performances si le mélange est désactivé.
GroverManheim

@GroverManheim dépend de l'architecture! L'étape Fusion / ROP de sortie doit également traiter des garanties de commande, même si le mélange est désactivé. Avec un triangle plein écran, il n’ya aucun risque de commande, mais le matériel peut ne pas le savoir. Il peut y avoir des voies rapides spéciales dans le matériel, mais sachant avec certitude que vous y êtes admissible…
John Calsbeek

11

John a déjà écrit une excellente réponse, alors considérez cette réponse comme une extension de la sienne.

Je travaille actuellement beaucoup avec des calcul shaders pour différents algorithmes. En général, j'ai constaté que les shaders de calcul pouvaient être beaucoup plus rapides que leurs équivalents de pixels équivalents ou transformer les alternatives basées sur le retour.

Une fois que vous avez compris le fonctionnement des calculeurs de shaders, ils ont beaucoup plus de sens dans de nombreux cas. L'utilisation de pixels shaders pour filtrer une image nécessite la configuration d'un framebuffer, l'envoi de sommets, l'utilisation de plusieurs niveaux de shader, etc. Pourquoi devrait-il être nécessaire pour filtrer une image? Avoir l'habitude de restituer des quadruples plein écran pour le traitement d'images est certainement la seule raison "valable" de continuer à les utiliser, à mon avis. Je suis convaincu qu'un nouveau venu dans le domaine des graphiques de calcul trouverait les shaders de calcul un ajustement beaucoup plus naturel pour le traitement d'image que le rendu aux textures.

Votre question concerne le filtrage d'images en particulier, je ne m'étendrai donc pas trop sur d'autres sujets. Dans certains de nos tests, le simple fait de configurer une réaction de transformation ou de changer les objets framebuffer pour les transformer en texture peut engendrer des coûts de performance d’environ 0,2 ms. Gardez à l'esprit que cela exclut tout rendu! Dans un cas, nous avons conservé le même algorithme que celui utilisé pour calculer les shaders et constaté une augmentation notable des performances.

Lorsque vous utilisez des calcul shaders, vous pouvez utiliser davantage de silicium sur le processeur graphique pour effectuer le travail réel. Toutes ces étapes supplémentaires sont requises lors de l’utilisation de la route pixel shader:

  • Assemblage de vertex (lecture des attributs de vertex, diviseurs de vertex, conversion de type, élargissement à vec4, etc.)
  • Le vertex shader doit être programmé, même s'il est minimal.
  • Le rastériseur doit calculer une liste de pixels pour ombrer et interpoler les sorties de sommet (probablement uniquement des coordonnées de texture pour le traitement de l'image).
  • Tous les différents états (test de profondeur, test alpha, ciseaux, fusion) doivent être définis et gérés

Vous pourriez faire valoir que tous les avantages de performance mentionnés précédemment pourraient être annulés par un pilote intelligent. Tu aurais raison. Un tel pilote pourrait identifier que vous restituez un quad plein écran sans test de profondeur, etc. et configurer un "tracé rapide" qui ignore tout le travail inutile effectué pour prendre en charge les pixel shaders. Je ne serais pas surpris que certains pilotes accélèrent les passes de post-traitement dans certains jeux AAA pour leurs GPU spécifiques. Vous pouvez bien sûr oublier tout traitement de ce type si vous ne travaillez pas sur un jeu AAA.

Cependant, le conducteur ne peut pas trouver de meilleures opportunités de parallélisme offertes par le pipeline de calcul. Prenons l'exemple classique d'un filtre gaussien. En utilisant des calcul shaders, vous pouvez faire quelque chose comme ceci (séparer le filtre ou non):

  1. Pour chaque groupe de travail, divisez l'échantillonnage de l'image source en fonction de la taille du groupe de travail et stockez les résultats dans un groupe de mémoire partagée.
  2. Calculez la sortie du filtre en utilisant les exemples de résultats stockés dans la mémoire partagée.
  3. Écrire dans la texture de sortie

L'étape 1 est la clé ici. Dans la version pixel shader, l’image source est échantillonnée plusieurs fois par pixel. Dans la version de calcul, chaque texel source est lu une seule fois dans un groupe de travail. Les lectures de texture utilisent généralement un cache basé sur des tuiles, mais ce cache est toujours beaucoup plus lent que la mémoire partagée.

Le filtre gaussien est l’un des exemples les plus simples. D'autres algorithmes de filtrage offrent d'autres possibilités de partager des résultats intermédiaires au sein de groupes de travail utilisant la mémoire partagée.

Il y a cependant un piège. Les shaders de calcul nécessitent des barrières de mémoire explicites pour synchroniser leur sortie. Il existe également moins de garanties pour se protéger contre les accès mémoire errants. Pour les programmeurs ayant de bonnes connaissances en programmation parallèle, les calcul shaders offrent beaucoup plus de flexibilité. Cette flexibilité signifie toutefois qu'il est également plus facile de traiter les shaders de calcul comme du code C ++ ordinaire et d'écrire du code lent ou incorrect.

Les références


Le parallélisme d'échantillonnage amélioré que vous décrivez est intriguant - j'ai un sim fluide qui est déjà implémenté avec des calcul shaders avec un grand nombre d'instances de plusieurs échantillons par pixel. mais je suis suspendu - comment puis-je accéder aux pixels voisins lorsqu'ils tombent dans un groupe de travail différent? Par exemple, si j'ai un domaine de simulation 64x64, réparti sur une dépêche (2,2,1) de numthreads (16,16,1), comment le pixel avec id.xy == [15,15] obtiendrait-il ses pixels voisins ?
Tossrock

Dans ce cas, je vois 2 choix principaux. 1) augmentez la taille du groupe à partir de 64 et n'écrivez que les résultats pour les pixels 64x64. 2) le premier échantillon 64 + nX64 + n est divisé en quelque sorte dans votre groupe de travail 64x64, puis utilise cette grille "d'entrée" plus grande pour les calculs. La meilleure solution dépend bien sûr de vos conditions spécifiques et je vous suggère de poser une autre question pour plus d’informations, car les commentaires sont mal adaptés.
Bernie

3

Je suis tombé sur ce blog: Compute Shader Optimizations for AMD

Étant donné les astuces possibles dans Compute Shader (spécifiques aux calculateurs), je me demandais si la réduction parallèle sur Compute Shader était plus rapide que sur Pixel Shader. J'ai envoyé un courrier électronique à l'auteur, Wolf Engel, pour lui demander s'il avait déjà essayé le pixel shader. Il a répondu par l'affirmative quand il a écrit sur le blog, la version de calcul était bien plus rapide que la version de pixel shader. Il a également ajouté qu’aujourd’hui les différences sont encore plus grandes. Donc, apparemment, il y a des cas où utiliser Compute Shader peut être d'un grand avantage.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.