2D文字绘图功能对性能影响巨大



我正在制作一个基于opengl的小游戏。这很简单,所以我的事件循环非常快,我得到了~1200fps的刷新率。

然而,我现在使用一些2D文本渲染功能,将2D纹理的部分映射到屏幕上。这不是我的代码,我使用这个源代码:http://www.opengl-tutorial.org/intermediate-tutorials/tutorial-11-2d-text/

这个代码的问题是它对性能有很大的影响。刷新率下降到大约800fps,这并不是一个真正的问题,但我也注意到一些峰值,其中单个事件循环传递需要很长时间。

我的正常通过大约0.002秒,即使在事件循环中使用std::cout。尖峰,单帧,从0.05s0.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]);

重要的是要记住,您必须限制字符串以适应最大缓冲区大小,或者仅在需要时重新分配缓冲区。一个好主意是为单个标签写一个类,它可以记住这些东西,但现在,这样就可以了。:)

感谢所有的性能提示,它们也有效果。

最新更新