Using OpenGL With SDL

SDL has the ability to create and use OpenGL contexts on several platforms(Linux/X11, Win32, BeOS, MacOS Classic/Toolbox, MacOS X, FreeBSD/X11 and Solaris/X11). This allows you to use SDL's audio, event handling, threads and times in your OpenGL applications (a function often performed by GLUT).

Initialisation

Initialising SDL to use OpenGL is not very different to initialising SDL normally. There are three differences; you must pass SDL_OPENGL to SDL_SetVideoMode , you must specify several GL attributes (depth buffer size, framebuffer sizes) using SDL_GL_SetAttribute and finally, if you wish to use double buffering you must specify it as a GL attribute, not by passing the SDL_DOUBLEBUF flag to SDL_SetVideoMode.

Example 2-7. Initializing SDL with OpenGL

    // Information about the current video settings.
const
info : PSDL_VideoInfo = nil;
// Dimensions of our window.
var
width : integer := 0;
height : integer := 0;
// Color depth in bits of our window.
bpp : integer := 0;
// Flags we will pass into SDL_SetVideoMode.
flags : integer := 0;
begin
// First, initialize SDL's video subsystem.
if( SDL_Init( SDL_INIT_VIDEO ) < 0 ) then
begin
// Failed, exit.
MessageBox(0, PChar(Format('Video initialization failed : %s', [SDL_GetError])), 'Error', MB_OK or MB_ICONHAND);
quit_tutorial( 1 );
end;

// Let's get some video information.
info := SDL_GetVideoInfo;

if( info = nil ) then
begin
// This should probably never happen.
MessageBox(0, PChar(Format('Video query failed : %s', [SDL_GetError])), 'Error', MB_OK or MB_ICONHAND);
quit_tutorial( 1 );
end;


{* Set our width/height to 640/480 (you would
* of course let the user decide this in a normal
* app). We get the bpp we will request from
* the display. On X11, VidMode can't change
* resolution, so this is probably being overly
* safe. Under Win32, ChangeDisplaySettings
* can change the bpp.
*}
width := 640;
height := 480;
bpp := info.vfmt.BitsPerPixel;

{*
* Now, we want to setup our requested
* window attributes for our OpenGL window.
* We want *at least* 5 bits of red, green
* and blue. We also want at least a 16-bit
* depth buffer.
*
* The last thing we do is request a double
* buffered window. '1' turns on double
* buffering, '0' turns it off.
*
* Note that we do not use SDL_DOUBLEBUF in
* the flags to SDL_SetVideoMode. That does
* not affect the GL attribute state, only
* the standard 2D blitting setup.
*}
SDL_GL_SetAttribute( SDL_GL_RED_SIZE, 5 );
SDL_GL_SetAttribute( SDL_GL_GREEN_SIZE, 5 );
SDL_GL_SetAttribute( SDL_GL_BLUE_SIZE, 5 );
SDL_GL_SetAttribute( SDL_GL_DEPTH_SIZE, 16 );
SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 );

{*
* We want to request that SDL provide us
* with an OpenGL window, in a fullscreen
* video mode.
*
* EXERCISE:
* Make starting windowed an option, and
* handle the resize events properly with
* glViewport.
*}
flags := SDL_OPENGL or SDL_FULLSCREEN;

{*
* Set the video mode
*}
if ( SDL_SetVideoMode( width, height, bpp, flags ) = 0 ) then
begin

{*
* This could happen for a variety of reasons,
* including DISPLAY not being set, the specified
* resolution not being available, etc.
*}
        MessageBox(0, PChar(Format('Video mode set failed : %s', [SDL_GetError])), 'Error', MB_OK or MB_ICONHAND);
quit_tutorial( 1 );
end;

Drawing

Apart from initialisation, using OpenGL within SDL is the same as using OpenGL with any other API, e.g. GLUT. You still use all the same function calls and data types. However if you are using a double-buffered display, then you must use SDL_GL_SwapBuffers() to swap the buffers and update the display. To request double-buffering with OpenGL, use SDL_GL_SetAttribute with SDL_GL_DOUBLEBUFFER, and use SDL_GL_GetAttribute to see if you actually got it.

A full example code listing is now presented below.

Example 2-8. SDL and OpenGL

{*
* SDL OpenGL Tutorial.
* (c) Michael Vance, 2000
* briareos@lokigames.com
*
* Distributed under terms of the LGPL.
*}

uses SDL,
OpenGL;
var
should_rotate : TGLboolean := GL_TRUE;

procedure quit_tutorial( code : integer );
begin
{*
* Quit SDL so we can release the fullscreen
* mode and restore the previous video settings,
* etc.
*}
SDL_Quit;

// Exit program.
halt( code );
end;

procedure handle_key_down( keysym : PSDL_keysym );
begin
{*
* We're only interested if 'Esc' has
* been presssed.
*
* EXERCISE:
* Handle the arrow keys and have that change the
* viewing position/angle.
*}
case keysym.sym of
SDLK_ESCAPE:
quit_tutorial( 0 );
SDLK_SPACE:
should_rotate := not should_rotate;
else
break;
end;

end;

procedure process_events;
var
    // Our SDL event placeholder.
event : TSDL_Event;
begin

// Grab all the events off the queue.
while( SDL_PollEvent( @event ) ) do
begin

case event.type_ of
SDL_KEYDOWN:
/* Handle key presses. */
handle_key_down( &event.key.keysym );

SDL_QUIT:
/* Handle quit requests (like Ctrl-c). */
quit_tutorial( 0 );
end;

end;

end;

procedure draw_screen;
var
angle : single;
v0, v1, v2, v3, v4, v5, v6, v7 : array[0..2] of TGLFloat;
red, green, blue, white, yellow, black, orange, purple : TGLuByte;
begin
// Our angle of rotation.
angle := 0.0;

{*
* EXERCISE:
* Replace this awful mess with vertex
* arrays and a call to glDrawElements.
*
* EXERCISE:
* After completing the above, change
* it to use compiled vertex arrays.
*
* EXERCISE:
* Verify my windings are correct here ;).
*}
v0[] := { -1.0f, -1.0f, 1.0f };
v1[] := { 1.0f, -1.0f, 1.0f };
v2[] := { 1.0f, 1.0f, 1.0f };
v3[] := { -1.0f, 1.0f, 1.0f };
v4[] := { -1.0f, -1.0f, -1.0f };
v5[] := { 1.0f, -1.0f, -1.0f };
v6[] := { 1.0f, 1.0f, -1.0f };
v7[] := { -1.0f, 1.0f, -1.0f };
red[] := { 255, 0, 0, 255 };
green[] := { 0, 255, 0, 255 };
blue[] := { 0, 0, 255, 255 };
white[] := { 255, 255, 255, 255 };
yellow[] := { 0, 255, 255, 255 };
black[] := { 0, 0, 0, 255 };
orange[] := { 255, 255, 0, 255 };
purple[] := { 255, 0, 255, 0 };

// Clear the color and depth buffers.
glClear( GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT );

// We don't want to modify the projection matrix.
glMatrixMode( GL_MODELVIEW );
glLoadIdentity;

// Move down the z-axis.
glTranslatef( 0.0, 0.0, -5.0 );

// Rotate.
glRotatef( angle, 0.0, 1.0, 0.0 );

if ( should_rotate ) then
begin

inc( angle, 1 );
if ( angle > 360.0 ) then
begin
angle := 0.0;
end;

end;

// Send our triangle data to the pipeline.
glBegin( GL_TRIANGLES );

glColor4ubv( red );
glVertex3fv( v0 );
glColor4ubv( green );
glVertex3fv( v1 );
glColor4ubv( blue );
glVertex3fv( v2 );

glColor4ubv( red );
glVertex3fv( v0 );
glColor4ubv( blue );
glVertex3fv( v2 );
glColor4ubv( white );
glVertex3fv( v3 );

glColor4ubv( green );
glVertex3fv( v1 );
glColor4ubv( black );
glVertex3fv( v5 );
glColor4ubv( orange );
glVertex3fv( v6 );

glColor4ubv( green );
glVertex3fv( v1 );
glColor4ubv( orange );
glVertex3fv( v6 );
glColor4ubv( blue );
glVertex3fv( v2 );

glColor4ubv( black );
glVertex3fv( v5 );
glColor4ubv( yellow );
glVertex3fv( v4 );
glColor4ubv( purple );
glVertex3fv( v7 );

glColor4ubv( black );
glVertex3fv( v5 );
glColor4ubv( purple );
glVertex3fv( v7 );
glColor4ubv( orange );
glVertex3fv( v6 );

glColor4ubv( yellow );
glVertex3fv( v4 );
glColor4ubv( red );
glVertex3fv( v0 );
glColor4ubv( white );
glVertex3fv( v3 );

glColor4ubv( yellow );
glVertex3fv( v4 );
glColor4ubv( white );
glVertex3fv( v3 );
glColor4ubv( purple );
glVertex3fv( v7 );

glColor4ubv( white );
glVertex3fv( v3 );
glColor4ubv( blue );
glVertex3fv( v2 );
glColor4ubv( orange );
glVertex3fv( v6 );

glColor4ubv( white );
glVertex3fv( v3 );
glColor4ubv( orange );
glVertex3fv( v6 );
glColor4ubv( purple );
glVertex3fv( v7 );

glColor4ubv( green );
glVertex3fv( v1 );
glColor4ubv( red );
glVertex3fv( v0 );
glColor4ubv( yellow );
glVertex3fv( v4 );

glColor4ubv( green );
glVertex3fv( v1 );
glColor4ubv( yellow );
glVertex3fv( v4 );
glColor4ubv( black );
glVertex3fv( v5 );

glEnd;

{*
* EXERCISE:
* Draw text telling the user that 'Spc'
* pauses the rotation and 'Esc' quits.
* Do it using vetors and textured quads.
*}

{*
* Swap the buffers. This this tells the driver to
* render the next frame from the contents of the
* back-buffer, and to set all rendering operations
* to occur on what was the front-buffer.
*
* Double buffering prevents nasty visual tearing
* from the application drawing on areas of the
* screen that are being updated at the same time.
*}
SDL_GL_SwapBuffers;
end;

procedure setup_opengl( width : integer; height : integer );
var
ration : single;
begin
ratio := width / height;

// Our shading model--Gouraud (smooth).
glShadeModel( GL_SMOOTH );

// Culling.
glCullFace( GL_BACK );
glFrontFace( GL_CCW );
glEnable( GL_CULL_FACE );

// Set the clear color.
glClearColor( 0, 0, 0, 0 );

// Setup our viewport.
glViewport( 0, 0, width, height );

{*
* Change to the projection matrix and set
* our viewing volume.
*}
glMatrixMode( GL_PROJECTION );
glLoadIdentity;
{*
* EXERCISE:
* Replace this with a call to glFrustum.
*}
gluPerspective( 60.0, ratio, 1.0, 1024.0 );
end;

var
width, height, bpp : integer;
flags : integer;
info : PSDL_VideoInfo;
begin
// Information about the current video settings.
info := nil;
// Dimensions of our window.
width := 0;
height := 0;
// Color depth in bits of our window.
bpp := 0;
// Flags we will pass into SDL_SetVideoMode.
flags := 0;

// First, initialize SDL's video subsystem.
if( SDL_Init( SDL_INIT_VIDEO ) < 0 ) then
begin
// Failed, exit.
MessageBox(0, PChar(Format('Video initialization failed : %s', [SDL_GetError])), 'Error', MB_OK or MB_ICONHAND);
quit_tutorial( 1 );
end;

// Let's get some video information.
info := SDL_GetVideoInfo;

if( info <> nil ) then
begin
// This should probably never happen.
MessageBox(0, PChar(Format('Video query failed : %s', [SDL_GetError])), 'Error', MB_OK or MB_ICONHAND);
quit_tutorial( 1 );
end;

{*
* Set our width/height to 640/480 (you would
* of course let the user decide this in a normal
* app). We get the bpp we will request from
* the display. On X11, VidMode can't change
* resolution, so this is probably being overly
* safe. Under Win32, ChangeDisplaySettings
* can change the bpp.
*}
width := 640;
height := 480;
bpp = info.vfmt.BitsPerPixel;

{*
* Now, we want to setup our requested
* window attributes for our OpenGL window.
* We want *at least* 5 bits of red, green
* and blue. We also want at least a 16-bit
* depth buffer.
*
* The last thing we do is request a double
* buffered window. '1' turns on double
* buffering, '0' turns it off.
*
* Note that we do not use SDL_DOUBLEBUF in
* the flags to SDL_SetVideoMode. That does
* not affect the GL attribute state, only
* the standard 2D blitting setup.
*}
SDL_GL_SetAttribute( SDL_GL_RED_SIZE, 5 );
SDL_GL_SetAttribute( SDL_GL_GREEN_SIZE, 5 );
SDL_GL_SetAttribute( SDL_GL_BLUE_SIZE, 5 );
SDL_GL_SetAttribute( SDL_GL_DEPTH_SIZE, 16 );
SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 );

{*
* We want to request that SDL provide us
* with an OpenGL window, in a fullscreen
* video mode.
*
* EXERCISE:
* Make starting windowed an option, and
* handle the resize events properly with
* glViewport.
*}
flags := SDL_OPENGL or SDL_FULLSCREEN;

{*
* Set the video mode
*}
if ( SDL_SetVideoMode( width, height, bpp, flags ) = 0 ) then
begin
/*
* This could happen for a variety of reasons,
* including DISPLAY not being set, the specified
* resolution not being available, etc.
*/
MessageBox(0, PChar(Format('Video mode set failed : %s', [SDL_GetError])), 'Error', MB_OK or MB_ICONHAND);
quit_tutorial( 1 );
end;

{*
* At this point, we should have a properly setup
* double-buffered window for use with OpenGL.
*}
setup_opengl( width, height );

{*
* Now we want to begin our normal app process--
* an event loop with a lot of redrawing.
*}
while( true ) do
begin
// Process incoming events.
process_events;
// Draw the screen.
draw_screen;
end;

{*
* EXERCISE:
* Record timings using SDL_GetTicks() and
* and print out frames per second at program
* end.
*}

// Never reached.
exit;
end;