Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_update_scheduler.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2010 Dmitry Kazakov <dimula73@gmail.com>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
8
9#include "klocalizedstring.h"
10#include "kis_image_config.h"
11#include "kis_merge_walker.h"
13
14#include "kis_updater_context.h"
16#include "kis_strokes_queue.h"
17
20
21#include <QReadWriteLock>
23#include <mutex>
24
25//#define DEBUG_BALANCING
26
27#ifdef DEBUG_BALANCING
28#define DEBUG_BALANCING_METRICS(decidedFirst, excl) \
29 dbgKrita << "Balance decision:" << decidedFirst \
30 << "(" << excl << ")" \
31 << "updates:" << m_d->updatesQueue.sizeMetric() \
32 << "strokes:" << m_d->strokesQueue.sizeMetric()
33#else
34#define DEBUG_BALANCING_METRICS(decidedFirst, excl)
35#endif
36
37
38struct Q_DECL_HIDDEN KisUpdateScheduler::Private {
40 : q(_q)
41 , updaterContext(KisImageConfig(true).maxNumberOfThreads(), q)
42 , projectionUpdateListener(p)
43 {}
44
46
50 bool processingBlocked = false;
51 qreal defaultBalancingRatio = 1.0; // desired strokes-queue-size / updates-queue-size
53 KisQueuesProgressUpdater *progressUpdater = 0;
54
56 QReadWriteLock updatesStartLock;
58
59 qreal balancingRatio() const {
60 const qreal strokeRatioOverride = strokesQueue.balancingRatioOverride();
61 return strokeRatioOverride > 0 ? strokeRatioOverride : defaultBalancingRatio;
62 }
63};
64
66 : QObject(parent),
67 m_d(new Private(this, projectionUpdateListener))
68{
71}
72
74 : m_d(new Private(this, 0))
75{
76}
77
79{
80 delete m_d->progressUpdater;
81 delete m_d;
82}
83
85{
86 KIS_SAFE_ASSERT_RECOVER_RETURN(!m_d->processingBlocked);
87
94 m_d->updaterContext.lock();
95 m_d->updaterContext.setThreadsLimit(value);
96 m_d->updaterContext.unlock();
97 unlock(false);
98}
99
101{
102 std::lock_guard<KisUpdaterContext> l(m_d->updaterContext);
103 return m_d->updaterContext.threadsLimit();
104}
105
107{
108 connect(KisImageConfigNotifier::instance(), SIGNAL(configChanged()),
109 SLOT(updateSettings()));
110}
111
113{
114 delete m_d->progressUpdater;
115 m_d->progressUpdater = progressProxy ?
116 new KisQueuesProgressUpdater(progressProxy, this) : 0;
117}
118
120{
121 if (!m_d->progressUpdater) return;
122
123 if(!m_d->strokesQueue.hasOpenedStrokes()) {
124 QString jobName = m_d->strokesQueue.currentStrokeName().toString();
125 if(jobName.isEmpty()) {
126 jobName = i18n("Updating...");
127 }
128
129 int sizeMetric = m_d->strokesQueue.sizeMetric();
130 if (!sizeMetric) {
131 sizeMetric = m_d->updatesQueue.sizeMetric();
132 }
133
134 m_d->progressUpdater->updateProgress(sizeMetric, jobName);
135 }
136 else {
137 m_d->progressUpdater->hide();
138 }
139}
140
141void KisUpdateScheduler::updateProjection(KisNodeSP node, const QVector<QRect> &rects, const QRect &cropRect, KisProjectionUpdateFlags flags)
142{
143 m_d->updatesQueue.addUpdateJob(node, rects, cropRect, currentLevelOfDetail(), flags);
145}
146
147void KisUpdateScheduler::fullRefreshAsync(KisNodeSP root, const QVector<QRect>& rects, const QRect &cropRect, KisProjectionUpdateFlags flags)
148{
149 m_d->updatesQueue.addFullRefreshJob(root, rects, cropRect, currentLevelOfDetail(), flags);
151}
152
153void KisUpdateScheduler::updateProjection(KisNodeSP node, const QRect &rc, const QRect &cropRect)
154{
155 updateProjection(node, {rc}, cropRect, KisProjectionUpdateFlag::None);
156}
157
158void KisUpdateScheduler::fullRefreshAsync(KisNodeSP root, const QRect &rc, const QRect &cropRect)
159{
160 fullRefreshAsync(root, {rc}, cropRect, KisProjectionUpdateFlag::None);
161}
162
164{
165 m_d->updatesQueue.addSpontaneousJob(spontaneousJob);
167}
168
170{
171 return !m_d->updatesQueue.isEmpty();
172}
173
175{
176 KisStrokeId id = m_d->strokesQueue.startStroke(strokeStrategy);
178 return id;
179}
180
182{
183 m_d->strokesQueue.addJob(id, data);
185}
186
188{
189 m_d->strokesQueue.endStroke(id);
191}
192
194{
195 bool result = m_d->strokesQueue.cancelStroke(id);
197 return result;
198}
199
201{
202 return m_d->strokesQueue.tryCancelCurrentStrokeAsync();
203}
204
206{
207 return m_d->strokesQueue.tryUndoLastStrokeAsync();
208}
209
211{
212 return m_d->strokesQueue.wrapAroundModeSupported();
213}
214
216{
217 m_d->strokesQueue.setLodPreferences(value);
218
225}
226
228{
229 return m_d->strokesQueue.lodPreferences();
230}
231
233{
234 m_d->strokesQueue.explicitRegenerateLevelOfDetail();
235
236 // \see a comment in setDesiredLevelOfDetail()
238}
239
241{
242 int levelOfDetail = m_d->updaterContext.currentLevelOfDetail();
243
244 if (levelOfDetail < 0) {
245 // it is safe, because is called iff updaterContext has no running jobs at all
246 levelOfDetail = m_d->updatesQueue.overrideLevelOfDetail();
247 }
248
249 if (levelOfDetail < 0) {
250 levelOfDetail = 0;
251 }
252
253 return levelOfDetail;
254}
255
257{
258 m_d->strokesQueue.setLod0ToNStrokeStrategyFactory(factory);
259}
260
262{
263 m_d->strokesQueue.setSuspendResumeUpdatesStrokeStrategyFactory(factory);
264}
265
266void KisUpdateScheduler::setPurgeRedoStateCallback(const std::function<void ()> &callback)
267{
268 m_d->strokesQueue.setPurgeRedoStateCallback(callback);
269}
270
272{
273 m_d->strokesQueue.setPostSyncLod0GUIPlaneRequestForResumeCallback(callback);
274}
275
277{
278 return m_d->strokesQueue.lodNPostExecutionUndoAdapter();
279}
280
282{
283 m_d->updatesQueue.updateSettings();
284 KisImageConfig config(true);
285 m_d->defaultBalancingRatio = config.schedulerBalancingRatio();
287}
288
290{
291 m_d->processingBlocked = true;
292 m_d->updaterContext.waitForDone();
293}
294
295void KisUpdateScheduler::unlock(bool resetLodLevels)
296{
297 if (resetLodLevels) {
302 m_d->strokesQueue.notifyUFOChangedImage();
303 }
304
305 m_d->processingBlocked = false;
307}
308
310{
311 bool result = false;
312
313 if (tryBarrierLock()) {
314 result = true;
315 unlock(false);
316 }
317
318 return result;
319}
320
322{
323 do {
325 m_d->updaterContext.waitForDone();
326 } while(!m_d->updatesQueue.isEmpty() || !m_d->strokesQueue.isEmpty());
327}
328
330{
331 if(!m_d->updatesQueue.isEmpty() || !m_d->strokesQueue.isEmpty()) {
332 return false;
333 }
334
335 m_d->processingBlocked = true;
336 m_d->updaterContext.waitForDone();
337 if(!m_d->updatesQueue.isEmpty() || !m_d->strokesQueue.isEmpty()) {
338 m_d->processingBlocked = false;
340 return false;
341 }
342
343 return true;
344}
345
347{
348 do {
349 m_d->processingBlocked = false;
351 m_d->processingBlocked = true;
352 m_d->updaterContext.waitForDone();
353 } while(!m_d->updatesQueue.isEmpty() || !m_d->strokesQueue.isEmpty());
354}
355
357{
359
360 if(m_d->processingBlocked) return;
361
362 if(m_d->strokesQueue.needsExclusiveAccess()) {
363 DEBUG_BALANCING_METRICS("STROKES", "X");
364 m_d->strokesQueue.processQueue(m_d->updaterContext,
365 !m_d->updatesQueue.isEmpty());
366
367 if(!m_d->strokesQueue.needsExclusiveAccess()) {
369 }
370 }
371 else if(m_d->balancingRatio() * m_d->strokesQueue.sizeMetric() > m_d->updatesQueue.sizeMetric()) {
372 DEBUG_BALANCING_METRICS("STROKES", "N");
373 m_d->strokesQueue.processQueue(m_d->updaterContext,
374 !m_d->updatesQueue.isEmpty());
376 }
377 else {
378 DEBUG_BALANCING_METRICS("UPDATES", "N");
380 m_d->strokesQueue.processQueue(m_d->updaterContext,
381 !m_d->updatesQueue.isEmpty());
382
383 }
384
386}
387
389{
390 m_d->updatesFinishedCondition.initWaiting();
391
392 m_d->updatesLockCounter.ref();
393 while(haveUpdatesRunning()) {
394 m_d->updatesFinishedCondition.wait();
395 }
396
397 m_d->updatesFinishedCondition.endWaiting();
398}
399
401{
402 m_d->updatesLockCounter.deref();
404}
405
407{
408 if(m_d->updatesLockCounter && !haveUpdatesRunning()) {
409 m_d->updatesFinishedCondition.wakeAll();
410 }
411}
412
414{
415 QReadLocker locker(&m_d->updatesStartLock);
416 if(m_d->updatesLockCounter) return;
417
418 m_d->updatesQueue.processQueue(m_d->updaterContext);
419}
420
422{
423 QWriteLocker locker(&m_d->updatesStartLock);
424
425 qint32 numMergeJobs, numStrokeJobs;
426 m_d->updaterContext.getJobsSnapshot(numMergeJobs, numStrokeJobs);
427
428 return numMergeJobs;
429}
430
432{
433 Q_ASSERT(m_d->projectionUpdateListener);
434 m_d->projectionUpdateListener->notifyProjectionUpdated(rect);
435}
436
438{
439 m_d->updatesQueue.optimize();
440}
441
446
448 qint32 threadCount)
449{
451 m_d->projectionUpdateListener = projectionUpdateListener;
452
453 setThreadsLimit(threadCount);
454 m_d->updaterContext.setTestingMode(true);
455
457}
458
460{
461 return &m_d->updaterContext;
462}
float value(const T *src, size_t ch)
const Params2D p
connect(this, SIGNAL(optionsChanged()), this, SLOT(saveOptions()))
static KisImageConfigNotifier * instance()
qreal schedulerBalancingRatio() const
int maxNumberOfThreads(bool defaultValue=false) const
KisUpdaterContext * updaterContext()
KisTestableUpdateScheduler(KisProjectionUpdateListener *projectionUpdateListener, qint32 threadCount)
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:128
std::function< std::pair< KisSuspendResumePair, KisSuspendResumePair >()> KisSuspendResumeStrategyPairFactory
std::function< KisLodSyncPair(bool)> KisLodSyncStrokeStrategyFactory
#define DEBUG_BALANCING_METRICS(decidedFirst, excl)
void unlock(bool resetLodLevels=true)
void addJob(KisStrokeId id, KisStrokeJobData *data) override
void setPostSyncLod0GUIPlaneRequestForResumeCallback(const std::function< void()> &callback)
void setProgressProxy(KoProgressProxy *progressProxy)
QReadWriteLock updatesStartLock
void updateProjection(KisNodeSP node, const QVector< QRect > &rects, const QRect &cropRect, KisProjectionUpdateFlags flags)
void setSuspendResumeUpdatesStrokeStrategyFactory(const KisSuspendResumeStrategyPairFactory &factory)
void addSpontaneousJob(KisSpontaneousJob *spontaneousJob)
KisStrokeId startStroke(KisStrokeStrategy *strokeStrategy) override
void setLodPreferences(const KisLodPreferences &value)
KisLazyWaitCondition updatesFinishedCondition
void setLod0ToNStrokeStrategyFactory(const KisLodSyncStrokeStrategyFactory &factory)
void continueUpdate(const QRect &rect)
void endStroke(KisStrokeId id) override
KisLodPreferences lodPreferences() const
KisUpdaterContext updaterContext
KisUpdateScheduler * q
void setThreadsLimit(int value)
KisPostExecutionUndoAdapter * lodNPostExecutionUndoAdapter() const
KisSimpleUpdateQueue updatesQueue
Private(KisUpdateScheduler *_q, KisProjectionUpdateListener *p)
void setPurgeRedoStateCallback(const std::function< void()> &callback)
bool cancelStroke(KisStrokeId id) override
void fullRefreshAsync(KisNodeSP root, const QVector< QRect > &rects, const QRect &cropRect, KisProjectionUpdateFlags flags)
KisProjectionUpdateListener * projectionUpdateListener