HomeBrew OpenGL view in Cocoa

Has anyone ever tried attaching an NSOpenGLContext to a generic NSView subclass? I can do it easily enough, but with one nagging design flaw: you have to check for the first call to -drawRect: in order to set the context's view to the current one.

I strongly dislike the fact that it's running an if() block every single frame after that -- seems like a waste of time, but mostly it's ugly. I've scoured the NSView, NSWindow and NSWindowController docs for a method to override that will allow me to set the view on the context before the first call to drawRect. The following do not work (you get "invalid drawable" error):
-awakeFromNIB
-didAttachToSuperview
-didAttachToWindow
-initWithFrame:
and the NSWindowController delegate calls don't look like much use (besides you'd then have to "hunt" for the sub-view with an -openGLContext method... or would the responder chain take care of that?)

Using NSOpenGLView from Apple I can actually avoid such weird first time checks (the context's view is set "automatically" when I call [self openGLContext]), but if Apple says that I can attach an NSOpenGLContext to any view, dammit, I want to know how to do it, and do it clean. And I want to know how *they* are setting the context's view. If they are doing the first time check but hiding it (say, in -display, then that sucks, and if they have a better way, I want to know what it is.

I've asked about this on Cocoa Dev but nobody wanted to actually answer me clearly.

You do have to init your context on the first call to drawRect: and yes, you have to do an if() check each time drawRect is called.

However, if you are animating, you shouldn't be doing your drawing in drawRect: and calling setNeedsDisplay:YES every frame. What you should be doing is calling lockFocus in your animation loop, doing your drawing and then calling unlock focus. That way your drawRect will only get called when the window server needs to update your window. A whole bunch of other stuff goes on under the hood when doing your drawing in the drawRect: method, and you'll be much better off if you bypass that drawing path as much as you can. Because, when you call setNeedsDisplay:YES, it has to draw everyhting underneath the view first (including that pretty window background) just in case your view has transparency. And until Quartz Extreme is released, your OpenGL context can't really have any transparency (that's why NSOpenGLView can get away with the drawRect: path, it knows how to not let the stuff underneath draw first)

So, to summarize... a simple if() isn't what will be the bottleneck when doing your drawing through the drawRect: mechanism... what you should use instead is an explicit lockFocus on your context and do your drawing in there, outside of your drawRect: routine. You should use your drawReect routine only for setting up your context if it hasn't been set up (also a good place to reset aspect ratio and whatnot when you resize)

Gooddoug, I'm getting two opinions -- yours and one from Cocoa-dev on this topic. Granted the other opinion is in the form of "what's good for me" as a Cocoa beginner. I was told not to do drawing at all outside of the official mechanism. Is -lockFocus/unlockFocus the only really important setup/finish required to do drawing outside of -drawRect:? Is there anything else I should worry about?

OneSadCookie, your experience doesn't make sense to me, because when I add a custom view to a window in Interface Builder and make it as big as I'm able, it has the same width and height as the window (going by the attributes panel in the Inspector). I don't see how it could be bigger. Is there a way to specify if a window's title bar is shown or not? IB doesn't have one... and I can't find a method. That's weird, dude.

...

OK, rate this solution: I can make the custom view the window's delegate, and implement -windowDidBecomeKey: where I can call [oglContext setView: self]. It works, except that this gets called after -drawRect:. But if I follow Gooddoug's advice and put my gl calls elsewhere, this isn't a problem. So what Cocoa laws would I be breaking? I can easily create a new delegate object with an outlet to hold the gl view to make it more orthodox. It works fine, except when I resize the view it does not resize the drawing rectangle (and it fills the rest of th space with garbage). I've double-checked the resize springs in IB and they're right (at least, they match settings from another app that resizes correctly). What have I done that the context doesn't know the size of the view now? Do I have to override a method to return the new frame/bounds rectangle?

Quote:Originally posted by Feanor
OneSadCookie, your experience doesn't make sense to me, because when I add a custom view to a window in Interface Builder and make it as big as I'm able, it has the same width and height as the window (going by the attributes panel in the Inspector). I don't see how it could be bigger. Is there a way to specify if a window's title bar is shown or not? IB doesn't have one... and I can't find a method. That's weird, dude.

What he means is using the window's content view, not a view made in IB. Each window has a single view that contains all the other views in the window. You can get it by calling -[NSWindow contentView].