SwapChain Present/sync to vertical trace error

Hi,

I’m using Direct3D 11 and have a question about IDXGISwapChain::Present() and sync to vertical retrace. Vertical sync doesn’t appear to be working correctly, or at least, it’s not working as I expect it to.

The refresh rate on my monitor is 60Hz (16.6ms). When I want to sync to vertical retrace I call Present(1, 0) on the IDXGISwapChain object. When the graphics engine would normally be running at greater than 60Hz, the Present(1, 0) call correctly clamps the framerate to 60Hz. The problem is when the engine is running under 60Hz. In this case, vertical sync doesn’t seem to be working. My assumption is that I should be seeing a framerate that is a multiple of the monitors refresh rate (16.6ms). So for my monitor I should be seeing framerates of 30Hz, 20Hz, 15Hz, etc depending on the current load on the engine. All of these are multiples of the monitor’s 16.6ms refresh rate. But that’s not what I’m seeing. I’m getting framerates of 50Hz, 45Hz. Nothing that’s a multiple of the monitor’s refresh rate.

Anyone have any ideas as to why vertical sync is acting this way? I’m using an NVIDIA GTX 780 graphics card.

I’ve seen this behavior on both my application and a DirectX SDK D3D11 application.

Thanks for any help.

Depending on the way you measure this, this is either an effect of a running averaged result of multiple frames, some at 60 Hz fewer at 30 Hz (e.g. using FRAPS), or the adaptive vsync feature kicked in:
http://www.geforce.com/hardware/technology/adaptive-vsync/technology

Thanks for the reply.

After looking into it a bit more I have to assume it’s the adaptive-vsync that’s turning vsync off for frames below the monitor’s refresh rate. I also have a GTX 765M mobile card that exhibits the same behavior. According to the cards website, it also supports adaptive vsync as does the GTX 780 card.

Is there a way to disable the adaptive vsync capability on the cards? I need to guarantee that the present is always occurring on vblank of the card whether this is at the monitor’s refresh rate or a multiple of that rate. In the NVIDIA Control Panel I tweaked the Vertical Sync settings (under 3D Settings) without much luck.

That is the setting to change. If that’s not helping when set to application controlled or force on, you should look at your measuring methodology again.
Maybe simply draw the milliseconds it took between each swap in a graph and if that jumps between 16.6 ms and 33.3 ms in an animation that would explain the averaged fps results.

What’s your OS? Windows desktop composition might have an influence.
Is your application running in a window or in full-screen exclusive mode?

I have it set to application controlled.

I set up something to print the per-frame stats to a flat file. Here’s what I get with vsync on (Present(1, 0)). Values in milliseconds.

33.6245, 19.2329, 30.5601, 19.8768, 30.4103, 33.3444, 20.4011, 29.7353, 19.0864, 31.1192, 33.8852, 33.072, 33.1368, 33.7225, 33.4468, 33.4705, 33.1961, 33.451, 19.4136, 30.493, 19.6383, 30.5985, 33.445, 33.549, 33.262, 19.5517, 30.5104, 19.1646, 31.3373, 33.1982, 19.1547, 30.6605, 19.4438, 30.9583, 33.4864, 33.2236, 19.1997, 30.8478, 19.3045, 30.8178, 19.3423, 31.0143, 20.0476, 29.9082, 33.3755, 33.4705, 33.3606, 19.2536, 30.7863, 33.5062, 19.1493, 31.0326, 33.416, 33.3246, 33.6098, 33.1658, 33.6149, 33.1949, 33.7405, 33.1949, 33.508, 33.3165, 33.4085, 19.185, 30.7306, 19.0804, 31.2543

The values aren’t at refresh rate multiples. Has the adaptive vsync taken over and essentially turned off vsync in this case?

I’m basically making changes to the DX SDK example now. It’s simpler than my application and also allows me to distribute it. The values above were collected from this app. Can I send it to you so that you can either verify what I’m seeing or determine what I’m doing wrong?

OS: Windows 7
And I’m testing the application in both windowed and full-screen mode. I see the same behavior in both modes.

Well, looks like the framerate toggles mostly between 60 Hz and 30 Hz.
The ones which are at 19 ms are most likely from 60 Hz frames. If you look closely the next number is always 3 to 4 ms shifted (e.g. 30 ms instead of 33.3 ms), so there is probably some asynchronous measurement going on.

From that it looks like it’s doing proper synchronization. If you sum up all these and average them you get 28.9257 ms which is about 34.57 fps. If that is displayed in FRAPS or something, that would be the expected result.

Feel free to attach a reproducer project if you like. Maybe someone can look at it, not me though. I’m not actively using DirectX, only OpenGL and OptiX.

Ok. I’ll attach the VS2012 project that reproduces the behavior.

Hmmm. Well I tried to upload it but doesn’t seem to be working right now. Or the zip file is too big. Not sure.

I still think that your measurement is not accurate enough for the numbers you seem to expect.
Present (or SwapBuffers in OpenGL) is not a synchronous operation. The display driver can put multiple frames into a command buffer if it fits (NV CPL: max pre-rendered frames) and issue them. This isn’t necessarily happening in the same thread either.
Simply taking the time after the point where you called Present isn’t going to give you perfect multiples of 16.66 ms. (That won’t happen everytime anyways because everything else going on inside the Windows operating system can influence this.)

Since your rendering doesn’t seem to finish in under 16.6 ms, what’s the full performance when not doing any wait for vsync? That should give you an estimate of how many frames will miss a refresh interval.
Similarly if you measure the time it takes before BeginFrame and after EndFrame before the Present.
Instead of instrumenting this you might want to profile your application with Nsight.

I’m trying to limit the number of frames the system can buffer using IDXGIDevice1::SetMaximumFrameLatency() to 1. If this is working correctly it’s supposed to define the number of frames the system is allowed to queue.

Full performance of the app is approximately 54Hz when not waiting on vsync.

I’ll try profiling the app with Nsight 4.0.