Poor performance on IE's Chalkboard benchmark.
authorcommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 29 Jan 2015 05:23:28 +0000 (05:23 +0000)
committercommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 29 Jan 2015 05:23:28 +0000 (05:23 +0000)
https://bugs.webkit.org/show_bug.cgi?id=140753.

Patch by Said Abou-Hallawa <sabouhallawa@apple.com> on 2015-01-28
Reviewed by Zalan Bujtas.

PerformanceTests:

* SVG/UnderTheSeeBenchmark.html: Added
* SVG/WorldcupBenchmark.html: Added.
* SVG/resources/RenderAnimator.css: Added.
* SVG/resources/RenderAnimator.js: Added.
These are benchmarks for the SVG rendering. Mainly we want to measure how fast
the SVG rendering will be when only a small part of it is drawn.

Source/WebCore:

Test: PerformanceTests/SVG/UnderTheSeeBenchmark.html
      PerformanceTests/SVG/WorldcupBenchmark.html

The SVG rendering code was not skipping the SVG elements which are outside the
clipping rectangle. We were drawing all the SVG elements even if some of them
are completely outside the clipping rectangle. The fix is to pass the correct
dirty rectangle to the ScrollView which then gets propagated to the SVG renderers.

* svg/graphics/SVGImage.cpp:
(WebCore::SVGImage::draw):
SVGImage::draw() needs to pass the intersection of 'srcRect' and context->clipBounds(),
to ScrollView::paint(). This will ensure RenderSVGShape::paint() gets the correct
clipping rectangle. If there is no intersection between the boundingBox of the
RenderSVGShape and the clipping rectangle, the RenderSVGShape will not be drawn.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@179335 268f45cc-cd09-0410-ab3c-d52691b4dbfc

PerformanceTests/ChangeLog
PerformanceTests/SVG/UnderTheSeeBenchmark.html [new file with mode: 0644]
PerformanceTests/SVG/WorldcupBenchmark.html [new file with mode: 0644]
PerformanceTests/SVG/resources/RenderAnimator.css [new file with mode: 0644]
PerformanceTests/SVG/resources/RenderAnimator.js [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/svg/graphics/SVGImage.cpp

index 978963b7d4a05893a876f67fbfc289b493643e01..d0808cccb429ac9d1b5101fd8a399c0ffeae4aa6 100644 (file)
@@ -1,3 +1,17 @@
+2015-01-28  Said Abou-Hallawa  <sabouhallawa@apple.com>
+
+        Poor performance on IE's Chalkboard benchmark.
+        https://bugs.webkit.org/show_bug.cgi?id=140753.
+
+        Reviewed by Zalan Bujtas.
+
+        * SVG/UnderTheSeeBenchmark.html: Added
+        * SVG/WorldcupBenchmark.html: Added.
+        * SVG/resources/RenderAnimator.css: Added.
+        * SVG/resources/RenderAnimator.js: Added.
+        These are benchmarks for the SVG rendering. Mainly we want to measure how fast
+        the SVG rendering will be when only a small part of it is drawn.
+        
 2015-01-21  Geoffrey Garen  <ggaren@apple.com>
 
         bmalloc: support aligned allocation
diff --git a/PerformanceTests/SVG/UnderTheSeeBenchmark.html b/PerformanceTests/SVG/UnderTheSeeBenchmark.html
new file mode 100644 (file)
index 0000000..4262d32
--- /dev/null
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <link rel="stylesheet" type="text/css" href="resources/RenderAnimator.css">
+    <script src="../resources/runner.js"></script>
+    <script src="resources/RenderAnimator.js"></script>
+  </head>
+  <body>
+    <img class="Benchmark" src="resources/UnderTheSee.svg" style="visibility: hidden;">
+  </body>
+</html>
diff --git a/PerformanceTests/SVG/WorldcupBenchmark.html b/PerformanceTests/SVG/WorldcupBenchmark.html
new file mode 100644 (file)
index 0000000..a674113
--- /dev/null
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <link rel="stylesheet" type="text/css" href="resources/RenderAnimator.css">
+    <script src="../resources/runner.js"></script>
+    <script src="resources/RenderAnimator.js"></script>
+  </head>
+  <body>
+    <img class="Benchmark" src="resources/Worldcup.svg" style="visibility: hidden;">
+  </body>
+</html>
diff --git a/PerformanceTests/SVG/resources/RenderAnimator.css b/PerformanceTests/SVG/resources/RenderAnimator.css
new file mode 100644 (file)
index 0000000..ba7a64a
--- /dev/null
@@ -0,0 +1,15 @@
+.Benchmark {
+    position: fixed;
+    visibility: hidden;
+    transition: opacity 1.2s;
+}
+
+.Timer {
+    position: absolute;
+    right: 0px;
+    bottom: 0px;
+    margin: 0px 40px 14px 0px;
+    font-family: Garamond;
+    font-size: 18pt;
+    transition: opacity 1.2s;
+}
diff --git a/PerformanceTests/SVG/resources/RenderAnimator.js b/PerformanceTests/SVG/resources/RenderAnimator.js
new file mode 100644 (file)
index 0000000..dbfa70a
--- /dev/null
@@ -0,0 +1,233 @@
+//
+// To use this script, the HTML has to have only one element and this element's className
+// should be equal to "Benchmark".
+//
+// This script forces rendering the HTML element many times by changing its position and its
+// size. The size can be very big to test the case of displaying only a small portion of the
+// element and measure how fast the display will be in this case.
+//
+
+function Point(x, y) {
+    this.x = x;
+    this.y = y;
+}
+
+Point.prototype = {
+    scale : function(other) {
+        return new Point(this.x * other.x, this.y * other.y);
+    },
+    add : function(other) {
+        return new Point(this.x + other.x, this.y + other.y);
+    },
+    subtract : function(other) {
+        return new Point(this.x - other.x, this.y - other.y);
+    },
+    isAlmostEqual : function(other, threshold) {
+        return Math.abs(this.x - other.x) < threshold &&  Math.abs(this.y - other.y) < threshold;
+    }
+}
+
+function Rectangle(position, size) {
+    this.position = position;
+    this.size = size;
+}
+
+Rectangle.prototype = {
+    left : function() {
+        return this.position.x;
+    },
+    top : function() {
+        return this.position.y;
+    },
+    width : function() {
+        return this.size.x;
+    },
+    height : function() {
+        return this.size.y;
+    },
+    isAlmostEqual : function(other, threshold) {
+        return this.position.isAlmostEqual(other.position, threshold) && this.size.isAlmostEqual(other.size, threshold);
+    }
+}
+
+function ElapsedTime() {
+    this._startTime = PerfTestRunner.now();
+    this._stopTime = this._startTime;
+}
+
+ElapsedTime.prototype = {
+    
+    start : function() {
+        this._startTime = this._stopTime = PerfTestRunner.now();
+    },
+
+    stop : function() {
+        this._stopTime = PerfTestRunner.now();
+    },
+
+    isActive : function() {
+        return this._startTime == this._stopTime;
+    },
+
+    elapsed : function() {
+        return (this.isActive() ? PerfTestRunner.now() : this._stopTime) - this._startTime;
+    },
+
+    elapsedString : function() {
+        var tenths = this.elapsed() / 1000;
+        return tenths.toFixed(2) + " Seconds";
+    }
+}
+
+function AnimateMove(offset, zoomFactor, animateFactor) {
+    this.offset = offset;
+    this.zoomFactor = zoomFactor;
+    this.animateFactor = animateFactor;
+}
+
+AnimateMove.centerFactor = new Point(0.5, 0.5);
+
+AnimateMove.prototype = {
+    
+    targetRect : function(windowSize, sourceSize) {
+        var offset = this.offset.scale(this.zoomFactor);
+        var size = sourceSize.scale(this.zoomFactor);
+        var position = windowSize.subtract(size).scale(AnimateMove.centerFactor).subtract(offset);
+        return new Rectangle(position, size);
+    },
+
+    nextAnimateRect : function(targetRect, animateRect) {
+        var deltaPosition = targetRect.position.subtract(animateRect.position).scale(this.animateFactor);
+        var deltaSize = targetRect.size.subtract(animateRect.size).scale(this.animateFactor);
+        return new Rectangle(animateRect.position.add(deltaPosition), animateRect.size.add(deltaSize));
+    }
+}
+
+function ElementAnimator(element, windowSize, sourceSize) {
+    this._element = element;
+    this._windowSize = windowSize;
+    this._sourceSize = sourceSize;
+
+    this._animateMoves = [
+        new AnimateMove(new Point(   0,    0), new Point( 0.7,  0.7), new Point(1.00, 1.00)),
+        new AnimateMove(new Point(-500, -300), new Point(12.0, 12.0), new Point(0.50, 0.50)),
+        new AnimateMove(new Point( 100, -200), new Point( 0.1,  0.1), new Point(0.50, 0.50)),
+        new AnimateMove(new Point(-100, -300), new Point( 5.0,  5.0), new Point(0.20, 0.20)),
+        new AnimateMove(new Point(   0,    0), new Point( 0.7,  0.7), new Point(0.50, 0.50))
+    ];
+
+    this._animateMoveIndex = 0;
+    this.nextTargetRect();
+    this._animateRect = this._targetRect;
+    this.moveToAnimateRect();
+}
+
+ElementAnimator.prototype = {
+    
+    nextTargetRect : function() {
+        if (this._animateMoveIndex >= this._animateMoves.length)
+            return false;
+
+        this._targetRect = this._animateMoves[this._animateMoveIndex++].targetRect(this._windowSize, this._sourceSize);
+        return true;
+    },
+    
+    nextAnimateRect : function() {
+        if (this._animateRect.isAlmostEqual(this._targetRect, 0.1))
+            return false;
+
+        this._animateRect = this._animateMoves[this._animateMoveIndex - 1].nextAnimateRect(this._targetRect, this._animateRect);
+        return true;
+    },
+    
+    moveToAnimateRect : function() {
+        this._element.style.width = this._animateRect.width() + "px";
+        this._element.style.left = this._animateRect.left() + "px";
+        this._element.style.top = this._animateRect.top() + "px";
+    }
+}
+
+function RenderAnimator() {
+    this.element = document.getElementsByClassName("Benchmark")[0];
+    this.sourceSize = new Point(this.element.width, this.element.height);
+    
+    // Tiling causes the rendering to slow down when the window width > 2000
+    this.windowSize = new Point(3000, 1500);
+    
+    this.timer = document.createElement("span");
+    this.timer.className = "Timer";
+    document.body.appendChild(this.timer);
+    
+    this.timeoutDelay = window.testRunner ? 0 : 500;
+    this.elapsedTime = new ElapsedTime();
+    this.elementAnimator = null;
+}
+
+RenderAnimator.prototype = {    
+
+    nextRun : function() {
+        this.showElements(true);
+        this.elementAnimator = new ElementAnimator(this.element, this.windowSize, this.sourceSize);
+        
+        var self = this;
+        setTimeout(function () {
+            self.elapsedTime.start();
+            self.moveToNextTargetRect();
+        }, this.timeoutDelay);
+    },
+
+    moveToNextTargetRect : function() {
+        if (this.elementAnimator.nextTargetRect())
+            setTimeout(this.moveToNextAnimateRect.bind(this), 0);
+        else {
+            this.elapsedTime.stop();
+            setTimeout(this.finishRun.bind(this), this.timeoutDelay);
+        }
+    },
+    
+    moveToNextAnimateRect : function() {
+        this.timer.innerHTML = this.elapsedTime.elapsedString();
+        
+        if (this.elementAnimator.nextAnimateRect())
+            window.requestAnimationFrame(this.moveToNextAnimateRect.bind(this));
+        else
+            this.moveToNextTargetRect();
+
+        this.elementAnimator.moveToAnimateRect();
+    },
+    
+    finishRun : function() {
+        this.showElements(false);
+        
+        var finishedTest = !PerfTestRunner.measureValueAsync(this.elapsedTime.elapsed());
+        PerfTestRunner.gc();
+        
+        if (!finishedTest)
+            setTimeout(this.nextRun.bind(this), this.timeoutDelay * 2);
+    },
+
+    showElements : function(show) {
+        this.element.style.visibility = "visible";
+        this.element.style.opacity = show ? 1 : 0;
+        this.timer.style.opacity = show ? 1 : 0;
+    },
+    
+    removeElements : function() {
+        this.element.parentNode.removeChild(this.element);
+        this.timer.parentNode.removeChild(this.timer);
+        this.element = null;
+        this.timer = null;
+    }
+}
+
+window.addEventListener("load", function() {
+    window.renderAnimator = new RenderAnimator();
+    window.renderAnimator.nextRun();
+});
+
+PerfTestRunner.prepareToMeasureValuesAsync({
+    unit: 'ms',
+    done: function () {
+        window.renderAnimator.removeElements();
+    }
+});
index f0f7b75c177b04f7b722f25923319dd46022a32f..c9904b7261996887a4727154f5caaf8452ca30b7 100644 (file)
@@ -1,3 +1,25 @@
+2015-01-28  Said Abou-Hallawa  <sabouhallawa@apple.com>
+
+        Poor performance on IE's Chalkboard benchmark.
+        https://bugs.webkit.org/show_bug.cgi?id=140753.
+
+        Reviewed by Zalan Bujtas.
+
+        Test: PerformanceTests/SVG/UnderTheSeeBenchmark.html
+              PerformanceTests/SVG/WorldcupBenchmark.html
+              
+        The SVG rendering code was not skipping the SVG elements which are outside the
+        clipping rectangle. We were drawing all the SVG elements even if some of them
+        are completely outside the clipping rectangle. The fix is to pass the correct
+        dirty rectangle to the ScrollView which then gets propagated to the SVG renderers.
+
+        * svg/graphics/SVGImage.cpp:
+        (WebCore::SVGImage::draw):
+        SVGImage::draw() needs to pass the intersection of 'srcRect' and context->clipBounds(),
+        to ScrollView::paint(). This will ensure RenderSVGShape::paint() gets the correct
+        clipping rectangle. If there is no intersection between the boundingBox of the
+        RenderSVGShape and the clipping rectangle, the RenderSVGShape will not be drawn.
+
 2015-01-28  Brent Fulgham  <bfulgham@apple.com>
 
         Scroll snap points do not work in the vertical direction
index 57215d978ac0185fe2ad4d37ad20f8a20ad2fba4..9d3043dccd612486ce1abb9bda232d5c07a06d9c 100644 (file)
@@ -249,7 +249,7 @@ void SVGImage::draw(GraphicsContext* context, const FloatRect& dstRect, const Fl
     if (view->needsLayout())
         view->layout();
 
-    view->paint(context, enclosingIntRect(srcRect));
+    view->paint(context, intersection(context->clipBounds(), enclosingIntRect(srcRect)));
 
     if (compositingRequiresTransparencyLayer)
         context->endTransparencyLayer();