Source: core/renderer/renderer.js

if (typeof(define) !== "function") {
    var define = require("amdefine")(module);
}
define([
        "odin/base/event_emitter",
        "odin/base/device",
        "odin/base/dom",
        "odin/base/util",
        "odin/math/mathf",
        "odin/math/color",
        "odin/math/vec3",
        "odin/math/mat4",
        "odin/core/enums",
        "odin/core/game/log",
        "odin/core/components/particle_system/emitter",
        "odin/core/renderer/shader_chunks",
        "odin/core/renderer/canvas"
    ],
    function(EventEmitter, Device, Dom, util, Mathf, Color, Vec3, Mat4, Enums, Log, Emitter, ShaderChunks, Canvas) {
        "use strict";


        var Blending = Enums.Blending,
            ShadowMapType = Enums.ShadowMapType,
            CullFace = Enums.CullFace,
            Side = Enums.Side,

            LightType = Enums.LightType,
            Shading = Enums.Shading,

            FilterMode = Enums.FilterMode,
            TextureFormat = Enums.TextureFormat,
            TextureWrap = Enums.TextureWrap,

            getWebGLContext = Dom.getWebGLContext,
            addEvent = Dom.addEvent,
            removeEvent = Dom.removeEvent,

            createProgram = Dom.createProgram,
            parseUniformsAttributes = Dom.parseUniformsAttributes,
            getUniformsAttributes = Dom.getUniformsAttributes,

            merge = util.merge,
            copy = util.copy,

            max = Math.max,
            floor = Math.floor,
            clamp = Mathf.clamp,
            isPowerOfTwo = Mathf.isPowerOfTwo,

            defineProperty = Object.defineProperty;


        function Renderer(opts) {
            opts || (opts = {});

            EventEmitter.call(this);

            var _this = this,
                _projScreenMatrix = new Mat4,
                _mat4 = new Mat4,
                _vector3 = new Vec3,
                _gl,
                _extensions,
                _canvas,
                _element,
                _context = false,
                _programs = [];


            this.attributes = merge(opts.attributes || {}, {
                alpha: true,
                antialias: true,
                depth: true,
                premultipliedAlpha: true,
                preserveDrawingBuffer: false,
                stencil: true
            });

            this.autoClear = opts.autoClear != undefined ? opts.autoClear : true;
            this.autoClearColor = opts.autoClearColor != undefined ? opts.autoClearColor : true;
            this.autoClearDepth = opts.autoClearDepth != undefined ? opts.autoClearDepth : true;
            this.autoClearStencil = opts.autoClearStencil != undefined ? opts.autoClearStencil : true;


            this.init = function(canvas) {
                if (_canvas) this.clear();

                _canvas = canvas;
                _element = canvas.element;

                _lastUniforms = {};

                initGL();
                _context = true;
                setDefaultGLState();

                addEvent(_element, "webglcontextlost", handleWebGLContextLost, this);
                addEvent(_element, "webglcontextrestored", handleWebGLContextRestored, this);

                return this;
            };


            this.clear = function() {
                if (!_canvas) return this;

                removeEvent(element, "webglcontextlost", handleWebGLContextLost, this);
                removeEvent(element, "webglcontextrestored", handleWebGLContextRestored, this);

                _gl = undefined
                _canvas = undefined;
                _element = undefined;
                _context = false;
                _programs.length = 0;

                _extensions = undefined;

                _precision = "highp";
                _maxAnisotropy = 0;
                _maxTextures = 0;
                _maxVertexTextures = 0;
                _maxTextureSize = 0;
                _maxCubeTextureSize = 0;
                _maxRenderBufferSize = 0;

                _maxUniforms = 0;
                _maxVaryings = 0;
                _maxAttributes = 0;

                _lastCamera = undefined;
                _lastResizeFn = undefined;
                _lastScene = undefined;

                _viewportX = 0;
                _viewportY = 0;
                _viewportWidth = 1;
                _viewportHeight = 1;

                _lastBlending = -1;
                _lastClearColor.set(0, 0, 0);
                _lastCullFace = -1;
                _lastDepthTest = -1;
                _lastDepthWrite = -1;
                _lastLineWidth = -1;

                _lastUniforms = undefined;
                _lastBuffer = undefined;
                _lastProgram = undefined;
                _enabledAttributes = undefined;

                return this;
            };


            defineProperty(this, "gl", {
                get: function() {
                    return _gl;
                }
            });
            defineProperty(this, "canvas", {
                get: function() {
                    return _canvas;
                }
            });
            defineProperty(this, "element", {
                get: function() {
                    return _element;
                }
            });
            defineProperty(this, "precision", {
                get: function() {
                    return _precision;
                }
            });
            defineProperty(this, "maxAnisotropy", {
                get: function() {
                    return _maxAnisotropy;
                }
            });
            defineProperty(this, "maxTextures", {
                get: function() {
                    return _maxTextures;
                }
            });
            defineProperty(this, "maxVertexTextures", {
                get: function() {
                    return _maxVertexTextures;
                }
            });
            defineProperty(this, "maxTextureSize", {
                get: function() {
                    return _maxTextureSize;
                }
            });
            defineProperty(this, "maxCubeTextureSize", {
                get: function() {
                    return _maxCubeTextureSize;
                }
            });
            defineProperty(this, "maxRenderBufferSize", {
                get: function() {
                    return _maxRenderBufferSize;
                }
            });
            defineProperty(this, "maxUniforms", {
                get: function() {
                    return _maxUniforms;
                }
            });
            defineProperty(this, "maxVaryings", {
                get: function() {
                    return _maxVaryings;
                }
            });
            defineProperty(this, "maxAttributes", {
                get: function() {
                    return _maxAttributes;
                }
            });


            var _viewportX = 0,
                _viewportY = 0,
                _viewportWidth = 1,
                _viewportHeight = 1;

            function setViewport(x, y, width, height) {
                x || (x = 0);
                y || (y = 0);
                width || (width = _canvas.pixelWidth);
                height || (height = _canvas.pixelHeight);

                if (_viewportX !== x || _viewportY !== y || _viewportWidth !== width || _viewportHeight !== height) {
                    _viewportX = x;
                    _viewportY = y;
                    _viewportWidth = width;
                    _viewportHeight = height;

                    _gl.viewport(x, y, width, height);
                }
            }
            this.setViewport = setViewport;


            var _lastDepthTest = -1;

            function setDepthTest(depthTest) {

                if (_lastDepthTest !== depthTest) {

                    if (depthTest) {
                        _gl.enable(_gl.DEPTH_TEST);
                    } else {
                        _gl.disable(_gl.DEPTH_TEST);
                    }

                    _lastDepthTest = depthTest;
                }
            }
            this.setDepthTest = setDepthTest;


            var _lastDepthWrite = -1;

            function setDepthWrite(depthWrite) {

                if (_lastDepthWrite !== depthWrite) {

                    _gl.depthMask(depthWrite);
                    _lastDepthWrite = depthWrite;
                }
            }
            this.setDepthWrite = setDepthWrite;


            var _lastLineWidth = 1;

            function setLineWidth(width) {

                if (_lastLineWidth !== width) {

                    _gl.lineWidth(width);
                    _lastLineWidth = width;
                }
            }
            this.setLineWidth = setLineWidth;


            var _lastCullFace = -1,
                _cullFaceDisabled = true;

            function setCullFace(cullFace) {

                if (_lastCullFace !== cullFace) {
                    if (!_lastCullFace || _lastCullFace === CullFace.None) _cullFaceDisabled = true;

                    if (cullFace === CullFace.Front) {
                        if (_cullFaceDisabled) _gl.enable(_gl.CULL_FACE);
                        _gl.cullFace(_gl.FRONT);
                    } else if (cullFace === CullFace.Back) {
                        if (_cullFaceDisabled) _gl.enable(_gl.CULL_FACE);
                        _gl.cullFace(_gl.BACK);
                    } else if (cullFace === CullFace.FrontBack) {
                        if (_cullFaceDisabled) _gl.enable(_gl.CULL_FACE);
                        _gl.cullFace(_gl.FRONT_AND_BACK);
                    } else {
                        _gl.disable(_gl.CULL_FACE);
                        _lastCullFace = CullFace.None;
                        return;
                    }

                    _lastCullFace = cullFace;
                }
            }
            this.setCullFace = setCullFace;


            var _lastBlending = -1;

            function setBlending(blending) {

                if (blending !== _lastBlending) {

                    if (blending === Blending.None) {
                        _gl.disable(_gl.BLEND);
                    } else if (blending === Blending.Additive) {
                        _gl.enable(_gl.BLEND);
                        _gl.blendEquation(_gl.FUNC_ADD);
                        _gl.blendFunc(_gl.SRC_ALPHA, _gl.ONE);
                    } else if (blending === Blending.Subtractive) {
                        _gl.enable(_gl.BLEND);
                        _gl.blendEquation(_gl.FUNC_ADD);
                        _gl.blendFunc(_gl.ZERO, _gl.ONE_MINUS_SRC_COLOR);
                    } else if (blending === Blending.Muliply) {
                        _gl.enable(_gl.BLEND);
                        _gl.blendEquation(_gl.FUNC_ADD);
                        _gl.blendFunc(_gl.ZERO, _gl.SRC_COLOR);
                    } else if (blending === Blending.Default) {
                        _gl.enable(_gl.BLEND);
                        _gl.blendEquationSeparate(_gl.FUNC_ADD, _gl.FUNC_ADD);
                        _gl.blendFuncSeparate(_gl.SRC_ALPHA, _gl.ONE_MINUS_SRC_ALPHA, _gl.ONE, _gl.ONE_MINUS_SRC_ALPHA);
                        _lastBlending = Blending.Default;
                        return;
                    }

                    _lastBlending = blending;
                }
            }
            this.setBlending = setBlending;


            function setScissor(x, y, width, height) {

                _gl.scissor(x, y, width, height);
            };
            this.setScissor = setScissor;


            var _clearColor = new Color,
                _clearAlpha = 1;

            function setClearColor(color, alpha) {
                alpha || (alpha = 1);

                if (!_clearColor.equals(color) || alpha !== _clearAlpha) {

                    _clearColor.copy(color);
                    _clearAlpha = alpha;

                    this.context.clearColor(_clearColor.r, _clearColor.g, _clearColor.b, _clearAlpha);
                }
            }
            this.setClearColor = setClearColor;


            function clearCanvas(color, depth, stencil) {
                var bits = 0;

                if (color === undefined || color) bits |= _gl.COLOR_BUFFER_BIT;
                if (depth === undefined || depth) bits |= _gl.DEPTH_BUFFER_BIT;
                if (stencil === undefined || stencil) bits |= _gl.STENCIL_BUFFER_BIT;

                _gl.clear(bits);
            }
            this.clearCanvas = clearCanvas;


            function clearColor() {

                _gl.clear(_gl.COLOR_BUFFER_BIT);
            }
            this.clearColor = clearColor;


            function clearDepth() {

                _gl.clear(_gl.DEPTH_BUFFER_BIT);
            }
            this.clearDepth = clearDepth;


            function clearStencil() {

                _gl.clear(_gl.STENCIL_BUFFER_BIT);
            }
            this.clearStencil = clearStencil;


            var _lastUniforms;

            function setUniform(force, location, value, key, type, isArray, index) {
                var lastUniforms = _lastUniforms,
                    state;

                if (isArray) {
                    lastUniforms = lastUniforms[key] || (lastUniforms[key] = []);
                    key = index;
                }
                state = lastUniforms[key];

                if (type === "int") {
                    if (force || state !== value) {
                        _gl.uniform1i(location, value);
                        lastUniforms[key] = value;
                    }
                } else if (type === "float") {
                    if (force || state !== value) {
                        _gl.uniform1f(location, value);
                        lastUniforms[key] = value;
                    }
                } else if (type === "vec2") {
                    if (force || !state || (state.x !== value.x || state.y !== value.y)) {
                        _gl.uniform2f(location, value.x, value.y);
                        state || (lastUniforms[key] = state = value.clone());
                        state.x = value.x;
                        state.y = value.y;
                    }
                } else if (type === "vec3") {
                    if (force || !state || (state.x !== value.x || state.y !== value.y || state.z !== value.z)) {
                        _gl.uniform3f(location, value.x, value.y, value.z);
                        state || (lastUniforms[key] = state = value.clone());
                        state.x = value.x;
                        state.y = value.y;
                        state.z = value.z;
                    }
                } else if (type === "vec4") {
                    if (force || !state || (state.x !== value.x || state.y !== value.y || state.z !== value.z || state.w !== value.w)) {
                        _gl.uniform3f(location, value.x, value.y, value.z, value.w);
                        state || (lastUniforms[key] = state = value.clone());
                        state.x = value.x;
                        state.y = value.y;
                        state.z = value.z;
                        state.w = value.w;
                    }
                } else if (type === "mat2") {
                    if (force || !state || !state.equals(value)) {
                        _gl.uniformMatrix2fv(force, location, false, value.elements);
                        (state || (lastUniforms[key] = state = value.clone())).copy(value);
                    }
                } else if (type === "mat3") {
                    if (force || !state || !state.equals(value)) {
                        _gl.uniformMatrix3fv(force, location, false, value.elements);
                        (state || (lastUniforms[key] = state = value.clone())).copy(value);
                    }
                } else if (type === "mat4") {
                    if (force || !state || !state.equals(value)) {
                        _gl.uniformMatrix4fv(force, location, false, value.elements);
                        (state || (lastUniforms[key] = state = value.clone())).copy(value);
                    }
                } else if (type === "sampler2D") {
                    setTexture(force, location, value, key);
                } else if (type === "samplerCube") {
                    setTextureCube(force, location, value, key);
                }
            }


            function setUniform1i(force, location, value, key, isArray, index) {
                var lastUniforms = _lastUniforms,
                    state;

                if (isArray) {
                    lastUniforms = lastUniforms[key] || (lastUniforms[key] = []);
                    key = index;
                }
                state = lastUniforms[key];

                if (force || state !== value) {
                    _gl.uniform1i(location, value);
                    lastUniforms[key] = value;
                }
            }


            function setUniform1f(force, location, value, key, isArray, index) {
                var lastUniforms = _lastUniforms,
                    state;

                if (isArray) {
                    lastUniforms = lastUniforms[key] || (lastUniforms[key] = []);
                    key = index;
                }
                state = lastUniforms[key];

                if (force || state !== value) {
                    _gl.uniform1f(location, value);
                    lastUniforms[key] = value;
                }
            }


            function setUniform2f(force, location, value, key, isArray, index) {
                var lastUniforms = _lastUniforms,
                    state;

                if (isArray) {
                    lastUniforms = lastUniforms[key] || (lastUniforms[key] = []);
                    key = index;
                }
                state = lastUniforms[key];

                if (force || !state || (state.x !== value.x || state.y !== value.y)) {
                    _gl.uniform2f(location, value.x, value.y);
                    state || (lastUniforms[key] = state = value.clone());
                    state.x = value.x;
                    state.y = value.y;
                }
            }


            function setUniform3f(force, location, value, key, isArray, index) {
                var lastUniforms = _lastUniforms,
                    state;

                if (isArray) {
                    lastUniforms = lastUniforms[key] || (lastUniforms[key] = []);
                    key = index;
                }
                state = lastUniforms[key];

                if (force || !state || (state.x !== value.x || state.y !== value.y || state.z !== value.z)) {
                    _gl.uniform3f(location, value.x, value.y, value.z);
                    state || (lastUniforms[key] = state = value.clone());
                    state.x = value.x;
                    state.y = value.y;
                    state.z = value.z;
                }
            }


            function setUniform4f(force, location, value, key, isArray, index) {
                var lastUniforms = _lastUniforms,
                    state;

                if (isArray) {
                    lastUniforms = lastUniforms[key] || (lastUniforms[key] = []);
                    key = index;
                }
                state = lastUniforms[key];

                if (force || !state || (state.x !== value.x || state.y !== value.y || state.z !== value.z || state.w !== value.w)) {
                    _gl.uniform4f(location, value.x, value.y, value.z, value.w);
                    state || (lastUniforms[key] = state = value.clone());
                    state.x = value.x;
                    state.y = value.y;
                    state.z = value.z;
                    state.w = value.w;
                }
            }


            function setUniformMatrix2fv(force, location, value, key, isArray, index) {
                var lastUniforms = _lastUniforms,
                    state;

                if (isArray) {
                    lastUniforms = lastUniforms[key] || (lastUniforms[key] = []);
                    key = index;
                }
                state = lastUniforms[key];

                if (force || !state || !state.equals(value)) {
                    _gl.uniformMatrix2fv(location, false, value.elements);
                    (state || (lastUniforms[key] = state = value.clone())).copy(value);
                }
            }


            function setUniformMatrix3fv(force, location, value, key, isArray, index) {
                var lastUniforms = _lastUniforms,
                    state;

                if (isArray) {
                    lastUniforms = lastUniforms[key] || (lastUniforms[key] = []);
                    key = index;
                }
                state = lastUniforms[key];

                if (force || !state || !state.equals(value)) {
                    _gl.uniformMatrix3fv(location, false, value.elements);
                    (state || (lastUniforms[key] = state = value.clone())).copy(value);
                }
            }


            function setUniformMatrix4fv(force, location, value, key, isArray, index) {
                var lastUniforms = _lastUniforms,
                    state;

                if (isArray) {
                    lastUniforms = lastUniforms[key] || (lastUniforms[key] = []);
                    key = index;
                }
                state = lastUniforms[key];

                if (force || !state || !state.equals(value)) {
                    _gl.uniformMatrix4fv(location, false, value.elements);
                    (state || (lastUniforms[key] = state = value.clone())).copy(value);
                }
            }


            var _textureIndex = 0;

            function setTexture(force, location, texture, key) {
                if (!texture || !texture.raw) return;
                var index, glTexture;

                if (_textureIndex >= _maxTextures) Log.warn("Renderer setTexure: using " + _textureIndex + " texture units, GPU only supports " + _maxTextures);

                if (!texture.needsUpdate && (glTexture = texture._webgl)) {
                    index = _textureIndex++;

                    _gl.activeTexture(_gl.TEXTURE0 + index);
                    _gl.bindTexture(_gl.TEXTURE_2D, glTexture);
                    setUniform1i(force, location, index, key);

                    return;
                }

                glTexture = texture._webgl || (texture._webgl = _gl.createTexture());
                index = _textureIndex++;

                var raw = texture.raw,
                    TFA = _extensions.EXT_texture_filter_anisotropic,

                    isPOT = isPowerOfTwo(raw.width) && isPowerOfTwo(raw.height),
                    anisotropy = clamp(texture.anisotropy || 1, 1, _maxAnisotropy),

                    TEXTURE_2D = _gl.TEXTURE_2D,
                    mipmap = texture.mipmap,
                    filter = texture.filter,
                    format = texture.format,
                    wrap = texture.wrap,
                    WRAP, MAG_FILTER, MIN_FILTER, FORMAT;

                if (filter === FilterMode.None) {
                    MAG_FILTER = _gl.NEAREST;
                    if (mipmap && isPOT) {
                        MIN_FILTER = _gl.LINEAR_MIPMAP_NEAREST;
                    } else {
                        MIN_FILTER = _gl.NEAREST;
                    }
                } else { //FilterMode.Linear
                    MAG_FILTER = _gl.LINEAR;
                    if (mipmap && isPOT) {
                        MIN_FILTER = _gl.LINEAR_MIPMAP_LINEAR;
                    } else {
                        MIN_FILTER = _gl.LINEAR;
                    }
                }

                if (format === TextureFormat.RGB) {
                    FORMAT = _gl.RGB;
                } else if (format === TextureFormat.RGBA) {
                    FORMAT = _gl.RGBA;
                } else if (format === TextureFormat.LuminanceAlpha) {
                    FORMAT = _gl.LUMINANCE_ALPHA;
                } else if (format === TextureFormat.Luminance) {
                    FORMAT = _gl.LUMINANCE;
                } else if (format === TextureFormat.Alpha) {
                    FORMAT = _gl.ALPHA;
                }

                if (wrap === TextureWrap.Clamp) {
                    WRAP = _gl.CLAMP_TO_EDGE;
                } else if (wrap === TextureWrap.MirrorRepeat) {
                    WRAP = isPOT ? _gl.MIRRORED_REPEAT : _gl.CLAMP_TO_EDGE;
                } else { //TextureWrap.Repeat
                    WRAP = isPOT ? _gl.REPEAT : _gl.CLAMP_TO_EDGE;
                }

                _gl.activeTexture(_gl.TEXTURE0 + index);
                _gl.bindTexture(TEXTURE_2D, glTexture);
                _gl.uniform1i(location, index);

                _gl.pixelStorei(_gl.UNPACK_FLIP_Y_WEBGL, texture.flipY ? 1 : 0);
                _gl.pixelStorei(_gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, texture.premultiplyAlpha ? 1 : 0);

                _gl.texImage2D(TEXTURE_2D, 0, FORMAT, FORMAT, _gl.UNSIGNED_BYTE, clampToMaxSize(raw, _maxTextureSize));

                _gl.texParameteri(TEXTURE_2D, _gl.TEXTURE_MAG_FILTER, MAG_FILTER);
                _gl.texParameteri(TEXTURE_2D, _gl.TEXTURE_MIN_FILTER, MIN_FILTER);

                _gl.texParameteri(TEXTURE_2D, _gl.TEXTURE_WRAP_S, WRAP);
                _gl.texParameteri(TEXTURE_2D, _gl.TEXTURE_WRAP_T, WRAP);

                if (TFA) _gl.texParameterf(TEXTURE_2D, TFA.TEXTURE_MAX_ANISOTROPY_EXT, anisotropy);
                if (mipmap && isPOT) _gl.generateMipmap(TEXTURE_2D);

                texture.needsUpdate = false;
            }


            function setTextureCube(force, location, cubeTexture, key) {
                if (!cubeTexture || !cubeTexture.raw) return;
                var glTexture = cubeTexture._webgl,
                    index;

                if (_textureIndex >= _maxTextures) Log.warn("Renderer setTexure: using " + _textureIndex + " texture units, GPU only supports " + _maxTextures);

                if (!cubeTexture.needsUpdate && glTexture) {
                    index = _textureIndex++;

                    _gl.activeTexture(_gl.TEXTURE0 + index);
                    _gl.bindTexture(_gl.TEXTURE_CUBE_MAP, glTexture);
                    setUniform1i(force, location, index, key);

                    return;
                }

                glTexture = cubeTexture._webgl || (cubeTexture._webgl = _gl.createTexture());
                index = _textureIndex++;

                var raw = cubeTexture.raw,
                    TFA = _extensions.EXT_texture_filter_anisotropic,

                    first = raw[0],
                    isPOT = isPowerOfTwo(first.width) && isPowerOfTwo(first.height),
                    anisotropy = clamp(cubeTexture.anisotropy || 1, 1, _maxAnisotropy),

                    TEXTURE_CUBE_MAP = _gl.TEXTURE_CUBE_MAP,
                    TEXTURE_CUBE_MAP_POSITIVE_X = _gl.TEXTURE_CUBE_MAP_POSITIVE_X,
                    UNSIGNED_BYTE = _gl.UNSIGNED_BYTE,

                    mipmap = cubeTexture.mipmap,
                    filter = cubeTexture.filter,
                    format = cubeTexture.format,
                    wrap = cubeTexture.wrap,
                    WRAP, MAG_FILTER, MIN_FILTER, FORMAT,
                    current, i;

                if (filter === FilterMode.None) {
                    MAG_FILTER = _gl.NEAREST;
                    if (mipmap && isPOT) {
                        MIN_FILTER = _gl.LINEAR_MIPMAP_NEAREST;
                    } else {
                        MIN_FILTER = _gl.NEAREST;
                    }
                } else { //FilterMode.Linear
                    MAG_FILTER = _gl.LINEAR;
                    if (mipmap && isPOT) {
                        MIN_FILTER = _gl.LINEAR_MIPMAP_LINEAR;
                    } else {
                        MIN_FILTER = _gl.LINEAR;
                    }
                }

                if (format === TextureFormat.RGB) {
                    FORMAT = _gl.RGB;
                } else if (format === TextureFormat.RGBA) {
                    FORMAT = _gl.RGBA;
                } else if (format === TextureFormat.LuminanceAlpha) {
                    FORMAT = _gl.LUMINANCE_ALPHA;
                } else if (format === TextureFormat.Luminance) {
                    FORMAT = _gl.LUMINANCE;
                } else if (format === TextureFormat.Alpha) {
                    FORMAT = _gl.ALPHA;
                }

                if (wrap === TextureWrap.Clamp) {
                    WRAP = _gl.CLAMP_TO_EDGE;
                } else { //TextureWrap.Repeat
                    WRAP = isPOT ? _gl.REPEAT : _gl.CLAMP_TO_EDGE;
                }

                _gl.activeTexture(_gl.TEXTURE0 + index);
                _gl.bindTexture(TEXTURE_CUBE_MAP, glTexture);
                _gl.uniform1i(location, index);

                _gl.pixelStorei(_gl.UNPACK_FLIP_Y_WEBGL, cubeTexture.flipY ? 1 : 0);
                _gl.pixelStorei(_gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, cubeTexture.premultiplyAlpha ? 1 : 0);

                _gl.texImage2D(_gl.TEXTURE_CUBE_MAP_POSITIVE_X, 0, FORMAT, FORMAT, UNSIGNED_BYTE, clampToMaxSize(raw[0], _maxCubeTextureSize));
                _gl.texImage2D(_gl.TEXTURE_CUBE_MAP_NEGATIVE_X, 0, FORMAT, FORMAT, UNSIGNED_BYTE, clampToMaxSize(raw[1], _maxCubeTextureSize));
                _gl.texImage2D(_gl.TEXTURE_CUBE_MAP_POSITIVE_Y, 0, FORMAT, FORMAT, UNSIGNED_BYTE, clampToMaxSize(raw[2], _maxCubeTextureSize));
                _gl.texImage2D(_gl.TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, FORMAT, FORMAT, UNSIGNED_BYTE, clampToMaxSize(raw[3], _maxCubeTextureSize));
                _gl.texImage2D(_gl.TEXTURE_CUBE_MAP_POSITIVE_Z, 0, FORMAT, FORMAT, UNSIGNED_BYTE, clampToMaxSize(raw[4], _maxCubeTextureSize));
                _gl.texImage2D(_gl.TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, FORMAT, FORMAT, UNSIGNED_BYTE, clampToMaxSize(raw[5], _maxCubeTextureSize));

                _gl.texParameteri(TEXTURE_CUBE_MAP, _gl.TEXTURE_MAG_FILTER, MAG_FILTER);
                _gl.texParameteri(TEXTURE_CUBE_MAP, _gl.TEXTURE_MIN_FILTER, MIN_FILTER);

                _gl.texParameteri(TEXTURE_CUBE_MAP, _gl.TEXTURE_WRAP_S, WRAP);
                _gl.texParameteri(TEXTURE_CUBE_MAP, _gl.TEXTURE_WRAP_T, WRAP);

                if (TFA) _gl.texParameterf(TEXTURE_CUBE_MAP, TFA.TEXTURE_MAX_ANISOTROPY_EXT, anisotropy);
                if (mipmap && isPOT) _gl.generateMipmap(TEXTURE_CUBE_MAP);

                cubeTexture.needsUpdate = false;
            }


            function clampToMaxSize(image, maxSize) {
                if (image.height <= maxSize && image.width <= maxSize) return image;
                var maxDim = 1 / max(image.width, image.height),
                    newWidth = floor(image.width * maxSize * maxDim),
                    newHeight = floor(image.height * maxSize * maxDim),
                    canvas = document.createElement("canvas"),
                    ctx = canvas.getContext("2d");

                canvas.width = newWidth;
                canvas.height = newHeight;
                ctx.drawImage(image, 0, 0, image.width, image.height, 0, 0, newWidth, newHeight);

                Log.once("Renderer clampToMaxSize: image height larger than machines max cube texture size (max = " + maxSize + ")");

                return canvas;
            }


            function initMeshBuffers(mesh) {
                if (!mesh.dynamic && mesh._webgl.inittedBuffers) return mesh._webgl;
                var webgl = mesh._webgl,
                    DRAW = mesh.dynamic ? _gl.DYNAMIC_DRAW : _gl.STATIC_DRAW,
                    ARRAY_BUFFER = _gl.ARRAY_BUFFER,
                    ELEMENT_ARRAY_BUFFER = _gl.ELEMENT_ARRAY_BUFFER,
                    bufferArray, items, item, i, len, offset, vertexIndex;

                items = mesh.vertices || EMPTY_ARRAY;
                len = items.length;
                if (len && mesh.verticesNeedUpdate) {
                    bufferArray = webgl.vertexArray;
                    if (!bufferArray || bufferArray.length !== len * 3) {
                        bufferArray = webgl.vertexArray = new Float32Array(len * 3);
                        webgl.vertexCount = len;
                    }

                    for (i = 0; i < len; i++) {
                        item = items[i];
                        offset = i * 3;

                        bufferArray[offset] = item.x;
                        bufferArray[offset + 1] = item.y;
                        bufferArray[offset + 2] = item.z;
                    }

                    webgl.vertexBuffer = webgl.vertexBuffer || _gl.createBuffer();
                    _gl.bindBuffer(ARRAY_BUFFER, webgl.vertexBuffer);
                    _gl.bufferData(ARRAY_BUFFER, bufferArray, DRAW);

                    mesh.verticesNeedUpdate = false;
                }

                items = mesh.normals || EMPTY_ARRAY;
                len = items.length;
                if (len && mesh.normalsNeedUpdate) {
                    bufferArray = webgl.normalArray;
                    if (!bufferArray || bufferArray.length !== len * 3) bufferArray = webgl.normalArray = new Float32Array(len * 3);

                    for (i = 0; i < len; i++) {
                        item = items[i];
                        offset = i * 3;

                        bufferArray[offset] = item.x;
                        bufferArray[offset + 1] = item.y;
                        bufferArray[offset + 2] = item.z;
                    }

                    webgl.normalBuffer = webgl.normalBuffer || _gl.createBuffer();
                    _gl.bindBuffer(ARRAY_BUFFER, webgl.normalBuffer);
                    _gl.bufferData(ARRAY_BUFFER, bufferArray, DRAW);

                    mesh.normalsNeedUpdate = false;
                }

                items = mesh.tangents || EMPTY_ARRAY;
                len = items.length;
                if (len && mesh.tangentsNeedUpdate) {
                    bufferArray = webgl.tangentArray;
                    if (!bufferArray || bufferArray.length !== len * 4) bufferArray = webgl.tangentArray = new Float32Array(len * 4);

                    for (i = 0; i < len; i++) {
                        item = items[i];
                        offset = i * 4;

                        bufferArray[offset] = item.x;
                        bufferArray[offset + 1] = item.y;
                        bufferArray[offset + 2] = item.z;
                        bufferArray[offset + 3] = item.w;
                    }

                    webgl.tangentBuffer = webgl.tangentBuffer || _gl.createBuffer();
                    _gl.bindBuffer(ARRAY_BUFFER, webgl.tangentBuffer);
                    _gl.bufferData(ARRAY_BUFFER, bufferArray, DRAW);

                    mesh.tangentsNeedUpdate = false;
                }

                items = mesh.indices || EMPTY_ARRAY;
                len = items.length;
                if (len && mesh.indicesNeedUpdate) {
                    bufferArray = webgl.indexArray;
                    if (!bufferArray || bufferArray.length !== len) {
                        bufferArray = webgl.indexArray = new Uint16Array(len);
                        webgl.indexCount = len;
                    }

                    for (i = 0; i < len; i++) bufferArray[i] = items[i];

                    webgl.indexBuffer = webgl.indexBuffer || _gl.createBuffer();
                    _gl.bindBuffer(ELEMENT_ARRAY_BUFFER, webgl.indexBuffer);
                    _gl.bufferData(ELEMENT_ARRAY_BUFFER, bufferArray, DRAW);

                    bufferArray = webgl.lineArray;
                    if (!bufferArray || bufferArray.length !== len * 3) {
                        bufferArray = webgl.lineArray = new Uint16Array(len * 3);
                        webgl.lineCount = len * 3;
                    }

                    vertexIndex = offset = 0;
                    for (i = 0; i < len; i++) {

                        bufferArray[offset] = items[vertexIndex];
                        bufferArray[offset + 1] = items[vertexIndex + 1];

                        bufferArray[offset + 2] = items[vertexIndex];
                        bufferArray[offset + 3] = items[vertexIndex + 2];

                        bufferArray[offset + 4] = items[vertexIndex + 1];
                        bufferArray[offset + 5] = items[vertexIndex + 2];

                        offset += 6;
                        vertexIndex += 3;
                    }

                    webgl.lineBuffer = webgl.lineBuffer || _gl.createBuffer();
                    _gl.bindBuffer(ELEMENT_ARRAY_BUFFER, webgl.lineBuffer);
                    _gl.bufferData(ELEMENT_ARRAY_BUFFER, bufferArray, DRAW);

                    mesh.indicesNeedUpdate = false;
                }

                items = mesh.colors || EMPTY_ARRAY;
                len = items.length;
                if (len && mesh.colorsNeedUpdate) {
                    bufferArray = webgl.colorArray;
                    if (!bufferArray || bufferArray.length !== len * 3) bufferArray = webgl.colorArray = new Float32Array(len * 3);

                    for (i = 0; i < len; i++) {
                        item = items[i];
                        offset = i * 3;

                        bufferArray[offset] = item.x;
                        bufferArray[offset + 1] = item.y;
                        bufferArray[offset + 2] = item.z;
                    }

                    webgl.colorBuffer = webgl.colorBuffer || _gl.createBuffer();
                    _gl.bindBuffer(ARRAY_BUFFER, webgl.colorBuffer);
                    _gl.bufferData(ARRAY_BUFFER, bufferArray, DRAW);

                    mesh.colorsNeedUpdate = false;
                }

                items = mesh.uvs || EMPTY_ARRAY;
                len = items.length;
                if (len && mesh.uvsNeedUpdate) {
                    bufferArray = webgl.uvArray;
                    if (!bufferArray || bufferArray.length !== len * 2) bufferArray = webgl.uvArray = new Float32Array(len * 2);

                    for (i = 0; i < len; i++) {
                        item = items[i];
                        offset = i * 2;

                        bufferArray[offset] = item.x;
                        bufferArray[offset + 1] = item.y;
                    }

                    webgl.uvBuffer = webgl.uvBuffer || _gl.createBuffer();
                    _gl.bindBuffer(ARRAY_BUFFER, webgl.uvBuffer);
                    _gl.bufferData(ARRAY_BUFFER, bufferArray, DRAW);

                    mesh.uvsNeedUpdate = false;
                }

                items = mesh.boneIndices || EMPTY_ARRAY;
                len = items.length;
                if (len && mesh.boneIndicesNeedUpdate) {
                    bufferArray = webgl.boneIndexArray;
                    if (!bufferArray || bufferArray.length !== len) bufferArray = webgl.boneIndexArray = new Uint16Array(len);

                    for (i = 0; i < len; i++) bufferArray[i] = items[i];

                    webgl.boneIndexBuffer = webgl.boneIndexBuffer || _gl.createBuffer();
                    _gl.bindBuffer(ARRAY_BUFFER, webgl.boneIndexBuffer);
                    _gl.bufferData(ARRAY_BUFFER, bufferArray, DRAW);

                    mesh.boneIndicesNeedUpdate = false;
                }

                items = mesh.boneWeights || EMPTY_ARRAY;
                len = items.length;
                if (len && mesh.boneWeightsNeedUpdate) {
                    bufferArray = webgl.boneWeightArray;
                    if (!bufferArray || bufferArray.length !== len) bufferArray = webgl.boneWeightArray = new Float32Array(len);

                    for (i = 0; i < len; i++) bufferArray[i] = items[i];

                    webgl.boneWeightBuffer = webgl.boneWeightBuffer || _gl.createBuffer();
                    _gl.bindBuffer(ARRAY_BUFFER, webgl.boneWeightBuffer);
                    _gl.bufferData(ARRAY_BUFFER, bufferArray, DRAW);

                    mesh.boneWeightsNeedUpdate = false;
                }

                webgl.inittedBuffers = true;

                return webgl;
            }


            function initEmitterBuffers(emitter, transform, attributes) {
                var MAX = Emitter.MAX_PARTICLES,

                    webgl = emitter._webgl,
                    DRAW = _gl.DYNAMIC_DRAW,
                    FLOAT = _gl.FLOAT,
                    ARRAY_BUFFER = _gl.ARRAY_BUFFER,

                    positionArray, dataArray,
                    positionBuffer, dataBuffer,

                    particles = emitter.particles,
                    particle,
                    i = 0,
                    len = particles.length,
                    offset, position,
                    me, x, y, z,
                    m13, m23, m33, m43,
                    m14, m24, m34, m44

                if (len) {
                    if (emitter.sort) {
                        _mat4.mmul(_projScreenMatrix, transform.matrixWorld);
                        me = _mat4.elements;
                        m13 = me[2];
                        m23 = me[6];
                        m33 = me[10];
                        m43 = me[14];
                        m14 = me[3];
                        m24 = me[7];
                        m34 = me[11];
                        m44 = me[15];

                        i = len;
                        while (i--) {
                            particle = particles[i];
                            position = particle.position;
                            x = position.x;
                            y = position.y;
                            z = position.z;

                            particle.z = (m13 * x + m23 * y + m33 * z + m43) / (m14 * x + m24 * y + m34 * z + m44);
                        }

                        particles.sort(numericalSort);
                    }

                    positionArray = webgl.positionArray || (webgl.positionArray = new Float32Array(MAX * 3));
                    dataArray = webgl.dataArray || (webgl.dataArray = new Float32Array(MAX * 3));

                    i = len;
                    while (i--) {
                        particle = particles[i];
                        position = particle.position;
                        offset = i * 3;

                        positionArray[offset] = position.x;
                        positionArray[offset + 1] = position.y;
                        positionArray[offset + 2] = position.z;

                        dataArray[offset] = particle.angle;
                        dataArray[offset + 1] = particle.size;
                        dataArray[offset + 2] = particle.alpha;
                    }

                    disableAttributes();

                    positionBuffer = webgl.positionBuffer || (webgl.positionBuffer = _gl.createBuffer());
                    _gl.bindBuffer(ARRAY_BUFFER, webgl.positionBuffer);
                    _gl.bufferData(ARRAY_BUFFER, positionArray, DRAW);
                    enableAttribute(attributes.position);
                    _gl.vertexAttribPointer(attributes.position, 3, FLOAT, false, 0, 0);

                    dataBuffer = webgl.dataBuffer || (webgl.dataBuffer = _gl.createBuffer());
                    _gl.bindBuffer(ARRAY_BUFFER, webgl.dataBuffer);
                    _gl.bufferData(ARRAY_BUFFER, dataArray, DRAW);
                    enableAttribute(attributes.data);
                    _gl.vertexAttribPointer(attributes.data, 3, FLOAT, false, 0, 0);
                }

                webgl.particleCount = len;
                _lastBuffer = webgl;

                return webgl;
            }


            function numericalSort(a, b) {

                return b.z - a.z;
            }


            function initMaterial(material, mesh, lights) {
                if (!material.needsUpdate && material._webgl) return material._webgl;

                var shader = material.shader,
                    uniforms = material.uniforms,
                    OES_standard_derivatives = !! _extensions.OES_standard_derivatives,
                    parameters = {};

                parameters.mobile = Device.mobile;
                parameters.useLights = shader.lights;
                parameters.useShadows = shader.shadows;
                parameters.useFog = shader.fog;
                parameters.useBones = mesh.useBones && mesh.bones.length > 0;
                parameters.useVertexLit = shader.vertexLit;
                parameters.useSpecular = shader.specular;

                parameters.useNormal = uniforms.normalMap;
                parameters.useBump = uniforms.bumpMap;

                parameters.positions = mesh.vertices.length > 0;
                parameters.normals = mesh.normals.length > 0;
                parameters.tangents = mesh.tangents.length > 0;
                parameters.uvs = mesh.uvs.length > 0;
                parameters.colors = mesh.colors.length > 0;

                parameters.OES_standard_derivatives = OES_standard_derivatives && shader.OES_standard_derivatives;

                allocateLights(lights, parameters);
                allocateShadows(lights, parameters);

                material._webgl = initProgram(shader.vertex, shader.fragment, parameters);
                material.needsUpdate = false;

                return material._webgl;
            }


            function initEmitterMaterial(material, emitter, lights) {
                if (!material.needsUpdate && material._webgl) return material._webgl;

                var shader = material.shader,
                    webgl = emitter._webgl,
                    uniforms = material.uniforms,
                    OES_standard_derivatives = !! _extensions.OES_standard_derivatives,
                    parameters = {};

                parameters.emitter = true;
                parameters.mobile = Device.mobile;
                parameters.useLights = shader.lights;
                parameters.useShadows = shader.shadows;
                parameters.useFog = shader.fog;
                parameters.useBones = false;
                parameters.useVertexLit = shader.vertexLit;
                parameters.useSpecular = shader.specular;

                parameters.useNormal = uniforms.normalMap;
                parameters.useBump = uniforms.bumpMap;

                parameters.positions = true;
                parameters.normals = false;
                parameters.tangents = false;
                parameters.uvs = false;
                parameters.colors = false;

                parameters.OES_standard_derivatives = OES_standard_derivatives && shader.OES_standard_derivatives;

                allocateLights(lights, parameters);
                allocateShadows(lights, parameters);

                material._webgl = initProgram(shader.vertex, shader.fragment, parameters);
                material.needsUpdate = false;

                return material._webgl;
            }


            function allocateLights(lights, parameters) {
                var maxPointLights = 0,
                    maxDirectionalLights = 0,
                    maxSpotLights = 0,
                    maxHemiLights = 0,
                    light, type,
                    i = 0,
                    il = lights.length;

                for (; i < il; i++) {
                    light = lights[i];
                    if (!light.visible || light.onlyShadow) continue;
                    type = light.type;

                    if (type === LightType.Point) {
                        maxPointLights++;
                    } else if (type === LightType.Directional) {
                        maxDirectionalLights++;
                    } else if (type === LightType.Spot) {
                        maxSpotLights++;
                    } else if (type === LightType.Hemi) {
                        maxHemiLights++;
                    }
                }

                parameters.maxPointLights = maxPointLights;
                parameters.maxDirectionalLights = maxDirectionalLights;
                parameters.maxSpotLights = maxSpotLights;
                parameters.maxHemiLights = maxHemiLights;
            }


            function allocateShadows(lights, parameters) {
                var maxShadows = 0,
                    light, type,
                    i = 0,
                    il = lights.length;

                for (; i < il; i++) {
                    light = lights[i];
                    if (!light.visible || !light.castShadow) continue;
                    type = light.type;

                    if (type === LightType.Directional) {
                        maxShadows++;
                    } else if (type === LightType.Spot) {
                        maxShadows++;
                    }
                }

                parameters.maxShadows = maxShadows;
            }


            var HEADER = /([\s\S]*)?(void[\s]+main)/,
                MAIN_FUNCTION = /void[\s]+main([\s]+)?(\((void)?\))([\s]+)?{([^}]*)}/,
                MAIN_SPLITER = /void[\s]+main([\s]+)?(\((void)?\))([\s]+)?{/;

            function initProgram(vertexShader, fragmentShader, parameters) {
                var chunks = [],
                    key, program, webglProgram, programInfo, code, i;

                chunks.push(fragmentShader, vertexShader);
                for (key in parameters) chunks.push(key, parameters[key]);

                code = chunks.join();

                for (i = _programs.length; i--;) {
                    programInfo = _programs[i];

                    if (programInfo.code === code) {
                        programInfo.used++;
                        return programInfo.program;
                    }
                }

                var emitter = parameters.emitter,
                    mobile = parameters.mobile,
                    useLights = parameters.useLights,
                    useShadows = parameters.useShadows,
                    useFog = parameters.useFog,
                    useBones = parameters.useBones,
                    useVertexLit = parameters.useVertexLit,
                    useSpecular = parameters.useSpecular,
                    OES_standard_derivatives = parameters.OES_standard_derivatives,

                    definesPrefix = [
                        "precision " + _precision + " float;",
                        "precision " + _precision + " int;",

                        useLights ? "#define USE_LIGHTS" : "",
                        useShadows ? "#define USE_SHADOWS" : "",
                        useBones ? "#define USE_SKINNING" : "",

                        useLights ? "#define MAX_DIR_LIGHTS " + parameters.maxDirectionalLights : "",
                        useLights ? "#define MAX_POINT_LIGHTS " + parameters.maxPointLights : "",
                        useLights ? "#define MAX_SPOT_LIGHTS " + parameters.maxSpotLights : "",
                        useLights ? "#define MAX_HEMI_LIGHTS " + parameters.maxHemiLights : "",

                        useShadows ? "#define MAX_SHADOWS " + parameters.maxShadows : "",
                        ""
                    ].join("\n"),

                    vertexPrefix = [
                        definesPrefix,

                        "uniform mat4 modelMatrix;",
                        "uniform mat4 modelViewMatrix;",
                        "uniform mat4 projectionMatrix;",
                        "uniform mat4 viewMatrix;",
                        "uniform mat3 normalMatrix;",
                        "uniform vec3 cameraPosition;",

                        parameters.positions ? "attribute vec3 position;" : "",
                        parameters.normals ? "attribute vec3 normal;" : "",
                        parameters.tangents ? "attribute vec4 tangent;" : "",
                        parameters.uvs ? "attribute vec2 uv;" : "",
                        parameters.colors ? "attribute vec3 color;" : "",
                        emitter ? "attribute vec3 data;" : "",

                        useBones ? "attribute int boneIndex;" : "",
                        useBones ? "attribute vec3 boneWeight;" : "",
                        useBones ? "uniform mat4 bone[" + parameters.bones + "];" : ""
                    ].join("\n"),

                    fragmentPrefix = [
                        OES_standard_derivatives ? "#extension GL_OES_standard_derivatives : enable" : "",
                        definesPrefix,

                        "uniform mat4 viewMatrix;",
                        "uniform vec3 cameraPosition;"
                    ].join("\n"),

                    glVertexShader = vertexPrefix + "\n" + vertexShader,
                    glFragmentShader = fragmentPrefix + "\n" + fragmentShader,

                    main = "void main(void) {\n",
                    footer = "\n}",

                    vertexHeader = glVertexShader.match(HEADER)[1],
                    vertexMain = glVertexShader.match(MAIN_FUNCTION)[5],
                    fragmentHeader = glFragmentShader.match(HEADER)[1],
                    fragmentMain = glFragmentShader.match(MAIN_FUNCTION)[5];

                if (emitter) {
                    vertexHeader += ShaderChunks.particle_header;
                    fragmentHeader += ShaderChunks.particle_header;
                    vertexMain = ShaderChunks.particle_vertex + vertexMain;
                }

                if (OES_standard_derivatives) {
                    if (parameters.useNormal) fragmentHeader += ShaderChunks.perturbNormal2Arb;
                    if (parameters.useBump) fragmentHeader += ShaderChunks.dHdxy_fwd + ShaderChunks.perturbNormalArb;
                }

                if (useLights) {
                    if (useVertexLit) {
                        vertexHeader += ShaderChunks.lights + ShaderChunks.VertexLight;
                    } else {
                        vertexHeader += ShaderChunks.perPixelVaryingHeader;
                        vertexMain = ShaderChunks.perPixelVaryingMain + vertexMain;

                        fragmentHeader += ShaderChunks.lights + ShaderChunks.perPixelVaryingHeader;
                        if (useSpecular) {
                            fragmentHeader += ShaderChunks.PixelLight;
                        } else {
                            fragmentHeader += ShaderChunks.PixelLightNoSpec;
                        }
                    }

                    vertexMain = ShaderChunks.mvPosition + vertexMain;
                    if (parameters.normals) vertexMain = ShaderChunks.transformedNormal + vertexMain;
                    vertexMain = ShaderChunks.worldPosition + vertexMain;
                } else {
                    vertexMain = ShaderChunks.mvPosition + vertexMain;
                }

                if (useBones) {
                    vertexHeader += ShaderChunks.getBoneMatrix;
                    if (parameters.normals) vertexMain = ShaderChunks.boneNormal + vertexMain;
                    vertexMain = ShaderChunks.bone + vertexMain;
                }

                glVertexShader = vertexHeader + main + vertexMain + footer;
                glFragmentShader = fragmentHeader + main + fragmentMain + footer;

                webglProgram = createProgram(_gl, glVertexShader, glFragmentShader);
                program = {
                    program: webglProgram,
                    uniforms: {},
                    attributes: {},
                    customUniforms: [],
                    customAttributes: [],
                    parameters: parameters
                }

                getUniformsAttributes(vertexShader, fragmentShader, program.customAttributes, program.customUniforms);
                parseUniformsAttributes(_gl, webglProgram, glVertexShader, glFragmentShader, program.attributes, program.uniforms);

                _programs.push({
                    used: 1,
                    code: code,
                    program: program
                });

                return program;
            }


            var _lastBuffer = undefined;


            function bindMesh(mesh, material) {
                if (_lastBuffer === mesh._webgl) return;
                disableAttributes();

                var webgl = mesh._webgl,
                    ARRAY_BUFFER = _gl.ARRAY_BUFFER,
                    FLOAT = _gl.FLOAT,
                    attributes = material._webgl.attributes;

                if (webgl.vertexBuffer && attributes.position > -1) {
                    _gl.bindBuffer(ARRAY_BUFFER, webgl.vertexBuffer);
                    enableAttribute(attributes.position);
                    _gl.vertexAttribPointer(attributes.position, 3, FLOAT, false, 0, 0);
                }
                if (webgl.normalBuffer && attributes.normal > -1) {
                    _gl.bindBuffer(ARRAY_BUFFER, webgl.normalBuffer);
                    enableAttribute(attributes.normal);
                    _gl.vertexAttribPointer(attributes.normal, 3, FLOAT, false, 0, 0);
                }
                if (webgl.tangentBuffer && attributes.tangent > -1) {
                    _gl.bindBuffer(ARRAY_BUFFER, webgl.tangentBuffer);
                    enableAttribute(attributes.tangent);
                    _gl.vertexAttribPointer(attributes.tangent, 4, FLOAT, false, 0, 0);
                }
                if (webgl.colorBuffer && attributes.color > -1) {
                    _gl.bindBuffer(ARRAY_BUFFER, webgl.colorBuffer);
                    enableAttribute(attributes.color);
                    _gl.vertexAttribPointer(attributes.color, 3, FLOAT, false, 0, 0);
                }
                if (webgl.uvBuffer && attributes.uv > -1) {
                    _gl.bindBuffer(ARRAY_BUFFER, webgl.uvBuffer);
                    enableAttribute(attributes.uv);
                    _gl.vertexAttribPointer(attributes.uv, 2, FLOAT, false, 0, 0);
                }
                if (webgl.boneIndexBuffer && attributes.boneIndex > -1) {
                    _gl.bindBuffer(ARRAY_BUFFER, webgl.boneIndexBuffer);
                    enableAttribute(attributes.boneIndex);
                    _gl.vertexAttribPointer(attributes.boneIndex, 1, FLOAT, false, 0, 0);
                }
                if (webgl.boneWeightBuffer && attributes.boneWeight > -1) {
                    _gl.bindBuffer(ARRAY_BUFFER, webgl.boneWeightBuffer);
                    enableAttribute(attributes.boneWeight);
                    _gl.vertexAttribPointer(attributes.boneWeight, 3, FLOAT, false, 0, 0);
                }

                if (material.wireframe) {
                    if (webgl.lineBuffer) _gl.bindBuffer(_gl.ELEMENT_ARRAY_BUFFER, webgl.lineBuffer);
                } else {
                    if (webgl.indexBuffer) _gl.bindBuffer(_gl.ELEMENT_ARRAY_BUFFER, webgl.indexBuffer);
                }

                _lastBuffer = mesh._webgl;
            }


            var _enabledAttributes = undefined;

            function enableAttribute(attribute) {

                if (_enabledAttributes[attribute] === 0) {
                    _gl.enableVertexAttribArray(attribute);
                    _enabledAttributes[attribute] = 1;
                }
            };


            function disableAttributes() {
                var i = _maxAttributes;

                while (i--) {

                    if (_enabledAttributes[i] === 1) {
                        _gl.disableVertexAttribArray(i);
                        _enabledAttributes[i] = 0;
                    }
                }
            };


            var _lastProgram = undefined,
                VEC3 = new Vec3,
                VEC3_2 = new Vec3,
                COLOR = new Color;

            function bindMaterial(material, transform, camera, lights, ambient) {
                var program = material._webgl,
                    webglProgram = material._webgl.program,
                    parameters = program.parameters,
                    uniforms = program.uniforms,
                    state, i, length, force = false;

                if (_lastProgram !== webglProgram) {
                    _gl.useProgram(webglProgram);
                    force = true;
                    _lastProgram = webglProgram;
                }

                if (uniforms.modelMatrix) setUniformMatrix4fv(force, uniforms.modelMatrix.location, transform.matrixWorld, "matrixWorld");
                if (uniforms.modelViewMatrix) setUniformMatrix4fv(force, uniforms.modelViewMatrix.location, transform.modelView, "modelView");
                if (uniforms.projectionMatrix) setUniformMatrix4fv(force, uniforms.projectionMatrix.location, camera.projection, "projectionMatrix");
                if (uniforms.viewMatrix) setUniformMatrix4fv(force, uniforms.viewMatrix.location, camera.view, "viewMatrix");
                if (uniforms.normalMatrix) setUniformMatrix3fv(force, uniforms.normalMatrix.location, transform.normalMatrix, "normalMatrix");
                if (uniforms.cameraPosition) setUniform3f(force, uniforms.cameraPosition.location, VEC3.positionFromMat4(camera.transform.matrixWorld), "cameraPosition");
                if (uniforms.ambient) setUniform3f(force, uniforms.ambient.location, ambient, "ambient");

                if (parameters.useLights && (length = lights.length)) {
                    var maxPointLights = parameters.maxPointLights,
                        maxDirectionalLights = parameters.maxDirectionalLights,
                        maxSpotLights = parameters.maxSpotLights,
                        maxHemiLights = parameters.maxHemiLights,

                        pointLights = 0,
                        pointLightColor = uniforms.pointLightColor ? uniforms.pointLightColor.location : undefined,
                        pointLightPosition = uniforms.pointLightPosition ? uniforms.pointLightPosition.location : undefined,
                        pointLightDistance = uniforms.pointLightDistance ? uniforms.pointLightDistance.location : undefined,

                        directionalLights = 0,
                        directionalLightColor = uniforms.directionalLightColor ? uniforms.directionalLightColor.location : undefined,
                        directionalLightDirection = uniforms.directionalLightDirection ? uniforms.directionalLightDirection.location : undefined,

                        spotLights = 0,
                        spotLightColor = uniforms.spotLightColor ? uniforms.spotLightColor.location : undefined,
                        spotLightPosition = uniforms.spotLightPosition ? uniforms.spotLightPosition.location : undefined,
                        spotLightDirection = uniforms.spotLightDirection ? uniforms.spotLightDirection.location : undefined,
                        spotLightDistance = uniforms.spotLightDistance ? uniforms.spotLightDistance.location : undefined,
                        spotLightAngleCos = uniforms.spotLightAngleCos ? uniforms.spotLightAngleCos.location : undefined,
                        spotLightExponent = uniforms.spotLightExponent ? uniforms.spotLightExponent.location : undefined,

                        hemiLights = 0,
                        hemiLightColor = uniforms.hemiLightColor ? uniforms.hemiLightColor.location : undefined,
                        hemiLightDirection = uniforms.hemiLightDirection ? uniforms.hemiLightDirection.location : undefined,

                        light, type;

                    for (i = 0; i < length; i++) {
                        light = lights[i];
                        if (!light.visible) continue;

                        type = light.type;
                        COLOR.copy(light.color).smul(light.energy);

                        if (pointLightColor.length && type === LightType.Point) {
                            if (pointLights >= maxPointLights) continue;

                            VEC3.positionFromMat4(light.transform.matrixWorld);

                            setUniform3f(force, pointLightColor[pointLights], COLOR, "pointLightColor", true, pointLights);
                            setUniform3f(force, pointLightPosition[pointLights], VEC3, "pointLightPosition", true, pointLights);
                            setUniform1f(force, pointLightDistance[pointLights], light.distance, "pointLightDistance", true, pointLights);
                            pointLights++;
                        } else if (directionalLightColor.length && type === LightType.Directional) {
                            if (directionalLights >= maxDirectionalLights) continue;

                            VEC3.positionFromMat4(light.transform.matrixWorld).sub(light.target).normalize();
                            if (VEC3.lengthSq() === 0) continue;

                            setUniform3f(force, directionalLightColor[directionalLights], COLOR, "directionalLightColor", true, directionalLights);
                            setUniform3f(force, directionalLightDirection[directionalLights], VEC3, "directionalLightDirection", true, directionalLights);
                            directionalLights++;

                        } else if (spotLightColor.length && type === LightType.Spot) {
                            if (spotLights >= maxSpotLights) continue;

                            VEC3.positionFromMat4(light.transform.matrixWorld);
                            if (VEC3.lengthSq() === 0) continue;

                            VEC3_2.copy(VEC3).sub(light.target).normalize();
                            if (VEC3_2.lengthSq() === 0) continue;

                            setUniform3f(force, spotLightColor[spotLights], COLOR, "spotLightColor", true, spotLights);
                            setUniform3f(force, spotLightPosition[spotLights], VEC3, "spotLightPosition", true, spotLights);
                            setUniform3f(force, spotLightDirection[spotLights], VEC3_2, "spotLightDirection", true, spotLights);
                            setUniform1f(force, spotLightDistance[spotLights], light.distance, "spotLightDistance", true, spotLights);
                            setUniform1f(force, spotLightAngleCos[spotLights], light._angleCos, "spotLightAngleCos", true, spotLights);
                            setUniform1f(force, spotLightExponent[spotLights], light.exponent, "spotLightExponent", true, spotLights);
                            spotLights++;

                        } else if (hemiLightColor.length && type === LightType.Hemi) {
                            if (hemiLights >= maxHemiLights) continue;

                            VEC3.positionFromMat4(light.transform.matrixWorld).sub(light.target).normalize();
                            if (VEC3.lengthSq() === 0) continue;

                            setUniform3f(force, hemiLightColor[hemiLights], COLOR, "hemiLightColor", true, hemiLights);
                            setUniform3f(force, hemiLightDirection[hemiLights], VEC3, "hemiLightDirection", true, hemiLights);
                            hemiLights++;
                        }
                    }
                }

                bindCustomUniforms(force, program.customUniforms, uniforms, material.name, material.uniforms);
                _textureIndex = 0;
            };


            function bindCustomUniforms(force, customUniforms, uniforms, materialName, materialUniforms) {
                var i = customUniforms.length,
                    customUniform, uniformValue, location, name, type, value;

                while (i--) {
                    customUniform = customUniforms[i];
                    name = customUniform.name;

                    uniformValue = uniforms[name];
                    value = materialUniforms[name];

                    if (!uniformValue) continue;
                    if (!value) throw "WebGLRenderer bindShader: material " + materialName + " was not given a uniform named " + name;

                    if (uniformValue.isArray) {
                        location = uniformValue.location;
                        type = uniformValue.type;

                        for (i = location.length; i--;) setUniform(force, location[i], value[i], name, type, true, i);
                    } else {
                        setUniform(force, uniformValue.location, value, name, uniformValue.type);
                    }
                }
            }


            var _lastCamera = undefined,
                _lastResizeFn = undefined,
                _lastScene = undefined;
            this.preRender = function(gui, scene, camera) {
                if (!_context) return;
                var background = camera.background;

                if (_clearColor.r !== background.r || _clearColor.g !== background.g || _clearColor.b !== background.b) {
                    _clearColor.copy(background);
                    _gl.clearColor(background.r, background.g, background.b, 1);
                    if (!this.autoClear) clearCanvas(true, this.autoClearDepth, this.autoClearStencil);
                }
                if (_lastCamera !== camera) {
                    var w = _canvas.pixelWidth,
                        h = _canvas.pixelHeight;

                    camera.set(w, h);
                    this.setViewport(0, 0, w, h);

                    if (_lastResizeFn) _canvas.off("resize", _lastResizeFn);

                    _lastResizeFn = function() {
                        var w = this.pixelWidth,
                            h = this.pixelHeight;

                        camera.set(w, h);
                        _this.setViewport(0, 0, w, h);
                    };

                    _canvas.on("resize", _lastResizeFn);
                    _lastCamera = camera;
                }
                if (scene && _lastScene !== scene) {

                    _lastScene = scene;
                }

                _projScreenMatrix.mmul(camera.projection, camera.view);
                if (this.autoClear) clearCanvas(this.autoClearColor, this.autoClearDepth, this.autoClearStencil);
            };


            this.renderGUI = function(gui, camera) {
                if (!_context) return;
                var components = gui.components,
                    transform,
                    i;

            };


            var EMPTY_ARRAY = [];
            /**
             * @method render
             * @memberof Renderer
             * @brief renderers scene from camera's perspective
             * @param Scene scene
             * @param Camera camera
             */
            this.render = function(scene, camera) {
                if (!_context) return;
                var lineWidth = _lastLineWidth,
                    blending = _lastBlending,
                    cullFace = _lastCullFace,

                    components = scene.components,
                    ambient = scene.world.ambient,
                    lights = components.Light || EMPTY_ARRAY,
                    meshFilters = components.MeshFilter || EMPTY_ARRAY,
                    particleSystems = components.ParticleSystem || EMPTY_ARRAY,
                    meshFilter, particleSystem, transform,
                    i;

                for (i = meshFilters.length; i--;) {
                    meshFilter = meshFilters[i];
                    transform = meshFilter.transform;

                    if (!transform) continue;

                    transform.updateMatrices(camera.view);
                    renderMeshFilter(camera, lights, ambient, transform, meshFilter);
                }

                for (i = particleSystems.length; i--;) {
                    particleSystem = particleSystems[i];
                    transform = particleSystem.transform;

                    if (!transform) continue;

                    transform.updateMatrices(camera.view);
                    renderParticleSystem(camera, lights, ambient, transform, particleSystem);
                }

                setCullFace(cullFace);
                setBlending(blending);
                setLineWidth(lineWidth);
            };


            function renderParticleSystem(camera, lights, ambient, transform, particleSystem) {
                var emitters = particleSystem.emitters,
                    emitter, i = emitters.length,
                    material, program, buffers;

                while (i--) {
                    emitter = emitters[i];
                    material = emitter.material;

                    program = initEmitterMaterial(material, emitter, lights);
                    bindMaterial(material, transform, camera, lights, ambient);

                    buffers = initEmitterBuffers(emitter, transform, program.attributes);
                    if (!buffers.particleCount) continue;

                    setBlending(material.blending);
                    setCullFace(CullFace.Back);

                    _gl.drawArrays(_gl.POINTS, 0, buffers.particleCount);
                }
            }


            function renderMeshFilter(camera, lights, ambient, transform, meshFilter) {
                var mesh = meshFilter.mesh,
                    material = meshFilter.material,
                    side = material.side,

                    buffers = initMeshBuffers(mesh);

                initMaterial(material, mesh, lights)

                bindMaterial(material, transform, camera, lights, ambient);
                bindMesh(mesh, material);

                setBlending(material.blending);

                if (side === Side.Front) {
                    setCullFace(CullFace.Back);
                } else if (side === Side.Back) {
                    setCullFace(CullFace.Front);
                } else if (side === Side.Both) {
                    setCullFace();
                }

                if (material.wireframe) {
                    setLineWidth(material.wireframeLineWidth);
                    _gl.drawElements(_gl.LINES, buffers.lineCount, _gl.UNSIGNED_SHORT, 0);
                } else {
                    _gl.drawElements(_gl.TRIANGLES, buffers.indexCount, _gl.UNSIGNED_SHORT, 0);
                }
            }


            function initGL() {
                try {
                    _gl = getWebGLContext(_element, _this.attributes);
                    if (_gl === null) throw "Error creating WebGL context";
                } catch (e) {
                    Log.error(e);
                }

                if (_gl.getShaderPrecisionFormat == undefined) {
                    _gl.getShaderPrecisionFormat = function() {
                        return {
                            rangeMin: 1,
                            rangeMax: 1,
                            precision: 1
                        };
                    }
                }

                getExtensions();
                getGPUInfo();

                _enabledAttributes = new Uint8Array(_maxAttributes);
            }


            function setDefaultGLState() {

                _gl.clearColor(0, 0, 0, 1);
                _gl.clearDepth(1);
                _gl.clearStencil(0);

                setDepthTest(true);
                _gl.depthFunc(_gl.LEQUAL);

                _gl.frontFace(_gl.CCW);

                setCullFace(CullFace.Back);
                setBlending(Blending.Default);

                setViewport();
            }


            function handleWebGLContextLost(e) {
                e.preventDefault();
                Log.warn("Renderer: webgl context was lost");

                _context = false;
                this.emit("webglcontextlost", e);
            }


            function handleWebGLContextRestored(e) {
                Log.log("Renderer: webgl context was restored");

                initGL();
                setDefaultGLState();

                _context = true;
                this.emit("webglcontextrestored", e);
            }


            var _precision = "highp",
                _maxAnisotropy = 0,
                _maxTextures = 0,
                _maxVertexTextures = 0,
                _maxTextureSize = 0,
                _maxCubeTextureSize = 0,
                _maxRenderBufferSize = 0,

                _maxUniforms = 0,
                _maxVaryings = 0,
                _maxAttributes = 0;

            function getGPUInfo() {
                var VERTEX_SHADER = _gl.VERTEX_SHADER,
                    FRAGMENT_SHADER = _gl.FRAGMENT_SHADER,
                    HIGH_FLOAT = _gl.HIGH_FLOAT,
                    MEDIUM_FLOAT = _gl.MEDIUM_FLOAT,

                    EXT_texture_filter_anisotropic = _extensions.EXT_texture_filter_anisotropic,

                    vsHighpFloat = _gl.getShaderPrecisionFormat(VERTEX_SHADER, HIGH_FLOAT),
                    vsMediumpFloat = _gl.getShaderPrecisionFormat(VERTEX_SHADER, MEDIUM_FLOAT),

                    fsHighpFloat = _gl.getShaderPrecisionFormat(FRAGMENT_SHADER, HIGH_FLOAT),
                    fsMediumpFloat = _gl.getShaderPrecisionFormat(FRAGMENT_SHADER, MEDIUM_FLOAT),

                    highpAvailable = vsHighpFloat.precision > 0 && fsHighpFloat.precision > 0,
                    mediumpAvailable = vsMediumpFloat.precision > 0 && fsMediumpFloat.precision > 0,

                    precision = "highp";

                if (!highpAvailable || Device.mobile) {
                    if (mediumpAvailable) {
                        precision = "mediump";
                    } else {
                        precision = "lowp";
                    }
                }

                _precision = precision;
                _maxAnisotropy = EXT_texture_filter_anisotropic ? _gl.getParameter(EXT_texture_filter_anisotropic.MAX_TEXTURE_MAX_ANISOTROPY_EXT) : 1;
                _maxTextures = _gl.getParameter(_gl.MAX_TEXTURE_IMAGE_UNITS);
                _maxVertexTextures = _gl.getParameter(_gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS);
                _maxTextureSize = _gl.getParameter(_gl.MAX_TEXTURE_SIZE);
                _maxCubeTextureSize = _gl.getParameter(_gl.MAX_CUBE_MAP_TEXTURE_SIZE);
                _maxRenderBufferSize = _gl.getParameter(_gl.MAX_RENDERBUFFER_SIZE);

                _maxUniforms = max(_gl.getParameter(_gl.MAX_FRAGMENT_UNIFORM_VECTORS), _gl.getParameter(_gl.MAX_VERTEX_UNIFORM_VECTORS)) * 4;
                _maxVaryings = _gl.getParameter(_gl.MAX_VARYING_VECTORS) * 4;
                _maxAttributes = _gl.getParameter(_gl.MAX_VERTEX_ATTRIBS);
            }


            function getExtensions() {
                _extensions = {};

                getExtension("EXT_texture_filter_anisotropic");

                getExtension("WEBGL_compressed_texture_s3tc");
                _extensions.WEBGL_compressed_texture_s3tc_formats = _extensions.WEBGL_compressed_texture_s3tc ? _gl.getParameter(_gl.COMPRESSED_TEXTURE_FORMATS) : null;

                getExtension("OES_standard_derivatives");

                getExtension("OES_texture_float");
                getExtension("OES_texture_float_linear");
            }


            var getExtension_prefixes = ["WEBKIT", "MOZ", "O", "MS", "webkit", "moz", "o", "ms"],
                getExtension_length = getExtension_prefixes.length;

            function getExtension(name) {
                var extension = _extensions[name] || (_extensions[name] = _gl.getExtension(name));

                if (extension == undefined) {
                    var i = getExtension_length;

                    while (i--) {
                        if ((extension = _gl.getExtension(getExtension_prefixes[i] + "_" + name))) return (_extensions[name] = extension);
                    }
                }

                return extension;
            }
            this.getExtension = getExtension;
        }

        EventEmitter.extend(Renderer);


        return Renderer;
    }
);