// Copyright 2005-2019 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 <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 "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");

	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, (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
