1. 程式人生 > 其它 >OpenGL ES —— PBO 使用

OpenGL ES —— PBO 使用

技術標籤:OpenGL ES 學習OpenGLPBOglReadPixels

文章目錄

OpenGL ES —— PBO 使用

簡介

PBO(Pixel Buffer Object),畫素緩衝區物件,它是視訊記憶體中的一塊記憶體。PBO 類似於 VAO、VBO,也是再 GPU 中開闢記憶體,只是 VAO、VBO 是用於儲存頂點資料,而 PBO 則用於儲存影象資料。PBO 使用了 DMA(Direct Memory Access,直接儲存器訪問) 技術,使得上傳和獲取渲染資料更快。

與 PBO 繫結的 Target 有兩種:

  • GL_PIXEL_PACK_BUFFER
  • GL_PIXEL_UNPACK_BUFFER

這兩種 Target 使用場景如下:

PBO Target

OpenGL PBO

從上圖可以看出,PBO 可以用於從 FrameBuffer 中讀取紋理資料,也可以利用 PBO 將影象資料設定給為例物件。若使用 glReadPixels() 對應從 FrameBuffer 將資料讀取到 PBO 中,使用 glGetTexImage() 函式對應從 Texture Object 中讀取紋理畫素到 PBO 中,這兩種處理都是“pack”畫素操作,Target 為 GL_PIXEL_PACK_BUFFER

;而使用 glDrawPixels() 函式對應將 PBO 影象資料繪製在 FrameBuffer 上,glTexImage2D()glTexSubImage2D() 函式對應將影象資料上傳到紋理物件,即 Texture Object,這種處理都是 “unpack” 操作,Target 為 GL_PIXEL_UNPACK_BUFFER;

PBO 可以利用 DMA 技術在 GPU 和 CPU 快速傳輸畫素資料,而不佔據 CPU 的執行週期,且是非同步 DMA 傳輸,因此可以用它來實現快速上傳和下載畫素資料。
下圖為一個例項,左側為不使用 PBO 將畫素資料上傳的紋理物件。首先將影象資料載入進 CPU 記憶體,然後使用 glTexImage2D()

函式將畫素資料上傳到 GPU 記憶體,即紋理物件,這個過程都是由 CPU 完成,且 glTexImage2D() 相對耗時,因此 CPU 週期會被佔用一部分。右圖為使用 PBO 上傳畫素資料到紋理物件,首先將 影象資料載入到 PBO 中,然後同樣用 glTexImage2D() 函式將畫素資料上傳到紋理物件,其中只有載入影象資料這一步為 CPU 操作,第二步則是由 GPU 完成,因此不會佔用 CPU 執行週期,即 glTexImage2D() 會立即返回。

在這裡插入圖片描述

不使用 PBO 上傳影象資料到紋理物件 使用 PBO 上傳影象資料到紋理物件

使用

與 VAO、VBO類似,PBO 建立方法:

  • glGenBuffers() 建立 PBO 物件
  • glBindBuffer() 繫結 PBO 物件
  • glBufferData() 拷貝資料到 PBO 物件

glBufferData() 函式的資料引數如果傳 nullptr,則只會分配一個記憶體,最後一個引數指明該 PBO 物件的用處,GL_STREAM_DRAW 是上傳畫素資料,GL_STREAM_READ 則為讀取紋理資料。
PBO 提供了記憶體對映的機制將 GPU 記憶體對映到 CPU 記憶體地址上,主要使用如下兩個函式:

  • void* glMapBuffer(GLenum target, GLenum access)
  • GLboolean glUnmapBuffer(GLenum target)

glMapBuffer() returns the pointer to the buffer object if success. Otherwise it returns NULL. The target parameter is either GL_PIXEL_PACK_BUFFER or GL_PIXEL_UNPACK_BUFFER. The second parameter, access specifies what to do with the mapped buffer; read data from the PBO (GL_READ_ONLY), write data to the PBO (GL_WRITE_ONLY), or both (GL_READ_WRITE).
Note that if GPU is still working with the buffer object, glMapBuffer() will not return until GPU finishes its job with the corresponding buffer object. To avoid this stall(wait), call glBufferData() with NULL pointer right before glMapBuffer(). Then, OpenGL will discard the old buffer, and allocate new memory space for the buffer object.
---- OpenGL Pixel Buffer Object (PBO)

如下為 OpenGL 手冊對 glMapBufferRange() 函式的解釋:

glMapBufferRange maps all or part of the data store of a buffer object into the client’s address space. – http://docs.gl/gl3/glMapBufferRange

拿到 pixelData 這個指標之後就可以用來儲存影象資料。

單 PBO 獲取渲染資料

使用單個 PBO 配合 FBO 來獲取渲染資料的部分程式碼:

glBindFramebuffer(GL_FRAMEBUFFER, mFBO);

... draw 

glBindBuffer(GL_PIXEL_PACK_BUFFER, m_PboArray[0]);
{
    AUTO_TIME_COUNT("PboHelper::draw glReadPixels")
    glReadPixels(0, 0, m_ScreenWidth, m_ScreenHeight, m_Channels == 4 ? GL_RGBA : GL_RGB, GL_UNSIGNED_BYTE, nullptr);
}
void *pixelData = nullptr;
{
    AUTO_TIME_COUNT("PboHelper::draw glMapBufferRange")
    pixelData = glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0, m_BufferSize, GL_MAP_READ_BIT);
}
glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
glBindBuffer(GL_PIXEL_PACK_BUFFER, GL_NONE);
glBindFramebuffer(GL_FRAMEBUFFER, GL_NONE);

經過測試,使用單個 PBO 拿到 1080x2000 的渲染資料耗時約為 9ms(OnePlus KB2000), 相比直接使用 glReadPixels() 函式快了一倍多。

注:由於裝置解析度是 1080x2003,因此對長和寬做了 4 畫素對齊,儲存紋理圖為 1080x2000。之後聽說 16 畫素對齊可以使得效率更高,於是嘗試使用 16 畫素對齊,獲得的紋理為 1072x2000 單 PBO 獲取時間從 9 ms 左右降低為 4.0 ms 左右。

雙 PBO 獲取渲染資料

因為 PBO 使用非同步 DMA 傳輸,即將將影象載入進 CPU 記憶體和 畫素資料上傳到 GPU 記憶體可以並行操作,因此可以使用多個 PBO,從而更好地提高效能。

在這裡插入圖片描述

使用 2 個 PBO 上傳影象資料到紋理物件

在這裡插入圖片描述

使用 2 個 PBO 讀取畫素資料到 PBO

從第二張圖可以看出,使用 2 個 PBO 提高效能的流程為,先繫結 PBO_0,然後使用 glReadPixels() 函式將畫素資料讀取到 PBO_0 中,然後繫結 PBO_1,從 PBO_1 中將畫素資料儲存下來,然後交換 PBO_0 和 PBO_1 的 index,即下次使用 glReadPixels() 函式將畫素資料存入 PBO_1 中,並從 PBO_0 中儲存資料,即錯幀儲存。這樣做的話理想結果是效能可以進一步得到提升,不過儲存下來的第一幀資料是空白的,因為當時 PBO_1 中還沒有資料。

為了使用 PBO,將上述程式碼做一點更新:

glBindFramebuffer(GL_FRAMEBUFFER, mFBO);

... draw 

// 繫結第一個 pbo
glBindBuffer(GL_PIXEL_PACK_BUFFER, m_PboArray[m_FirstPboIndex]);
{
    AUTO_TIME_COUNT("PboHelper::draw glReadPixels")
    glReadPixels(0, 0, m_ScreenWidth, m_ScreenHeight, m_Channels == 4 ? GL_RGBA : GL_RGB, GL_UNSIGNED_BYTE, nullptr);
}
// 繫結第二個 pbo
void *pixelData = nullptr;
glBindBuffer(GL_PIXEL_PACK_BUFFER, m_PboArray[1 - m_FirstPboIndex]);
{
    AUTO_TIME_COUNT("PboHelper::draw glMapBufferRange")
    pixelData = glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0, m_BufferSize, GL_MAP_READ_BIT);
}
glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
glBindBuffer(GL_PIXEL_PACK_BUFFER, GL_NONE);
glBindFramebuffer(GL_FRAMEBUFFER, GL_NONE);
// 更換 pbo index
m_FirstPboIndex = 1 - m_FirstPboIndex;

實測結果:上述機型,獲得1 072x2000 的 RGBA YUV資料耗時約為 1.5 ms(1000次平均資料),比僅用單 PBO 在效能上又有較大提高。

遺留問題

使用 PBO 來獲取渲染資料存在一個問題:畫素對齊,對於一個 1080x2003 的螢幕,在經過 16 畫素對齊之後,儲存下來的渲染資料變為了 1072x2000,和理想中的 size 不一致。如果使用 8 pixels 對齊,雖然可以獲得 1080x2000 的紋理圖,但是效能上提升又無法滿足實時需求。

參考閱讀

OpenGL Pixel Buffer Object (PBO) —— 本片文章將 PBO 的使用闡述的很清楚!