// GLTest1.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include "memcpy.h"

static const int texWidth = 1024;
static const int texHeight = 1024;
static const int texSize = 4 * texWidth * texHeight;

// scene variables
static GLfloat angle = 0.0f;

// timer variables
static LARGE_INTEGER freq, startTimeStamp, previousTimeStamp;
static GLuint ticks = 0;

// OpenGL IDs
static GLuint texBufferId = 0;
static GLuint textureId = 0;

// OpenGL extension function pointers
PFNGLGENBUFFERSPROC glGenBuffers;
PFNGLBINDBUFFERPROC glBindBuffer;
PFNGLBUFFERDATAPROC glBufferData;
PFNGLMAPBUFFERPROC glMapBuffer;
PFNGLUNMAPBUFFERPROC glUnmapBuffer;

static bool Found_GL_ARB_pixel_buffer_object;

struct GLExtensionFunc
{
   PROC *pproc;
   const char *exportedName;
};

struct GLExtension
{
   const char *name;
   const bool mandatory;
   bool& found;
   const size_t numberOfFunctions;
   GLExtensionFunc functions[ 20 ];
};

static const GLExtension soughtExtensions[] =
{
   {
      "GL_ARB_pixel_buffer_object",
      false,
      Found_GL_ARB_pixel_buffer_object,
      5,
      {
         { reinterpret_cast< PROC * >( &glGenBuffers ), "glGenBuffers" },
         { reinterpret_cast< PROC * >( &glBindBuffer ), "glBindBuffer" },
         { reinterpret_cast< PROC * >( &glBufferData ), "glBufferData" },
         { reinterpret_cast< PROC * >( &glMapBuffer ), "glMapBuffer" },
         { reinterpret_cast< PROC * >( &glUnmapBuffer ), "glUnmapBuffer" },
      }
   },
};
static const size_t numberOfSoughtExtensions = sizeof( soughtExtensions ) / sizeof( GLExtension );

// static texture data
static void * texData = reinterpret_cast< unsigned int * >( _aligned_malloc( texSize, 16 ) );

#define GLTEST1_RGBA_MAKE(r, g, b, a)   ( (unsigned int) ((((unsigned int)a & 0xff) << 24) | (((unsigned int)b & 0xff) << 16) | (((unsigned int)g & 0xff) << 8) | ((unsigned int)r & 0xff)))

bool checkExtensions()
{
   std::cerr << "OpenGL version:\t" << reinterpret_cast< const char * >( glGetString( GL_VERSION ) ) << std::endl;
   std::cerr << "OpenGL vendor:\t" << reinterpret_cast< const char * >( glGetString( GL_VENDOR ) ) << std::endl;
   const char *extensions = reinterpret_cast< const char * >( glGetString( GL_EXTENSIONS ) );
   if ( extensions == 0 )
   {
      return false;
   }
   char *token, *nextToken;
   const char *sep = " ";
   token = strtok_s( const_cast< char * >( extensions ), sep, &nextToken );
   if ( token == 0 )
   {
      return false;
   }
   std::set< std::string > extensionSet;
   while ( token != 0 )
   {
      extensionSet.insert( token );
      token = strtok_s( 0, sep, &nextToken );
   }
   std::cerr << "Extensions:" << std::endl;
   for ( std::set< std::string >::const_iterator i = extensionSet.begin(); i != extensionSet.end(); ++i )
   {
      std::cerr << "\t\t" << *i << std::endl;
   }

   // resolve extension function pointers
   std::vector< std::string > failedMandatoryExtensions, usedExtensions;
   failedMandatoryExtensions.reserve( numberOfSoughtExtensions );
   usedExtensions.reserve( numberOfSoughtExtensions );
   for ( size_t i = 0; i < numberOfSoughtExtensions; ++i )
   {
      const GLExtension& ext = soughtExtensions[ i ];
      if ( extensionSet.find( ext.name ) == extensionSet.end() )
      {
         // extension not found
         // check if mandatory
         if ( ext.mandatory )
         {
            failedMandatoryExtensions.push_back( ext.name );
         }
      }
      else
      {
         // get addresses of all ext procs
         ext.found = true;
         for ( size_t j = 0; j < ext.numberOfFunctions; ++j )
         {
            *( ext.functions[ j ].pproc ) = wglGetProcAddress( ext.functions[ j ].exportedName );
            if ( *( ext.functions[ j ].pproc ) == 0 )
            {
               // weird huh?  extension is reported but cannot find one of its related functions
               if ( ext.mandatory )
               {
                  failedMandatoryExtensions.push_back( ext.name );
               }
               ext.found = false;
               // bail out
               break;
            }
         }
         if ( ext.found )
         {
            usedExtensions.push_back( ext.name );
         }
      }
   }
   if ( !failedMandatoryExtensions.empty() )
   {
      std::cerr << "The following OpenGL extensions are mandatory for this program to run properly, but missing on your system:" << std::endl;
      for ( std::vector< std::string >::const_iterator i = failedMandatoryExtensions.begin(); i != failedMandatoryExtensions.end(); ++i )
      {
         std::cerr << "\t\t" << *i << std::endl;
      }
      return false;
   }
   if ( !usedExtensions.empty() )
   {
      std::cerr << "The following OpenGL extensions will be used by this program:" << std::endl;
      for ( std::vector< std::string >::const_iterator i = usedExtensions.begin(); i != usedExtensions.end(); ++i )
      {
         std::cerr << "\t\t" << *i << std::endl;
      }
   }
   return true;
}

void init()
{
   // one-time initialise texture data
   int k = 0;
   int * p = reinterpret_cast< int * >( texData );
   for ( int j = 0; j < texHeight; ++j )
   {
      for ( int i = 0; i < texWidth; ++i )
      {
         const int value = 128 * (((i >> 6) & 1) ^ ((j >> 6) & 1));
         p[ k++ ] = GLTEST1_RGBA_MAKE( value, value, value, 0xff );
      }
   }

   glClearColor( 0.0, 0.0, 0.0, 0.0 );
   glShadeModel( GL_FLAT );
   assert( glGetError() == 0 );

   // create texture image buffer object
   if ( Found_GL_ARB_pixel_buffer_object )
   {
      glGenBuffers( 1, &texBufferId );
      assert( glGetError() == 0 );

      // Explicitely bind the zero pixel unpack buffer object so that
      // passing NULL for the texture image data leaves the texture image
      // unspecified.
      glBindBuffer( GL_PIXEL_UNPACK_BUFFER_ARB, 0 );
      assert( glGetError() == 0 );
   }

   // create and set 2-D texture
   glGenTextures( 1, &textureId );
   assert( glGetError() == 0 );
   glBindTexture( GL_TEXTURE_2D, textureId );
   assert( glGetError() == 0 );
   glTexImage2D( GL_TEXTURE_2D, 0, 4, texWidth, texHeight, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL );
   glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
   glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
   assert( glGetError() == 0 );

   glEnable( GL_TEXTURE_2D );
   assert( glGetError() == 0 );

   QueryPerformanceFrequency( &freq );
   QueryPerformanceCounter( &startTimeStamp );
   QueryPerformanceCounter( &previousTimeStamp );
   ticks = 0;
}

void reshape( int width, int height )
{
   glViewport( 0, 0, width, height );

   glMatrixMode( GL_PROJECTION );
   glLoadIdentity();
   const float w = 1.0;
   const float h = w * height / (float)width;
   gluOrtho2D( -w, w, -h, h );

   glMatrixMode( GL_MODELVIEW );
   glLoadIdentity();

   assert( glGetError() == 0 );
}

void display()
{
   glBindTexture( GL_TEXTURE_2D, textureId );
   assert( glGetError() == 0 );

   if ( Found_GL_ARB_pixel_buffer_object )
   {
      glBindBuffer( GL_PIXEL_UNPACK_BUFFER_ARB, texBufferId );
      assert( glGetError() == 0 );

      // reset the contents of the texSize-sized buffer object
      glBufferData( GL_PIXEL_UNPACK_BUFFER_ARB, texSize, NULL, GL_STREAM_DRAW );
      assert( glGetError() == 0 );

      // map the texture image buffer into client memory
      void * pboMemory = glMapBuffer( GL_PIXEL_UNPACK_BUFFER_ARB, GL_WRITE_ONLY );
      assert( glGetError() == 0 );

      //LARGE_INTEGER li1,li2;
      //::QueryPerformanceCounter( &li1 );
      memcpySSE( pboMemory, texData, texSize );
      //_aligned_memcpy( pboMemory, texData, texSize );
      //::QueryPerformanceCounter( &li2 );
      //std::cerr << 1000.0 * (li2.QuadPart - li1.QuadPart) / (double) freq.QuadPart << std::endl;

      glUnmapBuffer( GL_PIXEL_UNPACK_BUFFER_ARB );
      assert( glGetError() == 0 );

      glTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, texWidth, texHeight, GL_BGRA, GL_UNSIGNED_BYTE, NULL );
      assert( glGetError() == 0 );
   }
   else
   {
      glTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, texWidth, texHeight, GL_BGRA, GL_UNSIGNED_BYTE, texData );
   }

   glClear( GL_COLOR_BUFFER_BIT );
   glPushMatrix();
   glRotatef( angle, 0.0f, 0.0f, 1.0f );
   glColor3f( 1.0f, 1.0f, 1.0f );
   glBegin( GL_TRIANGLE_STRIP );
   glTexCoord2f( 0.0f, 0.0f );
   glVertex2f( -0.5f, -0.5f );
   glTexCoord2f( 1.0f, 0.0f );
   glVertex2f( 0.5f, -0.5f );
   glTexCoord2f( 0.0f, 1.0f );
   glVertex2f( -0.5f, 0.5f );
   glTexCoord2f( 1.0f, 1.0f );
   glVertex2f( 0.5f, 0.5f );
   glEnd();
   glPopMatrix();

   if ( Found_GL_ARB_pixel_buffer_object )
   {
      glBindBuffer( GL_PIXEL_UNPACK_BUFFER_ARB, 0 );
      assert( glGetError() == 0 );
   }

   glutSwapBuffers();
   assert( glGetError() == 0 );
}

void idle()
{
   ++ticks;
   LARGE_INTEGER timeStamp;
   QueryPerformanceCounter( &timeStamp );
   const float timeDiffStartFrame = (timeStamp.QuadPart - startTimeStamp.QuadPart) / (float) freq.QuadPart;
   if ( timeDiffStartFrame > 1.0f )
   {
      const float averageTickTimeMilliSeconds = 1000.0f * timeDiffStartFrame / ticks;
      ticks = 0;
      startTimeStamp.QuadPart = timeStamp.QuadPart;
      std::cerr << "Average frame time: " << averageTickTimeMilliSeconds << "ms" << std::endl;
   }

   // time difference to previous frame
   const float timeDiffPreviousFrame = (timeStamp.QuadPart - previousTimeStamp.QuadPart) / (float) freq.QuadPart;
   previousTimeStamp.QuadPart = timeStamp.QuadPart;
   angle += 30.0f * timeDiffPreviousFrame;
   if ( angle >= 360.0f )
   {
      angle -= 360.0f;
   }
   glutPostRedisplay();
}



int _tmain(int argc, _TCHAR* argv[])
{
   glutInit( &argc, argv );

   glutInitDisplayMode( GLUT_DOUBLE | GLUT_RGB );
   glutInitWindowSize( texWidth, texHeight );
   glutInitWindowPosition( 50, 50 );
   glutCreateWindow( argv[ 0 ] );

   if ( !checkExtensions() )
   {
      std::cerr << "Missing certain OpenGL extensions that are required for this program to run!" << std::endl;
      return -1;
   }

   init();
   glutReshapeFunc( reshape );
   glutDisplayFunc( display );
   glutIdleFunc( idle );

   glutMainLoop();

   return 0;
}
