gKit2 light
|
Les shaders ont besoin de données pour fonctionner et de stockage pour écrire leurs résultats. Pour une application très simple, toutes les données sont statiques et l'application ne fait pas de mise à jour. Pour les applications un peu plus évoluées, il est souvent nécessaire de modifier certaines données utilisées par les shaders : des buffers ou des textures.
Lorsque l'application alloue un buffer, il faut indiquer l'utilisation du buffer : cf paramètre usage
et le flag GL_STATIC_DRAW
dans glBufferData()
, par exemple. Ce flag indique, en théorie, que le buffer est alloué dans la mémoire du gpu et qu'il n'est pas accessible par l'application / le cpu.
Pour pouvoir modifier le contenu d'un buffer, il faut le créer avec le flag GL_DYNAMIC_DRAW
, et utiliser glBufferSubData()
:
Mais que se passe-t-il, lorsque l'on modifie quand même le contenu d'un buffer statique ?
En pratique, rien... le contenu du buffer statique est bien modifié. Il y a juste un warning dans la console... Tous les drivers font quelquechose et modifient quand même le contenu du buffer. Les drivers peuvent utiliser au moins une de ces 2 techniques :
Les flags de glBufferData()
ne sont que informatifs, modifier un buffer statique ne provoque pas d'erreur dans openGL 3.3, par contre les performances seront variables, selon ce que fait le driver pour modifier le contenu du buffer, et les différences peuvent être importantes.
En résumé, une solution correcte pour openGL 3 ou 4 consiste tout simplement à utiliser les bons flags lors de la création des buffers. On peut même s'autoriser a créer un premier buffer statique, puis de le transformer en buffer dynamique lorsque l'on souhaite modifier son contenu.
en attendant une solution consiste à copier les données dans un buffer temporaire crée avec le flag dynamic puis à copier ce buffer dans le buffer statique... oui c'est tordu, mais c'est le cpu qui n'a pas d'acces direct au buffer statique... c'est la carte graphique qui effectue les copies de donnees entre buffers, et ça marche ! et ça marche encore mieux avec les fonctionnalités de openGL 4.4...
exemple : cf UpdateBuffer utilise par Mesh pour copier les attributs vers un buffer statique.
OpenGL 4.4 a introduit de nouvelles fonctionnalités qui permettent d'obtenir de meilleures performances quelque soit l'architecture des cartes graphiques :
glBufferStorage()
pour allouer des buffers et déclarer explicitement leur utilisation,glMapBufferRange()
pour écrire les données directement dans la zone de transfert vers la carte graphique, en évitant les copies réalisées par le driver.Il est possible de créer un buffer privé, non accessible par l'application, équivalent au flag GL_STATIC_DRAW
, par contre, essayer de modifier son contenu provoquera une erreur...
remarque : pour créer un buffer dynamique, le flag est GL_DYNAMIC_STORAGE_BIT
au lieu de 0.
C'est malin, comment on remplit le buffer maintenant ? Soit l'application a déjà préparé les données exactes et il suffit de passer le pointeur dans le paramètre data
, soit :
Les paramètres source offset
et destination offset
indiquent quelle région du buffer copier, et length
définit sa taille en octets.
Créer un buffer dynamique à chaque fois que l'application veut modifier un buffer privé, n'est pas très pratique. Une bonne idée consiste à créer un buffer dédié qui ne servira qu'à faire ce type de transferts. Mais comment modifier efficacement son contenu ? openGL 4.4 ajoute la possibilité d'obtenir un pointeur sur la zone de transfert allouée au buffer, et d'écrire directement dedans, sans passer par d'autre appels openGL. Mais il faut créer le buffer avec les bons flags : GL_DYNAMIC_STORAGE_BIT | GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT
pour obtenir ensuite le pointeur avec glMapBufferRange()
.
glBufferStorage()
et glMapBufferRange()
utilisent les mêmes flags, ce qui plutot cohérent, mais il ne faut pas oublier de préciser que l'on souhaite uniquement écrire dans le buffer, cf flag GL_MAP_WRITE_BIT
et que l'on ne veut pas connaitre le contenu précédent, cf flag GL_MAP_INVALIDATE_RANGE_BIT
, sinon le driver va transférer les données du gpu vers le cpu pour rien. Le dernier flag GL_MAP_FLUSH_EXPLICIT_BIT
indique que l'application va utiliser glFlushMappedBufferRange()
pour indiquer que les données sont pretes pour le transfert. On peut aussi utiliser le flag GL_MAP_COHERENT_BIT
pour laisser le driver déterminer quelle région du buffer à été modifiée.
exemple complet dans tuto_stream.cpp
Voila quelques exemples, sur 2 machines et 2 systemes :
glBufferData(GL_DYNAMIC_DRAW)
,glBufferSubData()
, d'un buffer crée avec le flag GL_DYNAMIC_DRAW
,glBufferStorage(GL_DYNAMIC_STORAGE_BIT)
+ glMapBufferRange()
.machine 1, systeme 1 :
strategie | cpu | gpu |
---|---|---|
strategie 1 | 1.8ms | 60us |
strategie 2 | 1.8ms | 60us |
strategie 3 | 1.8ms | 60us |
draw | 20us | 40us |
machine 1, systeme 2 :
strategie | cpu | gpu |
---|---|---|
strategie 1 | 2ms | 5ms |
strategie 2 | 1.2ms | 2.8ms |
strategie 3 | 1.1ms | 100us |
draw | 40us | 100us |
machine 2, systeme 1 & systeme 2, résultats équivalents :
strategie | cpu | gpu |
---|---|---|
strategie 1 | 3ms | 1ms |
strategie 2 | 3ms | 1ms |
strategie 3 | 1.3ms | 100us |
draw | 40us | 100us |
Ce qui confirme bien que la solution proposée est correcte et efficace sur ces différentes configurations. La seule différence vient des performances de la copie vers la zone de transfert sur la machine 1, et selon le système c'est 7GB/s ou 15GB/s... bizarre...