[krita/kazakov/multithreaded-brushes] /: Implement the first version of the multithreaded Pixel Brush

Dmitry Kazakov null at kde.org
Wed Sep 20 15:26:04 UTC 2017


Git commit e03e95338b7dbefe82b982287305af962d94b57b by Dmitry Kazakov.
Committed on 20/09/2017 at 15:23.
Pushed by dkazakov into branch 'kazakov/multithreaded-brushes'.

Implement the first version of the multithreaded Pixel Brush

All the presets that use Pixel Brush (KisBrushOp) are now
multithreaded and rendered asynchronously. Basically, it means
that if the brush is too slow, Krita will lower down FPS rate
for the sake of faster rendering of the stroke.


Short summary:

1) It doesn't use strokes system's threading, just QtConcurrent. It
   is not good, but works for now. I hope it is only a temporary
   solution.

2) Updates are coming asynchronously with the period of 20...80ms,
   which is 50...12fps. I didn't manage to implement a correct control
   loop for auto-adjusting the FPS value, because it needs porting the
   threading part into strokes system and a bit of refactoring of
   the strokes system itself. Therefore, the FPS adjustment is controlled
   by an open-loop system, based on one-dab-rendering-time. Basically,
   FPS is proportional to the time spent on rendering a single tile.

3) The patch adds two new API functions: KisPaintOpSettings::
   needsAsynchronousUpdates() tells if the paintop uses threading and
   needs asynchronous updates. When this function returns true, the
   freehand stroke does additional calls to
   KisPaintOp::doAsyncronousUpdate(), which does the rendering itself.

4) Still to be implemented:
     * color source options
     * postprocessing: sharpness and texturing
     * selection handling (works only in Wash mode)
     * mirroring mode
     * pipe brushes

CC:kimageshop at kde.org

C  +16   -0    libs/image/KisRenderedDab.h [from: plugins/paintops/defaultpaintops/brush/KisRenderedDab.h - 070% similarity]
M  +5    -0    libs/image/brushengine/kis_paintop.cc
M  +8    -0    libs/image/brushengine/kis_paintop.h
M  +5    -0    libs/image/brushengine/kis_paintop_settings.cpp
M  +6    -0    libs/image/brushengine/kis_paintop_settings.h
M  +14   -0    libs/image/kis_painter.cc
M  +12   -1    libs/image/kis_painter.h
M  +41   -29   libs/image/kis_painter_blt_multi_fixed.cpp
M  +4    -4    libs/image/kis_painter_p.h
A  +-    --    libs/image/tests/data/kispainter_test/massive_bitblt_full_update_6_varyop.png
A  +-    --    libs/image/tests/data/kispainter_test/massive_bitblt_partial_update_6_varyop.png
M  +22   -6    libs/image/tests/kis_painter_test.cpp
M  +2    -1    libs/image/tests/kis_painter_test.h
M  +12   -0    libs/pigment/KoCompositeOp.cpp
M  +4    -0    libs/pigment/KoCompositeOp.h
M  +5    -0    libs/ui/tool/kis_resources_snapshot.cpp
M  +1    -0    libs/ui/tool/kis_resources_snapshot.h
M  +32   -1    libs/ui/tool/kis_tool_freehand_helper.cpp
M  +5    -0    libs/ui/tool/kis_tool_freehand_helper.h
M  +7    -0    libs/ui/tool/kis_tool_multihand_helper.cpp
M  +3    -0    libs/ui/tool/kis_tool_multihand_helper.h
M  +108  -54   libs/ui/tool/strokes/freehand_stroke.cpp
M  +43   -7    libs/ui/tool/strokes/freehand_stroke.h
M  +5    -0    libs/ui/tool/strokes/kis_painter_based_stroke_strategy.cpp
M  +1    -0    libs/ui/tool/strokes/kis_painter_based_stroke_strategy.h
M  +1    -0    plugins/paintops/defaultpaintops/CMakeLists.txt
C  +4    -9    plugins/paintops/defaultpaintops/brush/KisBrushOpSettings.cpp [from: plugins/paintops/defaultpaintops/brush/KisRenderedDab.h - 081% similarity]
R  +8    -7    plugins/paintops/defaultpaintops/brush/KisBrushOpSettings.h [from: plugins/paintops/defaultpaintops/brush/KisRenderedDab.h - 076% similarity]
M  +8    -2    plugins/paintops/defaultpaintops/brush/KisDabRenderingExecutor.cpp
M  +4    -1    plugins/paintops/defaultpaintops/brush/KisDabRenderingExecutor.h
M  +15   -7    plugins/paintops/defaultpaintops/brush/KisDabRenderingJob.cpp
M  +1    -1    plugins/paintops/defaultpaintops/brush/KisDabRenderingJob.h
M  +50   -4    plugins/paintops/defaultpaintops/brush/KisDabRenderingQueue.cpp
M  +5    -2    plugins/paintops/defaultpaintops/brush/KisDabRenderingQueue.h
M  +76   -24   plugins/paintops/defaultpaintops/brush/kis_brushop.cpp
M  +6    -1    plugins/paintops/defaultpaintops/brush/kis_brushop.h
M  +2    -2    plugins/paintops/defaultpaintops/brush/kis_brushop_settings_widget.cpp
M  +21   -16   plugins/paintops/defaultpaintops/brush/tests/KisDabRenderingQueueTest.cpp
M  +2    -2    plugins/paintops/defaultpaintops/defaultpaintops_plugin.cc
M  +17   -5    plugins/paintops/libpaintop/kis_pressure_flow_opacity_option.cpp
M  +1    -0    plugins/paintops/libpaintop/kis_pressure_flow_opacity_option.h

https://commits.kde.org/krita/e03e95338b7dbefe82b982287305af962d94b57b

diff --git a/plugins/paintops/defaultpaintops/brush/KisRenderedDab.h b/libs/image/KisRenderedDab.h
similarity index 70%
copy from plugins/paintops/defaultpaintops/brush/KisRenderedDab.h
copy to libs/image/KisRenderedDab.h
index 7476fb791a8..92f7f9a7f24 100644
--- a/plugins/paintops/defaultpaintops/brush/KisRenderedDab.h
+++ b/libs/image/KisRenderedDab.h
@@ -20,11 +20,27 @@
 #define KISRENDEREDDAB_H
 
 #include "kis_types.h"
+#include "kis_fixed_paint_device.h"
 
 struct KisRenderedDab
 {
+    KisRenderedDab() {}
+    KisRenderedDab(KisFixedPaintDeviceSP _device)
+        : device(_device),
+          offset(_device->bounds().topLeft())
+    {
+    }
+
     KisFixedPaintDeviceSP device;
     QPoint offset;
+
+    qreal opacity = OPACITY_OPAQUE_F;
+    qreal flow = OPACITY_OPAQUE_F;
+    qreal averageOpacity = OPACITY_TRANSPARENT_F;
+
+    inline QRect realBounds() const {
+        return QRect(offset, device->bounds().size());
+    }
 };
 
 #endif // KISRENDEREDDAB_H
diff --git a/libs/image/brushengine/kis_paintop.cc b/libs/image/brushengine/kis_paintop.cc
index 24b1df7d358..cb8976a6de6 100644
--- a/libs/image/brushengine/kis_paintop.cc
+++ b/libs/image/brushengine/kis_paintop.cc
@@ -103,6 +103,11 @@ void KisPaintOp::splitCoordinate(qreal coordinate, qint32 *whole, qreal *fractio
     *fraction = f;
 }
 
+int KisPaintOp::doAsyncronousUpdate()
+{
+    return 40;
+}
+
 static void paintBezierCurve(KisPaintOp *paintOp,
                              const KisPaintInformation &pi1,
                              const KisVector2D &control1,
diff --git a/libs/image/brushengine/kis_paintop.h b/libs/image/brushengine/kis_paintop.h
index e97639249be..6e7245042fe 100644
--- a/libs/image/brushengine/kis_paintop.h
+++ b/libs/image/brushengine/kis_paintop.h
@@ -111,6 +111,14 @@ public:
      */
     static void splitCoordinate(qreal coordinate, qint32 *whole, qreal *fraction);
 
+    /**
+     * If the preset supports asynchronous updates, then the stroke execution core will
+     * call this method with a desured frame rate.
+     *
+     * @return the desired FPS rate (period of updates)
+     */
+    virtual int doAsyncronousUpdate();
+
 protected:
     friend class KisPaintInformation;
     /**
diff --git a/libs/image/brushengine/kis_paintop_settings.cpp b/libs/image/brushengine/kis_paintop_settings.cpp
index c6680e195a5..6702f91497e 100644
--- a/libs/image/brushengine/kis_paintop_settings.cpp
+++ b/libs/image/brushengine/kis_paintop_settings.cpp
@@ -332,6 +332,11 @@ bool KisPaintOpSettings::useSpacingUpdates() const
     return getBool(SPACING_USE_UPDATES, false);
 }
 
+bool KisPaintOpSettings::needsAsynchronousUpdates() const
+{
+    return false;
+}
+
 QPainterPath KisPaintOpSettings::brushOutline(const KisPaintInformation &info, OutlineMode mode)
 {
     QPainterPath path;
diff --git a/libs/image/brushengine/kis_paintop_settings.h b/libs/image/brushengine/kis_paintop_settings.h
index 7ed5fb2e5c6..bef12bea17c 100644
--- a/libs/image/brushengine/kis_paintop_settings.h
+++ b/libs/image/brushengine/kis_paintop_settings.h
@@ -151,6 +151,12 @@ public:
      */
     virtual bool useSpacingUpdates() const;
 
+    /**
+     * Indicates if the tool should call paintOp->doAsynchronousUpdate() inbetween
+     * paintAt() calls to do the asynchronous rendering
+     */
+    virtual bool needsAsynchronousUpdates() const;
+
     /**
      * This enum defines the current mode for painting an outline.
      */
diff --git a/libs/image/kis_painter.cc b/libs/image/kis_painter.cc
index 6855faaa6fc..fab1e0296ea 100644
--- a/libs/image/kis_painter.cc
+++ b/libs/image/kis_painter.cc
@@ -2542,6 +2542,20 @@ void KisPainter::setOpacityUpdateAverage(quint8 opacity)
     d->paramInfo.updateOpacityAndAverage(float(opacity) / 255.0f);
 }
 
+void KisPainter::setAverageOpacity(qreal averageOpacity)
+{
+    d->paramInfo.setOpacityAndAverage(d->paramInfo.opacity, averageOpacity);
+}
+
+qreal KisPainter::blendAverageOpacity(qreal opacity, qreal averageOpacity)
+{
+    const float exponent = 0.1;
+
+    return averageOpacity < opacity ?
+        opacity :
+        exponent * opacity + (1.0 - exponent) * (averageOpacity);
+}
+
 void KisPainter::setOpacity(quint8 opacity)
 {
     d->isOpacityUnit = opacity == OPACITY_OPAQUE_U8;
diff --git a/libs/image/kis_painter.h b/libs/image/kis_painter.h
index a36cbbd7c7b..f82030b93b5 100644
--- a/libs/image/kis_painter.h
+++ b/libs/image/kis_painter.h
@@ -53,6 +53,7 @@ class KoPattern;
 class KisPaintInformation;
 class KisPaintOp;
 class KisDistanceInformation;
+class KisRenderedDab;
 
 /**
  * KisPainter contains the graphics primitives necessary to draw on a
@@ -301,7 +302,7 @@ public:
      * If \p rc doesn't cross the device's rect, then the device is not
      * rendered at all.
      */
-    void bltFixed(const QRect &rc, const QList<KisFixedPaintDeviceSP> allSrcDevices);
+    void bltFixed(const QRect &rc, const QList<KisRenderedDab> allSrcDevices);
 
     /**
      * Convenience method that uses QPoint and QRect.
@@ -714,6 +715,16 @@ public:
      */
     void setOpacityUpdateAverage(quint8 opacity);
 
+    /**
+     * Sets average opacity, that is used to make ALPHA_DARKEN painting look correct
+     */
+    void setAverageOpacity(qreal averageOpacity);
+
+    /**
+     * Calculate average opacity value after painting a single dab with \p opacity
+     */
+    static qreal blendAverageOpacity(qreal opacity, qreal averageOpacity);
+
     /// Set the opacity which is used in painting (like filling polygons)
     void setOpacity(quint8 opacity);
 
diff --git a/libs/image/kis_painter_blt_multi_fixed.cpp b/libs/image/kis_painter_blt_multi_fixed.cpp
index e7f0c997575..81528ecb747 100644
--- a/libs/image/kis_painter_blt_multi_fixed.cpp
+++ b/libs/image/kis_painter_blt_multi_fixed.cpp
@@ -22,17 +22,18 @@
 #include "kis_paint_device.h"
 #include "kis_fixed_paint_device.h"
 #include "kis_random_accessor_ng.h"
+#include "KisRenderedDab.h"
 
 void KisPainter::Private::collectRowDevices(int x1, int x2, int y, int y2,
-                                            const QList<KisFixedPaintDeviceSP> allDevices,
-                                            QList<KisFixedPaintDeviceSP> *rowDevices,
+                                            const QList<KisRenderedDab> allDevices,
+                                            QList<KisRenderedDab> *rowDevices,
                                             int *numContiguousRows)
 {
     *numContiguousRows = y2 - y + 1;
     rowDevices->clear();
 
     for (auto it = allDevices.cbegin(); it != allDevices.cend(); ++it) {
-        const QRect rc = (*it)->bounds();
+        const QRect rc = it->realBounds();
 
         if (rc.left() > x2 || rc.right() < x1 ||
             rc.top() > y || rc.bottom() < y) {
@@ -53,18 +54,22 @@ void KisPainter::Private::collectRowDevices(int x1, int x2, int y, int y2,
 struct KisPainter::Private::ChunkDescriptor {
     quint8 *ptr;
     int rowStride;
+
+    float opacity = 1.0;
+    float averageOpacity = 1.0;
+    float flow = 1.0;
 };
 
 void KisPainter::Private::collectChunks(int x, int x2, int y,
-                                        QList<KisFixedPaintDeviceSP> rowDevices,
+                                        QList<KisRenderedDab> rowDevices,
                                         QList<ChunkDescriptor> *chunks,
                                         int *numContiguosColumns)
 {
     *numContiguosColumns = x2 - x + 1;
-    chunks->clear();
+    chunks->erase(chunks->begin(), chunks->end());
 
     for (auto it = rowDevices.cbegin(); it != rowDevices.cend(); ++it) {
-        const QRect rc = (*it)->bounds();
+        const QRect rc = it->realBounds();
 
         if (rc.left() > x || rc.right() < x) {
             const int distance = rc.left() - x;
@@ -78,12 +83,15 @@ void KisPainter::Private::collectChunks(int x, int x2, int y,
         *numContiguosColumns = qMin(*numContiguosColumns,
                                     rc.right() - x + 1);
 
-        const int pixelSize = (*it)->pixelSize();
+        const int pixelSize = it->device->pixelSize();
 
         ChunkDescriptor chunk;
+        chunk.opacity = it->opacity;
+        chunk.averageOpacity = it->averageOpacity;
+        chunk.flow = it->flow;
         chunk.rowStride = rc.width() * pixelSize;
         chunk.ptr =
-            (*it)->data() +
+            it->device->data() +
             chunk.rowStride * (y - rc.top()) +
             pixelSize * (x - rc.left());
 
@@ -94,7 +102,8 @@ void KisPainter::Private::collectChunks(int x, int x2, int y,
 void KisPainter::Private::applyChunks(int x, int y, int width, int height,
                                       KisRandomAccessorSP dstIt,
                                       const QList<ChunkDescriptor> &chunks,
-                                      const KoColorSpace *srcColorSpace)
+                                      const KoColorSpace *srcColorSpace,
+                                      KoCompositeOp::ParameterInfo &localParamInfo)
 {
     const int srcPixelSize = srcColorSpace->pixelSize();
     QList<ChunkDescriptor> savedChunks = chunks;
@@ -102,7 +111,6 @@ void KisPainter::Private::applyChunks(int x, int y, int width, int height,
     qint32 dstY = y;
     qint32 rowsRemaining = height;
 
-
     while (rowsRemaining > 0) {
         qint32 dstX = x;
 
@@ -121,19 +129,21 @@ void KisPainter::Private::applyChunks(int x, int y, int width, int height,
             qint32 dstRowStride = dstIt->rowStride(dstX, dstY);
             dstIt->moveTo(dstX, dstY);
 
-            paramInfo.dstRowStart   = dstIt->rawData();
-            paramInfo.dstRowStride  = dstRowStride;
-            paramInfo.maskRowStart  = 0;
-            paramInfo.maskRowStride = 0;
-            paramInfo.rows          = rows;
-            paramInfo.cols          = columns;
+            localParamInfo.dstRowStart   = dstIt->rawData();
+            localParamInfo.dstRowStride  = dstRowStride;
+            localParamInfo.maskRowStart  = 0;
+            localParamInfo.maskRowStride = 0;
+            localParamInfo.rows          = rows;
+            localParamInfo.cols          = columns;
 
             const int srcColumnStep = srcPixelSize * columns;
 
             for (auto it = rowChunks.begin(); it != rowChunks.end(); ++it) {
-                paramInfo.srcRowStart   = it->ptr;
-                paramInfo.srcRowStride  = it->rowStride;
-                colorSpace->bitBlt(srcColorSpace, paramInfo, compositeOp, renderingIntent, conversionFlags);
+                localParamInfo.srcRowStart   = it->ptr;
+                localParamInfo.srcRowStride  = it->rowStride;
+                localParamInfo.setOpacityAndAverage(it->opacity, it->averageOpacity);
+                localParamInfo.flow = it->flow;
+                colorSpace->bitBlt(srcColorSpace, localParamInfo, compositeOp, renderingIntent, conversionFlags);
 
                 it->ptr += srcColumnStep;
             }
@@ -153,31 +163,32 @@ void KisPainter::Private::applyChunks(int x, int y, int width, int height,
 }
 
 
-void KisPainter::bltFixed(const QRect &rc, const QList<KisFixedPaintDeviceSP> allSrcDevices)
+void KisPainter::bltFixed(const QRect &rc, const QList<KisRenderedDab> allSrcDevices)
 {
     const KoColorSpace *srcColorSpace = 0;
-    QList<KisFixedPaintDeviceSP> devices;
+    QList<KisRenderedDab> devices;
     QRect totalDevicesRect;
 
-    Q_FOREACH (KisFixedPaintDeviceSP dev, allSrcDevices) {
-        if (rc.intersects(dev->bounds())) {
-            devices.append(dev);
-            totalDevicesRect |= dev->bounds();
+    Q_FOREACH (const KisRenderedDab &dab, allSrcDevices) {
+        if (rc.intersects(dab.realBounds())) {
+            devices.append(dab);
+            totalDevicesRect |= dab.realBounds();
         }
 
         if (!srcColorSpace) {
-            srcColorSpace = dev->colorSpace();
+            srcColorSpace = dab.device->colorSpace();
         } else {
-            KIS_SAFE_ASSERT_RECOVER_RETURN(*srcColorSpace == *dev->colorSpace());
+            KIS_SAFE_ASSERT_RECOVER_RETURN(*srcColorSpace == *dab.device->colorSpace());
         }
     }
 
     if (devices.isEmpty() || !totalDevicesRect.intersects(rc)) return;
 
+    KoCompositeOp::ParameterInfo localParamInfo = d->paramInfo;
     KisRandomAccessorSP dstIt = d->device->createRandomAccessorNG(rc.left(), rc.top());
 
     int row = rc.top();
-    QList<KisFixedPaintDeviceSP> rowDevices;
+    QList<KisRenderedDab> rowDevices;
     int numContiguousRowsInDevices = 0;
 
     while (row <= rc.bottom()) {
@@ -199,7 +210,8 @@ void KisPainter::bltFixed(const QRect &rc, const QList<KisFixedPaintDeviceSP> al
                                    numContiguousColumnsInDevices, numContiguousRowsInDevices,
                                    dstIt,
                                    chunks,
-                                   srcColorSpace);
+                                   srcColorSpace,
+                                   localParamInfo);
                 }
 
                 column += numContiguousColumnsInDevices;
diff --git a/libs/image/kis_painter_p.h b/libs/image/kis_painter_p.h
index 622939f650d..fd9575324db 100644
--- a/libs/image/kis_painter_p.h
+++ b/libs/image/kis_painter_p.h
@@ -85,20 +85,20 @@ struct Q_DECL_HIDDEN KisPainter::Private {
     void fillPainterPathImpl(const QPainterPath& path, const QRect &requestedRect);
 
     static void collectRowDevices(int x1, int x2, int y, int y2,
-                                  const QList<KisFixedPaintDeviceSP> allDevices,
-                                  QList<KisFixedPaintDeviceSP> *rowDevices,
+                                  const QList<KisRenderedDab> allDevices,
+                                  QList<KisRenderedDab> *rowDevices,
                                   int *numContiguousRows);
 
 
     struct ChunkDescriptor;
     static void collectChunks(int x, int x2, int y,
-                              QList<KisFixedPaintDeviceSP> rowDevices,
+                              QList<KisRenderedDab> rowDevices,
                               QList<ChunkDescriptor> *chunks,
                               int *numContiguosColumns);
     void applyChunks(int x, int y, int width, int height,
                      KisRandomAccessorSP dstIt,
                      const QList<ChunkDescriptor> &chunks,
-                     const KoColorSpace *srcColorSpace);
+                     const KoColorSpace *srcColorSpace, KoCompositeOp::ParameterInfo &localParamInfo);
 
 };
 
diff --git a/libs/image/tests/data/kispainter_test/massive_bitblt_full_update_6_varyop.png b/libs/image/tests/data/kispainter_test/massive_bitblt_full_update_6_varyop.png
new file mode 100644
index 00000000000..4e5c38adcc3
Binary files /dev/null and b/libs/image/tests/data/kispainter_test/massive_bitblt_full_update_6_varyop.png differ
diff --git a/libs/image/tests/data/kispainter_test/massive_bitblt_partial_update_6_varyop.png b/libs/image/tests/data/kispainter_test/massive_bitblt_partial_update_6_varyop.png
new file mode 100644
index 00000000000..6ff304e66e5
Binary files /dev/null and b/libs/image/tests/data/kispainter_test/massive_bitblt_partial_update_6_varyop.png differ
diff --git a/libs/image/tests/kis_painter_test.cpp b/libs/image/tests/kis_painter_test.cpp
index cd409b5148b..4cca54b8cb9 100644
--- a/libs/image/tests/kis_painter_test.cpp
+++ b/libs/image/tests/kis_painter_test.cpp
@@ -517,8 +517,9 @@ void KisPainterTest::benchmarkBitBltOldData()
     }
 }
 #include "kis_paint_device_debug_utils.h"
+#include "KisRenderedDab.h"
 
-void testMassiveBltFixedImpl(int numRects)
+void testMassiveBltFixedImpl(int numRects, bool varyOpacity = false)
 {
     const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8();
     KisPaintDeviceSP dst = new KisPaintDevice(cs);
@@ -529,7 +530,7 @@ void testMassiveBltFixedImpl(int numRects)
     colors << Qt::blue;
 
     QRect devicesRect;
-    QList<KisFixedPaintDeviceSP> devices;
+    QList<KisRenderedDab> devices;
 
     for (int i = 0; i < numRects; i++) {
         const QRect rc(10 + i * 10, 10 + i * 10, 30, 30);
@@ -538,10 +539,18 @@ void testMassiveBltFixedImpl(int numRects)
         dev->initialize();
         dev->fill(rc, KoColor(colors[i % 3], cs));
         dev->fill(kisGrowRect(rc, -5), KoColor(Qt::white, cs));
-        devices << dev;
+
+        KisRenderedDab dab;
+        dab.device = dev;
+        dab.offset = dev->bounds().topLeft();
+        dab.opacity = varyOpacity ? qreal(1 + i) / numRects : 1.0;
+        dab.flow = 1.0;
+
+        devices << dab;
         devicesRect |= rc;
     }
 
+    const QString opacityPostfix = varyOpacity ? "_varyop" : "";
     const QRect fullRect = kisGrowRect(devicesRect, 10);
 
     {
@@ -551,7 +560,7 @@ void testMassiveBltFixedImpl(int numRects)
         QVERIFY(TestUtil::checkQImage(dst->convertToQImage(0, fullRect),
                                       "kispainter_test",
                                       "massive_bitblt",
-                                      QString("full_update_%1").arg(numRects)));
+                                      QString("full_update_%1%2").arg(numRects).arg(opacityPostfix)));
     }
 
     dst->clear();
@@ -568,7 +577,7 @@ void testMassiveBltFixedImpl(int numRects)
         QVERIFY(TestUtil::checkQImage(dst->convertToQImage(0, fullRect),
                                       "kispainter_test",
                                       "massive_bitblt",
-                                      QString("partial_update_%1").arg(numRects)));
+                                      QString("partial_update_%1%2").arg(numRects).arg(opacityPostfix)));
 
     }
 }
@@ -583,12 +592,17 @@ void KisPainterTest::testMassiveBltFixedMultiTile()
     testMassiveBltFixedImpl(6);
 }
 
+void KisPainterTest::testMassiveBltFixedMultiTileWithOpacity()
+{
+    testMassiveBltFixedImpl(6, true);
+}
+
 void KisPainterTest::testMassiveBltFixedCornerCases()
 {
     const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8();
     KisPaintDeviceSP dst = new KisPaintDevice(cs);
 
-    QList<KisFixedPaintDeviceSP> devices;
+    QList<KisRenderedDab> devices;
 
     QVERIFY(dst->extent().isEmpty());
 
@@ -607,6 +621,8 @@ void KisPainterTest::testMassiveBltFixedCornerCases()
     dev->initialize();
     dev->fill(rc, KoColor(Qt::white, cs));
 
+    devices.append(KisRenderedDab(dev));
+
     {
         // rect outside the devices bounds, shouldn't crash
         KisPainter painter(dst);
diff --git a/libs/image/tests/kis_painter_test.h b/libs/image/tests/kis_painter_test.h
index ef0c4eee617..3ad75c92a15 100644
--- a/libs/image/tests/kis_painter_test.h
+++ b/libs/image/tests/kis_painter_test.h
@@ -57,8 +57,9 @@ private Q_SLOTS:
     void testMassiveBltFixedSingleTile();
     void testMassiveBltFixedMultiTile();
 
-    void testMassiveBltFixedCornerCases();
+    void testMassiveBltFixedMultiTileWithOpacity();
 
+    void testMassiveBltFixedCornerCases();
 };
 
 #endif
diff --git a/libs/pigment/KoCompositeOp.cpp b/libs/pigment/KoCompositeOp.cpp
index 0b619a4af2f..66f3e8352e8 100644
--- a/libs/pigment/KoCompositeOp.cpp
+++ b/libs/pigment/KoCompositeOp.cpp
@@ -60,6 +60,18 @@ KoCompositeOp::ParameterInfo& KoCompositeOp::ParameterInfo::operator=(const Para
     return *this;
 }
 
+void KoCompositeOp::ParameterInfo::setOpacityAndAverage(float _opacity, float _averageOpacity)
+{
+    if (qFuzzyCompare(_opacity, _averageOpacity)) {
+        opacity = _opacity;
+        lastOpacity = &opacity;
+    } else {
+        opacity = _opacity;
+        _lastOpacityData = _averageOpacity;
+        lastOpacity = &_lastOpacityData;
+    }
+}
+
 void KoCompositeOp::ParameterInfo::copy(const ParameterInfo &rhs)
 {
     dstRowStart = rhs.dstRowStart;
diff --git a/libs/pigment/KoCompositeOp.h b/libs/pigment/KoCompositeOp.h
index 76a8b662d79..fb6fe18f56c 100644
--- a/libs/pigment/KoCompositeOp.h
+++ b/libs/pigment/KoCompositeOp.h
@@ -25,6 +25,8 @@
 #include <QMultiMap>
 #include <QBitArray>
 
+#include <boost/optional.hpp>
+
 #include "kritapigment_export.h"
 
 class KoColorSpace;
@@ -70,6 +72,8 @@ public:
         float*        lastOpacity;
         QBitArray     channelFlags;
 
+        void setOpacityAndAverage(float _opacity, float _averageOpacity);
+
         void updateOpacityAndAverage(float value);
     private:
         inline void copy(const ParameterInfo &rhs);
diff --git a/libs/ui/tool/kis_resources_snapshot.cpp b/libs/ui/tool/kis_resources_snapshot.cpp
index dc20e631137..7c556b7310a 100644
--- a/libs/ui/tool/kis_resources_snapshot.cpp
+++ b/libs/ui/tool/kis_resources_snapshot.cpp
@@ -373,6 +373,11 @@ bool KisResourcesSnapshot::presetAllowsLod() const
     return m_d->presetAllowsLod;
 }
 
+bool KisResourcesSnapshot::presetNeedsAsynchronousUpdates() const
+{
+    return m_d->currentPaintOpPreset && m_d->currentPaintOpPreset->settings()->needsAsynchronousUpdates();
+}
+
 void KisResourcesSnapshot::setFGColorOverride(const KoColor &color)
 {
     m_d->currentFgColor = color;
diff --git a/libs/ui/tool/kis_resources_snapshot.h b/libs/ui/tool/kis_resources_snapshot.h
index dede9ba1470..b223f401c1b 100644
--- a/libs/ui/tool/kis_resources_snapshot.h
+++ b/libs/ui/tool/kis_resources_snapshot.h
@@ -89,6 +89,7 @@ public:
 
     qreal effectiveZoom() const;
     bool presetAllowsLod() const;
+    bool presetNeedsAsynchronousUpdates() const;
 
     void setFGColorOverride(const KoColor &color);
     void setBGColorOverride(const KoColor &color);
diff --git a/libs/ui/tool/kis_tool_freehand_helper.cpp b/libs/ui/tool/kis_tool_freehand_helper.cpp
index 8cb22db0fd0..758b4cbad22 100644
--- a/libs/ui/tool/kis_tool_freehand_helper.cpp
+++ b/libs/ui/tool/kis_tool_freehand_helper.cpp
@@ -101,6 +101,8 @@ struct KisToolFreehandHelper::Private
     KisStabilizedEventsSampler stabilizedSampler;
     KisStabilizerDelayedPaintHelper stabilizerDelayedPaintHelper;
 
+    QTimer asynchronousUpdatesThresholdTimer;
+
     int canvasRotation;
     bool canvasMirroredH;
 
@@ -124,6 +126,7 @@ KisToolFreehandHelper::KisToolFreehandHelper(KisPaintingInformationBuilder *info
     m_d->strokeTimeoutTimer.setSingleShot(true);
     connect(&m_d->strokeTimeoutTimer, SIGNAL(timeout()), SLOT(finishStroke()));
     connect(&m_d->airbrushingTimer, SIGNAL(timeout()), SLOT(doAirbrushing()));
+    connect(&m_d->asynchronousUpdatesThresholdTimer, SIGNAL(timeout()), SLOT(doAsynchronousUpdate()));
     connect(&m_d->stabilizerPollTimer, SIGNAL(timeout()), SLOT(stabilizerPollAndPaint()));
 
     m_d->stabilizerDelayedPaintHelper.setPaintLineCallback(
@@ -306,9 +309,12 @@ void KisToolFreehandHelper::initPaintImpl(qreal startAngle,
     m_d->history.clear();
     m_d->distanceHistory.clear();
 
-    if(airbrushing) {
+    if (airbrushing) {
         m_d->airbrushingTimer.setInterval(computeAirbrushTimerInterval());
         m_d->airbrushingTimer.start();
+    } else if (m_d->resources->presetNeedsAsynchronousUpdates()) {
+        m_d->asynchronousUpdatesThresholdTimer.setInterval(80 /* msec */);
+        m_d->asynchronousUpdatesThresholdTimer.start();
     }
 
     if (m_d->smoothingOptions->smoothingType() == KisSmoothingOptions::STABILIZER) {
@@ -612,6 +618,10 @@ void KisToolFreehandHelper::endPaint()
         m_d->airbrushingTimer.stop();
     }
 
+    if (m_d->asynchronousUpdatesThresholdTimer.isActive()) {
+        m_d->asynchronousUpdatesThresholdTimer.stop();
+    }
+
     if (m_d->smoothingOptions->smoothingType() == KisSmoothingOptions::STABILIZER) {
         stabilizerEnd();
     }
@@ -642,6 +652,10 @@ void KisToolFreehandHelper::cancelPaint()
         m_d->airbrushingTimer.stop();
     }
 
+    if (m_d->asynchronousUpdatesThresholdTimer.isActive()) {
+        m_d->asynchronousUpdatesThresholdTimer.stop();
+    }
+
     if (m_d->stabilizerPollTimer.isActive()) {
         m_d->stabilizerPollTimer.stop();
     }
@@ -862,12 +876,24 @@ void KisToolFreehandHelper::doAirbrushing()
     }
 }
 
+void KisToolFreehandHelper::doAsynchronousUpdate()
+{
+    asyncUpdate();
+}
+
 int KisToolFreehandHelper::computeAirbrushTimerInterval() const
 {
     qreal realInterval = m_d->resources->airbrushingInterval() * AIRBRUSH_INTERVAL_FACTOR;
     return qMax(1, qFloor(realInterval));
 }
 
+void KisToolFreehandHelper::asyncUpdate(int painterInfoId)
+{
+    m_d->strokesFacade->addJob(m_d->strokeId,
+                               new FreehandStrokeStrategy::UpdateData(m_d->resources->currentNode(),
+                                                                      painterInfoId));
+}
+
 void KisToolFreehandHelper::paintAt(int painterInfoId,
                                     const KisPaintInformation &pi)
 {
@@ -942,6 +968,11 @@ void KisToolFreehandHelper::createPainters(QVector<PainterInfo*> &painterInfos,
     painterInfos << new PainterInfo(startDist);
 }
 
+void KisToolFreehandHelper::asyncUpdate()
+{
+    asyncUpdate(0);
+}
+
 void KisToolFreehandHelper::paintAt(const KisPaintInformation &pi)
 {
     paintAt(0, pi);
diff --git a/libs/ui/tool/kis_tool_freehand_helper.h b/libs/ui/tool/kis_tool_freehand_helper.h
index dcf07292b50..f6bcec7d3f0 100644
--- a/libs/ui/tool/kis_tool_freehand_helper.h
+++ b/libs/ui/tool/kis_tool_freehand_helper.h
@@ -113,6 +113,8 @@ protected:
 
     // lo-level methods for painting primitives
 
+    void asyncUpdate(int painterInfoId);
+
     void paintAt(int painterInfoId, const KisPaintInformation &pi);
 
     void paintLine(int painterInfoId,
@@ -127,6 +129,8 @@ protected:
 
     // hi-level methods for painting primitives
 
+    virtual void asyncUpdate();
+
     virtual void paintAt(const KisPaintInformation &pi);
 
     virtual void paintLine(const KisPaintInformation &pi1,
@@ -152,6 +156,7 @@ private Q_SLOTS:
 
     void finishStroke();
     void doAirbrushing();
+    void doAsynchronousUpdate();
     void stabilizerPollAndPaint();
 
 private:
diff --git a/libs/ui/tool/kis_tool_multihand_helper.cpp b/libs/ui/tool/kis_tool_multihand_helper.cpp
index 106e4ff78dd..e9466044c8e 100644
--- a/libs/ui/tool/kis_tool_multihand_helper.cpp
+++ b/libs/ui/tool/kis_tool_multihand_helper.cpp
@@ -54,6 +54,13 @@ void KisToolMultihandHelper::createPainters(QVector<PainterInfo*> &painterInfos,
     }
 }
 
+void KisToolMultihandHelper::asyncUpdate()
+{
+    for (int i = 0; i < d->transformations.size(); i++) {
+        asyncUpdate(i);
+    }
+}
+
 void KisToolMultihandHelper::paintAt(const KisPaintInformation &pi)
 {
     for (int i = 0; i < d->transformations.size(); i++) {
diff --git a/libs/ui/tool/kis_tool_multihand_helper.h b/libs/ui/tool/kis_tool_multihand_helper.h
index 68cc72ae479..2e79e52301a 100644
--- a/libs/ui/tool/kis_tool_multihand_helper.h
+++ b/libs/ui/tool/kis_tool_multihand_helper.h
@@ -38,6 +38,8 @@ protected:
     void createPainters(QVector<PainterInfo*> &painterInfos,
                         const KisDistanceInformation &startDist) override;
 
+    void asyncUpdate() override;
+
     void paintAt(const KisPaintInformation &pi) override;
 
     void paintLine(const KisPaintInformation &pi1,
@@ -48,6 +50,7 @@ protected:
                           const QPointF &control2,
                           const KisPaintInformation &pi2) override;
 
+    using KisToolFreehandHelper::asyncUpdate;
     using KisToolFreehandHelper::paintAt;
     using KisToolFreehandHelper::paintLine;
     using KisToolFreehandHelper::paintBezierCurve;
diff --git a/libs/ui/tool/strokes/freehand_stroke.cpp b/libs/ui/tool/strokes/freehand_stroke.cpp
index 2ca96405bc3..a1cc9534d5d 100644
--- a/libs/ui/tool/strokes/freehand_stroke.cpp
+++ b/libs/ui/tool/strokes/freehand_stroke.cpp
@@ -18,10 +18,13 @@
 
 #include "freehand_stroke.h"
 
+#include <QElapsedTimer>
+
 #include "kis_canvas_resource_provider.h"
 #include <brushengine/kis_paintop_preset.h>
 #include <brushengine/kis_paintop_settings.h>
 #include "kis_painter.h"
+#include "kis_paintop.h"
 
 #include "kis_update_time_monitor.h"
 
@@ -30,10 +33,19 @@
 
 struct FreehandStrokeStrategy::Private
 {
-    Private(KisResourcesSnapshotSP _resources) : resources(_resources) {}
+    Private(KisResourcesSnapshotSP _resources)
+        : resources(_resources),
+          needsAsynchronousUpdates(_resources->presetNeedsAsynchronousUpdates())
+    {
+    }
 
     KisStrokeRandomSource randomSource;
     KisResourcesSnapshotSP resources;
+
+    QElapsedTimer timeSinceLastUpdate;
+    int currentUpdatePeriod = 40;
+
+    const bool needsAsynchronousUpdates = false;
 };
 
 FreehandStrokeStrategy::FreehandStrokeStrategy(bool needsIndirectPainting,
@@ -80,64 +92,106 @@ void FreehandStrokeStrategy::init(bool needsIndirectPainting,
     setSupportsWrapAroundMode(true);
     enableJob(KisSimpleStrokeStrategy::JOB_DOSTROKE);
 
+    if (m_d->needsAsynchronousUpdates) {
+        m_d->timeSinceLastUpdate.start();
+    }
+
     KisUpdateTimeMonitor::instance()->startStrokeMeasure();
 }
 
 void FreehandStrokeStrategy::doStrokeCallback(KisStrokeJobData *data)
 {
-    Data *d = dynamic_cast<Data*>(data);
-    PainterInfo *info = painterInfos()[d->painterInfoId];
-
-    KisUpdateTimeMonitor::instance()->reportPaintOpPreset(info->painter->preset());
-    KisRandomSourceSP rnd = m_d->randomSource.source();
-
-    switch(d->type) {
-    case Data::POINT:
-        d->pi1.setRandomSource(rnd);
-        info->painter->paintAt(d->pi1, info->dragDistance);
-        break;
-    case Data::LINE:
-        d->pi1.setRandomSource(rnd);
-        d->pi2.setRandomSource(rnd);
-        info->painter->paintLine(d->pi1, d->pi2, info->dragDistance);
-        break;
-    case Data::CURVE:
-        d->pi1.setRandomSource(rnd);
-        d->pi2.setRandomSource(rnd);
-        info->painter->paintBezierCurve(d->pi1,
-                                        d->control1,
-                                        d->control2,
-                                        d->pi2,
-                                        info->dragDistance);
-        break;
-    case Data::POLYLINE:
-        info->painter->paintPolyline(d->points, 0, d->points.size());
-        break;
-    case Data::POLYGON:
-        info->painter->paintPolygon(d->points);
-        break;
-    case Data::RECT:
-        info->painter->paintRect(d->rect);
-        break;
-    case Data::ELLIPSE:
-        info->painter->paintEllipse(d->rect);
-        break;
-    case Data::PAINTER_PATH:
-        info->painter->paintPainterPath(d->path);
-        break;
-    case Data::QPAINTER_PATH:
-        info->painter->drawPainterPath(d->path, d->pen);
-        break;
-    case Data::QPAINTER_PATH_FILL: {
-        info->painter->setBackgroundColor(d->customColor);
-        info->painter->fillPainterPath(d->path);}
-        info->painter->drawPainterPath(d->path, d->pen);    
-        break;
-    };
-
-    QVector<QRect> dirtyRects = info->painter->takeDirtyRegion();
-    KisUpdateTimeMonitor::instance()->reportJobFinished(data, dirtyRects);
-    d->node->setDirty(dirtyRects);
+    KisNodeSP updateNode;
+    PainterInfo *info = 0;
+
+    if (UpdateData *d = dynamic_cast<UpdateData*>(data)) {
+        info = painterInfos()[d->painterInfoId];
+        updateNode = d->node;
+
+        // do nothing, just fall through till we reach the update code
+
+    } else if (Data *d = dynamic_cast<Data*>(data)) {
+        info = painterInfos()[d->painterInfoId];
+        updateNode = d->node;
+
+        KisUpdateTimeMonitor::instance()->reportPaintOpPreset(info->painter->preset());
+        KisRandomSourceSP rnd = m_d->randomSource.source();
+
+        switch(d->type) {
+        case Data::POINT:
+            d->pi1.setRandomSource(rnd);
+            info->painter->paintAt(d->pi1, info->dragDistance);
+            break;
+        case Data::LINE:
+            d->pi1.setRandomSource(rnd);
+            d->pi2.setRandomSource(rnd);
+            info->painter->paintLine(d->pi1, d->pi2, info->dragDistance);
+            break;
+        case Data::CURVE:
+            d->pi1.setRandomSource(rnd);
+            d->pi2.setRandomSource(rnd);
+            info->painter->paintBezierCurve(d->pi1,
+                                            d->control1,
+                                            d->control2,
+                                            d->pi2,
+                                            info->dragDistance);
+            break;
+        case Data::POLYLINE:
+            info->painter->paintPolyline(d->points, 0, d->points.size());
+            break;
+        case Data::POLYGON:
+            info->painter->paintPolygon(d->points);
+            break;
+        case Data::RECT:
+            info->painter->paintRect(d->rect);
+            break;
+        case Data::ELLIPSE:
+            info->painter->paintEllipse(d->rect);
+            break;
+        case Data::PAINTER_PATH:
+            info->painter->paintPainterPath(d->path);
+            break;
+        case Data::QPAINTER_PATH:
+            info->painter->drawPainterPath(d->path, d->pen);
+            break;
+        case Data::QPAINTER_PATH_FILL: {
+            info->painter->setBackgroundColor(d->customColor);
+            info->painter->fillPainterPath(d->path);}
+            info->painter->drawPainterPath(d->path, d->pen);
+            break;
+        };
+    }
+
+    KIS_SAFE_ASSERT_RECOVER_RETURN(info);
+    KIS_SAFE_ASSERT_RECOVER_RETURN(updateNode);
+
+    tryDoUpdate();
+}
+
+void FreehandStrokeStrategy::finishStrokeCallback()
+{
+    tryDoUpdate(true);
+    KisPainterBasedStrokeStrategy::finishStrokeCallback();
+}
+
+void FreehandStrokeStrategy::tryDoUpdate(bool force)
+{
+    // We do not distinguish between updates for each painter info. Just
+    // update all of them at once!
+
+    Q_FOREACH (PainterInfo *info, painterInfos()) {
+        KisPaintOp *paintop = info->painter->paintOp();
+        if (m_d->needsAsynchronousUpdates && paintop &&
+            (force || m_d->timeSinceLastUpdate.elapsed() > m_d->currentUpdatePeriod)) {
+
+            m_d->timeSinceLastUpdate.restart();
+            m_d->currentUpdatePeriod = paintop->doAsyncronousUpdate();
+        }
+
+        QVector<QRect> dirtyRects = info->painter->takeDirtyRegion();
+        //KisUpdateTimeMonitor::instance()->reportJobFinished(data, dirtyRects);
+        targetNode()->setDirty(dirtyRects);
+    }
 }
 
 KisStrokeStrategy* FreehandStrokeStrategy::createLodClone(int levelOfDetail)
diff --git a/libs/ui/tool/strokes/freehand_stroke.h b/libs/ui/tool/strokes/freehand_stroke.h
index 09c316b0006..7e95327b165 100644
--- a/libs/ui/tool/strokes/freehand_stroke.h
+++ b/libs/ui/tool/strokes/freehand_stroke.h
@@ -50,14 +50,16 @@ public:
 
         Data(KisNodeSP _node, int _painterInfoId,
              const KisPaintInformation &_pi)
-            : node(_node), painterInfoId(_painterInfoId),
+            : KisStrokeJobData(KisStrokeJobData::SEQUENTIAL),
+              node(_node), painterInfoId(_painterInfoId),
               type(POINT), pi1(_pi)
         {}
 
         Data(KisNodeSP _node, int _painterInfoId,
              const KisPaintInformation &_pi1,
              const KisPaintInformation &_pi2)
-            : node(_node), painterInfoId(_painterInfoId),
+            : KisStrokeJobData(KisStrokeJobData::SEQUENTIAL),
+              node(_node), painterInfoId(_painterInfoId),
               type(LINE), pi1(_pi1), pi2(_pi2)
         {}
 
@@ -66,7 +68,8 @@ public:
              const QPointF &_control1,
              const QPointF &_control2,
              const KisPaintInformation &_pi2)
-            : node(_node), painterInfoId(_painterInfoId),
+            : KisStrokeJobData(KisStrokeJobData::SEQUENTIAL),
+              node(_node), painterInfoId(_painterInfoId),
               type(CURVE), pi1(_pi1), pi2(_pi2),
               control1(_control1), control2(_control2)
         {}
@@ -74,21 +77,24 @@ public:
         Data(KisNodeSP _node, int _painterInfoId,
              DabType _type,
              const vQPointF &_points)
-            : node(_node), painterInfoId(_painterInfoId),
+            : KisStrokeJobData(KisStrokeJobData::SEQUENTIAL),
+              node(_node), painterInfoId(_painterInfoId),
             type(_type), points(_points)
         {}
 
         Data(KisNodeSP _node, int _painterInfoId,
              DabType _type,
              const QRectF &_rect)
-            : node(_node), painterInfoId(_painterInfoId),
+            : KisStrokeJobData(KisStrokeJobData::SEQUENTIAL),
+              node(_node), painterInfoId(_painterInfoId),
             type(_type), rect(_rect)
         {}
 
         Data(KisNodeSP _node, int _painterInfoId,
              DabType _type,
              const QPainterPath &_path)
-            : node(_node), painterInfoId(_painterInfoId),
+            : KisStrokeJobData(KisStrokeJobData::SEQUENTIAL),
+              node(_node), painterInfoId(_painterInfoId),
             type(_type), path(_path)
         {}
 
@@ -96,7 +102,8 @@ public:
              DabType _type,
              const QPainterPath &_path,
              const QPen &_pen, const KoColor &_customColor)
-            : node(_node), painterInfoId(_painterInfoId),
+            : KisStrokeJobData(KisStrokeJobData::SEQUENTIAL),
+              node(_node), painterInfoId(_painterInfoId),
             type(_type), path(_path),
             pen(_pen), customColor(_customColor)
         {}
@@ -171,6 +178,32 @@ public:
         KoColor customColor;
     };
 
+    class UpdateData : public KisStrokeJobData {
+    public:
+        UpdateData(KisNodeSP _node, int _painterInfoId)
+            : KisStrokeJobData(KisStrokeJobData::SEQUENTIAL),
+              node(_node),
+              painterInfoId(_painterInfoId)
+        {}
+
+
+        KisStrokeJobData* createLodClone(int levelOfDetail) override {
+            return new UpdateData(*this, levelOfDetail);
+        }
+
+    private:
+        UpdateData(const UpdateData &rhs, int levelOfDetail)
+            : KisStrokeJobData(rhs),
+              node(rhs.node),
+              painterInfoId(rhs.painterInfoId)
+        {
+            Q_UNUSED(levelOfDetail);
+        }
+    public:
+        KisNodeSP node;
+        int painterInfoId;
+    };
+
 public:
     FreehandStrokeStrategy(bool needsIndirectPainting,
                            const QString &indirectPaintingCompositeOp,
@@ -187,6 +220,7 @@ public:
     ~FreehandStrokeStrategy() override;
 
     void doStrokeCallback(KisStrokeJobData *data) override;
+    void finishStrokeCallback() override;
 
     KisStrokeStrategy* createLodClone(int levelOfDetail) override;
 
@@ -196,6 +230,8 @@ protected:
 private:
     void init(bool needsIndirectPainting, const QString &indirectPaintingCompositeOp);
 
+    void tryDoUpdate(bool force = false);
+
 private:
     struct Private;
     const QScopedPointer<Private> m_d;
diff --git a/libs/ui/tool/strokes/kis_painter_based_stroke_strategy.cpp b/libs/ui/tool/strokes/kis_painter_based_stroke_strategy.cpp
index ba003cb9a44..67c474f380e 100644
--- a/libs/ui/tool/strokes/kis_painter_based_stroke_strategy.cpp
+++ b/libs/ui/tool/strokes/kis_painter_based_stroke_strategy.cpp
@@ -314,3 +314,8 @@ void KisPainterBasedStrokeStrategy::resumeStrokeCallback()
         }
     }
 }
+
+KisNodeSP KisPainterBasedStrokeStrategy::targetNode() const
+{
+    return m_resources->currentNode();
+}
diff --git a/libs/ui/tool/strokes/kis_painter_based_stroke_strategy.h b/libs/ui/tool/strokes/kis_painter_based_stroke_strategy.h
index 040559ca918..fddcb8dab99 100644
--- a/libs/ui/tool/strokes/kis_painter_based_stroke_strategy.h
+++ b/libs/ui/tool/strokes/kis_painter_based_stroke_strategy.h
@@ -77,6 +77,7 @@ public:
     void resumeStrokeCallback() override;
 
 protected:
+    KisNodeSP targetNode() const;
     KisPaintDeviceSP targetDevice() const;
     KisSelectionSP activeSelection() const;
     const QVector<PainterInfo*> painterInfos() const;
diff --git a/plugins/paintops/defaultpaintops/CMakeLists.txt b/plugins/paintops/defaultpaintops/CMakeLists.txt
index 74e9811e5a5..a8cdef7153f 100644
--- a/plugins/paintops/defaultpaintops/CMakeLists.txt
+++ b/plugins/paintops/defaultpaintops/CMakeLists.txt
@@ -7,6 +7,7 @@ include_directories(brush
 set(kritadefaultpaintops_SOURCES
 	defaultpaintops_plugin.cc
 	brush/kis_brushop.cpp
+        brush/KisBrushOpSettings.cpp
 	brush/kis_brushop_settings_widget.cpp
         brush/KisDabRenderingQueue.cpp
         brush/KisDabRenderingQueueCache.cpp
diff --git a/plugins/paintops/defaultpaintops/brush/KisRenderedDab.h b/plugins/paintops/defaultpaintops/brush/KisBrushOpSettings.cpp
similarity index 81%
copy from plugins/paintops/defaultpaintops/brush/KisRenderedDab.h
copy to plugins/paintops/defaultpaintops/brush/KisBrushOpSettings.cpp
index 7476fb791a8..f7573c09a54 100644
--- a/plugins/paintops/defaultpaintops/brush/KisRenderedDab.h
+++ b/plugins/paintops/defaultpaintops/brush/KisBrushOpSettings.cpp
@@ -16,15 +16,10 @@
  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  */
 
-#ifndef KISRENDEREDDAB_H
-#define KISRENDEREDDAB_H
+#include "KisBrushOpSettings.h"
 
-#include "kis_types.h"
 
-struct KisRenderedDab
+bool KisBrushOpSettings::needsAsynchronousUpdates() const
 {
-    KisFixedPaintDeviceSP device;
-    QPoint offset;
-};
-
-#endif // KISRENDEREDDAB_H
+    return true;
+}
diff --git a/plugins/paintops/defaultpaintops/brush/KisRenderedDab.h b/plugins/paintops/defaultpaintops/brush/KisBrushOpSettings.h
similarity index 76%
rename from plugins/paintops/defaultpaintops/brush/KisRenderedDab.h
rename to plugins/paintops/defaultpaintops/brush/KisBrushOpSettings.h
index 7476fb791a8..f4dcd7ed666 100644
--- a/plugins/paintops/defaultpaintops/brush/KisRenderedDab.h
+++ b/plugins/paintops/defaultpaintops/brush/KisBrushOpSettings.h
@@ -16,15 +16,16 @@
  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  */
 
-#ifndef KISRENDEREDDAB_H
-#define KISRENDEREDDAB_H
+#ifndef KISBRUSHOPSETTINGS_H
+#define KISBRUSHOPSETTINGS_H
 
-#include "kis_types.h"
+#include "kis_brush_based_paintop_settings.h"
 
-struct KisRenderedDab
+
+class KisBrushOpSettings : public KisBrushBasedPaintOpSettings
 {
-    KisFixedPaintDeviceSP device;
-    QPoint offset;
+public:
+    bool needsAsynchronousUpdates() const;
 };
 
-#endif // KISRENDEREDDAB_H
+#endif // KISBRUSHOPSETTINGS_H
diff --git a/plugins/paintops/defaultpaintops/brush/KisDabRenderingExecutor.cpp b/plugins/paintops/defaultpaintops/brush/KisDabRenderingExecutor.cpp
index a654f143135..0f90cc909a1 100644
--- a/plugins/paintops/defaultpaintops/brush/KisDabRenderingExecutor.cpp
+++ b/plugins/paintops/defaultpaintops/brush/KisDabRenderingExecutor.cpp
@@ -49,9 +49,10 @@ KisDabRenderingExecutor::~KisDabRenderingExecutor()
 {
 }
 
-void KisDabRenderingExecutor::addDab(const KisDabCacheUtils::DabRequestInfo &request)
+void KisDabRenderingExecutor::addDab(const KisDabCacheUtils::DabRequestInfo &request,
+                                     qreal opacity, qreal flow)
 {
-    KisDabRenderingJob *job = m_d->renderingQueue->addDab(request);
+    KisDabRenderingJob *job = m_d->renderingQueue->addDab(request, opacity, flow);
     if (job) {
         m_d->sharedThreadPool->start(job);
     }
@@ -67,6 +68,11 @@ bool KisDabRenderingExecutor::hasPreparedDabs() const
     return m_d->renderingQueue->hasPreparedDabs();
 }
 
+int KisDabRenderingExecutor::averageDabRenderingTime() const
+{
+    return m_d->renderingQueue->averageExecutionTime();
+}
+
 void KisDabRenderingExecutor::waitForDone()
 {
     m_d->sharedThreadPool->waitForDone();
diff --git a/plugins/paintops/defaultpaintops/brush/KisDabRenderingExecutor.h b/plugins/paintops/defaultpaintops/brush/KisDabRenderingExecutor.h
index 5916f5acdea..d7e1c90e3c6 100644
--- a/plugins/paintops/defaultpaintops/brush/KisDabRenderingExecutor.h
+++ b/plugins/paintops/defaultpaintops/brush/KisDabRenderingExecutor.h
@@ -35,12 +35,15 @@ public:
     KisDabRenderingExecutor(const KoColorSpace *cs, KisDabCacheUtils::ResourcesFactory resourcesFactory);
     ~KisDabRenderingExecutor();
 
-    void addDab(const KisDabCacheUtils::DabRequestInfo &request);
+    void addDab(const KisDabCacheUtils::DabRequestInfo &request,
+                qreal opacity, qreal flow);
 
     QList<KisRenderedDab> takeReadyDabs();
 
     bool hasPreparedDabs() const;
 
+    int averageDabRenderingTime() const; // usecs
+
     void waitForDone();
 
 private:
diff --git a/plugins/paintops/defaultpaintops/brush/KisDabRenderingJob.cpp b/plugins/paintops/defaultpaintops/brush/KisDabRenderingJob.cpp
index a7764d16a1d..5ece6fc2689 100644
--- a/plugins/paintops/defaultpaintops/brush/KisDabRenderingJob.cpp
+++ b/plugins/paintops/defaultpaintops/brush/KisDabRenderingJob.cpp
@@ -18,12 +18,13 @@
 
 #include "KisDabRenderingJob.h"
 
+#include <QElapsedTimer>
+
 #include <KisSharedThreadPoolAdapter.h>
 
 #include "KisDabCacheUtils.h"
 #include "KisDabRenderingQueue.h"
 
-
 KisDabRenderingJob::KisDabRenderingJob()
 {
 }
@@ -63,13 +64,16 @@ KisDabRenderingJob &KisDabRenderingJob::operator=(const KisDabRenderingJob &rhs)
     return *this;
 }
 
-void KisDabRenderingJob::executeOneJob(KisDabRenderingJob *job)
+int KisDabRenderingJob::executeOneJob(KisDabRenderingJob *job)
 {
     using namespace KisDabCacheUtils;
 
     KIS_SAFE_ASSERT_RECOVER_NOOP(job->type == KisDabRenderingJob::Dab ||
                                  job->type == KisDabRenderingJob::Postprocess);
 
+    QElapsedTimer executionTime;
+    executionTime.start();
+
     if (job->type == KisDabRenderingJob::Dab) {
         // TODO: thing about better interface for the reverse queue link
         job->originalDevice = job->parentQueue->fetchCachedPaintDevce();
@@ -78,7 +82,7 @@ void KisDabRenderingJob::executeOneJob(KisDabRenderingJob *job)
     }
 
     // by now the original device should be already prepared
-    KIS_SAFE_ASSERT_RECOVER_RETURN(job->originalDevice);
+    KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(job->originalDevice, 0);
 
     if (job->type == KisDabRenderingJob::Dab ||
         job->type == KisDabRenderingJob::Postprocess) {
@@ -102,12 +106,16 @@ void KisDabRenderingJob::executeOneJob(KisDabRenderingJob *job)
             job->postprocessedDevice = job->originalDevice;
         }
     }
+
+    return executionTime.nsecsElapsed() / 1000;
 }
 
 void KisDabRenderingJob::runShared()
 {
-    executeOneJob(this);
-    QList<KisDabRenderingJob *> jobs = parentQueue->notifyJobFinished(this);
+    int executionTime = 0;
+
+    executionTime = executeOneJob(this);
+    QList<KisDabRenderingJob *> jobs = parentQueue->notifyJobFinished(this, executionTime);
 
     while (!jobs.isEmpty()) {
         // start all-but-first jobs asynchronously
@@ -117,8 +125,8 @@ void KisDabRenderingJob::runShared()
 
         // execute the first job in the current thread
         KisDabRenderingJob *job = jobs.first();
-        executeOneJob(job);
-        jobs = parentQueue->notifyJobFinished(job);
+        executionTime = executeOneJob(job);
+        jobs = parentQueue->notifyJobFinished(job, executionTime);
 
         // mimic the behavior of the thread pool
         if (job->autoDelete()) {
diff --git a/plugins/paintops/defaultpaintops/brush/KisDabRenderingJob.h b/plugins/paintops/defaultpaintops/brush/KisDabRenderingJob.h
index b10e7885807..56dc9538272 100644
--- a/plugins/paintops/defaultpaintops/brush/KisDabRenderingJob.h
+++ b/plugins/paintops/defaultpaintops/brush/KisDabRenderingJob.h
@@ -61,7 +61,7 @@ public:
     KisSharedThreadPoolAdapter *sharedThreadPool = 0;
 
 private:
-    static void executeOneJob(KisDabRenderingJob *job);
+    static int executeOneJob(KisDabRenderingJob *job);
 };
 
 #endif // KISDABRENDERINGJOB_H
diff --git a/plugins/paintops/defaultpaintops/brush/KisDabRenderingQueue.cpp b/plugins/paintops/defaultpaintops/brush/KisDabRenderingQueue.cpp
index 1a4335153c7..6fd9774f3a3 100644
--- a/plugins/paintops/defaultpaintops/brush/KisDabRenderingQueue.cpp
+++ b/plugins/paintops/defaultpaintops/brush/KisDabRenderingQueue.cpp
@@ -21,6 +21,14 @@
 #include "KisDabRenderingJob.h"
 #include "KisRenderedDab.h"
 #include "KisSharedThreadPoolAdapter.h"
+#include "kis_painter.h"
+
+#include <QMutex>
+#include <QMutexLocker>
+
+#include <boost/accumulators/accumulators.hpp>
+#include <boost/accumulators/statistics/stats.hpp>
+#include <boost/accumulators/statistics/rolling_mean.hpp>
 
 struct KisDabRenderingQueue::Private
 {
@@ -33,6 +41,9 @@ struct KisDabRenderingQueue::Private
 
         KisDabRenderingJob job;
         Status status = New;
+
+        qreal opacity = OPACITY_OPAQUE_F;
+        qreal flow = OPACITY_OPAQUE_F;
     };
 
     struct DumbCacheInterface : public CacheInterface {
@@ -59,7 +70,8 @@ struct KisDabRenderingQueue::Private
         : cacheInterface(new DumbCacheInterface),
           colorSpace(_colorSpace),
           sharedThreadPool(_sharedThreadPool),
-          resourcesFactory(_resourcesFactory)
+          resourcesFactory(_resourcesFactory),
+          avgExecutionTime(boost::accumulators::tag::rolling_window::window_size = 50)
     {
         KIS_SAFE_ASSERT_RECOVER_NOOP(resourcesFactory);
     }
@@ -75,11 +87,16 @@ struct KisDabRenderingQueue::Private
     QScopedPointer<CacheInterface> cacheInterface;
     const KoColorSpace *colorSpace;
     KisSharedThreadPoolAdapter *sharedThreadPool;
+    qreal averageOpacity = 0.0;
 
     KisDabCacheUtils::ResourcesFactory resourcesFactory;
     QList<KisDabCacheUtils::DabRenderingResources*> cachedResources;
     QList<KisFixedPaintDeviceSP> cachedPaintDevices;
 
+    QMutex mutex;
+
+    boost::accumulators::accumulator_set<qreal, boost::accumulators::stats<boost::accumulators::tag::lazy_rolling_mean> > avgExecutionTime;
+
     int findLastDabJobIndex(int startSearchIndex = -1);
     KisDabRenderingJob* createPostprocessingJob(const KisDabRenderingJob &postprocessingJob, int sourceDabJob);
     void cleanPaintedDabs();
@@ -119,8 +136,11 @@ KisDabRenderingJob *KisDabRenderingQueue::Private::createPostprocessingJob(const
     return job;
 }
 
-KisDabRenderingJob *KisDabRenderingQueue::addDab(const KisDabCacheUtils::DabRequestInfo &request)
+KisDabRenderingJob *KisDabRenderingQueue::addDab(const KisDabCacheUtils::DabRequestInfo &request,
+                                                 qreal opacity, qreal flow)
 {
+    QMutexLocker l(&m_d->mutex);
+
     const int seqNo =
         !m_d->jobs.isEmpty() ?
             m_d->jobs.last().job.seqNo + 1:
@@ -159,6 +179,8 @@ KisDabRenderingJob *KisDabRenderingQueue::addDab(const KisDabCacheUtils::DabRequ
         KisDabRenderingJob::Copy;
     wrapper.job.parentQueue = this;
     wrapper.job.sharedThreadPool = m_d->sharedThreadPool;
+    wrapper.opacity = opacity;
+    wrapper.flow = flow;
 
     KisDabRenderingJob *jobToRun = 0;
 
@@ -195,8 +217,10 @@ KisDabRenderingJob *KisDabRenderingQueue::addDab(const KisDabCacheUtils::DabRequ
     return jobToRun;
 }
 
-QList<KisDabRenderingJob *> KisDabRenderingQueue::notifyJobFinished(KisDabRenderingJob *job)
+QList<KisDabRenderingJob *> KisDabRenderingQueue::notifyJobFinished(KisDabRenderingJob *job, int usecsTime)
 {
+    QMutexLocker l(&m_d->mutex);
+
     QList<KisDabRenderingJob *> dependentJobs;
 
     const int jobIndex = job->seqNo - m_d->startSeqNo;
@@ -246,6 +270,10 @@ QList<KisDabRenderingJob *> KisDabRenderingQueue::notifyJobFinished(KisDabRender
         }
     }
 
+    if (usecsTime >= 0) {
+        m_d->avgExecutionTime(usecsTime);
+    }
+
     return dependentJobs;
 }
 
@@ -275,6 +303,8 @@ void KisDabRenderingQueue::Private::cleanPaintedDabs()
 
 QList<KisRenderedDab> KisDabRenderingQueue::takeReadyDabs()
 {
+    QMutexLocker l(&m_d->mutex);
+
     QList<KisRenderedDab> renderedDabs;
     if (m_d->startSeqNo < 0) return renderedDabs;
 
@@ -294,6 +324,12 @@ QList<KisRenderedDab> KisDabRenderingQueue::takeReadyDabs()
 
         dab.device = j.postprocessedDevice;
         dab.offset = j.dstDabOffset();
+        dab.opacity = w.opacity;
+        dab.flow = w.flow;
+
+        m_d->averageOpacity = KisPainter::blendAverageOpacity(w.opacity, m_d->averageOpacity);
+        dab.averageOpacity = m_d->averageOpacity;
+
 
         renderedDabs.append(dab);
 
@@ -306,6 +342,8 @@ QList<KisRenderedDab> KisDabRenderingQueue::takeReadyDabs()
 
 bool KisDabRenderingQueue::hasPreparedDabs() const
 {
+    QMutexLocker l(&m_d->mutex);
+
     const int nextToBePainted = m_d->lastPaintedJob + 1;
 
     return
@@ -321,10 +359,18 @@ void KisDabRenderingQueue::setCacheInterface(KisDabRenderingQueue::CacheInterfac
 
 KisFixedPaintDeviceSP KisDabRenderingQueue::fetchCachedPaintDevce()
 {
+    QMutexLocker l(&m_d->mutex);
+
     return
         m_d->cachedPaintDevices.isEmpty() ?
             new KisFixedPaintDevice(m_d->colorSpace) :
-            m_d->cachedPaintDevices.takeLast();
+                m_d->cachedPaintDevices.takeLast();
+}
+
+int KisDabRenderingQueue::averageExecutionTime() const
+{
+    using namespace boost::accumulators;
+    return rolling_mean(m_d->avgExecutionTime);
 }
 
 int KisDabRenderingQueue::testingGetQueueSize() const
diff --git a/plugins/paintops/defaultpaintops/brush/KisDabRenderingQueue.h b/plugins/paintops/defaultpaintops/brush/KisDabRenderingQueue.h
index 27cdcfce9f2..dc9069676ba 100644
--- a/plugins/paintops/defaultpaintops/brush/KisDabRenderingQueue.h
+++ b/plugins/paintops/defaultpaintops/brush/KisDabRenderingQueue.h
@@ -48,9 +48,10 @@ public:
     KisDabRenderingQueue(const KoColorSpace *cs, KisDabCacheUtils::ResourcesFactory resourcesFactory, KisSharedThreadPoolAdapter *sharedThreadPool = 0);
     ~KisDabRenderingQueue();
 
-    KisDabRenderingJob* addDab(const KisDabCacheUtils::DabRequestInfo &request);
+    KisDabRenderingJob* addDab(const KisDabCacheUtils::DabRequestInfo &request,
+                               qreal opacity, qreal flow);
 
-    QList<KisDabRenderingJob*> notifyJobFinished(KisDabRenderingJob *job);
+    QList<KisDabRenderingJob*> notifyJobFinished(KisDabRenderingJob *job, int usecsTime = -1);
 
     QList<KisRenderedDab> takeReadyDabs();
 
@@ -60,6 +61,8 @@ public:
 
     KisFixedPaintDeviceSP fetchCachedPaintDevce();
 
+    int averageExecutionTime() const;
+
     int testingGetQueueSize() const;
 
 private:
diff --git a/plugins/paintops/defaultpaintops/brush/kis_brushop.cpp b/plugins/paintops/defaultpaintops/brush/kis_brushop.cpp
index dbf10dac83b..134c4bab5e3 100644
--- a/plugins/paintops/defaultpaintops/brush/kis_brushop.cpp
+++ b/plugins/paintops/defaultpaintops/brush/kis_brushop.cpp
@@ -42,6 +42,12 @@
 #include <kis_fixed_paint_device.h>
 #include <kis_lod_transform.h>
 #include <kis_paintop_plugin_utils.h>
+#include "krita_utils.h"
+#include <QtConcurrent>
+#include "kis_algebra_2d.h"
+#include <KisDabRenderingExecutor.h>
+#include <KisDabCacheUtils.h>
+#include <KisRenderedDab.h>
 
 
 KisBrushOp::KisBrushOp(const KisPaintOpSettingsSP settings, KisPainter *painter, KisNodeSP node, KisImageSP image)
@@ -96,6 +102,20 @@ KisBrushOp::KisBrushOp(const KisPaintOpSettingsSP settings, KisPainter *painter,
 
     m_dabCache->setSharpnessPostprocessing(&m_sharpnessOption);
     m_rotationOption.applyFanCornersInfo(this);
+
+    KisBrushSP baseBrush = m_brush;
+    auto resourcesFactory =
+        [baseBrush] () {
+            KisDabCacheUtils::DabRenderingResources *resources =
+                new KisDabCacheUtils::DabRenderingResources();
+            resources->brush = baseBrush->clone();
+            return resources;
+        };
+
+    m_dabExecutor.reset(
+        new KisDabRenderingExecutor(
+                    painter->device()->compositionSourceColorSpace(),
+                    resourcesFactory));
 }
 
 KisBrushOp::~KisBrushOp()
@@ -124,19 +144,19 @@ KisSpacingInformation KisBrushOp::paintAt(const KisPaintInformation& info)
     qreal rotation = m_rotationOption.apply(info);
     qreal ratio = m_ratioOption.apply(info);
 
-    KisPaintDeviceSP device = painter()->device();
-
-
     KisDabShape shape(scale, ratio, rotation);
     QPointF cursorPos =
         m_scatterOption.apply(info,
                               brush->maskWidth(shape, 0, 0, info),
                               brush->maskHeight(shape, 0, 0, info));
 
-    quint8 origOpacity = painter()->opacity();
-
     m_opacityOption.setFlow(m_flowOption.apply(info));
-    m_opacityOption.apply(painter(), info);
+
+    quint8 dabOpacity = OPACITY_OPAQUE_U8;
+    quint8 dabFlow = OPACITY_OPAQUE_U8;
+
+    m_opacityOption.apply(info, &dabOpacity, &dabFlow);
+
     m_colorSource->selectColor(m_mixOption.apply(info), info);
     m_darkenOption.apply(m_colorSource, info);
 
@@ -147,30 +167,62 @@ KisSpacingInformation KisBrushOp::paintAt(const KisPaintInformation& info)
         m_colorSource->applyColorTransformation(m_hsvTransformation);
     }
 
-    QRect dabRect;
-    KisFixedPaintDeviceSP dab = m_dabCache->fetchDab(device->compositionSourceColorSpace(),
-                                m_colorSource,
-                                cursorPos,
-                                shape,
-                                info,
-                                m_softnessOption.apply(info),
-                                &dabRect);
-
-    // sanity check for the size calculation code
-    if (dab->bounds().size() != dabRect.size()) {
-        warnKrita << "KisBrushOp: dab bounds is not dab rect. See bug 327156" << dab->bounds().size() << dabRect.size();
+    KisDabCacheUtils::DabRequestInfo request(painter()->paintColor(),
+                                             cursorPos,
+                                             shape,
+                                             info,
+                                             m_softnessOption.apply(info));
+
+    m_dabExecutor->addDab(request, qreal(dabOpacity) / 255.0, qreal(dabFlow) / 255.0);
+
+    return effectiveSpacing(scale, rotation, &m_airbrushOption, &m_spacingOption, info);
+}
+
+int KisBrushOp::doAsyncronousUpdate()
+{
+    if (!m_dabExecutor->hasPreparedDabs()) return m_currentUpdatePeriod;
+
+    const int numThreads = 8;
+    const int splitInto = numThreads;
+
+    QList<KisRenderedDab> dabsQueue = m_dabExecutor->takeReadyDabs();
+    KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!dabsQueue.isEmpty(), m_currentUpdatePeriod);
+
+    QRect totalRect;
+    Q_FOREACH (const KisRenderedDab &dab, dabsQueue) {
+        totalRect |= dab.realBounds();
+    }
+
+    int idealPatchSize = KisAlgebra2D::maxDimension(totalRect) / splitInto;
+    idealPatchSize &= ~63;
+
+    idealPatchSize = qMax(idealPatchSize, 64);
+
+    QVector<QRect> rects = KritaUtils::splitRectIntoPatches(totalRect, QSize(idealPatchSize,idealPatchSize));
+
+    KisPainter *gc = painter();
+
+    //ENTER_FUNCTION() << ppVar(idealPatchSize) << ppVar(rects.size()) << ppVar(dabsQueue.size());
+
+    QtConcurrent::blockingMap(rects,
+        [gc, dabsQueue] (const QRect &rc) {
+             KisPainter painter(gc->device());
+             painter.bltFixed(rc, dabsQueue);
+        });
+
+    Q_FOREACH(const QRect &rc, rects) {
+        painter()->addDirtyRect(rc);
     }
 
-    painter()->bltFixed(dabRect.topLeft(), dab, dab->bounds());
+    painter()->setAverageOpacity(dabsQueue.last().averageOpacity);
 
-    painter()->renderMirrorMaskSafe(dabRect,
-                                    dab,
-                                    !m_dabCache->needSeparateOriginal());
-    painter()->setOpacity(origOpacity);
+    const int dabRenderingTime = m_dabExecutor->averageDabRenderingTime();
+    m_currentUpdatePeriod = qBound(20.0, 100.0 * dabRenderingTime, 80.0);
 
-    return effectiveSpacing(scale, rotation, &m_airbrushOption, &m_spacingOption, info);
+    return m_currentUpdatePeriod;
 }
 
+
 KisSpacingInformation KisBrushOp::updateSpacingImpl(const KisPaintInformation &info) const
 {
     const qreal scale = m_sizeOption.apply(info) * KisLodTransform::lodToScale(painter()->device());
diff --git a/plugins/paintops/defaultpaintops/brush/kis_brushop.h b/plugins/paintops/defaultpaintops/brush/kis_brushop.h
index 51e7b52fe0a..f860d425fd0 100644
--- a/plugins/paintops/defaultpaintops/brush/kis_brushop.h
+++ b/plugins/paintops/defaultpaintops/brush/kis_brushop.h
@@ -43,7 +43,7 @@
 
 class KisPainter;
 class KisColorSource;
-
+class KisDabRenderingExecutor;
 
 class KisBrushOp : public KisBrushBasedPaintOp
 {
@@ -55,6 +55,8 @@ public:
 
     void paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) override;
 
+    int doAsyncronousUpdate() override;
+
 protected:
     KisSpacingInformation paintAt(const KisPaintInformation& info) override;
 
@@ -82,6 +84,9 @@ private:
     KoColorTransformation *m_hsvTransformation;
     KisPaintDeviceSP m_lineCacheDevice;
     KisPaintDeviceSP m_colorSourceDevice;
+
+    QScopedPointer<KisDabRenderingExecutor> m_dabExecutor;
+    qreal m_currentUpdatePeriod = 20.0;
 };
 
 #endif // KIS_BRUSHOP_H_
diff --git a/plugins/paintops/defaultpaintops/brush/kis_brushop_settings_widget.cpp b/plugins/paintops/defaultpaintops/brush/kis_brushop_settings_widget.cpp
index bbe667d467e..e71b11a68cd 100644
--- a/plugins/paintops/defaultpaintops/brush/kis_brushop_settings_widget.cpp
+++ b/plugins/paintops/defaultpaintops/brush/kis_brushop_settings_widget.cpp
@@ -21,7 +21,7 @@
  */
 
 #include "kis_brushop_settings_widget.h"
-#include <kis_brush_based_paintop_settings.h>
+#include <KisBrushOpSettings.h>
 #include <kis_pressure_darken_option.h>
 #include <kis_pressure_opacity_option.h>
 #include <kis_pressure_flow_option.h>
@@ -90,7 +90,7 @@ KisBrushOpSettingsWidget::~KisBrushOpSettingsWidget()
 
 KisPropertiesConfigurationSP KisBrushOpSettingsWidget::configuration() const
 {
-    KisBrushBasedPaintOpSettingsSP config = new KisBrushBasedPaintOpSettings();
+    KisBrushBasedPaintOpSettingsSP config = new KisBrushOpSettings();
     config->setOptionsWidget(const_cast<KisBrushOpSettingsWidget*>(this));
     config->setProperty("paintop", "paintbrush"); // XXX: make this a const id string
     writeConfiguration(config);
diff --git a/plugins/paintops/defaultpaintops/brush/tests/KisDabRenderingQueueTest.cpp b/plugins/paintops/defaultpaintops/brush/tests/KisDabRenderingQueueTest.cpp
index 15ceb8bd3d1..2a6b907342a 100644
--- a/plugins/paintops/defaultpaintops/brush/tests/KisDabRenderingQueueTest.cpp
+++ b/plugins/paintops/defaultpaintops/brush/tests/KisDabRenderingQueueTest.cpp
@@ -96,7 +96,7 @@ void KisDabRenderingQueueTest::testCachedDabs()
     KisDabCacheUtils::DabRequestInfo request2(color, pos2, shape, pi2, 1.0);
 
     cacheInterface->typeOverride = KisDabRenderingJob::Dab;
-    KisDabRenderingJob *job0 = queue.addDab(request1);
+    KisDabRenderingJob *job0 = queue.addDab(request1, OPACITY_OPAQUE_F, OPACITY_OPAQUE_F);
 
     QVERIFY(job0);
     QCOMPARE(job0->seqNo, 0);
@@ -106,7 +106,7 @@ void KisDabRenderingQueueTest::testCachedDabs()
     QVERIFY(!job0->postprocessedDevice);
 
     cacheInterface->typeOverride = KisDabRenderingJob::Dab;
-    KisDabRenderingJob *job1 = queue.addDab(request2);
+    KisDabRenderingJob *job1 = queue.addDab(request2, OPACITY_OPAQUE_F, OPACITY_OPAQUE_F);
 
     QVERIFY(job1);
     QCOMPARE(job1->seqNo, 1);
@@ -116,11 +116,11 @@ void KisDabRenderingQueueTest::testCachedDabs()
     QVERIFY(!job1->postprocessedDevice);
 
     cacheInterface->typeOverride = KisDabRenderingJob::Copy;
-    KisDabRenderingJob *job2 = queue.addDab(request2);
+    KisDabRenderingJob *job2 = queue.addDab(request2, OPACITY_OPAQUE_F, OPACITY_OPAQUE_F);
     QVERIFY(!job2);
 
     cacheInterface->typeOverride = KisDabRenderingJob::Copy;
-    KisDabRenderingJob *job3 = queue.addDab(request2);
+    KisDabRenderingJob *job3 = queue.addDab(request2, OPACITY_OPAQUE_F, OPACITY_OPAQUE_F);
     QVERIFY(!job3);
 
     // we only added the dabs, but we haven't completed them yet
@@ -183,7 +183,7 @@ void KisDabRenderingQueueTest::testCachedDabs()
     {
         // add one more cached job and take it
         cacheInterface->typeOverride = KisDabRenderingJob::Copy;
-        KisDabRenderingJob *job = queue.addDab(request2);
+        KisDabRenderingJob *job = queue.addDab(request2, OPACITY_OPAQUE_F, OPACITY_OPAQUE_F);
         QVERIFY(!job);
 
         // now we should have at least one job in prepared state
@@ -205,7 +205,7 @@ void KisDabRenderingQueueTest::testCachedDabs()
         // add a 'dab' job and complete it
 
         cacheInterface->typeOverride = KisDabRenderingJob::Dab;
-        KisDabRenderingJob *job = queue.addDab(request1);
+        KisDabRenderingJob *job = queue.addDab(request1, OPACITY_OPAQUE_F, OPACITY_OPAQUE_F);
 
         QVERIFY(job);
         QCOMPARE(job->seqNo, 5);
@@ -261,7 +261,7 @@ void KisDabRenderingQueueTest::testPostprocessedDabs()
     KisDabCacheUtils::DabRequestInfo request2(color, pos2, shape, pi2, 1.0);
 
     cacheInterface->typeOverride = KisDabRenderingJob::Dab;
-    KisDabRenderingJob *job0 = queue.addDab(request1);
+    KisDabRenderingJob *job0 = queue.addDab(request1, OPACITY_OPAQUE_F, OPACITY_OPAQUE_F);
 
     QVERIFY(job0);
     QCOMPARE(job0->seqNo, 0);
@@ -271,7 +271,7 @@ void KisDabRenderingQueueTest::testPostprocessedDabs()
     QVERIFY(!job0->postprocessedDevice);
 
     cacheInterface->typeOverride = KisDabRenderingJob::Dab;
-    KisDabRenderingJob *job1 = queue.addDab(request2);
+    KisDabRenderingJob *job1 = queue.addDab(request2, OPACITY_OPAQUE_F, OPACITY_OPAQUE_F);
 
     QVERIFY(job1);
     QCOMPARE(job1->seqNo, 1);
@@ -281,11 +281,11 @@ void KisDabRenderingQueueTest::testPostprocessedDabs()
     QVERIFY(!job1->postprocessedDevice);
 
     cacheInterface->typeOverride = KisDabRenderingJob::Postprocess;
-    KisDabRenderingJob *job2 = queue.addDab(request2);
+    KisDabRenderingJob *job2 = queue.addDab(request2, OPACITY_OPAQUE_F, OPACITY_OPAQUE_F);
     QVERIFY(!job2);
 
     cacheInterface->typeOverride = KisDabRenderingJob::Postprocess;
-    KisDabRenderingJob *job3 = queue.addDab(request2);
+    KisDabRenderingJob *job3 = queue.addDab(request2, OPACITY_OPAQUE_F, OPACITY_OPAQUE_F);
     QVERIFY(!job3);
 
     // we only added the dabs, but we haven't completed them yet
@@ -379,7 +379,7 @@ void KisDabRenderingQueueTest::testPostprocessedDabs()
     {
         // add one more postprocessed job and take it
         cacheInterface->typeOverride = KisDabRenderingJob::Postprocess;
-        KisDabRenderingJob *job = queue.addDab(request2);
+        KisDabRenderingJob *job = queue.addDab(request2, OPACITY_OPAQUE_F, OPACITY_OPAQUE_F);
 
         QVERIFY(job);
         QCOMPARE(job->seqNo, 4);
@@ -419,7 +419,7 @@ void KisDabRenderingQueueTest::testPostprocessedDabs()
         // add a 'dab' job and complete it. That will clear the queue!
 
         cacheInterface->typeOverride = KisDabRenderingJob::Dab;
-        KisDabRenderingJob *job = queue.addDab(request1);
+        KisDabRenderingJob *job = queue.addDab(request1, OPACITY_OPAQUE_F, OPACITY_OPAQUE_F);
 
         QVERIFY(job);
         QCOMPARE(job->seqNo, 5);
@@ -478,7 +478,7 @@ void KisDabRenderingQueueTest::testRunningJobs()
     KisDabCacheUtils::DabRequestInfo request1(color, pos1, shape, pi1, 1.0);
     KisDabCacheUtils::DabRequestInfo request2(color, pos2, shape, pi2, 1.0);
 
-    KisDabRenderingJob *job0 = queue.addDab(request1);
+    KisDabRenderingJob *job0 = queue.addDab(request1, OPACITY_OPAQUE_F, OPACITY_OPAQUE_F);
 
     QVERIFY(job0);
     QCOMPARE(job0->seqNo, 0);
@@ -496,7 +496,7 @@ void KisDabRenderingQueueTest::testRunningJobs()
 
     QVERIFY(!job0->originalDevice->bounds().isEmpty());
 
-    KisDabRenderingJob *job1 = queue.addDab(request2);
+    KisDabRenderingJob *job1 = queue.addDab(request2, OPACITY_OPAQUE_F, OPACITY_OPAQUE_F);
     QVERIFY(!job1);
 
     QList<KisRenderedDab> renderedDabs = queue.takeReadyDabs();
@@ -527,8 +527,8 @@ void KisDabRenderingQueueTest::testExecutor()
     KisDabCacheUtils::DabRequestInfo request1(color, pos1, shape, pi1, 1.0);
     KisDabCacheUtils::DabRequestInfo request2(color, pos2, shape, pi2, 1.0);
 
-    executor.addDab(request1);
-    executor.addDab(request2);
+    executor.addDab(request1, 0.5, 0.25);
+    executor.addDab(request2, 0.125, 1.0);
 
     executor.waitForDone();
 
@@ -541,6 +541,11 @@ void KisDabRenderingQueueTest::testExecutor()
     QCOMPARE(renderedDabs[0].offset, QPoint(5,5));
     QCOMPARE(renderedDabs[1].offset, QPoint(15,15));
 
+    QCOMPARE(renderedDabs[0].opacity, 0.5);
+    QCOMPARE(renderedDabs[0].flow, 0.25);
+    QCOMPARE(renderedDabs[1].opacity, 0.125);
+    QCOMPARE(renderedDabs[1].flow, 1.0);
+
 }
 
 QTEST_MAIN(KisDabRenderingQueueTest)
diff --git a/plugins/paintops/defaultpaintops/defaultpaintops_plugin.cc b/plugins/paintops/defaultpaintops/defaultpaintops_plugin.cc
index b50be936f03..03d5bde99d0 100644
--- a/plugins/paintops/defaultpaintops/defaultpaintops_plugin.cc
+++ b/plugins/paintops/defaultpaintops/defaultpaintops_plugin.cc
@@ -33,7 +33,7 @@
 #include "kis_duplicateop_settings.h"
 #include "kis_global.h"
 #include <brushengine/kis_paintop_registry.h>
-#include "kis_brush_based_paintop_settings.h"
+#include "KisBrushOpSettings.h"
 #include "kis_brush_server.h"
 #include "kis_duplicateop_settings_widget.h"
 
@@ -44,7 +44,7 @@ DefaultPaintOpsPlugin::DefaultPaintOpsPlugin(QObject *parent, const QVariantList
     : QObject(parent)
 {
     KisPaintOpRegistry *r = KisPaintOpRegistry::instance();
-    r->add(new KisSimplePaintOpFactory<KisBrushOp, KisBrushBasedPaintOpSettings, KisBrushOpSettingsWidget>("paintbrush", i18nc("Pixel paintbrush", "Pixel"), KisPaintOpFactory::categoryStable(), "krita-paintbrush.png", QString(), QStringList(), 1));
+    r->add(new KisSimplePaintOpFactory<KisBrushOp, KisBrushOpSettings, KisBrushOpSettingsWidget>("paintbrush", i18nc("Pixel paintbrush", "Pixel"), KisPaintOpFactory::categoryStable(), "krita-paintbrush.png", QString(), QStringList(), 1));
     r->add(new KisSimplePaintOpFactory<KisDuplicateOp, KisDuplicateOpSettings, KisDuplicateOpSettingsWidget>("duplicate", i18nc("clone paintbrush (previously \"Duplicate\")", "Clone"), KisPaintOpFactory::categoryStable(), "krita-duplicate.png", QString(), QStringList(COMPOSITE_COPY), 15));
 
     KisBrushServer::instance();
diff --git a/plugins/paintops/libpaintop/kis_pressure_flow_opacity_option.cpp b/plugins/paintops/libpaintop/kis_pressure_flow_opacity_option.cpp
index c05e72927e7..007f585323d 100644
--- a/plugins/paintops/libpaintop/kis_pressure_flow_opacity_option.cpp
+++ b/plugins/paintops/libpaintop/kis_pressure_flow_opacity_option.cpp
@@ -82,10 +82,22 @@ void KisFlowOpacityOption::setOpacity(qreal opacity)
 
 void KisFlowOpacityOption::apply(KisPainter* painter, const KisPaintInformation& info)
 {
-    if (m_paintActionType == WASH && m_nodeHasIndirectPaintingSupport)
-        painter->setOpacityUpdateAverage(quint8(getDynamicOpacity(info) * 255.0));
-    else
-        painter->setOpacityUpdateAverage(quint8(getStaticOpacity() * getDynamicOpacity(info) * 255.0));
+    quint8 opacity = OPACITY_OPAQUE_U8;
+    quint8 flow = OPACITY_OPAQUE_U8;
 
-    painter->setFlow(quint8(getFlow() * 255.0));
+    apply(info, &opacity, &flow);
+
+    painter->setOpacityUpdateAverage(opacity);
+    painter->setFlow(flow);
+}
+
+void KisFlowOpacityOption::apply(const KisPaintInformation &info, quint8 *opacity, quint8 *flow)
+{
+    if (m_paintActionType == WASH && m_nodeHasIndirectPaintingSupport) {
+        *opacity = quint8(getDynamicOpacity(info) * 255.0);
+    } else {
+        *opacity = quint8(getStaticOpacity() * getDynamicOpacity(info) * 255.0);
+    }
+
+    *flow = quint8(getFlow() * 255.0);
 }
diff --git a/plugins/paintops/libpaintop/kis_pressure_flow_opacity_option.h b/plugins/paintops/libpaintop/kis_pressure_flow_opacity_option.h
index f822372fe6e..bb52cf6ec93 100644
--- a/plugins/paintops/libpaintop/kis_pressure_flow_opacity_option.h
+++ b/plugins/paintops/libpaintop/kis_pressure_flow_opacity_option.h
@@ -40,6 +40,7 @@ public:
     void setFlow(qreal flow);
     void setOpacity(qreal opacity);
     void apply(KisPainter* painter, const KisPaintInformation& info);
+    void apply(const KisPaintInformation& info, quint8 *opacity, quint8 *flow);
 
     qreal getFlow() const;
     qreal getStaticOpacity() const;


More information about the kimageshop mailing list