2007-12-12

Xcode/GLUT Tutorial

This was the most popular thing on my old site. I've updated it for Xcode 3 on Leopard, but that means that the instructions may be a little misleading for Xcode 2 or earlier...

Xcode? OpenGL? GLUT?

Xcode is Apple's integrated software development environment.

OpenGL is a graphics library. It does both 2D and 3D graphics, and takes advantage of hardware acceleration. It's cross-platform; that is, the same code will run on the Mac, Windows and Linux.

OpenGL itself is cross-platform, but each OS has a different way of making an OpenGL window and getting events. GLUT is a simple cross-platform wrapper for this functionality.

Making a new project

Open the Xcode application. You'll find it in /Developer/Applications/:

From Xcode's File menu, choose "New Project...". Select "Empty Project" from the list, then click "Next":

Xcode will ask you what to call your project and where to save it. I'm going to call mine "XcodeGLUT" and put it on my Desktop.

Now click "Finish". Your project's window will appear:

Making a GLUT target

Xcode projects can have any number of targets. A target describes how to make a product such as an application, library, framework or plug-in.

Our empty project didn't come with a target, so we need to make one. From the "Project" menu, choose "New Target...". Choose "Cocoa Application" and click "Next":

You can name your target whatever you like. I'm going to call mine "XcodeGLUT". Then click "Finish":

Xcode presents you with your new target's settings. Xcode's default Cocoa Application template is designed for writing a Cocoa application in Objective C. We're just writing a GLUT application in C, so we need to make a small adjustment to the target settings. Change the value of the GCC_PREFIX_HEADER setting to be empty:

In order to compile a GLUT application we need a couple of extra frameworks. Right- or control-click on your target's icon, and choose "Add ▸ Existing Frameworks...". You'll be presented with an Open dialog showing /System/Library/Frameworks. If you have trouble with the contextual menu, you can also use "Add to Project..." in the "Project" menu, but you'll need to navigate to /System/Library/Frameworks yourself. Choose both GLUT.framework and OpenGL.framework. You can use command (⌘)-click to select both at once. Then click "Add":

Xcode will ask you for some settings for the added frameworks; the defaults are fine. Just click "Add":

Finally, we need to make a file for the source code. From the "File" menu, choose "New File...". Select "Empty File in Project", then click "Next":

Xcode will ask you what you want to call the file, and where you want to save it. Call it "main.c"; the default settings for everything else are fine. click "Finish":

Building your first Application

Copy and paste the following code into "main.c". Don't worry at this stage what it all means, we'll go through it line-by-line later:

#include <stdlib.h>

#include <GLUT/glut.h>

void display(void)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
    glutSwapBuffers();
}

void reshape(int width, int height)
{
    glViewport(0, 0, width, height);
}

void idle(void)
{
    glutPostRedisplay();
}

int main(int argc, char** argv)
{
    glutInit(&argc, argv);
    
    glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH);
    glutInitWindowSize(640, 480);
    
    glutCreateWindow("GLUT Program");
    
    glutDisplayFunc(display);
    glutReshapeFunc(reshape);
    glutIdleFunc(idle);
    
    glutMainLoop();
    return EXIT_SUCCESS;
}

Save the file, then click "Build and Go" on the toolbar, or choose "Build and Run" from the "Build" menu. After some time, your new application should run. At this stage, it's just a black window. When you're done admiring your handiwork, quit it and return to Xcode.

What does all that code mean?

This section assumes you're not totally new to programming in C. If you are, you should probably go away now and find a quick introduction to the language.

#include <stdlib.h>

#include <GLUT/glut.h>

stdlib.h includes some fundamental constants and functions for programming in C. GLUT/glut.h includes the constants and functions for GLUT and OpenGL. On Linux and Windows, GLUT/glut.h is called GL/glut.h.

int main(int argc, char** argv)
{
    glutInit(&argc, argv);

main is where the program begins executing. argc and argv are used to pass command-line arguments to the program; you needn't worry about them other than that they get passed to glutInit. glutInit is always the first function you call in a GLUT program; Bad Things™ will happen if it's not.

    glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH);
    glutInitWindowSize(640, 480);

The call to glutInitDisplayMode here is telling GLUT that all our windows should be in red-green-blue-alpha mode, be double-buffered (to avoid flickering when drawing), and have a depth buffer (to ensure that 3D objects overlap correctly regardless of the order they're drawn in). The call to glutInitWindowSize tells GLUT that our windows should initially be 640 pixels wide by 480 pixels high.

    glutCreateWindow("GLUT Program");

The call to glutCreateWindow actually makes a window. The string passed is the title of the window. Until we've created a window, we can't make any OpenGL calls. Now, it's safe.

    glutDisplayFunc(display);
    glutReshapeFunc(reshape);
    glutIdleFunc(idle);

glutDisplayFunc, glutReshapeFunc and glutIdleFunc tell GLUT to call particular functions when certain things happen. The function we pass to glutDisplayFunc is called whenever it's time to draw a frame; the function we pass to glutReshapeFunc is called whenever the window is resized; and the function we pass to glutIdleFunc is called whenever GLUT isn't doing anything else.

    glutMainLoop();
    return EXIT_SUCCESS;
}

Finally, glutMainLoop tells GLUT to go off and do its thing, calling the functions we specified before whenever the appropriate things happen. glutMainLoop never returns, so returning EXIT_SUCCESS at the bottom is redundant, but it pacifies the compiler (which doesn't know that glutMainLoop never returns).

void idle(void)
{
    glutPostRedisplay();
}

Our idle function simply tells GLUT that we'd like to draw another frame now. This, combined with the fact that idle will be called whenever GLUT is not doing anything else, ensures that our program will run at the maximum possible frame rate.

void reshape(int width, int height)
{
    glViewport(0, 0, width, height);
}

Our reshape function simply informs OpenGL that the window size has changed. width and height are the new width and height of the window in pixels.

void display(void)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
    glutSwapBuffers();
}

Our display function asks OpenGL to clear the color and depth buffers, then to swap the back buffer onto the screen.

Because we asked for a double-buffered window, we have two mages, called the front and back buffer. The front buffer is what's visible on-screen in the window. All OpenGL commands modify the back buffer. The glutSwapBuffers call swaps the back buffer to become the front buffer, instantly making all our drawing visible.

The color buffer is cleared to black by default; this can be changed with the glClearColor function.

Actually drawing something

First, let's draw a white rectangle. Edit the display function so it looks like this:

void display(void)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
    glBegin(GL_QUADS);
    glVertex2f(-0.75f, -0.75f);
    glVertex2f( 0.75f, -0.75f);
    glVertex2f( 0.75f,  0.75f);
    glVertex2f(-0.75f,  0.75f);
    glEnd();
    
    glutSwapBuffers();
}

Again, save the file and "Build and Go". You should see a white rectangle filling most of the window.

A "Quad" is OpenGL's term for a four-sided polygon. The four glVertex2f calls say where the vertices (corners) of this polygon ie. By default, OpenGL's coordinate system has (-1, -1) in the lower left corner of the window, and (1, 1) in the upper right. Note that since our window isn't square, we get a rectangle rather than a square, as you might expect. We'll see how to fix this in the next section.

A pixel-perfect projection

In OpenGL, you can use whatever coordinate system is useful to you. One of the most useful for 2D graphics is the projection where OpenGL units are precisely one pixel.

Edit the reshape function to look like this:

void reshape(int width, int height)
{
    glViewport(0, 0, width, height);
    
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluOrtho2D(0, width, 0, height);
    glMatrixMode(GL_MODELVIEW);
}

If you've done any linear algebra, that will make some kind of sense to you. If not, don't worry too much — just accept it as some necessary mumbo-jumbo.

glMatrixMode tells OpenGL which of the matrices it uses for projecting OpenGL coordinates onto pixels we want to modify. Most changes are done to the model-view matrix, but in this case we want to change the projection. glLoadIdentity resets the matrix to the identity matrix, and gluOrtho2D sets up the 2D projection. The arguments to gluOrtho2D are left, right, bottom, top. Note that in OpenGL it's usual to put (0, 0) in the lower left (as is usual in mathematics) rather than the upper left (as is usual in computer graphics).

With this change to the coordinate system, the quad we're drawing is only three quarters of a pixel in size, right in the bottom left of the window. We should probably change that:

    glBegin(GL_QUADS);
    glVertex2f(  0.0f,   0.0f);
    glVertex2f(128.0f,   0.0f);
    glVertex2f(128.0f, 128.0f);
    glVertex2f(  0.0f, 128.0f);
    glEnd();

Again, build and run. You should see a 128- by 128-pixel white square in the lower left of the window.

Animation and timing

Let's get this square moving. How are we going to do that? One obvious way of doing it would be to move it a certain number of pixels every frame. That has a serious flaw, however — the speed of the square would be relative to the speed of the computer, since a faster computer will draw more frames per second than a slower one. That means we have to build the actual amount of time elapsed into our movement calculations.

GLUT has a handy way to get the number of milliseconds that have passed since glutInit() was called — glutGet(GLUT_ELAPSED_TIME). We'll want to work with the time in seconds, so we'll need to divide the number we get back by 1000.

Let's get this square moving. We'll move it right at 512 pixels per second, and when it's past the edge of the window, we'll move it back to the left.

We can also use glutGet to find the current width of the window in pixels, rather than storing it ourselves in the reshape function. We'll use this to make sure the square always goes to the edge of the window.

We'll need two global variables. One (lastFrameTime) to store the number of milliseconds elapsed at the last frame, so we know how much time we need to account for this frame, and one (boxX) to store the horizontal position of the box. These go just under the #includes:

int lastFrameTime = 0;
    
float boxX = 0.0f;

Then, we need to rewrite the display function:

First, we check if lastFrameTime is still zero. If it is, this is the first frame, and we need to set it to something sensible.

void display(void)
{
    if (lastFrameTime == 0)
    {
        lastFrameTime = glutGet(GLUT_ELAPSED_TIME);
    }

Next, we calculate how many seconds have elapsed since the previous frame. We get the current time, calculate how many milliseconds have elapsed since the last frame, convert that to seconds (being very careful to divide by a floating point number rather than an integer, which would give zero), then set lastFrameTime to the current time, in preparation for the next frame.

    int now = glutGet(GLUT_ELAPSED_TIME);
    int elapsedMilliseconds = now - lastFrameTime;
    float elapsedTime = elapsedMilliseconds / 1000.0f;
    lastFrameTime = now;

Next, we retrieve the window width in pixels from GLUT, then move our box. It goes right by 512 pixels every second, so we multiply 512 by the number of seconds elapsed, and add that to its current position. If it's now beyond the edge of the window, we move it back to the start again.

    int windowWidth = glutGet(GLUT_WINDOW_WIDTH);
    
    boxX += 512.0f * elapsedTime;
    if (boxX > windowWidth)
    {
        boxX -= windowWidth;
    }

We clear the color and depth bufers as usual.

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

glTranslatef(x, y, z) moves everything drawn after it the specified number of units. We'll use this command to draw the square in a different place, rather than calculating the new vertex positions by hand. Because we don't want this change to apply over multiple frames, we use glPushMatrix and glPopMatrix around the commands we want to be affected. We draw the square and swap the buffers just as before.

    glPushMatrix();
    glTranslatef(boxX, 0.0f, 0.0f);
    
    glBegin(GL_QUADS);
    glVertex2f(  0.0f,   0.0f);
    glVertex2f(128.0f,   0.0f);
    glVertex2f(128.0f, 128.0f);
    glVertex2f(  0.0f, 128.0f);
    glEnd();
    glPopMatrix();
    
    glutSwapBuffers();
}

Build and run the program again. Notice that no matter how jerky the movement is, the square always moves at the same speed. Try resizing the window, or running another OpenGL game at the same time to make it jerky.

Also notice that no matter how big the window is, the square always moves at the same speed, and always goes back when it eaches the window's edge.

31 comments:

José Henriques said...

Thanks!

Just looking for this!

Michael said...

Yup! Was looking for this too, the previous link was broken but now a shinier version came out :)

Leo said...

it works!!

Barnaby said...

Awesome. Thank you very much for writing this.

Bartosz said...

Thanks!

gavi said...

Thank you. This article is great.

E J Kalafarski said...

Great article. I wish all tutorials were this complete and down-to-earth.

alman1 said...

thanks, clear, it all works,
and a first step to begin and go further

kashmoney2006 said...

When running the program, nothing happens? It compiles fine, says its running, but no window comes up. any ideas? I dont get any errors or warnings, but nothing shows up when i run the program? any help? Thanks.

e-mail: surya.p.56@gmail.com

Hell said...

Great tutorial, I was exactly looking for something like this with screenshots and everything. Xcode is quite confusing for beginners.

efnx said...

Awesome, thanks! Got me running.

Neil said...

Exactly what I was looking for. Thanks!

Salvietta said...

This is really a good article for me! I'm a beginner...but the most important thing I would ask is (if possible)...I'm writing a plug-in for an existing application (I've a specific developer kit with all wrappers necessary to me to produce my functions). How can I integrate GLUT? Calling glutInit...glutCreateWindow etc...I don't see any GLUT window...and the main program lose control due to the glutMainLoop call...could you help me? (surely this is not the place for a question but actually i'm not able to attach to forum page) Thankyou Salvietta

pop said...

great!!
thanks

Mark said...

That was easy as pie -- thank you very much!

Speaking as a ten-year DirectX veteran programmer who switched to Mac recently, I just needed something to get me off the ground with Mac graphics, and I believe you just did that for me.

J' said...

Thank you, i was wondering how to setup a glut project!

Yaroslav said...

awesome!!!!

thanks!

Patrick said...

This is awesome!
Is it possible to add an NSopenpanel to a C/C++ project. I've seen lots of documentation on how to do this in Objective-C, but building on what you've shown here, I would really like to be able to have open/save dialogs.
Thanks!

Gragzo said...

Wow... what an awesome tutorial! Xcode has changed a bit (I'm using version 3.1.2), but just with a few changes, everything went ok.

Thanks a lot!

Greetings from Chile,
Gragzo

tadejm said...

Tnx, helped me a lot!

Hari said...

Thanks. your post saved a lot of time of mine.

--Hari

solgems said...
This post has been removed by the author.
solgems said...

really great!!

btw, if i add more than 1 file in the same project, say in ur example XCodeGLUT project other den main.c i have another main2.c with exactly the same code,

i get a compile error (when i try to build and go) with the exit saying duplicated symbol display, its under linking,

any idea?

thnx!

solgems said...

ahhh ok i found it.. haha

just need to switch the target memberships so only 1 .c file is associated with it (:

great guide!

makako said...

Hi, how do I exactly display pixels? Cocoa has already constructs for geometric figures, what´s the point of using OpenGL?

Anthony said...

Fantastic article! Everything I was looking for, thank you :)

qgi said...

Thanks a lot! This has been really helpful and easy to follow.

WarriBoyz said...

Awesome tutorial. I am more of a hands on kind of guy and I was looking for a barebones tutorial that just showed me the basics. This fit the bill perfectly. Now time to dive down into the guts of OpenGL :)

Omid Noor said...

Thanks a ton for the tutorial .. I tried a whole week to get this done before i found this one..

gonzobrains said...

Hey mang,

You have a tutorial like this but where the target is the iPhone simulator? I'm not sure if GLUT works on the iPhone, so if you can write up something on how to port GLUT-based OpenGL projects directly onto the iPhone that would be great.

Thanks!
Jeff

ActuarialPlayboy said...

Thank you so very much. I just obtained a copy of the red book, and haven't been able to use it until just now. You've ended days of strife for me.