MULTIMEDIA

OpenGL on OS X : OpenGL with Cocoa (part 2)

2/27/2013 8:48:10 PM

Wiring It All Together

Back in the Xcode project window, you see the MyOpenGLView header and implementation files. These contain the stubbed definition of the MyOpenGLView class, derived from NSOpenGLView. Interface Builder has already wired this class to our OpenGL view in the main window, and we now only need to add the class framework and OpenGL rendering code.

The edited header file for the new class is trivial and simply contains a member pointer to an NSTimer that will be used for animation:

#import <Cocoa/Cocoa.h>

@interface MyOpenGLView : NSOpenGLView {
    NSTimer *pTimer;
}

@end

In the implementation file, we add an idle function and implement four essential top-level OpenGL tasks that every OpenGL program needs: prepareGL for OpenGL initialization, clearGLContext for OpenGL cleanup, reshape for calculating the viewport and window bounds, and finally drawRect where we perform our rendering tasks. The entire source for our minimal OpenGL framework is given in Listing 1.

Listing 1. A Skeleton OpenGL View Class
#import "MyOpenGLView.h"

@implementation MyOpenGLView

- (void)idle:(NSTimer *)pTimer
    {
    [self drawRect:[self bounds]];
    }


- (void) prepareOpenGL
    {
    pTimer = [NSTimer timerWithTimeInterval:(1.0/60.0) target:self
              selector:@selector(idle:) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop]addTimer:pTimer forMode:
                                            NSDefaultRunLoopMode];

    glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
    }

- (void) clearGLContext
    {
    // Do any OpenGL Cleanup
    }

- (void) reshape
    {
    NSRect rect = [self bounds];
    glViewport(0, 0, rect.size.width, rect.size.height);
    }

- (void) drawRect:(NSRect)dirtyRect
    {
    glClear(GL_COLOR_BUFFER_BIT);

    glFlush();
    }

@end

					  

The output for CocoaGL is shown in Figure 11. As you can see it’s nothing but an empty blue window, but now we have a complete framework to build on for our next example.

Figure 11. OpenGL in a Cocoa view.


Double or Single Buffered?

At this point, the astute reader may be imagining the sound of screeching tires on pavement. Was that a glFlush you saw in Listing 14.1 instead of some sort of buffer swap call? Indeed it was, and this brings us to an interesting subtlety of OpenGL on Mac OS X (as well as a nice segue into the next section).

On Mac OS X, the entire desktop is actually OpenGL accelerated. Anytime you are rendering with OpenGL, you are always rendering to an off-screen buffer. A buffer swap does nothing but signal the OS that your rendering is ready to be composited with the rest of the desktop. You can think of the desktop compositing engine as your front buffer. Thus, in windowed OpenGL applications (this applies to both Cocoa and the now deprecated Carbon), all OpenGL windows are really single buffered. Depending on how you look at it, it would also be okay to say that all OpenGL windows are really double buffered, with the desktop composite being the front buffer. Pick whichever one helps you sleep best at night! In fact, if you were to execute a glDrawBuffer(GL_FRONT), the drivers on the Mac actually would fall into a triple-buffered mode! In reality, all OpenGL windows on the Mac should be treated as single buffered. The buffer swap calls are really just doing a glFlush, unless you are working with a full-screen context. For this reason (and many others—the least of which is that you are bypassing the driver’s own good sense as to when to flush) you should avoid glFlush in Cocoa views until you have completed all of your OpenGL rendering.

SphereWorld

We begin by creating a new Xcode project and following all of the same steps as we did for CocoaGL, with the exception that we call the custom view class SphereWorldView. Next, we copy the SphereWorld.cpp file and the three texture files to the folder with our new project. Add SphereWorld.cpp to the project and add the three texture files to the /Resources folder in Xcode.

GLTools and Objective-C++

For this example we use our C++ library GLTools again. We can add GLTools the same way we did for our previous GLUT-based examples, so we do not rehash that here. However, what we do need to do here that is new is allow our C++ code to work with Objective-C. As it turns out...you can’t. The solution is surprisingly simple: We change our project to use Objective-C++ instead! All you have to do is rename the .m files to .mm, and they become Objective-C++ files, and you can use your C++ classes within them just as if they were C++ files themselves. As a bonus, all the Cocoa Objective-C classes work just the same as well. Figure 12 shows our Xcode project with these changes so far. Now it’s time to clean up the SphereWorld.cpp file and place the appropriate functions calls in the Cocoa class.

Figure 12. Our Objective-C++ and GLTools-based project.

Pruning SphereWorld

If we remove the GLUT framework code from SphereWorld.cpp, we are left with four functions that need to be called from our Cocoa framework: SetupRC, ShutdownRC, ChangeSize, and RenderScene. You can probably guess where they go. Removing GLUT turns out to be trivial. First, remove the GLUT headers from the top of the source file.

#ifdef __APPLE__
#include <glut/glut.h>
#else
#define FREEGLUT_STATIC
#include <gl/glut.h>
#endif

Next, at the end of the RenderScene function, we perform a buffer swap and trigger a refresh. We don’t need the buffer swap any longer as the Cocoa framework takes care of that and has a timer that takes care of periodic refreshes. Removing these two lines nearly completes the job.

// Do the buffer Swap
glutSwapBuffers();

// Do it again
glutPostRedisplay();

The final bit of pruning is to remove the main function in its entirety and delete the SpecialKeys callback, as we no longer are receiving keyboard input through GLUT.

Wiring It In

Calling a function in a C++ module from an Objective-C++ module is no different than normal C++ cross module programming. Listing 2 shows our SphereWorldView implementation with the appropriate functions from SphereWorld declared and called where needed in the framework. We have also added the Cocoa message keyDown to catch keystrokes and the acceptFirstResponder method so that keystrokes will be handled by the OpenGL window. This allows us to move the camera in the same manner as we did in the GLUT-based version.

Listing 2. The Cocoa-Based SphereWorld
#include <GLTools.h>
#include <GLFrame.h>

#import "SphereWorldView.h"

void ChangeSize(int nWidth, int nHeight);
void RenderScene(void);
void ShutdownRC(void);
void SetupRC(void);

extern GLFrame       cameraFrame;     // Camera frame

@implementation SphereWorldView
- (void)idle:(NSTimer *)pTimer
{
    [self drawRect:[self bounds]];
}

- (BOOL)acceptsFirstResponder
{
    [[self window] makeFirstResponder:self];
    return YES;
}

- (void)keyDown:(NSEvent*)event
{
    float linear = 0.1f;
    float angular = float(m3dDegToRad(5.0f));

    int key = (int)[[event characters] characterAtIndex:0];

    switch(key)
        {
        case NSUpArrowFunctionKey:
            cameraFrame.MoveForward(linear);
            break;
        case NSDownArrowFunctionKey:
            cameraFrame.MoveForward(-linear);
            break;
        case NSLeftArrowFunctionKey:
            cameraFrame.RotateWorld(angular, 0.0f, 1.0f, 0.0f);
            break;
        case NSRightArrowFunctionKey:
            cameraFrame.RotateWorld(-angular, 0.0f, 1.0f, 0.0f);
            break;
        }
}

- (void) prepareOpenGL
{
    pTimer = [NSTimer timerWithTimeInterval:(1.0/60.0) target:self
                                   selector:@selector(idle:) userInfo:nil
                                   repeats:YES];

[[NSRunLoop currentRunLoop]addTimer:pTimer forMode: NSDefaultRunLoopMode];

    SetupRC();
}

- (void) clearGLContext
{
    ShutdownRC();
}

- (void) reshape
{
    NSRect rect = [self bounds];
    ChangeSize(rect.size.width, rect.size.height);
}

- (void) drawRect:(NSRect)dirtyRect
{
    RenderScene();

    glFlush();
}

@end

					  

Finding the Texture Files

Like in our GLUT-based samples, we put the texture files for SphereWorld in the Resources group in Xcode. Again, we must reset the current working directory so that our file functions can find them. In our GLUT samples, we put this in the main function. For Cocoa, we also put this in the main function. The entire main.mm file is shown here:

#include <GLTools.h>
#import <Cocoa/Cocoa.h>

int main(int argc, char *argv[])
    {
    gltSetWorkingDirectory(argv[0]);
    return NSApplicationMain(argc,  (const char **) argv);
    }

We needed to add the GLTools.h header file at the top of the file, and then the call to the by now familiar gltSetWorkingDirectory function takes care of the rest.

GLEW Versus Cocoa

There is one last thing we need to take care of. SphereWorld makes use of GLTools, but GLTools makes use of the GLEW library, which brings in additional OpenGL functions and extensions. There is a funny requirement about using GLEW, which is that the GLEW header files must be included before the actual system OpenGL header, gl.h. With our GLUT-based programs, this was never an issue, as GLTools brought in all the headers as needed. When we added SphereWorld.cpp to this project, again we had no problem. But as soon as we add GLTools to an .mm file...we get compiler errors. If you trace the errors down into the headers, you’ll find that they are caused because glew.h was included after gl.h. Search as you may, you will not find out where this is occurring by inspection of the source code of the project.

As it turns out, Cocoa itself is including the OpenGL headers. Remember, OpenGL is used everywhere on the Mac. There is a file in the Xcode project called SphereWorld_Prefix.pch that is the “prefix header.” It’s a precompiled header that automatically gets added to all the Objective-C/C++ modules. What we need to do is sneak our glew.h header file into that file. Not hard to do—it’s only four lines long, and that’s with our change!

#ifdef __OBJC__
    #include <gl/glew.h>
    #import <Cocoa/Cocoa.h>
#endif

Finally, we can get a clean build, and we have a real full- (well, mostly full) featured Cocoa-based OpenGL program. Figure 14.13 shows our final masterpiece.

Figure 13. SphereWorld in a Cocoa-based application.
Other  
 
Video tutorials
- How To Install Windows 8 On VMware Workstation 9

- How To Install Windows 8

- How To Install Windows Server 2012

- How To Disable Windows 8 Metro UI

- How To Change Account Picture In Windows 8

- How To Unlock Administrator Account in Windows 8

- How To Restart, Log Off And Shutdown Windows 8

- How To Login To Skype Using A Microsoft Account

- How To Enable Aero Glass Effect In Windows 8

- How To Disable Windows Update in Windows 8

- How To Disable Windows 8 Metro UI

- How To Add Widgets To Windows 8 Lock Screen
programming4us programming4us
Top 10
Free Mobile And Desktop Apps For Accessing Restricted Websites
MASERATI QUATTROPORTE; DIESEL : Lure of Italian limos
TOYOTA CAMRY 2; 2.5 : Camry now more comely
KIA SORENTO 2.2CRDi : Fuel-sipping slugger
How To Setup, Password Protect & Encrypt Wireless Internet Connection
Emulate And Run iPad Apps On Windows, Mac OS X & Linux With iPadian
Backup & Restore Game Progress From Any Game With SaveGameProgress
Generate A Facebook Timeline Cover Using A Free App
New App for Women ‘Remix’ Offers Fashion Advice & Style Tips
SG50 Ferrari F12berlinetta : Prancing Horse for Lion City's 50th