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