我正在制作一个基于opengl的小游戏。这很简单,所以我的事件循环非常快,我得到了~1200fps
的刷新率。
然而,我现在使用一些2D文本渲染功能,将2D纹理的部分映射到屏幕上。这不是我的代码,我使用这个源代码:http://www.opengl-tutorial.org/intermediate-tutorials/tutorial-11-2d-text/
这个代码的问题是它对性能有很大的影响。刷新率下降到大约800fps
,这并不是一个真正的问题,但我也注意到一些峰值,其中单个事件循环传递需要很长时间。
我的正常通过大约0.002
秒,即使在事件循环中使用std::cout
。尖峰,单帧,从0.05s
到0.1s
。问题是为什么我有了文本功能就会有这些尖峰,而没有文本功能就没有。
一些重要事实:该函数在每个循环中调用两次,大约有10个字符。字符使用的纹理是一个5mb的.dds
文件,2048²像素。我最关心的部分是OpenGL部分,切换着色器等等。它们对性能有很大的影响吗?
着色器不值得一提。我在游戏中的着色器为线框外观使用固定颜色,纹理着色器只计算纹理中的采样位置。你知道是什么引起了这些麻烦吗?
<INIT函数/strong>
void initText2D(const char * texturePath){
// Initialize texture
Text2DTextureID = loadDDS(texturePath);
// Initialize VBO
glGenBuffers(1, &Text2DVertexBufferID);
glGenBuffers(1, &Text2DUVBufferID);
// Initialize Shader
Text2DShaderID = LoadShaders( "TextVertexShader.vertexshader", "TextVertexShader.fragmentshader" );
// Initialize uniforms' IDs
Text2DUniformID = glGetUniformLocation( Text2DShaderID, "myTextureSampler" );
}
<画函数/strong>
void printText2D(const char * text, int x, int y, int size, int widthOffset){
unsigned int length = strlen(text);
// Fill buffers
std::vector<glm::vec2> vertices;
std::vector<glm::vec2> UVs;
for ( unsigned int i=0 ; i<length ; i++ ){
glm::vec2 vertex_up_left = glm::vec2( x+i*(size+widthOffset) , y+size );
glm::vec2 vertex_up_right = glm::vec2( x+i*(size+widthOffset)+size, y+size );
glm::vec2 vertex_down_right = glm::vec2( x+i*(size+widthOffset)+size, y );
glm::vec2 vertex_down_left = glm::vec2( x+i*(size+widthOffset) , y );
vertices.push_back(vertex_up_left );
vertices.push_back(vertex_down_left );
vertices.push_back(vertex_up_right );
vertices.push_back(vertex_down_right);
vertices.push_back(vertex_up_right);
vertices.push_back(vertex_down_left);
char character = text[i];
float uv_x = (character%16)/16.0f;
float uv_y = (character/16)/16.0f;
glm::vec2 uv_up_left = glm::vec2( uv_x , uv_y );
glm::vec2 uv_up_right = glm::vec2( uv_x+1.0f/16.0f, uv_y );
glm::vec2 uv_down_right = glm::vec2( uv_x+1.0f/16.0f, (uv_y + 1.0f/16.0f) );
glm::vec2 uv_down_left = glm::vec2( uv_x , (uv_y + 1.0f/16.0f) );
UVs.push_back(uv_up_left );
UVs.push_back(uv_down_left );
UVs.push_back(uv_up_right );
UVs.push_back(uv_down_right);
UVs.push_back(uv_up_right);
UVs.push_back(uv_down_left);
}
glBindBuffer(GL_ARRAY_BUFFER, Text2DVertexBufferID);
glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(glm::vec2), &vertices[0], GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, Text2DUVBufferID);
glBufferData(GL_ARRAY_BUFFER, UVs.size() * sizeof(glm::vec2), &UVs[0], GL_STATIC_DRAW);
// Bind shader
glUseProgram(Text2DShaderID);
// Bind texture
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, Text2DTextureID);
// Set our "myTextureSampler" sampler to user Texture Unit 0
glUniform1i(Text2DUniformID, 0);
// 1rst attribute buffer : vertices
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, Text2DVertexBufferID);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, (void*)0 );
// 2nd attribute buffer : UVs
glEnableVertexAttribArray(1);
glBindBuffer(GL_ARRAY_BUFFER, Text2DUVBufferID);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, (void*)0 );
glDisable(GL_DEPTH_TEST);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
// Draw call
glDrawArrays(GL_TRIANGLES, 0, vertices.size() );
//glDisable(GL_BLEND);
glEnable(GL_DEPTH_TEST);
glDisableVertexAttribArray(0);
glDisableVertexAttribArray(1);
}
您的代码中有一些问题可能会影响性能。
1)在紧循环中调用的函数中使用std::vector
:
std::vector<glm::vec2> vertices;
std::vector<glm::vec2> UVs;
std::vector
是一个可增长的数组,但增长它通常会增加成本(更有可能是新的内存分配和副本)。你在循环中做了很多push_back
,天知道向量会重新分配多少次,复制它的内容来容纳新的数据。
reserve()
方法预先预留内存:
// 3 vertes for each triangle in the quad
const size_t numVerts = 6;
cosnt size_t numUVs = 6;
vertices.reserve(length * numVerts);
UVs.reserve(length * numUVs);
for ( unsigned int i=0 ; i<length ; i++ )
{
// all the rest ...
}
2)一些不必要的GL状态改变:
glUniform1i(Text2DUniformID, 0);
可以在init函数中调用一次,因为它永远不会改变。
void initText2D(const char * texturePath)
{
...
glUseProgram(Text2DShaderID);
glUniform1i(Text2DUniformID, 0);
glUseProgram(0);
}
在draw函数中似乎有一些其他不必要的状态更改,例如:
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
除非您在其他地方更改了它,否则没有必要每次绘制时都调用它。
3) GL_STREAM_DRAW
and VAOs:
使用GL_STATIC_DRAW
指定缓冲区数据。对于频繁更新的缓冲区来说,这是错误的。最好的旗帜是GL_STREAM_DRAW
。查看glBufferData()
的文档获取更多信息。
同样,考虑使用顶点数组对象。这也应该优化你的渲染。
4)考虑批处理渲染调用:
这里的主要性能瓶颈显然是每个printText2D
调用都在更新GL缓冲区并执行draw调用。通过一些努力,您可以将所有这些打印调用批量处理到您选择的某个数据结构中,并立即提交所有缓冲区更新和绘制调用。让printText2D
写入cpu端缓冲区,然后在稍后的时间,也许在正常的3D渲染结束时,将该缓冲区一次性刷新到OpenGL。
我修复了延迟峰值!(然而,我仍然不知道为什么他们在那里)
我现在使用glBufferSubData
,而不是每帧使用glBufferData();
,它只更新缓冲区,但不重新分配它。文件现在看起来是这样的:
头(部分)
int maxSize = 20;
// Initialize VBO
glGenBuffers(1, &Text2DVertexBufferID);
glBindBuffer(GL_ARRAY_BUFFER, Text2DVertexBufferID);
glBufferData(GL_ARRAY_BUFFER, 6*maxSize * sizeof(glm::vec2), NULL, GL_STREAM_DRAW);
glGenBuffers(1, &Text2DUVBufferID);
glBindBuffer(GL_ARRAY_BUFFER, Text2DUVBufferID);
glBufferData(GL_ARRAY_BUFFER, 6*maxSize * sizeof(glm::vec2), NULL, GL_STREAM_DRAW);
RENDER FUNCTION (part)
// update buffer data
glBindBuffer(GL_ARRAY_BUFFER, Text2DVertexBufferID);
glBufferSubData(GL_ARRAY_BUFFER, 0, vertices.size() * sizeof(glm::vec2), &vertices[0]);
glBindBuffer(GL_ARRAY_BUFFER, Text2DUVBufferID);
glBufferSubData(GL_ARRAY_BUFFER, 0, UVs.size() * sizeof(glm::vec2), &UVs[0]);
重要的是要记住,您必须限制字符串以适应最大缓冲区大小,或者仅在需要时重新分配缓冲区。一个好主意是为单个标签写一个类,它可以记住这些东西,但现在,这样就可以了。:)
感谢所有的性能提示,它们也有效果。