1、OpenGL提供了許多圖像類型來表達未編碼的圖像數據,它們與紋理相比:(1)表達的是單一層級的紋理不帶有mipmap;(2)不支持濾波等采樣操作、深度比較。


layout (binding = 4, rgba32f) uniform image2D imageIns;//rgba32f為image的數據格式format,在圖像不是只寫入的情況下這個屬性是必須的;需要與圖像類型相對應,binding = 4 綁定到4紋理單元,不是必須的
2、每種shader單個使用圖像的數量是有限的,通過GL_MAX_VERTEX_IMAGE_UNIFORMS(型如GL_MAX_*_IMAGE_UNIFORMS)進行查詢單個着色器能夠使用多少個圖像的uniform,GL_MAX_COMBINED_IMAGE_UNIFORMS給定了所有shader中可用圖像uniform的總和。因平台而已,使用圖像是最好查詢一下GL_MAX_COMBINED_IMAGE_UNITS_AND_FRAGMENT_OUTPUTS看看時候有其它的限制。
3、圖像對象綁定與紋理綁定差別有點大
void glBindImageTexture(GLuint unit, GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum access, GLenum format)//unit紋理單元;texture圖像id;layered為GL_TURE表示綁定一個數組同時忽略layer參數,GL_FALSE表示綁定單個圖像單元;layer圖像綁定到的層;access訪問域(GL_READ_ONLY GL_WRITE_ONLY GL_READ_WRITE),定義的是shader對圖像的訪問方式。
4、圖像的讀取
gvec4 imageLoad(readonly gimage1D image, int P)
gvec4 imageLoad(readonly gimage2D image, ivec2 P)
gvec4 imageLoad(readonly gimage1D image, ivec3 P)
gvec4 imageLoad(readonly gimage2DRect image, ivec2 P)
gvec4 imageLoad(readonly gimageCube image, ivec3 P)
gvec4 imageLoad(readonly gimageBuffer image, int P)
......
圖像數據的寫入
gvec4 imageStore(writeonly gimage1D image, int P, gvec4 data)
gvec4 imageStore(writeonly gimage2D image, ivec2 P, gvec4 data)
......
圖像數據大小獲取
int imageSize(gimage1D image)
int imageSize(gimageBuffer image)
ivec2 imageSize(gimage2D image)
......
5、對於傳統的幀緩沖光柵化來說,片元寫入的位置是着色器運行之前的固定流程決定的,但使用圖像存儲方式就能夠在着色器中寫入位置信息;圖像存儲的方式沒有限制的能夠多次寫入,因此能夠寫入大量的數據后用於幀緩沖或者其它的附件。總的來說,圖像存儲非常有利於大量的齊次數據處理。
6、對於大型結構化數據相較於圖像存儲方式,SSBO(shader storage buffer object) 是一個更好的選擇,在接口前使用buffer關健字。
layout (std430, binding = 0) buffer BufferObject//binding=0表示這個快必須和索引為0的GL_SHADER_STORAGE_BUFFER關聯
{
int diss;
int viewIndex;
vec4 points[];
};
對應於應用程序中需要使用如下配合使用
glGenBuffers(1, &ssbo);
glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo);
glBufferData(GL_SHADER_STORAGE_BUFFER, size, nullptr, GL_DYNAMIC_COPY);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, bindindex, ssbo);
7、OpenGL不提供shader在同一個繪制命令中的執行順序,甚至是兩個繪制命令之間也不管,因此能夠高度並行得到良好的性能。所以對於同時讀寫一塊內存有時需要注意,如下
#version 420 core
layout (r32ui) uniform uimage2D overdrawCount;//unity scene有一個overdraw的顯示模式,表示每個像素被畫了多少次,與這個是一個意思
void main
{
uint count = imageLoad(overdarwCount, ivec2(gl_FrageCoord.xy));
count = count + 1;
iamgeStore(overdarwCount, ivec2(gl_FragCoord.xy), count);
}
上面這種做法會產生錯誤的結果,因為讀寫循環會被其它實例的相同shader中斷執行。下面圖例中,希望得到的是4但實際上得到的是3。

8、為了避免7中的問題,OpenGL提供了一系列原子(atomic)函數直接操作被共享的內存。它們擁有兩個方面的屬性能夠保障原子性,其一保障在"單一"的時間內執行而不被中斷,其二硬件方面能夠保障對同一內存的多個操作序列地依次執行。需要注意的是,這一系列函數保障的是一個關鍵操作之間的連續性而不互相影響計算結果,而不是保障所有調用的執行順序,並且只能用於整形哦。(Note that there is still no guarantee of order-just a guarantee that all invocations execute their operation without stepping on each other's results.)那么對於7中的例子,可修改如下:
#version 420 core
layout (r32ui) uniform uimage2D overdrawCount;
void main()
{
imageAtomicAdd(overdrawCount, ivec2(gl_FragCoord.xy), 1);
}
這一些列函數有加減法、邏輯操作、比較、交換等函數,返回值都是更新之前的值
uint imageAtomicAdd(IMAGE_PARAMS mem, uint data)//IMAGE_PARAMS 宏可以定義為所有圖像類型
uint imageAtomicXor(IMAGE_PARAMS mem, uint data)
uint imageAtomicCompSwap(IMAGE_PARAMS mem, uint compare, uint data)
......
9、除了圖像外緩存變量(buffer 關鍵字聲明)也有一系列的原子函數
uint atomicAdd(inout uint mem, uint data)//inout 的關鍵字參數用於引用內存位置
uint atomicXor(inout uint mem, uint data)
......
10、OpenGL大部分情況下CPU與GPU都是異步交互,但在一些情況下需要采用同步的方式,因此產生了同步對象(sync object)的概念,也稱柵欄(fence)。它本質上是一個命令流中的標記,可以在繪制或者狀態變化命令過程中被發送。它的起始生命為無信號狀態,在GPU執行過后變為有信號狀態,任何時候它的狀態都能夠被應用程序查詢。
GLsync glFenceSync(GLenum condition, GLbitfield flags)//創建一個刪欄對象並插入到命令流中,condition必須是GL_SYNC_GPU_COMMANDS_COMPLETE,flag保留參數設0
void glGetSynciv(GLsync sync, GLenum pname, GLsizei bufSize, GLsizei* length, GLint* values)//length將被寫入values的字節大小,pname為要查詢的信息如GL_SYNC_STATUS
GLenum glClientWaitSync(GLsync sync, GLbitfields flags, GLuint64 timeout)//timeout是超時時間單位為納秒(ns),這個函數能夠強制應用程序等待到達刪欄,它可能返回一下內容
1)GL_ALREADY_SIGNALED 調用這個函數的時刻就已經有信號了
2)GL_CONDITION_SATISFIED 調用時沒有信號當在超時前有信號了
3)GL_TIMEOUT_EXPIRED 超時了
4)GL_WAIT_FAILED 調用失敗,如參數錯誤
11、同步對象一個重要用途是判斷GPU是否已經使用過映射緩沖中的數據,如果緩沖(或者一部分)使用glMapBufferRange函數帶有GL_MAP_UNSYNCHRONIZED_BIT進行映射,就需要這種判斷。
glBindVertexArry(vao);
glDrawArrays(GL_TRIANGLES, 0 , count);
GLsync sync = glFenceSync();
void* data = glMapBufferRange(GL_UNIFORM_BUFFER, 0, size, GL_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT);
......//do something
switch(glClientWaitSync(sync, 0, 1000000));
glDeleteSync(sync);
......//do something
glUnmapBuffer(GL_UNIFORM_BUFFER);
12、在多個環境中共享對象的一般做法是在源環境中創建刪欄在目標環境中調用glWaitSync獲取信號
void glWaitSync(GLsync sync, GLbitfield flags, GLuint64 timeout)//flags 必須設置為0,timeout必須設置為GL_TIMEOUT_IGNORED,這兩個參數都由設備實現決定
13、幾個關鍵字
1)volatile 禁優化關鍵字,可用於全局變量或者函數參數,但是變量一旦被聲明為volatile的,那么不能作為一個參數不帶這個關鍵字的函數參數
2)restrict 表示某個圖像中的引用數據並不是其它圖像的數據別名,都是自己的東西,可以進行存在破壞性優化。(默認,編譯器會假設外部緩存可能存在別名替代的情形,因此不會使用對此有破壞性的優化方式,但是GLSL認為shader中不存在變量和參數的別名問題,所以優化功能全開)
3)readonly、writeonly 意思上很明顯,需要注意的是原子操作是 讀取-修改-寫入 的流程,所以被修飾的變量時無法用於原子操作的
4)coherent 現代GPU框架都是多級緩存,上級緩存的修改不會立即寫回下層,那么對於被共享的內容在不連續的情況下,內存的修改如何知會所有使用者是一個問題。兩種思路,其一忽略所欲緩存寫回后重新獲取但代價高昂,其二是將其移到下一個層級的緩存保證連續性
14、內存屏障,用於解決編譯器對內存操作進行從新排序可能導致意想不到的結果。
#version 420 core
layout (rgba32f) uniform coherent image2D imageIns;
void functionUsingBarriers(coherent uimageBuffer i)
{
uint val;
do { val = imageLoad(i, 0).x; }while (val != gl_PrimitiveID);
vec4 frag = imageLoad(imageIns, gl_FragCoord.xy)
......// do something to frag
imageStore(imageIns, gl_FragCoord.xy, frag);
memoryBarrier();//確保存儲操作已經完成
imageStore(1, 0, gl_PrimitiveID + 1);
memoryBarrier();//確保存儲結果進入內存
}
應用程序中也可以適應OpenGL的內存屏障
void glMemoryBarrier(GLbitfield flags);// 關於flags值得設置查閱 http://docs.gl/gl4/glMemoryBarrier
15、預先深度與模板測試
許多物體產生的片元會被其它的物體遮擋完全看不見,但是這些片元同樣執行了fragment shader,這是一種浪費,因此OpenGL提供了一種將深度與模板測試提前的方法,使用early_fragment_tsets 修飾輸入,這一特性需要4.2版本及以上或者支持ARB_shader_image_load_store擴展。但限制是在fragment shader中修改片元的gl_FragDepth時需要指明修改的值均不小於、或者不大於原來的值,否則該功能會被忽略。
layout (early_fragment_tests) in;
