Source: common/Renderer.js

/**
 *	@class Renderer
 *	@memberof SQR
 *
 *	@description Represents the rendering engine
 */
SQR.Renderer = function(c, options, onError) {

	var context;

	context = c && c.setAsCurrent ? c : SQR.Context(c, options, onError);

	var r = {
		currentTime: 0,
		deltaTime: 0,
		autoClear: true,
		context: context
	};
	
	var renderObjects = [], transparentObjects = [];
	var startTime, time;

	var updateTransform = function(t, camera, options) {

		if(!t.active) return;

		if(!t.__transformed) {
			t.transformWorld();
			t.__transformed = false;
		} 

		t.transformView(camera ? camera.inverseWorldMatrix : null);

		if(t.clip) t.clip.update(t, time, r.deltaTime);
		if(options.pointer3d) options.pointer3d.onTransform(t);
		
		if (t.numChildren > 0) {
			for (var i = 0; i < t.numChildren; i++) {
				updateTransform(t.children[i], camera, options);
			}
		}

		if(t.buffer && t.shader) {
			if(t.transparent) transparentObjects.push(t);
			else renderObjects.push(t);
		}
		
	}

	var lastBuffer, lastShader, shaderChanged, bufferChanged;
	var defOpts = {};
	var camrig = [];

	var setCommonUniforms = function(camera, lastShader) {
		if(camera) lastShader.setUniform('uEyePosition', camera.globalPosition);

		var p = (camera && camera.projection) ? camera.projection : r.projection;
		if(p) lastShader.setUniform('uProjection', p).setUniform('uNear', p.near).setUniform('uFar', p.far);

		lastShader.setUniform('uTime', time);
	}

	r.clearColor = function(c, g, b, a) {
		// c is a SQR.Color and we can ignore the rest
		if(c.r != null) {
			if(c.a == undefined) c.a = 1;
			context.gl.clearColor(c.r, c.g, c.b, c.a);
		} else {
			if(a == undefined) a = 1;
			context.gl.clearColor(c, g, b, a);
		}

		context.gl.clear(SQR.gl.COLOR_BUFFER_BIT);
		return r;
	}

	r.render = function(root, camera, options) {
		context.setAsCurrent();
		r.tick();
		r.beforeDraw(camera, options);
		r.update(root, camera, options);
		r.draw(null, camera, options);
		r.afterDraw(options);
	}


	r.tick = function() {
		if(!startTime) startTime = new Date().getTime();
		time = new Date().getTime() - startTime;
		r.deltaTime = time - r.currentTime;
		r.currentTime = time;
		return r;
	}

	r.beforeDraw = function(camera, options) {
		options = options || defOpts;
		if(options.pointer3d) {
			var p = (camera && camera.projection) ? camera.projection : r.projection;
			options.pointer3d.fromMousePosition(camera || root, p);
		}
		return r;
	}

	r.update = function(root, camera, options) {
		options = options || defOpts;

		renderObjects.length = 0;
		transparentObjects.length = 0;
		camrig.length = 0;

		if(camera) {

			var a = camera;
			while(a) {
				camrig.unshift(a);
				a = a.parent;
			}

			for(var i = 0, l = camrig.length; i < l; i++) {
				camrig[i].transformWorld();
				camrig[i].__transformed = true;
			};

			camera.computeInverseMatrix();
		}

		updateTransform(root, camera, options);
		return r;
	}	

	r.draw = function(root, camera, options) {
		var gl = SQR.gl;
		if(!gl) return;
		options = options || defOpts;

		if(r.autoClear) context.clear();

		gl.enable(gl.DEPTH_TEST);
		gl.depthMask(true);
		gl.disable(gl.STENCIL_TEST);
		gl.frontFace(gl.CW);
		
		if(options.customGLSetup) {
			options.customGLSetup(gl);
		}

		if(root && root.buffer && root.shader) {
			renderObjects.push(root);
		}

		renderObjects = renderObjects.concat(transparentObjects);

		var objectsToRender = renderObjects.length, ro, 
			lastBuffer = null, 
			lastShader = null,
			transparentRendering = false;
		

		var hasReplacementShader = options && options.replacementShader;

		if(hasReplacementShader) {
			lastShader = options.replacementShader.use().updateTextures();
			setCommonUniforms(camera, lastShader);
		}

		for(var i = 0; i < objectsToRender; i++) {

			shaderChanged = false, bufferChanged = false;

			var ro = renderObjects[i];

			if(ro.transparent) {
				if(!transparentRendering) {
					gl.enable(gl.BLEND);
					transparentRendering = true;
				}
				gl.blendFunc(ro.srcFactor, ro.dstFactor);
			}

			if(lastBuffer != ro.buffer) {
				lastBuffer = ro.buffer;
				lastBuffer.bind();
				bufferChanged = true;
			}

			if((lastShader != ro.shader) && !hasReplacementShader) {
				lastShader = ro.shader.use().updateTextures();
				setCommonUniforms(camera, lastShader);
				shaderChanged = true;
			}

			if(shaderChanged || bufferChanged) {
				lastShader.attribPointers(lastBuffer);
			}

			ro.draw(options);
		}

		gl.disable(gl.BLEND);
		return r;
	}

	r.afterDraw = function(options) {
		options = options || defOpts;
		if(options.pointer3d) options.pointer3d.onAfterRender();
		return r;
	}


	r.renderToScreen = function() {
		var gl = SQR.gl;
		gl.bindFramebuffer(gl.FRAMEBUFFER, null);
	}

	// Default the clear color to black 
	r.clearColor(0, 0, 0, 1);

	return r;

}