瀏覽代碼

Merge pull request #1185 from benaadams/perf-improves

Reduce allocations and GPU state changes
David Catuhe 9 年之前
父節點
當前提交
7ee372967a

+ 14 - 1
dist/preview release/what's new.md

@@ -2,7 +2,20 @@
   - **Major updates**    
     
   - **Updates**
-    
+    - Alloc reduction: Prevent Observable.notifyObservers allocation ([benaadams](https://github.com/benaadams)) 
+    - Alloc reduction: Prevent per mesh per render closure allocation ([benaadams](https://github.com/benaadams)) 
+    - Alloc reduction: Avoid inadvertent per geometry per render alloc ([benaadams](https://github.com/benaadams)) 
+    - Alloc reduction: Avoid inadvertent per mesh per render alloc ([benaadams](https://github.com/benaadams)) 
+    - Alloc reduction: Avoid Collider root calc allocations ([benaadams](https://github.com/benaadams)) 
+    - Alloc reduction: Reduce mesh rotation allocs ([benaadams](https://github.com/benaadams)) 
+    - Alloc reduction: Allocate less in vector project and unproject ([benaadams](https://github.com/benaadams)) 
+    - GPU state change reduction: Improved uniform caching ([benaadams](https://github.com/benaadams)) 
+    - GPU state change reduction: Cache program ([benaadams](https://github.com/benaadams)) 
+    - GPU state change reduction: Cache vertexAttribPointer ([benaadams](https://github.com/benaadams)) 
+    - GPU state change reduction: Reduce buffer binds ([benaadams](https://github.com/benaadams)) 
+    - GPU state change reduction: Only bind unbound framebuffers ([benaadams](https://github.com/benaadams)) 
+    - GPU state change reduction: Cache texture changes better ([benaadams](https://github.com/benaadams)) 
+	
   - **Exporters**
     
   - **API doc**

+ 5 - 7
src/Animations/babylon.animation.ts

@@ -89,7 +89,7 @@
     }
 
     export class Animation {
-        private _keys: Array<any>;
+        private _keys: Array<{frame:number, value: any}>;
         private _offsetsCache = {};
         private _highLimitsCache = {};
         private _stopped = false;
@@ -134,9 +134,7 @@
 
             var animation = new Animation(name, targetProperty, framePerSecond, dataType, loopMode);
 
-            var keys = [];
-            keys.push({ frame: 0, value: from });
-            keys.push({ frame: totalFrame, value: to });
+            var keys: Array<{frame: number, value:any}> = [{ frame: 0, value: from }, { frame: totalFrame, value: to }];
             animation.setKeys(keys);
 
             if (easingFunction !== undefined) {
@@ -256,7 +254,7 @@
             return this._stopped;
         }
 
-        public getKeys(): any[] {
+        public getKeys(): Array<{ frame: number, value: any }> {
             return this._keys;
         }
 
@@ -324,7 +322,7 @@
             return clone;
         }
 
-        public setKeys(values: Array<any>): void {
+        public setKeys(values: Array<{ frame: number, value: any }>): void {
             this._keys = values.slice(0);
             this._offsetsCache = {};
             this._highLimitsCache = {};
@@ -747,7 +745,7 @@
             var animation = new Animation(parsedAnimation.name, parsedAnimation.property, parsedAnimation.framePerSecond, parsedAnimation.dataType, parsedAnimation.loopBehavior);
 
             var dataType = parsedAnimation.dataType;
-            var keys = [];
+            var keys: Array<{ frame: number, value: any }> = [];
             var data;
             var index: number;
 

+ 31 - 27
src/Collisions/babylon.collider.ts

@@ -21,37 +21,41 @@
         return true;
     };
 
-    var getLowestRoot = (a: number, b: number, c: number, maxR: number) => {
-        var determinant = b * b - 4.0 * a * c;
-        var result = { root: 0, found: false };
-
-        if (determinant < 0)
-            return result;
-
-        var sqrtD = Math.sqrt(determinant);
-        var r1 = (-b - sqrtD) / (2.0 * a);
-        var r2 = (-b + sqrtD) / (2.0 * a);
+    var getLowestRoot: (a: number, b: number, c: number, maxR: number) => { root: number, found: boolean } =
+        (function () {
+            var result = { root: 0, found: false };
+            return function (a: number, b: number, c: number, maxR: number) {
+                result.root = 0; result.found = false;
+                var determinant = b * b - 4.0 * a * c;
+                if (determinant < 0)
+                    return result;
+
+                var sqrtD = Math.sqrt(determinant);
+                var r1 = (-b - sqrtD) / (2.0 * a);
+                var r2 = (-b + sqrtD) / (2.0 * a);
+
+                if (r1 > r2) {
+                    var temp = r2;
+                    r2 = r1;
+                    r1 = temp;
+                }
 
-        if (r1 > r2) {
-            var temp = r2;
-            r2 = r1;
-            r1 = temp;
-        }
+                if (r1 > 0 && r1 < maxR) {
+                    result.root = r1;
+                    result.found = true;
+                    return result;
+                }
 
-        if (r1 > 0 && r1 < maxR) {
-            result.root = r1;
-            result.found = true;
-            return result;
-        }
+                if (r2 > 0 && r2 < maxR) {
+                    result.root = r2;
+                    result.found = true;
+                    return result;
+                }
 
-        if (r2 > 0 && r2 < maxR) {
-            result.root = r2;
-            result.found = true;
-            return result;
+                return result;
+            }
         }
-
-        return result;
-    };
+    )();
 
     export class Collider {
         public radius = new Vector3(1, 1, 1);

+ 122 - 86
src/Materials/babylon.effect.ts

@@ -76,7 +76,7 @@
         private _indexParameters: any;
 
         private _program: WebGLProgram;
-        private _valueCache = [];
+        private _valueCache: { [key: string]: any } = {};
 
         constructor(baseName: any, attributesNames: string[], uniformsNames: string[], samplers: string[], engine, defines?: string, fallbacks?: EffectFallbacks, onCompiled?: (effect: Effect) => void, onError?: (effect: Effect, errors: string) => void, indexParameters?: any) {
             this._engine = engine;
@@ -391,103 +391,164 @@
             this._engine.setTextureFromPostProcess(this._samplers.indexOf(channel), postProcess);
         }
 
-        public _cacheMatrix(uniformName, matrix) {
-            if (!this._valueCache[uniformName]) {
-                this._valueCache[uniformName] = new Matrix();
+        public _cacheMatrix(uniformName: string, matrix: Matrix): boolean {
+            var changed = false;
+            var cache: Matrix = this._valueCache[uniformName];
+            if (!cache || !(cache instanceof Matrix)) {
+                changed = true;
+                cache = new Matrix();
             }
 
+            var tm = cache.m;
+            var om = matrix.m;
             for (var index = 0; index < 16; index++) {
-                this._valueCache[uniformName].m[index] = matrix.m[index];
+                if (tm[index] !== om[index]) { 
+                    tm[index] = om[index];
+                    changed = true;
+                }
             }
+
+            this._valueCache[uniformName] = cache;
+            return changed;
         }
 
-        public _cacheFloat2(uniformName: string, x: number, y: number): void {
-            if (!this._valueCache[uniformName]) {
-                this._valueCache[uniformName] = [x, y];
-                return;
+        public _cacheFloat2(uniformName: string, x: number, y: number): boolean {
+            var cache = this._valueCache[uniformName];
+            if (!cache) {
+                cache = [x, y];
+                this._valueCache[uniformName] = cache;
+                return true;
             }
 
-            this._valueCache[uniformName][0] = x;
-            this._valueCache[uniformName][1] = y;
+            var changed = false;
+            if (cache[0] !== x) {
+                cache[0] = x;
+                changed = true;
+            }
+            if (cache[1] !== y) {
+                cache[1] = y;
+                changed = true;
+            }
+
+            this._valueCache[uniformName] = cache;
+            return changed;
         }
 
-        public _cacheFloat3(uniformName: string, x: number, y: number, z: number): void {
-            if (!this._valueCache[uniformName]) {
-                this._valueCache[uniformName] = [x, y, z];
-                return;
+        public _cacheFloat3(uniformName: string, x: number, y: number, z: number): boolean {
+            var cache = this._valueCache[uniformName];
+            if (!cache) {
+                cache = [x, y, z];
+                this._valueCache[uniformName] = cache;
+                return true;
             }
 
-            this._valueCache[uniformName][0] = x;
-            this._valueCache[uniformName][1] = y;
-            this._valueCache[uniformName][2] = z;
+            var changed = false;
+            if (cache[0] !== x) {
+                cache[0] = x;
+                changed = true;
+            }
+            if (cache[1] !== y) {
+                cache[1] = y;
+                changed = true;
+            }
+            if (cache[2] !== z) {
+                cache[2] = z;
+                changed = true;
+            }
+
+            this._valueCache[uniformName] = cache;
+            return changed;
         }
 
-        public _cacheFloat4(uniformName: string, x: number, y: number, z: number, w: number): void {
-            if (!this._valueCache[uniformName]) {
-                this._valueCache[uniformName] = [x, y, z, w];
-                return;
+        public _cacheFloat4(uniformName: string, x: number, y: number, z: number, w: number): boolean {
+            var cache = this._valueCache[uniformName];
+            if (!cache) {
+                cache = [x, y, z, w];
+                this._valueCache[uniformName] = cache;
+                return true;
             }
 
-            this._valueCache[uniformName][0] = x;
-            this._valueCache[uniformName][1] = y;
-            this._valueCache[uniformName][2] = z;
-            this._valueCache[uniformName][3] = w;
+            var changed = false;
+            if (cache[0] !== x) {
+                cache[0] = x;
+                changed = true;
+            }
+            if (cache[1] !== y) {
+                cache[1] = y;
+                changed = true;
+            }
+            if (cache[2] !== z) {
+                cache[2] = z;
+                changed = true;
+            }
+            if (cache[3] !== w) {
+                cache[3] = w;
+                changed = true;
+            }
+
+            this._valueCache[uniformName] = cache;
+            return changed;
         }
 
         public setArray(uniformName: string, array: number[]): Effect {
+            this._valueCache[uniformName] = null;
             this._engine.setArray(this.getUniform(uniformName), array);
 
             return this;
         }
 
         public setArray2(uniformName: string, array: number[]): Effect {
+            this._valueCache[uniformName] = null;
             this._engine.setArray2(this.getUniform(uniformName), array);
 
             return this;
         }
 
         public setArray3(uniformName: string, array: number[]): Effect {
+            this._valueCache[uniformName] = null;
             this._engine.setArray3(this.getUniform(uniformName), array);
 
             return this;
         }
 
         public setArray4(uniformName: string, array: number[]): Effect {
+            this._valueCache[uniformName] = null;
             this._engine.setArray4(this.getUniform(uniformName), array);
 
             return this;
         }
 
         public setMatrices(uniformName: string, matrices: Float32Array): Effect {
+            this._valueCache[uniformName] = null;
             this._engine.setMatrices(this.getUniform(uniformName), matrices);
 
             return this;
         }
 
         public setMatrix(uniformName: string, matrix: Matrix): Effect {
-            //if (this._valueCache[uniformName] && this._valueCache[uniformName].equals(matrix))
-            //    return this;
-
-           // this._cacheMatrix(uniformName, matrix);
-            this._engine.setMatrix(this.getUniform(uniformName), matrix);
-
+            if (this._cacheMatrix(uniformName, matrix)) {
+                this._engine.setMatrix(this.getUniform(uniformName), matrix);
+            }
             return this;
         }
 
         public setMatrix3x3(uniformName: string, matrix: Float32Array): Effect {
+            this._valueCache[uniformName] = null;
             this._engine.setMatrix3x3(this.getUniform(uniformName), matrix);
 
             return this;
         }
 
-        public setMatrix2x2(uniformname: string, matrix: Float32Array): Effect {
-            this._engine.setMatrix2x2(this.getUniform(uniformname), matrix);
+        public setMatrix2x2(uniformName: string, matrix: Float32Array): Effect {
+            this._valueCache[uniformName] = null;
+            this._engine.setMatrix2x2(this.getUniform(uniformName), matrix);
 
             return this;
         }
 
         public setFloat(uniformName: string, value: number): Effect {
-            if (this._valueCache[uniformName] && this._valueCache[uniformName] === value)
+            var cache = this._valueCache[uniformName];
+            if (cache && cache === value)
                 return this;
 
             this._valueCache[uniformName] = value;
@@ -498,7 +559,8 @@
         }
 
         public setBool(uniformName: string, bool: boolean): Effect {
-            if (this._valueCache[uniformName] && this._valueCache[uniformName] === bool)
+            var cache = this._valueCache[uniformName];
+            if (cache && cache === bool)
                 return this;
 
             this._valueCache[uniformName] = bool;
@@ -509,84 +571,58 @@
         }
 
         public setVector2(uniformName: string, vector2: Vector2): Effect {
-            if (this._valueCache[uniformName] && this._valueCache[uniformName][0] === vector2.x && this._valueCache[uniformName][1] === vector2.y)
-                return this;
-
-            this._cacheFloat2(uniformName, vector2.x, vector2.y);
-            this._engine.setFloat2(this.getUniform(uniformName), vector2.x, vector2.y);
-
+            if (this._cacheFloat2(uniformName, vector2.x, vector2.y)) {
+                this._engine.setFloat2(this.getUniform(uniformName), vector2.x, vector2.y);
+            }
             return this;
         }
 
         public setFloat2(uniformName: string, x: number, y: number): Effect {
-            if (this._valueCache[uniformName] && this._valueCache[uniformName][0] === x && this._valueCache[uniformName][1] === y)
-                return this;
-
-            this._cacheFloat2(uniformName, x, y);
-            this._engine.setFloat2(this.getUniform(uniformName), x, y);
-
+            if (this._cacheFloat2(uniformName, x, y)) {
+                this._engine.setFloat2(this.getUniform(uniformName), x, y);
+            }
             return this;
         }
 
         public setVector3(uniformName: string, vector3: Vector3): Effect {
-            if (this._valueCache[uniformName] && this._valueCache[uniformName][0] === vector3.x && this._valueCache[uniformName][1] === vector3.y && this._valueCache[uniformName][2] === vector3.z)
-                return this;
-
-            this._cacheFloat3(uniformName, vector3.x, vector3.y, vector3.z);
-
-            this._engine.setFloat3(this.getUniform(uniformName), vector3.x, vector3.y, vector3.z);
-
+            if (this._cacheFloat3(uniformName, vector3.x, vector3.y, vector3.z)) {
+                this._engine.setFloat3(this.getUniform(uniformName), vector3.x, vector3.y, vector3.z);
+            }
             return this;
         }
 
         public setFloat3(uniformName: string, x: number, y: number, z: number): Effect {
-            if (this._valueCache[uniformName] && this._valueCache[uniformName][0] === x && this._valueCache[uniformName][1] === y && this._valueCache[uniformName][2] === z)
-                return this;
-
-            this._cacheFloat3(uniformName, x, y, z);
-            this._engine.setFloat3(this.getUniform(uniformName), x, y, z);
-
+            if (this._cacheFloat3(uniformName, x, y, z)) {
+                this._engine.setFloat3(this.getUniform(uniformName), x, y, z);
+            }
             return this;
         }
 
         public setVector4(uniformName: string, vector4: Vector4): Effect {
-            if (this._valueCache[uniformName] && this._valueCache[uniformName][0] === vector4.x && this._valueCache[uniformName][1] === vector4.y && this._valueCache[uniformName][2] === vector4.z && this._valueCache[uniformName][3] === vector4.w)
-                return this;
-
-            this._cacheFloat4(uniformName, vector4.x, vector4.y, vector4.z, vector4.w);
-
-            this._engine.setFloat4(this.getUniform(uniformName), vector4.x, vector4.y, vector4.z, vector4.w);
-
+            if (this._cacheFloat4(uniformName, vector4.x, vector4.y, vector4.z, vector4.w)) {
+                this._engine.setFloat4(this.getUniform(uniformName), vector4.x, vector4.y, vector4.z, vector4.w);
+            }
             return this;
         }
 
         public setFloat4(uniformName: string, x: number, y: number, z: number, w: number): Effect {
-            if (this._valueCache[uniformName] && this._valueCache[uniformName][0] === x && this._valueCache[uniformName][1] === y && this._valueCache[uniformName][2] === z && this._valueCache[uniformName][3] === w)
-                return this;
-
-            this._cacheFloat4(uniformName, x, y, z, w);
-            this._engine.setFloat4(this.getUniform(uniformName), x, y, z, w);
-
+            if (this._cacheFloat4(uniformName, x, y, z, w)) {
+                this._engine.setFloat4(this.getUniform(uniformName), x, y, z, w);
+            }
             return this;
         }
 
         public setColor3(uniformName: string, color3: Color3): Effect {
-            if (this._valueCache[uniformName] && this._valueCache[uniformName][0] === color3.r && this._valueCache[uniformName][1] === color3.g && this._valueCache[uniformName][2] === color3.b)
-                return this;
-
-            this._cacheFloat3(uniformName, color3.r, color3.g, color3.b);
-            this._engine.setColor3(this.getUniform(uniformName), color3);
-
+            if (this._cacheFloat3(uniformName, color3.r, color3.g, color3.b)) {
+                this._engine.setColor3(this.getUniform(uniformName), color3);
+            }
             return this;
         }
 
         public setColor4(uniformName: string, color3: Color3, alpha: number): Effect {
-            if (this._valueCache[uniformName] && this._valueCache[uniformName][0] === color3.r && this._valueCache[uniformName][1] === color3.g && this._valueCache[uniformName][2] === color3.b && this._valueCache[uniformName][3] === alpha)
-                return this;
-
-            this._cacheFloat4(uniformName, color3.r, color3.g, color3.b, alpha);
-            this._engine.setColor4(this.getUniform(uniformName), color3, alpha);
-
+            if (this._cacheFloat4(uniformName, color3.r, color3.g, color3.b, alpha)) {
+                this._engine.setColor4(this.getUniform(uniformName), color3, alpha);
+            }
             return this;
         }
 

+ 23 - 13
src/Math/babylon.math.ts

@@ -1149,25 +1149,32 @@
             result.normalize();
         }
 
+        private static _viewportMatrixCache: Matrix;
+        private static _matrixCache: Matrix;
         public static Project(vector: Vector3, world: Matrix, transform: Matrix, viewport: Viewport): Vector3 {
             var cw = viewport.width;
             var ch = viewport.height;
             var cx = viewport.x;
             var cy = viewport.y;
 
-            var viewportMatrix = Matrix.FromValues(
+            var viewportMatrix = Vector3._viewportMatrixCache ? Vector3._viewportMatrixCache : (Vector3._viewportMatrixCache = new Matrix());
+
+            Matrix.FromValuesToRef(
                 cw / 2.0, 0, 0, 0,
                 0, -ch / 2.0, 0, 0,
                 0, 0, 1, 0,
-                cx + cw / 2.0, ch / 2.0 + cy, 0, 1);
+                cx + cw / 2.0, ch / 2.0 + cy, 0, 1, viewportMatrix);
 
-            var finalMatrix = world.multiply(transform).multiply(viewportMatrix);
+            var matrix = Vector3._matrixCache ? Vector3._matrixCache : (Vector3._matrixCache = new Matrix());
+            world.multiplyToRef(transform, matrix);
+            matrix.multiplyToRef(viewportMatrix, matrix);
 
-            return Vector3.TransformCoordinates(vector, finalMatrix);
+            return Vector3.TransformCoordinates(vector, matrix);
         }
 
         public static UnprojectFromTransform(source: Vector3, viewportWidth: number, viewportHeight: number, world: Matrix, transform: Matrix): Vector3 {
-            var matrix = world.multiply(transform);
+            var matrix = Vector3._matrixCache ? Vector3._matrixCache : (Vector3._matrixCache = new Matrix());
+            world.multiplyToRef(transform, matrix);
             matrix.invert();
             source.x = source.x / viewportWidth * 2 - 1;
             source.y = -(source.y / viewportHeight * 2 - 1);
@@ -1182,7 +1189,9 @@
         }
 
         public static Unproject(source: Vector3, viewportWidth: number, viewportHeight: number, world: Matrix, view: Matrix, projection: Matrix): Vector3 {
-            var matrix = world.multiply(view).multiply(projection);
+            var matrix = Vector3._matrixCache ? Vector3._matrixCache : (Vector3._matrixCache = new Matrix());
+            world.multiplyToRef(view, matrix)
+            matrix.multiplyToRef(projection, matrix);
             matrix.invert();
             var screenSource = new Vector3(source.x / viewportWidth * 2 - 1, -(source.y / viewportHeight * 2 - 1), source.z);
             var vector = Vector3.TransformCoordinates(screenSource, matrix);
@@ -2011,7 +2020,10 @@
         }
 
         public static RotationAxis(axis: Vector3, angle: number): Quaternion {
-            var result = new Quaternion();
+            return Quaternion.RotationAxisToRef(axis, angle, new Quaternion());
+        }
+
+        public static RotationAxisToRef(axis: Vector3, angle: number, result: Quaternion): Quaternion {
             var sin = Math.sin(angle / 2);
 
             axis.normalize();
@@ -2033,14 +2045,10 @@
         }
 
         public static RotationYawPitchRoll(yaw: number, pitch: number, roll: number): Quaternion {
-            var result = new Quaternion();
-
-            Quaternion.RotationYawPitchRollToRef(yaw, pitch, roll, result);
-
-            return result;
+            return Quaternion.RotationYawPitchRollToRef(yaw, pitch, roll, new Quaternion());
         }
 
-        public static RotationYawPitchRollToRef(yaw: number, pitch: number, roll: number, result: Quaternion): void {
+        public static RotationYawPitchRollToRef(yaw: number, pitch: number, roll: number, result: Quaternion): Quaternion {
             // Produces a quaternion from Euler angles in the z-y-x orientation (Tait-Bryan angles)
             var halfRoll = roll * 0.5;
             var halfPitch = pitch * 0.5;
@@ -2057,6 +2065,8 @@
             result.y = (sinYaw * cosPitch * cosRoll) - (cosYaw * sinPitch * sinRoll);
             result.z = (cosYaw * cosPitch * sinRoll) - (sinYaw * sinPitch * cosRoll);
             result.w = (cosYaw * cosPitch * cosRoll) + (sinYaw * sinPitch * sinRoll);
+
+            return result;
         }
 
         public static RotationAlphaBetaGamma(alpha: number, beta: number, gamma: number): Quaternion {

+ 9 - 6
src/Mesh/babylon.abstractMesh.ts

@@ -361,6 +361,7 @@
             return this._isWorldMatrixFrozen;
         }
 
+        private static _rotationAxisCache = new Quaternion();
         public rotate(axis: Vector3, amount: number, space?: Space): void {
             axis.normalize();
 
@@ -370,8 +371,8 @@
             }
             var rotationQuaternion: Quaternion;
             if (!space || space === Space.LOCAL) {
-                rotationQuaternion = Quaternion.RotationAxis(axis, amount);
-                this.rotationQuaternion = this.rotationQuaternion.multiply(rotationQuaternion);
+                rotationQuaternion = Quaternion.RotationAxisToRef(axis, amount, AbstractMesh._rotationAxisCache);
+                this.rotationQuaternion.multiplyToRef(rotationQuaternion, this.rotationQuaternion);
             }
             else {
                 if (this.parent) {
@@ -380,8 +381,8 @@
 
                     axis = Vector3.TransformNormal(axis, invertParentWorldMatrix);
                 }
-                rotationQuaternion = Quaternion.RotationAxis(axis, amount);
-                this.rotationQuaternion = rotationQuaternion.multiply(this.rotationQuaternion);
+                rotationQuaternion = Quaternion.RotationAxisToRef(axis, amount, AbstractMesh._rotationAxisCache);
+                rotationQuaternion.multiplyToRef(this.rotationQuaternion, this.rotationQuaternion);
             }
         }
 
@@ -744,6 +745,7 @@
             this.position = Vector3.TransformCoordinates(vector3, this._localWorld);
         }
 
+        private static _lookAtVectorCache = new Vector3(0,0,0);
         public lookAt(targetPoint: Vector3, yawCor: number, pitchCor: number, rollCor: number): void {
             /// <summary>Orients a mesh towards a target point. Mesh must be drawn facing user.</summary>
             /// <param name="targetPoint" type="Vector3">The position (must be in same space as current mesh) to look at</param>
@@ -756,11 +758,12 @@
             pitchCor = pitchCor || 0;
             rollCor = rollCor || 0;
 
-            var dv = targetPoint.subtract(this.position);
+            var dv = AbstractMesh._lookAtVectorCache;
+            targetPoint.subtractToRef(this.position, dv);
             var yaw = -Math.atan2(dv.z, dv.x) - Math.PI / 2;
             var len = Math.sqrt(dv.x * dv.x + dv.z * dv.z);
             var pitch = Math.atan2(dv.y, len);
-            this.rotationQuaternion = Quaternion.RotationYawPitchRoll(yaw + yawCor, pitch + pitchCor, rollCor);
+            Quaternion.RotationYawPitchRollToRef(yaw + yawCor, pitch + pitchCor, rollCor, this.rotationQuaternion);
         }
 
         public attachToBone(bone: Bone, affectedMesh: AbstractMesh): void {

+ 4 - 0
src/Mesh/babylon.geometry.ts

@@ -415,6 +415,10 @@
 
             this.delayLoadState = Engine.DELAYLOADSTATE_LOADING;
 
+            this._queueLoad(scene, onLoaded);
+        }
+
+        private _queueLoad(scene: Scene, onLoaded?: () => void): void {
             scene._addPendingData(this);
             Tools.LoadFile(this.delayLoadingFile, data => {
                 this._delayLoadingFunction(JSON.parse(data), this);

+ 29 - 25
src/Mesh/babylon.mesh.ts

@@ -973,7 +973,7 @@
         }
 
         public _processRendering(subMesh: SubMesh, effect: Effect, fillMode: number, batch: _InstancesBatch, hardwareInstancedRendering: boolean,
-            onBeforeDraw: (isInstance: boolean, world: Matrix) => void) {
+            onBeforeDraw: (isInstance: boolean, world: Matrix, effectiveMaterial?: Material) => void, effectiveMaterial?: Material) {
             var scene = this.getScene();
             var engine = scene.getEngine();
 
@@ -983,7 +983,7 @@
                 if (batch.renderSelf[subMesh._id]) {
                     // Draw
                     if (onBeforeDraw) {
-                        onBeforeDraw(false, this.getWorldMatrix());
+                        onBeforeDraw(false, this.getWorldMatrix(), effectiveMaterial);
                     }
 
                     this._draw(subMesh, fillMode, this._overridenInstanceCount);
@@ -996,7 +996,7 @@
                         // World
                         var world = instance.getWorldMatrix();
                         if (onBeforeDraw) {
-                            onBeforeDraw(true, world);
+                            onBeforeDraw(true, world, effectiveMaterial);
                         }
 
                         // Draw
@@ -1063,12 +1063,7 @@
             }
 
             // Draw
-            this._processRendering(subMesh, effect, fillMode, batch, hardwareInstancedRendering,
-                (isInstance, world) => {
-                    if (isInstance) {
-                        effectiveMaterial.bindOnlyWorldMatrix(world);
-                    }
-                });
+            this._processRendering(subMesh, effect, fillMode, batch, hardwareInstancedRendering, this._onBeforeDraw);
 
             // Unbind
             effectiveMaterial.unbind();
@@ -1092,6 +1087,12 @@
             this.onAfterRenderObservable.notifyObservers(this);
         }
 
+        private _onBeforeDraw(isInstance: boolean, world: Matrix, effectiveMaterial: Material): void {
+            if (isInstance) {
+                effectiveMaterial.bindOnlyWorldMatrix(world);
+            }
+        }
+
         /**
          * Returns an array populated with ParticleSystem objects whose the mesh is the emitter. 
          */
@@ -1126,32 +1127,35 @@
         }
 
         public _checkDelayState(): void {
-            var that = this;
             var scene = this.getScene();
 
             if (this._geometry) {
                 this._geometry.load(scene);
             }
-            else if (that.delayLoadState === Engine.DELAYLOADSTATE_NOTLOADED) {
-                that.delayLoadState = Engine.DELAYLOADSTATE_LOADING;
+            else if (this.delayLoadState === Engine.DELAYLOADSTATE_NOTLOADED) {
+                this.delayLoadState = Engine.DELAYLOADSTATE_LOADING;
+
+                this._queueLoad(this, scene);
+            }
+        }
 
-                scene._addPendingData(that);
+        private _queueLoad(mesh: Mesh, scene: Scene): void {
+            scene._addPendingData(mesh);
 
-                var getBinaryData = (this.delayLoadingFile.indexOf(".babylonbinarymeshdata") !== -1);
+            var getBinaryData = (this.delayLoadingFile.indexOf(".babylonbinarymeshdata") !== -1);
 
-                Tools.LoadFile(this.delayLoadingFile, data => {
+            Tools.LoadFile(this.delayLoadingFile, data => {
 
-                    if (data instanceof ArrayBuffer) {
-                        this._delayLoadingFunction(data, this);
-                    }
-                    else {
-                        this._delayLoadingFunction(JSON.parse(data), this);
-                    }
+                if (data instanceof ArrayBuffer) {
+                    this._delayLoadingFunction(data, this);
+                }
+                else {
+                    this._delayLoadingFunction(JSON.parse(data), this);
+                }
 
-                    this.delayLoadState = Engine.DELAYLOADSTATE_LOADED;
-                    scene._removePendingData(this);
-                }, () => { }, scene.database, getBinaryData);
-            }
+                this.delayLoadState = Engine.DELAYLOADSTATE_LOADED;
+                scene._removePendingData(this);
+            }, () => { }, scene.database, getBinaryData);
         }
 
         /**

+ 11 - 1
src/Tools/babylon.observable.ts

@@ -9,8 +9,13 @@
         * If the callback of a given Observer set skipNextObservers to true the following observers will be ignored
         */
         constructor(mask: number, skipNextObservers = false) {
+            this.initalize(mask, skipNextObservers);
+        }
+
+        public initalize(mask: number, skipNextObservers = false): EventState {
             this.mask = mask;
             this.skipNextObservers = skipNextObservers;
+            return this;
         }
 
         /**
@@ -40,6 +45,8 @@
      * A given observer can register itself with only Move and Stop (mask = 0x03), then it will only be notified when one of these two occurs and will never be for Turn Left/Right.
      */
     export class Observable<T> {
+        private static _pooledEventState: EventState = null;
+
         _observers = new Array<Observer<T>>();
 
         /**
@@ -103,7 +110,8 @@
          * @param mask
          */
         public notifyObservers(eventData: T, mask: number = -1): void {
-            var state = new EventState(mask);
+            var state = Observable._pooledEventState ? Observable._pooledEventState.initalize(mask) : new EventState(mask);
+            Observable._pooledEventState = null;
 
             for (var obs of this._observers) {
                 if (obs.mask & mask) {
@@ -113,6 +121,8 @@
                     break;
                 }
             }
+
+            Observable._pooledEventState = state;
         }
 
         /**

+ 132 - 81
src/babylon.engine.ts

@@ -58,7 +58,7 @@
         var potWidth = Tools.GetExponentOfTwo(width, engine.getCaps().maxTextureSize);
         var potHeight = Tools.GetExponentOfTwo(height, engine.getCaps().maxTextureSize);
 
-        gl.bindTexture(gl.TEXTURE_2D, texture);
+        engine._bindTextureDirectly(gl.TEXTURE_2D, texture);
         gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, invertY === undefined ? 1 : (invertY ? 1 : 0));
 
         texture._baseWidth = width;
@@ -78,7 +78,7 @@
             gl.generateMipmap(gl.TEXTURE_2D);
         }
 
-        gl.bindTexture(gl.TEXTURE_2D, null);
+        engine._bindTextureDirectly(gl.TEXTURE_2D, null);
 
         engine.resetTextureCache();
         scene._removePendingData(texture);
@@ -337,8 +337,10 @@
         // Cache
         private _loadedTexturesCache = new Array<WebGLTexture>();
         private _maxTextureChannels = 16;
-        private _activeTexturesCache = new Array<BaseTexture>(this._maxTextureChannels);
+        private _activeTexture: number;
+        private _activeTexturesCache = new Array<WebGLTexture>(this._maxTextureChannels);
         private _currentEffect: Effect;
+        private _currentProgram: WebGLProgram;
         private _compiledEffects = {};
         private _vertexAttribArraysEnabled: boolean[];
         private _vertexAttribArraysToUse: boolean[];
@@ -349,6 +351,8 @@
         private _currentRenderTarget: WebGLTexture;
         private _uintIndicesCurrentlySet = false;
         private _currentBoundBuffer = new Array<WebGLBuffer>();
+        private _currentFramebuffer: WebGLFramebuffer;
+        private _currentBufferPointers: Array<{ indx: number, size: number, type: number, normalized: boolean, stride: number, offset: number, buffer: WebGLBuffer }> = [];
         private _currentInstanceLocations = new Array<number>();
         private _currentInstanceBuffers = new Array<WebGLBuffer>();
 
@@ -835,7 +839,7 @@
             this._currentRenderTarget = texture;
 
             var gl = this._gl;
-            gl.bindFramebuffer(gl.FRAMEBUFFER, texture._framebuffer);
+            this.bindUnboundFramebuffer(texture._framebuffer);
 
             if (texture.isCube) {
                 gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_CUBE_MAP_POSITIVE_X + faceIndex, texture, 0);
@@ -848,24 +852,31 @@
             this.wipeCaches();
         }
 
+        private bindUnboundFramebuffer(framebuffer: WebGLFramebuffer) {
+            if (this._currentFramebuffer !== framebuffer) {
+                this._gl.bindFramebuffer(this._gl.FRAMEBUFFER, framebuffer);
+                this._currentFramebuffer = framebuffer;
+            }
+        }
+
         public unBindFramebuffer(texture: WebGLTexture, disableGenerateMipMaps = false): void {
             this._currentRenderTarget = null;
             if (texture.generateMipMaps && !disableGenerateMipMaps) {
                 var gl = this._gl;
-                gl.bindTexture(gl.TEXTURE_2D, texture);
+                this._bindTextureDirectly(gl.TEXTURE_2D, texture);
                 gl.generateMipmap(gl.TEXTURE_2D);
-                gl.bindTexture(gl.TEXTURE_2D, null);
+                this._bindTextureDirectly(gl.TEXTURE_2D, null);
             }
 
-            this._gl.bindFramebuffer(this._gl.FRAMEBUFFER, null);
+            this.bindUnboundFramebuffer(null);
         }
 
         public generateMipMapsForCubemap(texture: WebGLTexture) {
             if (texture.generateMipMaps) {
                 var gl = this._gl;
-                gl.bindTexture(gl.TEXTURE_CUBE_MAP, texture);
+                this._bindTextureDirectly(gl.TEXTURE_CUBE_MAP, texture);
                 gl.generateMipmap(gl.TEXTURE_CUBE_MAP);
-                gl.bindTexture(gl.TEXTURE_CUBE_MAP, null);
+                this._bindTextureDirectly(gl.TEXTURE_CUBE_MAP, null);
             }
         }
 
@@ -875,7 +886,7 @@
 
         public restoreDefaultFramebuffer(): void {
             this._currentRenderTarget = null;
-            this._gl.bindFramebuffer(this._gl.FRAMEBUFFER, null);
+            this.bindUnboundFramebuffer(null);
 
             this.setViewport(this._cachedViewport);
 
@@ -884,13 +895,13 @@
 
         // VBOs
         private _resetVertexBufferBinding(): void {
-            this.bindBuffer(null, this._gl.ARRAY_BUFFER);
+            this.bindArrayBuffer(null);
             this._cachedVertexBuffers = null;
         }
 
         public createVertexBuffer(vertices: number[] | Float32Array): WebGLBuffer {
             var vbo = this._gl.createBuffer();
-            this.bindBuffer(vbo, this._gl.ARRAY_BUFFER);
+            this.bindArrayBuffer(vbo);
 
             if (vertices instanceof Float32Array) {
                 this._gl.bufferData(this._gl.ARRAY_BUFFER, <Float32Array>vertices, this._gl.STATIC_DRAW);
@@ -905,7 +916,7 @@
 
         public createDynamicVertexBuffer(vertices: number[] | Float32Array): WebGLBuffer {
             var vbo = this._gl.createBuffer();
-            this.bindBuffer(vbo, this._gl.ARRAY_BUFFER);
+            this.bindArrayBuffer(vbo);
 
             if (vertices instanceof Float32Array) {
                 this._gl.bufferData(this._gl.ARRAY_BUFFER, <Float32Array>vertices, this._gl.DYNAMIC_DRAW);
@@ -918,7 +929,7 @@
         }
 
         public updateDynamicVertexBuffer(vertexBuffer: WebGLBuffer, vertices: number[] | Float32Array, offset?: number, count?: number): void {
-            this.bindBuffer(vertexBuffer, this._gl.ARRAY_BUFFER);
+            this.bindArrayBuffer(vertexBuffer);
 
             if (offset === undefined) {
                 offset = 0;
@@ -942,13 +953,13 @@
         }
 
         private _resetIndexBufferBinding(): void {
-            this.bindBuffer(null, this._gl.ELEMENT_ARRAY_BUFFER);
+            this.bindIndexBuffer(null);
             this._cachedIndexBuffer = null;
         }
 
         public createIndexBuffer(indices: number[] | Int32Array): WebGLBuffer {
             var vbo = this._gl.createBuffer();
-            this.bindBuffer(vbo, this._gl.ELEMENT_ARRAY_BUFFER);
+            this.bindIndexBuffer(vbo);
 
             // Check for 32 bits indices
             var arrayBuffer;
@@ -979,8 +990,8 @@
             this.bindBuffer(buffer, this._gl.ARRAY_BUFFER);
         }
 
-        public updateArrayBuffer(data: Float32Array): void {
-            this._gl.bufferSubData(this._gl.ARRAY_BUFFER, 0, data);
+        private bindIndexBuffer(buffer: WebGLBuffer): void {
+            this.bindBuffer(buffer, this._gl.ELEMENT_ARRAY_BUFFER);
         }
 
         private bindBuffer(buffer: WebGLBuffer, target: number): void {
@@ -990,19 +1001,43 @@
             }
         }
 
+        public updateArrayBuffer(data: Float32Array): void {
+            this._gl.bufferSubData(this._gl.ARRAY_BUFFER, 0, data);
+        }
+
+        private vertexAttribPointer(buffer: WebGLBuffer, indx: number, size: number, type: number, normalized: boolean, stride: number, offset: number): void {
+            var pointer = this._currentBufferPointers[indx];
+
+            var changed = false;
+            if (!pointer) {
+                changed = true;
+                this._currentBufferPointers[indx] = { indx, size, type, normalized, stride, offset, buffer: buffer };
+            } else {
+                if (pointer.buffer !== buffer) { pointer.buffer = buffer; changed = true; }
+                if (pointer.size !== size) { pointer.size = size; changed = true; }
+                if (pointer.type !== type) { pointer.type = type; changed = true; }
+                if (pointer.normalized !== normalized) { pointer.normalized = normalized; changed = true; }
+                if (pointer.stride !== stride) { pointer.stride = stride; changed = true; }
+                if (pointer.offset !== offset) { pointer.offset = offset; changed = true; }
+            }
+
+            if (changed) {
+                this.bindArrayBuffer(buffer);
+                this._gl.vertexAttribPointer(indx, size, type, normalized, stride, offset);
+            }
+        }
+
         public bindBuffersDirectly(vertexBuffer: WebGLBuffer, indexBuffer: WebGLBuffer, vertexDeclaration: number[], vertexStrideSize: number, effect: Effect): void {
             if (this._cachedVertexBuffers !== vertexBuffer || this._cachedEffectForVertexBuffers !== effect) {
                 this._cachedVertexBuffers = vertexBuffer;
                 this._cachedEffectForVertexBuffers = effect;
 
-                this.bindBuffer(vertexBuffer, this._gl.ARRAY_BUFFER);
-
                 var offset = 0;
                 for (var index = 0; index < vertexDeclaration.length; index++) {
                     var order = effect.getAttributeLocation(index);
 
                     if (order >= 0) {
-                        this._gl.vertexAttribPointer(order, vertexDeclaration[index], this._gl.FLOAT, false, vertexStrideSize, offset);
+                        this.vertexAttribPointer(vertexBuffer, order, vertexDeclaration[index], this._gl.FLOAT, false, vertexStrideSize, offset);
                     }
                     offset += vertexDeclaration[index] * 4;
                 }
@@ -1010,7 +1045,7 @@
 
             if (this._cachedIndexBuffer !== indexBuffer) {
                 this._cachedIndexBuffer = indexBuffer;
-                this.bindBuffer(indexBuffer, this._gl.ELEMENT_ARRAY_BUFFER);
+                this.bindIndexBuffer(indexBuffer);
                 this._uintIndicesCurrentlySet = indexBuffer.is32Bits;
             }
         }
@@ -1031,8 +1066,7 @@
                             continue;
                         }
                         var buffer = vertexBuffer.getBuffer();
-                        this.bindBuffer(buffer, this._gl.ARRAY_BUFFER);
-                        this._gl.vertexAttribPointer(order, vertexBuffer.getSize(), this._gl.FLOAT, false, vertexBuffer.getStrideSize() * 4, vertexBuffer.getOffset() * 4);
+                        this.vertexAttribPointer(buffer, order, vertexBuffer.getSize(), this._gl.FLOAT, false, vertexBuffer.getStrideSize() * 4, vertexBuffer.getOffset() * 4);
 
                         if (vertexBuffer.getIsInstanced()) {
                             this._caps.instancedArrays.vertexAttribDivisorANGLE(order, 1);
@@ -1045,7 +1079,7 @@
 
             if (indexBuffer != null && this._cachedIndexBuffer !== indexBuffer) {
                 this._cachedIndexBuffer = indexBuffer;
-                this.bindBuffer(indexBuffer, this._gl.ELEMENT_ARRAY_BUFFER);
+                this.bindIndexBuffer(indexBuffer);
                 this._uintIndicesCurrentlySet = indexBuffer.is32Bits;
             }
         }
@@ -1056,7 +1090,7 @@
                 var instancesBuffer = this._currentInstanceBuffers[i];
                 if (boundBuffer != instancesBuffer) {
                     boundBuffer = instancesBuffer;
-                    this.bindBuffer(instancesBuffer, this._gl.ARRAY_BUFFER);
+                    this.bindArrayBuffer(instancesBuffer);
                 }
                 var offsetLocation = this._currentInstanceLocations[i];
                 this._caps.instancedArrays.vertexAttribDivisorANGLE(offsetLocation, 0);
@@ -1081,7 +1115,7 @@
 
             buffer.capacity = capacity;
 
-            this.bindBuffer(buffer, this._gl.ARRAY_BUFFER);
+            this.bindArrayBuffer(buffer);
             this._gl.bufferData(this._gl.ARRAY_BUFFER, capacity, this._gl.DYNAMIC_DRAW);
             return buffer;
         }
@@ -1091,7 +1125,7 @@
         }
 
         public updateAndBindInstancesBuffer(instancesBuffer: WebGLBuffer, data: Float32Array, offsetLocations: number[] | InstancingAttributeInfo[]): void {
-            this.bindBuffer(instancesBuffer, this._gl.ARRAY_BUFFER);
+            this.bindArrayBuffer(instancesBuffer);
             if (data) {
                 this._gl.bufferSubData(this._gl.ARRAY_BUFFER, 0, data);
             }
@@ -1105,7 +1139,7 @@
                 for (let i = 0; i < offsetLocations.length; i++) {
                     let ai = <InstancingAttributeInfo>offsetLocations[i];
                     this._gl.enableVertexAttribArray(ai.index);
-                    this._gl.vertexAttribPointer(ai.index, ai.attributeSize, ai.attribyteType || this._gl.FLOAT, ai.normalized || false, stride, ai.offset);
+                    this.vertexAttribPointer(instancesBuffer, ai.index, ai.attributeSize, ai.attribyteType || this._gl.FLOAT, ai.normalized || false, stride, ai.offset);
                     this._caps.instancedArrays.vertexAttribDivisorANGLE(ai.index, 1);
                     this._currentInstanceLocations.push(ai.index);
                     this._currentInstanceBuffers.push(instancesBuffer);
@@ -1114,7 +1148,7 @@
                 for (let index = 0; index < 4; index++) {
                     let offsetLocation = <number>offsetLocations[index];
                     this._gl.enableVertexAttribArray(offsetLocation);
-                    this._gl.vertexAttribPointer(offsetLocation, 4, this._gl.FLOAT, false, 64, index * 16);
+                    this.vertexAttribPointer(instancesBuffer, offsetLocation, 4, this._gl.FLOAT, false, 64, index * 16);
                     this._caps.instancedArrays.vertexAttribDivisorANGLE(offsetLocation, 1);
                     this._currentInstanceLocations.push(offsetLocation);
                     this._currentInstanceBuffers.push(instancesBuffer);
@@ -1271,7 +1305,7 @@
             this._vertexAttribArraysEnabled = this._vertexAttribArraysEnabled || [];
 
             // Use program
-            this._gl.useProgram(effect.getProgram());
+            this.setProgram(effect.getProgram());
 
             var i, ul;
             for (i = 0, ul = this._vertexAttribArraysToUse.length; i < ul; i++) {
@@ -1521,7 +1555,7 @@
         public setSamplingMode(texture: WebGLTexture, samplingMode: number): void {
             var gl = this._gl;
 
-            gl.bindTexture(gl.TEXTURE_2D, texture);
+            this._bindTextureDirectly(gl.TEXTURE_2D, texture);
 
             var magFilter = gl.NEAREST;
             var minFilter = gl.NEAREST;
@@ -1537,7 +1571,7 @@
             gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, magFilter);
             gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, minFilter);
 
-            gl.bindTexture(gl.TEXTURE_2D, null);
+            this._bindTextureDirectly(gl.TEXTURE_2D, null);
 
             texture.samplingMode = samplingMode;
         }
@@ -1680,7 +1714,7 @@
 
         public updateRawTexture(texture: WebGLTexture, data: ArrayBufferView, format: number, invertY: boolean, compression: string = null): void {
             var internalFormat = this._getInternalFormat(format);
-            this._gl.bindTexture(this._gl.TEXTURE_2D, texture);
+            this._bindTextureDirectly(this._gl.TEXTURE_2D, texture);
             this._gl.pixelStorei(this._gl.UNPACK_FLIP_Y_WEBGL, invertY === undefined ? 1 : (invertY ? 1 : 0));
 
             if (texture._width % 4 !== 0) {
@@ -1696,7 +1730,7 @@
             if (texture.generateMipMaps) {
                 this._gl.generateMipmap(this._gl.TEXTURE_2D);
             }
-            this._gl.bindTexture(this._gl.TEXTURE_2D, null);
+            this._bindTextureDirectly(this._gl.TEXTURE_2D, null);
             this.resetTextureCache();
             texture.isReady = true;
         }
@@ -1710,14 +1744,14 @@
             texture.references = 1;
 
             this.updateRawTexture(texture, data, format, invertY, compression);
-            this._gl.bindTexture(this._gl.TEXTURE_2D, texture);
+            this._bindTextureDirectly(this._gl.TEXTURE_2D, texture);
 
             // Filters
             var filters = getSamplingParameters(samplingMode, generateMipMaps, this._gl);
 
             this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_MAG_FILTER, filters.mag);
             this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_MIN_FILTER, filters.min);
-            this._gl.bindTexture(this._gl.TEXTURE_2D, null);
+            this._bindTextureDirectly(this._gl.TEXTURE_2D, null);
 
             texture.samplingMode = samplingMode;
 
@@ -1755,22 +1789,22 @@
             var filters = getSamplingParameters(samplingMode, texture.generateMipMaps, this._gl);
 
             if (texture.isCube) {
-                this._gl.bindTexture(this._gl.TEXTURE_CUBE_MAP, texture);
+                this._bindTextureDirectly(this._gl.TEXTURE_CUBE_MAP, texture);
 
                 this._gl.texParameteri(this._gl.TEXTURE_CUBE_MAP, this._gl.TEXTURE_MAG_FILTER, filters.mag);
                 this._gl.texParameteri(this._gl.TEXTURE_CUBE_MAP, this._gl.TEXTURE_MIN_FILTER, filters.min);
-                this._gl.bindTexture(this._gl.TEXTURE_CUBE_MAP, null);
+                this._bindTextureDirectly(this._gl.TEXTURE_CUBE_MAP, null);
             } else {
-                this._gl.bindTexture(this._gl.TEXTURE_2D, texture);
+                this._bindTextureDirectly(this._gl.TEXTURE_2D, texture);
 
                 this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_MAG_FILTER, filters.mag);
                 this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_MIN_FILTER, filters.min);
-                this._gl.bindTexture(this._gl.TEXTURE_2D, null);
+                this._bindTextureDirectly(this._gl.TEXTURE_2D, null);
             }
         }
 
         public updateDynamicTexture(texture: WebGLTexture, canvas: HTMLCanvasElement, invertY: boolean, premulAlpha: boolean = false): void {
-            this._gl.bindTexture(this._gl.TEXTURE_2D, texture);
+            this._bindTextureDirectly(this._gl.TEXTURE_2D, texture);
             this._gl.pixelStorei(this._gl.UNPACK_FLIP_Y_WEBGL, invertY ? 1 : 0);
             if (premulAlpha) {
                 this._gl.pixelStorei(this._gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, 1);
@@ -1779,7 +1813,7 @@
             if (texture.generateMipMaps) {
                 this._gl.generateMipmap(this._gl.TEXTURE_2D);
             }
-            this._gl.bindTexture(this._gl.TEXTURE_2D, null);
+            this._bindTextureDirectly(this._gl.TEXTURE_2D, null);
             this.resetTextureCache();
             texture.isReady = true;
         }
@@ -1789,7 +1823,7 @@
                 return;
             }
 
-            this._gl.bindTexture(this._gl.TEXTURE_2D, texture);
+            this._bindTextureDirectly(this._gl.TEXTURE_2D, texture);
             this._gl.pixelStorei(this._gl.UNPACK_FLIP_Y_WEBGL, invertY ? 0 : 1); // Video are upside down by default
 
             try {
@@ -1824,7 +1858,7 @@
                     this._gl.generateMipmap(this._gl.TEXTURE_2D);
                 }
 
-                this._gl.bindTexture(this._gl.TEXTURE_2D, null);
+                this._bindTextureDirectly(this._gl.TEXTURE_2D, null);
                 this.resetTextureCache();
                 texture.isReady = true;
 
@@ -1858,7 +1892,7 @@
             var gl = this._gl;
 
             var texture = gl.createTexture();
-            gl.bindTexture(gl.TEXTURE_2D, texture);
+            this._bindTextureDirectly(gl.TEXTURE_2D, texture);
 
             var width = size.width || size;
             var height = size.height || size;
@@ -1886,7 +1920,7 @@
             }
             // Create the framebuffer
             var framebuffer = gl.createFramebuffer();
-            gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
+            this.bindUnboundFramebuffer(framebuffer);
             if (generateDepthBuffer) {
                 gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthBuffer);
             }
@@ -1896,9 +1930,9 @@
             }
 
             // Unbind
-            gl.bindTexture(gl.TEXTURE_2D, null);
+            this._bindTextureDirectly(gl.TEXTURE_2D, null);
             gl.bindRenderbuffer(gl.RENDERBUFFER, null);
-            gl.bindFramebuffer(gl.FRAMEBUFFER, null);
+            this.bindUnboundFramebuffer(null);
 
             texture._framebuffer = framebuffer;
             if (generateDepthBuffer) {
@@ -1941,7 +1975,7 @@
 
             var filters = getSamplingParameters(samplingMode, generateMipMaps, gl);
 
-            gl.bindTexture(gl.TEXTURE_CUBE_MAP, texture);
+            this._bindTextureDirectly(gl.TEXTURE_CUBE_MAP, texture);
 
             for (var face = 0; face < 6; face++) {
                 gl.texImage2D((gl.TEXTURE_CUBE_MAP_POSITIVE_X + face), 0, gl.RGBA, size, size, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
@@ -1959,19 +1993,19 @@
 
             // Create the framebuffer
             var framebuffer = gl.createFramebuffer();
-            gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
+            this.bindUnboundFramebuffer(framebuffer);
             gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthBuffer);
 
             // Mipmaps
             if (texture.generateMipMaps) {
-                gl.bindTexture(gl.TEXTURE_CUBE_MAP, texture);
+                this._bindTextureDirectly(gl.TEXTURE_CUBE_MAP, texture);
                 gl.generateMipmap(gl.TEXTURE_CUBE_MAP);
             }
 
             // Unbind
-            gl.bindTexture(gl.TEXTURE_CUBE_MAP, null);
+            this._bindTextureDirectly(gl.TEXTURE_CUBE_MAP, null);
             gl.bindRenderbuffer(gl.RENDERBUFFER, null);
-            gl.bindFramebuffer(gl.FRAMEBUFFER, null);
+            this.bindUnboundFramebuffer(null);
 
             texture._framebuffer = framebuffer;
             texture._depthBuffer = depthBuffer;
@@ -2002,7 +2036,7 @@
 
                     var loadMipmap = (info.isRGB || info.isLuminance || info.mipmapCount > 1) && !noMipmap;
 
-                    gl.bindTexture(gl.TEXTURE_CUBE_MAP, texture);
+                    this._bindTextureDirectly(gl.TEXTURE_CUBE_MAP, texture);
                     gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1);
 
                     Internals.DDSTools.UploadDDSLevels(this._gl, this.getCaps().s3tc, data, info, loadMipmap, 6);
@@ -2016,7 +2050,7 @@
                     gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
                     gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
 
-                    gl.bindTexture(gl.TEXTURE_CUBE_MAP, null);
+                    this._bindTextureDirectly(gl.TEXTURE_CUBE_MAP, null);
 
                     this.resetTextureCache();
 
@@ -2038,7 +2072,7 @@
                         gl.TEXTURE_CUBE_MAP_NEGATIVE_X, gl.TEXTURE_CUBE_MAP_NEGATIVE_Y, gl.TEXTURE_CUBE_MAP_NEGATIVE_Z
                     ];
 
-                    gl.bindTexture(gl.TEXTURE_CUBE_MAP, texture);
+                    this._bindTextureDirectly(gl.TEXTURE_CUBE_MAP, texture);
                     gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 0);
 
                     for (var index = 0; index < faces.length; index++) {
@@ -2055,7 +2089,7 @@
                     gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
                     gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
 
-                    gl.bindTexture(gl.TEXTURE_CUBE_MAP, null);
+                    this._bindTextureDirectly(gl.TEXTURE_CUBE_MAP, null);
 
                     this.resetTextureCache();
 
@@ -2116,7 +2150,7 @@
                 height = texture._height;
                 isPot = (Tools.IsExponentOfTwo(width) && Tools.IsExponentOfTwo(height));
 
-                gl.bindTexture(gl.TEXTURE_CUBE_MAP, texture);
+                this._bindTextureDirectly(gl.TEXTURE_CUBE_MAP, texture);
                 gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 0);
 
                 if (!noMipmap && isPot) {
@@ -2205,7 +2239,7 @@
 
                 gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
                 gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
-                gl.bindTexture(gl.TEXTURE_CUBE_MAP, null);
+                this._bindTextureDirectly(gl.TEXTURE_CUBE_MAP, null);
 
                 texture.isReady = true;
 
@@ -2242,8 +2276,16 @@
             }
         }
 
+        private setProgram(program: WebGLProgram): void {
+            if (this._currentProgram !== program) {
+                this._gl.useProgram(program);
+                this._currentProgram = program;
+            }
+        }
+
         public bindSamplers(effect: Effect): void {
-            this._gl.useProgram(effect.getProgram());
+            this.setProgram(effect.getProgram());
+
             var samplers = effect.getSamplers();
             for (var index = 0; index < samplers.length; index++) {
                 var uniform = effect.getUniform(samplers[index]);
@@ -2252,16 +2294,28 @@
             this._currentEffect = null;
         }
 
+        private activateTexture(texture: number): void {
+            if (this._activeTexture !== texture) {
+                this._gl.activeTexture(texture);
+                this._activeTexture = texture;
+            }
+        }
+
+        public _bindTextureDirectly(target: number, texture: WebGLTexture): void
+        {
+            if (this._activeTexturesCache[this._activeTexture] !== texture) {
+                this._gl.bindTexture(target, texture);
+                this._activeTexturesCache[this._activeTexture] = texture;
+            }
+        }
 
         public _bindTexture(channel: number, texture: WebGLTexture): void {
             if (channel < 0) {
                 return;
             }
 
-            this._gl.activeTexture(this._gl["TEXTURE" + channel]);
-            this._gl.bindTexture(this._gl.TEXTURE_2D, texture);
-
-            this._activeTexturesCache[channel] = null;
+            this.activateTexture(this._gl["TEXTURE" + channel]);
+            this._bindTextureDirectly(this._gl.TEXTURE_2D, texture);
         }
 
         public setTextureFromPostProcess(channel: number, postProcess: PostProcess): void {
@@ -2270,10 +2324,9 @@
 
         public unbindAllTextures(): void {
             for (var channel = 0; channel < this._caps.maxTexturesImageUnits; channel++) {
-                this._gl.activeTexture(this._gl["TEXTURE" + channel]);
-                this._gl.bindTexture(this._gl.TEXTURE_2D, null);
-                this._gl.bindTexture(this._gl.TEXTURE_CUBE_MAP, null);
-                this._activeTexturesCache[channel] = null;
+                this.activateTexture(this._gl["TEXTURE" + channel]);
+                this._bindTextureDirectly(this._gl.TEXTURE_2D, null);
+                this._bindTextureDirectly(this._gl.TEXTURE_CUBE_MAP, null);
             }
         }
 
@@ -2284,10 +2337,9 @@
             // Not ready?
             if (!texture || !texture.isReady()) {
                 if (this._activeTexturesCache[channel] != null) {
-                    this._gl.activeTexture(this._gl["TEXTURE" + channel]);
-                    this._gl.bindTexture(this._gl.TEXTURE_2D, null);
-                    this._gl.bindTexture(this._gl.TEXTURE_CUBE_MAP, null);
-                    this._activeTexturesCache[channel] = null;
+                    this.activateTexture(this._gl["TEXTURE" + channel]);
+                    this._bindTextureDirectly(this._gl.TEXTURE_2D, null);
+                    this._bindTextureDirectly(this._gl.TEXTURE_CUBE_MAP, null);
                 }
                 return;
             }
@@ -2295,7 +2347,7 @@
             // Video
             var alreadyActivated = false;
             if (texture instanceof VideoTexture) {
-                this._gl.activeTexture(this._gl["TEXTURE" + channel]);
+                this.activateTexture(this._gl["TEXTURE" + channel]);
                 alreadyActivated = true;
                 texture.update();
             } else if (texture.delayLoadState === Engine.DELAYLOADSTATE_NOTLOADED) { // Delay loading
@@ -2303,19 +2355,18 @@
                 return;
             }
 
-            if (this._activeTexturesCache[channel] === texture) {
+            var internalTexture = texture.getInternalTexture();
+
+            if (this._activeTexturesCache[channel] === internalTexture) {
                 return;
             }
-            this._activeTexturesCache[channel] = texture;
-
-            var internalTexture = texture.getInternalTexture();
 
             if (!alreadyActivated) {
-                this._gl.activeTexture(this._gl["TEXTURE" + channel]);
+                this.activateTexture(this._gl["TEXTURE" + channel]);
             }
 
             if (internalTexture.isCube) {
-                this._gl.bindTexture(this._gl.TEXTURE_CUBE_MAP, internalTexture);
+                this._bindTextureDirectly(this._gl.TEXTURE_CUBE_MAP, internalTexture);
 
                 if (internalTexture._cachedCoordinatesMode !== texture.coordinatesMode) {
                     internalTexture._cachedCoordinatesMode = texture.coordinatesMode;
@@ -2327,7 +2378,7 @@
 
                 this._setAnisotropicLevel(this._gl.TEXTURE_CUBE_MAP, texture);
             } else {
-                this._gl.bindTexture(this._gl.TEXTURE_2D, internalTexture);
+                this._bindTextureDirectly(this._gl.TEXTURE_2D, internalTexture);
 
                 if (internalTexture._cachedWrapU !== texture.wrapU) {
                     internalTexture._cachedWrapU = texture.wrapU;