Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to do exactly one render per vertical sync (no repeating, no skipping)?

I'm trying to do vertical synced renders so that exactly one render is done per vertical sync, without skipping or repeating any frames. I would need this to work under Windows 7 and (in the future) Windows 8.

It would basically consist of drawing a sequence of QUADS that would fit the screen so that a pixel from the original images matches 1:1 a pixel on the screen. The rendering part is not a problem, either with OpenGL or DirectX. The problem is the correct syncing.

I previously tried using OpenGL, with the WGL_EXT_swap_control extension, by drawing and then calling

SwapBuffers(g_hDC);
glFinish();

I tried all combinations and permutation of these two instructions along with glFlush(), and it was not reliable.

I then tried with Direct3D 10, by drawing and then calling

g_pSwapChain->Present(1, 0);
pOutput->WaitForVBlank();

where g_pSwapChain is a IDXGISwapChain* and pOutput is the IDXGIOutput* associated to that SwapChain.

Both versions, OpenGL and Direct3D, result in the same: The first sequence of, say, 60 frames, doesn't last what it should (instead of about 1000ms at 60hz, is lasts something like 1030 or 1050ms), the following ones seem to work fine (about 1000.40ms), but every now and then it seems to skip a frame. I do the measuring with QueryPerformanceCounter.

On Direct3D, trying a loop of just the WaitForVBlank, the duration of 1000 iterations is consistently 1000.40 with little variation.

So the trouble here is not knowing exactly when each of the functions called return, and whether the swap is done during the vertical sync (not earlier, to avoid tearing).

Ideally (if I'm not mistaken), to achieve what I want, it would be to perform one render, wait until the sync starts, swap during the sync, then wait until the sync is done. How to do that with OpenGL or DirectX?

Edit: A test loop of just WaitForVSync 60x takes consistently from 1000.30ms to 1000.50ms. The same loop with Present(1,0) before WaitForVSync, with nothing else, no rendering, takes the same time, but sometimes it fails and takes 1017ms, as if having repeated a frame. There's no rendering, so there's something wrong here.

like image 882
slazaro Avatar asked May 08 '12 13:05

slazaro


2 Answers

I have the same problem in DX11. I want to guarantee that my frame rendering code takes an exact multiple of the monitor's refresh rate, to avoid multi-buffering latency.

Just calling pSwapChain->present(1,0) is not sufficient. That will prevent tearing in fullscreen mode, but it does not wait for the vblank to happen. The present call is asynchronous and it returns right away if there are frame buffers remaining to be filled. So if your render code is producing a new frame very quickly (say 10ms to render everything) and the user has set the driver's "Maximum pre-rendered frames" to 4, then you will be rendering four frames ahead of what the user sees. This means 4*16.7=67ms of latency between mouse action and screen response, which is unacceptable. Note that the driver's setting wins - even if your app asked for pOutput->setMaximumFrameLatency(1), you'll get 4 frames regardless. So the only way to guarantee no mouse-lag regardless of driver setting is for your render loop to voluntarily wait until the next vertical refresh interval, so that you never use those extra frameBuffers.

IDXGIOutput::WaitForVBlank() is intended for this purpose. But it does not work! When I call the following

<render something in ~10ms>
pSwapChain->present(1,0);
pOutput->waitForVBlank();

and I measure the time it takes for the waitForVBlank() call to return, I am seeing it alternate between 6ms and 22ms, roughly.

How can that happen? How could waitForVBlank() ever take longer than 16.7ms to complete? In DX9 we solved this problem using getRasterState() to implement our own, much-more-accurate version of waitForVBlank. But that call was deprecated in DX11.

Is there any other way to guarantee that my frame is exactly aligned with the monitor's refresh rate? Is there another way to spy the current scanline like getRasterState used to do?

like image 176
t-skin Avatar answered Oct 13 '22 17:10

t-skin


I previously tried using OpenGL, with the WGL_EXT_swap_control extension, by drawing and then calling

SwapBuffers(g_hDC);
glFinish();

That glFinish() or glFlush is superfluous. SwapBuffers implies a glFinish.

Could it be, that in your graphics driver settings you set "force V-Blank / V-Sync off"?

like image 44
datenwolf Avatar answered Oct 13 '22 17:10

datenwolf