這篇文章總結的太好了,化解了我很多疑惑,值得收藏:
使用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;
glgenframebuffer***t(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繫結的乙個輸出紋理、將輸出紋理中的資料讀取並儲存到傳統陣列中。
GPGPU計算觀念和基本思路總結
使用gpu進行通用計算和常規的使用cpu進行計算在觀念上具有非常大的區別,很多資料都會進行對比 比如經典的 gpu gem 2 但是通常用語都比較專業化,初學者可能很難想明白。這裡按照我目前的理解,先總結一下 首先需要明確的是,gpgpu中所有計算的資料,都儲存在紋理中。比如乙個長度為16的一維陣列...
系統設計和系統劃分的基本思路
今天要說說這兩個定律,乙個是墨菲定律,另外乙個是康威定律。有人說 在系統設計時,可以以 墨菲定律 作為警醒。墨菲定律 任何事物都沒有表面看起來那麼簡單。所有的事都會比你預計的時間長。可能出錯的事總會出錯。如果你擔心某種情況發生,那麼他就更有可能發生。任何事物都沒有表明看起來那麼簡單 比如在做系統分析...
寫符合web標準頁面的基本思路和重點
之前一直不太清楚到底怎麼寫出符合web標準的頁面,雖然是div css,但寫出的 還是比較亂的。最近讀了兩本css的書,一本是 精通css 一本是 無懈可擊的web設計 先讀了 精通css 雖然感覺翻譯的不是很好,有些地方不好理解,但對css的知識點還是講的很不錯。後來看 無懈可擊的web設計 就有...