GPGPU計算觀念和基本思路總結

這篇文章總結的太好了,化解了我很多疑惑,值得收藏:http://blog.csdn.net/wwybj/article/details/4218868

使用GPU進行通用計算和常規的使用CPU進行計算在觀念上具有非常大的區別,很多資料都會進行對比(比如經典的《GPU Gem 2》),但是通常用語都比較專業化,初學者可能很難想明白。這裏按照我目前的理解,先總結一下:

 

    首先需要明確的是,GPGPU中所有計算的數據,都保存在紋理中。比如一個長度爲16的一維數組,在GPGPU中就需要建立一個2*2的紋理,這樣這張紋理就有4個像素,而每個像素包含R、G、B、A四個顏色分量,所以總共是16個數據。

    具體的數據操作,則需要一個由內存(這裏指的是計算機內存)向顯存(這裏指顯卡內存)傳輸的過程——每一組數據都需要在內存中創建,然後將數據傳送到一個紋理中,這些數據在紋理中的表示,則是紋理中每一個像素的顏色值(這個顏色值可以是一個分量,也可以是多個分量,看具體的設計)。

 

    另外一個需要明確的是,由於我們做GPGPU時不是真正的在渲染畫面,因此不需要將渲染結果以圖像方式輸出,而僅僅需要取出渲染的結果(也就是像素顏色值。如前面所說,可以將它們看成是傳統的數組),因此我們不需要渲染到傳統的幀緩衝區中,所以,我們要創建一個“後臺的幀緩衝區”,就是FBO——Frame Buffer Object。FBO的概念在GPGPU中非常重要!

    在將FBO創建好,並設定好我們之後的渲染指向這個FBO後,還需要明確一個觀念——“渲染到紋理”(Render to Texture),這裏的RTT和三維建模中的烘焙(Baker)不是一個概念。由於我們需要一個計算的結果數據,而這個數據在顯存中是保存在一個紋理中的,所以GPGPU的最終渲染成果,是一張包含計算結果的紋理,因此這裏的RTT是指我們創建一個結果紋理,並和FBO綁定,讓GPU“渲染”(或者,這在裏也可以理解爲“計算”)到這張紋理上,於是這張結果紋理中每一個像素的顏色值組合起來就是我們需要的計算結果數組了。

 

    第三個需要明確的,是計算的實現。假設現在我們要做兩個數組的相加操作:Result = X + Y。三者都是同維數組,我們也創建好了三個紋理TexX、TexY和TexResult,前兩個紋理我們已經將X和Y的數據傳送進去並保存在顯存中了,TexResult紋理則已經和FBO綁定好,準備接受GPU的渲染(也就是接收GPU計算的數據)。那麼這個相加的計算怎麼做呢?這裏就是GPGPU最核心、最動人的部分了——使用Shader(着色器)。

    我們都知道Shader有兩種,Vertex Shader和Fragment Shader。說句題外話,過去的顯卡硬件對這兩種着色器的實現是不同的物理路徑,分別叫頂點處理單元和片段處理單元,而現在的顯卡則將兩者合二爲一,統稱爲“流處理單元”,用以實現更高效的GPU着色操作。回到我們的話題。我們的GPU計算,就是通過Shader來實現,也就是我們自己編寫着色器來改變默認的顯卡渲染管線。

 

     那麼,總的計算流程是什麼呢,這就是本文的核心內容了:

 

    1.初始化環境,比如glut、glew等,然後創建一個合適的渲染視口,通常使用gluOrtho2D()創建一個二維的,大小和紋理大小相同,這樣能夠很好的一一對應,後面的渲染工作也會很簡便。

 

    2.生成並綁定一個FBO,然渲染工作(也就是GPU計算工作)在後臺運行,不輸出顯示圖形:

    GLuint fb;
    glGenFramebuffersEXT(1,&fb);
    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT,fb);

 

    3.創建輸入數據(就是源數據)紋理,分配顯存,賦初值,並設置:

    GLuint texture_source;

    glGenTextures(1,&texture_source);

    glBindTexture(GL_TEXTURE_RECTANGLE_ARB, texture_source);

    //初始化紋理
    glTexParameteri(GL_TEXTURE_RECTANGLE_ARB,GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_RECTANGLE_ARB,GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_RECTANGLE_ARB,GL_TEXTURE_WRAP_S, GL_CLAMP);
    glTexParameteri(GL_TEXTURE_RECTANGLE_ARB,GL_TEXTURE_WRAP_T, GL_CLAMP);

    //分配顯存空間,並將紋理中的數據初始化爲0(由最後一個參數確定)

    glTexImage2D(GL_TEXTURE_RECTANGLE_ARB,0,GL_RGBA32F_ARB,texSize,texSize,0,GL_RGBA,GL_FLOAT,0);

 

    4.將源數據傳輸到texture_source中,具體方案是(這裏介紹一種方法),先將這個紋理與FBO綁定,然後使用glDrawPixels()函數逐個繪製像素,於是源數據就“繪製”到這張源紋理中了:

    //綁定FBO和紋理    glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT,GL_COLOR_ATTACHMENT0_EXT,GL_TEXTURE_RECTANGLE_ARB,texture_source,0);

    //確定渲染目標位FBO中的第0個(由上一個函數確定)

    glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT);

    //確定繪製原點

    glRasterPos2i(0,0);

    //逐個繪製像素,data是在計算機內存中存儲的數組

    glDrawPixels(texSize,texSize,GL_RGBA,GL_FLOAT,data);

    至此,源數據便傳送到了這張紋理中。如果有多個數據源和多個紋理,按照同樣的方法傳遞數據。

 

    5.創建輸出數據紋理texture_result,同上

 

    6.將輸出紋理和FBO綁定,使渲染結果保存在這個輸出紋理中,這裏可以綁定多個紋理,用“GL_COLOR_ATTACHMENT0_EXT”中的那個數字進行區分,之後(第6步)則可以通過這個參數選擇不同的紋理對象:

   glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT,GL_COLOR_ATTACHMENT0_EXT,GL_TEXTURE_RECTANGLE_ARB,texture_result,0);

 

    7.使用glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT);確定渲染目標緩衝爲FBO中的第0個(由上一個函數指定)

 

    9.創建並編譯Shader,這個具體就不在本文討論範疇了。

 

    10.使用glGetUniformLocationARB()獲取Shader參數位置(或者理解爲參數接口)

 

    11.使用glUseProgram(programObject);激活Shader

 

    12.向Shader傳遞參數:

    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_RECTANGLE_ARB,texture_source);
    glUniform1i(xParam, 0);

 

    13.確定FBO的渲染紋理對象:glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT);

 

    14.繪製一個四邊形,使整個渲染管線開始運作,從而實現計算,並將計算結果保存到輸出數據紋理texture_result中。

 

    15.下一步便可以從texture_result中讀取數據,並以數組形式保存到計算機內存中:

    glReadBuffer(GL_COLOR_ATTACHMENT0_EXT);
    glReadPixels(0, 0, texSize, texSize,GL_RGBA,GL_FLOAT,result);

 

    至此,一個基本的GPGPU流程就走完了。我們經歷了將輸入數據讀入源紋理、使用Shader對源紋理進行操作、將結果渲染到與FBO綁定的一個輸出紋理、將輸出紋理中的數據讀取並保存到傳統數組中。


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