Tuesday, November 17, 2009

Getting rid of evil

I know I said that I was done with updates and going into test/debug mode, but another essential one just came through.

Quake uses the Windows QueryPerformanceCounter and QueryPerformanceFrequency functions to control it's in-game timers. It turns out that these functions are evil on x64 and/or multi-core machines. Plenty of nightmare stories about timer drift, different cores running at different frequencies, and so on - just search on Google for more info.

This explains some of the issues I had experienced on a multi-core x64 machine, so I've switched over to the timeGetTime API family. This is the Windows built-in multimedia timer, and it can be configured to have a 1 millisecond resolution - perfect! (Side note: ID did the same for QuakeWorld and QuakeII, but for different reasons.)

Unfortunately I won't be able to confirm if this is a definitive resolution to the specific issues I had for at least a week or so.

I've also switched from using doubles for timing to using floats. This has finally enabled me to remove the D3DCREATE_FPU_PRESERVE flag from my video startup, and I now have an integer timer that's converted to a float every frame, so I'm effectively immune to all forms of timer drift caused by D3D switching to single precision mode unless you specify this flag (this was a serious problem in the old days while bringing up 1.0 for the first time).

The only issue with this timer is, because it's a 32-bit DWORD value, it's subject to the old wraparound bug after 49.7 days of continuous running. I guess this won't be happening too often though, and anyway the wraparound will only affect DirectQ if DirectQ happens to be running at precisely the moment in time it occurs, so I'm not going to worry too much about it.

All in all a very productive hour or so.

2 comments:

metlslime said...

ah... i think Baker found and fixed this bug in one of his fitzquake spinoffs. All i have is brief note in my todo list:

"CPU Clock fix (over/underspeeding issues that generate unplayable framerates.) Use Sys_DoubleTime instead of Sys_FloatTime. This is a problem on multicore windows machines, apparently"

mhquake said...

Ah-hah! I was thinking it was strange that nobody had seemed to notice it before, and wondering if anyone actually had.

It would also be a problem if a machine changed it's power-saving mode after a call to QPF.

I'd be of the opinion that as ID moved away from QPF to timeGetTime for subsequent engines, the Quake 1 way even back then was felt to be somehow flawed and that this was done for a Reason. I know that Carmack wrote about moving the timers from doubles to floats during the QW development, but can't recall any specifics right now.

It's also evil on x64 by the way. Apparently, again. I'll be able to do an exact apples-to-apples comparison shortly, so it'll be interesting to see what the result is.