瀏覽代碼

Merge pull request #3451 from BabylonJS/master

Nightly
David Catuhe 7 年之前
父節點
當前提交
e6664b632d
共有 42 個文件被更改,包括 21741 次插入21026 次删除
  1. 6 1
      .travis.yml
  2. 7617 7612
      Playground/babylon.d.txt
  3. 9 1
      Tools/DevLoader/BabylonLoader.js
  4. 29 11
      Tools/Gulp/gulpfile.js
  5. 12 1
      Tools/Gulp/package.json
  6. 2738 2731
      dist/preview release/babylon.d.ts
  7. 47 46
      dist/preview release/babylon.js
  8. 146 17
      dist/preview release/babylon.max.js
  9. 48 47
      dist/preview release/babylon.worker.js
  10. 10368 10319
      dist/preview release/customConfigurations/minimalGLTFViewer/babylon.d.ts
  11. 49 49
      dist/preview release/customConfigurations/minimalGLTFViewer/babylon.js
  12. 146 17
      dist/preview release/customConfigurations/minimalGLTFViewer/babylon.max.js
  13. 3 3
      dist/preview release/gui/babylon.gui.min.js
  14. 4 4
      dist/preview release/inspector/babylon.inspector.bundle.js
  15. 3 3
      dist/preview release/inspector/babylon.inspector.min.js
  16. 2 2
      dist/preview release/loaders/babylon.glTF1FileLoader.min.js
  17. 2 2
      dist/preview release/loaders/babylon.glTF2FileLoader.min.js
  18. 3 3
      dist/preview release/loaders/babylon.glTFFileLoader.min.js
  19. 1 1
      dist/preview release/loaders/babylon.objFileLoader.min.js
  20. 3 3
      dist/preview release/loaders/babylonjs.loaders.min.js
  21. 1 1
      dist/preview release/materialsLibrary/babylon.customMaterial.min.js
  22. 1 1
      dist/preview release/materialsLibrary/babylon.shadowOnlyMaterial.min.js
  23. 1 1
      dist/preview release/materialsLibrary/babylon.waterMaterial.min.js
  24. 3 3
      dist/preview release/materialsLibrary/babylonjs.materials.min.js
  25. 1 1
      dist/preview release/postProcessesLibrary/babylon.asciiArtPostProcess.min.js
  26. 1 1
      dist/preview release/postProcessesLibrary/babylon.digitalRainPostProcess.min.js
  27. 1 1
      dist/preview release/postProcessesLibrary/babylonjs.postProcess.min.js
  28. 1 1
      dist/preview release/serializers/babylon.glTF2Serializer.min.js
  29. 1 1
      dist/preview release/serializers/babylonjs.serializers.min.js
  30. 57 56
      dist/preview release/viewer/babylon.viewer.js
  31. 16 8
      src/Cameras/VR/babylon.webVRCamera.ts
  32. 144 7
      src/Engine/babylon.engine.ts
  33. 2 1
      src/Materials/Textures/babylon.internalTexture.ts
  34. 11 7
      src/Materials/Textures/babylon.multiRenderTarget.ts
  35. 12 4
      src/Materials/Textures/babylon.renderTargetTexture.ts
  36. 13 5
      src/Rendering/babylon.geometryBufferRenderer.ts
  37. 11 8
      tests/validation/config.json
  38. 39 1
      tests/validation/index.html
  39. 57 0
      tests/validation/integration.js
  40. 43 0
      tests/validation/karma.conf.js
  41. 74 0
      tests/validation/karma.conf.saucelabs.js
  42. 15 45
      tests/validation/validation.js

+ 6 - 1
.travis.yml

@@ -1,8 +1,13 @@
 language: node_js
 node_js:
   - "6"
+addons:
+  sauce_connect:
+    username: "vandenberghe.sebastien@gmail.com"
+  jwt:
+    secure: kbaJ3o6cVqpB8Gzfd0yDSgGwH+SQXukevtpjMQ8Gip+N6FOumQxihPDqsqYKxc8svvR024nDmkIhbjPjz/YguvC3WUgYW4xk3Za7P7C9cpj2fYdrFuO7pD4sd/fNdUCqvKQ8jcxlIq4eEdBuoBTOHsP9J5KH7Z1M7e58atkx0o8=
 before_script:
   - npm install -g gulp
   - cd ./Tools/Gulp
   - npm install
-script: gulp typescript
+script: gulp

File diff suppressed because it is too large
+ 7617 - 7612
Playground/babylon.d.txt


+ 9 - 1
Tools/DevLoader/BabylonLoader.js

@@ -39,7 +39,7 @@ var BABYLONDEVTOOLS;
             dependencies = [];
             callback = null;
             min = (document.location.href.toLowerCase().indexOf('dist=min') > 0);
-            useDist = (min || document.location.href.toLowerCase().indexOf('dist=true') > 0);            
+            useDist = (min || useDist || document.location.href.toLowerCase().indexOf('dist=true') > 0);            
             babylonJSPath = '';
         }
 
@@ -91,6 +91,11 @@ var BABYLONDEVTOOLS;
             return this;
         }
 
+        Loader.prototype.useDist = function() {
+            useDist = true;
+            return this;
+        }
+
         Loader.prototype.dequeue = function () {
             if (queue.length == 0) {
                 console.log('Scripts loaded');
@@ -236,6 +241,9 @@ var BABYLONDEVTOOLS;
             // Modules
             if (loadModules) {
                 for (var i = 0; i< settings.modules.length; i++) {
+                    if (settings.modules[i] === "viewer") {
+                        continue;
+                    }
                     this.loadModule(settings[settings.modules[i]]);
                 }
             }

+ 29 - 11
Tools/Gulp/gulpfile.js

@@ -25,6 +25,8 @@ var config = require("./config.json");
 
 var del = require("del");
 
+var karmaServer = require('karma').Server;
+
 var debug = require("gulp-debug");
 var includeShadersStream;
 var shadersStream;
@@ -226,14 +228,7 @@ gulp.task("build", ["shaders"], function () {
 /*
 * Compiles all typescript files and creating a js and a declaration file.
 */
-var alreadyCompiled = false;
-var forceCompile = false;
 gulp.task("typescript-compile", function () {
-    if (!forceCompile && alreadyCompiled) {
-        return;
-    }
-    alreadyCompiled = true;
-
     var tsResult = gulp.src(config.typescript)
         .pipe(sourcemaps.init())
         .pipe(tsProject());
@@ -425,7 +420,7 @@ var buildExternalLibrary = function (library, settings, watch) {
  * The default task, concat and min the main BJS files.
  */
 gulp.task("default", function (cb) {
-    runSequence("typescript-all", "intellisense", cb);
+    runSequence("typescript-all", "intellisense", "tests-saucelabs", cb);
 });
 
 gulp.task("mainBuild", function (cb) {
@@ -495,11 +490,8 @@ gulp.task("srcTscWatch", function() {
  * Watch ts files and fire repective tasks.
  */
 gulp.task("watch", ["srcTscWatch"], function () {
-    forceCompile = true;
     var interval = 1000;
 
-    // Keep during Prod Tests of srcTscWatch.
-    // var tasks = [gulp.watch(config.typescript, { interval: interval }, ["typescript-compile"])];
     var tasks = [];
 
     config.modules.map(function (module) {
@@ -567,6 +559,32 @@ gulp.task("webserver", function () {
 gulp.task("run", ["watch", "webserver"], function () {
 });
 
+
+gulp.task("tests-integration", function (done) {
+    var kamaServerOptions = {
+        configFile: __dirname + "/../../tests/validation/karma.conf.js",
+        singleRun: false
+    };
+
+    var server = new karmaServer(kamaServerOptions, done);
+    server.start();
+});
+
+gulp.task("tests-saucelabs", function (done) {
+    if (!process.env.TRAVIS) {
+        done();
+        return;
+    }
+
+    var kamaServerOptions = {
+        configFile: __dirname + "/../../tests/validation/karma.conf.saucelabs.js",
+        singleRun: true
+    };
+
+    var server = new karmaServer(kamaServerOptions, done);
+    server.start();
+});
+
 gulp.task("clean-JS-MAP", function () {
     return del([
         "../../src/**/*.js.map", "../../src/**/*.js"

+ 12 - 1
Tools/Gulp/package.json

@@ -43,7 +43,18 @@
         "through2": "~0.6.5",
         "ts-loader": "^2.3.7",
         "typescript": "^2.6.2",
-        "webpack-stream": "^4.0.0"
+        "webpack-stream": "^4.0.0",
+
+        "karma": "^2.0.0",
+        "karma-chrome-launcher": "^2.2.0",
+        "karma-sauce-launcher": "^1.2.0",
+
+        "mocha": "^4.0.1",
+        "chai": "^4.1.2",
+        "sinon": "^4.1.3",
+        "karma-mocha": "^1.3.0",
+        "karma-chai":"^0.1.0",
+        "karma-sinon": "^1.0.5"
     },
     "scripts": {
         "install": "npm --prefix ../../Playground/ install ../../Playground/ && gulp typescript-compile && gulp typescript-libraries && gulp deployLocalDev"

File diff suppressed because it is too large
+ 2738 - 2731
dist/preview release/babylon.d.ts


File diff suppressed because it is too large
+ 47 - 46
dist/preview release/babylon.js


+ 146 - 17
dist/preview release/babylon.max.js

@@ -8989,7 +8989,7 @@ var BABYLON;
             this.setDepthFunctionToLessOrEqual();
             this.setDepthWrite(true);
             // Texture maps
-            for (var slot = 0; slot < this._caps.maxTexturesImageUnits; slot++) {
+            for (var slot = 0; slot < this._caps.maxCombinedTexturesImageUnits; slot++) {
                 this._nextFreeTextureSlots.push(slot);
             }
         };
@@ -9029,7 +9029,7 @@ var BABYLON;
                 this._boundTexturesCache[key] = null;
             }
             this._nextFreeTextureSlots = [];
-            for (var slot = 0; slot < this._caps.maxTexturesImageUnits; slot++) {
+            for (var slot = 0; slot < this._caps.maxCombinedTexturesImageUnits; slot++) {
                 this._nextFreeTextureSlots.push(slot);
             }
             this._activeChannel = -1;
@@ -9511,6 +9511,51 @@ var BABYLON;
             }
             this.bindUnboundFramebuffer(null);
         };
+        Engine.prototype.unBindMultiColorAttachmentFramebuffer = function (textures, disableGenerateMipMaps, onBeforeUnbind) {
+            if (disableGenerateMipMaps === void 0) { disableGenerateMipMaps = false; }
+            this._currentRenderTarget = null;
+            // If MSAA, we need to bitblt back to main texture
+            var gl = this._gl;
+            if (textures[0]._MSAAFramebuffer) {
+                gl.bindFramebuffer(gl.READ_FRAMEBUFFER, textures[0]._MSAAFramebuffer);
+                gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, textures[0]._framebuffer);
+                var attachments = textures[0]._attachments;
+                if (!attachments) {
+                    attachments = new Array(textures.length);
+                    textures[0]._attachments = attachments;
+                }
+                for (var i = 0; i < textures.length; i++) {
+                    var texture = textures[i];
+                    for (var j = 0; j < attachments.length; j++) {
+                        attachments[j] = gl.NONE;
+                    }
+                    attachments[i] = gl[this.webGLVersion > 1 ? "COLOR_ATTACHMENT" + i : "COLOR_ATTACHMENT" + i + "_WEBGL"];
+                    gl.readBuffer(attachments[i]);
+                    gl.drawBuffers(attachments);
+                    gl.blitFramebuffer(0, 0, texture.width, texture.height, 0, 0, texture.width, texture.height, gl.COLOR_BUFFER_BIT, gl.NEAREST);
+                }
+                for (var i = 0; i < attachments.length; i++) {
+                    attachments[i] = gl[this.webGLVersion > 1 ? "COLOR_ATTACHMENT" + i : "COLOR_ATTACHMENT" + i + "_WEBGL"];
+                }
+                gl.drawBuffers(attachments);
+            }
+            for (var i = 0; i < textures.length; i++) {
+                var texture = textures[i];
+                if (texture.generateMipMaps && !disableGenerateMipMaps && !texture.isCube) {
+                    this._bindTextureDirectly(gl.TEXTURE_2D, texture);
+                    gl.generateMipmap(gl.TEXTURE_2D);
+                    this._bindTextureDirectly(gl.TEXTURE_2D, null);
+                }
+            }
+            if (onBeforeUnbind) {
+                if (textures[0]._MSAAFramebuffer) {
+                    // Bind the correct framebuffer
+                    this.bindUnboundFramebuffer(textures[0]._framebuffer);
+                }
+                onBeforeUnbind();
+            }
+            this.bindUnboundFramebuffer(null);
+        };
         Engine.prototype.generateMipMapsForCubemap = function (texture) {
             if (texture.generateMipMaps) {
                 var gl = this._gl;
@@ -10399,12 +10444,11 @@ var BABYLON;
                 this.setDepthFunctionToLessOrEqual();
                 this._alphaState.reset();
             }
-            this._cachedVertexBuffers = null;
+            this._resetVertexBufferBinding();
             this._cachedIndexBuffer = null;
             this._cachedEffectForVertexBuffers = null;
             this._unbindVertexArrayObject();
             this.bindIndexBuffer(null);
-            this.bindArrayBuffer(null);
         };
         /**
          * Set the compressed texture format to use, based on the formats you have, and the formats
@@ -10988,6 +11032,7 @@ var BABYLON;
                 texture.type = type;
                 texture._generateDepthBuffer = generateDepthBuffer;
                 texture._generateStencilBuffer = generateStencilBuffer;
+                texture._attachments = attachments;
                 this._internalTexturesCache.push(texture);
             }
             if (generateDepthTexture && this._caps.depthTextureExtension) {
@@ -11062,12 +11107,15 @@ var BABYLON;
             // Dispose previous render buffers
             if (texture._depthStencilBuffer) {
                 gl.deleteRenderbuffer(texture._depthStencilBuffer);
+                texture._depthStencilBuffer = null;
             }
             if (texture._MSAAFramebuffer) {
                 gl.deleteFramebuffer(texture._MSAAFramebuffer);
+                texture._MSAAFramebuffer = null;
             }
             if (texture._MSAARenderBuffer) {
                 gl.deleteRenderbuffer(texture._MSAARenderBuffer);
+                texture._MSAARenderBuffer = null;
             }
             if (samples > 1) {
                 var framebuffer = gl.createFramebuffer();
@@ -11094,6 +11142,63 @@ var BABYLON;
             this.bindUnboundFramebuffer(null);
             return samples;
         };
+        Engine.prototype.updateMultipleRenderTargetTextureSampleCount = function (textures, samples) {
+            if (this.webGLVersion < 2 || !textures || textures.length == 0) {
+                return 1;
+            }
+            if (textures[0].samples === samples) {
+                return samples;
+            }
+            var gl = this._gl;
+            samples = Math.min(samples, gl.getParameter(gl.MAX_SAMPLES));
+            // Dispose previous render buffers
+            if (textures[0]._depthStencilBuffer) {
+                gl.deleteRenderbuffer(textures[0]._depthStencilBuffer);
+                textures[0]._depthStencilBuffer = null;
+            }
+            if (textures[0]._MSAAFramebuffer) {
+                gl.deleteFramebuffer(textures[0]._MSAAFramebuffer);
+                textures[0]._MSAAFramebuffer = null;
+            }
+            for (var i = 0; i < textures.length; i++) {
+                if (textures[i]._MSAARenderBuffer) {
+                    gl.deleteRenderbuffer(textures[i]._MSAARenderBuffer);
+                    textures[i]._MSAARenderBuffer = null;
+                }
+            }
+            if (samples > 1) {
+                var framebuffer = gl.createFramebuffer();
+                if (!framebuffer) {
+                    throw new Error("Unable to create multi sampled framebuffer");
+                }
+                this.bindUnboundFramebuffer(framebuffer);
+                var depthStencilBuffer = this._setupFramebufferDepthAttachments(textures[0]._generateStencilBuffer, textures[0]._generateDepthBuffer, textures[0].width, textures[0].height, samples);
+                var attachments = [];
+                for (var i = 0; i < textures.length; i++) {
+                    var texture = textures[i];
+                    var attachment = gl[this.webGLVersion > 1 ? "COLOR_ATTACHMENT" + i : "COLOR_ATTACHMENT" + i + "_WEBGL"];
+                    var colorRenderbuffer = gl.createRenderbuffer();
+                    if (!colorRenderbuffer) {
+                        throw new Error("Unable to create multi sampled framebuffer");
+                    }
+                    gl.bindRenderbuffer(gl.RENDERBUFFER, colorRenderbuffer);
+                    gl.renderbufferStorageMultisample(gl.RENDERBUFFER, samples, gl.RGBA8, texture.width, texture.height);
+                    gl.framebufferRenderbuffer(gl.FRAMEBUFFER, attachment, gl.RENDERBUFFER, colorRenderbuffer);
+                    texture._MSAAFramebuffer = framebuffer;
+                    texture._MSAARenderBuffer = colorRenderbuffer;
+                    texture.samples = samples;
+                    texture._depthStencilBuffer = depthStencilBuffer;
+                    gl.bindRenderbuffer(gl.RENDERBUFFER, null);
+                    attachments.push(attachment);
+                }
+                gl.drawBuffers(attachments);
+            }
+            else {
+                this.bindUnboundFramebuffer(textures[0]._framebuffer);
+            }
+            this.bindUnboundFramebuffer(null);
+            return samples;
+        };
         Engine.prototype._uploadDataToTexture = function (target, lod, internalFormat, width, height, format, type, data) {
             this._gl.texImage2D(target, lod, internalFormat, width, height, 0, format, type, data);
         };
@@ -11777,7 +11882,7 @@ var BABYLON;
             this._bindTexture(channel, postProcess ? postProcess._textures.data[postProcess._currentRenderTextureInd] : null);
         };
         Engine.prototype.unbindAllTextures = function () {
-            for (var channel = 0; channel < this._caps.maxTexturesImageUnits; channel++) {
+            for (var channel = 0; channel < this._caps.maxCombinedTexturesImageUnits; channel++) {
                 this._activateTextureChannel(channel);
                 this._bindTextureDirectly(this._gl.TEXTURE_2D, null);
                 this._bindTextureDirectly(this._gl.TEXTURE_CUBE_MAP, null);
@@ -51742,8 +51847,16 @@ var BABYLON;
             // Ensure we don't exceed the render dimension (while staying POT)
             return Math.min(BABYLON.Tools.FloorPOT(renderDimension), curved);
         };
-        RenderTargetTexture.prototype.renderToTarget = function (faceIndex, currentRenderList, currentRenderListLength, useCameraPostProcess, dumpForDebug) {
+        RenderTargetTexture.prototype.unbindFrameBuffer = function (engine, faceIndex) {
             var _this = this;
+            if (!this._texture) {
+                return;
+            }
+            engine.unBindFramebuffer(this._texture, this.isCube, function () {
+                _this.onAfterRenderObservable.notifyObservers(faceIndex);
+            });
+        };
+        RenderTargetTexture.prototype.renderToTarget = function (faceIndex, currentRenderList, currentRenderListLength, useCameraPostProcess, dumpForDebug) {
             var scene = this.getScene();
             if (!scene) {
                 return;
@@ -51794,9 +51907,7 @@ var BABYLON;
                         engine.generateMipMapsForCubemap(this._texture);
                     }
                 }
-                engine.unBindFramebuffer(this._texture, this.isCube, function () {
-                    _this.onAfterRenderObservable.notifyObservers(faceIndex);
-                });
+                this.unbindFrameBuffer(engine, faceIndex);
             }
             else {
                 this.onAfterRenderObservable.notifyObservers(faceIndex);
@@ -51929,13 +52040,13 @@ var BABYLON;
             var types = [];
             var samplingModes = [];
             for (var i = 0; i < count; i++) {
-                if (options.types && options.types[i]) {
+                if (options.types && options.types[i] !== undefined) {
                     types.push(options.types[i]);
                 }
                 else {
-                    types.push(BABYLON.Engine.TEXTURETYPE_FLOAT);
+                    types.push(BABYLON.Engine.TEXTURETYPE_UNSIGNED_INT);
                 }
-                if (options.samplingModes && options.samplingModes[i]) {
+                if (options.samplingModes && options.samplingModes[i] !== undefined) {
                     samplingModes.push(options.samplingModes[i]);
                 }
                 else {
@@ -52032,9 +52143,7 @@ var BABYLON;
                 if (this._samples === value) {
                     return;
                 }
-                for (var i = 0; i < this._internalTextures.length; i++) {
-                    this._samples = this._engine.updateRenderTargetTextureSampleCount(this._internalTextures[i], value);
-                }
+                this._samples = this._engine.updateMultipleRenderTargetTextureSampleCount(this._internalTextures, value);
             },
             enumerable: true,
             configurable: true
@@ -52044,6 +52153,12 @@ var BABYLON;
             this._internalTextures = this._engine.createMultipleRenderTarget(size, this._multiRenderTargetOptions);
             this._createInternalTextures();
         };
+        MultiRenderTarget.prototype.unbindFrameBuffer = function (engine, faceIndex) {
+            var _this = this;
+            engine.unBindMultiColorAttachmentFramebuffer(this._internalTextures, this.isCube, function () {
+                _this.onAfterRenderObservable.notifyObservers(faceIndex);
+            });
+        };
         MultiRenderTarget.prototype.dispose = function () {
             this.releaseInternalTextures();
             _super.prototype.dispose.call(this);
@@ -61537,6 +61652,16 @@ var BABYLON;
         GeometryBufferRenderer.prototype.getGBuffer = function () {
             return this._multiRenderTarget;
         };
+        Object.defineProperty(GeometryBufferRenderer.prototype, "samples", {
+            get: function () {
+                return this._multiRenderTarget.samples;
+            },
+            set: function (value) {
+                this._multiRenderTarget.samples = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
         // Methods
         GeometryBufferRenderer.prototype.dispose = function () {
             this.getGBuffer().dispose();
@@ -72738,7 +72863,11 @@ var BABYLON;
             var _this = this;
             if (!this.rotationQuaternion.equals(this._cache.rotationQuaternion) || !this.position.equals(this._cache.position)) {
                 // Update to ensure devicePosition is up to date with most recent _deviceRoomPosition
-                this.update();
+                if (!this.updateCacheCalled) {
+                    // make sure it is only called once per loop. this.update() might cause an infinite loop.
+                    this.updateCacheCalled = true;
+                    this.update();
+                }
                 // Set working vector to the device position in room space rotated by the new rotation
                 this.rotationQuaternion.toRotationMatrix(this._workingMatrix);
                 BABYLON.Vector3.TransformCoordinatesToRef(this._deviceRoomPosition, this._workingMatrix, this._workingVector);
@@ -72757,11 +72886,11 @@ var BABYLON;
                     controller._deviceToWorld = _this._deviceToWorld;
                     controller.update();
                 });
-                this.update();
             }
             if (!ignoreParentClass) {
                 _super.prototype._updateCache.call(this);
             }
+            this.updateCacheCalled = false;
         };
         WebVRFreeCamera.prototype.update = function () {
             // Get current device position in babylon world

File diff suppressed because it is too large
+ 48 - 47
dist/preview release/babylon.worker.js


File diff suppressed because it is too large
+ 10368 - 10319
dist/preview release/customConfigurations/minimalGLTFViewer/babylon.d.ts


File diff suppressed because it is too large
+ 49 - 49
dist/preview release/customConfigurations/minimalGLTFViewer/babylon.js


+ 146 - 17
dist/preview release/customConfigurations/minimalGLTFViewer/babylon.max.js

@@ -8989,7 +8989,7 @@ var BABYLON;
             this.setDepthFunctionToLessOrEqual();
             this.setDepthWrite(true);
             // Texture maps
-            for (var slot = 0; slot < this._caps.maxTexturesImageUnits; slot++) {
+            for (var slot = 0; slot < this._caps.maxCombinedTexturesImageUnits; slot++) {
                 this._nextFreeTextureSlots.push(slot);
             }
         };
@@ -9029,7 +9029,7 @@ var BABYLON;
                 this._boundTexturesCache[key] = null;
             }
             this._nextFreeTextureSlots = [];
-            for (var slot = 0; slot < this._caps.maxTexturesImageUnits; slot++) {
+            for (var slot = 0; slot < this._caps.maxCombinedTexturesImageUnits; slot++) {
                 this._nextFreeTextureSlots.push(slot);
             }
             this._activeChannel = -1;
@@ -9511,6 +9511,51 @@ var BABYLON;
             }
             this.bindUnboundFramebuffer(null);
         };
+        Engine.prototype.unBindMultiColorAttachmentFramebuffer = function (textures, disableGenerateMipMaps, onBeforeUnbind) {
+            if (disableGenerateMipMaps === void 0) { disableGenerateMipMaps = false; }
+            this._currentRenderTarget = null;
+            // If MSAA, we need to bitblt back to main texture
+            var gl = this._gl;
+            if (textures[0]._MSAAFramebuffer) {
+                gl.bindFramebuffer(gl.READ_FRAMEBUFFER, textures[0]._MSAAFramebuffer);
+                gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, textures[0]._framebuffer);
+                var attachments = textures[0]._attachments;
+                if (!attachments) {
+                    attachments = new Array(textures.length);
+                    textures[0]._attachments = attachments;
+                }
+                for (var i = 0; i < textures.length; i++) {
+                    var texture = textures[i];
+                    for (var j = 0; j < attachments.length; j++) {
+                        attachments[j] = gl.NONE;
+                    }
+                    attachments[i] = gl[this.webGLVersion > 1 ? "COLOR_ATTACHMENT" + i : "COLOR_ATTACHMENT" + i + "_WEBGL"];
+                    gl.readBuffer(attachments[i]);
+                    gl.drawBuffers(attachments);
+                    gl.blitFramebuffer(0, 0, texture.width, texture.height, 0, 0, texture.width, texture.height, gl.COLOR_BUFFER_BIT, gl.NEAREST);
+                }
+                for (var i = 0; i < attachments.length; i++) {
+                    attachments[i] = gl[this.webGLVersion > 1 ? "COLOR_ATTACHMENT" + i : "COLOR_ATTACHMENT" + i + "_WEBGL"];
+                }
+                gl.drawBuffers(attachments);
+            }
+            for (var i = 0; i < textures.length; i++) {
+                var texture = textures[i];
+                if (texture.generateMipMaps && !disableGenerateMipMaps && !texture.isCube) {
+                    this._bindTextureDirectly(gl.TEXTURE_2D, texture);
+                    gl.generateMipmap(gl.TEXTURE_2D);
+                    this._bindTextureDirectly(gl.TEXTURE_2D, null);
+                }
+            }
+            if (onBeforeUnbind) {
+                if (textures[0]._MSAAFramebuffer) {
+                    // Bind the correct framebuffer
+                    this.bindUnboundFramebuffer(textures[0]._framebuffer);
+                }
+                onBeforeUnbind();
+            }
+            this.bindUnboundFramebuffer(null);
+        };
         Engine.prototype.generateMipMapsForCubemap = function (texture) {
             if (texture.generateMipMaps) {
                 var gl = this._gl;
@@ -10399,12 +10444,11 @@ var BABYLON;
                 this.setDepthFunctionToLessOrEqual();
                 this._alphaState.reset();
             }
-            this._cachedVertexBuffers = null;
+            this._resetVertexBufferBinding();
             this._cachedIndexBuffer = null;
             this._cachedEffectForVertexBuffers = null;
             this._unbindVertexArrayObject();
             this.bindIndexBuffer(null);
-            this.bindArrayBuffer(null);
         };
         /**
          * Set the compressed texture format to use, based on the formats you have, and the formats
@@ -10988,6 +11032,7 @@ var BABYLON;
                 texture.type = type;
                 texture._generateDepthBuffer = generateDepthBuffer;
                 texture._generateStencilBuffer = generateStencilBuffer;
+                texture._attachments = attachments;
                 this._internalTexturesCache.push(texture);
             }
             if (generateDepthTexture && this._caps.depthTextureExtension) {
@@ -11062,12 +11107,15 @@ var BABYLON;
             // Dispose previous render buffers
             if (texture._depthStencilBuffer) {
                 gl.deleteRenderbuffer(texture._depthStencilBuffer);
+                texture._depthStencilBuffer = null;
             }
             if (texture._MSAAFramebuffer) {
                 gl.deleteFramebuffer(texture._MSAAFramebuffer);
+                texture._MSAAFramebuffer = null;
             }
             if (texture._MSAARenderBuffer) {
                 gl.deleteRenderbuffer(texture._MSAARenderBuffer);
+                texture._MSAARenderBuffer = null;
             }
             if (samples > 1) {
                 var framebuffer = gl.createFramebuffer();
@@ -11094,6 +11142,63 @@ var BABYLON;
             this.bindUnboundFramebuffer(null);
             return samples;
         };
+        Engine.prototype.updateMultipleRenderTargetTextureSampleCount = function (textures, samples) {
+            if (this.webGLVersion < 2 || !textures || textures.length == 0) {
+                return 1;
+            }
+            if (textures[0].samples === samples) {
+                return samples;
+            }
+            var gl = this._gl;
+            samples = Math.min(samples, gl.getParameter(gl.MAX_SAMPLES));
+            // Dispose previous render buffers
+            if (textures[0]._depthStencilBuffer) {
+                gl.deleteRenderbuffer(textures[0]._depthStencilBuffer);
+                textures[0]._depthStencilBuffer = null;
+            }
+            if (textures[0]._MSAAFramebuffer) {
+                gl.deleteFramebuffer(textures[0]._MSAAFramebuffer);
+                textures[0]._MSAAFramebuffer = null;
+            }
+            for (var i = 0; i < textures.length; i++) {
+                if (textures[i]._MSAARenderBuffer) {
+                    gl.deleteRenderbuffer(textures[i]._MSAARenderBuffer);
+                    textures[i]._MSAARenderBuffer = null;
+                }
+            }
+            if (samples > 1) {
+                var framebuffer = gl.createFramebuffer();
+                if (!framebuffer) {
+                    throw new Error("Unable to create multi sampled framebuffer");
+                }
+                this.bindUnboundFramebuffer(framebuffer);
+                var depthStencilBuffer = this._setupFramebufferDepthAttachments(textures[0]._generateStencilBuffer, textures[0]._generateDepthBuffer, textures[0].width, textures[0].height, samples);
+                var attachments = [];
+                for (var i = 0; i < textures.length; i++) {
+                    var texture = textures[i];
+                    var attachment = gl[this.webGLVersion > 1 ? "COLOR_ATTACHMENT" + i : "COLOR_ATTACHMENT" + i + "_WEBGL"];
+                    var colorRenderbuffer = gl.createRenderbuffer();
+                    if (!colorRenderbuffer) {
+                        throw new Error("Unable to create multi sampled framebuffer");
+                    }
+                    gl.bindRenderbuffer(gl.RENDERBUFFER, colorRenderbuffer);
+                    gl.renderbufferStorageMultisample(gl.RENDERBUFFER, samples, gl.RGBA8, texture.width, texture.height);
+                    gl.framebufferRenderbuffer(gl.FRAMEBUFFER, attachment, gl.RENDERBUFFER, colorRenderbuffer);
+                    texture._MSAAFramebuffer = framebuffer;
+                    texture._MSAARenderBuffer = colorRenderbuffer;
+                    texture.samples = samples;
+                    texture._depthStencilBuffer = depthStencilBuffer;
+                    gl.bindRenderbuffer(gl.RENDERBUFFER, null);
+                    attachments.push(attachment);
+                }
+                gl.drawBuffers(attachments);
+            }
+            else {
+                this.bindUnboundFramebuffer(textures[0]._framebuffer);
+            }
+            this.bindUnboundFramebuffer(null);
+            return samples;
+        };
         Engine.prototype._uploadDataToTexture = function (target, lod, internalFormat, width, height, format, type, data) {
             this._gl.texImage2D(target, lod, internalFormat, width, height, 0, format, type, data);
         };
@@ -11777,7 +11882,7 @@ var BABYLON;
             this._bindTexture(channel, postProcess ? postProcess._textures.data[postProcess._currentRenderTextureInd] : null);
         };
         Engine.prototype.unbindAllTextures = function () {
-            for (var channel = 0; channel < this._caps.maxTexturesImageUnits; channel++) {
+            for (var channel = 0; channel < this._caps.maxCombinedTexturesImageUnits; channel++) {
                 this._activateTextureChannel(channel);
                 this._bindTextureDirectly(this._gl.TEXTURE_2D, null);
                 this._bindTextureDirectly(this._gl.TEXTURE_CUBE_MAP, null);
@@ -51588,8 +51693,16 @@ var BABYLON;
             // Ensure we don't exceed the render dimension (while staying POT)
             return Math.min(BABYLON.Tools.FloorPOT(renderDimension), curved);
         };
-        RenderTargetTexture.prototype.renderToTarget = function (faceIndex, currentRenderList, currentRenderListLength, useCameraPostProcess, dumpForDebug) {
+        RenderTargetTexture.prototype.unbindFrameBuffer = function (engine, faceIndex) {
             var _this = this;
+            if (!this._texture) {
+                return;
+            }
+            engine.unBindFramebuffer(this._texture, this.isCube, function () {
+                _this.onAfterRenderObservable.notifyObservers(faceIndex);
+            });
+        };
+        RenderTargetTexture.prototype.renderToTarget = function (faceIndex, currentRenderList, currentRenderListLength, useCameraPostProcess, dumpForDebug) {
             var scene = this.getScene();
             if (!scene) {
                 return;
@@ -51640,9 +51753,7 @@ var BABYLON;
                         engine.generateMipMapsForCubemap(this._texture);
                     }
                 }
-                engine.unBindFramebuffer(this._texture, this.isCube, function () {
-                    _this.onAfterRenderObservable.notifyObservers(faceIndex);
-                });
+                this.unbindFrameBuffer(engine, faceIndex);
             }
             else {
                 this.onAfterRenderObservable.notifyObservers(faceIndex);
@@ -51775,13 +51886,13 @@ var BABYLON;
             var types = [];
             var samplingModes = [];
             for (var i = 0; i < count; i++) {
-                if (options.types && options.types[i]) {
+                if (options.types && options.types[i] !== undefined) {
                     types.push(options.types[i]);
                 }
                 else {
-                    types.push(BABYLON.Engine.TEXTURETYPE_FLOAT);
+                    types.push(BABYLON.Engine.TEXTURETYPE_UNSIGNED_INT);
                 }
-                if (options.samplingModes && options.samplingModes[i]) {
+                if (options.samplingModes && options.samplingModes[i] !== undefined) {
                     samplingModes.push(options.samplingModes[i]);
                 }
                 else {
@@ -51878,9 +51989,7 @@ var BABYLON;
                 if (this._samples === value) {
                     return;
                 }
-                for (var i = 0; i < this._internalTextures.length; i++) {
-                    this._samples = this._engine.updateRenderTargetTextureSampleCount(this._internalTextures[i], value);
-                }
+                this._samples = this._engine.updateMultipleRenderTargetTextureSampleCount(this._internalTextures, value);
             },
             enumerable: true,
             configurable: true
@@ -51890,6 +51999,12 @@ var BABYLON;
             this._internalTextures = this._engine.createMultipleRenderTarget(size, this._multiRenderTargetOptions);
             this._createInternalTextures();
         };
+        MultiRenderTarget.prototype.unbindFrameBuffer = function (engine, faceIndex) {
+            var _this = this;
+            engine.unBindMultiColorAttachmentFramebuffer(this._internalTextures, this.isCube, function () {
+                _this.onAfterRenderObservable.notifyObservers(faceIndex);
+            });
+        };
         MultiRenderTarget.prototype.dispose = function () {
             this.releaseInternalTextures();
             _super.prototype.dispose.call(this);
@@ -61383,6 +61498,16 @@ var BABYLON;
         GeometryBufferRenderer.prototype.getGBuffer = function () {
             return this._multiRenderTarget;
         };
+        Object.defineProperty(GeometryBufferRenderer.prototype, "samples", {
+            get: function () {
+                return this._multiRenderTarget.samples;
+            },
+            set: function (value) {
+                this._multiRenderTarget.samples = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
         // Methods
         GeometryBufferRenderer.prototype.dispose = function () {
             this.getGBuffer().dispose();
@@ -72584,7 +72709,11 @@ var BABYLON;
             var _this = this;
             if (!this.rotationQuaternion.equals(this._cache.rotationQuaternion) || !this.position.equals(this._cache.position)) {
                 // Update to ensure devicePosition is up to date with most recent _deviceRoomPosition
-                this.update();
+                if (!this.updateCacheCalled) {
+                    // make sure it is only called once per loop. this.update() might cause an infinite loop.
+                    this.updateCacheCalled = true;
+                    this.update();
+                }
                 // Set working vector to the device position in room space rotated by the new rotation
                 this.rotationQuaternion.toRotationMatrix(this._workingMatrix);
                 BABYLON.Vector3.TransformCoordinatesToRef(this._deviceRoomPosition, this._workingMatrix, this._workingVector);
@@ -72603,11 +72732,11 @@ var BABYLON;
                     controller._deviceToWorld = _this._deviceToWorld;
                     controller.update();
                 });
-                this.update();
             }
             if (!ignoreParentClass) {
                 _super.prototype._updateCache.call(this);
             }
+            this.updateCacheCalled = false;
         };
         WebVRFreeCamera.prototype.update = function () {
             // Get current device position in babylon world

File diff suppressed because it is too large
+ 3 - 3
dist/preview release/gui/babylon.gui.min.js


File diff suppressed because it is too large
+ 4 - 4
dist/preview release/inspector/babylon.inspector.bundle.js


File diff suppressed because it is too large
+ 3 - 3
dist/preview release/inspector/babylon.inspector.min.js


File diff suppressed because it is too large
+ 2 - 2
dist/preview release/loaders/babylon.glTF1FileLoader.min.js


File diff suppressed because it is too large
+ 2 - 2
dist/preview release/loaders/babylon.glTF2FileLoader.min.js


File diff suppressed because it is too large
+ 3 - 3
dist/preview release/loaders/babylon.glTFFileLoader.min.js


File diff suppressed because it is too large
+ 1 - 1
dist/preview release/loaders/babylon.objFileLoader.min.js


File diff suppressed because it is too large
+ 3 - 3
dist/preview release/loaders/babylonjs.loaders.min.js


File diff suppressed because it is too large
+ 1 - 1
dist/preview release/materialsLibrary/babylon.customMaterial.min.js


File diff suppressed because it is too large
+ 1 - 1
dist/preview release/materialsLibrary/babylon.shadowOnlyMaterial.min.js


File diff suppressed because it is too large
+ 1 - 1
dist/preview release/materialsLibrary/babylon.waterMaterial.min.js


File diff suppressed because it is too large
+ 3 - 3
dist/preview release/materialsLibrary/babylonjs.materials.min.js


File diff suppressed because it is too large
+ 1 - 1
dist/preview release/postProcessesLibrary/babylon.asciiArtPostProcess.min.js


File diff suppressed because it is too large
+ 1 - 1
dist/preview release/postProcessesLibrary/babylon.digitalRainPostProcess.min.js


File diff suppressed because it is too large
+ 1 - 1
dist/preview release/postProcessesLibrary/babylonjs.postProcess.min.js


File diff suppressed because it is too large
+ 1 - 1
dist/preview release/serializers/babylon.glTF2Serializer.min.js


File diff suppressed because it is too large
+ 1 - 1
dist/preview release/serializers/babylonjs.serializers.min.js


File diff suppressed because it is too large
+ 57 - 56
dist/preview release/viewer/babylon.viewer.js


+ 16 - 8
src/Cameras/VR/babylon.webVRCamera.ts

@@ -325,40 +325,48 @@ module BABYLON {
         private _workingVector = Vector3.Zero();
         private _oneVector = Vector3.One();
         private _workingMatrix = Matrix.Identity();
+
+        private updateCacheCalled: boolean;
+
         public _updateCache(ignoreParentClass?: boolean): void {
             if (!this.rotationQuaternion.equals(this._cache.rotationQuaternion) || !this.position.equals(this._cache.position)) {
                 // Update to ensure devicePosition is up to date with most recent _deviceRoomPosition
-                this.update();
+                if (!this.updateCacheCalled) {
+                    // make sure it is only called once per loop. this.update() might cause an infinite loop.
+                    this.updateCacheCalled = true;
+                    this.update();
+                }
 
                 // Set working vector to the device position in room space rotated by the new rotation
                 this.rotationQuaternion.toRotationMatrix(this._workingMatrix);
                 Vector3.TransformCoordinatesToRef(this._deviceRoomPosition, this._workingMatrix, this._workingVector);
 
                 // Subtract this vector from the current device position in world to get the translation for the device world matrix
-                this.devicePosition.subtractToRef(this._workingVector, this._workingVector)
+                this.devicePosition.subtractToRef(this._workingVector, this._workingVector);
                 Matrix.ComposeToRef(this._oneVector, this.rotationQuaternion, this._workingVector, this._deviceToWorld);
 
                 // Add translation from anchor position
-                this._deviceToWorld.getTranslationToRef(this._workingVector)
+                this._deviceToWorld.getTranslationToRef(this._workingVector);
                 this._workingVector.addInPlace(this.position);
-                this._workingVector.subtractInPlace(this._cache.position)
-                this._deviceToWorld.setTranslation(this._workingVector)
+                this._workingVector.subtractInPlace(this._cache.position);
+                this._deviceToWorld.setTranslation(this._workingVector);
 
                 // Set an inverted matrix to be used when updating the camera
-                this._deviceToWorld.invertToRef(this._worldToDevice)
+                this._deviceToWorld.invertToRef(this._worldToDevice);
 
                 // Update the gamepad to ensure the mesh is updated on the same frame as camera
                 this.controllers.forEach((controller) => {
                     controller._deviceToWorld = this._deviceToWorld;
                     controller.update();
-                })
-                this.update();
+                });
             }
 
             if (!ignoreParentClass) {
                 super._updateCache();
             }
+            this.updateCacheCalled = false;
         }
+
         public update() {
             // Get current device position in babylon world
             Vector3.TransformCoordinatesToRef(this._deviceRoomPosition, this._deviceToWorld, this.devicePosition);

+ 144 - 7
src/Engine/babylon.engine.ts

@@ -1106,7 +1106,7 @@
             // Caps
             this._caps = new EngineCapabilities();
             this._caps.maxTexturesImageUnits = this._gl.getParameter(this._gl.MAX_TEXTURE_IMAGE_UNITS);
-            this._caps.maxCombinedTexturesImageUnits = this._gl.getParameter(this._gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS);            
+            this._caps.maxCombinedTexturesImageUnits = this._gl.getParameter(this._gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS);
             this._caps.maxVertexTextureImageUnits = this._gl.getParameter(this._gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS);
             this._caps.maxTextureSize = this._gl.getParameter(this._gl.MAX_TEXTURE_SIZE);
             this._caps.maxCubemapTextureSize = this._gl.getParameter(this._gl.MAX_CUBE_MAP_TEXTURE_SIZE);
@@ -1270,7 +1270,7 @@
             this.setDepthWrite(true);
 
             // Texture maps
-            for (let slot = 0; slot < this._caps.maxTexturesImageUnits; slot++) {
+            for (let slot = 0; slot < this._caps.maxCombinedTexturesImageUnits; slot++) {
                 this._nextFreeTextureSlots.push(slot);
             }
         }
@@ -1308,7 +1308,7 @@
                 this._boundTexturesCache[key] = null;
             }
             this._nextFreeTextureSlots = [];
-            for (let slot = 0; slot < this._caps.maxTexturesImageUnits; slot++) {
+            for (let slot = 0; slot < this._caps.maxCombinedTexturesImageUnits; slot++) {
                 this._nextFreeTextureSlots.push(slot);
             }
             this._activeChannel = -1;
@@ -1887,6 +1887,64 @@
             this.bindUnboundFramebuffer(null);
         }
 
+        public unBindMultiColorAttachmentFramebuffer(textures: InternalTexture[], disableGenerateMipMaps = false, onBeforeUnbind?: () => void): void {
+            this._currentRenderTarget = null;
+
+            // If MSAA, we need to bitblt back to main texture
+            var gl = this._gl;
+
+
+            if (textures[0]._MSAAFramebuffer) {
+                gl.bindFramebuffer(gl.READ_FRAMEBUFFER, textures[0]._MSAAFramebuffer);
+                gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, textures[0]._framebuffer);
+
+                var attachments = textures[0]._attachments;
+                if (!attachments) {
+                    attachments = new Array(textures.length);
+                    textures[0]._attachments = attachments;
+                }
+
+                for (var i = 0; i < textures.length; i++) {
+                    var texture = textures[i];
+
+                    for (var j = 0; j < attachments.length; j++) {
+                        attachments[j] = gl.NONE;
+                    }
+
+                    attachments[i] = (<any>gl)[this.webGLVersion > 1 ? "COLOR_ATTACHMENT" + i : "COLOR_ATTACHMENT" + i + "_WEBGL"];
+                    gl.readBuffer(attachments[i]);
+                    gl.drawBuffers(attachments);
+                    gl.blitFramebuffer(0, 0, texture.width, texture.height,
+                        0, 0, texture.width, texture.height,
+                        gl.COLOR_BUFFER_BIT, gl.NEAREST);
+
+                }
+                for (var i = 0; i < attachments.length; i++) {
+                    attachments[i] = (<any>gl)[this.webGLVersion > 1 ? "COLOR_ATTACHMENT" + i : "COLOR_ATTACHMENT" + i + "_WEBGL"];
+                }
+                gl.drawBuffers(attachments);
+            }
+
+            for (var i = 0; i < textures.length; i++) {
+                var texture = textures[i];
+                if (texture.generateMipMaps && !disableGenerateMipMaps && !texture.isCube) {
+                    this._bindTextureDirectly(gl.TEXTURE_2D, texture);
+                    gl.generateMipmap(gl.TEXTURE_2D);
+                    this._bindTextureDirectly(gl.TEXTURE_2D, null);
+                }
+            }
+
+            if (onBeforeUnbind) {
+                if (textures[0]._MSAAFramebuffer) {
+                    // Bind the correct framebuffer
+                    this.bindUnboundFramebuffer(textures[0]._framebuffer);
+                }
+                onBeforeUnbind();
+            }
+
+            this.bindUnboundFramebuffer(null);
+        }
+
         public generateMipMapsForCubemap(texture: InternalTexture) {
             if (texture.generateMipMaps) {
                 var gl = this._gl;
@@ -2958,12 +3016,11 @@
                 this._alphaState.reset();
             }
 
-            this._cachedVertexBuffers = null;
+            this._resetVertexBufferBinding();
             this._cachedIndexBuffer = null;
             this._cachedEffectForVertexBuffers = null;
             this._unbindVertexArrayObject();
             this.bindIndexBuffer(null);
-            this.bindArrayBuffer(null);
         }
 
         /**
@@ -3391,7 +3448,7 @@
             if (!texture) {
                 return;
             }
-            
+
             this._bindTextureDirectly(this._gl.TEXTURE_2D, texture, true);
             this._gl.pixelStorei(this._gl.UNPACK_FLIP_Y_WEBGL, invertY ? 1 : 0);
             if (premulAlpha) {
@@ -3646,6 +3703,7 @@
                 texture.type = type;
                 texture._generateDepthBuffer = generateDepthBuffer;
                 texture._generateStencilBuffer = generateStencilBuffer;
+                texture._attachments = attachments;
 
                 this._internalTexturesCache.push(texture);
             }
@@ -3754,14 +3812,17 @@
             // Dispose previous render buffers
             if (texture._depthStencilBuffer) {
                 gl.deleteRenderbuffer(texture._depthStencilBuffer);
+                texture._depthStencilBuffer = null;
             }
 
             if (texture._MSAAFramebuffer) {
                 gl.deleteFramebuffer(texture._MSAAFramebuffer);
+                texture._MSAAFramebuffer = null;
             }
 
             if (texture._MSAARenderBuffer) {
                 gl.deleteRenderbuffer(texture._MSAARenderBuffer);
+                texture._MSAARenderBuffer = null;
             }
 
             if (samples > 1) {
@@ -3799,6 +3860,82 @@
             return samples;
         }
 
+        public updateMultipleRenderTargetTextureSampleCount(textures: Nullable<InternalTexture[]>, samples: number): number {
+            if (this.webGLVersion < 2 || !textures || textures.length == 0) {
+                return 1;
+            }
+
+            if (textures[0].samples === samples) {
+                return samples;
+            }
+
+            var gl = this._gl;
+
+            samples = Math.min(samples, gl.getParameter(gl.MAX_SAMPLES));
+
+            // Dispose previous render buffers
+            if (textures[0]._depthStencilBuffer) {
+                gl.deleteRenderbuffer(textures[0]._depthStencilBuffer);
+                textures[0]._depthStencilBuffer = null;
+            }
+
+            if (textures[0]._MSAAFramebuffer) {
+                gl.deleteFramebuffer(textures[0]._MSAAFramebuffer);
+                textures[0]._MSAAFramebuffer = null;
+            }
+
+            for (var i = 0; i < textures.length; i++) {
+                if (textures[i]._MSAARenderBuffer) {
+                    gl.deleteRenderbuffer(textures[i]._MSAARenderBuffer);
+                    textures[i]._MSAARenderBuffer = null;
+                }
+            }
+
+            if (samples > 1) {
+                let framebuffer = gl.createFramebuffer();
+
+                if (!framebuffer) {
+                    throw new Error("Unable to create multi sampled framebuffer");
+                }
+
+                this.bindUnboundFramebuffer(framebuffer);
+
+                let depthStencilBuffer = this._setupFramebufferDepthAttachments(textures[0]._generateStencilBuffer, textures[0]._generateDepthBuffer, textures[0].width, textures[0].height, samples);
+
+                var attachments = [];
+
+                for (var i = 0; i < textures.length; i++) {
+                    var texture = textures[i];
+                    var attachment = (<any>gl)[this.webGLVersion > 1 ? "COLOR_ATTACHMENT" + i : "COLOR_ATTACHMENT" + i + "_WEBGL"];
+
+                    var colorRenderbuffer = gl.createRenderbuffer();
+
+                    if (!colorRenderbuffer) {
+                        throw new Error("Unable to create multi sampled framebuffer");
+                    }
+
+                    gl.bindRenderbuffer(gl.RENDERBUFFER, colorRenderbuffer);
+                    gl.renderbufferStorageMultisample(gl.RENDERBUFFER, samples, gl.RGBA8, texture.width, texture.height);
+
+                    gl.framebufferRenderbuffer(gl.FRAMEBUFFER, attachment, gl.RENDERBUFFER, colorRenderbuffer);
+
+                    texture._MSAAFramebuffer = framebuffer;
+                    texture._MSAARenderBuffer = colorRenderbuffer;
+                    texture.samples = samples;
+                    texture._depthStencilBuffer = depthStencilBuffer;
+                    gl.bindRenderbuffer(gl.RENDERBUFFER, null);
+                    attachments.push(attachment);
+                }
+                gl.drawBuffers(attachments);
+            } else {
+                this.bindUnboundFramebuffer(textures[0]._framebuffer);
+            }
+
+            this.bindUnboundFramebuffer(null);
+
+            return samples;
+        }
+
         public _uploadDataToTexture(target: number, lod: number, internalFormat: number, width: number, height: number, format: number, type: number, data: ArrayBufferView) {
             this._gl.texImage2D(target, lod, internalFormat, width, height, 0, format, type, data);
         }
@@ -4654,7 +4791,7 @@
         }
 
         public unbindAllTextures(): void {
-            for (var channel = 0; channel < this._caps.maxTexturesImageUnits; channel++) {
+            for (var channel = 0; channel < this._caps.maxCombinedTexturesImageUnits; channel++) {
                 this._activateTextureChannel(channel);
                 this._bindTextureDirectly(this._gl.TEXTURE_2D, null);
                 this._bindTextureDirectly(this._gl.TEXTURE_CUBE_MAP, null);

+ 2 - 1
src/Materials/Textures/babylon.internalTexture.ts

@@ -47,6 +47,7 @@ module BABYLON {
         public _depthStencilBuffer: Nullable<WebGLRenderbuffer>;
         public _MSAAFramebuffer: Nullable<WebGLFramebuffer>;
         public _MSAARenderBuffer: Nullable<WebGLRenderbuffer>;
+        public _attachments: Nullable<number[]>;
         public _cachedCoordinatesMode: Nullable<number>;
         public _cachedWrapU: Nullable<number>;
         public _cachedWrapV: Nullable<number>;
@@ -239,4 +240,4 @@ module BABYLON {
             }
         }
     }
-}
+}

+ 11 - 7
src/Materials/Textures/babylon.multiRenderTarget.ts

@@ -63,13 +63,13 @@ module BABYLON {
             var samplingModes = [];
 
             for (var i = 0; i < count; i++) {
-                if (options.types && options.types[i]) {
+                if (options.types && options.types[i] !== undefined) {
                     types.push(options.types[i]);
                 } else {
-                    types.push(Engine.TEXTURETYPE_FLOAT);
+                    types.push(Engine.TEXTURETYPE_UNSIGNED_INT);
                 }
 
-                if (options.samplingModes && options.samplingModes[i]) {
+                if (options.samplingModes && options.samplingModes[i] !== undefined) {
                     samplingModes.push(options.samplingModes[i]);
                 } else {
                     samplingModes.push(Texture.BILINEAR_SAMPLINGMODE);
@@ -132,9 +132,7 @@ module BABYLON {
                 return;
             }
 
-            for (var i = 0; i < this._internalTextures.length; i++) {
-                this._samples = this._engine.updateRenderTargetTextureSampleCount(this._internalTextures[i], value);
-            }
+            this._samples = this._engine.updateMultipleRenderTargetTextureSampleCount(this._internalTextures, value);
         }
 
         public resize(size: any) {
@@ -143,6 +141,12 @@ module BABYLON {
             this._createInternalTextures();
         }
 
+        protected unbindFrameBuffer(engine: Engine, faceIndex: number): void {
+            engine.unBindMultiColorAttachmentFramebuffer(this._internalTextures, this.isCube, () => {
+                this.onAfterRenderObservable.notifyObservers(faceIndex);
+            });
+        }
+
         public dispose(): void {
             this.releaseInternalTextures();
 
@@ -162,4 +166,4 @@ module BABYLON {
             }
         }
     }
-}
+}

+ 12 - 4
src/Materials/Textures/babylon.renderTargetTexture.ts

@@ -495,6 +495,15 @@
             return Math.min(Tools.FloorPOT(renderDimension), curved);
         }
 
+        protected unbindFrameBuffer(engine: Engine, faceIndex: number): void {
+            if (!this._texture) {
+                return;
+            }
+            engine.unBindFramebuffer(this._texture, this.isCube, () => {
+                this.onAfterRenderObservable.notifyObservers(faceIndex);
+            });
+        }
+
         private renderToTarget(faceIndex: number, currentRenderList: AbstractMesh[], currentRenderListLength: number, useCameraPostProcess: boolean, dumpForDebug: boolean): void {
             var scene = this.getScene();
             
@@ -559,9 +568,8 @@
                     }
                 }
 
-                engine.unBindFramebuffer(this._texture, this.isCube, () => {
-                    this.onAfterRenderObservable.notifyObservers(faceIndex);
-                });
+            this.unbindFrameBuffer(engine, faceIndex);
+
             } else {
                 this.onAfterRenderObservable.notifyObservers(faceIndex);
             }
@@ -702,4 +710,4 @@
             }
         }
     }
-}
+}

+ 13 - 5
src/Rendering/babylon.geometryBufferRenderer.ts

@@ -108,6 +108,14 @@ module BABYLON {
             return this._multiRenderTarget;
         }
 
+        public get samples(): number {
+            return this._multiRenderTarget.samples;
+        }
+
+        public set samples(value: number) {
+            this._multiRenderTarget.samples = value;
+        }
+
         // Methods
         public dispose(): void {
             this.getGBuffer().dispose();
@@ -117,7 +125,7 @@ module BABYLON {
             var engine = this._scene.getEngine();
             var count = this._enablePosition ? 3 : 2;
 
-            this._multiRenderTarget = new MultiRenderTarget("gBuffer", { width: engine.getRenderWidth() * this._ratio, height: engine.getRenderHeight() * this._ratio }, count, this._scene, { generateMipMaps : false, generateDepthTexture: true });
+            this._multiRenderTarget = new MultiRenderTarget("gBuffer", { width: engine.getRenderWidth() * this._ratio, height: engine.getRenderHeight() * this._ratio }, count, this._scene, { generateMipMaps: false, generateDepthTexture: true });
             if (!this.isSupported) {
                 return;
             }
@@ -126,7 +134,7 @@ module BABYLON {
             this._multiRenderTarget.refreshRate = 1;
             this._multiRenderTarget.renderParticles = false;
             this._multiRenderTarget.renderList = null;
-            
+
             // set default depth value to 1.0 (far away)
             this._multiRenderTarget.onClearObservable.add((engine: Engine) => {
                 engine.clear(new Color4(0.0, 0.0, 0.0, 1.0), true, true, true);
@@ -158,7 +166,7 @@ module BABYLON {
                 if (this.isReady(subMesh, hardwareInstancedRendering)) {
                     engine.enableEffect(this._effect);
                     mesh._bind(subMesh, this._effect, Material.TriangleFillMode);
-                    
+
 
                     this._effect.setMatrix("viewProjection", scene.getTransformMatrix());
                     this._effect.setMatrix("view", scene.getViewMatrix());
@@ -188,12 +196,12 @@ module BABYLON {
                 var index;
 
                 if (depthOnlySubMeshes.length) {
-                    engine.setColorWrite(false);            
+                    engine.setColorWrite(false);
                     for (index = 0; index < depthOnlySubMeshes.length; index++) {
                         renderSubMesh(depthOnlySubMeshes.data[index]);
                     }
                     engine.setColorWrite(true);
-                }                  
+                }
 
                 for (index = 0; index < opaqueSubMeshes.length; index++) {
                     renderSubMesh(opaqueSubMeshes.data[index]);

+ 11 - 8
tests/validation/config.json

@@ -36,25 +36,25 @@
       "title": "Retail",
       "sceneFolder": "/Scenes/Retail/",
       "sceneFilename": "Retail.babylon",
-      "referenceImage": "Retail.png"
+      "referenceImage": "retail.png"
     },
     {
       "title": "Hill Valley",
       "sceneFolder": "/Scenes/hillvalley/",
       "sceneFilename": "HillValley.incremental.babylon",
-      "referenceImage": "HillValley.png"
+      "referenceImage": "Hillvalley.png"
     },
     {
       "title": "Heart",
       "sceneFolder": "/Scenes/Heart/",
       "sceneFilename": "Heart.babylon",
-      "referenceImage": "heart.png"
+      "referenceImage": "Heart.png"
     },
     {
       "title": "Mansion",
       "sceneFolder": "/Scenes/Mansion/",
       "sceneFilename": "Mansion.babylon",
-      "referenceImage": "Mansion.png"
+      "referenceImage": "mansion.png"
     },
     {
       "title": "SpaceDeK",
@@ -241,7 +241,8 @@
       "renderCount": 20,
       "scriptToRun": "/Demos/GLTF1CesiumMan/index.js",
       "functionToCall": "createScene",
-      "referenceImage": "gltf1CesiumMan.png"
+      "referenceImage": "gltf1CesiumMan.png",
+      "onlyVisual": true
     },
     {
       "title": "GLTF Mesh Primitive Attribute Test",
@@ -263,13 +264,14 @@
       "renderCount": 20,
       "scriptToRun": "/Demos/PBRRough/index.js",
       "functionToCall": "CreatePBRRoughScene",
-      "referenceImage": "PBRRough.png"
+      "referenceImage": "pbrrough.png"
     },
     {
       "title": "GLTF LOD",
       "renderCount": 500,
       "playgroundId": "#TC80RU#41",
-      "referenceImage": "GLTF LOD.png"
+      "referenceImage": "GLTF LOD.png",
+      "onlyVisual": true
     },
     {
       "title": "Reflection probes",
@@ -336,7 +338,8 @@
       "scriptToRun": "/Demos/Procedural/proceduralTexture.js",
       "functionToCall": "CreateProceduralTextureTestScene",
       "referenceImage": "procedural.png",
-      "replace": "./land, https://cdn.rawgit.com/BabylonJS/Website/06ecbea7/Demos/Procedural/land"
+      "replace": "./land, https://cdn.rawgit.com/BabylonJS/Website/06ecbea7/Demos/Procedural/land",
+      "onlyVisual": true
     },
     {
       "title": "Water material (only visual check)",

+ 39 - 1
tests/validation/index.html

@@ -8,7 +8,45 @@
 <body>
 	<script>
         BABYLONDEVTOOLS.Loader.require('validation.js')
-            .load();
+			.load(function() {
+				// Loading tests
+				var xhr = new XMLHttpRequest();
+
+				xhr.open("GET", "config.json", true);
+
+				xhr.addEventListener("load", function () {
+					if (xhr.status === 200) {
+
+						config = JSON.parse(xhr.responseText);
+
+						// Run tests
+						var index = 0;
+						if (window.location.search) {
+							justOnce = true;
+							var title = window.location.search.replace("?", "").replace(/%20/g, " ");
+							for (var index = 0; index < config.tests.length; index++) {
+								if (config.tests[index].title === title) {
+									break;
+								}
+							}
+						}
+
+						var recursiveRunTest = function(i) {
+							runTest(i, function() {
+								i++;
+								if (justOnce || i >= config.tests.length) {
+									return;
+								}
+								recursiveRunTest(i);
+							});
+						}
+
+						recursiveRunTest(index);
+					}
+				}, false);
+
+				xhr.send();
+			});
     </script>	
 </body>
 </html>

+ 57 - 0
tests/validation/integration.js

@@ -0,0 +1,57 @@
+window.__karma__.loaded = function() {};
+
+// Loading tests
+var xhr = new XMLHttpRequest();
+
+xhr.open("GET", "/tests/validation/config.json", true);
+
+xhr.addEventListener("load", function () {
+    if (xhr.status === 200) {
+
+        config = JSON.parse(xhr.responseText);
+
+        describe("Validation Tests", function() {
+            before(function (done) {
+                this.timeout(180000);
+                require = null;
+                BABYLONDEVTOOLS.Loader
+                .require('/tests/validation/validation.js')
+                .useDist()
+                .load(function() {
+                    done();            
+                });
+            });
+        
+            // Run tests
+            for (let index = 0; index < config.tests.length; index++) {
+                var test = config.tests[index];
+                if (test.onlyVisual) {
+                    continue;
+                }
+
+                it(test.title, function(done) {
+                    this.timeout(240000);
+        
+                    try {
+                        runTest(index, function(result) {
+                            try {
+                                expect(result).to.be.true; 
+                                done();
+                            }
+                            catch (e) {
+                                done(e);
+                            }
+                        });                
+                    }
+                    catch (e) {
+                        done(e);
+                    }
+                });
+            };
+        });
+
+        window.__karma__.start();          
+    }
+}, false);
+
+xhr.send();

+ 43 - 0
tests/validation/karma.conf.js

@@ -0,0 +1,43 @@
+module.exports = function (config) {
+    'use strict';
+    config.set({
+
+        basePath: '../../',
+        browserNoActivityTimeout: 1800000,
+
+        urlRoot: '/karma',
+
+        frameworks: ['mocha', 'chai', 'sinon'],
+
+        files: [
+            './Tools/DevLoader/BabylonLoader.js',
+            './tests/validation/index.css',
+            './tests/validation/integration.js',
+            { pattern: 'dist/**/*', watched: false, included: false, served: true },
+            { pattern: 'assets/**/*', watched: false, included: false, served: true },
+            { pattern: 'tests/**/*', watched: false, included: false, served: true },
+            { pattern: 'Playground/scenes/**/*', watched: false, included: false, served: true },
+            { pattern: 'Playground/textures/**/*', watched: false, included: false, served: true },
+            { pattern: 'Playground/sounds/**/*', watched: false, included: false, served: true },
+            { pattern: 'Tools/DevLoader/**/*', watched: false, included: false, served: true },            
+            { pattern: 'Tools/Gulp/config.json', watched: false, included: false, served: true },
+        ],
+        proxies: {
+            '/': '/base/'
+        },
+
+        reporters: ['progress'],
+
+        port: 1338,
+        colors: true,
+        autoWatch: false,
+        singleRun: false,
+
+        // level of logging
+        // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
+        logLevel: config.LOG_INFO,
+
+        browsers: ['Chrome']
+
+    });
+};

+ 74 - 0
tests/validation/karma.conf.saucelabs.js

@@ -0,0 +1,74 @@
+// sl_ie_11: {
+//     base: 'SauceLabs',
+//     browserName: 'internet explorer',
+//     platform: 'Windows 10',
+//     version: '11'
+// },
+// sl_android: {
+//     base: 'SauceLabs',
+//     browserName: 'Browser',
+//     platform: 'Android',
+//     version: '4.4',
+//     deviceName: 'Samsung Galaxy S3 Emulator',
+//     deviceOrientation: 'portrait'
+// }
+
+module.exports = function (config) {
+    'use strict';
+    config.set({
+
+        basePath: '../../',
+        browserNoActivityTimeout: 1800000,
+
+        urlRoot: '/karma',
+
+        frameworks: ['mocha', 'chai', 'sinon'],
+
+        files: [
+            './Tools/DevLoader/BabylonLoader.js',
+            './tests/validation/index.css',
+            './tests/validation/integration.js',
+            { pattern: 'dist/**/*', watched: false, included: false, served: true },
+            { pattern: 'assets/**/*', watched: false, included: false, served: true },
+            { pattern: 'tests/**/*', watched: false, included: false, served: true },
+            { pattern: 'Playground/scenes/**/*', watched: false, included: false, served: true },
+            { pattern: 'Playground/textures/**/*', watched: false, included: false, served: true },
+            { pattern: 'Playground/sounds/**/*', watched: false, included: false, served: true },
+            { pattern: 'Tools/DevLoader/**/*', watched: false, included: false, served: true },            
+            { pattern: 'Tools/Gulp/config.json', watched: false, included: false, served: true },
+        ],
+        proxies: {
+            '/': '/base/'
+        },
+
+        port: 1338,
+        colors: true,
+        autoWatch: false,
+        singleRun: false,
+
+        // level of logging
+        // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
+        logLevel: config.LOG_ERROR,
+
+        sauceLabs: {
+            testName: 'Babylon JS Validation Tests',
+            startConnect: false,
+            tunnelIdentifier: process.env.TRAVIS_JOB_NUMBER,
+            build: process.env.TRAVIS_BUILD_NUMBER,
+            tags: [process.env.TRAVIS_BRANCH, process.env.TRAVIS_PULL_REQUEST],
+            public: 'public'
+        },
+        customLaunchers: {
+            sl_firefox: {
+              base: 'SauceLabs',
+              browserName: 'firefox',
+              platform: 'Windows 10',
+              version: '57',
+              tunnelIdentifier: process.env.TRAVIS_JOB_NUMBER
+            }
+        },
+        browsers: ['sl_firefox'],
+        reporters: ['dots', 'saucelabs'],
+        singleRun: true
+    });
+};

+ 15 - 45
tests/validation/validation.js

@@ -76,16 +76,19 @@ function saveRenderImage(data, canvas) {
     return screenshotCanvas.toDataURL();
 }
 
-function evaluate(test, resultCanvas, result, renderImage, index, waitRing) {
+function evaluate(test, resultCanvas, result, renderImage, index, waitRing, done) {
     var renderData = getRenderData(canvas, engine);
+    var testRes = true;
     if (!test.onlyVisual) {
 
         if (compare(renderData, resultCanvas)) {
             result.classList.add("failed");
             result.innerHTML = "×";
+            testRes = false;
             console.log('%c failed', 'color: red');
         } else {
             result.innerHTML = "✔";
+            testRes = true;
             console.log('%c validated', 'color: green');
         }
     }
@@ -95,12 +98,10 @@ function evaluate(test, resultCanvas, result, renderImage, index, waitRing) {
 
     currentScene.dispose();
 
-    if (!justOnce) {
-        runTest(index + 1);
-    }
+    done(testRes);
 }
 
-function processCurrentScene(test, resultCanvas, result, renderImage, index, waitRing) {
+function processCurrentScene(test, resultCanvas, result, renderImage, index, waitRing, done) {
     currentScene.executeWhenReady(function () {
         var renderCount = test.renderCount || 1;
 
@@ -111,19 +112,16 @@ function processCurrentScene(test, resultCanvas, result, renderImage, index, wai
 
             if (renderCount === 0) {
                 engine.stopRenderLoop();
-                evaluate(test, resultCanvas, result, renderImage, index, waitRing);
+                evaluate(test, resultCanvas, result, renderImage, index, waitRing, done);
             }
         });
 
     });
 }
 
-function
-
-
-runTest(index) {
+function runTest(index, done) {
     if (index >= config.tests.length) {
-        return;
+        done(false);
     }
 
     var test = config.tests[index];
@@ -147,7 +145,7 @@ runTest(index) {
     var waitRing = document.createElement("img");
     waitRing.className = "waitRing";
     titleContainer.appendChild(waitRing);
-    waitRing.src = "loading.gif";
+    waitRing.src = "/tests/validation/loading.gif";
 
     var resultCanvas = document.createElement("canvas");
     resultCanvas.className = "resultImage";
@@ -165,7 +163,7 @@ runTest(index) {
         resultContext.drawImage(img, 0, 0);
     }
 
-    img.src = "ReferenceImages/" + test.referenceImage;
+    img.src = "/tests/validation/ReferenceImages/" + test.referenceImage;
 
     var renderImage = new Image();
     renderImage.className = "renderImage";
@@ -176,12 +174,12 @@ runTest(index) {
     if (test.sceneFolder) {
         BABYLON.SceneLoader.Load(config.root + test.sceneFolder, test.sceneFilename, engine, function (newScene) {
             currentScene = newScene;
-            processCurrentScene(test, resultCanvas, result, renderImage, index, waitRing);
+            processCurrentScene(test, resultCanvas, result, renderImage, index, waitRing, done);
         });
     }
     else if (test.playgroundId) {
         var snippetUrl = "//babylonjs-api2.azurewebsites.net/snippets";
-        var pgRoot = "/playground"
+        var pgRoot = "/Playground"
         var xmlHttp = new XMLHttpRequest();
         xmlHttp.onreadystatechange = function () {
             if (xmlHttp.readyState === 4) {
@@ -194,7 +192,7 @@ runTest(index) {
                     code = code.replace(/"scenes\//g, "\"" + pgRoot + "/scenes/");
 
                     currentScene = eval(code + "\r\ncreateScene(engine)");
-                    processCurrentScene(test, resultCanvas, result, renderImage, index, waitRing);
+                    processCurrentScene(test, resultCanvas, result, renderImage, index, waitRing, done);
                 }
             }
         }
@@ -238,7 +236,7 @@ runTest(index) {
                 }
 
                 currentScene = eval(scriptToRun + test.functionToCall + "(engine)");
-                processCurrentScene(test, resultCanvas, result, renderImage, index, waitRing);
+                processCurrentScene(test, resultCanvas, result, renderImage, index, waitRing, done);
             }
         };
 
@@ -256,31 +254,3 @@ canvas.className = "renderCanvas";
 document.body.appendChild(canvas);
 engine = new BABYLON.Engine(canvas, false);
 engine.setDitheringState(false);
-
-// Loading tests
-var xhr = new XMLHttpRequest();
-
-xhr.open("GET", "config.json", true);
-
-xhr.addEventListener("load", function () {
-    if (xhr.status === 200) {
-
-        config = JSON.parse(xhr.responseText);
-
-        // Run tests
-        var index = 0;
-        if (window.location.search) {
-            justOnce = true;
-            var title = window.location.search.replace("?", "").replace(/%20/g, " ");
-            for (var index = 0; index < config.tests.length; index++) {
-                if (config.tests[index].title === title) {
-                    break;
-                }
-            }
-        }
-        runTest(index);
-
-    }
-}, false);
-
-xhr.send();