File indexing completed on 2024-05-19 04:26:39
0001 /* 0002 * SPDX-FileCopyrightText: 2010 Dmitry Kazakov <dimula73@gmail.com> 0003 * 0004 * SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "kis_update_scheduler.h" 0008 0009 #include "klocalizedstring.h" 0010 #include "kis_image_config.h" 0011 #include "kis_merge_walker.h" 0012 #include "kis_full_refresh_walker.h" 0013 0014 #include "kis_updater_context.h" 0015 #include "kis_simple_update_queue.h" 0016 #include "kis_strokes_queue.h" 0017 0018 #include "kis_queues_progress_updater.h" 0019 #include "KisImageConfigNotifier.h" 0020 0021 #include <QReadWriteLock> 0022 #include "kis_lazy_wait_condition.h" 0023 #include <mutex> 0024 0025 //#define DEBUG_BALANCING 0026 0027 #ifdef DEBUG_BALANCING 0028 #define DEBUG_BALANCING_METRICS(decidedFirst, excl) \ 0029 dbgKrita << "Balance decision:" << decidedFirst \ 0030 << "(" << excl << ")" \ 0031 << "updates:" << m_d->updatesQueue.sizeMetric() \ 0032 << "strokes:" << m_d->strokesQueue.sizeMetric() 0033 #else 0034 #define DEBUG_BALANCING_METRICS(decidedFirst, excl) 0035 #endif 0036 0037 0038 struct Q_DECL_HIDDEN KisUpdateScheduler::Private { 0039 Private(KisUpdateScheduler *_q, KisProjectionUpdateListener *p) 0040 : q(_q) 0041 , updaterContext(KisImageConfig(true).maxNumberOfThreads(), q) 0042 , projectionUpdateListener(p) 0043 {} 0044 0045 KisUpdateScheduler *q; 0046 0047 KisSimpleUpdateQueue updatesQueue; 0048 KisStrokesQueue strokesQueue; 0049 KisUpdaterContext updaterContext; 0050 bool processingBlocked = false; 0051 qreal defaultBalancingRatio = 1.0; // desired strokes-queue-size / updates-queue-size 0052 KisProjectionUpdateListener *projectionUpdateListener; 0053 KisQueuesProgressUpdater *progressUpdater = 0; 0054 0055 QAtomicInt updatesLockCounter; 0056 QReadWriteLock updatesStartLock; 0057 KisLazyWaitCondition updatesFinishedCondition; 0058 0059 qreal balancingRatio() const { 0060 const qreal strokeRatioOverride = strokesQueue.balancingRatioOverride(); 0061 return strokeRatioOverride > 0 ? strokeRatioOverride : defaultBalancingRatio; 0062 } 0063 }; 0064 0065 KisUpdateScheduler::KisUpdateScheduler(KisProjectionUpdateListener *projectionUpdateListener, QObject *parent) 0066 : QObject(parent), 0067 m_d(new Private(this, projectionUpdateListener)) 0068 { 0069 updateSettings(); 0070 connectSignals(); 0071 } 0072 0073 KisUpdateScheduler::KisUpdateScheduler() 0074 : m_d(new Private(this, 0)) 0075 { 0076 } 0077 0078 KisUpdateScheduler::~KisUpdateScheduler() 0079 { 0080 delete m_d->progressUpdater; 0081 delete m_d; 0082 } 0083 0084 void KisUpdateScheduler::setThreadsLimit(int value) 0085 { 0086 KIS_SAFE_ASSERT_RECOVER_RETURN(!m_d->processingBlocked); 0087 0088 /** 0089 * Thread limit can be changed without the full-featured barrier 0090 * lock, we can avoid waiting for all the jobs to complete. We 0091 * should just ensure there is no more jobs in the updater context. 0092 */ 0093 immediateLockForReadOnly(); 0094 m_d->updaterContext.lock(); 0095 m_d->updaterContext.setThreadsLimit(value); 0096 m_d->updaterContext.unlock(); 0097 unlock(false); 0098 } 0099 0100 int KisUpdateScheduler::threadsLimit() const 0101 { 0102 std::lock_guard<KisUpdaterContext> l(m_d->updaterContext); 0103 return m_d->updaterContext.threadsLimit(); 0104 } 0105 0106 void KisUpdateScheduler::connectSignals() 0107 { 0108 connect(KisImageConfigNotifier::instance(), SIGNAL(configChanged()), 0109 SLOT(updateSettings())); 0110 } 0111 0112 void KisUpdateScheduler::setProgressProxy(KoProgressProxy *progressProxy) 0113 { 0114 delete m_d->progressUpdater; 0115 m_d->progressUpdater = progressProxy ? 0116 new KisQueuesProgressUpdater(progressProxy, this) : 0; 0117 } 0118 0119 void KisUpdateScheduler::progressUpdate() 0120 { 0121 if (!m_d->progressUpdater) return; 0122 0123 if(!m_d->strokesQueue.hasOpenedStrokes()) { 0124 QString jobName = m_d->strokesQueue.currentStrokeName().toString(); 0125 if(jobName.isEmpty()) { 0126 jobName = i18n("Updating..."); 0127 } 0128 0129 int sizeMetric = m_d->strokesQueue.sizeMetric(); 0130 if (!sizeMetric) { 0131 sizeMetric = m_d->updatesQueue.sizeMetric(); 0132 } 0133 0134 m_d->progressUpdater->updateProgress(sizeMetric, jobName); 0135 } 0136 else { 0137 m_d->progressUpdater->hide(); 0138 } 0139 } 0140 0141 void KisUpdateScheduler::updateProjection(KisNodeSP node, const QVector<QRect> &rects, const QRect &cropRect) 0142 { 0143 m_d->updatesQueue.addUpdateJob(node, rects, cropRect, currentLevelOfDetail()); 0144 processQueues(); 0145 } 0146 0147 void KisUpdateScheduler::updateProjection(KisNodeSP node, const QRect &rc, const QRect &cropRect) 0148 { 0149 m_d->updatesQueue.addUpdateJob(node, rc, cropRect, currentLevelOfDetail()); 0150 processQueues(); 0151 } 0152 0153 void KisUpdateScheduler::updateProjectionNoFilthy(KisNodeSP node, const QVector<QRect>& rects, const QRect &cropRect) 0154 { 0155 m_d->updatesQueue.addUpdateNoFilthyJob(node, rects, cropRect, currentLevelOfDetail()); 0156 processQueues(); 0157 } 0158 0159 void KisUpdateScheduler::updateProjectionNoFilthy(KisNodeSP node, const QRect& rc, const QRect &cropRect) 0160 { 0161 m_d->updatesQueue.addUpdateNoFilthyJob(node, rc, cropRect, currentLevelOfDetail()); 0162 processQueues(); 0163 } 0164 0165 void KisUpdateScheduler::fullRefreshAsync(KisNodeSP root, const QVector<QRect>& rects, const QRect &cropRect) 0166 { 0167 m_d->updatesQueue.addFullRefreshJob(root, rects, cropRect, currentLevelOfDetail()); 0168 processQueues(); 0169 } 0170 0171 void KisUpdateScheduler::fullRefreshAsyncNoFilthy(KisNodeSP root, const QVector<QRect> &rects, const QRect &cropRect) 0172 { 0173 m_d->updatesQueue.addFullRefreshNoFilthyJob(root, rects, cropRect, currentLevelOfDetail()); 0174 processQueues(); 0175 } 0176 0177 void KisUpdateScheduler::fullRefresh(KisNodeSP root, const QRect& rc, const QRect &cropRect) 0178 { 0179 KisBaseRectsWalkerSP walker = new KisFullRefreshWalker(cropRect); 0180 walker->collectRects(root, rc); 0181 0182 bool needLock = true; 0183 0184 if(m_d->processingBlocked) { 0185 warnImage << "WARNING: Calling synchronous fullRefresh under a scheduler lock held"; 0186 warnImage << "We will not assert for now, but please port caller's to strokes"; 0187 warnImage << "to avoid this warning"; 0188 needLock = false; 0189 } 0190 0191 if(needLock) immediateLockForReadOnly(); 0192 m_d->updaterContext.lock(); 0193 0194 Q_ASSERT(m_d->updaterContext.isJobAllowed(walker)); 0195 m_d->updaterContext.addMergeJob(walker); 0196 m_d->updaterContext.unlock(); 0197 0198 m_d->updaterContext.waitForDone(); 0199 0200 if(needLock) unlock(true); 0201 } 0202 0203 void KisUpdateScheduler::addSpontaneousJob(KisSpontaneousJob *spontaneousJob) 0204 { 0205 m_d->updatesQueue.addSpontaneousJob(spontaneousJob); 0206 processQueues(); 0207 } 0208 0209 bool KisUpdateScheduler::hasUpdatesRunning() const 0210 { 0211 return !m_d->updatesQueue.isEmpty(); 0212 } 0213 0214 KisStrokeId KisUpdateScheduler::startStroke(KisStrokeStrategy *strokeStrategy) 0215 { 0216 KisStrokeId id = m_d->strokesQueue.startStroke(strokeStrategy); 0217 processQueues(); 0218 return id; 0219 } 0220 0221 void KisUpdateScheduler::addJob(KisStrokeId id, KisStrokeJobData *data) 0222 { 0223 m_d->strokesQueue.addJob(id, data); 0224 processQueues(); 0225 } 0226 0227 void KisUpdateScheduler::endStroke(KisStrokeId id) 0228 { 0229 m_d->strokesQueue.endStroke(id); 0230 processQueues(); 0231 } 0232 0233 bool KisUpdateScheduler::cancelStroke(KisStrokeId id) 0234 { 0235 bool result = m_d->strokesQueue.cancelStroke(id); 0236 processQueues(); 0237 return result; 0238 } 0239 0240 bool KisUpdateScheduler::tryCancelCurrentStrokeAsync() 0241 { 0242 return m_d->strokesQueue.tryCancelCurrentStrokeAsync(); 0243 } 0244 0245 UndoResult KisUpdateScheduler::tryUndoLastStrokeAsync() 0246 { 0247 return m_d->strokesQueue.tryUndoLastStrokeAsync(); 0248 } 0249 0250 bool KisUpdateScheduler::wrapAroundModeSupported() const 0251 { 0252 return m_d->strokesQueue.wrapAroundModeSupported(); 0253 } 0254 0255 void KisUpdateScheduler::setLodPreferences(const KisLodPreferences &value) 0256 { 0257 m_d->strokesQueue.setLodPreferences(value); 0258 0259 /** 0260 * The queue might have started an internal stroke for 0261 * cache synchronization. Process the queues to execute 0262 * it if needed. 0263 */ 0264 processQueues(); 0265 } 0266 0267 KisLodPreferences KisUpdateScheduler::lodPreferences() const 0268 { 0269 return m_d->strokesQueue.lodPreferences(); 0270 } 0271 0272 void KisUpdateScheduler::explicitRegenerateLevelOfDetail() 0273 { 0274 m_d->strokesQueue.explicitRegenerateLevelOfDetail(); 0275 0276 // \see a comment in setDesiredLevelOfDetail() 0277 processQueues(); 0278 } 0279 0280 int KisUpdateScheduler::currentLevelOfDetail() const 0281 { 0282 int levelOfDetail = m_d->updaterContext.currentLevelOfDetail(); 0283 0284 if (levelOfDetail < 0) { 0285 // it is safe, because is called iff updaterContext has no running jobs at all 0286 levelOfDetail = m_d->updatesQueue.overrideLevelOfDetail(); 0287 } 0288 0289 if (levelOfDetail < 0) { 0290 levelOfDetail = 0; 0291 } 0292 0293 return levelOfDetail; 0294 } 0295 0296 void KisUpdateScheduler::setLod0ToNStrokeStrategyFactory(const KisLodSyncStrokeStrategyFactory &factory) 0297 { 0298 m_d->strokesQueue.setLod0ToNStrokeStrategyFactory(factory); 0299 } 0300 0301 void KisUpdateScheduler::setSuspendResumeUpdatesStrokeStrategyFactory(const KisSuspendResumeStrategyPairFactory &factory) 0302 { 0303 m_d->strokesQueue.setSuspendResumeUpdatesStrokeStrategyFactory(factory); 0304 } 0305 0306 void KisUpdateScheduler::setPurgeRedoStateCallback(const std::function<void ()> &callback) 0307 { 0308 m_d->strokesQueue.setPurgeRedoStateCallback(callback); 0309 } 0310 0311 void KisUpdateScheduler::setPostSyncLod0GUIPlaneRequestForResumeCallback(const std::function<void ()> &callback) 0312 { 0313 m_d->strokesQueue.setPostSyncLod0GUIPlaneRequestForResumeCallback(callback); 0314 } 0315 0316 KisPostExecutionUndoAdapter *KisUpdateScheduler::lodNPostExecutionUndoAdapter() const 0317 { 0318 return m_d->strokesQueue.lodNPostExecutionUndoAdapter(); 0319 } 0320 0321 void KisUpdateScheduler::updateSettings() 0322 { 0323 m_d->updatesQueue.updateSettings(); 0324 KisImageConfig config(true); 0325 m_d->defaultBalancingRatio = config.schedulerBalancingRatio(); 0326 setThreadsLimit(config.maxNumberOfThreads()); 0327 } 0328 0329 void KisUpdateScheduler::immediateLockForReadOnly() 0330 { 0331 m_d->processingBlocked = true; 0332 m_d->updaterContext.waitForDone(); 0333 } 0334 0335 void KisUpdateScheduler::unlock(bool resetLodLevels) 0336 { 0337 if (resetLodLevels) { 0338 /** 0339 * Legacy strokes may have changed the image while we didn't 0340 * control it. Notify the queue to take it into account. 0341 */ 0342 m_d->strokesQueue.notifyUFOChangedImage(); 0343 } 0344 0345 m_d->processingBlocked = false; 0346 processQueues(); 0347 } 0348 0349 bool KisUpdateScheduler::isIdle() 0350 { 0351 bool result = false; 0352 0353 if (tryBarrierLock()) { 0354 result = true; 0355 unlock(false); 0356 } 0357 0358 return result; 0359 } 0360 0361 void KisUpdateScheduler::waitForDone() 0362 { 0363 do { 0364 processQueues(); 0365 m_d->updaterContext.waitForDone(); 0366 } while(!m_d->updatesQueue.isEmpty() || !m_d->strokesQueue.isEmpty()); 0367 } 0368 0369 bool KisUpdateScheduler::tryBarrierLock() 0370 { 0371 if(!m_d->updatesQueue.isEmpty() || !m_d->strokesQueue.isEmpty()) { 0372 return false; 0373 } 0374 0375 m_d->processingBlocked = true; 0376 m_d->updaterContext.waitForDone(); 0377 if(!m_d->updatesQueue.isEmpty() || !m_d->strokesQueue.isEmpty()) { 0378 m_d->processingBlocked = false; 0379 processQueues(); 0380 return false; 0381 } 0382 0383 return true; 0384 } 0385 0386 void KisUpdateScheduler::barrierLock() 0387 { 0388 do { 0389 m_d->processingBlocked = false; 0390 processQueues(); 0391 m_d->processingBlocked = true; 0392 m_d->updaterContext.waitForDone(); 0393 } while(!m_d->updatesQueue.isEmpty() || !m_d->strokesQueue.isEmpty()); 0394 } 0395 0396 void KisUpdateScheduler::processQueues() 0397 { 0398 wakeUpWaitingThreads(); 0399 0400 if(m_d->processingBlocked) return; 0401 0402 if(m_d->strokesQueue.needsExclusiveAccess()) { 0403 DEBUG_BALANCING_METRICS("STROKES", "X"); 0404 m_d->strokesQueue.processQueue(m_d->updaterContext, 0405 !m_d->updatesQueue.isEmpty()); 0406 0407 if(!m_d->strokesQueue.needsExclusiveAccess()) { 0408 tryProcessUpdatesQueue(); 0409 } 0410 } 0411 else if(m_d->balancingRatio() * m_d->strokesQueue.sizeMetric() > m_d->updatesQueue.sizeMetric()) { 0412 DEBUG_BALANCING_METRICS("STROKES", "N"); 0413 m_d->strokesQueue.processQueue(m_d->updaterContext, 0414 !m_d->updatesQueue.isEmpty()); 0415 tryProcessUpdatesQueue(); 0416 } 0417 else { 0418 DEBUG_BALANCING_METRICS("UPDATES", "N"); 0419 tryProcessUpdatesQueue(); 0420 m_d->strokesQueue.processQueue(m_d->updaterContext, 0421 !m_d->updatesQueue.isEmpty()); 0422 0423 } 0424 0425 progressUpdate(); 0426 } 0427 0428 void KisUpdateScheduler::blockUpdates() 0429 { 0430 m_d->updatesFinishedCondition.initWaiting(); 0431 0432 m_d->updatesLockCounter.ref(); 0433 while(haveUpdatesRunning()) { 0434 m_d->updatesFinishedCondition.wait(); 0435 } 0436 0437 m_d->updatesFinishedCondition.endWaiting(); 0438 } 0439 0440 void KisUpdateScheduler::unblockUpdates() 0441 { 0442 m_d->updatesLockCounter.deref(); 0443 processQueues(); 0444 } 0445 0446 void KisUpdateScheduler::wakeUpWaitingThreads() 0447 { 0448 if(m_d->updatesLockCounter && !haveUpdatesRunning()) { 0449 m_d->updatesFinishedCondition.wakeAll(); 0450 } 0451 } 0452 0453 void KisUpdateScheduler::tryProcessUpdatesQueue() 0454 { 0455 QReadLocker locker(&m_d->updatesStartLock); 0456 if(m_d->updatesLockCounter) return; 0457 0458 m_d->updatesQueue.processQueue(m_d->updaterContext); 0459 } 0460 0461 bool KisUpdateScheduler::haveUpdatesRunning() 0462 { 0463 QWriteLocker locker(&m_d->updatesStartLock); 0464 0465 qint32 numMergeJobs, numStrokeJobs; 0466 m_d->updaterContext.getJobsSnapshot(numMergeJobs, numStrokeJobs); 0467 0468 return numMergeJobs; 0469 } 0470 0471 void KisUpdateScheduler::continueUpdate(const QRect &rect) 0472 { 0473 Q_ASSERT(m_d->projectionUpdateListener); 0474 m_d->projectionUpdateListener->notifyProjectionUpdated(rect); 0475 } 0476 0477 void KisUpdateScheduler::doSomeUsefulWork() 0478 { 0479 m_d->updatesQueue.optimize(); 0480 } 0481 0482 void KisUpdateScheduler::spareThreadAppeared() 0483 { 0484 processQueues(); 0485 } 0486 0487 KisTestableUpdateScheduler::KisTestableUpdateScheduler(KisProjectionUpdateListener *projectionUpdateListener, 0488 qint32 threadCount) 0489 { 0490 updateSettings(); 0491 m_d->projectionUpdateListener = projectionUpdateListener; 0492 0493 setThreadsLimit(threadCount); 0494 m_d->updaterContext.setTestingMode(true); 0495 0496 connectSignals(); 0497 } 0498 0499 KisUpdaterContext *KisTestableUpdateScheduler::updaterContext() 0500 { 0501 return &m_d->updaterContext; 0502 }