package net.lax1dude.eaglercraft.glemu;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.HashMap;

import net.lax1dude.eaglercraft.EaglerAdapter;
import net.lax1dude.eaglercraft.adapter.EaglerAdapterImpl2;
import net.lax1dude.eaglercraft.glemu.StreamBuffer.StreamBufferInstance;
import net.lax1dude.eaglercraft.glemu.vector.Matrix4f;
import net.lax1dude.eaglercraft.glemu.vector.Vector3f;
import net.lax1dude.eaglercraft.glemu.vector.Vector4f;

public class EaglerAdapterGL30 extends EaglerAdapterImpl2 {

	public static final int GL_ZERO = RealOpenGLEnums.GL_ZERO;
	public static final int GL_ONE = RealOpenGLEnums.GL_ONE;
	public static final int GL_TEXTURE_2D = RealOpenGLEnums.GL_TEXTURE_2D;
	public static final int GL_SMOOTH = RealOpenGLEnums.GL_SMOOTH;
	public static final int GL_DEPTH_TEST = RealOpenGLEnums.GL_DEPTH_TEST;
	public static final int GL_LEQUAL = RealOpenGLEnums.GL_LEQUAL;
	public static final int GL_ALPHA_TEST = RealOpenGLEnums.GL_ALPHA_TEST;
	public static final int GL_GREATER = RealOpenGLEnums.GL_GREATER;
	public static final int GL_BACK = RealOpenGLEnums.GL_BACK;
	public static final int GL_PROJECTION = RealOpenGLEnums.GL_PROJECTION;
	public static final int GL_MODELVIEW = RealOpenGLEnums.GL_MODELVIEW;
	public static final int GL_COLOR_BUFFER_BIT = RealOpenGLEnums.GL_COLOR_BUFFER_BIT;
	public static final int GL_DEPTH_BUFFER_BIT = RealOpenGLEnums.GL_DEPTH_BUFFER_BIT;
	public static final int GL_LIGHTING = RealOpenGLEnums.GL_LIGHTING;
	public static final int GL_FOG = RealOpenGLEnums.GL_FOG;
	public static final int GL_COLOR_MATERIAL = RealOpenGLEnums.GL_COLOR_MATERIAL;
	public static final int GL_BLEND = RealOpenGLEnums.GL_BLEND;
	public static final int GL_RGBA = RealOpenGLEnums.GL_RGBA;
	public static final int GL_UNSIGNED_BYTE = RealOpenGLEnums.GL_UNSIGNED_BYTE;
	public static final int GL_TEXTURE_WIDTH = RealOpenGLEnums.GL_TEXTURE_WIDTH;
	public static final int GL_LIGHT0 = RealOpenGLEnums.GL_LIGHT0;
	public static final int GL_LIGHT1 = RealOpenGLEnums.GL_LIGHT1;
	public static final int GL_POSITION = RealOpenGLEnums.GL_POSITION;
	public static final int GL_DIFFUSE = RealOpenGLEnums.GL_DIFFUSE;
	public static final int GL_SPECULAR = RealOpenGLEnums.GL_SPECULAR;
	public static final int GL_AMBIENT = RealOpenGLEnums.GL_AMBIENT;
	public static final int GL_FLAT = RealOpenGLEnums.GL_FLAT;
	public static final int GL_LIGHT_MODEL_AMBIENT = RealOpenGLEnums.GL_LIGHT_MODEL_AMBIENT;
	public static final int GL_FRONT_AND_BACK = RealOpenGLEnums.GL_FRONT_AND_BACK;
	public static final int GL_AMBIENT_AND_DIFFUSE = RealOpenGLEnums.GL_AMBIENT_AND_DIFFUSE;
	public static final int GL_MODELVIEW_MATRIX = RealOpenGLEnums.GL_MODELVIEW_MATRIX;
	public static final int GL_PROJECTION_MATRIX = RealOpenGLEnums.GL_PROJECTION_MATRIX;
	public static final int GL_VIEWPORT = RealOpenGLEnums.GL_VIEWPORT;
	public static final int GL_RESCALE_NORMAL = RealOpenGLEnums.GL_RESCALE_NORMAL;
	public static final int GL_SRC_ALPHA = RealOpenGLEnums.GL_SRC_ALPHA;
	public static final int GL_ONE_MINUS_SRC_ALPHA = RealOpenGLEnums.GL_ONE_MINUS_SRC_ALPHA;
	public static final int GL_ONE_MINUS_DST_COLOR = RealOpenGLEnums.GL_ONE_MINUS_DST_COLOR;
	public static final int GL_ONE_MINUS_SRC_COLOR = RealOpenGLEnums.GL_ONE_MINUS_SRC_COLOR;
	public static final int GL_CULL_FACE = RealOpenGLEnums.GL_CULL_FACE;
	public static final int GL_TEXTURE_MIN_FILTER = RealOpenGLEnums.GL_TEXTURE_MIN_FILTER;
	public static final int GL_TEXTURE_MAG_FILTER = RealOpenGLEnums.GL_TEXTURE_MAG_FILTER;
	public static final int GL_LINEAR = RealOpenGLEnums.GL_LINEAR;
	public static final int GL_COLOR_LOGIC_OP = RealOpenGLEnums.GL_COLOR_LOGIC_OP;
	public static final int GL_OR_REVERSE = RealOpenGLEnums.GL_OR_REVERSE;
	public static final int GL_EQUAL = RealOpenGLEnums.GL_EQUAL;
	public static final int GL_SRC_COLOR = RealOpenGLEnums.GL_SRC_COLOR;
	public static final int GL_TEXTURE = RealOpenGLEnums.GL_TEXTURE;
	public static final int GL_FRONT = RealOpenGLEnums.GL_FRONT;
	public static final int GL_COMPILE = RealOpenGLEnums.GL_COMPILE;
	public static final int GL_S = RealOpenGLEnums.GL_S;
	public static final int GL_T = RealOpenGLEnums.GL_T;
	public static final int GL_R = RealOpenGLEnums.GL_R;
	public static final int GL_Q = RealOpenGLEnums.GL_Q;
	public static final int GL_TEXTURE_GEN_S = RealOpenGLEnums.GL_TEXTURE_GEN_S;
	public static final int GL_TEXTURE_GEN_T = RealOpenGLEnums.GL_TEXTURE_GEN_T;
	public static final int GL_TEXTURE_GEN_R = RealOpenGLEnums.GL_TEXTURE_GEN_R;
	public static final int GL_TEXTURE_GEN_Q = RealOpenGLEnums.GL_TEXTURE_GEN_Q;
	public static final int GL_TEXTURE_GEN_MODE = RealOpenGLEnums.GL_TEXTURE_GEN_MODE;
	public static final int GL_OBJECT_PLANE = RealOpenGLEnums.GL_OBJECT_PLANE;
	public static final int GL_EYE_PLANE = RealOpenGLEnums.GL_EYE_PLANE;
	public static final int GL_OBJECT_LINEAR = RealOpenGLEnums.GL_OBJECT_LINEAR;
	public static final int GL_EYE_LINEAR = RealOpenGLEnums.GL_EYE_LINEAR;
	public static final int GL_NEAREST = RealOpenGLEnums.GL_NEAREST;
	public static final int GL_CLAMP = RealOpenGLEnums.GL_CLAMP_TO_EDGE;
	public static final int GL_TEXTURE_WRAP_S = RealOpenGLEnums.GL_TEXTURE_WRAP_S;
	public static final int GL_TEXTURE_WRAP_T = RealOpenGLEnums.GL_TEXTURE_WRAP_T;
	public static final int GL_REPEAT = RealOpenGLEnums.GL_REPEAT;
	public static final int GL_BGRA = RealOpenGLEnums.GL_BGRA;
	public static final int GL_UNSIGNED_INT_8_8_8_8_REV = RealOpenGLEnums.GL_UNSIGNED_INT_8_8_8_8_REV;
	public static final int GL_DST_COLOR = RealOpenGLEnums.GL_DST_COLOR;
	public static final int GL_CONSTANT_COLOR = RealOpenGLEnums.GL_CONSTANT_COLOR;
	public static final int GL_ONE_MINUS_CONSTANT_COLOR = RealOpenGLEnums.GL_ONE_MINUS_CONSTANT_COLOR;
	public static final int GL_CONSTANT_ALPHA = RealOpenGLEnums.GL_CONSTANT_ALPHA;
	public static final int GL_ONE_MINUS_CONSTANT_ALPHA = RealOpenGLEnums.GL_ONE_MINUS_CONSTANT_ALPHA;
	public static final int GL_POLYGON_OFFSET_FILL = RealOpenGLEnums.GL_POLYGON_OFFSET_FILL;
	public static final int GL_NORMALIZE = RealOpenGLEnums.GL_NORMALIZE;
	public static final int GL_DST_ALPHA = RealOpenGLEnums.GL_DST_ALPHA;
	public static final int GL_FLOAT = RealOpenGLEnums.GL_FLOAT;
	public static final int GL_TEXTURE_COORD_ARRAY = RealOpenGLEnums.GL_TEXTURE_COORD_ARRAY;
	public static final int GL_SHORT = RealOpenGLEnums.GL_SHORT;
	public static final int GL_COLOR_ARRAY = RealOpenGLEnums.GL_COLOR_ARRAY;
	public static final int GL_VERTEX_ARRAY = RealOpenGLEnums.GL_VERTEX_ARRAY;
	public static final int GL_TRIANGLES = RealOpenGLEnums.GL_TRIANGLES;
	public static final int GL_NORMAL_ARRAY = RealOpenGLEnums.GL_NORMAL_ARRAY;
	public static final int GL_TEXTURE_3D = RealOpenGLEnums.GL_TEXTURE_3D;
	public static final int GL_FOG_MODE = RealOpenGLEnums.GL_FOG_MODE;
	public static final int GL_EXP = RealOpenGLEnums.GL_EXP;
	public static final int GL_FOG_DENSITY = RealOpenGLEnums.GL_FOG_DENSITY;
	public static final int GL_FOG_START = RealOpenGLEnums.GL_FOG_START;
	public static final int GL_FOG_END = RealOpenGLEnums.GL_FOG_END;
	public static final int GL_FOG_COLOR = RealOpenGLEnums.GL_FOG_COLOR;
	public static final int GL_TRIANGLE_STRIP = RealOpenGLEnums.GL_TRIANGLE_STRIP;
	public static final int GL_PACK_ALIGNMENT = RealOpenGLEnums.GL_PACK_ALIGNMENT;
	public static final int GL_UNPACK_ALIGNMENT = RealOpenGLEnums.GL_UNPACK_ALIGNMENT;
	public static final int GL_QUADS = RealOpenGLEnums.GL_QUADS;
	public static final int GL_TEXTURE0 = RealOpenGLEnums.GL_TEXTURE0;
	public static final int GL_TEXTURE1 = RealOpenGLEnums.GL_TEXTURE1;
	public static final int GL_TEXTURE2 = RealOpenGLEnums.GL_TEXTURE2;
	public static final int GL_TEXTURE3 = RealOpenGLEnums.GL_TEXTURE3;
	public static final int GL_INVALID_ENUM = RealOpenGLEnums.GL_INVALID_ENUM;
	public static final int GL_INVALID_VALUE = RealOpenGLEnums.GL_INVALID_VALUE;
	public static final int GL_INVALID_OPERATION = RealOpenGLEnums.GL_INVALID_OPERATION;
	public static final int GL_OUT_OF_MEMORY = RealOpenGLEnums.GL_OUT_OF_MEMORY;
	public static final int GL_CONTEXT_LOST_WEBGL = -100;
	public static final int GL_TRIANGLE_FAN = RealOpenGLEnums.GL_TRIANGLE_FAN;
	public static final int GL_LINE_STRIP = RealOpenGLEnums.GL_LINE_STRIP;
	public static final int EAG_SWAP_RB = -101;
	public static final int GL_LINES = RealOpenGLEnums.GL_LINES;
	public static final int GL_NEAREST_MIPMAP_LINEAR = RealOpenGLEnums.GL_NEAREST_MIPMAP_LINEAR;
	public static final int GL_TEXTURE_MAX_ANISOTROPY = -103;
	public static final int GL_TEXTURE_MAX_LEVEL = RealOpenGLEnums.GL_TEXTURE_MAX_LEVEL;
	public static final int GL_LINEAR_MIPMAP_LINEAR = RealOpenGLEnums.GL_LINEAR_MIPMAP_LINEAR;
	public static final int GL_LINEAR_MIPMAP_NEAREST = RealOpenGLEnums.GL_LINEAR_MIPMAP_NEAREST;
	public static final int GL_NEAREST_MIPMAP_NEAREST = RealOpenGLEnums.GL_NEAREST_MIPMAP_NEAREST;

	public static final boolean isWebGL = _wisWebGL();

	static final GLObjectMap<TextureGL> texObjects = new GLObjectMap<>(256);

	static boolean enableTexture2D = false;
	static boolean enableTexture2D_1 = false;
	static boolean enableLighting = false;
	static boolean enableAlphaTest = false;
	static float alphaThresh = 0.1f;

	static boolean isDepthTest = false;
	static int currentDepthFunc = -99999;
	static boolean isCullFace = false;
	static int currentCullFace = -99999;
	static boolean isPolygonOffset = false;
	static float polygonOffset1 = -999.9f;
	static float polygonOffset2 = -999.9f;
	static boolean isBlend = false;
	static int blendSRC = 0;
	static int blendDST = 0;
	static int colorMask = 15;
	static boolean isDepthMask = true;

	static boolean isCompilingDisplayList = false;
	static DisplayList compilingDisplayList = null;

	static boolean enableColorArray = false;
	static boolean enableNormalArray = false;
	static boolean enableTex0Array = false;
	static boolean enableTex1Array = false;

	static boolean enableAnisotropicFix = false;
	static int anisotropicFixSerial = 0;
	static float anisotropicFixX = 1024.0f;
	static float anisotropicFixY = 1024.0f;

	static int colorSerial = 0;
	static float colorR = 1.0f;
	static float colorG = 1.0f;
	static float colorB = 1.0f;
	static float colorA = 1.0f;

	static int normalSerial = 0;
	static float normalX = 1.0f;
	static float normalY = 0.0f;
	static float normalZ = 0.0f;

	static int selectedTex = 0;
	static int selectedClientTex = 0;
	static int[] boundTexI = new int[2];
	static TextureGL[] boundTex = new TextureGL[2];
	static int tex0Serial = 0;
	static float tex0X = 0;
	static float tex0Y = 0;
	static int tex1Serial = 0;
	static float tex1X = 0;
	static float tex1Y = 0;
	static TextureGL boundTexture0 = null;
	static boolean enableAnisotropicPatch = false;
	static boolean hintAnisotropicPatch = false;
	static boolean swapRB = false;

	public static final void anisotropicPatch(boolean e) {
		enableAnisotropicPatch = e;
	}

	static boolean enableTexGen = false;
	static boolean enableColorMaterial = false;

	static int texPlaneSerial = 0;
	static int texSSerial = 0;
	static int texS_plane = 0;
	static float texS_X = 0.0f;
	static float texS_Y = 0.0f;
	static float texS_Z = 0.0f;
	static float texS_W = 0.0f;

	static int texTSerial = 0;
	static int texT_plane = 0;
	static float texT_X = 0.0f;
	static float texT_Y = 0.0f;
	static float texT_Z = 0.0f;
	static float texT_W = 0.0f;

	static int texRSerial = 0;
	static int texR_plane = 0;
	static float texR_X = 0.0f;
	static float texR_Y = 0.0f;
	static float texR_Z = 0.0f;
	static float texR_W = 0.0f;

	static int texQSerial = 0;
	static int texQ_plane = 0;
	static float texQ_X = 0.0f;
	static float texQ_Y = 0.0f;
	static float texQ_Z = 0.0f;
	static float texQ_W = 0.0f;

	static int fogColorSerial = 0;
	static float fogColorR = 1.0f;
	static float fogColorG = 1.0f;
	static float fogColorB = 1.0f;
	static float fogColorA = 1.0f;
	static int fogCfgSerial = 0;
	static int fogMode = 1;
	static boolean fogEnabled = false;
	static float fogStart = 1.0f;
	static float fogEnd = 1.0f;
	static float fogDensity = 1.0f;

	static int bytesUploaded = 0;
	static int vertexDrawn = 0;
	static int triangleDrawn = 0;

	static int matrixMode = GL_MODELVIEW;

	static int matModelSerialCounter = 0;
	static int[] matModelVSerial = new int[32];
	static Matrix4f[] matModelV = new Matrix4f[32];
	static int matModelPointer = 0;

	static int matProjSerialCounter = 0;
	static int[] matProjVSerial = new int[6];
	static Matrix4f[] matProjV = new Matrix4f[6];
	static int matProjPointer = 0;

	static int matTexSerialCounter = 0;
	static int[] matTexVSerial = new int[16];
	static Matrix4f[] matTexV = new Matrix4f[16];
	static int matTexPointer = 0;

	static {
		for (int i = 0; i < matModelV.length; ++i) {
			matModelV[i] = new Matrix4f();
		}
		for (int i = 0; i < matProjV.length; ++i) {
			matProjV[i] = new Matrix4f();
		}
		for (int i = 0; i < matTexV.length; ++i) {
			matTexV[i] = new Matrix4f();
		}
	}

	public static void glClearStack() {
		matModelV[0].load(matModelV[matModelPointer]);
		matModelPointer = 0;
		matProjV[0].load(matProjV[matProjPointer]);
		matProjPointer = 0;
		matTexV[0].load(matTexV[matTexPointer]);
		matTexPointer = 0;
	}

	private static BufferGL quadsToTrianglesBuffer = null;
	private static BufferArrayGL currentArray = null;

	private static class DisplayList {
		private final int id;
		private BufferArrayGL glarray;
		private BufferGL glbuffer;
		private int shaderMode;
		private int listLength;

		private DisplayList(int id) {
			this.id = id;
			this.glarray = null;
			this.glbuffer = null;
			this.shaderMode = -1;
			this.listLength = 0;
		}
	}

	private static final HashMap<Integer, DisplayList> displayLists = new HashMap<>();
	private static final HashMap<Integer, DisplayList> displayListsInitialized = new HashMap<>();

	public static final int getDisplayListCount() {
		return displayListsInitialized.size();
	}

	public static final void glEnable(int p1) {
		switch (p1) {
		case GL_DEPTH_TEST:
			if(!isDepthTest) {
				_wglEnable(_wGL_DEPTH_TEST);
				isDepthTest = true;
			}
			break;
		case GL_CULL_FACE:
			if(!isCullFace) {
				_wglEnable(_wGL_CULL_FACE);
				isCullFace = true;
			}
			break;
		case GL_BLEND:
			if(!isBlend) {
				_wglEnable(_wGL_BLEND);
				isBlend = true;
			}
			break;
		case GL_RESCALE_NORMAL:
			break;
		case GL_TEXTURE_2D:
			if (selectedTex == 0) {
				enableTexture2D = true;
			}
			if (selectedTex == 1) {
				enableTexture2D_1 = true;
			}
			break;
		case GL_LIGHTING:
			enableLighting = true;
			break;
		case GL_ALPHA_TEST:
			enableAlphaTest = true;
			break;
		case GL_FOG:
			fogEnabled = true;
			break;
		case GL_COLOR_MATERIAL:
			enableColorMaterial = true;
			break;
		case GL_TEXTURE_GEN_S:
		case GL_TEXTURE_GEN_T:
		case GL_TEXTURE_GEN_R:
		case GL_TEXTURE_GEN_Q:
			enableTexGen = true;
			break;
		case GL_POLYGON_OFFSET_FILL:
			if(!isPolygonOffset) {
				_wglEnable(_wGL_POLYGON_OFFSET_FILL);
				isPolygonOffset = true;
			}
			break;
		case EAG_SWAP_RB:
			swapRB = true;
			break;
		default:
			break;
		}
	}

	public static final void glShadeModel(int p1) {

	}

	public static final void glClearDepth(float p1) {
		_wglClearDepth(-p1);
	}

	public static final void glDepthFunc(int p1) {
		int f = _wGL_GEQUAL;
		switch (p1) {
		case GL_GREATER:
			f = _wGL_LESS;
			break;
		case GL_LEQUAL:
			f = _wGL_GEQUAL;
			break;
		case GL_EQUAL:
			f = _wGL_EQUAL;
		default:
			break;
		}
		if(f != currentDepthFunc) {
			_wglDepthFunc(f);
			currentDepthFunc = f;
		}
	}

	public static final void glAlphaFunc(int p1, float p2) {
		alphaThresh = p2;
	}

	public static final void glCullFace(int p1) {
		if(p1 != currentCullFace) {
			_wglCullFace(p1);
			currentCullFace = p1;
		}
	}

	public static final void glMatrixMode(int p1) {
		matrixMode = p1;
	}

	private static final Matrix4f getMatrixIncrSerial() {
		switch (matrixMode) {
		case GL_MODELVIEW:
		default:
			matModelVSerial[matModelPointer] = ++matModelSerialCounter;
			return matModelV[matModelPointer];
		case GL_PROJECTION:
			matProjVSerial[matProjPointer] = ++matProjSerialCounter;
			return matProjV[matProjPointer];
		case GL_TEXTURE:
			matTexVSerial[matTexPointer] = ++matTexSerialCounter;
			return matTexV[matTexPointer];
		}
	}

	public static final void glLoadIdentity() {
		getMatrixIncrSerial().setIdentity();
	}

	public static final void glViewport(int p1, int p2, int p3, int p4) {
		_wglViewport(p1, p2, p3, p4);
	}

	public static final void glClear(int p1) {
		_wglClear(p1);
	}

	public static final void glOrtho(float left, float right, float bottom, float top, float zNear, float zFar) {
		Matrix4f res = getMatrixIncrSerial();
		res.m00 = 2.0f / (right - left);
		res.m01 = 0.0f;
		res.m02 = 0.0f;
		res.m03 = 0.0f;
		res.m10 = 0.0f;
		res.m11 = 2.0f / (top - bottom);
		res.m12 = 0.0f;
		res.m13 = 0.0f;
		res.m20 = 0.0f;
		res.m21 = 0.0f;
		res.m22 = 2.0f / (zFar - zNear);
		res.m23 = 0.0f;
		res.m30 = -(right + left) / (right - left);
		res.m31 = -(top + bottom) / (top - bottom);
		res.m32 = (zFar + zNear) / (zFar - zNear);
		res.m33 = 1.0f;
	}

	private static final Vector3f deevis = new Vector3f();

	public static final void glTranslatef(float p1, float p2, float p3) {
		deevis.set(p1, p2, p3);
		getMatrixIncrSerial().translate(deevis);
		if (isCompilingDisplayList) {
			System.err.println("matrix is not supported while recording display list use tessellator class instead");
		}
	}

	public static final void glClearColor(float p1, float p2, float p3, float p4) {
		_wglClearColor(p1, p2, p3, p4);
	}

	public static final void glDisable(int p1) {
		switch (p1) {
		case GL_DEPTH_TEST:
			if(isDepthTest) {
				_wglDisable(_wGL_DEPTH_TEST);
				isDepthTest = false;
			}
			break;
		case GL_CULL_FACE:
			if(isCullFace) {
				_wglDisable(_wGL_CULL_FACE);
				isCullFace = false;
			}
			break;
		case GL_BLEND:
			if(isBlend) {
				_wglDisable(_wGL_BLEND);
				isBlend = false;
			}
			break;
		case GL_RESCALE_NORMAL:
			break;
		case GL_TEXTURE_2D:
			if (selectedTex == 0) {
				enableTexture2D = false;
			}
			if (selectedTex == 1) {
				enableTexture2D_1 = false;
			}
			break;
		case GL_LIGHTING:
			enableLighting = false;
			break;
		case GL_ALPHA_TEST:
			enableAlphaTest = false;
			break;
		case GL_FOG:
			fogEnabled = false;
			break;
		case GL_COLOR_MATERIAL:
			enableColorMaterial = false;
			break;
		case GL_TEXTURE_GEN_S:
		case GL_TEXTURE_GEN_T:
		case GL_TEXTURE_GEN_R:
		case GL_TEXTURE_GEN_Q:
			enableTexGen = false;
			break;
		case GL_POLYGON_OFFSET_FILL:
			if(isPolygonOffset) {
				_wglDisable(_wGL_POLYGON_OFFSET_FILL);
				isPolygonOffset = false;
			}
			break;
		case EAG_SWAP_RB:
			swapRB = false;
			break;
		default:
			break;
		}
	}

	public static final void glColor4f(float p1, float p2, float p3, float p4) {
		++colorSerial;
		colorR = p1;
		colorG = p2;
		colorB = p3;
		colorA = p4;
	}

	public static final int glGetError() {
		int err = _wglGetError();
		if (err == _wGL_CONTEXT_LOST_WEBGL)
			return GL_CONTEXT_LOST_WEBGL;
		return err;
	}

	public static final void glFlush() {
		EaglerAdapter._wglFlush();
	}

	public static final void glLineWidth(float p1) {

	}

	public static final void glTexImage2D(int p1, int p2, int p3, int p4, int p5, int p6, int p7, int p8,
			ByteBuffer p9) {
		if (p2 == 0 && selectedTex == 0 && boundTexture0 != null) {
			boundTexture0.w = p4;
			boundTexture0.h = p5;
		}
		_wglTexImage2D(_wGL_TEXTURE_2D, p2, _wGL_RGBA8, p4, p5, p6, _wGL_RGBA, _wGL_UNSIGNED_BYTE, p9);
	}

	public static final void glLight(int p1, int p2, FloatBuffer p3) {

	}

	public static final void glLightModel(int p1, FloatBuffer p2) {

	}

	static int lightPos0Serial = 0;
	static int lightPos1Serial = 0;
	private static Vector4f lightPos0vec0 = new Vector4f();
	private static Vector4f lightPos1vec0 = new Vector4f();
	static Vector4f lightPos0vec = new Vector4f();
	static Vector4f lightPos1vec = new Vector4f();

	public static final void copyModelToLightMatrix() {
		++lightPos0Serial;
		++lightPos1Serial;
		lightPos0vec0.set(lightPos0vec);
		lightPos1vec0.set(lightPos1vec);
		lightPos0vec.set(0.2f, 1.0f, -0.7f, 0.0f);
		lightPos0vec.normalise();
		lightPos1vec.set(-0.2f, 1.0f, 0.7f, 0.0f);
		lightPos1vec.normalise();
		Matrix4f.transform(matModelV[matModelPointer], lightPos0vec, lightPos0vec).normalise();
		Matrix4f.transform(matModelV[matModelPointer], lightPos1vec, lightPos1vec).normalise();
	}

	public static final void flipLightMatrix() {
		++lightPos0Serial;
		++lightPos1Serial;
		lightPos0vec.x = -lightPos0vec.x;
		lightPos1vec.x = -lightPos1vec.x;
		lightPos0vec.y = -lightPos0vec.y;
		lightPos1vec.y = -lightPos1vec.y;
		lightPos0vec.z = -lightPos0vec.z;
		lightPos1vec.z = -lightPos1vec.z;
	}

	public static final void revertLightMatrix() {
		++lightPos0Serial;
		++lightPos1Serial;
		lightPos0vec.set(lightPos0vec0);
		lightPos1vec.set(lightPos1vec0);
	}

	public static final void glPushMatrix() {
		switch (matrixMode) {
		case GL_MODELVIEW:
		default:
			if (matModelPointer < matModelV.length - 1) {
				++matModelPointer;
				matModelV[matModelPointer].load(matModelV[matModelPointer - 1]);
				matModelVSerial[matModelPointer] = matModelVSerial[matModelPointer - 1];
			} else {
				System.err.println("modelview matrix stack overflow");
			}
			break;
		case GL_PROJECTION:
			if (matProjPointer < matProjV.length - 1) {
				++matProjPointer;
				matProjV[matProjPointer].load(matProjV[matProjPointer - 1]);
				matProjVSerial[matProjPointer] = matProjVSerial[matProjPointer - 1];
			} else {
				System.err.println("projection matrix stack overflow");
			}
			break;
		case GL_TEXTURE:
			if (matTexPointer < matTexV.length - 1) {
				++matTexPointer;
				matTexV[matTexPointer].load(matTexV[matTexPointer - 1]);
				matTexVSerial[matTexPointer] = matTexVSerial[matTexPointer - 1];
			} else {
				System.err.println("texture matrix stack overflow");
			}
			break;
		}
	}

	private static final float toRad = 0.0174532925f;

	public static final void glRotatef(float p1, float p2, float p3, float p4) {
		deevis.set(p2, p3, p4);
		getMatrixIncrSerial().rotate(p1 * toRad, deevis);
		if (isCompilingDisplayList) {
			System.err.println("matrix is not supported while recording display list use tessellator class instead");
		}
	}

	public static final void glPopMatrix() {
		switch (matrixMode) {
		case GL_MODELVIEW:
		default:
			if (matModelPointer > 0) {
				--matModelPointer;
			} else {
				System.err.println("modelview matrix stack underflow");
			}
			break;
		case GL_PROJECTION:
			if (matProjPointer > 0) {
				--matProjPointer;
			} else {
				System.err.println("projection matrix stack underflow");
			}
			break;
		case GL_TEXTURE:
			if (matTexPointer > 0) {
				--matTexPointer;
			} else {
				System.err.println("texture matrix stack underflow");
			}
			break;
		}
	}

	public static final void glColorMaterial(int p1, int p2) {

	}

	public static final void glGetFloat(int p1, FloatBuffer p2) {
		switch (p1) {
		case GL_MODELVIEW_MATRIX:
		default:
			matModelV[matModelPointer].store(p2);
			break;
		case GL_PROJECTION_MATRIX:
			matProjV[matProjPointer].store(p2);
			break;
		}
	}

	public static final void glGetInteger(int p1, int[] p2) {
		if (p1 == GL_VIEWPORT) {
			_wglGetParameter(_wGL_VIEWPORT, 4, p2);
		}
	}

	public static final void glScalef(float p1, float p2, float p3) {
		deevis.set(p1, p2, p3);
		getMatrixIncrSerial().scale(deevis);
		if (isCompilingDisplayList) {
			System.err.println("matrix is not supported while recording display list use tessellator class instead");
		}
	}

	private static final Matrix4f tmpMat = new Matrix4f();

	public static final void glMultMatrixf(Matrix4f mat) {
		getMatrixIncrSerial().load(Matrix4f.mul(getMatrixIncrSerial(), mat, tmpMat));
	}

	public static final void glBlendFunc(int p1, int p2) {
		if(overlayFBOBlending) {
			int i = p1 | 0x10000;
			int j = p2 | 0x10000;
			if(blendSRC != i || blendDST != j) {
				_wglBlendFuncSeparate(p1, p2, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
				blendSRC = i;
				blendDST = j;
			}
		}else {
			if(blendSRC != p1 || blendDST != p2) {
				_wglBlendFunc(p1, p2);
				blendSRC = p1;
				blendDST = p2;
			}
		}
	}

	private static boolean overlayFBOBlending = false;

	public static final void enableOverlayFramebufferBlending(boolean en) {
		overlayFBOBlending = en;
	}

	public static final void glDepthMask(boolean p1) {
		if(isDepthMask != p1) {
			_wglDepthMask(p1);
			isDepthMask = p1;
		}
	}

	public static final void glColorMask(boolean p1, boolean p2, boolean p3, boolean p4) {
		int hsh = (p1 ? 1 : 0) | (p2 ? 2 : 0) | (p3 ? 4 : 0) | (p4 ? 8 : 0);
		if(colorMask != hsh) {
			_wglColorMask(p1, p2, p3, p4);
			colorMask = hsh;
		}
	}

	private static final void updateAnisotropicPatch() {
		if (selectedTex == 0) {
			enableAnisotropicFix = false;
			if (enableAnisotropicPatch && boundTexture0 != null && boundTexture0.anisotropic && boundTexture0.nearest) {
				enableAnisotropicFix = true;
				++anisotropicFixSerial;
				anisotropicFixX = boundTexture0.w;
				anisotropicFixY = boundTexture0.h;
			}
		}
	}

	public static final void glBindTexture(int p1, int p2) {
		if(boundTexI[selectedTex] != p2) {
			TextureGL t = texObjects.get(p2);
			if(boundTex[selectedTex] != t) {
				_wglBindTexture(_wGL_TEXTURE_2D, t);
				if (selectedTex == 0) {
					boundTexture0 = t;
					updateAnisotropicPatch();
				}
				boundTex[selectedTex] = t;
			}
			boundTexI[selectedTex] = p2;
		}
	}

	public static final void glBindTexture(int p1, TextureGL p2) {
		boundTexI[selectedTex] = -1;
		if(boundTex[selectedTex] != p2) {
			_wglBindTexture(_wGL_TEXTURE_2D, p2);
			if (selectedTex == 0) {
				boundTexture0 = p2;
				updateAnisotropicPatch();
			}
			boundTex[selectedTex] = p2;
		}
	}

	public static final void glCopyTexSubImage2D(int p1, int p2, int p3, int p4, int p5, int p6, int p7, int p8) {
		_wglCopyTexSubImage2D(_wGL_TEXTURE_2D, p2, p3, p4, p5, p6, p7, p8);
	}

	public static final void glTexParameteri(int p1, int p2, int p3) {
		if (selectedTex == 0 && boundTexture0 != null && p2 == GL_TEXTURE_MAG_FILTER) {
			boundTexture0.nearest = p3 == GL_NEAREST;
		}
		_wglTexParameteri(p1, p2, p3);
		updateAnisotropicPatch();
	}

	public static final void glTexParameterf(int p1, int p2, float p3) {
		if(p2 == GL_TEXTURE_MAX_ANISOTROPY && !anisotropicFilteringSupported()) {
			return;
		}
		if (selectedTex == 0 && boundTexture0 != null && p2 == GL_TEXTURE_MAX_ANISOTROPY) {
			boundTexture0.anisotropic = p3 > 1.0f;
		}
		_wglTexParameterf(p1, p2 == GL_TEXTURE_MAX_ANISOTROPY ? _wGL_TEXTURE_MAX_ANISOTROPY : p2, p3);
		updateAnisotropicPatch();
	}

	public static final void glLogicOp(int p1) {

	}

	public static final void glNormal3f(float p1, float p2, float p3) {
		++normalSerial;
		float len = (float) Math.sqrt(p1 * p1 + p2 * p2 + p3 * p3);
		normalX = p1 / len;
		normalY = p2 / len;
		normalZ = p3 / len;
	}

	public static final int glGenLists(int p1) {
		int base = displayListId + 1;
		for (int i = 0; i < p1; i++) {
			int id = ++displayListId;
			displayLists.put(id, new DisplayList(id));
		}
		return base;
	}

	public static final void _wglBindVertexArray0(BufferArrayGL p1) {
		if(currentArray != p1) {
			currentArray = p1;
			_wglBindVertexArray(p1);
		}
	}

	private static int displayListId = 0;

	public static final void glCallList(int p1) {
		if (!isCompilingDisplayList) {
			DisplayList d = displayListsInitialized.get(p1);
			if (d != null && d.listLength > 0) {
				bindTheShader(d.shaderMode | getShaderModeFlag1());
				_wglBindVertexArray0(d.glarray);
				_wglDrawQuadArrays(0, d.listLength);
				shader.unuseProgram();
				vertexDrawn += d.listLength * 6 / 4;
				triangleDrawn += d.listLength / 2;
			}
		}
	}

	public static final void glNewList(int p1, int p2) {
		if (!isCompilingDisplayList) {
			compilingDisplayList = displayLists.get(p1);
			if (compilingDisplayList != null) {
				compilingDisplayList.shaderMode = -1;
				compilingDisplayList.listLength = 0;
				isCompilingDisplayList = true;
			}
		}
	}

	public static final void glEndList() {
		if (isCompilingDisplayList) {
			isCompilingDisplayList = false;
			Object upload = _wGetLowLevelBuffersAppended();
			int l = _wArrayByteLength(upload);
			if (l > 0) {
				if (compilingDisplayList.glbuffer == null) {
					displayListsInitialized.put(compilingDisplayList.id, compilingDisplayList);
					compilingDisplayList.glarray = _wglCreateVertexArray();
					compilingDisplayList.glbuffer = _wglCreateBuffer();
					FixedFunctionShader f = FixedFunctionShader.instance(compilingDisplayList.shaderMode);
					_wglBindVertexArray0(compilingDisplayList.glarray);
					_wglBindBuffer(_wGL_ARRAY_BUFFER, compilingDisplayList.glbuffer);
					f.setupArrayForProgram();
				}
				_wglBindBuffer(_wGL_ARRAY_BUFFER, compilingDisplayList.glbuffer);
				_wglBufferData(_wGL_ARRAY_BUFFER, upload, _wGL_STATIC_DRAW);
				bytesUploaded += l;
			}
		}
	}

	public static final void flushDisplayList(int p1) {
		DisplayList d = displayListsInitialized.get(p1);
		if (d != null) {
			if (d.glbuffer != null) {
				_wglDeleteBuffer(d.glbuffer);
				_wglDeleteVertexArray(d.glarray);
				d.glbuffer = null;
				d.glarray = null;
			}
		}
	}

	public static final HighPolyMesh loadMesh(String path) {
		try {
			HighPolyMesh ret = HighPolyMesh.loadMeshData(EaglerAdapter.loadResourceBytes(path));
			currentArray = ret.vertexArray;
			return ret;
		} catch (IOException ex) {
			System.err.println("Could not load HighPolyMesh! " + ex.toString());
			ex.printStackTrace();
			return null;
		}
	}

	public static final void glColor3f(float p1, float p2, float p3) {
		++colorSerial;
		colorR = p1;
		colorG = p2;
		colorB = p3;
		colorA = 1.0f;
	}

	public static final void glTexGeni(int p1, int p2, int p3) {

	}
	
	private static final Vector4f tmpTexGenPlane = new Vector4f();

	public static final void glTexGen(int p1, int p2, FloatBuffer p3) {
		Vector4f vec = tmpTexGenPlane;
		vec.load(p3);
		if(p2 == GL_EYE_PLANE) {
			tmpMat.load(matModelV[matModelPointer]).invert().transpose();
			Matrix4f.transform(tmpMat, vec, vec);
		}
		switch (p1) {
		case GL_S:
			++texPlaneSerial;
			++texSSerial;
			texS_plane = (p2 == GL_EYE_PLANE ? 1 : 0);
			texS_X = vec.x;
			texS_Y = vec.y;
			texS_Z = vec.z;
			texS_W = vec.w;
			break;
		case GL_T:
			++texPlaneSerial;
			++texTSerial;
			texT_plane = (p2 == GL_EYE_PLANE ? 1 : 0);
			texT_X = vec.x;
			texT_Y = vec.y;
			texT_Z = vec.z;
			texT_W = vec.w;
			break;
		case GL_R:
			++texPlaneSerial;
			++texRSerial;
			texR_plane = (p2 == GL_EYE_PLANE ? 1 : 0);
			texR_X = vec.x;
			texR_Y = vec.y;
			texR_Z = vec.z;
			texR_W = vec.w;
			break;
		case GL_Q:
			++texPlaneSerial;
			++texQSerial;
			texQ_plane = (p2 == GL_EYE_PLANE ? 1 : 0);
			texQ_X = vec.x;
			texQ_Y = vec.y;
			texQ_Z = vec.z;
			texQ_W = vec.w;
			break;
		}
	}

	public static final void glTexImage2D(int p1, int p2, int p3, int p4, int p5, int p6, int p7, int p8,
			IntBuffer p9) {
		/*
		 * int pp2 = 0; switch(p3) { default: case GL_RGBA: pp2 = _wGL_RGBA; break; case
		 * GL_BGRA: pp2 = _wGL_BGRA; break; } int pp3 = 0; switch(p7) { default: case
		 * GL_RGBA: pp3 = _wGL_RGBA; break; case GL_BGRA: pp3 = _wGL_BGRA; break; }
		 */
		if (p2 == 0 && selectedTex == 0 && boundTexture0 != null) {
			boundTexture0.w = p4;
			boundTexture0.h = p5;
		}
		bytesUploaded += p9.remaining() * 4;
		_wglTexImage2D(_wGL_TEXTURE_2D, p2, _wGL_RGBA8, p4, p5, p6, _wGL_RGBA, _wGL_UNSIGNED_BYTE, p9);
		updateAnisotropicPatch();
	}

	public static final void glTexImage2D_2(int p1, int p2, int p3, int p4, int p5, int p6, int p7, int p8,
			IntBuffer p9) {
		if (p2 == 0 && selectedTex == 0 && boundTexture0 != null) {
			boundTexture0.w = p4;
			boundTexture0.h = p5;
		}
		bytesUploaded += p9.remaining() * 4;
		_wglTexImage2D(_wGL_TEXTURE_2D, p2, _wGL_RGB8, p4, p5, p6, _wGL_RGB, _wGL_UNSIGNED_BYTE, p9);
		updateAnisotropicPatch();
	}

	public static final void glTexSubImage2D(int p1, int p2, int p3, int p4, int p5, int p6, int p7, int p8,
			IntBuffer p9) {
		int pp1 = 0;
		switch (p1) {
		default:
		case GL_TEXTURE_2D:
			pp1 = _wGL_TEXTURE_2D;
			break;
		// case GL_TEXTURE_3D: pp1 = _wGL_TEXTURE_3D; break;
		}
		/*
		 * int pp3 = 0; switch(p7) { default: case GL_RGBA: pp3 = _wGL_RGBA; break; case
		 * GL_BGRA: pp3 = _wGL_BGRA; break; }
		 */
		bytesUploaded += p9.remaining() * 4;
		_wglTexSubImage2D(pp1, p2, p3, p4, p5, p6, _wGL_RGBA, _wGL_UNSIGNED_BYTE, p9);
	}

	public static final void glDeleteTextures(int p1) {
		_wglDeleteTextures(texObjects.free(p1));
	}

	public static final void glPolygonOffset(float p1, float p2) {
		if(p1 != polygonOffset1 || p2 != polygonOffset2) {
			_wglPolygonOffset(p1, p2);
			polygonOffset1 = p1;
			polygonOffset2 = p2;
		}
	}

	public static final void glCallLists(IntBuffer p1) {
		while (p1.hasRemaining()) {
			glCallList(p1.get());
		}
	}

	public static final void glEnableVertexAttrib(int p1) {
		switch (p1) {
		case GL_COLOR_ARRAY:
			enableColorArray = true;
			break;
		case GL_NORMAL_ARRAY:
			enableNormalArray = true;
			break;
		case GL_TEXTURE_COORD_ARRAY:
			switch (selectedClientTex) {
			case 0:
				enableTex0Array = true;
				break;
			case 1:
				enableTex1Array = true;
				break;
			default:
				break;
			}
			break;
		default:
			break;
		}
	}

	public static final void glDisableVertexAttrib(int p1) {
		switch (p1) {
		case GL_COLOR_ARRAY:
			enableColorArray = false;
			break;
		case GL_NORMAL_ARRAY:
			enableNormalArray = false;
			break;
		case GL_TEXTURE_COORD_ARRAY:
			switch (selectedClientTex) {
			case 0:
				enableTex0Array = false;
				break;
			case 1:
				enableTex1Array = false;
				break;
			default:
				break;
			}
			break;
		default:
			break;
		}
	}

	public static final void hintAnisotropicFix(boolean hint) {
		hintAnisotropicPatch = hint;
	}

	private static final int getShaderModeFlag0() {
		int mode = 0;
		mode = (mode | (enableColorArray ? FixedFunctionShader.COLOR : 0));
		mode = (mode | (enableNormalArray ? FixedFunctionShader.NORMAL : 0));
		mode = (mode | (enableTex0Array ? FixedFunctionShader.TEXTURE0 : 0));
		mode = (mode | (enableTex1Array ? FixedFunctionShader.TEXTURE1 : 0));
		return mode;
	}

	private static final int getShaderModeFlag1() {
		int mode = 0;
		mode = (mode | (enableTexGen ? FixedFunctionShader.TEXGEN : 0));
		mode = (mode | ((enableColorMaterial && enableLighting) ? FixedFunctionShader.LIGHTING : 0));
		mode = (mode | (fogEnabled ? FixedFunctionShader.FOG : 0));
		mode = (mode | (enableAlphaTest ? FixedFunctionShader.ALPHATEST : 0));
		mode = (mode | (enableTexture2D ? FixedFunctionShader.UNIT0 : 0));
		mode = (mode | (enableTexture2D_1 ? FixedFunctionShader.UNIT1 : 0));
		mode = (mode | ((enableTexture2D && (enableAnisotropicFix || (hintAnisotropicPatch && enableAnisotropicPatch)))
				? FixedFunctionShader.FIX_ANISOTROPIC
				: 0));
		mode = (mode | (swapRB ? FixedFunctionShader.SWAP_RB : 0));
		return mode;
	}

	private static final int getShaderModeFlag() {
		int mode = 0;
		mode = (mode | (enableColorArray ? FixedFunctionShader.COLOR : 0));
		mode = (mode | (enableNormalArray ? FixedFunctionShader.NORMAL : 0));
		mode = (mode | (enableTex0Array ? FixedFunctionShader.TEXTURE0 : 0));
		mode = (mode | (enableTex1Array ? FixedFunctionShader.TEXTURE1 : 0));
		mode = (mode | (enableTexGen ? FixedFunctionShader.TEXGEN : 0));
		mode = (mode | ((enableColorMaterial && enableLighting) ? FixedFunctionShader.LIGHTING : 0));
		mode = (mode | (fogEnabled ? FixedFunctionShader.FOG : 0));
		mode = (mode | (enableAlphaTest ? FixedFunctionShader.ALPHATEST : 0));
		mode = (mode | (enableTexture2D ? FixedFunctionShader.UNIT0 : 0));
		mode = (mode | (enableTexture2D_1 ? FixedFunctionShader.UNIT1 : 0));
		mode = (mode | ((enableTexture2D && (enableAnisotropicFix || (hintAnisotropicPatch && enableAnisotropicPatch)))
				? FixedFunctionShader.FIX_ANISOTROPIC
				: 0));
		mode = (mode | (swapRB ? FixedFunctionShader.SWAP_RB : 0));
		return mode;
	}

	private static FixedFunctionShader shader = null;

	private static final void bindTheShader() {
		bindTheShader(getShaderModeFlag());
	}

	private static final void bindTheShader(int mode) {
		FixedFunctionShader s = shader = FixedFunctionShader.instance(mode);
		s.useProgram();
		s.update();
	}

	public static final void drawHighPoly(HighPolyMesh msh) {
		bindTheShader((msh.hasTexture ? (FixedFunctionShader.NORMAL | FixedFunctionShader.TEXTURE0)
				: FixedFunctionShader.NORMAL) | getShaderModeFlag1());
		_wglBindVertexArray0(msh.vertexArray);
		_wglDrawElements(_wGL_TRIANGLES, msh.indexCount, _wGL_UNSIGNED_SHORT, 0);
		triangleDrawn += msh.indexCount / 3;
		shader.unuseProgram();
	}

	private static Object blankUploadArray = _wCreateLowLevelIntBuffer(525000);

	public static final void glDrawArrays(int p1, int p2, int p3, Object buffer) {
		if (isCompilingDisplayList) {
			if (p1 == GL_QUADS) {
				if (compilingDisplayList.shaderMode == -1) {
					compilingDisplayList.shaderMode = getShaderModeFlag0();
				} else {
					if (compilingDisplayList.shaderMode != getShaderModeFlag0()) {
						System.err.println("vertex format inconsistent in display list");
					}
				}
				compilingDisplayList.listLength += p3;
				_wAppendLowLevelBuffer(buffer);
			} else {
				System.err.println("only GL_QUADS supported in a display list");
			}
		} else {
			int bl =  _wArrayByteLength(buffer);
			bytesUploaded += bl;
			vertexDrawn += p3;

			bindTheShader();

			StreamBufferInstance sb = shader.streamBuffer.getBuffer(bl);
			_wglBindVertexArray0(sb.getVertexArray());
			_wglBindBuffer(_wGL_ARRAY_BUFFER, sb.getVertexBuffer());
			if (!shader.bufferIsInitialized) {
				shader.bufferIsInitialized = true;
				_wglBufferData(_wGL_ARRAY_BUFFER, blankUploadArray, _wGL_DYNAMIC_DRAW);
			}
			_wglBufferSubData(_wGL_ARRAY_BUFFER, 0, buffer);

			if (p1 == GL_QUADS) {
				_wglDrawQuadArrays(p2, p3);
				triangleDrawn += p3 / 2;
			} else {
				switch (p1) {
				default:
				case GL_TRIANGLES:
					triangleDrawn += p3 / 3;
					break;
				case GL_TRIANGLE_STRIP:
					triangleDrawn += p3 - 2;
					break;
				case GL_TRIANGLE_FAN:
					triangleDrawn += p3 - 2;
					break;
				case GL_LINE_STRIP:
					triangleDrawn += p3 - 1;
					break;
				case GL_LINES:
					triangleDrawn += p3 / 2;
					break;
				}
				_wglDrawArrays(p1, p2, p3);
			}

			shader.unuseProgram();

		}
	}

	private static final void _wglDrawQuadArrays(int p2, int p3) {
		if (quadsToTrianglesBuffer == null) {
			IntBuffer upload = isWebGL ? IntBuffer.wrap(new int[98400 / 2])
					: ByteBuffer.allocateDirect(98400 * 2).order(ByteOrder.nativeOrder()).asIntBuffer();
			for (int i = 0; i < 16384; ++i) {
				int v1 = i * 4;
				int v2 = i * 4 + 1;
				int v3 = i * 4 + 2;
				int v4 = i * 4 + 3;
				upload.put(v1 | (v2 << 16));
				upload.put(v4 | (v2 << 16));
				upload.put(v3 | (v4 << 16));
			}
			upload.flip();
			quadsToTrianglesBuffer = _wglCreateBuffer();
			_wglBindBuffer(_wGL_ELEMENT_ARRAY_BUFFER, quadsToTrianglesBuffer);
			_wglBufferData0(_wGL_ELEMENT_ARRAY_BUFFER, upload, _wGL_STATIC_DRAW);
		}
		if (!currentArray.isQuadBufferBound) {
			currentArray.isQuadBufferBound = true;
			_wglBindBuffer(_wGL_ELEMENT_ARRAY_BUFFER, quadsToTrianglesBuffer);
		}
		_wglDrawElements(_wGL_TRIANGLES, p3 * 6 / 4, _wGL_UNSIGNED_SHORT, p2 * 6 / 4);
	}

	private static BufferArrayGL occlusion_vao = null;
	private static BufferGL occlusion_vbo = null;
	private static ProgramGL occlusion_program = null;
	private static UniformGL occlusion_matrix_m = null;
	private static UniformGL occlusion_matrix_p = null;

	private static final void initializeOcclusionObjects() {
		occlusion_vao = _wglCreateVertexArray();
		occlusion_vbo = _wglCreateBuffer();

		IntBuffer upload = (isWebGL ? IntBuffer.wrap(new int[108])
				: ByteBuffer.allocateDirect(108 << 2).order(ByteOrder.nativeOrder()).asIntBuffer());
		float[] verts = new float[] { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f,
				0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f,
				1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,
				1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f,
				1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f,
				1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f,
				1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f };
		for (int i = 0; i < verts.length; i++) {
			upload.put(Float.floatToRawIntBits(verts[i]));
		}
		upload.flip();

		_wglBindVertexArray(occlusion_vao);
		_wglBindBuffer(_wGL_ARRAY_BUFFER, occlusion_vbo);
		_wglBufferData0(_wGL_ARRAY_BUFFER, upload, _wGL_STATIC_DRAW);
		_wglEnableVertexAttribArray(0);
		_wglVertexAttribPointer(0, 3, _wGL_FLOAT, false, 12, 0);

		ShaderGL vert = _wglCreateShader(_wGL_VERTEX_SHADER);
		ShaderGL frag = _wglCreateShader(_wGL_FRAGMENT_SHADER);

		String src = fileContents("/glsl/occl.glsl");
		_wglShaderSource(vert, _wgetShaderHeader() + "\n#define CC_VERT\n" + src);
		_wglShaderSource(frag, _wgetShaderHeader() + "\n#define CC_FRAG\n" + src);

		_wglCompileShader(vert);
		if (!_wglGetShaderCompiled(vert))
			System.err.println(("\n" + _wglGetShaderInfoLog(vert)).replace("\n", "\n[/glsl/occl.glsl][VERT] ") + "\n");

		_wglCompileShader(frag);
		if (!_wglGetShaderCompiled(frag))
			System.err.println(("\n" + _wglGetShaderInfoLog(frag)).replace("\n", "\n[/glsl/occl.glsl][FRAG] ") + "\n");

		occlusion_program = _wglCreateProgram();

		_wglAttachShader(occlusion_program, vert);
		_wglAttachShader(occlusion_program, frag);
		_wglLinkProgram(occlusion_program);
		_wglDetachShader(occlusion_program, vert);
		_wglDetachShader(occlusion_program, frag);
		_wglDeleteShader(vert);
		_wglDeleteShader(frag);

		if (!_wglGetProgramLinked(occlusion_program))
			System.err.println(
					("\n\n" + _wglGetProgramInfoLog(occlusion_program)).replace("\n", "\n[/glsl/occl.glsl][LINKER] "));

		_wglUseProgram(occlusion_program);
		occlusion_matrix_m = _wglGetUniformLocation(occlusion_program, "matrix_m");
		occlusion_matrix_p = _wglGetUniformLocation(occlusion_program, "matrix_p");

	}

	private static final GLObjectMap<QueryGL> queryObjs = new GLObjectMap<>(256);

	public static final int glCreateQuery() {
		return queryObjs.register(_wglCreateQuery());
	}

	public static final void glBeginQuery(int obj) {
		_wglBeginQuery(_wGL_ANY_SAMPLES_PASSED, queryObjs.get(obj));
	}

	public static final void glDeleteQuery(int obj) {
		_wglDeleteQuery(queryObjs.free(obj));
	}

	private static final Matrix4f cachedOcclusionP = (Matrix4f) (new Matrix4f()).setZero();
	private static float[] occlusionModel = new float[16];
	private static float[] occlusionProj = new float[16];

	public static final void glBindOcclusionBB() {
		if (occlusion_vao == null)
			initializeOcclusionObjects();
		_wglUseProgram(occlusion_program);
		_wglBindVertexArray0(occlusion_vao);
		if (!cachedOcclusionP.equals(matProjV[matProjPointer])) {
			cachedOcclusionP.load(matProjV[matProjPointer]);
			cachedOcclusionP.store(occlusionProj);
			_wglUniformMat4fv(occlusion_matrix_p, occlusionProj);
		}
	}

	public static final void glEndOcclusionBB() {

	}

	public static final void glDrawOcclusionBB(float posX, float posY, float posZ, float sizeX, float sizeY,
			float sizeZ) {
		glPushMatrix();
		glTranslatef(posX - sizeX * 0.01f, posY - sizeY * 0.01f, posZ - sizeZ * 0.01f);
		glScalef(sizeX * 1.02f, sizeY * 1.02f, sizeZ * 1.02f);
		matModelV[matModelPointer].store(occlusionModel);
		_wglUniformMat4fv(occlusion_matrix_m, occlusionModel);
		_wglDrawArrays(_wGL_TRIANGLES, 0, 36);
		glPopMatrix();

	}

	public static final void glEndQuery() {
		_wglEndQuery(_wGL_ANY_SAMPLES_PASSED);
	}

	public static final boolean glGetQueryResult(int obj) {
		QueryGL q = queryObjs.get(obj);
		return _wglGetQueryObjecti(q, _wGL_QUERY_RESULT) > 0;
	}

	public static final boolean glGetQueryResultAvailable(int obj) {
		QueryGL q = queryObjs.get(obj);
		return _wglGetQueryObjecti(q, _wGL_QUERY_RESULT_AVAILABLE) > 0;
	}

	public static final int glGenTextures() {
		return texObjects.register(_wglGenTextures());
	}

	public static final void glTexSubImage2D(int p1, int p2, int p3, int p4, int p5, int p6, int p7, int p8,
			ByteBuffer p9) {
		int pp1 = 0;
		switch (p1) {
		default:
		case GL_TEXTURE_2D:
			pp1 = _wGL_TEXTURE_2D;
			break;
		// case GL_TEXTURE_3D: pp1 = _wGL_TEXTURE_3D; break;
		}
		/*
		 * int pp3 = 0; switch(p7) { default: case GL_RGBA: pp3 = _wGL_RGBA; break; case
		 * GL_BGRA: pp3 = _wGL_BGRA; break; }
		 */
		bytesUploaded += p9.remaining();
		_wglTexSubImage2D(pp1, p2, p3, p4, p5, p6, _wGL_RGBA, _wGL_UNSIGNED_BYTE, p9);
	}

	public static final void glFogi(int p1, int p2) {
		if (p1 == GL_FOG_MODE) {
			switch (p2) {
			default:
			case GL_LINEAR:
				++fogCfgSerial;
				fogMode = 1;
				break;
			case GL_EXP:
				++fogCfgSerial;
				fogMode = 2;
				break;
			}
		}
	}

	public static final void glFogf(int p1, float p2) {
		switch (p1) {
		case GL_FOG_START:
			++fogCfgSerial;
			fogStart = p2;
			break;
		case GL_FOG_END:
			++fogCfgSerial;
			fogEnd = p2;
			break;
		case GL_FOG_DENSITY:
			++fogCfgSerial;
			fogDensity = p2;
			break;
		default:
			break;
		}
	}

	public static final void glFog(int p1, FloatBuffer p2) {
		if (p1 == GL_FOG_COLOR) {
			++fogColorSerial;
			fogColorR = p2.get();
			fogColorG = p2.get();
			fogColorB = p2.get();
			fogColorA = p2.get();
		}
	}

	public static final void glDeleteLists(int p1, int p2) {
		for (int i = 0; i < p2; i++) {
			DisplayList d = displayListsInitialized.remove(p1 + i);
			if (d != null) {
				_wglDeleteVertexArray(d.glarray);
				_wglDeleteBuffer(d.glbuffer);
			}
			displayLists.remove(p1 + i);
		}
	}

	public static final void glActiveTexture(int p1) {
		switch (p1) {
		case GL_TEXTURE0:
			if(selectedTex != 0) {
				selectedTex = 0;
				_wglActiveTexture(_wGL_TEXTURE0);
			}
			break;
		case GL_TEXTURE1:
			if(selectedTex != 1) {
				selectedTex = 1;
				_wglActiveTexture(_wGL_TEXTURE1);
			}
			break;
		default:
			System.err.println("only two texture units implemented");
			break;
		}
	}

	public static final void glClientActiveTexture(int p1) {
		switch (p1) {
		case GL_TEXTURE0:
			selectedClientTex = 0;
			break;
		case GL_TEXTURE1:
			selectedClientTex = 1;
			break;
		default:
			System.err.println("only two texture units implemented");
			break;
		}
	}

	public static final void glMultiTexCoord2f(int p1, float p2, float p3) {
		switch (p1) {
		case GL_TEXTURE0:
			++tex0Serial;
			tex0X = p2;
			tex0Y = p3;
			break;
		case GL_TEXTURE1:
			++tex1Serial;
			tex1X = p2;
			tex1Y = p3;
			break;
		default:
			System.err.println("only two texture units implemented");
			break;
		}
	}

	private static Matrix4f unprojA = new Matrix4f();
	private static Matrix4f unprojB = new Matrix4f();
	private static Vector4f unprojC = new Vector4f();

	public static final void gluUnProject(float p1, float p2, float p3, FloatBuffer p4, FloatBuffer p5, int[] p6,
			FloatBuffer p7) {
		unprojA.load(p4);
		unprojB.load(p5);
		Matrix4f.mul(unprojA, unprojB, unprojB);
		unprojB.invert();
		unprojC.set(((p1 - (float) p6[0]) / (float) p6[2]) * 2f - 1f, ((p2 - (float) p6[1]) / (float) p6[3]) * 2f - 1f,
				p3, 1.0f);
		Matrix4f.transform(unprojB, unprojC, unprojC);
		p7.put(unprojC.x / unprojC.w);
		p7.put(unprojC.y / unprojC.w);
		p7.put(unprojC.z / unprojC.w);
	}

	public static final void gluPerspective(float fovy, float aspect, float zNear, float zFar) {
		Matrix4f res = getMatrixIncrSerial();
		float cotangent = (float) Math.cos(fovy * toRad * 0.5f) / (float) Math.sin(fovy * toRad * 0.5f);
		res.m00 = cotangent / aspect;
		res.m01 = 0.0f;
		res.m02 = 0.0f;
		res.m03 = 0.0f;
		res.m10 = 0.0f;
		res.m11 = cotangent;
		res.m12 = 0.0f;
		res.m13 = 0.0f;
		res.m20 = 0.0f;
		res.m21 = 0.0f;
		res.m22 = (zFar + zNear) / (zFar - zNear);
		res.m23 = -1.0f;
		res.m30 = 0.0f;
		res.m31 = 0.0f;
		res.m32 = 2.0f * zFar * zNear / (zFar - zNear);
		res.m33 = 0.0f;
	}

	public static final void gluPerspectiveFlat(float fovy, float aspect, float zNear, float zFar) {
		Matrix4f res = getMatrixIncrSerial();
		float cotangent = (float) Math.cos(fovy * toRad * 0.5f) / (float) Math.sin(fovy * toRad * 0.5f);
		res.m00 = cotangent / aspect;
		res.m01 = 0.0f;
		res.m02 = 0.0f;
		res.m03 = 0.0f;
		res.m10 = 0.0f;
		res.m11 = cotangent;
		res.m12 = 0.0f;
		res.m13 = 0.0f;
		res.m20 = 0.0f;
		res.m21 = 0.0f;
		res.m22 = ((zFar + zNear) / (zFar - zNear)) * 0.001f;
		res.m23 = -1.0f;
		res.m30 = 0.0f;
		res.m31 = 0.0f;
		res.m32 = 2.0f * zFar * zNear / (zFar - zNear);
		res.m33 = 0.0f;
	}

	public static final String gluErrorString(int p1) {
		switch (p1) {
		case GL_INVALID_ENUM:
			return "GL_INVALID_ENUM";
		case GL_INVALID_VALUE:
			return "GL_INVALID_VALUE";
		case GL_INVALID_OPERATION:
			return "GL_INVALID_OPERATION";
		case GL_OUT_OF_MEMORY:
			return "GL_OUT_OF_MEMORY";
		case GL_CONTEXT_LOST_WEBGL:
			return "CONTEXT_LOST_WEBGL";
		default:
			return "Unknown Error";
		}
	}

	public static final void optimize() {
		FixedFunctionShader.optimize();
	}

	private static long lastBandwidthReset = 0l;
	private static int lastBandwidth = 0;

	public static final int getBitsPerSecond() {
		if (steadyTimeMillis() - lastBandwidthReset > 1000) {
			lastBandwidthReset = steadyTimeMillis();
			lastBandwidth = bytesUploaded * 8;
			bytesUploaded = 0;
		}
		return lastBandwidth;
	}

	public static final int getVertexesPerSecond() {
		int ret = vertexDrawn;
		vertexDrawn = 0;
		return ret;
	}

	public static final int getTrianglesPerSecond() {
		int ret = triangleDrawn;
		triangleDrawn = 0;
		return ret;
	}

	public static boolean sync(int limitFramerate, long[] timerPtr) {
		boolean limitFPS = limitFramerate > 0 && limitFramerate <= 1000;
		boolean blocked = false;
		
		if(limitFPS) {
			long frameMillis = (1000l / limitFramerate);
			if(timerPtr[0] == 0l) {
				timerPtr[0] = steadyTimeMillis() + frameMillis;
			}else {
				long millis = steadyTimeMillis();
				long remaining = timerPtr[0] - millis;
				if(remaining > 0) {
					if(isWebGL && immediateContinueSupported()) {
						immediateContinue(); // cannot stack setTimeouts, or it will throttle
						millis = steadyTimeMillis();
						remaining = timerPtr[0] - millis;
						if(remaining > 0) {
							sleep((int)remaining);
							millis = steadyTimeMillis();
						}
					}else {
						sleep((int)remaining);
						millis = steadyTimeMillis();
					}
					blocked = true;
				}
				if((timerPtr[0] += frameMillis) < millis) {
					timerPtr[0] = millis;
				}
			}
		}else {
			timerPtr[0] = 0l;
		}
		
		return blocked;
	}

}
