Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_suspend_projection_updates_stroke_strategy.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2014 Dmitry Kazakov <dimula73@gmail.com>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6#include <QMutex>
7#include <QMutexLocker>
8
10
11#include <kis_image.h>
12#include <krita_utils.h>
16
17#include "kundo2command.h"
20#include "kis_paintop_utils.h"
21
22
23inline uint qHash(const QRect &rc) {
24 return rc.x() +
25 (rc.y() << 16) +
26 (rc.width() << 8) +
27 (rc.height() << 24);
28}
29
31{
33 bool suspend;
36 int updatesEpoch = 0;
39
42
44 {
45
46 struct Request {
48 Request(const QRect &_rect, KisProjectionUpdateFlags _flags)
49 : rect(_rect), flags(_flags)
50 {
51 }
52
53 QRect rect;
54 KisProjectionUpdateFlags flags;
55 };
56
59 FullRefreshRequest(const QRect &_rect, const QRect &_cropRect, KisProjectionUpdateFlags _flags)
60 : rect(_rect), cropRect(_cropRect), flags(_flags)
61 {
62 }
63
64 QRect rect;
65 QRect cropRect;
66 KisProjectionUpdateFlags flags;
67 };
68
69 typedef QHash<KisNodeSP, QVector<Request> > UpdatesHash;
70 typedef QHash<KisNodeSP, QVector<FullRefreshRequest> > RefreshesHash;
71 public:
73 {
74 }
75
76 void addExplicitUIUpdateRect(const QRect &rc) override
77 {
79 }
80
83 }
84
85 bool filter(KisImage *image, KisNode *node, const QVector<QRect> &rects, KisProjectionUpdateFlags flags) override {
86 if (image->currentLevelOfDetail() > 0) return false;
87
88 QMutexLocker l(&m_mutex);
89
90 Q_FOREACH(const QRect &rc, rects) {
91 m_requestsHash[KisNodeSP(node)].append(Request(rc, flags));
92 }
93
94 return true;
95 }
96
97 bool filterRefreshGraph(KisImage *image, KisNode *node, const QVector<QRect> &rects, const QRect &cropRect, KisProjectionUpdateFlags flags) override {
98 if (image->currentLevelOfDetail() > 0) return false;
99
100 QMutexLocker l(&m_mutex);
101
102 Q_FOREACH(const QRect &rc, rects) {
103 m_refreshesHash[KisNodeSP(node)].append(FullRefreshRequest(rc, cropRect, flags));
104 }
105
106 return true;
107 }
108
110 const int step = 64;
111
112 auto processRefreshes = [&] (bool noFilthyUpdates) {
113 RefreshesHash::const_iterator it = m_refreshesHash.constBegin();
114 RefreshesHash::const_iterator end = m_refreshesHash.constEnd();
115
116 for (; it != end; ++it) {
117 KisNodeSP node = it.key();
118
119 QHash<QRect, QVector<QRect>> fullRefreshRequests;
120
121 bool invalidateFrames = false;
122
123 Q_FOREACH (const FullRefreshRequest &req, it.value()) {
124 if (req.flags.testFlag(KisProjectionUpdateFlag::NoFilthy) == noFilthyUpdates) {
125 fullRefreshRequests[req.cropRect] += req.rect;
126 invalidateFrames |= !req.flags.testFlag(KisProjectionUpdateFlag::DontInvalidateFrames);
127 }
128 }
129
130 KisProjectionUpdateFlags finalFlags(KisProjectionUpdateFlag::None);
131 finalFlags.setFlag(KisProjectionUpdateFlag::DontInvalidateFrames, !invalidateFrames);
132 finalFlags.setFlag(KisProjectionUpdateFlag::NoFilthy, noFilthyUpdates);
133
134 auto reqIt = fullRefreshRequests.begin();
135 for (; reqIt != fullRefreshRequests.end(); ++reqIt) {
136 const QVector<QRect> simplifiedRects = KisRegion::fromOverlappingRects(reqIt.value(), step).rects();
137 image->refreshGraphAsync(node, simplifiedRects, reqIt.key(), finalFlags);
138 }
139 }
140 };
141
142 processRefreshes(false);
143 processRefreshes(true);
144
145 auto processUpdates = [&] (bool noFilthyUpdates) {
146 UpdatesHash::const_iterator it = m_requestsHash.constBegin();
147 UpdatesHash::const_iterator end = m_requestsHash.constEnd();
148
149 for (; it != end; ++it) {
150 KisNodeSP node = it.key();
151
152 QVector<QRect> dirtyRects;
153
154 bool invalidateFrames = false;
155
156 Q_FOREACH (const Request &req, it.value()) {
157 if (req.flags.testFlag(KisProjectionUpdateFlag::NoFilthy) == noFilthyUpdates) {
158 dirtyRects += req.rect;
159 invalidateFrames |= !req.flags.testFlag(KisProjectionUpdateFlag::DontInvalidateFrames);
160 }
161 }
162
163 KisProjectionUpdateFlags finalFlags(KisProjectionUpdateFlag::None);
164 finalFlags.setFlag(KisProjectionUpdateFlag::DontInvalidateFrames, !invalidateFrames);
165 finalFlags.setFlag(KisProjectionUpdateFlag::NoFilthy, noFilthyUpdates);
166
167 const QVector<QRect> simplifiedRects = KisRegion::fromOverlappingRects(dirtyRects, step).rects();
168
169 // FIXME: constness: port requestProjectionUpdate to shared pointers
170 image->requestProjectionUpdate(const_cast<KisNode*>(node.data()), simplifiedRects, finalFlags);
171 }
172 };
173
174 processUpdates(false);
175 processUpdates(true);
176 }
177
178 private:
182 QMutex m_mutex;
183 };
184
186
187
199
201 {
204 m_command(command)
205 {
206 }
207
208 void run() override {
210 m_command->redo();
211 }
212
213 QScopedPointer<StrokeJobCommand> m_command;
214 };
215
216 // Suspend job should be a barrier to ensure all
217 // previous lodN strokes reach the GUI. Otherwise,
218 // they will be blocked in
219 // KisImage::notifyProjectionUpdated()
249
250
281
283 {
284 UploadDataToUIData(const QRect &rc, int updateEpoch, KisSuspendProjectionUpdatesStrokeStrategy *strategy)
286 m_strategy(strategy),
287 m_rc(rc),
288 m_updateEpoch(updateEpoch)
289 {
290 }
291
292 void run() override {
293 // check if we've already started stinking...
294 if (m_strategy->m_d->updatesEpoch > m_updateEpoch) {
295 return;
296 }
297
298 KisImageSP image = m_strategy->m_d->image.toStrongRef();
300
302 }
303
305 QRect m_rc;
307 };
308
328
330 {
334
335 void redo() override {
336 KisImageSP image = m_strategy->m_d->image.toStrongRef();
338
343 const QVector<QRect> totalDirtyRects =
344 image->enableUIUpdates() + m_strategy->m_d->accumulatedDirtyRects;
345
346 const QRect totalRect =
347 image->bounds() &
348 std::accumulate(totalDirtyRects.begin(), totalDirtyRects.end(), QRect(), std::bit_or<QRect>());
349
350 m_strategy->m_d->accumulatedDirtyRects =
352 totalDirtyRects,
354
356
358 Q_FOREACH (const QRect &rc, m_strategy->m_d->accumulatedDirtyRects) {
359 jobsData << new Private::UploadDataToUIData(rc, m_strategy->m_d->updatesEpoch, m_strategy);
360 }
361
363
364 }
365
366 void undo() override {
367 KisImageSP image = m_strategy->m_d->image.toStrongRef();
369
372 }
373
375 };
376
378 {
382
383 void redo() override {
384 KisImageSP image = m_strategy->m_d->image.toStrongRef();
386
388 m_strategy->m_d->sanityResumingFinished = true;
389 m_strategy->m_d->accumulatedDirtyRects.clear();
390 KIS_SAFE_ASSERT_RECOVER_NOOP(m_strategy->m_d->usedFilters.isEmpty());
391 }
392
393 void undo() override {
406 KIS_SAFE_ASSERT_RECOVER_NOOP(m_strategy->m_d->usedFilters.isEmpty());
407 KIS_SAFE_ASSERT_RECOVER_NOOP(m_strategy->m_d->accumulatedDirtyRects.isEmpty());
408
409 m_strategy->m_d->sanityResumingFinished = false;
410
411 KisImageSP image = m_strategy->m_d->image.toStrongRef();
413
415 }
416
418 };
419
420
422};
423
426 QLatin1String("suspend_stroke_strategy") :
427 QLatin1String("resume_stroke_strategy")),
428 m_d(new Private)
429{
430 m_d->image = image;
431 m_d->suspend = suspend;
432 m_d->sharedData = sharedData;
433
441 enableJob(JOB_INIT, true);
442
443 enableJob(JOB_DOSTROKE, true);
444 enableJob(JOB_CANCEL, true);
445
448
451}
452
457
459{
461
462 if (m_d->suspend) {
464 } else {
466 jobs << new Private::BlockUILodSync(true, this);
469 jobs << new Private::BlockUILodSync(false, this);
470 }
471
473}
474
513{
514 KisRunnableStrokeJobDataBase *runnable = dynamic_cast<KisRunnableStrokeJobDataBase*>(data);
515 if (runnable) {
516 runnable->run();
517
518 if (Private::UndoableData *undoable = dynamic_cast<Private::UndoableData*>(data)) {
519 Private::StrokeJobCommand *command = undoable->m_command.take();
520 m_d->executedCommands.append(command);
521 }
522 }
523}
524
529
534
539
541{
542 if (!this->sharedData->installedFilterCookie) return;
543
545 this->sharedData->installedFilterCookie = KisProjectionUpdatesFilterCookie();
546
548
550 filter.dynamicCast<Private::SuspendLod0Updates>();
551
553
554 this->usedFilters.append(localFilter);
555}
556
558{
559 Q_FOREACH (QSharedPointer<Private::SuspendLod0Updates> filter, usedFilters) {
560 filter->notifyUpdates(image.data());
561
562 if (!filter->explicitUIUpdateRequest().isEmpty()) {
563 accumulatedDirtyRects.append(filter->explicitUIUpdateRequest());
564 }
565 }
566 usedFilters.clear();
567}
568
570{
571 KisImageSP image = m_d->image.toStrongRef();
572 if (!image) {
573 return;
574 }
575
576 for (auto it = m_d->executedCommands.rbegin(); it != m_d->executedCommands.rend(); ++it) {
577 (*it)->undo();
578 }
579
580 m_d->tryFetchUsedUpdatesFilter(image);
581
582 if (m_d->haveDisabledGUILodSync) {
584 }
585
593 if (!m_d->suspend) {
594 // FIXME: optimize
595 image->refreshGraphAsync();
596 }
597}
598
600{
608 !m_d->sanityResumingFinished ||
609 (m_d->sanityResumingFinished &&
610 m_d->usedFilters.isEmpty() &&
611 m_d->accumulatedDirtyRects.isEmpty()));
612
613 for (auto it = m_d->executedCommands.rbegin(); it != m_d->executedCommands.rend(); ++it) {
614 (*it)->undo();
615 }
616
617 // reset all the issued updates
618 m_d->updatesEpoch++;
619}
620
622{
624
625 Q_FOREACH (Private::StrokeJobCommand *command, m_d->executedCommands) {
626 jobs << new Private::UndoableData(command);
627 }
628 m_d->executedCommands.clear();
629
631}
unsigned int uint
KUndo2CommandPrivate * d
Definition kundo2stack.h:88
void emitRequestLodPlanesSyncBlocked(bool value)
void disableUIUpdates() override
void refreshGraphAsync(KisNodeSP root, const QVector< QRect > &rects, const QRect &cropRect, KisProjectionUpdateFlags flags=KisProjectionUpdateFlag::None) override
KisProjectionUpdatesFilterSP removeProjectionUpdatesFilter(KisProjectionUpdatesFilterCookie cookie) override
removes already installed filter from the stack of updates filers
KisProjectionUpdatesFilterCookie currentProjectionUpdatesFilter() const override
QVector< QRect > enableUIUpdates() override
int currentLevelOfDetail() const
KisImageSignalRouter * signalRouter()
QRect bounds() const override
KisProjectionUpdatesFilterCookie addProjectionUpdatesFilter(KisProjectionUpdatesFilterSP filter) override
void notifyProjectionUpdated(const QRect &rc) override
void requestProjectionUpdate(KisNode *node, const QVector< QRect > &rects, KisProjectionUpdateFlags flags) override
static KisRegion fromOverlappingRects(const QVector< QRect > &rects, int gridSize)
QVector< QRect > rects() const
KisRunnableStrokeJobsInterface * runnableJobsInterface() const
virtual void addRunnableJobs(const QVector< KisRunnableStrokeJobDataBase * > &list)=0
virtual void run()=0
void enableJob(JobType type, bool enable=true, KisStrokeJobData::Sequentiality sequentiality=KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::Exclusivity exclusivity=KisStrokeJobData::NORMAL)
void setClearsRedoOnStart(bool value)
void setNeedsExplicitCancel(bool value)
bool filterRefreshGraph(KisImage *image, KisNode *node, const QVector< QRect > &rects, const QRect &cropRect, KisProjectionUpdateFlags flags) override
bool filter(KisImage *image, KisNode *node, const QVector< QRect > &rects, KisProjectionUpdateFlags flags) override
KisSuspendProjectionUpdatesStrokeStrategy(KisImageWSP image, bool suspend, SharedDataSP sharedData)
static QList< KisStrokeJobData * > createSuspendJobsData(KisImageWSP image)
static QList< KisStrokeJobData * > createResumeJobsData(KisImageWSP image)
KisSharedPtr< T > toStrongRef() const
toStrongRef returns a KisSharedPtr which may be dereferenced.
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:128
#define KIS_SAFE_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:130
QSharedPointer< T > toQShared(T *ptr)
void * KisProjectionUpdatesFilterCookie
Definition kis_types.h:285
KisSharedPtr< KisNode > KisNodeSP
Definition kis_types.h:86
QVector< QRect > splitAndFilterDabRect(const QRect &totalRect, const QVector< QRect > &dabRects, int idealPatchSize)
QSize optimalPatchSize()
StrokeJobCommand(KisStrokeJobData::Sequentiality sequentiality=KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::Exclusivity exclusivity=KisStrokeJobData::NORMAL)
UploadDataToUIData(const QRect &rc, int updateEpoch, KisSuspendProjectionUpdatesStrokeStrategy *strategy)