Quellcode durchsuchen

Merge pull request #1227 from nockawa/PerfMetrics

Canvas2D: PerfMetric and bug fixes + misc
David Catuhe vor 9 Jahren
Ursprung
Commit
4883cf3e85

+ 12 - 0
src/Canvas2d/babylon.bounding2d.ts

@@ -133,6 +133,18 @@
             return r;
         }
 
+        public clear() {
+            this.center.copyFromFloats(0, 0);
+            this.radius = 0;
+            this.extent.copyFromFloats(0, 0);
+        }
+
+        public copyFrom(src: BoundingInfo2D) {
+            this.center.copyFrom(src.center);
+            this.radius = src.radius;
+            this.extent.copyFrom(src.extent);
+        }
+
         /**
          * return the max extend of the bounding info
          */

+ 192 - 0
src/Canvas2d/babylon.canvas2d.ts

@@ -69,6 +69,20 @@
         }) {
             super(settings);
 
+            this._drawCallsOpaqueCounter       = new PerfCounter();
+            this._drawCallsAlphaTestCounter    = new PerfCounter();
+            this._drawCallsTransparentCounter  = new PerfCounter();
+            this._groupRenderCounter           = new PerfCounter();
+            this._updateTransparentDataCounter = new PerfCounter();
+            this._cachedGroupRenderCounter     = new PerfCounter();
+            this._updateCachedStateCounter     = new PerfCounter();
+            this._updateLayoutCounter          = new PerfCounter();
+            this._updatePositioningCounter     = new PerfCounter();
+            this._updateLocalTransformCounter  = new PerfCounter();
+            this._updateGlobalTransformCounter = new PerfCounter();
+
+            this._profileInfoText = null;
+
             Prim2DBase._isCanvasInit = false;
 
             if (!settings) {
@@ -156,6 +170,50 @@
             this._setupInteraction(enableInteraction);
         }
 
+        public get drawCallsOpaqueCounter(): PerfCounter {
+            return this._drawCallsOpaqueCounter;
+        }
+
+        public get drawCallsAlphaTestCounter(): PerfCounter {
+            return this._drawCallsAlphaTestCounter;
+        }
+
+        public get drawCallsTransparentCounter(): PerfCounter {
+            return this._drawCallsTransparentCounter;
+        }
+
+        public get groupRenderCounter(): PerfCounter {
+            return this._groupRenderCounter;
+        }
+
+        public get updateTransparentDataCounter(): PerfCounter {
+            return this._updateTransparentDataCounter;
+        }
+
+        public get cachedGroupRenderCounter(): PerfCounter {
+            return this._cachedGroupRenderCounter;
+        }
+
+        public get updateCachedStateCounter(): PerfCounter {
+            return this._updateCachedStateCounter;
+        }
+
+        public get updateLayoutCounter(): PerfCounter {
+            return this._updateLayoutCounter;
+        }
+
+        public get updatePositioningCounter(): PerfCounter {
+            return this._updatePositioningCounter;
+        }
+
+        public get updateLocalTransformCounter(): PerfCounter {
+            return this._updateLocalTransformCounter;
+        }
+
+        public get updateGlobalTransformCounter(): PerfCounter {
+            return this._updateGlobalTransformCounter;
+        }
+
         protected _canvasPreInit(settings: any) {
             let cachingStrategy = (settings.cachingStrategy == null) ? Canvas2D.CACHESTRATEGY_DONTCACHE : settings.cachingStrategy;
             this._cachingStrategy = cachingStrategy;
@@ -866,12 +924,127 @@
             return this.__engineData;
         }
 
+        public createCanvasProfileInfoCanvas(): Canvas2D {
+            let canvas = new ScreenSpaceCanvas2D(this.scene, {
+                id: "ProfileInfoCanvas", cachingStrategy: Canvas2D.CACHESTRATEGY_DONTCACHE, children:
+                [
+                    new Rectangle2D({
+                        id: "ProfileBorder", border: "#FFFFFFFF", borderThickness: 2, roundRadius: 5, marginAlignment: "h: left, v: top", margin: "10", padding: "10", children:
+                        [
+                            new Text2D("Stats", { id: "ProfileInfoText", marginAlignment: "h: left, v: top", fontName: "10pt Lucida Console" })
+                        ]
+                    })
+
+                ]
+            });
+
+            this._profileInfoText = <Text2D>canvas.findById("ProfileInfoText");
+
+            return canvas;
+        }
+
         private checkBackgroundAvailability() {
             if (this._cachingStrategy === Canvas2D.CACHESTRATEGY_TOPLEVELGROUPS) {
                 throw Error("Can't use Canvas Background with the caching strategy TOPLEVELGROUPS");
             }
         }
 
+        private _initPerfMetrics() {
+            this._drawCallsOpaqueCounter.fetchNewFrame();
+            this._drawCallsAlphaTestCounter.fetchNewFrame();
+            this._drawCallsTransparentCounter.fetchNewFrame();
+            this._groupRenderCounter.fetchNewFrame();
+            this._updateTransparentDataCounter.fetchNewFrame();
+            this._cachedGroupRenderCounter.fetchNewFrame();
+            this._updateCachedStateCounter.fetchNewFrame();
+            this._updateLayoutCounter.fetchNewFrame();
+            this._updatePositioningCounter.fetchNewFrame();
+            this._updateLocalTransformCounter.fetchNewFrame();
+            this._updateGlobalTransformCounter.fetchNewFrame();
+        }
+
+        private _fetchPerfMetrics() {
+            this._drawCallsOpaqueCounter.addCount(0, true);
+            this._drawCallsAlphaTestCounter.addCount(0, true);
+            this._drawCallsTransparentCounter.addCount(0, true);
+            this._groupRenderCounter.addCount(0, true);
+            this._updateTransparentDataCounter.addCount(0, true);
+            this._cachedGroupRenderCounter.addCount(0, true);
+            this._updateCachedStateCounter.addCount(0, true);
+            this._updateLayoutCounter.addCount(0, true);
+            this._updatePositioningCounter.addCount(0, true);
+            this._updateLocalTransformCounter.addCount(0, true);
+            this._updateGlobalTransformCounter.addCount(0, true);
+        }
+
+        private _updateProfileCanvas() {
+            if (this._profileInfoText == null) {
+                return;
+            }
+
+            let format = (v: number) => (Math.round(v*100)/100).toString();
+
+            let p = `Draw Calls:\n` +
+                    ` - Opaque:      ${this.drawCallsOpaqueCounter.current}, (avg:${format(this.drawCallsOpaqueCounter.lastSecAverage)}, t:${format(this.drawCallsOpaqueCounter.total)})\n` +
+                    ` - AlphaTest:   ${this.drawCallsAlphaTestCounter.current}, (avg:${format(this.drawCallsAlphaTestCounter.lastSecAverage)}, t:${format(this.drawCallsAlphaTestCounter.total)})\n` +
+                    ` - Transparent: ${this.drawCallsTransparentCounter.current}, (avg:${format(this.drawCallsTransparentCounter.lastSecAverage)}, t:${format(this.drawCallsTransparentCounter.total)})\n` +
+                    `Group Render: ${this.groupRenderCounter.current}, (avg:${format(this.groupRenderCounter.lastSecAverage)}, t:${format(this.groupRenderCounter.total)})\n` + 
+                    `Update Transparent Data: ${this.updateTransparentDataCounter.current}, (avg:${format(this.updateTransparentDataCounter.lastSecAverage)}, t:${format(this.updateTransparentDataCounter.total)})\n` + 
+                    `Cached Group Render: ${this.cachedGroupRenderCounter.current}, (avg:${format(this.cachedGroupRenderCounter.lastSecAverage)}, t:${format(this.cachedGroupRenderCounter.total)})\n` + 
+                    `Update Cached States: ${this.updateCachedStateCounter.current}, (avg:${format(this.updateCachedStateCounter.lastSecAverage)}, t:${format(this.updateCachedStateCounter.total)})\n` + 
+                    ` - Update Layout: ${this.updateLayoutCounter.current}, (avg:${format(this.updateLayoutCounter.lastSecAverage)}, t:${format(this.updateLayoutCounter.total)})\n` + 
+                    ` - Update Positioning: ${this.updatePositioningCounter.current}, (avg:${format(this.updatePositioningCounter.lastSecAverage)}, t:${format(this.updatePositioningCounter.total)})\n` + 
+                    ` - Update Local  Trans: ${this.updateLocalTransformCounter.current}, (avg:${format(this.updateLocalTransformCounter.lastSecAverage)}, t:${format(this.updateLocalTransformCounter.total)})\n` + 
+                    ` - Update Global Trans: ${this.updateGlobalTransformCounter.current}, (avg:${format(this.updateGlobalTransformCounter.lastSecAverage)}, t:${format(this.updateGlobalTransformCounter.total)})\n`;
+            this._profileInfoText.text = p;
+        }
+
+        public _addDrawCallCount(count: number, renderMode: number) {
+            switch (renderMode) {
+                case Render2DContext.RenderModeOpaque:
+                    this._drawCallsOpaqueCounter.addCount(count, false);
+                    return;
+                case Render2DContext.RenderModeAlphaTest:
+                    this._drawCallsAlphaTestCounter.addCount(count, false);
+                    return;
+                case Render2DContext.RenderModeTransparent:
+                    this._drawCallsTransparentCounter.addCount(count, false);
+                    return;
+            }
+        }
+
+        public _addGroupRenderCount(count: number) {
+            this._groupRenderCounter.addCount(count, false);
+        }
+
+        public _addUpdateTransparentDataCount(count: number) {
+            this._updateTransparentDataCounter.addCount(count, false);
+        }
+
+        public addCachedGroupRenderCounter(count: number) {
+            this._cachedGroupRenderCounter.addCount(count, false);
+        }
+
+        public addUpdateCachedStateCounter(count: number) {
+            this._updateCachedStateCounter.addCount(count, false);
+        }
+
+        public addUpdateLayoutCounter(count: number) {
+            this._updateLayoutCounter.addCount(count, false);
+        }
+
+        public addUpdatePositioningCounter(count: number) {
+            this._updatePositioningCounter.addCount(count, false);
+        }
+
+        public addupdateLocalTransformCounter(count: number) {
+            this._updateLocalTransformCounter.addCount(count, false);
+        }
+
+        public addUpdateGlobalTransformCounter(count: number) {
+            this._updateGlobalTransformCounter.addCount(count, false);
+        }
+
         private __engineData: Canvas2DEngineBoundData;
         private _interactionEnabled: boolean;
         private _primPointerInfo: PrimitivePointerInfo;
@@ -907,6 +1080,20 @@
 
         public _renderingSize: Size;
 
+        private _drawCallsOpaqueCounter      : PerfCounter;
+        private _drawCallsAlphaTestCounter   : PerfCounter;
+        private _drawCallsTransparentCounter : PerfCounter;
+        private _groupRenderCounter          : PerfCounter;
+        private _updateTransparentDataCounter: PerfCounter;
+        private _cachedGroupRenderCounter    : PerfCounter;
+        private _updateCachedStateCounter    : PerfCounter;
+        private _updateLayoutCounter         : PerfCounter;
+        private _updatePositioningCounter    : PerfCounter;
+        private _updateGlobalTransformCounter: PerfCounter;
+        private _updateLocalTransformCounter : PerfCounter;
+
+        private _profileInfoText: Text2D;
+
         protected onPrimBecomesDirty() {
             this._addPrimToDirtyList(this);
         }
@@ -1038,6 +1225,8 @@
          */
         private _render() {
 
+            this._initPerfMetrics();
+
             this._updateTrackedNodes();
 
             this._updateCanvasState(false);
@@ -1066,6 +1255,9 @@
             if (this._cachingStrategy === Canvas2D.CACHESTRATEGY_CANVAS && this._cachedCanvasGroup) {
                 this._cachedCanvasGroup._renderCachedCanvas();
             }
+
+            this._fetchPerfMetrics();
+            this._updateProfileCanvas();
         }
 
         /**

+ 6 - 1
src/Canvas2d/babylon.ellipse2d.ts

@@ -29,7 +29,8 @@
                 this.effectsReady = true;
             }
 
-            var engine = instanceInfo.owner.owner.engine;
+            let canvas = instanceInfo.owner.owner;
+            var engine = canvas.engine;
 
             let depthFunction = 0;
             if (this.effectFill && this.effectBorder) {
@@ -56,10 +57,12 @@
                         this.instancingFillAttributes = this.loadInstancingAttributes(Shape2D.SHAPE2D_FILLPARTID, effect);
                     }
 
+                    canvas._addDrawCallCount(1, context.renderMode);
                     engine.updateAndBindInstancesBuffer(pid._partBuffer, null, this.instancingFillAttributes);
                     engine.draw(true, 0, this.fillIndicesCount, pid._partData.usedElementCount);
                     engine.unbindInstanceAttributes();
                 } else {
+                    canvas._addDrawCallCount(context.partDataEndIndex - context.partDataStartIndex, context.renderMode);
                     for (let i = context.partDataStartIndex; i < context.partDataEndIndex; i++) {
                         this.setupUniforms(effect, partIndex, pid._partData, i);
                         engine.draw(true, 0, this.fillIndicesCount);                        
@@ -84,10 +87,12 @@
                         this.instancingBorderAttributes = this.loadInstancingAttributes(Shape2D.SHAPE2D_BORDERPARTID, effect);
                     }
 
+                    canvas._addDrawCallCount(1, context.renderMode);
                     engine.updateAndBindInstancesBuffer(pid._partBuffer, null, this.instancingBorderAttributes);
                     engine.draw(true, 0, this.borderIndicesCount, pid._partData.usedElementCount);
                     engine.unbindInstanceAttributes();
                 } else {
+                    canvas._addDrawCallCount(context.partDataEndIndex - context.partDataStartIndex, context.renderMode);
                     for (let i = context.partDataStartIndex; i < context.partDataEndIndex; i++) {
                         this.setupUniforms(effect, partIndex, pid._partData, i);
                         engine.draw(true, 0, this.borderIndicesCount);

+ 5 - 0
src/Canvas2d/babylon.group2d.ts

@@ -269,6 +269,7 @@
         }
 
         public _renderCachedCanvas() {
+            this.owner._addGroupRenderCount(1);
             this.updateCachedStates(true);
             let context = new PrepareRender2DContext();
             this._prepareGroupRender(context);
@@ -412,6 +413,8 @@
 
             // Render the primitives if needed: either if we don't cache the content or if the content is cached but has changed
             if (!this.isCachedGroup || this._cacheGroupDirty) {
+                this.owner._addGroupRenderCount(1);
+
                 if (this.isCachedGroup) {
                     this._bindCacheTarget();
                 } else {
@@ -516,6 +519,8 @@
         }
 
         private _updateTransparentData() {
+            this.owner._addUpdateTransparentDataCount(1);
+
             let rd = this._renderableData;
 
             // If null, there was no change of ZOrder, we have nothing to do

+ 6 - 2
src/Canvas2d/babylon.lines2d.ts

@@ -28,8 +28,8 @@
                 }
                 this.effectsReady = true;
             }
-
-            var engine = instanceInfo.owner.owner.engine;
+            let canvas = instanceInfo.owner.owner;
+            var engine = canvas.engine;
 
             let depthFunction = 0;
             if (this.effectFill && this.effectBorder) {
@@ -56,10 +56,12 @@
                         this.instancingFillAttributes = this.loadInstancingAttributes(Shape2D.SHAPE2D_FILLPARTID, effect);
                     }
 
+                    canvas._addDrawCallCount(1, context.renderMode);
                     engine.updateAndBindInstancesBuffer(pid._partBuffer, null, this.instancingFillAttributes);
                     engine.draw(true, 0, this.fillIndicesCount, pid._partData.usedElementCount);
                     engine.unbindInstanceAttributes();
                 } else {
+                    canvas._addDrawCallCount(context.partDataEndIndex - context.partDataStartIndex, context.renderMode);
                     for (let i = context.partDataStartIndex; i < context.partDataEndIndex; i++) {
                         this.setupUniforms(effect, partIndex, pid._partData, i);
                         engine.draw(true, 0, this.fillIndicesCount);
@@ -84,10 +86,12 @@
                         this.instancingBorderAttributes = this.loadInstancingAttributes(Shape2D.SHAPE2D_BORDERPARTID, effect);
                     }
 
+                    canvas._addDrawCallCount(1, context.renderMode);
                     engine.updateAndBindInstancesBuffer(pid._partBuffer, null, this.instancingBorderAttributes);
                     engine.draw(true, 0, this.borderIndicesCount, pid._partData.usedElementCount);
                     engine.unbindInstanceAttributes();
                 } else {
+                    canvas._addDrawCallCount(context.partDataEndIndex - context.partDataStartIndex, context.renderMode);
                     for (let i = context.partDataStartIndex; i < context.partDataEndIndex; i++) {
                         this.setupUniforms(effect, partIndex, pid._partData, i);
                         engine.draw(true, 0, this.borderIndicesCount);

+ 130 - 43
src/Canvas2d/babylon.prim2dBase.ts

@@ -485,7 +485,7 @@
 
         /**
          * Set the thickness from a string value
-         * @param thickness format is "top: <value>, left:<value>, right:<value>, bottom:<value>" each are optional, auto will be set if it's omitted.
+         * @param thickness format is "top: <value>, left:<value>, right:<value>, bottom:<value>" or "<value>" (same for all edges) each are optional, auto will be set if it's omitted.
          * Values are: 'auto', 'inherit', 'XX%' for percentage, 'XXpx' or 'XX' for pixels.
          */
         public fromString(thickness: string) {
@@ -493,6 +493,17 @@
 
             let m = thickness.trim().split(",");
 
+            // Special case, one value to apply to all edges
+            if (m.length === 1 && thickness.indexOf(":") === -1) {
+                this._setStringValue(m[0], 0, false);
+                this._setStringValue(m[0], 1, false);
+                this._setStringValue(m[0], 2, false);
+                this._setStringValue(m[0], 3, false);
+
+                this._changedCallback();
+                return;
+            }
+
             let res = false;
             for (let cm of m) {
                 res = this._extractString(cm, false) || res;
@@ -1181,6 +1192,19 @@
             result.width = this.leftPixels + sourceArea.width + this.rightPixels;
             result.height = this.bottomPixels + sourceArea.height + this.topPixels;
         }
+
+        enlarge(sourceArea: Size, dstOffset: Vector2, enlargedArea: Size) {
+            this._computePixels(0, sourceArea, true);
+            this._computePixels(1, sourceArea, true);
+            this._computePixels(2, sourceArea, true);
+            this._computePixels(3, sourceArea, true);
+
+            dstOffset.x = this.leftPixels;
+            enlargedArea.width = sourceArea.width + (dstOffset.x + this.rightPixels);
+
+            dstOffset.y = this.bottomPixels;
+            enlargedArea.height = sourceArea.height + (dstOffset.y + this.topPixels);
+        }
     }
 
     /**
@@ -1321,8 +1345,10 @@
             this._layoutArea = Size.Zero();
             this._layoutAreaPos = Vector2.Zero();
             this._marginOffset = Vector2.Zero();
-            this._parentMargingOffset = Vector2.Zero();
+            this._paddingOffset = Vector2.Zero();
+            this._parentPaddingOffset = Vector2.Zero();
             this._parentContentArea = Size.Zero();
+            this._lastAutoSizeArea = Size.Zero();
             this._contentArea = new Size(null, null);
             this._pointerEventObservable = new Observable<PrimitivePointerInfo>();
             this._siblingDepthOffset = this._hierarchyDepthOffset = 0;
@@ -1609,7 +1635,7 @@
          */
         @dynamicLevelProperty(1, pi => Prim2DBase.positionProperty = pi, false, true)
         public get position(): Vector2 {
-            return this._position;
+            return this._position || Prim2DBase._nullPosition;
         }
 
         public set position(value: Vector2) {
@@ -2079,7 +2105,12 @@
          */
         public get boundingInfo(): BoundingInfo2D {
             if (this._isFlagSet(SmartPropertyPrim.flagBoundingInfoDirty)) {
-                this._boundingInfo = this.levelBoundingInfo.clone();
+
+                if (this.isSizedByContent) {
+                    this._boundingInfo.clear();
+                } else {
+                    this._boundingInfo.copyFrom(this.levelBoundingInfo);
+                }
                 let bi = this._boundingInfo;
 
                 var tps = new BoundingInfo2D();
@@ -2107,6 +2138,13 @@
         }
 
         /**
+         * Return true if this prim has an auto size which is set by the children's global bounding box
+         */
+        public get isSizedByContent(): boolean {
+            return (this._size == null) && (this._children.length > 0);
+        }
+
+        /**
          * Determine if the position is automatically computed or fixed because manually specified.
          * Use the actualPosition property to get the final/real position of the primitive
          * @returns true if the position is automatically computed, false if it were manually specified.
@@ -2400,23 +2438,16 @@
         private static _v0: Vector2 = Vector2.Zero();   // Must stay with the value 0,0
 
         private _updateLocalTransform(): boolean {
-            let parentMarginOffsetChanged = false;
-            let parentMarginOffset: Vector2 = null;
-            if (this._parent) {
-                parentMarginOffset = this._parent._marginOffset;
-                parentMarginOffsetChanged = !parentMarginOffset.equals(this._parentMargingOffset);
-                this._parentMargingOffset.copyFrom(parentMarginOffset);
-            } else {
-                parentMarginOffset = Prim2DBase._v0;
-            }
-
             let tflags = Prim2DBase.actualPositionProperty.flagId | Prim2DBase.rotationProperty.flagId | Prim2DBase.scaleProperty.flagId | Prim2DBase.originProperty.flagId;
-            if (parentMarginOffsetChanged || this.checkPropertiesDirty(tflags)) {
+            if (this.checkPropertiesDirty(tflags)) {
+                this.owner.addupdateLocalTransformCounter(1);
+
                 var rot = Quaternion.RotationAxis(new Vector3(0, 0, 1), this._rotation);
                 var local: Matrix;
+                let pos = this.position;
 
                 if (this._origin.x === 0 && this._origin.y === 0) {
-                    local = Matrix.Compose(new Vector3(this._scale, this._scale, 1), rot, new Vector3(this.actualPosition.x + parentMarginOffset.x, this.actualPosition.y + parentMarginOffset.y, 0));
+                    local = Matrix.Compose(new Vector3(this._scale, this._scale, 1), rot, new Vector3(pos.x, pos.y, 0));
                     this._localTransform = local;
                 } else {
                     // -Origin offset
@@ -2431,8 +2462,8 @@
                     Matrix.ScalingToRef(this._scale, this._scale, 1, Prim2DBase._t0);
                     Prim2DBase._t2.multiplyToRef(Prim2DBase._t0, Prim2DBase._t1);
 
-                    // -Origin * rotation * scale * (Origin + Position + Parent Margin Offset)
-                    Matrix.TranslationToRef((as.width * this._origin.x) + this.actualPosition.x + parentMarginOffset.x, (as.height * this._origin.y) + this.actualPosition.y + parentMarginOffset.y, 0, Prim2DBase._t2);
+                    // -Origin * rotation * scale * (Origin + Position)
+                    Matrix.TranslationToRef((as.width * this._origin.x) + pos.x, (as.height * this._origin.y) + pos.y, 0, Prim2DBase._t2);
                     Prim2DBase._t1.multiplyToRef(Prim2DBase._t2, this._localTransform);
                 }
 
@@ -2442,11 +2473,15 @@
             return false;
         }
 
+        private static _transMtx = Matrix.Zero();
+
         protected updateCachedStates(recurse: boolean) {
             if (this.isDisposed) {
                 return;
             }
 
+            this.owner.addCachedGroupRenderCounter(1);
+            
             // Check if the parent is synced
             if (this._parent && ((this._parent._globalTransformProcessStep !== this.owner._globalTransformProcessStep) || this._parent._areSomeFlagsSet(SmartPropertyPrim.flagLayoutDirty | SmartPropertyPrim.flagPositioningDirty))) {
                 this._parent.updateCachedStates(false);
@@ -2455,7 +2490,7 @@
             // Update actualSize only if there' not positioning to recompute and the size changed
             // Otherwise positioning will take care of it.
             let sizeDirty = this.checkPropertiesDirty(Prim2DBase.sizeProperty.flagId);
-            if (!this._isFlagSet(SmartPropertyPrim.flagLayoutDirty) && sizeDirty) {
+            if (!this._isFlagSet(SmartPropertyPrim.flagLayoutDirty) && !this._isFlagSet(SmartPropertyPrim.flagPositioningDirty) && sizeDirty) {
                 let size = this.size;
                 if (size) {
                     if (this.size.width != null) {
@@ -2471,18 +2506,26 @@
             // Check for layout update
             let positioningDirty = this._isFlagSet(SmartPropertyPrim.flagPositioningDirty);
             if (this._isFlagSet(SmartPropertyPrim.flagLayoutDirty)) {
+                this.owner.addUpdateLayoutCounter(1);
                 this._layoutEngine.updateLayout(this);
 
                 this._clearFlags(SmartPropertyPrim.flagLayoutDirty);
             }
 
             let positioningComputed = positioningDirty && !this._isFlagSet(SmartPropertyPrim.flagPositioningDirty);
+            let autoContentChanged = false;
+            if (this.isSizeAuto) {
+                autoContentChanged = (!this._lastAutoSizeArea.equals(this.size));
+            }
 
             // Check for positioning update
-            if (!positioningComputed && (sizeDirty || this._isFlagSet(SmartPropertyPrim.flagPositioningDirty) || (this._parent && !this._parent.contentArea.equals(this._parentContentArea)))) {
+            if (!positioningComputed && (autoContentChanged || sizeDirty || this._isFlagSet(SmartPropertyPrim.flagPositioningDirty) || (this._parent && !this._parent.contentArea.equals(this._parentContentArea)))) {
                 this._updatePositioning();
 
                 this._clearFlags(SmartPropertyPrim.flagPositioningDirty);
+                if (sizeDirty) {
+                    this.clearPropertiesDirty(Prim2DBase.sizeProperty.flagId);
+                }
                 positioningComputed = true;
             }
 
@@ -2492,6 +2535,8 @@
 
             // Check if we must update this prim
             if (this === <any>this.owner || this._globalTransformProcessStep !== this.owner._globalTransformProcessStep) {
+                this.owner.addUpdateGlobalTransformCounter(1);
+
                 let curVisibleState = this.isVisible;
                 this.isVisible = (!this._parent || this._parent.isVisible) && this.levelVisible;
 
@@ -2501,13 +2546,28 @@
                 // Get/compute the localTransform
                 let localDirty = this._updateLocalTransform();
 
+                let parentPaddingChanged = false;
+                let parentPaddingOffset: Vector2 = Prim2DBase._v0;
+                if (this._parent) {
+                    parentPaddingOffset = this._parent._paddingOffset;
+                    parentPaddingChanged = !parentPaddingOffset.equals(this._parentPaddingOffset);
+                }
+
                 // Check if there are changes in the parent that will force us to update the global matrix
                 let parentDirty = (this._parent != null) ? (this._parent._globalTransformStep !== this._parentTransformStep) : false;
 
                 // Check if we have to update the globalTransform
-                if (!this._globalTransform || localDirty || parentDirty) {
+                if (!this._globalTransform || localDirty || parentDirty || parentPaddingChanged) {
                     let globalTransform = this._parent ? this._parent._globalTransform : null;
-                    this._globalTransform = this._parent ? this._localTransform.multiply(globalTransform) : this._localTransform;
+
+                    let localTransform: Matrix;
+                    Prim2DBase._transMtx.copyFrom(this._localTransform);
+                    Prim2DBase._transMtx.m[12] += this._layoutAreaPos.x + this._marginOffset.x + parentPaddingOffset.x;
+                    Prim2DBase._transMtx.m[13] += this._layoutAreaPos.y + this._marginOffset.y + parentPaddingOffset.y;
+                    localTransform = Prim2DBase._transMtx;
+
+                    this._globalTransform = this._parent ? localTransform.multiply(globalTransform) : localTransform.clone();
+
                     this._invGlobalTransform = Matrix.Invert(this._globalTransform);
 
                     this._globalTransformStep = this.owner._globalTransformProcessStep + 1;
@@ -2528,6 +2588,8 @@
         private static _size = Size.Zero();
 
         private _updatePositioning() {
+            this.owner.addUpdatePositioningCounter(1);
+
             // From this point we assume that the primitive layoutArea is computed and up to date.
             // We know have to :
             //  1. Determine the PaddingArea and the ActualPosition based on the margin/marginAlignment properties, which will also set the size property of the primitive
@@ -2547,33 +2609,47 @@
             // Apply margin
             if (this._hasMargin) {
                 this.margin.computeWithAlignment(this.layoutArea, this.size, this.marginAlignment, this._marginOffset, Prim2DBase._size);
-
-                this.actualPosition = this._marginOffset.add(this._layoutAreaPos);
-
-                if (this.size.width != null) {
-                    this.size.width = Prim2DBase._size.width;
-                }
-                if (this.size.height != null) {
-                    this.size.height = Prim2DBase._size.height;
-                }
-                this.actualSize.copyFrom(Prim2DBase._size.clone());
+                this.actualSize = Prim2DBase._size.clone();
             }
 
+            let isSizeAuto = this.isSizeAuto;
             if (this._hasPadding) {
-                this._getInitialContentAreaToRef(this.actualSize, Prim2DBase._icPos, Prim2DBase._icArea);
-                Prim2DBase._icArea.width = Math.max(0, Prim2DBase._icArea.width);
-                Prim2DBase._icArea.height = Math.max(0, Prim2DBase._icArea.height);
-                this.padding.compute(Prim2DBase._icArea, this._marginOffset, Prim2DBase._size);
-                this._marginOffset.x += Prim2DBase._icPos.x;
-                this._marginOffset.y += Prim2DBase._icPos.y;
-                this._contentArea.copyFrom(Prim2DBase._size);
+                // Two cases from here: the size of the Primitive is Auto, its content can't be shrink, so me resize the primitive itself
+                if (isSizeAuto) {
+                    let content = this.size.clone();
+                    this._getActualSizeFromContentToRef(content, Prim2DBase._icArea);
+                    this.padding.enlarge(Prim2DBase._icArea, this._paddingOffset, Prim2DBase._size);
+                    this._contentArea.copyFrom(content);
+                    this.actualSize = Prim2DBase._size.clone();
+
+                    // Changing the padding has resize the prim, which forces us to recompute margin again
+                    if (this._hasMargin) {
+                        this.margin.computeWithAlignment(this.layoutArea, Prim2DBase._size, this.marginAlignment, this._marginOffset, Prim2DBase._size);
+                    }
+
+                } else {
+                    this._getInitialContentAreaToRef(this.actualSize, Prim2DBase._icPos, Prim2DBase._icArea);
+                    Prim2DBase._icArea.width = Math.max(0, Prim2DBase._icArea.width);
+                    Prim2DBase._icArea.height = Math.max(0, Prim2DBase._icArea.height);
+                    this.padding.compute(Prim2DBase._icArea, this._paddingOffset, Prim2DBase._size);
+                    this._paddingOffset.x += Prim2DBase._icPos.x;
+                    this._paddingOffset.y += Prim2DBase._icPos.y;
+                    this._contentArea.copyFrom(Prim2DBase._size);
+                }
             } else {
                 this._getInitialContentAreaToRef(this.actualSize, Prim2DBase._icPos, Prim2DBase._icArea);
                 Prim2DBase._icArea.width = Math.max(0, Prim2DBase._icArea.width);
                 Prim2DBase._icArea.height = Math.max(0, Prim2DBase._icArea.height);
-                this._marginOffset.copyFrom(Prim2DBase._icPos);
+                this._paddingOffset.copyFrom(Prim2DBase._icPos);
                 this._contentArea.copyFrom(Prim2DBase._icArea);
             }
+
+            let aPos = new Vector2(this._layoutAreaPos.x + this._marginOffset.x, this._layoutAreaPos.y + this._marginOffset.y);
+            this.actualPosition = aPos;
+
+            if (isSizeAuto) {
+                this._lastAutoSizeArea = this.size;                
+            }
         }
 
         /**
@@ -2641,11 +2717,20 @@
          * @param initialContentArea the size of the initial content area to compute, a valid object is passed, you have to set its properties. PLEASE ROUND the values, we're talking about pixels and fraction of them is not a good thing!
          */
         protected _getInitialContentAreaToRef(primSize: Size, initialContentPosition: Vector2, initialContentArea: Size) {
-            initialContentArea.width = primSize.width;
-            initialContentArea.height = primSize.height;
+            initialContentArea.copyFrom(primSize);
             initialContentPosition.x = initialContentPosition.y = 0;
         }
 
+        /**
+         * This method is used to calculate the new size of the primitive based on the content which must stay the same
+         * Check the Rectangle2D implementation for a concrete application.
+         * @param primSize the current size of the primitive
+         * @param newPrimSize the new size of the primitive. PLEASE ROUND THE values, we're talking about pixels and fraction of them are not our friends!
+         */
+        protected _getActualSizeFromContentToRef(primSize: Size, newPrimSize: Size) {
+            newPrimSize.copyFrom(primSize);
+        }
+
         private _owner: Canvas2D;
         private _parent: Prim2DBase;
         private _actionManager: ActionManager;
@@ -2670,8 +2755,10 @@
         protected _desiredSize: Size;
         private _layoutEngine: LayoutEngineBase;
         private _marginOffset: Vector2;
-        private _parentMargingOffset: Vector2;
+        private _paddingOffset: Vector2;
+        private _parentPaddingOffset: Vector2;
         private _parentContentArea: Size;
+        private _lastAutoSizeArea: Size;
         private _layoutAreaPos: Vector2;
         private _layoutArea: Size;
         private _contentArea: Size;

+ 18 - 2
src/Canvas2d/babylon.rectangle2d.ts

@@ -28,8 +28,8 @@
                 }
                 this.effectsReady = true;
             }
-
-            var engine = instanceInfo.owner.owner.engine;
+            let canvas = instanceInfo.owner.owner;
+            var engine = canvas.engine;
 
             let depthFunction = 0;
             if (this.effectFill && this.effectBorder) {
@@ -57,10 +57,12 @@
                         this.instancingFillAttributes = this.loadInstancingAttributes(Shape2D.SHAPE2D_FILLPARTID, effect);
                     }
 
+                    canvas._addDrawCallCount(1, context.renderMode);
                     engine.updateAndBindInstancesBuffer(pid._partBuffer, null, this.instancingFillAttributes);
                     engine.draw(true, 0, this.fillIndicesCount, pid._partData.usedElementCount);
                     engine.unbindInstanceAttributes();
                 } else {
+                    canvas._addDrawCallCount(context.partDataEndIndex - context.partDataStartIndex, context.renderMode);
                     for (let i = context.partDataStartIndex; i < context.partDataEndIndex; i++) {
                         this.setupUniforms(effect, partIndex, pid._partData, i);
                         engine.draw(true, 0, this.fillIndicesCount);                        
@@ -85,10 +87,12 @@
                         this.instancingBorderAttributes = this.loadInstancingAttributes(Shape2D.SHAPE2D_BORDERPARTID, effect);
                     }
 
+                    canvas._addDrawCallCount(1, context.renderMode);
                     engine.updateAndBindInstancesBuffer(pid._partBuffer, null, this.instancingBorderAttributes);
                     engine.draw(true, 0, this.borderIndicesCount, pid._partData.usedElementCount);
                     engine.unbindInstanceAttributes();
                 } else {
+                    canvas._addDrawCallCount(context.partDataEndIndex - context.partDataStartIndex, context.renderMode);
                     for (let i = context.partDataStartIndex; i < context.partDataEndIndex; i++) {
                         this.setupUniforms(effect, partIndex, pid._partData, i);
                         engine.draw(true, 0, this.borderIndicesCount);
@@ -481,6 +485,18 @@
             }
         }
 
+        protected _getActualSizeFromContentToRef(primSize: Size, newPrimSize: Size) {
+            // Fall back to default implementation if there's no round Radius
+            if (this._notRounded) {
+                super._getActualSizeFromContentToRef(primSize, newPrimSize);
+            } else {
+                let rr = Math.round((this.roundRadius - (this.roundRadius / Math.sqrt(2))) * 1.3);
+                newPrimSize.copyFrom(primSize);
+                newPrimSize.width  += rr * 2;
+                newPrimSize.height += rr * 2;
+            }
+        }
+
         protected createInstanceDataParts(): InstanceDataBase[] {
             var res = new Array<InstanceDataBase>();
             if (this.border) {

+ 1 - 0
src/Canvas2d/babylon.smartPropertyPrim.ts

@@ -346,6 +346,7 @@
                     curprim._setFlags(SmartPropertyPrim.flagBoundingInfoDirty);
                     if (curprim.isSizeAuto) {
                         curprim.onPrimitivePropertyDirty(Prim2DBase.sizeProperty.flagId);
+                        curprim._setFlags(SmartPropertyPrim.flagPositioningDirty);
                     }
 
                     if (curprim instanceof Group2D) {

+ 4 - 1
src/Canvas2d/babylon.sprite2d.ts

@@ -18,7 +18,8 @@
             }
 
             // Compute the offset locations of the attributes in the vertex shader that will be mapped to the instance buffer data
-            var engine = instanceInfo.owner.owner.engine;
+            let canvas = instanceInfo.owner.owner;
+            var engine = canvas.engine;
 
             let effect = context.useInstancing ? this.effectInstanced : this.effect;
 
@@ -37,10 +38,12 @@
                 if (!this.instancingAttributes) {
                     this.instancingAttributes = this.loadInstancingAttributes(Sprite2D.SPRITE2D_MAINPARTID, effect);
                 }
+                canvas._addDrawCallCount(1, context.renderMode);
                 engine.updateAndBindInstancesBuffer(pid._partBuffer, null, this.instancingAttributes);
                 engine.draw(true, 0, 6, pid._partData.usedElementCount);
                 engine.unbindInstanceAttributes();
             } else {
+                canvas._addDrawCallCount(context.partDataEndIndex - context.partDataStartIndex, context.renderMode);
                 for (let i = context.partDataStartIndex; i < context.partDataEndIndex; i++) {
                     this.setupUniforms(effect, 0, pid._partData, i);
                     engine.draw(true, 0, 6);

+ 4 - 2
src/Canvas2d/babylon.text2d.ts

@@ -16,8 +16,8 @@
                 }
                 this.effectsReady = true;
             }
-
-            var engine = instanceInfo.owner.owner.engine;
+            let canvas = instanceInfo.owner.owner;
+            var engine = canvas.engine;
 
             this.fontTexture.update();
 
@@ -37,10 +37,12 @@
                     this.instancingAttributes = this.loadInstancingAttributes(Text2D.TEXT2D_MAINPARTID, effect);
                 }
 
+                canvas._addDrawCallCount(1, context.renderMode);
                 engine.updateAndBindInstancesBuffer(pid._partBuffer, null, this.instancingAttributes);
                 engine.draw(true, 0, 6, pid._partData.usedElementCount);
                 engine.unbindInstanceAttributes();
             } else {
+                canvas._addDrawCallCount(context.partDataEndIndex - context.partDataStartIndex, context.renderMode);
                 for (let i = context.partDataStartIndex; i < context.partDataEndIndex; i++) {
                     this.setupUniforms(effect, 0, pid._partData, i);
                     engine.draw(true, 0, 6);

+ 9 - 2
src/Tools/babylon.tools.ts

@@ -1063,6 +1063,10 @@
             return this._current;
         }
 
+        public get total(): number {
+            return this._totalAccumulated;
+        }
+
         constructor() {
             this._startMonitoringTime = 0;
             this._min                 = 0;
@@ -1084,6 +1088,7 @@
         public fetchNewFrame() {
             this._totalValueCount++;
             this._current = 0;
+            this._lastSecValueCount++;
         }
 
         /**
@@ -1124,6 +1129,7 @@
 
         private _fetchResult() {
             this._totalAccumulated += this._current;
+            this._lastSecAccumulated += this._current;
 
             // Min/Max update
             this._min = Math.min(this._min, this._current);
@@ -1131,9 +1137,10 @@
             this._average = this._totalAccumulated / this._totalValueCount;
 
             // Reset last sec?
-            if ((this._startMonitoringTime - this._lastSecTime) > 1000) {
+            let now = Tools.Now;
+            if ((now - this._lastSecTime) > 1000) {
                 this._lastSecAverage = this._lastSecAccumulated / this._lastSecValueCount;
-                this._lastSecTime = this._startMonitoringTime;
+                this._lastSecTime = now;
                 this._lastSecAccumulated = 0;
                 this._lastSecValueCount = 0;
             }