/* Copyright (C) 2005-2011, Thorvald Natvig <thorvald@natvig.com>
   Copyright (C) 2008-2015, Mikkel Krautz <mikkel@krautz.dk>

   All rights reserved.

   Redistribution and use in source and binary forms, with or without
   modification, are permitted provided that the following conditions
   are met:

   - Redistributions of source code must retain the above copyright notice,
     this list of conditions and the following disclaimer.
   - Redistributions in binary form must reproduce the above copyright notice,
     this list of conditions and the following disclaimer in the documentation
     and/or other materials provided with the distribution.
   - Neither the name of the Mumble Developers nor the names of its
     contributors may be used to endorse or promote products derived from this
     software without specific prior written permission.

   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
   A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR
   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
   EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
   PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
   PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
   LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
   NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
   SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#define GLX_GLXEXT_LEGACY
#define GL_GLEXT_PROTOTYPES
#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/ipc.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/stat.h>
#include <time.h>
#include <semaphore.h>
#include <fcntl.h>
#include <stdarg.h>
#include <pwd.h>
#include <math.h>
#include <errno.h>
#include <time.h>
#include <limits.h>

#if defined(TARGET_UNIX)
# define GLX_GLXEXT_LEGACY
# define GL_GLEXT_PROTOTYPES
# define _GNU_SOURCE
# include <GL/glx.h>
# include <GL/gl.h>
# include <GL/glext.h>

#include <link.h>

typedef unsigned char bool;
# define true 1
# define false 0
#elif defined(TARGET_MAC)
# include <OpenGL/OpenGL.h>
# include <Carbon/Carbon.h>
# include <Cocoa/Cocoa.h>
# include <AGL/AGL.h>
#
# include <objc/objc-runtime.h>
#
# include "avail_mac.h"
#endif

#include "../overlay/overlay.h"

static bool bDebug = false;
static bool bCursorAvail = false;

typedef struct _Context {
	struct _Context *next;

#if defined(TARGET_UNIX)
	Display *dpy;
	GLXDrawable draw;
#elif defined(TARGET_MAC)
	CGLContextObj cglctx;
	NSOpenGLContext *nsctx;
#endif

	unsigned int uiWidth, uiHeight;
	unsigned int uiLeft, uiRight, uiTop, uiBottom;

	struct sockaddr_un saName;
	int iSocket;
	// overlay message, temporary variable for processing from socket
	struct OverlayMsg omMsg;
	// opengl overlay texture
	GLuint texture;

	// overlay texture in shared memory
	unsigned char *a_ucTexture;
	unsigned int uiMappedLength;

	bool bValid;
	bool bMesa;

	GLuint uiProgram;

	clock_t timeT;
	unsigned int frameCount;

	GLint maxVertexAttribs;
	GLboolean* vertexAttribStates;
} Context;

static const char vshader[] = ""
                              "void main() {"
                              "gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;"
                              "gl_TexCoord[0] = gl_MultiTexCoord0;"
                              "}";

static const char fshader[] = ""
                              "uniform sampler2D tex;"
                              "void main() {"
                              "gl_FragColor = texture2D(tex, gl_TexCoord[0].st);"
                              "}";

const GLfloat fBorder[] = {0.125f, 0.250f, 0.5f, 0.75f};

static Context *contexts = NULL;

#define AVAIL(name) dlsym(RTLD_DEFAULT,#name)
#define FDEF(name) static __typeof__(&name) o##name = NULL

#if defined(TARGET_UNIX)
 FDEF(dlsym);
 FDEF(glXSwapBuffers);
 FDEF(glXGetProcAddressARB);
 FDEF(glXGetProcAddress);
#elif defined(TARGET_MAC)
 FDEF(CGLFlushDrawable);
 FDEF(CGDisplayHideCursor);
 FDEF(CGDisplayShowCursor);
#endif

__attribute__((format(printf, 1, 2)))
static void ods(const char *format, ...) {
	if (! bDebug) {
		return;
	}

	fprintf(stderr, "MumbleOverlay: ");

	va_list args;
	va_start(args, format);
	vfprintf(stderr, format, args);
	va_end(args);
	fprintf(stderr, "\n");
	fflush(stderr);
}

static void newContext(Context * ctx) {
	ctx->iSocket = -1;
	ctx->omMsg.omh.iLength = -1;
	ctx->texture = ~0U;
	ctx->timeT = clock();
	ctx->frameCount = 0;

	char *home = getenv("HOME");
	if (home == NULL) {
		struct passwd *pwent= getpwuid(getuid());
		if (pwent && pwent->pw_dir && pwent->pw_dir[0]) {
			home = pwent->pw_dir;
		}
	}

	char *xdgRuntimeDir = getenv("XDG_RUNTIME_DIR");

	if (xdgRuntimeDir != NULL) {
		ctx->saName.sun_family = PF_UNIX;
		strcpy(ctx->saName.sun_path, xdgRuntimeDir);
		strcat(ctx->saName.sun_path, "/MumbleOverlayPipe");
	} else if (home) {
		ctx->saName.sun_family = PF_UNIX;
		strcpy(ctx->saName.sun_path, home);
		strcat(ctx->saName.sun_path, "/.MumbleOverlayPipe");
	}

	ods("OpenGL Version %s, Vendor %s, Renderer %s, Shader %s", glGetString(GL_VERSION), glGetString(GL_VENDOR), glGetString(GL_RENDERER), glGetString(GL_SHADING_LANGUAGE_VERSION));

	const char *vsource = vshader;
	const char *fsource = fshader;
	char buffer[8192];
	GLint l;
	GLuint vs = glCreateShader(GL_VERTEX_SHADER);
	GLuint fs = glCreateShader(GL_FRAGMENT_SHADER);
	glShaderSource(vs, 1, &vsource, NULL);
	glShaderSource(fs, 1, &fsource, NULL);
	glCompileShader(vs);
	glCompileShader(fs);
	glGetShaderInfoLog(vs, 8192, &l, buffer);
	ods("VERTEX: %s", buffer);
	glGetShaderInfoLog(fs, 8192, &l, buffer);
	ods("FRAGMENT: %s", buffer);
	ctx->uiProgram = glCreateProgram();
	glAttachShader(ctx->uiProgram, vs);
	glAttachShader(ctx->uiProgram, fs);
	glLinkProgram(ctx->uiProgram);

	glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &ctx->maxVertexAttribs);
	ctx->vertexAttribStates = calloc((size_t) ctx->maxVertexAttribs, sizeof(GLboolean));
}

static void releaseMem(Context *ctx) {
	if (ctx->a_ucTexture) {
		munmap(ctx->a_ucTexture, ctx->uiMappedLength);
		ctx->a_ucTexture = NULL;
		ctx->uiMappedLength = 0;
	}
	if (ctx->texture != ~0U) {
		glDeleteTextures(1, &ctx->texture);
		ctx->texture = ~0U;
	}
	ctx->uiLeft = ctx->uiTop = ctx->uiRight = ctx->uiBottom = 0;
}

static void disconnect(Context *ctx) {
	releaseMem(ctx);
	ctx->uiWidth = ctx->uiHeight = 0;
	if (ctx->iSocket != -1) {
		close(ctx->iSocket);
		ctx->iSocket = -1;
	}
	ods("Disconnected");
}

static bool sendMessage(Context *ctx, struct OverlayMsg *om) {
	if (ctx->iSocket != -1) {
		size_t wantsend = sizeof(struct OverlayMsgHeader) + (size_t)om->omh.iLength;
		ssize_t sent = send(ctx->iSocket, om, wantsend, MSG_DONTWAIT);
		if (sent != -1 && wantsend == (size_t)sent) {
			return true;
		}
		ods("Short write. Disconnecting pipe.");
	}
	disconnect(ctx);
	return false;
}

static void regenTexture(Context *ctx) {
	if (ctx->texture != ~0U) {
		glDeleteTextures(1, & ctx->texture);
	}
	glGenTextures(1, &ctx->texture);

	glBindTexture(GL_TEXTURE_2D, ctx->texture);
	glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, fBorder);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, ctx->uiWidth, ctx->uiHeight, 0, GL_BGRA, GL_UNSIGNED_BYTE, ctx->a_ucTexture);
}

static void drawOverlay(Context *ctx, unsigned int width, unsigned int height) {
	// if no socket is active, initialize and connect to socket
	if (ctx->iSocket == -1) {
		releaseMem(ctx);
		if (! ctx->saName.sun_path[0])
			return;
		ctx->iSocket = socket(AF_UNIX, SOCK_STREAM, 0);
		if (ctx->iSocket == -1) {
			ods("socket() failure");
			return;
		}
		fcntl(ctx->iSocket, F_SETFL, O_NONBLOCK, 1);
		if (connect(ctx->iSocket, (struct sockaddr *)(& ctx->saName), sizeof(ctx->saName)) != 0) {
			close(ctx->iSocket);
			ctx->iSocket = -1;
			ods("connect() failure %s", ctx->saName.sun_path);
			return;
		}
		ods("Socket connected");

		struct OverlayMsg om;
		om.omh.uiMagic = OVERLAY_MAGIC_NUMBER;
		om.omh.uiType = OVERLAY_MSGTYPE_PID;
		om.omh.iLength = sizeof(struct OverlayMsgPid);
		om.omp.pid = getpid();

		if (!sendMessage(ctx, &om))
			return;

		ods("SentPid");
	}

	// if overlay size (width or height) is not up-to-date create and send an overlay initialization message
	if ((ctx->uiWidth != width) || (ctx->uiHeight != height)) {
		ods("Sending init overlay msg with w h %i %i", width, height);
		releaseMem(ctx);

		ctx->uiWidth = width;
		ctx->uiHeight = height;

		struct OverlayMsg om;
		om.omh.uiMagic = OVERLAY_MAGIC_NUMBER;
		om.omh.uiType = OVERLAY_MSGTYPE_INIT;
		om.omh.iLength = sizeof(struct OverlayMsgInit);
		om.omi.uiWidth = ctx->uiWidth;
		om.omi.uiHeight = ctx->uiHeight;

		if (! sendMessage(ctx, &om))
			return;
	}

	// receive and process overlay messages
	while (1) {
		if (ctx->omMsg.omh.iLength < 0) {
			// receive the overlay message header
			ssize_t length = recv(ctx->iSocket, ctx->omMsg.headerbuffer, sizeof(struct OverlayMsgHeader), 0);
			if (length < 0) {
				if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
					break;
				disconnect(ctx);
				return;
			} else if (length != sizeof(struct OverlayMsgHeader)) {
				ods("Short header read on overlay message");
				disconnect(ctx);
				return;
			}
		} else {
			// receive the overlay message body
			ssize_t length = recv(ctx->iSocket, ctx->omMsg.msgbuffer, (size_t)ctx->omMsg.omh.iLength, 0);
			if (length < 0) {
				if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
					break;
				disconnect(ctx);
				return;
			} else if (length != ctx->omMsg.omh.iLength) {
				ods("Short overlay message read %x %ld/%d", ctx->omMsg.omh.uiType, length, ctx->omMsg.omh.iLength);
				disconnect(ctx);
				return;
			}
			// set len to -1 again for a clean state on next receive
			ctx->omMsg.omh.iLength = -1;

			switch (ctx->omMsg.omh.uiType) {
				// shared memory overlay message:
				case OVERLAY_MSGTYPE_SHMEM: {
						struct OverlayMsgShmem *oms = (struct OverlayMsgShmem *) & ctx->omMsg.omi;
						ods("SHMEM %s", oms->a_cName);
						releaseMem(ctx);
						int fd = shm_open(oms->a_cName, O_RDONLY, 0600);
						if (fd != -1) {
							struct stat buf;
							
							if (fstat(fd, &buf) != -1) {
								if (buf.st_size >= ctx->uiWidth * ctx->uiHeight * 4
								        && buf.st_size < 512 * 1024 * 1024) {
									ctx->uiMappedLength = (unsigned int)buf.st_size;
									ctx->a_ucTexture = mmap(NULL, (size_t)buf.st_size, PROT_READ, MAP_SHARED, fd, 0);
									if (ctx->a_ucTexture != MAP_FAILED) {
										// mmap successfull; send a new bodyless sharedmemory overlay message and regenerate the overlay texture
										struct OverlayMsg om;
										om.omh.uiMagic = OVERLAY_MAGIC_NUMBER;
										om.omh.uiType = OVERLAY_MSGTYPE_SHMEM;
										om.omh.iLength = 0;
	
										if (! sendMessage(ctx, &om))
											return;
	
										regenTexture(ctx);
										continue;
									}
									ctx->a_ucTexture = NULL;
								}
								ctx->uiMappedLength = 0;
							} else {
								ods("Failed to fstat memory map");
							}
							close(fd);
						}
						ods("Failed to map memory");
					}
					break;
				// blit overlay message: blit overlay texture from shared memory to gl-texture var
				case OVERLAY_MSGTYPE_BLIT: {
						struct OverlayMsgBlit *omb = & ctx->omMsg.omb;
						ods("BLIT %d %d %d %d", omb->x, omb->y, omb->w, omb->h);
						if ((ctx->a_ucTexture != NULL) && (ctx->texture != ~0U)) {
							glBindTexture(GL_TEXTURE_2D, ctx->texture);

							if ((omb->x == 0) && (omb->y == 0) && (omb->w == ctx->uiWidth) && (omb->h == ctx->uiHeight)) {
								ods("Optimzied fullscreen blit");
								glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, ctx->uiWidth, ctx->uiHeight, 0, GL_BGRA, GL_UNSIGNED_BYTE, ctx->a_ucTexture);
							} else {
								// allocate temporary memory
								unsigned int x = omb->x;
								unsigned int y = omb->y;
								unsigned int w = omb->w;
								unsigned int h = omb->h;
								unsigned char *ptr = (unsigned char *) malloc(w*h*4);
								unsigned int row;
								memset(ptr, 0, w * h * 4);

								// copy overlay texture to temporary memory to adapt to full opengl ui size (overlay at correct place)
								for (row = 0; row < h; ++row) {
									const unsigned char *sptr = ctx->a_ucTexture + 4 * ((y+row) * ctx->uiWidth + x);
									unsigned char *dptr = ptr + 4 * w * row;
									memcpy(dptr, sptr, w * 4);
								}

								// copy temporary texture to opengl
								glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, w, h, GL_BGRA, GL_UNSIGNED_BYTE, ptr);
								free(ptr);
							}
						}
					}
					break;
				case OVERLAY_MSGTYPE_ACTIVE: {
						struct OverlayMsgActive *oma = & ctx->omMsg.oma;
						ods("ACTIVE %d %d %d %d", oma->x, oma->y, oma->w, oma->h);
						ctx->uiLeft = oma->x;
						ctx->uiTop = oma->y;
						ctx->uiRight = oma->x + oma->w;
						ctx->uiBottom = oma->y + oma->h;
					}
					break;
				case OVERLAY_MSGTYPE_INTERACTIVE: {
#if defined(TARGET_MAC)
						struct OverlayMsgInteractive *omin = & ctx->omMsg.omin;
						ods("Interactive %d", omin->state);
						if (bCursorAvail) {
							if (omin->state) {
								oCGDisplayHideCursor(kCGNullDirectDisplay);
							} else {
								oCGDisplayShowCursor(kCGNullDirectDisplay);
							}
						}
#endif
					}
					break;
				default:
					break;
			}
		}
	}

	if ((ctx->a_ucTexture == NULL) || (ctx->texture == ~0U))
		return;

	// texture checks, that our gltexture is still valid and sane
	if (! glIsTexture(ctx->texture)) {
		ctx->texture = ~0U;
		ods("Lost texture");
		regenTexture(ctx);
	} else {
		glBindTexture(GL_TEXTURE_2D, ctx->texture);
		GLfloat bordercolor[4];
		glGetTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, bordercolor);
		if (bordercolor[0] != fBorder[0]
		    || bordercolor[1] != fBorder[1]
		    || bordercolor[2] != fBorder[2]
		    || bordercolor[3] != fBorder[3]) {
			ods("Texture was hijacked! Texture will be regenerated.");
			regenTexture(ctx);
		}
	}

	glBindTexture(GL_TEXTURE_2D, ctx->texture);
	glPushMatrix();

	float w = (float)(ctx->uiWidth);
	float h = (float)(ctx->uiHeight);

	float left   = (float)(ctx->uiLeft);
	float top    = (float)(ctx->uiTop);
	float right  = (float)(ctx->uiRight);
	float bottom = (float)(ctx->uiBottom);

	float xm  = left   / w;
	float ym  = top    / h;
	float xmx = right  / w;
	float ymx = bottom / h;


	GLfloat vertex[] = {left, bottom,
	        left, top,
	        right, top,
	        right, bottom};
	GLfloat tex[] = {xm, ymx, xm, ym, xmx, ym, xmx, ymx};
	glVertexPointer(2, GL_FLOAT, 0, vertex);
	glTexCoordPointer(2, GL_FLOAT, 0, tex);
	glDrawArrays(GL_QUADS, 0, 4);

	glPopMatrix();
}

static void drawContext(Context * ctx, int width, int height) {
	// calculate FPS and send it as an overlay message
	clock_t t = clock();
	float elapsed = (float)(t - ctx->timeT) / CLOCKS_PER_SEC;
	++(ctx->frameCount);
	if (elapsed > OVERLAY_FPS_INTERVAL) {
		struct OverlayMsg om;
		om.omh.uiMagic = OVERLAY_MAGIC_NUMBER;
		om.omh.uiType = OVERLAY_MSGTYPE_FPS;
		om.omh.iLength = sizeof(struct OverlayMsgFps);
		om.omf.fps = (float)ctx->frameCount / elapsed;

		sendMessage(ctx, &om);

		ctx->frameCount = 0;
		ctx->timeT = t;
	}

	GLuint program;
	GLint viewport[4];
	int i;

	glPushAttrib(GL_ALL_ATTRIB_BITS);
	glPushClientAttrib(GL_ALL_ATTRIB_BITS);
	glGetIntegerv(GL_VIEWPORT, viewport);
	glGetIntegerv(GL_CURRENT_PROGRAM, (GLint *)&program);

	glViewport(0, 0, width, height);

	glMatrixMode(GL_PROJECTION);
	glPushMatrix();
	glLoadIdentity();
	glOrtho(0, width, height, 0, -100.0, 100.0);

	glMatrixMode(GL_MODELVIEW);
	glPushMatrix();
	glLoadIdentity();

	glMatrixMode(GL_TEXTURE);
	glPushMatrix();
	glLoadIdentity();

	glDisable(GL_ALPHA_TEST);
	glDisable(GL_AUTO_NORMAL);
	// Skip clip planes, there are thousands of them.
	glDisable(GL_COLOR_LOGIC_OP);
	glDisable(GL_COLOR_TABLE);
	glDisable(GL_CONVOLUTION_1D);
	glDisable(GL_CONVOLUTION_2D);
	glDisable(GL_CULL_FACE);
	glDisable(GL_DEPTH_TEST);
	glDisable(GL_DITHER);
	glDisable(GL_FOG);
	glDisable(GL_HISTOGRAM);
	glDisable(GL_INDEX_LOGIC_OP);
	glDisable(GL_LIGHTING);
	glDisable(GL_NORMALIZE);
	// Skip line smmooth
	// Skip map
	glDisable(GL_MINMAX);
	// Skip polygon offset
	glDisable(GL_SEPARABLE_2D);
	glDisable(GL_SCISSOR_TEST);
	glDisable(GL_STENCIL_TEST);

	GLboolean b = 0;
	glGetBooleanv(GL_TEXTURE_GEN_Q, &b);
	if (b)
		glDisable(GL_TEXTURE_GEN_Q);
	glGetBooleanv(GL_TEXTURE_GEN_R, &b);
	if (b)
		glDisable(GL_TEXTURE_GEN_R);
	glGetBooleanv(GL_TEXTURE_GEN_S, &b);
	if (b)
		glDisable(GL_TEXTURE_GEN_S);
	glGetBooleanv(GL_TEXTURE_GEN_T, &b);
	if (b)
		glDisable(GL_TEXTURE_GEN_T);

	glRenderMode(GL_RENDER);

	glDisableClientState(GL_VERTEX_ARRAY);
	glDisableClientState(GL_NORMAL_ARRAY);
	glDisableClientState(GL_COLOR_ARRAY);
	glDisableClientState(GL_INDEX_ARRAY);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	glDisableClientState(GL_EDGE_FLAG_ARRAY);

	glPixelStorei(GL_UNPACK_SWAP_BYTES, 0);
	glPixelStorei(GL_UNPACK_LSB_FIRST, 0);
	glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
	glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
	glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
	glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

	GLint texunits = 1;

	glGetIntegerv(GL_MAX_TEXTURE_UNITS, &texunits);

	for (i=texunits-1;i>=0;--i) {
		glActiveTexture(GL_TEXTURE0 + i);
		glDisable(GL_TEXTURE_1D);
		glDisable(GL_TEXTURE_2D);
		glDisable(GL_TEXTURE_3D);
	}

	glDisable(GL_TEXTURE_CUBE_MAP);
	glDisable(GL_VERTEX_PROGRAM_ARB);
	glDisable(GL_FRAGMENT_PROGRAM_ARB);

	GLint enabled;
	for (i=0;i<ctx->maxVertexAttribs;++i) {
		enabled = GL_FALSE;
		glGetVertexAttribiv((GLuint)i, GL_VERTEX_ATTRIB_ARRAY_ENABLED, &enabled);
		if (enabled == GL_TRUE) {
			glDisableVertexAttribArray((GLuint)i);
			ctx->vertexAttribStates[i] = GL_TRUE;
		}
	}

	glUseProgram(ctx->uiProgram);

	glEnable(GL_COLOR_MATERIAL);
	glEnable(GL_TEXTURE_2D);
	glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
	glEnable(GL_BLEND);
	glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
	glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

	glMatrixMode(GL_MODELVIEW);

	GLint uni = glGetUniformLocation(ctx->uiProgram, "tex");
	glUniform1i(uni, 0);

	glEnableClientState(GL_VERTEX_ARRAY);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);

	GLuint bound = 0, vbobound = 0;
	glGetIntegerv(GL_PIXEL_UNPACK_BUFFER_BINDING, (GLint *)&bound);
	glGetIntegerv(GL_ARRAY_BUFFER_BINDING, (GLint *)&vbobound);

	if (bound != 0) {
		glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, 0);
	}
	if (vbobound != 0) {
		glBindBuffer(GL_ARRAY_BUFFER, 0);
	}

	drawOverlay(ctx, width, height);

	if (bound != 0) {
		glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, bound);
	}
	if (vbobound != 0) {
		glBindBuffer(GL_ARRAY_BUFFER, vbobound);
	}

	for (i=0;i<ctx->maxVertexAttribs;++i) {
		if (ctx->vertexAttribStates[i] == GL_TRUE) {
			glEnableVertexAttribArray((GLuint)i);
			ctx->vertexAttribStates[i] = GL_FALSE;
		}
	}

	glMatrixMode(GL_TEXTURE);
	glPopMatrix();

	glMatrixMode(GL_MODELVIEW);
	glPopMatrix();

	glMatrixMode(GL_PROJECTION);
	glPopMatrix();

	glPopClientAttrib();
	glPopAttrib();
	glViewport(viewport[0], viewport[1], viewport[2], viewport[3]);
	glUseProgram(program);

	// drain opengl error queue
	while (glGetError() != GL_NO_ERROR);
}

#if defined(TARGET_UNIX)
# include "init_unix.c"
#elif defined(TARGET_MAC)
# include "init_mac.c"
#endif
