فهرست منبع

Merge pull request #4481 from bghgary/gltf-perf-counters

Add perf counters to glTF loader
David Catuhe 7 سال پیش
والد
کامیت
52f9bf6e6b

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

@@ -39,7 +39,7 @@
 - Added support for KHR_texture_transform ([bghgary](http://www.github.com/bghgary))
 - Added `onNodeLODsLoadedObservable` and `onMaterialLODsLoadedObservable` to MSFT_lod loader extension ([bghgary](http://www.github.com/bghgary))
 - Added glTF loader settings to the GLTF tab in the debug layer ([bghgary](http://www.github.com/bghgary))
-- Added support for debug logging ([bghgary](http://www.github.com/bghgary))
+- Added debug logging and performance counters ([bghgary](http://www.github.com/bghgary))
 
 ### Viewer
 

+ 2 - 2
inspector/src/tabs/GLTFTab.ts

@@ -159,8 +159,8 @@ module INSPECTOR {
                 defaults.extensions[extension.name] = extensionDefaults;
             });
 
-            const data = '{ "asset": { "version": "2.0" }, "scenes": [ { } ] }';
-            return loader.loadAsync(scene, data, "").then(() => {
+            const data = '{ "asset": { "version": "2.0" } }';
+            return loader.importMeshAsync([], scene, data, "").then(() => {
                 scene.dispose();
                 engine.dispose();
 

+ 60 - 63
loaders/src/glTF/2.0/Extensions/MSFT_lod.ts

@@ -32,30 +32,52 @@ module BABYLON.GLTF2.Extensions {
          */
         public onMaterialLODsLoadedObservable = new Observable<number>();
 
-        private _loadingNodeLOD: Nullable<_ILoaderNode> = null;
-        private _loadNodeSignals: { [nodeIndex: number]: Deferred<void> } = {};
-        private _loadNodePromises = new Array<Array<Promise<void>>>();
+        private _nodeIndexLOD: Nullable<number> = null;
+        private _nodeSignalLODs = new Array<Deferred<void>>();
+        private _nodePromiseLODs = new Array<Array<Promise<void>>>();
 
-        private _loadingMaterialLOD: Nullable<_ILoaderMaterial> = null;
-        private _loadMaterialSignals: { [materialIndex: number]: Deferred<void> } = {};
-        private _loadMaterialPromises = new Array<Array<Promise<void>>>();
+        private _materialIndexLOD: Nullable<number> = null;
+        private _materialSignalLODs = new Array<Deferred<void>>();
+        private _materialPromiseLODs = new Array<Array<Promise<void>>>();
 
         constructor(loader: GLTFLoader) {
             super(loader);
 
-            this._loader._onReadyObservable.addOnce(() => {
-                for (let indexLOD = 0; indexLOD < this._loadNodePromises.length; indexLOD++) {
-                    Promise.all(this._loadNodePromises[indexLOD]).then(() => {
+            this._loader._readyPromise.then(() => {
+                for (let indexLOD = 0; indexLOD < this._nodePromiseLODs.length; indexLOD++) {
+                    const promise = Promise.all(this._nodePromiseLODs[indexLOD]).then(() => {
+                        if (indexLOD !== 0) {
+                            this._loader._parent._endPerformanceCounter(`Node LOD ${indexLOD}`);
+                        }
+
                         this._loader._parent._log(`Loaded node LOD ${indexLOD}`);
                         this.onNodeLODsLoadedObservable.notifyObservers(indexLOD);
+
+                        if (indexLOD !== this._nodePromiseLODs.length - 1) {
+                            this._loader._parent._startPerformanceCounter(`Node LOD ${indexLOD + 1}`);
+                            this._nodeSignalLODs[indexLOD].resolve();
+                        }
                     });
+
+                    this._loader._completePromises.push(promise);
                 }
 
-                for (let indexLOD = 0; indexLOD < this._loadMaterialPromises.length; indexLOD++) {
-                    Promise.all(this._loadMaterialPromises[indexLOD]).then(() => {
+                for (let indexLOD = 0; indexLOD < this._materialPromiseLODs.length; indexLOD++) {
+                    const promise = Promise.all(this._materialPromiseLODs[indexLOD]).then(() => {
+                        if (indexLOD !== 0) {
+                            this._loader._parent._endPerformanceCounter(`Material LOD ${indexLOD}`);
+                        }
+
                         this._loader._parent._log(`Loaded material LOD ${indexLOD}`);
                         this.onMaterialLODsLoadedObservable.notifyObservers(indexLOD);
+
+                        if (indexLOD !== this._materialPromiseLODs.length - 1) {
+                            this._loader._parent._startPerformanceCounter(`Material LOD ${indexLOD + 1}`);
+                            this._materialSignalLODs[indexLOD].resolve();
+                        }
                     });
+
+                    this._loader._completePromises.push(promise);
                 }
             });
         }
@@ -63,10 +85,13 @@ module BABYLON.GLTF2.Extensions {
         public dispose() {
             super.dispose();
 
-            this._loadingNodeLOD = null;
-            this._loadNodeSignals = {};
-            this._loadingMaterialLOD = null;
-            this._loadMaterialSignals = {};
+            this._nodeIndexLOD = null;
+            this._nodeSignalLODs.length = 0;
+            this._nodePromiseLODs.length = 0;
+
+            this._materialIndexLOD = null;
+            this._materialSignalLODs.length = 0;
+            this._materialPromiseLODs.length = 0;
 
             this.onMaterialLODsLoadedObservable.clear();
             this.onNodeLODsLoadedObservable.clear();
@@ -83,11 +108,8 @@ module BABYLON.GLTF2.Extensions {
                     const nodeLOD = nodeLODs[indexLOD];
 
                     if (indexLOD !== 0) {
-                        this._loadingNodeLOD = nodeLOD;
-
-                        if (!this._loadNodeSignals[nodeLOD._index]) {
-                            this._loadNodeSignals[nodeLOD._index] = new Deferred<void>();
-                        }
+                        this._nodeIndexLOD = indexLOD;
+                        this._nodeSignalLODs[indexLOD] = this._nodeSignalLODs[indexLOD] || new Deferred();
                     }
 
                     const promise = this._loader._loadNodeAsync(`#/nodes/${nodeLOD._index}`, nodeLOD).then(() => {
@@ -98,27 +120,17 @@ module BABYLON.GLTF2.Extensions {
                                 delete previousNodeLOD._babylonMesh;
                             }
                         }
-
-                        if (indexLOD !== nodeLODs.length - 1) {
-                            const nodeIndex = nodeLODs[indexLOD + 1]._index;
-
-                            if (this._loadNodeSignals[nodeIndex]) {
-                                this._loadNodeSignals[nodeIndex].resolve();
-                                delete this._loadNodeSignals[nodeIndex];
-                            }
-                        }
                     });
 
                     if (indexLOD === 0) {
                         firstPromise = promise;
                     }
                     else {
-                        this._loader._completePromises.push(promise);
-                        this._loadingNodeLOD = null;
+                        this._nodeIndexLOD = null;
                     }
 
-                    this._loadNodePromises[indexLOD] = this._loadNodePromises[indexLOD] || [];
-                    this._loadNodePromises[indexLOD].push(promise);
+                    this._nodePromiseLODs[indexLOD] = this._nodePromiseLODs[indexLOD] || [];
+                    this._nodePromiseLODs[indexLOD].push(promise);
                 }
 
                 this._loader._parent._logClose();
@@ -128,7 +140,7 @@ module BABYLON.GLTF2.Extensions {
 
         protected _loadMaterialAsync(context: string, material: _ILoaderMaterial, mesh: _ILoaderMesh, babylonMesh: Mesh, babylonDrawMode: number, assign: (babylonMaterial: Material) => void): Nullable<Promise<void>> {
             // Don't load material LODs if already loading a node LOD.
-            if (this._loadingNodeLOD) {
+            if (this._nodeIndexLOD) {
                 return null;
             }
 
@@ -142,11 +154,7 @@ module BABYLON.GLTF2.Extensions {
                     const materialLOD = materialLODs[indexLOD];
 
                     if (indexLOD !== 0) {
-                        this._loadingMaterialLOD = materialLOD;
-
-                        if (!this._loadMaterialSignals[materialLOD._index]) {
-                            this._loadMaterialSignals[materialLOD._index] = new Deferred<void>();
-                        }
+                        this._materialIndexLOD = indexLOD;
                     }
 
                     const promise = this._loader._loadMaterialAsync(`#/materials/${materialLOD._index}`, materialLOD, mesh, babylonMesh, babylonDrawMode, indexLOD === 0 ? assign : () => {}).then(() => {
@@ -160,26 +168,17 @@ module BABYLON.GLTF2.Extensions {
                                 delete previousBabylonDataLOD[babylonDrawMode];
                             }
                         }
-
-                        if (indexLOD !== materialLODs.length - 1) {
-                            const materialIndex = materialLODs[indexLOD + 1]._index;
-                            if (this._loadMaterialSignals[materialIndex]) {
-                                this._loadMaterialSignals[materialIndex].resolve();
-                                delete this._loadMaterialSignals[materialIndex];
-                            }
-                        }
                     });
 
                     if (indexLOD === 0) {
                         firstPromise = promise;
                     }
                     else {
-                        this._loader._completePromises.push(promise);
-                        this._loadingMaterialLOD = null;
+                        this._materialIndexLOD = null;
                     }
 
-                    this._loadMaterialPromises[indexLOD] = this._loadMaterialPromises[indexLOD] || [];
-                    this._loadMaterialPromises[indexLOD].push(promise);
+                    this._materialPromiseLODs[indexLOD] = this._materialPromiseLODs[indexLOD] || [];
+                    this._materialPromiseLODs[indexLOD].push(promise);
                 }
 
                 this._loader._parent._logClose();
@@ -188,22 +187,20 @@ module BABYLON.GLTF2.Extensions {
         }
 
         protected _loadUriAsync(context: string, uri: string): Nullable<Promise<ArrayBufferView>> {
-            if (this._loadingMaterialLOD || this._loadingNodeLOD) {
-                if (this._loader._parent.loggingEnabled) {
-                    this._loader._parent._log(`deferred`);
-                }
-            }
-
             // Defer the loading of uris if loading a material or node LOD.
-            if (this._loadingMaterialLOD) {
-                const index = this._loadingMaterialLOD._index;
-                return this._loadMaterialSignals[index].promise.then(() => {
+            if (this._materialIndexLOD !== null) {
+                this._loader._parent._log(`deferred`);
+                const previousIndexLOD = this._materialIndexLOD - 1;
+                this._materialSignalLODs[previousIndexLOD] = this._materialSignalLODs[previousIndexLOD] || new Deferred<void>();
+                return this._materialSignalLODs[previousIndexLOD].promise.then(() => {
                     return this._loader._loadUriAsync(context, uri);
                 });
             }
-            else if (this._loadingNodeLOD) {
-                const index = this._loadingNodeLOD._index;
-                return this._loadNodeSignals[index].promise.then(() => {
+            else if (this._nodeIndexLOD !== null) {
+                this._loader._parent._log(`deferred`);
+                const previousIndexLOD = this._nodeIndexLOD - 1;
+                this._nodeSignalLODs[previousIndexLOD] = this._nodeSignalLODs[previousIndexLOD] || new Deferred<void>();
+                return this._nodeSignalLODs[this._nodeIndexLOD - 1].promise.then(() => {
                     return this._loader._loadUriAsync(context, uri);
                 });
             }

+ 33 - 26
loaders/src/glTF/2.0/babylon.glTFLoader.ts

@@ -25,8 +25,8 @@ module BABYLON.GLTF2 {
         public _parent: GLTFFileLoader;
         public _gltf: _ILoaderGLTF;
         public _babylonScene: Scene;
+        public _readyPromise: Promise<void>;
         public _completePromises = new Array<Promise<void>>();
-        public _onReadyObservable = new Observable<IGLTFLoader>();
 
         private _disposed = false;
         private _state: Nullable<GLTFLoaderState> = null;
@@ -79,8 +79,8 @@ module BABYLON.GLTF2 {
 
             delete this._gltf;
             delete this._babylonScene;
+            delete this._readyPromise;
             this._completePromises.length = 0;
-            this._onReadyObservable.clear();
 
             for (const name in this._extensions) {
                 this._extensions[name].dispose();
@@ -101,14 +101,14 @@ module BABYLON.GLTF2 {
                 this._progressCallback = onProgress;
                 this._loadData(data);
 
-                let nodes: Nullable<Array<_ILoaderNode>> = null;
+                let nodes: Nullable<Array<number>> = null;
 
                 if (meshesNames) {
-                    const nodeMap: { [name: string]: _ILoaderNode } = {};
+                    const nodeMap: { [name: string]: number } = {};
                     if (this._gltf.nodes) {
                         for (const node of this._gltf.nodes) {
                             if (node.name) {
-                                nodeMap[node.name] = node;
+                                nodeMap[node.name] = node._index;
                             }
                         }
                     }
@@ -116,7 +116,7 @@ module BABYLON.GLTF2 {
                     const names = (meshesNames instanceof Array) ? meshesNames : [meshesNames];
                     nodes = names.map(name => {
                         const node = nodeMap[name];
-                        if (!node) {
+                        if (node === undefined) {
                             throw new Error(`Failed to find node '${name}'`);
                         }
 
@@ -145,10 +145,16 @@ module BABYLON.GLTF2 {
             });
         }
 
-        private _loadAsync(nodes: Nullable<Array<_ILoaderNode>>): Promise<void> {
+        private _loadAsync(nodes: Nullable<Array<number>>): Promise<void> {
             return Promise.resolve().then(() => {
+                this._parent._startPerformanceCounter("Loading => Ready");
+                this._parent._startPerformanceCounter("Loading => Complete");
+
                 this._state = GLTFLoaderState.LOADING;
-                this._parent._log(`Loading`);
+                this._parent._log("Loading");
+
+                const readyDeferred = new Deferred<void>();
+                this._readyPromise = readyDeferred.promise;
 
                 this._loadExtensions();
                 this._checkExtensions();
@@ -156,7 +162,7 @@ module BABYLON.GLTF2 {
                 const promises = new Array<Promise<void>>();
 
                 if (nodes) {
-                    promises.push(this._loadNodesAsync(nodes));
+                    promises.push(this._loadSceneAsync("#/nodes", { nodes: nodes, _index: -1 }));
                 }
                 else {
                     const scene = GLTFLoader._GetProperty(`#/scene`, this._gltf.scenes, this._gltf.scene || 0);
@@ -173,13 +179,16 @@ module BABYLON.GLTF2 {
 
                 const resultPromise = Promise.all(promises).then(() => {
                     this._state = GLTFLoaderState.READY;
-                    this._parent._log(`Ready`);
+                    this._parent._log("Ready");
+
+                    readyDeferred.resolve();
 
-                    this._onReadyObservable.notifyObservers(this);
                     this._startAnimations();
                 });
 
                 resultPromise.then(() => {
+                    this._parent._endPerformanceCounter("Loading => Ready");
+
                     if (this._rootBabylonMesh) {
                         this._rootBabylonMesh.setEnabled(true);
                     }
@@ -187,8 +196,10 @@ module BABYLON.GLTF2 {
                     Tools.SetImmediate(() => {
                         if (!this._disposed) {
                             Promise.all(this._completePromises).then(() => {
+                                this._parent._endPerformanceCounter("Loading => Complete");
+
                                 this._state = GLTFLoaderState.COMPLETE;
-                                this._parent._log(`Complete`);
+                                this._parent._log("Complete");
 
                                 this._parent.onCompleteObservable.notifyObservers(undefined);
                                 this._parent.onCompleteObservable.clear();
@@ -313,18 +324,6 @@ module BABYLON.GLTF2 {
             return rootNode;
         }
 
-        private _loadNodesAsync(nodes: _ILoaderNode[]): Promise<void> {
-            const promises = new Array<Promise<void>>();
-
-            for (let node of nodes) {
-                promises.push(this._loadNodeAsync(`#/nodes/${node._index}`, node));
-            }
-
-            promises.push(this._loadAnimationsAsync());
-
-            return Promise.all(promises).then(() => {});
-        }
-
         public _loadSceneAsync(context: string, scene: _ILoaderScene): Promise<void> {
             const promise = GLTFLoaderExtension._LoadSceneAsync(this, context, scene);
             if (promise) {
@@ -1675,6 +1674,8 @@ module BABYLON.GLTF2 {
         }
 
         private _compileMaterialsAsync(): Promise<void> {
+            this._parent._startPerformanceCounter("Compile materials");
+
             const promises = new Array<Promise<void>>();
 
             if (this._gltf.materials) {
@@ -1697,10 +1698,14 @@ module BABYLON.GLTF2 {
                 }
             }
 
-            return Promise.all(promises).then(() => {});
+            return Promise.all(promises).then(() => {
+                this._parent._endPerformanceCounter("Compile materials");
+            });
         }
 
         private _compileShadowGeneratorsAsync(): Promise<void> {
+            this._parent._startPerformanceCounter("Compile shadow generators");
+
             const promises = new Array<Promise<void>>();
 
             const lights = this._babylonScene.lights;
@@ -1711,7 +1716,9 @@ module BABYLON.GLTF2 {
                 }
             }
 
-            return Promise.all(promises).then(() => {});
+            return Promise.all(promises).then(() => {
+                this._parent._endPerformanceCounter("Compile shadow generators");
+            });
         }
 
         public _applyExtensions<T>(actionAsync: (extension: GLTFLoaderExtension) => Nullable<Promise<T>>): Nullable<Promise<T>> {

+ 104 - 55
loaders/src/glTF/babylon.glTFFileLoader.ts

@@ -181,39 +181,6 @@ module BABYLON {
         public _normalizeAnimationGroupsToBeginAtZero = true;
 
         /**
-         * Defines if the loader logging is enabled.
-         */
-        public loggingEnabled = false;
-
-        /**
-         * Observable raised when the loader logs a message.
-         */
-        public readonly onLogObservable = new Observable<string>();
-
-        private _logIndentLevel = 0;
-        private static readonly _logSpaces = "                                ";
-
-        /** @hidden */
-        public _log(message: string): void {
-            if (this.loggingEnabled) {
-                const spaces = GLTFFileLoader._logSpaces.substr(0, this._logIndentLevel * 2);
-                this.onLogObservable.notifyObservers(`${spaces}${message}`);
-                Tools.Log(`${spaces}${message}`);
-            }
-        }
-
-        /** @hidden */
-        public _logOpen(message: string): void {
-            this._log(message);
-            this._logIndentLevel++;
-        }
-
-        /** @hidden */
-        public _logClose(): void {
-            --this._logIndentLevel;
-        }
-
-        /**
          * Function called before loading a url referenced by the asset.
          */
         public preprocessUrlAsync = (url: string) => Promise.resolve(url);
@@ -359,6 +326,52 @@ module BABYLON {
             return this._loader ? this._loader.state : null;
         }
 
+        /**
+         * Defines if the loader logging is enabled.
+         */
+        public get loggingEnabled(): boolean {
+            return this._loggingEnabled;
+        }
+
+        public set loggingEnabled(value: boolean) {
+            if (this._loggingEnabled === value) {
+                return;
+            }
+
+            this._loggingEnabled = value;
+
+            if (this._loggingEnabled) {
+                this._log = this._logEnabled;
+            }
+            else {
+                this._log = this._logDisabled;
+            }
+        }
+
+        /**
+         * Defines if the loader should capture performance counters.
+         */
+        public get capturePerformanceCounters(): boolean {
+            return this._capturePerformanceCounters;
+        }
+
+        public set capturePerformanceCounters(value: boolean) {
+            if (this._capturePerformanceCounters === value) {
+                return;
+            }
+
+            this._capturePerformanceCounters = value;
+
+            if (this._capturePerformanceCounters) {
+                this._startPerformanceCounter = this._startPerformanceCounterEnabled;
+                this._endPerformanceCounter = this._endPerformanceCounterEnabled;
+            }
+            else {
+                this._startPerformanceCounter = this._startPerformanceCounterDisabled;
+                this._endPerformanceCounter = this._endPerformanceCounterDisabled;
+            }
+        }
+
         // #endregion
 
         private _loader: Nullable<IGLTFLoader> = null;
@@ -483,19 +496,16 @@ module BABYLON {
         }
 
         private _parse(data: string | ArrayBuffer): IGLTFLoaderData {
+            this._startPerformanceCounter("Parse");
+
             let parsedData: IGLTFLoaderData;
             if (data instanceof ArrayBuffer) {
-                if (this.loggingEnabled) {
-                    this._log(`Parsing binary`);
-                }
-
+                this._log(`Parsing binary`);
                 parsedData = this._parseBinary(data);
             }
             else {
-                if (this.loggingEnabled) {
-                    this._log(`Parsing JSON`);
-                    this._log(`JSON length: ${data.length}`);
-                }
+                this._log(`Parsing JSON`);
+                this._log(`JSON length: ${data.length}`);
 
                 parsedData = {
                     json: JSON.parse(data),
@@ -506,23 +516,16 @@ module BABYLON {
             this.onParsedObservable.notifyObservers(parsedData);
             this.onParsedObservable.clear();
 
+            this._endPerformanceCounter("Parse");
             return parsedData;
         }
 
         private _getLoader(loaderData: IGLTFLoaderData): IGLTFLoader {
             const asset = (<any>loaderData.json).asset || {};
 
-            if (this.loggingEnabled) {
-                this._log(`Asset version: ${asset.version}`);
-
-                if (asset.minVersion) {
-                    this._log(`Asset minimum version: ${asset.minVersion}`);
-                }
-
-                if (asset.generator) {
-                    this._log(`Asset generator: ${asset.generator}`);
-                }
-            }
+            this._log(`Asset version: ${asset.version}`);
+            asset.minVersion && this._log(`Asset minimum version: ${asset.minVersion}`);
+            asset.generator && this._log(`Asset generator: ${asset.generator}`);
 
             const version = GLTFFileLoader._parseVersion(asset.version);
             if (!version) {
@@ -558,9 +561,7 @@ module BABYLON {
                 Magic: 0x46546C67
             };
 
-            if (this.loggingEnabled) {
-                this._log(`Binary length: ${data.byteLength}`);
-            }
+            this._log(`Binary length: ${data.byteLength}`);
 
             const binaryReader = new BinaryReader(data);
 
@@ -699,6 +700,54 @@ module BABYLON {
 
             return result;
         }
+
+        private static readonly _logSpaces = "                                ";
+        private _logIndentLevel = 0;
+        private _loggingEnabled = false;
+
+        /** @hidden */
+        public _log = this._logDisabled;
+
+        /** @hidden */
+        public _logOpen(message: string): void {
+            this._log(message);
+            this._logIndentLevel++;
+        }
+
+        /** @hidden */
+        public _logClose(): void {
+            --this._logIndentLevel;
+        }
+
+        private _logEnabled(message: string): void {
+            const spaces = GLTFFileLoader._logSpaces.substr(0, this._logIndentLevel * 2);
+            Tools.Log(`${spaces}${message}`);
+        }
+
+        private _logDisabled(message: string): void {
+        }
+
+        private _capturePerformanceCounters = false;
+
+        /** @hidden */
+        public _startPerformanceCounter = this._startPerformanceCounterDisabled;
+
+        /** @hidden */
+        public _endPerformanceCounter = this._endPerformanceCounterDisabled;
+
+        private _startPerformanceCounterEnabled(counterName: string): void {
+            Tools.StartPerformanceCounter(counterName);
+        }
+
+        private _startPerformanceCounterDisabled(counterName: string): void {
+        }
+
+        private _endPerformanceCounterEnabled(counterName: string): void {
+            Tools.EndPerformanceCounter(counterName);
+        }
+
+        private _endPerformanceCounterDisabled(counterName: string): void {
+        }
     }
 
     class BinaryReader {

+ 2 - 1
sandbox/index.js

@@ -264,8 +264,9 @@ if (BABYLON.Engine.isSupported()) {
 
     window.addEventListener("keydown", function (event) {
         // Press R to reload
-        if (event.keyCode === 82 && event.target.nodeName !== "INPUT") {
+        if (event.keyCode === 82 && event.target.nodeName !== "INPUT" && currentScene) {
             debugLayerLastActiveTab = currentScene.debugLayer.getActiveTab();
+
             if (assetUrl) {
                 loadFromAssetUrl();
             }

+ 54 - 0
src/Instrumentation/babylon.sceneInstrumentation.ts

@@ -30,6 +30,9 @@ module BABYLON {
         private _captureAnimationsTime = false;
         private _animationsTime = new PerfCounter();
 
+        private _captureCameraRenderTime = false;
+        private _cameraRenderTime = new PerfCounter();
+
         // Observers
         private _onBeforeActiveMeshesEvaluationObserver: Nullable<Observer<Scene>> = null;
         private _onAfterActiveMeshesEvaluationObserver: Nullable<Observer<Scene>> = null;
@@ -54,6 +57,9 @@ module BABYLON {
 
         private _onAfterAnimationsObserver: Nullable<Observer<Scene>> = null;
 
+        private _onBeforeCameraRenderObserver: Nullable<Observer<Camera>> = null;
+        private _onAfterCameraRenderObserver: Nullable<Observer<Camera>> = null;
+
         // Properties
         /**
          * Gets the perf counter used for active meshes evaluation time
@@ -390,6 +396,48 @@ module BABYLON {
         }
 
         /**
+         * Gets the perf counter used for camera render time capture
+         */
+        public get cameraRenderTimeCounter(): PerfCounter {
+            return this._cameraRenderTime;
+        }
+
+        /**
+         * Gets the camera render time capture status
+         */
+        public get captureCameraRenderTime(): boolean {
+            return this._captureCameraRenderTime;
+        }
+
+        /**
+         * Enable or disable the camera render time capture
+         */
+        public set captureCameraRenderTime(value: boolean) {
+            if (value === this._captureCameraRenderTime) {
+                return;
+            }
+
+            this._captureCameraRenderTime = value;
+
+            if (value) {
+                this._onBeforeCameraRenderObserver = this.scene.onBeforeCameraRenderObservable.add(camera => {
+                    this._cameraRenderTime.beginMonitoring();
+                    Tools.StartPerformanceCounter(`Rendering camera ${camera.name}`);
+                });
+
+                this._onAfterCameraRenderObserver = this.scene.onAfterCameraRenderObservable.add(camera => {
+                    this._cameraRenderTime.endMonitoring(false);
+                    Tools.EndPerformanceCounter(`Rendering camera ${camera.name}`);
+                });
+            } else {
+                this.scene.onBeforeCameraRenderObservable.remove(this._onBeforeCameraRenderObserver);
+                this._onBeforeCameraRenderObserver = null;
+                this.scene.onAfterCameraRenderObservable.remove(this._onAfterCameraRenderObserver);
+                this._onAfterCameraRenderObserver = null;
+            }
+        }
+
+        /**
          * Gets the perf counter used for draw calls
          */
         public get drawCallsCounter(): PerfCounter {
@@ -502,6 +550,12 @@ module BABYLON {
             this.scene.onAfterAnimationsObservable.remove(this._onAfterAnimationsObserver);
             this._onAfterAnimationsObserver = null;
 
+            this.scene.onBeforeCameraRenderObservable.remove(this._onBeforeCameraRenderObserver);
+            this._onBeforeCameraRenderObserver = null;
+
+            this.scene.onAfterCameraRenderObservable.remove(this._onAfterCameraRenderObserver);
+            this._onAfterCameraRenderObserver = null;
+
             (<any>this.scene) = null;
         }
     }

+ 0 - 4
src/babylon.scene.ts

@@ -4477,8 +4477,6 @@
             if (!this.activeCamera)
                 throw new Error("Active camera not set");
 
-            Tools.StartPerformanceCounter("Rendering camera " + this.activeCamera.name);
-
             // Viewport
             engine.setViewport(this.activeCamera.viewport);
 
@@ -4657,8 +4655,6 @@
             this._alternateRendering = false;
 
             this.onAfterCameraRenderObservable.notifyObservers(this.activeCamera);
-
-            Tools.EndPerformanceCounter("Rendering camera " + this.activeCamera.name);
         }
 
         private _processSubCameras(camera: Camera): void {