Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Improved iOS game loop/v-sync timing #1030

Closed
wants to merge 30 commits into from

9 participants

@RayBatts

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
@tomspilman tomspilman Merge branch 'develop3d' into stable_ARMED! e5b3b90
@tomspilman tomspilman Merge branch 'stable_ARMED!' of github.com:SickheadGames/MonoGame int…
…o stable_ARMED!
9373dda
@tomspilman tomspilman Merge branch 'develop3d' into stable_ARMED! 605ad95
@tomspilman tomspilman Merge branch 'develop3d' into stable_ARMED! 5e0d21e
@tomspilman tomspilman Merge branch 'develop3d' into stable_ARMED! 202d06b
@tomspilman tomspilman Merge branch 'winrt_fixes' into stable_ARMED! 069c1d0
@tomspilman tomspilman Merge branch 'develop3d' into stable_ARMED! 72b7861
@tomspilman tomspilman Merge branch 'soundfix' into stable_ARMED! 38fcfc0
@tomspilman tomspilman Merge branch 'miscfix' into stable_ARMED! 66ea3df
@tomspilman tomspilman Merge branch 'develop3d' into stable_ARMED! fe4f1a3
@tomspilman tomspilman Merge branch 'stable_ARMED!' of github.com:SickheadGames/MonoGame int…
…o stable_ARMED!
4183670
@tomspilman tomspilman Merge branch 'develop3d' into stable_ARMED! 1ea3287
@tomspilman tomspilman Merge branch 'soundfix' into stable_ARMED! 687842e
@tomspilman tomspilman Merge branch 'miscfix2' into stable_ARMED! 2b9428e
@tomspilman tomspilman Merge branch 'stable_ARMED!' of github.com:SickheadGames/MonoGame int…
…o stable_ARMED!
e7f49cd
@tomspilman tomspilman Merge branch 'develop3d' into stable_ARMED! 3e26f74
@tomspilman tomspilman Merge branch 'develop3d' into stable_ARMED! b1368a2
@tomspilman tomspilman Merge branch 'develop3d' into stable_ARMED! 1545e27
@tomspilman tomspilman Merge branch 'develop3d' into stable_ARMED! 00f975a
@tomspilman tomspilman Merge branch 'mplayer' into stable_ARMED! 2ae2661
@tomspilman tomspilman Merge branch 'develop3d' into stable_ARMED! 441a8fb
@tomspilman tomspilman Merge branch 'develop3d' into stable_ARMED! e4d5fa2
@RayBatts RayBatts First run at changing the iOS game loop to use a CADisplayLink, rathe…
…r than a NSTimer in attempt to straighten out vsync timing.
7c9ad15
@RayBatts RayBatts Miscommit. d17606a
@tomspilman tomspilman Merge branch 'develop3d' into stable_ARMED! 9c0ad17
@RayBatts RayBatts Merge branch 'develop3d' of https://github.com/SickheadGames/MonoGame
…into ImprovediOSGameLoop
45d808d
@RayBatts RayBatts Merge branch 'stable_ARMED!' of https://github.com/SickheadGames/Mono…
…Game into ImprovediOSGameLoop
a57348a
@RayBatts RayBatts Removing an unecessary comment. 09e8ff3
@totallyeviljake

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

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
Owner

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.

@johnHolmes

Hi,

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

@johnHolmes

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

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.

@johnHolmes

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

@RayBatts

@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

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.

@johnHolmes

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

@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.

@johnHolmes

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

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:

http://stackoverflow.com/questions/10889695/how-do-i-get-the-ios-device-and-version-in-monotouch

@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:

http://developer.apple.com/library/ios/#documentation/3DDrawing/Conceptual/OpenGLES_ProgrammingGuide/WorkingwithEAGLContexts/WorkingwithEAGLContexts.html

@ddebilt

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.

@johnHolmes

@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

@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

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:

http://eu.battle.net/d3/en/forum/topic/4209821326

http://us.battle.net/d3/en/forum/topic/5149180791

"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:

http://forums.runicgames.com/viewtopic.php?f=9&t=22686

@RayBatts

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: #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

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
Owner
@tomspilman
Owner

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

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
Owner

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

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.

@johnHolmes

Tom,

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

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

@tomspilman
Owner

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

@totallyevil

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
Owner

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

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

@tomspilman
Owner

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:

http://monogame.codeplex.com/discussions/406451

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

@Nezz

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

@tomspilman
Owner

Sure... that would be helpful.

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

@Nezz

Tested on an iPhone 4 where we have performance problems:
http://www.youtube.com/watch?v=BaXsE9UmeDQ

@Nezz

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

@tomspilman
Owner

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

Yep, it introduces the blue flashing.

@tomspilman
Owner

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

The relevant code extracted:

public override void Update(GameTime gameTime)
{
this.GraphicsDevice.SetRenderTarget(renderTarget);
this.GraphicsDevice.Clear(Color.Transparent);
this.SpriteBatch.Begin();
this.SpriteBatch.Draw(texture1,...);
this.SpriteBatch.End();
this.SpriteBatch.Begin(SpriteSortMode.Immediate, blendState);
this.SpriteBatch.Draw(texture2...);
this.SpriteBatch.End();
this.GraphicsDevice.SetRenderTarget(null);
}

public override void Draw(GameTime gameTime)
{
this.GraphicsDevice.Clear(Color.Black);
this.SpriteBatch.Begin();
this.SpriteBatch.Draw(renderTarget,...);
this.SpriteBatch.End();
}

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
Owner
@Nezz

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
Owner

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
Closed

GL Shader Precisions #1090

@RayBatts

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
Owner

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

If we can verify that then we can merge this.

@Nezz

I will look at this tomorrow.

@Nezz

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
Owner

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

@tomspilman
Owner

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.

@RayBatts

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
Collaborator

Can one of the admins verify this patch?

@RayBatts RayBatts closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Oct 9, 2012
  1. @tomspilman
  2. @tomspilman
Commits on Oct 10, 2012
  1. @tomspilman
Commits on Oct 11, 2012
  1. @tomspilman
Commits on Oct 17, 2012
  1. @tomspilman
Commits on Oct 26, 2012
  1. @tomspilman
  2. @tomspilman
Commits on Nov 8, 2012
  1. @tomspilman
Commits on Nov 9, 2012
  1. @tomspilman
Commits on Nov 11, 2012
  1. @tomspilman
  2. @tomspilman
  3. @tomspilman
Commits on Nov 12, 2012
  1. @tomspilman
  2. @tomspilman
  3. @tomspilman
Commits on Nov 13, 2012
  1. @tomspilman
  2. @tomspilman
  3. @tomspilman
Commits on Nov 14, 2012
  1. @tomspilman
  2. @tomspilman
  3. @tomspilman
Commits on Nov 20, 2012
  1. @tomspilman
Commits on Nov 21, 2012
  1. @RayBatts

    First run at changing the iOS game loop to use a CADisplayLink, rathe…

    RayBatts authored
    …r than a NSTimer in attempt to straighten out vsync timing.
  2. @RayBatts

    Miscommit.

    RayBatts authored
Commits on Nov 22, 2012
  1. @tomspilman
Commits on Nov 26, 2012
  1. @RayBatts

    Merge branch 'develop3d' of https://github.com/SickheadGames/MonoGame

    RayBatts authored
    …into ImprovediOSGameLoop
  2. @RayBatts
  3. @RayBatts
Commits on Dec 17, 2012
  1. @RayBatts
  2. @RayBatts
This page is out of date. Refresh to see the latest.
View
50 MonoGame.Framework/iOS/iOSGamePlatform.cs
@@ -73,12 +73,15 @@ 1. Definitions
using MonoTouch.Foundation;
using MonoTouch.OpenGLES;
using MonoTouch.UIKit;
+using MonoTouch.CoreAnimation;
+using MonoTouch.ObjCRuntime;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Input.Touch;
using Microsoft.Xna.Framework.GamerServices;
+using System.Diagnostics;
namespace Microsoft.Xna.Framework
{
@@ -88,8 +91,7 @@ class iOSGamePlatform : GamePlatform
private UIWindow _mainWindow;
private List<NSObject> _applicationObservers;
private OpenALSoundController soundControllerInstance = null;
- private NSTimer _runTimer;
- private bool _isExitPending;
+ private CADisplayLink _displayLink;
public iOSGamePlatform(Game game) :
base(game)
@@ -173,6 +175,22 @@ public override void RunLoop()
throw new NotSupportedException("The iOS platform does not support synchronous run loops");
}
+ public override void TargetElapsedTimeChanged()
+ {
+ CreateDisplayLink();
+ }
+
+ private void CreateDisplayLink()
+ {
+ if (_displayLink != null)
+ _displayLink.RemoveFromRunLoop(NSRunLoop.Main, NSRunLoop.NSDefaultRunLoopMode);
+
+ _displayLink = UIScreen.MainScreen.CreateDisplayLink(_viewController.View as iOSGameView, new Selector("doTick"));
+ _displayLink.FrameInterval = (int)Math.Round(Game.TargetElapsedTime.TotalSeconds / (1.0f / 60.0f));
+
+ _displayLink.AddToRunLoop(NSRunLoop.Main, NSRunLoop.NSDefaultRunLoopMode);
+ }
+
public override void StartRunLoop()
{
// Show the window
@@ -181,10 +199,13 @@ public override void StartRunLoop()
BeginObservingUIApplication();
_viewController.View.BecomeFirstResponder();
- _runTimer = NSTimer.CreateRepeatingScheduledTimer(Game.TargetElapsedTime, Tick);
+ CreateDisplayLink();
}
- private void Tick()
+
+ double _bank = 0.0;
+ System.Diagnostics.Stopwatch _sw = new System.Diagnostics.Stopwatch();
+ public void Tick()
{
if (!Game.IsActive)
return;
@@ -192,16 +213,35 @@ private void Tick()
if (IsPlayingVideo)
return;
+ // There's no way to disable vSync on iOS, so avoid missing it.
+ // Skip rendering the frame if it doesn't line up with the vertical sync.
+ // Technique discussed here: http://www.ananseproductions.com/game-loops-on-ios
+ var curTime = CAAnimation.CurrentMediaTime();
+ var timeToNext = Game.TargetElapsedTime.TotalSeconds - (curTime - _displayLink.Timestamp);
+
+ var dropFrame = _bank > timeToNext;
+ if (dropFrame)
+ Game.SuppressDraw();
+
+ _bank = 0.0;
+
+ _sw.Restart();
+
// FIXME: Remove this call, and the whole Tick method, once
// GraphicsDevice is where platform-specific Present
// functionality is actually implemented. At that
// point, it should be possible to pass Game.Tick
// directly to NSTimer.CreateRepeatingTimer.
_viewController.View.MakeCurrent();
+
Game.Tick ();
- if (!IsPlayingVideo)
+ if (!dropFrame && !IsPlayingVideo)
_viewController.View.Present ();
+
+ _sw.Stop();
+
+ _bank += _sw.Elapsed.TotalSeconds - timeToNext;
}
public override bool BeforeDraw(GameTime gameTime)
View
13 MonoGame.Framework/iOS/iOSGameView.cs
@@ -84,8 +84,11 @@ 1. Definitions
using All = OpenTK.Graphics.ES20.All;
-namespace Microsoft.Xna.Framework {
- partial class iOSGameView : UIView {
+namespace Microsoft.Xna.Framework
+{
+ [Register ("iOSGameView")]
+ partial class iOSGameView : UIView
+ {
private readonly iOSGamePlatform _platform;
private int _colorbuffer;
private int _depthbuffer;
@@ -183,6 +186,12 @@ private void CreateContext ()
__renderbuffergraphicsContext.MakeCurrent (null);
}
+ [Export ("doTick")]
+ void DoTick()
+ {
+ _platform.Tick();
+ }
+
private void DestroyContext ()
{
AssertNotDisposed ();
Something went wrong with that request. Please try again.