Buffer object et VBO

 

Image non trouvée !A partir de l'API level 8

 

Buffer object

La solution "vertex array" était déjà une belle avancée dans le monde OpenGL, en effet, initialement les vertice étaient envoyés un par un (mode immédiat via une fonction nommée glVertex() éventuellement épaulé par les display lists). Ce tableau a donc permis une amélioration des performances puisqu'il ne fallait plus qu"une commande pour envoyer l'ensemble des vertice.

Cependant, cette solution a encore un défaut: il faut envoyer toutes les données lors de chaque affichage à l'écran: perte de temps si l'objet 3D n'a pas changé et consommation mémoire inutile puisque présent en mémoire à la fois côté CPU et en mémoire sur la carte graphique (côté GPU).

D'où une nouvelle solution qui consiste à utiliser un buffer object. Ce buffer object est une zone mémoire ou tampon que vous réservez directement dans la mémoire de la carte graphique. Vous en faîtes ensuite ce que vous voulez. L'avantage: vous ne transférez l'information qu'une fois à la carte graphique, vous pouvez donc supprimer ces informations de la mémoire côté CPU (en laissant le carbage collector oeuvrer pour vous). D'où un gain de temps dans les traitements.

Image non trouvée !L'allocation mémoire est très basique: Allocation et libération de la mémoire allouée. Ne comptez donc pas modifier la taille du bloc mémoire. Il faudra supprimer ce bloc puis le recréer.

 

Le VBO

VBO signifie Vertex Buffer Object. Il s'agit simplement d'un buffer object (vu ci-dessus) "spécialisé" dans les vertice. Cette zone mémoire sera donc composée des coordonnées de vertice, mais aussi éventuellement d'autres informations utiles aux vertice comme la couleur, la position des texel, ...

 

Le fonctionnement

Pour pouvoir utiliser un (des) buffer object, il faut demander un nom ou mieux un identifiant buffer à OpenGL.

Image non trouvée !Aucune zone mémoire n'est encore réservée lors de cette phase du traitement.

 

Alimenter le VBO

public static void glGenBuffers(int n, IntBuffer buffers)

ou

public static void glGenBuffers(int n, int[] buffers, int offset)

n: nombre d'identifiants de buffers voulus.

buffers: Tableau d'entiers qui recevra les noms (identifiants) des buffers.

offset: Offset dans le tableau buffers où doit commencer l'alimentation des nouveaux identifiants. A priori, ce sera depuis le début du tableau, donc 0...

 

Exemple

Pour réserver 4 noms de buffer depuis le début du tableau

final int buffers[] = new int[4];
GLES20.glGenBuffers(4, buffers, 0);

 

Les identifiants des buffers étant connus, il sera possible de réserver une zone mémoire et d'alimenter ceux-ci avec les données que vous voulez:

Pour cela, il faut indiquez sur quel buffer vous voulez travailler grâce à la maintenant traditionnelle commande bind

public static void glBindBuffer (int target, int buffer)

En entrée:

Le type de zone mémoire utilisé: il sera positionné à GLES20.GL_ARRAY_BUFFER

Le nom/identifiant du buffer à utiliser parmi ceux préalablement générés

Exemple:

// Maintenant, nous voulons travailller sur ce buffer
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, buffers[0]);

Transférer les données dans la zone mémoire:

public static void glBufferData (int target, int size, Buffer data, int usage)

En entrée:

target toujours positionné à GLES20.GL_ARRAY_BUFFER

size: la taille en octet du buffer contenant les informations à transmettre dans le buffer object

Image non trouvée !et pour rappel, un Float est d'une taille de 4 octets. Donc un buffer de 12 éléments occupe et a une taille de 12 * 4 octets !

data: la zone mémoire qui contient les données des vertice

usage: Positionnée GLES20.GL_STATIC_DRAW pour indiquer que le buffer ne sera pas maj dynamiquement.

 

Exemple:

// Ok, on peut transférer les informations


GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, vertexBuffer.capacity() * BYTES_PER_FLOAT,
vertexBuffer, GLES20.GL_STATIC_DRAW);
Image non trouvée !Les informations auront été préalablement stockées en mémoire native, d'autant plus qu'ici, il s'agit de transmettre des données vers une zone mémoire de la carte graphique encore moins accessible depuis votre programme !

Lorsque vous avez terminé le transfert, il faut éviter que les opérations suivantes ne soient affectées au buffer:
// Fini de travailler avec ce buffer !
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);

Image non trouvée !A mettre impérativement, sinon vous allez avoir des surprises (désagréables) !

 

Utiliser le vbo lors du traitement de l'image

Reste la phase indiquant à OpenGL qu'il faut utiliser le buffer object pour affichage:

Les shaders ne changent pas, ils seront toujours codés de la même manière qu'avec les vertex array et la gestion des couleurs.

Le début du code sera toujours le même, déclaration est uilisation des attributs pour indiquer au vertex shader la position et la couleur d'un vertex

// Indiquer les programmes shader à utiliser...
GLES20.glUseProgram(mProgram);

// Création d'un attribut aPosition à l'index 0
GLES20.glBindAttribLocation(mProgram, 0, "aPosition");
GLES20.glBindAttribLocation(mProgram, 1, "aColor");

// Récupération de l'index, c'est juste pour l'exemple, puisqu'ici, il est déjà connu !
indexVPosition = GLES20.glGetAttribLocation(mProgram, "aPosition");
indexAColor = GLES20.glGetAttribLocation(mProgram, "aColor");

// Indiquer que l'attribut va pointer sur un buffer
GLES20.glEnableVertexAttribArray(indexVPosition);

GLES20.glEnableVertexAttribArray(indexAColor);

Par contre, il y a quelques changements par rapports au vertex array:

- il n'est pas nécessaire de renvoyer les coordonnées des vertice dans le buffer.

- il suffit d'indiquer le buffer object à utiliser !

Je vais traiter le cas le plus compliqué, mais en même temps le plus intéressant pour le système: le buffer est composé des coordonnées et des couleurs d'un vertex ...

// Et utilisation du buffer OpenGL
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, buffers[0]);

 

Il faut donc expliquer à OpenGL comment est construit le buffer pour pouvoir alimenter correctement vPosition dans le vertex shader.

Nous réutilisons la méthode GLES20.glVertexAttribPointer, mais sous la forme suivante:

public static void glVertexAttribPointer (int indx, int size, int type, boolean normalized, int stride, int offset)

En entrée:

indx: index de l'attribut du shader qui sera alimenté lors de l'exécution. Par exemple indexVPosition...

size: Nombre d'éléménts, par exemple 4 si le vertex est composé de 4 coordonnées

type: A priori, vous utiliserez du float, donc GLES20.GL_FLOAT

normalized: laissons le à false pour le moment...

stride: toujours le pas, à calculer en octet !

offset: le point de départ dans le buffer.

Image non trouvée !offset en octet ! Donc un float = 4 octets, l'offset sera deplacement à faire x 4 !

D'où le code suivant:

En partant du principe que le tableau est composé de 4 coordonnées, suivies de 4 composantes de couleurs:

// L'offset est à 0, car les coordonnées commencent à 0 dans le tableau
GLES20.glVertexAttribPointer(indexVPosition, NB_COORDS_PAR_VERTEX, GLES20.GL_FLOAT, false, (NB_COORDS_PAR_VERTEX + NB_COORDS_PAR_COULEUR) * BYTES_PER_FLOAT, 0);

GLES20.glVertexAttribPointer(indexAColor, // Index de l'attribut
NB_COORDS_PAR_COULEUR, // 4 informations par couleur (correspondant à r, v, b et alpha)
GLES20.GL_FLOAT, // De type float
false,
(NB_COORDS_PAR_VERTEX + NB_COORDS_PAR_COULEUR) * BYTES_PER_FLOAT, // La taille occupée en byte par un vertex est donc 4 * taille d'un float

// L'offset est à 4x4, car les composantes de la couleur commencent après les 4 floats des coordonnées d'un vertex dans le tableau
NB_COORDS_PAR_VERTEX * BYTES_PER_FLOAT); // Et l'index de départ * taille d'un float

// Ok, on peut dessiner maintenant !
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, NB_VERTEX);

// Disable du vertex buffer pour ne plus l'utiliser par la suite...
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
GLES20.glDisableVertexAttribArray(indexVPosition);
GLES20.glDisableVertexAttribArray(indexAColor);

 

Image non trouvée !Comme pour les vertex array, rien ne vous empêchera d'utiliser deux buffers objects, un pour les vertice, et un autre pour les couleurs...

 

Effacer les buffer objects

De la mémoire a été réservée sur la carte vidéo. Or le carbage collector ne fait pas de nettoyage aussi "loin". Donc tout ce que vous réservez ne se libérera pas tout seul !

Il faut donc faire ce travail:

La méthode GLES20.glDeleteBuffers permettra de libérer les blocs mémoires:


public static void glDeleteBuffers (int n, int[] buffers, int offset)

public static void glDeleteBuffers (int n, IntBuffer buffers)

 

Exemple

final int[] buffersToDelete = new int[] { buffers[0], buffers[1]};
GLES20.glDeleteBuffers(buffersToDelete.length, buffersToDelete, 0);

 

Compléments

public static boolean glIsBuffer (int buffer) va permettre de vérifier qu'un buffer est valide.

 

Exemple

Voici les codes principaux qui vont gérer les buffer objects

Tout d'abord une nouvelle classe: VBO

Et la classe Triangle réadaptée pour utiliser une instance de VBO

Pour le reste, quelques changements, secondaires...

Externalisation des shaders dans le repertoire asset, mais pas de changement particulier:

Image non trouvée !

Après tout, je n'ai pas fait une classe helper de chargement de fichiers shader pour rien !

Image non trouvée !

Et tout ce qui permet de compiler et de linker les shaders toujours inchangés

Image non trouvée !

Et enfin l'activité, la surface et le renderer légèrement modifiés pour charger les shaders, détruire les buffer objects, ...

Image non trouvée !

Le AndroidManifest.xml n'a pas changé...