学习了顶点处理,你就知道固定功能流水线怎么将顶点从模型空间坐标系统转化到屏幕空间坐标系统。虽然固定功能流水线也可以通过设置渲染状态和参数来改变最终输出的结果,但是它的整体功能还是受限。当我们想实现一个外来的光照模型,外来的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]