Istrebitel's picture

Two problems with VSync

Greetings!

I have two different problems with VSync. I'm making a simple app that has to show an image to the user (string of text) for a very small amount of time. Basically, 1 frame on 60hz monitor. Obviously, this is a very simple program and if I unlock fps (VSync off), It's getting 130 FPS on an old Compaq 6510b notebook or 200 (max) on my PC.

In order to test both if the VSync is working, and if the 1 out of 60 frames is actually showing, I've made at test program. All relevant code is in the rendering event :

protected override void OnRenderFrame(FrameEventArgs e)
        {
            frames++;
            time_elapsed += e.Time;
 
            if (time_elapsed >= ttl_max) // const double ttl_max = 1.0
            {
                double fps = frames / time_elapsed;
                SetText(fps.ToString());
 
                frames = 0.0;
                time_elapsed = 0.0;
            }
 
            GL.Clear(ClearBufferMask.ColorBufferBit);
 
            if (frames == 0.0)
            {
                GL.MatrixMode(MatrixMode.Projection);
                GL.LoadIdentity();
                GL.Ortho(0, Width, Height, 0, -1, 1);
 
                GL.Enable(EnableCap.Texture2D);
                GL.Enable(EnableCap.Blend);
                GL.BlendFunc(BlendingFactorSrc.One, BlendingFactorDest.OneMinusSrcAlpha);
 
                GL.BindTexture(TextureTarget.Texture2D, text_texture);
 
                GL.Begin(BeginMode.Quads);
 
                GL.TexCoord2(0f, 0f); GL.Vertex2(0f, 0f);
                GL.TexCoord2(1f, 0f); GL.Vertex2(1920, 0f);
                GL.TexCoord2(1f, 1f); GL.Vertex2(1920, 1200);
                GL.TexCoord2(0f, 1f); GL.Vertex2(0f, 1200);
 
                GL.End();
            }
 
            this.SwapBuffers();
        }

Basically, I'm measuring FPS, and outputting it once per second. Of course, in the OnLoad of the GameWindow I've set VSync property to On.

Here are my two problems:

1) It doesn't work on some machines even if it is enabled in the application and allowed in drivers.

I've tested it on some PC's with different graphics cards and OS'es (XP and 7). On all of them, VSync worked , meaning, despite running the window with 200 target fps, It was showing 60 (or 75 for a 75Hz monitor). Now, am testing on a Compaq 6510b notebook (it has mobile intel 965 express chipset for a graphics cart) and it doesn't - it shows ~130 FPS. I've double checked, and in the OpenGL settings of the driver (Intel Graphics Media Accelerator Driver for mobile) it's set to default setting - which is Off for Asynchronous Flip - which means, Vsync is enabled (http://www.intel.com/support/graphics/sb/CS-030506.htm Figure 3). However, I just CANNOT get Vsync to work.

What can I be doing wrong? Is there any way to debug this?

2) Frames get skipped despite Vsync being on and working

On the PC I'm testing, sometimes the frame is skipped! PC is a quite modern one, GTX460 video card and Intel i7 processor 3GHz 4cores, Win7 x64, with vsync off I get up to 700 FPS (somehow, even though I specify 200 as target, because that's the maximum allowed). So, I'm running the app and I'm noticing every once in a while, about 2 seconds (instead of 1) pass between text flashing on the screen. Basically, sometimes a frame is skipped!

How can this be happening?

From what I understand, Vsync means that every buffer flip waits until the monitor requests the next frame, and flips right after. Therefore, there is absolutely no chance for any single "flipped" image NOT to appear on the monitor, whatever the frame rate (even if the frame rate would slow to a crawl due to a busy scene, which is not the case because as I said, the PC is quite fast and the task is trivial). Because in order for the image not to ever appear, only one thing can happen: before it got sent to the monitor, another image took its place. But it cannot happen because of the Vsync!

So, what can allow it to happen?

Thanks in advance!


Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
the Fiddler's picture

1)
VSync requires support for the WGL_EXT_swap_interval extension. This was published in 1999, so all modern GPUs should support it. The relevant code in OpenTK can be found in Source/OpenTK/Platform/Windows/WinGLContext.cs line 315.

If this extension is not supported, you will not be able to modify vsync at runtime. For instance:

window.Context.SwapInterval = 1;
if (window.Context.SwapInterval != 1)
{
    // vsync is not supported
}

In this case, your only option is a driver update from the GPU manufacturer. However, you should still be able to force vsync through the settings UI for your GPU.

2)
For the 700fps issue, please file a bug report at https://github.com/opentk/opentk/issues. It would help if you attach a tiny test case that I could compile and run directly.

Skipped frames can happen for a lot of different reasons, some of them fixable, some not. Most common:

  1. Latency spikes due to garbage collection. 700 fps gives you a moving average, but does not tell you anything about the standard deviation.
  2. DWM interference. Starting with Windows Vista, you no longer render directly to the screen. Instead, you render to an offscreen buffer and the DWM decides when and where to display that buffer. This can (and does) introduce stuttering.
  3. Framerate mismatch. Some monitors are not clocked to 60 Hz, but some number close to that (e.g. 59.94 to match the NTSC standard). This introduces a periodic stuttering (but the period is much longer than 2-3 seconds)

Rules of thumb:

  • Log your frametimes, not your framerate. If you see a frametime sequence of (milliseconds) "2, 2, 20, 2, ..." you can immediately locate the frameskip.
  • Avoid generating garbage at runtime. Load and cache everything you can at startup. This includes strings.
  • Double check that your monitor is running an exact integer refresh rate. There are applications that can build custom display modes if necessary.
  • Try disabling the Aero theme. (This is no longer an option on Windows 8)
  • Try increasing your process priority to "AboveNormal".

These may help alleviate, but not eliminate, the issue. Depending on the application you are building and the amount of accuracy you require, you might have to consider more drastic measures.

Istrebitel's picture

1) I don't think I can force it, the only option is to turn on Asynchronous Flip which disables Vsync, but there's no option (at least as far as I see) to force Vsync to On. Or maybe Asynchronous Flip = Off means Vsync is forced, but still, it's not happening.

The suggested code seems reasonable, but it doesn't work! I've just tried it on the notebook, and SwapInterval gets set to 1 all right (and when I compare it, it's = 1), but Vsync still is not happening.

2) Hmm, so even DirectX 11 games and such all render to the buffer that DWM manipulates?... Shouldn't it hurt in modern competitive FPS titles?

I have just tested, and wierd thing is, this "skipping" almost never happens on the 1.0 version, but it happens a lot of 1.1 (nightly) version. It still sometimes happens on 1.0 but very, very rarely.

Here's an archive with two programs I'm testing wtih http://www1.datafilehost.com/d/f255443a (uncheck the box at the bottom to get the file without the extra BS this site tries to give you).
Program should display FPS, lowest frametime in the last second, and then every frametime for the last second below in rows of 4. When the second passes, a text string should "blink" in the top left corner of the screen (right now it's just the same FPS and lowest frametime string). This "blink" should happen once every second. With 1.0 library verison, it does (I can't even get to to skip once right now, I waited for some time and it didn't skip once). With 1.1 it skips constantly (like, it takes me about several seconds to get the first skip).

What could have changed from 1.0 to 1.1 to introduce those?

Also, I tried filing an issue but "404" screen popped. Here's the version of my test app with vsync off http://www1.datafilehost.com/d/dcf10adf it gives 700FPS on my machine, even though the code is

using (SimpleWindow example = new SimpleWindow())           {                example.Run(200, 200);            }
the Fiddler's picture

Thanks for testing, this means that WGL_EXT_swap_buffer is supported but the driver is choosing to ignore that for some reason. Can you please do one more test with the SDL backend in the nightly? Just copy SDL.dll from Dependencies/x86 or x64 to your application directory and try again.

If the behavior has changed between 1.0 and 1.1, then this is a strong hint that the stuttering is caused by the garbage collector. The implementation of GameWindow is designed to avoid runtime memory allocations - I have filed a bug at https://github.com/opentk/opentk/issues/1

Fullscreen windows can bypass the DWM, so fullscreen games are generally not affected. When running in a window, there is small but measurable performance impact (but it doesn't really matter unless you are under severely limited in memory bandwidth.)

Istrebitel's picture

About SDL: Strangely, the lib is called SDL2.DLL in the x86 Dependencies, I've copied it and made a copy of it called SDL.dll just for sure in the folder with my program. No effect :(

About GameWindow: hmm, okay, then I guess my immediate question is this:
Can I myself fix the problem here http://www.opentk.com/node/3435#comment-14675 or is it too complicated? Could you probably hint me how to do it?

The thing I'm coding needs to be done ASAP, so while waiting for 1.1 release to tackle the GameWindow issue, I'd rather use the 1.0 - if I can make it possible to open new GameWindow after previous one was Exit'ed.

the Fiddler's picture

You are right, it should be named SDL2.dll, not SDL.dll! You can verify that you are getting the SDL backing as follows:

if (Configuration.RunningOnSdl2)
{
    // SDL2 backend
}

I am planning to make a 1.1 beta release later today, so I would suggest waiting for that if at all possible. There are no plans to backport fixes to the 1.0 branch at this point.

Istrebitel's picture

So, the difference between 1.1 and 1.0 (https://github.com/opentk/opentk/issues/1) will be fixed in this beta release, but it is not yet fixed in the nightly release 2013-11-12?

the Fiddler's picture

I'm investigating as we speak. It's the last blocking issue for the beta release.

Edit: memory allocations are flat after startup, which is how they are supposed to be. This is not a GC issue (or if it is, it's not caused by OpenTK.)

Investigating whether there are any other changes that might affect this.

Istrebitel's picture

About frame skips, I assume on your PC when you run my example programs, you experience the same (almost no skips with 1.0, regular skips with 1.1)?

About notebook, I did what you said, yes, RunningOnSdl2 = true, and no, it doesn't help. I understand this is probably a narrow issue with a brand of integrated graphics chipsets or even this exact notebook, and not a problem in OpenTK. However, I'd still like to learn as much about the problem as possible, because the users of the program would be even more confused - they have enabled VSync, and it doesn't work - and will blame the application, most likely.

At the very least I'd like to find a way to check if Vsync is being used or not. So far my only idea is to run it for several seconds, with no output and with target FPS of 200, and check what is the average FPS, and if it exceeds refresh rate by some margin, then conclude that VSync is not being used.

Btw, is there a way to get current monitor refresh rate? Couldn't find an obvious way...

the Fiddler's picture

I cannot evaluate the frame skips directly, because I use windows through a VM.

If SDL backend also fails to vsync, then it is probably safe to say that this issue is caused by a lower level of the stack (i.e. the drivers). In which case, your best chance would be to report the issue to Intel and/or the opengl.org forums.

You can get the current refresh rate using:

DisplayDevice.GetDisplay(DisplayIndex.First).RefreshRate

Btw, you should not use vsync and a "soft" frame limiter at the same time. In other words, if you are using vsync you should set TargetRefreshRate to 0.

Istrebitel's picture

Hey there! Any update on the 1.1 beta?

I think I may have some new information on the issue. As I have been recording the frame times for all the frames, I noticed the following:

OpenTK 1.0 - No frame skips
At the beginning, they are erratic, but after about a second or two, they usually settle to times that look like this:
1. 0,0216
2. 0,0118
3. 0,0167 (and then afterwards most have 0,0167 which is standart for 60Hz)
Very rarely will I get different numbers (I mean, there will be variation, but minimal, like, I won't get 0,0516 as a first number ever, but may get 0,220 or something)

OpenTK 1.1 - Frame skips quite regularily
At the beginning, they are erratic, and even after a second or two passes, they still are! Typical values look like this:
1. from 0,0560 to 0,0470
2. from 0,0280 to 0,0030
3. 0,0167 (and then afterwards most have 0,0167 which is standart for 60Hz)
Typically, when frame skip happens, value #2 is at its lowest (it can even very rarely go as low as 0,0003 or something) but sometimes value #2 is low but frame skip won't happen.

OpenTK 1.1 with SDL2.dll - Haven't seen a single frame skip yet (tested for several minutes)
Behaves just like OpenTK 1.0 with different values
1. 0,0316
2. 0,0028
3. 0,0167 (and then afterwards most have 0,0167 which is standart for 60Hz)
So, it's like 0,0100 is subtracted from 2 and added to 1. But it seems that there are no frame skips (at least, haven't encountered any yet!)

Now I have to explain what the numbers mean. Here's the relevant code:

 void SetText(string text)
        {
            if (Context.SwapInterval != 1)
            {
                text = "VSOFF " + text;
            }
            if (Configuration.RunningOnSdl2)
            {
                text = "SDL2 " + text;
            }
 
            using (Graphics gfx = Graphics.FromImage(text_bmp))
            {
                gfx.Clear(Color.White);
                gfx.DrawString(text, new Font("Arial", 24.0f), new SolidBrush(Color.Black), 0.0f, 0.0f); // Draw as many strings as you need
            }
 
            System.Drawing.Imaging.BitmapData data = text_bmp.LockBits(new Rectangle(0, 0, text_bmp.Width, text_bmp.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
            GL.BindTexture(TextureTarget.Texture2D, text_texture);
            GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, Width, Height, 0, PixelFormat.Bgra, PixelType.UnsignedByte, data.Scan0);
            text_bmp.UnlockBits(data);
        }
 
 
        /// <summary>
        /// Add your game rendering code here.
        /// </summary>
        /// <param name="e">Contains timing information.</param>
        /// <remarks>There is no need to call the base implementation.</remarks>
        protected override void OnRenderFrame(FrameEventArgs e)
        {
            frames++;
            time_elapsed += e.Time;
            time_list += e.Time.ToString("0.0000")+" ";
            time_list_word_count++;
            if (time_list_word_count > 3)
            {
                time_list_word_count = 0;
                time_list += "\n";
            }
 
            if (min_time > e.Time) min_time = e.Time;
 
            if (time_elapsed >= ttl_max)
            {
                double fps = frames / time_elapsed;
                SetText(fps.ToString() + " "+ min_time.ToString()+"\n"+time_list);
 
                frames = 0.0;
                time_elapsed = 0.0;
                min_time = double.MaxValue;
                time_list = "";
                time_list_word_count = 0;
 
            }
 
            GL.Clear(ClearBufferMask.ColorBufferBit);
 
            GL.MatrixMode(MatrixMode.Projection);
            GL.LoadIdentity();
            GL.Ortho(0, Width, Height, 0, -1, 1);
 
            GL.Enable(EnableCap.Texture2D);
            GL.Enable(EnableCap.Blend);
            GL.BlendFunc(BlendingFactorSrc.One, BlendingFactorDest.OneMinusSrcAlpha);
 
            GL.Begin(BeginMode.Quads);
 
            if (frames == 1.0 )
            {
                GL.BindTexture(TextureTarget.Texture2D, text_texture);
                GL.TexCoord2(0f, 0f); GL.Vertex2(0f, 0f);
                GL.TexCoord2(1f, 0f); GL.Vertex2(1920, 0f);
                GL.TexCoord2(1f, 1f); GL.Vertex2(1920, 1200);
                GL.TexCoord2(0f, 1f); GL.Vertex2(0f, 1200);
            }
 
            GL.BindTexture(TextureTarget.Texture2D, text_texture);
            GL.TexCoord2(0f, 0f); GL.Vertex2(0f, 40f);
            GL.TexCoord2(1f, 0f); GL.Vertex2(1920, 40f);
            GL.TexCoord2(1f, 1f); GL.Vertex2(1920, 1240f);
            GL.TexCoord2(0f, 1f); GL.Vertex2(0f, 1240f);
 
            GL.End();
 
            this.SwapBuffers();
        }

Here's what's happening, and what the numbers are about:
* The number itself is taken from e.Time of OnRenderFrame event.
* During frame zero (which comes before the first frame recorded in the list) I do all the "busy work" - namely, I DrawText on the rather big bitmap (1920 x 1200) and then TexImage2D its contents into OpenGL texture. Then I output the image - twice on this frame, and once on every other frame, which creates the "blink" effect. So the frame zero's SwapBuffers is what draws the frame that gets skipped.
* Then comes frame one. Since e.Time period is difference between the moments when OnRenderFrame event fires, it has a big e.Time, which stands for the time the "busy work" from the previous frame took to execute. On its own, it just outputs image once, like any other frame.
* Then comes frame two. Another normal frame. And all frames after it.

The same code is executed in all three cases, however, the result, as I have shown above, is different. I can only guess what causes them. It seems that TexImage2D is somewhat more... "volatile"? In the 1.1 version? It is definetly not due to the "busy work" taking more on 1.1 than 1.0 - I have added this line of code to the SetText routine:
Random rnd = new Random();  System.Threading.Thread.Sleep(rnd.Next(50)+50);
And it didn't make 1.0 version skip frames - it still shows every "blink" frame. While it seems (may be wrong here) that 1.1 started skipping more frames.

Maybe it is somehow related to how 1.0 and 1.1 treat frames that are "late" - meaning, that come after a frame that took too much time to get rendered?

There is another interesting thing, however.

The picture of frame times looks like this when I Sleep(100) in SetText:

OpenTK 1.0:
1. 0,1210
2. 0,0004
3. 0,0003
4. 0,0130
5. 0,0167 and so on

OpenTK 1.1:
1. ~0,1400
2. ~0,0200
3. ~0,0167 (and so on)

Maybe this is the key difference?

And actually, I'm kinda puzzled - so, despite Vsync, OpenTK 1.0 is actually skipping frames which are late (0,0004 and 0,0003 in a quick succession, followed by 0,0130 - those three frames couldn't have seen the light of the day, could they?) So it doesn't skip the frame that takes long to render, but skips other frames? While OpenTK 1.1 is more volatile - sometimes it'll skip the frame that took long to render, sometimes the frame that goes after it?

Another thing I'm puzzled by is that no matter what Sleep interval I enter (i tried 100, 200, 300) - the picture in the OpenTK 1.0 will be the same:

OpenTK 1.0:
1. 0,0210 + sleep interval
2. 0,0004
3. 0,0003
4. 0,0130
5. 0,0167 and so on

So no matter how much time did it take to render frame 0, it will always "recover" by frame number 5.

I am not sure what to make of it, except that I'm probably going to be satisfied with 1.1 + SDL2 for my task, as that appears to be working without skipping frames. But hopefully this may somehow help you find out the difference, and maybe fix some bug/clunky routine in OpenTK, if that is ever the case.

PS: About the notebook, I have tested on a completely differnt one, ASUS Ul20A, which has Embedded Intel® GMA 4500MHD for a video card, and it has the same problem - no matter what I set (it even has "Force VSync On" option, if it's translated into russian correctly of course) it won't use Vsync (it will report SwapInterval = 1, it will report vsync available, but won't use it).