File indexing completed on 2024-05-12 15:58:44

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