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
42typedef enum
43{
44   SPHERE,
45   CUBE,
46   NUM_SHAPES
47} shape;
48
49
50static char *FragShaderFile = NULL;
51static char *VertShaderFile = NULL;
52static char *ConfigFile = NULL;
53
54/* program/shader objects */
55static GLuint fragShader;
56static GLuint vertShader;
57static GLuint Program;
58
59
60#define MAX_UNIFORMS 100
61static struct uniform_info Uniforms[MAX_UNIFORMS];
62static GLuint NumUniforms = 0;
63
64
65#define MAX_ATTRIBS 100
66static struct attrib_info Attribs[MAX_ATTRIBS];
67static GLuint NumAttribs = 0;
68
69
70/**
71 * Config file info.
72 */
73struct 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
86static GLint win = 0;
87static GLboolean Anim = GL_FALSE;
88static GLfloat TexRot = 0.0;
89static GLfloat xRot = 0.0f, yRot = 0.0f, zRot = 0.0f;
90static shape Object = SPHERE;
91
92
93static float
94RandomFloat(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 */
103static void
104RandomUniformValues(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
129static void
130Idle(void)
131{
132   yRot += 2.0;
133   if (yRot > 360.0)
134      yRot -= 360.0;
135   glutPostRedisplay();
136}
137
138
139
140static void
141SquareVertex(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 */
166static void
167Square(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
188static void
189Cube(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
235static void
236Sphere(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
251static void
252Redisplay(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
279static void
280Reshape(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
292static void
293CleanUp(void)
294{
295   glDeleteShader(fragShader);
296   glDeleteShader(vertShader);
297   glDeleteProgram(Program);
298   glutDestroyWindow(win);
299}
300
301
302static void
303Key(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
340static void
341SpecialKey(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
366static void
367InitUniforms(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
387static void
388LoadTexture(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
446static GLenum
447TypeFromName(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 */
483static void
484ReadConfigFile(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
580static void
581Init(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
640static void
641Keys(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
652static void
653Usage(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
663static void
664ParseOptions(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
691int
692main(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