VBO 比绘制图元的过时方法慢 - 为啥?
Posted
技术标签:
【中文标题】VBO 比绘制图元的过时方法慢 - 为啥?【英文标题】:VBOs slower than obsolete method of drawing primitives - why?VBO 比绘制图元的过时方法慢 - 为什么? 【发布时间】:2013-02-27 15:45:13 【问题描述】:我正在开发基于 Tile 的 OpenGL、C++ 应用程序。 我正在从应用程序中添加示例屏幕,以便更清晰:
我有 Tile
类,其中包含 Object
s 数组。每个图块最多可以存储 15 个对象 - 例如 Tile
上面有绿色和黄色方块(两个对象),所以它是 10x10x15 = 1500 Object
s 来绘制(在最坏的情况下,因为我不处理“空”的)。通常它会更少,在我的测试中我使用了大约 600 个。 Object
有自己的图形,可以绘制。每个Object
一次属于一个Tile
,但可以移动(例如图片中的红色方块)。
Object
s 背景将有一个边框并且它们需要很好地扩展,所以我使用 9-patch 模式来绘制它们(它们由 9 个四边形组成)。
没有绘制Tile
s(准确地说是他们的Object
s),我的应用程序大约有600 fps
。
起初,我一直使用过时的方法来绘制那些Tile
s - 使用glBegin(GL_QUADS)
/glEnd()
和glDisplayList
s。由于那幅画,我的表现大幅下降 - 从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
。我用过this tutorial,据此我改变了我的方法:
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;
它与以前的版本非常相似,但我使用的是VBO
,而不是glDisplayList
和glVertex2i()
,它是从存储在数组中的数据创建的。
但是结果让我很失望,因为我的性能下降而不是提升,我几乎没有
~260 fps
我必须注意,在这个方法版本中我还没有实现纹理的使用,所以现在只有四边形,没有任何纹理绑定到它。
我已经阅读了几篇文章以找出导致速度变慢的原因,并发现这可能是由于大量的小VBO
s 而我应该有一个包含所有背景的VBO
数据而不是每个背景的单独VBO
。但问题是Object
s 可以四处移动并且它们有不同的纹理(纹理图集对我来说不是一个好的解决方案),所以我很难为那些改变了它们的Object
s 更新这些更改状态。现在,当Object
s 被更改时,我只是重新创建它的VBO
,其余的VBO
s 保持不变。
所以我的问题是 - 我做错了什么?使用更大(~600)个小VBO
s 真的比使用glVertex2i
绘制的过时方法慢吗?在我的情况下,什么可能是——也许不是最好的,但更好的——解决方案?
【问题讨论】:
每秒帧数是terrible comparative metric。切换到每帧(毫秒)秒。 @genpfault 谢谢,很高兴知道这一点。不过,事实仍然是 VBO 在这种情况下速度较慢。 有些驱动其实很擅长编译显示列表。 【参考方案1】:从外观上看,您正在用每一帧重新创建 VBO。如果您只想更改数据,请使用 glBufferSubData
,因为 glBufferData
会经历整个冗长的 VBO 初始化。
如果数据是静态的,则只创建一次 VBO,然后重复使用它。
【讨论】:
请注意,如果您想拥有一个数据变化很大的 VBO,您应该使用 GL_DYNAMIC_DRAW 作为 VBO 的数据类型。 这看起来正是问题所在。将您的 VBO 创建代码移动到不同的方法中(例如createMesh
),并且仅在需要新对象时调用它(例如初始化)。每帧重新创建 VBO 会使使用它的任何收益无效。
VBO 不会在每一帧都重新创建 - 仅当绘图对象的大小发生变化时,这种情况很少发生。
我不明白为什么要更改 VBO 只是为了缩放 - 只需缩放模型矩阵(glScalef 或其他)。【参考方案2】:
仅仅因为固定功能的东西陈旧、不推荐使用并且通常不推荐使用,并不一定意味着它总是很慢。
带有缓冲区和着色器之类的花哨的“新”(已经有一段时间了)功能也不一定意味着一切都会快如闪电。
当您将绘图包装在显示列表中时,您基本上是将一堆操作传递给驱动程序。这实际上为驱动程序提供了相当大的空间来优化正在发生的事情。它可以很好地将您正在做的大部分事情打包成一个非常高效的预打包 GPU 操作块。这可能比您将数据打包到缓冲区并将它们发送出去时的效率略高。
这并不是说我会建议坚持使用旧式界面,但我当然不会感到惊讶,因为在某些情况下它做得很好。
【讨论】:
以上是关于VBO 比绘制图元的过时方法慢 - 为啥?的主要内容,如果未能解决你的问题,请参考以下文章
为啥绘制我的 OpenGL-ES VBO 网格会阻止其他三角形显示?