Correct place to set up shaders for GLKView? - ios

I'm having trouble finding the correct place to do my shader setup for an OpenGLES application using GLKView and GLKViewController.
It seems like viewDidLoad is a natural place to do this, but shader creation fails when I try to do this here. My setup is something like this:
//shader helper method
int setupShaders(const char* vShader, const char* fShader); //returns a program handle
//inside GLKViewController subclass
static int program;
-(void)viewDidLoad{
[super viewDidLoad];
program = setupShaders(vsh, fsh); //program will be zero indicating setup failure
}
I know the setup code works because it succeeds if I call it inside -(void)glkView:(GLKView *)view drawInRect:(CGRect)rect.
So I'm assuming OpenGL isn't fully initialized when -(void)viewDidLoad is called, or something has to be done to set the correct OpenGL context for the setup I'm trying to do, I just can't find any documentation on where or how to do setup correctly.

You are right that the earliest place you can easily set up your shaders is in the drawRect method. This is because there must be a valid GL context current. Per the GLKView documentation:
Before calling its drawRect: method, the view makes its EAGLContext object the current OpenGL ES context and binds its framebuffer object to the OpenGL ES context as the target for rendering commands.
So, the easiest thing to do is hang onto some information, like the program handle, and only initialize if it is non-zero.
if (program == 0)
program = setupShaders(vsh, fsh);
If you don't like this approach, you can consider initializing your GLKView with a context that you provide, or overriding bindDrawable. Or you could not use GLKView and do things manually...

There needs to be a current EAGLContext before you can call any OpenGL ES API, and that includes setup work like compiling shaders. GLKView makes its context current before invoking your drawRect: (or glkView:drawInRect:) method, but you're welcome to make it the current context at any time.
The view's context is current as of viewDidAppear: because that method is called after the first time the view draws itself. I'm not sure it's guaranteed to be current at that point, though -- there's no documented API contract that a GLKView's context will remain current after the end of drawing. So it's better to call [EAGLContext setCurrentContext:myContext] yourself whenever you want to do setup work for your OpenGL ES scene (even if you choose to do it in viewDidAppear:).
See the OpenGL ES Game template when you create a new Xcode project for an example of GLKView/GLKViewController setup.

So it turns out it works perfectly if you initialize from inside -(void)viewDidAppear. Jacob's solution works fine as well, but it seems slightly cleaner to me to use a callback rather than adding a conditional to the draw method.

Related

What is the difference between glkView and update methods?

When subclassing GLKViewController, it has two methods amongst others:
- (void)update,
and - (void)glkView:(GLKView *)view drawInRect:(CGRect)rect.
Both of these methods seem to be called each frame. Documentation reads:
As an alternative to implementing a glkViewControllerUpdate: method in a delegate, your subclass can provide an update method instead. The method must have the following signature:
- (void)update;
Which one should I override in my code, and which of them should contain draw calls?
This thread answers this: Difference between glkView:drawInRect: and glkViewControllerUpdate:? OpenGL ES 2 - iOS
Quoting:
About update() The view controller calls its delegate’s glkViewControllerUpdate: method. Your delegate should update frame data that does not involve rendering the results to the screen.
About drawInRect: the GLKView object makes its OpenGL ES context the current context and binds its framebuffer as the target for OpenGL ES rendering commands. Your delegate method should then draw the view’s contents.

iOS - Can UIGraphicsGetCurrentContext be used outside drawRect?

I want to dynamically change the current CGContextRef according to different user actions? Is this possible or is its modification only possible within drawRect: of a view instance? What happens when I call UIGraphicsGetCurrentContext() outside drawRect: and are there any limitations in doing so, is this recommended? Any possible implications I need to consider?
According to the docs the graphics context is only set just before this function is called. This means that if this function is not called it won't be set and if you don't make the system call it again (never do this yourself for that reason) it won't be there either.
Use one of these functions to force the view back into drawRect:
setNeedsDisplay:
setNeedsDisplayInRect:
It doesn't mean you can only do stuff inside drawRect however. This context is sort of globally available at that moment and you can call clean separate functions or even classes for drawing things. Passing the reference to those functions is a clean way to do it.

Drawing on UIView vs UIViewController

I'm new to CG drawing, and I'm confused on where the CG code goes.
What is stopping the idea of putting the draw functions in the UIViewController vs the UIView? How should I determine which parts of the CG code should go where? I see that some of the tutorials have code in viewDidLoad from the view controller, but others say it goes in the view. What determines what goes where?
(Yes this is kind of an MVC question, but Im still having trouble differentiating.)
The correct place for custom drawing code is (almost always) in the drawRect method of a subclass of UIView. The usual way to go is to make a custom subclass of UIView and make that the root view of your view controller. In the view controller's loadView method, for example, you can assign self.view = [[MyCustomView alloc] init]; (autorelease that view if you're in non-ARC code!) Then your custom drawing code should go in the drawRect method of MyCustomView.
Core Graphics drawing code can go where ever a valid context is. This means it can go in your own functions if you create your own context.
The reason you generally place Core Graphics drawing code in a UIView subclass is you normally may want to encapsulate the code in a reusable form. But if you were going to create an image from the Core Graphics code you could easily start a new image context, draw, then save the contents of the context into a UIImage. This type of drawing can go anywhere, even a UIViewController. Core Graphics drawing can even be used to generate PDFs. Its simply a simple geometric drawing framework. As long as you have a valid context be it the one created before drawRect: is called in a UIView or a context you created on command.

Can I pass the CGContextRef to methods called from drawRect?

I found this in the Quartz 2D Programming Guide:
To draw to the screen in an iOS application, you set up a UIView object and implement its drawRect: method to perform drawing. The view’s drawRect: method is called when the view is visible onscreen and its contents need updating. Before calling your custom drawRect: method, the view object automatically configures its drawing environment so that your code can start drawing immediately. As part of this configuration, the UIView object creates a graphics context (a CGContextRef opaque type) for the current drawing environment. You obtain this graphics context in your drawRect: method by calling the UIKit function UIGraphicsGetCurrentContext.
Since I am having problems with invalid Context (because it's 0x00 when I go back to re-draw), I was wondering if I could get the current context in the beginning of -drawRect and somehow pass it to the methods I call from within -drawRect?
You can definitely pass CGContextRef to methods called from drawRect: as long as these methods do not save the reference for use outside the duration of the drawRect: call, your code should be fine. However, the context reference that you pass around would be equivalent to the context retrieved through UIGraphicsGetCurrentContext, so I doubt that there is much to gain by adding an extra parameter.
UIGraphicsGetCurrentContext can only be called from drawRect: method (or methods called from it) otherwise it will return nil.
You can use UIGraphicsGetCurrentContext from any method called from -drawRect. It's worth noting that you should not call -drawRect directly when you need to update your view; call -setNeedsDisplay instead.
If you want to use the UIKit drawing system with your own off-screen context, you can use UIGraphicsPushContext to set the current context.
In my experience, passing CGContextRef produces a memory leak that's pretty "fast."

On-demand OpenGL ES rendering using GLKit

I am looking into converting my OpenGL rendering code to take advantage of a few features of GLKit (namely the asynchronous texture loading and the automation provided by GLKView/Controller). However, it appears that the classes are designed mainly to accommodate people rendering using an animation loop, whereas I'm working with on-demand rendering. Additionally, some of the rendering is to a texture rather than the GLKView's framebuffer, so should I be looking to just subclass the GLKView and add additional FBOs?
Is there a recommended approach for this type of setup? I would expect something along the lines of:
Set the view controller's preferredFramesPerSecond to 0, or just
pause the frame updates?
Ignore the glkViewControllerUpdate or glkView:drawInRect: methods
and just draw what I need, when I need it.
Use the view's setNeedsDisplay as with a normal UIView in order
to display the frame (do I need to call bindDrawable given that I
will be rendering to a texture as well?).
Perhaps it's not worth the effort if this is not what the new API is designed for? I wish the documentation was a little more thorough than it is. Perhaps more samples will be provided when the API has 'matured' a little...
Thanks
The approach I ended up using was to not bother with the GLKViewController, but just use GLKView directly under a UIViewController subclass.
Clearly, the GLKViewController is intended for use by people who need a consistent rendering loop for apps such as games. Without it, drawing to the GLKView is as simple as calling [glkView setNeedsDisplay]. Be sure to set enableSetNeedsDisplay to YES in order to enable this behaviour.
If you did still want to make use of a GLKViewController, you can disable the animation rendering loop in viewWillAppear like so:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated]; // setPaused automatically set to NO in super's implementation
[self setPaused:YES];
}
Also, set resumeOnDidBecomeActive to NO to prevent the view controller from resuming again automatically.
Using a plain UIViewController with a GLKView is perfectly acceptable however, and I have seen it recommended by an Apple engineer as an appropriate way to perform on-demand drawing.
I've just converted my code from using an EAGLContext manager I rolled myself to using the GLKit classes.
You suggest you might "..ignore the.. glkView:drawInRect: methods and just draw what [you] need, when I need it". This seems like a sensible option performance-wise; I assume (though haven't tried) if you simply don't specify a GLKViewDelegate or provide a subclassed GLKView with its drawInRect: defined then no animation loop rendering will occur. Have you attempted this?
The alternative would be to simply create some #property (assign, nonatomic) BOOL shouldUpdate; in your MyController : GLKViewController <GLKViewDelegate> class which will only update if there is something to do:
[self setDelegate:self]; // in init or awakeFromNib or other..
-(void)glkView:(GLKView *)view drawInRect:(CGRect)rect {
if ([self shouldUpdate]) { ...
I'm sure you get the idea, it's hardly complicated.
One thing worth mentioning: the official API docs state that viewDidLoad should be used in your GLKViewController for initial GL setup. I had issues with this; for some reason my glCreateShader calls always returned zero. This may have been due to my setting the EAGLContext post-initialisation; I couldn't pass it as an init parameter since I created the controller in Storyboard. However, there was nothing logically wrong with the code, so I offer this friendly warning in case you encounter similar issues. My solution is simply to have the following in my drawInRect:
-(void)glkView:(GLKView *)view drawInRect:(CGRect)rect {
if ([self initialGLSetupDone] == NO) {
[self beforeFirstRender];
[self setInitialGLSetupDone:YES];
}
// .. rest of render code goes here.
}
Obviously it's not ideal to have an IF in there unnecessarily, but it was an easy solution.
Let me know how it goes if you try updating to use GLKit.
After you have created GLKView, add this line:
glkView.enableSetNeedsDisplay = TRUE;
(Because of this, no one will redraw the view automatically)
When you want redraw, insert this line:
[glkView setNeedsDisplay];
... then drawInRect routine will be called only once.
Hope it helps.

Resources