|
Development Guide Library
Setting up an OpenGL capable window in SDL by baldurkIn 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
| |
#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
#include <GL/gl.h>
#include <GL/glu.h>
#include <SDL.h>
bool keys[SDLK_LAST];
const int width = 800, height = 600, bitdepth = 32;
void Draw(int TimePassed)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity();
glColor3f(1.0f, 1.0f, 1.0f);
}
void Init()
{
}
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;
}
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 -1;
}
glViewport(0, 0, width, height);
glMatrixMode(GL_PROJECTION);
gluPerspective(45.0f, float(width)/float(height), 1.0f, 500.0f);
glMatrixMode(GL_MODELVIEW);
Init();
for(int i=0; i < SDLK_LAST; i++) keys[i] = false;
int TimePrev = SDL_GetTicks(), TimeCurrent, TimePassed;
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;
}
SDL_Quit();
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
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
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
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
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.
|