in Graphics, iOS

Using Multiple OpenGL Views And UIKit

The iPhone includes OpenGL ES for graphics rendering (thank you Apple for not coming up with a custom API!). Specifically, it uses OpenGL ES 1.1 plus a few extensions. It’s all very familiar and standard, except for the actual setup, which requires some integration with the iPhone UI.

The now gone CrashLander sample, in spite of some atrocious code, was the best example on how to get a simple, OpenGL app on the iPhone. It covered the basics: creating an OpenGL frame buffer, loading some textures, drawing some polys, and presenting the result to the screen. It was very useful because it was a very small and concise and it made for a great starting point for any OpenGL-based app.

Unfortunately, because it was so basic, it didn’t show how OpenGL can play with the rest of UIKit user interface. Instead, it took the approach from many games of taking control of the full screen and viewing it a a single resource. That’s fine as long as you’re making those kind of games, but I found myself having to mix OpenGL and UIKit quite a bit in my current project. And eventually, I even had to use multiple OpenGL views in different parts of the app.

Single OpenGL View

To render with OpenGL to a view, you have to first set the class-static method +layerClass so it creates the right type of layer. Specifically, we need a CAEAGLLayer:

+ (Class)layerClass
{
    return [CAEAGLLayer class];
}

You can create a view of that type in code, or you can lay it out on the Interface Builder. I actually like the Interface Builder quite a bit, so that’s how I end up creating all of mine.

Then, you need to create an OpenGL context:

    m_eaglContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1];
    [EAGLContext setCurrentContext:m_eaglContext];

Finally, you need to create a valid frame buffer object from the CAEAGLLayer from the view you just created:

    glGenFramebuffersOES(1, &buffer.m_frameBufferHandle);
    glGenRenderbuffersOES(1, &buffer.m_colorBufferHandle);
    glGenRenderbuffersOES(1, &buffer.m_depthBufferHandle);

    glBindRenderbufferOES(GL_RENDERBUFFER_OES, buffer.m_colorBufferHandle);
    [m_eaglContext renderbufferStorage:GL_RENDERBUFFER_OES fromDrawable:drawable];
    glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_WIDTH_OES, &buffer.m_width);
    glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_HEIGHT_OES, &buffer.m_height);

    glBindRenderbufferOES(GL_RENDERBUFFER_OES, buffer.m_depthBufferHandle);
    glRenderbufferStorageOES(GL_RENDERBUFFER_OES, GL_DEPTH_COMPONENT16_OES, buffer.m_width, buffer.m_height);
    glBindRenderbufferOES(GL_RENDERBUFFER_OES, buffer.m_colorBufferHandle);

    glBindFramebufferOES(GL_FRAMEBUFFER_OES, buffer.m_frameBufferHandle);
    glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, buffer.m_colorBufferHandle); 
    glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES, GL_RENDERBUFFER_OES, buffer.m_depthBufferHandle)

It’s all pretty standard frame buffer OpenGL stuff. The renderBufferStorage:fromDrawable function is the one that actually allocates buffer storage for that particular view.

After that, you’re home free and you can use OpenGL the way you’ve always done. The only difference is that to present, you call this functions instead:

    [m_eaglContext presentRenderbuffer:GL_RENDERBUFFER_OES];

Done.

Integrating OpenGL and UIKit

After you have the basic OpenGL app set up, you can treat it as a full-screen, exclusive resource and do all your output through it. That works fine for a lot of games, but there are a lot of games and apps that benefit from using some amount of UIKit functionality. After all, UIKit is a great UI API, and it seems like a shame to reinvent it all if it’s not necessary.

The good news is that OpenGL rendering is happen in a view, so you can do everything you can do with views: Animate it, do fancy transitions, add them to nav bars or tab bars, etc. The bad news is that, unless you’re careful, performance will suffer.

Apple has some best practices on what to do with OpenGL views, but it comes down to avoid doing transforms on them (instead, use the transforms in OpenGL), and avoid putting other UIKit elements on top of it, especially transparent ones. Although, I seem to be able to get away with small buttons and such without affecting the frame rate, so that’s always a plus.

So having realized that, now you can make your application transition between the OpenGL view, and other views. Maybe you have a table view with settings, or some sort of UIKit dialog box, or a high-score table. You can safely do all of that with UIKit (and add your own graphics and custom rendering if you want to give it a totally different look).

One thing to watch out for: Whenever your OpenGL view isn’t visible, make sure to stop doing frame updates (for simulation and rendering). Otherwise, the rest of the UI is going to be very unresponsive (and you might not find that out until you run it on the device, because the simulator is very fast). The UIViewController viewWillAppear and viewDidDisappear functions are perfect to find out when you should start and stop frame updates.

Multiple Views

Things get more complicated when you want to have multiple views with OpenGL rendering. I don’t mean multiple viewports in the same screen, but multiple views in the sense of multiple UIViews. My current project, for example, has different views connected with a UITabBarController. Some of the are custom UIViews and some of them are OpenGL views.

My first thought was to try to have different OpenGL contexts for each view, but that would complicate things because I wanted to share the same textures and other resources. I know there’s a shared groups option, but that was definitely not the way to go.

The best solution I found was to use multiple frame buffers, binding each of them to the correct OpenGL view. That way, when I switch views, I switch frame buffers and everything works correctly.

On the -viewDidLoad function for each view controller with an OpenGL view, I call the function listed above to create a new frame buffer bound to that view, and it returns an index. Then, in the -viewWillAppear function, I set the frame buffer corresponding to that index:

    const FrameBuffer& buffer = m_frameBuffers[bufferIndex];
    glBindFramebufferOES(GL_FRAMEBUFFER_OES, buffer.m_frameBufferHandle);   
    glBindRenderbufferOES(GL_RENDERBUFFER_OES, buffer.m_colorBufferHandle);
    glViewport(....);

That’s it. Now you can switch between different OpenGL views and UIKit views without any problems. Just make sure to only render the visible views! That can be trickier than it sounds because the viewWillAppear and viewDidDisappear functions only get called for views that were layed out in the Interface Builder. If you added them by hand, you need to call those methods yourself (why??!?!). So keep tabs on that, otherwise everything will slow down to a halt.

And To Wrap It Up…

You thought I had forgotten about the promise to slowly unveil art from my current project, uh? Fear not, here comes the next teaser. This is an actually screenshot taken a minute ago. Incidentally, this is not one of the views using OpenGL. We’ll have to save those for another day 🙂

screenshot

22 Comments

  1. Great post, Noel! When I first started my game I had wanted multiple OpenGL views, but I was still so new to iPhone development that I nervous about trying it, so I ended up finding ways around it instead. Are you using CoreAnimation with your OpenGL views? Maybe I’ll do some messing around with OpenGL and UIKit once I get this game out the door. 🙂

  2. Hi Owen,

    Yes, I’m using core animation on my OpenGL views. The frame rate dips when I do, but it’s not a big deal because it’s only for transitions. For example, I use the page flip transition to display some help. I also transition by animating the OpenGL view to the side while I bring in some other view. Works like a charm.

  3. Anyone see any issues using CoreAnimation transitions in a separate sibling view to an OpenGL view (same window)?

    When the animation ends, I get one frame of black (no rendering) the next time I draw my OpenGL view and present the renderBuffer. I make sure to set the current context, bind the framebuffer and renderbuffer, glviewport, etc. every frame. But for some reason I’m getting this blank frame when rendering, right when the transition (in a separate sibling view) completes.

    Seems like the transition’s use of OpenGL is somehow causing a glitch/conflict with my OpenGL view, but can’t figure it out, since my context/etc. all seem to be properly set each frame.

    Any ideas?

  4. Groovy, I’m having the same problem. I added admob to my OpenGL ES app and am getting the same black screen flash at the end of the animation. Did you ever figure the problem out?

  5. Hello there,

    thx for the tutorial but i am having some problems creating more than one framebuffers.

    I get two different kinds of errors.

    when i call glGenFramebuffersOES(1, &viewFramebuffer); the first time all works fine. when i call it afterwards for the others views i get either GL_INVALD_ENUM or the id for the framebuffer starts a 1 again

    Any idea what i could have wrong?

    here is some code:

    glGenFramebuffersOES(1, &viewFramebuffer);
    glGenRenderbuffersOES(1, &viewRenderbuffer);

    glBindRenderbufferOES( GL_RENDERBUFFER_OES, viewRenderbuffer );
    [context renderbufferStorage:GL_RENDERBUFFER_OES fromDrawable:(CAEAGLLayer*)self.layer];

    glBindFramebufferOES( GL_FRAMEBUFFER_OES, viewFramebuffer );

    glFramebufferRenderbufferOES( GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, viewRenderbuffer );

    glGetRenderbufferParameterivOES( GL_RENDERBUFFER_OES, GL_RENDERBUFFER_WIDTH_OES, &backingWidth );

    glGetRenderbufferParameterivOES( GL_RENDERBUFFER_OES, GL_RENDERBUFFER_HEIGHT_OES, &backingHeight );

    if( glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES) != GL_FRAMEBUFFER_COMPLETE_OES ) {
    NSLog( @”failed to make complete framebuffer object %x”, glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES) );
    return NO;
    }

    Would be awesome if anyone had a hint for me:-)

  6. Rozik, Your code seems fine to me. There might be something going on outside of that code (for example, are you destroying those buffers accidentally?).

    Just to be sure, call glGenFramebuffersOES() twice in a row. It should work and give you different buffer handles.

  7. @groovy @Bob
    I am having issue when integrate AdMob with OpenGL ES. Everytime admob updates its ad, seems like entire OpenGLES View flickers momentarily.

    I have
    GameViewController : UIViewController
    {
    AdMobView *ad_;
    EAGLView *gview_;
    ….
    }

    initialize:(CGRect)rect
    {
    gview_ = [[EAGLView alloc] initWithFrame:rect];
    [self.view addSubview:gview_];

    ad_ = [AdMobView requestAdWithDelegate:self];
    [ad_ retain];
    }

    Size of my EAGLView is full screen. AdMob view is placed on top of EAGLView.

    Any input/feedback much appreciated.

    Thanks

  8. The black flash created by admob went away when I upgraded my phone from 2.2 to 3.0

  9. Hi there! , thanks for this great post!!

    I would love if you could put up a very basic demo project 🙂 , I think it would be awesome for me, since I do learn better with examples.

    Again, thank you very much!

    _NG

  10. Noel, thanks for the great post. I’m struggling to get an OpenGLES view to coexist with a “utility application” (as Apple calls it). I want to have a main screen for OpenGLES, click a button and have it flip to “conventional” screen. I think it’s quite similar to what you described.

    Anyway you can post a template or example?

    Thanks again!

    • Nicolas and Stuart, Sounds good. I’ll put together a really simple demo in the next post. I’ll try to do that in the next couple of days.

  11. Looking forward to seeing your demo. I’ve been banging my head against the wall for a few nights now! For some reason, this is really giving me a hard time!

  12. I was able to adapt Apple’s “Utility Application” template to use OpenGLES on the main view without too much effort. (That is, not too much effort after trying 100 other ways…) So far, I am able to liberally apply UIKit items (toolbar with a few buttons, dynamic labels) over the GL graphics and the performance seems to be fine.

    Since this violates Apple’s published “best practices” (mixing OpenGLES and UIKit), do you think it may get rejected from the Application Store?

  13. Stuart, An app will definitely *not* be rejected because of mixing OpenGL and UIKit. Many games do it to greater or lesser extent, and Flower Garden is about half and half.

    That’s just a performance recommendation or best practice from Apple, not a violation of the expected user interface for example. So go for it!

  14. A very interesting article indeed. Any chance you could demo a small example of switching between OpenGL views? I just started writing apps for the iPhone, having never done C, Obj-C or OpenGL and visual reference is incredibly handy for me to learn.

    Thanks for posting anyway.

  15. Noel, I wanted to let you and your followers that my app was completed and approved for sale on the App Store. The mix of an OpenGLES and normal UIKit didn’t seem to have any performance impact, but then again, my app is pretty low conservative with processing needs.

    If anyone is interested, the app is “Lulav Wizard”, a simulator and educational tool for “shaking the lulav” for the Jewish holiday of Sukkot.

    itms://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=331224871&mt=8&s=143441

    Noel, if you’re interested, I can probably get you a “free” code.

  16. Regarding the multiple GL views problem, I have a few questions.
    Who actually does the generation of the singular context? I assume Not the individual controllers which own the views, since you need only one context. The application delegate?
    Then, how do you pass the context down the line? Is the context a global property?

    Why have a returned index for the respective frame buffers. Aren’t the frame buffers just properties of the view object? That way, each view should “know” it’s own frame buffer. Why pass it up and down the hierarchy?

    Thanks a lot Noel!

    Stuart

  17. Stuart,

    The view needs the handles for the color and depth buffers. The FrameBuffer structure is just a convenient way of packing those two handles. So yes, you have the right idea and they’re all contained in the view, so they’re not passed around anywhere.

  18. Can anyone tell how to combine an OpenGL ES template and a Utility app template?

    What I do: I create an OpenGL ES application template and a Utility Application template. I copy the files EAGLView.m and h, and the five ES*renderer.* files from the Opengl project to the utility project.

    I copy these lines from the OpenGl project to the Utility project: (in utilityAppDelegate.m)

    – (void)applicationDidFinishLaunching:(UIApplication *)application {

    [glView startAnimation];
    }

    – (void) applicationWillResignActive:(UIApplication *)application
    {
    [glView stopAnimation];
    }

    – (void) applicationDidBecomeActive:(UIApplication *)application
    {
    [glView startAnimation];
    }

    – (void)applicationWillTerminate:(UIApplication *)application
    {
    [glView stopAnimation];
    }

    – (void)dealloc {

    [glView release];

    }
    And in the utilityAppDelegate.h I add:

    @class EAGLView;
    EAGLView *glView;
    @property (nonatomic, retain) IBOutlet EAGLView *glView;
    I go to the view’s identity inspector, and change the class identity to EAGLview.

    I open the mainview.xib and mainwindow.xib and drag the app_delegate from the mainwindow.xib, to be able to connect the glView outlet to the view. (Don’t know if this is the way to do it, but that’s the only way I could connect the glView variable to the view)

    When I build and run, the multi colored square show up on the screen, but it does not animate!

    When I debug, the glView variable is 0 in applicationDidFinishLaunching, and the startAnimation is not called. I suspect this has something to to with the way the outlet is connected to the view? Probably not connected. 🙂

    What is missing? What have I done wrong?

    Thank you!

Comments are closed.

Webmentions

  • Multiple OpenGL UIViews at Under The Bridge November 17, 2009

    […] a tip you definitely need to give a read if you’re of the common opinion that OpenGL is only appropriate for fullscreen applications […]

  • links for 2009-04-16 « Blarney Fellow November 17, 2009

    […] Using Multiple OpenGL Views And UIKit (tags: opengl iphone viapss graphics) […]

  • iWyre November 17, 2009

    […] on Friday by admin on February 24th, 2009 Using Multiple OpenGL Views And UIKit In the Mobile Zodiac application for Chicago Tribune I started out using standard views for all […]

  • iWyre November 17, 2009

    […] on Friday by admin on February 7th, 2009 Using Multiple OpenGL Views And UIKit In the Mobile Zodiac application for Chicago Tribune I started out using standard views for all […]