天天看点

Android OpenGL ES 3.0 Pixel Buffer Object使用

关于PBO,找了很多资料,然而google了很久,大部分的PBO资料都和这个类似https://blog.csdn.net/panda1234lee/article/details/51546502 。在上传texture的过程中,我分别试了1、2、3个PBO来进行上传,然而效率并没有增加,反而下低了,有点无法理解。然而在读取数据时候,使用两个PBO是可以提高效率,所以总的来说还是有一定研究价值的。

使用两个PBO减少glReadPixels的时间

我这里模仿了一下不断采集纹理,我每次用同一张图片去完整更新纹理,就好像每次都采集了一张图片。然后使用FBO进行中间处理,读取数据后复制出来到一个数组。总的来说就是模拟摄像头采集然后编码的流程吧。主要是为了避免过多的逻辑尽量减少额外的功能点。

首先先生成两个PBO,并且分配空间,这里我还生成了三个上传纹理的PBO,但是没有使用了,因为上传纹理没办法通过PBO提高效率,有知道的大佬可以告诉一下原因。

if (init) {
        init= false;
        uploadPobs = new GLuint[];
        downloadPbos = new GLuint[];
        glGenBuffers(, uploadPobs);
        glGenBuffers(, downloadPbos);
        int align = ;
        int width = curPicWidth;
        int height = curPicHeight;
        mPboSize = ((width *  + (align - )) & ~(align - )) * height;
        glBindBuffer(GL_PIXEL_UNPACK_BUFFER, uploadPobs[]);
        glBufferData(GL_PIXEL_UNPACK_BUFFER, mPboSize, , GL_STREAM_DRAW);
        glBindBuffer(GL_PIXEL_UNPACK_BUFFER, uploadPobs[]);
        glBufferData(GL_PIXEL_UNPACK_BUFFER, mPboSize, , GL_STREAM_DRAW);
        glBindBuffer(GL_PIXEL_UNPACK_BUFFER, uploadPobs[]);
        glBufferData(GL_PIXEL_UNPACK_BUFFER, mPboSize, , GL_STREAM_DRAW);
        glBindBuffer(GL_PIXEL_UNPACK_BUFFER, );
        glBindBuffer(GL_PIXEL_PACK_BUFFER, downloadPbos[]);
        glBufferData(GL_PIXEL_PACK_BUFFER, mPboSize, , GL_STATIC_READ);
        glBindBuffer(GL_PIXEL_PACK_BUFFER, downloadPbos[]);
        glBufferData(GL_PIXEL_PACK_BUFFER, mPboSize, , GL_STATIC_READ);
        glBindBuffer(GL_PIXEL_PACK_BUFFER, );
    }
           

接下来就是正常的OpenGL 的纹理绘制,我们将坐标绑定到VAO,然后创建FBO,并且绘制到FBO的纹理上。然后读取数据。最后我们再切换回屏幕,在绘制到屏幕上,这次看到的图形就是上下颠倒的了。之前我也试过不使用FBO直接每次更新单个纹理的整体数据来进行绘制,再使用PBO进行读取,但是无法提高效率。应该更新纹理的时候,发现异步DMA没有完成会等待,所以会导致纹理更新时间变长。所以还是需要使用FBO。

void PboRender::render() {
    glClearColor(f, f, f, f);
    glDisable(GL_DITHER);
    glEnable(GL_CULL_FACE);
    glEnable(GL_DEPTH_TEST);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glViewport(, , curPicWidth, curPicHeight);
    glUseProgram(program);
//    resetTexture();
    glBindFramebuffer(GL_FRAMEBUFFER, frame);
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, texture);
    glUniform1i(textureLocation, );
    glBindVertexArray(VAO[]);
    glDrawArrays(GL_TRIANGLE_STRIP, , );
    readPixels();
    glBindFramebuffer(GL_FRAMEBUFFER, );
    glViewport(, , _backingWidth, _backingHeight);
    glClearColor(f, f, f, f);
    glBindTexture(GL_TEXTURE_2D, textureFrame);
    glBindVertexArray(VAO[]);
    glDrawArrays(GL_TRIANGLE_STRIP, , );

}
           

接着下面就是读取数据,同样模拟一下,功能就是读取数据之后复制到另一个数组,在这个过程中PBO真的是个大坑。同样的代码在不同的设备上性能差距太大了。提一下我发现的点吧。

  • 首先看glReadPixels(),这个函数在使用PBO的时候,网上的资料都说会立刻返回,然后我实测依然会有等待时间。读取的byte数(也就是宽*高*4)不是128的倍数的时候,使用PBO和不使用几乎没有提升性能。而如果是128的倍数,这个时间几乎减少了5倍左右。应该是64位cpu的优化吧,也就是说如果我们录屏的时候分辨率设置设置得当,在使用软编的时候,是可以得到一些性能提升的。但是如果分辨率设置不当,考虑到后面需要复制数据,综合起来性能甚至会降低。(对于可能不是64位cpu的手机没有测试过)。
  • 接着就是复制数据时候的坑了,直接是memcpy()进行复制的时候,在不使用PBO的时候速度很快,但是在使用PBO的时候,我们再glMapBufferRange拿到对应数据的指针后,使用memcpy()会复制得特别慢,官方文档对这个API的描述也说了,可能会拿到不可缓存的内存区域,所以在读取的时候,性能会无法得到保证。再加上上一条说的时间,综合起来甚至不如不适用PBO,真的略坑。后来反复查找资料,使用ARM架构的NEON优化来拷贝数据,针对这种不可缓存的内存,在c++中使用汇编。测试下来,性能提升了很多。需要在build.gradle中开启NEON。

    在defaultConfig{}下添加。

externalNativeBuild {
            cmake {
                cppFlags "-std=c++11 -frtti -fexceptions"
                arguments '-DANDROID_ARM_NEON=TRUE'
            }
        }
           

复制数据的方法

void my_copy(volatile unsigned char *dst, volatile unsigned char *src, int sz)
{
    if (sz & ) {
        sz = (sz & -) + ;
    }
    asm volatile (
    "NEONCopyPLD: \n"
            " VLDM %[src]!,{d0-d7} \n"
            " VSTM %[dst]!,{d0-d7} \n"
            " SUBS %[sz],%[sz],#0x40 \n"
            " BGT NEONCopyPLD \n"
    : [dst]"+r"(dst), [src]"+r"(src), [sz]"+r"(sz) : : "d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "cc", "memory");
}
           

读取数据方法

void PboRender::readPixels() {

    int size = mPboSize;
    char path[];
    sprintf(path, "/mnt/sdcard/pixel/readPixel%d.rgba", picCount);
//    picCount--;
    if (picCount <= ) {
        return;
    }
    if (!cachePixel) {
        cachePixel = new byte[size];
        memset(cachePixel, , size);
    }
//    if (access("/mnt/sdcard/pixel", 0)) {
//        mkdir("/mnt/sdcard/pixel", S_IRUSR | S_IWUSR | S_IXUSR | S_IRWXG | S_IRWXO);
//    }
//    FILE *file = fopen(path, "wb");
    glBindBuffer(GL_PIXEL_PACK_BUFFER, );

    long long curTime = getCurrentTime();
    if (downloadPboType == NONE) {
        long long cc = getCurrentTime();
        byte *pixel = new byte[size];
        glReadPixels(, , curPicWidth, curPicHeight, GL_RGBA, GL_UNSIGNED_BYTE, pixel);
        LOGE("glReadPixels Time %lld", getCurrentTime() - cc);
        cc = getCurrentTime();
//        fwrite(pixel, size, 1, file);
        memcpy(cachePixel, pixel, size);
        LOGE("内存复制耗时 %lld", getCurrentTime() - cc);
        delete[] pixel;
    } else if (downloadPboType == ONE) {


    } else if (downloadPboType == TWO) {
        index = (index + ) % ;
        nextIndex = (index + ) % ;
        glBindBuffer(GL_PIXEL_PACK_BUFFER, downloadPbos[index]);
        long long cc = getCurrentTime();
        glReadPixels(, , curPicWidth, curPicHeight, GL_RGBA, GL_UNSIGNED_BYTE, );
        LOGE("glReadPixels Time %lld", getCurrentTime() - cc);

        if (readPixInit) {
            readPixInit = false;
            glBindBuffer(GL_PIXEL_PACK_BUFFER, );
            return;
        }
        glBindBuffer(GL_PIXEL_PACK_BUFFER, downloadPbos[nextIndex]);
        GLubyte *ptr = static_cast<GLubyte *>(glMapBufferRange(GL_PIXEL_PACK_BUFFER, ,
                                                               mPboSize,
                                                               GL_MAP_READ_BIT));
        glUnmapBuffer(GL_PIXEL_PACK_BUFFER); // release pointer to mapping buffer
        cc = getCurrentTime();
        if (ptr) {
            my_copy(cachePixel,ptr,size);


        }
        glBindBuffer(GL_PIXEL_PACK_BUFFER, );

//            fwrite(ptr, size, 1, file);
        LOGE("内存复制耗时 %lld", getCurrentTime() - cc);

    }
//    fclose(file);

    LOGE("完成耗时%lld", getCurrentTime() - curTime);
}
           

好了,文章就到这里了,不是特别长,代码逻辑也比较简单,但是在性能测试上真的花了不少时间,PBO确实可以在一定条件下大幅提升性能,如果项目需要还是可以尝试一下,当然我们还可以在GPU内把数据转成YUV422,这样读取的时候时间还可以减少一半。总的来说性能应该提升比较大的,就是PBO需要OpenGL ES 3.0,在手机版本上需要适配。

源码