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 }