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 }