CUDA與OpenGL交互開發

最近在學習OpenGL,過程中需要使用CUDA進行並行計算。因此,需要解決OpenGL與CUDA的交互問題。學習記錄如下:

  • Step1. 共享數據區
想到交互,不難想到通信,數據共享等詞語。這裏使用的是共享數據的方式來完成OpenGL與CUDA的交互。而OpenGL與CUDA都有着自己獨特的類型定義。因此,對於共享的數據區,我們需要給它起兩個不同的名字,分別爲OpenGL以及CUDA服務

OpenGL:
  1. GLuint bufferObj; 
  1. GLuint bufferObj;  

CUDA:
[java] view plaincopy
  1. cudaGraphicsResource *resource; 
[java] view plaincopy
  1. cudaGraphicsResource *resource;  

  • Step2.將顯卡設備與OpenGL關聯(已廢除)
注:在CUDA5.0版本以及以後的版本,不需要此操作。參考NVIDIA官方文檔如下:
cudaError_t cudaGLSetGLDevice ( int device )
DeprecatedThis function is deprecated as of CUDA 5.0.This function is deprecated and should no longer be used. It is no longer necessary to associate a CUDA device with an OpenGL context in order to achieve maximum interoperability performance.

具體的設置代碼爲:
  1. cudaDeviceProp prop; 
  2. int dev; 
  3. memset(&prop, 0, sizeof(cudaDeviceProp)); 
  4. prop.major = 1; 
  5. prop.minor = 0; 
  6. cudaChooseDevice(&dev, &prop); 
  7. cudaGLSetGLDevice(dev); 
  1. cudaDeviceProp prop;  
  2. int dev;  
  3. memset(&prop, 0, sizeof(cudaDeviceProp));  
  4. prop.major = 1;  
  5. prop.minor = 0;  
  6. cudaChooseDevice(&dev, &prop);  
  7. cudaGLSetGLDevice(dev);  

  • Step3. 初始化OpenGL
  1. #define DIM 512 
  2. glutInit(argc, argv); 
  3. glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA); 
  4. glutInitWindowSize(DIM, DIM); 
  5. glutCreateWindow("bitmap"); 
  6. glewInit(); 
  1. #define DIM 512  
  2. glutInit(argc, argv);  
  3. glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);  
  4. glutInitWindowSize(DIM, DIM);  
  5. glutCreateWindow("bitmap");  
  6. glewInit();  

這裏需要注意的是:
  1. 需要使用opengl擴展庫:glew32.dll。若您沒有glew32擴展庫,點此下載  (其安裝方式與glut,freeglut等相同)
  2. (重要)需要在opengl初始化代碼最後加上:glewInit(),否則會在後面執行到glGenBuffers報運行時錯誤:0xC0000005: Access violation.
  3. 使用glew庫需要: #include "gl/glew.h",且其聲明的位置儘量放在代碼最頂端,否則編譯報錯。
  4. 具體示例代碼,點此下載

——————————————————   華麗的分割線 ————————————————————
到此爲止,基本的準備工作就完成了。下面開始實際的工作。
共享數據緩衝區是在CUDA C核函數 和 OpenGL渲染操作之間 實現互操作的關鍵部分。爲了實現兩者之間的數據傳遞,我們首先需要創建一個緩衝區。

  • Step4. 使用OpenGL API創建數據緩衝區
  1. const GLubyte* a; 
  2. a = glGetString(GL_EXTENSIONS); 
  3.  
  4. glGenBuffers(1, &bufferObj);//生成一個緩衝區句柄 
  5. glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, bufferObj);//將句柄綁定到像素緩衝區(即緩衝區存放的數據類型爲:PBO) 
  6. glBufferData(GL_PIXEL_UNPACK_BUFFER_ARB, DIM*DIM*4, NULL, GL_DYNAMIC_DRAW_ARB);//申請內存空間並設置相關屬性以及初始值 
  1. const GLubyte* a;  
  2. a = glGetString(GL_EXTENSIONS);  
  3.   
  4. glGenBuffers(1, &bufferObj);//生成一個緩衝區句柄  
  5. glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, bufferObj);//將句柄綁定到像素緩衝區(即緩衝區存放的數據類型爲:PBO)  
  6. glBufferData(GL_PIXEL_UNPACK_BUFFER_ARB, DIM*DIM*4, NULL, GL_DYNAMIC_DRAW_ARB);//申請內存空間並設置相關屬性以及初始值  

這裏,我們使用如下枚舉值:
  1. GL_PIXEL_UNPACK_BUFFER_ARB 
  1. GL_PIXEL_UNPACK_BUFFER_ARB  
表示指定緩衝區存儲的內容是一個Pixel Buffer Object(PBO)

  1. GL_DYNAMIC_DRAW_ARB 
  1. GL_DYNAMIC_DRAW_ARB  
表示應用程序將會對緩衝區進行修改

這時,可能你會疑問,前兩句代碼是幹嘛的?
因爲,GL_PIXEL_UNPACK_BUFFER_ARB對glew擴展庫的版本有要求,所以最好檢查一下當前環境是否支持GL_PIXEL_UNPACK_BUFFER_ARB枚舉值。



可以看到,我的環境是支持GL_ARB_pixel_buffer_object的,如果您的環境不支持該枚舉值,可能需要您更新glew擴展庫版本

  • Step5. 把緩衝區分享給CUDA
由於我們的目的是要使用CUDA的並行計算能力,所以CUDA必須要有權利訪問共享數據緩衝區。
要實現該操作,需要將緩衝區句柄註冊爲一個圖形資源,即Graphics Resource;然後“分享給CUDA”
  1. cudaGraphicsGLRegisterBuffer(&resource, bufferObj, cudaGraphicsMapFlagsNone) 
  1. cudaGraphicsGLRegisterBuffer(&resource, bufferObj, cudaGraphicsMapFlagsNone)  

代碼中的resource即之前定義的:
[java] view plaincopy
  1. cudaGraphicsResource *resource; 
[java] view plaincopy
  1. cudaGraphicsResource *resource;  

方法cudaGraphicsGLRegisterBuffer的參數3表示緩衝區屬性,它有以下三種可選值:
1. cudaGraphicsRegisterFlagsNone: Specifies no hints about how this resource will be used. It is therefore assumed that this resource will be read from and written to by CUDA. This is the default value.
2. cudaGraphicsRegisterFlagsReadOnly: Specifies that CUDA will not write to this resource.(只讀)
3. cudaGraphicsRegisterFlagsWriteDiscard: Specifies that CUDA will not read from this resource and will write over the entire contents of the resource, so none of the data previously stored in the resource will be preserved.(只寫)

  • Step6. 讓CUDA映射共享資源,並獲取相對於顯卡而言的設備指針
  1. uchar4* devPtr; 
  2. size_t size; 
  3. cudaGraphicsMapResources(1, &resource, NULL); 
  4. cudaGraphicsResourceGetMappedPointer((void**)&devPtr, &size, resource); 
  1. uchar4* devPtr;  
  2. size_t size;  
  3. cudaGraphicsMapResources(1, &resource, NULL);  
  4. cudaGraphicsResourceGetMappedPointer((void**)&devPtr, &size, resource);  

CUDA官方文檔中這樣描述:CUDA在訪問圖形接口(比如openGL)的共享資源之前,需要首先對其進行映射(map),然後纔可以訪問共享數據區,CUDA對資源的訪問過程中,OpenGL不能對該數據區其進行任何操作,直到CUDA對數據區解除映射(unmap)爲止。

Nvidia的原文描述如下:

Map graphics resources for access by CUDA. Maps the count graphics resources in resources for access by CUDA.

The resources in resources may be accessed by CUDA until they are unmapped. The graphics API from whichresources were registered should not access any resources while they are mapped by CUDA. If an application does so, the results are undefined.


映射完成後,我們需要獲得緩衝區對於顯卡(設備)而言的指針,即代碼中的 devPtr。沒有設備指針,我們怎麼進行並行計算呢。

  • Step7. 執行CUDA核函數
  1. dim3 grids(DIM/16, DIM/16); 
  2. dim3 threads(16, 16); 
  3.  
  4. kernel_opengl<<<grids, threads>>>(devPtr); 
  1. dim3 grids(DIM/16, DIM/16);  
  2. dim3 threads(16, 16);  
  3.   
  4. kernel_opengl<<<grids, threads>>>(devPtr);  

一個簡單的核函數kernel_opengl的定義如下:
  1. __global__ void kernel_opengl(uchar4* ptr){ 
  2.     int x = threadIdx.x + blockIdx.x * blockDim.x; 
  3.     int y = threadIdx.y + blockIdx.y * blockDim.y; 
  4.     int offset = x + y * blockDim.x * gridDim.x; 
  5.  
  6.     float fx = x/(float)DIM - 0.5f; 
  7.     float fy = y/(float)DIM - 0.5f; 
  8.  
  9.     unsigned char green = 128 + 127 * sin(abs(fx*100) - abs(fy*100)); 
  10.     ptr[offset].x = 0; 
  11.     ptr[offset].y = green; 
  12.     ptr[offset].z = 0; 
  13.     ptr[offset].w = 255; 
  1. __global__ void kernel_opengl(uchar4* ptr){  
  2.     int x = threadIdx.x + blockIdx.x * blockDim.x;  
  3.     int y = threadIdx.y + blockIdx.y * blockDim.y;  
  4.     int offset = x + y * blockDim.x * gridDim.x;  
  5.   
  6.     float fx = x/(float)DIM - 0.5f;  
  7.     float fy = y/(float)DIM - 0.5f;  
  8.   
  9.     unsigned char green = 128 + 127 * sin(abs(fx*100) - abs(fy*100));  
  10.     ptr[offset].x = 0;  
  11.     ptr[offset].y = green;  
  12.     ptr[offset].z = 0;  
  13.     ptr[offset].w = 255;  
  14. }  

此時,執行完核函數,CUDA的使命也就完成了。它的產出就是:緩衝區的數據已經被更新了~~
  • Step8. 解除CUDA對共享緩衝區的映射
  1. cudaGraphicsUnmapResources(1, &resource, NULL) 
  1. cudaGraphicsUnmapResources(1, &resource, NULL)  

如果不解除映射,那麼OpenGL將沒有權限訪問共享數據區,因此也就沒有辦法完成圖像的渲染顯示了。

  • Step9. 調用OpenGL API顯示
  1. glutKeyboardFunc(key_func); 
  2. glutDisplayFunc(draw_func); 
  3. glutMainLoop(); 
  1. glutKeyboardFunc(key_func);  
  2. glutDisplayFunc(draw_func);  
  3. glutMainLoop();  

其中,顯示回調函數爲:
  1. static void draw_func(void){ 
  2.         glDrawPixels(DIM, DIM, GL_RGBA, GL_UNSIGNED_BYTE, 0); 
  3.         glutSwapBuffers(); 
  1. static void draw_func(void){  
  2.         glDrawPixels(DIM, DIM, GL_RGBA, GL_UNSIGNED_BYTE, 0);  
  3.         glutSwapBuffers();  
  4. }  
乍一看,可能感覺會比較奇怪。因爲draw_func裏面沒有使用到緩衝區句柄bufferObj,那麼數據如何會顯示呢?

因爲,之前的代碼:
  1. glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, bufferObj); 
  1. glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, bufferObj);  
該調用將共享緩衝區指定爲一個像素源,OpenGL驅動程序隨後會在所有對glDrawPixels()的調用中使用這個像素源。這也就說明了我們爲什麼使用glDrawPixels來繪製圖形了。

通過查看glDrawPixels的文檔:
  1. void glDrawPixels( 
  2.   GLsizei width, 
  3.   GLsizei height, 
  4.   GLenum format, 
  5.   GLenum type, 
  6.   const GLvoid *pixels 
  7. ); 
  1. void glDrawPixels(  
  2.   GLsizei width,  
  3.   GLsizei height,  
  4.   GLenum format,  
  5.   GLenum type,  
  6.   const GLvoid *pixels  
  7. );  
不難發現其最後一個參數爲一個緩衝區指針。如果沒有任何緩衝區被指定爲GL_PIXEL_UNPACK_BUFFER_ARB源,那麼OpenGL將從這個參數指定的緩衝區進行數據複製並顯示。但在本例中,我們已經將共享數據緩衝區指定爲GL_PIXEL_UNPACK_BUFFER_ARB。此時,該參數含義將變爲:已綁定緩衝區內的偏移量,由於我們要繪製整個緩衝區,因此這便宜量就是0.

—————————————————————— 華麗的分割線 ————————————————
最終,運行程序,得到指定的結果。

轉自:http://blog.csdn.net/lingling_y/article/details/8915163

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章