OpenGL 擴(kuò)展
許多高級特性,如那些要在GPU上進(jìn)行普通浮點(diǎn)運(yùn)算的功能,都不是OpenGL內(nèi)核的一部份。因此,OpenGL Extensions通過對OpenGL API的擴(kuò)展, 為我們提供了一種可以訪問及使用硬件高級特性的機(jī)制。OpenGL擴(kuò)展的特點(diǎn):不是每一種顯卡都支持該擴(kuò)展,即便是該顯卡在硬件上支持該擴(kuò)展,但不同版本的顯卡驅(qū)動(dòng),也會對該擴(kuò)展的運(yùn)算能力造成影響,因?yàn)镺penGL擴(kuò)展設(shè)計(jì)出來的目的,就是為了最大限度地挖掘顯卡運(yùn)算的能力,提供給那些在該方面有特別需求的程序員來使用。在實(shí)際編程的過程中,我們必須小心檢測當(dāng)前系統(tǒng)是否支持該擴(kuò)展,如果不支持的話,應(yīng)該及時(shí)把錯(cuò)誤信息返回給軟件進(jìn)行處理。當(dāng)然,為了降低問題的復(fù)雜性,本教程的代碼跳過了這些檢測步驟。
OpenGL Extension Registry OpenGL擴(kuò)展注冊列表中,列出了幾乎所有的OpenGL可用擴(kuò)展,有需要的朋友可能的查看一下。
當(dāng)我們要在程序中使用某些高級擴(kuò)展功能的時(shí)候,我們必須在程序中正確引入這些擴(kuò)展的擴(kuò)展函數(shù)名。有一些小工具可以用來幫助我們檢測一下某個(gè)給出的擴(kuò)展函數(shù)是否被當(dāng)前的硬件及驅(qū)動(dòng)所支持,如:glewinfo, OpenGL extension viewer等等,甚至OpenGL本身就可以(在上面的連接中,就有一個(gè)相關(guān)的例子)。
如何獲取這些擴(kuò)展函數(shù)的入口指針,是一個(gè)比較高級的問題。下面這個(gè)例子,我們使用GLEW來作為擴(kuò)展載入函數(shù)庫,該函數(shù)庫把許多復(fù)雜的問題進(jìn)行了底層的封裝,給我們使用高級擴(kuò)展提供了一組簡潔方便的訪問函數(shù)。
[cpp] view plaincopyvoid initGLEW (void) {
// init GLEW, obtain function pointers
int err = glewInit();
// Warning: This does not check if all extensions used
// in a given implementation are actually supported.
// Function entry points created by glewInit() will be
// NULL in that case!
if (GLEW_OK != err) {
printf((char*)glewGetErrorString(err));
exit(ERROR_GLEW);
}
}
OpenGL離屏渲染的準(zhǔn)備工作
在傳統(tǒng)的GPU渲染流水線中,每次渲染運(yùn)算的最終結(jié)束點(diǎn)就是幀緩沖區(qū)。所謂幀緩沖區(qū),其實(shí)是顯卡內(nèi)存中的一塊,它特別這處在于,保存在該內(nèi)存區(qū)塊中的圖像數(shù)據(jù),會實(shí)時(shí)地在顯示器上顯示出來。根據(jù)顯示器設(shè)置的不同,幀緩沖區(qū)最大可以取得32位的顏色深度,也就是說紅、綠、藍(lán)、alpha四個(gè)顏色通道共享這32位的數(shù)據(jù),每個(gè)通道占8位。當(dāng)然用32位來記錄顏色,如果加起來的話,可以表示160萬種不同的顏色,這對于顯示器來說可能是足夠了,但是如果我們要在浮點(diǎn)數(shù)字下工作,用8位來記錄一個(gè)浮點(diǎn)數(shù),其數(shù)學(xué)精度是遠(yuǎn)遠(yuǎn)不夠的。另外還有一個(gè)問題就是,幀緩存中的數(shù)據(jù)最大最小值會被限定在一個(gè)范圍內(nèi),也就是 [0/255; 255/255]
如何解決以上的一些問題呢?一種比較苯拙的做法就是用有符號指數(shù)記數(shù)法,把一個(gè)標(biāo)準(zhǔn)的IEEE 32位浮點(diǎn)數(shù)映射保存到8位的數(shù)據(jù)中。不過幸運(yùn)的是,我們不需要這樣做。首先,通過使用一些OpenGL的擴(kuò)展函數(shù),我們可以給GPU提供32位精度的浮點(diǎn)數(shù)。另外有一個(gè)叫EXT_framebuffer_object 的OpenGL的擴(kuò)展, 該擴(kuò)展允許我們把一個(gè)離屏緩沖區(qū)作為我們渲染運(yùn)算的目標(biāo),這個(gè)離屏緩沖區(qū)中的RGBA四個(gè)通道,每個(gè)都是32位浮點(diǎn)的,這樣一來, 要想GPU上實(shí)現(xiàn)四分量的向量運(yùn)算就比較方便了,而且得到的是一個(gè)全精度的浮點(diǎn)數(shù),同時(shí)也消除了限定數(shù)值范圍的問題。我們通常把這一技術(shù)叫FBO,也就是Frame Buffer Object的縮寫。
要使用該擴(kuò)展,或者說要把傳統(tǒng)的幀緩沖區(qū)關(guān)閉,使用一個(gè)離屏緩沖區(qū)作我們的渲染運(yùn)算區(qū),只要以下很少的幾行代碼便可以實(shí)現(xiàn)了。有一點(diǎn)值得注意的是:當(dāng)我用使用數(shù)字0,來綁定一個(gè)FBO的時(shí)候,無論何時(shí),它都會還原window系統(tǒng)的特殊幀緩沖區(qū),這一特性在一些高級應(yīng)用中會很有用,但不是本教程的范圍,有興趣的朋友可能自已研究一下。
?。踓pp] view plaincopyGLuint fb;
void initFBO(void) {
// create FBO (off-screen framebuffer)
glGenFramebuffersEXT(1, &fb);
// bind offscreen buffer
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fb);
}
Back to top
GPGPU 概念
1: 數(shù)組 = 紋理
一維數(shù)組是本地CPU最基本的數(shù)據(jù)排列方式,多維的數(shù)組則是通過對一個(gè)很大的一維數(shù)組的基準(zhǔn)入口進(jìn)行坐標(biāo)偏移來訪問的(至少目前大多數(shù)的編譯器都是這樣做的)。一個(gè)小例子可以很好說明這一點(diǎn),那就是一個(gè)MxN維的數(shù)組 a[i][j] = a[i*M+j];我們可能把一個(gè)多維數(shù)組,映射到一個(gè)一維數(shù)組中去。這些數(shù)組我開始索引都被假定為0;
而對于GPU,最基本的數(shù)據(jù)排列方式,是二維數(shù)組。一維和三維的數(shù)組也是被支持的,但本教程的技術(shù)不能直接使用。數(shù)組在GPU內(nèi)存中我們把它叫做紋理或者是紋理樣本。紋理的最大尺寸在GPU中是有限定的。每個(gè)維度的允許最大值,通過以下一小段代碼便可能查詢得到,這些代碼能正確運(yùn)行,前提是OpenGL的渲染上下文必須被正確初始化。
?。踓pp] view plaincopyint maxtexsize;
glGetIntegerv(GL_MAX_TEXTURE_SIZE,&maxtexsize);
printf(“GL_MAX_TEXTURE_SIZE, %d ”,maxtexsize);
就目前主流的顯卡來說,這個(gè)值一般是2048或者4096每個(gè)維度,值得提醒大家的就是:一塊顯卡,雖然理論上講它可以支持4096*4096*4096的三維浮點(diǎn)紋理,但實(shí)際中受到顯卡內(nèi)存大小的限制,一般來說,它達(dá)不到這個(gè)數(shù)字。
在CPU中,我們常會討論到數(shù)組的索引,而在GPU中,我們需要的是紋理坐標(biāo),有了紋理坐標(biāo)才可以訪問紋理中每個(gè)數(shù)據(jù)的值。而要得到紋理坐標(biāo),我們又必須先得到紋理中心的地址。
傳統(tǒng)上講,GPU是可以四個(gè)分量的數(shù)據(jù)同時(shí)運(yùn)算的,這四個(gè)分量也就是指紅、綠、藍(lán)、alpha(RGBA)四個(gè)顏色通道。稍后的章節(jié)中,我將會介紹如何使用顯卡這一并行運(yùn)算的特性,來實(shí)現(xiàn)我們想要的硬件加速運(yùn)算。
在CPU上生成數(shù)組
讓我們來回顧一下前面所要實(shí)現(xiàn)的運(yùn)算:也就是給定兩個(gè)長度為N的數(shù)組,現(xiàn)在要求兩數(shù)組的加權(quán)和y=y+alpha*x,我們現(xiàn)在需要兩個(gè)數(shù)組來保存每個(gè)浮點(diǎn)數(shù)的值,及一個(gè)記錄alpha值的浮點(diǎn)數(shù)。
[cpp] view plaincopyfloat* dataY = (float*)malloc(N*sizeof(float)); float* dataX = (float*)malloc(N*sizeof(float)); float alpha;
雖然我們的實(shí)際運(yùn)算是在GPU上運(yùn)行,但我們?nèi)匀灰贑PU上分配這些數(shù)組空間,并對數(shù)組中的每個(gè)元素進(jìn)行初始化賦值。
在GPU上生成浮點(diǎn)紋理
這個(gè)話題需要比較多的解釋才行,讓我們首先回憶一下在CPU上是如何實(shí)現(xiàn)的,其實(shí)簡單點(diǎn)來說,我們就是要在GPU上建立兩個(gè)浮點(diǎn)數(shù)組,我們將使用浮點(diǎn)紋理來保存數(shù)據(jù)。
有許多因素的影響,從而使問題變得復(fù)雜起來。其中一個(gè)重要的因素就是,我們有許多不同的紋理對像可供我們選擇。即使我們排除掉一些非本地的目標(biāo),以及限定只能使用2維的紋理對像。我們依然還有兩個(gè)選擇,GL_TEXTURE_2D是傳統(tǒng)的OpenGL二維紋理對像,而ARB_texture_rectangle則是一個(gè)OpenGL擴(kuò)展,這個(gè)擴(kuò)展就是用來提供所謂的texture rectangles的。對于那些沒有圖形學(xué)背景的程序員來說,選擇后者可能會比較容易上手。texture2Ds 和 texture rectangles 在概念上有兩大不同之處。我們可以從下面這個(gè)列表來對比一下,稍后我還會列舉一些例子。
另外一個(gè)重要的影響因素就是紋理格式,我們必須謹(jǐn)慎選擇。在GPU中可能同時(shí)處理標(biāo)量及一到四分量的向量。本教程主要關(guān)注標(biāo)量及四分量向量的使用。比較簡單的情況下我們可以在中紋理中為每個(gè)像素只分配一個(gè)單精度浮點(diǎn)數(shù)的儲存空間,在OpenGL中,GL_LUMNANCE就是這樣的一種紋理格式。但是如果我們要想使用四個(gè)通道來作運(yùn)算的話,我們就可以采用GL_RGBA這種紋理格式。使用這種紋理格式,意味著我們會使用一個(gè)像素?cái)?shù)據(jù)來保存四個(gè)浮點(diǎn)數(shù),也就是說紅、綠、藍(lán)、alpha四個(gè)通道各占一個(gè)32位的空間,對于LUMINANCE格式的紋理,每個(gè)紋理像素只占有32位4個(gè)字節(jié)的顯存空間,而對于RGBA格式,保存一個(gè)紋理像素需要的空間是4*32=128位,共16個(gè)字節(jié)。
接下來的選擇,我們就要更加小心了。在OpenGL中,有三個(gè)擴(kuò)展是真正接受單精度浮點(diǎn)數(shù)作為內(nèi)部格式的紋理的。分別是:NV_float_buffer,ATI_texture_float 和ARB_texture_float.每個(gè)擴(kuò)展都就定義了一組自已的列舉參數(shù)及其標(biāo)識,如:(GL_FLOAT_R32_NV) ,( 0x8880),在程序中使用不同的參數(shù),可以生成不同格式的紋理對像,下面會作詳細(xì)描述。
在這里,我們只對其中兩個(gè)列舉參數(shù)感興趣,分別是GL_FLOAT_R32_NV和GL_FLOAT_RGBA32_NV. 前者是把每個(gè)像素保存在一個(gè)浮點(diǎn)值中,后者則是每個(gè)像素中的四個(gè)分量分別各占一個(gè)浮點(diǎn)空間。這兩個(gè)列舉參數(shù),在另外兩個(gè)擴(kuò)展(ATI_texture_float andARB_texture_float )中也分別有其對應(yīng)的名稱:GL_LUMINANCE_FLOAT32_ATI,GL_RGBA_FLOAT32_ATI 和 GL_LUMINANCE32F_ARB,GL_RGBA32F_ARB 。在我看來,他們名稱不同,但作用都是一樣的,我想應(yīng)該是多個(gè)不同的參數(shù)名稱對應(yīng)著一個(gè)相同的參數(shù)標(biāo)識。至于選擇哪一個(gè)參數(shù)名,這只是看個(gè)人的喜好,因?yàn)樗鼈內(nèi)慷技戎С諲V顯卡也支持ATI的顯卡。
最后還有一個(gè)要解決的問題就是,我們?nèi)绾伟袰PU中的數(shù)組元素與GPU中的紋理元素一一對應(yīng)起來。這里,我們采用一個(gè)比較容易想到的方法:如果紋理是LUMINANCE格式,我們就把長度為N的數(shù)組,映射到一張大小為sqrt(N) x sqrt(N)和紋理中去(這里規(guī)定N是剛好能被開方的)。如果采用RGBA的紋理格式,那么N個(gè)長度的數(shù)組,對應(yīng)的紋理大小就是sqrt(N/4) x sqrt(N/4),舉例說吧,如果N=1024^2,那么紋理的大小就是512*512 。
以下的表格總結(jié)了我們上面所討論的問題,作了一下分類,對應(yīng)的GPU分別是: NVIDIA GeForce FX (NV3x), GeForce 6 and 7 (NV4x, G7x) 和 ATI.
(*) Warning: 這些格式作為紋理是被支持的,但是如果作為渲染對像,就不一定全部都能夠得到良好的支持(seebelow)。
講完上面的一大堆基礎(chǔ)理論這后,是時(shí)候回來看看代碼是如何實(shí)現(xiàn)的。比較幸運(yùn)的是,當(dāng)我們弄清楚了要用那些紋理對像、紋理格式、及內(nèi)部格式之后,要生成一個(gè)紋理是很容易的。
?。踓pp] view plaincopy// create a new texture name
GLuint texID;
glGenTextures (1, &texID);
// bind the texture name to a texture target
glBindTexture(texture_target,texID);
// turn off filtering and set proper wrap mode
// (obligatory for float textures atm)
glTexParameteri(texture_target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(texture_target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(texture_target, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri(texture_target, GL_TEXTURE_WRAP_T, GL_CLAMP);
// set texenv to replace instead of the default modulate
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
// and allocate graphics memory
glTexImage2D(texture_target, 0, internal_format,
texSize, texSize, 0, texture_format, GL_FLOAT, 0);
讓我們來消化一下上面這段代碼的最后那個(gè)OpenGL函數(shù),我來逐一介紹一下它每個(gè)參數(shù):第一個(gè)參數(shù)是紋理對像,上面已經(jīng)說過了;第二個(gè)參數(shù)是0,是告訴GL不要使用多重映像紋理。接下來是內(nèi)部格式及紋理大小,上面也說過了,應(yīng)該清楚了吧。第六個(gè)參數(shù)是也是0,這是用來關(guān)閉紋理邊界的,這里不需要邊界。接下來是指定紋理格式,選擇一種你想要的格式就可以了。對于參數(shù)GL_FLOAT,我們不要被它表面的意思迷惑,它并不會影響我們所保存在紋理中的浮點(diǎn)數(shù)的精度。其實(shí)它只與CPU方面有關(guān)系,目的就是要告訴GL稍后將要傳遞過去的數(shù)據(jù)是浮點(diǎn)型的。最后一個(gè)參數(shù)還是0,意思是生成一個(gè)紋理,但現(xiàn)在不給它指定任何數(shù)據(jù),也就是空的紋理。該函數(shù)的調(diào)用必須按上面所說的來做,才能正確地生成一個(gè)合適的紋理。上面這段代碼,和CPU里分配內(nèi)存空間的函數(shù)malloc(),功能上是很相像的,我們可能用來對比一下。
最后還有一點(diǎn)要提醒注意的:要選擇一個(gè)適當(dāng)?shù)臄?shù)據(jù)排列映射方式。這里指的就是紋理格式、紋理大小要與你的CPU數(shù)據(jù)相匹配,這是一個(gè)非常因地制宜的問題,根據(jù)解決的問題不同,其相應(yīng)的處理問題方式也不同。從經(jīng)驗(yàn)上看,一些情況下,定義這樣一個(gè)映射方式是很容易的,但某些情況下,卻要花費(fèi)你大量的時(shí)間,一個(gè)不理想的映射方式,甚至?xí)?yán)重影響你的系統(tǒng)運(yùn)行。
數(shù)組索引與紋理坐標(biāo)的一一對應(yīng)關(guān)系
在后面的章節(jié)中,我們會講到如何通過一個(gè)渲染操作,來更新我們保存在紋理中的那些數(shù)據(jù)。在我們對紋理進(jìn)行運(yùn)算或存取的時(shí)候,為了能夠正確地控制每一個(gè)數(shù)據(jù)元素,我們得選擇一個(gè)比較特殊的投影方式,把3D世界映射到2D屏幕上(從世界坐標(biāo)空間到屏幕設(shè)備坐標(biāo)空間),另外屏幕像素與紋理元素也要一一對應(yīng)。這種關(guān)系要成功,關(guān)鍵是要采用正交投影及合適的視口。這樣便能做到幾何坐標(biāo)(用于渲染)、紋理坐標(biāo)(用作數(shù)據(jù)輸入)、像素坐標(biāo)(用作數(shù)據(jù)輸出)三者一一對應(yīng)。有一個(gè)要提醒大家的地方:如果使用texture2D,我們則須要對紋理坐標(biāo)進(jìn)行適當(dāng)比例的縮放,讓坐標(biāo)的值在0到1之間,前面有相關(guān)的說明。
為了建立一個(gè)一一對應(yīng)的映射,我們把世界坐標(biāo)中的Z坐標(biāo)設(shè)為0,把下面這段代碼加入到initFBO()這個(gè)函數(shù)中
?。踓pp] view plaincopy// viewport for 1:1 pixel=texel=geometry mapping
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(0.0, texSize, 0.0, texSize);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glViewport(0, 0, texSize, texSize);
使用紋理作為渲染對像
其實(shí)一個(gè)紋理,它不僅可以用來作數(shù)據(jù)輸入對像,也還可以用作數(shù)據(jù)輸出對像。這也是提高GPU運(yùn)算效率和關(guān)鍵所在。通過使用 framebuffer_object這個(gè)擴(kuò)展,我們可以把數(shù)據(jù)直接渲染輸出到一個(gè)紋理上。但是有一個(gè)缺點(diǎn):一個(gè)紋理對像不能同時(shí)被讀寫,也就是說,一個(gè)紋理,要么是只讀的,要么就是只寫的。顯卡設(shè)計(jì)的人提供這樣一個(gè)解釋:GPU在同一時(shí)間段內(nèi)會把渲染任務(wù)分派到幾個(gè)通道并行運(yùn)行, 它們之間都是相互獨(dú)立的(稍后的章節(jié)會對這個(gè)問題作詳細(xì)的討論)。如果我們允許對一個(gè)紋理同時(shí)進(jìn)行讀寫操作的話,那我們需要一個(gè)相當(dāng)復(fù)雜的邏輯算法來解決讀寫沖突的問題, 即使在芯片邏輯上可以做到,但是對于GPU這種沒有數(shù)據(jù)安全性約束的處理單元來說,也是沒辦法把它實(shí)現(xiàn)的,因?yàn)镚PU并不是基von Neumann的指令流結(jié)構(gòu),而是基于數(shù)據(jù)流的結(jié)構(gòu)。因此在我們的程序中,我們要用到3個(gè)紋理,兩個(gè)只讀紋理分別用來保存輸入數(shù)組x,y。一個(gè)只寫紋理用來保存運(yùn)算結(jié)果。用這種方法意味著要把先前的運(yùn)算公式:y = y + alpha * x 改寫為:y_new = y_old + alpha * x.
FBO 擴(kuò)展提供了一個(gè)簡單的函數(shù)來實(shí)現(xiàn)把數(shù)據(jù)渲染到紋理。為了能夠使用一個(gè)紋理作為渲染對像,我們必須先把這個(gè)紋理與FBO綁定,這里假設(shè)離屏幀緩沖已經(jīng)被指定好了。
?。踓pp] view plaincopyglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, texture_target, texID, 0);
第一個(gè)參數(shù)的意思是很明顯的。第二個(gè)參數(shù)是定義一個(gè)綁定點(diǎn)(每個(gè)FBO最大可以支持四個(gè)不同的綁定點(diǎn),當(dāng)然,不同的顯卡對這個(gè)最大綁定數(shù)的支持不一樣,可以用GL_MAX_COLOR_ATTACHMENTS_EXT來查詢一下)。第三和第四個(gè)參數(shù)應(yīng)該清楚了吧,它們是實(shí)際紋理的標(biāo)識。最后一個(gè)參數(shù)指的是使用多重映像紋理,這里沒有用到,因此設(shè)為0。
為了能成功綁定一紋理,在這之前必須先用glTexImage2D()來對它定義和分配空間。但不須要包含任何數(shù)據(jù)。我們可以把FBO想像為一個(gè)數(shù)據(jù)結(jié)構(gòu)的指針,為了能夠?qū)σ粋€(gè)指定的紋理直接進(jìn)行渲染操作,我們須要做的就調(diào)用OpenGL來給這些指針賦以特定的含義。
不幸的是,在FBO的規(guī)格中,只有GL_RGB和GL_RGBA兩種格式的紋理是可以被綁定為渲染對像的(后來更新這方面得到了改進(jìn)),LUMINANCE這種格式的綁定有希望在后繼的擴(kuò)展中被正式定義使用。在我定本教程的時(shí)候,NVIDIA的硬件及驅(qū)動(dòng)已經(jīng)對這個(gè)全面支持,但是只能結(jié)會對應(yīng)的列舉參數(shù)NV_float_buffer一起來使用才行。換句話說,紋理中的浮點(diǎn)數(shù)的格式與渲染對像中的浮點(diǎn)數(shù)格式有著本質(zhì)上的區(qū)別。
下面這個(gè)表格對目前不同的顯卡平臺總結(jié)了一下,指的是有哪些紋理格式及紋理對像是可能用來作為渲染對像的,(可能還會有更多被支持的格式,這里只關(guān)心是浮點(diǎn)數(shù)的紋理格式):
列表中最后一行所列出來的格式在目前來說,不能被所有的GPU移植使用。如果你想采用LUMINANCE格式,你必須使用ractangles紋理,并且只能在NVIDIA的顯卡上運(yùn)行。想要寫出兼容NVIDIA及ATI兩大類顯卡的代是可能的,但只支持NV4x以上。幸運(yùn)的是要修改的代碼比較少,只在一個(gè)switch開關(guān),便能實(shí)現(xiàn)代碼的可移植性了。相信隨著ARB新版本擴(kuò)展的發(fā)布,各平臺之間的兼容性將會得到進(jìn)一步的提高,到時(shí)候各種不同的格式也可能相互調(diào)用了。
把數(shù)據(jù)從CPU的數(shù)組傳輸?shù)紾PU的紋理
為了把數(shù)據(jù)傳輸?shù)郊y理中去,我們必須綁定一個(gè)紋理作為紋理目標(biāo),并通過一個(gè)GL函數(shù)來發(fā)送要傳輸?shù)臄?shù)據(jù)。實(shí)際上就是把數(shù)據(jù)的首地址作為一個(gè)參數(shù)傳遞給該涵數(shù),并指定適當(dāng)?shù)募y理大小就可以了。如果用LUMINANCE格式,則意味著數(shù)組中必須有texSize x texSize個(gè)元數(shù)。而RGBA格式,則是這個(gè)數(shù)字的4倍。注意的是,在把數(shù)據(jù)從內(nèi)存?zhèn)鞯斤@卡的過程中,是全完不需要人為來干預(yù)的,由驅(qū)動(dòng)來自動(dòng)完成。一但傳輸完成了,我們便可能對CPU上的數(shù)據(jù)作任意修改,這不會影響到顯卡中的紋理數(shù)據(jù)。 而且我們下次再訪問該紋理的時(shí)候,它依然是可用的。在NVIDIA的顯卡中,以下的代碼是得到硬件加速的。
?。踓pp] view plaincopyglBindTexture(texture_target, texID);
glTexSubImage2D(texture_target,0,0,0,texSize,texSize,
texture_format,GL_FLOAT,data);
這里三個(gè)值是0的參數(shù),是用來定義多重映像紋理的,由于我們這里要求一次把整個(gè)數(shù)組傳輸一個(gè)紋理中,不會用到多重映像紋理,因此把它們都關(guān)閉掉。
以上是NVIDIA顯卡的實(shí)現(xiàn)方法,但對于ATI的顯卡,以下的代碼作為首選的技術(shù)。在ATI顯卡中,要想把數(shù)據(jù)傳送到一個(gè)已和FBO綁定的紋理中的話,只需要把OpenGL的渲染目標(biāo)改為該綁定的FBO對像就可以了。
glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT);glRasterPos2i(0,0);glDrawPixels(texSize,texSize,texture_format,GL_FLOAT,data);
第一個(gè)函數(shù)是改變輸出的方向,第二個(gè)函數(shù)中我們使用了起點(diǎn)作為參與點(diǎn),因?yàn)槲覀冊诘谌齻€(gè)函數(shù)中要把整個(gè)數(shù)據(jù)塊都傳到紋理中去。
兩種情況下,CPU中的數(shù)據(jù)都是以行排列的方式映射到紋理中去的。更詳細(xì)地說,就是:對于RGBA格式,數(shù)組中的前四個(gè)數(shù)據(jù),被傳送到紋理的第一個(gè)元素的四個(gè)分量中,分別與R,G,B,A分量一一對應(yīng),其它類推。而對于LUMINANCE 格式的紋理,紋理中第一行的第一個(gè)元素,就對應(yīng)數(shù)組中的第一個(gè)數(shù)據(jù)。其它紋理元素,也是與數(shù)組中的數(shù)據(jù)一一對應(yīng)的。
把數(shù)據(jù)從GPU紋理,傳輸?shù)紺PU的數(shù)組
這是一個(gè)反方向的操作,那就是把數(shù)據(jù)從GPU傳輸回來,存放在CPU的數(shù)組上。同樣,有兩種不同的方法可供我們選擇。傳統(tǒng)上,我們是使用OpenGL獲取紋理的方法,也就是綁定一個(gè)紋理目標(biāo),然后調(diào)用glGetTexImage()這個(gè)函數(shù)。這些函數(shù)的參數(shù),我們在前面都有見過。
glBindTexture(texture_target,texID);glGetTexImage(texture_target,0,texture_format,GL_FLOAT,data);
但是這個(gè)我們將要讀取的紋理,已經(jīng)和一個(gè)FBO對像綁定的話,我們可以采用改變渲染指針方向的技術(shù)來實(shí)現(xiàn)。
glReadBuffer(GL_COLOR_ATTACHMENT0_EXT);glReadPixels(0,0,texSize,texSize,texture_format,GL_FLOAT,data);
由于我們要讀取GPU的整個(gè)紋理,因此這里前面兩個(gè)參數(shù)是0,0。表示從0起始點(diǎn)開始讀取。該方法是被推薦使用的。
一個(gè)忠告:比起在GPU內(nèi)部的傳輸來說,數(shù)據(jù)在主機(jī)內(nèi)存與GPU內(nèi)存之間相互傳輸,其花費(fèi)的時(shí)間是巨大的,因此要謹(jǐn)慎使用。由其是從CPU到GPU的逆向傳輸。
在前面“ 當(dāng)前顯卡設(shè)備運(yùn)行的問題” 中 提及到該方面的問題。
一個(gè)簡單的例子
?。踓pp] view plaincopy#include 《stdio.h》
#include 《stdlib.h》
#include 《GL/glew.h》
#include 《GL/glut.h》
int main(int argc, char **argv) {
// 這里聲明紋理的大小為:teSize;而數(shù)組的大小就必須是texSize*texSize*4
int texSize = 2;
int i;
// 生成測試數(shù)組的數(shù)據(jù)
float* data = (float*)malloc(4*texSize*texSize*sizeof(float));
float* result = (float*)malloc(4*texSize*texSize*sizeof(float));
for (i=0; i《texSize*texSize*4; i++)
data[i] = (i+1.0)*0.01F;
// 初始化OpenGL的環(huán)境
glutInit (&argc, argv);
glutCreateWindow(“TEST1”);
glewInit();
// 視口的比例是 1:1 pixel=texel=data 使得三者一一對應(yīng)
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(0.0,texSize,0.0,texSize);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glViewport(0,0,texSize,texSize);
// 生成并綁定一個(gè)FBO,也就是生成一個(gè)離屏渲染對像
GLuint fb;
glGenFramebuffersEXT(1,&fb);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT,fb);
// 生成兩個(gè)紋理,一個(gè)是用來保存數(shù)據(jù)的紋理,一個(gè)是用作渲染對像的紋理
GLuint tex,fboTex;
glGenTextures (1, &tex);
glGenTextures (1, &fboTex);
glBindTexture(GL_TEXTURE_RECTANGLE_ARB,fboTex);
// 設(shè)定紋理參數(shù)
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);
// 這里在顯卡上分配FBO紋理的貯存空間,每個(gè)元素的初始值是0;
glTexImage2D(GL_TEXTURE_RECTANGLE_ARB,0,GL_RGBA32F_ARB,
texSize,texSize,0,GL_RGBA,GL_FLOAT,0);
// 分配數(shù)據(jù)紋理的顯存空間
glBindTexture(GL_TEXTURE_RECTANGLE_ARB,tex);
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);
glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_COLOR,GL_DECAL);
glTexImage2D(GL_TEXTURE_RECTANGLE_ARB,0,GL_RGBA32F_ARB,
texSize,texSize,0,GL_RGBA,GL_FLOAT,0);
//把當(dāng)前的FBO對像,與FBO紋理綁定在一起
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT,
GL_COLOR_ATTACHMENT0_EXT,
GL_TEXTURE_RECTANGLE_ARB,fboTex,0);
// 把本地?cái)?shù)據(jù)傳輸?shù)斤@卡的紋理上。
glBindTexture(GL_TEXTURE_RECTANGLE_ARB,tex);
glTexSubImage2D(GL_TEXTURE_RECTANGLE_ARB,0,0,0,texSize,texSize,
GL_RGBA,GL_FLOAT,data);
//--------------------begin-------------------------
//以下代碼是渲染一個(gè)大小為texSize * texSize矩形,
//其作用就是把紋理中的數(shù)據(jù),經(jīng)過處理后,保存到幀緩沖中去,
//由于用到了離屏渲染,這里的幀緩沖區(qū)指的就是FBO紋理。
//在這里,只是簡單地把數(shù)據(jù)從紋理直接傳送到幀緩沖中,
//沒有對這些流過GPU的數(shù)據(jù)作任何處理,但是如果我們會用CG、
//GLSL等高級著色語言,對顯卡進(jìn)行編程,便可以在GPU中
//截獲這些數(shù)據(jù),并對它們進(jìn)行任何我們所想要的復(fù)雜運(yùn)算。
//這就是GPGPU技術(shù)的精髓所在。問題討論:www.physdev.com
glColor4f(1.00f,1.00f,1.00f,1.0f);
glBindTexture(GL_TEXTURE_RECTANGLE_ARB,tex);
glEnable(GL_TEXTURE_RECTANGLE_ARB);
glBegin(GL_QUADS);
glTexCoord2f(0.0, 0.0);
glVertex2f(0.0, 0.0);
glTexCoord2f(texSize, 0.0);
glVertex2f(texSize, 0.0);
glTexCoord2f(texSize, texSize);
glVertex2f(texSize, texSize);
glTexCoord2f(0.0, texSize);
glVertex2f(0.0, texSize);
glEnd();
//--------------------end------------------------
// 從幀緩沖中讀取數(shù)據(jù),并把數(shù)據(jù)保存到result數(shù)組中。
glReadBuffer(GL_COLOR_ATTACHMENT0_EXT);
glReadPixels(0, 0, texSize, texSize,GL_RGBA,GL_FLOAT,result);
// 顯示最終的結(jié)果
printf(“Data before roundtrip: ”);
for (i=0; i《texSize*texSize*4; i++)
printf(“%f ”,data[i]);
printf(“Data after roundtrip: ”);
for (i=0; i《texSize*texSize*4; i++)
printf(“%f ”,result[i]);
// 釋放本地內(nèi)存
free(data);
free(result);
// 釋放顯卡內(nèi)存
glDeleteFramebuffersEXT (1,&fb);
glDeleteTextures (1,&tex);
glDeleteTextures(1,&fboTex);
return 0;
}
現(xiàn)在是時(shí)候讓我們回頭來看一下前面要解決的問題,我強(qiáng)烈建議在開始一個(gè)新的更高級的話題之前,讓我們先弄一個(gè)顯淺的例子來實(shí)踐一下。下面通過一個(gè)小的程序,嘗試著使用各種不同的紋理格式,紋理對像以及內(nèi)部格式,來把數(shù)據(jù)發(fā)送到GPU,然后再把數(shù)據(jù)從GPU取回來,保存在CPU的另一個(gè)數(shù)組中。在這里,兩個(gè)過程都沒有對數(shù)據(jù)作任何運(yùn)算修該,目的只是看一下數(shù)據(jù)GPU和CPU之間相互傳輸,所需要使用到的技術(shù)及要注意的細(xì)節(jié)。也就是把前面提及到的幾個(gè)有迷惑性的問題放在同一個(gè)程序中來運(yùn)行一下。在稍后的章節(jié)中將會詳細(xì)討論如何來解決這些可能會出現(xiàn)的問題。
由于趕著要完成整個(gè)教程,這里就只寫了一個(gè)最為簡單的小程序,采用rectangle紋理、ARB_texture_float作紋理對像并且只能在NVIDIA的顯卡上運(yùn)行。
你可以在這里下載到為ATI顯卡寫的另一個(gè)版本。
以上代碼是理解GPU編程的基礎(chǔ),如果你完全看得懂,并且能對這代碼作簡單的修改運(yùn)用的話,那恭喜你,你已經(jīng)向成功邁進(jìn)了一大步,并可以繼續(xù)往下看,走向更深入的學(xué)習(xí)了。但如看不懂,那回頭再看一編吧。
Back to top
評論
查看更多