// Copyright 2007-2023 The Mumble Developers. All rights reserved.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file at the root of the
// Mumble source tree or at <https://www.mumble.info/LICENSE>.

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

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

#	include <link.h>

#elif defined(TARGET_MAC)
#	include <AGL/agl.h>
#	include <Carbon/Carbon.h>
#	include <Cocoa/Cocoa.h>
#	include <OpenGL/OpenGL.h>
#
#	include <objc/objc-runtime.h>
#
#	include "mach_override.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");
	const char *overlayPipeXdgDir  = "/MumbleOverlayPipe";
	const char *overlayPipeHomeDir = "/.MumbleOverlayPipe";
	// ctx->saName.sun_path is a statically sized char array, therefore sizeof is correct here
	size_t sunPathBufLen = sizeof(ctx->saName.sun_path) / sizeof(ctx->saName.sun_path[0]);

	if (xdgRuntimeDir != NULL && strlen(xdgRuntimeDir) + strlen(overlayPipeXdgDir) < sunPathBufLen) {
		ctx->saName.sun_family = PF_UNIX;
		strcpy(ctx->saName.sun_path, xdgRuntimeDir);
		strcat(ctx->saName.sun_path, overlayPipeXdgDir);
	} else if (home && strlen(home) + strlen(overlayPipeHomeDir) < sunPathBufLen) {
		ctx->saName.sun_family = PF_UNIX;
		strcpy(ctx->saName.sun_path, home);
		strcat(ctx->saName.sun_path, overlayPipeHomeDir);
	}

	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, (GLsizei) ctx->uiWidth, (GLsizei) 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     = (unsigned int) getpid(); // getpid can't fail

		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 %zd/%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) {
							unsigned int buflen = buf.st_size;
							if (buflen >= ctx->uiWidth * ctx->uiHeight * 4 && buflen < 512 * 1024 * 1024) {
								ctx->uiMappedLength = buflen;
								ctx->a_ucTexture    = mmap(NULL, (size_t) buflen, 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, (GLsizei) ctx->uiWidth, (GLsizei) 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, (GLint) x, (GLint) y, (GLint) w, (GLint) 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,

						 left, bottom, right, top, right, bottom };
	glVertexPointer(2, GL_FLOAT, 0, vertex);

	GLfloat tex[] = { xm, ymx, xm,  ym, xmx, ym,

					  xm, ymx, xmx, ym, xmx, ymx };
	glTexCoordPointer(2, GL_FLOAT, 0, tex);

	glDrawArrays(GL_TRIANGLES, 0, 6);

	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();

	glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);

	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 + (GLenum) 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, (unsigned int) width, (unsigned int) 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
