Skip to main content
GameDev.net gamedev.net
🔒 Locked

Vsync causing input lag

Started by Deran Jan 13, 2011 at 6:45 PM 14 replies 10.4k views
Original Post
Deran
Deran
Hello everyone!

I'm working on a game in C++ using Direct3D and I'm having a problem with vertical sync.

When I have my presentation interval set to default (Vsync on) I'm getting input lag, that is, the controls get all sloppy and slow. However if i change the presentation interval to immediate i get no lag whatsoever.

What I think is going on is that IDirect3DDevice9::Present is stalling the application because it's waiting for vertical sync. I'm looking for a way of getting around this problem.

I've found out that the IDirect3DDevice9Ex device (which only works on vista and later systems) has a Present method which can be set to not wait for vertical sync and instead return an error message if the hardware is not ready. So the question is, how do I achieve this using the normal IDirect3DDevice9 device?
MJP
MJP

Hello everyone!

I'm working on a game in C++ using Direct3D and I'm having a problem with vertical sync.

When I have my presentation interval set to default (Vsync on) I'm getting input lag, that is, the controls get all sloppy and slow. However if i change the presentation interval to immediate i get no lag whatsoever.

What I think is going on is that IDirect3DDevice9::Present is stalling the application because it's waiting for vertical sync. I'm looking for a way of getting around this problem.

I've found out that the IDirect3DDevice9Ex device (which only works on vista and later systems) has a Present method which can be set to not wait for vertical sync and instead return an error message if the hardware is not ready. So the question is, how do I achieve this using the normal IDirect3DDevice9 device?


That's the entire point of VSYNC: to block Present until the next vertical refresh. Regular D3D9 doesn't have any means of querying for the when that refresh happens. You can try and make a good guess based on frame time if you want, but I'm not sure what you would plan to do with that extra time.
Palidine
Palidine

What I think is going on is that IDirect3DDevice9::Present is stalling the application because it's waiting for vertical sync. I'm looking for a way of getting around this problem.


Yes. That is how vsync works. So a couple things

1) It is weird you are getting input lag. Updating at 60 fps shouldn't be noticeable as lag. Are you actually "feeling" it or are you just, with timing, noticing that your input is delayed by 1/60th of a second? If the latter humans shouldn't be able to notice that, many games ship with VSYNC on and input in the main thread. So if you're getting more than 1/60th of a second of lag there is a bug elsewhere; perhaps you aren't processing input correctly. Something else to look out for would be a framerate spike where occasionally a frame takes a long time to render. Profiling is a good approach here.

2) If for some bizarre reason you actually *need* better than 1/60th of a second of response time (something that I don't think a keyboard can even deliver) you can put input processing in a separate thread.

-me
Deran
Deran

That's the entire point of VSYNC: to block Present until the next vertical refresh. Regular D3D9 doesn't have any means of querying for the when that refresh happens. You can try and make a good guess based on frame time if you want, but I'm not sure what you would plan to do with that extra time.


Alright. I was hoping there was a way around it but I guess not. Thanks for the quick reply!



Yes. That is how vsync works. So a couple things

1) It is weird you are getting input lag. Updating at 60 fps shouldn't be noticeable as lag. Are you actually "feeling" it or are you just, with timing, noticing that your input is delayed by 1/60th of a second? If the latter humans shouldn't be able to notice that, many games ship with VSYNC on and input in the main thread. So if you're getting more than 1/60th of a second of lag there is a bug elsewhere; perhaps you aren't processing input correctly. Something else to look out for would be a framerate spike where occasionally a frame takes a long time to render. Profiling is a good approach here.

2) If for some bizarre reason you actually *need* better than 1/60th of a second of response time (something that I don't think a keyboard can even deliver) you can put input processing in a separate thread.

-me


Yes, indeed it is. I have spent alot of time debugging this issue; I even rewrote half of the input system. And to answer your question: Yes, It's very noticeable. I'm controlling camera movement with the WASD keys and, for example, if you hold down one of them for a while and release, the camera can keep moving for a bit after you release the key. It's also almost impossible to perform delicate movements with the camera, as it seems the application only picks up short key presses some of the time, while some of the time it doesn't. Mouse movement controls the direction of the camera, and it gets somewhat sloppy aswell.

As I said in the OP, this only happens when I have presentation interval set to default. If i set it to immediate i get smooth input with no lag at all. This is why I was wondering if i could somehow prevent presenting if the device is still waiting for vertical sync.

An interesting note is that this only happens in fullscreen. In windowed mode i have no lag whatsoever even if i enable vsync.

As I see it, there's two explanations:

1) Input happens as the application is waiting for Present to finish, and this somehow causes lag. This is plausible because it only happens when Present is causing the application to wait. However, it doesn't make much sense, since the lag shouldn't be more than 1/60th of a second, like you said.

2) Something is wrong with my input system, which I realize now is much more likely. I'm using raw input, and whenever i capture the WM_INPUT message in my windows message loop, my input system processes the input. Could something be wrong with this model? Should i use some other technique?
stonemetal
stonemetal
Sounds like you are processing one message then moving on to rendering instead of emptying the input queue each frame.
Deran
Deran

Could you post your message loop?



Message Loop:


MSG msg;
ZeroMemory( &msg, sizeof( msg ) );
while( msg.message != WM_QUIT )
{
if( PeekMessage( &msg, NULL, 0U, 0U, PM_REMOVE ) )
{
TranslateMessage( &msg );
DispatchMessage( &msg );
}
else{
if(paused == true){Sleep(100);}
velocity->Run();
}
}




Message Processor:


LRESULT WINAPI MsgProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
switch( msg )
{
case WM_DESTROY:
{
PostQuitMessage( 0 );
return 0;
}
case WM_ACTIVATE:
{
if(lParam == SC_MINIMIZE){
paused = true;
}else if(lParam == SC_MAXIMIZE){
paused = false;
}
}
case WM_INPUT:
{
velocity->input->Get(lParam);
}
}
return DefWindowProc( hWnd, msg, wParam, lParam );
}


Velocity is the class that encapsulates the entire game. The run method basically only checks if the device is lost, and if it is resets it, otherwise it renders.

lParam is passed to the class that handles input by the Get method.

If you can't find anything wrong here, feel free to request any other part of the code.
Deran
Deran

Sounds like you are processing one message then moving on to rendering instead of emptying the input queue each frame.


Wow, I can't believe I didn't think of that. That might just be the problem. I'm going to fix that and see if it does it.

Any idea why it only causes input lag when in fullscreen though?
Deran
Deran
Changed the message loop, however it did not fix the problem :/

Here's the new message loop:

MSG msg;
ZeroMemory( &msg, sizeof( msg ) );
while( msg.message != WM_QUIT )
{
while( PeekMessage( &msg, NULL, 0U, 0U, PM_REMOVE ) )
{
TranslateMessage( &msg );
DispatchMessage( &msg );
}

if(paused == true){Sleep(100);}
velocity->Run();
}
Promethium
Promethium
How do you get your input? It sounds like maybe you use unbuffered input, which means that the key have to be active (held down) in the precise instant that the key is queried. If this is the case, try switching to a buffered input method, for example using WM_KEYDOWN/WM_KEYUP.
Deran
Deran

How do you get your input? It sounds like maybe you use unbuffered input, which means that the key have to be active (held down) in the precise instant that the key is queried. If this is the case, try switching to a buffered input method, for example using WM_KEYDOWN/WM_KEYUP.


I am using raw unbuffered input. Should that really matter though? When i trap the WM_INPUT message, the input object is notified that a key has been pressed and immediately notifies the camera object.

I will try using buffered input and see what the result is.
Deran
Deran
Something weird just happened. I just found that the problem goes away if i disable my second monitor. I'm running two monitors (one is a TV, actually), and my windows desktop extends over both monitors. I have noticed before that having two monitors enabled can cause some performance drops in games (nothing significant though, usually just a bit lower framerate) so i tried disabling the second monitor and, voila, the lag disappeared. Also, I had a friend try and run the game on his machine and he experienced no lag.

I'm still puzzled as to how having my windows desktop extended to a second monitor could cause my application to lag. Does direct3d behave differently when there's two monitors enabled? Since this problem only appears when running fullscreen, could it be that I need to tell direct3d which monitor to use?

EDIT: Could it be that two monitors is causing the application to wait for vsync on both monitors?
Deran
Deran
Bump.

Anyone have any info on this?
SimmerD
SimmerD

Bump.

Anyone have any info on this?


Windows is most likely copying your backbuffer to a shared surface ( possibly in system memory ), in order to keep the two desktops in sync. This will cause more buffering and slower perf.
Adam_42
Adam_42
Another thing you could be running into here is that if the CPU is running ahead of the GPU D3D will buffer up to 3 frames worth of graphics commands. This can give you some noticeable input lag, especially if you have a slow monitor refresh rate.

You can limit the number of frames it buffers by doing an occlusion query every frame, and reading the result one frame later (ideally two frames later if using SLI in AFR mode).

http://developer.nvi...archive.html#16 has some more details.
Zoner
Zoner
Always a fun topic, since its basically covered in landmines.

Guidelines:

Pump the entire message loop until it is empty, before rendering anything. This might mean you need to keep a copy of various messages (primarily mouse/keyboard input) into your own queues for processing later. If you have a seperate rendering thread then correctly handling things like WM_SIZE events can be rather exciting and complicated, since it is absolutely unsafe for the main thread to wait on a d3d rendering thread.

Rendering on a seperate thread is a good idea but you also are required to create the d3d device context on the main message thread if you ever need to run in fullscreen. However D3D uses SendMessage to do bookkeeping/mode switches from the rendering thread and its pretty easy to get the threads deadlocked (render thread asks for mode switch, main thread sees the message and tries to flush the rendering thread which is already waiting on the main thread etc). The code that calls Reset and Present should be be in functions handled by user based windows messages and run on the main thread via PostMessage. The ideal is to keep the message queue empty at all times. Present isnt technically required to be on the main thread but it can causes mode switches and send messages requiring syncronous handling on the main message thread and its easier to deal with (and avoid deadlocks) if you are already there.

Present can block, but its only going to do that if the maximum number of frames are already in the command queue and it has to wait on one of the older ones to be evicted. This results in a paradox where running at a balanced or less than optimal frame rate can have less input latency than running with the default 3 queued frames worth of stuff. If your game or render threads take a combined 30ms, and the GPU is running at 30fps, the input latency is going to be 60ms. But if your game/render threads only take 2 ms, and the GPU is still running at 30fps, you can get 90 ms of input latency, and should see some 86ms of time blocked in Present.

In DXGI the frame limiter is configurable through the API (1 to 16 frames). The main way to get this down to 2 or 1 in D3D9 when you don't have the DXGI API is to force a stall in the rendering thread, by either locking a rendertarget (ideally the previous frames target sometime at the start of the next frame), or spin-waiting on an d3d query result to be available. Neither approach makes the GPU vendors very happy but they kind of get myopic on total theoretical frame rate instead of user input latency concerns. To be fair at extremely high frame rates (60+) the latency issues should more or less dissolve.

Modern display devices have frame buffers and introduce their own latencies which are hard to deal with at times.

Sadly there is no way to use WaitForSingleObject for pending occlusion queries, and spin waiting on these can burn a lot of cycles. The best that can be done here is to make sure your app calls timeBeginPeriod(1) at startup so a Sleep(1) can be made to work in the spin.

If your input data is old you should consider ignoring it. This is absolutely requires being able to pump the main message loop nearly instantly so you can timestamp everything coming in. This also means that the game state should also be running on its own dedicated tread or threads separate from the main thread, leaving the message pump for the main HWND a lean mean dispatching machine.
http://www.gearboxsoftware.com/

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.