2D文本绘图功能对性能有很大影响

时间:2014-07-07 22:29:01

标签: performance opengl text shader

我正在开发一款基于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 FUNCTION

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

绘制功能

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

}

2 个答案:

答案 0 :(得分:2)

您的代码存在一些可能影响性能的问题。

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和VAO:

您正在使用GL_STATIC_DRAW指定缓冲区数据。对于经常更新的缓冲区,这是错误的。最好的标志是GL_STREAM_DRAW。有关详细信息,请参阅glBufferData()的文档。

另外,请考虑使用Vertex Array Object。这也应该优化你的渲染。

4)考虑批处理渲染调用:

这里的主要性能瓶颈显然是每个printText2D调用正在更新GL缓冲区并执行绘制调用。通过一些努力,您可以将所有这些打印调用批量处理为您选择的某些数据结构,并提交所有缓冲区更新并立即绘制调用。使printText2D写入CPU侧缓冲区,然后在后一时间,也许在正常3D渲染结束时,将该缓冲区一次性刷新到OpenGL。这可能会影响性能。

答案 1 :(得分:0)

我修复了滞后尖峰! (但我仍然不知道他们为什么在那里首先出现)

我现在使用glBufferData();而不是每帧都使用glBufferSubData,它只更新缓冲区但不重新分配它。

所以文件现在看起来像这样:

HEADER(部分)

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(部分)

// 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]);

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

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