File indexing completed on 2024-05-19 04:26:36
0001 /* 0002 * SPDX-FileCopyrightText: 2011 Dmitry Kazakov <dimula73@gmail.com> 0003 * 0004 * SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "kis_strokes_queue.h" 0008 0009 #include <QQueue> 0010 #include <QMutex> 0011 #include <QMutexLocker> 0012 #include "kis_stroke.h" 0013 #include "kis_updater_context.h" 0014 #include "kis_stroke_job_strategy.h" 0015 #include "kis_stroke_strategy.h" 0016 #include "kis_undo_stores.h" 0017 #include "kis_post_execution_undo_adapter.h" 0018 #include "KisCppQuirks.h" 0019 0020 typedef QQueue<KisStrokeSP> StrokesQueue; 0021 typedef QQueue<KisStrokeSP>::iterator StrokesQueueIterator; 0022 0023 #include "kis_image_interfaces.h" 0024 class KisStrokesQueue::LodNUndoStrokesFacade : public KisStrokesFacade 0025 { 0026 public: 0027 LodNUndoStrokesFacade(KisStrokesQueue *_q) : q(_q) {} 0028 0029 KisStrokeId startStroke(KisStrokeStrategy *strokeStrategy) override { 0030 return q->startLodNUndoStroke(strokeStrategy); 0031 } 0032 0033 void addJob(KisStrokeId id, KisStrokeJobData *data) override { 0034 KisStrokeSP stroke = id.toStrongRef(); 0035 KIS_SAFE_ASSERT_RECOVER_NOOP(stroke); 0036 KIS_SAFE_ASSERT_RECOVER_NOOP(!stroke->lodBuddy()); 0037 KIS_SAFE_ASSERT_RECOVER_NOOP(stroke->type() == KisStroke::LODN); 0038 0039 q->addJob(id, data); 0040 } 0041 0042 void endStroke(KisStrokeId id) override { 0043 KisStrokeSP stroke = id.toStrongRef(); 0044 KIS_SAFE_ASSERT_RECOVER_NOOP(stroke); 0045 KIS_SAFE_ASSERT_RECOVER_NOOP(!stroke->lodBuddy()); 0046 KIS_SAFE_ASSERT_RECOVER_NOOP(stroke->type() == KisStroke::LODN); 0047 0048 q->endStroke(id); 0049 } 0050 0051 bool cancelStroke(KisStrokeId id) override { 0052 Q_UNUSED(id); 0053 qFatal("Not implemented"); 0054 return false; 0055 } 0056 0057 private: 0058 KisStrokesQueue *q; 0059 }; 0060 0061 0062 struct Q_DECL_HIDDEN KisStrokesQueue::Private { 0063 Private(KisStrokesQueue *_q) 0064 : q(_q), 0065 openedStrokesCounter(0), 0066 needsExclusiveAccess(false), 0067 wrapAroundModeSupported(false), 0068 balancingRatioOverride(-1.0), 0069 currentStrokeLoaded(false), 0070 lodNNeedsSynchronization(true), 0071 desiredLevelOfDetail(0), 0072 nextDesiredLevelOfDetail(0), 0073 lodNStrokesFacade(_q), 0074 lodNPostExecutionUndoAdapter(&lodNUndoStore, &lodNStrokesFacade) {} 0075 0076 KisStrokesQueue *q; 0077 StrokesQueue strokesQueue; 0078 int openedStrokesCounter; 0079 bool needsExclusiveAccess; 0080 bool wrapAroundModeSupported; 0081 qreal balancingRatioOverride; 0082 bool currentStrokeLoaded; 0083 0084 bool lodNNeedsSynchronization; 0085 int desiredLevelOfDetail; 0086 int nextDesiredLevelOfDetail; 0087 QMutex mutex; 0088 KisLodSyncStrokeStrategyFactory lod0ToNStrokeStrategyFactory; 0089 KisSuspendResumeStrategyPairFactory suspendResumeUpdatesStrokeStrategyFactory; 0090 std::function<void()> purgeRedoStateCallback; 0091 std::function<void()> postSyncLod0GUIPlaneRequestForResume; 0092 KisSurrogateUndoStore lodNUndoStore; 0093 LodNUndoStrokesFacade lodNStrokesFacade; 0094 KisPostExecutionUndoAdapter lodNPostExecutionUndoAdapter; 0095 KisLodPreferences lodPreferences; 0096 0097 void cancelForgettableStrokes(); 0098 void startLod0ToNStroke(int levelOfDetail, bool forgettable); 0099 0100 0101 std::pair<StrokesQueueIterator, StrokesQueueIterator> currentLodRange(); 0102 StrokesQueueIterator findNewLod0Pos(); 0103 StrokesQueueIterator findNewLodNPos(KisStrokeSP lodN); 0104 bool shouldWrapInSuspendUpdatesStroke(); 0105 0106 void switchDesiredLevelOfDetail(bool forced); 0107 bool hasUnfinishedStrokes() const; 0108 void tryClearUndoOnStrokeCompletion(KisStrokeSP finishingStroke); 0109 void forceResetLodAndCloseCurrentLodRange(); 0110 void loadStroke(KisStrokeSP stroke); 0111 }; 0112 0113 0114 KisStrokesQueue::KisStrokesQueue() 0115 : m_d(new Private(this)) 0116 { 0117 } 0118 0119 KisStrokesQueue::~KisStrokesQueue() 0120 { 0121 Q_FOREACH (KisStrokeSP stroke, m_d->strokesQueue) { 0122 stroke->cancelStroke(); 0123 } 0124 0125 delete m_d; 0126 } 0127 0128 template <class StrokePair, class StrokesQueue> 0129 typename StrokesQueue::iterator 0130 executeStrokePair(const StrokePair &pair, StrokesQueue &queue, typename StrokesQueue::iterator it, KisStroke::Type type, int levelOfDetail, KisStrokesQueueMutatedJobInterface *mutatedJobsInterface) { 0131 KisStrokeStrategy *strategy = pair.first; 0132 QList<KisStrokeJobData*> jobsData = pair.second; 0133 0134 KisStrokeSP stroke(new KisStroke(strategy, type, levelOfDetail)); 0135 strategy->setMutatedJobsInterface(mutatedJobsInterface, stroke); 0136 it = queue.insert(it, stroke); 0137 Q_FOREACH (KisStrokeJobData *jobData, jobsData) { 0138 stroke->addJob(jobData); 0139 } 0140 stroke->endStroke(); 0141 0142 return it; 0143 } 0144 0145 void KisStrokesQueue::Private::startLod0ToNStroke(int levelOfDetail, bool forgettable) 0146 { 0147 // precondition: lock held! 0148 // precondition: lod > 0 0149 KIS_ASSERT_RECOVER_RETURN(levelOfDetail); 0150 0151 { 0152 // sanity check: there should be no open LoD range now! 0153 StrokesQueueIterator it; 0154 StrokesQueueIterator end; 0155 std::tie(it, end) = currentLodRange(); 0156 KIS_SAFE_ASSERT_RECOVER_NOOP(it == end); 0157 } 0158 0159 if (!this->lod0ToNStrokeStrategyFactory) return; 0160 0161 KisLodSyncPair syncPair = this->lod0ToNStrokeStrategyFactory(forgettable); 0162 executeStrokePair(syncPair, this->strokesQueue, this->strokesQueue.end(), KisStroke::LODN, levelOfDetail, q); 0163 0164 this->lodNNeedsSynchronization = false; 0165 } 0166 0167 void KisStrokesQueue::Private::cancelForgettableStrokes() 0168 { 0169 if (!strokesQueue.isEmpty() && !hasUnfinishedStrokes()) { 0170 Q_FOREACH (KisStrokeSP stroke, strokesQueue) { 0171 KIS_ASSERT_RECOVER_NOOP(stroke->isEnded()); 0172 0173 if (stroke->canForgetAboutMe()) { 0174 stroke->cancelStroke(); 0175 } 0176 } 0177 } 0178 } 0179 0180 std::pair<StrokesQueueIterator, StrokesQueueIterator> KisStrokesQueue::Private::currentLodRange() 0181 { 0182 /** 0183 * LoD-capable strokes should live in the range after the last 0184 * legacy stroke of the queue 0185 */ 0186 0187 for (auto it = std::make_reverse_iterator(strokesQueue.end()); 0188 it != std::make_reverse_iterator(strokesQueue.begin()); 0189 ++it) { 0190 0191 if ((*it)->type() == KisStroke::LEGACY) { 0192 // it.base() returns the next element after the one we found 0193 return std::make_pair(it.base(), strokesQueue.end()); 0194 } 0195 } 0196 0197 return std::make_pair(strokesQueue.begin(), strokesQueue.end()); 0198 } 0199 0200 bool KisStrokesQueue::Private::shouldWrapInSuspendUpdatesStroke() 0201 { 0202 StrokesQueueIterator it; 0203 StrokesQueueIterator end; 0204 std::tie(it, end) = currentLodRange(); 0205 0206 for (; it != end; ++it) { 0207 KisStrokeSP stroke = *it; 0208 0209 if (stroke->isCancelled()) continue; 0210 0211 if (stroke->type() == KisStroke::RESUME) { 0212 return false; 0213 } 0214 } 0215 0216 return true; 0217 } 0218 0219 StrokesQueueIterator KisStrokesQueue::Private::findNewLod0Pos() 0220 { 0221 StrokesQueueIterator it; 0222 StrokesQueueIterator end; 0223 std::tie(it, end) = currentLodRange(); 0224 0225 for (; it != end; ++it) { 0226 if ((*it)->isCancelled()) continue; 0227 0228 if ((*it)->type() == KisStroke::RESUME) { 0229 return it; 0230 } 0231 } 0232 0233 return it; 0234 } 0235 0236 StrokesQueueIterator KisStrokesQueue::Private::findNewLodNPos(KisStrokeSP lodN) 0237 { 0238 StrokesQueueIterator it; 0239 StrokesQueueIterator end; 0240 std::tie(it, end) = currentLodRange(); 0241 0242 for (; it != end; ++it) { 0243 if ((*it)->isCancelled()) continue; 0244 0245 if ((*it)->type() == KisStroke::LOD0 || 0246 (*it)->type() == KisStroke::SUSPEND || 0247 (*it)->type() == KisStroke::RESUME) { 0248 0249 if (it != end && it == strokesQueue.begin()) { 0250 KisStrokeSP head = *it; 0251 0252 if (head->supportsSuspension()) { 0253 head->suspendStroke(lodN); 0254 } 0255 } 0256 0257 return it; 0258 } 0259 } 0260 0261 return it; 0262 } 0263 0264 KisStrokeId KisStrokesQueue::startLodNUndoStroke(KisStrokeStrategy *strokeStrategy) 0265 { 0266 QMutexLocker locker(&m_d->mutex); 0267 0268 KIS_SAFE_ASSERT_RECOVER_NOOP(!m_d->lodNNeedsSynchronization); 0269 KIS_SAFE_ASSERT_RECOVER_NOOP(m_d->desiredLevelOfDetail > 0); 0270 0271 KisStrokeSP buddy(new KisStroke(strokeStrategy, KisStroke::LODN, m_d->desiredLevelOfDetail)); 0272 strokeStrategy->setMutatedJobsInterface(this, buddy); 0273 m_d->strokesQueue.insert(m_d->findNewLodNPos(buddy), buddy); 0274 0275 KisStrokeId id(buddy); 0276 m_d->openedStrokesCounter++; 0277 0278 return id; 0279 } 0280 0281 KisStrokeId KisStrokesQueue::startStroke(KisStrokeStrategy *strokeStrategy) 0282 { 0283 QMutexLocker locker(&m_d->mutex); 0284 0285 KisStrokeSP stroke; 0286 KisStrokeStrategy* lodBuddyStrategy; 0287 0288 // we should let forgettable strokes to queue up 0289 if (!strokeStrategy->canForgetAboutMe()) { 0290 m_d->cancelForgettableStrokes(); 0291 } 0292 0293 if (m_d->desiredLevelOfDetail && 0294 (m_d->lodPreferences.lodPreferred() || strokeStrategy->forceLodModeIfPossible()) && 0295 (lodBuddyStrategy = 0296 strokeStrategy->createLodClone(m_d->desiredLevelOfDetail))) { 0297 0298 if (m_d->lodNNeedsSynchronization) { 0299 m_d->startLod0ToNStroke(m_d->desiredLevelOfDetail, false); 0300 } 0301 0302 stroke = KisStrokeSP(new KisStroke(strokeStrategy, KisStroke::LOD0, 0)); 0303 0304 KisStrokeSP buddy(new KisStroke(lodBuddyStrategy, KisStroke::LODN, m_d->desiredLevelOfDetail)); 0305 lodBuddyStrategy->setMutatedJobsInterface(this, buddy); 0306 stroke->setLodBuddy(buddy); 0307 m_d->strokesQueue.insert(m_d->findNewLodNPos(buddy), buddy); 0308 0309 if (m_d->shouldWrapInSuspendUpdatesStroke()) { 0310 0311 KisSuspendResumePair suspendPair; 0312 KisSuspendResumePair resumePair; 0313 std::tie(suspendPair, resumePair) = m_d->suspendResumeUpdatesStrokeStrategyFactory(); 0314 0315 StrokesQueueIterator it = m_d->findNewLod0Pos(); 0316 0317 it = executeStrokePair(resumePair, m_d->strokesQueue, it, KisStroke::RESUME, 0, this); 0318 it = m_d->strokesQueue.insert(it, stroke); 0319 it = executeStrokePair(suspendPair, m_d->strokesQueue, it, KisStroke::SUSPEND, 0, this); 0320 0321 } else { 0322 m_d->strokesQueue.insert(m_d->findNewLod0Pos(), stroke); 0323 } 0324 0325 } else { 0326 stroke = KisStrokeSP(new KisStroke(strokeStrategy, KisStroke::LEGACY, 0)); 0327 m_d->strokesQueue.enqueue(stroke); 0328 } 0329 0330 KisStrokeId id(stroke); 0331 strokeStrategy->setMutatedJobsInterface(this, id); 0332 0333 m_d->openedStrokesCounter++; 0334 0335 if (stroke->type() == KisStroke::LEGACY) { 0336 m_d->lodNNeedsSynchronization = true; 0337 } 0338 0339 return id; 0340 } 0341 0342 void KisStrokesQueue::addJob(KisStrokeId id, KisStrokeJobData *data) 0343 { 0344 QMutexLocker locker(&m_d->mutex); 0345 0346 KisStrokeSP stroke = id.toStrongRef(); 0347 KIS_SAFE_ASSERT_RECOVER_RETURN(stroke); 0348 0349 KisStrokeSP buddy = stroke->lodBuddy(); 0350 if (buddy) { 0351 KisStrokeJobData *clonedData = 0352 data->createLodClone(buddy->worksOnLevelOfDetail()); 0353 KIS_ASSERT_RECOVER_RETURN(clonedData); 0354 0355 buddy->addJob(clonedData); 0356 } 0357 0358 stroke->addJob(data); 0359 } 0360 0361 void KisStrokesQueue::addMutatedJobs(KisStrokeId id, const QVector<KisStrokeJobData *> list) 0362 { 0363 QMutexLocker locker(&m_d->mutex); 0364 0365 KisStrokeSP stroke = id.toStrongRef(); 0366 KIS_SAFE_ASSERT_RECOVER_RETURN(stroke); 0367 0368 stroke->addMutatedJobs(list); 0369 } 0370 0371 void KisStrokesQueue::endStroke(KisStrokeId id) 0372 { 0373 QMutexLocker locker(&m_d->mutex); 0374 0375 KisStrokeSP stroke = id.toStrongRef(); 0376 KIS_SAFE_ASSERT_RECOVER_RETURN(stroke); 0377 stroke->endStroke(); 0378 m_d->openedStrokesCounter--; 0379 0380 KisStrokeSP buddy = stroke->lodBuddy(); 0381 if (buddy) { 0382 buddy->endStroke(); 0383 } 0384 } 0385 0386 bool KisStrokesQueue::cancelStroke(KisStrokeId id) 0387 { 0388 QMutexLocker locker(&m_d->mutex); 0389 0390 KisStrokeSP stroke = id.toStrongRef(); 0391 if(stroke) { 0392 stroke->cancelStroke(); 0393 m_d->openedStrokesCounter--; 0394 0395 KisStrokeSP buddy = stroke->lodBuddy(); 0396 if (buddy) { 0397 buddy->cancelStroke(); 0398 } 0399 } 0400 return stroke; 0401 } 0402 0403 bool KisStrokesQueue::Private::hasUnfinishedStrokes() const 0404 { 0405 Q_FOREACH (KisStrokeSP stroke, strokesQueue) { 0406 if (!stroke->isEnded()) { 0407 return true; 0408 } 0409 } 0410 0411 return false; 0412 } 0413 0414 bool KisStrokesQueue::tryCancelCurrentStrokeAsync() 0415 { 0416 bool anythingCanceled = false; 0417 0418 QMutexLocker locker(&m_d->mutex); 0419 0420 /** 0421 * We cancel only ended strokes. This is done to avoid 0422 * handling dangling pointers problem (KisStrokeId). The owner 0423 * of a stroke will cancel the stroke itself if needed. 0424 */ 0425 if (!m_d->strokesQueue.isEmpty() && 0426 !m_d->hasUnfinishedStrokes()) { 0427 0428 /** 0429 * 1) We can cancel only cancellable strokes 0430 * 2) We can only cancel a continuos set of strokes 0431 * 0432 * Basically, a non-cancellable stroke adds a barrier 0433 * wall into the strokes queue, preventing cancellation 0434 * of any strokes that we added into the queue earlier. 0435 */ 0436 const auto lastNonCancellableStrokeIt = 0437 std::find_if_not(std::make_reverse_iterator(m_d->strokesQueue.end()), 0438 std::make_reverse_iterator(m_d->strokesQueue.begin()), 0439 std::mem_fn(&KisStroke::isAsynchronouslyCancellable)); 0440 0441 bool needsLodNSynchronization = false; 0442 0443 for (auto it = lastNonCancellableStrokeIt.base(); it != m_d->strokesQueue.end(); it++) { 0444 KisStrokeSP currentStroke = *it; 0445 KIS_ASSERT_RECOVER_NOOP(currentStroke->isEnded()); 0446 KIS_ASSERT_RECOVER_NOOP(currentStroke->isAsynchronouslyCancellable()); 0447 0448 currentStroke->cancelStroke(); 0449 anythingCanceled = true; 0450 0451 // we shouldn't cancel buddies... 0452 if (currentStroke->type() == KisStroke::LOD0) { 0453 /** 0454 * If the buddy has already finished, we cannot undo it because 0455 * it doesn't store any undo data. Therefore we just regenerate 0456 * the LOD caches. 0457 */ 0458 needsLodNSynchronization = true; 0459 } 0460 0461 } 0462 0463 if (needsLodNSynchronization) { 0464 m_d->forceResetLodAndCloseCurrentLodRange(); 0465 } 0466 } 0467 0468 /** 0469 * NOTE: We do not touch the openedStrokesCounter here since 0470 * we work with closed id's only here 0471 */ 0472 0473 return anythingCanceled; 0474 } 0475 0476 UndoResult KisStrokesQueue::tryUndoLastStrokeAsync() 0477 { 0478 UndoResult result = UNDO_FAIL; 0479 0480 QMutexLocker locker(&m_d->mutex); 0481 0482 KisStrokeSP lastStroke; 0483 KisStrokeSP lastBuddy; 0484 bool buddyFound = false; 0485 0486 for (auto it = std::make_reverse_iterator(m_d->strokesQueue.constEnd()); 0487 it != std::make_reverse_iterator(m_d->strokesQueue.constBegin()); 0488 ++it) { 0489 0490 if ((*it)->type() == KisStroke::LEGACY) { 0491 break; 0492 } 0493 0494 if (!lastStroke && (*it)->type() == KisStroke::LOD0 && !(*it)->isCancelled()) { 0495 lastStroke = *it; 0496 lastBuddy = lastStroke->lodBuddy(); 0497 0498 KIS_SAFE_ASSERT_RECOVER(lastBuddy) { 0499 lastStroke.clear(); 0500 lastBuddy.clear(); 0501 break; 0502 } 0503 } 0504 0505 KIS_SAFE_ASSERT_RECOVER(!lastStroke || *it == lastBuddy || (*it)->type() != KisStroke::LODN) { 0506 lastStroke.clear(); 0507 lastBuddy.clear(); 0508 break; 0509 } 0510 0511 if (lastStroke && *it == lastBuddy) { 0512 KIS_SAFE_ASSERT_RECOVER(lastBuddy->type() == KisStroke::LODN) { 0513 lastStroke.clear(); 0514 lastBuddy.clear(); 0515 break; 0516 } 0517 buddyFound = true; 0518 break; 0519 } 0520 } 0521 0522 if (!lastStroke) return UNDO_FAIL; 0523 if (!lastStroke->isEnded()) return UNDO_FAIL; 0524 if (lastStroke->isCancelled()) return UNDO_FAIL; 0525 0526 KIS_SAFE_ASSERT_RECOVER_NOOP(!buddyFound || 0527 lastStroke->isCancelled() == lastBuddy->isCancelled()); 0528 KIS_SAFE_ASSERT_RECOVER_NOOP(lastBuddy->isEnded()); 0529 0530 if (!lastStroke->canCancel()) { 0531 return UNDO_WAIT; 0532 } 0533 lastStroke->cancelStroke(); 0534 0535 if (buddyFound && lastBuddy->canCancel()) { 0536 lastBuddy->cancelStroke(); 0537 } else { 0538 // TODO: assert that checks that there is no other lodn strokes 0539 locker.unlock(); 0540 m_d->lodNUndoStore.undo(); 0541 m_d->lodNUndoStore.purgeRedoState(); 0542 locker.relock(); 0543 } 0544 0545 result = UNDO_OK; 0546 0547 return result; 0548 } 0549 0550 void KisStrokesQueue::Private::tryClearUndoOnStrokeCompletion(KisStrokeSP finishingStroke) 0551 { 0552 if (finishingStroke->type() != KisStroke::RESUME) return; 0553 0554 bool hasResumeStrokes = false; 0555 bool hasLod0Strokes = false; 0556 0557 auto it = std::find(strokesQueue.begin(), strokesQueue.end(), finishingStroke); 0558 KIS_SAFE_ASSERT_RECOVER_RETURN(it != strokesQueue.end()); 0559 ++it; 0560 0561 for (; it != strokesQueue.end(); ++it) { 0562 KisStrokeSP stroke = *it; 0563 if (stroke->type() == KisStroke::LEGACY) break; 0564 0565 hasLod0Strokes |= stroke->type() == KisStroke::LOD0; 0566 hasResumeStrokes |= stroke->type() == KisStroke::RESUME; 0567 } 0568 0569 KIS_SAFE_ASSERT_RECOVER_NOOP(!hasLod0Strokes || hasResumeStrokes); 0570 0571 if (!hasResumeStrokes && !hasLod0Strokes) { 0572 lodNUndoStore.clear(); 0573 } 0574 } 0575 0576 void KisStrokesQueue::Private::forceResetLodAndCloseCurrentLodRange() 0577 { 0578 lodNNeedsSynchronization = true; 0579 0580 if (!strokesQueue.isEmpty() && strokesQueue.last()->type() != KisStroke::LEGACY) { 0581 0582 std::pair<KisStrokeStrategy*, QList<KisStrokeJobData*>> fakeStrokePair 0583 (new KisStrokeStrategy(QLatin1String("fake_sync")), {}); 0584 0585 executeStrokePair(fakeStrokePair, this->strokesQueue, this->strokesQueue.end(), KisStroke::LEGACY, 0, q); 0586 } 0587 } 0588 0589 void KisStrokesQueue::processQueue(KisUpdaterContext &updaterContext, 0590 bool externalJobsPending) 0591 { 0592 updaterContext.lock(); 0593 m_d->mutex.lock(); 0594 0595 while(updaterContext.hasSpareThread() && 0596 processOneJob(updaterContext, 0597 externalJobsPending)); 0598 0599 m_d->mutex.unlock(); 0600 updaterContext.unlock(); 0601 } 0602 0603 bool KisStrokesQueue::needsExclusiveAccess() const 0604 { 0605 return m_d->needsExclusiveAccess; 0606 } 0607 0608 bool KisStrokesQueue::wrapAroundModeSupported() const 0609 { 0610 return m_d->wrapAroundModeSupported; 0611 } 0612 0613 qreal KisStrokesQueue::balancingRatioOverride() const 0614 { 0615 return m_d->balancingRatioOverride; 0616 } 0617 0618 KisLodPreferences KisStrokesQueue::lodPreferences() const 0619 { 0620 QMutexLocker locker(&m_d->mutex); 0621 0622 /** 0623 * The desired level of detail might have not been activated due to 0624 * multi-stage activation process 0625 */ 0626 return KisLodPreferences(m_d->lodPreferences.flags(), m_d->desiredLevelOfDetail); 0627 } 0628 0629 void KisStrokesQueue::setLodPreferences(const KisLodPreferences &value) 0630 { 0631 QMutexLocker locker(&m_d->mutex); 0632 0633 m_d->lodPreferences = value; 0634 0635 if (m_d->lodPreferences.desiredLevelOfDetail() != m_d->nextDesiredLevelOfDetail || 0636 (m_d->lodPreferences.lodPreferred() && m_d->lodNNeedsSynchronization)) { 0637 0638 m_d->nextDesiredLevelOfDetail = m_d->lodPreferences.desiredLevelOfDetail(); 0639 m_d->switchDesiredLevelOfDetail(false); 0640 } 0641 } 0642 0643 bool KisStrokesQueue::isEmpty() const 0644 { 0645 QMutexLocker locker(&m_d->mutex); 0646 return m_d->strokesQueue.isEmpty(); 0647 } 0648 0649 qint32 KisStrokesQueue::sizeMetric() const 0650 { 0651 QMutexLocker locker(&m_d->mutex); 0652 if(m_d->strokesQueue.isEmpty()) return 0; 0653 0654 // just a rough approximation 0655 return qMax(1, m_d->strokesQueue.head()->numJobs()) * m_d->strokesQueue.size(); 0656 } 0657 0658 void KisStrokesQueue::Private::switchDesiredLevelOfDetail(bool forced) 0659 { 0660 if (forced || nextDesiredLevelOfDetail != desiredLevelOfDetail) { 0661 Q_FOREACH (KisStrokeSP stroke, strokesQueue) { 0662 if (stroke->type() != KisStroke::LEGACY) 0663 return; 0664 } 0665 0666 const bool forgettable = 0667 forced && !lodNNeedsSynchronization && 0668 desiredLevelOfDetail == nextDesiredLevelOfDetail; 0669 0670 desiredLevelOfDetail = nextDesiredLevelOfDetail; 0671 lodNNeedsSynchronization |= !forgettable; 0672 0673 if (desiredLevelOfDetail && lodPreferences.lodPreferred()) { 0674 startLod0ToNStroke(desiredLevelOfDetail, forgettable); 0675 } 0676 } 0677 } 0678 0679 void KisStrokesQueue::explicitRegenerateLevelOfDetail() 0680 { 0681 QMutexLocker locker(&m_d->mutex); 0682 m_d->switchDesiredLevelOfDetail(true); 0683 } 0684 0685 void KisStrokesQueue::notifyUFOChangedImage() 0686 { 0687 QMutexLocker locker(&m_d->mutex); 0688 0689 m_d->forceResetLodAndCloseCurrentLodRange(); 0690 } 0691 0692 void KisStrokesQueue::debugDumpAllStrokes() 0693 { 0694 QMutexLocker locker(&m_d->mutex); 0695 0696 qDebug() <<"==="; 0697 Q_FOREACH (KisStrokeSP stroke, m_d->strokesQueue) { 0698 qDebug() << ppVar(stroke->name()) << ppVar(stroke->type()) << ppVar(stroke->numJobs()) << ppVar(stroke->isInitialized()) << ppVar(stroke->isCancelled()); 0699 } 0700 qDebug() <<"==="; 0701 } 0702 0703 void KisStrokesQueue::setLod0ToNStrokeStrategyFactory(const KisLodSyncStrokeStrategyFactory &factory) 0704 { 0705 m_d->lod0ToNStrokeStrategyFactory = factory; 0706 } 0707 0708 void KisStrokesQueue::setSuspendResumeUpdatesStrokeStrategyFactory(const KisSuspendResumeStrategyPairFactory &factory) 0709 { 0710 m_d->suspendResumeUpdatesStrokeStrategyFactory = factory; 0711 } 0712 0713 void KisStrokesQueue::setPurgeRedoStateCallback(const std::function<void ()> &callback) 0714 { 0715 m_d->purgeRedoStateCallback = callback; 0716 } 0717 0718 void KisStrokesQueue::setPostSyncLod0GUIPlaneRequestForResumeCallback(const std::function<void ()> &callback) 0719 { 0720 m_d->postSyncLod0GUIPlaneRequestForResume = callback; 0721 } 0722 0723 KisPostExecutionUndoAdapter *KisStrokesQueue::lodNPostExecutionUndoAdapter() const 0724 { 0725 return &m_d->lodNPostExecutionUndoAdapter; 0726 } 0727 0728 KUndo2MagicString KisStrokesQueue::currentStrokeName() const 0729 { 0730 QMutexLocker locker(&m_d->mutex); 0731 if(m_d->strokesQueue.isEmpty()) return KUndo2MagicString(); 0732 0733 return m_d->strokesQueue.head()->name(); 0734 } 0735 0736 bool KisStrokesQueue::hasOpenedStrokes() const 0737 { 0738 QMutexLocker locker(&m_d->mutex); 0739 return m_d->openedStrokesCounter; 0740 } 0741 0742 bool KisStrokesQueue::processOneJob(KisUpdaterContext &updaterContext, 0743 bool externalJobsPending) 0744 { 0745 if(m_d->strokesQueue.isEmpty()) return false; 0746 bool result = false; 0747 0748 const int levelOfDetail = updaterContext.currentLevelOfDetail(); 0749 0750 const KisUpdaterContextSnapshotEx snapshot = updaterContext.getContextSnapshotEx(); 0751 0752 const bool hasStrokeJobs = !(snapshot == ContextEmpty || 0753 snapshot == HasMergeJob); 0754 const bool hasMergeJobs = snapshot & HasMergeJob; 0755 0756 if(checkStrokeState(hasStrokeJobs, levelOfDetail) && 0757 checkExclusiveProperty(hasMergeJobs, hasStrokeJobs) && 0758 checkSequentialProperty(snapshot, externalJobsPending)) { 0759 0760 KisStrokeSP stroke = m_d->strokesQueue.head(); 0761 updaterContext.addStrokeJob(stroke->popOneJob()); 0762 result = true; 0763 } 0764 0765 return result; 0766 } 0767 0768 void KisStrokesQueue::Private::loadStroke(KisStrokeSP stroke) 0769 { 0770 needsExclusiveAccess = stroke->isExclusive(); 0771 wrapAroundModeSupported = stroke->supportsWrapAroundMode(); 0772 balancingRatioOverride = stroke->balancingRatioOverride(); 0773 currentStrokeLoaded = true; 0774 0775 /** 0776 * Some of the strokes can cancel their work with undoing all the 0777 * changes they did to the paint devices. The problem is that undo 0778 * stack will know nothing about it. Therefore, just notify it 0779 * explicitly 0780 */ 0781 if (purgeRedoStateCallback && 0782 stroke->clearsRedoOnStart()) { 0783 0784 purgeRedoStateCallback(); 0785 } 0786 } 0787 0788 bool KisStrokesQueue::checkStrokeState(bool hasStrokeJobsRunning, 0789 int runningLevelOfDetail) 0790 { 0791 KisStrokeSP stroke = m_d->strokesQueue.head(); 0792 bool result = false; 0793 0794 /** 0795 * We cannot start/continue a stroke if its LOD differs from 0796 * the one that is running on CPU 0797 */ 0798 bool hasLodCompatibility = checkLevelOfDetailProperty(runningLevelOfDetail); 0799 bool hasJobs = stroke->hasJobs(); 0800 0801 /** 0802 * The stroke may be cancelled very fast. In this case it will 0803 * end up in the state: 0804 * 0805 * !stroke->isInitialized() && stroke->isEnded() && !stroke->hasJobs() 0806 * 0807 * This means that !isInitialised() doesn't imply there are any 0808 * jobs present. 0809 */ 0810 if(!stroke->isInitialized() && hasJobs && hasLodCompatibility) { 0811 /** 0812 * It might happen that the stroke got initialized, but its job was not 0813 * started due to some other reasons like exclusivity. Therefore the 0814 * stroke might end up in loaded, but uninitialized state. 0815 */ 0816 if (!m_d->currentStrokeLoaded) { 0817 m_d->loadStroke(stroke); 0818 } 0819 0820 result = true; 0821 } 0822 else if(hasJobs && hasLodCompatibility) { 0823 /** 0824 * If the stroke has no initialization phase, then it can 0825 * arrive here unloaded. 0826 */ 0827 if (!m_d->currentStrokeLoaded) { 0828 m_d->loadStroke(stroke); 0829 } 0830 0831 result = true; 0832 } 0833 else if(stroke->isEnded() && !hasJobs && !hasStrokeJobsRunning) { 0834 m_d->tryClearUndoOnStrokeCompletion(stroke); 0835 0836 const bool needsSyncLod0PlaneToGUI = 0837 stroke->type() == KisStroke::LOD0 && 0838 stroke->isCancelled(); 0839 0840 /** 0841 * If the Lod0 stroke has been cancelled without even being 0842 * started, it means that the GUI still has LodN tiles active, 0843 * so we should reread the data from the image to switch GUI 0844 * tiles into Lod0 mode. 0845 */ 0846 if (needsSyncLod0PlaneToGUI && 0847 m_d->postSyncLod0GUIPlaneRequestForResume) { 0848 m_d->postSyncLod0GUIPlaneRequestForResume(); 0849 } 0850 0851 m_d->strokesQueue.dequeue(); // deleted by shared pointer 0852 m_d->needsExclusiveAccess = false; 0853 m_d->wrapAroundModeSupported = false; 0854 m_d->balancingRatioOverride = -1.0; 0855 m_d->currentStrokeLoaded = false; 0856 0857 m_d->switchDesiredLevelOfDetail(false); 0858 0859 if(!m_d->strokesQueue.isEmpty()) { 0860 result = checkStrokeState(false, runningLevelOfDetail); 0861 } 0862 } 0863 0864 return result; 0865 } 0866 0867 bool KisStrokesQueue::checkExclusiveProperty(bool hasMergeJobs, 0868 bool hasStrokeJobs) 0869 { 0870 Q_UNUSED(hasStrokeJobs); 0871 0872 if(!m_d->strokesQueue.head()->isExclusive()) return true; 0873 return hasMergeJobs == 0; 0874 } 0875 0876 bool KisStrokesQueue::checkSequentialProperty(KisUpdaterContextSnapshotEx snapshot, 0877 bool externalJobsPending) 0878 { 0879 KisStrokeSP stroke = m_d->strokesQueue.head(); 0880 0881 if (snapshot & HasSequentialJob || 0882 snapshot & HasBarrierJob) { 0883 return false; 0884 } 0885 0886 KisStrokeJobData::Sequentiality nextSequentiality = 0887 stroke->nextJobSequentiality(); 0888 0889 if (nextSequentiality == KisStrokeJobData::UNIQUELY_CONCURRENT && 0890 snapshot & HasUniquelyConcurrentJob) { 0891 0892 return false; 0893 } 0894 0895 if (nextSequentiality == KisStrokeJobData::SEQUENTIAL && 0896 (snapshot & HasUniquelyConcurrentJob || 0897 snapshot & HasConcurrentJob)) { 0898 0899 return false; 0900 } 0901 0902 if (nextSequentiality == KisStrokeJobData::BARRIER && 0903 (snapshot & HasUniquelyConcurrentJob || 0904 snapshot & HasConcurrentJob || 0905 snapshot & HasMergeJob || 0906 externalJobsPending)) { 0907 0908 return false; 0909 } 0910 0911 return true; 0912 } 0913 0914 bool KisStrokesQueue::checkLevelOfDetailProperty(int runningLevelOfDetail) 0915 { 0916 KisStrokeSP stroke = m_d->strokesQueue.head(); 0917 0918 return runningLevelOfDetail < 0 || 0919 stroke->nextJobLevelOfDetail() == runningLevelOfDetail; 0920 }