zoukankan      html  css  js  c++  java
  • OpenGL顶点数据传输速度优化

    《OpenGL顶点数据传输速度优化》
    作者: 游蓝海
    文章链接:http://blog.csdn.net/you_lan_hai/article/details/50994121
    转载请注明出处

    前言

    最近在给cocos2d-x v2.x的一个项目做渲染优化,执行渲染批处理(Batch)的时候,发现顶点数据传输速度很慢,实在是颠覆了我的OpenGL认知。
    常规的Batch原理:
    · 将渲染命令加入到一个缓冲区当中
    · 根据需求对渲染命令进行排序
    · 合并所有命令的顶点数据
    · 提交顶点数据
    · 根据不同材质分批次渲染

    传输顶点数据用到的两个API:

    void glBufferData(GLenum target, GLsizeiptr size, const GLvoid * data, GLenum usage);
    void glBufferSubData(GLenum target, GLintptr offset, GLsizeiptr size, const GLvoid * data);

    glBufferData会分配一块新的内存,供程序存放数据。glBufferSubData会使用之前已经分配过的内存来存放数据。

    问题

    我事先创建了一个顶点buffer和索引buffer,索引buffer不用多说,其数据是一次就计算完成的,之后就不用修改了。而顶点数据每帧都会变化,所以需要动态的更新。

    我在创建顶点buffer的时候,使用glBufferData分配足够大的空间。然后每次提交顶点数据的时候,使用glBufferSubData,仅提交实际使用到的数据。理论上来说,这样不会引发额外的内存分配行为,传输效率应该会很高。伪代码如下:

    void init()
    {
        glGenBuffer(...);
        glBindBuffer(...);
        //分配一块很大的空间
        glBufferData(GL_ARRAY_BUFFER, 32 * 1024, NULL, GL_DYNAMIC); 
        ...
    }
    void draw()
    {
        for(int k = 0; k < parts.size(); ++k)
        {
            Part *p = parts[i];
            //仅提交用到的数据。可能只有几百字节,或者几k。
            glBufferSubData(GL_ARRAY_BUFFER, 0, p->vertices.size(), p->vertices.data());
            for(int i = 0; i < p->commands.size(); ++i)
            {
                drawCommand(p->commands[i]);
            }
        }
    }

    实测之后发现,掉帧特别明显,glBufferSubData操作相当耗时。百思不得其解,就查看了cocos2d-x v3.x渲染部分的代码,发现他每次提交顶点数据的时候,都是直接使用glBufferData分配空间,而且他们刻意的注释掉了使用glBufferSubData的代码。这让我更迷惑了,感觉自己的理论彻底被颠覆了。

    原因

    接着就去google上寻找原因,看到有人推荐看一下《OpenGL Insights》渲染效率优化部分的文章,文章全是英文,看的不是完全懂。大致理解如下:

    所有的OpenGL操作,并不会被立即同步到显卡,而是被缓存在本地,等到合适的时机(比如swapBuffer)批量传输到显卡。对于顶点数据,会使用DMA进行异步传输,并不会阻塞CPU。

    那么问题就来了,如果现在顶点buffer的数据还没有传输完成,我们立马又要向顶点buffer写数据了,这会出现什么情况?比如,如下的操作,写数据与渲染交替进行:

    glBufferSubData(GL_ARRAY_BUFFER, 0, bytes, datas);
    glDrawElements(GL_TRIANGLES, n, GL_UNSIGNED_SHORT);
    glBufferSubData(GL_ARRAY_BUFFER, 0, bytes, datas);
    glDrawElements(GL_TRIANGLES, n, GL_UNSIGNED_SHORT);
    ...

    结论是,CPU还是会阻塞,直到DMA把顶点buffer中的数据传输完毕后,才允许我们继续向里面写数据。即便是有DMA异步传输,碰到不良的代码,还是会发生同步等待。

    解决办法

    书中说到,可以使用多个顶点buffer交替进行,或者使用顶点buffer的不同位置,都可以达到避免阻塞的作用。
    当然,还有一种简单方法,我们也可以调用glBufferData,来强制OpenGL在本地分配一块新的内存,供我们存贮新数据。这样DMA会继续传输旧内存地址上的数据,然后再传输新内存地址上的数据,这种方式下CPU也就不用等待DMA了。虽然浪费了额外的内存空间,但是提高了数据传输的吞吐量,还是值得的。
    总结来说,OpenGL的操作是异步的,我们要避免同步操作所引发的阻塞行为。

    更多阅读

    1. 顶点数据传输优化: https://www.opengl.org/wiki/Buffer_Object_Streaming
    2. 《OpenGL Insights》第28章:http://download.csdn.net/download/you_lan_hai/9787675
  • 相关阅读:
    MVC @Html.TextBox 添加属性和样式
    当项目中出现“未能找到与此解决方案关联的源代码管理提供程序。项目将视为不受源代码管理”
    MVC视图中读取ViewBag传递过来的HashTable表数据
    使用NPOI导入导出标准的Excel
    网站文件下载代码
    C# 刷新页面浏览次数(点击量)+1
    sed
    Shell变量的取用、删除、取代与替换
    non-member function cannot have cv-qualifier
    awk学习
  • 原文地址:https://www.cnblogs.com/ygxsk/p/7693974.html
Copyright © 2011-2022 走看看