1. 程式人生 > >OpenGL超級寶典第七版學習筆記-著色器儲存塊(shader storage block)

OpenGL超級寶典第七版學習筆記-著色器儲存塊(shader storage block)

20170321-shader storage block
1、shader storage block與uniform block最大的區別是在shader中可以對前者進行寫入操作,甚至是對其成員的記憶體進行原子操作,而後者在shader中是隻讀的。shader storage block擁有更高的大小上限
2、shader storage block支援std140的佈局方式,同時也支援std430的方式。std430的佈局方式能使整型和浮點型的陣列、包含他們的結構體的佈局更加緊湊,這一點正是std140最缺乏的,這樣能使像C++之類的程式語言的編譯器更加有效率的使用記憶體。
3、宣告一個shader storage block的例子:
layout(binding=0,std430)myShaderStorageBlock
{
vec4aa;
vec3 bb;
intarr[];
MyStructurestructure;
};

20170321-shader storage block-原子記憶體操作(atomic memory oprations)(2)
1、原子操作是指為了保證結果的正確性,如果在記憶體讀取後可能緊跟著有記憶體寫入的操作,那麼在這一系列操作期間是絕對不能被打斷的。(還有一種原子操作的概念是不可能被打斷的最小的操作,比如一條CPU指令,是不可能在指令執行期間被打斷的),類似於多執行緒的資料同步問題中的原子鎖的原子概念,產生這種概念的原因都是一樣的。由於GPU是並行工作的,各個shader的執行必然是並行的,可以看做是多執行緒的,那麼當多個shader需要同時訪問/修改同一處記憶體時,便可能產生資料不同步的問題。
2、為了解決這個問題,OpenGL提供了一系列原子操作的函式,這些函式分為int版本和uint版本的,各版本的返回值及引數都是相應的型別的。這一系列函式都是atomatic*()形式的。
3、在有多個shader程式在同一時間使用同一原子操作函式(呼叫)操作同一段記憶體時,這些呼叫會被序列化(serialized),即會順序執行,而不會同時執行。這樣就不會保證你在進行原子記憶體操作時會得到特定的返回值。
4、當你從緩衝區中讀取資料時,你不必擔心shader讀取的順序,但是當你需要通過變數或者通過原子操作對記憶體進行寫入時,就要注意規避風險了。
5、記憶體寫入風險分為三類:一是寫入完之後立即讀取(Read-After-Write,RAW),這種風險發生與否與系統架構有關。(有些系統架構下,編譯器或者CPU的換序優化會更改讀與寫的順序。);二是寫完之後立即再寫(Write-After-Write,WAW),即程式前後兩次往同一記憶體中寫入資料,同樣由於換序問題最終記憶體中的資料不一定是你以為的後寫入的那個值。三是讀完後立即寫入(WAR),產生的原因同上。
6、在主程式中使用分界符(barrier)解決資料同步的問題。barrier影響了他指定的記憶體子系統的記憶體操作順序。使用glMemoryBarrier(GLbitfield barriers)可以在主程式中插入分界符。該函式的唯一的引數指定了其影響的記憶體子系統。幾個常見用例如下:
(1)、引數指定為GL_SHADER_STORAGE_BARRIER_BIT且該函式在shader中的往shader storage block中寫資料的操作之後呼叫,那麼在資料寫入完之前,其他讀取這段記憶體的shader的操作將會被“阻塞”,寫入完之後,這些資料才會對其他shader可見。
(2)引數指定為GL_UNIFORM_BARRIER_BIT,且該函式同樣在shader中的往記憶體中寫資料的操作之後呼叫,而且之後想把這塊記憶體當做uniform buffer用。
(3)引數指定為GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT,道理同上。
7、在shader程式碼中使用barrier。比如在某函式中你定義了一些往緩衝區寫資料的操作,之後立即呼叫memoryBarrier()函式,之後讀取該緩衝區的內容,就能讀取到最新的值,若不呼叫memoryBarrier()函式,那麼之後獲取的資料可能還是原來的資料。
8、原子計數器(atomic  counter)。原子計數器是一種特殊的變數,它的儲存在不同的shader呼叫中是共享的。這種儲存是由緩衝區物件支撐的,並且GLSL為他提供了在緩衝區中進行自增和自減的函式。
9、在shader中宣告一個原子計數器的方法:
layout(binding=0) uniform atomic_uintmy_variable;
9、OpenGL提供了許多用以繫結儲存了原子計數器值的緩衝區的繫結點。在緩衝區中原子計數器變數是以偏移的方式進行儲存的。緩衝區繫結索引與原子計數器在繫結點繫結的緩衝區中的偏移值是可以用bingding與offset這兩個layout  qualifier進行指定的,並且可以應用於一個原子計數器的uniform變數的宣告,比如:
layout(binding=0,offset=8) uniform atomic_uintmyVariable;
10、GLSL原子計數器自增函式:uintatomicCounterIncrement(atomic_uint c);此函式獲取到c的值,讓其加1並寫回到c代表的原子計數器,但是返回的是原來的值。
11、GLSL原子計數器自增函式:uintatomicCounterDecrement(atomic_uint c);此函式獲取到c的值,讓其減1並寫回到c代表的原子計數器,返回的是新的值。
12、獲取原子計數器的值:uintatomicCounter(atomic_uint c);
13、shader執行時,原子計數器是儲存在圖形處理器的特殊的記憶體中的,這就是為什麼原子計數器的操作要比對一般的如shader storage block的成員的記憶體的原子操作快的多。但是當shader執行完畢後,這些值會被寫回到記憶體中,所以原子計數器的自增和自減操作也會面臨上面所說的記憶體操作風險。同樣使用glMemoryBarrier(GL_ATOMIC_COUNTER_BARRIER_BIT)可以為原子計數器新增分界符,以規避風險。
14、當你往某緩衝區寫完資料這一操作能體現在原子計數器的值的變化上,即操作完成後,
根據你的邏輯,原子計數器的值會變化,那麼應該在原子計數器變化前呼叫:
glMemoryBarrier(GL_ATOMIC_COUNTER_BARRIER_BIT ),以防止在資料操作完前就改變了原子計數器的值。如果你要用原子計數器改變一個緩衝區中的值,並且
改完之後要用這個緩衝區做其他事情,那麼在做其他
事情之前應該呼叫分界符函式,傳遞的引數應與你要做
其他事情時把他當做的型別一致