Profiling OpenGL ES App

Sage
Posts: 1,066
Joined: 2004.07
Post: #1
I'm just about at my wits end today. I had a game started that I was hoping to just use UIKit for all the graphics. Yes, it's hackish but it worked until I got about 95% of the stuff in the game.

Now I've switch over to OpenGL but I cannot for the life of me figure out why it's running at about 46-47 fps instead of 60. Right now I'm drawing three quads (as three triangle strips). All three are textured and sampled from one 512x512 sprite sheet (so I never switch textures). This isn't my entire game, so I'm worried that it's already at 46-47 fps when it really should be able to tear into this and get 60fps.

I think I need to be using Instruments for this but I can't figure out what to look for in terms of bottle neck. I used the OpenGL template (which is how I found out the frame rate), but I can't figure out what to look at to determine the bottleneck.

Any ideas or help?
Quote this message in a reply
Sage
Posts: 1,066
Joined: 2004.07
Post: #2
Since i couldn't quite figure out Instruments or Shark, and my project is so small right now, I started just taking out lines until something made a difference. In my custom EAGLViewController, in my viewDidLoad method, I had this line:

self.view.backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:1];

Where self.view is the EAGLView itself. Apparently this was my problem. Once I took it out, the whole thing started running faster. Not entirely sure why, but then I'm not entirely sure why I had that line in there to begin with. Rasp

Edit: Looks like I spoke too soon. This helped, but did not solve the entire problem. Back to trying to figure out Instruments/Shark.
Quote this message in a reply
Member
Posts: 440
Joined: 2002.09
Post: #3
If you're using an NSTimer to drive frames you'll want to crank up the interval. Running a timer at 60Hz won't necessarily give you 60 FPS due to VBL syncing and other blocking event updates. Some folks recommend using a separate thread for rendering but I've never run into any problems with NSTimer - you just gotta crank it up - but note that you'll never exceed 60FPS on the iPhone since that's the hard-coded VBL rate.

What I do is run my frame timer at 120Hz and de-couple the logic/physics steps from the graphics updates using the technique described here. But I also cap the graphics updates, so I run physics at 60Hz, and draw at 30Hz. The timer might spin needlessly at times in this scenario but that 120Hz ensures you have some breathing room to catch up on updates when things are bogging down.

And yes - set your CAEAGLLayer to opaque (check the OpenGL ES project template).
Quote this message in a reply
Sage
Posts: 1,066
Joined: 2004.07
Post: #4
Hm. Guess I'll have to look into that. My EAGLView class is basically ripped from the template, but I just added in a delegate to farm out the code in the middle of the draw method as well as handle the touch input for the view.
Quote this message in a reply
Member
Posts: 51
Joined: 2009.02
Post: #5
I've worked on a lot of games that worked similarly to Frank C's description. There's a discrete physics frame that's running at 60, 90, or 120 fps. The graphics render whenever possible. We did this all the time for PC games, where performance of the system was highly variable.

After all, future iPhones could come along with faster processors and graphics. So I think it's a good way to go.

I'm not as far as you are yet. Is there a callbacks you get when a frame has rendered? Is there a callback when it's been displayed? Or our we filling a queue with frames?
Quote this message in a reply
Moderator
Posts: 3,570
Joined: 2003.06
Post: #6
Nosredna Wrote:I'm not as far as you are yet. Is there a callbacks you get when a frame has rendered? Is there a callback when it's been displayed? Or our we filling a queue with frames?

Callback.

There are two schools of thought on this: loop or timer (either being optionally threaded, and I don't like threaded on iPhone). I'm with Frank and use the NSTimer, with which you register a method to be called back at a regular interval. This timer technique is what is used in the OpenGL ES application template. Frank does it at 120. I do it at 60 and it seems to work okay for me.

I too decouple the draw from the update (which is not demonstrated in the template, unfortunately). Everything tends to stay smoother that way, and it also helps physics integration remain stable. My update (or "tick") is set to be 60 Hz on iPhone. So drawing can be (and is) variable by the very nature of the capabilities of the system and hardware, but the tick rate remains stable at all times. It's not pristine perfection, but it's the best all-around trade-off that I've seen.
Quote this message in a reply
Member
Posts: 51
Joined: 2009.02
Post: #7
AnotherJake Wrote:Callback.

There are two schools of thought on this: loop or timer (either being optionally threaded, and I don't like threaded on iPhone).

Seems reasonable. I don't see a reason to thread on an iPhone game.

I'm sure I'll have more questions later. Glad I found this board.
Quote this message in a reply
Sage
Posts: 1,066
Joined: 2004.07
Post: #8
AnotherJake Wrote:I too decouple the draw from the update (which is not demonstrated in the template, unfortunately). Everything tends to stay smoother that way, and it also helps physics integration remain stable. My update (or "tick") is set to be 60 Hz on iPhone. So drawing can be (and is) variable by the very nature of the capabilities of the system and hardware, but the tick rate remains stable at all times. It's not pristine perfection, but it's the best all-around trade-off that I've seen.

Might you be willing to share some of that code. I've been reading over the link Frank posted but for some reason I can't seem to wrap my head around how to do that using Objective-C. Mainly I'm having issues with figuring out exactly how much time has passed. If I use the time() method, it returns integer seconds which isn't useful. I've tried using [NSDate date], storing the last tick, and subtracting the two, but that doesn't seem to yield accurate results so I must be doing something wrong.

So would either of you be willing to share a bit of your timing code for the rest of us to learn from? Grin
Quote this message in a reply
Member
Posts: 440
Joined: 2002.09
Post: #9
Nick Wrote:Might you be willing to share some of that code. I've been reading over the link Frank posted but for some reason I can't seem to wrap my head around how to do that using Objective-C.

The trick I use when working with Objective-C is to avoid it as much as possible Wink

Here's my "get time" function (there are a few discussions on using "mach time" if you search the forum):

Code:
double CurrentTime(void)
{
  static double conversion = 0.0;
    
  if( conversion == 0.0 )
  {
    mach_timebase_info_data_t info = {0,0};
    kern_return_t err = mach_timebase_info(&info);
    if (!err) {
      conversion = 1e-9 * (double)info.numer/(double)info.denom;
    }
  }

  return conversion * (double)mach_absolute_time();
}

And here's my run-frame function, minus some fluff:

Code:
double gameRunFrame(void)
{
  static double    currentTime = 0.0;
  static double    accumulator = 0.0;
  static double    drawTime = 0.0;
  static double    drawDelta = 0.0;
  double newTime, deltaTime;

  newTime = CurrentTime();
  deltaTime = newTime - currentTime;
    
  if (deltaTime > kGameFrameMaxDelta) {
    deltaTime = kGameFrameMaxDelta;
  } else if (deltaTime <= 0.0) {
    deltaTime = 0.0;
    drawTime = 0.0;
    drawDelta = 0.0;
  }
    
  currentTime = newTime;
    
  if (gameState != kGameStatePaused) {
    gameTime += deltaTime;
    accumulator += deltaTime;
    while (accumulator >= kGameFrameStepInterval) {
      accumulator -= kGameFrameStepInterval;
      gameStep(kGameFrameStepInterval);
    }
  }

  drawDelta += deltaTime;
  if (currentTime > drawTime) {
    drawTime = currentTime + kGameFrameDrawInterval;
    deltaTime = drawDelta;
    drawDelta = 0.0;
    return deltaTime;
  }
    
  return -1.0;
}

That gameRunFrame function is called from the NSTimer callback in my EAGLView class:

Code:
- (void)runFrame {
    
  double dt = gameRunFrame();
    
  if (dt >= 0.0) {
    gameDrawFrame(dt);
    [context presentRenderbuffer:GL_RENDERBUFFER_OES];
  }
}

Hopefully the other functions (gameStep, gameDrawFrame) are self explanatory. I keep gameTime exposed globally since it's handy to have around everywhere, ditto for gameState. My typical settings for the frame constants are:

Code:
#define kGameFrameIntervalNormal    1.0/120.0
#define kGameFrameIntervalIdle        1.0/10.0
#define kGameFrameStepInterval        1.0/60.0
#define kGameFrameDrawInterval        1.0/33.0
#define kGameFrameMaxDelta        1.0/10.0

So - kGameFrameIntervalNormal is the timer frequency, kGameFrameIntervalIdle is the timer frequency when the app is "idle" (the app delegate decides when to use that). kGameFrameStepInterval is the guaranteed frequency for physics/logic steps and kGameFrameDrawInterval is the maximum frame rate (not guaranteed). Note that I set kGameFrameDrawInterval 10% higher than I actually need it, since you still get some blocking issues with the timer at 120Hz. kGameFrameMaxDelta is there to ensure simulations won't explode when the renderer is bogging - if the game hits that limit it won't be realtime, but at least it won't freeze up or go totally wacky.

I should also note that I don't bother doing any drawing interpolation on the iPhone, since physics updates are never fewer than screen updates. This greatly simplifies gameRunFrame and gameDrawFrame.
Quote this message in a reply
Moderator
Posts: 3,570
Joined: 2003.06
Post: #10
Ha! Frank beat me to it! Oh well, I'll post this anyway.

The stuff I've been using is pretty messy and intertwined with some other non-relevant code, so I hacked this together for you instead. I hope it works, but it should at least get you rolling Wink

BTW, you can also check out ThemsAllTook's excellent tutorial on the subject, which is where I originally learned it.

1) Create a new "OpenGL ES Application"
2) Add new file... and call it Game.c, with header Game.h
3) Copy these two declarations into Game.h:
Code:
void GameInit(void);
void GameFrame(void);
4) In EAGLView.m, #import "Game.h"
5) In EAGLView.m, add GameInit(); on line 63, right under where it says animationInterval = 1.0 / 60.0;
6) In EAGLView.m, replace the drawView method with:
Code:
- (void)drawView {
    [EAGLContext setCurrentContext:context];
    
    glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
  
    GameFrame();
    
    glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
    [context presentRenderbuffer:GL_RENDERBUFFER_OES];
}
7) Copy all this junk into Game.c and run it (all I did was basically copy over the draw view code and add some timing calculations to decouple update from draw):
Code:
#import <stdlib.h>
#import <stdio.h>
#import <OpenGLES/ES1/gl.h>
#import <mach/mach.h>
#import <mach/mach_time.h>
#import "Game.h"

#define TICK_RATE            60.0
#define MINIMUM_FRAME_RATE    4.0

// timing variables
static double    gPrevFrameTime;
static double    gTimeLeftOver;
static double    gMaxTimeAllowed;
static double    gLastFPSUpdate;
static float    gFPS;
static unsigned    gFrameCount;

// delta time, used everywhere
double            dt = 0.0;

// game variables
static GLfloat rot;

// local function declarations
static double DoublePrecisionSeconds(void);
static void Update(void);
static void Draw(void);

void GameInit(void)
{
    double        currTime;
    
    // init timing
    currTime = DoublePrecisionSeconds();
    dt = 1.0 / TICK_RATE;
    gPrevFrameTime = currTime;
    gTimeLeftOver = 0.0;
    gMaxTimeAllowed = (TICK_RATE / MINIMUM_FRAME_RATE) * dt;
    gLastFPSUpdate = currTime;
    gFPS = 0.0f;
    gFrameCount = 0;
    
    // init game variables
    rot = 0.0f;
}

void GameFrame(void)
{
    double    currTime, frameRateDelta, drawDelta, timeAllowed;
    
    currTime = DoublePrecisionSeconds();
    
    // calculate frame rate (optional, added for fun)
    gFrameCount++;
    frameRateDelta = currTime - gLastFPSUpdate;
    if (frameRateDelta > 0.5)
    {
        gFPS = gFrameCount / frameRateDelta;
        gFrameCount = 0;
        gLastFPSUpdate = currTime;
        //printf("fps: %0.1f\n", gFPS); // uncomment to print out FPS
    }
    
    // update the game world at a constant tick rate
    drawDelta = currTime - gPrevFrameTime;
    gPrevFrameTime = currTime;
    if (drawDelta < (1.0 / MINIMUM_FRAME_RATE))
    {
        timeAllowed = drawDelta + gTimeLeftOver;
        if (timeAllowed > gMaxTimeAllowed)
            timeAllowed = gMaxTimeAllowed;
        while (timeAllowed > dt)
        {
            timeAllowed -= dt;
            Update();
        }
        gTimeLeftOver = timeAllowed;
    }
    
    // draw a "snapshot" of what the game looks like at this instant
    Draw();
}

static double DoublePrecisionSeconds(void)
{
    static uint64_t        timebase = 0;
    uint64_t            time, nanos;
    double                seconds;
    
    // calculate the time base for this platform, only on the first time through
    if (timebase == 0)
    {
        mach_timebase_info_data_t    timebaseInfo;
        mach_timebase_info(&timebaseInfo);
        timebase = timebaseInfo.numer / timebaseInfo.denom;
    }
    
    // calculate time
    time = mach_absolute_time();
    nanos = time * timebase;
    
    // convert from nanoseconds to seconds
    seconds = (double)nanos * 1.0e-9;
    
    return seconds;
}

static void Update(void)
{
    // rotate at 200 degrees per second
    rot += 200.0f * dt;
    while (rot > 360.0f)
        rot -= 360.0f;
}

static void Draw(void)
{
    const GLfloat squareVertices[] = {
        -0.5f, -0.5f,
        0.5f,  -0.5f,
        -0.5f,  0.5f,
        0.5f,   0.5f,
    };
    const GLubyte squareColors[] = {
        255, 255,   0, 255,
        0,   255, 255, 255,
        0,     0,   0,   0,
        255,   0, 255, 255,
    };
    
    glViewport(0, 0, 320, 480);
    
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrthof(-1.0f, 1.0f, -1.5f, 1.5f, -1.0f, 1.0f);
    glMatrixMode(GL_MODELVIEW);
    
    glLoadIdentity();
    glRotatef(rot, 0.0f, 0.0f, 1.0f);
    
    glClearColor(0.5f, 0.5f, 0.5f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    
    glVertexPointer(2, GL_FLOAT, 0, squareVertices);
    glEnableClientState(GL_VERTEX_ARRAY);
    glColorPointer(4, GL_UNSIGNED_BYTE, 0, squareColors);
    glEnableClientState(GL_COLOR_ARRAY);
    
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
Quote this message in a reply
Moderator
Posts: 3,570
Joined: 2003.06
Post: #11
I was just looking over Frank's version, and I noticed this little snippet:

Frank C. Wrote:else if (deltaTime <= 0.0) {
deltaTime = 0.0;
drawTime = 0.0;
}

This is a *good* idea, and let me tell you why, hehe... I had accidentally converted the time from double precision to single precision at one point. Everything worked great up until some seemingly random point in the game it seemed to freeze. Actually it just stopped updating because the way the math worked out (cutting out the precision wound up giving me like a 50 minute negative delta). That was a tricky bug to track down. If this check was in place, it would have had a tiny unexplained hiccup every 50 minutes, but at least the game would have continued to play!
Quote this message in a reply
Member
Posts: 440
Joined: 2002.09
Post: #12
AnotherJake Wrote:This is a *good* idea
Ya, that was a safety net back when I was using CFAbsoluteTimeGetCurrent but I left it in 'cause I'm paranoid.
Quote this message in a reply
Sage
Posts: 1,066
Joined: 2004.07
Post: #13
Wow. Thanks guys. I'm going to take a little bit to absorb all of that, but I'll let you know if I run into any issues along the way.
Quote this message in a reply
Member
Posts: 440
Joined: 2002.09
Post: #14
Just bumping this to note that I've fixed a bug with the code I previously posted. It was returning deltaTime when it was time to draw but that time slice didn't make much sense. It now tracks and returns the delta between render updates (drawDelta) which can be used for non-critical variable rate updates while drawing - e.g. texture or color animations etc.
Quote this message in a reply
Member
Posts: 161
Joined: 2005.07
Post: #15
Oh wow, I'm glad I saw this thread. My game's framerate seems highly variable even though I'm rendering the same objects every frame, but since I only started about two weeks ago I haven't been very concerned about it. Is the NSTimer really that wonky?
Quote this message in a reply
Post Reply 

Possibly Related Threads...
Thread: Author Replies: Views: Last Post
  Profiling the non-OpenGL parts of my game monteboyd 10 5,321 Oct 25, 2012 04:03 PM
Last Post: monteboyd