[Apple] Use Accelerate framework to speed-up FEGaussianBlur
authordino@apple.com <dino@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 9 Dec 2014 01:31:37 +0000 (01:31 +0000)
committerdino@apple.com <dino@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 9 Dec 2014 01:31:37 +0000 (01:31 +0000)
https://bugs.webkit.org/show_bug.cgi?id=139310
<rdar://problem/18434594>

PerformanceTests:

Reviewed by Simon Fraser.

Add an interactive performance test that measures the speed of a set
of blur operations on a generated images.

* Interactive/blur-filter-timing.html: Added.

Source/WebCore:

<rdar://problem/18434594>

Reviewed by Simon Fraser.

Using Apple's Accelerate framework provides faster blurs
than the parallel jobs approach, especially since r168577
which started performing retina-accurate filters.

Using Accelerate.framework to replace the existing box blur (what
we use to approximate Gaussian blurs) gets about a 20% speedup on
desktop class machines, but between a 2x-6x speedup on iOS hardware.
Obviously this depends on the size of the content being blurred,
but it is still good.

The change is to intercept the platformApply function on
FEGaussianBlur and send it off to Accelerate.

There is an interactive performance test: PerformanceTests/Interactive/blur-filter-timing.html

* platform/graphics/filters/FEGaussianBlur.cpp:
(WebCore::kernelPosition): Move this to a file static function from the .h.
(WebCore::accelerateBoxBlur): The Accelerate implementation.
(WebCore::standardBoxBlur): The default generic/standard implementation.
(WebCore::FEGaussianBlur::platformApplyGeneric): Use accelerate or the default form.
(WebCore::FEGaussianBlur::platformApply): Don't try the parallelJobs approach if Accelerate is available.
* platform/graphics/filters/FEGaussianBlur.h:
(WebCore::FEGaussianBlur::kernelPosition): Deleted. Move into the .cpp.

Source/WTF:

<rdar://problem/18434594>

Reviewed by Simon Fraser.

Add a HAVE_ACCELERATE flag, true on Apple platforms.

* wtf/Platform.h:

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

PerformanceTests/ChangeLog
PerformanceTests/Interactive/blur-filter-timing.html [new file with mode: 0644]
Source/WTF/ChangeLog
Source/WTF/wtf/Platform.h
Source/WebCore/ChangeLog
Source/WebCore/platform/graphics/filters/FEGaussianBlur.cpp
Source/WebCore/platform/graphics/filters/FEGaussianBlur.h

index 159772a35183bf40fb6bdc7d712099d0749ad06c..879e37a354843f0bc49cdadb97cc9e813d321413 100644 (file)
@@ -1,3 +1,15 @@
+2014-12-08  Dean Jackson  <dino@apple.com>
+
+        [Apple] Use Accelerate framework to speed-up FEGaussianBlur
+        https://bugs.webkit.org/show_bug.cgi?id=139310
+
+        Reviewed by Simon Fraser.
+
+        Add an interactive performance test that measures the speed of a set
+        of blur operations on a generated images.
+
+        * Interactive/blur-filter-timing.html: Added.
+
 2014-11-13  Zalan Bujtas  <zalan@apple.com>
 
         Simple line layout: Add performance test case to measure line layout speed of monolithic text content.
diff --git a/PerformanceTests/Interactive/blur-filter-timing.html b/PerformanceTests/Interactive/blur-filter-timing.html
new file mode 100644 (file)
index 0000000..2300dc0
--- /dev/null
@@ -0,0 +1,93 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>Timing test for blur filter</title>
+  <style>
+  img {
+      width: 600px;
+      height: 600px;
+  }
+  </style>
+  <script>
+
+  var WIDTH = 600;
+  var HEIGHT = 600;
+  var NUM_ITERATIONS = 10;
+  var MAX_RADIUS = 20;
+  var currentIteration = 0;
+  var currentRadius = 0;
+  var testIsRunning = false;
+  var image = null;
+  var startTime = null;
+
+  function init() {
+      document.querySelector("button").addEventListener("click", run, false);
+      image = document.querySelector("img");
+
+      // Fill the image with generated content. We can't use a canvas directly,
+      // since that gets composited.
+      var canvas = document.createElement("canvas");
+      canvas.width = WIDTH * window.devicePixelRatio;
+      canvas.height = HEIGHT * window.devicePixelRatio;
+
+      // Fill the canvas with some generated content.
+      var ctx = canvas.getContext("2d");
+      ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
+
+      for (var i = 0; i < WIDTH; i += 20) {
+          for (var j = 0; j < HEIGHT; j += 20) {
+              ctx.fillStyle = "rgb(" + Math.round(i / WIDTH * 255) + ", " + Math.round(j / HEIGHT * 255) + ", " + (i % 40 ? 64 : 192) + ")";
+              ctx.fillRect(i, j, 20, 20);
+          }
+      }
+
+      image.src = canvas.toDataURL();
+  }
+
+  function run() {
+      if (testIsRunning)
+          return;
+
+      testIsRunning = true;
+      currentIteration = 0;
+      currentRadius = 0;
+      startTime = Date.now();
+
+      step();
+  }
+
+  function step() {
+      var usedRadius = (currentIteration % 2) ? (MAX_RADIUS - currentRadius) : currentRadius;
+      image.style.webkitFilter = "blur(" + usedRadius + "px)";
+      currentRadius++;
+      if (currentRadius > MAX_RADIUS) {
+          currentIteration++;
+          currentRadius = 0;
+      }
+
+      if (currentIteration < NUM_ITERATIONS)
+          setTimeout(step, 0);
+      else
+          end();
+  }
+
+  function end() {
+      testIsRunning = false;
+      var elapsedTime = (Date.now() - startTime) / 1000;
+      var result = document.createElement("p");
+      result.textContent = (NUM_ITERATIONS * MAX_RADIUS) + " blurs done in " + elapsedTime + " seconds";
+      document.body.appendChild(result);
+      if (window.testRunner)
+          testRunner.notifyDone();
+  }
+
+  window.addEventListener("load", init, false);
+  </script>
+</head>
+<body>
+<img>
+<p>
+    <button>Start</button>
+</p>
+</body>
+</html>
index 7edd718292235b8575d5b96e01fe843e2a4908b0..cc30c050d265733d36bea27fa6b1d65f79997fe4 100644 (file)
@@ -1,3 +1,15 @@
+2014-12-08  Dean Jackson  <dino@apple.com>
+
+        [Apple] Use Accelerate framework to speed-up FEGaussianBlur
+        https://bugs.webkit.org/show_bug.cgi?id=139310
+        <rdar://problem/18434594>
+
+        Reviewed by Simon Fraser.
+
+        Add a HAVE_ACCELERATE flag, true on Apple platforms.
+
+        * wtf/Platform.h:
+
 2014-12-08  Myles C. Maxfield  <mmaxfield@apple.com>
 
         Fix iOS build after r176971.
index 8638dab9bdabc8a46e535658597c00239f994308..18c86a28b37b75813ac187cbab1dcd029f82cb26 100644 (file)
 #define WTF_USE_MEDIATOOLBOX 1
 #endif
 
+#if PLATFORM(COCOA)
+#define HAVE_ACCELERATE 1
+#endif
+
 #endif /* WTF_Platform_h */
index e6be1e0e39f3986847ba37d519d7b74c03a7b2cb..f9142abe34ef54af0d5de90b06d3a89f185bb640 100644 (file)
@@ -1,3 +1,35 @@
+2014-12-08  Dean Jackson  <dino@apple.com>
+
+        [Apple] Use Accelerate framework to speed-up FEGaussianBlur
+        https://bugs.webkit.org/show_bug.cgi?id=139310
+        <rdar://problem/18434594>
+
+        Reviewed by Simon Fraser.
+
+        Using Apple's Accelerate framework provides faster blurs
+        than the parallel jobs approach, especially since r168577
+        which started performing retina-accurate filters.
+
+        Using Accelerate.framework to replace the existing box blur (what
+        we use to approximate Gaussian blurs) gets about a 20% speedup on
+        desktop class machines, but between a 2x-6x speedup on iOS hardware.
+        Obviously this depends on the size of the content being blurred,
+        but it is still good.
+
+        The change is to intercept the platformApply function on
+        FEGaussianBlur and send it off to Accelerate.
+
+        There is an interactive performance test: PerformanceTests/Interactive/blur-filter-timing.html
+
+        * platform/graphics/filters/FEGaussianBlur.cpp:
+        (WebCore::kernelPosition): Move this to a file static function from the .h.
+        (WebCore::accelerateBoxBlur): The Accelerate implementation.
+        (WebCore::standardBoxBlur): The default generic/standard implementation.
+        (WebCore::FEGaussianBlur::platformApplyGeneric): Use accelerate or the default form.
+        (WebCore::FEGaussianBlur::platformApply): Don't try the parallelJobs approach if Accelerate is available.
+        * platform/graphics/filters/FEGaussianBlur.h:
+        (WebCore::FEGaussianBlur::kernelPosition): Deleted. Move into the .cpp.
+
 2014-12-08  Beth Dakin  <bdakin@apple.com>
 
         Copy and Lookup menu items should be disabled when something is not copyable
index e2eefae986762e56d3307b57954f23acc8cfcb73..347c43965707b5673aaf9125af6a7b31a0e55597 100644 (file)
 #include "GraphicsContext.h"
 #include "TextStream.h"
 
+#if HAVE(ACCELERATE)
+#include <Accelerate/Accelerate.h>
+#endif
+
 #include <runtime/JSCInlines.h>
 #include <runtime/TypedArrayInlines.h>
 #include <runtime/Uint8ClampedArray.h>
@@ -45,6 +49,34 @@ static const int gMaxKernelSize = 500;
 
 namespace WebCore {
 
+inline void kernelPosition(int blurIteration, unsigned& radius, int& deltaLeft, int& deltaRight)
+{
+    // Check http://www.w3.org/TR/SVG/filters.html#feGaussianBlurElement for details.
+    switch (blurIteration) {
+    case 0:
+        if (!(radius % 2)) {
+            deltaLeft = radius / 2 - 1;
+            deltaRight = radius - deltaLeft;
+        } else {
+            deltaLeft = radius / 2;
+            deltaRight = radius - deltaLeft;
+        }
+        break;
+    case 1:
+        if (!(radius % 2)) {
+            deltaLeft++;
+            deltaRight--;
+        }
+        break;
+    case 2:
+        if (!(radius % 2)) {
+            deltaRight++;
+            radius++;
+        }
+        break;
+    }
+}
+
 FEGaussianBlur::FEGaussianBlur(Filter* filter, float x, float y, EdgeModeType edgeMode)
     : FilterEffect(filter)
     , m_stdX(x)
@@ -262,15 +294,51 @@ inline void boxBlur(const Uint8ClampedArray* srcPixelArray, Uint8ClampedArray* d
     }
 }
 
-inline void FEGaussianBlur::platformApplyGeneric(Uint8ClampedArray* srcPixelArray, Uint8ClampedArray* tmpPixelArray, unsigned kernelSizeX, unsigned kernelSizeY, IntSize& paintSize)
+#if HAVE(ACCELERATE)
+inline void accelerateBoxBlur(const Uint8ClampedArray* src, Uint8ClampedArray* dst, unsigned kernelSize, int stride, int effectWidth, int effectHeight)
+{
+    // We must always use an odd radius.
+    if (kernelSize % 2 != 1)
+        kernelSize += 1;
+
+    vImage_Buffer effectInBuffer;
+    effectInBuffer.data = src->data();
+    effectInBuffer.width = effectWidth;
+    effectInBuffer.height = effectHeight;
+    effectInBuffer.rowBytes = stride;
+
+    vImage_Buffer effectOutBuffer;
+    effectOutBuffer.data = dst->data();
+    effectOutBuffer.width = effectWidth;
+    effectOutBuffer.height = effectHeight;
+    effectOutBuffer.rowBytes = stride;
+
+    // Determine the size of a temporary buffer by calling the function first with a special flag. vImage will return
+    // the size needed, or an error (which are all negative).
+    size_t tmpBufferSize = vImageBoxConvolve_ARGB8888(&effectInBuffer, &effectOutBuffer, 0, 0, 0, kernelSize, kernelSize, 0, kvImageEdgeExtend | kvImageGetTempBufferSize);
+    if (tmpBufferSize <= 0)
+        return;
+
+    void* tmpBuffer = fastMalloc(tmpBufferSize);
+    vImageBoxConvolve_ARGB8888(&effectInBuffer, &effectOutBuffer, tmpBuffer, 0, 0, kernelSize, kernelSize, 0, kvImageEdgeExtend);
+    vImageBoxConvolve_ARGB8888(&effectOutBuffer, &effectInBuffer, tmpBuffer, 0, 0, kernelSize, kernelSize, 0, kvImageEdgeExtend);
+    vImageBoxConvolve_ARGB8888(&effectInBuffer, &effectOutBuffer, tmpBuffer, 0, 0, kernelSize, kernelSize, 0, kvImageEdgeExtend);
+    WTF::fastFree(tmpBuffer);
+
+    // The final result should be stored in src.
+    if (dst == src) {
+        ASSERT(src->length() == dst->length());
+        memcpy(dst->data(), src->data(), src->length());
+    }
+}
+#endif
+
+inline void standardBoxBlur(Uint8ClampedArray* src, Uint8ClampedArray* dst, unsigned kernelSizeX, unsigned kernelSizeY, int stride, IntSize& paintSize, bool isAlphaImage, EdgeModeType edgeMode)
 {
-    int stride = 4 * paintSize.width();
     int dxLeft = 0;
     int dxRight = 0;
     int dyLeft = 0;
     int dyRight = 0;
-    Uint8ClampedArray* src = srcPixelArray;
-    Uint8ClampedArray* dst = tmpPixelArray;
 
     for (int i = 0; i < 3; ++i) {
         if (kernelSizeX) {
@@ -279,9 +347,9 @@ inline void FEGaussianBlur::platformApplyGeneric(Uint8ClampedArray* srcPixelArra
             if (!isAlphaImage())
                 boxBlurNEON(src, dst, kernelSizeX, dxLeft, dxRight, 4, stride, paintSize.width(), paintSize.height());
             else
-                boxBlur(src, dst, kernelSizeX, dxLeft, dxRight, 4, stride, paintSize.width(), paintSize.height(), true, m_edgeMode);
+                boxBlur(src, dst, kernelSizeX, dxLeft, dxRight, 4, stride, paintSize.width(), paintSize.height(), true, edgeMode);
 #else
-            boxBlur(src, dst, kernelSizeX, dxLeft, dxRight, 4, stride, paintSize.width(), paintSize.height(), isAlphaImage(), m_edgeMode);
+            boxBlur(src, dst, kernelSizeX, dxLeft, dxRight, 4, stride, paintSize.width(), paintSize.height(), isAlphaImageedgeMode);
 #endif
             std::swap(src, dst);
         }
@@ -292,20 +360,33 @@ inline void FEGaussianBlur::platformApplyGeneric(Uint8ClampedArray* srcPixelArra
             if (!isAlphaImage())
                 boxBlurNEON(src, dst, kernelSizeY, dyLeft, dyRight, stride, 4, paintSize.height(), paintSize.width());
             else
-                boxBlur(src, dst, kernelSizeY, dyLeft, dyRight, stride, 4, paintSize.height(), paintSize.width(), true, m_edgeMode);
+                boxBlur(src, dst, kernelSizeY, dyLeft, dyRight, stride, 4, paintSize.height(), paintSize.width(), true, edgeMode);
 #else
-            boxBlur(src, dst, kernelSizeY, dyLeft, dyRight, stride, 4, paintSize.height(), paintSize.width(), isAlphaImage(), m_edgeMode);
+            boxBlur(src, dst, kernelSizeY, dyLeft, dyRight, stride, 4, paintSize.height(), paintSize.width(), isAlphaImageedgeMode);
 #endif
             std::swap(src, dst);
         }
     }
 
-    // The final result should be stored in srcPixelArray.
-    if (dst == srcPixelArray) {
+    // The final result should be stored in src.
+    if (dst == src) {
         ASSERT(src->length() == dst->length());
         memcpy(dst->data(), src->data(), src->length());
     }
+}
+
+inline void FEGaussianBlur::platformApplyGeneric(Uint8ClampedArray* srcPixelArray, Uint8ClampedArray* tmpPixelArray, unsigned kernelSizeX, unsigned kernelSizeY, IntSize& paintSize)
+{
+    int stride = 4 * paintSize.width();
+
+#if HAVE(ACCELERATE)
+    if (kernelSizeX == kernelSizeY && (m_edgeMode == EDGEMODE_NONE || m_edgeMode == EDGEMODE_DUPLICATE)) {
+        accelerateBoxBlur(srcPixelArray, tmpPixelArray, kernelSizeX, stride, paintSize.width(), paintSize.height());
+        return;
+    }
+#endif
 
+    standardBoxBlur(srcPixelArray, tmpPixelArray, kernelSizeX, kernelSizeY, stride, paintSize, isAlphaImage(), m_edgeMode);
 }
 
 void FEGaussianBlur::platformApplyWorker(PlatformApplyParameters* parameters)
@@ -317,6 +398,7 @@ void FEGaussianBlur::platformApplyWorker(PlatformApplyParameters* parameters)
 
 inline void FEGaussianBlur::platformApply(Uint8ClampedArray* srcPixelArray, Uint8ClampedArray* tmpPixelArray, unsigned kernelSizeX, unsigned kernelSizeY, IntSize& paintSize)
 {
+#if !HAVE(ACCELERATE)
     int scanline = 4 * paintSize.width();
     int extraHeight = 3 * kernelSizeY * 0.5f;
     int optimalThreadNumber = (paintSize.width() * paintSize.height()) / (s_minimalRectDimension + extraHeight * paintSize.width());
@@ -378,6 +460,7 @@ inline void FEGaussianBlur::platformApply(Uint8ClampedArray* srcPixelArray, Uint
         }
         // Fallback to single threaded mode.
     }
+#endif
 
     // The selection here eventually should happen dynamically on some platforms.
     platformApplyGeneric(srcPixelArray, tmpPixelArray, kernelSizeX, kernelSizeY, paintSize);
index cba3da0cc43724d0a4d8e39f073ca6ecd1922bf5..7af542939c67e9af0cd18f23f4d70ba4fbe2c35e 100644 (file)
@@ -70,7 +70,6 @@ private:
 
     FEGaussianBlur(Filter*, float, float, EdgeModeType);
 
-    static inline void kernelPosition(int boxBlur, unsigned& std, int& dLeft, int& dRight);
     inline void platformApply(Uint8ClampedArray* srcPixelArray, Uint8ClampedArray* tmpPixelArray, unsigned kernelSizeX, unsigned kernelSizeY, IntSize& paintSize);
 
     inline void platformApplyGeneric(Uint8ClampedArray* srcPixelArray, Uint8ClampedArray* tmpPixelArray, unsigned kernelSizeX, unsigned kernelSizeY, IntSize& paintSize);
@@ -80,34 +79,6 @@ private:
     EdgeModeType m_edgeMode;
 };
 
-inline void FEGaussianBlur::kernelPosition(int boxBlur, unsigned& std, int& dLeft, int& dRight)
-{
-    // check http://www.w3.org/TR/SVG/filters.html#feGaussianBlurElement for details
-    switch (boxBlur) {
-    case 0:
-        if (!(std % 2)) {
-            dLeft = std / 2 - 1;
-            dRight = std - dLeft;
-        } else {
-            dLeft = std / 2;
-            dRight = std - dLeft;
-        }
-        break;
-    case 1:
-        if (!(std % 2)) {
-            dLeft++;
-            dRight--;
-        }
-        break;
-    case 2:
-        if (!(std % 2)) {
-            dRight++;
-            std++;
-        }
-        break;
-    }
-}
-
 } // namespace WebCore
 
 #endif // FEGaussianBlur_h