File indexing completed on 2024-05-19 04:26:36

0001 /*
0002  *  SPDX-FileCopyrightText: 2014 Dmitry Kazakov <dimula73@gmail.com>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "kis_suspend_projection_updates_stroke_strategy.h"
0008 
0009 #include <kis_image.h>
0010 #include <krita_utils.h>
0011 #include <kis_projection_updates_filter.h>
0012 #include "kis_image_signal_router.h"
0013 #include "kis_image_animation_interface.h"
0014 
0015 #include "kundo2command.h"
0016 #include "KisRunnableStrokeJobDataBase.h"
0017 #include "KisRunnableStrokeJobsInterface.h"
0018 #include "kis_paintop_utils.h"
0019 
0020 
0021 inline uint qHash(const QRect &rc) {
0022     return rc.x() +
0023         (rc.y() << 16) +
0024         (rc.width() << 8) +
0025         (rc.height() << 24);
0026 }
0027 
0028 struct KisSuspendProjectionUpdatesStrokeStrategy::Private
0029 {
0030     KisImageWSP image;
0031     bool suspend;
0032     QVector<QRect> accumulatedDirtyRects;
0033     bool sanityResumingFinished = false;
0034     int updatesEpoch = 0;
0035     bool haveDisabledGUILodSync = false;
0036     SharedDataSP sharedData;
0037 
0038     void tryFetchUsedUpdatesFilter(KisImageSP image);
0039     void tryIssueRecordedDirtyRequests(KisImageSP image);
0040 
0041     class SuspendLod0Updates : public SuspendUpdatesFilterInterface
0042     {
0043 
0044         struct Request {
0045             Request() : resetAnimationCache(false) {}
0046             Request(const QRect &_rect, bool _resetAnimationCache)
0047                 : rect(_rect), resetAnimationCache(_resetAnimationCache)
0048             {
0049             }
0050 
0051             QRect rect;
0052             bool resetAnimationCache;
0053         };
0054 
0055         struct FullRefreshRequest {
0056             FullRefreshRequest() {}
0057             FullRefreshRequest(const QRect &_rect, const QRect &_cropRect, KisUpdatesFacade::UpdateFlags _flags)
0058                 : rect(_rect), cropRect(_cropRect), flags(_flags)
0059             {
0060             }
0061 
0062             QRect rect;
0063             QRect cropRect;
0064             KisUpdatesFacade::UpdateFlags flags;
0065         };
0066 
0067         struct NoFilthyUpdate {
0068             NoFilthyUpdate() {}
0069             NoFilthyUpdate(const QRect &_rect, const QRect &_cropRect, bool _resetAnimationCache)
0070                 : rect(_rect), cropRect(_cropRect), resetAnimationCache(_resetAnimationCache)
0071             {
0072             }
0073 
0074             QRect rect;
0075             QRect cropRect;
0076             bool resetAnimationCache { false };
0077         };
0078 
0079         typedef QHash<KisNodeSP, QVector<Request> > UpdatesHash;
0080         typedef QHash<KisNodeSP, QVector<FullRefreshRequest> > RefreshesHash;
0081         typedef QHash<KisNodeSP, QVector<NoFilthyUpdate> > NoFilthyUpdatesHash;
0082     public:
0083         SuspendLod0Updates()
0084         {
0085         }
0086 
0087         void addExplicitUIUpdateRect(const QRect &rc) override
0088         {
0089             m_explicitUIUpdateRequest |= rc;
0090         }
0091 
0092         QRect explicitUIUpdateRequest() const {
0093             return m_explicitUIUpdateRequest;
0094         }
0095 
0096         bool filter(KisImage *image, KisNode *node, const QVector<QRect> &rects,  bool resetAnimationCache) override {
0097             if (image->currentLevelOfDetail() > 0) return false;
0098 
0099             QMutexLocker l(&m_mutex);
0100 
0101             Q_FOREACH(const QRect &rc, rects) {
0102                 m_requestsHash[KisNodeSP(node)].append(Request(rc, resetAnimationCache));
0103             }
0104 
0105             return true;
0106         }
0107 
0108         bool filterRefreshGraph(KisImage *image, KisNode *node, const QVector<QRect> &rects, const QRect &cropRect, KisUpdatesFacade::UpdateFlags flags) override {
0109             if (image->currentLevelOfDetail() > 0) return false;
0110 
0111             QMutexLocker l(&m_mutex);
0112 
0113             Q_FOREACH(const QRect &rc, rects) {
0114                 m_refreshesHash[KisNodeSP(node)].append(FullRefreshRequest(rc, cropRect, flags));
0115             }
0116 
0117             return true;
0118         }
0119 
0120         bool filterProjectionUpdateNoFilthy(KisImage *image, KisNode* pseudoFilthy, const QVector<QRect> &rects, const QRect &cropRect, const bool resetAnimationCache) override {
0121             if (image->currentLevelOfDetail() > 0) return false;
0122 
0123             QMutexLocker l(&m_mutex);
0124 
0125             Q_FOREACH(const QRect &rc, rects) {
0126                 m_noFilthyRequestsHash[KisNodeSP(pseudoFilthy)].append(NoFilthyUpdate(rc, cropRect, resetAnimationCache));
0127             }
0128 
0129             return true;
0130         }
0131 
0132         static inline QRect alignRect(const QRect &rc, const int step) {
0133             static const int decstep = step - 1;
0134             static const int invstep = ~decstep;
0135 
0136             int x0, y0, x1, y1;
0137             rc.getCoords(&x0, &y0, &x1, &y1);
0138 
0139             x0 &= invstep;
0140             y0 &= invstep;
0141             x1 |= decstep;
0142             y1 |= decstep;
0143 
0144             QRect result;
0145             result.setCoords(x0, y0, x1, y1);
0146             return result;
0147         }
0148 
0149         void notifyUpdates(KisImageSP image) {
0150             const int step = 64;
0151 
0152             {   // filthy refreshes
0153                 RefreshesHash::const_iterator it = m_refreshesHash.constBegin();
0154                 RefreshesHash::const_iterator end = m_refreshesHash.constEnd();
0155 
0156 
0157                 for (; it != end; ++it) {
0158                     KisNodeSP node = it.key();
0159 
0160                     QHash<QRect, QVector<QRect>> fullRefreshRequests;
0161 
0162                     Q_FOREACH (const FullRefreshRequest &req, it.value()) {
0163                         if (!req.flags.testFlag(KisUpdatesFacade::NoFilthyUpdate)) {
0164                             fullRefreshRequests[req.cropRect] += req.rect;
0165                         }
0166                     }
0167 
0168                     auto reqIt = fullRefreshRequests.begin();
0169                     for (; reqIt != fullRefreshRequests.end(); ++reqIt) {
0170                         const QVector<QRect> simplifiedRects = KisRegion::fromOverlappingRects(reqIt.value(), step).rects();
0171 
0172                         //Block frame cache drop here. We handle this manually later anyway, so we should just block invalidation.
0173                         SuspendFrameInvalidationHandle handle(image->animationInterface());
0174                         image->refreshGraphAsync(node, simplifiedRects, reqIt.key());
0175                     }
0176                 }
0177             }
0178 
0179             {   // non-filthy refreshes
0180                 RefreshesHash::const_iterator it = m_refreshesHash.constBegin();
0181                 RefreshesHash::const_iterator end = m_refreshesHash.constEnd();
0182 
0183 
0184                 for (; it != end; ++it) {
0185                     KisNodeSP node = it.key();
0186 
0187                     QHash<QRect, QVector<QRect>> fullRefreshRequests;
0188 
0189                     Q_FOREACH (const FullRefreshRequest &req, it.value()) {
0190                         if (req.flags.testFlag(KisUpdatesFacade::NoFilthyUpdate)) {
0191                             fullRefreshRequests[req.cropRect] += req.rect;
0192                         }
0193                     }
0194 
0195                     auto reqIt = fullRefreshRequests.begin();
0196                     for (; reqIt != fullRefreshRequests.end(); ++reqIt) {
0197                         const QVector<QRect> simplifiedRects = KisRegion::fromOverlappingRects(reqIt.value(), step).rects();
0198 
0199                         //Block frame cache drop here. We handle this manually later anyway, so we should just block invalidation.
0200                         SuspendFrameInvalidationHandle handle(image->animationInterface());
0201                         image->refreshGraphAsync(node, simplifiedRects, reqIt.key());
0202                     }
0203                 }
0204             }
0205 
0206             {
0207                 UpdatesHash::const_iterator it = m_requestsHash.constBegin();
0208                 UpdatesHash::const_iterator end = m_requestsHash.constEnd();
0209 
0210                 for (; it != end; ++it) {
0211                     KisNodeSP node = it.key();
0212 
0213                     QVector<QRect> dirtyRects;
0214 
0215                     bool resetAnimationCache = false;
0216                     Q_FOREACH (const Request &req, it.value()) {
0217                         dirtyRects += req.rect;
0218                         resetAnimationCache |= req.resetAnimationCache;
0219                     }
0220 
0221                     const QVector<QRect> simplifiedRects = KisRegion::fromOverlappingRects(dirtyRects, step).rects();
0222 
0223                     // FIXME: constness: port requestProjectionUpdate to shared pointers
0224                     image->requestProjectionUpdate(const_cast<KisNode*>(node.data()), simplifiedRects, resetAnimationCache);
0225                 }
0226             }
0227 
0228             {
0229                 NoFilthyUpdatesHash::const_iterator it = m_noFilthyRequestsHash.constBegin();
0230                 NoFilthyUpdatesHash::const_iterator end = m_noFilthyRequestsHash.constEnd();
0231 
0232                 for (; it != end; ++it) {
0233                     KisNodeSP node = it.key();
0234 
0235                     QHash<QRect, std::pair<QVector<QRect>, bool>> noFilthyRequests;
0236 
0237                     Q_FOREACH (const NoFilthyUpdate &req, it.value()) {
0238                         noFilthyRequests[req.cropRect].first += req.rect;
0239                         noFilthyRequests[req.cropRect].second |= req.resetAnimationCache;
0240                     }
0241 
0242 
0243                     auto reqIt = noFilthyRequests.begin();
0244                     for (; reqIt != noFilthyRequests.end(); ++reqIt) {
0245                         const QVector<QRect> simplifiedRects = KisRegion::fromOverlappingRects(reqIt.value().first, step).rects();
0246 
0247                         image->requestProjectionUpdateNoFilthy(node, simplifiedRects, reqIt.key(), reqIt.value().second);
0248                     }
0249                 }
0250             }
0251         }
0252 
0253     private:
0254         UpdatesHash m_requestsHash;
0255         RefreshesHash m_refreshesHash;
0256         NoFilthyUpdatesHash m_noFilthyRequestsHash;
0257         QRect m_explicitUIUpdateRequest;
0258         QMutex m_mutex;
0259     };
0260 
0261     QVector<QSharedPointer<SuspendLod0Updates>> usedFilters;
0262 
0263 
0264     struct StrokeJobCommand : public KUndo2Command
0265     {
0266         StrokeJobCommand(KisStrokeJobData::Sequentiality sequentiality = KisStrokeJobData::SEQUENTIAL,
0267                          KisStrokeJobData::Exclusivity exclusivity = KisStrokeJobData::NORMAL)
0268             : m_sequentiality(sequentiality),
0269               m_exclusivity(exclusivity)
0270         {}
0271 
0272         KisStrokeJobData::Sequentiality m_sequentiality;
0273         KisStrokeJobData::Exclusivity m_exclusivity;
0274     };
0275 
0276     struct UndoableData : public KisRunnableStrokeJobDataBase
0277     {
0278         UndoableData(StrokeJobCommand *command)
0279             : KisRunnableStrokeJobDataBase(command->m_sequentiality, command->m_exclusivity),
0280               m_command(command)
0281         {
0282         }
0283 
0284         void run() override {
0285             KIS_SAFE_ASSERT_RECOVER_RETURN(m_command);
0286             m_command->redo();
0287         }
0288 
0289         QScopedPointer<StrokeJobCommand> m_command;
0290     };
0291 
0292     // Suspend job should be a barrier to ensure all
0293     // previous lodN strokes reach the GUI. Otherwise,
0294     // they will be blocked in
0295     // KisImage::notifyProjectionUpdated()
0296     struct SuspendUpdatesCommand : public StrokeJobCommand
0297     {
0298         SuspendUpdatesCommand(Private *d)
0299             : StrokeJobCommand(KisStrokeJobData::BARRIER),
0300               m_d(d) {}
0301 
0302         void redo() override {
0303             KisImageSP image = m_d->image.toStrongRef();
0304             KIS_SAFE_ASSERT_RECOVER_RETURN(image);
0305             KIS_SAFE_ASSERT_RECOVER_RETURN(!image->currentProjectionUpdatesFilter());
0306             KIS_SAFE_ASSERT_RECOVER_RETURN(!m_d->sharedData->installedFilterCookie);
0307 
0308             m_d->sharedData->installedFilterCookie = image->addProjectionUpdatesFilter(
0309                 toQShared(new Private::SuspendLod0Updates()));
0310         }
0311 
0312 
0313         void undo() override {
0314             KisImageSP image = m_d->image.toStrongRef();
0315             KIS_SAFE_ASSERT_RECOVER_RETURN(image);
0316             KIS_SAFE_ASSERT_RECOVER_RETURN(image->currentProjectionUpdatesFilter());
0317             KIS_SAFE_ASSERT_RECOVER_RETURN(image->currentProjectionUpdatesFilter() == m_d->sharedData->installedFilterCookie);
0318 
0319 
0320             m_d->tryFetchUsedUpdatesFilter(image);
0321         }
0322 
0323         Private *m_d;
0324     };
0325 
0326 
0327     struct ResumeAndIssueGraphUpdatesCommand : public StrokeJobCommand
0328     {
0329         ResumeAndIssueGraphUpdatesCommand(Private *d)
0330             : StrokeJobCommand(KisStrokeJobData::BARRIER),
0331               m_d(d) {}
0332 
0333         void redo() override {
0334             KisImageSP image = m_d->image.toStrongRef();
0335             KIS_SAFE_ASSERT_RECOVER_RETURN(image);
0336             KIS_SAFE_ASSERT_RECOVER_RETURN(image->currentProjectionUpdatesFilter());
0337             KIS_SAFE_ASSERT_RECOVER_RETURN(image->currentProjectionUpdatesFilter() == m_d->sharedData->installedFilterCookie);
0338 
0339             image->disableUIUpdates();
0340             m_d->tryFetchUsedUpdatesFilter(image);
0341             m_d->tryIssueRecordedDirtyRequests(image);
0342         }
0343 
0344         void undo() override {
0345             KisImageSP image = m_d->image.toStrongRef();
0346             KIS_SAFE_ASSERT_RECOVER_RETURN(image);
0347             KIS_SAFE_ASSERT_RECOVER_RETURN(!image->currentProjectionUpdatesFilter());
0348             KIS_SAFE_ASSERT_RECOVER_RETURN(!m_d->sharedData->installedFilterCookie);
0349 
0350             m_d->sharedData->installedFilterCookie = image->addProjectionUpdatesFilter(
0351                 toQShared(new Private::SuspendLod0Updates()));
0352             image->enableUIUpdates();
0353         }
0354 
0355         Private *m_d;
0356     };
0357 
0358     struct UploadDataToUIData : public KisRunnableStrokeJobDataBase
0359     {
0360         UploadDataToUIData(const QRect &rc, int updateEpoch, KisSuspendProjectionUpdatesStrokeStrategy *strategy)
0361             : KisRunnableStrokeJobDataBase(KisStrokeJobData::CONCURRENT),
0362               m_strategy(strategy),
0363               m_rc(rc),
0364               m_updateEpoch(updateEpoch)
0365         {
0366         }
0367 
0368         void run() override {
0369             // check if we've already started stinking...
0370             if (m_strategy->m_d->updatesEpoch > m_updateEpoch) {
0371                 return;
0372             }
0373 
0374             KisImageSP image = m_strategy->m_d->image.toStrongRef();
0375             KIS_SAFE_ASSERT_RECOVER_RETURN(image);
0376 
0377             image->notifyProjectionUpdated(m_rc);
0378         }
0379 
0380         KisSuspendProjectionUpdatesStrokeStrategy *m_strategy;
0381         QRect m_rc;
0382         int m_updateEpoch;
0383     };
0384 
0385     struct BlockUILodSync : public KisRunnableStrokeJobDataBase
0386     {
0387         BlockUILodSync(bool block, KisSuspendProjectionUpdatesStrokeStrategy *strategy)
0388             : KisRunnableStrokeJobDataBase(KisStrokeJobData::BARRIER),
0389               m_strategy(strategy),
0390               m_block(block)
0391         {}
0392 
0393         void run() override {
0394             KisImageSP image = m_strategy->m_d->image.toStrongRef();
0395             KIS_SAFE_ASSERT_RECOVER_RETURN(image);
0396 
0397             image->signalRouter()->emitRequestLodPlanesSyncBlocked(m_block);
0398             m_strategy->m_d->haveDisabledGUILodSync = m_block;
0399         }
0400 
0401         KisSuspendProjectionUpdatesStrokeStrategy *m_strategy;
0402         bool m_block;
0403     };
0404 
0405     struct StartBatchUIUpdatesCommand : public StrokeJobCommand
0406     {
0407         StartBatchUIUpdatesCommand(KisSuspendProjectionUpdatesStrokeStrategy *strategy)
0408             : StrokeJobCommand(KisStrokeJobData::BARRIER),
0409               m_strategy(strategy) {}
0410 
0411         void redo() override {
0412             KisImageSP image = m_strategy->m_d->image.toStrongRef();
0413             KIS_SAFE_ASSERT_RECOVER_RETURN(image);
0414 
0415             /**
0416              * We accumulate dirty rects from all(!) epochs, because some updates of the
0417              * previous epochs might have been cancelled without doing any real work.
0418              */
0419             const QVector<QRect> totalDirtyRects =
0420                 image->enableUIUpdates() + m_strategy->m_d->accumulatedDirtyRects;
0421 
0422             const QRect totalRect =
0423                 image->bounds() &
0424                 std::accumulate(totalDirtyRects.begin(), totalDirtyRects.end(), QRect(), std::bit_or<QRect>());
0425 
0426             m_strategy->m_d->accumulatedDirtyRects =
0427                 KisPaintOpUtils::splitAndFilterDabRect(totalRect,
0428                                                        totalDirtyRects,
0429                                                        KritaUtils::optimalPatchSize().width());
0430 
0431             image->signalRouter()->emitNotifyBatchUpdateStarted();
0432 
0433             QVector<KisRunnableStrokeJobDataBase*> jobsData;
0434             Q_FOREACH (const QRect &rc, m_strategy->m_d->accumulatedDirtyRects) {
0435                 jobsData << new Private::UploadDataToUIData(rc, m_strategy->m_d->updatesEpoch, m_strategy);
0436             }
0437 
0438             m_strategy->runnableJobsInterface()->addRunnableJobs(jobsData);
0439 
0440         }
0441 
0442         void undo() override {
0443             KisImageSP image = m_strategy->m_d->image.toStrongRef();
0444             KIS_SAFE_ASSERT_RECOVER_RETURN(image);
0445 
0446             image->signalRouter()->emitNotifyBatchUpdateEnded();
0447             image->disableUIUpdates();
0448         }
0449 
0450         KisSuspendProjectionUpdatesStrokeStrategy *m_strategy;
0451     };
0452 
0453     struct EndBatchUIUpdatesCommand : public StrokeJobCommand
0454     {
0455         EndBatchUIUpdatesCommand(KisSuspendProjectionUpdatesStrokeStrategy *strategy)
0456             : StrokeJobCommand(KisStrokeJobData::BARRIER),
0457               m_strategy(strategy) {}
0458 
0459         void redo() override {
0460             KisImageSP image = m_strategy->m_d->image.toStrongRef();
0461             KIS_SAFE_ASSERT_RECOVER_RETURN(image);
0462 
0463             image->signalRouter()->emitNotifyBatchUpdateEnded();
0464             m_strategy->m_d->sanityResumingFinished = true;
0465             m_strategy->m_d->accumulatedDirtyRects.clear();
0466             KIS_SAFE_ASSERT_RECOVER_NOOP(m_strategy->m_d->usedFilters.isEmpty());
0467         }
0468 
0469         void undo() override {
0470             /**
0471              * Even though this command is the last command of the stroke is can
0472              * still be undone by suspendStrokeCallback(). It happens when a LodN
0473              * stroke is started right after the last job of resume strategy was
0474              * being executed. In such a case new stroke is placed right in front
0475              * of our resume strategy and all the resuming work is undone (mimicking
0476              * a normal suspend strategy).
0477              *
0478              * The only thing we should control here is whether the state of the
0479              * stroke is reset to default. Otherwise we'll do all the updates twice.
0480              */
0481 
0482             KIS_SAFE_ASSERT_RECOVER_NOOP(m_strategy->m_d->usedFilters.isEmpty());
0483             KIS_SAFE_ASSERT_RECOVER_NOOP(m_strategy->m_d->accumulatedDirtyRects.isEmpty());
0484 
0485             m_strategy->m_d->sanityResumingFinished = false;
0486 
0487             KisImageSP image = m_strategy->m_d->image.toStrongRef();
0488             KIS_SAFE_ASSERT_RECOVER_RETURN(image);
0489 
0490             image->signalRouter()->emitNotifyBatchUpdateStarted();
0491         }
0492 
0493         KisSuspendProjectionUpdatesStrokeStrategy *m_strategy;
0494     };
0495 
0496 
0497     QVector<StrokeJobCommand*> executedCommands;
0498 };
0499 
0500 KisSuspendProjectionUpdatesStrokeStrategy::KisSuspendProjectionUpdatesStrokeStrategy(KisImageWSP image, bool suspend, SharedDataSP sharedData)
0501     : KisRunnableBasedStrokeStrategy(suspend ?
0502                                      QLatin1String("suspend_stroke_strategy") :
0503                                      QLatin1String("resume_stroke_strategy")),
0504       m_d(new Private)
0505 {
0506     m_d->image = image;
0507     m_d->suspend = suspend;
0508     m_d->sharedData = sharedData;
0509 
0510     /**
0511      * Here we add a dumb INIT job so that KisStrokesQueue would know that the
0512      * stroke has already started or not. When the queue reaches the resume
0513      * stroke and starts its execution, no Lod0 can execute anymore. So all the
0514      * new Lod0 strokes should go to the end of the queue and wrapped into
0515      * their own Suspend/Resume pair.
0516      */
0517     enableJob(JOB_INIT, true);
0518 
0519     enableJob(JOB_DOSTROKE, true);
0520     enableJob(JOB_CANCEL, true);
0521 
0522     enableJob(JOB_SUSPEND, true, KisStrokeJobData::BARRIER);
0523     enableJob(JOB_RESUME, true, KisStrokeJobData::BARRIER);
0524 
0525     setNeedsExplicitCancel(true);
0526     setClearsRedoOnStart(false);
0527 }
0528 
0529 KisSuspendProjectionUpdatesStrokeStrategy::~KisSuspendProjectionUpdatesStrokeStrategy()
0530 {
0531     qDeleteAll(m_d->executedCommands);
0532 }
0533 
0534 void KisSuspendProjectionUpdatesStrokeStrategy::initStrokeCallback()
0535 {
0536     QVector<KisRunnableStrokeJobDataBase*> jobs;
0537 
0538     if (m_d->suspend) {
0539         jobs << new Private::UndoableData(new Private::SuspendUpdatesCommand(m_d.data()));
0540     } else {
0541         jobs << new Private::UndoableData(new Private::ResumeAndIssueGraphUpdatesCommand(m_d.data()));
0542         jobs << new Private::BlockUILodSync(true, this);
0543         jobs << new Private::UndoableData(new Private::StartBatchUIUpdatesCommand(this));
0544         jobs << new Private::UndoableData(new Private::EndBatchUIUpdatesCommand(this));
0545         jobs << new Private::BlockUILodSync(false, this);
0546     }
0547 
0548     runnableJobsInterface()->addRunnableJobs(jobs);
0549 }
0550 
0551 /**
0552  * When the Lod0 stroke is being recalculated in the background we
0553  * should block all the updates it issues to avoid user distraction.
0554  * The result of the final stroke should be shown to the user in the
0555  * very end when everything is fully ready. Ideally the use should not
0556  * notice that the image has changed :)
0557  *
0558  * (Don't mix this process with suspend/resume capabilities of a
0559  * single stroke. That is a different system!)
0560  *
0561  * The process of the Lod0 regeneration consists of the following:
0562  *
0563  * 1) Suspend stroke executes. It sets a special updates filter on the
0564  *    image. The filter blocks all the updates and saves them in an
0565  *    internal structure to be emitted in the future.
0566  *
0567  * 2) Lod0 strokes are being recalculated. All their updates are
0568  *    blocked and saved in the filter.
0569  *
0570  * 3) Resume stroke starts:
0571  *
0572  *     3.1) First it disables emitting of sigImageUpdated() so the gui
0573  *          will not get any update notifications.
0574  *
0575  *     3.2) Then it enables updates themselves.
0576  *
0577  *     3.3) Initiates all the updates that were requested by the Lod0
0578  *          stroke. The node graph is regenerated, but the GUI does
0579  *          not get this change.
0580  *
0581  *     3.4) Special barrier job waits for all the updates to finish
0582  *          and, when they are done, enables GUI notifications again.
0583  *
0584  *     3.5) In a multithreaded way emits the GUI notifications for the
0585  *          entire image. Multithreaded way is used to conform the
0586  *          double-stage update principle of KisCanvas2.
0587  */
0588 void KisSuspendProjectionUpdatesStrokeStrategy::doStrokeCallback(KisStrokeJobData *data)
0589 {
0590     KisRunnableStrokeJobDataBase *runnable = dynamic_cast<KisRunnableStrokeJobDataBase*>(data);
0591     if (runnable) {
0592         runnable->run();
0593 
0594         if (Private::UndoableData *undoable = dynamic_cast<Private::UndoableData*>(data)) {
0595             Private::StrokeJobCommand *command =  undoable->m_command.take();
0596             m_d->executedCommands.append(command);
0597         }
0598     }
0599 }
0600 
0601 QList<KisStrokeJobData*> KisSuspendProjectionUpdatesStrokeStrategy::createSuspendJobsData(KisImageWSP /*image*/)
0602 {
0603     return QList<KisStrokeJobData*>();
0604 }
0605 
0606 QList<KisStrokeJobData*> KisSuspendProjectionUpdatesStrokeStrategy::createResumeJobsData(KisImageWSP /*_image*/)
0607 {
0608     return QList<KisStrokeJobData*>();
0609 }
0610 
0611 KisSuspendProjectionUpdatesStrokeStrategy::SharedDataSP KisSuspendProjectionUpdatesStrokeStrategy::createSharedData()
0612 {
0613     return toQShared(new SharedData());
0614 }
0615 
0616 void KisSuspendProjectionUpdatesStrokeStrategy::Private::tryFetchUsedUpdatesFilter(KisImageSP image)
0617 {
0618     if (!this->sharedData->installedFilterCookie) return;
0619 
0620     KisProjectionUpdatesFilterSP filter = image->removeProjectionUpdatesFilter(image->currentProjectionUpdatesFilter());
0621     this->sharedData->installedFilterCookie = KisProjectionUpdatesFilterCookie();
0622 
0623     KIS_SAFE_ASSERT_RECOVER_RETURN(filter);
0624 
0625     QSharedPointer<Private::SuspendLod0Updates> localFilter =
0626         filter.dynamicCast<Private::SuspendLod0Updates>();
0627 
0628     KIS_SAFE_ASSERT_RECOVER_RETURN(localFilter);
0629 
0630     this->usedFilters.append(localFilter);
0631 }
0632 
0633 void KisSuspendProjectionUpdatesStrokeStrategy::Private::tryIssueRecordedDirtyRequests(KisImageSP image)
0634 {
0635     Q_FOREACH (QSharedPointer<Private::SuspendLod0Updates> filter, usedFilters) {
0636         filter->notifyUpdates(image.data());
0637 
0638         if (!filter->explicitUIUpdateRequest().isEmpty()) {
0639             accumulatedDirtyRects.append(filter->explicitUIUpdateRequest());
0640         }
0641     }
0642     usedFilters.clear();
0643 }
0644 
0645 void KisSuspendProjectionUpdatesStrokeStrategy::cancelStrokeCallback()
0646 {
0647     KisImageSP image = m_d->image.toStrongRef();
0648     if (!image) {
0649         return;
0650     }
0651 
0652     for (auto it = m_d->executedCommands.rbegin(); it != m_d->executedCommands.rend(); ++it) {
0653         (*it)->undo();
0654     }
0655 
0656     m_d->tryFetchUsedUpdatesFilter(image);
0657 
0658     if (m_d->haveDisabledGUILodSync) {
0659         image->signalRouter()->emitRequestLodPlanesSyncBlocked(false);
0660     }
0661 
0662     /**
0663      * We shouldn't emit any ad-hoc updates when cancelling the
0664      * stroke.  It generates weird temporary holes on the canvas,
0665      * making the user feel awful, thinking his image got
0666      * corrupted. We will just emit a common refreshGraphAsync() that
0667      * will do all the work in a beautiful way
0668      */
0669     if (!m_d->suspend) {
0670         // FIXME: optimize
0671         image->refreshGraphAsync();
0672     }
0673 }
0674 
0675 void KisSuspendProjectionUpdatesStrokeStrategy::suspendStrokeCallback()
0676 {
0677     /**
0678      * The resume stroke can be suspended even when all its jobs are completed.
0679      * In such a case, we should just ensure that all the internal state is reset
0680      * to default.
0681      */
0682 
0683     KIS_SAFE_ASSERT_RECOVER_NOOP(m_d->suspend ||
0684                                  !m_d->sanityResumingFinished ||
0685                                  (m_d->sanityResumingFinished &&
0686                                   m_d->usedFilters.isEmpty() &&
0687                                   m_d->accumulatedDirtyRects.isEmpty()));
0688 
0689     for (auto it = m_d->executedCommands.rbegin(); it != m_d->executedCommands.rend(); ++it) {
0690         (*it)->undo();
0691     }
0692 
0693     // reset all the issued updates
0694     m_d->updatesEpoch++;
0695 }
0696 
0697 void KisSuspendProjectionUpdatesStrokeStrategy::resumeStrokeCallback()
0698 {
0699     QVector<KisRunnableStrokeJobDataBase*> jobs;
0700 
0701     Q_FOREACH (Private::StrokeJobCommand *command, m_d->executedCommands) {
0702         jobs << new Private::UndoableData(command);
0703     }
0704     m_d->executedCommands.clear();
0705 
0706     runnableJobsInterface()->addRunnableJobs(jobs);
0707 }
0708 
0709 KisSuspendProjectionUpdatesStrokeStrategy::SuspendFrameInvalidationHandle::SuspendFrameInvalidationHandle(KisImageAnimationInterface *interface)
0710     : m_interface(interface)
0711 {
0712     KIS_ASSERT(m_interface);
0713     m_interface->blockFrameInvalidation(true);
0714 }
0715 
0716 KisSuspendProjectionUpdatesStrokeStrategy::SuspendFrameInvalidationHandle::~SuspendFrameInvalidationHandle()
0717 {
0718     m_interface->blockFrameInvalidation(false);
0719 }