跟OpenGL ES類似映企,Metal也有自己的著色器語言诡宗,相比更接近C語言的GLSL锹雏,Metal Shading Language的語言更接近于C++要拂。而且抠璃,Metal有著和OpenGL管道相同的渲染流程,即:頂點著色器->圖元裝配->光柵化->片元著色器->提交緩沖區(qū)脱惰,不同的是搏嗡,Metal中將著色器描述為函數(shù),但本質是相同的拉一。
但是畢竟是蘋果的親兒子采盒,Xcode不僅可以提供了創(chuàng)建Metal文件的入口,還能使用Clang+LLVM對Metal文件進行編譯鏈接蔚润,這相比于要開發(fā)者手敲GLSL的OpenGL ES來說磅氨,無疑是比較優(yōu)勢的一點,畢竟僅僅是以字符串形式存在的著色器來說嫡纠,查找錯誤是一個麻煩的過程烦租。
但是Metal的語法仿佛繼承了OC語言惡心的特質,學習起來還是需要一定成本的除盏,本文將對Metal Shading Language做一個學習歸納叉橱,方便日后查看。
Metal Shading Language 基本語法
Metal Shading Language使用的是 Clang 和 LLVM 進行編譯處理的者蠕,它基于C++ 11.0 語言設計窃祝,但它也不完全等同與C++的語言,Metal不支持以下特征:
- Lambda表達式
- 遞歸函數(shù)調用
- 動態(tài)轉換操作符
- 類型識別
- 對象創(chuàng)建new和銷毀delete操作符
- 操作符noexcept
- goto跳轉
- 變量存儲修飾符register和thread_local
- 虛函數(shù)修飾符
- 派生類
- 異常處理
- C++標準庫
另外踱侣,Metal像素坐標系統(tǒng)使用的原點是左上角粪小,這與OpenGL的左下角是不同的,所以我們使用Metal渲染紋理時不再需要翻轉抡句。
而且糕再,Metal函數(shù)名不能命名為Main,這點和GLSL是不同的玉转。
Metal支持結構體和枚舉突想。
基本數(shù)據(jù)類型
Metal 有以下基本數(shù)據(jù)類型:
類型 | 描述 |
---|---|
bool | 布爾類型,true/false |
char | 有符號8-bit整數(shù) |
unsigned char / uchar | 無符號8-bit整數(shù) |
short | 有符號16-bit整數(shù) |
unsigned short / ushort | 無符號32-bit整數(shù) |
int | 有符號32-bit整數(shù) |
unsigned int / uint | 無符號32-bit整數(shù) |
half | 16-bit浮點數(shù) |
float | 32-bit浮點數(shù) |
size_t | 無符號64-bit整數(shù) |
prtdiff_t | 64-bit有符號整數(shù)究抓,表示兩個指針的差 |
void | 表示一個空的值集合 |
與OpenGL不同的是猾担,Metal支持數(shù)字后綴表示字面量類型,如0.4f刺下,0.5h绑嘹。
指針類型
Metal Shading Language 也是支持指針類型的,同樣是使用 * 表示指針,但是在Metal Shading Language中橘茉,對指針的使用以下限制:
- Metal圖型和并行計算函數(shù)用的的入?yún)⑷绻侵羔樆蛘邞妙愋凸ひ福仨毷褂玫刂房臻g修飾符(如device姨丈,threadgroup,constant)
- 不支持函數(shù)指針
向量和矩陣數(shù)據(jù)類型
向量
Metal Shading Language 使用基本數(shù)據(jù)類型+維度表示向量擅腰,如float3表示每個維度類型為float的3維向量蟋恬,它的定義可以寫為:
short4 vector1 = {1,2,3,4};
float3 vector2 = float3(1.f, 2.f, 3.f);
和GLSL類似,它也可以使用.xyzw或者.rgba讀取或賦予對應的值:
short2 vector3 = vector1.xy; // vector3 = (1, 2)
float2 vector4 = vector2.rb; // vector4 = (1.f, 3.f)
向量讀取的是后實際是使用索引值趁冈,所以向量的xyzw(或rgba)可以顛倒和重復:
vector3.xy = vector3.yx; // vector3 = (2, 1)
vector4.rg = vector4.rr; // vector4 = (1.f, 1,f)
另外歼争,因為xyzw(或rgba)讀取的索引值,所以不能讀取越界的索引渗勘,也不能xyzw和rgba混用沐绒,如
vector3.zw // 錯誤,二維向量沒有zw值
vector4.xg // 錯誤旺坠,xyzw和rgba不能混用
向量支持如下類型:
- booln
- charn
- shortn
- intn
- ucharn
- ushortn
- uintn
- halfn
- floatn
0<n≤4,表示維度
矩陣
矩陣支持如下類型:
- halfnxm
- floatnxm
n乔遮、m分別表示矩陣的行數(shù)和列數(shù)
它可以這么定義:
half2x2 matrix = half2x2(1.h,2.h,3.h,4.h);
matrix = {2.h,2.h,2.h,2.h};
紋理類型
紋理類型是一個句柄,它指向一個一維/二維/三維紋理數(shù)據(jù)取刃。他是這么定義的:
texture1d<T, access a = access::sample>
texture2d<T, access a = access::sample>
texture3d<T, access a = access::sample>
T表示數(shù)據(jù)類型蹋肮,設定了從紋理中讀取的或是向紋理中寫入時的顏色類型,可以是half蝉衣,float括尸,short,int等
access
是一個枚舉值病毡,定義了訪問權利:
enum class access {
sample, // 默認值濒翻, 紋理對象可以被采樣,采樣一維時使用或不使用都從紋理中讀取數(shù)據(jù)
read, // 不使用采樣器啦膜,一個圖形渲染函數(shù)或一個并行計算函數(shù)可以讀取紋理對象
write // 一個圖形渲染函數(shù)或者一個并行函數(shù)可以像紋理對象寫入數(shù)據(jù)
}
示例如下:
void foo(texture2d<float> imgA[[ texture(0) ]],
texture2d<float, access::read> imgB [[ texture(1) ]],
texture2d<float, access::write> imgC [[ texture(2) ]])
采樣器類型
采樣器類型決定了如何對一個紋理進行采樣操作有送,在Metal框架中有一個對應著著色器語言的采樣器對象MTLSamplerState,這個對象作為圖形渲染著色器函數(shù)參數(shù)或者并行計算函數(shù)的參數(shù)傳遞僧家。
需要注意的是雀摘,Metal程序中,初始化采樣器必須使用constexpr
修飾符聲明八拱。
constexpr sampler s(coord::normalized)
例中括號內表示采樣器的采樣參數(shù)阵赠,是一個枚舉值,Metal中采樣器以下幾種類型:
枚舉名稱 | 有效值 | 描述 |
---|---|---|
coord | {normalized, pixel} | 從紋理中采樣時肌稻,紋理坐標是否需要歸一化 |
filter | {nearest, linear} | 紋理采用的過濾方式清蚀,放大/縮小的過濾方式 |
min_filter | {nearest, linear} | 紋理采用的縮小過濾方式 |
mag_filter | {nearest, linear} | 紋理采用的放大過濾方式 |
s_address, t_address, r_address | {clamp_to_zero, clamp_to_edge, repeat, mirrored_repeat} | 設置s、t爹谭、r坐標的尋址模式 |
address | {clamp_to_zero, clamp_to_edge, repeat, mirrored_repeat} | 設置所有的紋理坐標尋址模式 |
compare_func | {none, less, less_equal, greater, greater_equal, equal, not_equal} | 為使用r紋理坐標做shadow map枷邪,設置比較測試邏輯,這個狀態(tài)值只可以在Metal Shading Language中完成 |
例:
constexpr sampler s(coord::pixel, address::clamp_to_zero, filter::linear);
constexpr sampler a(address::repeat);
修飾符
修飾符是Metal Shading Language中比較難理解的點诺凡,Metal中的修飾符有函數(shù)修飾符东揣、地址空間修飾符和傳遞修飾符3種
函數(shù)修飾符
函數(shù)修飾符位于函數(shù)之前践惑,例如:
kernel void foo(...) {
...
}
Metal有以下3種函數(shù)修飾符
- kernel:表示該函數(shù)是一個數(shù)據(jù)并行計算函數(shù),他可以分配在一維/二維/三維線程組中去執(zhí)行嘶卧。
- vertex:表示該函數(shù)是一個頂點著色函數(shù)尔觉,它將頂點數(shù)據(jù)劉總的每個頂點數(shù)據(jù)執(zhí)行一次然后為每個頂點生成數(shù)據(jù)輸出到繪制管線。
- fragment:表示該函數(shù)是一個片元著色函數(shù)脸候,它將為片元數(shù)據(jù)流中的每個片元和其關聯(lián)執(zhí)行一次然后將每個片元生成的顏色數(shù)據(jù)輸出到繪制管線中穷娱。
只有圖形著色器函數(shù)才可以被vertex和fragment修飾绑蔫,對于圖形著色函數(shù)运沦,返回值類型可以辨別出他是為頂點做計算還是為每個像素做計算,圖型函數(shù)返回值可以為void配深,但這意味著該函數(shù)不產生數(shù)據(jù)輸出到繪制管線携添,這是一個無意義的動作。
函數(shù)修飾符有以下注意點:
- 使用kernel修飾的函數(shù)篓叶,其返回值類型必須是void類型烈掠;
- 一個被函數(shù)修飾符修飾的函數(shù)不能調用其他被函數(shù)修飾符修飾的函數(shù);
地址空間修飾符
地址空間修飾符用于變量或者參數(shù)缸托,位于變量或參數(shù)類型之前左敌,例如:
device float4 *color;
空間修飾符表示一個變量被分配在哪一片內存區(qū)域,所有著色器函數(shù)(vertex俐镐,fragment矫限,kernel)的參數(shù),如果是指針或者應用佩抹,都必須帶有地址空間修飾符。
空間修飾符有以下4種棍苹。
- device:設備地址空間
- threadgroup:線程組地址空間
- constant:常量地址空間
- thread:線程地址空間
對于圖形著色器函數(shù)无宿,其指針或者應用類型參數(shù)必須定位為device或constant地址空間;
對于并行計算函數(shù)枢里,其指針或者應用類型參數(shù)必須定義為device或threadgroup或constant地址空間彬碱;
1.設備地址空間
指的是設備內存池分配出來的緩存對象,它是可讀可寫的搬泥,一個緩存對象可以被聲明為一個標量忿檩、向量或者是用戶自定義結構體的指針或應用。
另外注意的是辨图,紋理對象總是在設備地址空間分配內存,device地址空間修飾符不必出現(xiàn)在紋理類型定義中,一個紋理對象的內容無法直接訪問,Metal提供讀寫紋理的內建函數(shù)姨俩。
例:
device float4 *color;
struct Foo {
float a[3];
int b[2];
}
device Foo *my_info;
2.線程組地址空間
指的是用于為并行計算著色函數(shù)分配內存變量的空間哼勇,這些變量被一個線程組的所有線程共享积担。
在線程組地址空間分配的變量不能用與圖形繪制著色函數(shù)湿刽。
在并行計算著色函數(shù)中渴庆,在線程組地址空間分配的變量為一個線程組使用耸弄,聲明周期和線程組相同捌显。
例:
kernel void my_func(threadgroup float *a [[threadgroup(0)]]) {
threadgroup float x;
threadgroup float b[10];
}
3.常量地址空間
指向的緩存對象也是設備內存池分配,但他是只讀的尉间,它修飾的變量必須在定義的時候初始化击罪,用來初始化的值必須是編譯時的常量媳禁,修飾的變量在程序域的生命周期和程序一樣竣稽,在程序中的并行計算著色函數(shù)或者圖形繪制著色函數(shù)調用時,它的的值都會保持不變耍缴。
值得注意的是防嗡,常量地址空間的指針或引用也可以作為函數(shù)的參數(shù),為聲明的常量賦值會產生編譯錯誤,聲明常量但是沒有賦予初值也會編譯錯誤近顷;
例如:
constant float samplers[] = {1.f, 2.f, 3.f, 3.f};
sampler[4] = {3,3,3,3}; // 編譯失敗
constant float a; // 編譯失敗
4.線程地址空間
指向每個線程準備的地址空間域醇,這個線程地址空間定義的變量在其他線程不可見酪呻〗赘裕可以在圖形繪制函數(shù)或者并行計算函數(shù)中使用闷尿。
例:
kernel void my_func(...) {
float x;
thread float = &x;
}
傳遞修飾符
對于圖形繪制或并行著色器函數(shù)來說,輸入/輸出都需要通過參數(shù)傳遞(除了常量地址空間變量和程序域中定義的采樣器以外)女坑,參數(shù)可以以下之一:
- device buffer - 設備緩存填具,一個指向設備地址空間的任意數(shù)據(jù)類型的指針或者引用
- constant buffer - 常量緩存區(qū),一個指向常量地址空間的任意數(shù)據(jù)類型的引用
- texture - 紋理對象
- sampler - 采樣器對象
- threadgroup - 在線程組中供各線程共享的緩存
注意的是堂飞,著色器的緩存(device 和 constant)不能重名灌旧。
對于每個著色器來說,一個變量傳遞修飾符是必須指定的绰筛,它用來設置一個緩沖枢泰、紋理、采樣器的位置铝噩。
傳遞修飾符位于參數(shù)變量之后衡蚂,用[[]]表示,并在()內指定它的位置。
通常我們有以下幾種類型可以從外部傳入著色器:
- [[buffer(index)]] // device buffer / constant buffer
- [[texture(index)]] // texture
- [[sampler(index)]] // sampler
- [[threadgroup(index)]] // threadgroup
index是一個unsigned integer類型的值毛甲,它表示一個緩存年叮、紋理、采樣器參數(shù)的位置玻募。
參考代碼如下:
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];
}
thread_position_in_grid: 用于表示當前節(jié)點在多線程網絡中的位置,不需要用戶傳入
內建變量屬性修飾符
除了以上的傳遞修飾符只损,Metal還提供了內建的傳遞修飾符。常見的有以下幾種:
- [[vertex_id]] 頂點id表示符七咧,即當前是第幾個頂點
- [[position]] 頂點信息跃惫,表述片元的窗口相對坐標(x, y, z, 1/w)
- [[point_size]] 點的大小
- [[color(m)]] 顏色,m編譯前得確定,表示傳入顏色附著點m
- [[stage_in]] 表示由頂點著色函數(shù)輸出經過光柵化生成傳入片元函數(shù)的數(shù)據(jù)艾栋,一個頂點和片元函數(shù)都只能有一個參數(shù)被聲明為stage_in