ソースを参照

fix issues related to invalidate rect optimization

Trevor Baron 6 年 前
コミット
f231388736

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

@@ -23,7 +23,7 @@
   - Added new [ScrollViewer](https://doc.babylonjs.com/how_to/scrollviewer) with mouse wheel scrolling for larger containers to be viewed using Sliders ([JohnK](https://github.com/BabylonJSGuide/) / [Deltakosh](https://github.com/deltakosh))
   - Moved to a measure / draw mechanism ([Deltakosh](https://github.com/deltakosh))
   - Added support for [nine patch stretch](https://www.babylonjs-playground.com/#G5H9IN#2) mode for images. ([Deltakosh](https://github.com/deltakosh))
-  - InvalidateRect added to AdvancedDynamicTexture to improve perf for highly populated GUIs ([TrevorDev](https://github.com/TrevorDev))
+  - InvalidateRect added to AdvancedDynamicTexture to improve perf for heavily populated GUIs ([TrevorDev](https://github.com/TrevorDev))
 
 ## Updates
 

+ 15 - 28
gui/src/2D/advancedDynamicTexture.ts

@@ -331,39 +331,27 @@ export class AdvancedDynamicTexture extends DynamicTexture {
         this._useInvalidateRectOptimization = value;
     }
 
-    private _clearRectangle: Nullable<Measure> = null;
+    // Invalidated rectangle which is the combination of all invalidated controls after they have been rotated into absolute position
     private _invalidatedRectangle: Nullable<Measure> = null;
     /**
      * Invalidates a rectangle area on the gui texture
-     * @param clearMinX left most position of the rectangle to clear in the texture
-     * @param clearMinY top most position of the rectangle to clear in the texture
-     * @param clearMaxX right most position of the rectangle to clear in the texture
-     * @param clearMaxY bottom most position of the rectangle to clear in the texture
-     * @param minX left most position of the rectangle to invalidate in absolute coordinates (not taking in account local transformation)
-     * @param minY top most position of the rectangle to invalidate in absolute coordinates (not taking in account local transformation)
-     * @param maxX right most position of the rectangle to invalidate in absolute coordinates (not taking in account local transformation)
-     * @param maxY bottom most position of the rectangle to invalidate in absolute coordinates (not taking in account local transformation)
+     * @param invalidMinX left most position of the rectangle to invalidate in the texture
+     * @param invalidMinY top most position of the rectangle to invalidate in the texture
+     * @param invalidMaxX right most position of the rectangle to invalidate in the texture
+     * @param invalidMaxY bottom most position of the rectangle to invalidate in the texture
      */
-    public invalidateRect(clearMinX: number, clearMinY: number, clearMaxX: number, clearMaxY: number, minX: number, minY: number, maxX: number, maxY: number) {
+    public invalidateRect(invalidMinX: number, invalidMinY: number, invalidMaxX: number, invalidMaxY: number) {
         if (!this._useInvalidateRectOptimization) {
             return;
         }
-        if (!this._clearRectangle || !this._invalidatedRectangle) {
-            this._clearRectangle = new Measure(clearMinX, clearMinY, clearMaxX - clearMinX + 1, clearMaxY - clearMinY + 1);
-            this._invalidatedRectangle = new Measure(minX, minY, maxX - minX + 1, maxY - minY + 1);
+        if (!this._invalidatedRectangle) {
+            this._invalidatedRectangle = new Measure(invalidMinX, invalidMinY, invalidMaxX - invalidMinX + 1, invalidMaxY - invalidMinY + 1);
         } else {
             // Compute intersection
-            var maxX = Math.ceil(Math.max(this._clearRectangle.left + this._clearRectangle.width - 1, clearMaxX));
-            var maxY = Math.ceil(Math.max(this._clearRectangle.top + this._clearRectangle.height - 1, clearMaxY));
-            this._clearRectangle.left = Math.floor(Math.min(this._clearRectangle.left, clearMinX));
-            this._clearRectangle.top = Math.floor(Math.min(this._clearRectangle.top, clearMinY));
-            this._clearRectangle.width = maxX - this._clearRectangle.left + 1;
-            this._clearRectangle.height = maxY - this._clearRectangle.top + 1;
-
-            maxX = Math.max(this._invalidatedRectangle.left + this._invalidatedRectangle.width - 1, maxX);
-            maxY = Math.max(this._invalidatedRectangle.top + this._invalidatedRectangle.height - 1, maxY);
-            this._invalidatedRectangle.left = Math.min(this._invalidatedRectangle.left, minX);
-            this._invalidatedRectangle.top = Math.min(this._invalidatedRectangle.top, minY);
+            var maxX = Math.ceil(Math.max(this._invalidatedRectangle.left + this._invalidatedRectangle.width - 1, invalidMaxX));
+            var maxY = Math.ceil(Math.max(this._invalidatedRectangle.top + this._invalidatedRectangle.height - 1, invalidMaxY));
+            this._invalidatedRectangle.left = Math.floor(Math.min(this._invalidatedRectangle.left, invalidMinX));
+            this._invalidatedRectangle.top = Math.floor(Math.min(this._invalidatedRectangle.top, invalidMinY));
             this._invalidatedRectangle.width = maxX - this._invalidatedRectangle.left + 1;
             this._invalidatedRectangle.height = maxY - this._invalidatedRectangle.top + 1;
         }
@@ -465,7 +453,7 @@ export class AdvancedDynamicTexture extends DynamicTexture {
                 this._rootContainer._markAllAsDirty();
             }
         }
-        this.invalidateRect(0, 0, textureSize.width - 1, textureSize.height - 1, 0, 0, textureSize.width - 1, textureSize.height - 1);
+        this.invalidateRect(0, 0, textureSize.width - 1, textureSize.height - 1);
     }
     /** @hidden */
     public _getGlobalViewport(scene: Scene): Viewport {
@@ -548,8 +536,8 @@ export class AdvancedDynamicTexture extends DynamicTexture {
         this._isDirty = false; // Restoring the dirty state that could have been set by controls during layout processing
 
         // Clear
-        if (this._clearRectangle) {
-            this._clearMeasure.copyFrom(this._clearRectangle);
+        if (this._invalidatedRectangle) {
+            this._clearMeasure.copyFrom(this._invalidatedRectangle);
         } else {
             this._clearMeasure.copyFromFloats(0, 0, renderWidth, renderHeight);
         }
@@ -565,7 +553,6 @@ export class AdvancedDynamicTexture extends DynamicTexture {
         this.onBeginRenderObservable.notifyObservers(this);
         this._rootContainer._render(context, this._invalidatedRectangle);
         this.onEndRenderObservable.notifyObservers(this);
-        this._clearRectangle = null;
         this._invalidatedRectangle = null;
     }
     /** @hidden */

+ 4 - 9
gui/src/2D/controls/container.ts

@@ -283,12 +283,12 @@ export class Container extends Control {
 
     /** @hidden */
     public _layout(parentMeasure: Measure, context: CanvasRenderingContext2D): boolean {
-        if (!this.isVisible || this.notRenderable) {
+        if (!this.isDirty && (!this.isVisible || this.notRenderable)) {
             return false;
         }
 
         if (this._isDirty) {
-            this._tempCurrentMeasure.copyFrom(this._currentMeasure);
+            this._currentMeasure.rotateByTransformMatrixToRef(this._transformMatrix, this._prevCurrentMeasureTransformedIntoGlobalSpace);
         }
 
         let rebuildCount = 0;
@@ -346,12 +346,7 @@ export class Container extends Control {
         context.restore();
 
         if (this._isDirty) {
-            this.invalidateRect(
-                Math.min(this._currentMeasure.left, this._tempCurrentMeasure.left),
-                Math.min(this._currentMeasure.top, this._tempCurrentMeasure.top),
-                Math.max(this._currentMeasure.left + this._currentMeasure.width, this._tempCurrentMeasure.left + this._tempCurrentMeasure.width) - 1,
-                Math.max(this._currentMeasure.top + this._currentMeasure.height, this._tempCurrentMeasure.top + this._tempCurrentMeasure.height) - 1
-            );
+            this.invalidateRect();
 
             this._isDirty = false;
         }
@@ -376,7 +371,7 @@ export class Container extends Control {
             // Only redraw parts of the screen that are invalidated
             if (invalidatedRectangle) {
                 if (!child._intersectsRect(invalidatedRectangle)) {
-                    // continue;
+                    continue;
                 }
             }
             child._render(context, invalidatedRectangle);

+ 39 - 44
gui/src/2D/controls/control.ts

@@ -50,7 +50,7 @@ export class Control {
     /** @hidden */
     public _tempParentMeasure = Measure.Empty();
     /** @hidden */
-    public _tempCurrentMeasure = Measure.Empty();
+    public _prevCurrentMeasureTransformedIntoGlobalSpace = Measure.Empty();
     /** @hidden */
     protected _cachedParentMeasure = Measure.Empty();
     private _paddingLeft = new ValueAndUnit(0);
@@ -66,7 +66,8 @@ export class Control {
     private _rotation = 0;
     private _transformCenterX = 0.5;
     private _transformCenterY = 0.5;
-    private _transformMatrix = Matrix2D.Identity();
+    /** @hidden */
+    public _transformMatrix = Matrix2D.Identity();
     /** @hidden */
     protected _invertTransformMatrix = Matrix2D.Identity();
     /** @hidden */
@@ -309,7 +310,6 @@ export class Control {
         }
 
         this._scaleX = value;
-        this._transform();
         this._markAsDirty();
         this._markMatrixAsDirty();
     }
@@ -327,7 +327,6 @@ export class Control {
         }
 
         this._scaleY = value;
-        this._transform();
         this._markAsDirty();
         this._markMatrixAsDirty();
     }
@@ -1077,19 +1076,21 @@ export class Control {
 
     /** @hidden */
     public _intersectsRect(rect: Measure) {
-        if (this._currentMeasure.left >= rect.left + rect.width) {
+        // Rotate the control's current measure into local space and check if it intersects the passed in rectangle
+        this._currentMeasure.rotateByTransformMatrixToRef(this._transformMatrix, this._tmpMeasureA);
+        if (this._tmpMeasureA.left >= rect.left + rect.width) {
             return false;
         }
 
-        if (this._currentMeasure.top >= rect.top + rect.height) {
+        if (this._tmpMeasureA.top >= rect.top + rect.height) {
             return false;
         }
 
-        if (this._currentMeasure.left + this._currentMeasure.width <= rect.left) {
+        if (this._tmpMeasureA.left + this._tmpMeasureA.width <= rect.left) {
             return false;
         }
 
-        if (this._currentMeasure.top + this._currentMeasure.height <= rect.top) {
+        if (this._tmpMeasureA.top + this._tmpMeasureA.height <= rect.top) {
             return false;
         }
 
@@ -1097,29 +1098,19 @@ export class Control {
     }
 
     /** @hidden */
-    protected invalidateRect(left: number, top: number, right: number, bottom: number) {
+    protected invalidateRect() {
+        this._transform();
         if (this.host && this.host.useInvalidateRectOptimization) {
-            // Compute AABB of transformed container box (eg. to handle rotation and scaling)
-            var rectanglePoints = BABYLON.Polygon.Rectangle(left, top, right, bottom);
-            var min = new Vector2(Number.MAX_VALUE, Number.MAX_VALUE);
-            var max = new Vector2(0, 0);
-            for (var i = 0; i < 4; i++) {
-                this._transformMatrix.transformCoordinates(rectanglePoints[i].x, rectanglePoints[i].y, rectanglePoints[i]);
-                min.x = Math.min(min.x, rectanglePoints[i].x);
-                min.y = Math.min(min.y, rectanglePoints[i].y);
-                max.x = Math.max(max.x, rectanglePoints[i].x);
-                max.y = Math.max(max.y, rectanglePoints[i].y);
-            }
-
+            // Rotate by transform to get the measure transformed to global space
+            this._currentMeasure.rotateByTransformMatrixToRef(this._transformMatrix, this._tmpMeasureA);
+            // get the boudning box of the current measure and last frames measure in global space and invalidate it
+            // the previous measure is used to properly clear a control that is scaled down
+            Measure.CombineToRef(this._tmpMeasureA, this._prevCurrentMeasureTransformedIntoGlobalSpace, this._tmpMeasureA);
             this.host.invalidateRect(
-                min.x,
-                min.y,
-                max.x,
-                max.y,
-                left,
-                top,
-                right,
-                bottom
+                Math.floor(this._tmpMeasureA.left),
+                Math.floor(this._tmpMeasureA.top),
+                Math.ceil(this._tmpMeasureA.left + this._tmpMeasureA.width),
+                Math.ceil(this._tmpMeasureA.top + this._tmpMeasureA.height),
             );
         }
     }
@@ -1236,12 +1227,12 @@ export class Control {
 
     /** @hidden */
     public _layout(parentMeasure: Measure, context: CanvasRenderingContext2D): boolean {
-        if (!this.isVisible || this.notRenderable) {
+        if (!this.isDirty && (!this.isVisible || this.notRenderable)) {
             return false;
         }
 
         if (this._isDirty || !this._cachedParentMeasure.isEqualsTo(parentMeasure)) {
-            this._tempCurrentMeasure.copyFrom(this._currentMeasure);
+            this._currentMeasure.rotateByTransformMatrixToRef(this._transformMatrix, this._prevCurrentMeasureTransformedIntoGlobalSpace);
 
             context.save();
 
@@ -1260,12 +1251,7 @@ export class Control {
             }
 
             context.restore();
-            this.invalidateRect(
-                Math.min(this._currentMeasure.left, this._tempCurrentMeasure.left),
-                Math.min(this._currentMeasure.top, this._tempCurrentMeasure.top),
-                Math.max(this._currentMeasure.left + this._currentMeasure.width, this._tempCurrentMeasure.left + this._tempCurrentMeasure.width),
-                Math.max(this._currentMeasure.top + this._currentMeasure.height, this._tempCurrentMeasure.top + this._tempCurrentMeasure.height)
-            );
+            this.invalidateRect();
             this._evaluateClippingState(parentMeasure);
         }
 
@@ -1440,17 +1426,26 @@ export class Control {
     }
 
     private static _ClipMeasure = new Measure(0, 0, 0, 0);
+    private _tmpMeasureA = new Measure(0, 0, 0, 0);
     private _clip(context: CanvasRenderingContext2D, invalidatedRectangle?: Nullable<Measure>) {
         context.beginPath();
-
         Control._ClipMeasure.copyFrom(this._currentMeasure);
         if (invalidatedRectangle) {
-            var right = Math.min(invalidatedRectangle.left + invalidatedRectangle.width, this._currentMeasure.left + this._currentMeasure.width);
-            var bottom = Math.min(invalidatedRectangle.top + invalidatedRectangle.height, this._currentMeasure.top + this._currentMeasure.height);
-            Control._ClipMeasure.left = Math.max(invalidatedRectangle.left, this._currentMeasure.left);
-            Control._ClipMeasure.top = Math.max(invalidatedRectangle.top, this._currentMeasure.top);
-            Control._ClipMeasure.width = right - Control._ClipMeasure.left;
-            Control._ClipMeasure.height = bottom - Control._ClipMeasure.top;
+            // Get the context's current transform into a babylon matrix
+            debugger;
+            // var inverseContextTransform = (context as any).getTransform().inverse();
+            // this._tmpMatrix.fromValues(inverseContextTransform.a, inverseContextTransform.b, inverseContextTransform.c, inverseContextTransform.d, inverseContextTransform.e, inverseContextTransform.f);
+
+            // Rotate the invalidated rect into the control's space
+            invalidatedRectangle.rotateByTransformMatrixToRef(this._invertTransformMatrix, this._tmpMeasureA);
+
+            // Get the intersection of the rect in context space and the current context
+            var intersection = new Measure(0, 0, 0, 0);
+            intersection.left = Math.max(this._tmpMeasureA.left, this._currentMeasure.left);
+            intersection.top = Math.max(this._tmpMeasureA.top, this._currentMeasure.top);
+            intersection.width = Math.min(this._tmpMeasureA.left + this._tmpMeasureA.width, this._currentMeasure.left + this._currentMeasure.width) - intersection.left;
+            intersection.height = Math.min(this._tmpMeasureA.top + this._tmpMeasureA.height, this._currentMeasure.top + this._currentMeasure.height) - intersection.top;
+            Control._ClipMeasure.copyFrom(intersection);
         }
 
         if (this.shadowBlur || this.shadowOffsetX || this.shadowOffsetY) {

+ 41 - 0
gui/src/2D/measure.ts

@@ -1,3 +1,5 @@
+import { Matrix2D } from "./math2D";
+import { Vector2 } from "babylonjs";
 
 /**
  * Class used to store 2D control sizes
@@ -48,6 +50,45 @@ export class Measure {
     }
 
     /**
+     * Computes the axis aligned bounding box measure for two given measures
+     * @param a Input measure
+     * @param b Input measure
+     * @param result the resulting bounding measure
+     */
+    public static CombineToRef(a: Measure, b: Measure, result: Measure) {
+        var left = Math.min(a.left, b.left);
+        var top = Math.min(a.top, b.top);
+        var right = Math.max(a.left + a.width, b.left + b.width);
+        var bottom = Math.max(a.top + a.height, b.top + b.height);
+        result.left = left;
+        result.top = top;
+        result.width = right - left;
+        result.height = bottom - top;
+    }
+
+    /**
+     * Computes the axis aligned bounding box of the measure after it is modified by a given transform
+     * @param transform the matrix to transform the measure before computing the AABB
+     * @param result the resulting AABB
+     */
+    public rotateByTransformMatrixToRef(transform: Matrix2D, result: Measure) {
+        var rectanglePoints = BABYLON.Polygon.Rectangle(this.left, this.top, this.left + this.width, this.top + this.height);
+        var min = new Vector2(Number.MAX_VALUE, Number.MAX_VALUE);
+        var max = new Vector2(0, 0);
+        for (var i = 0; i < 4; i++) {
+            transform.transformCoordinates(rectanglePoints[i].x, rectanglePoints[i].y, rectanglePoints[i]);
+            min.x = Math.floor(Math.min(min.x, rectanglePoints[i].x));
+            min.y = Math.floor(Math.min(min.y, rectanglePoints[i].y));
+            max.x = Math.ceil(Math.max(max.x, rectanglePoints[i].x));
+            max.y = Math.ceil(Math.max(max.y, rectanglePoints[i].y));
+        }
+        result.left = min.x;
+        result.top = min.y;
+        result.width = max.x - min.x;
+        result.height = max.y - min.y;
+    }
+
+    /**
      * Check equality between this measure and another one
      * @param other defines the other measures
      * @returns true if both measures are equals

BIN
tests/validation/ReferenceImages/TransformStackPanel.png


+ 5 - 0
tests/validation/config.json

@@ -2,6 +2,11 @@
   "root": "https://rawgit.com/BabylonJS/Website/master",
   "tests": [
     {
+      "title": "GUI Transform StackPanel",
+      "playgroundId": "#BS60AB#0",
+      "referenceImage": "TransformStackPanel.png"
+    },
+    {
       "title": "GUI StackPanel",
       "playgroundId": "#LLVZ90#0",
       "referenceImage": "StackPanel.png"