【问题标题】:FreeType OpenGL dynamic Text = abysmal performanceFreeType OpenGL 动态文本 = 糟糕的性能
【发布时间】:2015-06-04 13:22:11
【问题描述】:

我目前正在寻找代码中的瓶颈,结果发现 GUI 就是其中之一。好吧,实际上不是 GUI,而是在那里绘制的动态文本。

初始化

        if (FT_Init_FreeType(&m_FreeType))
            throw Helpers::ExceptionWithMsg("Could not init freetype lib");

        if (FT_New_Face(m_FreeType, "res\\fonts\\FreeSans.ttf", 0, &m_FontFace))
            throw Helpers::ExceptionWithMsg("Could not open font");

        m_ShaderID = ... // Loads the corresponding shader
        m_TextColorLocation = glGetUniformLocation(m_ShaderID, "color");
        m_CoordinatesLocation = glGetAttribLocation(m_ShaderID, "coord");

        glGenBuffers(1, &m_VBO);

        FT_Set_Pixel_Sizes(m_FontFace, 0, m_FontSize);
        glyph = m_FontFace->glyph;

        glGenTextures(1, &m_Texture);

        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, m_Texture);

        // We require 1 byte alignment when uploading texture data 
        glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

        // Linear filtering usually looks best for text 
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

        // Clamping to edges is important to prevent artifacts when scaling 
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

        glUseProgram(m_ShaderID);
            glUniform4f(m_TextColorLocation, m_TextColor.x, m_TextColor.y, m_TextColor.z, m_TextColor.w);
        glUseProgram(0);

我做什么:我初始化 FreeType,获取字体,初始化着色器和所有制服。

然后我为纹理坐标创建 vbo,为字体设置像素,获取字形。

现在我生成纹理,激活它,绑定它...我想设置所有参数,然后设置永不改变的制服。

渲染:

    glUseProgram(m_ShaderID);

    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, m_Texture);

    // Linear filtering usually looks best for text 
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    // Set up the VBO for our vertex data 
    glEnableVertexAttribArray(m_CoordinatesLocation);
    glBindBuffer(GL_ARRAY_BUFFER, m_VBO);
    glVertexAttribPointer(m_CoordinatesLocation, 4, GL_FLOAT, GL_FALSE, 0, 0);

    GLfloat cursorPosX = m_X;
    GLfloat cursorPosY = m_Y;
    for (size_t i = 0; i < m_Text.size(); ++i)
    {
        // If Loading a char fails, just continue
        if (FT_Load_Char(m_FontFace, m_Text[i], FT_LOAD_RENDER))
            continue;

        glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, glyph->bitmap.width, glyph->bitmap.rows, 0, GL_ALPHA, GL_UNSIGNED_BYTE, glyph->bitmap.buffer);

        // Calculate the vertex and texture coordinates 
        GLfloat x2 = cursorPosX + glyph->bitmap_left * m_SX;
        GLfloat y2 = -cursorPosY - glyph->bitmap_top * m_SY;
        GLfloat w = glyph->bitmap.width * m_SX;
        GLfloat h = glyph->bitmap.rows * m_SY;

        PointStruct box[4] =
        {
            { x2, -y2, 0, 0 },
            { x2 + w, -y2, 1, 0 },
            { x2, -y2 - h, 0, 1 },
            { x2 + w, -y2 - h, 1, 1 }
        };

        // Draw the character on the screen
        glBufferData(GL_ARRAY_BUFFER, sizeof box, box, GL_DYNAMIC_DRAW);
        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

        // Advance the cursor to the start of the next character 
        cursorPosX += glyph->advance.x / 64 * m_SX;
        cursorPosY += glyph->advance.y / 64 * m_SY;
    }

    glDisableVertexAttribArray(m_CoordinatesLocation);
    glDeleteTextures(1, &m_Texture);
    glDisable(GL_BLEND);
    glUseProgram(0);

设置着色器和其他东西是显而易见的。

对于每个渲染调用,我激活纹理,绑定它,启用我存储纹理坐标的 VBO。然后我遍历文本中的每个字符,并使用 FT_LOAD_CHAR 加载它。 然后我用 glTexImage2D 指定纹理,计算顶点和纹理坐标并绘制所有内容。

这似乎非常低效,但我找不到提高性能的方法,但仍然有可读的文本。

我想在 init 中只设置一次文本参数 -> 所有字符都是框。

我想将 GL_DYNAMIC_DRAW 设置为 GL_STATIC_DRAW...差别不大。 我还能做什么?

我渲染的文本是动态的,每一帧都会改变(或可能改变),所以我有点卡住了。

我用查询来查询这些东西的性能。如果我不渲染动态文本,它会非常低,但如果我渲染动态文本,它会变得非常高......在这个过程中没有其他事情发生,它只是在绘制 GUI。

真正困扰我的是什么

有一件事我真的不明白(可能是晴天……)

如果我没有在 render-method() 中设置线性过滤,我会得到奇怪的立方体字形,但这是为什么呢? OpenGL 是一个状态机,纹理参数设置为当前绑定的。那么如果我在初始化时将 Min 和 Mag 过滤器设置为 GL_LINEAR 为什么还不够呢?

如果我在渲染中删除这 2 行,我会从查询中获得更好的性能(数字要低得多),但它不会绘制任何可读的内容。

【问题讨论】:

  • 关于您的第二个问题(立方体字形?)您的纹理可能不完整。搜索“opengl 纹理完整性”。

标签: c++ opengl freetype


【解决方案1】:

这绝对会很慢。

对于每个渲染调用,我激活纹理,绑定它,启用我存储纹理坐标的 VBO。然后我遍历文本中的每个字符,并使用 FT_LOAD_CHAR 加载它。然后我用 glTexImage2D 指定纹理,计算顶点和纹理坐标并绘制所有内容。

不幸的是,这个问题很难。这是我使用的方法:

  • 有一个纹理,采用GL_RED8 格式,用于存储字形。

  • 每当需要新字形时,都会将其添加到纹理中。这是通过调用FT_Render_Glyph() 并将结果复制到纹理缓冲区来完成的。如果新的字形不适合,则整个字形纹理将调整大小并重新打包。 (我使用天际线算法来打包字形,因为它很简单。)

  • 如果添加了任何新的字形,我会致电glTexSubImage2D()。代码的结构应确保每帧仅调用一次。

  • 为了渲染文本,我创建了一个 VBO,其中包含渲染一段文本所需的所有四边形的顶点和纹理坐标。 (请理解“四边形”是指两个三角形,而不是GL_QUAD)。

因此,当您更改要呈现的文本时,

  • 您必须更新 VBO,但每帧只需更新一次

  • 您可能需要更新纹理,但每帧只需更新一次,而且随着字形纹理被您使用的字形填满,这种情况发生的频率可能会降低。

制作这种系统原型的一个好方法是首先将字体中的所有字形渲染到纹理中,但如果您最终使用多种字体并且样式,或者如果您想呈现中文、韩文或日文文本。

其他注意事项包括换行、字形替换、字距调整、双向、国际文本的一般问题、如何指定样式等。我建议将 HarfBuzz 与 FreeType 结合使用。 HarfBuzz 处理困难的字形替换和定位问题。如果您的程序只有英文文本,那么这些都不是绝对必要的。

有一些库可以做到所有这些,但我没有使用它们。

另一种方法是,如果您想打破僵局,则可以在您的应用程序中嵌入像 Chromium(Awesomium、WebKit、Gecko 等多种选择)这样的 Web 浏览器,然后将所有文本渲染都外包给该浏览器。

【讨论】:

  • 另一种方法是分配一个新的,而不是在填充时调整字形纹理的大小。这将需要管理哪些 VBO 引用了哪些字形纹理,但可能会更节省内存,特别是如果您的平台需要 POT 纹理(某些 GLES 平台需要)。
  • @MuertoExcobito:这将需要额外的上下文切换和更多的绘图调用,或者纹理指针等深奥的功能。更新现有的 VBO 以更改纹理坐标很容易,尤其是考虑到重新打包并不经常发生,而且 VBO 可能不是很大。
【解决方案2】:

您的瓶颈可能是许多绘图调用。首先你在你的绘制例程中缓冲纹理:而不是提供一个纹理,你可以将字符映射到四边形位置,然后替换以下内容:

    // Calculate the vertex and texture coordinates 
    GLfloat x2 = cursorPosX + glyph->bitmap_left * m_SX;
    GLfloat y2 = -cursorPosY - glyph->bitmap_top * m_SY;
    GLfloat w = glyph->bitmap.width * m_SX;
    GLfloat h = glyph->bitmap.rows * m_SY;

    PointStruct box[4] =
    {
        { x2, -y2, 0, 0 },
        { x2 + w, -y2, 1, 0 },
        { x2, -y2 - h, 0, 1 },
        { x2 + w, -y2 - h, 1, 1 }
    };

    // Draw the character on the screen
    glBufferData(GL_ARRAY_BUFFER, sizeof box, box, GL_DYNAMIC_DRAW);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

使用预处理文本的代码,生成更大的PointStruct 缓冲区(不要忘记调整查找纹理的纹理坐标)并在每次绘制调用时绘制多个字符。

【讨论】:

    【解决方案3】:

    有两种简单的方法可以提高性能。

    1. 创建一个纹理,其中包含您需要在设置的偏移处渲染的所有字符。然后,您的文本字符串将成为一个模型(缓冲区对象),它将引用正确的偏移系列来创建您的文本字符串。这样做的缺点是您将无法进行字距调整或任何其他花哨的字体连接。
    2. 使用PangoCairo 将您的文本完全呈现为单个位图。该位图将使用正确的字距和连接格式化文本。然后,您只需为整个文本输出上传和绘制一个纹理。

    【讨论】:

      猜你喜欢
      • 2012-06-14
      • 1970-01-01
      • 1970-01-01
      • 2021-04-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-26
      • 2013-02-04
      相关资源
      最近更新 更多