學習了頂點處理,你就知道固定功能流水線怎么將頂點從模型空間坐標系統轉化到屏幕空間坐標系統。雖然固定功能流水線也可以通過設置渲染狀態和參數來改變最終輸出的結果,但是它的整體功能還是受限。當我們想實現一個外來的光照模型,外來的Fog或者點大小計算方式,等等,我們可能就放棄使用固定功能流水線,轉而使用CPU來實現這些計算。
使用vertex shaders,它用一段小程序替換固定功能處理。這段小程序的輸入是模型空間的頂點,輸出齊次剪裁空間的頂點,並且還攜帶一些信息,如:per-vertex diffuse 和 specualr,霧,透明度,紋理坐標和點大小。
這一節我們將先講述vertex shaders的匯編語言編程模型。
Vertex Shader Arichitecture
Direct3D對於不同的圖形處理器有不同的vertex shaders架構版本。每個版本都有不同數目和類型的寄存器和不同的指令集。一般情況,高版本一般是低版本的衍生品,提供了更多的指令和更少限制。我們將先看完整的1.1版本,然后討論各個版本在上面的增量。
DirectX 9.0c 支持的vertex shader版本包括1.1,2.0,2.x和3.0。 這些版本的匯編語言的語法標志是:vs_1_1, vs_2_0, vs_2_x和vs_3_0。在老的SDK和文檔里面,你也許會看到vs_2_a和vs_2_b,他們已經融合到了vs_2_x版本里面了。當你安裝SDK的時候,vertex shader的特殊版本也將會安裝,如vs_2_sw和vs_3_sw,這兩個版本只用於軟件處理,專門用於做模擬和調試之用。shader的軟件版本實現了2.0和3.0架構所有的功能,並且大部分的shader驗證將被放開。
shader 版本 | vs_1_1 | vs_2_0 | vs_2_x | vs_3_0 |
指令數目 | 128 | 256 | >=256 | >=512 |
所有的架構都共享一個公共的執行模型。執行程序稱做shader,它在每個頂點上執行一次。shader包含一個或多個指令,每個指令由一個操作碼與0個或多個操作數組成的。shader可以訪問五組不同的寄存器:頂點數據的input寄存器,渲染參數的const寄存器,用於查詢const寄存器的地址寄存器,存儲臨時數據的臨時寄存器,采樣紋理的采樣寄存器,shader輸出結果的ouput寄存器。不同類型寄存器的數目如下表。
Version | a0 | aL | bn | cn | in | on | p0 | rn | sn | vn |
vs_1_1 | 1 | 0 | 0 | >=96 | 0 | 13 | 0 | 12 | 0 | 16 |
vs_2_0 | 1 | 1 | 16 | >=256 | 16 | 13 | 0 | 12 | 0 | 16 |
vs_2_x | 1 | 1 | 16 | >=256 | 16 | 13 | 1 | >=12 | 0 | 16 |
vs_3_0 | 1 | 1 | 16 | >=256 | 16 | 12 | 1 | 32 | 4 | 16 |
每個臨時寄存器都存儲一個四維的向量值,大多數指令都是在四維向量上進行操作。每個值都是一個浮點值,一般有6個小數數字。指令一般是通用算術運算,如加,乘和一般的向量計算(點積,向量矩陣乘法)。跟一般的CPU不一樣的是,低版本的shader一般不支持流控制,以便於shader更加簡單和容易硬件加速。
- input 寄存器
頂點組件通過合適的頂點聲明映射到對應的semantics上。semantics使用dcl_usage指令與shader的輸入寄存器關聯。輸入寄存器是只讀的,只能用作頂點shader指令的數據源。雖然不同的操作數能應用到不同的修飾符,每個指令只能引用一個input regesiter。
- const 寄存器和地址寄存器
不隨着每個頂點變化的參數可以存放在const 寄存器。所有的shader版本都支持浮點const,整數const , bool const只能用在2.0以上的shader版本。每個指令一次只能訪問一個const 寄存器,但是不同的源操作數可以訪問帶有修飾符的同一個const寄存器。 const寄存器值在shader 里面一般是通過def, defb和defi指令定義的,它也能來自於設備,通過方法:SetVertexShaderConstantF, SetVertexShaderConstantB 和 SetVertexShaderConstantI。你可以認為通過shader指令定義的值為local const,而通過設備方法定義的const為global const。
地址寄存器是一個帶符號的整數,記錄了距離base const寄存器的位置偏移量。const寄存器是只讀的,地址寄存器是可寫的。當地址寄存器越界,它的值將是(0,0,0,0)。在使用地址寄存器之前,必須先初始化它。
shader 1.1 只能使用地址寄存器的x組件來作為索引。並且地址寄存器只能被設置成mov指令的目的地,當使用它的時候它將進行四舍五入成整數。 shader 2.0以上的版本提供了更加通用的一種使用方式。寄存器的四個組件都可以用來作為索引,能夠同時索引const寄存器的不同的部分。mova指令用於設置地址寄存器的值。
- output寄存器
output寄存器用於存儲shader計算的結果。output寄存器是可寫的。它用來存儲頂點的同次剪裁空間的坐標以及每個頂點相關的數據,如顏色,紋理坐標信息。3.0之前的shader版本,output寄存器將會分別命名。位置寄存器oPos, 顏色寄存器oD0和oD1,fog 寄存器oFog,點大小寄存器oPts和紋理坐標寄存器oT0到oT7。 每個頂點shader都得寫oPos的四個組件。fog系數和點大小的縮放值將分別取oFog和oPts寄存器的x組件。oFog和oPts將被縮放到【0,1】區間。在shader 3.0版本,output寄存器將會使用dcl_usage指令定義。
- 臨時寄存器
頂點shader里面通常會有大量的工作。shader 通常會將數據從輸入寄存器移動到臨時寄存器,然后在臨時寄存器上執行計算,最后把結果寫入到輸出寄存器。其他類型的寄存器在一個指令可能只使用一次,但是臨時寄存卻有可能使用多次。在一個指令里面有可能有3個臨時寄存器被讀,一個被寫。任何讀取一個沒有寫入數據的臨時寄存器都會產生錯誤。
- 循環計數器寄存器
shader 2.0或者更高版本使用loop和endloop指令來控制流,循環計數寄存器al包含計數器的當前值。在循環體外部,這個值是未定義的。在循環體內部它的值將是固定數組的偏移量。在shader 3.0中,循環計數寄存器將用於索引輸出寄存器和const數組。
- 條件寄存器
shader 2.x 或者更高版本將提供了條件寄存器,它包含一個boo值的四維向量。bool值將用於執行條件控制流。setp_comp是唯一的賦值條件寄存器的指令。條件寄存器bool值用來控制if ,callnz,breakp指令。
- 采樣寄存器
shader3.0 采用采樣寄存器來訪問紋理。采樣寄存器本身使用texldl指令來采樣紋理。采樣寄存器在使用前必須使用dcl_usage聲明。使用采樣寄存器,頂點shader能夠執行紋理查詢。
- 寄存器修飾符
每個指令默認情況下操作在源操作數和目的操作數的四維向量值上。為了提高頂點shader的靈活性,並且使指令數減少,每個操作數可以包含一個修飾符來提取某幾個維度的值。對於頂點shader指令,共有四種修飾符:目的操作數寫掩碼,源操作數multiplex,源操作數negation和絕對值操作數。修飾符的語法如下:
目的寄存器寫掩碼: r.xyzw
源寄存器multiplex: r.[xyzw][xyzw][xyzw][xyzw]
源寄存器negation: -r
絕對值: r_abs
邏輯negation: !r
multiplex修改符允許一個四維向量從一個源寄存器的四個組件構造得到。一個組件可能被組合到一個向量的多個組件。
一個操作數也能使用多個修飾符,多個修飾符也能應用到一個指令里面。
Vertex Shader 1.1 架構
shader 1.1架構是最簡單的架構,沒流控制也沒有條件分支。最少有96個頂點shader const 寄存器。D3DCAPS9::MaxVertexShaderConst定義最大數目的const寄存器。constant寄存器在被地址寄存器的x組件索引。
指令用於聲明,基本運算,矩陣計算,簡單比較以及基本光照計算。更高版本的shader能完全支持1.1的指令,只是在某些指令上有些微小的變動。
Vertex Shader 2.0 架構
2.0架構保留了1.1所有的指令和寄存器,並且增加了很多額外的功能。版本2.0主要的改進增加了是靜態流控制。靜態條件指令包括subroutine,分支和循環指令。在靜態流控制里面,計算分支點的條件表達式指向那些在shader執行過程中是const的值。使用靜態流控制,執行固定次數的循環,並且條件執行遵循同樣的路徑使用同一組constants來繪制primitives。 primitives的不同的batch處理可以通過改變constants來改變它們的行為。所有的流控制指令都是成對出現,並且屬於一個指令block。
提供了新的constant寄存器文件來定義了用於管理控制流的constants。在控制流里面,你能寫一個頂點shader應用到不同類型的頂點。定義流的constants可以在兩次draw primitives調用之間重新更新。
2.0版本或者更高版本也增強了地址寄存器的使用,提供了新的bool和整數寄存器文件。寄存器a0的四個組件都可以用來索引浮點數寄存器文件。bool和整數寄存器文件不可以被索引。地址寄存器的任何一個組件都可以用作一個索引,但是在一個指令里面的所有的源操作數必須用同樣的組件和base寄存器。
地址寄存器能夠使用mova指令來賦值,mov指令用於向臨時寄存器和output寄存器寫值。新的算術指令包括:abs,crs,lrp,nrm, pow, sgn和sincos指令。bool constant寄存器用於if,else,endif指令的條件分支。每個寄存器都有一個組件包含一個bool值。bool 寄存器文件的值能通過defb指令賦值。非條件suroutine使用call調用。subroutine的調用對象是lable和ret之間的block。 使用bool寄存器的條件subroutine使用callnz調用。整數constant寄存器文件里面每個寄存器都有四個組件,但是第四個組件必須是0。 寄存器控制了rep, endrep,loop和endloop循環的執行次數。rep使用一個重復次數定義了一個簡單的循環,在循環過程中,不會訪問內部計數寄存器。 Loop指令定義了一個循環,這個循環通過al 循環計數寄存器控制內部計數器。在循環開始之前,就初始化這個寄存器。每當循環一次,它就加1。這個循環計數寄存器也可以像地址寄存器一樣來索引constant寄存器數組。整數寄存器文件的值能通過defi定義或者通過API SetVertexShaderConstantI方法定義。
頂點Shader 2.x 架構
頂點2.x引入了版本2.0架構的擴展。在版本2.0的基礎上增加了條件,靜態流控制的深度嵌套和動態流控制指令。D3DCaps9的VS20Caps(它是一個D3DVSSHADERCAPS2_0結構)描述了可選的支持情況。2.x可選的支持包括predicate寄存器,動態流控制,大於12個臨時寄存器和靜態流控制的深度嵌套。
typedef struct _D3DVSHADERCAPS2_0
{
DWORD Caps;
INT DynamicFlowControlDepth;
INT NumTemps;
INT StaticFlowControlDepth;
} D3DVSHADERCAPS2_0;
如果Caps的D3DVS20CAPS_PREDICATION為被設置,設備將支持predicate寄存器p0和它相關的指令setp_comp,if, callnz和breakp。predicate寄存器是一個四維的bool向量,只能通過setp_cmp賦值。
NumpTemps制定了能支持的臨時寄存器rn的數目,一般至少是12個,它的實際值將在【12,32】之間,D3DVS20_MIN_NUMTEMP和D3DVS20_MAX_NUMTEMPS指定了最大值和最小值。
動態流控制的指令包括if_comp和break_comp。 如果dynamicFlowControlDepth不是0,它將能支持。
頂點Shader 3.0架構
頂點shader 3.0放開了很多限制,產生了input和output寄存器文件,增加了saturate指令修飾符,並且使用新的采樣寄存器和相關指令來做紋理采樣。臨時寄存器的數目上升到32個。最小的指令slot可以達到512。input 和 output寄存器文件可以像浮點const 寄存器一樣被索引。它允許shader在一個循環里面訪問input寄存器,然后產生ouput。output寄存器不用指定特定的名字,它就像input寄存器,統一命名為on。它可以使用dcl_usage指定把output寄存器與一個semantics關聯。這樣就可以將shader的output映射到像素shader的input semantics。
sn采樣寄存器與dcl_texture關聯。聲明之后,就可以使用texldl指令從對應的紋理采樣。
Shader指令語法
在內部Direct3D使用一個DWORD數組來encode一個shader程序。這個encoding可以被認為是一個shader程序的機器語言。因為很難直接創建一個DWORD數組程序指令,SDK提供了工具把一個shader程序文本編譯成機器語言。
shader指令的語法也跟大多數CPU匯編語言類似,首先是操作碼,然后是操作數。shader 程序文本首先被解析成一串可解析的符號。空格和注釋將會被忽略。跟其它匯編語言不同的是,它不必一行只能允許一條指令。一行可以寫多條指令。
每個shader指令是由一個操作碼和多個操作數組成,並且他們都是大小寫敏感的。通常const寄存器操作數一般是c0....。但是,可以通過地址寄存器a0來索引const寄存器,c[16+a0.x] 或者c16[a0.x]。
執行模型
頂點shader的執行模型是相當簡單,每個指令按照它在DWORD里面的次序執行。每個頂點shader的開始都必須放置一個vs指令,用來定義頂點shader的架構版本。 3.0之前的版本,都必須把值存放在oPos寄存器;3.0版本,output postion semantic關聯的寄存器必須要賦值。
頂點軟件處理
使用軟件或者混合處理創建的設備可以在CPU上運行頂點shader。頂點軟件處理能夠執行所有的頂點shader版本。
頂點shader 1.1 指令
頂點shader指令分成兩組,一組是簡單指令,一組是復雜指令。簡單指令只在一個slot里面執行,復雜指令需要在多個slot里面執行。1.1支持的指令如下:
Instruction | Slots | Function |
add d,s0,s1 | 1 | add |
dcl_usage d | —— | declare input register |
def d,v0,v1,v2,v3 | —— | constant definition |
dp3 d,s0,s1 | 1 | 3D dot product |
dp4 d,s0,s1 | 1 | 4D dot product |
dst d,s0,s1 | 1 | distance |
exp d,s | <=10 | full-precision exponentiate(指數) |
expp d,s | 1 | patial-precision exponentiate |
frc d,s | <=3 | fractional part(小數部分) |
lit d,s | 1 | lighting |
log d,s | <=10 | full-precision logarithm(全精度對數) |
logp d,s | 1 | partial-precision(半精度對數) |
m3*2 d,s0 ,s1 | <=2 | vector, 3*2 matrix product |
m3*3 d,s0,s1 | <=3 | vector, 3*3 matrix product |
m3*3 d,s0,s1 | <=4 | vector, 3*4 matrix product |
m4*3 d,s0,s1 | <=3 | vector, 4*3 matrix product |
m4*4 d,s0,s1 | <=4 | vector,4*4 matrix product |
mad d,s0,s1,s2 | 1 | multiply accumulate |
max d,s0,s1 | 1 | maximum |
min d,s0,s1 | 1 | minimum |
mov d,s | 1 | copy |
mul d,s0,s1 | 1 | multiply |
nop | 1 | no operation |
rcp d,s | >=1 | reciprocal(倒數) |
rsp d,s | >=1 | reciparocal square root |
sge d,s0,s1 | 1 | >= compare |
slt d,s0,s1 | 1 | < compare |
sub d,s0,s1 | 1 | sutract |
vs _major_minor_ | —— | shader version |
在詳細討論每個指令之前,我們先看看一個簡單的shader程序。這個shader 程序把輸入頂點數據直接寫入到對應的output寄存器。
vs_1_1
dcl_position v0
dcl_color0 v1
dcl_color1 v2
dcl_fog v2.w
dcl_texcoord0 v3
dcl_texcoord1 v4
dcl_texcoord2 v5
dcl_texcoord3 v6
mov oPos, v0
mov oDo , v1
mov oD1, v2.xyz
mov oFog, v2.w
mov oT0,v3
mov oT1,v4
mov oT2,v5
mov oT3,v6
- 指令聲明
每個頂點shader都必須使用vs指令聲明它的版本號碼,而且這個指令必須是這個shader程序的第一個指令。在shader通過SetVertexShader綁定到到設備的時候,頂點shader的constants也需要綁定。def 指令可以用來定義一個四浮點值的constant寄存器。def指令必須出現在版本指令之后,在任何計算指令之前。
def d,v0,v1,v2,v3 -------------> d<---------(v0,v1,v2,v3)
為了將頂點的input寄存器映射到頂點對應的組件,dcl_usage指令被使用。
dcl_positionn s
dcl_blendweightn s
dcl_blendindicesn s
dcl_normaln s
dcl_psizen s
dcl_texcoordn s
dcl_tangentn s
dcl_binormaln s
dcl_tessfactorn s
dcl_colorn s
dcl_fogn s
dcl_depthn s
dcl_sample s
- 基本算術指令
mov指令用來拷貝數據從源操作數到目的操作數。基本的運算執行只使用add,sub,mul 和mad指令。向量的加減使用add和sub指令。
mov d ,s d<-------s
add d,s0,s1 d<---------(s0x+s1x,s0y+s1y,s0z+s1z,s0w+s1w)
sub d, s0,s1 d<---------(s0x-s1x,s0y-s1y,s0z-s1z,s0w-s1w)
mul d,s0,s1 d<---------(s0x s1x,s0y s1y,s0z s1z,s0w s1w)
mad d,s0,s1,s2 d<---------(s0x s1x + s2x, s0y s1y + s2y, s0z s1z + s2z, s0w s1w + s2w)
rcp d, s 只計算w 組件。
如果sw = 1, d = (1,1,1,1); 如果sw = 0, d = (無窮大,無窮大,無窮大,無窮大);否則,d= (1/sw,1/sw,1/sw,1/sw)。
rsp d,s
如果abs(sw) = 1, d = (1,1,1,1); 如果abs(sw) = 0,d = (無窮大,無窮大,無窮大,無窮大); 否則 d= (1/squart root(sw), 1/squart root(sw),1/squart root(sw),1/squart root(sw))
dp3 d, s0,s1
d = (f,f,f,f) f = s0x s1x + s0y s1y + s0z s1z
dp4 d, s0,s1
d= (f,f,f,f) f = s0x s1x + s0y s1y + s0z s1z + s0w s1w
min d , s0,s1
d = (min(s0x,s1x),min(s0y,s1y),min(s0z,s1z),min(s0w,s1w))
max d, s0,s1
d = (max(s0x,s1x),max(s0y,s1y),max(s0z,s1z),max(s0w,s1w))
exp d,s
d = (f,f,f,f) f=2為底指數為sw的冪
log d,s
如果|sw| = 0 ,d = (負無窮大,負無窮大,負無窮大,負無窮大);否則,d= (f,f,f,f) f= log2(|sw|)
- 矩陣指令
m3*2, m3*3,m3*4,m4*4都是向量與矩陣相乘的指令。他們第一個操作數是向量,第二次操作數是矩陣。矩陣存放在連續的寄存器里面,並且在同一個寄存器文件里面。只有4*4,3*4修改了所有的四個組件,m3*2只修改xy,m3*3和m4*3只計算xyz。
- 比較指令
雖然1.1里面不可以使用分支指令,但是執行一些有限的比較也是可能的。如果你想要在diffuse color上再增加一個color。既然分支計算不允許,你只能寫兩個shader。一個增加顏色,一個不增加顏色。然而,你也可以在一個shader里面實現,當你不想增加的時候,另外一個顏色是0。sge和slt指令讓你可以這么多。
sge d,s0,s1
d = (s0x >= s1x, s0y>=s1y,s0z >= s1z, s0w >= s1w)。 True的時候組件是1.0, False的時候組件值是0.0。
slt d,s0,s1
d= (s0x < s1x, s0y < s1y, s0z<s1z,s0w<s1w)。< p="">
- 光照指令
dst 和 lit指令用於光照效果的計算。dst計算向量s0(*,k的平方,k的平方,*)和 s1=(*,1/k,*,1/k)的距離向量。 lit指令計算光照系數,給定兩個dot product和一個指數。源寄存器的x組件包含頂點法線和光線的點積,y組件包含頂點法線和halfway向量的點積,w組件包含一個指數。這個指數范圍將在[-128,128]。
dst d,s0, s1
d = (1,k,k2,1/k) s0=(*,k的平方,k平方,*) s1= (*,1/k,*,1/k)
lit d, s
如果sx >0 , sy >0 , d = (1,sx,sy為底指數為sw的冪,1);
如果sx>0, sy<=0 , d = (1,sx,0,1)
否則,d= (1,0,0,1)
頂點Shader2.0 指令
defb和def 定義bool和整數constant寄存器。
defb d, v
d = v
defi d, i0,i1,i2,i3
d= (i0,i1,i2,i3)
mova d, s
d= (round(sx),round(sy),round(sz),round(sw))
abs d,s
d= (|sx|,|sy|,|sz|,|sw|)
sgn d,s0,s1,s2
d= (f(s0x),f(s0y),f(s0z),f(s0w))
f(x) =-1(x<0); 0 (x=0); 1;(x>0)
crs d,s0,s1 d為s0和s1的叉積
nrm d, s normalize向量s。
Instruction | Slots | Function |
abs d,s | 1 | absolute value |
call l | 2 | call a subroutine |
callnz l,b | 3 | contionally call a subroutine |
crs d, s0,s1 | 2 | vector cross product |
defb d, v0 | —— | bool constant definition |
defi d,v0,v1,v2,v3 | —— | integer constant definition |
else | 1 | start a else block |
endif | 1 | end an if or esle block |
endloop | 2 | end a loop block |
endrep | 2 | end a repeat block |
if b | 3 | start an if block |
label l | —— | start subroutine block |
loop aL, i | 3 | start a loop block |
lrp d,s0,s1,s2 | 2 | linear interpolation |
mova d,s | 1 | write a address register |
nrm d, s | 3 | vector normalization |
pow d,s0,s1 | 3 | full precision s0為底s1為指數的冪 |
rep i | 3 | start a repeat block |
ret | 1 | end a subroutine block |
sgn d, s | 3 | sign function |
sincos d, s0,s1,s2 | 8 | sine and cosine,d = (cos(soc),sin(s0c),?,?);目的寄存器必須是一個臨時寄存器,並且必須使用寫掩碼.x,.y和.xy。s1,s2是浮點固定寄存器,他們的值分別是D3DSINCOSCONST1和D3DSINCOSCONST2。 |
最簡單的循環是一個repeat block,如下:
rep i //使用一個整數const寄存器作為操作數,它的x組件將是循環的次數,范圍在[0,255]。
count <---ix
loop <----pc+1 //pc指向程序計數器,pc+1就是下個程序指令的地址
if count = 0 then pc <-------endloop
endrep
endloop <----- pc+1
count <---------count -1
if count >0 then pc <------- loop
loop循環重復執行loop和endloop之間的代碼block,一個loop block通過aL寄存器控制循環,al寄存器將作為目的操作數。
loop aL,i
aL <--- iy
count <----ix
loop <-------pc=1
if count = 0 then pc<--------endloop
endloop
endloop <----- pc+1
count <---------count -1
aL <----------aL + iz
if count >0 then pc <------- loop
一個subroutine block將包含在lable和ret指令塊。 為了使用一個subroutine,你必須在shader之前使用ret結束 main routine。
label l
l <------------pc+1
ret
pc <----------pop(pc)
call l
push(pc,pc+1)
pc <------------l
callnz l,b
if(b = true) then
push(pc,pc+1)
pc<-----l
endif
- 版本2.0 flow control 嵌套限制
GPU有限的的資源使shader里面的流控制增加了一些限制。每種流控制指令(循環,分支,subroutine)都有對應的的嵌套限制。在一個指令block里面嵌入另一個指令block,這就是嵌套block。嵌套限制如下表:
Feature | 2.0 | 2.x | 3.0 |
Call Nesting | 1 | 1-4 | 4 |
static condition | 16 | 16 | 24 |
Dynamic Conditions | —— | 0-24 | 0-24 |
Loop Nesting | 1 | 1-4 | 4 |
static Flow count | 16 | 16 | 無窮大 |
除了對嵌套的限制外,對控制流指令的數目也有限制。控制流指令的總數目稱作靜態流計數(static flow count)。if ,else,rep,loop,call和callnz都會增加static flow count。 在2.0里面,靜態流條件指令只能出現在一個routine的top層。 call 和callnz也只能有一層調用,你不能在call里面在調用另外一個call。Loop和rep也只能有一層嵌套,rep 可以放在if block里面,但是它不能放在loop block。
頂點Shader 2.x指令
它在2.0的基礎上增加了predicate寄存器以及動態流控制。prediation指令一般是使用一個指令的修飾符來實現的。
新指令如下表:
Instruction | Slots | Function |
break | 1 | break out of a loop |
break_comp s0,1 | 3 | conditionally break out of a loop |
break p | 3 | conditionally break out of a loop |
callnz l,p | 3 | conditionally call a subroutine |
if_comp s0,s1 | 3 | start a dynamic if block |
if p | 3 | start a dynamic if block |
setp_comp d,s0,s1 | 1 | set predicate register |
//p0= (true,false,false,true)
(p0.x) add r3,r1,r2 //r3 = r1+r2
(p0) add r4,r1,r2 //r4.x = r1.x + r2.x
// r4.w = r1.w + r2.w
(!p0.x) add r5, r1,r2 // r5 unchangned
setp_comp指令是唯一能寫入predicate寄存器的指令。
setp_eq d,s0,s1
d = (s0x= s1x,s0y= s1y,s0z = s1z,s0w = s1w)
setp_ne , setp_ge,setp_gt,setp_le,setp_lt 這些指令都用於predicate寄存器賦值。
if 指令可以將predicate寄存器的某個組件結合起來使用,
break_eq s0.c,s1.c
if s0c= s1c then pc<--- endloop
break_ne,break_ge,break_gt,break_le,break_lt 也break_eq類似。
if_eq s0.c,s1.c
if(s0c = s1c) then ...
if_ne, if_ge,if_gt,if_le,if_lt 與if_eq類似。
頂點Shader 3.0指令
在3.0架構里面,所有的ouput寄存器都必須聲明。聲明語法類似input的聲明語法,關聯一個semantic usage和索引。地址寄存器除了索引const 寄存器外,還可以索引input和output寄存器。3.0新增指令如下表:
Instruction | Slots | Function |
dcl_position d | 0 | declare a positon output |
dcl_blendweightn d | 0 | declare a blend weight output |
dcl_blendindicesn d | 0 | declare a blend indices output |
dcl_psizen d | 0 | declare a point size output |
dcl_normaln d | 0 | declare a normal vector ouput |
dcl_fogn d | 0 | declare a fog factor output |
dcl_texcoordn d | 0 | declare a texture coordinate output |
dcl_tangentn d | 0 | declare a tangent vector output |
dcl_binormaln d | 0 | declare a binormal vector output |
dcl_tessfactorn d | 0 | declare a tessellation factor output |
dcl_depthn d | 0 | declare a depth output |
dcl_2d s | 0 | declare a 2D texture sampler |
dcl_cube s | 0 | declare a cube texture sampler |
dcl_volume s | 0 | declare a volume texture sampler |
texldl s | 2 or 5 | sample texture |
3.0 架構在頂點處理階段引入了采樣功能。源紋理的拓撲結構使用dcl_2d,dcl_cube,dcl_volume指令聲明的。每個指令都攜帶單個操作數,它將采樣寄存器sn與一個紋理關聯起來。如: dcl_2d s, dcl_cube s , dcl_volume s。
一旦采樣寄存器被聲明,texldl指令用於將一個紋理采樣到一個臨時寄存器。下面的代碼描述了它的基本原理。
texldl d, s0,s1 //s0是紋理的紋理坐標,s1是采樣寄存器,指示哪個紋理將會采樣。
L = s0w + SSLODBias //s0w用於選擇mipmap level,如果這個值是負數,它將選擇紋理的the most detailed miplevel。它的小數部分將用於兩個miplevel之前插值。
if(L<=0) then L = max(SSMaxMipLevel,0)
L = max(SSMaxMipLevel,0)
filter = SSMagFilter
q = lookup(s0,s1,L,filter) //對紋理進行采樣
else
L = Max(SSMaxMipLevel,L)
filter = SSMinFilter
q = Lookup(s0,s1,floor(L),filter)
if (SSMipFilter = Linear) then
r = lookup(s0,s1,ceil(L),filter)
f = s0w - floor(s0w)
q = (1-f)q + fr
endif
endif
d = q
Manipulating Shaders
我們可以使用CreateVertexShader, SetVertexShader和GetVertexShader方法來管理頂點shader。應用程序可以使用D3DX把頂點shader源代碼編譯DWORD指令數組,提供給CreateVertexShader使用。如果你想要構建運行時動態shaders,最簡單的方法就是從字符串構建shader函數,然后把字符串編譯成DWORD指令數組。匯編shader比high level shader 語言要快,因為它不必重新匯編shader。雖然high leve shader語言更消耗CPU,但是動態創建的high-level shader也可以按照這樣的方式執行。
設備的頂點shader constant 文件屬性可以直接通過GetVertexShaderConstant和SetVertexShaderConstant管理。每個寄存器文件都它自己的設備方法,如:
HRESULT GetVertexShaderConstantB(DWORD start, BOOL* value, DWORD count);
HRESULT GetVertexShaderConstantF(DWORD start, float* value, DWORD count);
HRESULT GetVertexShaderConstantI(DWORD start, int* value, DWORD count);
HRESULT SetVertexShaderConstantB(DWORD start, const BOOL *value, DWORD count);
HRESULT SetVertexShaderConstantF(DWORD start, const float* value, DWORD count)
HRESULT SetVertexShaderConstantI(DWORD start, const int * value, DWORD count);
start 參數指示第一個寄存器的序號,count指示四維向量值的數目,value指向一個值的數組。下面的例子將在寄存器c15里面存放一個值:
const float data[4] = {1.f,0.f,0.f,0.f};
device->SetVertexShaderConstantF(15,&data[0],1);
頂點shader constant隨着頂點shader 聲明隱式的變化。constant寄存器的內容一直維持到設備rest。如果幾個頂點shader使用同樣的constant寄存器layout,constant寄存器可以只load一次,應用程序能夠在幾個頂點shader里面前后前換,而不必重新load constant寄存器。 設備能支持的最大的constant寄存器的數目定義在D3DCAPS9::MaxVertexShaderConst。版本1.1至少是96個。
如果D3DCAPS9::VertexShaderVersion非0,設備將支持頂點shader。
Drawing Multiple Instances
頂點shader 3.0架構支持以不同的速率采樣不同的頂點流。這使我們能夠能夠繪制一個模型的多個實例,模型的數據將隨着每個頂點和每個實例變化。場景數據被至少將被分成兩組流,一組為每頂點的數據,一組為每實例的數據。source流的采樣頻率可以通過方法SetStreamSourceFreq方法設置。
HRESULT SetStreamSourceFreq(UINT stream, UINT frequency);
frequency將告訴runtime庫在跳到下一組組件之前每組頂點組件將需要重用多少次。Flags 將告訴runtime庫是否將將流解析成每頂點或者每實例。頂點shader 3.0版本支持instancing的索引流。頂點流的序號一般是從0開始,實例流的序號一般從大序號開始。
最簡單的例子如,使用相同的幾何體,不同的每實例數據繪制n個幾何體實例。 幾何體數據將重復n次,實例數據將只重復一次。
對於indexed primitives,將幾何流的頻率設置為D3DSTREAMSOURCE_INDEXDATA與流重復次數的“或”連接,將實例流的平率設置為D3DSTREAMSOURCE_INSTANCEDATA 與重用次數的"或"連接。
如下例子:
struct GeometryVertex
{
D3DVECTOR m_position;
D3DVECTOR m_normal;
}
struct InstanceVertex
{
D3DVECTOR m_offset; //每個實例位置的偏移量
D3DCOLOR m_diffuse; //每個實例的diffuse color
}
IDirect3DVertexBuffer9 *geometry = fill_geometry();
IDirect3DVertexBuffer9* instance = fill_instance();
THR(device->SetStreamSource(0,geometry,0,sizeof(GeometryVertex)));
THR(device->SetStreamSourceFreq(0,D3DSTREAMSOURCE_INDEXEDDATA|20));
THR(device->SetStreamSource(1,instances,0,sizeof(InstanceVertex)));
THR(device->SetStreamSourceFreq(1,D3DSTREAMSOURCE_INSTANCEDATA | 1));
Common Computation
- Constant generation
既然每個指令只能引用一個constant寄存器,使用多個constant寄存器作為源操作數是非法的。你可以使用mov指令將一個constant賦值給一個臨時寄存器。然而,如果這個constant的只是1或者0,你可以使用調用指令來產生值,而不用浪費一個constant。
slt r0,r0,r0 //r0= (0,0,0,0)
sge r0,r0,r0 //r0= (1,1,1,1)
- Fractional Part
expp的結果的y組件存放的是源操作數w組件的小數部分。
expp r1.y r0.x ; r1.y = r0.x - floor(r0.x)
mov r1.x ,r1.y
- Absolute value
max r0 r0,-r0
- Division
rcp r2.x, r1.x ;1/r1.x
mul r2.x, r2.x, r0.x
- Square root
rsq r1.x,r0.x
mul r1.x ,r1.x,r0.x
- conditional selection
; r0 = (c1 < c2) ? r3:r4
slt r0, r1,r2; c = (r1<r2)< p="">
mul r3,r3,r0 ;r3 = r3*r0
sge r0,r1,r2 ;c = (r1 >= r2)
mad r0 , r0 , r4, r3; r0 = c*r4 + r3
- clamping to an interval
;interval [A, B]
def c0 , A, B, 0,0
;r0 = clamp(r0,A,B)
max r0, r0,c0.x
min r0,r0,c0.y
- floor and ceiling
- vector cross product
c = a和b的叉積 = (aybz - az by, az bx - ax bz, ax by - aybx)
- vector normalization
- Transposed matrix multipliation
- signum function
- minimum and maximum vector component
- trigonometric functions
- exponential and logarithmic functions
固定功能處理
- 寄存器layout
使用在固定流水線的臨時寄存器的layout如下表:
r0 scratch r1 scratch r2 scratch r3(Rd) scratch, light vector r4(Rr,Rf,Rl) scratch , reflectance, eye or light vectors r5(Rs, Rv) scratch, sphere vector, eye vector r6(Rx) scratch, specular color r7(Rc) scratch, diffuse color r8(RH) scratch, half-angle vector r9(Rh) homogeneous eye space position r10(R3) cartesian eye space position r11(Rn) eye space normal
- constant 寄存器 layout
下面表給出了固定流水線constant 寄存器的layout。
Register Meaning or Value C[NORMAL0_MATRIX_X]
C[NORMAL0_MATRIX_Y]
C[NORMAL0_MATRIX_Z]inverse transpose world * view matrix 0 C[NORMAL1_MATRIX_X]
C[NORMAL1_MATRIX_Y]
C[NORMAL1_MATRIX_Z]inverse transpose world * view matrix 1 C[NORMAL2_MATRIX_X]
C[NORMAL2_MATRIX_Y]
C[NORMAL2_MATRIX_Z]inverse transpose world * view matrix 2 C[NORMAL3_MATRIX_X]
C[NORMAL3_MATRIX_Y]
C[NORMAL3_MATRIX_Z]inverse transpose world * view matrix 3 c[WORLDVIEW0_MATRIX_X]
c[WORLDVIEW0_MATRIX_Y]
c[WORLDVIEW0_MATRIX_Z]world * view matrix 0 c[WORLDVIEW1_MATRIX_X]
c[WORLDVIEW1_MATRIX_Y]
c[WORLDVIEW1_MATRIX_Z]world* view matrix 1 c[WORLDVIEW2_MATRIX_X]
c[WORLDVIEW2_MATRIX_Y]
c[WORLDVIEW2_MATRIX_Z]world * view matrix 2 c[WORLDVIEW3_MATRIX_X]
c[WORLDVIEW3_MATRIX_Y]
c[WORLDVIEW3_MATRIX_Z]world*view matrix 3 c[PROJECTION_MATRIX_X]
c[PROJECTION_MATRIX_Y]
c[PROJECTION_MATRIX_Z]
c[PROJECTION_MATRIX_W]projection matrix c[COMPOSITE_MATRIX_X]
c[COMPOSITE_MATRIX_Y]
c[COMPOSITE_MATRIX_Z]
c[COMPOSITE_MATRIX_W]world * view * projection matrix c[TEXTURE_MATRIX_X]
c[TEXTURE_MATRIX_Y]
c[TEXTURE_MATRIX_Z]
c[TEXTURE_MATRIX_W]texture matrix c[GLOBAL_ILLUMINATION] RGBA,emission + global ambient c[LIGHT_POSITION] x,y,z c[LIGHT_HALF_ANGLE] x,y,z for infinite light w/local viewer c[LIGHT_AMBIENT] RGB,light * material c[LIGHT_DIFFUSE] RGB, light * material c[LIGHT_SPECULAR] RGB, light * material, specular power c[LIGHT_ATTENUATION] a0,a1,a2,spot power c[LIGHT_SPOT_DIRECTION] x,y,z,cos(CUTOFF) c[POINT_PARAMETER] size, max,min c[POINT_ATTENUATION] a0,a1,a2 c[TEXTURE_OBJECT_PLANE_X]
c[TEXTURE_OBJECT_PLANE_Y]
c[TEXTURE_OBJECT_PLANE_Z]
c[TEXTURE_OBJECT_PLANE_W]x,y,z,w
x,y,z,w
x,y,z,w
x,y,z,wc[TEXTURE_EYE_PLANE_X]
c[TEXTURE_EYE_PLANE_Y]
c[TEXTURE_EYE_PLANE_Z]
c[TEXTURE_EYE_PLANE_W]x,y,z,w
x,y,z,w
x,y,z,w
x,y,z,wc[EYE_POSITION] x,y,z,w c[CONSTANT0] -1,0,1,0.5
- 坐標轉換
頂點位置和法線被一個連接的世界和視圖矩陣所轉換。如果使用skinning,位置和法線還要被每個skinning矩陣轉換。
;position
m4*4 Rh, vPosition , c[MODELVIEW0_MATRIX_X]
;normal
m3*3 Rn, vNormal, c[NORMAL0_MATRIX_X]
- 頂點blending
; weight preparation
mov r0, c[WEIGHT]
mov r0.w, c[CONSTANT0].z
dp4 r0.y , r0, c[CONSTANT0].xyyz
dp4 r0.z , r0, c[CONSTANT0].xxyz
dp4 r0.w, r0,c[CONSTNAT0].xxxz
;position /normal blend
mul Rh, r0.x, Rh //1st weight
mul Rn, r0.x ,Rn
mad Rh,r0.y,r3,Rh // 2th weight
mad Rn,r0.y,r2,Rn
mad Rh,r0.z,r5,Rh // 3th weight
mad Rn,r0.z, r5,Rn
mad Rh,r0.w,r7,Rh // 4th weight
mad Rn, r0.w,r6,Rn
- 位置輸出
m4*4 oPos, Rh,c[PROJECTION_MATRIX_X]
- normalize eye normal
dp3 Rn.w,Rn,Rn
rsq Rn.w Rn.w
mul Rn,Rn, Rn.w
- non-Homogeneous eye position
rcp r0.w, Rh.w
mul Re,Rh,r0.w
- Eye space vectors
頂點和眼睛之間的距離:
add r0, -Re,c[EYE_POSITION]
dp3 r0.w,r0,r0
rsq r1.w,r0.w
mul Rv, r0,r1.w
dst Rf, r0.w, r1.w
- Fog output
;radial fog
mov oFog.x Rf.y
;linear Z fog
mov oFog.x -Re.z
- Point Parameters
對於point sprites,頂點shader需要計算一個點的大小,輸出到oPts寄存器。
dp3 r0.w Rf, c[POINT_PARAMETER_ATTENUATION]
rsq r0.w r0.w
mul r0.w r0.w, c[POINT_PARAMETER].x
mul r0.w, r0.w, c[POINT_PARAMETER].y
mul oPts.w r0.w, c[POINT_PARAMETER].z
- Lighting
初始化
;diffsue only
mov Rc c[GLOBAL_ILLUMINATION]
; diffuse and specular
mov Rc c[GLOBAL_ILLUMINATION]
mov Rx, c[CONSTANT0].y
infinite Light 或者 infinite Viewer
dp3 r0.x , Rn , c[Light_position]
dp3 r0.y, Rn, c[LIGHT_HALF_ANGEL_VECTOR]
mov r0.w , c[LIGHT_SPECULAR].w
lit r0, r0
mad Rc.xyz, r0.x , c[LIGHT_AMBIENT], Rc
mad Rc.xyz, r0.y, c[LIGHT_DIFFUSE], Rc
mad Rc.xyz, r0.z, c[LIGHT_SPECULAR],Rx
SpotLight , Local viewer
;light direction/distance vectors
add r0, -Re, c[LIGHT_POSITION]
dp3 r0.w, r0,r0
rsq r1.w,r0.w
mul r1, r0,r1.w
dst Rd, r0.w, r1.w
; half-angle vector
add Rh, Rv, Rl
;normalize
dp3 Rh.w Rh, Rh
rsq Rh.w, Rh.w
mul Rh, Rh, Rh.w
;distance attenuation
dp3 r0.y , Rl, -c[LIGHT_SPOT_DIRECTION]
add r0.x , r0.y , -c[LIGHT_SPOT_DIRECTION].w
mov r0.w, c[LIGHT_ATTENUATION].w
lit r0,r0
mul Rd, Rd.w, r0.z
dp3 r0.x, Rn, Rl
dp3 r0.y, Rn, RH
mov r0.w, c[LIGHT_SPECULAR].w
lit r0,r0
mul r0,r0,Rd.w
mad Rc.xyz, r0.x, c[LIGHT_AMBIENT], Rc
mad Rc.xyz, r0.y, c[LIGHT_DIFFUSE], Rc
mad Rc.xyz, r0.z, c[LIGHT_SPECULAR], Rc
Lighting output
;diffuse only
mov oDO, Rc
;diffuse and specular
mov oD0 , Rc
mov oD1, Rx
- Texture 坐標generation
紋理坐標可以直接從頂點產生,或者由應用程序直接傳遞給shader。
;pass-thru
mov r0, v[TEX0]
Initialization
;reflection vector
mul r0, Rn, c[EYE_POSITION].w
dp3 Rr.w Rn, Rv
mad Rr, Rr.w, R0,-Rv
;sphere map vector
add r0,c[CONSTANT0].yyzy,Rr
dp3 r0.w , r0, r0
rsq r0.w r0.w
mul r0.xyz, r0, c[CONSTANT0].wwyy
mad Rs,r0.w,r0,c[CONSTANT0].wwyy
Texture Coordinate Generation
;object space plane
m4*4 r0, vPosition, c[TEXTURE_OBJECT_PLANE_X]
;eye space plane
m4*4 r0,Rh, c[TEXTURE_EYE_PLANE_X]
;sphere map
mov r0.xy, Rs
;normal vector
mov r0.xyz, Rn
;reflection vector
mov r0.xyz, Rr
Texture Coordinate Transform
m4*4 oT0,r0,c[TEXTURE_MATRIX_X]