Locking framerate to 30fps (iOS)?

Home / Forums / Marmalade Archive / General Development Questions / Locking framerate to 30fps (iOS)?
57 replies [Last post]
by: bluescrn
Status: Offline
Joined: 2011-02-26

Is there a good way to do this?

I've got a game that'll run at 60fps on newer devices, but needs limiting to 30fps on older ones, to avoid a nasty varying framerate.

I've tried limiting to 30 with timers and s3eDeviceYield - and while that slows the game down to around 30hz on the CPU side, it looks more juddery than I'd expect, as if the GPU side isn't running at a very constant rate.

Is there anything that can be done to make sure that GLES swaps it's buffers every two vblanks, instead of at the next vblank? (along the lines of PresentInterval with D3D)

by: Jez Hammond
Status: Offline
Joined: 2010-10-11
I'm not aware of any v-sync

I'm not aware of any v-sync operation (would be welcome on iOS if doesn't exist?).

You might want to take a look at http://gafferongames.com/game-physics/fix-your-timestep/ where a solution exists to animate smoothly, but it's tricky to perfect...In my latest game I've been holding back before implementing the full method. I too get 30-50fps on iPad 1 and iPhone 3GS, with 60fps on > devices. Well 50fps always looked juddery on the PAL Amiga (relative to NTSC), for sure a locked 30 would look more silky with a consistent 'trail', but from what I can tell the above link is the real way to solve this.

by: bluescrn
Status: Offline
Joined: 2011-02-26
Hmm, just tried:

Hmm, just tried:

eglSwapInterval( IwGLGetEGLDisplay(), 2 );

Didn't have any effect, though... mustn't be supported on iOS?

The game supports variable-timestep, so that's not an issue - it's just that iOS is always vsynced - and it looks unpleasant when it runs at 60fps for a few frames, then 30fps, and so on.

When limiting the CPU framerate by yielding, I think I'm getting a mix of 'short frames' and 'long frames', making it look more juddery than a constant 30fps would look...

by: matth
Status: Offline
Joined: 2010-07-23
You could try this. At the

You could try this. At the start of your game you could calculate the max frame rate of the device by creating a small rendering loop and time it for say 10-20 frames then calculate how long a single frame lasts. If 60fps then call IwGxSwapBuffers() / IwGLSwapBuffers() twice in a single frame.

by: bluescrn
Status: Offline
Joined: 2011-02-26
You can't just call

You can't just call IwGLSwapBuffers twice - as on the 2nd swap there'll be nothing there to swap to the front buffer.

I suppose I could render the entire game to an off-screen texture, and just blit it to the screen before each swap, but that seems like real overkill for what I'm trying to achieve...

by: Jez Hammond
Status: Offline
Joined: 2010-10-11
I should have said, it's the

I should have said, it's the paragraph entitled 'The Final Touch' which I was referring to. Basically your accumulator loop's remainder (from 1 time step) is used to calculate an 'offset' between two render states. By rendering this 'third' state you will get ultra smooth animation at variable framerate.

The reason I have waited before fully implementing the method in latest game, and for now sticking to a basic accumulator buffer, was to flesh out the physics in a single state which is easier to maintain for me at least.

by: bluescrn
Status: Offline
Joined: 2011-02-26
No, regardless of how clever

No, regardless of how clever your timing and maths is, you can't get smooth animation if the rendering is jumping between 30hz and 60hz. It may work nicely for non-vsynced PC games with variable framerates, but I don't think it's relavant in my case.

I'm just looking for a solution to locking the rendering to 30Hz. Tempted to try the render-to-texture and swap twice approach, but I'm using 2x multisampling, and I suspect that might not play nicely with rendering-to-texture...

by: Jez Hammond
Status: Offline
Joined: 2010-10-11
The brain will compensate.

The brain will compensate. But let's see what we both come up with ;-)

Will be interesting to see how others have tackled this quest for silk.

by: bluescrn
Status: Offline
Joined: 2011-02-26
I'm a bit obsessive about

I'm a bit obsessive about framerates. The game is fairly fast-paced (http://www.youtube.com/watch?v=bhHqgaICZvs - work-in-progress), so any inconsistencies will be very noticable.

I'm making sure that it runs at 60fps on iPhone4/iPad2 and above - but for 3GS and iPad1, it won't manage it, which is where I'm wanting to lock to 30. 3GS could probably run at 60 if I didn't antialias, but the low-res screen really needs at least 2x multisampling, and that's why it can't hit 60 when the iPhone 4 can.

by: bluescrn
Status: Offline
Joined: 2011-02-26
Suggestion from a native iOS

Suggestion from a native iOS ObjC developer was this:

[aDisplayLink setFrameInterval:animationFrameInterval], Where animationFrameInterval = 2.

Now the question is... can I do that via Marmalade - maybe via an extension???

by: Jez Hammond
Status: Offline
Joined: 2010-10-11
Video looks great, though

Video looks great, though obviously doesn't show the 30-60fps side effect. For sure you want the illusion of consistent movement in any game especially yours. Reminds me of WipeOut!

Looking at your track texture, though only via YouTube low res, I think you don't need to AA it. You could use (or on parts) a lower mipmap (by design) to let GL do it's nice blending between texels (linear mipmapping).

by: Jez Hammond
Status: Offline
Joined: 2010-10-11
(2nd comment based on that

(2nd comment based on that your texture is mostly straight lines)

by: bluescrn
Status: Offline
Joined: 2011-02-26
The video is taken from my PC

The video is taken from my PC build, running with 4xAA (outside Marmalade, using D3D), so it doesn't show the varying framerate - and Youtube limits the framerate to 30ish anyway.

It looks fine with no AA on the iPhone 4, but on the low-res screen, the jagged polygon edges aren't very nice.

Going to see if I can figure out a way to try that setFrameInterval in an EDK extension...

by: bluescrn
Status: Offline
Joined: 2011-02-26
Hmm, after looking into it a

Hmm, after looking into it a bit more, if there is acutally a CADisplayLink, it's going to be hidden away in Marmalade, and I can't see any obvious way to get to it from the EDK?...

(Anyone with more ObjC experience got any ideas?)

by: Jez Hammond
Status: Offline
Joined: 2010-10-11
I was inspired by the thread

I was inspired by the thread to experiment some already. Now debating if the result is as nice as I had hoped for, as it does involve locking framerate on slower devices to 30fps (so the OP might be happy at least!).

Unable to test any EDK suggestions, I quickly attempted to implement Glenn Fiedler's method (The Final Touch). But I soon ran in to what seems a paradox as I keep the same live model matrix (avoiding Gimble lock, being free...).

This then led me back to your (OP) desire to lock the framerate. As my game looks good in the simulator at 30fps, I thought about why you might be having difficulties with judder still. I decided that you could have forgotten to set delta time to the desired interval *after* you had examined it to compute your extra time required to yield. So here's my loop and it's working well, though not as visuly pleasing as when the game would run at 45fps, though inconsistently. For now at least I am sticking with this as it is a general improvement! Thanks for the inspiration.

(for devices which can't handle 60fps)

static int extraTime = 0;
extraTime += int( ((1.0f / 30) - dt) * 1000 );
dt = 1.0 / 30.0f;
s3eDeviceYield( max(1, extraTime) );

(obviously dt is delta time originally computed from high-res timer <--- for anyone new).

by: Jez Hammond
Status: Offline
Joined: 2010-10-11
Getting better results all

Getting better results all the time by modifying the above, will post final version another time but for sure the static variable should be a float (plus other mods).

by: Jez Hammond
Status: Offline
Joined: 2010-10-11
Plus of course the code is

Plus of course the code is useless without subtracting the int amount that was yielded from your new float version of your extraTime accumulator.

*Why no edit post? They were annoying when we used to be able to subscribe, but now it would be ideal for the above code which I typed on an iPhone that replaces half of what I type with words like 'Bessie' lol

by: Jez Hammond
Status: Offline
Joined: 2010-10-11
Well how about that,

Well how about that, something magical just happened. Not only smoothest I've seen one of my games < 60fps on iPad1, but also the extra yielding has allowed the other threads to chill thus increasing the efficiency of my render thread! Probably old news to some around here, but I'm a reinvent the wheel (invent wheel haha;) kinda guy.

Here goes at 'touch typing' again:

static float extraTime = 0.0f;
extraTime += (1.0f / 30) - dt;
dt = 1.0f / 30;
s3eDeviceYield( max(1, int(extraTime * 1000) - 1) );
extraTime -= (int)extraTime;

Z

by: Chris D
Status: Offline
Joined: 2009-11-01
Dear all,

Dear all,
Here is some insight. I do recommend, that you are using frame time independent rendering, but I also recommend, that you don't limit fps. For yielding I suggest that you yield with parameter 0 (which is the minimum time needed for the OS to do it's housekeeping).
Now to get everything looking smooth, you will need to interpolate every position of any in-game objects and any animations of in-game objects dependent on the time that has passed since the last frame.

The time you can check easily using s3eTimerGetUTC(). So if for example you have to move an object for 1000 pixels in 1 second and the first frame time is 150ms, then you move the object 150 pixels and if the next frame takes 200 ms you need to move the object further by 200 pixels. (These numbers are of course unrealistic, but they should demonstrate the concept; What the people who received jittery rendering did, was to render and then once the 30 frames were used up, they just did nothing for the rest of the second; This is what is causing the jittering).

My method will guarantee, that your application runs at maximum speed (=maximum smoothness) and if there is a performance hit, the speed of the game should not change.

Somebody above mentioned that 30 fps produces jittery rendering. NTSC (US) TV has a frame rate of 30 frames per second, I believe PAL is running at 25. So if anybody experiences jittery artifacts, it is due to your code and not due to the 30 fps.

Good luck,
Chris

by: bluescrn
Status: Offline
Joined: 2011-02-26
No, I'm still looking for a

No, I'm still looking for a way to lock to 30fps. The main reasons are:

- iOS forces vsync on. Frequently changing between 60fps and 30fps looks bad, even if variable timesteps are handled well.

- Dealing with known, fixed hardware: I only need to do this on iPhone 3GS and iPad 1 (obviously, this isn't a good solution for Android)

- It's what console games do! - If variable timesteps/framerates looked good, why do nearly all console games lock themselves to either 30 or 60hz? Because it looks better!

I'm not new to game development, I've been in the industry for over a decade, working on a wide variety of platforms. I just need a solution to my problem - making IwGLSwapBuffers() swap every 2 vblanks, instead of on the next vblank.

by: Jez Hammond
Status: Offline
Joined: 2010-10-11
You can be sure that I've

You can be sure that I've spent plenty of time trying different methods. But each to their own.

by: DionisioX
Status: Offline
Joined: 2010-11-02
What I do for every game I

What I do for every game I develop is separate logic update from graphics update. The logic update is fixed (for example 30fps) and the graphics update can be fixed too or free to max available.

This way, the objects move always at the same speed no matter what device you run, but the graphics will be smoother on faster ones ;)

by: Jez Hammond
Status: Offline
Joined: 2010-10-11
Unable to exit post. There is

Unable to edit post. There is no need to manually reset delta time (dt), anyone using my limiters may remove the line a I've the yield. This will be more scientifically correct (not scientifically accurate due to yield being approximate plus floating point error). And remain framerate independent should the fps fall below 30.

by: Jez Hammond
Status: Offline
Joined: 2010-10-11
*Edit

*Edit

Seriously! Why remove the edit facility lol.

Also wanted to mention that results are identical with said line removed (still visually pleasing).

by: Jez Hammond
Status: Offline
Joined: 2010-10-11
@bluescrn Are you saying that

@bluescrn Are you saying that my method is not producing the original desire of this thread? Or you just want a 'magic button'...

by: bluescrn
Status: Offline
Joined: 2011-02-26
I haven't tried your code,

I haven't tried your code, but I've experimented with my own timing/yield solutions, without particularly great results

The problem is that the CPU and GPU run somewhat independently, so even if the CPU main loop runs at a precise 30hz, it's not necessarily a guarantee that the GPU will. On the GPU side, you can still get a mix of 1/60th (1 vsync), 1/30th (2 vsyncs), and 1/20th (3 vsyncs) frames - depending on the exact timings and GPU loads of each frame. Which makes things juddery.

What's needed is some way of enforcing the 'swap buffers every 2 vsync' on the GPU side. Either that or a 'wait for vsync' on the CPU side.

by: bluescrn
Status: Offline
Joined: 2011-02-26
(can't edit post to fix typo.

(can't edit post to fix typo... argh... has the creator of this forum ever used any other internet forums?!)

by: Jez Hammond
Status: Offline
Joined: 2010-10-11
This place is making a fool

This place is making a fool of me I have requested that my posting privilGes be removed. Enough is enough.

by: Jez Hammond
Status: Offline
Joined: 2010-10-11
Right there.

Right there.

by: Jez Hammond
Status: Offline
Joined: 2010-10-11
Follow up thoughts:

Follow up thoughts:

1) For sure the forum needs the edit facility returned.
2) As either iOS or the GL drivers are ignoring eglSwapInterval(..., x) then all we can do is to take advantage of this fact by setting the interval to 0 allowing the simulator to run synch-free too (x86 build complies with the standard). Now we can optimise our own solutions without the need to deploy to a device.
3) Again, still no edit post facility on Marmalade forums? It's removal causing much off-topic discussion and frustration, plus useless code nuggets that could have eventually became gems. Also, the forum now says "Too many redirects" when selected from the Developer Network.

by: Jez Hammond
Status: Offline
Joined: 2010-10-11
Anyway, made my bed now I

Anyway, made my bed now I will lay in it. Here's my solution to this thread (presuming the post WORKS else I will have to post again with a fix for the fix for the...have a nice day. Now I work instead of post with crayons.

/* Lock frame-rate @ 30fps (by Zenout)
* for these devices (: iPhone3GS, iPod3, iPhone4, iPod4, iPad1 - Higher devices are running at 60fps no problem.
* Requires, iOS: nothing, x86: eglSwapInterval( IwGLGetEGLDisplay(), 0 );
*/
if(deviceRating < 3) // for these devices (: iPhone3GS, iPod3, iPhone4, iPod4, iPad1 - Higher devices are running at 60fps no problem (2012).
{
static s64 prevTime = s3eTimerGetUSTNanoseconds();
static f64 extraTime = 0.0;
extraTime += (1.0 / 30);
while(extraTime >= 1.0 / 30)
{
s3eDeviceYield(1);
//
const s64 newTime = s3eTimerGetUSTNanoseconds();
extraTime -= f64(newTime - prevTime) / 1000000000.0;
prevTime = newTime;
}
s3eDeviceYield(1);
}
else
s3eDeviceYield(1);

by: Jez Hammond
Status: Offline
Joined: 2010-10-11
More bugs found which I have

More bugs found which I have no interest in documenting. I'm sticking to just the last line and be done with it. Seems nobody cares anyway.

by: bluescrn
Status: Offline
Joined: 2011-02-26
Yeah, eglSwapInterval is the

Yeah, eglSwapInterval is the correct solution... if it worked! :)

The forums are a disaster, and surely must be costing Marmalade customers.

With each passing day, I'm increasingly tempted too abandon it and switch to native XCode development.

The framerate issue is currently a minor problem - my project is dependent on threading (well, at least one worker thread). I'm unable to determine whether Marmalade threading can be trusted. I've got random issues on iOS that look like memory corruptions (not seen on the x86 build) since the threading code went in, and have seen posts on here indicating that threading might not actually work... (Yes, for now it could be just a bug in my code... but it seems that I'm using an 'unsupported' area of Marmalade...)

by: Jez Hammond
Status: Offline
Joined: 2010-10-11
It would be good to know if

It would be good to know if any of the Marmalade engineers even have the ability to enable this synching issue. Perhaps only an updated GL driver is required? Got a feeling we will never find out.

I also started looking in to synchronising a dummy texture upload / pbuffer / whatever / anything and everything. But it really has became low priority now due to the lack of interest in a solution.

@bluescrn sounds like your threading issue could be R9 corruption (as I read). My iPad 3 tester can only run multi-threaded stuff with the app.icf s3e setting SysFastLockDisable=1

@this website designer, get off your ass.

by: bluescrn
Status: Offline
Joined: 2011-02-26
Zenout: I decided to stop

Zenout: I decided to stop moaning, install phpBB, and start working on 'The Alternative Marmalade SDK forums'

It's at http://marmalade.inverseblue.com

See what you think... it'll be no use unless people use it... but it only took an hour or two to get it up and running...

I'll announce it on the General forum later, and hope that the Marmalade team don't object...

by: bluescrn
Status: Offline
Joined: 2011-02-26
Oh, thanks for the reminder

Oh, thanks for the reminder about SysFastLockDisable=1, I'd seen that thread a while ago, and I thought I already had it enabled - but double-checking my .icf now, it wasn't there... Although I think I removed it when I had a more repeatable unexplainable problem (compressed textures randomly failing to upload) that wasn't fixed by this.

Anyway, I've just put it back in and will do some further testing...

by: Adam E
Status: Offline
Joined: 2012-01-23
Attempting to clean this

Attempting to clean this thread up with typo fixes and double posts cause I personally find the topic interesting and I don't want you guys to stop just because the forums are in this ridiculous state right now.

I'm not gonna make a habit of this, I'm just especially enjoying this discussion.

by: bluescrn
Status: Offline
Joined: 2011-02-26
Back to the 30fps locking

Back to the 30fps locking topic - basically, doing anything based on CPU timing is a hack, that might give OK-ish results, but doesn't achieve the real goal - of syncing the game to every 2nd vsync.

Ideally, we'd be able to use one of these:

- eglSwapInterval (Not sure this is supported at all on iOS, though, with or without Marmalade)

or

- setFrameInterval in CADisplayLink (which we can't access, and it would have to be implemented in Marmalade - assuming that Marmalade uses CADisplayLink internally - which it may well not do)

But in practice we can't so we're back to CPU timing/yielding. Which may give 'fairly acceptable' results, but it doesn't really work in practice.

Here's a diagram to show why it doesn't work (It assumes triple-buffering is being used, and that the GPU works like most desktop/console GPUs, and doesn't stall the CPU, but runs 'behind' the CPU, executing a command buffer built by the CPU)

https://dl.dropbox.com/u/19400176/30hzlock.png

What this shows, is that with the CPU loop locked to 30hz by timing+yielding, you can still get a mix of 1/60th, 1/30th. and 1/20th second frames being presented.

by: bluescrn
Status: Offline
Joined: 2011-02-26
To explain the diagram a

To explain the diagram a little more, it does assume that the GPU and CPU frame time will vary between frames (as it does in practice, depending on what's visible and happening in-game)

It's intentionally showing the GPU starting work about 2/3rds of the way through the CPU update (an approximation of a game loop consisting of Update(), Render() )

All 3 frames are well under 1/30th sec on the GPU - in fact, only just over 1/60th. But because of where the vsyncs lie on the timeline, we can still see that 3/60ths of a second pass between frame 0 being presented and frame 1 being presented.

by: Jez Hammond
Status: Offline
Joined: 2010-10-11
Thanks for helping to get the

Thanks for helping to get the thread back in order Adam.

Yes bluescrn every method is a hack without having some sort of beacon to synchronise with in the first place. During testing some of my methods would get lucky and hit a good sync while later I would run the same test to see it had lost it. Like you say it would miss a frame. Then causing a chain reaction of sync in the wrong place. Well hacks just won't cut it in this case I think maybe investigate the sync'd texture upload, haven't read much about that yet though.

Your forum ha nice work bluescrn! Beats my idea of mocking up how I think this forum should look. Hope to see lots of fellow Marmalade users join up and join in! Looks like a great compliment.

by: matth
Status: Offline
Joined: 2010-07-23
Nice forum bluescrn, just

Nice forum bluescrn, just signed up!

by: bluescrn
Status: Offline
Joined: 2011-02-26
Well, as there's no other

Well, as there's no other option... I implemented the render-to-texture approach:

Game Update
Game Render (to texture)
Copy to back buffer
IwGLSwapBuffers()
Copy same frame to back buffer again
IwGLSwapBuffers()

Was quite a hassle to do, but well, it seems to work. Problem is that I lost the hardware multisampling that I'd been using. Instead I did 2x supersampling (rendering to 480x960 for 3GS), but that costs a lot more performance than hardware multisampling, so it can't quite manage a solid 30 any more... meh.

by: bluescrn
Status: Offline
Joined: 2011-02-26
(should have said 480x640,

(should have said 480x640, which is 480x320, double height)

by: bluescrn
Status: Offline
Joined: 2011-02-26
Might actually have solved

Might actually have solved this(!!!)

- Only for iOS
- Requires writing an EDK extension
- Only tested on an iPhone4 and iPod Touch 3rd Gen
- Still rather experimental... but results are looking promising...
- May be adding to s3eSound stuttering issues :( (Need a way to increase that mixing buffer size!!)

So I created an extension that counts vsyncs, using CADisplayLink. Currently that's a filthy hack within my Flurry extension... but as a proof-of-concept it's done the job...

// Lock to 30hz
int iVsync;	
int iFailTimer = 0;
do 
{
   iVsync = s3e%EXTENSIONNAME%GetVsyncCount();
   s3eDeviceYield(1);
   iFailTimer++;
} while ( iVsync<s_iLastVsync+2 && iFailTimer<100 );
 
s_iLastVsync = iVsync;
 
IwGLSwapBuffers();	

's3eExperimentGetVsyncCount' is the call to the extension, which gets the number of vsyncs - well - calls to the CADisplayLink event - since the app started up

(The 'failtimer' was just a quick hack to make sure it never gets stuck if the counter wraps around)

by: Jez Hammond
Status: Offline
Joined: 2010-10-11
Sounds ideal bluescrn. If you

Sounds ideal bluescrn. If you can get something online in a few days I can test on iPad 1 and 3GS.

I've already cut back loads to hold 60fps, but would rather do this and use the full potential of my design.

by: WIliammm
Status: Offline
Joined: 2012-06-13
Does Android suffer from

Does Android suffer from similar problem?

by: bluescrn
Status: Offline
Joined: 2011-02-26
I suspect that it does, but I

I suspect that it does, but I've not got any suitable old/slower devices to test on here. But there's so many Android devices, there's no real way of knowing which ones can do 60fps, which to lock to 30fps, and which won't even manage that...

So for Android, you've just got to go for a variable-timestep solution, really. And try to run at 60fps on fairly modern devices.

by: bluescrn
Status: Offline
Joined: 2011-02-26
(maybe eglSwapInterval works

(maybe eglSwapInterval works on Android, though - haven't tried it yet)

by: Jez Hammond
Status: Offline
Joined: 2010-10-11
Looking forward to this.

Looking forward to this. While also frustrated that can't just do it myself. I wonder if it's possible to build an iPhone extension inside a virtual box running a legit copy of OSX. @Marmalade, any chance of a solution your end, like (made up name) s3eSwapInterval(1) would do what bluescrn is doing?

by: bluescrn
Status: Offline
Joined: 2011-02-26
It should be possible to

It should be possible to build extensions in a VM. It's against the OSX licence agreement to run it on non-Apple hardware, but that doesn't stop people doing it... I tried out XCode on Snow Leopard under VMWare 18 months or so agao, before eventually choosing Marmalade. Should really get that set up again, should be less hassle than jumping between my PC and an old Macbook to do extension work...

Anyway, I've been away for a few days, need to find a bit of time to get this little extension into a release-able form. It's only a tiny amount of code involved, but it needs pulling out of my existing Flurry extension and tidying up, and it'd be nice to have a little demo to compare it to 'timing+yielding', and see how the two look side-by-side.

by: bluescrn
Status: Offline
Joined: 2011-02-26
Here's a first version of the

Here's a first version of the CADisplayLink extension, and a little demo app:

https://dl.dropbox.com/u/19400176/Marmalade/IwGLFrameRateLock.zip

To be honest, after doing that, I'm not entirely convinced that it's noticably smoother than with timing+yielding. (But when I switched to it in a real game, it definitely seemed better)

I tried to put the GPU under some load in that sample, by drawing a whole load of alpha-blended rects, to ensure it couldn't quite manage 60fps on my iPhone 4. But by the time I'd done that, it wasn't a great demo for comparing movement smoothness. So you might want to mess with the demo, or just try it in a real game.

Tap the screen to cycle through unlimited framerate, 30fps with timing+yielding, and 30fps with CADisplaylink for synchronization.

So just give it a try, mess with it, and see if it's any use...

(Also note that the timing+yielding method used in the demo is a bit poor, it only uses the millisecond-accurate timer)

Pages

Latest Posts

Latest Comments

Top Contributors

  • amanda
  • riaan
  • alexand3
  • alexand4
  • alexand7
  • alexand889
  • alexand12191
  • remote9991
  • remote9992
  • test