天天看點

NDK OpenGL ES 3.0 開發(二十二):PBOPBO 是什麼為什麼要用 PBO怎麼用 PBO參考文章

作者:位元組流動

來源:

https://blog.csdn.net/Kennethdroid/article/details/103931627

PBO 是什麼

OpenGL PBO(Pixel Buffer Object),被稱為像素緩沖區對象,主要被用于異步像素傳輸操作。PBO 僅用于執行像素傳輸,不連接配接到紋理,且與 FBO (幀緩沖區對象)無關。

OpenGL PBO(像素緩沖區對象) 類似于 VBO(頂點緩沖區對象),PBO 開辟的也是 GPU 緩存,而存儲的是圖像資料。

NDK OpenGL ES 3.0 開發(二十二):PBOPBO 是什麼為什麼要用 PBO怎麼用 PBO參考文章

與 PBO 綁定相關的 Target 标簽有 2 個:

GL_PIXEL_UNPACK_BUFFER

GL_PIXEL_PACK_BUFFER

其中将 PBO 綁定為

GL_PIXEL_UNPACK_BUFFER

時,

glTexImage2D()

glTexSubImage2D()

表示從 PBO 中解包(unpack)像素資料并複制到幀緩沖區 。

将 PBO 綁定為

GL_PIXEL_PACK_BUFFER

glReadPixels()

表示從幀緩沖區中讀取像素資料并打包進(pack) PBO 。

為什麼要用 PBO

在 OpenGL 開發中,特别是在低端平台上處理高分辨率的圖像時,圖像資料在記憶體和顯存之前拷貝往往會造成性能瓶頸,而利用 PBO 可以在一定程度上解決這個問題。

使用 PBO 可以在 GPU 的緩存間快速傳遞像素資料,不影響 CPU 時鐘周期,除此之外,PBO 還支援異步傳輸。

NDK OpenGL ES 3.0 開發(二十二):PBOPBO 是什麼為什麼要用 PBO怎麼用 PBO參考文章

上圖從檔案中加載紋理,圖像資料首先被加載到 CPU 記憶體中,然後通過

glTexImage2D

函數将圖像資料從 CPU 記憶體複制到 OpenGL 紋理對象中 (GPU 記憶體),兩次資料傳輸(加載和複制)完全由 CPU 執行和控制。

NDK OpenGL ES 3.0 開發(二十二):PBOPBO 是什麼為什麼要用 PBO怎麼用 PBO參考文章

如上圖所示,檔案中的圖像資料可以直接加載到 PBO 中,這個操作是由 CPU 控制。我們可以通過

glMapBufferRange

擷取 PBO 對應 GPU 緩沖區的記憶體位址。

将圖像資料加載到 PBO 後,再将圖像資料從 PBO 傳輸到紋理對象中完全是由 GPU 控制,不會占用 CPU 時鐘周期。是以,綁定 PBO 後,執行

glTexImage2D

(将圖像資料從 PBO 傳輸到紋理對象) 操作,CPU 無需等待,可以立即傳回。

通過對比這兩種(将圖像資料傳送到紋理對象中)方式,可以看出,利用 PBO 傳輸圖像資料,省掉了一步 CPU 耗時操作(将圖像資料從 CPU 記憶體複制到 紋理對象中)。

怎麼用 PBO

int imgByteSize = m_Image.width * m_Image.height * 4;//RGBA
glGenBuffers(1, &uploadPboId);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboId);
glBufferData(GL_PIXEL_UNPACK_BUFFER, imgByteSize, 0, GL_STREAM_DRAW);
glGenBuffers(1, &downloadPboId);
glBindBuffer(GL_PIXEL_PACK_BUFFER, downloadPboId);
glBufferData(GL_PIXEL_PACK_BUFFER, imgByteSize, 0, GL_STREAM_DRAW);      

PBO 的建立和初始化類似于 VBO ,以上示例表示建立 PBO ,并申請大小為 imgByteSize 的緩沖區。綁定為

GL_PIXEL_UNPACK_BUFFER

表示該 PBO 用于将像素資料從程式傳送到 OpenGL 中;綁定為

GL_PIXEL_PACK_BUFFER

表示該 PBO 用于從 OpenGL 中讀回像素資料。

從上面内容我們知道,加載圖像資料到紋理對象時,CPU 負責将圖像資料拷貝到 PBO ,而 GPU 負責将圖像資料從 PBO 傳送到紋理對象。是以,當我們使用多個 PBO 時,通過交換 PBO 的方式進行拷貝和傳送,可以實作這兩步操作同時進行。

使用兩個 PBO 加載圖像資料到紋理對象

NDK OpenGL ES 3.0 開發(二十二):PBOPBO 是什麼為什麼要用 PBO怎麼用 PBO參考文章

如圖示,利用 2 個 PBO 加載圖像資料到紋理對象,使用

glTexSubImage2D

通知 GPU 将圖像資料從 PBO1 傳送到紋理對象,同時 CPU 将新的圖像資料複制到 PBO2 中。

int dataSize = m_RenderImage.width * m_RenderImage.height * 4;
//使用 `glTexSubImage2D` 将圖像資料從 PBO1 傳送到紋理對象
int index = m_FrameIndex % 2;
int nextIndex = (index + 1) % 2;
BEGIN_TIME("PBOSample::UploadPixels Copy Pixels from PBO to Textrure Obj")
glBindTexture(GL_TEXTURE_2D, m_ImageTextureId);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, m_UploadPboIds[index]);
//調用 glTexSubImage2D 後立即傳回,不影響 CPU 時鐘周期
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_RenderImage.width, m_RenderImage.height, GL_RGBA, GL_UNSIGNED_BYTE, 0);
END_TIME("PBOSample::UploadPixels Copy Pixels from PBO to Textrure Obj")
//更新圖像資料,複制到 PBO 中
BEGIN_TIME("PBOSample::UploadPixels Update Image data")
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, m_UploadPboIds[nextIndex]);
glBufferData(GL_PIXEL_UNPACK_BUFFER, dataSize, nullptr, GL_STREAM_DRAW);
GLubyte *bufPtr = (GLubyte *) glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0,
                                               dataSize,
                                               GL_MAP_WRITE_BIT |
                                               GL_MAP_INVALIDATE_BUFFER_BIT);
LOGCATE("PBOSample::UploadPixels bufPtr=%p",bufPtr);
if(bufPtr)
{
    memcpy(bufPtr, m_RenderImage.ppPlane[0], static_cast<size_t>(dataSize));
    //update image data
    int randomRow = rand() % (m_RenderImage.height - 5);
    memset(bufPtr + randomRow * m_RenderImage.width * 4, 188,
    static_cast<size_t>(m_RenderImage.width * 4 * 5));
    glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
}
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
END_TIME("PBOSample::UploadPixels Update Image data")      

我們對比下使用 2 個 PBO 和不使用 PBO 加載圖像資料到紋理對象的耗時差别:

使用 2 個 PBO 加載圖像資料的耗時

NDK OpenGL ES 3.0 開發(二十二):PBOPBO 是什麼為什麼要用 PBO怎麼用 PBO參考文章

不使用 PBO 加載圖像資料的耗時

NDK OpenGL ES 3.0 開發(二十二):PBOPBO 是什麼為什麼要用 PBO怎麼用 PBO參考文章

使用兩個 PBO 從幀緩沖區讀回圖像資料

NDK OpenGL ES 3.0 開發(二十二):PBOPBO 是什麼為什麼要用 PBO怎麼用 PBO參考文章

如上圖所示,利用 2 個 PBO 從幀緩沖區讀回圖像資料,使用

glReadPixels

通知 GPU 将圖像資料從幀緩沖區讀回到 PBO1 中,同時 CPU 可以直接處理 PBO2 中的圖像資料。

//交換 PBO
int index = m_FrameIndex % 2;
int nextIndex = (index + 1) % 2;
//将圖像資料從幀緩沖區讀回到 PBO 中
BEGIN_TIME("DownloadPixels glReadPixels with PBO")
glBindBuffer(GL_PIXEL_PACK_BUFFER, m_DownloadPboIds[index]);
glReadPixels(0, 0, m_RenderImage.width, m_RenderImage.height, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
END_TIME("DownloadPixels glReadPixels with PBO")
// glMapBufferRange 擷取 PBO 緩沖區指針
BEGIN_TIME("DownloadPixels PBO glMapBufferRange")
glBindBuffer(GL_PIXEL_PACK_BUFFER, m_DownloadPboIds[nextIndex]);
GLubyte *bufPtr = static_cast<GLubyte *>(glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0,
                                                       dataSize,
                                                       GL_MAP_READ_BIT));
if (bufPtr) {
    nativeImage.ppPlane[0] = bufPtr;
    //NativeImageUtil::DumpNativeImage(&nativeImage, "/sdcard/DCIM", "PBO");
    glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
}
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
END_TIME("DownloadPixels PBO glMapBufferRange")
      

我們對比下從幀緩沖區讀回圖像資料,使用 PBO 和不使用 PBO 兩種情況的耗時差别:

使用 PBO 從幀緩沖區讀回圖像資料耗時

NDK OpenGL ES 3.0 開發(二十二):PBOPBO 是什麼為什麼要用 PBO怎麼用 PBO參考文章

glMapBufferRange 操作的耗時

NDK OpenGL ES 3.0 開發(二十二):PBOPBO 是什麼為什麼要用 PBO怎麼用 PBO參考文章

不使用 PBO 從幀緩沖區讀回圖像資料耗時

NDK OpenGL ES 3.0 開發(二十二):PBOPBO 是什麼為什麼要用 PBO怎麼用 PBO參考文章

對比性能資料可以看出,使用 PBO 明顯優于傳統的

glReadPixels

方式。

實作代碼路徑:

NDK_OpenGLES_3_0

參考文章

http://www.songho.ca/opengl/gl_pbo.html
「視訊雲技術」你最值得關注的音視訊技術公衆号,每周推送來自阿裡雲一線的實踐技術文章,在這裡與音視訊領域一流工程師交流切磋。
NDK OpenGL ES 3.0 開發(二十二):PBOPBO 是什麼為什麼要用 PBO怎麼用 PBO參考文章