#if !defined( lint ) && !defined( SABER )
static const char sccsid[] = "@(#)visgl.c	5.24 2007/01/18 xlockmore";

#endif

/*-
 * visgl.c - separated out of vis.c for lots of good reasons !
 *         - parts grabbed from xscreensaver
 *
 * Copyright (c) 2001 by Eric Lassauge
 *
 * Revision History:
 *
 * E.Lassauge - 09-Mar-2001:
 *	- Use showfps vs erroneous fpsTop
 *      - use MI_DELAY instead of MI_PAUSE for accumulating FPS value
 */


#include "xlock.h"
#include "vis.h"
#include "visgl.h"

#ifdef USE_GL
#ifdef WIN32
HGLRC
init_GL(ModeInfo *mi)
{
	PIXELFORMATDESCRIPTOR pfd;
	int pixelFormat;
#if 1
	BYTE type = PFD_TYPE_RGBA;
#else
	BYTE type = PFD_TYPE_COLORINDEX;
#endif
	DWORD flags = PFD_DOUBLEBUFFER;
	HGLRC context;

	memset(&pfd, 0, sizeof(PIXELFORMATDESCRIPTOR));
	pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);
	pfd.nVersion = 1;
	pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | flags;
	pfd.dwLayerMask = PFD_MAIN_PLANE;
	/*pfd.cColorBits = 8;
	pfd.cDepthBits = 16;*/
	pfd.iPixelType = type;
	pfd.cColorBits = 24;
	pfd.cDepthBits = 32;
	if ((pixelFormat = ChoosePixelFormat(hdc, &pfd)) == 0) {
		DWORD err;

		err = GetLastError();
		/* 6 ERROR_INVALID_HANDLE */
#if DEBUG
		{
			char buf1[80];

			(void) sprintf(buf1, "%s: %d\n",
				"ChoosePixelFormat failed", err);
			OutputDebugString(buf1);
		}
#endif
		return NULL;
	}

	if (SetPixelFormat(hdc, pixelFormat, &pfd) == FALSE) {
#if DEBUG
		OutputDebugString("SetPixelFormat failed");
#endif
		MessageBox(NULL, "SetPixelFormat failed", "Error", MB_OK);
		return NULL;
	}
	DescribePixelFormat(hdc, pixelFormat,
		sizeof(PIXELFORMATDESCRIPTOR), &pfd);
	{
		GLboolean rgbaMode;
		glGetBooleanv(GL_RGBA_MODE, &rgbaMode);
		if (!rgbaMode)
			glClearIndex(0.0);
	}
	context = wglCreateContext(hdc);
	wglMakeCurrent(hdc, context);
	return context;
}

/*void
glXMakeCurrent(Display *display, Window window, HGLRC hglrc)
{
	wglMakeCurent(hdc, hglrc);
}*/

void
FreeAllGL(ModeInfo * mi)
{
}
void
do_fps (ModeInfo *mi)
{
}
#else
#include <GL/glu.h>

static GLXContext *glXContext = (GLXContext *) NULL;
Bool *glOK = (Bool *) NULL;

/* fps stuff */
static XFontStruct *font_struct = (XFontStruct *) NULL;
static GLuint   font_dlist;
extern Bool     fpsTop;
extern char 	*fpsfontname;

/*-
 * NOTE WELL:  We _MUST_ destroy the glXContext between each mode
 * in random mode, otherwise OpenGL settings and paramaters from one
 * mode will affect the default initial state for the next mode.
 * BUT, we are going to keep the visual returned by glXChooseVisual,
 * because it will still be good (and because Mesa must keep track
 * of each one, even after XFree(), causing a small memory leak).
 */

XVisualInfo *
getGLVisual(Display * display, int screen, XVisualInfo * wantVis, int monochrome)
{
	if (wantVis) {
		/* Use glXGetConfig() to see if wantVis has what we need already. */
		int         depthBits, doubleBuffer;

		/* glXGetConfig(display, wantVis, GLX_RGBA, &rgbaMode); */
		glXGetConfig(display, wantVis, GLX_DEPTH_SIZE, &depthBits);
		glXGetConfig(display, wantVis, GLX_DOUBLEBUFFER, &doubleBuffer);

		if ((depthBits > 0) && doubleBuffer) {
			return wantVis;
		} else {
			XFree((char *) wantVis);	/* Free it up since its useless now. */
			wantVis = (XVisualInfo *) NULL;
		}
	}
	/* If wantVis is useless, try glXChooseVisual() */
	if (monochrome) {
		/* Monochrome display - use color index mode */
		int         attribList[] =
		{GLX_DOUBLEBUFFER, None};

		return glXChooseVisual(display, screen, attribList);
	} else {
		int         attribList[] =
#if 1
		{GLX_RGBA, GLX_RED_SIZE, 1, GLX_GREEN_SIZE, 1, GLX_BLUE_SIZE, 1,
		 GLX_DOUBLEBUFFER, GLX_DEPTH_SIZE, 1, None};

#else
		{GLX_RGBA, GLX_DOUBLEBUFFER, GLX_DEPTH_SIZE, 1, None};

#endif
		return glXChooseVisual(display, screen, attribList);
	}
}

/*-
 * The following function should be called on startup of any GL mode.
 * It returns a GLXContext for the calling mode to use with
 * glXMakeCurrent().
 */
GLXContext *
init_GL(ModeInfo * mi)
{
	Display    *display = MI_DISPLAY(mi);
	Window      window = MI_WINDOW(mi);
	int         screen = MI_SCREEN(mi);
	XVisualInfo xvi_in, *xvi_out;
	int         num_vis;
	GLboolean   rgbaMode;
	Colormap cmap;

#ifdef FX
	extern void resetSize(ModeInfo * mi, Bool setGL);

#endif

	if (!glXContext) {
		glXContext = (GLXContext *) calloc(MI_NUM_SCREENS(mi),
						   sizeof (GLXContext));
		if (!glXContext)
			return (GLXContext *) NULL;
	}
	if (glXContext[screen]) {
		glXDestroyContext(display, glXContext[screen]);
		glXContext[screen] = (GLXContext) NULL;
	}
	xvi_in.screen = screen;
	xvi_in.visualid = XVisualIDFromVisual(MI_VISUAL(mi));
	xvi_out = XGetVisualInfo(display, VisualScreenMask | VisualIDMask,
				 &xvi_in, &num_vis);
	if (!xvi_out) {
		(void) fprintf(stderr, "Could not get XVisualInfo\n");
		return (GLXContext *) NULL;
	}
   
	if ( xvi_out->CLASS == DirectColor ) {
		XColor xc[256];
		int maxColors = 256, i;
		int bits0 = 8, bits1 = 8, bits2 = 8;

		cmap = XCreateColormap(display, window, MI_VISUAL(mi),
			AllocAll);
		if (xvi_out->depth < 24) {
			maxColors = 8;
			bits0 = 2, bits1 = 3, bits2 = 3;
		}
		/* init the red, green, blue maps to linear ramps */

		for (i = 0; i < maxColors; i++) {
			xc[i].pixel = (i << (bits0 + bits1)) | (i << bits0) | i;
			xc[i].red = (i << bits2) | i;
			xc[i].green = (i << bits1) | i;
			xc[i].blue = (i << bits0) | i;
			xc[i].flags = DoRed | DoGreen | DoBlue;
		}
#ifdef DIRECTCOLOR_FIX
	MI_BLACK_PIXEL(mi) = xc[0].pixel;
	MI_WHITE_PIXEL(mi) = xc[maxColors - 1].pixel;
#else
#ifndef NO_RESERVE_BLACK
		i = 0;
		xc[i].pixel = MI_BLACK_PIXEL(mi);
		xc[i].red = 0;
		xc[i].green = 0;
		xc[i].blue = 0;
#endif
#ifndef NO_RESERVE_WHITE
		i = 1;
		/* i = maxColors - 1; */
		xc[i].pixel = MI_WHITE_PIXEL(mi);
		xc[i].red = 0xFFFF;
		xc[i].green = 0xFFFF;
		xc[i].blue = 0xFFFF;
#endif
#endif
		XStoreColors(display, cmap, xc, maxColors);
		XInstallColormap(display, cmap);
	}
   
	if (glOK && glOK[screen]) {
#ifdef SUN_OGL_NO_VERTEX_MACROS
/* Cores when run in GrayScale|StaticGray|StaticColor on OpenGL Solaris 10 */
		if (xvi_out->CLASS == StaticGray ||
				xvi_out->CLASS == GrayScale ||
				xvi_out->CLASS == StaticColor) {
			XFree((char *) xvi_out);
			if (MI_IS_VERBOSE(mi))
				(void) fprintf(stderr,
				       "Avoiding possible problem of using glxCreateContext with %s on screen %d.\n",
					nameOfVisualClass(xvi_out->CLASS),
					screen);
			return (GLXContext *) NULL;
		}
#endif
/*-
 * PURIFY 4.0.1 on SunOS4 and on Solaris 2 reports a 104 byte memory leak on
 * the next line each time that a GL mode is run in random mode when using
 * MesaGL 2.2.  This cumulative leak can cause xlock to eventually crash if
 * available memory is depleted.  This bug is fixed in MesaGL 2.3. */
		glXContext[screen] = glXCreateContext(display, xvi_out,
			0, GL_TRUE);
	}
	XFree((char *) xvi_out);
	if (!glXContext[screen]) {
		if (MI_IS_VERBOSE(mi))
			(void) fprintf(stderr,
				       "GL could not create rendering context on screen %d.\n", screen);
		return (GLXContext *) NULL;
	}
/*-
 * PURIFY 4.0.1 on Solaris2 reports an uninitialized memory read on the next
 * line. PURIFY 4.0.1 on SunOS4 does not report this error. */
	if (!glXMakeCurrent(display, window, glXContext[screen])) {
		if (MI_IS_DEBUG(mi)) {
			(void) fprintf(stderr, "GLX error\n");
			(void) fprintf(stderr, "If using MesaGL, XGetWindowAttributes is\n");
			(void) fprintf(stderr, "probably returning a null colormap in\n");
			(void) fprintf(stderr, "XMesaCreateWindowBuffer in xmesa1.c .\n");
		}
		return (GLXContext *) NULL;
	}
	/* True Color junk */
	glGetBooleanv(GL_RGBA_MODE, &rgbaMode);
	if (!rgbaMode) {
		glIndexi(MI_WHITE_PIXEL(mi));
		glClearIndex((float) MI_BLACK_PIXEL(mi));
	}
#ifdef FX
	resetSize(mi, True);
#endif
	return (&(glXContext[screen]));
}

void
FreeAllGL(ModeInfo * mi)
{
	int         scr;

#ifdef FX
	extern void resetSize(ModeInfo * mi, Bool setGL);

#endif

	if (glXContext) {
		/* To release the current context */
		glXMakeCurrent(MI_DISPLAY(mi), None, (GLXContext) NULL);
		/* then destroy all contexts */
		for (scr = 0; scr < MI_NUM_SCREENS(mi); scr++) {
			if (glXContext[scr]) {
				glXDestroyContext(MI_DISPLAY(mi), glXContext[scr]);
				glXContext[scr] = (GLXContext) NULL;
			}
		}
		free(glXContext);
		glXContext = (GLXContext *) NULL;
	}
#ifdef FX
	resetSize(mi, False);
#endif

#if 0
	if (glOK) {
		free(glOK);
		glOK = NULL;
	}
#endif
	if (MI_IS_FPS(mi))
	{
		/* Free font stuff */
		if (font_struct)
		{
  			int last;

  			last = font_struct->max_char_or_byte2;
  			clear_gl_error ();
			if (glIsList(font_dlist)) {
	  			glDeleteLists (font_dlist,(GLuint) last+1);
  				(void) check_gl_error ("glDeleteLists");
				font_dlist = 0;
			}
			XFreeFont(MI_DISPLAY(mi),font_struct);
			/* font_struct should be freed now */
			font_struct = (XFontStruct *) NULL;
		}
		/* else oops ? */
	}
}

/* clear away any lingering error codes */
void
clear_gl_error (void)
{
  while (glGetError() != GL_NO_ERROR);
}

/* report a GL error. */
Bool
check_gl_error (const char *type)
{
  char buf[100];
  GLenum i;
  const char *e;
  switch ((i = glGetError())) {
  case GL_NO_ERROR: return False;
  case GL_INVALID_ENUM:          e = "invalid enum";      break;
  case GL_INVALID_VALUE:         e = "invalid value";     break;
  case GL_INVALID_OPERATION:     e = "invalid operation"; break;
  case GL_STACK_OVERFLOW:        e = "stack overflow";    break;
  case GL_STACK_UNDERFLOW:       e = "stack underflow";   break;
  case GL_OUT_OF_MEMORY:         e = "out of memory";     break;
#ifdef GL_TABLE_TOO_LARGE_EXT
  case GL_TABLE_TOO_LARGE_EXT:   e = "table too large";   break;
#endif
#ifdef GL_TEXTURE_TOO_LARGE_EXT
  case GL_TEXTURE_TOO_LARGE_EXT: e = "texture too large"; break;
#endif
  default:
    e = buf; (void) sprintf (buf, "unknown error %d", (int) i); break;
  }
  (void) fprintf (stderr, "XLock: %s error: %s\n", type, e);
  return True;
}


/* Frames-per-second statistics */

static int fps_text_x = 10;
static int fps_text_y = 10;
static int fps_sample_frames = 10;

static Bool
fps_init (ModeInfo *mi)
{
  const char *font = fpsfontname;
  XFontStruct *f;
  Font id;
  int first, last;

  if (!font) font = "-*-courier-bold-r-normal-*-180-*";
  f = XLoadQueryFont(MI_DISPLAY(mi), font);
  if (!f) f = XLoadQueryFont(MI_DISPLAY(mi), "fixed");

  font_struct = f; /* save font struct info */
  id = f->fid;
  first = f->min_char_or_byte2;
  last = f->max_char_or_byte2;

  clear_gl_error ();
  font_dlist = glGenLists ((GLuint) last+1);
  if (check_gl_error ("glGenLists"))
 	return False;

  if (fpsTop) /* Draw string on top of screen */
    fps_text_y = - (f->ascent + 10);

  glXUseXFont(id, first, last-first+1, font_dlist + first);
  return (!check_gl_error ("glXUseXFont"));
}

static Bool
fps_print_string (ModeInfo *mi, GLfloat x, GLfloat y, const char *string)
{
  static GLfloat red = 0.0, green = 0.0, blue = 0.0;
  int i;
  /* save the current state */
  /* note: could be expensive! */

  while (red == 0.0 && green == 0.0 && blue == 0.0) {
     red = NRAND(2) ? 1.0 : 0.0;
     green = NRAND(2) ? 1.0 : 0.0;
     blue = NRAND(2) ? 1.0 : 0.0;
  }
  if (y < 0)
    y = MI_HEIGHT(mi) + y;

  clear_gl_error ();
  glPushAttrib(GL_ALL_ATTRIB_BITS);
  {
    if (check_gl_error ("glPushAttrib"))
	return False;
    /* disable lighting and texturing when drawing bitmaps!
       (glPopAttrib() restores these, I believe.)
     */
    glDisable(GL_TEXTURE_2D);
    glDisable(GL_LIGHTING);
    glDisable(GL_BLEND);

    /* glPopAttrib() does not restore matrix changes, so we must
       push/pop the matrix stacks to be non-intrusive there.
     */
    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    {
      if (check_gl_error ("glPushMatrix"))
	return False;
      glLoadIdentity();

      /* Each matrix mode has its own stack, so we need to push/pop
         them separately. */
      glMatrixMode(GL_MODELVIEW);
      glPushMatrix();
      {
        if (check_gl_error ("glPushMatrix"))
	  return False;
        glLoadIdentity();

        gluOrtho2D(0.0, (GLfloat) MI_WIDTH(mi), 0.0, (GLfloat) MI_HEIGHT(mi));
        if (check_gl_error ("gluOrtho2D"))
	  return False;
        /* draw the text */
        glColor3f (red, green, blue);
        glRasterPos2f (x, y);
        for (i = 0; i < (int) strlen(string); i++)
          glCallList (font_dlist + (int)string[i]);

        if (check_gl_error ("fps_print_string"))
	  return False;
      }
      glPopMatrix();
    }
    glMatrixMode(GL_PROJECTION);
    glPopMatrix();

  }
  /* clean up after our state changes */
  glPopAttrib();
  return (!check_gl_error ("glPopAttrib"));
}


void
do_fps (ModeInfo *mi)
{
  /* every N frames, get the time and use it to get the frames per second */
  static int frame_counter = -1;
  static double oldtime = 0; /* time in usecs, as a double */
  static double newtime = 0;

  static char msg [1024] = { 0, };

  if (MI_IS_ICONIC(mi))
  {
	/* don't do it in iconic state */
	return;
  }
  if (frame_counter == -1)
    {
      if (!fps_init (mi))
	return;
      frame_counter = fps_sample_frames;
    }

  if (frame_counter++ == fps_sample_frames)
    {
      double fps;
      struct timeval now;
# ifdef GETTIMEOFDAY_TWO_ARGS
#ifdef VMS
	(void) gettimeofday(&now, NULL);
#else
	struct timezone tzp;
	(void) gettimeofday(&now, &tzp);
#endif
# else
      (void) gettimeofday(&now);
# endif

      oldtime = newtime;
      newtime = now.tv_sec + ((double) now.tv_usec * 0.000001);

      fps = fps_sample_frames / (newtime - oldtime);

      if (fps < 0.0001)
        {
          (void) strcpy(msg, "FPS: (accumulating...)");
        }
      else
        {
          (void) sprintf(msg, "FPS: %.02f", fps);

          if (MI_DELAY(mi) != 0 && !fpsTop)
            { /* Include delay information in FPS display only if not on top */
              char buf[40];
              (void) sprintf(buf, "%f", MI_DELAY(mi) / 1000000.0); /* FTSO C */
              while(*buf && buf[strlen(buf)-1] == '0')
                buf[strlen(buf)-1] = 0;
              if (buf[strlen(buf)-1] == '.')
                buf[strlen(buf)-1] = 0;
              (void) sprintf(msg + strlen(msg), " (including %s sec/frame delay)",
                      buf);
            }
        }

      frame_counter = 0;
    }
    (void) fps_print_string (mi, (float) fps_text_x, (float) -fps_text_y, msg);
}
#endif
#endif
