Bonjour j'aimerai savoir comment modifier un vbo sans laisser des trou dans le buffer par exemple si j'ai 1000 vertices et que je veux supprimer la 801, 802, 803 et 804 il faudrai que je les supprime et que je décale toute la data du buffer pour qu'il contienne 996 vertices ?
J'ai vu glBufferSubData et glMapBuffer mais elle laisse quand même des "trous" dans le buffer.
exemple :
si j'ai 6 vertices :
1.0, 8.0, 4.0, 5.0, 7.0, 9.0
et que je veux change la 2 et 3eme je me retrouve avec ceci
1.0, OVERWRITE, OVERWRITE, 5.0, 7.0, 9.0 //toujours 6 vertices
alors que je voudrais ceci :
1.0, 5.0, 7.0, 9.0 // 4 vertices
Comment m'y prendre ?
Salut!
si j'ai 1000 vertices et que je veux supprimer la 801, 802, 803 et 804 il faudrai que je les supprime et que je décale toute la data du buffer pour qu'il contienne 996 vertices ?
Oui, mais je ne pense que ca n'est pas "jouable" en dehors de cas très particulier.
Que veux tu faire exactement? "En général" le VBO une fois initialisé n'est plus modifié (ce n'est pas toujours vrai, car il y a des cas ou on va le faire toute les frames), il faut le voir comme un object "atomique" pour le rendu.
Ca peut tout à fait être valable de le modifier, mais supprimer et shifter des éléments, ca risque de prendre beaucoup de temps cpu/gpu "pour rien", alors qu'une méthode adapté au problème serait surement plus éfficace.
En fait je veux juste render des quads avec textures couleur et position et si j'ai vu que mettre la data de tous les quads dans 1 seul vbo était plus rapide que d'avoir un vbo par quad mais si je veux supprimer ou ajouter un quad du vbo je ne sais pas comment m'y prendre.
Par exemple pour render plusieurs quads mais a des positions différente
Ok!
Alors oui, un VBO par quad c'est un peu trop dans ce cas là !
Ce que tu peux faire c'est créer un seul VBO qui peut contenir une certaine quantité de quad (par exemple 1000, sur un GPU moderne de toute facon tu as de la marge), et l'utiliser comme cache pour dessiner les quads en groupe.
Donc tu aurais quelque part déclarer le buffer, et 2 fonctions : une qui permet d'ajouter un quad dans le cache, et une qui dessine et vide le cache.
Dans celle qui permet d'ajouter un quad, tu test si le cache est plein, et si oui, tu l'affiche.
Le cache est coté CPU, et c'est au moment de faire l'affichage que tu fait un appel à glBufferSubData pour envoyé les données à afficher, et tout dessiner d'un coup.
Ca se complique un peu cependant si les quads utilisent des textures différentes...
Qu'est ce que un cache ? Comment savoir si il est plein ?
Qu'est ce que un cache ? Comment savoir si il est plein ?
Un cache peut être un tableau de vertex (position, uv, couleur, etc) d'une taille fixe auquel on associe un compteur pour connaitre sa taille.
Ajouter un vertex dans ce cache revient à définir le n-ième élément de ce tableau, et incrémenter le compteur.
Quand le compteur est égale à la taille du cache, c'est qu'il est plein, et peut être affiché, avec un glBufferSubData pour envoyé le contenu du cache au VBO, puis glDrawArrays (ou glDrawElements), et vidé simplement en remettant le compteur à 0.
exemple:
typedef struct {
GLfloat x, y;
GLfloat color[4];
GLfloat u, v;
} Vertex;
Vertex Buffer[1000];
size_t BufferSize = 0;
Je ne comprends pas trop si j'ai un buffer de 1000 vertex et que je veux en dessiner seulement 992 je fais comment car tu expliques que le compte dois étre égale a la taille du cache ? et si je veux supprimer un buffer ca laisse un trou dans l'array ? Et je dois a chaque fois remettre toute la data dans le vbo ?
Pardon oui, j'ai oublié le plus important dans l'histoire : tu "dessine" à chaque frame tous les quads (et le cache sert à optimiser les transferts cpu->gpu). Je met dessine entre "" car en fait ca ne dessine, ca met les vertex dans le cache. C'est seulement quand le cache est plein OU que tu as finis de dessiner ce que tu veux qu'il est envoyé au GPU et dessiné.
en brouillon ca ressemble à un truc comme ca:
typedef struct {
GLfloat x, y;
GLfloat color[4];
GLfloat u, v;
} Vertex;
#define BufferSize 1000
Vertex Buffer[BufferSize];
size_t BufferSize = 0;
// envois le cache au gpu, le dessine, et le "vide" (BufferSize à 0)
void FlushBuffer() {
glBindBuffer(...);
glBufferSubData(vbo, 0, BufferSize * sizeof(Vertex), Buffer);
glDrawArrays(GL_TRIANGLES, 0, BufferSize);
BufferSize = 0
}
// Ajoute 6 vertex au buffer (2 triangles de 3 vertex chacun)
void DrawQuad(GLfloat x, GLfloat y) {
if(BufferSize + 6 > BufferSize) {
FlushBuffer();
}
Buffer[BufferSize + 0].x = ...
Buffer[BufferSize + 0].y = ...
// etc pour les 6 vertex de BufferSize + 0 à BufferSize + 5
BufferSize += 6;
}
int main() {
while(1) {
DrawQuad(45, 56);
DrawQuad(765, 454);
// ...
// Autant de DrawQuad que necessaire
FlushBuffer(); // pour dessiner les quads restant dans le buffer
}
}
Il y a plusieurs chose que je comprends toujours pas. Pourquoi "vider le cache" ? A quoi cela sert-t-il ? Tu dis que la function void FlushBuffer() envoie le cache au gpu ce que je comprends mais après tu explique qu'elle vide le cache en assignant buffersize = 0 je ne comprends pas ce que ca change ? Finalement je ne comprends pas cette partie du code :
if(BufferSize + 6 > BufferSize) {
FlushBuffer();
}
Oups oui c'est une erreur,
#define BUFFER_SIEZE 1000
Vertex Buffer[BUFFER_SIZE];
size_t BufferIndex = 0;
Et ensuite
if(BufferIndex + 6 > BUFFER_SIZE) {
FlushBuffer();
}
Donc quand il n'y a plus de place dans le cache, les quad sont affichés.
void FlushBuffer() {
glBindBuffer(...);
glBufferSubData(vbo, 0, BufferSize * sizeof(Vertex), Buffer);
glDrawArrays(GL_TRIANGLES, 0, BufferSize);
BufferIndex = 0;
}
En remettant BufferIndex a zero , DrawQuad va ajouter les quads suivant au début du cache (et ils seront effectivement dessinés au prochain appel de FlushBuffer). Le tableau en memoire existe toujours, mais il ne contient plus de données pertinentes, on peut les remplacer par de nouvelles. Donc si BufferIndex = 0, le cache est vide.
Le cache sert a regrouper le dessin (et l'upload en VRAM) de plusieurs quads en meme temps, et ainsi d'etre beaucoup plus efficaces que des les faire afficher un a un, ou de maintenir a jour le vbo.
Et si je ne veux que 992 vertices a la place de 1000 ca veut dire que le cache ne sera pas plein et je ne pourrai pas les dessiner ?
Si si justement, tu peux en dessiner autant que tu veux comme ca.
Dés que tu appel FlushBuffer le contenu du cache est dessiné, peut importe le nombre de quad qui ont été mis dedans.
D'ailleurs j'ai 1000 comme taille, mais c'est arbitraire, ca pourrait être plus ou moins.
J'avoue ne pas savoir quelle serait une bonne valeur pour la taille du buffer.
D'accord merci on m'a aussi dit que utiliser GL_SHADER_STORAGE_BUFFER comme buffer ou la fonction glDrawArraysInstanced() pourrai fonctionner
Par contre pour dessiner tu dois utiliser la fonction flushbuffer vu que l'appel à gldrawarray est dans cette fonction ce qui signifie que a chaque itération de la main loop tu utilises glbuffersubdata même quand on ne souhaite pas remettre de nouvelle data n'est ce pas ?
Les instances ou GL_SHADER_STORAGE_BUFFER ne serait qu'une variation de ce que je décris, car il faudrait dans les 2 cas construires un buffer pour y stocker les infos des quads que tu veux dessiner.
Et oui et non, si tu appel FlushBuffer sans avoir fait d'appel à DrawQuad (qui ne dessine rien en fait, il met juste des vertex dans le cache), ca ne va rien dessiner.
En fait tu es "obligé" d'appeller DrawQuad chaque frame pour tous les objets que tu veux afficher. Tu dois explicitement demander le dessin des quads.
glBufferSubData et glDrawArrays (ou glDrawElements si tu as des index) sont des fonctions très coûteuse, c'est pour ca que dans ce cas là il faut utiliser un cache, pour les appeller le moins possible.
Après, suivant les cas, tu peux faire des variations de ce système. Supposons que tu affiche un background dans un jeu 2D, composé de plusieurs quads, et qu'ils ne sont pas modifié toutes les frames, alors là tu pourrais utiliser un VBO que tu ne reconstruit que quand c'est necessaire. Il n'y a pas de solution tout terrain, suivant les cas de figures il faut adapter.
de plus je comprends pas pourquoi il faut appeller drawquad a chaque frame
Je ne comprends vraiment pas, si tu appelle flushbuffer sans avoir fais d'appel a drawquad la fonction glbuffersubdata sera quand même appellée pareil pour gldrawarrays et en plus flushbuffer est dans la main loop donc a chaque itération on appellera glbuffersubdata même si on ne veut pas de nouveau quad a afficher
Regarde ces 3 bouts de code :
#define BUFFER_SIZE 1000
Vertex Buffer[BUFFER_SIZE];
size_t BufferIndex = 0;
void FlushBuffer() {
glBindBuffer(...);
glBufferSubData(vbo, 0, BufferIndex * sizeof(Vertex), Buffer);
glDrawArrays(GL_TRIANGLES, 0, BufferIndex);
BufferIndex = 0
}
void DrawQuad(...) {
if(BufferIndex + 6 > BUFFER_SIZE) {
FlushBuffer();
}
...
BufferIndex += 6;
}
DrawQuad va stocker temporairement dans Buffer les vertex necessaires pour dessiner un quad, et ajouter 6 à BufferIndex. DrawQuad ne dessine rien, ca ne fait que mettre des datas dans le cache pour grouper leur affichage.
Donc après n appels à DrawQuad, BufferIndex vaut 6 * n.
Ensuite tu appel FlushBuffer, qui upload BufferIndex data vers le gpu (avec glBufferSubData), puis dessine ces datas (glDrawArrays avec BufferIndex comme nombre d'éléments.
Et ensuite, BufferIndex est remis à 0. Si tu appel FlushBuffer tout de suite après, ca va uploader 0 data, et dessiner 0 triangles, car le cache est vide (BufferIndex = 0).
Donc il faut faire des appels à DrawQuad si tu veux que FlushBuffer dessine quelque chose (c'est le principe du cache, les quads à dessiner sont stockés temporairement dans Buffer, et uploadés puis dessinés en même temps).
L'interet c'est que tu n'a plus besoin de t'embêter à gérer une liste de quad à affiicher, mais par contre, il faut, à chaque frame, appellé DrawQuad pour chaque quad que tu veux dessiner.
Imagine par exemple que tu as une liste d'enemis dans ton jeu, par exemple 10, et que tu ne veux les affichers que si ils sont encore "vivant", et bien tu aura quelque part dans la main loop un truc du genre :
while(1] { // main loop
...
// dessin des enemies
for(int i = 0; i < 10; i++) {
if(Enemies[i].life > 0.0f) {
DrawQuad(enemyQuad);
}
}
...
FlushBuffer(); // un dernier Flush pour être sur de dessiner les derniers quads
}
Et ils seront dessinés à un moment ou à un autre dans la frame, à l'appel suivant de FlushBuffer.