e14743d1 |
1 | <HTML |
2 | ><HEAD |
3 | ><TITLE |
4 | >Using OpenGL With SDL</TITLE |
5 | ><META |
6 | NAME="GENERATOR" |
7 | CONTENT="Modular DocBook HTML Stylesheet Version 1.76b+ |
8 | "><LINK |
9 | REL="HOME" |
10 | TITLE="SDL Library Documentation" |
11 | HREF="index.html"><LINK |
12 | REL="UP" |
13 | TITLE="Graphics and Video" |
14 | HREF="guidevideo.html"><LINK |
15 | REL="PREVIOUS" |
16 | TITLE="Graphics and Video" |
17 | HREF="guidevideo.html"><LINK |
18 | REL="NEXT" |
19 | TITLE="Input handling" |
20 | HREF="guideinput.html"></HEAD |
21 | ><BODY |
22 | CLASS="SECT1" |
23 | BGCOLOR="#FFF8DC" |
24 | TEXT="#000000" |
25 | LINK="#0000ee" |
26 | VLINK="#551a8b" |
27 | ALINK="#ff0000" |
28 | ><DIV |
29 | CLASS="NAVHEADER" |
30 | ><TABLE |
31 | SUMMARY="Header navigation table" |
32 | WIDTH="100%" |
33 | BORDER="0" |
34 | CELLPADDING="0" |
35 | CELLSPACING="0" |
36 | ><TR |
37 | ><TH |
38 | COLSPAN="3" |
39 | ALIGN="center" |
40 | >SDL Library Documentation</TH |
41 | ></TR |
42 | ><TR |
43 | ><TD |
44 | WIDTH="10%" |
45 | ALIGN="left" |
46 | VALIGN="bottom" |
47 | ><A |
48 | HREF="guidevideo.html" |
49 | ACCESSKEY="P" |
50 | >Prev</A |
51 | ></TD |
52 | ><TD |
53 | WIDTH="80%" |
54 | ALIGN="center" |
55 | VALIGN="bottom" |
56 | >Chapter 2. Graphics and Video</TD |
57 | ><TD |
58 | WIDTH="10%" |
59 | ALIGN="right" |
60 | VALIGN="bottom" |
61 | ><A |
62 | HREF="guideinput.html" |
63 | ACCESSKEY="N" |
64 | >Next</A |
65 | ></TD |
66 | ></TR |
67 | ></TABLE |
68 | ><HR |
69 | ALIGN="LEFT" |
70 | WIDTH="100%"></DIV |
71 | ><DIV |
72 | CLASS="SECT1" |
73 | ><H1 |
74 | CLASS="SECT1" |
75 | ><A |
76 | NAME="GUIDEVIDEOOPENGL" |
77 | ></A |
78 | >Using OpenGL With SDL</H1 |
79 | ><P |
80 | >SDL has the ability to create and use OpenGL contexts on several platforms(Linux/X11, Win32, BeOS, MacOS Classic/Toolbox, Mac OS 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 |
81 | ><DIV |
82 | CLASS="SECT2" |
83 | ><H2 |
84 | CLASS="SECT2" |
85 | ><A |
86 | NAME="AEN103" |
87 | ></A |
88 | >Initialisation</H2 |
89 | ><P |
90 | >Initialising SDL to use OpenGL is not very different to initialising SDL normally. There are three differences; you must pass <TT |
91 | CLASS="LITERAL" |
92 | >SDL_OPENGL</TT |
93 | > to <A |
94 | HREF="sdlsetvideomode.html" |
95 | ><TT |
96 | CLASS="FUNCTION" |
97 | >SDL_SetVideoMode</TT |
98 | ></A |
99 | >, you must specify several GL attributes (depth buffer size, framebuffer sizes) using <A |
100 | HREF="sdlglsetattribute.html" |
101 | ><TT |
102 | CLASS="FUNCTION" |
103 | >SDL_GL_SetAttribute</TT |
104 | ></A |
105 | > and finally, if you wish to use double buffering you must specify it as a GL attribute, <SPAN |
106 | CLASS="emphasis" |
107 | ><I |
108 | CLASS="EMPHASIS" |
109 | >not</I |
110 | ></SPAN |
111 | > by passing the <TT |
112 | CLASS="LITERAL" |
113 | >SDL_DOUBLEBUF</TT |
114 | > flag to <TT |
115 | CLASS="FUNCTION" |
116 | >SDL_SetVideoMode</TT |
117 | >.</P |
118 | ><DIV |
119 | CLASS="EXAMPLE" |
120 | ><A |
121 | NAME="AEN114" |
122 | ></A |
123 | ><P |
124 | ><B |
125 | >Example 2-7. Initializing SDL with OpenGL</B |
126 | ></P |
127 | ><PRE |
128 | CLASS="PROGRAMLISTING" |
129 | > /* Information about the current video settings. */ |
130 | const SDL_VideoInfo* info = NULL; |
131 | /* Dimensions of our window. */ |
132 | int width = 0; |
133 | int height = 0; |
134 | /* Color depth in bits of our window. */ |
135 | int bpp = 0; |
136 | /* Flags we will pass into SDL_SetVideoMode. */ |
137 | int flags = 0; |
138 | |
139 | /* First, initialize SDL's video subsystem. */ |
140 | if( SDL_Init( SDL_INIT_VIDEO ) < 0 ) { |
141 | /* Failed, exit. */ |
142 | fprintf( stderr, "Video initialization failed: %s\n", |
143 | SDL_GetError( ) ); |
144 | quit_tutorial( 1 ); |
145 | } |
146 | |
147 | /* Let's get some video information. */ |
148 | info = SDL_GetVideoInfo( ); |
149 | |
150 | if( !info ) { |
151 | /* This should probably never happen. */ |
152 | fprintf( stderr, "Video query failed: %s\n", |
153 | SDL_GetError( ) ); |
154 | quit_tutorial( 1 ); |
155 | } |
156 | |
157 | /* |
158 | * Set our width/height to 640/480 (you would |
159 | * of course let the user decide this in a normal |
160 | * app). We get the bpp we will request from |
161 | * the display. On X11, VidMode can't change |
162 | * resolution, so this is probably being overly |
163 | * safe. Under Win32, ChangeDisplaySettings |
164 | * can change the bpp. |
165 | */ |
166 | width = 640; |
167 | height = 480; |
168 | bpp = info->vfmt->BitsPerPixel; |
169 | |
170 | /* |
171 | * Now, we want to setup our requested |
172 | * window attributes for our OpenGL window. |
173 | * We want *at least* 5 bits of red, green |
174 | * and blue. We also want at least a 16-bit |
175 | * depth buffer. |
176 | * |
177 | * The last thing we do is request a double |
178 | * buffered window. '1' turns on double |
179 | * buffering, '0' turns it off. |
180 | * |
181 | * Note that we do not use SDL_DOUBLEBUF in |
182 | * the flags to SDL_SetVideoMode. That does |
183 | * not affect the GL attribute state, only |
184 | * the standard 2D blitting setup. |
185 | */ |
186 | SDL_GL_SetAttribute( SDL_GL_RED_SIZE, 5 ); |
187 | SDL_GL_SetAttribute( SDL_GL_GREEN_SIZE, 5 ); |
188 | SDL_GL_SetAttribute( SDL_GL_BLUE_SIZE, 5 ); |
189 | SDL_GL_SetAttribute( SDL_GL_DEPTH_SIZE, 16 ); |
190 | SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 ); |
191 | |
192 | /* |
193 | * We want to request that SDL provide us |
194 | * with an OpenGL window, in a fullscreen |
195 | * video mode. |
196 | * |
197 | * EXERCISE: |
198 | * Make starting windowed an option, and |
199 | * handle the resize events properly with |
200 | * glViewport. |
201 | */ |
202 | flags = SDL_OPENGL | SDL_FULLSCREEN; |
203 | |
204 | /* |
205 | * Set the video mode |
206 | */ |
207 | if( SDL_SetVideoMode( width, height, bpp, flags ) == 0 ) { |
208 | /* |
209 | * This could happen for a variety of reasons, |
210 | * including DISPLAY not being set, the specified |
211 | * resolution not being available, etc. |
212 | */ |
213 | fprintf( stderr, "Video mode set failed: %s\n", |
214 | SDL_GetError( ) ); |
215 | quit_tutorial( 1 ); |
216 | }</PRE |
217 | ></DIV |
218 | ></DIV |
219 | ><DIV |
220 | CLASS="SECT2" |
221 | ><H2 |
222 | CLASS="SECT2" |
223 | ><A |
224 | NAME="AEN117" |
225 | ></A |
226 | >Drawing</H2 |
227 | ><P |
228 | >Apart from initialisation, using OpenGL within SDL is the same as using OpenGL |
229 | with any other API, e.g. GLUT. You still use all the same function calls and |
230 | data types. However if you are using a double-buffered display, then you must |
231 | use |
232 | <A |
233 | HREF="sdlglswapbuffers.html" |
234 | ><TT |
235 | CLASS="FUNCTION" |
236 | >SDL_GL_SwapBuffers()</TT |
237 | ></A |
238 | > |
239 | to swap the buffers and update the display. To request double-buffering |
240 | with OpenGL, use |
241 | <A |
242 | HREF="sdlglsetattribute.html" |
243 | ><TT |
244 | CLASS="FUNCTION" |
245 | >SDL_GL_SetAttribute</TT |
246 | ></A |
247 | > |
248 | with <TT |
249 | CLASS="LITERAL" |
250 | >SDL_GL_DOUBLEBUFFER</TT |
251 | >, and use |
252 | <A |
253 | HREF="sdlglgetattribute.html" |
254 | ><TT |
255 | CLASS="FUNCTION" |
256 | >SDL_GL_GetAttribute</TT |
257 | ></A |
258 | > |
259 | to see if you actually got it.</P |
260 | ><P |
261 | >A full example code listing is now presented below.</P |
262 | ><DIV |
263 | CLASS="EXAMPLE" |
264 | ><A |
265 | NAME="AEN128" |
266 | ></A |
267 | ><P |
268 | ><B |
269 | >Example 2-8. SDL and OpenGL</B |
270 | ></P |
271 | ><PRE |
272 | CLASS="PROGRAMLISTING" |
273 | >/* |
274 | * SDL OpenGL Tutorial. |
275 | * (c) Michael Vance, 2000 |
276 | * briareos@lokigames.com |
277 | * |
278 | * Distributed under terms of the LGPL. |
279 | */ |
280 | |
281 | #include <SDL/SDL.h> |
282 | #include <GL/gl.h> |
283 | #include <GL/glu.h> |
284 | |
285 | #include <stdio.h> |
286 | #include <stdlib.h> |
287 | |
288 | static GLboolean should_rotate = GL_TRUE; |
289 | |
290 | static void quit_tutorial( int code ) |
291 | { |
292 | /* |
293 | * Quit SDL so we can release the fullscreen |
294 | * mode and restore the previous video settings, |
295 | * etc. |
296 | */ |
297 | SDL_Quit( ); |
298 | |
299 | /* Exit program. */ |
300 | exit( code ); |
301 | } |
302 | |
303 | static void handle_key_down( SDL_keysym* keysym ) |
304 | { |
305 | |
306 | /* |
307 | * We're only interested if 'Esc' has |
308 | * been presssed. |
309 | * |
310 | * EXERCISE: |
311 | * Handle the arrow keys and have that change the |
312 | * viewing position/angle. |
313 | */ |
314 | switch( keysym->sym ) { |
315 | case SDLK_ESCAPE: |
316 | quit_tutorial( 0 ); |
317 | break; |
318 | case SDLK_SPACE: |
319 | should_rotate = !should_rotate; |
320 | break; |
321 | default: |
322 | break; |
323 | } |
324 | |
325 | } |
326 | |
327 | static void process_events( void ) |
328 | { |
329 | /* Our SDL event placeholder. */ |
330 | SDL_Event event; |
331 | |
332 | /* Grab all the events off the queue. */ |
333 | while( SDL_PollEvent( &event ) ) { |
334 | |
335 | switch( event.type ) { |
336 | case SDL_KEYDOWN: |
337 | /* Handle key presses. */ |
338 | handle_key_down( &event.key.keysym ); |
339 | break; |
340 | case SDL_QUIT: |
341 | /* Handle quit requests (like Ctrl-c). */ |
342 | quit_tutorial( 0 ); |
343 | break; |
344 | } |
345 | |
346 | } |
347 | |
348 | } |
349 | |
350 | static void draw_screen( void ) |
351 | { |
352 | /* Our angle of rotation. */ |
353 | static float angle = 0.0f; |
354 | |
355 | /* |
356 | * EXERCISE: |
357 | * Replace this awful mess with vertex |
358 | * arrays and a call to glDrawElements. |
359 | * |
360 | * EXERCISE: |
361 | * After completing the above, change |
362 | * it to use compiled vertex arrays. |
363 | * |
364 | * EXERCISE: |
365 | * Verify my windings are correct here ;). |
366 | */ |
367 | static GLfloat v0[] = { -1.0f, -1.0f, 1.0f }; |
368 | static GLfloat v1[] = { 1.0f, -1.0f, 1.0f }; |
369 | static GLfloat v2[] = { 1.0f, 1.0f, 1.0f }; |
370 | static GLfloat v3[] = { -1.0f, 1.0f, 1.0f }; |
371 | static GLfloat v4[] = { -1.0f, -1.0f, -1.0f }; |
372 | static GLfloat v5[] = { 1.0f, -1.0f, -1.0f }; |
373 | static GLfloat v6[] = { 1.0f, 1.0f, -1.0f }; |
374 | static GLfloat v7[] = { -1.0f, 1.0f, -1.0f }; |
375 | static GLubyte red[] = { 255, 0, 0, 255 }; |
376 | static GLubyte green[] = { 0, 255, 0, 255 }; |
377 | static GLubyte blue[] = { 0, 0, 255, 255 }; |
378 | static GLubyte white[] = { 255, 255, 255, 255 }; |
379 | static GLubyte yellow[] = { 0, 255, 255, 255 }; |
380 | static GLubyte black[] = { 0, 0, 0, 255 }; |
381 | static GLubyte orange[] = { 255, 255, 0, 255 }; |
382 | static GLubyte purple[] = { 255, 0, 255, 0 }; |
383 | |
384 | /* Clear the color and depth buffers. */ |
385 | glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); |
386 | |
387 | /* We don't want to modify the projection matrix. */ |
388 | glMatrixMode( GL_MODELVIEW ); |
389 | glLoadIdentity( ); |
390 | |
391 | /* Move down the z-axis. */ |
392 | glTranslatef( 0.0, 0.0, -5.0 ); |
393 | |
394 | /* Rotate. */ |
395 | glRotatef( angle, 0.0, 1.0, 0.0 ); |
396 | |
397 | if( should_rotate ) { |
398 | |
399 | if( ++angle > 360.0f ) { |
400 | angle = 0.0f; |
401 | } |
402 | |
403 | } |
404 | |
405 | /* Send our triangle data to the pipeline. */ |
406 | glBegin( GL_TRIANGLES ); |
407 | |
408 | glColor4ubv( red ); |
409 | glVertex3fv( v0 ); |
410 | glColor4ubv( green ); |
411 | glVertex3fv( v1 ); |
412 | glColor4ubv( blue ); |
413 | glVertex3fv( v2 ); |
414 | |
415 | glColor4ubv( red ); |
416 | glVertex3fv( v0 ); |
417 | glColor4ubv( blue ); |
418 | glVertex3fv( v2 ); |
419 | glColor4ubv( white ); |
420 | glVertex3fv( v3 ); |
421 | |
422 | glColor4ubv( green ); |
423 | glVertex3fv( v1 ); |
424 | glColor4ubv( black ); |
425 | glVertex3fv( v5 ); |
426 | glColor4ubv( orange ); |
427 | glVertex3fv( v6 ); |
428 | |
429 | glColor4ubv( green ); |
430 | glVertex3fv( v1 ); |
431 | glColor4ubv( orange ); |
432 | glVertex3fv( v6 ); |
433 | glColor4ubv( blue ); |
434 | glVertex3fv( v2 ); |
435 | |
436 | glColor4ubv( black ); |
437 | glVertex3fv( v5 ); |
438 | glColor4ubv( yellow ); |
439 | glVertex3fv( v4 ); |
440 | glColor4ubv( purple ); |
441 | glVertex3fv( v7 ); |
442 | |
443 | glColor4ubv( black ); |
444 | glVertex3fv( v5 ); |
445 | glColor4ubv( purple ); |
446 | glVertex3fv( v7 ); |
447 | glColor4ubv( orange ); |
448 | glVertex3fv( v6 ); |
449 | |
450 | glColor4ubv( yellow ); |
451 | glVertex3fv( v4 ); |
452 | glColor4ubv( red ); |
453 | glVertex3fv( v0 ); |
454 | glColor4ubv( white ); |
455 | glVertex3fv( v3 ); |
456 | |
457 | glColor4ubv( yellow ); |
458 | glVertex3fv( v4 ); |
459 | glColor4ubv( white ); |
460 | glVertex3fv( v3 ); |
461 | glColor4ubv( purple ); |
462 | glVertex3fv( v7 ); |
463 | |
464 | glColor4ubv( white ); |
465 | glVertex3fv( v3 ); |
466 | glColor4ubv( blue ); |
467 | glVertex3fv( v2 ); |
468 | glColor4ubv( orange ); |
469 | glVertex3fv( v6 ); |
470 | |
471 | glColor4ubv( white ); |
472 | glVertex3fv( v3 ); |
473 | glColor4ubv( orange ); |
474 | glVertex3fv( v6 ); |
475 | glColor4ubv( purple ); |
476 | glVertex3fv( v7 ); |
477 | |
478 | glColor4ubv( green ); |
479 | glVertex3fv( v1 ); |
480 | glColor4ubv( red ); |
481 | glVertex3fv( v0 ); |
482 | glColor4ubv( yellow ); |
483 | glVertex3fv( v4 ); |
484 | |
485 | glColor4ubv( green ); |
486 | glVertex3fv( v1 ); |
487 | glColor4ubv( yellow ); |
488 | glVertex3fv( v4 ); |
489 | glColor4ubv( black ); |
490 | glVertex3fv( v5 ); |
491 | |
492 | glEnd( ); |
493 | |
494 | /* |
495 | * EXERCISE: |
496 | * Draw text telling the user that 'Spc' |
497 | * pauses the rotation and 'Esc' quits. |
498 | * Do it using vetors and textured quads. |
499 | */ |
500 | |
501 | /* |
502 | * Swap the buffers. This this tells the driver to |
503 | * render the next frame from the contents of the |
504 | * back-buffer, and to set all rendering operations |
505 | * to occur on what was the front-buffer. |
506 | * |
507 | * Double buffering prevents nasty visual tearing |
508 | * from the application drawing on areas of the |
509 | * screen that are being updated at the same time. |
510 | */ |
511 | SDL_GL_SwapBuffers( ); |
512 | } |
513 | |
514 | static void setup_opengl( int width, int height ) |
515 | { |
516 | float ratio = (float) width / (float) height; |
517 | |
518 | /* Our shading model--Gouraud (smooth). */ |
519 | glShadeModel( GL_SMOOTH ); |
520 | |
521 | /* Culling. */ |
522 | glCullFace( GL_BACK ); |
523 | glFrontFace( GL_CCW ); |
524 | glEnable( GL_CULL_FACE ); |
525 | |
526 | /* Set the clear color. */ |
527 | glClearColor( 0, 0, 0, 0 ); |
528 | |
529 | /* Setup our viewport. */ |
530 | glViewport( 0, 0, width, height ); |
531 | |
532 | /* |
533 | * Change to the projection matrix and set |
534 | * our viewing volume. |
535 | */ |
536 | glMatrixMode( GL_PROJECTION ); |
537 | glLoadIdentity( ); |
538 | /* |
539 | * EXERCISE: |
540 | * Replace this with a call to glFrustum. |
541 | */ |
542 | gluPerspective( 60.0, ratio, 1.0, 1024.0 ); |
543 | } |
544 | |
545 | int main( int argc, char* argv[] ) |
546 | { |
547 | /* Information about the current video settings. */ |
548 | const SDL_VideoInfo* info = NULL; |
549 | /* Dimensions of our window. */ |
550 | int width = 0; |
551 | int height = 0; |
552 | /* Color depth in bits of our window. */ |
553 | int bpp = 0; |
554 | /* Flags we will pass into SDL_SetVideoMode. */ |
555 | int flags = 0; |
556 | |
557 | /* First, initialize SDL's video subsystem. */ |
558 | if( SDL_Init( SDL_INIT_VIDEO ) < 0 ) { |
559 | /* Failed, exit. */ |
560 | fprintf( stderr, "Video initialization failed: %s\n", |
561 | SDL_GetError( ) ); |
562 | quit_tutorial( 1 ); |
563 | } |
564 | |
565 | /* Let's get some video information. */ |
566 | info = SDL_GetVideoInfo( ); |
567 | |
568 | if( !info ) { |
569 | /* This should probably never happen. */ |
570 | fprintf( stderr, "Video query failed: %s\n", |
571 | SDL_GetError( ) ); |
572 | quit_tutorial( 1 ); |
573 | } |
574 | |
575 | /* |
576 | * Set our width/height to 640/480 (you would |
577 | * of course let the user decide this in a normal |
578 | * app). We get the bpp we will request from |
579 | * the display. On X11, VidMode can't change |
580 | * resolution, so this is probably being overly |
581 | * safe. Under Win32, ChangeDisplaySettings |
582 | * can change the bpp. |
583 | */ |
584 | width = 640; |
585 | height = 480; |
586 | bpp = info->vfmt->BitsPerPixel; |
587 | |
588 | /* |
589 | * Now, we want to setup our requested |
590 | * window attributes for our OpenGL window. |
591 | * We want *at least* 5 bits of red, green |
592 | * and blue. We also want at least a 16-bit |
593 | * depth buffer. |
594 | * |
595 | * The last thing we do is request a double |
596 | * buffered window. '1' turns on double |
597 | * buffering, '0' turns it off. |
598 | * |
599 | * Note that we do not use SDL_DOUBLEBUF in |
600 | * the flags to SDL_SetVideoMode. That does |
601 | * not affect the GL attribute state, only |
602 | * the standard 2D blitting setup. |
603 | */ |
604 | SDL_GL_SetAttribute( SDL_GL_RED_SIZE, 5 ); |
605 | SDL_GL_SetAttribute( SDL_GL_GREEN_SIZE, 5 ); |
606 | SDL_GL_SetAttribute( SDL_GL_BLUE_SIZE, 5 ); |
607 | SDL_GL_SetAttribute( SDL_GL_DEPTH_SIZE, 16 ); |
608 | SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 ); |
609 | |
610 | /* |
611 | * We want to request that SDL provide us |
612 | * with an OpenGL window, in a fullscreen |
613 | * video mode. |
614 | * |
615 | * EXERCISE: |
616 | * Make starting windowed an option, and |
617 | * handle the resize events properly with |
618 | * glViewport. |
619 | */ |
620 | flags = SDL_OPENGL | SDL_FULLSCREEN; |
621 | |
622 | /* |
623 | * Set the video mode |
624 | */ |
625 | if( SDL_SetVideoMode( width, height, bpp, flags ) == 0 ) { |
626 | /* |
627 | * This could happen for a variety of reasons, |
628 | * including DISPLAY not being set, the specified |
629 | * resolution not being available, etc. |
630 | */ |
631 | fprintf( stderr, "Video mode set failed: %s\n", |
632 | SDL_GetError( ) ); |
633 | quit_tutorial( 1 ); |
634 | } |
635 | |
636 | /* |
637 | * At this point, we should have a properly setup |
638 | * double-buffered window for use with OpenGL. |
639 | */ |
640 | setup_opengl( width, height ); |
641 | |
642 | /* |
643 | * Now we want to begin our normal app process-- |
644 | * an event loop with a lot of redrawing. |
645 | */ |
646 | while( 1 ) { |
647 | /* Process incoming events. */ |
648 | process_events( ); |
649 | /* Draw the screen. */ |
650 | draw_screen( ); |
651 | } |
652 | |
653 | /* |
654 | * EXERCISE: |
655 | * Record timings using SDL_GetTicks() and |
656 | * and print out frames per second at program |
657 | * end. |
658 | */ |
659 | |
660 | /* Never reached. */ |
661 | return 0; |
662 | }</PRE |
663 | ></DIV |
664 | ></DIV |
665 | ></DIV |
666 | ><DIV |
667 | CLASS="NAVFOOTER" |
668 | ><HR |
669 | ALIGN="LEFT" |
670 | WIDTH="100%"><TABLE |
671 | SUMMARY="Footer navigation table" |
672 | WIDTH="100%" |
673 | BORDER="0" |
674 | CELLPADDING="0" |
675 | CELLSPACING="0" |
676 | ><TR |
677 | ><TD |
678 | WIDTH="33%" |
679 | ALIGN="left" |
680 | VALIGN="top" |
681 | ><A |
682 | HREF="guidevideo.html" |
683 | ACCESSKEY="P" |
684 | >Prev</A |
685 | ></TD |
686 | ><TD |
687 | WIDTH="34%" |
688 | ALIGN="center" |
689 | VALIGN="top" |
690 | ><A |
691 | HREF="index.html" |
692 | ACCESSKEY="H" |
693 | >Home</A |
694 | ></TD |
695 | ><TD |
696 | WIDTH="33%" |
697 | ALIGN="right" |
698 | VALIGN="top" |
699 | ><A |
700 | HREF="guideinput.html" |
701 | ACCESSKEY="N" |
702 | >Next</A |
703 | ></TD |
704 | ></TR |
705 | ><TR |
706 | ><TD |
707 | WIDTH="33%" |
708 | ALIGN="left" |
709 | VALIGN="top" |
710 | >Graphics and Video</TD |
711 | ><TD |
712 | WIDTH="34%" |
713 | ALIGN="center" |
714 | VALIGN="top" |
715 | ><A |
716 | HREF="guidevideo.html" |
717 | ACCESSKEY="U" |
718 | >Up</A |
719 | ></TD |
720 | ><TD |
721 | WIDTH="33%" |
722 | ALIGN="right" |
723 | VALIGN="top" |
724 | >Input handling</TD |
725 | ></TR |
726 | ></TABLE |
727 | ></DIV |
728 | ></BODY |
729 | ></HTML |
730 | > |