Comment fonctionne la mise à jour d'un tampon de profondeur dans le GPU?

10

En ce moment, j'essaie d'implémenter une sorte de tampon de profondeur dans le logiciel et j'ai un gros problème quand j'écris dessus. Avoir un mutex est une exagération absolue. J'ai donc créé un nombre de mutex égal au nombre de threads. Je bloque un mutex basé sur le pixel actuel (pixel_index% mutexes_number) et cela fonctionne mieux, mais toujours très très lentement. Et je me demande comment c'est fait dans un vrai GPU? Existe-t-il un algorithme intelligent ou un matériel qui le gère?

nikitablack
la source

Réponses:

9

Un matériel hautement spécialisé le gère. Une stratégie typique consiste pour un GPU à rasteriser les tuiles et à stocker les informations de profondeur dans des formats compressés (par exemple l'équation z lorsqu'un polygone recouvre complètement une tuile). Cela permet de tester simultanément une tuile entière; d'autres astuces HW intéressantes incluent des tests de profondeur avant l'exécution du pixel shader (en supposant que les conditions le permettent - le shader ne peut pas écrire de valeur de profondeur). Vous pouvez envisager quelque chose de similaire dans le logiciel, comme avoir chaque thread "propriétaire" d'un sous-ensemble de tuiles et parcourir chaque primitive indépendamment, ou imiter des stratégies multi-GPU telles que des images alternées ou des lignes raster alternatives.

Daniel M Gessel
la source
11

Dans un vrai GPU, au lieu d'avoir plusieurs cœurs essayant de lire / écrire la même région du tampon de profondeur et de tenter de se synchroniser entre eux, le tampon de profondeur est divisé en tuiles (telles que 16 × 16 ou 32 × 32), et chacune la tuile est affectée à un seul noyau. Ce noyau est alors responsable de toute la pixellisation de cette tuile: tous les triangles qui touchent cette tuile seront tramés (dans cette tuile) par le noyau propriétaire. Il n'y a alors aucune interférence entre les cœurs, et il n'est pas nécessaire qu'ils se synchronisent lorsqu'ils accèdent à leur partie du framebuffer.

Cela implique que les triangles qui touchent plusieurs tuiles devront être tramés par plusieurs coeurs. Il y a donc une étape de redistribution du travail entre le traitement de la géométrie (opérations sur les sommets et triangles) et le traitement des pixels.

À l'étape de la géométrie, chaque cœur peut traiter un bloc de primitives d'entrée; puis, pour chaque primitive, il peut rapidement déterminer les tuiles touchées par la primitive (c'est ce que l'on appelle la «rasterisation grossière») et ajouter la primitive à une file d'attente pour chaque cœur qui possède l'une des tuiles affectées.

Ensuite, à l'étape des pixels, chaque cœur peut lire la liste des primitives dans sa file d'attente, calculer la couverture en pixels pour les carreaux que possède le cœur et procéder aux tests de profondeur, à l'ombrage des pixels et à la mise à jour du framebuffer, sans aucune coordination supplémentaire. avec d'autres noyaux.

Nathan Reed
la source