在Pixel Shader 3.0中使用动态流控制(Dynamic Flow Control)
作者:
cywater2000
日期:
2007-1-27
前段时间需要用到
pixel shader 3.0 (
以下简称
ps3)
的动态流控制特性。结果郁闷地发现
ps3
还是像
ps2.x
那样把分支与循环给展开了。百思不得其解
!
于是
google
之,在
GameDev
上发现了一些线索。再后来,终于在
DirectX Documentation
找到了真正的答案。
原来一切都是梯度惹的祸
!
在
DX
文档的“
Flow Control Limitations
”中,下面有一段介绍“
Interaction of Per-Pixel Flow Control With Screen Gradients
”。大意是说很多
ps
指令
(
主要是与纹理采样有关
)
需要用到屏幕梯度信息
(
比如计算
LOD)
,而计算梯度需要相邻象素的信息(
▽p=(δp/δx, δp/δy) )
,如果使用动态分支则会影响到硬件计算这些梯度信息。因此如果要在动态分支中使用带梯度信息的指令,则必须满足一定的条件。原文讲得很详细,我就不重复了。
下面举例说明:
void
test(float2 coords : TEXCOORD0,
out float4 newColor : COLOR)
{
float
j = tex2D(s0, coords).x;
while
(j != 100)
{
coords += float2(1, 1) / 256;
j += tex2D(s0, coords).x;
}
newColor = j;
}
使用
fxc.exe /Od /Zi /T ps_3_0 /E test /Fc test.txt test.fx
编译一下看看有什么效果?
error X3511: loop does not appear to terminate in a timely manner (1024 iterations)
好吧,让我们告诉微软的编译器只需要循环
2
次即可
(
听说新版编译器
fxc10
解决了这个
bug
,但是
…
还是展开的
)
void
test(float2 coords : TEXCOORD0,
out float4 newColor : COLOR)
{
float
j = tex2D(s0, coords).x;
int
i = 0;
while
(j != 100 && i++ < 2)
{
coords += float2(1, 1) / 256;
j += tex2D(s0, coords).x;
}
newColor = j;
}
再编译一下看看会生成什么?
ps_3_0
def c0, -100, 0, 1, 0.00390625
dcl_texcoord v0.xy // coords<0,1>
dcl_2d s0
texld r5, v0, s0
mov r2.z, r5.x // j<0>
add r12.w, r2.z, c0.x
mov r11.w, -r12.w
mov r13.w, -r11.w
add r10.w, r12.w, r13.w
cmp r8.w, r10.w, r12.w, r11.w
mov r8.w, -r8.w
add r9.w, r8.w, r8.w
cmp r7.w, r9.w, c0.y, c0.z
mul r6.w, r7.w, c0.z
mul r7.w, r6.w, c0.z
add r8.xy, v0, c0.w
mov r8.xy, r8 // coords<0,1>
texld r4, r8, s0
add r8.z, r2.z, r4.x // j<0>
mov r9.xyz, -r7.w
mov r2.xy, v0
cmp r6.xyz, r9, r2, r8 // coords<0,1>, j<0>
mov r8.xy, r8 // coords<0,1>
texld r3, r8, s0
add r5.w, r2.z, r3.x // j<0>
add r2.w, r5.w, c0.x
mov r3.w, -r2.w
mov r4.w, -r3.w
add r1.w, r2.w, r4.w
cmp r0.w, r1.w, r2.w, r3.w
mov r0.w, -r0.w
add r15.w, r0.w, r0.w
cmp r14.w, r15.w, c0.y, c0.z
mul r13.w, r14.w, c0.z
mul r11.w, r7.w, r13.w
add r10.xy, r6, c0.w // coords<0,1>
texld r1, r10, s0
add r12.w, r6.z, r1.x // j<0>
mov r11.w, -r11.w
cmp r0, r11.w, r6.z, r12.w // j<0,0,0,0>
mov oC0, r0 // newColor<0,1,2,3>
// approximately 38 instruction slots used (4 texture, 34 arithmetic)
What the heck!
你给我展开做什么?
!~~~~~
原来是因为
tex2D
对应的指令是
texld
,后者是要用到梯度信息的。咋办?
看看
DX
的文档,上面说想要在动态分支中使用这些指令,就必须由用户自己提供相关的信息。比如使用
texldl
,就由用户提供
LOD
信息。
好,让我们改一下代码:
void
test(float2 coords : TEXCOORD0,
out float4 newColor : COLOR)
{
float
j = tex2D(s0, coords).x;
while
(j != 100)
{
coords += float2(1, 1) / 256;
j += tex2Dlod( s0, float4(coords,0,0) ).x;
}
newColor = j;
}
会发生什么事?
ps_3_0
def c0, 0, -100, 0.00390625, 0
defi i0, 255, 0, 0, 0
dcl_texcoord v0.xy // coords<0,1>
dcl_2d s0
texld r8, v0, s0
mov r7.w, r8.x // j<0>
mov r2.w, v0.x // coords<0>
mov r1.w, v0.y // coords<1>
mov r0.w, r7.w // j<0>
rep i0
add r12.w, r0.w, c0.y
mov r13.w, -r12.w
mov r14.w, -r13.w
add r11.w, r12.w, r14.w
cmp r10.w, r11.w, r12.w, r13.w
mov r9.w, -r10.w
if_ge r9.w, r10.w
mov r4.w, r1.w
break
endif
mov r1.x, r2.w
add r5.yw, r1.xwzx, c0.z // coords<1,0>
mov r5.xz, c0.w
texldl r6, r5.wyzx, s0
add r0.w, r0.w, r6.x // j<0>
mov r4.w, r5.y
mov r2.w, r5.w
mov r1.w, r4.w
endrep
mov r3, r0.w // j<0,0,0,0>
mov oC0, r3 // newColor<0,1,2,3>
// approximately 27 instruction slots used (2 texture, 25 arithmetic)
终于看到
rep, if, break
了
!
哭
~~~~~
高兴之余提醒一下:
tex2Dlod
需要提供
LOD
信息
(
在纹理坐标的第
4
个分量中
)
,我这里只是简单地设定为
0
。如果你的纹理没有创建
LOD
层级,当然没问题,否则你必须要手动计算
LOD
。
另外,关于其他一些
ps
指令
(
比如
texld, texldb, texldp
)
的限制用法请参考
DX
文档
“
Interaction of Per-Pixel Flow Control With Screen Gradients
”。
好了,让我们利用动态分支开始写更复杂的
pixel shader
程序吧
!