FurMark
Current Version: 1.8.2
»FurMark homepage
»FurMark 1.8.x Submissions
»Benchmark Submissions

GPU Caps Viewer
Current Version: 1.9.0
»GPU Caps Viewer homepage
»GPU DB Submissions

PhysX FluidMark
Current Version: 1.2.0
»FluidMark homepage
»FluidMark 1.2.x Submissions
»Benchmark Submissions

GeeXLab
Current Version: 0.2.0
»GeeXLab homepage
»GeeXLab Overview

GPU Shark
Current Version: 0.2.3
»GPU Shark homepage

TessMark
Current Version: 0.2.0
»TessMark homepage

Blogs
»JeGX's Infamous Lab

 
OpenGL Vertex Buffer Objects

Par Christophe [Groove] Riccio - www.g-truc.net
Et
Jerome [JeGX] Guinot - jegx[NO-SPAM-THANKS]@ozone3d.net

Version initiale: 1 Mai 2006

Dernière MAJ: 7 Janvier 2007


[ Index ]

Intro | Page 1 | Page 2 | Page 3

»Next Page



2 - Pratique

Chaque sous partie de la partie pratique est associée à une classe dans le programme accompagnant cet article. Deux autres exemples, non décrits dans ce document, montrent l’utilisation de GLSL et Cg avec les VBOs. L’implémentation avec des classes a pour but unique de rendre interchangeables les différentes méthodes dans le programme d’exemple.

2.1. Utilisation de base façon Vertex Array (classe CTest1)

Pour faciliter la compréhension, commençons par un exemple correspondant parfaitement au fonctionnement de base des vertex arrays. Les VBOs utilisent une API similaire aux objets de textures pour leur gestion.

GLvoid glGenBuffers(GLsizei n, GLuint* buffers);
GLvoid glDeleteBuffers(GLsizei n, const GLuint* buffers);

buffers est un tableau créé par l’utilisateur dans lequel sont stockés les identifiants des VBOs. n objets sont créés ou détruits, attention donc à la taille de buffers.

Admettons que nous souhaitions afficher un carré à l’écran au moyen de deux triangles. Nos sources sont par exemple :

static const GLsizeiptr PositionSize = 6 * 2 * sizeof(GLfloat);
static const GLfloat PositionData[] =
{
	-1.0f,-1.0f,
	 1.0f,-1.0f,
	 1.0f, 1.0f,
	 1.0f, 1.0f,
	-1.0f, 1.0f,
	-1.0f,-1.0f,
};

static const GLsizeiptr ColorSize = 6 * 3 * sizeof(GLubyte);
static const GLubyte ColorData[] =
{
	255,   0,   0,
	255, 255,   0,
	  0, 255,   0,
	  0, 255,   0,
	  0,   0, 255,
	255,   0,   0
};

Nous allons utiliser deux VBOs pour afficher les six sommets décrits par les tableaux précédents. Les tableaux sont identifiés par GL_POSITION_OBJECT et GL_COLOR_OBJECT simplement par commodité. La création ou la destruction des VBOs s’effectue avec les fonctions glGenBuffers et glDeleteBuffers. La fonction glBindBuffer permet de sélectionner le VBO actif.

static const int BufferSize = 2;
static GLuint BufferName[BufferSize];

static const GLsizei VertexCount = 6; 

enum
{
    POSITION_OBJECT = 0,
    COLOR_OBJECT = 1
};

Le code C++ pour afficher ce carré est alors :

glBindBuffer(GL_ARRAY_BUFFER, BufferName[COLOR_OBJECT]);
glBufferData(GL_ARRAY_BUFFER, ColorSize, ColorData, GL_STREAM_DRAW);
glColorPointer(3, GL_UNSIGNED_BYTE, 0, 0);

glBindBuffer(GL_ARRAY_BUFFER, BufferName[POSITION_OBJECT]);
glBufferData(GL_ARRAY_BUFFER, PositionSize, PositionData, GL_STREAM_DRAW);
glVertexPointer(2, GL_FLOAT, 0, 0);

glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);

glDrawArrays(GL_TRIANGLES, 0, VertexCount);

glDisableClientState(GL_COLOR_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY); 

glBufferData initialise le stockage de données du VBO. Le dernier paramètre spécifie l’usage du VBO tel qu’il est décrit en section 1.2 et 1.3. La liste de tous les usages est disponible en section 3.1. Les fonctions glColorPointer et glVertexPointer permettent de spécifier l’emplacement pour trouver respectivement les couleurs et les coordonnées spatiales des sommets.

L’ordre de ces trois appels de fonctions est particulièrement important. Il est intuitif qu’il faut sélectionner le VBO actif en premier pour le paramétrer. Cependant, l’ordre de glBufferData et gl*Pointer est également important. En effet, gl*Pointer réfère à la source des données du VBO actif, cette source étant initialisée par glBufferData.

Remarques :

  • Parfois, on préférera séparer la tâche de chargement des données et la tâche de description des données pour des problèmes de flexibilité d’utilisation. Ainsi, la solution suivante est tout à fait viable:
  • glBindBuffer(GL_ARRAY_BUFFER, BufferName[POSITION_OBJECT]);
    glBufferData(GL_ARRAY_BUFFER, PositionSize, PositionData, GL_STREAM_DRAW);
    ...
    glBindBuffer(GL_ARRAY_BUFFER, BufferName[POSITION_OBJECT]);
    glVertexPointer(3, GL_FLOAT, 0, 0);
  • Dès lors que la fonction glBindBuffer est appelée avec un nom de VBOs valide, OpenGL bascule en mode VBO. Pour revenir en mode Vertex Array, il faut utiliser glBindBuffer avec la valeur 0 comme nom d’objet.
  • Le rendu s’effectue avec l’une des fonctions dédiées aux rendus de tableaux : glDrawArrays ou glMultiDrawArrays dans cet exemple.

    Dans le cas plutôt rare où la taille totale de la mémoire de la carte graphique est inférieure à la taille que l’on demande de réserver pour un unique VBO, une erreur de type GL_OUT_OF_MEMORY est émise et récupérable comme habituellement avec glGetError.

    2.2. Utilisation avec index buffer (classe CTest2)

    Lors du premier exemple nous avons utilisé la cible GL_ARRAY_BUFFER. Elle est utilisée pour tout type de données, excepté les tableaux d’indices qui utilisent la cible GL_ELEMENT_ARRAY_BUFFER.

    L’initialisation d’un tableau d’indexes s’effectue ainsi :

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, BufferName[INDEX_OBJECT]);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, IndexSize, IndexData, GL_STREAM_DRAW);

    C’est la partie qui diffère le plus des vertex arrays. Si la fonction de rendu par élément est utilisée avec la valeur nulle à la place d’un tableau d’indices, alors le VBO actif ayant pour cible GL_ELEMENT_ARRAY_BUFFER est utilisé.

    Dans cet exemple, le rendu s’effectue avec l’une des fonctions dédiées aux tableaux indexés : glDrawElements, glDrawRangeElements ou glMultiDrawElements.

    2.3. Utilisation des tableaux entrelacés (classe CTest3)

    Il n’y a pas eu de mise à jour de la fonction glInterleavedArrays [3.4.2] depuis son arrivée avec OpenGL 1.1. Cette fonction fut largement utilisée pour le rendu avec tableau entrelacé. Elle peut encore être utilisée avec les VBOs. mais il y a une alternative bien plus intéressante basée sur les fonctions gl*Pointer. L’idée est de spécifier pour chaque attribut du tableau entrelacé la source des données en utilisant le paramètre de stride.

    #pragma pack(push, 1)
    struct SVertex
    {
        GLubyte r;
        GLubyte g;
        GLubyte b;
        GLfloat x;
        GLfloat y;
    };
    #pragma pack(pop)
    
    glBindBuffer(GL_ARRAY_BUFFER, BufferName);
    glBufferData(GL_ARRAY_BUFFER, VertexSize, VertexData, GL_STREAM_DRAW);
    
    glColorPointer(3, GL_UNSIGNED_BYTE, sizeof(SVertex), BUFFER_OFFSET(ColorOffset));
    glVertexPointer(2, GL_FLOAT, sizeof(SVertex), BUFFER_OFFSET(VertexOffset));
    
    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_COLOR_ARRAY);
    
    glDrawArrays(GL_TRIANGLES, 0, VertexCount);
    
    glDisableClientState(GL_COLOR_ARRAY);
    glDisableClientState(GL_VERTEX_ARRAY);

    Pour cet exemple, nous utilisons une structure qui nous permet d’entrelacer les données. Notons que la définition de la structure est entourée des instructions du pré-processeur standard #pragma pack. En effet, si l’on recherche la taille de la structure avec l’instruction sizeof, il y a toutes les chances pour que la valeur retournée soit 12, voir 16 octets au lieu de 11 dans le cas présent. GLfloat fait usuellement 4 octets, et GLubyte fait usuellement 1 octet donc une taille requise de 11 octets. Cependant, les compilateurs alignent les données en mémoire car les processeurs sont optimisés pour récupérer des données de la taille de leurs registres. 4 octets pour les CPU 32 bits et 8 octets pour les CPU 64 bits. Bonne initiative, mais quand l’espace mémoire coûte cher, nous allons oublier cette optimisation. En effet, dans notre cas, non seulement les structures coûtent 1/12 de mémoire supplémentaire, mais c’est aussi 1/12 de données à transférer jusqu’à la carte graphique. Enfin, il se peut que des difficultés apparaissent quant à la gestion des octets supplémentaires, particulièrement dans le cas présent. Où se trouvent les octets supplémentaires?

    Les fonctions glColorPointer et glVertexPointer doivent toujours indiquer les sources et les types des données stockées dans le VBO. Pour cela, les spécifications proposent une macro nommée BUFFER_OFFSET :

    #define BUFFER_OFFSET(i) ((char*)NULL + (i))

    Avec les VBOs, il ne s’agit plus de donner l’adresse de la source de donnée, car la source est stockée par le VBO quelque part, mais plutôt un offset qui indique où l’on doit commencer à lire les données dans la mémoire du VBOs. sizeof(SVertex) est dans cet exemple la valeur de stride, c'est-à-dire qu’elle indique le nombre d’octets entre deux sommets pour un même attribut. Habituellement cette valeur est nulle pour simplifier l’API OpenGL. Ceci signifie que les valeurs sont contiguës, c'est-à-dire que le VBO ne contient qu’un seul attribut par vertex et aucun espace vide. En conséquence, si nous créons un tableau ne contenant que les positions spatiales 3D des sommets, alors les deux appels suivant sont équivalents :

    glVertexPointer(3, GL_FLOAT, 0, 0);
    glVertexPointer(3, GL_FLOAT, sizeof(float) * 3, 0);

    La macro BUFFER_OFFSET permet également d’éviter un warning de conversion d’un entier en pointer.

    2.4. Utilisation des tableaux sérialisés (classe CTest4)

    Pour de nombreux cas, les données utilisées pour décrire les primitives géométriques n’ont aucune raison d’être entrelacées, ce choix pouvant même être nuisible. Il est tout à fait possible que nous ne voulions mettre à jour qu’une partie des donnés. Par exemple, dans le cas de l’animation d’un maillage tel qu’un personnage, les coordonnées de textures n’ont pas besoin d’être mises à jour, au contraire des positions et des normales des sommets.

    Pour cela, nous n’utilisons qu’un seul VBO dans lequel nous insérons plusieurs types de données au moyen de la fonction glBufferSubData.

    En premier lieu, nous réservons l’espace mémoire pour l’intégralité des données avec la fonction glBufferData. Au lieu de passer la source des données dans le troisième paramètre nous utilisons la valeur 0.

    Ensuite, nous utilisons la fonction glBufferSubData pour remplir le tableau. Le second paramètre correspond à l’offset dans les données du VBO. Le troisième indique la taille des données sources à ajouter et le dernier est la source des données.

    glBindBuffer(GL_ARRAY_BUFFER, BufferName);
    glBufferData(GL_ARRAY_BUFFER, ColorSize + PositionSize, 0, GL_STREAM_DRAW);
    
    glBufferSubData(GL_ARRAY_BUFFER, 0, ColorSize, ColorData);
    glBufferSubData(GL_ARRAY_BUFFER, ColorSize, PositionSize, PositionData);
    
    glColorPointer(3, GL_UNSIGNED_BYTE, 0, 0);
    glVertexPointer(2, GL_FLOAT, 0, BUFFER_OFFSET(ColorSize));
    
    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_COLOR_ARRAY);
    
    glDrawArrays(GL_TRIANGLES, 0, VertexCount);
    
    glDisableClientState(GL_COLOR_ARRAY);
    glDisableClientState(GL_VERTEX_ARRAY);

    La fonction glBufferSubData peut être utilisée pour mettre à jour seulement une partie des données, comme par exemple dans le cas de modèles partiellement animés ou lorsque plusieurs modèles sont stockés dans un seul VBO, ce qui peut-être très efficace.

    2.5. Vertex mapping (classe CTest5)

    Dans certains cas nous voudrions nous passer d’un tableau intermédiaire pour stocker les données de la géométrie. Ceci peut accélérer le rendu en évitant une copie de données inutile. Le vertex mapping utilise la fonction glMapBuffer pour accéder via un pointeur à la mémoire réservée par le VBO.

    glBindBuffer(GL_ARRAY_BUFFER, BufferName[POSITION_OBJECT]);
    glBufferData(GL_ARRAY_BUFFER, PositionSize, NULL, GL_STREAM_DRAW);
    GLvoid* PositionBuffer = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
    memcpy(PositionBuffer, PositionData, PositionSize);
    glUnmapBuffer(GL_ARRAY_BUFFER);
    glVertexPointer(2, GL_FLOAT, 0, 0);

    Une fois de plus, la fonction glBufferData n’est utilisée que pour réserver l’espace mémoire, l’initialisation est effectuée à la main par l’utilisateur au moyen de la fonction glMapBuffer. Il existe trois types d’accès aux données du VBO : GL_WRITE_ONLY, GL_READ_ONLY et GL_READ_WRITE. Les noms sont particulièrement explicites. Les modes autorisant la lecture sont également très utiles car ils évitent la duplication de données pour des utilisations non graphique. La fonction glUnmapBuffer invalide le pointeur. Il est préférable d’appeler glUnmapBuffer le plus tôt possible car le vertex mapping implique des synchronisations du CPU et du GPU.

    Lorsque plusieurs VBOs sont utilisés, une bonne optimisation consiste à les initialiser en parallèle car ce procédé diminue le nombre de synchronisation CPU/GPU. Voici une solution :

    glBindBuffer(GL_ARRAY_BUFFER, BufferName[COLOR_OBJECT]);
    glBufferData(GL_ARRAY_BUFFER, ColorSize, NULL, GL_STREAM_DRAW);
    GLvoid* ColorBuffer = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
    
    glBindBuffer(GL_ARRAY_BUFFER, BufferName[POSITION_OBJECT]);
    glBufferData(GL_ARRAY_BUFFER, PositionSize, NULL, GL_STREAM_DRAW);
    GLvoid* PositionBuffer = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
    
    memcpy(ColorBuffer, ColorData, ColorSize);
    memcpy(PositionBuffer, PositionData, PositionSize);
    
    glBindBuffer(GL_ARRAY_BUFFER, BufferName[COLOR_OBJECT]);
    glUnmapBuffer(GL_ARRAY_BUFFER);
    glColorPointer(3, GL_UNSIGNED_BYTE, 0, 0);
    
    glBindBuffer(GL_ARRAY_BUFFER, BufferName[POSITION_OBJECT]);
    glUnmapBuffer(GL_ARRAY_BUFFER);
    glVertexPointer(2, GL_FLOAT, 0, 0);

    Ceci n’est valable qu'à titre d’exemple, car l’idée même du Vertex Mapping est d’éviter une copie de données... ce que memcpy fait ici.



    2.6. Démo avec Animation par Vertex Shader en GLSL

    Cette démo utilise le shader de déformation présenté dans le tutoriel suivant:
    Mesh Deformers - Twister.


    La démo montre l'utilisation des VBOs en mode GL_STATIC_DRAW et la déformation de la boîte est assurée par un shader GLSL.

    A des fins de comparaison, la démo est livrée en deux versions: une utilisant les VBOs (XPGL_Demo_vbo.exe) pour le rendu et l'autre les Vertex Arrays classiques (XPGL_Demo_va.exe).

    Le tableau suivant nous montre la différence de performance entre les VBO et les Vertex Arrays (VA):

    Graphic CardXPGL_Demo_vbo.exeXPGL_Demo_va.exe
    ATI X1950XTX760 fps145 fps

    La démo utilise une petite librairie spécialement développée pour des experimentations OpenGL: XPGL (eXPerimental Graphics Library)

    .




    [ Index ]

    Intro | Page 1 | Page 2 | Page 3

    »Next Page







    Langue:

    3D Graphics Search Engine:

    The Geeks Of 3D





    Geeks3D's Articles
    »GPU Memory Speed Demystified

    »Multi-Threading Programming Resources

    »GeForce and Radeon OpenCL Overview

    »How to Get your Multi-core CPU Busy at 100%

    »How To Make a VGA Dummy Plug

    »Night Vision Post Processing Filter



    Geeks3D latest news

    Demoniak3D
    Current Version: 1.23.0
    »Demoniak3D
    »Download
    »Libraries and Plugins
    »Demos
    »Online Help - Reference Guide
    »Codes Samples


    Misc
    »Texture DataPack #1
    »Asus Silent Knight CPU Cooler
    Page generated in 0.044761896133423 seconds.