天天看點

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,在手機版本上需要适配。

源碼