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 978963b..d0808cc 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 f0f7b75..c9904b7 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 57215d9..9d3043d 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();