VBOs медленнее, чем устаревший метод рисования примитивов — почему?

Я работаю над TG-приложением OpenGL, C ++.
Я добавляю пример экрана из приложения, чтобы он был более понятным:

я имею Tile класс, который содержит массив Objects. В каждой плитке может храниться до 15 объектов. Tile с зеленым и желтым квадратом на нем (два объекта), так что это 10x10x15 = 1500 Objects рисовать (в худшем случае, потому что я не работаю с «пустыми»). Обычно это меньше, в своих тестах я использую около 600 из них. Object имеет свой собственный рисунок, который можно нарисовать. каждый Object принадлежит одному Tile одновременно, но его можно переместить (например, красные квадраты на картинке).

ObjectФоны s будут иметь границы, и они должны быть хорошо масштабируемыми, поэтому я использую паттерн из 9 патчей, чтобы нарисовать их (они сделаны из 9 квадратов).

Без рисунка Tileс (их Objects, чтобы быть точным), мое приложение имеет около 600 fps,

Сначала я использовал устаревший метод, чтобы нарисовать Tiles — использование glBegin(GL_QUADS)/glEnd() а также glDisplayLists. У меня было большое падение производительности из-за этого рисунка — от 600 в 320 fps, Вот как я их рисовал:

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;
}

Там, наверное, слишком много кода, но я хотел показать все. Главное в этой версии — использование списков отображения и glVertex2i которые устарели.

Я думал, что проблемой такого замедления было использование этого устаревшего метода, который я читал довольно медленно, поэтому я решил пойти на VBO, Я использовал этот урок и в соответствии с этим я изменил свой метод так:

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 coordinatesint 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;
}

Это очень похоже на предыдущую версию, но вместо glDisplayList а также glVertex2i() я использовал VBO который создается из данных, хранящихся в массиве.

Но результаты разочаровали меня, потому что я получил падение производительности вместо повышения, я получил только
~260 fps и я должен отметить, что в этой версии метода я еще не реализовал использование текстур, так что пока есть только четыре квадрата без какой-либо текстуры, связанной с ним.

Я прочитал несколько статей, чтобы найти причину такого замедления и выяснил, что, возможно, это связано с большим количеством мелких VBOс, и я, вероятно, должен иметь один VBO содержащий все фоновые данные вместо отдельных VBO для каждого фона. Но проблема в том, что ObjectОни могут перемещаться, и у них разные текстуры (и текстурный атлас не является хорошим решением для меня), поэтому мне было бы сложно обновить эти изменения для них. Objectс, которые изменили свое состояние. На данный момент, когда ObjectS меняется, я просто воссоздаю это VBO и остальное VBOОстаться нетронутым.

Итак, мой вопрос — что я делаю не так? Использует ли большее (~ 600) количество маленьких VBOS действительно медленнее, чем устаревший метод рисования с glVertex2i? И что может быть — может быть, не лучшим, но лучшим — решением в моем случае?

7

Решение

Тот факт, что исправленные функции устарели, устарели и, как правило, не рекомендуется, не обязательно означает, что они всегда медленные.

И при этом не причудливая «новая» (уже давно) функциональность с буферами и шейдерами и тому подобным не обязательно означает, что все будет молниеносно.

Когда вы оборачиваете свой чертеж в список отображения, вы в основном передаете кучу операций драйверу. Это на самом деле дает водителю достаточно возможностей для оптимизации происходящего. Это может очень хорошо упаковать большую часть того, что вы делаете, в довольно эффективный пакетный пакет операций с графическим процессором. Это может быть немного более эффективным, чем то, что происходит, когда вы Упакуйте ваши данные в буферы и отправьте их.

Это не значит, что я бы порекомендовал придерживаться интерфейса в старом стиле, но, конечно, я не удивлен, что есть случаи, когда он делает хорошую работу.

3

Другие решения

Судя по всему, вы воссоздаете VBO с каждым кадром. Если вы просто хотите изменить данные, используйте glBufferSubData, как glBufferData проходит всю долгую инициализацию VBO.

Если данные статические, создайте VBO только один раз, а затем используйте его повторно.

7

По вопросам рекламы [email protected]