GPGPU 概念 4: 反饋
當(dāng)運(yùn)算全部完成之后,的、得到的結(jié)果會被保存在目標(biāo)紋理y_new中。
多次渲染傳遞。
在一些通用運(yùn)算中,我們會希望把前一次運(yùn)算結(jié)果傳遞給下一個運(yùn)算用來作為后繼運(yùn)算的輸入變量。但是在GPU中,一個紋理不能同時被讀寫,這就意味著我們要創(chuàng)建另外一個渲染通道,并給它綁定不同的輸入輸出紋理,甚至要生成一個不同的運(yùn)算內(nèi)核。有一種非常重要的技術(shù)可以用來解決這種多次渲染傳遞的問題,讓運(yùn)算效率得到非常好的提高,這就是“乒乓”技術(shù)。
關(guān)于乒乓技術(shù)
乒乓技術(shù),是一個用來把渲染輸出轉(zhuǎn)換成為下一次運(yùn)算的輸入的技術(shù)。在本文中(y_new =y_old +alpha*x) ,這就意味我們要切換兩個紋理的角色,y_new 和y_old 。有三種可能的方法來實(shí)現(xiàn)這種技術(shù)(看一下以下這篇論文Simon Green‘s FBO slides ,這是最經(jīng)典的資料了):
為每個將要被用作渲染輸出的紋理指定一個綁定點(diǎn),并使用函數(shù)glBindFramebufferEXT()來為每個渲染通道綁定一個不同的FBO.
只使用一個FBO,但每次通道渲染的時候,使用函數(shù)glBindFramebufferEXT()來重新綁定渲染的目標(biāo)紋理。
使用一個FBO和多個綁定點(diǎn),使用函數(shù)glDrawBuffer()來交換它們。
由于每個FBO最多有4個綁定點(diǎn)可以被使用,而且,最后一種方法的運(yùn)算是最快的,我們在這里將詳細(xì)解釋一下,看看我們是如何在兩個不同的綁定點(diǎn)之間實(shí)現(xiàn)“乒乓” 的。
要實(shí)現(xiàn)這個,我們首先需要一組用于管理控制的變量。
?。踓pp] view plaincopy// two textures identifiers referencing y_old and y_new
GLuint yTexID[2];
// ping pong management vars
int writeTex = 0;
int readTex = 1;
GLenum attachmentpoints[] = { GL_COLOR_ATTACHMENT0_EXT,
GL_COLOR_ATTACHMENT1_EXT
};
在運(yùn)算其間,我們只需要做的就是給內(nèi)核傳遞正確的參數(shù)值,并且每次運(yùn)算都要交換一次組組的索引值:
?。踓pp] view plaincopy// attach two textures to FBO
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT,
attachmentpoints[writeTex],
texture_Target, yTexID[writeTex], 0);
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT,
attachmentpoints[readTex],
texture_Target, yTexID[readTex], 0);
// enable fragment profile, bind program [。。。]
// enable texture x (read-only) and uniform parameter [。。。]
// iterate computation several times
for (int i=0; i《numIterations; i++) {
// set render destination
glDrawBuffer (attachmentpoints[writeTex]);
// enable texture y_old (read-only)
cgGLSetTextureParameter(yParam, yTexID[readTex]);
cgGLEnableTextureParameter(yParam);
// and render multitextured viewport-sized quad
// swap role of the two textures (read-only source becomes
// write-only target and the other way round):
swap();
}
Back to top
把所有東西放在一起
對本文附帶源代碼的一個簡要說明
在附帶的代碼例子中,使用到了本文所有闡述過的所有概念,主要實(shí)現(xiàn)了以下幾個運(yùn)算:
為每個數(shù)組生成一個浮點(diǎn)的紋理。
把初始化的數(shù)據(jù)傳輸?shù)郊y理中去 。
使用CG或者GLSL來生成一個片段著色器。
一個多次重復(fù)運(yùn)算的模塊,主要是用來演試“乒乓”技術(shù)。
把最終的運(yùn)算結(jié)果返回到主內(nèi)存中。
把結(jié)果與CPU的參考結(jié)果進(jìn)行比較。
執(zhí)行過行中的可變化部份
在代碼中,我們使用了一系列的結(jié)構(gòu)體來保存各種可能的參數(shù),主要是為了方便OpenGL的調(diào)用,例如:不同類型的浮點(diǎn)紋理擴(kuò)展,不同的紋理格式,不同的著色器之間的細(xì)微差別,等等。下面這段代碼就是這樣一個結(jié)構(gòu)體的示例,采用LUMINANCE格式,RECTANGLES紋理,及NV_float_buffer的擴(kuò)展。
[cpp] view plaincopyrect_nv_r_32.name = “TEXRECT - float_NV - R - 32”;
rect_nv_r_32.texTarget = GL_TEXTURE_RECTANGLE_ARB;
rect_nv_r_32.texInternalFormat = GL_FLOAT_R32_NV;
rect_nv_r_32.texFormat = GL_LUMINANCE;
rect_nv_r_32.shader_source = “float saxpy (”
“in float2 coords : TEXCOORD0,”
“uniform samplerRECT textureY,”
“uniform samplerRECT textureX,”
“uniform float alpha ) : COLOR {”
“float y = texRECT (textureY, coords);”
“float x = texRECT (textureX, coords);”
“return y+alpha*x; }”;
為了給不同的情況取得一個合適的工作版本,我們只須要查找和替換就可以了?;蛘呤褂玫诙€命令行參數(shù)如:rect_nv_r_32。在應(yīng)用程序中,一個全局變量textureParameters 指向我們實(shí)現(xiàn)要使用的結(jié)構(gòu)體。
命令行參數(shù)
在程序中,使用命令行參數(shù)來對程序進(jìn)行配置。如果你運(yùn)行該程序而沒帶任何參數(shù)的話,程序會輸出一個對各種不同參數(shù)的解釋。提醒大家注意的是:本程序?qū)γ钚袇?shù)的解釋是不穩(wěn)定的,一個不正確的參數(shù)有可能會造成程序的崩潰。因此我強(qiáng)烈建義大家使用輸出級的參數(shù)來顯示運(yùn)算的結(jié)果,這樣可以降低出現(xiàn)問題的可能性,尤其是當(dāng)你不相信某些運(yùn)算錯誤的時候。請查看包含在示例中的批處理文件。
測試模式
本程序可以用來對一個給定的GPU及其驅(qū)動的 結(jié)合進(jìn)行測試,主要是測試一下,看看哪種內(nèi)部格式及紋理排列是可以在FBO擴(kuò)展中被組合在一起使用的。示例中有一個批處理文件叫做:run_test_*.bat,是使用各種不同的命令行參數(shù)來運(yùn)行程序,并會生成一個報告文件。如果是在LINUX下,這個文件也可能當(dāng)作一個shell腳本來使用,只需要稍作修改就可以了。這ZIP文檔中包含有對一些顯卡測試后的結(jié)果。
基準(zhǔn)模式
這種模式被寫進(jìn)程序中,完全是為了好玩。它可以對不同的問題產(chǎn)成一個運(yùn)算時序,并在屏幕上生成MFLOP/s速率圖,和其它的一些性能測試軟件一樣。它并不代表GPU運(yùn)算能力的最高值,只是接近最高值的一種基準(zhǔn)性能測試。想知道如何運(yùn)行它的話,請查看命令行參數(shù)。
Back to top
附言
簡單對比一下Windows 和 Linux,NVIDIA 和 ATI 之間的差別
對于NVIDIA的顯卡,不管是Windows還是Linux,它們都提供了相同的函數(shù)來實(shí)現(xiàn)本教程中的例子。但如果是ATI的顯卡,它對LINUX的支持就不是很好。因此如果是ATI顯卡,目前還是建義在Windows下使用。
看一看這片相關(guān)的文章 table summarizing renderable texture formats on various hardware.
本文中提供下載的源代碼,是在NV4X以上的顯卡上編譯通過的。對于ATI的用戶,則要作以下的修改才行:在transferToTexture() 函數(shù)中,把NVIDIA相應(yīng)部份的代碼注釋掉,然使用ATI版本的代碼,如這里所描述的。
Cg 1.5 combined with the precompiled freeglut that ships with certain Linus distributions somehow breaks “true offscreen rendering” since a totally meaningless empty window pops up. There are three workarounds: Live with it. Use “real GLUT” instead of freeglut. Use plain X as described in the OpenGL.org wiki (just leave out the mapping of the created window to avoid it being displayed)。
問題及局限性
對于ATI顯卡,當(dāng)我們把數(shù)據(jù)傳送到紋理中去時,如果使用glTexSubImage2D(),會產(chǎn)生一個非常奇怪的問題:就是原本是RGBA排列的數(shù)據(jù),會被改變?yōu)锽GRA格式。這是一個已得到確認(rèn)的BUG,希望在以后的版本中能得到修正,目前只能用glDrawPixels() 來代替。
而對于NV3X系列顯卡,如果想用glDrawPixels() ,則要求一定要在GPU中綁定一個著色程序。因此這里用glTexSubImage()函數(shù)代替(其實(shí)對于所有的NVIDIA 的顯卡,都推薦使用該函數(shù))。
ATI顯卡,在GLSL中不支持rectangles紋理采樣,甚至這樣的著色代碼沒法被編譯通過。samplerRect 或sampler2DRect 被指定為保留的關(guān)鍵字,ARB_texture_rextangle的擴(kuò)展說明書中得到定義,但驅(qū)動沒有實(shí)現(xiàn)對它們的支持。可以用CG來代替。
在ATI中,當(dāng)我們使用glDrawPixels() 下載一個紋理的時候,如果紋理是被enable的,則會導(dǎo)致下載失敗,這不是一個BUG,但是也是一個有爭議性的問題,因為這樣會使程序難以調(diào)試。
對于NVIDIA的顯卡,我們不能把紋理渲染到紋理最大值的最后一行中去。也就是說,盡管我們用函數(shù)glGetIntegerv(GL_MAX_TEXTURE_SIZE,&maxtexsize); 得到的值是4096,但是你也只能渲染一張4095 x 4095 紋理。這是一個已知的BUG,同樣也希望以后能得到修正。
檢查OpenGL的錯誤
高度推薦大家在代碼中經(jīng)常使用以下函數(shù)來檢測OpenGL運(yùn)行過程中產(chǎn)生的錯誤。
?。踓pp] view plaincopyvoid checkGLErrors(const char *label) {
GLenum errCode;
const GLubyte *errStr;
if ((errCode = glGetError()) != GL_NO_ERROR) {
errStr = gluErrorString(errCode);
printf(“OpenGL ERROR: ”);
printf((char*)errStr);
printf(“(Label: ”);
printf(label);
printf(“) .”);
}
}
檢查FBO中的錯誤
EXT_framebuffer_object 擴(kuò)展,定義了一個很好用的運(yùn)行時Debug函數(shù)。這里只列出了它的一些常見的反回值作參考,要詳細(xì)解釋這些返回信息,請查看規(guī)格說明書的framebuffer completeness 部分。
?。踓pp] view plaincopybool checkFramebufferStatus() {
GLenum status;
status=(GLenum)glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
switch(status) {
case GL_FRAMEBUFFER_COMPLETE_EXT:
return true;
case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT:
printf(“Framebuffer incomplete,incomplete attachment ”);
return false;
case GL_FRAMEBUFFER_UNSUPPORTED_EXT:
printf(“Unsupported framebuffer format ”);
return false;
case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT:
printf(“Framebuffer incomplete,missing attachment ”);
return false;
case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT:
printf(“Framebuffer incomplete,attached images
must have same dimensions ”);
return false;
case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT:
printf(“Framebuffer incomplete,attached images
must have same format ”);
return false;
case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT:
printf(“Framebuffer incomplete,missing draw buffer ”);
return false;
case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT:
printf(“Framebuffer incomplete,missing read buffer ”);
return false;
}
return false;
}
檢查CG的錯誤
在CG中檢查錯誤有一些細(xì)微的不同,一個自寫入的錯誤處理句柄被傳遞給CG的錯誤處理回調(diào)函數(shù)。
?。踓pp] view plaincopy// register the error callback once the context has been created
cgSetErrorCallback(cgErrorCallback);
// callback function
void cgErrorCallback(void) {
CGerror lastError = cgGetError();
if(lastError) {
printf(cgGetErrorString(lastError));
printf(cgGetLastListing(cgContext));
}
}
檢查GLSL的錯誤
使用以下的函數(shù)來查看編譯的結(jié)果:
[cpp] view plaincopy/**
* copied from
* http://www.lighthouse3d.com/opengl/glsl/index.php?oglinfo
*/
void printInfoLog(GLhandleARB obj) {
int infologLength = 0;
int charsWritten = 0;
char *infoLog;
glGetObjectParameterivARB(obj,
GL_OBJECT_INFO_LOG_LENGTH_ARB,
&infologLength);
if (infologLength 》 1) {
infoLog = (char *)malloc(infologLength);
glGetInfoLogARB(obj, infologLength,
&charsWritten, infoLog);
printf(infoLog);
printf(“ ”);
free(infoLog);
}
}
大多數(shù)情況下,你可以使用以上查詢函數(shù),詳細(xì)內(nèi)容可以查看一下GLSL的規(guī)格說明書。還有另一個非常重要的查詢函數(shù),是用來檢查程序是否可以被連接:
[cpp] view plaincopyGLint success;
glGetObjectParameterivARB(programObject,
GL_OBJECT_LINK_STATUS_ARB,
&success);
if (!success) {
printf(“Shader could not be linked! ”);
}
評論
查看更多