diff --git a/libs/global/KisRollingMeanAccumulatorWrapper.cpp b/libs/global/KisRollingMeanAccumulatorWrapper.cpp index 0ae502a78b..d7280171bc 100644 --- a/libs/global/KisRollingMeanAccumulatorWrapper.cpp +++ b/libs/global/KisRollingMeanAccumulatorWrapper.cpp @@ -1,61 +1,72 @@ /* * Copyright (c) 2017 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KisRollingMeanAccumulatorWrapper.h" #include #include #include using namespace boost::accumulators; struct KisRollingMeanAccumulatorWrapper::Private { Private(int windowSize) : accumulator(tag::rolling_window::window_size = windowSize) { } accumulator_set > accumulator; }; KisRollingMeanAccumulatorWrapper::KisRollingMeanAccumulatorWrapper(int windowSize) : m_d(new Private(windowSize)) { } KisRollingMeanAccumulatorWrapper::~KisRollingMeanAccumulatorWrapper() { } void KisRollingMeanAccumulatorWrapper::operator()(qreal value) { m_d->accumulator(value); } qreal KisRollingMeanAccumulatorWrapper::rollingMean() const { return boost::accumulators::rolling_mean(m_d->accumulator); } +qreal KisRollingMeanAccumulatorWrapper::rollingMeanSafe() const +{ + return boost::accumulators::rolling_count(m_d->accumulator) > 0 ? + boost::accumulators::rolling_mean(m_d->accumulator) : 0; +} + +int KisRollingMeanAccumulatorWrapper::rollingCount() const +{ + return boost::accumulators::rolling_count(m_d->accumulator); +} + void KisRollingMeanAccumulatorWrapper::reset(int windowSize) { m_d->accumulator = accumulator_set>( tag::rolling_window::window_size = windowSize); } diff --git a/libs/global/KisRollingMeanAccumulatorWrapper.h b/libs/global/KisRollingMeanAccumulatorWrapper.h index 0d696b565e..89c882f14f 100644 --- a/libs/global/KisRollingMeanAccumulatorWrapper.h +++ b/libs/global/KisRollingMeanAccumulatorWrapper.h @@ -1,60 +1,72 @@ /* * Copyright (c) 2017 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KISROLLINGMEANACCUMULATORWRAPPER_H #define KISROLLINGMEANACCUMULATORWRAPPER_H #include #include #include "kritaglobal_export.h" /** * @brief A simple wrapper class that hides boost includes from QtCreator preventing it * from crashing when one adds boost's accumulator into a file */ class KRITAGLOBAL_EXPORT KisRollingMeanAccumulatorWrapper { public: /** * Create a rolling mean accumulator with window \p windowSize */ KisRollingMeanAccumulatorWrapper(int windowSize); ~KisRollingMeanAccumulatorWrapper(); /** * Add \p value to a set of numbers */ void operator()(qreal value); /** - * Get rolling mean of the numbers passed to the operator + * Get rolling mean of the numbers passed to the operator. If there are no elements + * in the rolling window, returns NaN. */ qreal rollingMean() const; + /** + * Get rolling mean of the numbers passed to the operator. If there are no elements + * in the rolling window, returns 0. + */ + qreal rollingMeanSafe() const; + + /** + * Get the number of elements in the rolling window + */ + int rollingCount() const; + /** * Reset accumulator and any stored value */ void reset(int windowSize); private: struct Private; const QScopedPointer m_d; }; #endif // KISROLLINGMEANACCUMULATORWRAPPER_H diff --git a/libs/image/brushengine/kis_paintop.cc b/libs/image/brushengine/kis_paintop.cc index 6c6571c618..ac28205c37 100644 --- a/libs/image/brushengine/kis_paintop.cc +++ b/libs/image/brushengine/kis_paintop.cc @@ -1,211 +1,211 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2004 Clarence Dang * Copyright (c) 2004 Adrian Page * Copyright (c) 2004,2007,2010 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_paintop.h" #include #include #include #include #include "kis_painter.h" #include "kis_layer.h" #include "kis_image.h" #include "kis_paint_device.h" #include "kis_global.h" #include "kis_datamanager.h" #include #include #include "kis_vec.h" #include "kis_perspective_math.h" #include "kis_fixed_paint_device.h" #include "kis_paintop_utils.h" #define BEZIER_FLATNESS_THRESHOLD 0.5 #include #include struct Q_DECL_HIDDEN KisPaintOp::Private { Private(KisPaintOp *_q) : q(_q), dab(0), fanCornersEnabled(false), fanCornersStep(1.0) {} KisPaintOp *q; KisFixedPaintDeviceSP dab; KisPainter* painter; bool fanCornersEnabled; qreal fanCornersStep; }; KisPaintOp::KisPaintOp(KisPainter * painter) : d(new Private(this)) { d->painter = painter; } KisPaintOp::~KisPaintOp() { d->dab.clear(); delete d; } KisFixedPaintDeviceSP KisPaintOp::cachedDab() { return cachedDab(d->painter->device()->colorSpace()); } KisFixedPaintDeviceSP KisPaintOp::cachedDab(const KoColorSpace *cs) { if (!d->dab || *d->dab->colorSpace() != *cs) { d->dab = new KisFixedPaintDevice(cs); } return d->dab; } void KisPaintOp::setFanCornersInfo(bool fanCornersEnabled, qreal fanCornersStep) { d->fanCornersEnabled = fanCornersEnabled; d->fanCornersStep = fanCornersStep; } void KisPaintOp::splitCoordinate(qreal coordinate, qint32 *whole, qreal *fraction) { const qint32 i = std::floor(coordinate); const qreal f = coordinate - i; *whole = i; *fraction = f; } -int KisPaintOp::doAsyncronousUpdate(QVector &jobs) +std::pair KisPaintOp::doAsyncronousUpdate(QVector &jobs) { Q_UNUSED(jobs); - return 40; + return std::make_pair(40, false); } static void paintBezierCurve(KisPaintOp *paintOp, const KisPaintInformation &pi1, const KisVector2D &control1, const KisVector2D &control2, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) { LineEquation line = LineEquation::Through(toKisVector2D(pi1.pos()), toKisVector2D(pi2.pos())); qreal d1 = line.absDistance(control1); qreal d2 = line.absDistance(control2); if ((d1 < BEZIER_FLATNESS_THRESHOLD && d2 < BEZIER_FLATNESS_THRESHOLD) || qIsNaN(d1) || qIsNaN(d2)) { paintOp->paintLine(pi1, pi2, currentDistance); } else { // Midpoint subdivision. See Foley & Van Dam Computer Graphics P.508 KisVector2D l2 = (toKisVector2D(pi1.pos()) + control1) / 2; KisVector2D h = (control1 + control2) / 2; KisVector2D l3 = (l2 + h) / 2; KisVector2D r3 = (control2 + toKisVector2D(pi2.pos())) / 2; KisVector2D r2 = (h + r3) / 2; KisVector2D l4 = (l3 + r2) / 2; KisPaintInformation middlePI = KisPaintInformation::mix(toQPointF(l4), 0.5, pi1, pi2); paintBezierCurve(paintOp, pi1, l2, l3, middlePI, currentDistance); paintBezierCurve(paintOp, middlePI, r2, r3, pi2, currentDistance); } } void KisPaintOp::paintBezierCurve(const KisPaintInformation &pi1, const QPointF &control1, const QPointF &control2, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) { return ::paintBezierCurve(this, pi1, toKisVector2D(control1), toKisVector2D(control2), pi2, currentDistance); } void KisPaintOp::paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) { KisPaintOpUtils::paintLine(*this, pi1, pi2, currentDistance, d->fanCornersEnabled, d->fanCornersStep); } void KisPaintOp::paintAt(const KisPaintInformation& info, KisDistanceInformation *currentDistance) { Q_ASSERT(currentDistance); KisPaintInformation pi(info); pi.paintAt(*this, currentDistance); } void KisPaintOp::updateSpacing(const KisPaintInformation &info, KisDistanceInformation ¤tDistance) const { KisPaintInformation pi(info); KisSpacingInformation spacingInfo; { KisPaintInformation::DistanceInformationRegistrar r = pi.registerDistanceInformation(¤tDistance); spacingInfo = updateSpacingImpl(pi); } currentDistance.updateSpacing(spacingInfo); } void KisPaintOp::updateTiming(const KisPaintInformation &info, KisDistanceInformation ¤tDistance) const { KisPaintInformation pi(info); KisTimingInformation timingInfo; { KisPaintInformation::DistanceInformationRegistrar r = pi.registerDistanceInformation(¤tDistance); timingInfo = updateTimingImpl(pi); } currentDistance.updateTiming(timingInfo); } KisTimingInformation KisPaintOp::updateTimingImpl(const KisPaintInformation &info) const { Q_UNUSED(info); return KisTimingInformation(); } KisPainter* KisPaintOp::painter() const { return d->painter; } KisPaintDeviceSP KisPaintOp::source() const { return d->painter->device(); } diff --git a/libs/image/brushengine/kis_paintop.h b/libs/image/brushengine/kis_paintop.h index f1365270a9..8291eaef50 100644 --- a/libs/image/brushengine/kis_paintop.h +++ b/libs/image/brushengine/kis_paintop.h @@ -1,165 +1,165 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2004 Clarence Dang * Copyright (c) 2004 Adrian Page * Copyright (c) 2004,2010 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_PAINTOP_H_ #define KIS_PAINTOP_H_ #include #include "kis_shared.h" #include "kis_types.h" #include class QPointF; class KoColorSpace; class KisPainter; class KisPaintInformation; class KisRunnableStrokeJobData; /** * KisPaintOp are use by tools to draw on a paint device. A paintop takes settings * and input information, like pressure, tilt or motion and uses that to draw pixels */ class KRITAIMAGE_EXPORT KisPaintOp : public KisShared { struct Private; public: KisPaintOp(KisPainter * painter); virtual ~KisPaintOp(); /** * Paint at the subpixel point pos using the specified paint information.. * * The distance/time between two calls of the paintAt is always specified by spacing and timing, * which are automatically saved into the current distance information object. */ void paintAt(const KisPaintInformation& info, KisDistanceInformation *currentDistance); /** * Updates the spacing settings in currentDistance based on the provided information. Note that * the spacing is updated automatically in the paintAt method, so there is no need to call this * method if paintAt has just been called. */ void updateSpacing(const KisPaintInformation &info, KisDistanceInformation ¤tDistance) const; /** * Updates the timing settings in currentDistance based on the provided information. Note that * the timing is updated automatically in the paintAt method, so there is no need to call this * method if paintAt has just been called. */ void updateTiming(const KisPaintInformation &info, KisDistanceInformation ¤tDistance) const; /** * Draw a line between pos1 and pos2 using the currently set brush and color. * If savedDist is less than zero, the brush is painted at pos1 before being * painted along the line using the spacing setting. * * @return the drag distance, that is the remains of the distance * between p1 and p2 not covered because the currenlty set brush * has a spacing greater than that distance. */ virtual void paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance); /** * Draw a Bezier curve between pos1 and pos2 using control points 1 and 2. * If savedDist is less than zero, the brush is painted at pos1 before being * painted along the curve using the spacing setting. * @return the drag distance, that is the remains of the distance between p1 and p2 not covered * because the currenlty set brush has a spacing greater than that distance. */ virtual void paintBezierCurve(const KisPaintInformation &pi1, const QPointF &control1, const QPointF &control2, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance); /** * Whether this paintop can paint. Can be false in case that some setting isn't read correctly. * @return if paintop is ready for painting, default is true */ virtual bool canPaint() const { return true; } /** * Split the coordinate into whole + fraction, where fraction is always >= 0. */ 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. The jobs that should be run to prepare the update * are returned via \p jobs * - * @return the desired FPS rate (period of updates) + * @return a pair of */ - virtual int doAsyncronousUpdate(QVector &jobs); + virtual std::pair doAsyncronousUpdate(QVector &jobs); protected: friend class KisPaintInformation; /** * The implementation of painting of a dab and updating spacing. This does NOT need to update * the timing information. */ virtual KisSpacingInformation paintAt(const KisPaintInformation& info) = 0; /** * Implementation of a spacing update */ virtual KisSpacingInformation updateSpacingImpl(const KisPaintInformation &info) const = 0; /** * Implementation of a timing update. The default implementation always disables timing. This is * suitable for paintops that do not support airbrushing. */ virtual KisTimingInformation updateTimingImpl(const KisPaintInformation &info) const; KisFixedPaintDeviceSP cachedDab(); KisFixedPaintDeviceSP cachedDab(const KoColorSpace *cs); /** * Return the painter this paintop is owned by */ KisPainter* painter() const; /** * Return the paintdevice the painter this paintop is owned by */ KisPaintDeviceSP source() const; private: friend class KisPressureRotationOption; void setFanCornersInfo(bool fanCornersEnabled, qreal fanCornersStep); private: Private* const d; }; #endif // KIS_PAINTOP_H_ diff --git a/libs/ui/tool/strokes/freehand_stroke.cpp b/libs/ui/tool/strokes/freehand_stroke.cpp index b5aed4d7d6..63c2dd989c 100644 --- a/libs/ui/tool/strokes/freehand_stroke.cpp +++ b/libs/ui/tool/strokes/freehand_stroke.cpp @@ -1,300 +1,316 @@ /* * Copyright (c) 2011 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "freehand_stroke.h" #include #include "kis_canvas_resource_provider.h" #include #include #include "kis_painter.h" #include "kis_paintop.h" #include "kis_update_time_monitor.h" #include #include #include "FreehandStrokeRunnableJobDataWithUpdate.h" #include #include "KisStrokeEfficiencyMeasurer.h" #include struct FreehandStrokeStrategy::Private { Private(KisResourcesSnapshotSP _resources) : resources(_resources), needsAsynchronousUpdates(_resources->presetNeedsAsynchronousUpdates()) { if (needsAsynchronousUpdates) { timeSinceLastUpdate.start(); } } Private(const Private &rhs) : randomSource(rhs.randomSource), resources(rhs.resources), needsAsynchronousUpdates(rhs.needsAsynchronousUpdates) { if (needsAsynchronousUpdates) { timeSinceLastUpdate.start(); } } KisStrokeRandomSource randomSource; KisResourcesSnapshotSP resources; KisStrokeEfficiencyMeasurer efficiencyMeasurer; QElapsedTimer timeSinceLastUpdate; int currentUpdatePeriod = 40; const bool needsAsynchronousUpdates = false; std::mutex updateEntryMutex; }; FreehandStrokeStrategy::FreehandStrokeStrategy(bool needsIndirectPainting, const QString &indirectPaintingCompositeOp, KisResourcesSnapshotSP resources, PainterInfo *painterInfo, const KUndo2MagicString &name) : KisPainterBasedStrokeStrategy("FREEHAND_STROKE", name, resources, painterInfo), m_d(new Private(resources)) { init(needsIndirectPainting, indirectPaintingCompositeOp); } FreehandStrokeStrategy::FreehandStrokeStrategy(bool needsIndirectPainting, const QString &indirectPaintingCompositeOp, KisResourcesSnapshotSP resources, QVector painterInfos, const KUndo2MagicString &name) : KisPainterBasedStrokeStrategy("FREEHAND_STROKE", name, resources, painterInfos), m_d(new Private(resources)) { init(needsIndirectPainting, indirectPaintingCompositeOp); } FreehandStrokeStrategy::FreehandStrokeStrategy(const FreehandStrokeStrategy &rhs, int levelOfDetail) : KisPainterBasedStrokeStrategy(rhs, levelOfDetail), m_d(new Private(*rhs.m_d)) { m_d->randomSource.setLevelOfDetail(levelOfDetail); } FreehandStrokeStrategy::~FreehandStrokeStrategy() { KisStrokeSpeedMonitor::instance()->notifyStrokeFinished(m_d->efficiencyMeasurer.averageCursorSpeed(), m_d->efficiencyMeasurer.averageRenderingSpeed(), m_d->efficiencyMeasurer.averageFps(), m_d->resources->currentPaintOpPreset()); KisUpdateTimeMonitor::instance()->endStrokeMeasure(); } void FreehandStrokeStrategy::init(bool needsIndirectPainting, const QString &indirectPaintingCompositeOp) { setNeedsIndirectPainting(needsIndirectPainting); setIndirectPaintingCompositeOp(indirectPaintingCompositeOp); setSupportsWrapAroundMode(true); enableJob(KisSimpleStrokeStrategy::JOB_DOSTROKE); if (m_d->needsAsynchronousUpdates) { /** * In case the paintop uses asynchronous updates, we should set priority to it, * because FPS is controlled separately, not by the queue's merging algorithm. */ setBalancingRatioOverride(0.01); // set priority to updates } KisUpdateTimeMonitor::instance()->startStrokeMeasure(); m_d->efficiencyMeasurer.setEnabled(KisStrokeSpeedMonitor::instance()->haveStrokeSpeedMeasurement()); } void FreehandStrokeStrategy::initStrokeCallback() { KisPainterBasedStrokeStrategy::initStrokeCallback(); m_d->efficiencyMeasurer.notifyRenderingStarted(); } void FreehandStrokeStrategy::finishStrokeCallback() { m_d->efficiencyMeasurer.notifyRenderingFinished(); KisPainterBasedStrokeStrategy::finishStrokeCallback(); } void FreehandStrokeStrategy::doStrokeCallback(KisStrokeJobData *data) { PainterInfo *info = 0; if (UpdateData *d = dynamic_cast(data)) { // this job is lod-clonable in contrast to FreehandStrokeRunnableJobDataWithUpdate! tryDoUpdate(d->forceUpdate); } else if (Data *d = dynamic_cast(data)) { info = painterInfos()[d->painterInfoId]; KisUpdateTimeMonitor::instance()->reportPaintOpPreset(info->painter->preset()); KisRandomSourceSP rnd = m_d->randomSource.source(); KisPerStrokeRandomSourceSP strokeRnd = m_d->randomSource.perStrokeSource(); switch(d->type) { case Data::POINT: d->pi1.setRandomSource(rnd); d->pi1.setPerStrokeRandomSource(strokeRnd); info->painter->paintAt(d->pi1, info->dragDistance); m_d->efficiencyMeasurer.addSample(d->pi1.pos()); break; case Data::LINE: d->pi1.setRandomSource(rnd); d->pi2.setRandomSource(rnd); d->pi1.setPerStrokeRandomSource(strokeRnd); d->pi2.setPerStrokeRandomSource(strokeRnd); info->painter->paintLine(d->pi1, d->pi2, info->dragDistance); m_d->efficiencyMeasurer.addSample(d->pi2.pos()); break; case Data::CURVE: d->pi1.setRandomSource(rnd); d->pi2.setRandomSource(rnd); d->pi1.setPerStrokeRandomSource(strokeRnd); d->pi2.setPerStrokeRandomSource(strokeRnd); info->painter->paintBezierCurve(d->pi1, d->control1, d->control2, d->pi2, info->dragDistance); m_d->efficiencyMeasurer.addSample(d->pi2.pos()); break; case Data::POLYLINE: info->painter->paintPolyline(d->points, 0, d->points.size()); m_d->efficiencyMeasurer.addSamples(d->points); break; case Data::POLYGON: info->painter->paintPolygon(d->points); m_d->efficiencyMeasurer.addSamples(d->points); break; case Data::RECT: info->painter->paintRect(d->rect); m_d->efficiencyMeasurer.addSample(d->rect.topLeft()); m_d->efficiencyMeasurer.addSample(d->rect.topRight()); m_d->efficiencyMeasurer.addSample(d->rect.bottomRight()); m_d->efficiencyMeasurer.addSample(d->rect.bottomLeft()); break; case Data::ELLIPSE: info->painter->paintEllipse(d->rect); // TODO: add speed measures break; case Data::PAINTER_PATH: info->painter->paintPainterPath(d->path); // TODO: add speed measures 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; }; tryDoUpdate(); } else { KisPainterBasedStrokeStrategy::doStrokeCallback(data); FreehandStrokeRunnableJobDataWithUpdate *dataWithUpdate = dynamic_cast(data); if (dataWithUpdate) { tryDoUpdate(); } } } void FreehandStrokeStrategy::tryDoUpdate(bool forceEnd) { // we should enter this function only once! std::unique_lock entryLock(m_d->updateEntryMutex, std::try_to_lock); if (!entryLock.owns_lock()) return; if (m_d->needsAsynchronousUpdates) { if (forceEnd || m_d->timeSinceLastUpdate.elapsed() > m_d->currentUpdatePeriod) { m_d->timeSinceLastUpdate.restart(); Q_FOREACH (PainterInfo *info, painterInfos()) { KisPaintOp *paintop = info->painter->paintOp(); KIS_SAFE_ASSERT_RECOVER_RETURN(paintop); // TODO: well, we should count all N simultaneous painters for FPS rate! QVector jobs; - m_d->currentUpdatePeriod = paintop->doAsyncronousUpdate(jobs); - if (!jobs.isEmpty() || info->painter->hasDirtyRegion()) { + bool needsMoreUpdates = false; + + std::tie(m_d->currentUpdatePeriod, needsMoreUpdates) = + paintop->doAsyncronousUpdate(jobs); + + if (!jobs.isEmpty() || + info->painter->hasDirtyRegion() || + (forceEnd && needsMoreUpdates)) { + jobs.append(new KisRunnableStrokeJobData( [this] () { this->issueSetDirtySignals(); }, KisStrokeJobData::SEQUENTIAL)); + if (forceEnd && needsMoreUpdates) { + jobs.append(new KisRunnableStrokeJobData( + [this] () { + this->tryDoUpdate(true); + }, + KisStrokeJobData::SEQUENTIAL)); + } + + runnableJobsInterface()->addRunnableJobs(jobs); m_d->efficiencyMeasurer.notifyFrameRenderingStarted(); } } } } else { issueSetDirtySignals(); } } void FreehandStrokeStrategy::issueSetDirtySignals() { QVector dirtyRects; Q_FOREACH (PainterInfo *info, painterInfos()) { dirtyRects.append(info->painter->takeDirtyRegion()); } //KisUpdateTimeMonitor::instance()->reportJobFinished(data, dirtyRects); targetNode()->setDirty(dirtyRects); } KisStrokeStrategy* FreehandStrokeStrategy::createLodClone(int levelOfDetail) { if (!m_d->resources->presetAllowsLod()) return 0; FreehandStrokeStrategy *clone = new FreehandStrokeStrategy(*this, levelOfDetail); return clone; } void FreehandStrokeStrategy::notifyUserStartedStroke() { m_d->efficiencyMeasurer.notifyCursorMoveStarted(); } void FreehandStrokeStrategy::notifyUserEndedStroke() { m_d->efficiencyMeasurer.notifyCursorMoveFinished(); } diff --git a/plugins/paintops/defaultpaintops/brush/KisDabRenderingExecutor.cpp b/plugins/paintops/defaultpaintops/brush/KisDabRenderingExecutor.cpp index 509f69514a..1dc215ab3d 100644 --- a/plugins/paintops/defaultpaintops/brush/KisDabRenderingExecutor.cpp +++ b/plugins/paintops/defaultpaintops/brush/KisDabRenderingExecutor.cpp @@ -1,88 +1,90 @@ /* * Copyright (c) 2017 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KisDabRenderingExecutor.h" #include "KisDabRenderingQueue.h" #include "KisDabRenderingQueueCache.h" #include "KisDabRenderingJob.h" #include "KisRenderedDab.h" #include "KisRunnableStrokeJobsInterface.h" #include "KisRunnableStrokeJobData.h" #include struct KisDabRenderingExecutor::Private { QScopedPointer renderingQueue; KisRunnableStrokeJobsInterface *runnableJobsInterface; }; KisDabRenderingExecutor::KisDabRenderingExecutor(const KoColorSpace *cs, KisDabCacheUtils::ResourcesFactory resourcesFactory, KisRunnableStrokeJobsInterface *runnableJobsInterface, KisPressureMirrorOption *mirrorOption, KisPrecisionOption *precisionOption) : m_d(new Private) { m_d->runnableJobsInterface = runnableJobsInterface; m_d->renderingQueue.reset( new KisDabRenderingQueue(cs, resourcesFactory)); KisDabRenderingQueueCache *cache = new KisDabRenderingQueueCache(); cache->setMirrorPostprocessing(mirrorOption); cache->setPrecisionOption(precisionOption); m_d->renderingQueue->setCacheInterface(cache); } KisDabRenderingExecutor::~KisDabRenderingExecutor() { } void KisDabRenderingExecutor::addDab(const KisDabCacheUtils::DabRequestInfo &request, qreal opacity, qreal flow) { KisDabRenderingJobSP job = m_d->renderingQueue->addDab(request, opacity, flow); if (job) { m_d->runnableJobsInterface->addRunnableJob( new FreehandStrokeRunnableJobDataWithUpdate( new KisDabRenderingJobRunner(job, m_d->renderingQueue.data(), m_d->runnableJobsInterface), KisStrokeJobData::CONCURRENT)); } } -QList KisDabRenderingExecutor::takeReadyDabs(bool returnMutableDabs) +QList KisDabRenderingExecutor::takeReadyDabs(bool returnMutableDabs, + int oneTimeLimit, + bool *someDabsLeft) { - return m_d->renderingQueue->takeReadyDabs(returnMutableDabs); + return m_d->renderingQueue->takeReadyDabs(returnMutableDabs, oneTimeLimit, someDabsLeft); } bool KisDabRenderingExecutor::hasPreparedDabs() const { return m_d->renderingQueue->hasPreparedDabs(); } -int KisDabRenderingExecutor::averageDabRenderingTime() const +qreal KisDabRenderingExecutor::averageDabRenderingTime() const { return m_d->renderingQueue->averageExecutionTime(); } int KisDabRenderingExecutor::averageDabSize() const { return m_d->renderingQueue->averageDabSize(); } diff --git a/plugins/paintops/defaultpaintops/brush/KisDabRenderingExecutor.h b/plugins/paintops/defaultpaintops/brush/KisDabRenderingExecutor.h index ffd969f1ad..df23d54b34 100644 --- a/plugins/paintops/defaultpaintops/brush/KisDabRenderingExecutor.h +++ b/plugins/paintops/defaultpaintops/brush/KisDabRenderingExecutor.h @@ -1,63 +1,63 @@ /* * Copyright (c) 2017 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KISDABRENDERINGEXECUTOR_H #define KISDABRENDERINGEXECUTOR_H #include "kritadefaultpaintops_export.h" #include #include class KisRenderedDab; #include "KisDabCacheUtils.h" class KisPressureMirrorOption; class KisPrecisionOption; class KisRunnableStrokeJobsInterface; class KRITADEFAULTPAINTOPS_EXPORT KisDabRenderingExecutor { public: KisDabRenderingExecutor(const KoColorSpace *cs, KisDabCacheUtils::ResourcesFactory resourcesFactory, KisRunnableStrokeJobsInterface *runnableJobsInterface, KisPressureMirrorOption *mirrorOption = 0, KisPrecisionOption *precisionOption = 0); ~KisDabRenderingExecutor(); void addDab(const KisDabCacheUtils::DabRequestInfo &request, qreal opacity, qreal flow); - QList takeReadyDabs(bool returnMutableDabs = false); + QList takeReadyDabs(bool returnMutableDabs = false, int oneTimeLimit = -1, bool *someDabsLeft = 0); bool hasPreparedDabs() const; - int averageDabRenderingTime() const; // usecs + qreal averageDabRenderingTime() const; // msecs int averageDabSize() const; private: KisDabRenderingExecutor(const KisDabRenderingExecutor &rhs) = delete; struct Private; const QScopedPointer m_d; }; #endif // KISDABRENDERINGEXECUTOR_H diff --git a/plugins/paintops/defaultpaintops/brush/KisDabRenderingQueue.cpp b/plugins/paintops/defaultpaintops/brush/KisDabRenderingQueue.cpp index 0b99c105a1..961c2df651 100644 --- a/plugins/paintops/defaultpaintops/brush/KisDabRenderingQueue.cpp +++ b/plugins/paintops/defaultpaintops/brush/KisDabRenderingQueue.cpp @@ -1,442 +1,461 @@ /* * Copyright (c) 2017 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KisDabRenderingQueue.h" #include "KisDabRenderingJob.h" #include "KisRenderedDab.h" #include "kis_painter.h" #include "KisOptimizedByteArray.h" #include #include #include #include #include "kis_algebra_2d.h" struct KisDabRenderingQueue::Private { struct DumbCacheInterface : public CacheInterface { void getDabType(bool hasDabInCache, KisDabCacheUtils::DabRenderingResources *resources, const KisDabCacheUtils::DabRequestInfo &request, /* out */ KisDabCacheUtils::DabGenerationInfo *di, bool *shouldUseCache) override { Q_UNUSED(hasDabInCache); Q_UNUSED(resources); Q_UNUSED(request); di->needsPostprocessing = false; *shouldUseCache = false; } bool hasSeparateOriginal(KisDabCacheUtils::DabRenderingResources *resources) const override { Q_UNUSED(resources); return false; } }; Private(const KoColorSpace *_colorSpace, KisDabCacheUtils::ResourcesFactory _resourcesFactory) : cacheInterface(new DumbCacheInterface), colorSpace(_colorSpace), resourcesFactory(_resourcesFactory), paintDeviceAllocator(new KisOptimizedByteArray::PooledMemoryAllocator()), avgExecutionTime(50), avgDabSize(50) { KIS_SAFE_ASSERT_RECOVER_NOOP(resourcesFactory); } ~Private() { // clear the jobs, so that they would not keep references to any // paint devices anymore jobs.clear(); qDeleteAll(cachedResources); cachedResources.clear(); } QList jobs; int nextSeqNoToUse = 0; int lastPaintedJob = -1; int lastDabJobInQueue = -1; QScopedPointer cacheInterface; const KoColorSpace *colorSpace; qreal averageOpacity = 0.0; KisDabCacheUtils::ResourcesFactory resourcesFactory; QList cachedResources; QSharedPointer paintDeviceAllocator; QMutex mutex; KisRollingMeanAccumulatorWrapper avgExecutionTime; KisRollingMeanAccumulatorWrapper avgDabSize; int calculateLastDabJobIndex(int startSearchIndex); void cleanPaintedDabs(); bool dabsHaveSeparateOriginal(); + bool hasPreparedDabsImpl() const; KisDabCacheUtils::DabRenderingResources* fetchResourcesFromCache(); void putResourcesToCache(KisDabCacheUtils::DabRenderingResources *resources); }; KisDabRenderingQueue::KisDabRenderingQueue(const KoColorSpace *cs, KisDabCacheUtils::ResourcesFactory resourcesFactory) : m_d(new Private(cs, resourcesFactory)) { } KisDabRenderingQueue::~KisDabRenderingQueue() { } int KisDabRenderingQueue::Private::calculateLastDabJobIndex(int startSearchIndex) { if (startSearchIndex < 0) { startSearchIndex = jobs.size() - 1; } // try to use cached value if (startSearchIndex >= lastDabJobInQueue) { return lastDabJobInQueue; } // if we are below the cached value, just iterate through the dabs // (which is extremely(!) slow) for (int i = startSearchIndex; i >= 0; i--) { if (jobs[i]->type == KisDabRenderingJob::Dab) { return i; } } return -1; } KisDabRenderingJobSP KisDabRenderingQueue::addDab(const KisDabCacheUtils::DabRequestInfo &request, qreal opacity, qreal flow) { QMutexLocker l(&m_d->mutex); const int seqNo = m_d->nextSeqNoToUse++; KisDabCacheUtils::DabRenderingResources *resources = m_d->fetchResourcesFromCache(); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(resources, KisDabRenderingJobSP()); // We should sync the cached brush into the current seqNo resources->syncResourcesToSeqNo(seqNo, request.info); const int lastDabJobIndex = m_d->lastDabJobInQueue; KisDabRenderingJobSP job(new KisDabRenderingJob()); bool shouldUseCache = false; m_d->cacheInterface->getDabType(lastDabJobIndex >= 0, resources, request, &job->generationInfo, &shouldUseCache); m_d->putResourcesToCache(resources); resources = 0; // TODO: initialize via c-tor job->seqNo = seqNo; job->type = !shouldUseCache ? KisDabRenderingJob::Dab : job->generationInfo.needsPostprocessing ? KisDabRenderingJob::Postprocess : KisDabRenderingJob::Copy; job->opacity = opacity; job->flow = flow; if (job->type == KisDabRenderingJob::Dab) { job->status = KisDabRenderingJob::Running; } else if (job->type == KisDabRenderingJob::Postprocess || job->type == KisDabRenderingJob::Copy) { KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(lastDabJobIndex >= 0, KisDabRenderingJobSP()); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(lastDabJobIndex < m_d->jobs.size(), KisDabRenderingJobSP()); if (m_d->jobs[lastDabJobIndex]->status == KisDabRenderingJob::Completed) { if (job->type == KisDabRenderingJob::Postprocess) { job->status = KisDabRenderingJob::Running; job->originalDevice = m_d->jobs[lastDabJobIndex]->originalDevice; } else if (job->type == KisDabRenderingJob::Copy) { job->status = KisDabRenderingJob::Completed; job->originalDevice = m_d->jobs[lastDabJobIndex]->originalDevice; job->postprocessedDevice = m_d->jobs[lastDabJobIndex]->postprocessedDevice; + m_d->avgExecutionTime(0); } } } m_d->jobs.append(job); KisDabRenderingJobSP jobToRun; if (job->status == KisDabRenderingJob::Running) { jobToRun = job; } if (job->type == KisDabRenderingJob::Dab) { m_d->lastDabJobInQueue = m_d->jobs.size() - 1; m_d->cleanPaintedDabs(); } // collect some statistics about the dab m_d->avgDabSize(KisAlgebra2D::maxDimension(job->generationInfo.dstDabRect)); return jobToRun; } QList KisDabRenderingQueue::notifyJobFinished(int seqNo, int usecsTime) { QMutexLocker l(&m_d->mutex); QList dependentJobs; /** * Here we use binary search for locating the necessary original dab */ auto finishedJobIt = std::lower_bound(m_d->jobs.begin(), m_d->jobs.end(), seqNo, [] (KisDabRenderingJobSP job, int seqNo) { return job->seqNo < seqNo; }); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(finishedJobIt != m_d->jobs.end(), dependentJobs); KisDabRenderingJobSP finishedJob = *finishedJobIt; KIS_SAFE_ASSERT_RECOVER_NOOP(finishedJob->status == KisDabRenderingJob::Running); KIS_SAFE_ASSERT_RECOVER_NOOP(finishedJob->seqNo == seqNo); KIS_SAFE_ASSERT_RECOVER_NOOP(finishedJob->originalDevice); KIS_SAFE_ASSERT_RECOVER_NOOP(finishedJob->postprocessedDevice); finishedJob->status = KisDabRenderingJob::Completed; if (finishedJob->type == KisDabRenderingJob::Dab) { for (auto it = finishedJobIt + 1; it != m_d->jobs.end(); ++it) { KisDabRenderingJobSP j = *it; // next dab job closes the chain if (j->type == KisDabRenderingJob::Dab) break; // the non 'dab'-type job couldn't have // been started before the source ob was completed KIS_SAFE_ASSERT_RECOVER_BREAK(j->status == KisDabRenderingJob::New); if (j->type == KisDabRenderingJob::Copy) { j->originalDevice = finishedJob->originalDevice; j->postprocessedDevice = finishedJob->postprocessedDevice; j->status = KisDabRenderingJob::Completed; + m_d->avgExecutionTime(0); } else if (j->type == KisDabRenderingJob::Postprocess) { j->originalDevice = finishedJob->originalDevice; j->status = KisDabRenderingJob::Running; dependentJobs << j; } } } if (usecsTime >= 0) { m_d->avgExecutionTime(usecsTime); } return dependentJobs; } void KisDabRenderingQueue::Private::cleanPaintedDabs() { const int nextToBePainted = lastPaintedJob + 1; const int lastSourceJob = calculateLastDabJobIndex(qMin(nextToBePainted, jobs.size() - 1)); if (lastPaintedJob >= 0) { int numRemovedJobs = 0; int numRemovedJobsBeforeLastSource = 0; auto it = jobs.begin(); for (int i = 0; i <= lastPaintedJob; i++) { KisDabRenderingJobSP job = *it; if (i < lastSourceJob || job->type != KisDabRenderingJob::Dab){ KIS_ASSERT_RECOVER_NOOP(job->originalDevice); it = jobs.erase(it); numRemovedJobs++; if (i < lastDabJobInQueue) { numRemovedJobsBeforeLastSource++; } } else { ++it; } } KIS_SAFE_ASSERT_RECOVER_RETURN(jobs.size() > 0); lastPaintedJob -= numRemovedJobs; lastDabJobInQueue -= numRemovedJobsBeforeLastSource; } } -QList KisDabRenderingQueue::takeReadyDabs(bool returnMutableDabs) +QList KisDabRenderingQueue::takeReadyDabs(bool returnMutableDabs, + int oneTimeLimit, + bool *someDabsLeft) { QMutexLocker l(&m_d->mutex); QList renderedDabs; if (m_d->jobs.isEmpty()) return renderedDabs; KIS_SAFE_ASSERT_RECOVER_NOOP( m_d->jobs.isEmpty() || m_d->jobs.first()->type == KisDabRenderingJob::Dab); const int copyJobAfterInclusive = returnMutableDabs && !m_d->dabsHaveSeparateOriginal() ? m_d->lastDabJobInQueue : std::numeric_limits::max(); - for (int i = 0; i < m_d->jobs.size(); i++) { + if (oneTimeLimit < 0) { + oneTimeLimit = std::numeric_limits::max(); + } + + for (int i = 0; i < m_d->jobs.size() && oneTimeLimit > 0; i++, oneTimeLimit--) { KisDabRenderingJobSP j = m_d->jobs[i]; if (j->status != KisDabRenderingJob::Completed) break; if (i <= m_d->lastPaintedJob) continue; KisRenderedDab dab; KisFixedPaintDeviceSP resultDevice = j->postprocessedDevice; if (i >= copyJobAfterInclusive) { resultDevice = new KisFixedPaintDevice(*resultDevice); } dab.device = resultDevice; dab.offset = j->dstDabOffset(); dab.opacity = j->opacity; dab.flow = j->flow; m_d->averageOpacity = KisPainter::blendAverageOpacity(j->opacity, m_d->averageOpacity); dab.averageOpacity = m_d->averageOpacity; renderedDabs.append(dab); m_d->lastPaintedJob = i; } m_d->cleanPaintedDabs(); + + if (someDabsLeft) { + *someDabsLeft = m_d->hasPreparedDabsImpl(); + } + return renderedDabs; } -bool KisDabRenderingQueue::hasPreparedDabs() const +bool KisDabRenderingQueue::Private::hasPreparedDabsImpl() const { - QMutexLocker l(&m_d->mutex); - - const int nextToBePainted = m_d->lastPaintedJob + 1; + const int nextToBePainted = lastPaintedJob + 1; return nextToBePainted >= 0 && - nextToBePainted < m_d->jobs.size() && - m_d->jobs[nextToBePainted]->status == KisDabRenderingJob::Completed; + nextToBePainted < jobs.size() && + jobs[nextToBePainted]->status == KisDabRenderingJob::Completed; +} + + +bool KisDabRenderingQueue::hasPreparedDabs() const +{ + QMutexLocker l(&m_d->mutex); + return m_d->hasPreparedDabsImpl(); } void KisDabRenderingQueue::setCacheInterface(KisDabRenderingQueue::CacheInterface *interface) { m_d->cacheInterface.reset(interface); } KisFixedPaintDeviceSP KisDabRenderingQueue::fetchCachedPaintDevce() { /** * We create a special type of a fixed paint device that * uses a custom allocator for better efficiency. */ return new KisFixedPaintDevice(m_d->colorSpace, m_d->paintDeviceAllocator); } -int KisDabRenderingQueue::averageExecutionTime() const +qreal KisDabRenderingQueue::averageExecutionTime() const { QMutexLocker l(&m_d->mutex); - return qRound(m_d->avgExecutionTime.rollingMean()); + return m_d->avgExecutionTime.rollingMean() / 1000.0; } int KisDabRenderingQueue::averageDabSize() const { QMutexLocker l(&m_d->mutex); return qRound(m_d->avgDabSize.rollingMean()); } bool KisDabRenderingQueue::Private::dabsHaveSeparateOriginal() { KisDabCacheUtils::DabRenderingResources *resources = fetchResourcesFromCache(); const bool result = cacheInterface->hasSeparateOriginal(resources); putResourcesToCache(resources); return result; } KisDabCacheUtils::DabRenderingResources *KisDabRenderingQueue::Private::fetchResourcesFromCache() { KisDabCacheUtils::DabRenderingResources *resources = 0; // fetch/create a temporary resources object if (!cachedResources.isEmpty()) { resources = cachedResources.takeLast(); } else { resources = resourcesFactory(); } return resources; } void KisDabRenderingQueue::Private::putResourcesToCache(KisDabCacheUtils::DabRenderingResources *resources) { cachedResources << resources; } KisDabCacheUtils::DabRenderingResources *KisDabRenderingQueue::fetchResourcesFromCache() { // TODO: make a separate lock for that QMutexLocker l(&m_d->mutex); return m_d->fetchResourcesFromCache(); } void KisDabRenderingQueue::putResourcesToCache(KisDabCacheUtils::DabRenderingResources *resources) { QMutexLocker l(&m_d->mutex); m_d->putResourcesToCache(resources); } int KisDabRenderingQueue::testingGetQueueSize() const { QMutexLocker l(&m_d->mutex); return m_d->jobs.size(); } diff --git a/plugins/paintops/defaultpaintops/brush/KisDabRenderingQueue.h b/plugins/paintops/defaultpaintops/brush/KisDabRenderingQueue.h index 4aa526a898..ee39e68bb2 100644 --- a/plugins/paintops/defaultpaintops/brush/KisDabRenderingQueue.h +++ b/plugins/paintops/defaultpaintops/brush/KisDabRenderingQueue.h @@ -1,78 +1,78 @@ /* * Copyright (c) 2017 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KISDABRENDERINGQUEUE_H #define KISDABRENDERINGQUEUE_H #include #include "kritadefaultpaintops_export.h" #include class KisDabRenderingJob; class KisRenderedDab; #include "KisDabCacheUtils.h" class KRITADEFAULTPAINTOPS_EXPORT KisDabRenderingQueue { public: struct CacheInterface { virtual ~CacheInterface() {} virtual void getDabType(bool hasDabInCache, KisDabCacheUtils::DabRenderingResources *resources, const KisDabCacheUtils::DabRequestInfo &request, /* out */ KisDabCacheUtils::DabGenerationInfo *di, bool *shouldUseCache) = 0; virtual bool hasSeparateOriginal(KisDabCacheUtils::DabRenderingResources *resources) const = 0; }; public: KisDabRenderingQueue(const KoColorSpace *cs, KisDabCacheUtils::ResourcesFactory resourcesFactory); ~KisDabRenderingQueue(); KisDabRenderingJobSP addDab(const KisDabCacheUtils::DabRequestInfo &request, qreal opacity, qreal flow); QList notifyJobFinished(int seqNo, int usecsTime = -1); - QList takeReadyDabs(bool returnMutableDabs = false); + QList takeReadyDabs(bool returnMutableDabs = false, int oneTimeLimit = -1, bool *someDabsLeft = 0); bool hasPreparedDabs() const; void setCacheInterface(CacheInterface *interface); KisFixedPaintDeviceSP fetchCachedPaintDevce(); void putResourcesToCache(KisDabCacheUtils::DabRenderingResources *resources); KisDabCacheUtils::DabRenderingResources* fetchResourcesFromCache(); - int averageExecutionTime() const; + qreal averageExecutionTime() const; int averageDabSize() const; int testingGetQueueSize() const; private: struct Private; const QScopedPointer m_d; }; #endif // KISDABRENDERINGQUEUE_H diff --git a/plugins/paintops/defaultpaintops/brush/kis_brushop.cpp b/plugins/paintops/defaultpaintops/brush/kis_brushop.cpp index db1337295a..0826288dd7 100644 --- a/plugins/paintops/defaultpaintops/brush/kis_brushop.cpp +++ b/plugins/paintops/defaultpaintops/brush/kis_brushop.cpp @@ -1,346 +1,379 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004-2008 Boudewijn Rempt * Copyright (c) 2004 Clarence Dang * Copyright (c) 2004 Adrian Page * Copyright (c) 2004 Cyrille Berger * Copyright (c) 2010 Lukáš Tvrdý * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_brushop.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "krita_utils.h" #include #include "kis_algebra_2d.h" #include #include #include #include "KisBrushOpResources.h" #include #include #include #include #include "kis_image_config.h" KisBrushOp::KisBrushOp(const KisPaintOpSettingsSP settings, KisPainter *painter, KisNodeSP node, KisImageSP image) : KisBrushBasedPaintOp(settings, painter) , m_opacityOption(node) , m_avgSpacing(50) , m_avgNumDabs(50) + , m_avgUpdateTimePerDab(50) , m_idealNumRects(KisImageConfig().maxNumberOfThreads()) + , m_minUpdatePeriod(20) + , m_maxUpdatePeriod(100) { Q_UNUSED(image); Q_ASSERT(settings); /** * We do our own threading here, so we need to forbid the brushes * to do threading internally */ m_brush->setThreadingAllowed(false); m_airbrushOption.readOptionSetting(settings); m_opacityOption.readOptionSetting(settings); m_flowOption.readOptionSetting(settings); m_sizeOption.readOptionSetting(settings); m_ratioOption.readOptionSetting(settings); m_spacingOption.readOptionSetting(settings); m_rateOption.readOptionSetting(settings); m_softnessOption.readOptionSetting(settings); m_rotationOption.readOptionSetting(settings); m_scatterOption.readOptionSetting(settings); m_sharpnessOption.readOptionSetting(settings); m_opacityOption.resetAllSensors(); m_flowOption.resetAllSensors(); m_sizeOption.resetAllSensors(); m_ratioOption.resetAllSensors(); m_rateOption.resetAllSensors(); m_softnessOption.resetAllSensors(); m_sharpnessOption.resetAllSensors(); m_rotationOption.resetAllSensors(); m_scatterOption.resetAllSensors(); m_sharpnessOption.resetAllSensors(); m_rotationOption.applyFanCornersInfo(this); KisBrushSP baseBrush = m_brush; auto resourcesFactory = [baseBrush, settings, painter] () { KisDabCacheUtils::DabRenderingResources *resources = new KisBrushOpResources(settings, painter); resources->brush = baseBrush->clone(); return resources; }; m_dabExecutor.reset( new KisDabRenderingExecutor( painter->device()->compositionSourceColorSpace(), resourcesFactory, painter->runnableStrokeJobsInterface(), &m_mirrorOption, &m_precisionOption)); } KisBrushOp::~KisBrushOp() { } KisSpacingInformation KisBrushOp::paintAt(const KisPaintInformation& info) { if (!painter()->device()) return KisSpacingInformation(1.0); KisBrushSP brush = m_brush; Q_ASSERT(brush); if (!brush) return KisSpacingInformation(1.0); if (!brush->canPaintFor(info)) return KisSpacingInformation(1.0); qreal scale = m_sizeOption.apply(info); scale *= KisLodTransform::lodToScale(painter()->device()); if (checkSizeTooSmall(scale)) return KisSpacingInformation(); qreal rotation = m_rotationOption.apply(info); qreal ratio = m_ratioOption.apply(info); KisDabShape shape(scale, ratio, rotation); QPointF cursorPos = m_scatterOption.apply(info, brush->maskWidth(shape, 0, 0, info), brush->maskHeight(shape, 0, 0, info)); m_opacityOption.setFlow(m_flowOption.apply(info)); quint8 dabOpacity = OPACITY_OPAQUE_U8; quint8 dabFlow = OPACITY_OPAQUE_U8; m_opacityOption.apply(info, &dabOpacity, &dabFlow); KisDabCacheUtils::DabRequestInfo request(painter()->paintColor(), cursorPos, shape, info, m_softnessOption.apply(info)); m_dabExecutor->addDab(request, qreal(dabOpacity) / 255.0, qreal(dabFlow) / 255.0); KisSpacingInformation spacingInfo = effectiveSpacing(scale, rotation, &m_airbrushOption, &m_spacingOption, info); // gather statistics about dabs m_avgSpacing(spacingInfo.scalarApprox()); return spacingInfo; } struct KisBrushOp::UpdateSharedState { // rendering data KisPainter *painter = 0; QList dabsQueue; // speed metrics QVector dabPoints; QElapsedTimer dabRenderingTimer; // final report QVector allDirtyRects; }; void KisBrushOp::addMirroringJobs(Qt::Orientation direction, QVector &rects, UpdateSharedStateSP state, QVector &jobs) { jobs.append(new KisRunnableStrokeJobData(0, KisStrokeJobData::SEQUENTIAL)); for (KisRenderedDab &dab : state->dabsQueue) { jobs.append( new KisRunnableStrokeJobData( [state, &dab, direction] () { state->painter->mirrorDab(direction, &dab); }, KisStrokeJobData::CONCURRENT)); } jobs.append(new KisRunnableStrokeJobData(0, KisStrokeJobData::SEQUENTIAL)); for (QRect &rc : rects) { state->painter->mirrorRect(direction, &rc); jobs.append( new KisRunnableStrokeJobData( [rc, state] () { state->painter->bltFixed(rc, state->dabsQueue); }, KisStrokeJobData::CONCURRENT)); } state->allDirtyRects.append(rects); } -int KisBrushOp::doAsyncronousUpdate(QVector &jobs) +std::pair KisBrushOp::doAsyncronousUpdate(QVector &jobs) { - if (!m_updateSharedState && m_dabExecutor->hasPreparedDabs()) { + bool someDabsAreStillInQueue = false; + const bool hasPreparedDabsAtStart = m_dabExecutor->hasPreparedDabs(); + + if (!m_updateSharedState && hasPreparedDabsAtStart) { m_updateSharedState = toQShared(new UpdateSharedState()); UpdateSharedStateSP state = m_updateSharedState; state->painter = painter(); - state->dabsQueue = m_dabExecutor->takeReadyDabs(painter()->hasMirroring()); + { + const qreal dabRenderingTime = m_dabExecutor->averageDabRenderingTime(); + const qreal totalRenderingTimePerDab = dabRenderingTime + m_avgUpdateTimePerDab.rollingMeanSafe(); + + // we limit the number of fetched dabs to fit the maximum update period and not + // make visual hiccups + const int dabsLimit = + totalRenderingTimePerDab > 0 ? + qMax(10, int(m_maxUpdatePeriod / totalRenderingTimePerDab * m_idealNumRects)) : + -1; + + state->dabsQueue = m_dabExecutor->takeReadyDabs(painter()->hasMirroring(), dabsLimit, &someDabsAreStillInQueue); + } + KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!state->dabsQueue.isEmpty(), - m_currentUpdatePeriod); + std::make_pair(m_currentUpdatePeriod, false)); const int diameter = m_dabExecutor->averageDabSize(); const qreal spacing = m_avgSpacing.rollingMean(); const int idealNumRects = m_idealNumRects; QVector rects = KisPaintOpUtils::splitDabsIntoRects(state->dabsQueue, idealNumRects, diameter, spacing); state->allDirtyRects = rects; Q_FOREACH (const KisRenderedDab &dab, state->dabsQueue) { state->dabPoints.append(dab.realBounds().center()); } state->dabRenderingTimer.start(); Q_FOREACH (const QRect &rc, rects) { jobs.append( new KisRunnableStrokeJobData( [rc, state] () { state->painter->bltFixed(rc, state->dabsQueue); }, KisStrokeJobData::CONCURRENT)); } /** * After the dab has been rendered once, we should mirror it either one * (h __or__ v) or three (h __and__ v) times. This sequence of 'if's achives * the goal without any extra copying. Please note that it has __no__ 'else' * branches, which is done intentionally! */ if (state->painter->hasHorizontalMirroring()) { addMirroringJobs(Qt::Horizontal, rects, state, jobs); } if (state->painter->hasVerticalMirroring()) { addMirroringJobs(Qt::Vertical, rects, state, jobs); } if (state->painter->hasHorizontalMirroring() && state->painter->hasVerticalMirroring()) { addMirroringJobs(Qt::Horizontal, rects, state, jobs); } jobs.append( new KisRunnableStrokeJobData( - [state, this] () { + [state, this, someDabsAreStillInQueue] () { Q_FOREACH(const QRect &rc, state->allDirtyRects) { state->painter->addDirtyRect(rc); } state->painter->setAverageOpacity(state->dabsQueue.last().averageOpacity); const int updateRenderingTime = state->dabRenderingTimer.elapsed(); - const int dabRenderingTime = m_dabExecutor->averageDabRenderingTime() / 1000; + const qreal dabRenderingTime = m_dabExecutor->averageDabRenderingTime(); + m_avgNumDabs(state->dabsQueue.size()); - // release all the dab devices - state->dabsQueue.clear(); + const qreal currentUpdateTimePerDab = qreal(updateRenderingTime) / state->dabsQueue.size(); + m_avgUpdateTimePerDab(currentUpdateTimePerDab); + + /** + * NOTE: using currentUpdateTimePerDab in the calculation for the next update time instead + * of the average one makes rendering speed about 40% faster. It happens because the + * adaptation period is shorter than if it used + */ + const qreal totalRenderingTimePerDab = dabRenderingTime + currentUpdateTimePerDab; - const int approxDabRenderingTime = qreal(dabRenderingTime) / m_idealNumRects * m_avgNumDabs.rollingMean(); + const int approxDabRenderingTime = + qreal(totalRenderingTimePerDab) * m_avgNumDabs.rollingMean() / m_idealNumRects; - m_currentUpdatePeriod = qBound(20, int(1.5 * (approxDabRenderingTime + updateRenderingTime)), 100); + m_currentUpdatePeriod = + someDabsAreStillInQueue ? m_minUpdatePeriod : + qBound(m_minUpdatePeriod, int(1.5 * approxDabRenderingTime), m_maxUpdatePeriod); { // debug chunk -// const int updateRenderingTime = state->dabRenderingTimer.nsecsElapsed() / 1000; -// const int dabRenderingTime = m_dabExecutor->averageDabRenderingTime(); // ENTER_FUNCTION() << ppVar(state->allDirtyRects.size()) << ppVar(state->dabsQueue.size()) << ppVar(dabRenderingTime) << ppVar(updateRenderingTime); -// ENTER_FUNCTION() << ppVar(m_currentUpdatePeriod); +// ENTER_FUNCTION() << ppVar(m_currentUpdatePeriod) << ppVar(someDabsAreStillInQueue); } + // release all the dab devices + state->dabsQueue.clear(); + m_updateSharedState.clear(); }, KisStrokeJobData::SEQUENTIAL)); + } else if (m_updateSharedState && hasPreparedDabsAtStart) { + someDabsAreStillInQueue = true; } - return m_currentUpdatePeriod; + return std::make_pair(m_currentUpdatePeriod, someDabsAreStillInQueue); } KisSpacingInformation KisBrushOp::updateSpacingImpl(const KisPaintInformation &info) const { const qreal scale = m_sizeOption.apply(info) * KisLodTransform::lodToScale(painter()->device()); qreal rotation = m_rotationOption.apply(info); return effectiveSpacing(scale, rotation, &m_airbrushOption, &m_spacingOption, info); } KisTimingInformation KisBrushOp::updateTimingImpl(const KisPaintInformation &info) const { return KisPaintOpPluginUtils::effectiveTiming(&m_airbrushOption, &m_rateOption, info); } void KisBrushOp::paintLine(const KisPaintInformation& pi1, const KisPaintInformation& pi2, KisDistanceInformation *currentDistance) { if (m_sharpnessOption.isChecked() && m_brush && (m_brush->width() == 1) && (m_brush->height() == 1)) { if (!m_lineCacheDevice) { m_lineCacheDevice = source()->createCompositionSourceDevice(); } else { m_lineCacheDevice->clear(); } KisPainter p(m_lineCacheDevice); p.setPaintColor(painter()->paintColor()); p.drawDDALine(pi1.pos(), pi2.pos()); QRect rc = m_lineCacheDevice->extent(); painter()->bitBlt(rc.x(), rc.y(), m_lineCacheDevice, rc.x(), rc.y(), rc.width(), rc.height()); //fixes Bug 338011 painter()->renderMirrorMask(rc, m_lineCacheDevice); } else { KisPaintOp::paintLine(pi1, pi2, currentDistance); } } diff --git a/plugins/paintops/defaultpaintops/brush/kis_brushop.h b/plugins/paintops/defaultpaintops/brush/kis_brushop.h index 390f84e4cc..0e234b37dd 100644 --- a/plugins/paintops/defaultpaintops/brush/kis_brushop.h +++ b/plugins/paintops/defaultpaintops/brush/kis_brushop.h @@ -1,103 +1,107 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004-2008 Boudewijn Rempt * Copyright (c) 2004 Clarence Dang * Copyright (c) 2004 Adrian Page * Copyright (c) 2004 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_BRUSHOP_H_ #define KIS_BRUSHOP_H_ #include "kis_brush_based_paintop.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include class KisPainter; class KisColorSource; class KisDabRenderingExecutor; class KisRenderedDab; class KisRunnableStrokeJobData; class KisBrushOp : public KisBrushBasedPaintOp { public: KisBrushOp(const KisPaintOpSettingsSP settings, KisPainter * painter, KisNodeSP node, KisImageSP image); ~KisBrushOp() override; void paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) override; - int doAsyncronousUpdate(QVector &jobs) override; + std::pair doAsyncronousUpdate(QVector &jobs) override; protected: KisSpacingInformation paintAt(const KisPaintInformation& info) override; KisSpacingInformation updateSpacingImpl(const KisPaintInformation &info) const override; KisTimingInformation updateTimingImpl(const KisPaintInformation &info) const override; struct UpdateSharedState; typedef QSharedPointer UpdateSharedStateSP; void addMirroringJobs(Qt::Orientation direction, QVector &rects, UpdateSharedStateSP state, QVector &jobs); UpdateSharedStateSP m_updateSharedState; private: KisAirbrushOption m_airbrushOption; KisPressureSizeOption m_sizeOption; KisPressureRatioOption m_ratioOption; KisPressureSpacingOption m_spacingOption; KisPressureRateOption m_rateOption; KisPressureFlowOption m_flowOption; KisFlowOpacityOption m_opacityOption; KisPressureSoftnessOption m_softnessOption; KisPressureSharpnessOption m_sharpnessOption; KisPressureRotationOption m_rotationOption; KisPressureScatterOption m_scatterOption; KisPaintDeviceSP m_lineCacheDevice; QScopedPointer m_dabExecutor; qreal m_currentUpdatePeriod = 20.0; KisRollingMeanAccumulatorWrapper m_avgSpacing; KisRollingMeanAccumulatorWrapper m_avgNumDabs; + KisRollingMeanAccumulatorWrapper m_avgUpdateTimePerDab; const int m_idealNumRects; + + const int m_minUpdatePeriod; + const int m_maxUpdatePeriod; }; #endif // KIS_BRUSHOP_H_