Friday, February 03, 2006

Direct3D and math functions not working properly

I was working with some 3D geometry in C#. I found that before I created the DirectX device, all my calculations would work fine. The same calculations will not work, after creation of the DirectX device. It just did not make sense that (322460.391181 - 322460.195319) would be equal to 0.19586199999321 before creation of the directX device and after creation would be equal to 0.19586199522. A big change when it came to the kind of calculations I was doing. The eureka moment, when I realized that the after value was close to what I would get if I cast the original value to a float. Armed with that knowledge I googled to see if it was a bug. And the answer, no its not a bug, its an expected behavior. For more info, read the following info, which I got from Tom Miller's MSDN blog: Tom Miller's Blog : Direct3D and the FPU..: "Direct3D and the FPU.. I had an email this morning about Managed Direct3D 'breaking' the math functions in the CLR. The person who wrote discovered that this method: public void AssertMath() { double dMin = 0.54797677334988781; double dMax = 4.61816551621179; double dScale = 1/(dMax - dMin); double dNewMax = 1/dScale dMin; System.Diagnostics.Debug.Assert( dMax == dNewMax); } Behaved differently depending on whether or not a Direct3D device had been created. It worked before the device was created, and failed afterwords. Naturally, he assumed this was a bug, and was concerned. Since i've had to answer questions similar to this multiple times now, well that pretty much assures it needs it's own blog entry. The short of it is this is caused by the floating point unit (FPU). When a Direct3D device is created, the runtime will change the FPU to suit its needs (by default switch to single precision, the default for the CLR is double precision). This is done because it has better performance than double precision (naturally). Now, the code above works before the device is created because the CLR is running in double precision. Then you create a Direct3D device, the FPU is switched to single precision, and there are no longer enough digits of precision to accurately calculate the above code. Thus the 'failure'. Luckily, you can avoid all of this by simply telling Direct3D not to mess with the FPU at all. When creating the device you should use the CreateFlags.FpuPreserve flag to keep the CLR's double precision, and have your code functioning as you expect it."

1 comment:

Anonymous said...

Yep - this one shows up on the forums every so often. Its not very obvious is it?

I wrote about it here