/* Copyright (C)2007 Sun Microsystems, Inc.
 * Copyright (C)2011, 2013-2015, 2017-2022 D. R. Commander
 *
 * This library is free software and may be redistributed and/or modified under
 * the terms of the wxWindows Library License, Version 3.1 or (at your option)
 * any later version.  The full license is in the LICENSE.txt file included
 * with this distribution.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * wxWindows Library License for more details.
 */

/* This program's goal is to reproduce, as closely as possible, the image
   output of the nVidia SphereMark demo by R. Stephen Glanville using the
   simplest available rendering method.  EGLXSpheres is meant primarily to
   serve as an image pipeline benchmark for VirtualGL. */


#include <X11/Xutil.h>
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GL/glu.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "vglutil.h"


extern int Fake_glXUseXFont(Display *dpy, Window win, Font font, int first,
	int count, int listbase);


#define THROW(m) \
{ \
	fprintf(stderr, "ERROR in line %d:\n%s\n", __LINE__,  m); \
	retval = -1; \
	goto bailout; \
}

#define CASE(c)  case c:  return #c

static const char *getEGLErrorString(void)
{
	switch(eglGetError())
	{
		CASE(EGL_SUCCESS);
		CASE(EGL_NOT_INITIALIZED);
		CASE(EGL_BAD_ACCESS);
		CASE(EGL_BAD_ALLOC);
		CASE(EGL_BAD_ATTRIBUTE);
		CASE(EGL_BAD_CONFIG);
		CASE(EGL_BAD_CONTEXT);
		CASE(EGL_BAD_CURRENT_SURFACE);
		CASE(EGL_BAD_DISPLAY);
		CASE(EGL_BAD_MATCH);
		CASE(EGL_BAD_NATIVE_PIXMAP);
		CASE(EGL_BAD_NATIVE_WINDOW);
		CASE(EGL_BAD_PARAMETER);
		CASE(EGL_BAD_SURFACE);
		CASE(EGL_CONTEXT_LOST);
		default:  return "Unknown EGL error";
	}
}

#define THROW_EGL() \
{ \
	fprintf(stderr, "%s ERROR in line %d\n", getEGLErrorString(), __LINE__); \
	retval = -1; \
	goto bailout; \
}

#define TRY_EGL(f)  if(!(f)) THROW_EGL();

#define CATCH(f) \
{ \
	if((f) == -1) \
	{ \
		retval = -1; \
		goto bailout; \
	} \
}

#define NP2(i)  ((i) > 0 ? (1 << (int)(log((double)(i)) / log(2.))) : 0)

#define SPHERE_RED(f)  fabs(MAXI * (2. * f - 1.))
#define SPHERE_GREEN(f)  fabs(MAXI * (2. * fmod(f + 2. / 3., 1.) - 1.))
#define SPHERE_BLUE(f)  fabs(MAXI * (2. * fmod(f + 1. / 3., 1.) - 1.))


#define DEF_WIDTH  1240
#define DEF_HEIGHT  900

#define DEF_SLICES  32
#define DEF_STACKS  32

#define DEF_SPHERES  20

#define _2PI  6.283185307180
#define MAXI  (220. / 255.)

#define NSCHEMES  7
enum { GRAY = 0, RED, GREEN, BLUE, YELLOW, MAGENTA, CYAN };

#define DEFBENCHTIME  2.0

Display *dpy = NULL;  Window win = 0;
EGLDisplay edpy = 0;  EGLSurface eglwin = 0;
EGLContext ctx = 0;
int useDC = 0, useImm = 0, interactive = 0, loColor = 0, maxFrames = 0,
	totalFrames = 0, bpc = -1, deadYet = 0, swapInterval = -1, useSRGB = 0;
int rshift = 0, gshift = 0, bshift = 0;
double benchTime = DEFBENCHTIME;
int nColors = 0, colorScheme = GRAY;
Colormap colormap = 0;

int sphereList = 0, fontListBase = 0;
GLUquadricObj *sphereQuad = NULL;
int slices = DEF_SLICES, stacks = DEF_STACKS, spheres = DEF_SPHERES;
GLfloat x = 0., y = 0., z = -3.;
GLfloat outerAngle = 0., middleAngle = 0., innerAngle = 0.;
GLfloat loneSphereColor = 0.;
unsigned int transPixel = 0;

int width = DEF_WIDTH, height = DEF_HEIGHT;


static int setColorScheme(Colormap cmap, int colors, int bits, int scheme)
{
	XColor xc[1024];  int i, maxColors = (1 << bits), retval = 0;

	if(!colors || !cmap) THROW("Color map not allocated");
	if(scheme < 0 || scheme > NSCHEMES - 1 || !cmap) THROW("Invalid argument");

	for(i = 0; i < colors; i++)
	{
		xc[i].flags = DoRed | DoGreen | DoBlue;
		xc[i].pixel = (i << rshift) | (i << gshift) | (i << bshift);
		xc[i].red = xc[i].green = xc[i].blue = 0;
		if(scheme == GRAY || scheme == RED || scheme == YELLOW
			|| scheme == MAGENTA)
			xc[i].red = (i * (maxColors / colors)) << (16 - bits);
		if(scheme == GRAY || scheme == GREEN || scheme == YELLOW
			|| scheme == CYAN)
			xc[i].green = (i * (maxColors / colors)) << (16 - bits);
		if(scheme == GRAY || scheme == BLUE || scheme == MAGENTA
			|| scheme == CYAN)
			xc[i].blue = (i * (maxColors / colors)) << (16 - bits);
	}
	XStoreColors(dpy, cmap, xc, colors);

	bailout:
	return retval;
}


static void reshape(int newWidth, int newHeight)
{
	if(newWidth <= 0) newWidth = 1;
	if(newHeight <= 0) newHeight = 1;
	width = newWidth;  height = newHeight;
}


static void setSphereColor(GLfloat color)
{
	if(useDC)
	{
		GLfloat mat[] = { color, color, color };
		glColor3f(color, color, color);
		glMaterialfv(GL_FRONT, GL_AMBIENT, mat);
		glMaterialfv(GL_FRONT, GL_DIFFUSE, mat);
	}
	else
	{
		GLfloat mat[4] =
		{
			SPHERE_RED(color), SPHERE_GREEN(color), SPHERE_BLUE(color), 0.25
		};
		glColor3f(SPHERE_RED(color), SPHERE_GREEN(color), SPHERE_BLUE(color));
		glMaterialfv(GL_FRONT, GL_AMBIENT, mat);
		glMaterialfv(GL_FRONT, GL_DIFFUSE, mat);
	}
}


static void renderSpheres(void)
{
	int i;
	GLfloat xAspect, yAspect;
	GLfloat nearDist = 1.5, farDist = 40.;

	if(useSRGB) glEnable(GL_FRAMEBUFFER_SRGB);

	xAspect = (GLfloat)width / (GLfloat)(min(width, height));
	yAspect = (GLfloat)height / (GLfloat)(min(width, height));

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glFrustum(-xAspect, xAspect, -yAspect, yAspect, nearDist, farDist);
	glMatrixMode(GL_MODELVIEW);
	glViewport(0, 0, width, height);

	/* Begin rendering */
	glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
	glClearDepth(20.0f);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	/* Lone sphere */
	glPushMatrix();
	glTranslatef(0., 0., z);
	setSphereColor(loneSphereColor);
	if(useImm) gluSphere(sphereQuad, 1.3, slices, stacks);
	else glCallList(sphereList);
	glPopMatrix();

	/* Outer ring */
	glPushMatrix();
	glRotatef(outerAngle, 0., 0., 1.);
	for(i = 0; i < spheres; i++)
	{
		double f = (double)i / (double)spheres;
		glPushMatrix();
		glTranslatef(sin(_2PI * f) * 5., cos(_2PI * f) * 5., -10.);
		setSphereColor(f);
		if(useImm) gluSphere(sphereQuad, 1.3, slices, stacks);
		else glCallList(sphereList);
		glPopMatrix();
	}
	glPopMatrix();

	/* Middle ring */
	glPushMatrix();
	glRotatef(middleAngle, 0., 0., 1.);
	for(i = 0; i < spheres; i++)
	{
		double f = (double)i / (double)spheres;
		glPushMatrix();
		glTranslatef(sin(_2PI * f) * 5., cos(_2PI * f) * 5., -17.);
		setSphereColor(f);
		if(useImm) gluSphere(sphereQuad, 1.3, slices, stacks);
		else glCallList(sphereList);
		glPopMatrix();
	}
	glPopMatrix();

	/* Inner ring */
	glPushMatrix();
	glRotatef(innerAngle, 0., 0., 1.);
	for(i = 0; i < spheres; i++)
	{
		double f = (double)i / (double)spheres;
		glPushMatrix();
		glTranslatef(sin(_2PI * f) * 5., cos(_2PI * f) * 5., -29.);
		setSphereColor(f);
		if(useImm) gluSphere(sphereQuad, 1.3, slices, stacks);
		else glCallList(sphereList);
		glPopMatrix();
	}
	glPopMatrix();

	/* Move the eye back to the middle */
	glMatrixMode(GL_PROJECTION);
}


static int display(int advance)
{
	static int first = 1;
	static double start = 0., elapsed = 0., mpixels = 0.;
	static unsigned long frames = 0;
	static char temps[700];
	XFontStruct *fontInfo = NULL;  int minChar, maxChar;
	GLfloat xAspect, yAspect;
	int retval = 0;

	if(first)
	{
		GLfloat id4[] = { 1., 1., 1., 1. };
		GLfloat light0Amb[] = { 0.3, 0.3, 0.3, 1. };
		GLfloat light0Dif[] = { 0.8, 0.8, 0.8, 1. };
		GLfloat light0Pos[] = { 1., 1., 1., 0. };

		sphereList = glGenLists(1);
		if(!(sphereQuad = gluNewQuadric()))
			THROW("Could not allocate GLU quadric object");
		glNewList(sphereList, GL_COMPILE);
		gluSphere(sphereQuad, 1.3, slices, stacks);
		glEndList();

		if(!loColor)
		{
			if(useDC)
			{
				glMaterialf(GL_FRONT, GL_SHININESS, 50.);
			}
			else
			{
				glMaterialfv(GL_FRONT, GL_AMBIENT, id4);
				glMaterialfv(GL_FRONT, GL_DIFFUSE, id4);
				glMaterialfv(GL_FRONT, GL_SPECULAR, id4);
				glMaterialf(GL_FRONT, GL_SHININESS, 50.);

				glLightfv(GL_LIGHT0, GL_AMBIENT, light0Amb);
				glLightfv(GL_LIGHT0, GL_DIFFUSE, light0Dif);
				glLightfv(GL_LIGHT0, GL_SPECULAR, id4);
			}
			glLightfv(GL_LIGHT0, GL_POSITION, light0Pos);
			glLightf(GL_LIGHT0, GL_SPOT_CUTOFF, 180.);
			glEnable(GL_LIGHTING);
			glEnable(GL_LIGHT0);
		}

		if(loColor) glShadeModel(GL_FLAT);
		else glShadeModel(GL_SMOOTH);
		glEnable(GL_DEPTH_TEST);
		glDepthFunc(GL_LESS);

		if(!(fontInfo = XLoadQueryFont(dpy, "fixed")))
			THROW("Could not load X font");
		minChar = fontInfo->min_char_or_byte2;
		maxChar = fontInfo->max_char_or_byte2;
		fontListBase = glGenLists(maxChar + 1);
		CATCH(Fake_glXUseXFont(dpy, win, fontInfo->fid, minChar,
			maxChar - minChar + 1, fontListBase + minChar));
		XFreeFont(dpy, fontInfo);  fontInfo = NULL;
		snprintf(temps, 700, "Measuring performance ...");

		first = 0;
	}

	if(advance)
	{
		z -= 0.5;
		if(z < -29.)
		{
			if(useDC)
			{
				colorScheme = (colorScheme + 1) % NSCHEMES;
				CATCH(setColorScheme(colormap, nColors, bpc, colorScheme));
			}
			z = -3.5;
		}
		outerAngle += 0.1;  if(outerAngle > 360.) outerAngle -= 360.;
		middleAngle -= 0.37;  if(middleAngle < -360.) middleAngle += 360.;
		innerAngle += 0.63;  if(innerAngle > 360.) innerAngle -= 360.;
		loneSphereColor += 0.005;
		if(loneSphereColor > 1.) loneSphereColor -= 1.;
	}

	renderSpheres();

	glPushAttrib(GL_CURRENT_BIT);
	glPushAttrib(GL_LIST_BIT);
	glPushAttrib(GL_ENABLE_BIT);
	glDisable(GL_LIGHTING);
	if(useDC) glColor3f(254., 254., 254.);
	else glColor3f(1., 1., 1.);
	xAspect = (GLfloat)width / (GLfloat)(min(width, height));
	yAspect = (GLfloat)height / (GLfloat)(min(width, height));
	glRasterPos3f(-0.95 * xAspect, -0.95 * yAspect, -1.5);
	glListBase(fontListBase);
	glCallLists(strlen(temps), GL_UNSIGNED_BYTE, temps);
	glPopAttrib();
	glPopAttrib();
	glPopAttrib();
	eglSwapBuffers(edpy, eglwin);

	if(start > 0.)
	{
		elapsed += GetTime() - start;  frames++;  totalFrames++;
		mpixels += (double)width * (double)height / 1000000.;
		if(elapsed > benchTime || (maxFrames && totalFrames > maxFrames))
		{
			snprintf(temps, 700, "%f frames/sec - %f Mpixels/sec",
				(double)frames / elapsed, mpixels / elapsed);
			printf("%s\n", temps);
			elapsed = mpixels = 0.;  frames = 0;
		}
	}
	if(maxFrames && totalFrames > maxFrames)
	{
		deadYet = 1;  goto bailout;
	}

	start = GetTime();
	return 0;

	bailout:
	if(sphereQuad) { gluDeleteQuadric(sphereQuad);  sphereQuad = NULL; }
	return retval;
}


Atom protoAtom = 0, deleteAtom = 0;


static int eventLoop(void)
{
	int retval = 0;

	while(!deadYet)
	{
		int advance = 0, doDisplay = 0;

		while(1)
		{
			XEvent event;
			if(interactive) XNextEvent(dpy, &event);
			else
			{
				if(XPending(dpy) > 0) XNextEvent(dpy, &event);
				else break;
			}
			switch(event.type)
			{
				case Expose:
					doDisplay = 1;
					break;
				case ConfigureNotify:
					reshape(event.xconfigure.width, event.xconfigure.height);
					break;
				case KeyPress:
				{
					char buf[10];
					XLookupString(&event.xkey, buf, sizeof(buf), NULL, NULL);
					switch(buf[0])
					{
						case 27: case 'q': case 'Q':
							return 0;
					}
					break;
				}
				case MotionNotify:
					if(event.xmotion.state & Button1Mask) doDisplay = advance = 1;
					break;
				case ClientMessage:
				{
					XClientMessageEvent *cme = (XClientMessageEvent *)&event;
					if(cme->message_type == protoAtom
						&& cme->data.l[0] == (long)deleteAtom)
						return 0;
				}
			}
			if(interactive)
			{
				if(XPending(dpy) <= 0) break;
			}
		}
		if(!interactive) { CATCH(display(1)); }
		else
		{
			if(doDisplay) { CATCH(display(advance)); }
		}
	}

	bailout:
	return retval;
}


static void usage(char **argv)
{
	printf("\nUSAGE: %s [options]\n\n", argv[0]);
	printf("Options:\n");
	printf("-dc = Use DirectColor rendering (default is TrueColor)\n");
	printf("-fs = Full-screen mode\n");
	printf("-i = Interactive mode (frames advance in response to mouse movement)\n");
	printf("-l = Use fewer than 24 colors (to force non-JPEG subencoding in TurboVNC)\n");
	printf("-m = Use immediate mode rendering (default is display list)\n");
	printf("-p <p> = Use (approximately) <p> polygons to render scene\n");
	printf("         (max. is 57600 per sphere due to limitations of GLU)\n");
	printf("-n <n> = Render (approximately) <n> spheres (default: %d)\n",
		DEF_SPHERES * 3 + 1);
	printf("-alpha = Use an EGL config with an alpha channel\n");
	printf("-bpc = Specify bits per component (default is determined from the default depth\n");
	printf("       of the X server)\n");
	printf("-srgb = Use sRGB color space\n");
	printf("-f <n> = Render <n> frames and exit\n");
	printf("-bt <t> = Print benchmark results every <t> seconds (default: %.1f)\n",
		DEFBENCHTIME);
	printf("-w <wxh> = Specify window width and height\n");
	printf("-sc <s> = Create window on X screen # <s>\n");
	printf("-si <n> = Set swap interval to <n> frames\n");
	printf("\n");
	exit(1);
}


int main(int argc, char **argv)
{
	int i, n = 0, useAlpha = 0, nPolys = -1, retval = 0;
	XVisualInfo *v = NULL, vtemp;
	EGLConfig c;
	EGLAttrib dpyAttribs[] = { EGL_NONE, EGL_NONE, EGL_NONE };
	EGLint cfgAttribs[] = { EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8,
		EGL_DEPTH_SIZE, 1, EGL_NATIVE_VISUAL_TYPE, EGL_DONT_CARE,
		EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, EGL_NONE, EGL_NONE, EGL_NONE };
	XSetWindowAttributes swa;  Window root;
	int fullScreen = 0;  unsigned long mask = 0;
	int screen = -1, pps;
	int cfgid = -1, red = -1, green = -1, blue = -1, alpha = -1, samples = -1,
		vid = -1;

	if(argc > 1) for(i = 1; i < argc; i++)
	{
		if(!stricmp(argv[i], "-h") || !strcmp(argv[i], "-?")) usage(argv);
		else if(!stricmp(argv[i], "-dc"))
		{
			useDC = 1;  cfgAttribs[9] = DirectColor;
		}
		else if(!stricmp(argv[i], "-i")) interactive = 1;
		else if(!stricmp(argv[i], "-l")) loColor = 1;
		else if(!stricmp(argv[i], "-m")) useImm = 1;
		else if(!stricmp(argv[i], "-w") && i < argc - 1)
		{
			if(sscanf(argv[++i], "%dx%d", &width, &height) < 2 || width < 1
				|| height < 1)
				usage(argv);
			printf("Window dimensions: %d x %d\n", width, height);
		}
		else if(!stricmp(argv[i], "-fs")) fullScreen = 1;
		else if(!stricmp(argv[i], "-f") && i < argc - 1)
		{
			maxFrames = atoi(argv[++i]);
			if(maxFrames <= 0) usage(argv);
			printf("Number of frames to render: %d\n", maxFrames);
		}
		else if(!stricmp(argv[i], "-bt") && i < argc - 1)
		{
			benchTime = atof(argv[++i]);
			if(benchTime <= 0.0) usage(argv);
		}
		else if(!stricmp(argv[i], "-sc") && i < argc - 1)
		{
			screen = atoi(argv[++i]);
			if(screen < 0) usage(argv);
			printf("Rendering to screen %d\n", screen);
			dpyAttribs[0] = EGL_PLATFORM_X11_SCREEN_EXT;
			dpyAttribs[1] = screen;
		}
		else if(!stricmp(argv[i], "-si") && i < argc - 1)
		{
			swapInterval = atoi(argv[++i]);
			if(swapInterval < 1) usage(argv);
		}
		else if(!stricmp(argv[i], "-srgb")) useSRGB = 1;
		else if(!stricmp(argv[i], "-alpha")) useAlpha = 1;
		else if(!stricmp(argv[i], "-bpc") && i < argc - 1)
		{
			int temp = atoi(argv[++i]);
			if(temp != 8 && temp != 10) usage(argv);
			bpc = temp;
		}
		else if(!stricmp(argv[i], "-n") && i < argc - 1)
		{
			int temp = atoi(argv[++i]);
			if(temp <= 0) usage(argv);
			spheres = (int)(((double)temp - 1.0) / 3.0 + 0.5);
			if(spheres < 1) spheres = 1;
		}
		else if(!stricmp(argv[i], "-p") && i < argc - 1)
		{
			nPolys = atoi(argv[++i]);
			if(nPolys <= 0) usage(argv);
		}
		else usage(argv);
	}

	if(nPolys >= 0)
	{
		slices = stacks =
			(int)(sqrt((double)nPolys / ((double)(3 * spheres + 1))) + 0.5);
		if(slices < 1) slices = stacks = 1;
	}

	pps = slices * stacks;
	if(pps > 57600)
	{
		fprintf(stderr, "WARNING: polygons per sphere clamped to 57600 due to limitations of GLU\n");
		pps = 57600;
	}
	fprintf(stderr, "Polygons in scene: %d (%d spheres * %d polys/spheres)\n",
		(spheres * 3 + 1) * pps, spheres * 3 + 1, pps);

	if((dpy = XOpenDisplay(0)) == NULL) THROW("Could not open display");
	if(screen < 0) screen = DefaultScreen(dpy);
	if((edpy = eglGetPlatformDisplay(EGL_PLATFORM_X11_EXT, dpy,
		dpyAttribs)) == EGL_NO_DISPLAY)
		THROW("Could not get EGL display");
	TRY_EGL(eglInitialize(edpy, NULL, NULL));

	if(bpc < 0)
	{
		if(DefaultDepth(dpy, screen) == 30) bpc = 10;
		else bpc = 8;
	}
	cfgAttribs[1] = cfgAttribs[3] = cfgAttribs[5] = bpc;
	if(useAlpha)
	{
		cfgAttribs[12] = EGL_ALPHA_SIZE;
		cfgAttribs[13] = 32 - bpc * 3;
	}

	TRY_EGL(eglChooseConfig(edpy, cfgAttribs, &c, 1, &n));
	if(n < 1)
		THROW("Could not obtain EGL config with requested properties");
	TRY_EGL(eglGetConfigAttrib(edpy, c, EGL_NATIVE_VISUAL_ID, &vid));
	vtemp.visualid = (VisualID)vid;
	if((v = XGetVisualInfo(dpy, VisualIDMask, &vtemp, &n)) == NULL || n < 1)
		THROW("Could not obtain RGB visual with requested properties");
	TRY_EGL(eglGetConfigAttrib(edpy, c, EGL_CONFIG_ID, &cfgid));
	TRY_EGL(eglGetConfigAttrib(edpy, c, EGL_RED_SIZE, &red));
	TRY_EGL(eglGetConfigAttrib(edpy, c, EGL_GREEN_SIZE, &green));
	TRY_EGL(eglGetConfigAttrib(edpy, c, EGL_BLUE_SIZE, &blue));
	TRY_EGL(eglGetConfigAttrib(edpy, c, EGL_ALPHA_SIZE, &alpha));
	TRY_EGL(eglGetConfigAttrib(edpy, c, EGL_SAMPLES, &samples));
	if(samples > 0)
		fprintf(stderr, "EGL config ID of window: 0x%.2x (%d/%d/%d/%d, %d samples)\n",
			cfgid, red, green, blue, alpha, samples);
	else
		fprintf(stderr, "EGL config ID of window: 0x%.2x (%d/%d/%d/%d)\n", cfgid,
			red, green, blue, alpha);
	fprintf(stderr, "Visual ID of window: 0x%.2x\n", (int)v->visualid);

	root = RootWindow(dpy, screen);
	swa.border_pixel = 0;
	swa.event_mask = StructureNotifyMask | ExposureMask | KeyPressMask;
	swa.override_redirect = fullScreen ? True : False;
	if(useDC)
	{
		swa.colormap = colormap = XCreateColormap(dpy, root, v->visual, AllocAll);
		nColors = NP2(v->colormap_size);
		if(nColors < 32) THROW("Color map is not large enough");
		rshift = 0;  while((v->red_mask & (1 << rshift)) == 0) rshift++;
		gshift = 0;  while((v->green_mask & (1 << gshift)) == 0) gshift++;
		bshift = 0;  while((v->blue_mask & (1 << bshift)) == 0) bshift++;
		CATCH(setColorScheme(colormap, nColors, bpc, colorScheme));
	}
	else swa.colormap = XCreateColormap(dpy, root, v->visual, AllocNone);

	if(interactive) swa.event_mask |= PointerMotionMask | ButtonPressMask;

	mask = CWBorderPixel | CWColormap | CWEventMask;
	if(fullScreen)
	{
		mask |= CWOverrideRedirect;
		width = DisplayWidth(dpy, screen);
		height = DisplayHeight(dpy, screen);
	}
	if(!(protoAtom = XInternAtom(dpy, "WM_PROTOCOLS", False)))
		THROW("Cannot obtain WM_PROTOCOLS atom");
	if(!(deleteAtom = XInternAtom(dpy, "WM_DELETE_WINDOW", False)))
		THROW("Cannot obtain WM_DELETE_WINDOW atom");
	if((win = XCreateWindow(dpy, root, 0, 0, width, height, 0, v->depth,
		InputOutput, v->visual, mask, &swa)) == 0)
		THROW("Could not create window");
	XSetWMProtocols(dpy, win, &deleteAtom, 1);
	XStoreName(dpy, win, "EGL Spheres");
	XMapWindow(dpy, win);
	if(fullScreen)
	{
		XSetInputFocus(dpy, win, RevertToParent, CurrentTime);
		XGrabKeyboard(dpy, win, GrabModeAsync, True, GrabModeAsync, CurrentTime);
	}
	XSync(dpy, False);
	TRY_EGL(eglwin = eglCreateWindowSurface(edpy, c, win, NULL));

	TRY_EGL(eglBindAPI(EGL_OPENGL_API));
	TRY_EGL(ctx = eglCreateContext(edpy, c, NULL, NULL));
	XFree(v);  v = NULL;

	TRY_EGL(eglMakeCurrent(edpy, eglwin, eglwin, ctx))

	if(swapInterval > 0)
	{
		TRY_EGL(eglSwapInterval(edpy, swapInterval));
		fprintf(stderr, "Swap interval: %d frames\n", swapInterval);
	}

	fprintf(stderr, "OpenGL Renderer: %s\n", glGetString(GL_RENDERER));

	CATCH(eventLoop());

	bailout:
	if(v) XFree(v);
	if(edpy && ctx) eglDestroyContext(edpy, ctx);
	if(edpy && eglwin) eglDestroySurface(edpy, eglwin);
	if(edpy) eglTerminate(edpy);
	if(dpy && win) XDestroyWindow(dpy, win);
	if(dpy) XCloseDisplay(dpy);

	return retval;
}
