为何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
相关产品推荐
相关产品推荐

