D3D修改浮点数精度

来源:互联网 发布:张艺兴钢琴水平知乎 编辑:程序博客网 时间:2024/06/03 09:20

前几天策划要求给任务系统加上日常任务的功能,这个需求很简单:象wow一样,日常任务在每天某个时候定时刷新。在思考添加这个功能时,很直接想到的方案是服务器取出3个时间:任务上次完成时间(pre),当前时间(now)和当天刷新时间(refresh),然后经过一些较复杂的逻辑完成(此处省略xx字)。由于该方案需要涉及一些时间操作,而在luac++里面做日期时间的操作又是比较麻烦的一件事。水古给出了一个解法,很好的解决了这个问题:首先用当前时间(比如现在是18:00)减去当天刷新时间(比如wow的日常每天15:00刷新),这样就把时间对齐到前一天的0点,然后分别用当前时间和上次完成时间除以一天的秒数(24*60*60),就分别得到了当前时间和上次完成时间的天数,如果上次完成时间的天数小于当前时间的天数,则日常任务刷新。lua代码大致如下:

local now = os.time()

local pre = task.completed_time

local refresh = (15-8)*(60*60)  --8是因为os.time()得到的是UTC时间,而北京时间是UTC+8

local pre_days = (pre-refresh)/(24*60*60)

local now_days = (now-refresh)/(24*60*60)

if(now_days > pre_days) then

       --刷新任务

end

很简洁,整个算法只需要一个时间函数——os.time()

 

本以为这个功能就这样完成了,但经过反复测试,悲剧发生了:服务器的时间正确,客户端却有1分钟左右的偏差。同样的库,同样的脚本,却得到不同的时间。掘地三尺后发现在客户端中lua中的双精度浮点数经过加减运算后变成了单精度浮点数,由于精度问题导致产生一定的时间偏差——和谐的阳光照在杯具上,每一个杯具都笑开颜。

首先想到的还是lua库的问题,毕竟是在lua脚本里面精度变了,但我直接在c++里面写了一个简单的测试,发现精度也改变了。再次和几个朋友讨论这个问题,水古提到:DX会改变浮点精度,确实,我记忆中好像在哪看到过,搜索D3D sdk,在CreateDevice中发现D3DCREATE有这么一个选项

D3DCREATE_FPU_PRESERVE

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:

  • Double-precision mode will reduce Direct3D performance.
  • Portions of Direct3D assume floating-point unit exceptions are masked; unmasking these exceptions may result in undefined behavior.

很明显了,d3d在未指定该参数的时候会把FPU设置为单精度模式,其原因是因为处理单精度数据比双精度具备更好的性能。由于D3D默认修改了FPU,影响到了lua的精度(lua里面number只有double),造成了一定时间的误差。

至于性能问题,我是这样想的,以单个浮点运算来看,现代cpudoublefloat的运算效率非常接近了,更多的效率考虑可能是cpu高速缓存的命中和内存带宽的暂用吧,毕竟doublefloat多一倍的数据量,而在3D环境中浮点运算大量存在。

 

最后说一下,该问题只是一个中间过程,最后把客户端时间修改成服务器的时间,在客户端就不用很精确的时间控制,所以这里暂时无需修改客户端代码,客户端仍然以默认的24FPU精度模式运行,如何兼顾性能和精度,也就没去深究了。