3cf7852e03
git-svn-id: svn://kolibrios.org@5131 a494cfbc-eb01-0410-851d-a64ba20cac60
719 lines
16 KiB
HTML
719 lines
16 KiB
HTML
<HTML
|
|
><HEAD
|
|
><TITLE
|
|
>Using OpenGL With SDL</TITLE
|
|
><META
|
|
NAME="GENERATOR"
|
|
CONTENT="Modular DocBook HTML Stylesheet Version 1.64
|
|
"><LINK
|
|
REL="HOME"
|
|
TITLE="SDL Library Documentation"
|
|
HREF="index.html"><LINK
|
|
REL="UP"
|
|
TITLE="Graphics and Video"
|
|
HREF="guidevideo.html"><LINK
|
|
REL="PREVIOUS"
|
|
TITLE="Graphics and Video"
|
|
HREF="guidevideo.html"><LINK
|
|
REL="NEXT"
|
|
TITLE="Input handling"
|
|
HREF="guideinput.html"></HEAD
|
|
><BODY
|
|
CLASS="SECT1"
|
|
BGCOLOR="#FFF8DC"
|
|
TEXT="#000000"
|
|
LINK="#0000ee"
|
|
VLINK="#551a8b"
|
|
ALINK="#ff0000"
|
|
><DIV
|
|
CLASS="NAVHEADER"
|
|
><TABLE
|
|
WIDTH="100%"
|
|
BORDER="0"
|
|
CELLPADDING="0"
|
|
CELLSPACING="0"
|
|
><TR
|
|
><TH
|
|
COLSPAN="3"
|
|
ALIGN="center"
|
|
>SDL Library Documentation</TH
|
|
></TR
|
|
><TR
|
|
><TD
|
|
WIDTH="10%"
|
|
ALIGN="left"
|
|
VALIGN="bottom"
|
|
><A
|
|
HREF="guidevideo.html"
|
|
>Prev</A
|
|
></TD
|
|
><TD
|
|
WIDTH="80%"
|
|
ALIGN="center"
|
|
VALIGN="bottom"
|
|
>Chapter 2. Graphics and Video</TD
|
|
><TD
|
|
WIDTH="10%"
|
|
ALIGN="right"
|
|
VALIGN="bottom"
|
|
><A
|
|
HREF="guideinput.html"
|
|
>Next</A
|
|
></TD
|
|
></TR
|
|
></TABLE
|
|
><HR
|
|
ALIGN="LEFT"
|
|
WIDTH="100%"></DIV
|
|
><DIV
|
|
CLASS="SECT1"
|
|
><H1
|
|
CLASS="SECT1"
|
|
><A
|
|
NAME="GUIDEVIDEOOPENGL"
|
|
>Using OpenGL With SDL</A
|
|
></H1
|
|
><P
|
|
>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).</P
|
|
><DIV
|
|
CLASS="SECT2"
|
|
><H2
|
|
CLASS="SECT2"
|
|
><A
|
|
NAME="AEN103"
|
|
>Initialisation</A
|
|
></H2
|
|
><P
|
|
>Initialising SDL to use OpenGL is not very different to initialising SDL normally. There are three differences; you must pass <TT
|
|
CLASS="LITERAL"
|
|
>SDL_OPENGL</TT
|
|
> to <A
|
|
HREF="sdlsetvideomode.html"
|
|
><TT
|
|
CLASS="FUNCTION"
|
|
>SDL_SetVideoMode</TT
|
|
></A
|
|
>, you must specify several GL attributes (depth buffer size, framebuffer sizes) using <A
|
|
HREF="sdlglsetattribute.html"
|
|
><TT
|
|
CLASS="FUNCTION"
|
|
>SDL_GL_SetAttribute</TT
|
|
></A
|
|
> and finally, if you wish to use double buffering you must specify it as a GL attribute, <I
|
|
CLASS="EMPHASIS"
|
|
>not</I
|
|
> by passing the <TT
|
|
CLASS="LITERAL"
|
|
>SDL_DOUBLEBUF</TT
|
|
> flag to <TT
|
|
CLASS="FUNCTION"
|
|
>SDL_SetVideoMode</TT
|
|
>.</P
|
|
><DIV
|
|
CLASS="EXAMPLE"
|
|
><A
|
|
NAME="AEN114"
|
|
></A
|
|
><P
|
|
><B
|
|
>Example 2-7. Initializing SDL with OpenGL</B
|
|
></P
|
|
><PRE
|
|
CLASS="PROGRAMLISTING"
|
|
> /* Information about the current video settings. */
|
|
const SDL_VideoInfo* info = NULL;
|
|
/* Dimensions of our window. */
|
|
int width = 0;
|
|
int height = 0;
|
|
/* Color depth in bits of our window. */
|
|
int bpp = 0;
|
|
/* Flags we will pass into SDL_SetVideoMode. */
|
|
int flags = 0;
|
|
|
|
/* First, initialize SDL's video subsystem. */
|
|
if( SDL_Init( SDL_INIT_VIDEO ) < 0 ) {
|
|
/* Failed, exit. */
|
|
fprintf( stderr, "Video initialization failed: %s\n",
|
|
SDL_GetError( ) );
|
|
quit_tutorial( 1 );
|
|
}
|
|
|
|
/* Let's get some video information. */
|
|
info = SDL_GetVideoInfo( );
|
|
|
|
if( !info ) {
|
|
/* This should probably never happen. */
|
|
fprintf( stderr, "Video query failed: %s\n",
|
|
SDL_GetError( ) );
|
|
quit_tutorial( 1 );
|
|
}
|
|
|
|
/*
|
|
* 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 | SDL_FULLSCREEN;
|
|
|
|
/*
|
|
* Set the video mode
|
|
*/
|
|
if( SDL_SetVideoMode( width, height, bpp, flags ) == 0 ) {
|
|
/*
|
|
* This could happen for a variety of reasons,
|
|
* including DISPLAY not being set, the specified
|
|
* resolution not being available, etc.
|
|
*/
|
|
fprintf( stderr, "Video mode set failed: %s\n",
|
|
SDL_GetError( ) );
|
|
quit_tutorial( 1 );
|
|
}</PRE
|
|
></DIV
|
|
></DIV
|
|
><DIV
|
|
CLASS="SECT2"
|
|
><H2
|
|
CLASS="SECT2"
|
|
><A
|
|
NAME="AEN117"
|
|
>Drawing</A
|
|
></H2
|
|
><P
|
|
>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
|
|
<A
|
|
HREF="sdlglswapbuffers.html"
|
|
><TT
|
|
CLASS="FUNCTION"
|
|
>SDL_GL_SwapBuffers()</TT
|
|
></A
|
|
>
|
|
to swap the buffers and update the display. To request double-buffering
|
|
with OpenGL, use
|
|
<A
|
|
HREF="sdlglsetattribute.html"
|
|
><TT
|
|
CLASS="FUNCTION"
|
|
>SDL_GL_SetAttribute</TT
|
|
></A
|
|
>
|
|
with <TT
|
|
CLASS="LITERAL"
|
|
>SDL_GL_DOUBLEBUFFER</TT
|
|
>, and use
|
|
<A
|
|
HREF="sdlglgetattribute.html"
|
|
><TT
|
|
CLASS="FUNCTION"
|
|
>SDL_GL_GetAttribute</TT
|
|
></A
|
|
>
|
|
to see if you actually got it.</P
|
|
><P
|
|
>A full example code listing is now presented below.</P
|
|
><DIV
|
|
CLASS="EXAMPLE"
|
|
><A
|
|
NAME="AEN128"
|
|
></A
|
|
><P
|
|
><B
|
|
>Example 2-8. SDL and OpenGL</B
|
|
></P
|
|
><PRE
|
|
CLASS="PROGRAMLISTING"
|
|
>/*
|
|
* SDL OpenGL Tutorial.
|
|
* (c) Michael Vance, 2000
|
|
* briareos@lokigames.com
|
|
*
|
|
* Distributed under terms of the LGPL.
|
|
*/
|
|
|
|
#include <SDL/SDL.h>
|
|
#include <GL/gl.h>
|
|
#include <GL/glu.h>
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
static GLboolean should_rotate = GL_TRUE;
|
|
|
|
static void quit_tutorial( int code )
|
|
{
|
|
/*
|
|
* Quit SDL so we can release the fullscreen
|
|
* mode and restore the previous video settings,
|
|
* etc.
|
|
*/
|
|
SDL_Quit( );
|
|
|
|
/* Exit program. */
|
|
exit( code );
|
|
}
|
|
|
|
static void handle_key_down( SDL_keysym* keysym )
|
|
{
|
|
|
|
/*
|
|
* We're only interested if 'Esc' has
|
|
* been presssed.
|
|
*
|
|
* EXERCISE:
|
|
* Handle the arrow keys and have that change the
|
|
* viewing position/angle.
|
|
*/
|
|
switch( keysym->sym ) {
|
|
case SDLK_ESCAPE:
|
|
quit_tutorial( 0 );
|
|
break;
|
|
case SDLK_SPACE:
|
|
should_rotate = !should_rotate;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
static void process_events( void )
|
|
{
|
|
/* Our SDL event placeholder. */
|
|
SDL_Event event;
|
|
|
|
/* Grab all the events off the queue. */
|
|
while( SDL_PollEvent( &event ) ) {
|
|
|
|
switch( event.type ) {
|
|
case SDL_KEYDOWN:
|
|
/* Handle key presses. */
|
|
handle_key_down( &event.key.keysym );
|
|
break;
|
|
case SDL_QUIT:
|
|
/* Handle quit requests (like Ctrl-c). */
|
|
quit_tutorial( 0 );
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
static void draw_screen( void )
|
|
{
|
|
/* Our angle of rotation. */
|
|
static float angle = 0.0f;
|
|
|
|
/*
|
|
* 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 ;).
|
|
*/
|
|
static GLfloat v0[] = { -1.0f, -1.0f, 1.0f };
|
|
static GLfloat v1[] = { 1.0f, -1.0f, 1.0f };
|
|
static GLfloat v2[] = { 1.0f, 1.0f, 1.0f };
|
|
static GLfloat v3[] = { -1.0f, 1.0f, 1.0f };
|
|
static GLfloat v4[] = { -1.0f, -1.0f, -1.0f };
|
|
static GLfloat v5[] = { 1.0f, -1.0f, -1.0f };
|
|
static GLfloat v6[] = { 1.0f, 1.0f, -1.0f };
|
|
static GLfloat v7[] = { -1.0f, 1.0f, -1.0f };
|
|
static GLubyte red[] = { 255, 0, 0, 255 };
|
|
static GLubyte green[] = { 0, 255, 0, 255 };
|
|
static GLubyte blue[] = { 0, 0, 255, 255 };
|
|
static GLubyte white[] = { 255, 255, 255, 255 };
|
|
static GLubyte yellow[] = { 0, 255, 255, 255 };
|
|
static GLubyte black[] = { 0, 0, 0, 255 };
|
|
static GLubyte orange[] = { 255, 255, 0, 255 };
|
|
static GLubyte purple[] = { 255, 0, 255, 0 };
|
|
|
|
/* Clear the color and depth buffers. */
|
|
glClear( GL_COLOR_BUFFER_BIT | 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 ) {
|
|
|
|
if( ++angle > 360.0f ) {
|
|
angle = 0.0f;
|
|
}
|
|
|
|
}
|
|
|
|
/* 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( );
|
|
}
|
|
|
|
static void setup_opengl( int width, int height )
|
|
{
|
|
float ratio = (float) width / (float) 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 );
|
|
}
|
|
|
|
int main( int argc, char* argv[] )
|
|
{
|
|
/* Information about the current video settings. */
|
|
const SDL_VideoInfo* info = NULL;
|
|
/* Dimensions of our window. */
|
|
int width = 0;
|
|
int height = 0;
|
|
/* Color depth in bits of our window. */
|
|
int bpp = 0;
|
|
/* Flags we will pass into SDL_SetVideoMode. */
|
|
int flags = 0;
|
|
|
|
/* First, initialize SDL's video subsystem. */
|
|
if( SDL_Init( SDL_INIT_VIDEO ) < 0 ) {
|
|
/* Failed, exit. */
|
|
fprintf( stderr, "Video initialization failed: %s\n",
|
|
SDL_GetError( ) );
|
|
quit_tutorial( 1 );
|
|
}
|
|
|
|
/* Let's get some video information. */
|
|
info = SDL_GetVideoInfo( );
|
|
|
|
if( !info ) {
|
|
/* This should probably never happen. */
|
|
fprintf( stderr, "Video query failed: %s\n",
|
|
SDL_GetError( ) );
|
|
quit_tutorial( 1 );
|
|
}
|
|
|
|
/*
|
|
* 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 | SDL_FULLSCREEN;
|
|
|
|
/*
|
|
* Set the video mode
|
|
*/
|
|
if( SDL_SetVideoMode( width, height, bpp, flags ) == 0 ) {
|
|
/*
|
|
* This could happen for a variety of reasons,
|
|
* including DISPLAY not being set, the specified
|
|
* resolution not being available, etc.
|
|
*/
|
|
fprintf( stderr, "Video mode set failed: %s\n",
|
|
SDL_GetError( ) );
|
|
quit_tutorial( 1 );
|
|
}
|
|
|
|
/*
|
|
* 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( 1 ) {
|
|
/* Process incoming events. */
|
|
process_events( );
|
|
/* Draw the screen. */
|
|
draw_screen( );
|
|
}
|
|
|
|
/*
|
|
* EXERCISE:
|
|
* Record timings using SDL_GetTicks() and
|
|
* and print out frames per second at program
|
|
* end.
|
|
*/
|
|
|
|
/* Never reached. */
|
|
return 0;
|
|
}</PRE
|
|
></DIV
|
|
></DIV
|
|
></DIV
|
|
><DIV
|
|
CLASS="NAVFOOTER"
|
|
><HR
|
|
ALIGN="LEFT"
|
|
WIDTH="100%"><TABLE
|
|
WIDTH="100%"
|
|
BORDER="0"
|
|
CELLPADDING="0"
|
|
CELLSPACING="0"
|
|
><TR
|
|
><TD
|
|
WIDTH="33%"
|
|
ALIGN="left"
|
|
VALIGN="top"
|
|
><A
|
|
HREF="guidevideo.html"
|
|
>Prev</A
|
|
></TD
|
|
><TD
|
|
WIDTH="34%"
|
|
ALIGN="center"
|
|
VALIGN="top"
|
|
><A
|
|
HREF="index.html"
|
|
>Home</A
|
|
></TD
|
|
><TD
|
|
WIDTH="33%"
|
|
ALIGN="right"
|
|
VALIGN="top"
|
|
><A
|
|
HREF="guideinput.html"
|
|
>Next</A
|
|
></TD
|
|
></TR
|
|
><TR
|
|
><TD
|
|
WIDTH="33%"
|
|
ALIGN="left"
|
|
VALIGN="top"
|
|
>Graphics and Video</TD
|
|
><TD
|
|
WIDTH="34%"
|
|
ALIGN="center"
|
|
VALIGN="top"
|
|
><A
|
|
HREF="guidevideo.html"
|
|
>Up</A
|
|
></TD
|
|
><TD
|
|
WIDTH="33%"
|
|
ALIGN="right"
|
|
VALIGN="top"
|
|
>Input handling</TD
|
|
></TR
|
|
></TABLE
|
|
></DIV
|
|
></BODY
|
|
></HTML
|
|
> |