Metal Shading Language(Metal 著色語言規(guī)范)

簡介

  • Metal 著色語言是用來編寫 '3D 圖形渲染' 和 '并行計算核心邏輯' 的 ??編程語?。 當(dāng)你使? Metal 框架來完成APP的實現(xiàn)時,則需要使?Metal 編程語?
  • Metal 語言使用 Clang 和 LLVM 進(jìn)行編譯處理,編譯器對于在 GPU 上的代碼執(zhí)行效率有更好的控制
  • Metal 這?門語?言是基于 C++ 11.0 標(biāo)準(zhǔn)設(shè)計的。它在 C++ 基礎(chǔ)上多了一些拓拓展和限制
  • 主要?來編寫在 GPU 上執(zhí)?的圖像渲染邏輯代碼以及通?并?計算邏輯代碼

C++ 11.0 和 Metal 語?的異同之處

  • 如下的 C++11.0 的特性在 Metal 著?語言中是不支持的

    • Lambda 表達(dá)式
    • 遞歸函數(shù)調(diào)用
    • 動態(tài)轉(zhuǎn)換操作符
    • 類型識別
    • 對象創(chuàng)建(new)和釋放(delete)操作符
    • 操作符 noexcept
    • goto 跳轉(zhuǎn)
    • 變量存儲修飾符 register 和 thread_local
    • 虛函數(shù)修飾符
    • 派生類
    • 異常處理
    • C++ 標(biāo)準(zhǔn)庫在 Metal 語言中也不可使用
  • Metal 語言對于指針使用的限制

    • Metal 圖形和并行計算函數(shù)用到的入?yún)ⅲū热缰羔?/ 引用),如果是指針 / 引用必須使用地址空間修飾符(比如device、threadgroup、constant)
    • 不支持函數(shù)指針
    • 函數(shù)名不能出現(xiàn) main
  • Metal 像素坐標(biāo)系統(tǒng):metal 中紋理/幀緩存區(qū) attachment 的像素使用的坐標(biāo)系統(tǒng)的原點在左上角

數(shù)據(jù)類型

包括標(biāo)量、向量、矩陣、紋理、采樣器等類型

標(biāo)量類型

如下圖所示:

Metal ?持后綴表示字?量類型, 例如 0.5F, 0.5f; 0.5h, 0.5H。

//基本數(shù)據(jù)類型
bool a = true;
char b = 5;
int  d = 15;
size_t c = 1;
ptrdiff_t f = 2;

向量和矩陣

向量支持如下類型:booln、charn、shortn、intn、ucharn、ushortn、uintn、halfn、floatn,其中 n 表示向量的維度,最多不超過4維向量

//向量
bool2 A= {1,2};
float4 pos = float4(1.0,2.0,3.0,4.0);
float x = pos[0];
float y = pos[1];

float4 VB;
for(int i = 0; i < 4 ; I++)
    VB[i] = pos[i] * 2.0f;
向量訪問規(guī)則
  • 通過向量字母獲取元素: 向量中的向量字母僅有2種,分別為xyzw、rgba
int4 test = int4(0,1,2,3);
int a = test.x; //獲取的向量元素0
int b = test.y; //獲取的向量元素1
int c = test.z; //獲取的向量元素2
int d = test.w; //獲取的向量元素3

int e = test.r; //獲取的向量元素0
int f = test.g; //獲取的向量元素1
int g = test.b; //獲取的向量元素2
int h = test.a; //獲取的向量元素3
  • 多個分量同時訪問
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);
  • 多分量訪問可以亂序/重復(fù)

    • 賦值時分量不可重復(fù),取值時分量可重復(fù)
    • 右邊是取值 和 左邊賦值都合法
    • xyzw與rgba不能混合使用
float4 pos = float4(1.0f,2.0f,3.0f,4.0f);
//向量分量逆序訪問
float4 swiz = pos.wxyz;  //swiz = (4.0,1.0,2.0,3.0);
//向量分量重復(fù)訪問
float4 dup = pos.xxyy;  //dup = (1.0f,1.0f,2.0f,2.0f);

//可以僅對 xw / wx 修改
//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);

//可以僅對 xyz 進(jìn)行修改
//pos = (3.0f,5.0f,9.0f,7.0f);
pos.xyz = float3(3.0f,5.0f,9.0f);

float2 pos;
pos.x = 1.0f; //合法
pos.z = 1.0f; //非法,pos是二維向量,沒有z這個索引

float3 pos2;
pos2.z = 1.0f; //合法
pos2.w = 1.0f; //非法

// 賦值 時 分量不可重復(fù),取值 時 分量可重復(fù)
//非法,x出現(xiàn)2次
pos.xx = float2(3.0,4.0f);
pos.xy = swiz.xx;

//向量中xyzw與rgba兩組分量不能混合使用
float4 pos4 = float4(1.0f,2.0f,3.0f,4.0f);
pos4.x = 1.0f;
pos4.y = 2.0f;
//非法,.rgba與.xyzw 混合使用
pos4.xg = float2(2.0f,3.0f);
////非法,.rgba與.xyzw 混合使用
float3 coord = pos4.ryz;

矩陣支持以下類型:halfnxm 和 floatnxm,nxm分別指的是矩陣的?數(shù)和列數(shù)

普通的矩陣其本質(zhì)就是一個數(shù)組

float4x4 m;
//將第二行的所有值都設(shè)置為2.0
m[1] = float4(2.0f);

//設(shè)置第一行/第一列為1.0f
m[0][0] = 1.0f;

//設(shè)置第三行第四列的元素為3.0f
m[2][3] = 3.0f;

float4 類型向量的構(gòu)造方式

//float4類型向量的所有可能構(gòu)造方式
//1個一維向量,表示一行都是x
float4(float x);/
//4個一維向量 --> 4維向量
float4(float x,float y,float z,float w);
//2個二維向量 --> 4維向量
float4(float2 a,float2 b);
//1個二維向量+2個一維向量 --> 4維向量
float4(float2 a,float b,float c);
float4(float a,float2 b,float c);
float4(float a,float b,float2 c);
//1個三維向量+1個一維向量 --> 4維向量
float4(float3 a,float b);
float4(float a,float3 b);
//1個四維向量 --> 4維向量
float4(float4 x);

緩沖buffer

  • 在Metal 中實現(xiàn)緩存靠的是?個指針,它指向?個在Device 或者 constant 地址空間中的內(nèi)建或是開發(fā)者?定義的數(shù)據(jù)塊,緩存可以被定在程序域域中,或是當(dāng)做函數(shù)的參數(shù)傳遞。
//緩存buffer
device float4 *device_buffer;
struct my_user_data{
    float4 a;
    float b;
    int2 c;
};
constant my_user_data *user_data;

紋理類型(Textures)

紋理類型是一個句柄,指向一維/二維/三維紋理數(shù)據(jù)

紋理的訪問權(quán)限

在一個函數(shù)中描述紋理對象的類型,枚舉值定義了紋理的訪問權(quán)利 enum class access {sample, read, write};,有以下3種訪問權(quán)利,當(dāng)沒寫access時,默認(rèn)的access 就是 sample

  • sample:紋理對象可以被采樣(即使用采樣器去紋理中讀取數(shù)據(jù),相當(dāng)于OpenGL ES的GLSL中sampler2D),采樣一維這時使用 或者 不使用都可以從紋理中讀取數(shù)據(jù)(即可讀可寫可采樣)
  • read:不使用采樣器,一個圖形渲染函數(shù) 或者 一個并行計算函數(shù)可以讀取紋理對象(即僅可讀)
  • write:一個圖形渲染函數(shù) 或者 一個并行計算可以向紋理對象寫入數(shù)據(jù)(即 可讀可寫)
定義紋理類型

描述一個紋理對象/類型,有以下三種方式,分別對應(yīng)一維/二維/三維

texture1d<T, access a = access::sample>
texture2d<T, access a = access::sample>
texture3d<T, access a = access::sample>

T代表 泛型,設(shè)定了從紋理中讀取數(shù)據(jù) 或是 寫入時的顏色類型,T可以是half、float、short、int等;access 表示紋理訪問權(quán)限,當(dāng)access沒寫時,默認(rèn)是sample

//類型 變量 修飾符
/*
 類型
    - texture2d<float>,讀取的數(shù)據(jù)類型是float,沒寫access,默認(rèn)是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)]])
{
}

采樣器類型(Samplers)

采樣器類型決定了如何對一個紋理進(jìn)行采樣操作,在 Metal 框架中有一個對應(yīng)著色器語言的采樣器的對象 MTLSamplerState,這個對象作為圖形渲染著色器函數(shù)參數(shù)或是并行計算函數(shù)的參數(shù)傳遞

  • enum class coord { normalized, pixel }; 從紋理中采樣時,紋理坐標(biāo)是否需要歸一化
  • enum class filter { nearest, linear }; 紋理采樣過濾方式,放大/縮小過濾方式
  • enum class min_filter { nearest, linear }; 設(shè)置紋理采樣的縮小過濾方式
  • enum class mag_filter { nearest, linear }; 設(shè)置紋理采樣的放大過濾方式
  • 設(shè)置紋理s、t、r坐標(biāo)(對應(yīng)紋理坐標(biāo)的x、y、z)的尋址方式
    • s坐標(biāo):enum class s_address { clamp_to_zero, clamp_to_edge, repeat, mirrored_repeat };
    • t坐標(biāo):enum class t_address { clamp_to_zero, clamp_to_edge, repeat, mirrored_repeat };
    • r坐標(biāo):enum class r_address { clamp_to_zero, clamp_to_edge, repeat, mirrored_repeat };
  • enum class address { clamp_to_zero, clamp_to_edge, repeat, mirrored_repeat }; 設(shè)置所有紋理坐標(biāo)的尋址方式
  • enum class mip_filter { none, nearest, linear }; 設(shè)置紋理采樣的mipMap過濾模式, 如果是none,那么只有一層紋理生效

采樣器所有狀態(tài)如下所示:

??注意:在 Metal 程序中初始化的采樣器必須使用 constexpr 修飾符聲明;openGL ES 中紋理坐標(biāo)對應(yīng)的是 stq,Metal 中紋理坐標(biāo)對應(yīng)是 str

/*
constexpr:修飾符(必須寫)
sampler:類型
s:采樣器變量名稱
參數(shù)
    - coord: 是否需要歸一化,不需要歸一化,用的是像素pixel
    - address: 地址環(huán)繞方式
    - filter: 過濾方式
*/
constexpr sampler s(coord::pixel, address::clamp_to_zero, filter::linear);

constexpr sampler a(coord::normalized);

constexpr sampler b(address::repeat);

函數(shù)修飾符

Metal 有三種函數(shù)修飾符,放在函數(shù)的返回值前面

  • kernel:表示該函數(shù)是一個數(shù)據(jù)并行計算著色函數(shù),它可以被分配在一維/二維/三維線程組中去執(zhí)行
  • vertex:表示該函數(shù)是一個頂點著色函數(shù),它將為頂點數(shù)據(jù)流中的每個頂點數(shù)據(jù)執(zhí)行一次,然后為每個頂點生成數(shù)據(jù)輸出到繪制管線
  • fragment:表示該函數(shù)是一個片元著色函數(shù),它將為片元數(shù)據(jù)流中的每個片元 和其相關(guān)聯(lián)的數(shù)據(jù)執(zhí)行一次,然后將每個片元生成的顏色數(shù)據(jù)輸出到繪制管線中
//并行計算函數(shù)(kernel)
kernel void CCTestKernelFunctionA(int a,int b)
{ 
    /*
     注意:
     1. 使用kernel 修飾的函數(shù)返回值必須是void 類型
     2. 一個被函數(shù)修飾符修飾過的函數(shù),不允許在調(diào)用其他的被函數(shù)修飾過的函數(shù). 非法
     3. 被函數(shù)修飾符修飾過的函數(shù),只允許在客戶端對其進(jìn)行操作. 不允許被普通的函數(shù)調(diào)用.
     */
     
    //不可以的!
    //一個被函數(shù)修飾符修飾過的函數(shù),不允許在調(diào)用其他的被函數(shù)修飾過的函數(shù). 非法
    CCTestKernelFunctionB(1,2);//非法,錯誤調(diào)用!?。?    CCTestVertexFunctionB(1,2);//非法,錯誤調(diào)用!??!
    
    //可以! 你可以調(diào)用普通函數(shù).而且在Metal 不僅僅只有這3種被修飾過的函數(shù).普通函數(shù)也可以存在
    CCTest();
    
}

//并行計算函數(shù)
kernel void CCTestKernelFunctionB(int a,int b)
{
    .....
}

//頂點函數(shù)
vertex int CCTestVertexFunctionB(int a,int b)
{
    .....
}

//片元函數(shù)
fragment int CCTestVertexFunctionB(int a,int b)
{
    .....
}

//普通函數(shù)
void CCTest()
{
    .....
}

??注意

  • 使?kernel 修飾的函數(shù),其返回值類型必須是 void 類型
  • 一個被函數(shù)修飾符修飾的函數(shù)不能在調(diào)用其他也被函數(shù)修飾符修飾的函數(shù),這樣會導(dǎo)致編譯失敗,即 Kernel、vertex、fragment 修飾的函數(shù)不能相互調(diào)用,也不能同修飾符函數(shù)相互調(diào)用。但是可以調(diào)用普通函數(shù)
  • 被函數(shù)修飾符修飾過的函數(shù),只允許在客戶端對其進(jìn)行操作. 不允許被普通的函數(shù)調(diào)用
  • 只有圖形著色函數(shù)才可以被 vertex 和 fragment 修飾,對于圖形著色函數(shù),通過返回值類型可以辨認(rèn)出是為頂點計算還是像素計算,其返回值也可以是 void,意味著不產(chǎn)生數(shù)據(jù)輸出到繪制管線,是一個無意義的動作

變量、參數(shù)的地址空間修飾符

Metal 著?器語?使?地址空間修飾符 來表示?個函數(shù)變量或者參數(shù)變量被分配于哪??內(nèi)存區(qū)域

  • device 設(shè)備地址空間
  • threadgroup 線程組地址空間
  • constant 常量地址空間
  • thread 線程地址空間

??注意

  • 所有的著?函數(shù)(vertex, fragment, kernel)的參數(shù),如果是指針或是引?,都必須帶有地址空間修飾符號
  • 對于圖形著色器函數(shù)(即vertex/fragment修飾的函數(shù)),其指針/引用類型的參數(shù)必須定義為 device、constant 地址空間
  • 對于并行計算函數(shù)(即 kernel 修飾的函數(shù)),其指針/引用類型的參數(shù)必須定義為 device、threadgroup、constant
  • 并不是所有的變量都需要修飾符(例如普通變量)
//變量/參數(shù)地址空間修飾符
void CCTestFouncitionE(device int *g_data,
                       threadgroup int *l_data,
                       constant float *c_data
                       )
{
}

設(shè)備地址空間修飾符(device)

在設(shè)備地址空間(Device) 指向設(shè)備內(nèi)存池分配出來的緩存對象,它是可讀也是可寫的;?個緩存對象可以被聲明成?個標(biāo)量、向量或是?戶?定義結(jié)構(gòu)體的指針或是引?

// 設(shè)備地址空間: device 用來修飾指針.引用
//1.修飾指針變量
device float4 *color;

struct CCStruct{
    float a[3];
    int b[2];
};
//2.修飾結(jié)構(gòu)體類的指針變量
device CCStruct *my_CS;

??注意

  • 紋理對象總是在設(shè)備地址空間分配內(nèi)存,即紋理對象默認(rèn)在GPU分配內(nèi)存
  • device 地址空間修飾符不必出現(xiàn)在紋理類型定義中
  • 一個紋理對象的內(nèi)容無法直接訪問,Metal 提供讀寫紋理的內(nèi)建函數(shù)

線程組地址空間修飾符(threadgroup)

  • 線程組地址空間用于為并行計算著色器函數(shù)分配內(nèi)存變量,這些變量被一個線程組的所有線程共享,在線程組地址空間分配的變量不能用于圖形繪制著色函數(shù)(即頂點著色函數(shù) / 片元著色函數(shù)),即在圖形繪制著色函數(shù)中不能使用線程組
  • 在并行計算著色函數(shù)中,在線程組地址空間分配的變量為一個線程組使用,生命周期和線程組相同
/*
 1. threadgroup 被并行計算計算分配內(nèi)存變量, 這些變量被一個線程組的所有線程共享. 在線程組分配變量不能被用于圖像繪制.
 2. thread 指向每個線程準(zhǔn)備的地址空間. 在其他線程是不可見切不可用的
 */
kernel void CCTestFouncitionF(threadgroup float *a)
{
    //在線程組地址空間分配一個浮點類型變量x
    threadgroup float x;
    
    //在線程組地址空間分配一個10個浮點類型數(shù)的數(shù)組y;
    threadgroup float y[10];
    
}

constant float sampler[] = {1.0f,2.0f,3.0f,4.0f};
kernel void CCTestFouncitionG(void)
{
    //在線程空間分配空間給x,p
    float x;
    thread float p = &x;
    
}

常量地址空間修飾符(constant)

  • 常量地址空間指向的緩存對象也是從設(shè)備內(nèi)存池分配存儲,但是它是只讀的
  • 在程序域的變量必須定義在常量地址空間并且聲明時初始化,用來初始化的值必須是編譯時的常量
  • 在程序域的變量的生命周期和程序一樣,在程序中的并行計算著色函數(shù) 或者 圖形繪制著色函數(shù)調(diào)用,但是 constant 的值會保持不變
constant float samples[] = { 1.0f, 2.0f, 3.0f, 4.0f }; 
 //對?個常量地址空間的變量進(jìn)?修改也會失敗,因為它只讀的 
 sampler[4] = {3,3,3,3}; //編譯失敗; 

 //定義為常量地址空間聲明時不賦初值也會編譯失敗 
 constant float a;

??注意

  • 常量地址空間的指針/引用可以作為函數(shù)的參數(shù),向聲明為常量的變量賦值會產(chǎn)生編譯錯誤
  • 聲明常量但是沒有賦予初值也會產(chǎn)生編譯錯誤

線程地址空間修飾符(thread)

  • 線程地址空間指向每個線程準(zhǔn)備的地址空間,也是在 GPU 中,該線程的地址空間定義的變量在其他線程不可見(即變量不共享)
  • 在圖形繪制著色函數(shù)或者并行計算著色函數(shù)中聲明的變量,在線程地址空間分配存儲
 kernel void my_func(...)
 { 
  float x;
  thread float p = &x; 
  ... 
 }

函數(shù)參數(shù)與變量

圖形繪制或者并?計算著?器函數(shù)的輸?輸出都是通過參數(shù)傳遞, 除了常量地址空間變量和程序域定義的采樣器以外。其他參數(shù)修飾的可以如下

  • device buffer 設(shè)備緩存, ?個指向設(shè)備地址空間的任意數(shù)據(jù)類型的指針或者引?;
  • constant buffer 常量緩存區(qū), ?個指向常量地址空間的任意數(shù)據(jù)類型的指針或引?
  • texture 紋理對象;
  • sampler 采樣器對象;
  • threadGrounp 在線程組中供各線程共享的緩存

為什么需要屬性修飾符?

  • 參數(shù)表示資源的定位,可以理解為端口,相當(dāng)于OpenGl ES中的location
  • 在固定管線和可編程管線進(jìn)行內(nèi)建變量的傳遞
  • 將數(shù)據(jù)沿著渲染管線從頂點函數(shù)傳遞到片元函數(shù)

被著?器函數(shù)的緩存(device 和 constant) 不能重名

對于每個著色函數(shù)來說,一個修飾符是必須指定的,它用來設(shè)置一個緩存、紋理、采樣器的位置

  • device buffer ---> [[buffer(index)]]
  • constant buffer ---> [[buffer(index)]]
  • texture ---> [[texture(index)]]
  • sampler ---> [[sampler(index)]]
  • threadGroup ---> [[threadGroup(index)]]

??注意

  • index 是?個unsigned integer類型的值,它表示了?個緩存、紋理、采樣器參數(shù)的位置(在函數(shù)參數(shù)索引 表中的位置)
  • 從語法上講,屬性修飾符的聲明位置應(yīng)該位于參數(shù)變量名之后
//屬性修飾符
/*
 1. device buffer(設(shè)備緩存)
 2. constant buffer(常量緩存)
 3. texture Object(紋理對象)
 4. sampler Object(采樣器對象)
 5. 線程組 threadgroup
 
 屬性修飾符目的:
 1. 參數(shù)表示資源如何定位? 可以理解為端口
 2. 在固定管線和可編程管線進(jìn)行內(nèi)建變量的傳遞
 3. 將數(shù)據(jù)沿著渲染管線從頂點函數(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ù)add_vectros ,實現(xiàn)2個設(shè)備地址空間中的緩存A與緩存B相加.然后將結(jié)果寫入到緩存out.
//屬性修飾符"(buffer(index))" 為著色函數(shù)參數(shù)設(shè)定了緩存的位置
//并行計算著色器函數(shù)add_vectros ,實現(xiàn)2個設(shè)備地址空間中的緩存A與緩存B相加.然后將結(jié)果寫入到緩存out.
//屬性修飾符"(buffer(index))" 為著色函數(shù)參數(shù)設(shè)定了緩存的位置
kernel void add_vectros(
                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 : ?于表示當(dāng)前節(jié)點在多線程?格中的位置;

//著色函數(shù)的多個參數(shù)使用不同類型的屬性修飾符的情況
kernel void my_kernel(device float4 *p [[buffer(0)]],
                      texture2d<float> img [[texture(0)]],
                      sampler sam [[sampler(0)]])
{
    //.....
    
}

內(nèi)建變量修飾符

  • [[vertex_id]] 頂點id 標(biāo)識符
  • [[position]]
    • 在頂點著色函數(shù)中,表示當(dāng)前的頂點信息,類型是 float4
    • 還可以表示描述了片元的窗口的相對坐標(biāo)(x,y,z,1/w),即該像素點在屏幕上的位置信息
  • [[point_size]] 點的大小,類型是 float
  • [[color(m)]] 顏色,m 在編譯前就必須確定
//定義了片元輸入的結(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; 
}
  • [[stage_in]] ?元著?函數(shù)使?的單個?元輸?數(shù)據(jù)是由頂點著?函數(shù)輸出然后經(jīng)過光柵化?成的。
    • 頂點和?元著?函數(shù)都是只能有?個參數(shù)被聲明為使?“stage_in”修飾符,
    • 對于?個使? 了“stage_in”修飾符的?定義的結(jié)構(gòu)體,其成員可以為?個整形或浮點標(biāo)量,或是整形或浮點向量
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容