Title: [225172] trunk/Source/WebCore
Revision
225172
Author
[email protected]
Date
2017-11-27 08:37:13 -0800 (Mon, 27 Nov 2017)

Log Message

Optimize FEMorphology
https://bugs.webkit.org/show_bug.cgi?id=180020

Reviewed by Sam Weinig.

Use const PaintingData&.
Compute all components at once.
Avoid Vector<> capacity changes during the pixel loop.
Tweak the parallel jobs scaling.
Templatize the the inner loop functions that compute min or max based
on the filter type to avoid conditionals in tight loops.

This is about a 4x speedup before the parallel jobs tweaking. With fixed parallelism,
a 200x200 filter went from 15ms to about 1ms with these changes.

* platform/graphics/ColorUtilities.h:
(WebCore::ColorComponents::fromRGBA):
(WebCore::ColorComponents::toRGBA const):
(WebCore::perComponentMax):
(WebCore::perComponentMin):
* platform/graphics/filters/FEColorMatrix.cpp:
(WebCore::FEColorMatrix::platformApplySoftware): Remove some old perf logging code.
TimingScope now does something similar.
* platform/graphics/filters/FEMorphology.cpp:
(WebCore::pixelArrayIndex):
(WebCore::minOrMax):
(WebCore::columnExtremum):
(WebCore::kernelExtremum):
(WebCore::FEMorphology::platformApplyGeneric):
(WebCore::FEMorphology::platformApplyWorker):
(WebCore::FEMorphology::platformApply):
(WebCore::FEMorphology::platformApplySoftware):
(WebCore::shouldSupersedeExtremum): Deleted.
* platform/graphics/filters/FEMorphology.h:

Modified Paths

Diff

Modified: trunk/Source/WebCore/ChangeLog (225171 => 225172)


--- trunk/Source/WebCore/ChangeLog	2017-11-27 16:30:58 UTC (rev 225171)
+++ trunk/Source/WebCore/ChangeLog	2017-11-27 16:37:13 UTC (rev 225172)
@@ -1,3 +1,40 @@
+2017-11-27  Simon Fraser  <[email protected]>
+
+        Optimize FEMorphology
+        https://bugs.webkit.org/show_bug.cgi?id=180020
+
+        Reviewed by Sam Weinig.
+
+        Use const PaintingData&.
+        Compute all components at once.
+        Avoid Vector<> capacity changes during the pixel loop.
+        Tweak the parallel jobs scaling.
+        Templatize the the inner loop functions that compute min or max based
+        on the filter type to avoid conditionals in tight loops.
+        
+        This is about a 4x speedup before the parallel jobs tweaking. With fixed parallelism,
+        a 200x200 filter went from 15ms to about 1ms with these changes.
+
+        * platform/graphics/ColorUtilities.h:
+        (WebCore::ColorComponents::fromRGBA):
+        (WebCore::ColorComponents::toRGBA const):
+        (WebCore::perComponentMax):
+        (WebCore::perComponentMin):
+        * platform/graphics/filters/FEColorMatrix.cpp:
+        (WebCore::FEColorMatrix::platformApplySoftware): Remove some old perf logging code.
+        TimingScope now does something similar.
+        * platform/graphics/filters/FEMorphology.cpp:
+        (WebCore::pixelArrayIndex):
+        (WebCore::minOrMax):
+        (WebCore::columnExtremum):
+        (WebCore::kernelExtremum):
+        (WebCore::FEMorphology::platformApplyGeneric):
+        (WebCore::FEMorphology::platformApplyWorker):
+        (WebCore::FEMorphology::platformApply):
+        (WebCore::FEMorphology::platformApplySoftware):
+        (WebCore::shouldSupersedeExtremum): Deleted.
+        * platform/graphics/filters/FEMorphology.h:
+
 2017-11-27  Yacine Bandou  <[email protected]>
 
         [EME][GStreamer] Change the ClearKey's SystemID value

Modified: trunk/Source/WebCore/platform/graphics/ColorUtilities.h (225171 => 225172)


--- trunk/Source/WebCore/platform/graphics/ColorUtilities.h	2017-11-27 16:30:58 UTC (rev 225171)
+++ trunk/Source/WebCore/platform/graphics/ColorUtilities.h	2017-11-27 16:37:13 UTC (rev 225172)
@@ -93,7 +93,12 @@
 
 struct ColorComponents {
     ColorComponents(const FloatComponents&);
-
+    
+    static ColorComponents fromRGBA(unsigned pixel)
+    {
+        return ColorComponents((pixel >> 24) & 0xFF, (pixel >> 16) & 0xFF, (pixel >> 8) & 0xFF, pixel & 0xFF);
+    }
+    
     ColorComponents(uint8_t a = 0, uint8_t b = 0, uint8_t c = 0, uint8_t d = 0)
     {
         components[0] = a;
@@ -101,9 +106,35 @@
         components[2] = c;
         components[3] = d;
     }
+    
+    unsigned toRGBA() const
+    {
+        return components[0] << 24 | components[1] << 16 | components[2] << 8 | components[3];
+    }
+
     uint8_t components[4] { };
 };
 
+inline ColorComponents perComponentMax(const ColorComponents& a, const ColorComponents& b)
+{
+    return {
+        std::max(a.components[0], b.components[0]),
+        std::max(a.components[1], b.components[1]),
+        std::max(a.components[2], b.components[2]),
+        std::max(a.components[3], b.components[3])
+    };
+}
+
+inline ColorComponents perComponentMin(const ColorComponents& a, const ColorComponents& b)
+{
+    return {
+        std::min(a.components[0], b.components[0]),
+        std::min(a.components[1], b.components[1]),
+        std::min(a.components[2], b.components[2]),
+        std::min(a.components[3], b.components[3])
+    };
+}
+
 inline uint8_t clampedColorComponent(float f)
 {
     // See also colorFloatToRGBAByte().

Modified: trunk/Source/WebCore/platform/graphics/filters/FEColorMatrix.cpp (225171 => 225172)


--- trunk/Source/WebCore/platform/graphics/filters/FEColorMatrix.cpp	2017-11-27 16:30:58 UTC (rev 225171)
+++ trunk/Source/WebCore/platform/graphics/filters/FEColorMatrix.cpp	2017-11-27 16:37:13 UTC (rev 225172)
@@ -25,17 +25,14 @@
 
 #include "Filter.h"
 #include "GraphicsContext.h"
-#include <wtf/text/TextStream.h>
-
 #include <runtime/Uint8ClampedArray.h>
 #include <wtf/MathExtras.h>
+#include <wtf/text/TextStream.h>
 
 #if USE(ACCELERATE)
 #include <Accelerate/Accelerate.h>
 #endif
 
-#define PRINT_FILTER_PERFORMANCE 0
-
 namespace WebCore {
 
 FEColorMatrix::FEColorMatrix(Filter& filter, ColorMatrixType type, const Vector<float>& values)
@@ -283,10 +280,6 @@
     if (!resultImage)
         return;
 
-#if PRINT_FILTER_PERFORMANCE
-    MonotonicTime startTime = MonotonicTime::now();
-#endif
-
     ImageBuffer* inBuffer = in->imageBufferResult();
     if (inBuffer)
         resultImage->context().drawImageBuffer(*inBuffer, drawingRegionOfInputImage(in->absolutePaintRect()));
@@ -318,20 +311,6 @@
     }
 
     resultImage->putByteArray(Unmultiplied, *pixelArray, imageRect.size(), imageRect, IntPoint());
-
-#if PRINT_FILTER_PERFORMANCE
-    MonotonicTime endTime = MonotonicTime::now();
-    Seconds duration = endTime - startTime;
-    unsigned pixelCount = imageRect.width() * imageRect.height();
-    
-    static double totalMegapixels;
-    totalMegapixels += (double)pixelCount / 1048576.0;
-    
-    static Seconds totalTime;
-    totalTime += duration;
-    
-    WTFLogAlways("FEColorMatrix::platformApplySoftware (%dx%d) took %.4fms (ave %.4fms/mp)", imageRect.width(), imageRect.height(), duration.milliseconds(), totalTime.milliseconds() / totalMegapixels);
-#endif
 }
 
 static TextStream& operator<<(TextStream& ts, const ColorMatrixType& type)

Modified: trunk/Source/WebCore/platform/graphics/filters/FEMorphology.cpp (225171 => 225172)


--- trunk/Source/WebCore/platform/graphics/filters/FEMorphology.cpp	2017-11-27 16:30:58 UTC (rev 225171)
+++ trunk/Source/WebCore/platform/graphics/filters/FEMorphology.cpp	2017-11-27 16:37:13 UTC (rev 225172)
@@ -4,6 +4,7 @@
  * Copyright (C) 2005 Eric Seidel <[email protected]>
  * Copyright (C) 2009 Dirk Schulze <[email protected]>
  * Copyright (C) Research In Motion Limited 2010. All rights reserved.
+ * Copyright (C) Apple Inc. 2017. All rights reserved.
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Library General Public
@@ -24,12 +25,12 @@
 #include "config.h"
 #include "FEMorphology.h"
 
+#include "ColorUtilities.h"
 #include "Filter.h"
-#include <wtf/text/TextStream.h>
-
 #include <runtime/Uint8ClampedArray.h>
 #include <wtf/ParallelJobs.h>
 #include <wtf/Vector.h>
+#include <wtf/text/TextStream.h>
 
 namespace WebCore {
 
@@ -83,78 +84,96 @@
     setAbsolutePaintRect(enclosingIntRect(paintRect));
 }
 
-static inline bool shouldSupersedeExtremum(unsigned char newValue, unsigned char currentValue, MorphologyOperatorType type)
+static inline int pixelArrayIndex(int x, int y, int width)
 {
-    return (type == FEMORPHOLOGY_OPERATOR_ERODE && newValue < currentValue)
-        || (type == FEMORPHOLOGY_OPERATOR_DILATE && newValue > currentValue);
+    return (y * width + x) * 4;
 }
 
-static inline int pixelArrayIndex(int x, int y, int width, int colorChannel)
+template<MorphologyOperatorType type>
+ALWAYS_INLINE ColorComponents minOrMax(const ColorComponents& a, const ColorComponents& b)
 {
-    return (y * width + x) * 4 + colorChannel;
+    if (type == FEMORPHOLOGY_OPERATOR_ERODE)
+        return perComponentMin(a, b);
+
+    return perComponentMax(a, b);
 }
 
-static inline unsigned char columnExtremum(const Uint8ClampedArray& srcPixelArray, int x, int yStart, int yEnd, int width, unsigned colorChannel, MorphologyOperatorType type)
+template<MorphologyOperatorType type>
+ALWAYS_INLINE ColorComponents columnExtremum(const Uint8ClampedArray& srcPixelArray, int x, int yStart, int yEnd, int width)
 {
-    unsigned char extremum = srcPixelArray.item(pixelArrayIndex(x, yStart, width, colorChannel));
+    auto extremum = ColorComponents::fromRGBA(*reinterpret_cast<const unsigned*>(srcPixelArray.data() + pixelArrayIndex(x, yStart, width)));
+
     for (int y = yStart + 1; y < yEnd; ++y) {
-        unsigned char pixel = srcPixelArray.item(pixelArrayIndex(x, y, width, colorChannel));
-        if (shouldSupersedeExtremum(pixel, extremum, type))
-            extremum = pixel;
+        auto pixel = ColorComponents::fromRGBA(*reinterpret_cast<const unsigned*>(srcPixelArray.data() + pixelArrayIndex(x, y, width)));
+        extremum = minOrMax<type>(extremum, pixel);
     }
     return extremum;
 }
 
-static inline unsigned char kernelExtremum(const Vector<unsigned char>& kernel, MorphologyOperatorType type)
+ALWAYS_INLINE ColorComponents columnExtremum(const Uint8ClampedArray& srcPixelArray, int x, int yStart, int yEnd, int width, MorphologyOperatorType type)
 {
-    Vector<unsigned char>::const_iterator iter = kernel.begin();
-    unsigned char extremum = *iter;
-    for (Vector<unsigned char>::const_iterator end = kernel.end(); ++iter != end; ) {
-        if (shouldSupersedeExtremum(*iter, extremum, type))
-            extremum = *iter;
-    }
+    if (type == FEMORPHOLOGY_OPERATOR_ERODE)
+        return columnExtremum<FEMORPHOLOGY_OPERATOR_ERODE>(srcPixelArray, x, yStart, yEnd, width);
+
+    return columnExtremum<FEMORPHOLOGY_OPERATOR_DILATE>(srcPixelArray, x, yStart, yEnd, width);
+}
+
+using ColumnExtrema = Vector<ColorComponents, 16>;
+
+template<MorphologyOperatorType type>
+ALWAYS_INLINE ColorComponents kernelExtremum(const ColumnExtrema& kernel)
+{
+    auto extremum = kernel[0];
+    for (size_t i = 1; i < kernel.size(); ++i)
+        extremum = minOrMax<type>(extremum, kernel[i]);
+
     return extremum;
 }
 
-void FEMorphology::platformApplyGeneric(PaintingData* paintingData, int yStart, int yEnd)
+ALWAYS_INLINE ColorComponents kernelExtremum(const ColumnExtrema& kernel, MorphologyOperatorType type)
 {
-    const Uint8ClampedArray& srcPixelArray = *paintingData->srcPixelArray;
-    Uint8ClampedArray& dstPixelArray = *paintingData->dstPixelArray;
-    const int radiusX = paintingData->radiusX;
-    const int radiusY = paintingData->radiusY;
-    const int width = paintingData->width;
-    const int height = paintingData->height;
+    if (type == FEMORPHOLOGY_OPERATOR_ERODE)
+        return kernelExtremum<FEMORPHOLOGY_OPERATOR_ERODE>(kernel);
 
+    return kernelExtremum<FEMORPHOLOGY_OPERATOR_DILATE>(kernel);
+}
+
+void FEMorphology::platformApplyGeneric(const PaintingData& paintingData, int startY, int endY)
+{
+    const auto& srcPixelArray = *paintingData.srcPixelArray;
+    auto& dstPixelArray = *paintingData.dstPixelArray;
+
+    const int radiusX = paintingData.radiusX;
+    const int radiusY = paintingData.radiusY;
+    const int width = paintingData.width;
+    const int height = paintingData.height;
+
     ASSERT(radiusX <= width || radiusY <= height);
-    ASSERT(yStart >= 0 && yEnd <= height && yStart < yEnd);
+    ASSERT(startY >= 0 && endY <= height && startY < endY);
 
-    Vector<unsigned char> extrema;
-    for (int y = yStart; y < yEnd; ++y) {
-        int yStartExtrema = std::max(0, y - radiusY);
-        int yEndExtrema = std::min(height - 1, y + radiusY);
+    ColumnExtrema extrema;
+    extrema.reserveInitialCapacity(2 * radiusX + 1);
 
-        for (unsigned colorChannel = 0; colorChannel < 4; ++colorChannel) {
-            extrema.clear();
-            // Compute extremas for each columns
-            for (int x = 0; x < radiusX; ++x)
-                extrema.append(columnExtremum(srcPixelArray, x, yStartExtrema, yEndExtrema, width, colorChannel, m_type));
+    for (int y = startY; y < endY; ++y) {
+        int yRadiusStart = std::max(0, y - radiusY);
+        int yRadiusEnd = std::min(height, y + radiusY + 1);
 
-            // Kernel is filled, get extrema of next column
-            for (int x = 0; x < width; ++x) {
-                if (x < width - radiusX) {
-                    int xEnd = std::min(x + radiusX, width - 1);
-                    extrema.append(columnExtremum(srcPixelArray, xEnd, yStartExtrema, yEndExtrema + 1, width, colorChannel, m_type));
-                }
+        extrema.shrink(0);
 
-                if (x > radiusX)
-                    extrema.remove(0);
+        // We start at the left edge, so compute extreme for the radiusX columns.
+        for (int x = 0; x < radiusX; ++x)
+            extrema.append(columnExtremum(srcPixelArray, x, yRadiusStart, yRadiusEnd, width, m_type));
 
-                // The extrema original size = radiusX.
-                // Number of new addition = width - radiusX.
-                // Number of removals = width - radiusX - 1.
-                ASSERT(extrema.size() >= static_cast<size_t>(radiusX + 1));
-                dstPixelArray.set(pixelArrayIndex(x, y, width, colorChannel), kernelExtremum(extrema, m_type));
-            }
+        // Kernel is filled, get extrema of next column
+        for (int x = 0; x < width; ++x) {
+            if (x < width - radiusX)
+                extrema.append(columnExtremum(srcPixelArray, x + radiusX, yRadiusStart, yRadiusEnd, width, m_type));
+
+            if (x > radiusX)
+                extrema.remove(0);
+
+            unsigned* destPixel = reinterpret_cast<unsigned*>(dstPixelArray.data() + pixelArrayIndex(x, y, width));
+            *destPixel = kernelExtremum(extrema, m_type).toRGBA();
         }
     }
 }
@@ -161,12 +180,17 @@
 
 void FEMorphology::platformApplyWorker(PlatformApplyParameters* param)
 {
-    param->filter->platformApplyGeneric(param->paintingData, param->startY, param->endY);
+    param->filter->platformApplyGeneric(*param->paintingData, param->startY, param->endY);
 }
 
-void FEMorphology::platformApply(PaintingData* paintingData)
+void FEMorphology::platformApply(const PaintingData& paintingData)
 {
-    int optimalThreadNumber = (paintingData->width * paintingData->height) / s_minimalArea;
+    // Empirically, runtime is approximately linear over reasonable kernel sizes with a slope of about 0.65.
+    float kernelFactor = sqrt(paintingData.radiusX * paintingData.radiusY) * 0.65;
+
+    static const int minimalArea = (160 * 160); // Empirical data limit for parallel jobs
+    int optimalThreadNumber = (paintingData.width * paintingData.height * kernelFactor) / minimalArea;
+
     if (optimalThreadNumber > 1) {
         ParallelJobs<PlatformApplyParameters> parallelJobs(&WebCore::FEMorphology::platformApplyWorker, optimalThreadNumber);
         int numOfThreads = parallelJobs.numberOfJobs();
@@ -173,8 +197,8 @@
         if (numOfThreads > 1) {
             // Split the job into "jobSize"-sized jobs but there a few jobs that need to be slightly larger since
             // jobSize * jobs < total size. These extras are handled by the remainder "jobsWithExtra".
-            const int jobSize = paintingData->height / numOfThreads;
-            const int jobsWithExtra = paintingData->height % numOfThreads;
+            const int jobSize = paintingData.height / numOfThreads;
+            const int jobsWithExtra = paintingData.height % numOfThreads;
             int currentY = 0;
             for (int job = numOfThreads - 1; job >= 0; --job) {
                 PlatformApplyParameters& param = parallelJobs.parameter(job);
@@ -182,7 +206,7 @@
                 param.startY = currentY;
                 currentY += job < jobsWithExtra ? jobSize + 1 : jobSize;
                 param.endY = currentY;
-                param.paintingData = paintingData;
+                param.paintingData = &paintingData;
             }
             parallelJobs.execute();
             return;
@@ -190,7 +214,7 @@
         // Fallback to single thread model
     }
 
-    platformApplyGeneric(paintingData, 0, paintingData->height);
+    platformApplyGeneric(paintingData, 0, paintingData.height);
 }
 
 bool FEMorphology::platformApplyDegenerate(Uint8ClampedArray& dstPixelArray, const IntRect& imageRect, int radiusX, int radiusY)
@@ -237,7 +261,7 @@
 
     if (platformApplyDegenerate(*dstPixelArray, effectDrawingRect, radiusX, radiusY))
         return;
-
+    
     PaintingData paintingData;
     paintingData.srcPixelArray = srcPixelArray.get();
     paintingData.dstPixelArray = dstPixelArray;
@@ -246,7 +270,7 @@
     paintingData.radiusX = ceilf(radiusX * filter.filterScale());
     paintingData.radiusY = ceilf(radiusY * filter.filterScale());
 
-    platformApply(&paintingData);
+    platformApply(paintingData);
 }
 
 static TextStream& operator<<(TextStream& ts, const MorphologyOperatorType& type)

Modified: trunk/Source/WebCore/platform/graphics/filters/FEMorphology.h (225171 => 225172)


--- trunk/Source/WebCore/platform/graphics/filters/FEMorphology.h	2017-11-27 16:30:58 UTC (rev 225171)
+++ trunk/Source/WebCore/platform/graphics/filters/FEMorphology.h	2017-11-27 16:37:13 UTC (rev 225172)
@@ -67,19 +67,17 @@
         int radiusY;
     };
 
-    static const int s_minimalArea = (300 * 300); // Empirical data limit for parallel jobs
-
     struct PlatformApplyParameters {
         FEMorphology* filter;
         int startY;
         int endY;
-        PaintingData* paintingData;
+        const PaintingData* paintingData;
     };
 
     static void platformApplyWorker(PlatformApplyParameters*);
 
-    inline void platformApply(PaintingData*);
-    inline void platformApplyGeneric(PaintingData*, const int yStart, const int yEnd);
+    void platformApply(const PaintingData&);
+    void platformApplyGeneric(const PaintingData&, int startY, int endY);
 
     MorphologyOperatorType m_type;
     float m_radiusX;
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to