OPenGL中的缓冲区对象
引自:http://blog.csdn.net/mzyang272/article/details/7655464
在許多OpenGL操作中,我們都向OpenGL發送一大塊數據,例如向它傳遞需要處理的頂點數組數據。傳輸這種數據可能非常簡單,例如把數據從系統的內存中復制到圖形卡。但是,由于OpenGL是按照客戶機-服務器模式設計的,在OpenGL需要數據的任何時候,都必須把數據從客戶機內存傳輸到服務器。如果數據并沒有修改,或者客戶機和服務器位于不同的計算機(分布式渲染),數據的傳輸可能會比較緩慢,或者是冗余的。
OpenGL 1.5版本增加了緩沖區對象(buffer object),允許應用程序顯式地指定把哪些數據存儲在圖形服務器中。
當前版本的OpenGL中使用了很多不同類型的緩沖區對象:
從OpenGL 1.5開始,數組中的頂點數據可以存儲在服務器端緩沖區對象中。
在OpenGL 2.1中,加入了在緩沖區對象中存儲像素數據(例如,紋理貼圖或像素塊)的支持。
OpenGL 3.1增加了統一緩沖對象(uniform buffer object)以存儲成塊的、用于著色器的統一變量數據。
讀者還會發現OpenGL中有很多其他的功能用到了術語“對象”,但是這些功能并不都適用于存儲塊數據。例如,(OpenGL 1.1引入的)紋理對象只是封裝了和紋理貼圖相關聯的各種狀態設置。同樣,OpenGL 3.0中增加的頂點數組對象,封裝了和使用頂點數組相關的狀態參數。這些類型的對象允許我們使用較少的函數調用就能夠修改大量的狀態設置。為了使性能最大化,只要習慣它們的操作,就應該盡可能地嘗試使用它們。
注意:通過對象的名字來引用它,其名字是一個無符號的整型標識符。從OpenGL 3.1開始,所有的名字必須由OpenGL使用glGen*()函數之一來生成,不再接受用戶定義的名字。
創建緩沖區對象
任何非零的無符號整數都可以作為緩沖區對象的標識符使用。可以任意選擇一個有代表性的值,也可以讓OpenGL負責分配和管理這些標識符。這兩種做法有什么區別呢?讓OpenGL分配標識符可以保證避免重復使用已被使用的緩沖區對象標識符,從而消除無意修改數據的風險。
為了讓OpenGL分配緩沖區對象標識符,可以調用glGenBuffers()函數。
void?glGenBuffers(GLsizei?n,?GLuint?*buffers);?在buffers數組中返回n個當前未使用的名稱,表示緩沖區對象。在buffers數組中返回的名稱并不需要是連續的整數。
返回的名稱被標記為已使用,以便分配給緩沖區對象。但是,當它們被綁定之后,它們只獲得一個合法的狀態。
零是一個被保留的緩沖區對象名稱,從來不會被glGenBuffers()作為緩沖區對象返回。
還可以調用glIsBuffer()函數,判斷一個標識符是否是一個當前被使用的緩沖區對象的標識符。
GLboolean glIsBuffer(GLuint buffer);
如果buffer是一個已經綁定的緩沖區對象的名稱,而且還沒有刪除,這個函數返回GL_TRUE。
如果buffer為0或者它不是一個緩沖區對象的名稱,這個函數返回GL_FALSE。
激活緩沖區對象
為了激活緩沖區對象,首先需要將它綁定。綁定緩沖區對象表示選擇未來的操作(對數據進行初始化或者使用緩沖區對象進行渲染)將影響哪個緩沖區對象。也就是說,如果應用程序有多個緩沖區對象,就需要多次調用glBindBuffer()函數:一次用于初始化緩沖區對象以及它的數據,以后的調用要么選擇用于渲染的緩沖區對象,要么對緩沖區對象的數據進行更新。
為了禁用緩沖區對象,可以用0作為緩沖區對象的標識符來調用glBindBuffer()函數。這將把OpenGL切換為默認的不使用緩沖區對象的模式。
void?glBindBuffer(GLenum?target,?GLuint?buffer);?指定了當前的活動緩沖區對象。target必須設置為GL_ARRAY_BUFFER、GL_ELEMENT_ARRAY_BUFFER、GL_PIXEL_PACK_BUFFER、GL_PIXEL_UNPACK_BUFFER、GL_COPY_READ_BUFFER、GL_COPY_WRITE_BUFFER、GL_TRANSFORM_ FEEDBACK_ BUFFER或者GL_UNIFORM_BUFFER。buffer指定了將要綁定的緩沖區對象。
glBindBuffer()完成3個任務之一:①當buffer是一個首次使用的非零無符號整數時,它就創建一個新的緩沖區對象,并把buffer分配給這個緩沖區對象,作為它的名稱。②當綁定到一個以前創建的緩沖區對象時,這個緩沖區對象便成為活動的緩沖區對象。③當綁定到一個值為零的buffer時,OpenGL就會停止使用緩沖區對象。
用數據分配和初始化緩沖區對象
一旦綁定了一個緩沖區對象,就需要保留空間以存儲數據,這是通過調用glBufferData()函數實現的。
void?glBufferData(GLenum?target,?GLsizeiptr?size,?const?GLvoid?*data, ?GLenum?usage);?分配size個存儲單位(通常是字節)的OpenGL服務器內存,用于存儲頂點數據或索引。以前所有與當前綁定對象相關聯的數據都將刪除。
target可以是GL_ARRAY_BUFFER(表示頂點數據)、GL_ELEMENT_ARRAY_BUFFER(表示索引數據)、G L _ P I X E L _ U N PACK_BUFEER( 表示傳遞給O p e n G L 的像素數據) 或GL_PIXEL_PACK_BUFFER(表示從OpenGL獲取的像素數據)、GL_COPY_READ_BUFFER 和GL_COPY_WRITE_BUFFER(表示在緩沖區之間復制數據)、GL_TEXTURE_BUFFER(表示作為紋理緩沖區存儲的紋理數據)、GL_TRANSFORM_FEEDBACK_BUFFER(表示執行一個變換反饋著色器的結果),或者GL_UNIFORM_BUFFER(表示統一變量值)。
size是存儲相關數據所需要的內存數量。這個值通常是數據元素的個數乘以它們各自的存儲長度。
data可以是一個指向客戶機內存的指針(用于初始化緩沖區對象),也可以是NULL。如果它傳遞的是一個有效的指針,size個單位的存儲空間就從客戶機復制到服務器。如果它傳遞的是NULL,這個函數將會保留size個單位的存儲空間供以后使用,但不會對它進行初始化。
usage提供了一個提示, 就是數據在分配之后將如何進行讀取和寫入。它的有效值包括GL_STREAM_DRAW、GL_STREAM_READ、GL_STREAM_COPY、GL_STATIC_DRAW、GL_STATIC_READ、GL_STATIC_COPY、GL_DYNAMIC_DRAW、GL_DYNAMIC_READ、GL_DYNAMIC_COPY。
如果請求分配的內存數量超過了服務器能夠分配的內存, g l B u f f e r D a t a ( ) 將返回GL_OUT_OF_MEMORY。如果usage并不是允許使用的值之一,這個函數就返回GL_INVALID_VALUE。glBufferData()首先在OpenGL服務器中分配內存以存儲數據。如果請求的內存太多,它會設置GL_OUT_OF_MEMORY錯誤。如果成功分配了存儲空間,并且data參數的值不是NULL,size個存儲單位(通常是字節)就從客戶機的內存復制到這個緩沖區對象。但是,如果需要在創建了緩沖區對象之后的某個時刻動態地加載數據,可以把data參數設置為NULL,為數據保留適當的存儲空間,但不對它進行初始化。
glBufferData()的最后一個參數usage是向OpenGL提供的一個性能提示。根據usage參數指定的值,
OpenGL可能會對數據進行優化,進一步提高性能。它也可以選擇忽略這個提示。在緩沖區對象數據
上,可以進行3種類型的操作:
1) 繪圖:客戶機指定了用于渲染的數據。
2) 讀取:從OpenGL緩沖區讀取(例如幀緩沖區)數據值,并且在應用程序中用于各種與渲染并不直接相關的計算過程。
3) 復制:從OpenGL緩沖區讀取數據值,作為用于渲染的數據。
另外,根據數據更新的頻率,有幾種不同的操作提示描述了數據的讀取頻率或在渲染中使用的頻率:
流模式:緩沖區對象中的數據常常需要更新,但是在繪圖或其他操作中使用這些數據的次數較少。
靜態模式:緩沖區對象中的數據只指定1次,但是這些數據被使用的頻率很高。
動態模式:緩沖區對象中的數據不僅常常需要進行更新,而且使用頻率也非常高。
usage參數可能使用的值見表2-6。
表2-6 glBufferData()的usage參數的值
更新緩沖區對象的數據值?
有兩種方法可以更新存儲在緩沖區對象中的數據。第一種方法假設我們已經在應用程序的一個緩沖區中準備了相同類型的數據。glBufferSubData()將用我們提供的數據替換被綁定的緩沖區對象的一些數據子集。
void?glBufferSubData(GLenum?target,?GLintptr?offset,?GLsizeiptr?size, ?const?GLvoid?*data);?用data指向的數據更新與target相關聯的當前綁定緩沖區對象中從offset(以字節為單位)開始的size個字節數據。target必須是GL_ARRAY_BUFFER、GL_ELEMENT_ ARRAY_BUFFER、GL_PIXEL_UNPACK_BUFFER、GL_PIXEL_PACK_BUFFER、GL_COPY_READ_BUFFER、G L _ C O P Y _ W R I T E _ B U F F E R 、G L _ T R A N S F O R M _ F E E D B A C K _ B U F F E R 或GL_UNIFORM_BUFFER。
如果size小于0或者size+offset大于緩沖區對象創建時所指定的大小,glBufferSubData()將產生一個GL_INVALID_VALUE錯誤。
第二種方法允許我們更靈活地選擇需要更新的數據。glMapBuffer()返回一個指向緩沖區對象的指針,可以在這個緩沖區對象中寫入新值(或簡單地讀取數據,這取決于內存訪問權限),就像對數組進行賦值一樣。在完成了對緩沖區對象的數據更新之后,可以調用glUnmapBuffer(),表示已經完成了對數據的更新。
glMapBuffer()提供了對緩沖區對象中包含的整個數據集合的訪問。如果需要修改緩沖區中的大多數數據,這種方法很有用,但是,如果有一個很大的緩沖區并且只需要更新很小的一部分值,這種方法效率很低。
GLvoid?*glMapBuffer(GLenum?target,?GLenum?access);?返回一個指針,指向與t a rg e t 相關聯的當前綁定緩沖區對象的數據存儲。t a rg e t 可以是GL_ARRAY_BUFFER、GL_ELEMENT_ARRAY_BUFFER、GL_PIXEL_PACK_BUFFER、GL_PIXEL_UNPACK_BUFFER、GL_COPY_READ_BUFFER、GL_COPY_WRITE_BUFFER、GL_TRANSFORM_FEEDBACK_BUFFER或GL_UNIFORM_BUFFER。a c c e s s 必須是GL_READ_ONLY、GL_WRITE_ONLY或GL_READ_WRITE之一,表示客戶可以對數據進行的操作。
如果這個緩沖區無法被映射(把OpenGL錯誤狀態設置為GL_OUT_OF_MEMORY)或者它以前已經被映射(把OpenGL錯誤狀態設置為GL_INVALID_OPERATION),glMapBuffer()將返回NULL。
在完成了對數據存儲的訪問之后,可以調用glUnmapBuffer()取消對這個緩沖區的映射。
GLboolean?glUnmapBuffer(GLenum?target);?表示對當前綁定緩沖區對象的更新已經完成, 并且這個緩沖區可以釋放。t a rg e t 必須是GL_ARRAY_BUFFER、GL_ELEMENT_ARRAY_BUFFER、GL_PIXEL_PACK_BUFFER,GL_PIXEL_UNPACK_BUFFER、GL_COPY_READ_BUFFER、GL_COPY_WRITE_BUFFER、GL_TRANSFORM_FEEDBACK_BUFFER或GL_UNIFORM_BUFFER。
下面是一個簡單的例子,說明了如何選擇性地更新數據元素。我們將使用glMapBuffer()獲取一個指向緩沖區對象中的數據(這些數據包含了三維的位置坐標)的指針,然后,只更新z坐標。
GLfloat* data; data = (GLfloat*) glMapBuffer(GL_ARRAY_BUFFER, GL_READ_WRITE); if (data != (GLfloat*) NULL) { for( i = 0; i < 8; ++i ) data[3*i+2] *= 2.0; /* Modify Z values */ glUnmapBuffer(GL_ARRAY_BUFFER); } else { /* Handle not being able to update data */ }如果只需要更新緩沖區中相對較少的值(與值的總體數目相比),或者更新一個很大的緩沖區對象中的很小的連續范圍的值,使用glMapBufferRange()效率更高。它允許只修改所需的范圍內的數據值。
GLvoid *glMapBufferRange(GLenum target, GLintptr offset, GLsizeiptr length, GLbitfield access);?
返回一個指針, 指向與t a rg e t 相關聯的當前綁定緩沖區對象的數據存儲。t a rg e t 可以是GL_ARRAY_BUFFER、GL_ELEMENT_ARRAY_BUFFER、GL_PIXEL_PACK_BUFFER、GL_PIXEL_UNPACK_BUFFER、GL_COPY_READ_BUFFER、GL_COPY_WRITE_BUFFER、GL_TRANSFORM_FEEDBACK_BUFFER或GL_UNIFORM_BUFFER。offset和length指定了映射的范圍。access是GL_MAP_READ_BIT和GL_MAP_WRITE_BIT的一個位掩碼組合,表示客戶可以對數據進行的操作;也可以是GL_MAP_INVALIDATE_RANGE_BIT、GL_MAP_INVALIDATE_BUFFER_BIT、GL_MAP_FLUSH_EXPLICIT_BIT或GL_MAP_UNSYNCHRONIZED_BIT,它們針對OpenGL應該如何管理緩沖區中的數據給出提示。
如果發生錯誤,glMapBufferRange()將返回NULL。如果offset或length為負值,或者offset+length比緩沖區的大小還要大,將會產生GL_INVALID_VALUE。如果不能獲取足夠的內存來映射緩沖區,將會產生G L _ O U T _ O F _ M E M O RY錯誤。如果發生如下的任何一種情況, 將會產生GL_INVALID_OPERATION: 緩沖區已經映射; a c c e s s 沒有GL_MAP_READ_BIT或G L _ M A P _ W R I T E _ B I T 設置; a c c e s s 擁有G L _ M A P _ R E A D _ B I T 設置, 并且GL_MAP_INVALIDATE_RANGE_BIT、GL_MAP_INVALIDATE_BUFFER_BIT或GL_MAP_UNSYNCHRONIZED_BIT中的任何一個也設置了;access中的GL_MAP_WRITE_BIT和GL_MAP_FLUSH_EXPLICIT_BIT都設置了。
使用glMapBufferRange(),可以通過在access中設置額外的位來指定可選的提示。這些標志描述了在映射之前OpenGL服務器需要如何保護緩沖區中原有的數據。這個提示用來幫助OpenGL實現確定需要保留哪些數據值,以及保持這些數據的任何內部拷貝正確和一致需要達到多長時間。
正如表2-7中所述,當使用glMapBufferRange()映射一個緩沖區的時候,若在access標志中指定了GL_MAP_FLUSH_EXPLICIT_BIT,應該通過調用glFlushMappedBufferRange()向OpenGL表明映射緩沖區中的范圍需要修改。
表2-7 glMapBufferRange()的access參數值
?
GLvoid?glFlushMappedBufferRange(GLenum?target,?GLintptr?offset,GLsizeiptr?length);??
表示一個緩沖區范圍中的值已經修改,這可能引發OpenGL服務器更新緩沖區對象的緩存版本。target必須是如下值之一:GL_ARRAY_BUFFER, GL_ELEMENT_ARRAY_BUFFER、GL_PIXEL_PACK_BUFFER、GL_PIXEL_UNPACK_BUFFER、GL_COPY_READ_BUFFER、GL_COPY_WRITE_BUFFER, GL_TRANSFORM_FEEDBACK_BUFFER或GL_UNIFORM_BUFFER。offset和length指定了映射緩沖區區域的范圍,它們相對于緩沖區映射范圍的開始處。
如果o f f s e t 或l e n g t h 為負值, 或者o f f s e t + l e n g t h 比映射區域的大小還要大, 將會產生GL_INVALID_VALUE。如果沒有緩沖區綁定到target(例如,在glBindBuffer()調用中,0指定為綁定到target的緩沖區),或者如果綁定到target的緩沖區沒有映射,或者如果它映射了卻沒有設置GL_MAP_FLUSH_EXPLICIT_BIT,將會產生一個GL_INVALID_OPERATION錯誤。
在緩沖區對象之間復制數據
有時候,我們可能需要把數據從一個緩沖區對象復制到另一個緩沖區對象。在OpenGL 3.1以前的版本中,這個過程分兩步:
1) 把數據從緩沖區對象復制到應用程序的內存中。可以通過以下兩種方法之一來做到:映射緩沖區并將其復制到本地內存緩沖區中,或者調用glGetBufferSubData()從服務器復制數據。
2) 通過綁定到新的對象,然后使用glBufferData()發送新的數據(或者如果只是替換一個子集,使用glBufferSubData()),來更新另一個緩沖區對象中的數據。也可以映射緩沖區,然后把數據從一個本地內存緩沖區復制到映射的緩沖區。
在OpenGL 3.1中,glCopyBufferSubData()命令復制數據,而不需要迫使數據在應用程序的內存中做短暫停留。
void glCopyBufferSubData(GLenum readbuffer, GLenum writebuffer, GLintptr readoffset, GLintptr writeoffset, GLsizeiptr size);?
把數據從與readbuffer相關聯的緩沖區對象復制到綁定到writebuffer的緩沖區對象。readbuffer和writebuffer必須是如下的值之一: GL_ARRAY_BUFFER, GL_COPY_READ_BUFFER、GL_COPY_WRITE_BUFFER、GL_ELEMENT_ARRAY_BUFFER, GL_PIXEL_PACK_BUFFER、GL_PIXEL_UNPACK_BUFFER、GL_TEXTURE_BUFFER、GL_TRANSFORM_ FEEDBACK_BUFFER或GL_UNIFORM_BUFFER。
readoffset和size指定了復制到目標緩沖區對象中的數據的數量,會從writeoffset開始替換同樣大小的數據。
下面的情形會導致GL_INVALID_VALUE錯誤:readoffset、writeoffset或size為負值;readoffset +size超過了綁定到readbuffer的緩沖區對象的范圍;writeoffset + size超過了綁定到writebuffer的緩沖區對象的范圍;如果readbuffer和writebuffer綁定到同一個對象,并且readoffset和size所指定的區域與writeoffset和size所確定的區域有交叉。
如果readbuffer或writebuffer中的任意一個綁定為0,或者任意一個緩沖區是當前映射的,將會產生L_INVALID_OPERATION錯誤。
清除緩沖區對象
完成了對緩沖區對象的操作之后,可以釋放它的資源,并使它的標識符可以由其他緩沖區對象使用。為此,可以調用glDeleteBuffers()。被刪除的當前綁定緩沖區對象的所有綁定都將重置為零。
void?glDeleteBuffers(GLsizei?n,?const?GLuint?*buffers);?刪除n個緩沖區對象,它們的名稱就是buffers數組的元素。釋放的緩沖區對象可以被復用(例如,通過調用glGenBuffers())。
如果一個緩沖區對象是在綁定時刪除的,這個對象的所有綁定都重置為默認的緩沖區對象,就像以0作為指定的緩沖區對象參數調用了glBindBuffer()一樣。如果試圖刪除不存在的緩沖區對象或名稱為0的緩沖區對象,這個操作將被忽略,并不會產生錯誤。
使用緩沖區對象存儲頂點數組數據
要在緩沖區對象中存儲頂點數組數據,需要給應用程序添加如下步驟:
1) 生成緩沖區對象標識符(這個步驟是可選的)。
2) 綁定一個緩沖區對象,確定它是用于存儲頂點數據還是索引。
3) 請求數據的存儲空間,并且對這些數據元素進行初始化(后一個步驟可選)。
4) 指定相對于緩沖區起始位置的偏移量,對諸如glVertexPointer()這樣的頂點數組函數進行初始化。
5) 綁定適當的緩沖區對象,用于渲染。
6) 使用適當的頂點數組渲染函數進行渲染,例如glDrawArrays()或glDrawElements()。
如果想初始化多個緩沖區對象,就需要為每個緩沖區對象重復步驟2)~4)。
頂點數組數據的所有“格式”都適用于緩沖區對象。如第2.6.2節所述,頂點、顏色、光照法線或其他任何類型的相關聯頂點數據都可以存儲在緩沖區對象中。另外,第2.6.6節所描述的混合頂點數組數據也可以存儲在緩沖區對象中。不論是哪種情況,我們都將創建一個緩沖區對象,保存所有作為頂點數組使用的數據。
就像在客戶機的內存中指定內存地址一樣(OpenGL應該在客戶機的內存中訪問頂點數組數據),需要根據機器單位(通常是字節)指定緩沖區對象中數據的偏移量。為了幫助讀者理解偏移量的計算,我們將使用下面這個宏來簡化偏移量的表達形式:
#define?BUFFER_OFFSET(bytes)?((GLubyte*)?NULL?+?(bytes))?例如,如果每個頂點的顏色和位置數據是浮點類型,也許它們可以用下面這個數組來表示:
GLfloat vertexData[][6] = { { R0, G0, B0, X0, Y0, Z0 }, { R1, G1, B1, X1, Y1, Z1 }, ... { Rn, Gn, Bn, Xn, Yn, Zn } };這個數組用于初始化緩沖區對象,可以用兩個獨立的頂點數組調用來指定數據,其中一個表示顏色,另一個表示頂點:
glColorPointer(3, GL_FLOAT, 6*sizeof(GLfloat),BUFFER_OFFSET(0)); glVertexPointer(3, GL_FLOAT, 6*sizeof(GLfloat), BUFFER_OFFSET(3*sizeof(GLfloat)); glEnableClientState(GL_COLOR_ARRAY); glEnableClientState(GL_VERTEX_ARRAY);相反, 由于v e r t e x D a t a 中的數據與一個混合頂點數組的格式相匹配, 因此可以使用glInterleavedArrays()來指定頂點數組數據:
glInterleavedArrays(GL_C3F_V3F,?0,?BUFFER_OFFSET(0));?示例程序2-17綜合了所有這些內容,演示了如何使用包含頂點數據的緩沖區對象。這個例子創建了兩個緩沖區對象,一個包含頂點數據,另一個包含索引數據。
示例程序2-17 在緩沖區對象中使用頂點數據
#define VERTICES 0 #define INDICES 1 #define NUM_BUFFERS 2 GLuint buffers[NUM_BUFFERS]; GLfloat vertices[][3] = { { -1.0, -1.0, -1.0 }, { 1.0, -1.0, -1.0 }, { 1.0, 1.0, -1.0 }, { -1.0, 1.0, -1.0 }, { -1.0, -1.0, 1.0 }, { 1.0, -1.0, 1.0 }, { 1.0, 1.0, 1.0 }, { -1.0, 1.0, 1.0 } }; GLubyte indices[][4] = { { 0, 1, 2, 3 }, { 4, 7, 6, 5 }, { 0, 4, 5, 1 }, { 3, 2, 6, 7 }, { 0, 3, 7, 4 }, { 1, 5, 6, 2 } }; glGenBuffers(NUM_BUFFERS, buffers); glBindBuffer(GL_ARRAY_BUFFER, buffers[VERTICES]); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); glVertexPointer(3, GL_FLOAT, 0, BUFFER_OFFSET(0)); glEnableClientState(GL_VERTEX_ARRAY); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffers[INDICES]); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices GL_STATIC_DRAW); glDrawElements(GL_QUADS, 24, GL_UNSIGNED_BYTE, BUFFER_OFFSET(0));?
?
轉載于:https://www.cnblogs.com/Anita9002/p/4980653.html
總結
以上是生活随笔為你收集整理的OPenGL中的缓冲区对象的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: iOS xocde编译报错 NSObj
- 下一篇: metaprogramming笔记