/*
 * Measure glTexSubImage and glCopyTexSubImage speed
 *
 * Brian Paul
 * 26 Jan 2006
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <glad/glad.h>
#include "glut_wrap.h"

static GLint WinWidth = 1024, WinHeight = 512;
static GLint TexWidth = 512, TexHeight = 512;

static GLuint TexObj = 1;

static GLenum IntFormat = GL_RGBA8;
static GLenum ReadFormat = GL_RGBA; /* for glReadPixels */

static GLboolean DrawQuad = GL_TRUE;


/**
 * draw teapot image, size TexWidth by TexHeight
 */
static void
DrawTestImage(void)
{
   GLfloat ar;

   glViewport(0, 0, TexWidth, TexHeight);
   glScissor(0, 0, TexWidth, TexHeight);
   glEnable(GL_SCISSOR_TEST);

   glClearColor(0.5, 0.5, 0.5, 0.0);
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

   ar = (float) TexWidth / TexHeight;

   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   glFrustum(-ar, ar, -1.0, 1.0, 5.0, 25.0);
   glMatrixMode(GL_MODELVIEW);

   glEnable(GL_LIGHTING);
   glEnable(GL_LIGHT0);
   glEnable(GL_DEPTH_TEST);
   glFrontFace(GL_CW);
   glPushMatrix();
   glRotatef(45, 1, 0, 0);
   glRotatef(45, 0, 1, 0);
   glutSolidTeapot(2.3);
   glPopMatrix();
   glFrontFace(GL_CCW);
   glDisable(GL_DEPTH_TEST);
   glDisable(GL_LIGHTING);

   glDisable(GL_SCISSOR_TEST);

   glViewport(0, 0, WinWidth, WinHeight);
   glFinish();
}


/**
 * Do glCopyTexSubImage2D call (update texture with framebuffer data)
 * If doSubRect is true, do the copy in four pieces instead of all at once.
 */
static void
DoCopyTex(GLboolean doSubRect)
{
   if (doSubRect) {
      /* copy in four parts */
      int w = TexWidth / 2, h = TexHeight / 2;
      int x0 = 0, y0 = 0;
      int x1 = w, y1 = h;
#if 1
      glCopyTexSubImage2D(GL_TEXTURE_2D, 0, x0, y0, x0, y0, w, h);
      glCopyTexSubImage2D(GL_TEXTURE_2D, 0, x1, y0, x1, y0, w, h);
      glCopyTexSubImage2D(GL_TEXTURE_2D, 0, x0, y1, x0, y1, w, h);
      glCopyTexSubImage2D(GL_TEXTURE_2D, 0, x1, y1, x1, y1, w, h);
#else
      /* scramble */
      glCopyTexSubImage2D(GL_TEXTURE_2D, 0, x0, y0, x1, y1, w, h);
      glCopyTexSubImage2D(GL_TEXTURE_2D, 0, x1, y0, x0, y1, w, h);
      glCopyTexSubImage2D(GL_TEXTURE_2D, 0, x0, y1, x1, y0, w, h);
      glCopyTexSubImage2D(GL_TEXTURE_2D, 0, x1, y1, x0, y0, w, h);
#endif
   }
   else {
      glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, TexWidth, TexHeight);
   }
}


/**
 * Do glTexSubImage2D (update texture w/ user data)
 * If doSubRect, do update in four pieces, else all at once.
 */
static void
SubTex(GLboolean doSubRect, const GLubyte *image)
{
   if (doSubRect) {
      /* four pieces */
      int w = TexWidth / 2, h = TexHeight / 2;
      int x0 = 0, y0 = 0;
      int x1 = w, y1 = h;
      glPixelStorei(GL_UNPACK_ROW_LENGTH, TexWidth);
      glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

      glPixelStorei(GL_UNPACK_SKIP_ROWS, y0);
      glPixelStorei(GL_UNPACK_SKIP_PIXELS, x0);
      glTexSubImage2D(GL_TEXTURE_2D, 0, x0, y0, w, h,
                      ReadFormat, GL_UNSIGNED_BYTE, image);

      glPixelStorei(GL_UNPACK_SKIP_ROWS, y0);
      glPixelStorei(GL_UNPACK_SKIP_PIXELS, x1);
      glTexSubImage2D(GL_TEXTURE_2D, 0, x1, y0, w, h,
                      ReadFormat, GL_UNSIGNED_BYTE, image);

      glPixelStorei(GL_UNPACK_SKIP_ROWS, y1);
      glPixelStorei(GL_UNPACK_SKIP_PIXELS, x0);
      glTexSubImage2D(GL_TEXTURE_2D, 0, x0, y1, w, h,
                      ReadFormat, GL_UNSIGNED_BYTE, image);

      glPixelStorei(GL_UNPACK_SKIP_ROWS, y1);
      glPixelStorei(GL_UNPACK_SKIP_PIXELS, x1);
      glTexSubImage2D(GL_TEXTURE_2D, 0, x1, y1, w, h,
                      ReadFormat, GL_UNSIGNED_BYTE, image);
   }
   else {
      /* all at once */
      glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, TexWidth, TexHeight,
                      ReadFormat, GL_UNSIGNED_BYTE, image);
   }
}


/**
 * Measure gl[Copy]TexSubImage rate.
 * This actually also includes time to render a quad and SwapBuffers.
 */
static void
RunTest(GLboolean copyTex, GLboolean doSubRect)
{
   double t0, t1;
   int iters = 0;
   float copyRate, mbRate;
   float rot = 0.0;
   int bpp, r, g, b, a;
   int w, h;
   GLubyte *image = NULL;

   glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_RED_SIZE, &r);
   glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_GREEN_SIZE, &g);
   glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_BLUE_SIZE, &b);
   glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_ALPHA_SIZE, &a);
   bpp = (r + g + b + a) / 8;

   if (!copyTex) {
      /* read image from frame buffer */
      image = (GLubyte *) malloc(TexWidth * TexHeight * bpp);
      glPixelStorei(GL_PACK_ALIGNMENT, 1);
      glReadPixels(0, 0, TexWidth, TexHeight,
                   ReadFormat, GL_UNSIGNED_BYTE, image);
   }

   glEnable(GL_TEXTURE_2D);
   glViewport(WinWidth / 2, 0, WinWidth / 2, WinHeight);

   t0 = glutGet(GLUT_ELAPSED_TIME) / 1000.0;

   do {
      if (copyTex)
         /* Framebuffer -> Texture */
         DoCopyTex(doSubRect);
      else {
         /* Main Mem -> Texture */
         SubTex(doSubRect, image);
      }

      /* draw textured quad */
      if (DrawQuad) {
         glPushMatrix();
            glRotatef(rot, 0, 0, 1);
            glTranslatef(1, 0, 0);
            glBegin(GL_POLYGON);
               glTexCoord2f(0, 0);  glVertex2f(-1, -1);
               glTexCoord2f(1, 0);  glVertex2f( 1, -1);
               glTexCoord2f(1, 1);  glVertex2f( 1,  1);
               glTexCoord2f(0, 1);  glVertex2f(-1,  1);
            glEnd();
         glPopMatrix();
      }

      iters++;
      rot += 2.0;

      t1 = glutGet(GLUT_ELAPSED_TIME) / 1000.0;
      if (DrawQuad) {
         glutSwapBuffers();
      }
   } while (t1 - t0 < 5.0);

   glDisable(GL_TEXTURE_2D);
   if (image)
      free(image);

   if (doSubRect) {
      w = TexWidth / 2;
      h = TexHeight / 2;
      iters *= 4;
   }
   else {
      w = TexWidth;
      h = TexHeight;
   }

   copyRate = iters / (t1 - t0);
   mbRate = w * h * bpp * copyRate / (1024 * 1024);

   if (copyTex)
      printf("glCopyTexSubImage: %d x %d, %d Bpp:\n", w, h, bpp);
   else
      printf("glTexSubImage: %d x %d, %d Bpp:\n", w, h, bpp);
   printf("   %d calls in %.2f = %.2f calls/sec, %.2f MB/s\n",
          iters, t1-t0, copyRate, mbRate);
}


static void
Draw(void)
{
   glClearColor(0.2, 0.2, 0.8, 0);
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

   DrawTestImage();
   if (!DrawQuad) {
      glutSwapBuffers();
   }

   RunTest(GL_FALSE, GL_FALSE);
   RunTest(GL_FALSE, GL_TRUE);
   RunTest(GL_TRUE, GL_FALSE);
   RunTest(GL_TRUE, GL_TRUE);

   glutSwapBuffers();

   printf("exiting\n");
   exit(0);
}


static void
Reshape(int width, int height)
{
   glViewport(0, 0, width, height);
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   glFrustum(-1.0, 1.0, -1.0, 1.0, 5.0, 25.0);
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();
   glTranslatef(0.0, 0.0, -15.0);
}


static void
Key(unsigned char key, int x, int y)
{
   (void) x;
   (void) y;
   switch (key) {
      case 27:
         exit(0);
         break;
   }
   glutPostRedisplay();
}


static void
SpecialKey(int key, int x, int y)
{
   (void) x;
   (void) y;
   switch (key) {
      case GLUT_KEY_UP:
         break;
      case GLUT_KEY_DOWN:
         break;
      case GLUT_KEY_LEFT:
         break;
      case GLUT_KEY_RIGHT:
         break;
   }
   glutPostRedisplay();
}


static void
Init(void)
{
   /* create initial, empty teximage */
   glBindTexture(GL_TEXTURE_2D, TexObj);
   glTexImage2D(GL_TEXTURE_2D, 0, IntFormat, TexWidth, TexHeight, 0,
                GL_RGBA, GL_UNSIGNED_BYTE, NULL);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
   glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
}



static void
ParseArgs(int argc, char *argv[])
{
   int i;
   for (i = 1; i < argc; i++) {
      if (strcmp(argv[i], "-nodraw") == 0)
         DrawQuad = GL_FALSE;
   }
}


int
main(int argc, char *argv[])
{
   GLint mode = GLUT_RGB | GLUT_ALPHA | GLUT_DOUBLE | GLUT_DEPTH;
   glutInit(&argc, argv);

   ParseArgs(argc, argv);

   glutInitWindowPosition(0, 0);
   glutInitWindowSize(WinWidth, WinHeight);
   glutInitDisplayMode(mode);
   glutCreateWindow(argv[0]);
   gladLoadGL();
   glutReshapeFunc(Reshape);
   glutKeyboardFunc(Key);
   glutSpecialFunc(SpecialKey);
   glutDisplayFunc(Draw);

   printf("GL_RENDERER: %s\n", (char *) glGetString(GL_RENDERER));
   Init();

   glutMainLoop();
   return 0;
}
