When to create custom OpenGL view instead of subclass NSOpenGL view

When exactly would it be appropriate to create my own subclass of NSView instead of subclassing NSOpenGLView?

I intend to use a Core Video display link to drive my game loop and also have toggle-able fullscreen. I get the impression that I wouldn't be doing my drawing routines inside of drawRect if I use a display link, and fullscreen support seems to suggest having to handle OpenGL contexts myself (not that I'm any expert at that, mind you) so I kind of thought a custom OpenGL view would be appropriate.

I'm actually asking because the Apple documentation on writing custom OpenGL views mentions sample code available on ACD that I can't seem to find it.

Feel free to subclass all the way from NSView at any time. There is no particular benefit to subclassing from NSOpenGLView except that maybe you might use a little less code. Some of the guys around here prefer to *always* subclass from NSView.

[edit] arekkusu once mentioned that there may be threading issues you may have to contend with when subclassing from NSView. I don't know if there are still issues, but using a display link will necessarily mean you're threaded, so you might want to keep that in mind.

I'm still wondering about that sample code though. I actually started working on an NSView subclass but it still isn't drawing anything or even displaying the clear-colour. And I'm not even using the display link yet (well, I was, but the first time I ran that code caused my computer to spectacularly crash).

Coyote Wrote:And I'm not even using the display link yet (well, I was, but the first time I ran that code caused my computer to spectacularly crash).

Uhh... Well, I was just putting together a small working demo using a display link in an NSOpenGLView (subclassed in code, not from IB) and I just kernel panicked too. I don't think I was doing anything wrong, but it crashed instantly on launch one time. Ran fine a whole bunch of other times. I'm on the mighty, bug-free Snow Leopard. I've never had a kernel panic using this code for the display link on Leopard that I recall. I'm not sure what to make of it, but clearly it isn't a useful demo like this, sorry

I wonder if this is actually an OS bug or if I'm doing something wrong myself? Kernel panics of any kind usually are bugs. I don't really have time to investigate this right now, but it's now on my list of things to do when/if I get some free time.

[adding] This kinda sucks too, because I was actually seeing some slightly smoother performance than I recalled seeing on Leopard. Just when display link was looking great I get a kernel panic... Go figure.

Well, I still don't think it should've kernel panicked, but I did notice that I forgot to lock the GL context from one entry point in the code, so that darn-well may have precipitated the event. If I gain some confidence back in it, I might post the demo later for you to look at.

Wow, I'm getting some absolutely *fantastic* results from display link for the first time ever! I got black and white Pong running smooth as glass on the Mac. Yeah, just Pong. I haven't tried anything more complicated yet. In the past, even if I tried to synch my delta time to the actual display rate I'd get a dropped frame or at least some stuttering. After fifteen minutes of staring at it in amazement, I've only seen like one tiny little hiccup. Unbelievable. Definitely as close to video irq I've ever seen on OS X... of course, I'm just looking at Pong, but hey

I'll post a tiny demo with code tomorrow after I clean out the mess and verify that I haven't been hallucinating.

Yeah, the kernel panic must've been rooted in the fact that I forgot to lock the gl context at one point in the code. I haven't had any troubles since.

Here's an absolute bare-bones example of getting a display link working with OpenGL. Here's how to put it together:

- create a new Cocoa Application
- ceate glView.h/glView.m
- copy and paste in this code and save files
- add frameworks: QuartzCore and OpenGL
- open up MainMenu.xib
- drag Custom View into main window and resize to fill window
- set the autosizing of view to have the two inner arrows on so it resizes automatically with the window
- set the class of the Custom View to be glView
- save nib
- go back to Xcode and run project

Coyote Wrote:Nice work, but I noticed you subclassed NSOpenGLView instead of NSView.

Honestly, I've never attempted to subclass directly from NSView since NSOpenGLView has worked fine for everything for me over the years.

Coyote Wrote:Also, could I use deltaTime to regulate my framerate, or would I need something different?

Yeah you can do that, but I don't think it's possible to change the display link callback rate, if that's what you mean. deltaTime is so important for timing in general and I discovered how to calculate it with display link info, which is why I included it, but don't actually use it in the example. For some unknown reason, it seems a more stable calculation from display link than calculating between frames with mach time. What I do is *draw* the frame every time, regardless, and *update* at a separate rate so that the update is at a solid rate and compatible with physics engines. As you may know, the constant update rate is needed for stable physics integration. Frame rate itself may vary as the system gets overtaxed from heavy drawing, so it's important to keep them separated.

AnotherJake Wrote:Honestly, I've never attempted to subclass directly from NSView since NSOpenGLView has worked fine for everything for me over the years.

I guess it's just as well then. It seems all of the sample code on the Mac Dev Centre just subclass NSOpenGLView as well.

Quote:Yeah you can do that, but I don't think it's possible to change the display link callback rate, if that's what you mean. deltaTime is so important for timing in general and I discovered how to calculate it with display link info, which is why I included it, but don't actually use it in the example. For some unknown reason, it seems a more stable calculation from display link than calculating between frames with mach time. What I do is *draw* the frame every time, regardless, and *update* at a separate rate so that the update is at a solid rate and compatible with physics engines. As you may know, the constant update rate is needed for stable physics integration. Frame rate itself may vary as the system gets overtaxed from heavy drawing, so it's important to keep them separated.

Would you mind clarifying that code a bit? I'm not sure what each of the variables are. For one thing, you put the time delta into frameDeltaTime instead of deltaTime, but you still have deltaTime in your code.

Though I suppose I could just look over the time-based animation guide. The only difference between them and myself is that I calculated the time delta directly instead of getting it from the current time and the last recorded time.

Alright, well it's not as tiny as I hoped (maybe should've linked a project instead...), but it should illustrate everything and then some in as small amount of code as I could come up with. It builds off the previous example. You'll find the timing section in the getFrameForTime: method.

dt is what I use for deltaTime in the update and is set in the initWithFrame method, and is "fixed", although it could actually be changed for effects like slo-mo. frameDeltaTime is the time between frames, not updates. It's pretty hard to fully describe exactly what's happening, but I can add detailed comments for this timing section if you'd like me to try.

As noted in the code, I'm using 110 Hz here, but you can change it to whatever. If you change it to near 60 and you're using an LCD it'll appear to smooth out, but beware that other displays like CRTs could be many other refresh rates and it won't look as smooth on them.

Speaking of "smoothness", you may notice that the fixed rate timing isn't quite as glass smooth as just using the delta time from display link alone. To get it back to ultra smooth, you'd have to do sub-update interpolations (that is, interpolating data between two updates to precisely match where it would be in actual time) -- I think Bullet physics has support for this BTW. That is way too much to describe right here, so I hope you're already aware of how to do that. Normally though, while the tiny un-smoothness sticks out in pong because the contrast is so high and the objects are simple, in a regular game with action you probably won't even notice it. I have had user reviews claiming the animations were silky smooth, even though I knew I could make them even better because I wasn't doing sub-update interpolation.

To really see the difference in what I'm talking about with smoothness, change the #define for USE_FIXED_RATE_TIMING to 0 to get back to the same, but non-fixed, delta time in the previous example I gave a few posts up.

You might also notice that I'm using a thread-friendly way of grabbing input, using queues.

Similar to before (just different class name), to try it out yourself:

- create a new Cocoa Application
- ceate gameView.h/gameView.m
- copy and paste in this code and save files
- add frameworks: QuartzCore and OpenGL
- open up MainMenu.xib
- drag Custom View into main window and resize to fill window
- set the autosizing of view to have the two inner arrows on so it resizes automatically with the window
- set the class of the Custom View to be gameView
- save nib
- go back to Xcode and run project

// slo-mo only works with fixed rate, although it can be modified to work otherwise as well
#define USE_SLO_MO 0

// arbitrary -- you can pick whatever rate you want. I like 110 because it seems to be the
// smoothest compromise across different display refresh rates, but that is completely subjective
#define UPDATE_RATE 110.0

// we stop updating below this frame rate, which effectively pauses the game until drawing catches up,
// so not a good idea to go too high with this constant
#define MINIMUM_FRAME_RATE 4.0

// 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;
}

// set up our fixed delta time (which we maintain as an instance variable in case you want to change
// it on the fly somewhere down the road, for whatever reason -- you could just as well use a constant
// or define if you prefer instead)
dt = 1.0 / UPDATE_RATE;

#if USE_SLO_MO

// as mentioned, note that dt can be changed for interesting time effects, like "bullet time" with
// something like:
dt *= 0.25;

AnotherJake Wrote:Speaking of "smoothness", you may notice that the fixed rate timing isn't quite as glass smooth as just using the delta time from display link alone. To get it back to ultra smooth, you'd have to do sub-update interpolations (that is, interpolating data between two updates to precisely match where it would be in actual time) -- I think Bullet physics has support for this BTW.

Follow-up: Yes Bullet does indeed do this, although I haven't had time to make my own subjective assessment of the quality. I've just been feeding it my fixed dt and it seems to work fine, although I've noticed what appears to be some temporal aliasing now that I was looking for it. I need to try feeding it the deltaTime between frames instead of the fixed rate and see if it handles things better. I have heard others say that it's not quite what they would expect. The problem with feeding it the frame rate delta and letting it do its thing means it'll be out of step with my own game logic, which may be a serious issue to contend with...

Anyway, I remembered someone posting a link to a site which describes how this temporal aliasing can be remedied. Since I don't have time to describe it myself, here's that link, in case you aren't already aware of the technique. It's the same thing as ThemsAllTooks' tutorial you linked to earlier; it just adds an extra step which is simple in the timing section, but you have to interpolate all the data from (between) your updates during render which may or may not be a pain, depending on your game logic/physics implementation.