2013-02-27 8 views
7

Sto lavorando a un'applicazione OpenGL basata su tile, C++. Sto aggiungendo schermata di esempio dall'applicazione, in modo che sarà più chiaro:VBO più lento del metodo obsoleto di disegno delle primitive - perché?

Ho Tile classe che contiene un array di Object s. Ogni riquadro può contenere fino a 15 oggetti: l'esempio è Tile con un quadrato verde e giallo (due oggetti), quindi è 10 x 10 x 15 = 1500 Object s disegnare (nel peggiore dei casi, perché non sto gestendo 'vuoto 'quelli). Di solito è meno, nei miei test ne uso circa 600. Object ha il proprio grafico, che può essere disegnato. Ogni Tile appartiene a uno Tile alla volta, ma può essere spostato (come ad esempio i quadrati rossi nell'immagine).

Object s gli sfondi avranno un bordo e devono essere ben scalabili, quindi sto usando un modello a 9 patch per disegnarli (sono fatti di 9 quad).

Senza il disegno Tile s (il loro Object s per la precisione), la mia applicazione ha circa 600 fps.

In un primo momento, ho usato il metodo obsoleto per disegnare quei Tile s - usando glBegin(GL_QUADS)/glEnd() e glDisplayList s. Ho avuto un grande calo di prestazioni a causa di quel disegno - da 600 a 320 fps. Questo è il modo che ho di loro disegnando:

bool Background::draw(const TPoint& pos, int width, int height) 
{ 
    if(width <= 0 || height <= 0) 
     return false; 
    //glFrontFace(GL_CW); 
    glPushMatrix(); 
    glTranslatef((GLfloat)pos.x, (GLfloat)pos.y, 0.0f);  // Move background to right direction 
    if((width != m_savedWidth) || (height != m_savedHeight)) // If size to draw is different than the one saved in display list, 
     // then recalculate everything and save in display list 
    { 
     // That size will be now saved in display list 
     m_savedWidth = width; 
     m_savedHeight = height; 

     // If this background doesn't have unique display list id specified yet, 
     // then let OpenGL generate one 
     if(m_displayListId == NO_DISPLAY_LIST_ID) 
     { 
      GLuint displayList; 
      displayList = glGenLists(1); 
      m_displayListId = displayList; 
     } 

     glNewList(m_displayListId, GL_COMPILE); 

     GLfloat texelCentersOffsetX = (GLfloat)1/(2*m_width); 

     // Instead of coordinates range 0..1 we need to specify new ones 
     GLfloat maxTexCoordWidth = m_bTiling ? (GLfloat)width/m_width : 1.0; 
     GLfloat maxTexCoordHeight = m_bTiling ? (GLfloat)height/m_height : 1.0; 

     GLfloat maxTexCoordBorderX = (GLfloat)m_borderWidth/m_width; 
     GLfloat maxTexCoordBorderY = (GLfloat)m_borderWidth/m_height; 

     /* 9-cell-pattern 

     ------------------- 
     | 1 | 2 | 3 | 
     ------------------- 
     | |   | | 
     | 4 | 9 | 5 | 
     | |   | | 
     ------------------- 
     | 6 | 7 | 8 | 
     ------------------- 

     */ 

     glBindTexture(GL_TEXTURE_2D, m_texture);    // Select Our Texture 

     // Top left quad [1] 
     glBegin(GL_QUADS); 
      // Bottom left 
      glTexCoord2f(0.0, maxTexCoordBorderY); 
      glVertex2i(0, 0 + m_borderWidth); 

      // Top left 
      glTexCoord2f(0.0, 0.0); 
      glVertex2i(0, 0); 

      // Top right 
      glTexCoord2f(maxTexCoordBorderX, 0.0); 
      glVertex2i(0 + m_borderWidth, 0); 

      // Bottom right 
      glTexCoord2f(maxTexCoordBorderX, maxTexCoordBorderY); 
      glVertex2i(0 + m_borderWidth, 0 + m_borderWidth); 
     glEnd(); 

     // Top middle quad [2] 
     glBegin(GL_QUADS); 
      // Bottom left 
      glTexCoord2f(maxTexCoordBorderX + texelCentersOffsetX, maxTexCoordBorderY); 
      glVertex2i(0 + m_borderWidth, 0 + m_borderWidth); 

      // Top left 
      glTexCoord2f(maxTexCoordBorderX + texelCentersOffsetX, 0.0); 
      glVertex2i(0 + m_borderWidth, 0); 

      // Top right 
      glTexCoord2f((GLfloat)1.0 - maxTexCoordBorderX - texelCentersOffsetX, 0.0); 
      glVertex2i(0 + width - m_borderWidth, 0); 

      // Bottom right 
      glTexCoord2f((GLfloat)1.0 - maxTexCoordBorderX - texelCentersOffsetX, maxTexCoordBorderY); 
      glVertex2i(0 + width - m_borderWidth, 0 + m_borderWidth); 
     glEnd(); 

     // Top right quad [3] 
     glBegin(GL_QUADS); 
      // Bottom left 
      glTexCoord2f((GLfloat)1.0 - maxTexCoordBorderX, maxTexCoordBorderY); 
      glVertex2i(0 + width - m_borderWidth, 0 + m_borderWidth); 

      // Top left 
      glTexCoord2f((GLfloat)1.0 - maxTexCoordBorderX, 0.0); 
      glVertex2i(0 + width - m_borderWidth, 0); 

      // Top right 
      glTexCoord2f(1.0, 0.0); 
      glVertex2i(0 + width, 0); 

      // Bottom right 
      glTexCoord2f(1.0, maxTexCoordBorderY); 
      glVertex2i(0 + width, 0 + m_borderWidth); 
     glEnd(); 

     // Middle left quad [4] 
     glBegin(GL_QUADS); 
      // Bottom left 
      glTexCoord2f(0.0, (GLfloat)1.0 - maxTexCoordBorderY); 
      glVertex2i(0, 0 + height - m_borderWidth); 

      // Top left 
      glTexCoord2f(0.0, maxTexCoordBorderY); 
      glVertex2i(0, 0 + m_borderWidth); 

      // Top right 
      glTexCoord2f(maxTexCoordBorderX, maxTexCoordBorderY); 
      glVertex2i(0 + m_borderWidth, 0 + m_borderWidth); 

      // Bottom right 
      glTexCoord2f(maxTexCoordBorderX, (GLfloat)1.0 - maxTexCoordBorderY); 
      glVertex2i(0 + m_borderWidth, 0 + height - m_borderWidth); 
     glEnd(); 

     // Middle right quad [5] 
     glBegin(GL_QUADS); 
      // Bottom left 
      glTexCoord2f((GLfloat)1.0 - maxTexCoordBorderX, (GLfloat)1.0 - maxTexCoordBorderY); 
      glVertex2i(0 + width - m_borderWidth, 0 + height - m_borderWidth); 

      // Top left 
      glTexCoord2f((GLfloat)1.0 - maxTexCoordBorderX, maxTexCoordBorderY); 
      glVertex2i(0 + width - m_borderWidth, 0 + m_borderWidth); 

      // Top right 
      glTexCoord2f(1.0, maxTexCoordBorderY); 
      glVertex2i(0 + width, 0 + m_borderWidth); 

      // Bottom right 
      glTexCoord2f(1.0, (GLfloat)1.0 - maxTexCoordBorderY); 
      glVertex2i(0 + width, 0 + height - m_borderWidth); 
     glEnd(); 

     // Bottom left quad [6] 
     glBegin(GL_QUADS); 
      // Bottom left 
      glTexCoord2f(0.0f, 1.0); 
      glVertex2i(0, 0 + height); 

      // Top left 
      glTexCoord2f(0.0f, (GLfloat)1.0 - maxTexCoordBorderY); 
      glVertex2i(0, 0 + height - m_borderWidth); 

      // Top right 
      glTexCoord2f(maxTexCoordBorderX, (GLfloat)1.0 - maxTexCoordBorderY); 
      glVertex2i(0 + m_borderWidth, 0 + height - m_borderWidth); 

      // Bottom right 
      glTexCoord2f(maxTexCoordBorderX, 1.0); 
      glVertex2i(0 + m_borderWidth, 0 + height); 
     glEnd(); 

     // Bottom middle quad [7] 
     glBegin(GL_QUADS); 
      // Bottom left 
      glTexCoord2f(maxTexCoordBorderX + texelCentersOffsetX, 1.0); 
      glVertex2i(0 + m_borderWidth, 0 + height); 

      // Top left 
      glTexCoord2f(maxTexCoordBorderX + texelCentersOffsetX, (GLfloat)1.0 - maxTexCoordBorderY); 
      glVertex2i(0 + m_borderWidth, 0 + height - m_borderWidth); 

      // Top right 
      glTexCoord2f((GLfloat)1.0 - maxTexCoordBorderX - texelCentersOffsetX, (GLfloat)1.0 - maxTexCoordBorderY); 
      glVertex2i(0 + width - m_borderWidth, 0 + height - m_borderWidth); 

      // Bottom right 
      glTexCoord2f((GLfloat)1.0 - maxTexCoordBorderX - texelCentersOffsetX, 1.0); 
      glVertex2i(0 + width - m_borderWidth, 0 + height); 
     glEnd(); 

     // Bottom right quad [8] 
     glBegin(GL_QUADS); 
      // Bottom left 
      glTexCoord2f((GLfloat)1.0 - maxTexCoordBorderX, 1.0); 
      glVertex2i(0 + width - m_borderWidth, 0 + height); 

      // Top left 
      glTexCoord2f((GLfloat)1.0 - maxTexCoordBorderX, (GLfloat)1.0 - maxTexCoordBorderY); 
      glVertex2i(0 + width - m_borderWidth, 0 + height - m_borderWidth); 

      // Top right 
      glTexCoord2f(1.0, (GLfloat)1.0 - maxTexCoordBorderY); 
      glVertex2i(0 + width, 0 + height - m_borderWidth); 

      // Bottom right 
      glTexCoord2f(1.0, 1.0); 
      glVertex2i(0 + width, 0 + height); 
     glEnd(); 

     GLfloat xTexOffset; 
     GLfloat yTexOffset; 

     if(m_borderWidth > 0) 
     { 
      glBindTexture(GL_TEXTURE_2D, m_centerTexture);  // If there's a border, we have to use 
      // second texture now for middle quad 
      xTexOffset = 0.0;         // We are using another texture, so middle middle quad 
      yTexOffset = 0.0;         // has to be texture with a whole texture 
     } 
     else 
     { 
      // Don't bind any texture here - we're still using the same one 

      xTexOffset = maxTexCoordBorderX;     // But it implies using offset which equals 
      yTexOffset = maxTexCoordBorderY;     // maximum texture coordinates 
     } 

     // Middle middle quad [9] 
     glBegin(GL_QUADS); 
      // Bottom left 
      glTexCoord2f(xTexOffset, maxTexCoordHeight - yTexOffset); 
      glVertex2i(0 + m_borderWidth, 0 + height - m_borderWidth); 

      // Top left 
      glTexCoord2f(xTexOffset, yTexOffset); 
      glVertex2i(0 + m_borderWidth, 0 + m_borderWidth); 

      // Top right 
      glTexCoord2f(maxTexCoordWidth - xTexOffset, yTexOffset); 
      glVertex2i(0 + width - m_borderWidth, 0 + m_borderWidth); 

      // Bottom right 
      glTexCoord2f(maxTexCoordWidth - xTexOffset, maxTexCoordHeight - yTexOffset); 
      glVertex2i(0 + width - m_borderWidth, 0 + height - m_borderWidth); 
     glEnd(); 

     glEndList(); 
    } 

    glCallList(m_displayListId); // Now we can call earlier or now created display list 

    glPopMatrix(); 

    return true; 
} 

Probabilmente c'è troppo di codice di lì, ma ho voluto mostrare tutto. La cosa principale di questa versione è l'uso di liste di visualizzazione e glVertex2i che sono deprecate.

Ho pensato che il problema di tale rallentamento fosse l'uso di questo metodo obsoleto che ho letto è piuttosto lento, quindi ho deciso di andare su VBO. Ho usato this tutorial e in base ad esso ho cambiato metodo come questo:

bool Background::draw(const TPoint& pos, int width, int height) 
{ 
    if(width <= 0 || height <= 0) 
     return false; 

    glPushMatrix(); 
    glTranslatef((GLfloat)pos.x, (GLfloat)pos.y, 0.0f);    // Move background to right direction 
    if((width != m_savedWidth) || (height != m_savedHeight))  // If size to draw is different than the one saved in display list, 
                    // then recalculate everything and save in display list 
    { 
     // That size will be now saved in display list 
     m_savedWidth = width; 
     m_savedHeight = height; 

     GLfloat texelCentersOffsetX = (GLfloat)1/(2*m_width); 

     // Instead of coordinates range 0..1 we need to specify new ones 
     GLfloat maxTexCoordWidth = m_bTiling ? (GLfloat)width/m_width : 1.0; 
     GLfloat maxTexCoordHeight = m_bTiling ? (GLfloat)height/m_height : 1.0; 

     GLfloat maxTexCoordBorderX = (GLfloat)m_borderWidth/m_width; 
     GLfloat maxTexCoordBorderY = (GLfloat)m_borderWidth/m_height; 

     /* 9-cell-pattern, each number represents one quad 

     ------------------- 
     | 1 | 2 | 3 | 
     ------------------- 
     | |   | | 
     | 4 | 9 | 5 | 
     | |   | | 
     ------------------- 
     | 6 | 7 | 8 | 
     ------------------- 

     */ 

     /* How vertices are distributed on one quad made of two triangles 

     v1 ------ v0 
     |  /| 
     | / | 
     |/  | 
     v2 ------ v3 

     */ 

     GLfloat vertices[] = { 
           // Top left quad [1] 
           m_borderWidth, 0, 0,       // v0 
           0, 0, 0,          // v1  
           0, m_borderWidth, 0,       // v2    

           0, m_borderWidth, 0,       // v2 
           m_borderWidth, m_borderWidth, 0,    // v3 
           m_borderWidth, 0, 0,       // v0 

           // Top middle quad [2] 
           width-m_borderWidth, 0, 0,      // v0 
           m_borderWidth, 0, 0,       // v1 
           m_borderWidth, m_borderWidth, 0,    // v2 

           m_borderWidth, m_borderWidth, 0,    // v2 
           width-m_borderWidth, m_borderWidth, 0,   // v3 
           width-m_borderWidth, 0, 0,      // v0 

           // Top right quad [3] 
           width, 0, 0,         // v0 
           width-m_borderWidth, 0, 0,      // v1 
           width-m_borderWidth, m_borderWidth, 0,   // v2 

           width-m_borderWidth, m_borderWidth, 0,   // v2 
           width, m_borderWidth, 0,      // v3 
           width, 0, 0,         // v0 

           // Middle left quad [4] 
           m_borderWidth, m_borderWidth, 0,    // v0 
           0, m_borderWidth, 0,       // v1 
           0, height-m_borderWidth, 0,      // v2 

           0, height-m_borderWidth, 0,      // v2 
           m_borderWidth, height-m_borderWidth, 0,   // v3 
           m_borderWidth, m_borderWidth, 0,    // v0 

           // Middle right quad [5] 
           width, m_borderWidth, 0,      // v0 
           width-m_borderWidth, m_borderWidth, 0,   // v1 
           width-m_borderWidth, height-m_borderWidth, 0, // v2 

           width-m_borderWidth, height-m_borderWidth, 0, // v2 
           width, height-m_borderWidth, 0,     // v3 
           width, m_borderWidth, 0,      // v0 

           // Bottom left quad [6] 
           m_borderWidth, height-m_borderWidth, 0,   // v0 
           0, height-m_borderWidth, 0,      // v1 
           0, height, 0,         // v2 

           0, height, 0,         // v2 
           m_borderWidth, height, 0,      // v3 
           m_borderWidth, height-m_borderWidth, 0,   // v0 

           // Bottom middle quad [7] 
           width-m_borderWidth, height-m_borderWidth, 0, // v0 
           m_borderWidth, height-m_borderWidth, 0,   // v1 
           m_borderWidth, height, 0,      // v2 

           m_borderWidth, height, 0,      // v2 
           width-m_borderWidth, height, 0,     // v3 
           width-m_borderWidth, height-m_borderWidth, 0, // v0 

           // Bottom right quad [8] 
           width, height-m_borderWidth, 0,     // v0 
           width-m_borderWidth, height-m_borderWidth, 0, // v1 
           width-m_borderWidth, height, 0,     // v2 

           width-m_borderWidth, height, 0,     // v2 
           width, height, 0,        // v3 
           width, height-m_borderWidth, 0,     // v0 

           // Middle middle quad [9] 
           width-m_borderWidth, m_borderWidth, 0,   // v0 
           m_borderWidth, m_borderWidth, 0,    // v1 
           m_borderWidth, height-m_borderWidth, 0,   // v2 

           m_borderWidth, height-m_borderWidth, 0,   // v2 
           width-m_borderWidth, height-m_borderWidth, 0, // v3 
           width-m_borderWidth, m_borderWidth, 0   // v0 
          }; 

     copy(vertices, vertices + 162, m_vCoords);    // 162, because we have 162 coordinates 


     int dataSize = 162 * sizeof(GLfloat); 
     m_vboId = createVBO(m_vCoords, dataSize); 

    } 

    // bind VBOs for vertex array 
    glBindBufferARB(GL_ARRAY_BUFFER_ARB, m_vboId);   // for vertex coordinates 

    glEnableClientState(GL_VERTEX_ARRAY);     // activate vertex coords array 
     glVertexPointer(3, GL_FLOAT, 0, 0);      
     glDrawArrays(GL_TRIANGLES, 0, 162); 
    glDisableClientState(GL_VERTEX_ARRAY);     // deactivate vertex array 

    // bind with 0, so, switch back to normal pointer operation 
    glBindBufferARB(GL_ARRAY_BUFFER_ARB, NO_VBO_ID); 

    glPopMatrix(); 

    return true; 
} 

E 'molto simile alla versione precedente, ma invece di glDisplayList e glVertex2i() ho usato VBO che viene creata con dati memorizzati in un array .

ma i risultati mi ha deluso, perché ho ottenuto calo di prestazioni, invece di spinta, ho avuto a malapena ~260 fps e devo notare che in questa versione il metodo non ho ancora implementato l'uso di texture, quindi non ci sono solo i quad per ora senza qualsiasi trama associata ad esso.

Ho letto alcuni articoli per trovare quale potrebbe essere il motivo di tale rallentamento e ho scoperto che forse è dovuto a una grande quantità di piccoli VBO se dovrei probabilmente avere uno VBO contenente tutti i dati di sfondi invece di separare VBO per ogni sfondo. Ma il problema è che i Object possono spostarsi e hanno trame diverse (e l'atlante di texture non è una buona soluzione per me), quindi sarebbe difficile per me aggiornare quelle modifiche per quelle Object che hanno cambiato il loro stato. Per ora, quando viene modificato Object s, ho appena ricreato è VBO e il resto VBO rimangono intatti.

Quindi la mia domanda è: cosa sto sbagliando?L'uso di un numero più grande (~ 600) di piccoli VBO s è molto più lento del metodo di disegno obsoleto con glVertex2i? E quale potrebbe essere - forse non la migliore, ma migliore - soluzione nel mio caso?

+1

Il frame al secondo è una [terribile metrica comparativa] (http://www.mvps.org/directx/articles/fps_versus_frame_time.htm). Passa a (milli-) secondi per fotogramma. – genpfault

+0

@genpfault Grazie, è sicuramente bello saperlo. Resta il fatto che VBO è più lento in quel caso, però. –

+1

Alcuni driver sono in realtà molto bravi nella compilazione degli elenchi di visualizzazione. – JasonD

risposta

3

Solo perché la roba a funzione fissa è obsoleta, deprecata e in genere non consigliata, non significa necessariamente che sia sempre lenta.

Né la fantasiosa 'nuova' (è stata intorno a un po ') funzionalità con buffer e shader e simili, significa necessariamente che tutto sarà veloce come un lampo.

Quando si avvolge il disegno in un elenco di visualizzazione, si sta praticamente passando un mucchio di operazioni al driver. Questo in effetti fornisce un buon margine di manovra al guidatore per ottimizzare ciò che sta accadendo. Può benissimo impacchettare la maggior parte di ciò che stai facendo in un ammasso pre-confezionato di operazioni GPU piuttosto efficiente. Questo potrebbe essere leggermente più efficiente di quello che succede quando si impacchettano i dati in buffer e li mandano via.

Ciò non vuol dire che consiglierei di attenermi all'interfaccia vecchio stile, ma certamente non mi sorprende che ci siano casi in cui fa un buon lavoro.

7

A giudicare dall'aspetto, stai ricreando il VBO con ogni fotogramma. Se si desidera semplicemente modificare i dati, utilizzare glBufferSubData, poiché glBufferData passa attraverso l'intera, lunga inizializzazione VBO.

Se i dati sono statici, creare il VBO solo una volta, quindi riutilizzarlo.

+3

Si noti che se si desidera avere un VBO con dati che cambiano molto, è necessario utilizzare GL_DYNAMIC_DRAW come tipo di dati per il VBO. – Ethereal

+1

Questo sembra essere proprio il problema. Sposta il tuo codice di creazione VBO in un metodo diverso (come 'createMesh') e chiamalo solo quando è necessario un nuovo oggetto (come l'inizializzazione). Ricreando il VBO ogni frame annulla qualsiasi guadagno dall'utilizzarlo. – ssell

+1

VBO non viene ricreato ogni fotogramma - solo se la dimensione dell'oggetto di disegno viene modificata, cosa che accade molto raramente. –