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