[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 159772a..879e37a 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 7edd718..cc30c05 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 8638dab..18c86a2 100644 (file)
 #define WTF_USE_MEDIATOOLBOX 1
 #endif
 
+#if PLATFORM(COCOA)
+#define HAVE_ACCELERATE 1
+#endif
+
 #endif /* WTF_Platform_h */
index e6be1e0..f9142ab 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 e2eefae..347c439 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 cba3da0..7af5429 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