DirectX: D3D Mouse Lag
Sunday, February 1, 2009 6:38:40 AM
What can happen is one of two things:
1. The mouse seems to move behind where you expect it to be even though the game runs at acceptable speeds. It'll feel like your mouse is gliding on ice. This can also be delayed character movements, etc. but the game and character runs at full speed.
2. The mouse moves fine, but the display is behind by up to a second or more.
I was getting #2 because I use the default Windows mouse cursor.
So what is going on here? Why would this happen?
What is actually happening is that the fill rate is being used up while the command buffer isn't. Huh? Let me explain.
Suppose you want to draw a simple texture that fills the screen. This is TWO triangles (D3D only works on triangles), but you have millions of pixels. Now consider the case where you would move the texture to where the mouse pointer is. What you MIGHT get is a delay in where the texture is drawn. The texture could be drawn where the mouse pointer was up to a second ago. So the texture is lagging behind. If you have a super fast card, the fill rate (maximum pixels drawn per second) won't be used up, so the lag might not even exist. But for complex games, it can be. On older cards, the fill rate will be used up quite easily.
The problem with D3D is that you can queue multiple frames until the command buffer is filled up. The more frames you queue up, the larger the lag. And game programmers will do everything they can to reduce the number of state changes, compounding the problem.
What you really want is to have only one or two frames in the command buffer. Some might think that the Present() call will force the command buffer to be flushed out. But this is not true. It can return with a positive result, and what it really did was simply to queue the Present command into the command buffer. So while you're waiting for a frame to render, you have tons of frames queued in your command buffer that were computed in under one or two millisecond. When they get rendered, they will look identical since not much can happen in that short a time.
Storing commands is quick.
Rendering them is another story all together.
In essence what is happening is that I'm computing 100 frames worth of commands in 1 millisecond which fills up my command buffer. But the video card would take 1000 milliseconds to render those 100 frames @ 10 ms each which is rather good frame rate (100 fps). And this is how you get a top speed frame rate while having lag. Every frame you render from then on will be 1 second old because that's how long a frame takes to go from the back of the command buffer to the front. The extra 99 frames in your command buffer is what is causing the lag. (These numbers are examples only).
How to fix this?
If you're using DirectX 9, you can use an occlusion query. Make sure that the request ends AFTER the Present() call. When the query returns data, then you know that the frame has been displayed. You can now move on to rendering the next frame. I had tried to end the query before the Present() call, thinking that Present() flushes the command buffer, and it did not work.
With DX8 and before, you can try to lock the backbuffer and read a pixel. This has varying degrees of success from what I've heard. Locking will cause your thread to pause until the command buffer is done. So you might need to use threading and other tools to get your program to work correctly.
If you want to render 2 or 3 frames ahead (needed to take advantage of multi GPU cards), you can always use multiple queries. When the second or third one returns data respectively, you can draw the next frame. Make sure to rotate your query indices.
Anyways, I'm glad I've finally fixed this problem. In Project V, when you drag components around, they now stay relatively close to the mouse pointer. There is a one frame delay because of double buffering, but it's not really noticeable. So if anyone else ever gets this problem, even on faster cards, this is how you fix it.