diff --git a/libs/image/kis_recalculate_transform_mask_job.cpp b/libs/image/kis_recalculate_transform_mask_job.cpp index 37518b592a..7a448ca78f 100644 --- a/libs/image/kis_recalculate_transform_mask_job.cpp +++ b/libs/image/kis_recalculate_transform_mask_job.cpp @@ -1,74 +1,92 @@ /* * Copyright (c) 2014 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 "kis_recalculate_transform_mask_job.h" #include "kis_transform_mask.h" #include "kis_debug.h" #include "kis_layer.h" #include "kis_image.h" #include "kis_abstract_projection_plane.h" - +#include "kis_transform_mask_params_interface.h" KisRecalculateTransformMaskJob::KisRecalculateTransformMaskJob(KisTransformMaskSP mask) : m_mask(mask) { + setExclusive(true); } bool KisRecalculateTransformMaskJob::overrides(const KisSpontaneousJob *_otherJob) { const KisRecalculateTransformMaskJob *otherJob = dynamic_cast(_otherJob); return otherJob && otherJob->m_mask == m_mask; } void KisRecalculateTransformMaskJob::run() { /** * The mask might have been deleted from the layers stack. In * such a case, don't try do update it. */ if (!m_mask->parent()) return; m_mask->recaclulateStaticImage(); KisLayerSP layer = qobject_cast(m_mask->parent().data()); if (!layer) { warnKrita << "WARNING: KisRecalculateTransformMaskJob::run() Mask has no parent layer! Skipping projection update!"; return; } KisImageSP image = layer->image(); Q_ASSERT(image); + /** - * When we call requestProjectionUpdateNoFilthy() on a layer, - * its masks' change rect is not counted, because it is considered - * to be N_ABOVE_FILTHY. Therefore, we should expand the dirty - * rect manually to get the correct update + * Depending on whether the mask is hidden we should either + * update it entirely via the setDirty() call, or we can use a + * lightweight approach by directly regenerating the + * precalculated static image using + * KisRecalculateTransformMaskJob. */ - QRect updateRect = layer->projectionPlane()->changeRect(layer->extent(), KisLayer::N_FILTHY); - image->requestProjectionUpdateNoFilthy(layer, updateRect, image->bounds()); + if (m_mask->transformParams()->isHidden()) { + QRect updateRect = m_mask->extent(); + + if (layer->original()) { + updateRect |= layer->original()->defaultBounds()->bounds(); + } + m_mask->setDirty(updateRect); + } else { + /** + * When we call requestProjectionUpdateNoFilthy() on a layer, + * its masks' change rect is not counted, because it is considered + * to be N_ABOVE_FILTHY. Therefore, we should expand the dirty + * rect manually to get the correct update + */ + QRect updateRect = layer->projectionPlane()->changeRect(layer->extent(), KisLayer::N_FILTHY); + image->requestProjectionUpdateNoFilthy(layer, updateRect, image->bounds()); + } } int KisRecalculateTransformMaskJob::levelOfDetail() const { return 0; } diff --git a/libs/image/kis_spontaneous_job.h b/libs/image/kis_spontaneous_job.cpp similarity index 54% copy from libs/image/kis_spontaneous_job.h copy to libs/image/kis_spontaneous_job.cpp index e204121960..cb612d41a9 100644 --- a/libs/image/kis_spontaneous_job.h +++ b/libs/image/kis_spontaneous_job.cpp @@ -1,37 +1,30 @@ /* - * Copyright (c) 2013 Dmitry Kazakov + * Copyright (c) 2018 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 __KIS_SPONTANEOUS_JOB_H -#define __KIS_SPONTANEOUS_JOB_H +#include "kis_spontaneous_job.h" -#include "kis_runnable.h" -/** - * This class represents a simple update just that should be - * executed by the updates system from time to time, without - * any recording or undo support. Just some useful update that - * can be run concurrently with other types updates. - */ -class KRITAIMAGE_EXPORT KisSpontaneousJob : public KisRunnable +bool KisSpontaneousJob::isExclusive() const +{ + return m_isExclusive; +} + +void KisSpontaneousJob::setExclusive(bool value) { -public: - virtual bool overrides(const KisSpontaneousJob *otherJob) = 0; - virtual int levelOfDetail() const = 0; -}; -#endif /* __KIS_SPONTANEOUS_JOB_H */ +} diff --git a/libs/image/kis_spontaneous_job.h b/libs/image/kis_spontaneous_job.h index e204121960..6a4194062d 100644 --- a/libs/image/kis_spontaneous_job.h +++ b/libs/image/kis_spontaneous_job.h @@ -1,37 +1,48 @@ /* * Copyright (c) 2013 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 __KIS_SPONTANEOUS_JOB_H #define __KIS_SPONTANEOUS_JOB_H #include "kis_runnable.h" /** * This class represents a simple update just that should be * executed by the updates system from time to time, without * any recording or undo support. Just some useful update that * can be run concurrently with other types updates. */ class KRITAIMAGE_EXPORT KisSpontaneousJob : public KisRunnable { public: virtual bool overrides(const KisSpontaneousJob *otherJob) = 0; virtual int levelOfDetail() const = 0; + bool isExclusive() const { + return m_isExclusive; + } + +protected: + void setExclusive(bool value) { + m_isExclusive = value; + } + +private: + bool m_isExclusive = false; }; #endif /* __KIS_SPONTANEOUS_JOB_H */ diff --git a/libs/image/kis_transform_mask.cpp b/libs/image/kis_transform_mask.cpp index fa48e3475f..254016552d 100644 --- a/libs/image/kis_transform_mask.cpp +++ b/libs/image/kis_transform_mask.cpp @@ -1,465 +1,477 @@ /* * Copyright (c) 2007 Boudewijn Rempt * * * 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 #include #include #include "kis_layer.h" #include "kis_transform_mask.h" #include "filter/kis_filter.h" #include "filter/kis_filter_configuration.h" #include "filter/kis_filter_registry.h" #include "kis_selection.h" #include "kis_processing_information.h" #include "kis_node.h" #include "kis_node_visitor.h" #include "kis_processing_visitor.h" #include "kis_node_progress_proxy.h" #include "kis_transaction.h" #include "kis_painter.h" #include "kis_busy_progress_indicator.h" #include "kis_perspectivetransform_worker.h" #include "kis_transform_mask_params_interface.h" #include "kis_transform_mask_params_factory_registry.h" #include "kis_recalculate_transform_mask_job.h" #include "kis_thread_safe_signal_compressor.h" #include "kis_algebra_2d.h" #include "kis_safe_transform.h" #include "kis_keyframe_channel.h" #include "kis_image_config.h" //#include "kis_paint_device_debug_utils.h" //#define DEBUG_RENDERING //#define DUMP_RECT QRect(0,0,512,512) #define UPDATE_DELAY 3000 /*ms */ struct Q_DECL_HIDDEN KisTransformMask::Private { Private() : worker(0, QTransform(), 0), staticCacheValid(false), recalculatingStaticImage(false), updateSignalCompressor(UPDATE_DELAY, KisSignalCompressor::POSTPONE), offBoundsReadArea(0.5) { } Private(const Private &rhs) : worker(rhs.worker), params(rhs.params), staticCacheValid(rhs.staticCacheValid), recalculatingStaticImage(rhs.recalculatingStaticImage), updateSignalCompressor(UPDATE_DELAY, KisSignalCompressor::POSTPONE), offBoundsReadArea(rhs.offBoundsReadArea) { } void reloadParameters() { QTransform affineTransform; if (params->isAffine()) { affineTransform = params->finalAffineTransform(); } worker.setForwardTransform(affineTransform); params->clearChangedFlag(); staticCacheValid = false; } KisPerspectiveTransformWorker worker; KisTransformMaskParamsInterfaceSP params; bool staticCacheValid; bool recalculatingStaticImage; KisPaintDeviceSP staticCacheDevice; KisThreadSafeSignalCompressor updateSignalCompressor; qreal offBoundsReadArea; }; KisTransformMask::KisTransformMask() : KisEffectMask(), m_d(new Private()) { setTransformParams( KisTransformMaskParamsInterfaceSP( new KisDumbTransformMaskParams())); connect(&m_d->updateSignalCompressor, SIGNAL(timeout()), SLOT(slotDelayedStaticUpdate())); + connect(this, SIGNAL(sigInternalForceStaticImageUpdate()), SLOT(slotInternalForceStaticImageUpdate())); KisImageConfig cfg; m_d->offBoundsReadArea = cfg.transformMaskOffBoundsReadArea(); } KisTransformMask::~KisTransformMask() { } KisTransformMask::KisTransformMask(const KisTransformMask& rhs) : KisEffectMask(rhs), m_d(new Private(*rhs.m_d)) { connect(&m_d->updateSignalCompressor, SIGNAL(timeout()), SLOT(slotDelayedStaticUpdate())); } KisPaintDeviceSP KisTransformMask::paintDevice() const { return 0; } QIcon KisTransformMask::icon() const { return KisIconUtils::loadIcon("transformMask"); } void KisTransformMask::setTransformParams(KisTransformMaskParamsInterfaceSP params) { KIS_ASSERT_RECOVER(params) { params = KisTransformMaskParamsInterfaceSP( new KisDumbTransformMaskParams()); } m_d->params = params; m_d->reloadParameters(); m_d->updateSignalCompressor.stop(); } KisTransformMaskParamsInterfaceSP KisTransformMask::transformParams() const { return m_d->params; } void KisTransformMask::slotDelayedStaticUpdate() { /** * The mask might have been deleted from the layers stack in the * meanwhile. Just ignore the updates in the case. */ KisLayerSP parentLayer = qobject_cast(parent().data()); if (!parentLayer) return; KisImageSP image = parentLayer->image(); if (image) { image->addSpontaneousJob(new KisRecalculateTransformMaskJob(this)); } } KisPaintDeviceSP KisTransformMask::buildPreviewDevice() { /** * Note: this function must be called from within the scheduler's * context. We are accessing parent's updateProjection(), which * is not entirely safe. */ KisLayerSP parentLayer = qobject_cast(parent().data()); KIS_ASSERT_RECOVER(parentLayer) { return new KisPaintDevice(colorSpace()); } KisPaintDeviceSP device = new KisPaintDevice(parentLayer->original()->colorSpace()); QRect requestedRect = parentLayer->original()->exactBounds(); parentLayer->buildProjectionUpToNode(device, this, requestedRect); return device; } void KisTransformMask::recaclulateStaticImage() { /** * Note: this function must be called from within the scheduler's * context. We are accessing parent's updateProjection(), which * is not entirely safe. */ KisLayerSP parentLayer = qobject_cast(parent().data()); KIS_ASSERT_RECOVER_RETURN(parentLayer); if (!m_d->staticCacheDevice) { m_d->staticCacheDevice = new KisPaintDevice(parentLayer->original()->colorSpace()); } m_d->recalculatingStaticImage = true; /** * updateProjection() is assuming that the requestedRect takes * into account all the change rects of all the masks. Usually, * this work is done by the walkers. */ QRect requestedRect = parentLayer->changeRect(parentLayer->original()->exactBounds()); /** * Here we use updateProjection() to regenerate the projection of * the layer and after that a special update call (no-filthy) will * be issued to pass the changed further through the stack. */ parentLayer->updateProjection(requestedRect, this); m_d->recalculatingStaticImage = false; m_d->staticCacheValid = true; } QRect KisTransformMask::decorateRect(KisPaintDeviceSP &src, KisPaintDeviceSP &dst, const QRect & rc, PositionToFilthy maskPos) const { Q_ASSERT_X(src != dst, "KisTransformMask::decorateRect", "src must be != dst, because we can't create transactions " "during merge, as it breaks reentrancy"); KIS_ASSERT_RECOVER(m_d->params) { return rc; } if (m_d->params->isHidden()) return rc; KIS_ASSERT_RECOVER_NOOP(maskPos == N_FILTHY || maskPos == N_ABOVE_FILTHY || maskPos == N_BELOW_FILTHY); if (m_d->params->hasChanged()) m_d->reloadParameters(); if (!m_d->recalculatingStaticImage && (maskPos == N_FILTHY || maskPos == N_ABOVE_FILTHY)) { m_d->staticCacheValid = false; m_d->updateSignalCompressor.start(); } if (m_d->recalculatingStaticImage) { m_d->staticCacheDevice->clear(); m_d->params->transformDevice(const_cast(this), src, m_d->staticCacheDevice); QRect updatedRect = m_d->staticCacheDevice->extent(); KisPainter::copyAreaOptimized(updatedRect.topLeft(), m_d->staticCacheDevice, dst, updatedRect); #ifdef DEBUG_RENDERING qDebug() << "Recalculate" << name() << ppVar(src->exactBounds()) << ppVar(dst->exactBounds()) << ppVar(rc); KIS_DUMP_DEVICE_2(src, DUMP_RECT, "recalc_src", "dd"); KIS_DUMP_DEVICE_2(dst, DUMP_RECT, "recalc_dst", "dd"); #endif /* DEBUG_RENDERING */ } else if (!m_d->staticCacheValid && m_d->params->isAffine()) { m_d->worker.runPartialDst(src, dst, rc); #ifdef DEBUG_RENDERING qDebug() << "Partial" << name() << ppVar(src->exactBounds()) << ppVar(src->extent()) << ppVar(dst->exactBounds()) << ppVar(dst->extent()) << ppVar(rc); KIS_DUMP_DEVICE_2(src, DUMP_RECT, "partial_src", "dd"); KIS_DUMP_DEVICE_2(dst, DUMP_RECT, "partial_dst", "dd"); #endif /* DEBUG_RENDERING */ } else if (m_d->staticCacheDevice && m_d->staticCacheValid) { KisPainter::copyAreaOptimized(rc.topLeft(), m_d->staticCacheDevice, dst, rc); #ifdef DEBUG_RENDERING qDebug() << "Fetch" << name() << ppVar(src->exactBounds()) << ppVar(dst->exactBounds()) << ppVar(rc); KIS_DUMP_DEVICE_2(src, DUMP_RECT, "fetch_src", "dd"); KIS_DUMP_DEVICE_2(dst, DUMP_RECT, "fetch_dst", "dd"); #endif /* DEBUG_RENDERING */ } KIS_ASSERT_RECOVER_NOOP(this->busyProgressIndicator()); this->busyProgressIndicator()->update(); return rc; } bool KisTransformMask::accept(KisNodeVisitor &v) { return v.visit(this); } void KisTransformMask::accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter) { return visitor.visit(this, undoAdapter); } QRect KisTransformMask::changeRect(const QRect &rect, PositionToFilthy pos) const { Q_UNUSED(pos); /** * FIXME: This check of the emptiness should be done * on the higher/lower level */ if (rect.isEmpty()) return rect; QRect changeRect = rect; if (m_d->params->isAffine()) { QRect bounds; QRect interestRect; KisNodeSP parentNode = parent(); if (parentNode) { bounds = parentNode->original()->defaultBounds()->bounds(); interestRect = parentNode->original()->extent(); } else { bounds = QRect(0,0,777,777); interestRect = QRect(0,0,888,888); warnKrita << "WARNING: transform mask has no parent (change rect)." << "Cannot run safe transformations." << "Will limit bounds to" << ppVar(bounds); } const QRect limitingRect = KisAlgebra2D::blowRect(bounds, m_d->offBoundsReadArea); if (m_d->params->hasChanged()) m_d->reloadParameters(); KisSafeTransform transform(m_d->worker.forwardTransform(), limitingRect, interestRect); changeRect = transform.mapRectForward(rect); } else { QRect interestRect; interestRect = parent() ? parent()->original()->extent() : QRect(); changeRect = m_d->params->nonAffineChangeRect(rect); } return changeRect; } QRect KisTransformMask::needRect(const QRect& rect, PositionToFilthy pos) const { Q_UNUSED(pos); /** * FIXME: This check of the emptiness should be done * on the higher/lower level */ if (rect.isEmpty()) return rect; if (!m_d->params->isAffine()) return rect; QRect bounds; QRect interestRect; KisNodeSP parentNode = parent(); if (parentNode) { bounds = parentNode->original()->defaultBounds()->bounds(); interestRect = parentNode->original()->extent(); } else { bounds = QRect(0,0,777,777); interestRect = QRect(0,0,888,888); warnKrita << "WARNING: transform mask has no parent (need rect)." << "Cannot run safe transformations." << "Will limit bounds to" << ppVar(bounds); } QRect needRect = rect; if (m_d->params->isAffine()) { const QRect limitingRect = KisAlgebra2D::blowRect(bounds, m_d->offBoundsReadArea); if (m_d->params->hasChanged()) m_d->reloadParameters(); KisSafeTransform transform(m_d->worker.forwardTransform(), limitingRect, interestRect); needRect = transform.mapRectBackward(rect); } else { needRect = m_d->params->nonAffineNeedRect(rect, interestRect); } return needRect; } QRect KisTransformMask::extent() const { QRect rc = KisMask::extent(); QRect partialChangeRect; QRect existentProjection; KisLayerSP parentLayer = qobject_cast(parent().data()); if (parentLayer) { partialChangeRect = parentLayer->partialChangeRect(const_cast(this), rc); existentProjection = parentLayer->projection()->extent(); } return changeRect(partialChangeRect) | existentProjection; } QRect KisTransformMask::exactBounds() const { QRect rc = KisMask::exactBounds(); QRect partialChangeRect; QRect existentProjection; KisLayerSP parentLayer = qobject_cast(parent().data()); if (parentLayer) { partialChangeRect = parentLayer->partialChangeRect(const_cast(this), rc); existentProjection = parentLayer->projection()->exactBounds(); } return changeRect(partialChangeRect) | existentProjection; } void KisTransformMask::setX(qint32 x) { m_d->params->translate(QPointF(x - this->x(), 0)); setTransformParams(m_d->params); KisEffectMask::setX(x); } void KisTransformMask::setY(qint32 y) { m_d->params->translate(QPointF(0, y - this->y())); setTransformParams(m_d->params); KisEffectMask::setY(y); } void KisTransformMask::forceUpdateTimedNode() { if (m_d->updateSignalCompressor.isActive()) { KIS_SAFE_ASSERT_RECOVER_NOOP(!m_d->staticCacheValid); m_d->updateSignalCompressor.stop(); slotDelayedStaticUpdate(); } } +void KisTransformMask::threadSafeForceStaticImageUpdate() +{ + emit sigInternalForceStaticImageUpdate(); +} + +void KisTransformMask::slotInternalForceStaticImageUpdate() +{ + m_d->updateSignalCompressor.stop(); + slotDelayedStaticUpdate(); +} + KisKeyframeChannel *KisTransformMask::requestKeyframeChannel(const QString &id) { if (id == KisKeyframeChannel::TransformArguments.id() || id == KisKeyframeChannel::TransformPositionX.id() || id == KisKeyframeChannel::TransformPositionY.id() || id == KisKeyframeChannel::TransformScaleX.id() || id == KisKeyframeChannel::TransformScaleY.id() || id == KisKeyframeChannel::TransformShearX.id() || id == KisKeyframeChannel::TransformShearY.id() || id == KisKeyframeChannel::TransformRotationX.id() || id == KisKeyframeChannel::TransformRotationY.id() || id == KisKeyframeChannel::TransformRotationZ.id()) { KisAnimatedTransformParamsInterface *animatedParams = dynamic_cast(m_d->params.data()); if (!animatedParams) { auto converted = KisTransformMaskParamsFactoryRegistry::instance()->animateParams(m_d->params); if (converted.isNull()) return KisEffectMask::requestKeyframeChannel(id); m_d->params = converted; animatedParams = dynamic_cast(converted.data()); } KisKeyframeChannel *channel = animatedParams->getKeyframeChannel(id, parent()->original()->defaultBounds()); if (channel) return channel; } return KisEffectMask::requestKeyframeChannel(id); } diff --git a/libs/image/kis_transform_mask.h b/libs/image/kis_transform_mask.h index bdb8c44647..8ffd632a77 100644 --- a/libs/image/kis_transform_mask.h +++ b/libs/image/kis_transform_mask.h @@ -1,90 +1,97 @@ /* * Copyright (c) 2007 Boudewijn Rempt * * 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_TRANSFORM_MASK_ #define _KIS_TRANSFORM_MASK_ #include #include "kis_types.h" #include "kis_effect_mask.h" #include "KisDelayedUpdateNodeInterface.h" /** Transform a layer according to a matrix transform */ class KRITAIMAGE_EXPORT KisTransformMask : public KisEffectMask, public KisDelayedUpdateNodeInterface { Q_OBJECT public: /** * Create an empty filter mask. */ KisTransformMask(); ~KisTransformMask() override; QIcon icon() const override; KisNodeSP clone() const override { return KisNodeSP(new KisTransformMask(*this)); } KisPaintDeviceSP paintDevice() const override; bool accept(KisNodeVisitor &v) override; void accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter) override; KisTransformMask(const KisTransformMask& rhs); QRect decorateRect(KisPaintDeviceSP &src, KisPaintDeviceSP &dst, const QRect & rc, PositionToFilthy maskPos) const override; QRect changeRect(const QRect &rect, PositionToFilthy pos = N_FILTHY) const override; QRect needRect(const QRect &rect, PositionToFilthy pos = N_FILTHY) const override; QRect extent() const override; QRect exactBounds() const override; void setTransformParams(KisTransformMaskParamsInterfaceSP params); KisTransformMaskParamsInterfaceSP transformParams() const; void recaclulateStaticImage(); KisPaintDeviceSP buildPreviewDevice(); void setX(qint32 x) override; void setY(qint32 y) override; void forceUpdateTimedNode() override; + void threadSafeForceStaticImageUpdate(); + protected: KisKeyframeChannel *requestKeyframeChannel(const QString &id) override; +Q_SIGNALS: + void sigInternalForceStaticImageUpdate(); + private Q_SLOTS: void slotDelayedStaticUpdate(); + void slotInternalForceStaticImageUpdate(); + private: struct Private; const QScopedPointer m_d; }; #endif //_KIS_TRANSFORM_MASK_ diff --git a/libs/image/kis_update_job_item.h b/libs/image/kis_update_job_item.h index 663b1223e5..01f7947545 100644 --- a/libs/image/kis_update_job_item.h +++ b/libs/image/kis_update_job_item.h @@ -1,258 +1,258 @@ /* * 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. */ #ifndef __KIS_UPDATE_JOB_ITEM_H #define __KIS_UPDATE_JOB_ITEM_H #include #include #include #include "kis_stroke_job.h" #include "kis_spontaneous_job.h" #include "kis_base_rects_walker.h" #include "kis_async_merger.h" class KisUpdateJobItem : public QObject, public QRunnable { Q_OBJECT public: enum class Type : int { EMPTY = 0, WAITING, MERGE, STROKE, SPONTANEOUS }; public: KisUpdateJobItem(QReadWriteLock *exclusiveJobLock) : m_exclusiveJobLock(exclusiveJobLock), m_atomicType(Type::EMPTY), m_runnableJob(0) { setAutoDelete(false); KIS_SAFE_ASSERT_RECOVER_NOOP(m_atomicType.is_lock_free()); } ~KisUpdateJobItem() override { delete m_runnableJob; } void run() override { if (!isRunning()) return; /** * Here we break the idea of QThreadPool a bit. Ideally, we should split the * jobs into distinct QRunnable objects and pass all of them to QThreadPool. * That is a nice idea, but it doesn't work well when the jobs are small enough * and the number of available cores is high (>4 cores). It this case the * threads just tend to execute the job very quickly and go to sleep, which is * an expensive operation. * * To overcome this problem we try to bulk-process the jobs. In sigJobFinished() * signal (which is DirectConnection), the context may add the job to ourselves(!!!), * so we switch from "done" state into "running" again. */ while (1) { KIS_SAFE_ASSERT_RECOVER_RETURN(isRunning()); if(m_exclusive) { m_exclusiveJobLock->lockForWrite(); } else { m_exclusiveJobLock->lockForRead(); } if(m_atomicType == Type::MERGE) { runMergeJob(); } else { KIS_ASSERT(m_atomicType == Type::STROKE || m_atomicType == Type::SPONTANEOUS); m_runnableJob->run(); } setDone(); emit sigDoSomeUsefulWork(); // may flip the current state from Waiting -> Running again emit sigJobFinished(); m_exclusiveJobLock->unlock(); // try to exit the loop. Please note, that no one can flip the state from // WAITING to EMPTY except ourselves! Type expectedValue = Type::WAITING; if (m_atomicType.compare_exchange_strong(expectedValue, Type::EMPTY)) { break; } } } inline void runMergeJob() { KIS_SAFE_ASSERT_RECOVER_RETURN(m_atomicType == Type::MERGE); KIS_SAFE_ASSERT_RECOVER_RETURN(m_walker); // dbgKrita << "Executing merge job" << m_walker->changeRect() // << "on thread" << QThread::currentThreadId(); m_merger.startMerge(*m_walker); QRect changeRect = m_walker->changeRect(); emit sigContinueUpdate(changeRect); } // return true if the thread should actually be started inline bool setWalker(KisBaseRectsWalkerSP walker) { KIS_ASSERT(m_atomicType <= Type::WAITING); m_accessRect = walker->accessRect(); m_changeRect = walker->changeRect(); m_walker = walker; m_exclusive = false; m_runnableJob = 0; const Type oldState = m_atomicType.exchange(Type::MERGE); return oldState == Type::EMPTY; } // return true if the thread should actually be started inline bool setStrokeJob(KisStrokeJob *strokeJob) { KIS_ASSERT(m_atomicType <= Type::WAITING); m_runnableJob = strokeJob; m_strokeJobSequentiality = strokeJob->sequentiality(); m_exclusive = strokeJob->isExclusive(); m_walker = 0; m_accessRect = m_changeRect = QRect(); const Type oldState = m_atomicType.exchange(Type::STROKE); return oldState == Type::EMPTY; } // return true if the thread should actually be started inline bool setSpontaneousJob(KisSpontaneousJob *spontaneousJob) { KIS_ASSERT(m_atomicType <= Type::WAITING); m_runnableJob = spontaneousJob; - m_exclusive = false; + m_exclusive = spontaneousJob->isExclusive(); m_walker = 0; m_accessRect = m_changeRect = QRect(); const Type oldState = m_atomicType.exchange(Type::SPONTANEOUS); return oldState == Type::EMPTY; } inline void setDone() { m_walker = 0; delete m_runnableJob; m_runnableJob = 0; m_atomicType = Type::WAITING; } inline bool isRunning() const { return m_atomicType >= Type::MERGE; } inline Type type() const { return m_atomicType; } inline const QRect& accessRect() const { return m_accessRect; } inline const QRect& changeRect() const { return m_changeRect; } inline KisStrokeJobData::Sequentiality strokeJobSequentiality() const { return m_strokeJobSequentiality; } Q_SIGNALS: void sigContinueUpdate(const QRect& rc); void sigDoSomeUsefulWork(); void sigJobFinished(); private: /** * Open walker and stroke job for the testing suite. * Please, do not use it in production code. */ friend class KisSimpleUpdateQueueTest; friend class KisStrokesQueueTest; friend class KisUpdateSchedulerTest; friend class KisTestableUpdaterContext; inline KisBaseRectsWalkerSP walker() const { return m_walker; } inline KisStrokeJob* strokeJob() const { KisStrokeJob *job = dynamic_cast(m_runnableJob); Q_ASSERT(job); return job; } inline void testingSetDone() { setDone(); } private: /** * \see KisUpdaterContext::m_exclusiveJobLock */ QReadWriteLock *m_exclusiveJobLock; bool m_exclusive; std::atomic m_atomicType; volatile KisStrokeJobData::Sequentiality m_strokeJobSequentiality; /** * Runnable jobs part * The job is owned by the context and deleted after completion */ KisRunnable *m_runnableJob; /** * Merge jobs part */ KisBaseRectsWalkerSP m_walker; KisAsyncMerger m_merger; /** * These rects cache actual values from the walker * to eliminate concurrent access to a walker structure */ QRect m_accessRect; QRect m_changeRect; }; #endif /* __KIS_UPDATE_JOB_ITEM_H */ diff --git a/plugins/tools/tool_transform2/kis_modify_transform_mask_command.cpp b/plugins/tools/tool_transform2/kis_modify_transform_mask_command.cpp index 63add5f21c..7495ffa9a4 100644 --- a/plugins/tools/tool_transform2/kis_modify_transform_mask_command.cpp +++ b/plugins/tools/tool_transform2/kis_modify_transform_mask_command.cpp @@ -1,96 +1,68 @@ /* * 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_modify_transform_mask_command.h" #include "kundo2command.h" #include "kis_types.h" #include "kis_recalculate_transform_mask_job.h" #include "kis_transform_mask.h" #include "kis_transform_mask_params_interface.h" #include "tool_transform_args.h" #include "kis_scalar_keyframe_channel.h" #include "kis_transform_args_keyframe_channel.h" #include "kis_animated_transform_parameters.h" KisModifyTransformMaskCommand::KisModifyTransformMaskCommand(KisTransformMaskSP mask, KisTransformMaskParamsInterfaceSP params) : m_mask(mask), m_params(params), m_oldParams(m_mask->transformParams()) { m_wasHidden = m_oldParams->isHidden(); auto *animatedParameters = dynamic_cast(m_oldParams.data()); if (animatedParameters) { int time = m_mask->parent()->original()->defaultBounds()->currentTime(); KisAnimatedTransformMaskParameters::addKeyframes(m_mask, time, params, this); } } void KisModifyTransformMaskCommand::redo() { KisTransformMaskParamsInterfaceSP params; auto *animatedParameters = dynamic_cast(m_oldParams.data()); if (animatedParameters) { params = m_oldParams; animatedParameters->setHidden(m_params->isHidden()); KUndo2Command::redo(); } else { params = m_params; } m_mask->setTransformParams(params); - - updateMask(m_params->isHidden()); + m_mask->threadSafeForceStaticImageUpdate(); } void KisModifyTransformMaskCommand::undo() { auto *animatedParameters = dynamic_cast(m_oldParams.data()); if (animatedParameters) { animatedParameters->setHidden(m_wasHidden); KUndo2Command::undo(); } m_mask->setTransformParams(m_oldParams); - - updateMask(m_oldParams->isHidden()); -} - -void KisModifyTransformMaskCommand::updateMask(bool isHidden) { - /** - * Depending on whether the mask is hidden we should either - * update it entirely via the setDirty() call, or we can use a - * lightweight approach by directly regenerating the - * precalculated static image using - * KisRecalculateTransformMaskJob. - */ - - if (!isHidden) { - KisRecalculateTransformMaskJob updateJob(m_mask); - updateJob.run(); - } else { - m_mask->recaclulateStaticImage(); - - QRect updateRect = m_mask->extent(); - - KisNodeSP parent = m_mask->parent(); - if (parent && parent->original()) { - updateRect |= parent->original()->defaultBounds()->bounds(); - } - - m_mask->setDirty(updateRect); - } + m_mask->threadSafeForceStaticImageUpdate(); } diff --git a/plugins/tools/tool_transform2/kis_modify_transform_mask_command.h b/plugins/tools/tool_transform2/kis_modify_transform_mask_command.h index 3b8d1da77f..668c986f7e 100644 --- a/plugins/tools/tool_transform2/kis_modify_transform_mask_command.h +++ b/plugins/tools/tool_transform2/kis_modify_transform_mask_command.h @@ -1,44 +1,41 @@ /* * 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 __MODIFY_TRANSFORM_MASK_COMMAND_H #define __MODIFY_TRANSFORM_MASK_COMMAND_H #include "kundo2command.h" #include "kis_types.h" #include "kritatooltransform_export.h" #include "KoID.h" class KisAnimatedTransformMaskParameters; class KRITATOOLTRANSFORM_EXPORT KisModifyTransformMaskCommand : public KUndo2Command { public: KisModifyTransformMaskCommand(KisTransformMaskSP mask, KisTransformMaskParamsInterfaceSP params); void redo() override; void undo() override; -private: - void updateMask(bool isHidden); - private: KisTransformMaskSP m_mask; KisTransformMaskParamsInterfaceSP m_params; KisTransformMaskParamsInterfaceSP m_oldParams; bool m_wasHidden; }; #endif