/*
 * Test glReadPixels speed
 * Brian Paul
 * 9 April 2004
 *
 * Compile:
 * gcc readrate.c -L/usr/X11R6/lib -lglut -lGLU -lGL -lX11 -o readrate
 */

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

/* Hack, to test drawing instead of reading */
#define DRAW 0

#define MAX_WIDTH 1280
#define MAX_HEIGHT 1024

#define NUM_WIDTHS 4
#define NUM_HEIGHTS 4
static const GLint Widths[] = {256, 512, 1024, 1280};
static const GLint Heights[] = {4, 32, 256, 512, 768, 1024};
static int WidthIndex = 1, HeightIndex = 3;
static GLubyte *Buffer = NULL;
static GLboolean Benchmark = GL_TRUE;

#define NUM_PBO 2

static GLuint PBObjects[4];

static GLboolean HavePBO = GL_FALSE;


struct format_type {
   const char *Name;
   GLuint Bytes;
   GLenum Format;
   GLenum Type;
};

static struct format_type Formats[] = {
   { "GL_RGB, GLubyte", 3, GL_RGB, GL_UNSIGNED_BYTE },
   { "GL_BGR, GLubyte", 3, GL_BGR, GL_UNSIGNED_BYTE },
   { "GL_RGBA, GLubyte", 4, GL_RGBA, GL_UNSIGNED_BYTE },
   { "GL_BGRA, GLubyte", 4, GL_BGRA, GL_UNSIGNED_BYTE },
   { "GL_ABGR, GLubyte", 4, GL_ABGR_EXT, GL_UNSIGNED_BYTE },
   { "GL_RGBA, GLuint_8_8_8_8", 4, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8 },
   { "GL_BGRA, GLuint_8_8_8_8", 4, GL_BGRA_EXT, GL_UNSIGNED_INT_8_8_8_8 },
   { "GL_BGRA, GLuint_8_8_8_8_rev", 4, GL_BGRA_EXT, GL_UNSIGNED_INT_8_8_8_8_REV },
#ifdef GL_EXT_packed_depth_stencil
   { "GL_DEPTH_STENCIL_EXT, GLuint24+8", 4, GL_DEPTH_STENCIL_EXT, GL_UNSIGNED_INT_24_8_EXT },
#endif
   { "GL_DEPTH_COMPONENT, GLfloat", 4, GL_DEPTH_COMPONENT, GL_FLOAT },
   { "GL_DEPTH_COMPONENT, GLuint", 4, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT }
};

#define NUM_FORMATS (sizeof(Formats) / sizeof(struct format_type))


static void
PrintString(const char *s)
{
   while (*s) {
      glutBitmapCharacter(GLUT_BITMAP_8_BY_13, (int) *s);
      s++;
   }
}


static void
MeasureFormat(struct format_type *fmt, GLint width, GLint height, GLuint pbo)
{
   double t0 = glutGet(GLUT_ELAPSED_TIME) * 0.001;
   double t1;
   int j;

   for (j = 0; ; j++) {

      glBegin(GL_POINTS);
      glVertex2f(1,1);
      glEnd();

#if DRAW
      glWindowPos2iARB(0,0);
      glDrawPixels(width, height,
                   fmt->Format, fmt->Type, Buffer);
      glFinish();
#else
      if (pbo) {
         glBindBufferARB(GL_PIXEL_PACK_BUFFER_EXT, PBObjects[j % NUM_PBO]);
         glReadPixels(0, 0, width, height,
                      fmt->Format, fmt->Type, 0);
      }
      else {
         glReadPixels(0, 0, width, height,
                      fmt->Format, fmt->Type, Buffer);
      }
#endif

      t1 = glutGet(GLUT_ELAPSED_TIME) * 0.001;
      if (t1 - t0 > 2.0) {
         GLdouble rate = width * height / (1024.0 * 1024.0) * j / (t1 - t0);
#if DRAW
         printf("%-32s  %.2f draws/sec %.2f MPixels/sec  %.2f MBytes/sec\n",
                fmt->Name, j / (t1-t0), rate, rate * fmt->Bytes);
#else
         printf("%-32s  %.2f reads/sec %.2f MPixels/sec  %.2f MBytes/sec\n",
                fmt->Name, j / (t1-t0), rate, rate * fmt->Bytes);
#endif
         break;
      }

      if (j == 0) {
         /* check for error */
         GLenum err = glGetError();
         if (err) {
            printf("GL Error 0x%x for %s\n", err, fmt->Name);
            return;
         }
      }
   }
}



static void
Draw(void)
{
   char str[1000];
   int width = Widths[WidthIndex];
   int height = Heights[HeightIndex];
   int y = MAX_HEIGHT - 50;

   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

   glWindowPos2iARB(10, y);
   sprintf(str, "ReadPixels size: %d x %d", width, height);
   PrintString(str);
   y -= 14;

   glWindowPos2iARB(10, y);
   PrintString("Press up/down/left/right to change image size.");
   y -= 14;

   glWindowPos2iARB(10, y);
   PrintString("Press 'b' to run benchmark test.");
   y -= 14;

   if (Benchmark) {
      glWindowPos2iARB(10, y);
      PrintString("Testing...");
   }

   glutSwapBuffers();

   if (Benchmark) {
      GLuint i, pbo;
#if DRAW
      printf("Draw size: Width=%d Height=%d\n", width, height);
#else
      printf("Read size: Width=%d Height=%d\n", width, height);
#endif
      for (pbo = 0; pbo <= HavePBO; pbo++) {
         printf("Pixel Buffer Object: %d\n", pbo);

         if (pbo == 0) {
            glBindBufferARB(GL_PIXEL_PACK_BUFFER_EXT, 0);
         }

         for (i = 0; i < NUM_FORMATS; i++) {
            MeasureFormat(Formats + i, width, height, pbo);
         }
      }

      Benchmark = GL_FALSE;

      /* redraw window text */
      glutPostRedisplay();
   }

}


static void
Reshape(int width, int height)
{
   glViewport(0, 0, width, height);
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   glOrtho(-1, 1, -1, 1, -1, 1);
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();
}


static void
Key(unsigned char key, int x, int y)
{
   (void) x;
   (void) y;
   switch (key) {
      case 'b':
         Benchmark = 1;
         break;
      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:
         if (HeightIndex + 1 < NUM_WIDTHS)
            HeightIndex++;
         break;
      case GLUT_KEY_DOWN:
         if (HeightIndex > 0)
            HeightIndex--;
         break;
      case GLUT_KEY_LEFT:
         if (WidthIndex > 0)
            WidthIndex--;
         break;
      case GLUT_KEY_RIGHT:
         if (WidthIndex + 1 < NUM_HEIGHTS)
            WidthIndex++;
         break;
   }
   glutPostRedisplay();
}


static void
Init(void)
{
   Buffer = malloc(MAX_WIDTH * MAX_HEIGHT * 4);
   assert(Buffer);
#if DRAW
   printf("glDrawPixels test report:\n");
#else
   printf("glReadPixels test report:\n");
#endif
   printf("GL_RENDERER: %s\n", (char *) glGetString(GL_RENDERER));
   printf("GL_VERSION: %s\n", (char *) glGetString(GL_VERSION));

   if (glutExtensionSupported("GL_ARB_pixel_buffer_object")) {
      int i;
      HavePBO = 1;
      glGenBuffersARB(NUM_PBO, PBObjects);
      for (i = 0; i < NUM_PBO; i++) {
         glBindBufferARB(GL_PIXEL_PACK_BUFFER_EXT, PBObjects[i]);
         glBufferDataARB(GL_PIXEL_PACK_BUFFER_EXT,
                         MAX_WIDTH * MAX_HEIGHT * 4, NULL, GL_STREAM_READ);
      }
   }
}


int
main(int argc, char *argv[])
{
   glutInit(&argc, argv);
   glutInitWindowPosition(0, 0);
   glutInitWindowSize(MAX_WIDTH, MAX_HEIGHT);
   glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH | GLUT_STENCIL);
   glutCreateWindow(argv[0]);
   gladLoadGL();
   glutReshapeFunc(Reshape);
   glutKeyboardFunc(Key);
   glutSpecialFunc(SpecialKey);
   glutDisplayFunc(Draw);
   Init();
   glutMainLoop();
   return 0;
}
