GLProgramming.com

home :: about :: development guides :: irc :: forums :: search :: paste :: links :: contribute :: code dump

-> Click here to learn how to get live help <-



 
 

Development Guide Library


Setting up an OpenGL capable window in SDL by baldurk

In this DG I will give a simple basecode that can be used in small projects where you are more focussed on having a simple steady base to build an app off, rather than having a basecode that fits your other code.

For this reason, I will also explain how the basecode works so that you can see, and then make your own. This step is important. You haven't really learned anything until you can recreate this basecode, not perfectly, but in your own way. Try it out, use it for your projects, but also play around with the code and see if you can learn about it.

Please note that this basecode has not been designed with good design in mind, simply ease of use. I'd recommend you rewrite this code in a more OO fashion.

I'll also provide details on how to set up the code to compile in linux and windows both in Microsoft Visual C++ 6 and Visual C++ .NET.

First, I'll dump the code on you. I'd advise you to put it in a file, and have the file open seperately from this page, so that you can flick between them as I describe the various stages in the basecode.

The code below is available for download here


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
 
///////////////////////////////////////////////////////////////
// A SIMPLE SDL BASECODE BY BALDURK AT GLPROGRAMMING.COM
///////////////////////////////////////////////////////////////
// find us at glprogramming.com or on IRC at irc.enterthegame.com/#opengl
///////////////////////////////////////////////////////////////
// Last Modified: 19/05/04
///////////////////////////////////////////////////////////////
// SDL documentation here: http://sdldoc.csn.ul.ie/index.php
//
// OpenGL documentation here: http://www.opengl.org
//             and here: http://www.glprogramming.com/red
//             and here: http://www.glprogramming.com/blue
///////////////////////////////////////////////////////////////



// Necessary windows libraries and includes.
#ifdef _WIN32
    #pragma comment(lib, "glu32.lib")
    #pragma comment(lib, "opengl32.lib")
    #pragma comment(lib, "SDL.lib")
    #pragma comment(lib, "SDLmain.lib")    

    #include <windows.h>
#endif

// Includes for the OpenGL, GLU and SDL headers.
#include <GL/gl.h>
#include <GL/glu.h>
#include <SDL.h>

// An array to keep key-states.
bool keys[SDLK_LAST];
// The width, height and bitdepth
const int width = 800, height = 600, bitdepth = 32;

// A function to draw the scene. The parameter is the number of milliseconds passed
// since the previous frame.
// This is where you put your code that executes every frame.
void Draw(int TimePassed)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();
    glColor3f(1.0f, 1.0f, 1.0f);
}

// This function performs any Initialisation you need to do for your project before
// the program starts up
void Init()
{

}

// The Basecode below here.

int main(int argc, char **argv)
{
    // Initialise SDL, complain if we can't.
    if(SDL_InitSubSystem(SDL_INIT_VIDEO) < 0)
    {
        fprintf(stderr, "InitSubSystem() failed. SDL Error: %s\n", SDL_GetError());
        SDL_Quit();
        return -1;
    }

    // Get a surface.
    SDL_Surface *Surface = SDL_SetVideoMode(width, height, bitdepth, SDL_OPENGL);

    // Check the surface, complain if we didn't get it.
    if(!Surface)
    {
        fprintf(stderr, "SetVideoMode() Failed. SDL_Error: %s\n", SDL_GetError());
        SDL_Quit();
        return -1;
    }

    // Set up the GL viewport
    glViewport(0, 0, width, height);
    glMatrixMode(GL_PROJECTION);
    gluPerspective(45.0f, float(width)/float(height), 1.0f, 500.0f);
    glMatrixMode(GL_MODELVIEW);

    // Call the user defined Init function.
    Init();

    // initialise all the key stats to "up" initially
    for(int i=0; i < SDLK_LAST; i++) keys[i] = false;

    // initialise the time variables
    int TimePrev = SDL_GetTicks(), TimeCurrent, TimePassed;
    // main message pump
    while(1)
    {
        // Do the time-based movement calculations
        TimeCurrent = SDL_GetTicks();
        TimePassed  = TimeCurrent - TimePrev;

        // Call the user defined Draw function
        Draw(TimePassed);
        // Swap the front and back buffers
        SDL_GL_SwapBuffers();

        // Do the time-based movement calculations
        TimePrev = TimeCurrent;

        // Get an event
        SDL_Event Event;
        SDL_PollEvent(&Event);

        // Check its type.
        // If it's a quit-type event, break out of the SDL loop
        if((Event.type == SDL_KEYDOWN && Event.key.keysym.sym == SDLK_ESCAPE) ||
           Event.type == SDL_QUIT)
            break; // we get out of the while(1) like this.

        // If it's a keydown event, set the relevant key to true/down
        if(Event.type == SDL_KEYDOWN)
            keys[Event.key.keysym.sym] = true;

        // If it's a keyup event, set the relevant to false/up
        if(Event.type == SDL_KEYUP)
            keys[Event.key.keysym.sym] = false;
    }

    SDL_Quit();

    // Everything went fine.
    return 0;
}


As you can see, the code is relatively simple. This code could be stripped down more, but I wanted to keep input and also time-based movement. These will prove useful in any demos you make with this code. I'll now breakdown the code for you, section by section. If you see any code that isn't covered, it'll be covered in a later section. I'll strip out any comments because they are essentially a small version of the comments I'll be making on the code.

Includes and declarations:


1
2
3
4
5
6
 
#include <GL/gl.h>
#include <GL/glu.h>
#include <SDL.h>

bool keys[SDLK_LAST];
const int width = 800, height = 600, bitdepth = 32;


Here we include the header files necessary for the functions we'll be using. This is pretty simple. If you get problems with any of these headers not being found, check your installation setup in one of the setup sections later in this DG.

We also declare an array of size SDLK_LAST. Here is where we hold the states of all the keys on the keyboard. It's a simple true/down false/up list, which you can reference into by any of the SDLK_ definitions. A complete list can be found here.

Other than that, we just have some constants for our window size

User defined functions


1
2
3
4
5
6
7
8
9
10
11
12
 
void Draw(int TimePassed)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();
    glColor3f(1.0f, 1.0f, 1.0f);
}

void Init()


{
}


These two functions are bare. This is where you will put in your code to do whatever you want to do. I've just put in some starting code in the Draw function so that the screen gets cleared.

The Init() function gets called only when the application starts up, after the window is created. That means that you can put in whatever commands you want in there, you'll have a valid GL context by this time. This includes loading textures, setting lights, calculating geometry, and other such one-time tasks.

The Draw() function is called every single frame, but you can obviously do more than just draw in this part of the code. Any frame update goes in here. It takes one parameter, an integer which holds the number of milliseconds that have passed since the previous time the function was called. Although it's probably not good enough to keep a clock that only loses a microsecond each year, it's enough to get any motion in your demo running time-based rather than frame-based. Assuming, of course, that you factor that variable into your motion equations.

Initialising SDL


1
2
3
4
5
6
7
8
 
int main(int argc, char **argv)
{
    if(SDL_InitSubSystem(SDL_INIT_VIDEO) < 0)
    {
        fprintf(stderr, "InitSubSystem() failed. SDL Error: %s\n", SDL_GetError());
        SDL_Quit();
        return -1;
    }


Here we begin the program, with the entry point main(). This is where the execution of the program starts. The absolute first thing that we do is initialise the Video subsystem of SDL. We only need video, we don't need audio or anything else. Input is automatically initialised with any of the other subsystems, we don't need to explicitely initialise it.

We check the return value of SDL_InitSubSystem to make sure there were no errors (0). If there were errors, we print an error, with details we get from SDL with SDL_GetError(), and then quit SDL and our program.

Otherwise, we carry on.

Creating the window


1
2
3
4
5
6
7
8
 
    SDL_Surface *Surface = SDL_SetVideoMode(width, height, bitdepth, SDL_OPENGL);

    if(!Surface)
    {
        fprintf(stderr, "SetVideoMode() Failed. SDL_Error: %s\n", SDL_GetError());
        SDL_Quit();
        return false;
    }


This is where we actually create our window. SDL calls it a surface, but in our case it is a window. If you are doing any 2D rendering with SDL, you will find other uses for surfaces, but we will only use it here. First we create a variable to point to our new Surface variable, then we call SDL_SetVideoMode() to create our window. We don't actually need the Surface variable, we just use it to check the return value. If the video mode can't be set then we fail in a similar way to if there was a problem with SDL_Init().

We're creating a window that's 800 pixels wide, 600 high at a depth of 32 bits, using the constants we declared at the start of the code. The fourth parameter is a flags parameter. There are several you can use, most aren't applicable. In fact, after some experimentation, SDL_OPENGL is the only one you need. There is another flag, SDL_GL_DOUBLEBUFFER, to enable double buffering but it seems to be enabled by default. If you find it differently, you just need to use bitwise OR (|) to add in SDL_GL_DOUBLEBUFFER.

Setting up the OpenGL viewport


1
2
3
4
 
    glViewport(0, 0, width, height);
    glMatrixMode(GL_PROJECTION);
    gluPerspective(45.0f, float(width)/float(height), 1.0f, 500.0f);
    glMatrixMode(GL_MODELVIEW);


This piece of code is the first OpenGL you've seen in the window set up. It sets up the OpenGL viewport so that you render to the whole screen and in perspective mode.

First we call glViewport, this command makes sure that we're drawing to the whole screen.

Then we switch the matrix mode so that we're modifiying the Projection matrix. This is the matrix that converts from world co-ordinates to screen co-ordinates, and we need to select it before going into perspective mode.

The third line sets up the view as perspective. This is the normal 3D view you'd expect, with objects in the distance smaller than ones in the foreground. One alternative is orthographics mode, in which all objects of the same size are equally sized, regardless of their depth. We set up the normal view of 45 degrees on the Y axis, and set the aspect ratio to a fixed one of 4:3. The near and far planes are set, respectively, to 1 and 500. That's a quick explantion of perspective, better docs lie elsewhere.

Finally all we do is switch back to modelview for any translations you do in Draw()

Final Initialisation


1
2
3
4
 
    Init();
    for(int i=0; i < SDLK_LAST; i++) keys[i] = false;

    int TimePrev = SDL_GetTicks(), TimeCurrent, TimePassed;


Here we call the user defined initialisation functions, reset all the keys to the false/up and declare the variables we'll need later for time-based movement. It's all pretty simple stuff

One thing I should explain is the SDL_GetTicks() call. It returns the number of milliseconds since a specific point. Possibly since the program started, but all that really matters is that the point is constant, because we just deal in differences.

The Message pump


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 
    while(1)
    {
        TimeCurrent = SDL_GetTicks();
        TimePassed  = TimeCurrent - TimePrev;

        Draw(TimePassed);
        SDL_GL_SwapBuffers();

        TimePrev = TimeCurrent;

        SDL_Event Event;
        SDL_PollEvent(&Event);
        if((Event.type == SDL_KEYDOWN && Event.key.keysym.sym == SDLK_ESCAPE) ||
           Event.type == SDL_QUIT)
            break;
        if(Event.type == SDL_KEYDOWN)
            keys[Event.key.keysym.sym] = true;
        if(Event.type == SDL_KEYUP)
            keys[Event.key.keysym.sym] = false;
    }


This might look complex at first, but it really isn't. First, we get the current TickCount (remember SDL_GetTicks() from the final initialisation?) and get the time passed between the previous run through, and this one. We set TimePrev to the previous time either at the end of this loop (when we actually set it to timecurrent, but it's the same thing) or outside the loop. This is what gives us our time passed variable.

After that, we draw (passing the variable), and flip the double buffers that we have.

Finally, we grab an event from SDL. We then go through a series of ifs to check what it is, and thanks to the clarity of SDL macros, it should be clear what it's doing. Event.key.keysym.sym is an integer which offsets into our array of keys. Each one is unique. It also corresponds with ASCII characters in many situations.

Final cleanup and conclusion


1
2
3
4
 
    SDL_Quit();

    return 0;
}


Simple enough. We shut down SDL, then return out of the function.

There's a run through of the basecode. You should now have a fairly good idea of how to set up a window and begin using OpenGL in SDL. What follows are sections to show you how to set up the code on Visual C++ .NET.

Microsoft Visual C++ .NET

Open up visual studio, and create a new project.



Select a Win32 Console Project, and supply a project name.



Under application settings, confirm that it is an empty console application.



Now that you have a new project, add the basecode, main.cpp to your project.



Next you will have to make a slight change to the project properties to get SDL

compiling in visual studio.



Under Configuration Properties, select the folder marked C/C++. Under this folder, select Code Generation.

Its important that you change the Runtime Library to Multi-threaded DLL (/MD), or Multi-threaded Debug DLL (/MDd).

Either option will work fine.



Now you can compile and run the basecode as you would a normal project. If you're having trouble with the setup, feel free to email baldurk.