标签:
最近在对我们的渲染引擎进行优化的时候,发现一个奇怪的现象,因为我们做了Pre-Z(把比较大的物体先绘制一遍,这个时候关闭颜色写,只开启深度测试和写入,目的是为了减少后面一些不可见像素的计算。),面在绘制另外一遍的时候(这一遍我们是把相同的进行了合批处理,采用硬件实例化技术(Hardware instancing)),会发现某些像素会闪烁,这个就说明了两次计算的深度并不一样才导致了闪烁的问题,知道了问题出现的原因,那就开始查找。
一开始主要有以下几个猜测:
1、可能是在把世界位置传给GPU时精度丢失了一部分精度,或者在GPU和CPU上计算的world-view-projection矩阵有差距?这个通过修改微软自带的Instancing实例进行验证,此猜测是错误的。
2、因为我们是把顶点位置进行了压缩成16位浮点数,是不是这样造成的?同样,我们紧接着修改了下微软自带的示例来进行验证,此猜测也是错误的。
3、难道两次计算的结果不一样?经过跟踪,发现两次计算相乘的矩阵是完全一样的,但是再次结果某些浮点数后几位确实是不一样的。这时候怀疑是不是多线程引起的,写了个测试程序,多个线程同时跑计算出来的结果是一样的,再次证明猜测是错误的。
4、也怀疑是编译器对浮点数计算优化的问题,把编译选项改成了/fp:precise结果还是一样,说明跟这个猜测也是错误的。
5、难道是在计算时浮点运算器x87 FPU运算时状态被修改了?这个时候同事帮忙在网上搜到了D3D9下面浮点精度的问题,这才找到了问题,原来是D3D9为了优化搞的鬼,看来对D3D9还是不太熟悉。D3D9在创建设备时有个BehaviorFlags,用于控制创建设备的。
其中有一个标记就是D3DCREATE_FPU_PRESERVE,D3D9对它的解释是这样的:
Set the precision for Direct3D floating-point calculations to the precision used by the calling thread. If you do not specify this flag, Direct3D defaults to single-precision round-to-nearest mode for two reasons:
也就是说D3D9为了效率考虑,把使用D3D9的线程强制修改了浮点运算器的标记位,这样在计算的时候就可以使用单精度浮点数进行计算了,这样做主要有两个原因:
1、双数度模式会降低D3D的性能。
2、一些函数假设浮点单元异常被标记了,否则会可能会出现未定义的行为。
在FPU中,存在着三种运算精度:single precision(24bits),double precision(53bits),double extended precision(64bits)。而默认精度是53bits的double precision,也就是双精度浮点。D3D出于性能考虑,会将fpu的计算精度改为单精度。因为fpu线程相关的特性,渲染线程中所有的浮点运算都会保持与D3D一致。这种转变体现在fpu的控制寄存器(CTRL)的变化上,CTRL寄存器的值从007F变成027F。
RC字段,这个字段控制浮点转整型的转换方式,
00 = 朝最接近或者偶数舍入 01 = 朝负无穷大方向舍入
10 = 朝正无穷大方向舍入 11 = 超0方向截断
PC 字段精度控制
00 = 单精度 01 = 保留 10 = 双精度 11 = 扩展精度[1]
既然找到了问题的原因,那么就要找一个比较好的解决方案:
1、创建设备时指定D3DCREATE_FPU_PRESERVE标记,让D3D9采用双精度浮点运算,这样会降低D3D9的运行效率。
2、让主线程和渲染线程同时使用单精度进行浮点数的计算,可以使用微软提供的函数_controlfp_s[2]来达到目的,这样可以提高所有的效果,但是可能会出现其它未预料到的精度问题,还有一个不好的地方就是需要客户端处理这个事情。比如有些人就遇到了客户端中lua出了问题的现象。[3]
3、所有需要传给GPU的浮点运算均在渲染线程执行,理论上可以,但是实际操作起来并不是那么现实。
我比较倾向于使用第一种方法,原因在上面已经说明了,不知道各位会怎么解决这个问题,欢迎留言讨论。
[1] http://www.cnblogs.com/wxxweb/archive/2011/12/13/2285565.html
[3] http://m.blog.csdn.net/blog/xiaohyy/5309145
标签:
原文地址:http://www.cnblogs.com/ghl_carmack/p/4662135.html