blob: fa477d9eeb785ef27229cc8e94be2226477ea3e4 [file] [log] [blame]
Brian Paulae99e4c2009-08-13 12:52:13 -06001/*
2 * Simple shader test harness.
3 * Brian Paul
4 * 13 Aug 2009
5 *
6 * Usage:
7 * shtest --vs vertShaderFile --fs fragShaderFile
8 *
9 * In this case the given vertex/frag shaders are read and compiled.
10 * Random values are assigned to the uniforms.
11 *
12 * or:
13 * shtest configFile
14 *
15 * In this case a config file is read that specifies the file names
16 * of the shaders plus initial values for uniforms.
17 *
18 * Example config file:
19 *
20 * vs shader.vert
21 * fs shader.frag
22 * uniform pi 3.14159
23 * uniform v1 1.0 0.5 0.2 0.3
Brian Paul0062bd62009-08-20 14:19:01 -060024 * texture 0 2D texture0.rgb
25 * texture 1 CUBE texture1.rgb
26 * texture 2 RECT texture2.rgb
Brian Paulae99e4c2009-08-13 12:52:13 -060027 *
28 */
29
30
31#include <assert.h>
32#include <string.h>
33#include <stdio.h>
34#include <stdlib.h>
35#include <string.h>
36#include <math.h>
37#include <GL/glew.h>
38#include <GL/glu.h>
39#include <GL/glut.h>
40#include "shaderutil.h"
Brian Paul62d11322009-08-13 15:53:49 -060041#include "readtex.h"
Brian Paulae99e4c2009-08-13 12:52:13 -060042
43
44typedef enum
45{
46 SPHERE,
47 CUBE,
48 NUM_SHAPES
49} shape;
50
51
52static char *FragShaderFile = NULL;
53static char *VertShaderFile = NULL;
54static char *ConfigFile = NULL;
55
56/* program/shader objects */
57static GLuint fragShader;
58static GLuint vertShader;
59static GLuint Program;
60
61
62#define MAX_UNIFORMS 100
63static struct uniform_info Uniforms[MAX_UNIFORMS];
64static GLuint NumUniforms = 0;
65
66
67#define MAX_ATTRIBS 100
68static struct attrib_info Attribs[MAX_ATTRIBS];
69static GLuint NumAttribs = 0;
70
71
72/**
73 * Config file info.
74 */
75struct config_file
76{
77 struct name_value
78 {
79 char name[100];
80 float value[4];
81 int type;
82 } uniforms[100];
83
84 int num_uniforms;
85};
86
87
88static GLint win = 0;
89static GLboolean Anim = GL_FALSE;
90static GLfloat TexRot = 0.0;
91static GLfloat xRot = 0.0f, yRot = 0.0f, zRot = 0.0f;
92static shape Object = SPHERE;
93
94
95static float
96RandomFloat(float min, float max)
97{
98 int k = rand() % 10000;
99 float x = min + (max - min) * k / 10000.0;
100 return x;
101}
102
103
104/** Set new random values for uniforms */
105static void
106RandomUniformValues(void)
107{
108 GLuint i;
109 for (i = 0; i < NumUniforms; i++) {
110 if (Uniforms[i].type == GL_FLOAT) {
111 Uniforms[i].value[0] = RandomFloat(0.0, 1.0);
112 }
113 else {
114 Uniforms[i].value[0] = RandomFloat(-1.0, 2.0);
115 Uniforms[i].value[1] = RandomFloat(-1.0, 2.0);
116 Uniforms[i].value[2] = RandomFloat(-1.0, 2.0);
117 Uniforms[i].value[3] = RandomFloat(-1.0, 2.0);
118 }
119 }
120}
121
122
123static void
124Idle(void)
125{
126 yRot += 2.0;
127 if (yRot > 360.0)
128 yRot -= 360.0;
129 glutPostRedisplay();
130}
131
132
133
134static void
135SquareVertex(GLfloat s, GLfloat t, GLfloat size)
136{
137 GLfloat x = -size + s * 2.0 * size;
138 GLfloat y = -size + t * 2.0 * size;
Brian Paul08ecd8632009-08-13 16:02:24 -0600139 GLuint i;
140
141 glMultiTexCoord2f(GL_TEXTURE0, s, t);
142 glMultiTexCoord2f(GL_TEXTURE1, s, t);
143 glMultiTexCoord2f(GL_TEXTURE2, s, t);
144 glMultiTexCoord2f(GL_TEXTURE3, s, t);
145
146 /* assign (s,t) to the generic attributes */
147 for (i = 0; i < NumAttribs; i++) {
148 if (Attribs[i].location >= 0) {
149 glVertexAttrib2f(Attribs[i].location, s, t);
150 }
151 }
152
Brian Paulae99e4c2009-08-13 12:52:13 -0600153 glVertex2f(x, y);
154}
155
156
157/*
158 * Draw a square, specifying normal and tangent vectors.
159 */
160static void
161Square(GLfloat size)
162{
163 GLint tangentAttrib = 1;
164 glNormal3f(0, 0, 1);
165 glVertexAttrib3f(tangentAttrib, 1, 0, 0);
166 glBegin(GL_POLYGON);
Brian Paul08ecd8632009-08-13 16:02:24 -0600167#if 1
Brian Paulae99e4c2009-08-13 12:52:13 -0600168 SquareVertex(0, 0, size);
169 SquareVertex(1, 0, size);
170 SquareVertex(1, 1, size);
171 SquareVertex(0, 1, size);
172#else
173 glTexCoord2f(0, 0); glVertex2f(-size, -size);
174 glTexCoord2f(1, 0); glVertex2f( size, -size);
175 glTexCoord2f(1, 1); glVertex2f( size, size);
176 glTexCoord2f(0, 1); glVertex2f(-size, size);
177#endif
178 glEnd();
179}
180
181
182static void
183Cube(GLfloat size)
184{
185 /* +X */
186 glPushMatrix();
187 glRotatef(90, 0, 1, 0);
188 glTranslatef(0, 0, size);
189 Square(size);
190 glPopMatrix();
191
192 /* -X */
193 glPushMatrix();
194 glRotatef(-90, 0, 1, 0);
195 glTranslatef(0, 0, size);
196 Square(size);
197 glPopMatrix();
198
199 /* +Y */
200 glPushMatrix();
201 glRotatef(90, 1, 0, 0);
202 glTranslatef(0, 0, size);
203 Square(size);
204 glPopMatrix();
205
206 /* -Y */
207 glPushMatrix();
208 glRotatef(-90, 1, 0, 0);
209 glTranslatef(0, 0, size);
210 Square(size);
211 glPopMatrix();
212
213
214 /* +Z */
215 glPushMatrix();
216 glTranslatef(0, 0, size);
217 Square(size);
218 glPopMatrix();
219
220 /* -Z */
221 glPushMatrix();
222 glRotatef(180, 0, 1, 0);
223 glTranslatef(0, 0, size);
224 Square(size);
225 glPopMatrix();
226}
227
228
229static void
230Sphere(GLfloat radius, GLint slices, GLint stacks)
231{
232 static GLUquadricObj *q = NULL;
233
234 if (!q) {
235 q = gluNewQuadric();
236 gluQuadricDrawStyle(q, GLU_FILL);
237 gluQuadricNormals(q, GLU_SMOOTH);
238 gluQuadricTexture(q, GL_TRUE);
239 }
240
241 gluSphere(q, radius, slices, stacks);
242}
243
244
245static void
246Redisplay(void)
247{
248 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
249
250 glPushMatrix();
251 glRotatef(xRot, 1.0f, 0.0f, 0.0f);
252 glRotatef(yRot, 0.0f, 1.0f, 0.0f);
253 glRotatef(zRot, 0.0f, 0.0f, 1.0f);
254
255 glMatrixMode(GL_TEXTURE);
256 glLoadIdentity();
257 glRotatef(TexRot, 0.0f, 1.0f, 0.0f);
258 glMatrixMode(GL_MODELVIEW);
259
260 if (Object == SPHERE) {
Brian Paul174054c2009-08-20 10:58:05 -0600261 Sphere(2.5, 20, 10);
Brian Paulae99e4c2009-08-13 12:52:13 -0600262 }
263 else if (Object == CUBE) {
264 Cube(2.0);
265 }
266
267 glPopMatrix();
268
269 glutSwapBuffers();
270}
271
272
273static void
274Reshape(int width, int height)
275{
276 glViewport(0, 0, width, height);
277 glMatrixMode(GL_PROJECTION);
278 glLoadIdentity();
279 glFrustum(-1.0, 1.0, -1.0, 1.0, 5.0, 25.0);
280 glMatrixMode(GL_MODELVIEW);
281 glLoadIdentity();
282 glTranslatef(0.0f, 0.0f, -15.0f);
283}
284
285
286static void
287CleanUp(void)
288{
289 glDeleteShader(fragShader);
290 glDeleteShader(vertShader);
291 glDeleteProgram(Program);
292 glutDestroyWindow(win);
293}
294
295
296static void
297Key(unsigned char key, int x, int y)
298{
299 const GLfloat step = 2.0;
300 (void) x;
301 (void) y;
302
303 switch(key) {
304 case 'a':
305 Anim = !Anim;
306 if (Anim)
307 glutIdleFunc(Idle);
308 else
309 glutIdleFunc(NULL);
310 break;
311 case 'z':
312 zRot += step;
313 break;
314 case 'Z':
315 zRot -= step;
316 break;
317 case 'o':
318 Object = (Object + 1) % NUM_SHAPES;
319 break;
320 case 'r':
321 RandomUniformValues();
322 SetUniformValues(Program, Uniforms);
323 PrintUniforms(Uniforms);
324 break;
325 case 27:
326 CleanUp();
327 exit(0);
328 break;
329 }
330 glutPostRedisplay();
331}
332
333
334static void
335SpecialKey(int key, int x, int y)
336{
337 const GLfloat step = 2.0;
338
339 (void) x;
340 (void) y;
341
342 switch(key) {
343 case GLUT_KEY_UP:
344 xRot += step;
345 break;
346 case GLUT_KEY_DOWN:
347 xRot -= step;
348 break;
349 case GLUT_KEY_LEFT:
350 yRot -= step;
351 break;
352 case GLUT_KEY_RIGHT:
353 yRot += step;
354 break;
355 }
356 glutPostRedisplay();
357}
358
359
360static void
361InitUniforms(const struct config_file *conf,
362 struct uniform_info uniforms[])
363{
364 int i;
365
366 for (i = 0; i < conf->num_uniforms; i++) {
367 int j;
368 for (j = 0; uniforms[j].name; j++) {
369 if (strcmp(uniforms[j].name, conf->uniforms[i].name) == 0) {
370 uniforms[j].type = conf->uniforms[i].type;
371 uniforms[j].value[0] = conf->uniforms[i].value[0];
372 uniforms[j].value[1] = conf->uniforms[i].value[1];
373 uniforms[j].value[2] = conf->uniforms[i].value[2];
374 uniforms[j].value[3] = conf->uniforms[i].value[3];
375 }
376 }
377 }
378}
379
380
Brian Paul62d11322009-08-13 15:53:49 -0600381static void
Brian Paul0062bd62009-08-20 14:19:01 -0600382LoadTexture(GLint unit, GLenum target, const char *texFileName)
Brian Paul62d11322009-08-13 15:53:49 -0600383{
384 GLint imgWidth, imgHeight;
385 GLenum imgFormat;
386 GLubyte *image = NULL;
387 GLuint tex;
388 GLenum filter = GL_LINEAR;
Brian Paul0062bd62009-08-20 14:19:01 -0600389 GLenum objTarget;
Brian Paul62d11322009-08-13 15:53:49 -0600390
391 image = LoadRGBImage(texFileName, &imgWidth, &imgHeight, &imgFormat);
392 if (!image) {
393 printf("Couldn't read %s\n", texFileName);
394 exit(1);
395 }
396
Brian Paul0062bd62009-08-20 14:19:01 -0600397 printf("Load Texture: unit %d, target 0x%x: %s %d x %d\n",
398 unit, target, texFileName, imgWidth, imgHeight);
399
400 if (target >= GL_TEXTURE_CUBE_MAP_POSITIVE_X &&
401 target <= GL_TEXTURE_CUBE_MAP_NEGATIVE_Z) {
402 objTarget = GL_TEXTURE_CUBE_MAP;
403 }
404 else {
405 objTarget = target;
406 }
Brian Paul62d11322009-08-13 15:53:49 -0600407
408 glActiveTexture(GL_TEXTURE0 + unit);
409 glGenTextures(1, &tex);
Brian Paul0062bd62009-08-20 14:19:01 -0600410 glBindTexture(objTarget, tex);
Brian Paul62d11322009-08-13 15:53:49 -0600411
Brian Paul0062bd62009-08-20 14:19:01 -0600412 if (target == GL_TEXTURE_3D) {
413 /* depth=1 */
414 gluBuild3DMipmaps(target, 4, imgWidth, imgHeight, 1,
415 imgFormat, GL_UNSIGNED_BYTE, image);
416 }
417 else if (target == GL_TEXTURE_1D) {
418 gluBuild1DMipmaps(target, 4, imgWidth,
419 imgFormat, GL_UNSIGNED_BYTE, image);
420 }
421 else {
422 gluBuild2DMipmaps(target, 4, imgWidth, imgHeight,
423 imgFormat, GL_UNSIGNED_BYTE, image);
424 }
425
Brian Paul62d11322009-08-13 15:53:49 -0600426 free(image);
427
Brian Paul0062bd62009-08-20 14:19:01 -0600428 glTexParameteri(objTarget, GL_TEXTURE_WRAP_S, GL_REPEAT);
429 glTexParameteri(objTarget, GL_TEXTURE_WRAP_T, GL_REPEAT);
430 glTexParameteri(objTarget, GL_TEXTURE_MIN_FILTER, filter);
431 glTexParameteri(objTarget, GL_TEXTURE_MAG_FILTER, filter);
Brian Paul62d11322009-08-13 15:53:49 -0600432}
433
434
435static GLenum
436TypeFromName(const char *n)
437{
438 static const struct {
439 const char *name;
440 GLenum type;
441 } types[] = {
442 { "GL_FLOAT", GL_FLOAT },
443 { "GL_FLOAT_VEC2", GL_FLOAT_VEC2 },
444 { "GL_FLOAT_VEC3", GL_FLOAT_VEC3 },
445 { "GL_FLOAT_VEC4", GL_FLOAT_VEC4 },
446 { "GL_INT", GL_INT },
447 { "GL_INT_VEC2", GL_INT_VEC2 },
448 { "GL_INT_VEC3", GL_INT_VEC3 },
449 { "GL_INT_VEC4", GL_INT_VEC4 },
Brian Paul0062bd62009-08-20 14:19:01 -0600450 { "GL_SAMPLER_1D", GL_SAMPLER_1D },
Brian Paul62d11322009-08-13 15:53:49 -0600451 { "GL_SAMPLER_2D", GL_SAMPLER_2D },
Brian Paul0062bd62009-08-20 14:19:01 -0600452 { "GL_SAMPLER_3D", GL_SAMPLER_3D },
Brian Paul174054c2009-08-20 10:58:05 -0600453 { "GL_SAMPLER_CUBE", GL_SAMPLER_CUBE },
Brian Paul0062bd62009-08-20 14:19:01 -0600454 { "GL_SAMPLER_2D_RECT", GL_SAMPLER_2D_RECT_ARB },
Brian Paul62d11322009-08-13 15:53:49 -0600455 { NULL, 0 }
456 };
457 GLuint i;
458
459 for (i = 0; types[i].name; i++) {
460 if (strcmp(types[i].name, n) == 0)
461 return types[i].type;
462 }
463 abort();
464 return GL_NONE;
465}
466
467
468
Brian Paulae99e4c2009-08-13 12:52:13 -0600469/**
470 * Read a config file.
471 */
472static void
473ReadConfigFile(const char *filename, struct config_file *conf)
474{
475 char line[1000];
476 FILE *f;
477
478 f = fopen(filename, "r");
479 if (!f) {
480 fprintf(stderr, "Unable to open config file %s\n", filename);
481 exit(1);
482 }
483
484 conf->num_uniforms = 0;
485
486 /* ugly but functional parser */
487 while (!feof(f)) {
488 fgets(line, sizeof(line), f);
Brian Paul62d11322009-08-13 15:53:49 -0600489 if (!feof(f) && line[0]) {
Brian Paulae99e4c2009-08-13 12:52:13 -0600490 if (strncmp(line, "vs ", 3) == 0) {
491 VertShaderFile = strdup(line + 3);
492 VertShaderFile[strlen(VertShaderFile) - 1] = 0;
493 }
494 else if (strncmp(line, "fs ", 3) == 0) {
495 FragShaderFile = strdup(line + 3);
496 FragShaderFile[strlen(FragShaderFile) - 1] = 0;
497 }
Brian Paul62d11322009-08-13 15:53:49 -0600498 else if (strncmp(line, "texture ", 8) == 0) {
Brian Paul0062bd62009-08-20 14:19:01 -0600499 char target[100], texFileName[100];
Brian Paul62d11322009-08-13 15:53:49 -0600500 int unit, k;
Brian Paul0062bd62009-08-20 14:19:01 -0600501 k = sscanf(line + 8, "%d %s %s", &unit, target, texFileName);
502 assert(k == 3 || k == 8);
503 if (strcmp(target, "CUBE") == 0) {
504 char texFileNames[6][100];
505 k = sscanf(line + 8, "%d %s %s %s %s %s %s %s",
506 &unit, target,
507 texFileNames[0],
508 texFileNames[1],
509 texFileNames[2],
510 texFileNames[3],
511 texFileNames[4],
512 texFileNames[5]);
513 LoadTexture(unit, GL_TEXTURE_CUBE_MAP_POSITIVE_X, texFileNames[0]);
514 LoadTexture(unit, GL_TEXTURE_CUBE_MAP_NEGATIVE_X, texFileNames[1]);
515 LoadTexture(unit, GL_TEXTURE_CUBE_MAP_POSITIVE_Y, texFileNames[2]);
516 LoadTexture(unit, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, texFileNames[3]);
517 LoadTexture(unit, GL_TEXTURE_CUBE_MAP_POSITIVE_Z, texFileNames[4]);
518 LoadTexture(unit, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, texFileNames[5]);
519 }
520 else if (!strcmp(target, "2D")) {
521 LoadTexture(unit, GL_TEXTURE_2D, texFileName);
522 }
523 else if (!strcmp(target, "3D")) {
524 LoadTexture(unit, GL_TEXTURE_3D, texFileName);
525 }
526 else if (!strcmp(target, "RECT")) {
527 LoadTexture(unit, GL_TEXTURE_RECTANGLE_ARB, texFileName);
528 }
529 else {
530 printf("Bad texture target: %s\n", target);
531 exit(1);
532 }
Brian Paul62d11322009-08-13 15:53:49 -0600533 }
Brian Paulae99e4c2009-08-13 12:52:13 -0600534 else if (strncmp(line, "uniform ", 8) == 0) {
Brian Paul62d11322009-08-13 15:53:49 -0600535 char name[1000], typeName[100];
Brian Paulae99e4c2009-08-13 12:52:13 -0600536 int k;
Brian Paul62d11322009-08-13 15:53:49 -0600537 float v1 = 0.0F, v2 = 0.0F, v3 = 0.0F, v4 = 0.0F;
Brian Paulae99e4c2009-08-13 12:52:13 -0600538 GLenum type;
539
Brian Paul62d11322009-08-13 15:53:49 -0600540 k = sscanf(line + 8, "%s %s %f %f %f %f", name, typeName,
541 &v1, &v2, &v3, &v4);
Brian Paulae99e4c2009-08-13 12:52:13 -0600542
Brian Paul62d11322009-08-13 15:53:49 -0600543 type = TypeFromName(typeName);
Brian Paulae99e4c2009-08-13 12:52:13 -0600544
545 strcpy(conf->uniforms[conf->num_uniforms].name, name);
546 conf->uniforms[conf->num_uniforms].value[0] = v1;
547 conf->uniforms[conf->num_uniforms].value[1] = v2;
548 conf->uniforms[conf->num_uniforms].value[2] = v3;
549 conf->uniforms[conf->num_uniforms].value[3] = v4;
550 conf->uniforms[conf->num_uniforms].type = type;
551 conf->num_uniforms++;
552 }
553 else {
554 if (strlen(line) > 1) {
555 fprintf(stderr, "syntax error in: %s\n", line);
556 break;
557 }
558 }
559 }
560 }
561
562 fclose(f);
563}
564
565
566static void
567Init(void)
568{
Brian Paula215da52009-08-20 14:30:08 -0600569 GLdouble vertTime, fragTime, linkTime;
Brian Paulae99e4c2009-08-13 12:52:13 -0600570 struct config_file config;
Brian Paula215da52009-08-20 14:30:08 -0600571
Brian Paulae99e4c2009-08-13 12:52:13 -0600572 memset(&config, 0, sizeof(config));
573
574 if (ConfigFile)
575 ReadConfigFile(ConfigFile, &config);
576
577 if (!VertShaderFile) {
578 fprintf(stderr, "Error: no vertex shader\n");
579 exit(1);
580 }
581
582 if (!FragShaderFile) {
583 fprintf(stderr, "Error: no fragment shader\n");
584 exit(1);
585 }
586
587 if (!ShadersSupported())
588 exit(1);
589
590 vertShader = CompileShaderFile(GL_VERTEX_SHADER, VertShaderFile);
Brian Paula215da52009-08-20 14:30:08 -0600591 vertTime = GetShaderCompileTime();
Brian Paulae99e4c2009-08-13 12:52:13 -0600592 fragShader = CompileShaderFile(GL_FRAGMENT_SHADER, FragShaderFile);
Brian Paula215da52009-08-20 14:30:08 -0600593 fragTime = GetShaderCompileTime();
594
Brian Paulae99e4c2009-08-13 12:52:13 -0600595 Program = LinkShaders(vertShader, fragShader);
Brian Paula215da52009-08-20 14:30:08 -0600596 linkTime = GetShaderLinkTime();
597
598 printf("Time to compile vertex shader: %fs\n", vertTime);
599 printf("Time to compile fragment shader: %fs\n", fragTime);
600 printf("Time to link shaders: %fs\n", linkTime);
Brian Paulae99e4c2009-08-13 12:52:13 -0600601
602 glUseProgram(Program);
603
604 NumUniforms = GetUniforms(Program, Uniforms);
605 if (config.num_uniforms) {
606 InitUniforms(&config, Uniforms);
607 }
608 else {
609 RandomUniformValues();
610 }
611 SetUniformValues(Program, Uniforms);
612 PrintUniforms(Uniforms);
613
614 NumAttribs = GetAttribs(Program, Attribs);
615 PrintAttribs(Attribs);
616
617 //assert(glGetError() == 0);
618
619 glClearColor(0.4f, 0.4f, 0.8f, 0.0f);
620
621 glEnable(GL_DEPTH_TEST);
622
623 glColor3f(1, 0, 0);
624}
625
626
627static void
628Keys(void)
629{
630 printf("Keyboard:\n");
631 printf(" a Animation toggle\n");
632 printf(" r Randomize uniform values\n");
633 printf(" o Change object\n");
634 printf(" arrows Rotate object\n");
635 printf(" ESC Exit\n");
636}
637
638
639static void
640Usage(void)
641{
642 printf("Usage:\n");
643 printf(" shtest config.shtest\n");
644 printf(" Run w/ given config file.\n");
645 printf(" shtest --vs vertShader --fs fragShader\n");
646 printf(" Load/compile given shaders.\n");
647}
648
649
650static void
651ParseOptions(int argc, char *argv[])
652{
653 int i;
654
655 if (argc == 1) {
656 Usage();
657 exit(1);
658 }
659
660 for (i = 1; i < argc; i++) {
661 if (strcmp(argv[i], "--fs") == 0) {
662 FragShaderFile = argv[i+1];
663 i++;
664 }
665 else if (strcmp(argv[i], "--vs") == 0) {
666 VertShaderFile = argv[i+1];
667 i++;
668 }
669 else {
670 /* assume the arg is a config file */
671 ConfigFile = argv[i];
672 break;
673 }
674 }
675}
676
677
678int
679main(int argc, char *argv[])
680{
Brian Paulae99e4c2009-08-13 12:52:13 -0600681 glutInitWindowSize(400, 400);
Brian Paul174054c2009-08-20 10:58:05 -0600682 glutInit(&argc, argv);
Brian Paulae99e4c2009-08-13 12:52:13 -0600683 glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);
684 win = glutCreateWindow(argv[0]);
685 glewInit();
686 glutReshapeFunc(Reshape);
687 glutKeyboardFunc(Key);
688 glutSpecialFunc(SpecialKey);
689 glutDisplayFunc(Redisplay);
690 ParseOptions(argc, argv);
691 Init();
692 Keys();
693 glutMainLoop();
694 return 0;
695}
696