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