/*
 * This program is under the GNU GPL.
 * Use at your own risk.
 *
 * written by David Bucciarelli (tech.hmw@plus.it)
 *            Humanware s.r.l.
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <GL/glut.h>

typedef struct
{
   char *name;
   char *unit;
   void (*init) (void);
   int (*run) (int, int);
   int type;
   int numsize;
   int size[10];
}
benchmark;

static int frontbuffer = 1;

/***************************************************************************/

static void
init_test01(void)
{
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   gluOrtho2D(-0.5, 639.5, -0.5, 479.5);
   glMatrixMode(GL_MODELVIEW);

   glShadeModel(GL_FLAT);
   glDisable(GL_DEPTH_TEST);

   glClearColor(0.0, 0.1, 1.0, 0.0);
   glClear(GL_COLOR_BUFFER_BIT);
   glColor3f(1.0, 0.0, 0.0);
}

static int
test01(int size, int num)
{
   int x, y;

   glBegin(GL_POINTS);
   for (y = 0; y < num; y++)
      for (x = 0; x < 480; x++)
	 glVertex2i(x, x);
   glEnd();

   return 480 * num;
}

/***************************************************************************/

static void
init_test02(void)
{
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   gluOrtho2D(-0.5, 639.5, -0.5, 479.5);
   glMatrixMode(GL_MODELVIEW);

   glShadeModel(GL_SMOOTH);
   glDisable(GL_DEPTH_TEST);

   glClearColor(0.0, 0.1, 1.0, 0.0);
   glClear(GL_COLOR_BUFFER_BIT);
}

static int
test02(int size, int num)
{
   int x, y;

   glBegin(GL_LINES);
   for (y = 0; y < num; y++)
      for (x = 0; x < size; x++) {
	 glColor3f(0.0, 1.0, y / (float) num);
	 glVertex2i(0, size - 1);
	 glColor3f(1.0, 0.0, x / (float) size);
	 glVertex2i(x, x);
      }
   glEnd();

   return num * size;
}

/***************************************************************************/

static void
init_test03(void)
{
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   glOrtho(-0.5, 639.5, -0.5, 479.5, 1.0, -1000.0 * 480.0);
   glMatrixMode(GL_MODELVIEW);

   glShadeModel(GL_SMOOTH);
   glEnable(GL_DEPTH_TEST);

   glClearColor(0.0, 0.1, 1.0, 0.0);
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}

static int
test03(int size, int num)
{
   int x, y, z;

   glBegin(GL_TRIANGLES);
   for (y = 0; y < num; y++)
      for (x = 0; x < size; x += 5) {
	 z = num * size - (y * size + x);
	 glColor3f(0.0, 1.0, 0.0);
	 glVertex3i(0, x, z);

	 glColor3f(1.0, 0.0, x / (float) size);
	 glVertex3i(size - 1 - x, 0, z);

	 glColor3f(1.0, x / (float) size, 0.0);
	 glVertex3i(x, size - 1 - x, z);
      }
   glEnd();

   return size * num / 5;
}

/***************************************************************************/

static void
init_test04(void)
{
   int x, y;
   GLubyte tex[128 * 128 * 3];
   GLenum gluerr;

   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   glOrtho(-0.5, 639.5, -0.5, 479.5, 1.0, -1000.0 * 480.0);

   glMatrixMode(GL_MODELVIEW);

   glShadeModel(GL_SMOOTH);
   glEnable(GL_DEPTH_TEST);

   glEnable(GL_BLEND);
   glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

   for (y = 0; y < 128; y++)
      for (x = 0; x < 128; x++) {
	 tex[(x + y * 128) * 3 + 0] = ((x % (128 / 4)) < (128 / 8)) ? 255 : 0;
	 tex[(x + y * 128) * 3 + 1] = ((y % (128 / 4)) < (128 / 8)) ? 255 : 0;
	 tex[(x + y * 128) * 3 + 2] = x;
      }

   glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
   if ((gluerr = gluBuild2DMipmaps(GL_TEXTURE_2D, 3, 128, 128, GL_RGB,
				   GL_UNSIGNED_BYTE, (GLvoid *) (&tex[0])))) {
      fprintf(stderr, "GLULib%s\n", gluErrorString(gluerr));
      exit(-1);
   }

   glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
   glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

   glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
		   GL_LINEAR_MIPMAP_NEAREST);
   glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

   glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
   glEnable(GL_TEXTURE_2D);

   glClearColor(0.0, 0.1, 1.0, 0.0);
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}

static int
test04(int size, int num)
{
   int x, y, z;

   glBegin(GL_TRIANGLES);
   for (y = 0; y < num; y++)
      for (x = 0; x < size; x += 5) {
	 z = num * size - (y * size + x);
	 glTexCoord2f(1.0, 1.0);
	 glColor3f(1.0, 0.0, 0.0);
	 glVertex3i(0, x, z);

	 glTexCoord2f(0.0, 1.0);
	 glColor3f(0.0, 1.0, 0.0);
	 glVertex3i(size - 1 - x, 0, z);

	 glTexCoord2f(1.0, 0.0);
	 glColor3f(0.0, 0.0, 1.0);
	 glVertex3i(x, size - 1 - x, z);
      }
   glEnd();

   return num * size / 5;
}

/***************************************************************************/

static void
init_test05(void)
{
   int x, y;
   GLubyte tex[128 * 128 * 3];
   GLenum gluerr;

   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   glOrtho(-0.5, 639.5, -0.5, 479.5, -1.0, 1.0);

   glMatrixMode(GL_MODELVIEW);

   glShadeModel(GL_SMOOTH);
   glEnable(GL_DEPTH_TEST);

   glEnable(GL_BLEND);
   glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

   for (y = 0; y < 128; y++)
      for (x = 0; x < 128; x++) {
	 tex[(x + y * 128) * 3 + 0] = ((x % (128 / 4)) < (128 / 8)) ? 255 : 0;
	 tex[(x + y * 128) * 3 + 1] = ((y % (128 / 4)) < (128 / 8)) ? 255 : 0;
	 tex[(x + y * 128) * 3 + 2] = x;
      }

   glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
   if ((gluerr = gluBuild2DMipmaps(GL_TEXTURE_2D, 3, 128, 128, GL_RGB,
				   GL_UNSIGNED_BYTE, (GLvoid *) (&tex[0])))) {
      fprintf(stderr, "GLULib%s\n", gluErrorString(gluerr));
      exit(-1);
   }

   glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
   glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

   glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
		   GL_LINEAR_MIPMAP_NEAREST);
   glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

   glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
   glEnable(GL_TEXTURE_2D);

   glDepthFunc(GL_ALWAYS);

   glClearColor(0.0, 0.1, 1.0, 0.0);
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}

static int
test05(int size, int num)
{
   int y;
   float v0[3], v1[3], v2[3], v3[3];
   float cv0[3], cv1[3], cv2[3], cv3[3];
   float tv0[3], tv1[3], tv2[3], tv3[3];

   v0[0] = 320 - size / 2;
   v0[1] = 240 - size / 2;
   v0[2] = 0.0;
   v1[0] = 320 + size / 2;
   v1[1] = 240 - size / 2;
   v1[2] = 0.0;
   v2[0] = 320 - size / 2;
   v2[1] = 240 + size / 2;
   v2[2] = 0.0;
   v3[0] = 320 + size / 2;
   v3[1] = 240 + size / 2;
   v3[2] = 0.0;
   cv0[0] = 1.0;
   cv0[1] = 0.0;
   cv0[2] = 0.0;
   cv1[0] = 1.0;
   cv1[1] = 1.0;
   cv1[2] = 0.0;
   cv2[0] = 1.0;
   cv2[1] = 0.0;
   cv2[2] = 1.0;
   cv3[0] = 1.0;
   cv3[1] = 1.0;
   cv3[2] = 1.0;
   tv0[0] = 0.0;
   tv0[1] = 0.0;
   tv0[2] = 0.0;
   tv1[0] = 1.0;
   tv1[1] = 0.0;
   tv1[2] = 0.0;
   tv2[0] = 0.0;
   tv2[1] = 1.0;
   tv2[2] = 0.0;
   tv3[0] = 1.0;
   tv3[1] = 1.0;
   tv3[2] = 0.0;

   glBegin(GL_TRIANGLE_STRIP);
   for (y = 0; y < num; y++) {
      glColor3fv(cv0);
      glTexCoord2fv(tv0);
      glVertex3fv(v0);

      glColor3fv(cv1);
      glTexCoord2fv(tv1);
      glVertex3fv(v1);

      glColor3fv(cv2);
      glTexCoord2fv(tv2);
      glVertex3fv(v2);

      glColor3fv(cv3);
      glTexCoord2fv(tv3);
      glVertex3fv(v3);
   }
   glEnd();

   return 4 * num - 2;
}

/***************************************************************************/

static void
init_test06(void)
{
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   gluOrtho2D(-0.5, 639.5, -0.5, 479.5);
   glMatrixMode(GL_MODELVIEW);

   glShadeModel(GL_SMOOTH);
   glEnable(GL_DEPTH_TEST);

   glClearColor(0.0, 0.1, 1.0, 0.0);
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}

static int
test06(int size, int num)
{
   int y;

   for (y = 0; y < num; y++) {
      glClearColor(y / (float) num, 0.1, 1.0, 0.0);
      glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
   }

   return num;
}

/***************************************************************************/

#define BMARKS_TIME 5.0

#define NUM_BMARKS 6

/* 554 ~= sqrt(640*480) */

static benchmark bmarks[NUM_BMARKS] = {
   {"Simple Points", "Pnts", init_test01, test01, 0, 0,
    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}},
   {"Smooth Lines", "Lins", init_test02, test02, 1, 5,
    {480, 250, 100, 50, 25, 0, 0, 0, 0, 0}},
   {"ZSmooth Triangles", "Tris", init_test03, test03, 1, 5,
    {480, 250, 100, 50, 25, 0, 0, 0, 0, 0}},
   {"ZSmooth Tex Blend Triangles", "Tris", init_test04, test04, 1, 5,
    {480, 250, 100, 50, 25, 0, 0, 0, 0, 0}},
   {"ZSmooth Tex Blend TMesh Triangles", "Tris", init_test05, test05, 2, 8,
    {400, 250, 100, 50, 25, 10, 5, 2, 0, 0}},
   {"Color/Depth Buffer Clears", "Clrs", init_test06, test06, 3, 0,
    {554, 0, 0, 0, 0, 0, 0, 0, 0, 0}}
};

/***************************************************************************/

static void
dotest0param(benchmark * bmark)
{
   float stime, etime, dtime, tottime, maxtime, mintime;
   int num, numelem, calibnum, j;

   glPushAttrib(GL_ALL_ATTRIB_BITS);
   bmark->init();

   stime = glutGet(GLUT_ELAPSED_TIME);

   dtime = 0.0;
   calibnum = 0;
   while (dtime < 2.0) {
      bmark->run(0, 1);
      glFinish();
      etime = glutGet(GLUT_ELAPSED_TIME);
      dtime = (etime - stime) / 1000.0;
      calibnum++;
   }
   glPopAttrib();

   fprintf(stderr, "Elapsed time for the calibration test (%d): %f\n",
	   calibnum, dtime);

   num = (int) ((BMARKS_TIME / dtime) * calibnum);

   if (num < 1)
      num = 1;

   fprintf(stderr, "Selected number of benchmark iterations: %d\n", num);

   mintime = HUGE_VAL;
   maxtime = -HUGE_VAL;

   for (tottime = 0.0, j = 0; j < 5; j++) {
      glPushAttrib(GL_ALL_ATTRIB_BITS);
      bmark->init();

      stime = glutGet(GLUT_ELAPSED_TIME);
      numelem = bmark->run(0, num);
      glFinish();
      etime = glutGet(GLUT_ELAPSED_TIME);

      glPopAttrib();

      dtime = (etime - stime) / 1000.0;
      tottime += dtime;

      fprintf(stderr, "Elapsed time for run %d: %f\n", j, dtime);

      if (dtime < mintime)
	 mintime = dtime;
      if (dtime > maxtime)
	 maxtime = dtime;
   }

   tottime -= mintime + maxtime;

   fprintf(stdout, "%s\n%f %s/sec", bmark->name, numelem / (tottime / 3.0),
	   bmark->unit);

   if (bmark->type == 3)
      fprintf(stdout, ", MPixel Fill/sec: %f\n\n",
	      (numelem * bmark->size[0] * (float) bmark->size[0]) /
	      (1000000.0 * tottime / 3.0));
   else
      fprintf(stdout, "\n\n");
}

/***************************************************************************/

static void
dotest1param(benchmark * bmark)
{
   float stime, etime, dtime, tottime, maxtime, mintime;
   int num, numelem, calibnum, j, k;

   fprintf(stdout, "%s\n", bmark->name);

   for (j = 0; j < bmark->numsize; j++) {
      fprintf(stderr, "Current size: %d\n", bmark->size[j]);

      glPushAttrib(GL_ALL_ATTRIB_BITS);
      bmark->init();

      stime = glutGet(GLUT_ELAPSED_TIME);

      dtime = 0.0;
      calibnum = 0;
      while (dtime < 2.0) {
	 bmark->run(bmark->size[j], 1);
	 glFinish();
	 etime = glutGet(GLUT_ELAPSED_TIME);
	 dtime = (etime - stime) / 1000.0;
	 calibnum++;
      }
      glPopAttrib();

      fprintf(stderr, "Elapsed time for the calibration test (%d): %f\n",
	      calibnum, dtime);

      num = (int) ((BMARKS_TIME / dtime) * calibnum);

      if (num < 1)
	 num = 1;

      fprintf(stderr, "Selected number of benchmark iterations: %d\n", num);

      mintime = HUGE_VAL;
      maxtime = -HUGE_VAL;

      for (numelem = 1, tottime = 0.0, k = 0; k < 5; k++) {
	 glPushAttrib(GL_ALL_ATTRIB_BITS);
	 bmark->init();

	 stime = glutGet(GLUT_ELAPSED_TIME);
	 numelem = bmark->run(bmark->size[j], num);
	 glFinish();
	 etime = glutGet(GLUT_ELAPSED_TIME);

	 glPopAttrib();

	 dtime = (etime - stime) / 1000.0;
	 tottime += dtime;

	 fprintf(stderr, "Elapsed time for run %d: %f\n", k, dtime);

	 if (dtime < mintime)
	    mintime = dtime;
	 if (dtime > maxtime)
	    maxtime = dtime;
      }

      tottime -= mintime + maxtime;

      fprintf(stdout, "SIZE=%03d => %f %s/sec", bmark->size[j],
	      numelem / (tottime / 3.0), bmark->unit);
      if (bmark->type == 2)
	 fprintf(stdout, ", MPixel Fill/sec: %f\n",
		 (numelem * bmark->size[j] * bmark->size[j] / 2) /
		 (1000000.0 * tottime / 3.0));
      else
	 fprintf(stdout, "\n");
   }

   fprintf(stdout, "\n\n");
}

/***************************************************************************/

static void
display(void)
{
   int i;

   if (frontbuffer)
      glDrawBuffer(GL_FRONT);
   else
      glDrawBuffer(GL_BACK);

   for (i = 0; i < NUM_BMARKS; i++) {
      fprintf(stderr, "Benchmark: %d\n", i);

      switch (bmarks[i].type) {
      case 0:
      case 3:
	 dotest0param(&bmarks[i]);
	 break;
      case 1:
      case 2:
	 dotest1param(&bmarks[i]);
	 break;
      }
   }

   exit(0);
}

int
main(int ac, char **av)
{
   fprintf(stderr, "GLTest v1.0\nWritten by David Bucciarelli\n");

   if (ac == 2)
      frontbuffer = 0;

   glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
   glutInitWindowPosition(0, 0);
   glutInitWindowSize(640, 480);
   glutCreateWindow("OpenGL/Mesa Performances");
   glutDisplayFunc(display);
   glutMainLoop();

   return 0;
}