text: 
  /**
  * The global scope that contains all the classes from LiteGL and also all the enums of WebGL so you dont need to create a context to use the values.
  * @class GL
  */
  
  /**
  * creates a new WebGL context (it can create the canvas or use an existing one)
  * @method create
  * @param {Object} options supported are: 
  * - width
  * - height
  * - canvas
  * - container (string or element)
  * @return {WebGLRenderingContext} webgl context with all the extra functions (check gl in the doc for more info)
  */
  GL.create = function(options) {
  	options = options || {};
  	var canvas = null;
  	if(options.canvas)
  	{
  		if(typeof(options.canvas) == "string")
  		{
  			canvas = document.getElementById( options.canvas );
  			if(!canvas) throw("Canvas element not found: " + options.canvas );
  		}
  		else 
  			canvas = options.canvas;
  	}
  	else
  	{
  		var root = null;
  		if(options.container)
  			root = options.container.constructor === String ? document.querySelector( options.container ) : options.container;
  		if(root && !options.width)
  		{
  			var rect = root.getBoundingClientRect();
  			options.width = rect.width;
  			options.height = rect.height;
  		}
  
  		canvas = createCanvas(  options.width || 800, options.height || 600 );
  		if(root)
  			root.appendChild(canvas);
  	}
  
  	if (!('alpha' in options)) options.alpha = false;
  
  
  	/**
  	* the webgl context returned by GL.create, its a WebGLRenderingContext with some extra methods added
  	* @class gl
  	*/
  	var gl = null;
  
  	var seq = null;
  	if(options.version == 2)	
  		seq = ['webgl2','experimental-webgl2'];
  	else if(options.version == 1 || options.version === undefined) //default
  		seq = ['webgl','experimental-webgl'];
  	else if(options.version === 0) //latest
  		seq = ['webgl2','experimental-webgl2','webgl','experimental-webgl'];
  
  	if(!seq)
  		throw 'Incorrect WebGL version, must be 1 or 2';
  
  	var context_options = {
  		alpha: options.alpha === undefined ? true : options.alpha,
  		depth: options.depth === undefined ? true : options.depth,
  		stencil: options.stencil === undefined ? true : options.stencil,
  		antialias: options.antialias === undefined ? true : options.antialias,
  		premultipliedAlpha: options.premultipliedAlpha === undefined ? true : options.premultipliedAlpha,
  		preserveDrawingBuffer: options.preserveDrawingBuffer === undefined ? true : options.preserveDrawingBuffer
  	};
  
  	for(var i = 0; i < seq.length; ++i)
  	{
  		try { gl = canvas.getContext( seq[i], context_options ); } catch (e) {}
  		if(gl)
  			break;
  	}
  
  	if (!gl)
  	{
  		if( canvas.getContext( "webgl" ) )
  			throw 'WebGL supported but not with those parameters';
  		throw 'WebGL not supported';
  	}
  
  	//context globals
  	gl.webgl_version = gl.constructor.name === "WebGL2RenderingContext" ? 2 : 1;
  	global.gl = gl;
  	canvas.is_webgl = true;
  	canvas.gl = gl;
  	gl.context_id = this.last_context_id++;
  
  	//get some common extensions for webgl 1
  	gl.extensions = {};
  	gl.extensions["OES_standard_derivatives"] = gl.derivatives_supported = gl.getExtension('OES_standard_derivatives') || false;
  	gl.extensions["WEBGL_depth_texture"] = gl.getExtension("WEBGL_depth_texture") || gl.getExtension("WEBKIT_WEBGL_depth_texture") || gl.getExtension("MOZ_WEBGL_depth_texture");
  	gl.extensions["OES_element_index_uint"] = gl.getExtension("OES_element_index_uint");
  	gl.extensions["WEBGL_draw_buffers"] = gl.getExtension("WEBGL_draw_buffers");
  	gl.extensions["EXT_shader_texture_lod"] = gl.getExtension("EXT_shader_texture_lod");
  	gl.extensions["EXT_sRGB"] = gl.getExtension("EXT_sRGB");
  	gl.extensions["EXT_texture_filter_anisotropic"] = gl.getExtension("EXT_texture_filter_anisotropic") || gl.getExtension("WEBKIT_EXT_texture_filter_anisotropic") || gl.getExtension("MOZ_EXT_texture_filter_anisotropic");
  	gl.extensions["EXT_frag_depth"] = gl.getExtension("EXT_frag_depth") || gl.getExtension("WEBKIT_EXT_frag_depth") || gl.getExtension("MOZ_EXT_frag_depth");
  	gl.extensions["WEBGL_lose_context"] = gl.getExtension("WEBGL_lose_context") || gl.getExtension("WEBKIT_WEBGL_lose_context") || gl.getExtension("MOZ_WEBGL_lose_context");
  	gl.extensions["ANGLE_instanced_arrays"] = gl.getExtension("ANGLE_instanced_arrays");
  	gl.extensions["disjoint_timer_query"] = gl.getExtension("EXT_disjoint_timer_query");
  
  	//for float textures
  	gl.extensions["OES_texture_float_linear"] = gl.getExtension("OES_texture_float_linear");
  	if(gl.extensions["OES_texture_float_linear"])
  		gl.extensions["OES_texture_float"] = gl.getExtension("OES_texture_float");
  	gl.extensions["EXT_color_buffer_float"] = gl.getExtension("EXT_color_buffer_float");
  
  	//for half float textures in webgl 1 require extension
  	gl.extensions["OES_texture_half_float_linear"] = gl.getExtension("OES_texture_half_float_linear");
  	if(gl.extensions["OES_texture_half_float_linear"])
  		gl.extensions["OES_texture_half_float"] = gl.getExtension("OES_texture_half_float");
  
  	if( gl.webgl_version == 1 )
  		gl.HIGH_PRECISION_FORMAT = gl.extensions["OES_texture_half_float"] ? GL.HALF_FLOAT_OES : (gl.extensions["OES_texture_float"] ? GL.FLOAT : GL.UNSIGNED_BYTE); //because Firefox dont support half float
  	else
  		gl.HIGH_PRECISION_FORMAT = GL.HALF_FLOAT_OES;
  
  	gl.max_texture_units = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS);
  
  	//viewport hack to retrieve it without using getParameter (which is slow and generates garbage)
  	if(!gl._viewport_func)
  	{
  		gl._viewport_func = gl.viewport;
  		gl.viewport_data = new Float32Array([0,0,gl.canvas.width,gl.canvas.height]); //32000 max viewport, I guess its fine
  		gl.viewport = function(a,b,c,d) { var v = this.viewport_data; v[0] = a|0; v[1] = b|0; v[2] = c|0; v[3] = d|0; this._viewport_func(a,b,c,d); }
  		gl.getViewport = function(v) { 
  			if(v) { v[0] = gl.viewport_data[0]; v[1] = gl.viewport_data[1]; v[2] = gl.viewport_data[2]; v[3] = gl.viewport_data[3]; return v; }
  			return new Float32Array( gl.viewport_data );
  		};
  		gl.setViewport = function( v, flip_y ) {
  			gl.viewport_data.set(v);
  			if(flip_y)
  				gl.viewport_data[1] = this.drawingBufferHeight-v[1]-v[3];
  			this._viewport_func(v[0],gl.viewport_data[1],v[2],v[3]);
  		};
  	}
  	else
  		console.warn("Creating LiteGL context over the same canvas twice");
  
  	//reverse names helper (assuming no names repeated)
  	if(!GL.reverse)
  	{
  		GL.reverse = {}; 
  		for(var i in gl)
  			if( gl[i] && gl[i].constructor === Number )
  				GL.reverse[ gl[i] ] = i;
  	}
  	
  	//just some checks
  	if(typeof(glMatrix) == "undefined")
  		throw("glMatrix not found, LiteGL requires glMatrix to be included");
  
  	var last_click_time = 0;
  
  	//some global containers, use them to reuse assets
  	gl.shaders = {};
  	gl.textures = {};
  	gl.meshes = {};
  
  	gl.draw_calls = 0;
  
  	/**
  	* sets this context as the current global gl context (in case you have more than one)
  	* @method makeCurrent
  	*/
  	gl.makeCurrent = function()
  	{
  		global.gl = this;
  	}
  
  	/**
  	* executes callback inside this webgl context
  	* @method execute
  	* @param {Function} callback
  	*/
  	gl.execute = function(callback)
  	{
  		var old_gl = global.gl;
  		global.gl = this;
  		callback();
  		global.gl = old_gl;
  	}
  
  
  	/**
  	* Launch animation loop (calls gl.onupdate and gl.ondraw every frame)
  	* example: gl.ondraw = function(){ ... }   or  gl.onupdate = function(dt) { ... }
  	* @method animate
  	*/
  	gl.animate = function(v) {
  		if(v === false)
  		{
  			global.cancelAnimationFrame( this._requestFrame_id );
  			this._requestFrame_id = null;
  			return;
  		}
  
  		var post = global.requestAnimationFrame;
  		var time = getTime();
  		var context = this;
  		var last_mouse_buttons = 0;
  
  		//loop only if browser tab visible
  		function loop() {
  			if(gl.destroyed) //to stop rendering once it is destroyed
  				return;
  
  			context._requestFrame_id = post(loop); //do it first, in case it crashes
  
  			var now = getTime();
  			var dt = (now - time) * 0.001;
  			if(context.mouse)
  				context.mouse.last_buttons = last_mouse_buttons;
  			last_mouse_buttons = context.mouse.buttons;
  			if (context.onupdate) 
  				context.onupdate(dt);
  			LEvent.trigger( context, "update", dt);
  			if (context.ondraw)
  			{
  				//make sure the ondraw is called using this gl context (in case there is more than one)
  				var old_gl = global.gl;
  				global.gl = context;
  				//call ondraw
  				context.ondraw();
  				LEvent.trigger(context,"draw");
  				//restore old context
  				global.gl = old_gl;
  			}
  			time = now;
  		}
  		this._requestFrame_id = post(loop); //launch main loop
  	}	
  
  	//store binded to be able to remove them if destroyed
  	/*
  	var _binded_events = [];
  	function addEvent(object, type, callback)
  	{
  		_binded_events.push(object,type,callback);
  	}
  	*/
  
  	/**
  	* Destroy this WebGL context (removes also the Canvas from the DOM)
  	* @method destroy
  	*/
  	gl.destroy = function() {
  		//unbind global events
  		if(onkey_handler)
  		{
  			document.removeEventListener("keydown", onkey_handler );
  			document.removeEventListener("keyup", onkey_handler );
  		}
  
  		if(this.canvas.parentNode)
  			this.canvas.parentNode.removeChild(this.canvas);
  		this.destroyed = true;
  		if(global.gl == this)
  			global.gl = null;
  	}
  
  	var mouse = gl.mouse = {
  		buttons: 0, //this should always be up-to-date with mouse state
  		last_buttons: 0, //button state in the previous frame
  		left_button: false,
  		middle_button: false,
  		right_button: false,
  		position: new Float32Array(2),
  		x:0, //in canvas coordinates
  		y:0,
  		deltax: 0,
  		deltay: 0,
  		clientx:0, //in client coordinates
  		clienty:0,
  		isInsideRect: function(x,y,w,h, flip_y )
  		{
  			var mouse_y = this.y;
  			if(flip_y)
  				mouse_y = gl.canvas.height - mouse_y;
  			if( this.x > x && this.x < x + w &&
  				mouse_y > y && mouse_y < y + h)
  				return true;
  			return false;
  		},
  
  		/**
  		* returns true if button num is pressed (where num could be GL.LEFT_MOUSE_BUTTON, GL.RIGHT_MOUSE_BUTTON, GL.MIDDLE_MOUSE_BUTTON
  		* @method captureMouse
  		* @param {boolean} capture_wheel capture also the mouse wheel
  		*/
  		isButtonPressed: function(num)
  		{
  			if(num == GL.LEFT_MOUSE_BUTTON)
  				return this.buttons & GL.LEFT_MOUSE_BUTTON_MASK;
  			if(num == GL.MIDDLE_MOUSE_BUTTON)
  				return this.buttons & GL.MIDDLE_MOUSE_BUTTON_MASK;
  			if(num == GL.RIGHT_MOUSE_BUTTON)
  				return this.buttons & GL.RIGHT_MOUSE_BUTTON_MASK;
  		},
  
  		wasButtonPressed: function(num)
  		{
  			var mask = 0;
  			if(num == GL.LEFT_MOUSE_BUTTON)
  				mask = GL.LEFT_MOUSE_BUTTON_MASK;
  			else if(num == GL.MIDDLE_MOUSE_BUTTON)
  				mask = GL.MIDDLE_MOUSE_BUTTON_MASK;
  			else if(num == GL.RIGHT_MOUSE_BUTTON)
  				mask = GL.RIGHT_MOUSE_BUTTON_MASK;
  			return (this.buttons & mask) && !(this.last_buttons & mask);
  		}
  	};
  
  	/**
  	* Tells the system to capture mouse events on the canvas. 
  	* This will trigger onmousedown, onmousemove, onmouseup, onmousewheel callbacks assigned in the gl context
  	* example: gl.onmousedown = function(e){ ... }
  	* The event is a regular MouseEvent with some extra parameters
  	* @method captureMouse
  	* @param {boolean} capture_wheel capture also the mouse wheel
  	*/
  	gl.captureMouse = function(capture_wheel, translate_touchs ) {
  
  		canvas.addEventListener("mousedown", onmouse);
  		canvas.addEventListener("mousemove", onmouse);
  		canvas.addEventListener("dragstart", onmouse);
  		if(capture_wheel)
  		{
  			canvas.addEventListener("mousewheel", onmouse, false);
  			canvas.addEventListener("wheel", onmouse, false);
  			//canvas.addEventListener("DOMMouseScroll", onmouse, false); //deprecated or non-standard
  		}
  		//prevent right click context menu
  		canvas.addEventListener("contextmenu", function(e) { e.preventDefault(); return false; });
  
  		if( translate_touchs )
  			this.captureTouch( true );
  	}
  
  	function onmouse(e) {
  
  		if(gl.ignore_events)
  			return;
  		//console.log(e.type); //debug
  		var old_mouse_mask = gl.mouse.buttons;
  		GL.augmentEvent(e, canvas);
  		e.eventType = e.eventType || e.type; //type cannot be overwritten, so I make a clone to allow me to overwrite
  		var now = getTime();
  
  		//gl.mouse info
  		mouse.dragging = e.dragging;
  		mouse.position[0] = e.canvasx;
  		mouse.position[1] = e.canvasy;
  		mouse.x = e.canvasx;
  		mouse.y = e.canvasy;
  		mouse.mousex = e.mousex;
  		mouse.mousey = e.mousey;
  		mouse.canvasx = e.canvasx;
  		mouse.canvasy = e.canvasy;
  		mouse.clientx = e.mousex;
  		mouse.clienty = e.mousey;
  		mouse.buttons = e.buttons;
  		mouse.left_button = !!(mouse.buttons & GL.LEFT_MOUSE_BUTTON_MASK);
  		mouse.middle_button = !!(mouse.buttons & GL.MIDDLE_MOUSE_BUTTON_MASK);
  		mouse.right_button = !!(mouse.buttons & GL.RIGHT_MOUSE_BUTTON_MASK);
  
  		if(e.eventType == "mousedown")
  		{
  			if(old_mouse_mask == 0) //no mouse button was pressed till now
  			{
  				canvas.removeEventListener("mousemove", onmouse);
  				var doc = canvas.ownerDocument;
  				doc.addEventListener("mousemove", onmouse);
  				doc.addEventListener("mouseup", onmouse);
  			}
  			last_click_time = now;
  
  			if(gl.onmousedown)
  				gl.onmousedown(e);
  			LEvent.trigger(gl,"mousedown");
  		}
  		else if(e.eventType == "mousemove")
  		{ 
  			if(gl.onmousemove)
  				gl.onmousemove(e); 
  			LEvent.trigger(gl,"mousemove",e);
  		} 
  		else if(e.eventType == "mouseup")
  		{
  			if(gl.mouse.buttons == 0) //no more buttons pressed
  			{
  				canvas.addEventListener("mousemove", onmouse);
  				var doc = canvas.ownerDocument;
  				doc.removeEventListener("mousemove", onmouse);
  				doc.removeEventListener("mouseup", onmouse);
  			}
  			e.click_time = now - last_click_time;
  			//last_click_time = now; //commented to avoid reseting click time when unclicking two mouse buttons
  
  			if(gl.onmouseup)
  				gl.onmouseup(e);
  			LEvent.trigger(gl,"mouseup",e);
  		}
  		else if((e.eventType == "mousewheel" || e.eventType == "wheel" || e.eventType == "DOMMouseScroll"))
  		{ 
  			e.eventType = "mousewheel";
  			if(e.type == "wheel")
  				e.wheel = -e.deltaY; //in firefox deltaY is 1 while in Chrome is 120
  			else
  				e.wheel = (e.wheelDeltaY != null ? e.wheelDeltaY : e.detail * -60);
  
  			//from stack overflow
  			//firefox doesnt have wheelDelta
  			e.delta = e.wheelDelta !== undefined ? (e.wheelDelta/40) : (e.deltaY ? -e.deltaY/3 : 0);
  			//console.log(e.delta);
  			if(gl.onmousewheel)
  				gl.onmousewheel(e);
  
  			LEvent.trigger(gl, "mousewheel", e);
  		}
  		else if(e.eventType == "dragstart")
  		{
  			if(gl.ondragstart)
  				gl.ondragstart(e);
  			LEvent.trigger(gl, "dragstart", e);
  		}
  
  		if(gl.onmouse)
  			gl.onmouse(e);
  
  		if(!e.skip_preventDefault)
  		{
  			if(e.eventType != "mousemove")
  				e.stopPropagation();
  			e.preventDefault();
  			return false;
  		}
  	}
  
  	var translate_touches = false;
  
  	gl.captureTouch = function( translate_to_mouse_events )
  	{
  		translate_touches = translate_to_mouse_events;
  
  		canvas.addEventListener("touchstart", ontouch, true);
  		canvas.addEventListener("touchmove", ontouch, true);
  		canvas.addEventListener("touchend", ontouch, true);
  		canvas.addEventListener("touchcancel", ontouch, true);   
  
  		canvas.addEventListener('gesturestart', ongesture );
  		canvas.addEventListener('gesturechange', ongesture );
  		canvas.addEventListener('gestureend', ongesture );
  	}
  
  	//translates touch events in mouseevents
  	function ontouch( e )
  	{
  		var touches = e.changedTouches,
  			first = touches[0],
  			type = "";
  
  		if( gl.ontouch && gl.ontouch(e) === true )
  			return;
  
  		if( LEvent.trigger( gl, e.type, e ) === true )
  			return;
  
  		if(!translate_touches)
  			return;
  
  		//ignore secondary touches
          if(e.touches.length && e.changedTouches[0].identifier !== e.touches[0].identifier)
          	return;
          	
  		if(touches > 1)
  			return;
  
  		 switch(e.type)
  		{
  			case "touchstart": type = "mousedown"; break;
  			case "touchmove":  type = "mousemove"; break;        
  			case "touchend":   type = "mouseup"; break;
  			default: return;
  		}
  
  		var simulatedEvent = document.createEvent("MouseEvent");
  		simulatedEvent.initMouseEvent(type, true, true, window, 1,
  								  first.screenX, first.screenY,
  								  first.clientX, first.clientY, false,
  								  false, false, false, 0/*left*/, null);
  		simulatedEvent.originalEvent = simulatedEvent;
  		simulatedEvent.is_touch = true;
  		first.target.dispatchEvent( simulatedEvent );
  
  		//if we block this touch (to avoid weird canvas draggings) then we are blocking the gestures
  		e.preventDefault();
  	}
  
  	function ongesture(e)
  	{
  		e.eventType = e.type;
  
  		if(gl.ongesture && gl.ongesture(e) === false )
  			return;
  
  		if( LEvent.trigger( gl, e.type, e ) === false )
  			return;
  
  		e.preventDefault();
  	}
  
  	var keys = gl.keys = {};
  
  	/**
  	* Tells the system to capture key events on the canvas. This will trigger onkey
  	* @method captureKeys
  	* @param {boolean} prevent_default prevent default behaviour (like scroll on the web, etc)
  	* @param {boolean} only_canvas only caches keyboard events if they happen when the canvas is in focus
  	*/
  	var onkey_handler = null;
  	gl.captureKeys = function( prevent_default, only_canvas ) {
  		if(onkey_handler) 
  			return;
  		gl.keys = {};
  
  		var target = only_canvas ? gl.canvas : document;
  
  		document.addEventListener("keydown", inner );
  		document.addEventListener("keyup", inner );
  		function inner(e) { onkey(e, prevent_default); }
  		onkey_handler = inner;
  	}
  
  
  
  	function onkey(e, prevent_default)
  	{
  		e.eventType = e.type; //type cannot be overwritten, so I make a clone to allow me to overwrite
  
  		var target_element = e.target.nodeName.toLowerCase();
  		if(target_element === "input" || target_element === "textarea" || target_element === "select")
  			return;
  
  		e.character = String.fromCharCode(e.keyCode).toLowerCase();
  		var prev_state = false;
  		var key = GL.mapKeyCode(e.keyCode);
  		if(!key) //this key doesnt look like an special key
  			key = e.character;
  
  		//regular key
  		if (!e.altKey && !e.ctrlKey && !e.metaKey) {
  			if (key) 
  				gl.keys[key] = e.type == "keydown";
  			prev_state = gl.keys[e.keyCode];
  			gl.keys[e.keyCode] = e.type == "keydown";
  		}
  
  		//avoid repetition if key stays pressed
  		if(prev_state != gl.keys[e.keyCode])
  		{
  			if(e.type == "keydown" && gl.onkeydown) 
  				gl.onkeydown(e);
  			else if(e.type == "keyup" && gl.onkeyup) 
  				gl.onkeyup(e);
  			LEvent.trigger(gl, e.type, e);
  		}
  
  		if(gl.onkey)
  			gl.onkey(e);
  
  		if(prevent_default && (e.isChar || GL.blockable_keys[e.keyIdentifier || e.key ]) )
  			e.preventDefault();
  	}
  
  	//gamepads
  	gl.gamepads = null;
  	/*
  	function onButton(e, pressed)
  	{
  		console.log(e);
  		if(pressed && gl.onbuttondown)
  			gl.onbuttondown(e);
  		else if(!pressed && gl.onbuttonup)
  			gl.onbuttonup(e);
  		if(gl.onbutton)
  			gl.onbutton(e);
  		LEvent.trigger(gl, pressed ? "buttondown" : "buttonup", e );
  	}
  	function onGamepad(e)
  	{
  		console.log(e);
  		if(gl.ongamepad) 
  			gl.ongamepad(e);
  	}
  	*/
  
  	/**
  	* Tells the system to capture gamepad events on the canvas. 
  	* @method captureGamepads
  	*/
  	gl.captureGamepads = function()
  	{
  		var getGamepads = navigator.getGamepads || navigator.webkitGetGamepads || navigator.mozGetGamepads; 
  		if(!getGamepads) return;
  		this.gamepads = getGamepads.call(navigator);
  
  		//only in firefox, so I cannot rely on this
  		/*
  		window.addEventListener("gamepadButtonDown", function(e) { onButton(e, true); }, false);
  		window.addEventListener("MozGamepadButtonDown", function(e) { onButton(e, true); }, false);
  		window.addEventListener("WebkitGamepadButtonDown", function(e) { onButton(e, true); }, false);
  		window.addEventListener("gamepadButtonUp", function(e) { onButton(e, false); }, false);
  		window.addEventListener("MozGamepadButtonUp", function(e) { onButton(e, false); }, false);
  		window.addEventListener("WebkitGamepadButtonUp", function(e) { onButton(e, false); }, false);
  		window.addEventListener("gamepadconnected", onGamepad, false);
  		window.addEventListener("gamepaddisconnected", onGamepad, false);
  		*/
  
  	}
  
  	/**
  	* returns the detected gamepads on the system
  	* @method getGamepads
  	* @param {bool} skip_mapping if set to true it returns the basic gamepad, otherwise it returns a class with mapping info to XBOX controller
  	*/
  	gl.getGamepads = function(skip_mapping)
  	{
  		//gamepads
  		var getGamepads = navigator.getGamepads || navigator.webkitGetGamepads || navigator.mozGetGamepads; 
  		if(!getGamepads)
  			return;
  		var gamepads = getGamepads.call(navigator);
  		if(!this.gamepads)
  			this.gamepads = [];
  
  		//iterate to generate events
  		for(var i = 0; i < 4; i++)
  		{
  			var gamepad = gamepads[i]; //current state
  
  			if(gamepad && !skip_mapping)
  				addGamepadXBOXmapping(gamepad);
  
  			//launch connected gamepads events
  			if(gamepad && !gamepad.prev_buttons)
  			{
  				gamepad.prev_buttons = new Uint8Array(32);
  				var event = new CustomEvent("gamepadconnected");
  				event.eventType = event.type;
  				event.gamepad = gamepad;
  				if(this.ongamepadconnected)
  					this.ongamepadconnected(event);
  				LEvent.trigger(gl,"gamepadconnected",event);
  			}
  			/*
  			else if(old_gamepad && !gamepad)
  			{
  				var event = new CustomEvent("gamepaddisconnected");
  				event.eventType = event.type;
  				event.gamepad = old_gamepad;
  				if(this.ongamepaddisconnected)
  					this.ongamepaddisconnected(event);
  				LEvent.trigger(gl,"gamepaddisconnected",event);
  			}
  			*/
  
  			//seek buttons changes to trigger events
  			if(gamepad)
  			{
  				for(var j = 0; j < gamepad.buttons.length; ++j)
  				{
  					var button = gamepad.buttons[j];
  					button.was_pressed = false;
  					if( button.pressed && !gamepad.prev_buttons[j] )
  					{
  						button.was_pressed = true;
  						var event = new CustomEvent("gamepadButtonDown");
  						event.eventType = event.type;
  						event.button = button;
  						event.which = j;
  						event.gamepad = gamepad;
  						if(gl.onbuttondown)
  							gl.onbuttondown(event);
  						LEvent.trigger(gl,"buttondown",event);
  					}
  					else if( !button.pressed && gamepad.prev_buttons[j] )
  					{
  						var event = new CustomEvent("gamepadButtonUp");
  						event.eventType = event.type;
  						event.button = button;
  						event.which = j;
  						event.gamepad = gamepad;
  						if(gl.onbuttondown)
  							gl.onbuttondown(event);
  						LEvent.trigger(gl,"buttonup",event);
  					}
  
  					gamepad.prev_buttons[j] = button.pressed ? 1 : 0;
  				}
  			}
  		}
  		this.gamepads = gamepads;
  		return gamepads;
  	}
  
  	function addGamepadXBOXmapping(gamepad)
  	{
  		//xbox controller mapping
  		var xbox = gamepad.xbox || { axes:[], buttons:{}, hat: ""};
  		xbox.axes["lx"] = gamepad.axes[0];
  		xbox.axes["ly"] = gamepad.axes[1];
  		xbox.axes["rx"] = gamepad.axes[2];
  		xbox.axes["ry"] = gamepad.axes[3];
  		xbox.axes["triggers"] = gamepad.axes[4];
  
  		for(var i = 0; i < gamepad.buttons.length; i++)
  		{
  			switch(i) //I use a switch to ensure that a player with another gamepad could play
  			{
  				case 0: xbox.buttons["a"] = gamepad.buttons[i].pressed; break;
  				case 1: xbox.buttons["b"] = gamepad.buttons[i].pressed; break;
  				case 2: xbox.buttons["x"] = gamepad.buttons[i].pressed; break;
  				case 3: xbox.buttons["y"] = gamepad.buttons[i].pressed; break;
  				case 4: xbox.buttons["lb"] = gamepad.buttons[i].pressed; break;
  				case 5: xbox.buttons["rb"] = gamepad.buttons[i].pressed; break;
  				case 6: xbox.buttons["lt"] = gamepad.buttons[i].pressed; break;
  				case 7: xbox.buttons["rt"] = gamepad.buttons[i].pressed; break;
  				case 8: xbox.buttons["back"] = gamepad.buttons[i].pressed; break;
  				case 9: xbox.buttons["start"] = gamepad.buttons[i].pressed; break;
  				case 10: xbox.buttons["ls"] = gamepad.buttons[i].pressed; break;
  				case 11: xbox.buttons["rs"] = gamepad.buttons[i].pressed; break;
  				case 12: if( gamepad.buttons[i].pressed) xbox.hat += "up"; break;
  				case 13: if( gamepad.buttons[i].pressed) xbox.hat += "down"; break;
  				case 14: if( gamepad.buttons[i].pressed) xbox.hat += "left"; break;
  				case 15: if( gamepad.buttons[i].pressed) xbox.hat += "right"; break;
  				case 16: xbox.buttons["home"] = gamepad.buttons[i].pressed; break;
  				default:
  			}
  		}
  		gamepad.xbox = xbox;
  	}
  
  	/**
  	* launches de canvas in fullscreen mode
  	* @method fullscreen
  	*/
  	gl.fullscreen = function()
  	{
  		var canvas = this.canvas;
  		if(canvas.requestFullScreen)
  			canvas.requestFullScreen();
  		else if(canvas.webkitRequestFullScreen)
  			canvas.webkitRequestFullScreen();
  		else if(canvas.mozRequestFullScreen)
  			canvas.mozRequestFullScreen();
  		else
  			console.error("Fullscreen not supported");
  	}
  
  	/**
  	* returns a canvas with a snapshot of an area
  	* this is safer than using the canvas itself due to internals of webgl
  	* @method snapshot
  	* @param {Number} startx viewport x coordinate
  	* @param {Number} starty viewport y coordinate from bottom
  	* @param {Number} areax viewport area width
  	* @param {Number} areay viewport area height
  	* @return {Canvas} canvas
  	*/
  	gl.snapshot = function(startx, starty, areax, areay, skip_reverse)
  	{
  		var canvas = createCanvas(areax,areay);
  		var ctx = canvas.getContext("2d");
  		var pixels = ctx.getImageData(0,0,canvas.width,canvas.height);
  
  		var buffer = new Uint8Array(areax * areay * 4);
  		gl.readPixels(startx, starty, canvas.width, canvas.height, gl.RGBA,gl.UNSIGNED_BYTE, buffer);
  
  		pixels.data.set( buffer );
  		ctx.putImageData(pixels,0,0);
  
  		if(skip_reverse)
  			return canvas;
  
  		//flip image 
  		var final_canvas = createCanvas(areax,areay);
  		var ctx = final_canvas.getContext("2d");
  		ctx.translate(0,areay);
  		ctx.scale(1,-1);
  		ctx.drawImage(canvas,0,0);
  
  		return final_canvas;
  	}
  
  	//from https://webgl2fundamentals.org/webgl/lessons/webgl1-to-webgl2.html
  	function getAndApplyExtension( gl, name ) {
  		var ext = gl.getExtension(name);
  		if (!ext) {
  			return false;
  		}
  		var suffix = name.split("_")[0];
  		var prefix = suffix = '_';
  		var suffixRE = new RegExp(suffix + '$');
  		var prefixRE = new RegExp('^' + prefix);
  		for (var key in ext) {
  			var val = ext[key];
  			if (typeof(val) === 'function') {
  				// remove suffix (eg: bindVertexArrayOES -> bindVertexArray)
  				var unsuffixedKey = key.replace(suffixRE, '');
  				if (key.substing)
  					gl[unprefixedKey] = ext[key].bind(ext);
  			} else {
  				var unprefixedKey = key.replace(prefixRE, '');
  				gl[unprefixedKey] = ext[key];
  			}
  		}
  	}
  
  
  	//mini textures manager
  	var loading_textures = {};
  	/**
  	* returns a texture and caches it inside gl.textures[]
  	* @method loadTexture
  	* @param {String} url
  	* @param {Object} options (same options as when creating a texture)
  	* @param {Function} callback function called once the texture is loaded
  	* @return {Texture} texture
  	*/
  	gl.loadTexture = function(url, options, on_load)
  	{
  		if(this.textures[ url ])
  			return this.textures[url];
  
  		if( loading_textures[url] )
  			return null;
  
  		var img = new Image();
  		img.url = url;
  		img.onload = function()
  		{
  			var texture = GL.Texture.fromImage(this, options);
  			texture.img = this;
  			gl.textures[this.url] = texture;
  			delete loading_textures[this.url];
  			if(on_load)
  				on_load(texture);
  		} 
  		img.src = url;
  		loading_textures[url] = true;
  		return null;
  	}
  
  	/**
  	* draws a texture to the viewport
  	* @method drawTexture
  	* @param {Texture} texture
  	* @param {number} x in viewport coordinates 
  	* @param {number} y in viewport coordinates 
  	* @param {number} w in viewport coordinates 
  	* @param {number} h in viewport coordinates 
  	* @param {number} tx texture x in texture coordinates
  	* @param {number} ty texture y in texture coordinates
  	* @param {number} tw texture width in texture coordinates
  	* @param {number} th texture height in texture coordinates
  	*/
  	gl.drawTexture = (function() {
  		//static variables: less garbage
  		var identity = mat3.create();
  		var pos = vec2.create();
  		var size = vec2.create();
  		var area = vec4.create();
  		var white = vec4.fromValues(1,1,1,1);
  		var viewport = vec2.create();
  		var _uniforms = {u_texture: 0, u_position: pos, u_color: white, u_size: size, u_texture_area: area, u_viewport: viewport, u_transform: identity };
  
  		return (function(texture, x,y, w,h, tx,ty, tw,th, shader, uniforms)
  		{
  			pos[0] = x;	pos[1] = y;
  			if(w === undefined)
  				w = texture.width;
  			if(h === undefined)
  				h = texture.height;
  			size[0] = w;
  			size[1] = h;
  
  			if(tx === undefined) tx = 0;
  			if(ty === undefined) ty = 0;
  			if(tw === undefined) tw = texture.width;
  			if(th === undefined) th = texture.height;
  
  			area[0] = tx / texture.width;
  			area[1] = ty / texture.height;
  			area[2] = (tx + tw) / texture.width;
  			area[3] = (ty + th) / texture.height;
  
  			viewport[0] = this.viewport_data[2];
  			viewport[1] = this.viewport_data[3];
  
  			shader = shader || Shader.getPartialQuadShader(this);
  			var mesh = Mesh.getScreenQuad(this);
  			texture.bind(0);
  			shader.uniforms( _uniforms );
  			if( uniforms )
  				shader.uniforms( uniforms );
  			shader.draw( mesh, gl.TRIANGLES );
  		});
  	})();
  
  	gl.canvas.addEventListener("webglcontextlost", function(e) {
  		e.preventDefault();
  		gl.context_lost = true;
  		if(gl.onlosecontext)
  			gl.onlosecontext(e);
  	}, false);
  
  	/**
  	* use it to reset the the initial gl state
  	* @method gl.reset
  	*/
  	gl.reset = function()
  	{
  		//viewport
  		gl.viewport(0,0, this.canvas.width, this.canvas.height );
  
  		//flags
  		gl.disable( gl.BLEND );
  		gl.disable( gl.CULL_FACE );
  		gl.disable( gl.DEPTH_TEST );
  		gl.frontFace( gl.CCW );
  
  		//texture
  		gl._current_texture_drawto = null;
  		gl._current_fbo_color = null;
  		gl._current_fbo_depth = null;
  	}
  
  	gl.dump = function()
  	{
  		console.log("userAgent: ", navigator.userAgent );
  		console.log("Supported extensions:");
  		var extensions = gl.getSupportedExtensions();
  		console.log( extensions.join(",") );
  		var info = [ "VENDOR", "VERSION", "MAX_VERTEX_ATTRIBS", "MAX_VARYING_VECTORS", "MAX_VERTEX_UNIFORM_VECTORS", "MAX_VERTEX_TEXTURE_IMAGE_UNITS", "MAX_FRAGMENT_UNIFORM_VECTORS", "MAX_TEXTURE_SIZE", "MAX_TEXTURE_IMAGE_UNITS" ];
  		console.log("WebGL info:");
  		for(var i in info)
  			console.log(" * " + info[i] + ": " + gl.getParameter( gl[info[i]] ));
  		console.log("*************************************************")
  	}
  
  	//Reset state
  	gl.reset();
  
  	//Return
  	return gl;
  }
  
  GL.mapKeyCode = function(code)
  {
  	var named = {
  		8: 'BACKSPACE',
  		9: 'TAB',
  		13: 'ENTER',
  		16: 'SHIFT',
  		17: 'CTRL',
  		27: 'ESCAPE',
  		32: 'SPACE',
  		37: 'LEFT',
  		38: 'UP',
  		39: 'RIGHT',
  		40: 'DOWN'
  	};
  	return named[code] || (code >= 65 && code <= 90 ? String.fromCharCode(code) : null);
  }
  
  //add useful info to the event
  GL.dragging = false;
  GL.last_pos = [0,0];
  
  //adds extra info to the MouseEvent (coordinates in canvas axis, deltas and button state)
  GL.augmentEvent = function(e, root_element)
  {
  	var offset_left = 0;
  	var offset_top = 0;
  	var b = null;
  
  	root_element = root_element || e.target || gl.canvas;
  	b = root_element.getBoundingClientRect();
  		
  	e.mousex = e.clientX - b.left;
  	e.mousey = e.clientY - b.top;
  	e.canvasx = e.mousex;
  	e.canvasy = b.height - e.mousey;
  	e.deltax = 0;
  	e.deltay = 0;
  	
  	if(e.type == "mousedown")
  	{
  		this.dragging = true;
  		//gl.mouse.buttons |= (1 << e.which); //enable
  	}
  	else if (e.type == "mousemove")
  	{
  	}
  	else if (e.type == "mouseup")
  	{
  		//gl.mouse.buttons = gl.mouse.buttons & ~(1 << e.which);
  		if(e.buttons == 0)
  			this.dragging = false;
  	}
  
  	if( e.movementX !== undefined && !GL.isMobile() ) //pointer lock (mobile gives always zero)
  	{
  		//console.log( e.movementX )
  		e.deltax = e.movementX;
  		e.deltay = e.movementY;
  	}
  	else
  	{
  		e.deltax = e.mousex - this.last_pos[0];
  		e.deltay = e.mousey - this.last_pos[1];
  	}
  	this.last_pos[0] = e.mousex;
  	this.last_pos[1] = e.mousey;
  
  	//insert info in event
  	e.dragging = this.dragging;
  	e.leftButton = !!(gl.mouse.buttons & GL.LEFT_MOUSE_BUTTON_MASK);
  	e.middleButton = !!(gl.mouse.buttons & GL.MIDDLE_MOUSE_BUTTON_MASK);
  	e.rightButton = !!(gl.mouse.buttons & GL.RIGHT_MOUSE_BUTTON_MASK);
  	//shitty non-coherent API, e.buttons use 1:left,2:right,4:middle) but e.button uses (0:left,1:middle,2:right)
  	e.buttons_mask = 0;
  	if( e.leftButton ) e.buttons_mask = 1;
  	if( e.middleButton ) e.buttons_mask |= 2;
  	if( e.rightButton ) e.buttons_mask |= 4;
  	e.isButtonPressed = function(num) { return this.buttons_mask & (1<<num); }
  }
  
  /**
  * Tells you if the app is running on a mobile device (iOS or Android)
  * @method isMobile
  * @return {boolean} true if is running on a iOS or Android device
  */
  GL.isMobile = function()
  {
  	if(this.mobile !== undefined)
  		return this.mobile;
  
  	if(!global.navigator) //server side js?
  		return this.mobile = false;
  
  	if( (navigator.userAgent.match(/iPhone/i)) || 
  		(navigator.userAgent.match(/iPod/i)) || 
  		(navigator.userAgent.match(/iPad/i)) || 
  		(navigator.userAgent.match(/SamsungBrowser/i)) || 
  		(navigator.userAgent.match(/Mobile\ VR/i)) || 
  		(navigator.userAgent.match(/Android/i))) {
  		return this.mobile = true;
  	}
  	return this.mobile = false;
  }