我們今天來大致講一下Metal Shading Language 著色語言規(guī)范蜡峰。看一下平時寫Metal 需要注意些什么~
一臭蚁、Metal Shading Language
1邮偎、定義及作用
Metal著?語? 是?來編寫 3D 圖形渲染邏輯
和 并?計算核?邏輯
的??編程語?. 當你使? Metal 框架來完成APP 的實現(xiàn)時則需要使?Metal 編程語?;
Metal 語?使?Clang 和 LLVM 進?編譯處理~
Metal 基于C++ 11.0 語?設(shè)計的,我們主要?來編寫 在 GPU 上執(zhí)?的圖像渲染邏輯代碼
以及 通? 并?計算邏輯代碼
;
2棵里、注意點
-
對于指針的限制:
- Metal圖形和并?計算函數(shù)?到的?參數(shù); 如果是指針必須使?地址空間修飾符(device, threadgroup, constant)
- 不?持函數(shù)指針
- 函數(shù)名不能出現(xiàn) main
Metal 像素坐標系統(tǒng): Metal中 紋理/幀緩存區(qū)attachment 的像素使?的坐標系統(tǒng)的原點是左上?(與OpenGL里的不同)润文。
3、Metal語? 與 C++ 11.0 的不同之處
雖然 Metal 是基于 C++ 11.0 語?設(shè)計的衍慎,但是有一些 C++ 11.0 中的特性語法转唉,在Metal中 不支持
:
- Lambda 表達式
- 遞歸函數(shù)調(diào)?
- 動態(tài)轉(zhuǎn)換操作符
- 類型識別
- 對象創(chuàng)建new 和銷毀delete 操作符
- 操作符 noexcept
- goto 跳轉(zhuǎn)
- 變量存儲修飾符register 和 thread_local
- 虛函數(shù)修飾符
- 派?類
- 異常處理
- C++ 標準庫在Metal 語?中也不可使?
二、基礎(chǔ)數(shù)據(jù)類型
與GLSL語言不同稳捆,float類型后面允許帶f
或者F
募壕。同樣,half類型后面允許帶h
或者H
河狐。
1、標量
- 注意 unsigned 修飾的可以簡寫成 u款侵。例如:unsigned char 簡寫 uchar
bool a = true;
char b = 5;
int c = 15;
//用于表示內(nèi)存空間的大小
size_t d = 1;
ptrdiff_t e = 2;
2、向量
booln 侧纯、charn 新锈、shortn 、intn 眶熬、ucharn 妹笆、ushortn 、uintn 娜氏、halfn 拳缠、floatn(n指的是維度 1~4)
//初始化類型1:直接賦值
bool2 A = {1,0};
float4 pos ={1.0,2.0,3.0,4.0};
//初始化類型2:通過內(nèi)建變量賦值
bool2 A = bool2(1,0);
float4 pos = float4(1.0,2.0,3.0,4.0);
//根據(jù)下標取值
float x = pos[0];
float y = pos[1];
//通過for循環(huán)賦值
float4 VB;
for(int i = 0; i < 4 ; i++)
{
VB[i] = pos[i] * 2.0f;
}
向量有且只有兩套向量分量(xyzw
、rgba
)用來獲取元素贸弥,但是注意:
可以單套亂序取值
窟坐,不能兩套混合使用
,賦值的時候分量不能重復(fù)绵疲,取值的時候分量可以重復(fù)
//通過向量字母來獲取元素(向量分量)
int4 test = int4(0,1,2,3);
int a = test.x;
int b = test.y;
int c = test.z;
int d = test.w;
int e = test.r;
int f = test.g;
int g = test.b;
int h = test.a;
float4 c;
c.xyzw = float4(1.0f,2.0f,3.0f,4.0f);
c.z = 1.0f;
c.xy = float2(3.0f,4.0f);
c.xyz = float3(3.0f,4.0f,5.0f);
float4 pos = float4(1.0f,2.0f,3.0f,4.0f);
float4 swiz = pos.wxyz; //swiz = (4.0,1.0,2.0,3.0);
float4 dup = pos.xxyy; //dup = (1.0f,1.0f,2.0f,2.0f);
//pos = (5.0f,2.0,3.0,6.0)
pos.xw = float2(5.0f,6.0f);
//pos = (8.0f,2.0f,3.0f,7.0f)
pos.wx = float2(7.0f,8.0f);
//pos = (3.0f,5.0f,9.0f,7.0f);
pos.xyz = float3(3.0f,5.0f,9.0f);
pos.xr //非法的哲鸳,不能混合使用
pos.xx = float2(2.0f,3.0f); //非法的,不能連續(xù)重復(fù)賦值
注意盔憨,不要越界
float2 pos;
pos.x = 1.0f; //合法
pos.z = 1.0f; //非法徙菠,只定義了2個元素,取第3個就越界了
float3 pos2;
pos2.z = 1.0f; //合法
pos2.w = 1.0f; //非法般渡,只定義了3個元素懒豹,取第4個就越界了
列舉一下,二維向量驯用、三維向量脸秽、四維向量所有的構(gòu)造方式
//float2類型向量的所有可能的構(gòu)造方式
float2(float x);
float2(float x,float y);
float2(float2 x);
//float3類型向量的所有可能的構(gòu)造的方式
float3(float x);
float3(float x,float y,float z);
float3(float a,float2 b);
float3(float2 a,float b);
float3(float3 x);
//float4類型向量的所有可能構(gòu)造方式
float4(float x);
float4(float x,float y,float z,float w);
float4(float2 a,float2 b);
float4(float2 a,float b,float c);
float4(float a,float2 b,float c);
float4(float a,float b,float2 c);
float4(float3 a,float b);
float4(float a,float3 b);
float4(float4 x);
3、矩陣
halfnxm 蝴乔、floatnxm (nxm分別指的是矩陣的?數(shù)和列數(shù))
//定義一個4x4的矩陣m 注意记餐,4行4列是 m[3][3]
float4x4 m;
//設(shè)置第一行/第一列為1.0f
m[0][0] = 1.0f;
//將第二排的4個值都設(shè)置為0
m[1] = float4(2.0f);
//設(shè)置第三行第四列的元素為3.0f
m[2][3] = 3.0f;
三、紋理類型和采樣器類型
1薇正、紋理類型 Textures
紋理類型是?個句柄(id), 它指向?個?維/?維/三維紋理數(shù)據(jù)片酝。是定義好的枚舉值:
enum class access {
sample , //紋理對象可以被采樣. 采樣?維這是使?或不使?采樣器從紋理中讀取數(shù)據(jù);
read , //不使?采樣器, ?個圖形渲染函數(shù)或者?個并?計算函數(shù)可以讀取紋理對象
write //?個圖形渲染函數(shù)或者?個并?計算函數(shù)可以向紋理對象寫?數(shù)據(jù)
};
//sample 用的最多,可讀可寫可采樣
3種紋理:
texture1d<T, access a = access::sample>
texture2d<T, access a = access::sample>
texture3d<T, access a = access::sample>
T : 數(shù)據(jù)類型 設(shè)定了從紋理中讀取或是向紋理中寫?時的顏?類型. T可以是half, float, short, int 等
代碼示例:
/*
類型 變量 修飾符
類型:二維紋理
texture2d<float>挖腰,讀取的數(shù)據(jù)類型是float雕沿,沒寫access,默認是sample
texture2d<float,access::read>猴仑,讀取的數(shù)據(jù)類型是float审轮,讀取的方式是read
texture2d<float,access::write>,讀取的數(shù)據(jù)類型是float,讀取的方式是write
變量:
imgA
imgB
imgC
修飾符:
[[texture(0)]] 對應(yīng)紋理0
[[texture(1)]] 對應(yīng)紋理1
[[texture(2)]] 對應(yīng)紋理2
*/
void foo ( texture2d<float> imgA [[ texture(0) ]] ,
texture2d<float, access::read> imgB [[ texture(1) ]],
texture2d<float, access::write> imgC [[ texture(2) ]])
{
...
}
2疾渣、采樣器類型 Samplers
采取器類型決定了如何對?個紋理進?采樣操作. 在Metal 框架中有?個對應(yīng)著?器語?的采樣器的對象 MTLSamplerState 這個對象作為圖形渲染著?器函數(shù)參數(shù)或是并?計算函數(shù)的參數(shù)傳遞;
- 從紋理中采樣時,紋理坐標是否需要歸?化
enum class coord { normalized, pixel };
- 紋理采樣過濾?式, 放?/縮?過濾模式
enum class filter { nearest, linear };
- 設(shè)置紋理采樣的縮?過濾模式
enum class min_filter { nearest, linear };
- 設(shè)置紋理采樣的放?過濾模式
enum class mag_filter { nearest, linear };
- 設(shè)置所有的紋理坐標的尋址模式
enum class address { clamp_to_zero, clamp_to_edge, repeat, mirrored_repeat };
- 設(shè)置紋理s,t,r坐標的尋址模式;
enum class s_address { clamp_to_zero, clamp_to_edge, repeat, mirrored_repeat };
enum class t_address { clamp_to_zero, clamp_to_edge, repeat, mirrored_repeat };
enum class r_address { clamp_to_zero, clamp_to_edge, repeat, mirrored_repeat };
- 設(shè)置紋理采樣的mipMap過濾模式(如果是none,那么只有?層紋理?效)
enum class mip_filter { none, nearest, linear };
注意: 在Metal 程序中初始化的采樣器必須使? constexpr
修飾符聲明篡诽。
代碼示例:
//設(shè)置歸一化、設(shè)置尋址模式榴捡、過濾方式
constexpr sampler s ( coord::pixel,
address::clamp_to_zero,
filter::linear );
//如果都是用默認也可以不寫杈女,或者只寫需要設(shè)定的
constexpr sampler a ( coord::normalized );
constexpr sampler b ( address::repeat );
四、函數(shù)修飾符
Metal 有以下3種函數(shù)修飾符:kernel
吊圾、vertex
达椰、fragment
。
1项乒、kernel
表示該函數(shù)是?個數(shù)據(jù)并?計算著?函數(shù)
. 它可以被分配在?維/?維/三維線程組中去執(zhí)?
kernel void foo(...)
{
...
}
2砰碴、vertex
表示該函數(shù)是?個頂點著?函數(shù)
, 它將為頂點數(shù)據(jù)流中的每個頂點數(shù)據(jù)執(zhí)??次然后為每個頂 點?成數(shù)據(jù)輸出到繪制管線;
3、fragment
表示該函數(shù)是?個?元著?函數(shù)
, 它將為?元數(shù)據(jù)流中的每個?元 和其關(guān)聯(lián)執(zhí)??次然后 將每個?元?成的顏?數(shù)據(jù)輸出到繪制管線中;
注意:
- 被上面三種修飾符
修飾的函數(shù)
中板丽,不能調(diào)用 同樣被這三個修飾符修飾的函數(shù)
,否則會直接編譯失敗
趁尼“<睿可以調(diào)用普通函數(shù)。 - 使?kernel 修飾的函數(shù). 其
返回值類型必須是 void 類型
- 只有圖形著?函數(shù)才可以被 vertex 和 fragment 修飾酥泞,返回值類型可以辨認出它是為 頂點做計算還是為每像素做計算
- 圖形著?函數(shù)的返回值可以為 void , 但是這也就意味著該函數(shù)不產(chǎn)?數(shù) 據(jù)輸出到繪制管線; 這是?個?意義的動作
五砚殿、地址空間修飾符(?于變量或者參數(shù))
地址空間修飾符有4種: device、threadgrounp芝囤、constant似炎、thread
使? 地址空間修飾符 來表示?個函數(shù)變量或者參數(shù)變量 被分配于那??內(nèi)存區(qū)域
- 所有的著?函數(shù)(vertex, fragment, kernel)的參數(shù),如果是指針或是引?, 都必須帶有地址空間修飾符號
- 對于圖形著?器函數(shù), 其指針或是引?類型的參數(shù)必須定義為 device 或是 constant 地址空間
- 對于并?計算著?函數(shù), 其指針或是引?類型的參數(shù)必須定義為 device 或是 threadgrounp 或是 constant 地址空間
1、device Address Space(設(shè)備地址空間)
device(設(shè)備地址空間)指向設(shè)備內(nèi)存池分配出來的緩存對象
(這里設(shè)備指的的顯存悯姊,GPU)羡藐。它是可讀也是可寫的。?個緩存對象可 以被聲明成?個標量
悯许、向量
仆嗦、?戶?定義結(jié)構(gòu)體
或者結(jié)構(gòu)體的指針
- 放在顯存中,是為了讀取更快
- 紋理對象先壕,不用device修飾瘩扼,也會默認放在 顯存中
//1.修飾指針變量。表示:這個 四維向量顏色 的指針 放在顯存中
device float4 *color;
//2垃僚、修飾結(jié)構(gòu)體指針集绰。表示:把這個結(jié)構(gòu)體的指針 放在顯存中
struct Foo {
float a[3];
int b[2];
};
device Foo *my_info;
2、threadgrounp Address Space 線程組地址空間
threadgrounp(線程組地址空間)?于為 并?計算著?函數(shù)
分配內(nèi)存變量谆棺。 這些變量被?個線程組的所有線程共享. 在線 程組地址空間分配的變量不能被?于圖形繪制著?函數(shù)(頂點著?函數(shù), ?元著?函數(shù))栽燕,也就是在頂點、 ?元著?函數(shù)中不能使用threadgrounp修飾變量
在并?計算著?函數(shù)中, 在線程組地址空間分配的變量為?個線程組使?, 聲明周期和線程組相同
/*
傳入?yún)?shù):
在線程組地址空間分配一個浮點類型變量a的指針
*/
kernel void my_func(threadgroup float *a [[ threadgroup(0) ]], ...)
{
//在線程組地址空間分配一個浮點類型變量x
threadgroup float x;
//在線程組地址空間分配一個10個浮點類型數(shù)的數(shù)組y;
threadgroup float y[10];
}
3、constant Address Space 常量地址空間
- constant(常量地址空間)指向的緩存對象也是從設(shè)備內(nèi)存池(顯存中)分配存儲, 但是它是
只讀的
- 在程序域中纫谅,constant 修飾的變量炫贤,
必須在聲明的時候就初始化并賦值
- 在程序域中,constant 修飾的變量的
生命周期和程序一樣
付秕,在程序中的并?計算著?函數(shù)或者圖形繪制著?函數(shù)調(diào)?, 但是 constant 的值會保持不變(也就是一旦初始化就是常量兰珍,不允許修改
)
注意:constant修飾的變量,如果不賦值询吴、或者后面進行修改掠河,都會產(chǎn)生編譯錯誤
//這樣聲明是正確的 ?
constant float samples[] = { 1.0f, 2.0f, 3.0f, 4.0f };
//但是,一旦修改猛计,就編譯錯誤了 ?
sampler[4] = {3,3,3,3};
//這樣聲明是錯誤的 ? 沒有賦值唠摹,直接編譯失敗
constant float a;
4、thread Address Space 線程地址空間
thread(線程地址空間)指向每個線程準備的地址空間奉瘤,這個線程的地址空間定義的變量在其他線程不可?勾拉,在 圖形繪制著?函數(shù)或者并?計算著?函數(shù)中聲明的變量thread 地址空間分配
- threadgroup修飾的變量,線程之間可以共享盗温。thread修飾的就不行藕赞,
- 在著色函數(shù)中,不能用threadgroup卖局, 可以用thread
kernel void my_func(...)
{
//在線程空間分配空間給x,p
float x;
thread float p = &x;
}
六斧蜕、傳遞修飾符(函數(shù)參數(shù)與變量的屬性修飾符)
在圖形繪制 或者 并行計算著色器函數(shù) 的 輸入輸出
都是通過參數(shù)傳遞
的。除了常量地址空間變量和程序域定義的采樣器之外, 其他參數(shù)修飾的可以是如下之一砚偶,有以下5種屬性修飾符:
- device buffer :設(shè)備緩存批销,一個指向設(shè)備地址空間的任意數(shù)據(jù)類型的指針/引用
- constant buffer :常量緩存,一個指向常量地址空間的任意數(shù)據(jù)類型的指針/引用
- texture :紋理對象
- sampler :采樣器對象
- threadGroup :在線程組中供線程共享的緩存
那么染坯,為什么需要屬性修飾符均芽?
- 參數(shù)表示資源的定位,可以理解為端口酒请,相當于OpenGl ES中的location
- 在固定管線和可編程管線進行內(nèi)建變量的傳遞
- 將數(shù)據(jù)沿著渲染管線從頂點函數(shù)傳遞到片元函數(shù)
對于每個著色函數(shù)來說骡技,一個修飾符是必須指定的,它用來設(shè)置一個緩存羞反、紋理布朦、采樣器的位置,傳遞修飾符對應(yīng)的寫法如下:
- device buffer ---> [[buffer(index)]]
- constant buffer ---> [[buffer(index)]]
- texture ---> [[texture(index)]]
- sampler ---> [[sampler(index)]]
- threadGroup ---> [[threadGroup(index)]]
index 可以由開發(fā)者來指定昼窗,是一個unsigned interger類型的值是趴,
表示了一個緩存、紋理澄惊、采樣器參數(shù)的位置唆途,即在函數(shù)參數(shù)索引表中的位置富雅。
屬性修飾符一般放在變量名后面。
在代碼中如何表現(xiàn):
1.已知條件:device buffer(設(shè)備緩存)/constant buffer(常量緩存)
代碼表現(xiàn):[[buffer(index)]]
解讀:不變的buffer ,index 可以由開發(fā)者來指定.
2.已知條件:texture Object(紋理對象)
代碼表現(xiàn): [[texture(index)]]
解讀:不變的texture ,index 可以由開發(fā)者來指定.
3.已知條件:sampler Object(采樣器對象)
代碼表示: [[sampler(index)]]
解讀:不變的sampler ,index 可以由開發(fā)者來指定.
4.已知條件:threadgroup Object(線程組對象)
代碼表示: [[threadgroup(index)]]
解讀:不變的threadgroup ,index 可以由開發(fā)者來指定.
代碼示例
//并行計算著色器函數(shù)肛搬,屬性修飾符"[[buffer(index)]]" 為著色函數(shù)參數(shù)設(shè)定了緩存的位置
/*
kernel :并行計算函數(shù)修飾符
void :函數(shù)的返回值 (kernel修飾没佑,返回值必須是void)
add_vectors:函數(shù)名
const device float4 *inA [[buffer(0)]]:
const :修飾的這個變量不能修改
device:修飾的這個變量,放在顯存中
float4:這個變量的類型:四維向量
inA:變量名
[[buffer(0)]]:表示這個變量在 buffer中對應(yīng)的0這個id
thread_position_in_grid:用于表示當前節(jié)點在多線程網(wǎng)格中的位置,并不需要開發(fā)者傳遞温赔,是Metal自帶的蛤奢。
*/
kernel void add_vectors(const device float4 *inA [[ buffer(0) ]],
const device float4 *inB [[ buffer(1) ]],
device float4 *out [[ buffer(2) ]],
uint id [[ thread_position_in_grid ]])
{
out[id] = inA[id] + inB[id];
}
七、常見的內(nèi)建變量屬性修飾符
-
[[vertex_id]]
:頂點id標識符陶贼,并不由開發(fā)者傳遞 -
[[position]]
:- 在頂點函數(shù)中啤贩,表示
頂點信息
,類型是float4 - 在片元函數(shù)中拜秧,表示
片元的窗口相對坐標
(x,y,z,w),即像素在屏幕上的位置信息
- 在頂點函數(shù)中啤贩,表示
-
[[point_size]]
:點的大小痹屹,float類型 -
[[color(m)]]
:顏色,m在編譯前必須確定 -
[[stage_in]]
:由頂點函數(shù)輸出 經(jīng)過光柵化生成 傳入片元函數(shù)的數(shù)據(jù)枉氮。 需要注意:無論在頂點還是片元函數(shù)中志衍,只能聲明一個被stage_in修飾的參數(shù)。這個參數(shù)也可以是一個結(jié)構(gòu)體聊替,結(jié)構(gòu)體內(nèi)的成員類型可以是整型/浮點的標量/向量
//定義了片元輸入的結(jié)構(gòu)體足画,
struct MyFragmentOutput {
// color attachment 0 顏色附著點0
float4 clr_f [[color(0)]];
// color attachment 1 顏色附著點1
int4 clr_i [[color(1)]];
// color attachment 2 顏色附著點2
uint4 clr_ui [[color(2)]];
};
fragment MyFragmentOutput my_frag_shader( ... )
{
MyFragmentOutput f;
....
f.clr_f = ...;
....
return f;
}