gKit2 light
|
cf tuto_uniform_buffers.cpp et uniform.glsl
Les uniforms déclarés dans les shaders se manipulent de manière assez directe, mais lorsque plusieurs shaders sont nécessaires pour dessiner une image, re-affecter une valeur à chaque uniform de chaque shader devient plutot lent. De plus, leur taille totale est très limitée (32Ko ou 64Ko), ce qui les rend au final, assez peu pratiques.
Mais il est possible d'associer un buffer à un groupe d'uniforms et d'éliminer la limite de taille. Le même buffer peut rester sélectionné pendant l'exécution de plusieurs shaders et il suffit d'affecter les valeurs une seule fois, en initialisant correctement le contenu du buffer. La déclaration dans le shader ressemble à celle d'une structure, mais utilise le mot clé uniform
:
Pour que l'application puisse associer un buffer à la déclaration dans le shader, il faut nommer le groupe d'uniforms, cf frameData
et simpleData
dans l'exemple précédent. C'est glGetUniformBlockIndex()
qui permet de récupérer l'identifiant du groupe d'uniforms. Et comme il est possible d'utiliser plusieurs uniform buffers, ils sont numérotés. C'est l'application qui choisit l'indice de l'uniform buffer avec glUniformBlockBinding()
:
remarque : à partir de openGL 4.2, il est possible de préciser directement l'indice dans la déclaration du groupe d'uniforms :
Pour sélectionner le buffer contenant les valeurs des uniforms, il suffit d'utiliser une variante de glBindBuffer()
qui utilise un paramètre supplémentaire : l'indice associé au groupe d'uniforms : cf glBindBufferBase()
. La totalité du buffer est utilisée dans ce cas, mais il est possible de n'utiliser qu'une partie du buffer avec glBindBufferRange()
qui utilise les paramètres offset et size :
L'utilisation des uniform buffers est assez directe, mais il y a une grosse différence par rapport aux uniforms classiques, il n'y a pas d'interface (cf glUniform()) permettant d'affecter des valeurs aux variables déclarées dans le groupe d'uniforms. Il faut le faire "à la main" en remplissant correctement le buffer. Un cpu et un gpu sont des processeurs conçus différemment, et ils n'accèdent pas à la mémoire de la même manière... Il faut donc placer les données au bon endroit dans le buffer pour que les shaders relisent correctement toutes les valeurs. Ce n'est pas très compliqué, voici quelques exemples, pour mieux comprendre les diférences.
attention : par contre, il est existe plusieurs organisations de données, il faut préciser laquelle utiliser avec une décoration :
remarque : pourquoi std140
? tout simplement parce que cette fonctionnalité date de la version 1.40 du langage de shader. Les storage buffers, introduit dans openGL 4.3, utilisent... std430
L'organisation des données suit quelques règles (communes à std140 et std430) :
Par contre, std140 impose d'aligner sur 16 octets les éléments d'un tableau, quelque soit le type de l'élement. int t[4]
occuppe 16 octets par élement dans un buffer, soit 64 octets au lieu de 16 sur un cpu...
Quelle est l'organisation mémoire, sur cpu, de la structure :
offsetof(type, field)
du c++ renvoie la position en octets d'un champ à l'intérieur d'une structure :
ce qui correspond à l'organisation mémoire :
sur gpu, on obtient :
ce qui correspond à :
comme pour les storage buffers, il est possible de déclarer des variables supplementaires dans la structure cpu pour forcer le compilateur à produire la même organisation mémoire que le gpu, par exemple :
mais c'est rapidement pénible, surtout pour les tableaux. Une autre solution, pour les cas simples, consiste à n'utiliser que des vec4 :
mais l'affectation des valeurs ne sera pas très naturelle.
La dernière solution consiste à utiliser une fonctionnalité du C++11 qui permet d'indiquer l'alignement des variables en mémoire : alignas(n)
. C'est mieux, mais les tableaux sont toujours difficiles à déclarer.
tuto_uniform_buffers.cpp propose les différents types dans le namespace glsl
:
et dans le namespace glsl::array
pour les tableaux :
ce qui permet au final d'obtenir la déclaration de T
suivante :
Comme pour les storage buffers, il est également possible de demander au compilateur de shaders l'organisation mémoire utilisée, pour vérifier qu'il n'y a pas de problèmes. Le code complet est également dans tuto_uniform_buffers.cpp, voici ce qu'il affiche dans ce cas :