Jelajahi Sumber

Merge pull request #1214 from nockawa/PerfMetrics

Tools and rendering improvement with PerfCounter
David Catuhe 9 tahun lalu
induk
melakukan
55d7aa5313

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

@@ -9,6 +9,7 @@
 - MapTexture: add `supersample` mode to double font quality. ([nockawa](https://github.com/nockawa))
 - new `invertUV` parameter an all ribbon based shapes : ribbon, tube, lathe, basic and custom extrusion
 - Text2D: new `fontSuperSample` setting to use high quality font.
+- PerfCounter class added to monitor time/counter and expose min/max/average/lastSecondAverage/current metrics. Updated engine/scene current counter to use this class, exposing new properties as well to access the PerfCounter object. ([nockawa](https://github.com/nockawa))
 
 ### Exporters
     

+ 1 - 1
src/Bones/babylon.skeleton.ts

@@ -258,7 +258,7 @@
 
             this._isDirty = false;
 
-            this._scene._activeBones += this.bones.length;
+            this._scene._activeBones.addCount(this.bones.length, false);
         }
 
         public getAnimatables(): IAnimatable[] {

+ 1 - 1
src/Materials/Textures/babylon.renderTargetTexture.ts

@@ -254,7 +254,7 @@
 
                         for (var subIndex = 0; subIndex < mesh.subMeshes.length; subIndex++) {
                             var subMesh = mesh.subMeshes[subIndex];
-                            scene._activeIndices += subMesh.indexCount;
+                            scene._activeIndices.addCount(subMesh.indexCount, false);
                             this._renderingManager.dispatch(subMesh);
                         }
                     }

+ 5 - 5
src/Rendering/babylon.renderingManager.ts

@@ -22,7 +22,7 @@
 
             // Particles
             var activeCamera = this._scene.activeCamera;
-            var beforeParticlesDate = Tools.Now;
+            this._scene._particlesDuration.beginMonitoring();
             for (var particleIndex = 0; particleIndex < this._scene._activeParticleSystems.length; particleIndex++) {
                 var particleSystem = this._scene._activeParticleSystems.data[particleIndex];
 
@@ -37,10 +37,10 @@
                 this._clearDepthBuffer();
 
                 if (!particleSystem.emitter.position || !activeMeshes || activeMeshes.indexOf(particleSystem.emitter) !== -1) {
-                    this._scene._activeParticles += particleSystem.render();
+                    this._scene._activeParticles.addCount(particleSystem.render(), false);
                 }
             }
-            this._scene._particlesDuration += Tools.Now - beforeParticlesDate;
+            this._scene._particlesDuration.endMonitoring(false);
         }
 
         private _renderSprites(index: number): void {
@@ -50,7 +50,7 @@
 
             // Sprites       
             var activeCamera = this._scene.activeCamera;
-            var beforeSpritessDate = Tools.Now;
+            this._scene._spritesDuration.beginMonitoring();
             for (var id = 0; id < this._scene.spriteManagers.length; id++) {
                 var spriteManager = this._scene.spriteManagers[id];
 
@@ -59,7 +59,7 @@
                     spriteManager.render();
                 }
             }
-            this._scene._spritesDuration += Tools.Now - beforeSpritessDate;
+            this._scene._spritesDuration.endMonitoring(false);
         }
 
         private _clearDepthBuffer(): void {

+ 133 - 0
src/Tools/babylon.tools.ts

@@ -1020,6 +1020,139 @@
     }
 
     /**
+     * This class is used to track a performance counter which is number based.
+     * The user has access to many properties which give statistics of different nature
+     * 
+     * The implementer can track two kinds of Performance Counter: time and count
+     * For time you can optionally call fetchNewFrame() to notify the start of a new frame to monitor, then call beginMonitoring() to start and endMonitoring() to record the lapsed time. endMonitoring takes a newFrame parameter for you to specify if the monitored time should be set for a new frame or accumulated to the current frame being monitored.
+     * For count you first have to call fetchNewFrame() to notify the start of a new frame to monitor, then call addCount() how many time required to increment the count value you monitor.
+     */
+    export class PerfCounter {
+        /**
+         * Returns the smallest value ever
+         */
+        public get min(): number {
+            return this._min;
+        }
+
+        /**
+         * Returns the biggest value ever
+         */
+        public get max(): number {
+            return this._max;
+        }
+
+        /**
+         * Returns the average value since the performance counter is running
+         */
+        public get average(): number {
+            return this._average;
+        }
+
+        /**
+         * Returns the average value of the last second the counter was monitored
+         */
+        public get lastSecAverage(): number {
+            return this._lastSecAverage;
+        }
+
+        /**
+         * Returns the current value
+         */
+        public get current(): number {
+            return this._current;
+        }
+
+        constructor() {
+            this._startMonitoringTime = 0;
+            this._min                 = 0;
+            this._max                 = 0;
+            this._average             = 0;
+            this._lastSecAverage      = 0;
+            this._current             = 0;
+            this._totalValueCount     = 0;
+            this._totalAccumulated    = 0;
+            this._lastSecAccumulated  = 0;
+            this._lastSecTime         = 0;
+            this._lastSecValueCount   = 0;
+        }
+
+        /**
+         * Call this method to start monitoring a new frame.
+         * This scenario is typically used when you accumulate monitoring time many times for a single frame, you call this method at the start of the frame, then beginMonitoring to start recording and endMonitoring(false) to accumulated the recorded time to the PerfCounter or addCount() to accumulate a monitored count.
+         */
+        public fetchNewFrame() {
+            this._totalValueCount++;
+            this._current = 0;
+        }
+
+        /**
+         * Call this method to monitor a count of something (e.g. mesh drawn in viewport count)
+         * @param newCount the count value to add to the monitored count
+         * @param fetchResult true when it's the last time in the frame you add to the counter and you wish to update the statistics properties (min/max/average), false if you only want to update statistics.
+         */
+        public addCount(newCount: number, fetchResult: boolean) {
+            this._current += newCount;
+            if (fetchResult) {
+                this._fetchResult();
+            }
+        }
+
+        /**
+         * Start monitoring this performance counter
+         */
+        public beginMonitoring() {
+            this._startMonitoringTime = Tools.Now;
+        }
+
+        /**
+         * Compute the time lapsed since the previous beginMonitoring() call.
+         * @param newFrame true by default to fetch the result and monitor a new frame, if false the time monitored will be added to the current frame counter
+         */
+        public endMonitoring(newFrame: boolean = true) {
+            if (newFrame) {
+                this.fetchNewFrame();
+            }
+
+            let currentTime = Tools.Now;
+            this._current = currentTime - this._startMonitoringTime;
+
+            if (newFrame) {
+                this._fetchResult();
+            }
+        }
+
+        private _fetchResult() {
+            this._totalAccumulated += this._current;
+
+            // Min/Max update
+            this._min = Math.min(this._min, this._current);
+            this._max = Math.max(this._max, this._current);
+            this._average = this._totalAccumulated / this._totalValueCount;
+
+            // Reset last sec?
+            if ((this._startMonitoringTime - this._lastSecTime) > 1000) {
+                this._lastSecAverage = this._lastSecAccumulated / this._lastSecValueCount;
+                this._lastSecTime = this._startMonitoringTime;
+                this._lastSecAccumulated = 0;
+                this._lastSecValueCount = 0;
+            }
+        }
+
+        private _startMonitoringTime: number;
+        private _min: number;
+        private _max: number;
+        private _average: number;
+        private _current: number;
+        private _totalValueCount: number;
+        private _totalAccumulated: number;
+        private _lastSecAverage: number;
+        private _lastSecAccumulated: number;
+        private _lastSecTime: number;
+        private _lastSecValueCount: number;
+    }
+
+    /**
      * Use this className as a decorator on a given class definition to add it a name.
      * You can then use the Tools.getClassName(obj) on an instance to retrieve its class name.
      * This method is the only way to get it done in all cases, even if the .js file declaring the class is minified

+ 7 - 8
src/babylon.engine.ts

@@ -312,7 +312,7 @@
 
         private _loadingScreen: ILoadingScreen;
 
-        private _drawCalls = 0;
+        public  _drawCalls = new PerfCounter();
 
         private _glVersion: string;
         private _glRenderer: string;
@@ -595,12 +595,11 @@
         }
 
         public get drawCalls(): number {
-            return this._drawCalls;
+            return this._drawCalls.current;
         }
 
-        // Methods
-        public resetDrawCalls(): void {
-            this._drawCalls = 0;
+        public get drawCallsPerfCounter(): PerfCounter {
+            return this._drawCalls;
         }
 
         public getDepthFunction(): number {
@@ -1165,7 +1164,7 @@
             // Apply states
             this.applyStates();
 
-            this._drawCalls++;
+            this._drawCalls.addCount(1, false);
             // Render
             var indexFormat = this._uintIndicesCurrentlySet ? this._gl.UNSIGNED_INT : this._gl.UNSIGNED_SHORT;
             var mult = this._uintIndicesCurrentlySet ? 4 : 2;
@@ -1180,7 +1179,7 @@
         public drawPointClouds(verticesStart: number, verticesCount: number, instancesCount?: number): void {
             // Apply states
             this.applyStates();
-            this._drawCalls++;
+            this._drawCalls.addCount(1, false);
 
             if (instancesCount) {
                 this._caps.instancedArrays.drawArraysInstancedANGLE(this._gl.POINTS, verticesStart, verticesCount, instancesCount);
@@ -1193,7 +1192,7 @@
         public drawUnIndexed(useTriangles: boolean, verticesStart: number, verticesCount: number, instancesCount?: number): void {
             // Apply states
             this.applyStates();
-            this._drawCalls++;
+            this._drawCalls.addCount(1, false);
 
             if (instancesCount) {
                 this._caps.instancedArrays.drawArraysInstancedANGLE(useTriangles ? this._gl.TRIANGLES : this._gl.LINES, verticesStart, verticesCount, instancesCount);

+ 92 - 34
src/babylon.scene.ts

@@ -419,16 +419,24 @@
 
         // Private
         private _engine: Engine;
-        private _totalVertices = 0;
-        public _activeIndices = 0;
-        public _activeParticles = 0;
-        private _lastFrameDuration = 0;
-        private _evaluateActiveMeshesDuration = 0;
-        private _renderTargetsDuration = 0;
-        public _particlesDuration = 0;
-        private _renderDuration = 0;
-        public _spritesDuration = 0;
-        private _animationRatio = 0;
+
+        // Performance counters
+        private _totalMeshesCounter           = new PerfCounter();
+        private _totalLightsCounter           = new PerfCounter();
+        private _totalMaterialsCounter        = new PerfCounter();
+        private _totalTexturesCounter         = new PerfCounter();
+        private _totalVertices                = new PerfCounter();
+        public  _activeIndices                = new PerfCounter();
+        public  _activeParticles              = new PerfCounter();
+        private _lastFrameDuration            = new PerfCounter();
+        private _evaluateActiveMeshesDuration = new PerfCounter();
+        private _renderTargetsDuration        = new PerfCounter();
+        public  _particlesDuration            = new PerfCounter();
+        private _renderDuration               = new PerfCounter();
+        public  _spritesDuration              = new PerfCounter();
+        private _animationRatio               = new PerfCounter();
+        public  _activeBones                  = new PerfCounter();
+
         private _animationStartDate: number;
         public _cachedMaterial: Material;
 
@@ -445,7 +453,6 @@
         public _activeParticleSystems = new SmartArray<ParticleSystem>(256);
         private _activeSkeletons = new SmartArray<Skeleton>(32);
         private _softwareSkinnedMeshes = new SmartArray<Mesh>(32);
-        public _activeBones = 0;
 
         private _renderingManager: RenderingManager;
         private _physicsEngine: PhysicsEngine;
@@ -584,27 +591,51 @@
         }
 
         public getTotalVertices(): number {
+            return this._totalVertices.current;
+        }
+
+        public get totalVerticesPerfCounter(): PerfCounter {
             return this._totalVertices;
         }
 
         public getActiveIndices(): number {
+            return this._activeIndices.current;
+        }
+
+        public get totalActiveIndicesPerfCounter(): PerfCounter {
             return this._activeIndices;
         }
 
         public getActiveParticles(): number {
+            return this._activeParticles.current;
+        }
+
+        public get activeParticlesPerfCounter(): PerfCounter {
             return this._activeParticles;
         }
 
         public getActiveBones(): number {
+            return this._activeBones.current;
+        }
+
+        public get activeBonesPerfCounter(): PerfCounter {
             return this._activeBones;
         }
 
         // Stats
         public getLastFrameDuration(): number {
+            return this._lastFrameDuration.current;
+        }
+
+        public get lastFramePerfCounter(): PerfCounter {
             return this._lastFrameDuration;
         }
 
         public getEvaluateActiveMeshesDuration(): number {
+            return this._evaluateActiveMeshesDuration.current;
+        }
+
+        public get evaluateActiveMeshesDurationPerfCounter(): PerfCounter {
             return this._evaluateActiveMeshesDuration;
         }
 
@@ -613,22 +644,38 @@
         }
 
         public getRenderTargetsDuration(): number {
-            return this._renderTargetsDuration;
+            return this._renderTargetsDuration.current;
         }
 
         public getRenderDuration(): number {
+            return this._renderDuration.current;
+        }
+
+        public get renderDurationPerfCounter(): PerfCounter {
             return this._renderDuration;
         }
 
         public getParticlesDuration(): number {
+            return this._particlesDuration.current;
+        }
+
+        public get particlesDurationPerfCounter(): PerfCounter {
             return this._particlesDuration;
         }
 
         public getSpritesDuration(): number {
+            return this._spritesDuration.current;
+        }
+
+        public get spriteDuractionPerfCounter(): PerfCounter {
             return this._spritesDuration;
         }
 
         public getAnimationRatio(): number {
+            return this._animationRatio.current;
+        }
+
+        public animationRatioPerfCounter(): PerfCounter {
             return this._animationRatio;
         }
 
@@ -1774,7 +1821,7 @@
                     }
 
                     // Dispatch
-                    this._activeIndices += subMesh.indexCount;
+                    this._activeIndices.addCount(subMesh.indexCount, false);
                     this._renderingManager.dispatch(subMesh);
                 }
             }
@@ -1815,7 +1862,7 @@
                     continue;
                 }
 
-                this._totalVertices += mesh.getTotalVertices();
+                this._totalVertices.addCount(mesh.getTotalVertices(), false);
 
                 if (!mesh.isReady() || !mesh.isEnabled()) {
                     continue;
@@ -1847,6 +1894,7 @@
             }
 
             // Particle systems
+            this._particlesDuration.beginMonitoring();
             var beforeParticlesDate = Tools.Now;
             if (this.particlesEnabled) {
                 Tools.StartPerformanceCounter("Particles", this.particleSystems.length > 0);
@@ -1864,7 +1912,7 @@
                 }
                 Tools.EndPerformanceCounter("Particles", this.particleSystems.length > 0);
             }
-            this._particlesDuration += Tools.Now - beforeParticlesDate;
+            this._particlesDuration.endMonitoring(false);
         }
 
         private _activeMesh(mesh: AbstractMesh): void {
@@ -1915,6 +1963,7 @@
 
         private _renderForCamera(camera: Camera): void {
             var engine = this._engine;
+            var startTime = Tools.Now;
 
             this.activeCamera = camera;
 
@@ -1934,10 +1983,10 @@
             this.onBeforeCameraRenderObservable.notifyObservers(this.activeCamera);
 
             // Meshes
-            var beforeEvaluateActiveMeshesDate = Tools.Now;
+            this._evaluateActiveMeshesDuration.beginMonitoring();
             Tools.StartPerformanceCounter("Active meshes evaluation");
             this._evaluateActiveMeshes();
-            this._evaluateActiveMeshesDuration += Tools.Now - beforeEvaluateActiveMeshesDate;
+            this._evaluateActiveMeshesDuration.endMonitoring(false);
             Tools.EndPerformanceCounter("Active meshes evaluation");
 
             // Software skinning
@@ -1948,6 +1997,7 @@
             }
 
             // Render targets
+            this._renderTargetsDuration.beginMonitoring();
             var beforeRenderTargetDate = Tools.Now;
             if (this.renderTargetsEnabled && this._renderTargets.length > 0) {
                 this._intermediateRendering = true;
@@ -1966,12 +2016,12 @@
                 this._renderId++;
                 engine.restoreDefaultFramebuffer(); // Restore back buffer
             }
-            this._renderTargetsDuration += Tools.Now - beforeRenderTargetDate;
+            this._renderTargetsDuration.endMonitoring(false);
 
             // Prepare Frame
             this.postProcessManager._prepareFrame();
 
-            var beforeRenderDate = Tools.Now;
+            this._renderDuration.beginMonitoring();
             // Backgrounds
             var layerIndex;
             var layer;
@@ -2024,7 +2074,7 @@
                 engine.setDepthBuffer(true);
             }
 
-            this._renderDuration += Tools.Now - beforeRenderDate;
+            this._renderDuration.endMonitoring(false);
 
             // Finalize frame
             this.postProcessManager._finalizeFrame(camera.isIntermediate);
@@ -2098,17 +2148,17 @@
         }
 
         public render(): void {
-            var startDate = Tools.Now;
-            this._particlesDuration = 0;
-            this._spritesDuration = 0;
-            this._activeParticles = 0;
-            this._renderDuration = 0;
-            this._renderTargetsDuration = 0;
-            this._evaluateActiveMeshesDuration = 0;
-            this._totalVertices = 0;
-            this._activeIndices = 0;
-            this._activeBones = 0;
-            this.getEngine().resetDrawCalls();
+            this._lastFrameDuration.beginMonitoring();
+            this._particlesDuration.fetchNewFrame();
+            this._spritesDuration.fetchNewFrame();
+            this._activeParticles.fetchNewFrame();
+            this._renderDuration.fetchNewFrame();
+            this._renderTargetsDuration.fetchNewFrame();
+            this._evaluateActiveMeshesDuration.fetchNewFrame();
+            this._totalVertices.fetchNewFrame();
+            this._activeIndices.fetchNewFrame();
+            this._activeBones.fetchNewFrame();
+            this.getEngine()._drawCalls.fetchNewFrame();;
             this._meshesForIntersections.reset();
             this.resetCachedMaterial();
 
@@ -2126,7 +2176,7 @@
 
             // Animations
             var deltaTime = Math.max(Scene.MinDeltaTime, Math.min(this._engine.getDeltaTime(), Scene.MaxDeltaTime));
-            this._animationRatio = deltaTime * (60.0 / 1000.0);
+            this._animationRatio.addCount(deltaTime * (60.0 / 1000.0), true);
             this._animate();
 
             // Physics
@@ -2140,6 +2190,7 @@
             this.onBeforeRenderObservable.notifyObservers(this);
 
             // Customs render targets
+            this._renderTargetsDuration.beginMonitoring();
             var beforeRenderTargetDate = Tools.Now;
             var engine = this.getEngine();
             var currentActiveCamera = this.activeCamera;
@@ -2172,7 +2223,7 @@
             if (this.customRenderTargets.length > 0) { // Restore back buffer
                 engine.restoreDefaultFramebuffer();
             }
-            this._renderTargetsDuration += Tools.Now - beforeRenderTargetDate;
+            this._renderTargetsDuration.endMonitoring();
             this.activeCamera = currentActiveCamera;
 
             // Procedural textures
@@ -2257,7 +2308,14 @@
             }
 
             Tools.EndPerformanceCounter("Scene rendering");
-            this._lastFrameDuration = Tools.Now - startDate;
+            this._lastFrameDuration.endMonitoring();
+            this._totalMeshesCounter.addCount(this.meshes.length, true);
+            this._totalLightsCounter.addCount(this.lights.length, true);
+            this._totalMaterialsCounter.addCount(this.materials.length, true);
+            this._totalTexturesCounter.addCount(this.textures.length, true);
+            this._activeBones.addCount(0, true);
+            this._activeIndices.addCount(0, true);
+            this._activeParticles.addCount(0, true);
         }
 
         private _updateAudioParameters() {