天天看點

像素緩沖區對象(PBO)的異步Read-Back 源碼解析

接這篇文章  OpenGL深入探索——像素緩沖區對象 (PBO)

(附完整工程代碼位址)

原理示意圖如下:

像素緩沖區對象(PBO)的異步Read-Back 源碼解析
關鍵代碼如下:

int main(int argc, char **argv)
{
    initSharedMem();
 
    // register exit callback
    atexit(exitCB);
 
    // init GLUT and GL
    initGLUT(argc, argv);
    initGL();
 
    // get OpenGL info
    glInfo glInfo;
    glInfo.getInfo();
    glInfo.printSelf();
 
#ifdef _WIN32
    // 檢查視訊顯示卡是否支援 PBO
    if (glInfo.isExtensionSupported("GL_ARB_pixel_buffer_object"))
    {
        // get pointers to GL functions
        glGenBuffersARB = (PFNGLGENBUFFERSARBPROC)wglGetProcAddress("glGenBuffersARB");
        glBindBufferARB = (PFNGLBINDBUFFERARBPROC)wglGetProcAddress("glBindBufferARB");
        glBufferDataARB = (PFNGLBUFFERDATAARBPROC)wglGetProcAddress("glBufferDataARB");
        glBufferSubDataARB = (PFNGLBUFFERSUBDATAARBPROC)wglGetProcAddress("glBufferSubDataARB");
        glDeleteBuffersARB = (PFNGLDELETEBUFFERSARBPROC)wglGetProcAddress("glDeleteBuffersARB");
        glGetBufferParameterivARB = (PFNGLGETBUFFERPARAMETERIVARBPROC)wglGetProcAddress("glGetBufferParameterivARB");
        glMapBufferARB = (PFNGLMAPBUFFERARBPROC)wglGetProcAddress("glMapBufferARB");
        glUnmapBufferARB = (PFNGLUNMAPBUFFERARBPROC)wglGetProcAddress("glUnmapBufferARB");
 
        // check once again PBO extension
        if (glGenBuffersARB && glBindBufferARB && glBufferDataARB && glBufferSubDataARB &&
                glMapBufferARB && glUnmapBufferARB && glDeleteBuffersARB && glGetBufferParameterivARB)
        {
            pboSupported = pboUsed = true;
            std::cout << "Video card supports GL_ARB_pixel_buffer_object." << std::endl;
        }
        else
        {
            pboSupported = pboUsed = false;
            std::cout << "Video card does NOT support GL_ARB_pixel_buffer_object." << std::endl;
        }
    }
 
    // check EXT_swap_control is supported
    if (glInfo.isExtensionSupported("WGL_EXT_swap_control"))
    {
        // get pointers to WGL functions
        wglSwapIntervalEXT = (PFNWGLSWAPINTERVALEXTPROC)wglGetProcAddress("wglSwapIntervalEXT");
        wglGetSwapIntervalEXT = (PFNWGLGETSWAPINTERVALEXTPROC)wglGetProcAddress("wglGetSwapIntervalEXT");
        if (wglSwapIntervalEXT && wglGetSwapIntervalEXT)
        {
            // disable v-sync
            wglSwapIntervalEXT(0);
            std::cout << "Video card supports WGL_EXT_swap_control." << std::endl;
        }
    }
 
#else // for linux, do not need to get function pointers, it is up-to-date
    if (glInfo.isExtensionSupported("GL_ARB_pixel_buffer_object"))
    {
        pboSupported = pboUsed = true;
        std::cout << "Video card supports GL_ARB_pixel_buffer_object." << std::endl;
    }
    else
    {
        pboSupported = pboUsed = false;
        std::cout << "Video card does NOT support GL_ARB_pixel_buffer_object." << std::endl;
    }
#endif
 
    if (pboSupported)
    {
        // create 2 pixel buffer objects, you need to delete them when program exits.
        //(建立兩個 PBO)
        // glBufferDataARB with NULL pointer reserves only memory space.
        glGenBuffersARB(PBO_COUNT, pboIds);
        glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, pboIds[0]);
        glBufferDataARB(GL_PIXEL_PACK_BUFFER_ARB, DATA_SIZE, 0, GL_STREAM_READ_ARB);
        glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, pboIds[1]);
        glBufferDataARB(GL_PIXEL_PACK_BUFFER_ARB, DATA_SIZE, 0, GL_STREAM_READ_ARB);
 
        glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, 0);
    }
 
    // start timer, the elapsed time will be used for updateVertices()
    timer.start();
 
    // the last GLUT call (LOOP)
    // window will be shown and display callback is triggered by events
    // NOTE: this call never return main().
    glutMainLoop(); /* Start GLUT event-processing loop */
 
    return 0;
}      

省略掉中間其他一些無關代碼,剩下關鍵方法:

void displayCB()
{
    static int shift = 0;
    static int index = 0;
    int nextIndex = 0;                  // pbo index used for next frame
 
    // brightness shift amount
    shift = ++shift % 200;
 
    // increment current index first then get the next index(增加目前的index,再獲得 nextIndex)
    // "index" is used to read pixels from a framebuffer to a PBO(從 FB 中讀取像素到 index 指定的 PBO 中)
    // "nextIndex" is used to process pixels in the other PBO(nextIndex 指定 PBO 中待處理的像素[先前從 FB 中讀取])
    index = (index + 1) % 2;    // 兩個 PBO 交替
    nextIndex = (index + 1) % 2;
 
    // set the framebuffer to read(設定目前讀取的 FB)
    glReadBuffer(GL_FRONT);
 
    if (pboUsed) // with PBO
    {
        // read framebuffer ///
        t1.start();  // 啟動計時器,計算讀取 FB 到 PBO 的時間
 
        // copy pixels from framebuffer to PBO (從 FB 中拷貝像素到 index指定的 PBO 中)
        // Use offset instead of ponter.(注意glReadPixels最後一個參數不是指針,而是偏移量)
        // OpenGL should perform asynch DMA transfer, so glReadPixels() will return immediately.
        //(OpenGL 将執行一個異步的DMA[Direcct Memory Access],是以 glReadPixels方法會立刻傳回,不會阻塞CPU時間)
        glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, pboIds[index]);
        glReadPixels(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, PIXEL_FORMAT, GL_UNSIGNED_BYTE, 0);
 
        // measure the time reading framebuffer
        t1.stop();
        readTime = t1.getElapsedTimeInMilliSec();
        ///
 
        // process pixel data /
        t1.start(); // 啟動計時器,計算處理 PBO中像素的時間
 
        // map the PBO that contain framebuffer pixels before processing it
        //(映射存儲着先前 FB 像素的 PBO ,便于處理像素)
        glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, pboIds[nextIndex]);
        // 後續的 add() 并沒有改變 PBO 中的像素值,計算的結果儲存在 colorBuffer 中,是以标記為隻讀
        GLubyte *src = (GLubyte *)glMapBufferARB(GL_PIXEL_PACK_BUFFER_ARB, GL_READ_ONLY_ARB);
        if (src)
        {
            // change brightness
            add(src, SCREEN_WIDTH, SCREEN_HEIGHT, shift, colorBuffer);
            glUnmapBufferARB(GL_PIXEL_PACK_BUFFER_ARB);     // release pointer to the mapped buffer
        }
 
        // measure the time processing the pixels of PBO
        t1.stop();
        processTime = t1.getElapsedTimeInMilliSec();
        ///
 
        glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, 0);
    }
    else        // without PBO
    {
        // read framebuffer ///
        t1.start();
        // 不使用 PBO 的情況下,最後一個參數就是儲存讀取資料的指針
        glReadPixels(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, PIXEL_FORMAT, GL_UNSIGNED_BYTE, colorBuffer);
 
        // measure the time reading framebuffer
        t1.stop();
        readTime = t1.getElapsedTimeInMilliSec();
        ///
 
        // covert to greyscale 
        t1.start();
 
        // change brightness
        add(colorBuffer, SCREEN_WIDTH, SCREEN_HEIGHT, shift, colorBuffer);
 
        // measure the time reading framebuffer
        t1.stop();
        processTime = t1.getElapsedTimeInMilliSec();
        ///
    }
 
    // render to the framebuffer //
    glDrawBuffer(GL_BACK);  // 設定繪制在後緩沖區
    toPerspective(); // set to perspective on the left side of the window(目前視窗左側的投影矩陣)
 
    // clear buffer
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
 
    // tramsform camera
    glTranslatef(0, 0, -cameraDistance);
    glRotatef(cameraAngleX, 1, 0, 0);   // pitch
    glRotatef(cameraAngleY, 0, 1, 0);   // heading
 
    // draw a cube(在左側繪制普通的立方體)
    glPushMatrix();
    draw();
    glPopMatrix();
 
    // draw the read color buffer to the right side of the window
    //(在右側繪制之前處理的 colorBuffer)
    toOrtho();      // set to orthographic on the right side of the window(視窗右側的正交矩陣[想象為把左側的圖處理一下直接貼上去])
    glRasterPos2i(0, 0);    // 設定字型光栅的位置
    glDrawPixels(SCREEN_WIDTH, SCREEN_HEIGHT, PIXEL_FORMAT, GL_UNSIGNED_BYTE, colorBuffer);
 
    // draw info messages
    showInfo();
    printTransferRate();
 
    glutSwapBuffers();// 切換前後緩沖區
}      

程式的執行對比情況:

像素緩沖區對象(PBO)的異步Read-Back 源碼解析

可見通過 PBO的異步read-back技術,FPS 還是有明顯提高的(我的顯示卡是 GeForce GTX 970,CPU 是 i7-6700 HQ,主頻為2.6GHz)。

繼續閱讀