Clone of mesa.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

shtest.c 16KB

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