Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Correct way to wait for VBLANK on windows 10 in windowed mode

What is the correct way to wait for VBLANK to present on windows 10 in windowed mode? Currently I'm doing the following:

D3DKMTWaitForVerticalBlankEvent(&waitData);
swapchain->Present(0, 0);

However this leads to occasional stutter. My current theory on the stutter is the following:
DWM completes composition at some time before the VBLANK event and then during the VBLANK interval, DWM tries to write to the front buffer as fast as possible. Therefore if Present is called in the VBLANK interval it possibly occurs after the DWM has written to the front buffer. This is the picture in my head: bad

So a more correct method would be call Present(1, 0) before VBLANK to queue the window's back buffer with DWM. Then call D3DKMTWaitForVerticalBlankEvent to wait until the next VBLANK. That is:

swapchain->Present(1, 0);
D3DKMTWaitForVerticalBlankEvent(&waitData);

With corresponding picture: good This second method seems to work perfectly, there is no longer any stutter.

My question is: What is the correct way to present in the VBLANK interval and is my theory correct or is there something more complicated/simple happening? Also are there any resources to learn more about this interaction?

Potentially helpful links:
MSDN Present
DXGI Best Practices
DXGI Waitable Swap Chain
D3DKMTWaitForVerticalBlankEvent

Edit: Not sure if this should be an answer or edit. After some googling I found this explanation by Nicholas Steel which seems align with my theory

There is no sure fire way to detect exactly when VBlank will occur because Windows doesn't expose a VBlank interrupt, and displays/GPU aren't necessarily required to generate one anyway (in addition, 'current scanline' information as given by e.g. IDirect3DDevice9::GetRasterStatus may not be accurate). As a result, programs generally poll for VBlank or rely on Direct3D/OpenGL to do it for them.

Programs present video frames during VBlank to avoid tearing, since the monitor will happily switch to the new frame mid-draw. With the compositor in Windows Vista and later versions of Windows, these programs will still detect VBlank and only present frames during it, as they think they are presenting video frames directly, when in reality the video frames are feeding into the compositor first. Frames sent to the compositor (from any running programs on the PC) will be queued up by the compositor, and merged together to be swapped/copied into place during VBlank.

Problems that can occur with this system:

1) A program polling for VBlank may miss composition. This will cause the frame to be queued up for the next composition, meaning the previous frame will be shown twice as long.

2) Worse, the next frame may not miss composition, and end up overwriting the previously queued up frame - so you end up with a duplicate frame followed by a skipped frame.

3) The program's VSync implementation may naturally fail to detect VBlank (which has only a short duration), causing it to wait until the next VBlank and risk problems 1 and/or 2.

4) These problems may even combine to generate a 'perfect storm' of duplicate and/or missed frames.

As you can see, this polling setup is not ideal, and far worse when a compositor is present. There are multiple problems that can cause a new video frame to fail to be displayed, causing a previous frame to be displayed for longer than intended and potentially skipping the new frame altogether!

The solution is to work with the compositor instead of against it. Present a new video frame immediately and afterward, call a command that will block until the compositor has completed it's task (DwmFlush). This will ensure that at most 1 new video frame is presented to the compositor between each VBlank period. As long as the compositor is active, you also won't have to worry about polling for VBlank yourself anymore.

So the best thing to do is:

if (DWM.isEnabled()) 
{ 
    present(); 
    DwmFlush(); 
} 
else 
{
    waitForVBlank(); 
    present(); 
}

Another edit: For future readers there is are two other ways to wait for VBLANK that I wasn't aware of they are IDXGIOutput::WaitForVBlank and IDirectDraw7::WaitForVerticalBlank the latter being deprecated.

like image 457
douglasquaid84 Avatar asked Mar 12 '18 21:03

douglasquaid84


1 Answers

Why are you trying to wait for VBLANK in windowed mode? DWM will ensure that the composited surface will flip in the VBLANK region.

When you say "DWM completes composition at some time before the VBLANK event", what would make DWM initiate composition? It does not happen after any app presents, or after the app in focus presents. Composition actually begins when the VBLANK occurs. It may finish and flip the composited surface within the VBLANK, or it may end up actually flipping in the next VBLANK (so you may end up with an extra frame duration in latency). That isn't in your control though - it depends on the duration of the composition and not on your app's present.

In a windowed app, when you call present, that just flips your app's swapchain - it does not flip the surface that is displayed. DWM will handle that. So "As long as the compositor is active, you also won't have to worry about polling for VBlank yourself anymore."

However, using DwmFlush() to achieve "at most 1 new video frame is presented to the compositor between each VBlank period" is not necessarily desirable. Unless you definitely want to just show 1 frame each monitor cycle, then you can do that (though I'm not sure if DwmFlush() is the most elegant approach). But in most cases, I don't think enforcing that limit is desirable.

like image 171
Alan Daniels Avatar answered Sep 25 '22 06:09

Alan Daniels