You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何glDrawArrays性能低于glBegin?调用Print函数2000次帧率骤降400

为什么用glDrawArrays渲染字体反而帧率暴跌?

嘿,我来帮你拆解下这个帧率下降的问题——你以为glDrawArrays应该更高效,但实际掉帧的锅根本不在它身上,而是你调用Print的方式不对。核心问题是2000次独立的绘制调用+重复的状态切换,把GPU的性能给拖垮了,具体原因和优化方案如下:

问题根源分析

  • 绘制调用的固定开销:OpenGL的任何绘制调用(包括glDrawArrays)都有固定的上下文切换、命令提交、状态校验开销。哪怕单次调用只画一个字符,2000次累积起来的开销也会非常惊人——GPU在频繁切换绘制任务的过程中根本没法高效工作。
  • 重复的状态切换:从你给出的代码片段看,每次Print都调用了glEnableClientState,大概率还会重复绑定纹理、设置顶点/UV指针这些操作。GPU对状态切换的敏感度很高,每次切换都需要停顿等待状态更新,2000次重复操作会让帧率直接跳水。
  • 小批量绘制的低效性:glDrawArrays的优势是处理大批量顶点数据,但你用它每次只画几个字符的顶点,完全没发挥出它的优势,反而因为频繁调用放大了开销。

优化方案(直接解决帧率问题)

1. 实现字符批处理机制

把所有要渲染的文本的顶点数据打包到一个大的缓冲区里,只调用一次glDrawArrays完成所有字符的绘制,而不是每个字符/每段文本调用一次。

2. 合并状态切换

把字体纹理绑定、客户端状态开启(比如GL_VERTEX_ARRAY、GL_TEXTURE_COORD_ARRAY)这些操作,移到所有文本渲染的最开始,渲染完成后再统一关闭,避免重复切换状态。

3. 可选:使用顶点缓冲区对象(VBO)

把顶点数据放到GPU内存的VBO中,减少CPU到GPU的数据传输开销,进一步提升批量绘制的性能。

代码示例思路(修改你的Print逻辑)

// 类内或全局维护一个动态顶点缓冲区,存储所有要渲染的字符顶点(格式:x, y, u, v)
std::vector<float> m_vertexBuffer;

void Font::Print(const char* Text, int x, int y) {
    int sLen = (int)strnlen(Text, BFG_MAXSTRING);
    int curX = x;
    int curY = y;

    for (int loop = 0; loop < sLen; loop++) {
        char c = Text[loop];
        int col = c % 16;
        int row = c / 16;

        // 计算当前字符的UV坐标
        float u = col / 16.0f;
        float v = row / 16.0f;
        float u1 = (col + 1) / 16.0f;
        float v1 = (row + 1) / 16.0f;

        // 将当前字符的两个三角形(6个顶点)添加到缓冲区
        // 顶点顺序:左下、右下、左上、右下、左上、右上
        m_vertexBuffer.push_back(curX);     m_vertexBuffer.push_back(curY);     m_vertexBuffer.push_back(u);  m_vertexBuffer.push_back(v);
        m_vertexBuffer.push_back(curX+16); m_vertexBuffer.push_back(curY);     m_vertexBuffer.push_back(u1); m_vertexBuffer.push_back(v);
        m_vertexBuffer.push_back(curX);     m_vertexBuffer.push_back(curY+16); m_vertexBuffer.push_back(u);  m_vertexBuffer.push_back(v1);
        m_vertexBuffer.push_back(curX+16); m_vertexBuffer.push_back(curY+16); m_vertexBuffer.push_back(u1); m_vertexBuffer.push_back(v1);
        m_vertexBuffer.push_back(curX);     m_vertexBuffer.push_back(curY+16); m_vertexBuffer.push_back(u);  m_vertexBuffer.push_back(v1);
        m_vertexBuffer.push_back(curX+16); m_vertexBuffer.push_back(curY);     m_vertexBuffer.push_back(u1); m_vertexBuffer.push_back(v);

        curX += 16; // 按字符宽度偏移X坐标
    }
}

// 新增:统一渲染所有文本的函数
void Font::RenderAllText() {
    if (m_vertexBuffer.empty()) return;

    // 一次性设置所有状态
    glBindTexture(GL_TEXTURE_2D, m_fontTexture);
    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);

    // 指定顶点和UV数据的格式与位置
    glVertexPointer(2, GL_FLOAT, 4*sizeof(float), m_vertexBuffer.data());
    glTexCoordPointer(2, GL_FLOAT, 4*sizeof(float), m_vertexBuffer.data() + 2);

    // 一次绘制所有字符
    glDrawArrays(GL_TRIANGLES, 0, m_vertexBuffer.size() / 4);

    // 统一恢复状态
    glDisableClientState(GL_TEXTURE_COORD_ARRAY);
    glDisableClientState(GL_VERTEX_ARRAY);
    glBindTexture(GL_TEXTURE_2D, 0);

    // 清空缓冲区,准备下一帧
    m_vertexBuffer.clear();
}

总结

glDrawArrays本身是高效的绘制接口,但它的优势是处理大批量、连续的顶点数据。你之前的用法相当于用重载卡车拉快递,每次只拉一个包裹,完全浪费了它的运力——改成批量拉货后,帧率会直接回升到正常水平。

内容的提问来源于stack exchange,提问作者user9589156

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 07:49:01