Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

Already on GitHub? Sign in to your account

Improved iOS game loop/v-sync timing #1030

wants to merge 30 commits into


None yet
9 participants

RayBatts commented Nov 26, 2012

Note: This change needs some heavy testing before it's pulled in with the rest of it. It's a pretty big change that only touched a couple lines of code.

After a lot of experimentation, we found that we were hitting v-sync waits in our application. Since there's no option to disable v-sync, it was causing a significant FPS loss in some cases. I implemented the technique discussed here: http://www.ananseproductions.com/game-loops-on-ios/

I'm open to any thoughts or suggestions, not sure if there's a better way we can get around having the DisplayLink's selector call Platform.Tick(), since whatever function CADisplayLink calls needs to be exported to ObjC.

tomspilman and others added some commits Oct 9, 2012

First run at changing the iOS game loop to use a CADisplayLink, rathe…
…r than a NSTimer in attempt to straighten out vsync timing.

totallyeviljake commented Nov 26, 2012

haha. That's funny. Why? Back in the old System 6 Mac programmer days, we had to call System.Tick() to do cooperative multitasking on the MacSE. A friend found that if you clicked the mouse while doing a big operation, like using Stuffit Expander, the big operation would run faster! Maybe there is some low level interrupt on the v-sync that you can trigger with some platform-specific code? Maybe we just need to send mice with iOS devices to cause platform interrupts. haha. Seriously, though, does Armed run faster if you tap the screen???

ddebilt commented Nov 27, 2012

Thanks for making this available. I had a chance to test it for a few hours tonight. I had some mixed results, but I made a couple changes to the algorithm while still keeping the overall theme of suppressing draws by looking at past performance. The best I am seeing on the 4th gen iPod (which is my only problem device) is consistently dropping 1 of every 6 draws. I will spend a few more hours on it tomorrow night when I get home from work, and then post my results from all 4 of my iOS devices (3rd gen iPod, 4th gen iPod, iPhone 4s, and iPad 2), as well as the modifications I used.


tomspilman commented Nov 27, 2012

The most important metric would be did it make your game perform better overall on all the different iOS devices you tested.

This cannot be merged if it makes things worse.


Just tested these changes on my MG Perf Test (http://monogame.codeplex.com/discussions/401025) and it seems to perform worse then before.

You can download the MG Perf test here (https://dl.dropbox.com/u/40411013/MonoGame/MGPerfTest.zip)

Results are:

develop2.0 60 FPS (Using Monogame 2.0 gles 1.1 still the best)
develop3d (no changes) 44 FPS (Using Monogame 3.0)
develop3d (with changes) 36 FPS (Using Monogame 3.0)

ddebilt commented Nov 27, 2012

John, would you be willing to replace your iOSGamePlatform.cs file with the following and run your tests?
EDIT: http://www.pasteall.org/37578/csharp

The modifications I made were to find out how much time is left until the next v-sync by subtracting the last v-sync time from the current time (CAAnimation.CurrentMediaTime()), and then seeing if the last frame overextended by more than that amount. If so, then suppress. I have a lot of data to sift through yet tonight and will posts results, but this was where I ended last night.

I'm having problem compiling it, it miss a _sw member


RayBatts commented Nov 27, 2012

@johnHolmes - Uncomment line #209.

Also, it's worth mentioning one of the notes about this technique from the linked article:

"This should take account sporadic frames being longer than usual. This isn’t meant to protect against a stream of frames that consistently take longer that 16 msecs. If that’s the case you need to lower your frame rate."

I'll have some time later today to run @johnHolmes 's test and see what's going on. A decrease from 44FPS to 36FPS seems to match up with what @ddebilt was saying about dropping 1/6 frames.

ddebilt commented Nov 27, 2012

Sorry about that, I shouldn't be editing code with NotePad++ at work :). I made an edit to my previous post with a new link.

Ok this time FPS are back to 44.

If you take a look to my test please note that the zip file will include a test app with two version of MG. An older version (which we currently user for our game) and the latest develop3d branch. You can run the test changing the MG version linked. You can see that the old MG (which use gl es 1.1) will perform better than the new develop3d branch. Maybe I'm missing something?

ddebilt commented Nov 27, 2012

@johnHolmes, what devices models are you testing on? I only see issues on the 4th gen iPod and regular iPhone 4. I saw a post elsewhere that someone else had performance issues on those specific models, and that I may be able to turn retina off to improve performance (on those models) via the UIView. I will have to try that tonight.

Also, before I posted that code link, I had manually removed all of my diagnostic code that recorded timing information { current time, last v-sync, ms after update, ms after draw, ms after present }. I would be curious to see how your game behaves as well in MonoGame 3.

In the current MG 3 branch, without any draw suppression, I would get the following frame pattern:

  • 2 good, 1 bad, 1 good, 1 bad

I will get the pattern I saw for the initial changes above, but it was somewhat similar to the current MG 3 branch.

With my modifications, I am able to get the following frame pattern:

  • 4 good, 1 bad, 1 suppressed { 1 of every 6 suppressed in order to catch up }

Where good is a tick length that is less than 17ms overall, and a bad is when the tick length was around 29ms.

I'm testing on a regular iPhone4 (6.0.1)

I've done tests many days ago (before your changes) with my perf tool and my game. MG 2.0 was performing much better than the new MG 3.0 (don't know why). I'm talking about FPS results only. I'm not sure my FPS issues are related to the problem you are investigating. Maybe yes.

During my tests I've noticed a strange behavior. Don't know if it's related. The perf tool draws 200 64x64 sprites. The draw call take ~5ms, but the FPS is still < 50. If I repleace the SpriteBatch.Draw call with a Thread.Sleep(5) the FPS become 60 stable.

ddebilt commented Nov 28, 2012

I changed the UIView.ContentScaleFactor to 1, and everything works perfectly. 60 FPS. At this point, I'm just going to add logic to set the scale factor back down to 1 if the model is 4th gen iPod or iPhone 4. I really don't notice a big difference in the quality due to the smaller buffer. I am using the following logic to find the model:


@johnHolmes, if you want to try this temporary code fix, add this line at the beginning of iOSGameView.CreateFrameBuffer:

this.ContentScaleFactor = 1;

It probably belongs in a better spot, but right now I set it in the Game object ctor() after I determine the model, and access it within iOSGameView from _platform.Game.

Another thing I was going to look into, if the above didn't work, was replacing the current OpenTK assembly with the one from mono/OpenTK on github. That version on GitHub has the DiscardFrameBufferEXT method, which would supposedly improve performance. It would have to be tested to know for sure. I read it from here:


ddebilt commented Nov 28, 2012

Even though setting the scale factor back to 1 made my initial test run fine, other types of tests are still running into the overall issue. I'd be interested to see the differences between 2.0 & 3.0.

I am seeing that drawing a 960x640 image, with a 0.5f scale, 5 times, has the same effect as drawing a 480x320 image, with a scale of 1.0f, 5 times. It only takes around 1.5 ms to make the 5 draw calls, but the ticks take longer than 12ms, and every 6th tick takes 29ms. This probably makes sense for those who know how it all works internally since the final result is the same, but it seemed somewhat suspect to me since I don't have any experience yet with OpenGL, etc. Also, the time stamp recorded at the beginning of each tick is always 4-8 ms passed the last vsync time (which suggests to me that something is still lingering/processing). When things are running fine, I see that the tick usually starts within 1-2 ms of when the last vsync has occurred.

@debbit: As you wrote, setting the scale factor to 1 (not the best solution IMHO, the device will run in a 320x480 resolution) doesn't work for me.

This topic is becoming a bit confusing for me, we need to clarify some points to be sure we are talking and working on the same problem. I'm not a guru of OpenGL and it's really hard to master.

Problem: OpenGL Performances
Objective: iPod4th/iPhone4 and above should run at 60FPS (stable)

johnHolmes: Noticed a performance degradation passing from MG 2.0 (gl es 1.1) to MG 3.0 (gl es 2.0)
Running the small app I've produced you can see FPS rating (you need to compile and run the app using MG2 or MG3 to see the resultin FPS) The app is provided with two version of MG. Change MG reference, compile and run.
App Download: https://dl.dropbox.com/u/40411013/MonoGame/MGPerfTest.zip

RayBatts: Discovered a potential issue related on v-sync wait which cause a performance degradation. It's related to single frame timing. Solution proposed comes from this link (http://www.ananseproductions.com/game-loops-on-ios/) but it seems not work.

Am I right? Am I missing something?

Are those issues related? Don't know, but it could be.

Some notes:

  • To run @ 60FPS the game should not take more than 16ms (Update/Draw) per frame (not always possible).
  • Setting Game.ElapsedGameTime = TimeSpan.FromSeconds() with values greater than 1/ 60f generate strange behavious
  • Having a Draw() call which renders sprites in 5ms or have nothing to draw but a Thread.Sleep() of 5ms generate different results (can't understand if drawing operations occurs after SB.End() or base.Draw()

ddebilt commented Nov 28, 2012

@johnHolmes, I agree, we should clarify to ensure that we're seeing and trying to solve the same issue.

ddebilt: 9 days ago I had posted on the MonoGame forums, refer to http://monogame.codeplex.com/discussions/403890#post945860, indicating that I was hitting a wall on the iPod4th/iPhone4. I was getting lower FPS than my other iOS devices. When looking into it, I was/am seeing the following:

  1. PresentRenderBuffer was ocassionally blocking for a long time, causing ticks to last longer than 1/60 of a second, even though the time it took for update/draw to complete was well within the 1/60 time period. V-sync was the cause of the block, and I had also stumbled upon the same game loop solution link, as above, that gave me that info.
  2. Even when a tick was not taking the full 1/60, subsequent ticks would not start immediately after the last v-sync had completed. This suggests to me that something is still processing/lingering that is causing this to happen. The link referring to using "DiscardFrameBufferEXT" suggests that if we do discard the contents of the frame buffers after we're done drawing "expensive tasks to keep the contents of those buffers updated can be avoided". I'm not sure this applies, or if this is causing late tick fires, but it sounds like something worth looking into.

I am willing to bet we are all experiencing the same issue, which is causing the FPS to drop.


totallyeviljake commented Nov 28, 2012

Out of curiosity, I did a little searching to see what other vsync problems exist. Seems there are quite a few issues with 60 fps and vsync:



"vsynced 60FPS with rare graduate FPS drops to 40-50 levels + tearing I got old plain vsync behavior: well-known 60-30-60FPS stutter you get with double-buffered vsync rendering."

It could very well be that you are dealing with a platform hardware driver problem on the iphone. Does the vsync problem appear for windows or linux targets? Could be that the GPU on the iPhone has some problems with 60 fps and the GL options that are enabled for it.

An interesting post about vsync:



RayBatts commented Nov 28, 2012

As for staying on topic, this pull request is only dealing with the vsync issue on iOS. This isn't meant to address any other performance issues. If we can keep this discussion focused on the vsync issue it'll help the people that'll eventually pull it into the repo.

Comments about general iOS performance or those not related to vsync should go to the following thread: mono#909

@ddebilt - Setting the Content scale isn't an solution. You're probably seeing a performance increase with this due to only drawing half of the pixels you were before.

@johnHolmes - I haven't had my Mac for a day or two. I should get it back soon. When I'm going to run a ton of profiling on it and see if it's vsync related.

"Setting Game.ElapsedGameTime = TimeSpan.FromSeconds() with values greater than 1/ 60f generate strange >behavious"

I think this is expected behavior. To my knowledge, iOS isn't capable of rendering more than 60FPS. CADisplayLink's interval can (rather as suggested by Apple SHOULDN'T) be set lower than 1.0, which will provide an update every 16ms.

"Having a Draw() call which renders sprites in 5ms or have nothing to draw but a Thread.Sleep() of 5ms generate >different results (can't understand if drawing operations occurs after SB.End() or base.Draw()"

Using a Thread.Sleep() doesn't sound like the correct way to test this vsync issue to me.

I kick myself for not saving my test case, but about a week ago I set one up that screamed "V-sync problem". I had virtually nothing rendering on the screen and most of my game logic commented out and was hitting a smooth 60FPS. After I added added back in method that did nothing more than a couple of matrix multiplications, the FPS dropped to ~30. When I re-enabled all drawing and game logic, our game was at ~15 FPS, and removing the matrix multiplications did not make a noticeable difference in performance. Because of these conditions, It's fairly obvious that the couple of multiplications in our test case were causing us to cause a vsync wait, which was effectively halving our draw calls. I'll try and set up a test case when I have my Mac again.


totallyevil commented Nov 30, 2012

I re-read the section in Tom Miller's book XNA Programming where it talks about v-sync performance and the "Present" method. I think Armored is suffering, not from a frame rate related issue in MonoGame, but rather a graphics pipeline problem in its own rendering/update code.

The iPhone GPU is clearly not as performant as other 60 fps devices, like your desktop. That means, not only do you have HALF as much time to "Present" your hardware changes, but you likely have HALF as much command buffer on the GPU (as you would on a 30 fps device, like WP7).

Miller suggests that if you see performance problems in the vsync update, then look at your own draw() calls that may be saturating the command buffer on the GPU. Armored (and Sly's game - Boot Camp) will likely need to revisit how they do the draw() calls on the iPhone at 60 fps using MonoGame.

This could also be a problem in SharpDX, but I can't really speak to that much. So long as MonoGame calls "Present" at the 60 fps rate, or whatever rate the vsync refresh rate runs at, then it's fine. It's up to the game developer to ensure that the draw() calls do not saturate the command buffer and cause hiccups in the calls to "Present".

Miller's book recommends setting vsynch=false and fixed time step = false to test your game's graphics performance.

My $0.02 worth.


KonajuGames commented Nov 30, 2012

Boot Camp on Windows Phone 7 ran at 15-20fps. No chance of getting near 60.

It was a very pretty 15-20fps though. :)


tomspilman commented Nov 30, 2012

fixed time step = false to test your game's graphics performance

That is a good point.

Testing at a fixed time step can cause vsync issues on platforms like iOS where you cannot disable vsync.

End of the day... if you are blocking on vsync most likely your update/draw is too slow.


totallyevil commented Nov 30, 2012

Hey Tom, that's where Miller would argue with you. His comments made it clear that it's not simple when the Present call is slow. In our experience though, and we all have quite a bit of it I think, doing batching of the draw() calls to better "consolidate" repetitive poly updates would likely gain the 10% - 20% performance boost needed to stop the hiccupy frame rate updates. Nobody wants to hear that it's their code that's the problem, though, right?

Honestly, I don't get the 60 fps rage. It cuts our update/draw time down in half, reduces the bandwidth available to us on the PCI interface, and only gets us double VBI bandwidth for better metadata on video playback. What's the point? :) haha.

There's some good performance component code in that book that I will get and put into the MG base. Along with catching up with the other PRs that I need to resolve.


tomspilman commented Nov 30, 2012

His comments made it clear that it's not simple
when the Present call is slow.

Present is not "slow"... it is limited by two things:

  1. Vsync.
  2. Your rendering work.

If Present is the CPU bottleneck in your game you are either taking too long to update/draw or submitting too much work for the GPU.

There is no other choice there.


totallyevil commented Nov 30, 2012

The "Present" call is when the graphics commands are flushed to the hardware, thusly drawing the scene. if that call is slow, or causes your game to appear to stop or slow down, then the hardware call is overloading the capabilities of the GPU. That's what I meant by "present is slow." We are both saying the same thing - the game is doing too much in draw(). What I know, though, the call to Present() is not asynchronous, so it can appear to run "slow."

For Ray to test this out more, he should wrap some performance checks around the update(), draw(), and present() calls to see where the frame rate is starting to degrade. I don't think there is anything that can be done in MonoGame to address any 'vsync' issues.


except the v-sync problem, did you have a chance to test my small "perf-test" to see how MG3 is performing? Anyone had a chance to try it?

ddebilt commented Dec 3, 2012

@johnHolmes: I did try it out, and posted in #909 in few minutes ago. To summarize, I am seeing what you're seeing.


tomspilman commented Dec 3, 2012

@johnHolmes - Please keep your discussion in the #909 thread... this pull request is addressing a different issue.


I don't think there is anything that can be done in
MonoGame to address any 'vsync' issues.

I disagree.

The core of this pull request is replacing NSTimer which does not support high resolutions. It is limited to a resolution of 50-100 milliseconds... that is not fast enough to consistently run a game at 30fps much less 60fps.

If the timer is firing late or inconsistently you can be blocked on vsync thru no fault of your own.

This is all this pull request is trying to solve. It will not "fix" a game that is already slow to update/draw.


tomspilman commented Dec 3, 2012

Also one could argue here that the SurpressDraw behavior here should be move to within Game.Tick() and make it something we do for all platforms and not just iOS. This would be more consistent with how MS XNA behaves when a game update/draw is falling behind.


totallyeviljake commented Dec 3, 2012

ha! I agree with you on both counts @tomspilman ... I didn't realize that NSTimer was not capable of high resolution ticking ...


tomspilman commented Dec 12, 2012

So lets get this pull request back on track.

  • This will not magically make a slow game fast.
  • This is only about ensuring we get accurate timer events from iOS.

Someone else posted about this issue on Codeplex:


I'm trying to see if I can get his test case to further validate these changes.


Nezz commented Dec 15, 2012

I can test this with our game if you want me to.


tomspilman commented Dec 15, 2012

Sure... that would be helpful.

@RayBatts - Did you ever run johnHolmes's test project above? Did you look into ddeblit's changes?


Nezz commented Dec 16, 2012

Tested on an iPhone 4 where we have performance problems:


Nezz commented Dec 17, 2012

The blue flashing occurs when we draw into a rendertarget in update.


tomspilman commented Dec 17, 2012

I cannot tell from that video what your point is.

Is it better? Is it worse? Is the blue flashing a bug introduced by this change?


Nezz commented Dec 17, 2012

Yep, it introduces the blue flashing.


tomspilman commented Dec 17, 2012

It seems to me that the 'SuppressDraw()' could cause that depending on how your render targets are updated. I don't know your code, so you have to tell me what you think it is.

Other than the blue flashing... is the frame rate the same, better, worse?


Nezz commented Dec 17, 2012

The relevant code extracted:

public override void Update(GameTime gameTime)
this.SpriteBatch.Begin(SpriteSortMode.Immediate, blendState);

public override void Draw(GameTime gameTime)

No noticable change in the framerate.

I see that the screen is always cleared with a cornflower blue-ish color. Why is that so?

I would like to note that this code halves our framerate on iPhone 4, so we had to disable it. On WP7, this resulted in no noticable change in framerate, even on the problematic first gen devices.


KonajuGames commented Dec 17, 2012

I always thought rendering in the Update wasn't fully supported.


Nezz commented Dec 17, 2012

Update and Draw functions are just guidelines, designed to make things easy for those who are new to game programming. In XNA, update is always called as many times as the programmers wants to (say, 60 times a second), even if draw can't keep up. This allows the usage of fixed time step animations (so you won't have to work with time elapsed), but results in performance problems if slow things (like draw) are called in update.
This behaviour is not supported by MonoGame (at least in async platforms like iOS), so it doesn't really matter where the draw code is.

We render in update because our framework was designed to encapsulate all draw functions in a single spritebatch.begin/end to make things as fast as possible.


tomspilman commented Dec 17, 2012

Hum... maybe this is the issue:

Game.Tick ();

if (!IsPlayingVideo)
   _viewController.View.Present ();

We probably shouldn't be calling Present() if the draw is suppressed.

@RayBatts RayBatts referenced this pull request Dec 17, 2012


GL Shader Precisions #1090


RayBatts commented Dec 17, 2012

Good catch, @tomspilman . I tested @ddebilt 's fix for this issue. It seemed to provide the same results, but I made sure that we skip the calls to present and setting the context if we're dropping supressing draw.


tomspilman commented Dec 28, 2012

Ok... so @RayBatts changes should have fixed @Nezz issues in his game.

If we can verify that then we can merge this.


Nezz commented Dec 28, 2012

I will look at this tomorrow.


Nezz commented Dec 29, 2012

The flashing is gone, but loading of our game has become a lot worse. There are a couple of empty screens presented after the built in loading screen disappears and black screen for a second after our own loading screen should fade out, so there mus be something wrong. I just don't know what it is.
As before, we did not gain any frames with this.


KonajuGames commented Feb 4, 2013

Not sure what to do here. Nezz appears to get different results to Ray.


tomspilman commented Feb 4, 2013

Timing issues are hard to fix by consensus. Results will vary between different apps and everyone feels their code is correct.

The core of this pull request is still valid... NSTimer is not high resolution as per Apple's docs so we shouldn't be using it as such.


I suggest the following:

  1. Close this pull request.
  2. Submit a new pull request with only the replacement of NSTimer.
  3. After that is fixed we can submit another pull to discuss the "vsync alignment" issue.

mgbot commented Feb 6, 2013

Can one of the admins verify this patch?

@RayBatts RayBatts closed this Feb 8, 2013

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment