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