Vertex Shaders汇编语言编程模型.docx
《Vertex Shaders汇编语言编程模型.docx》由会员分享,可在线阅读,更多相关《Vertex Shaders汇编语言编程模型.docx(26页珍藏版)》请在冰豆网上搜索。
![Vertex Shaders汇编语言编程模型.docx](https://file1.bdocx.com/fileroot1/2023-1/22/f68691e6-05aa-4b66-9931-036f1c21ed24/f68691e6-05aa-4b66-9931-036f1c21ed241.gif)
VertexShaders汇编语言编程模型
VertexShaders汇编语言编程模型
文章出处:
学习了顶点处理,你就知道固定功能流水线怎么将顶点从模型空间坐标系统转化到屏幕空间坐标系统。
虽然固定功能流水线也可以通过设置渲染状态和参数来改变最终输出的结果,但是它的整体功能还是受限。
当我们想实现一个外来的光照模型,外来的Fog或者点大小计算方式,等等,我们可能就放弃使用固定功能流水线,转而使用CPU来实现这些计算。
使用vertexshaders,它用一段小程序替换固定功能处理。
这段小程序的输入是模型空间的顶点,输出齐次剪裁空间的顶点,并且还携带一些信息,如:
per-vertexdiffuse和specualr,雾,透明度,纹理坐标和点大小。
这一节我们将先讲述vertexshaders的汇编语言编程模型。
VertexShaderArichitecture
Direct3D对于不同的图形处理器有不同的vertexshaders架构版本。
每个版本都有不同数目和类型的寄存器和不同的指令集。
一般情况,高版本一般是低版本的衍生品,提供了更多的指令和更少限制。
我们将先看完整的1.1版本,然后讨论各个版本在上面的增量。
DirectX9.0c支持的vertexshader版本包括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的时候,vertexshader的特殊版本也将会安装,如vs_2_sw和vs_3_sw,这两个版本只用于软件处理,专门用于做模拟和调试之用。
shader的软件版本实现了2.0和3.0架构所有的功能,并且大部分的shader验证将被放开。
shader版本vs_1_1vs_2_0vs_2_xvs_3_0
指令数目128256>=256>=512
所有的架构都共享一个公共的执行模型。
执行程序称做shader,它在每个顶点上执行一次。
shader包含一个或多个指令,每个指令由一个操作码与0个或多个操作数组成的。
shader可以访问五组不同的寄存器:
顶点数据的input寄存器,渲染参数的const寄存器,用于查询const寄存器的地址寄存器,存储临时数据的临时寄存器,采样纹理的采样寄存器,shader输出结果的ouput寄存器。
不同类型寄存器的数目如下表。
Versiona0aLbncninonp0rnsnvn
vs_1_1100>=96013012016
vs_2_01116>=2561613012016
vs_2_x1116>=25616131>=12016
vs_3_01116>=2561612132416
每个临时寄存器都存储一个四维的向量值,大多数指令都是在四维向量上进行操作。
每个值都是一个浮点值,一般有6个小数数字。
指令一般是通用算术运算,如加,乘和一般的向量计算(点积,向量矩阵乘法)。
跟一般的CPU不一样的是,低版本的shader一般不支持流控制,以便于shader更加简单和容易硬件加速。
input寄存器
顶点组件通过合适的顶点声明映射到对应的semantics上。
semantics使用dcl_usage指令与shader的输入寄存器关联。
输入寄存器是只读的,只能用作顶点shader指令的数据源。
虽然不同的操作数能应用到不同的修饰符,每个指令只能引用一个inputregesiter。
const寄存器和地址寄存器
不随着每个顶点变化的参数可以存放在const寄存器。
所有的shader版本都支持浮点const,整数const,boolconst只能用在2.0以上的shader版本。
每个指令一次只能访问一个const寄存器,但是不同的源操作数可以访问带有修饰符的同一个const寄存器。
const寄存器值在shader里面一般是通过def,defb和defi指令定义的,它也能来自于设备,通过方法:
SetVertexShaderConstantF,SetVertexShaderConstantB和SetVertexShaderConstantI。
你可以认为通过shader指令定义的值为localconst,而通过设备方法定义的const为globalconst。
地址寄存器是一个带符号的整数,记录了距离baseconst寄存器的位置偏移量。
const寄存器是只读的,地址寄存器是可写的。
当地址寄存器越界,它的值将是(0,0,0,0)。
在使用地址寄存器之前,必须先初始化它。
shader1.1只能使用地址寄存器的x组件来作为索引。
并且地址寄存器只能被设置成mov指令的目的地,当使用它的时候它将进行四舍五入称整数。
shader2.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】区间。
在shader3.0版本,output寄存器将会使用dcl_usage指令定义。
临时寄存器
顶点shader里面通常会有大量的工作。
shader通常会将数据从输入寄存器移动到临时寄存器,然后在临时寄存器上执行计算,最后把结果写入到输出寄存器。
其他类型的寄存器在一个指令可能只使用一次,但是临时寄存却有可能使用多次。
在一个指令里面有可能有3个临时寄存器被读,一个被写。
任何读取一个没有写入数据的临时寄存器都会产生错误。
循环计数器寄存器
shader2.0或者更高版本使用loop和endloop指令来控制流,循环计数寄存器al包含计数器的当前值。
在循环体外部,这个值是未定义的。
在循环体内部它的值将是固定数组的偏移量。
在shader3.0中,循环计数寄存器将用于索引输出寄存器和const数组。
条件寄存器
shader2.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修改符允许一个四维向量从一个源寄存器的四个组件构造得到。
一个组件可能被组合到一个向量的多个组件。
一个操作数也能使用多个修饰符,多个修饰符也能应用到一个指令里面。
VertexShader1.1架构
shader1.1架构是最简单的架构,没流控制也没有条件分支。
最少有96个顶点shaderconst寄存器。
D3DCAPS9:
:
MaxVertexShaderConst定义最大数目的const寄存器。
constant寄存器在被地址寄存器的x组件索引。
指令用于声明,基本运算,矩阵计算,简单比较以及基本光照计算。
更高版本的shader能完全支持1.1的指令,只是在某些指令上有些微小的变动。
VertexShader2.0架构
2.0架构保留了1.1所有的指令和寄存器,并且增加了很多额外的功能。
版本2.0主要的改进增加了是静态流控制。
静态条件指令包括subroutine,分支和循环指令。
在静态流控制里面,计算分支点的条件表达式指向那些在shader执行过程中是const的值。
使用静态流控制,执行固定次数的循环,并且条件执行遵循同样的路径使用同一组constants来绘制primitives。
primitives的不同的batch处理可以通过改变constants来改变它们的行为。
所有的流控制指令都是成对出现,并且属于一个指令block。
提供了新的constant寄存器文件来定义了用于管理控制流的constants。
在控制流里面,你能写一个顶点shader应用到不同类型的顶点。
定义流的constants可以在两次drawprimitives调用之间重新更新。
2.0版本或者更高版本也增强了地址寄存器的使用,提供了新的bool和整数寄存器文件。
寄存器a0的四个组件都可以用来索引浮点数寄存器文件。
bool和整数寄存器文件不可以被索引。
地址寄存器的任何一个组件都可以用作一个索引,但是在一个指令里面的所有的源操作数必须用同样的组件和base寄存器。
地址寄存器能够使用mova指令来赋值,mov指令用于向临时寄存器和output寄存器写值。
新的算术指令包括:
abs,crs,lrp,nrm,pow,sgn和sincos指令。
boolconstant寄存器用于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定义或者通过APISetVertexShaderConstantI方法定义。
顶点Shader2.x架构
顶点2.x引入了版本2.0架构的扩展。
在版本2.0的基础上增加了条件,静态流控制的深度嵌套和动态流控制指令。
D3DCaps9的VS20Caps(它是一个D3DVSSHADERCAPS2_0结构)描述了可选的支持情况。
2.x可选的支持包括predicate寄存器,动态流控制,大于12个临时寄存器和静态流控制的深度嵌套。
typedefstruct_D3DVSHADERCAPS2_0
{
DWORDCaps;
INTDynamicFlowControlDepth;
INTNumTemps;
INTStaticFlowControlDepth;
}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,它将能支持。
顶点Shader3.0架构
顶点shader3.0放开了很多限制,产生了input和output寄存器文件,增加了saturate指令修饰符,并且使用新的采样寄存器和相关指令来做纹理采样。
临时寄存器的数目上升到32个。
最小的指令slot可以达到512。
input和output寄存器文件可以像浮点const寄存器一样被索引。
它可以允许shader在一个循环里面访问input寄存器,然后产生ouput。
output寄存器不用指定特定的名字,它就像input寄存器,统一命名为on。
它可以使用dcl_usage指定把output寄存器与一个semantics关联。
这样就可以将shader的output映射到像素shader的inputsemantics。
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版本,outputpostionsemantic关联的寄存器必须要赋值。
顶点软件处理
使用软件和混合顶点处理创建的设备可以在CPU上执行顶点shader。
软件顶点处理能够执行所有的顶点shader版本。
顶点shader1.1指令
顶点shader指令分成两组,一组是简单指令,一组是复杂指令。
简单指令只在一个slot里面执行,复杂指令需要在多个slot里面执行。
1.1支持的指令如下:
InstructionSlotsFunction
addd,s0,s11add
dcl_usaged——declareinputregister
defd,v0,v1,v2,v3——constantdefinition
dp3d,s0,s113Ddotproduct
dp4d,s0,s114Ddotproduct
dstd,s0,s11distance
expd,s<=10full-precisionexponentiate(指数)
exppd,s1patial-precisionexponentiate
frcd,s<=3fractionalpart(小数部分)
litd,s1lighting
logd,s<=10full-precisionlogarithm(全精度对数)
logpd,s1partial-precision(半精度对数)
m3*2d,s0,s1<=2vector,3*2matrixproduct
m3*3d,s0,s1<=3vector,3*3matrixproduct
m3*3d,s0,s1<=4vector,3*4matrixproduct
m4*3d,s0,s1<=3vector,4*3matrixproduct
m4*4d,s0,s1<=4vector,4*4matrixproduct
madd,s0,s1,s21multiplyaccumulate
maxd,s0,s11maximum
mind,s0,s11minimum
movd,s1copy
muld,s0,s11multiply
nop1nooperation
rcpd,s>=1reciprocal(倒数)
rspd,s>=1reciparocalsquareroot
sged,s0,s11>=compare
sltd,s0,s11subd,s0,s11sutract
vs_major_minor_——shaderversion
在详细讨论每个指令之前,我们先看看一个简单的shader程序。
这个shader程序把输入顶点数据直接写入到对应的output寄存器。
vs_1_1
dcl_positionv0
dcl_color0v1
dcl_color1v2
dcl_fogv2.w
dcl_texcoord0v3
dcl_texcoord1v4
dcl_texcoord2v5
dcl_texcoord3v6
movoPos,v0
movoDo,v1
movoD1,v2.xyz
movoFog,v2.w
movoT0,v3
movoT1,v4
movoT2,v5
movoT3,v6
指令声明
每个顶点shader都必须使用vs指令声明它的版本号码,而且这个指令必须是这个shader程序的第一个指令。
在shader通过SetVertexShader绑定到到设备的时候,顶点shader的constants也需要绑定。
def指令可以用来定义一个四浮点值的constant寄存器。
def指令必须出现在版本指令之后,在任何计算指令之前。
defd,v0,v1,v2,v3------------->d<---------(v0,v1,v2,v3)
为了将顶点的input寄存器映射到顶点对应的组件,dcl_usage指令被使用。
dcl_positionns
dcl_blendweightns
dcl_blendindicesns
dcl_normalns
dcl_psizens
dcl_texcoordns
dcl_tangentns
dcl_binormalns
dcl_tessfactorns
dcl_colorns
dcl_fogns
dcl_depthns
dcl_samples
基本算术指令
mov指令用来拷贝数据从源操作数到目的操作数。
基本的运算执行只使用add,sub,mul和mad指令。
向量的加减使用add和sub指令。
movd,sd<-------s
addd,s0,s1d<---------(s0x+s1x,s0y+s1y,s0z+s1z,s0w+s1w)
subd,s0,s1d<---------(s0x-s1x,s0y-s1y,s0z-s1z,s0w-s1w)
muld,s0,s1d<---------(s0xs1x,s0ys1y,s0zs1z,s0ws1w)
madd,s0,s1,s2d<---------(s0xs1x+s2x,s0ys1y+s2y,s0zs1z+s2z,s0ws1w+s2w)
rcpd,s只计算w组件。
如果sw=1,d=(1,1,1,1);如果sw=0,d=(无穷大,无穷大,无穷大,无穷大);否则,d=(1/sw,1/sw,1/sw,1/sw)。
rspd,s
如果abs(sw)=1,d=(1,1,1,1);如果abs(sw)=0,d=(无穷大,无穷大,无穷大,无穷大);否则d=(1/squartroot(sw),1/squartroot(sw),1/squartroot(sw),1/squartroot(sw))
dp3d,s0,s1
d=(f,f,f,f)f=s0xs1x+s0ys1y+s0zs1z
dp4d,s0,s1
d=(f,f,f,f)f=s0xs1x+s0ys1y+s0zs1z+s0ws1w
mind,s0,s1
d=(min(s0x,s1x),min(s0y,s1y),min(s0z,s1z),min(s0w,s1w))
maxd,s0,s1
d=(max(s0x,s1x),max(s0y,s1y),max(s0z,s1z),max(s0w,s1w))
expd,s
d=(f,f,f,f)f=2为底指数为sw的幂
logd,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里面不可以使用分支指令,但是执行一些有限的比较也是可能的。
如果你想要在diffusecolor上再增加一个color。
既然分支计算不允许,你只能写两个shader。
一个增加颜色,一个不增加颜色。
然而,你也可以在一个shader里面实现,当你不想增加的时候,另外一个颜色是0。
sge和slt指令让你可以这么多。
sged,s0,s1
d=(s0x>=s1x,s0y>=s1y,s0z>=s1z,s0w>=s1w)。
True的时候组件是1.0,False的时候组件值是0.0。
sltd,s0,s1
d=(s0x光照指令
dst和lit指令用于光照效果的计算。
dst计算向量s0(*,k的平方,k的平方,*)和s1=(*,1/k,*,1/k)的