接这篇文章 OpenGL深入探索——像素缓冲区对象 (PBO)(附完整工程代码地址)
原理示意图如下:
关键代码如下:
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技术,FPS 还是有明显提高的(我的显卡是 GeForce GTX 970,CPU 是 i7-6700 HQ,主频为2.6GHz)。