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#include <memory>
23
24
25inline uint qHash(const QRect &rc) {
26 return rc.x() +
27 (rc.y() << 16) +
28 (rc.width() << 8) +
29 (rc.height() << 24);
30}
31
33{
35 bool suspend;
38 int updatesEpoch = 0;
41
44
46 {
47
48 struct Request {
50 Request(const QRect &_rect, KisProjectionUpdateFlags _flags)
51 : rect(_rect), flags(_flags)
52 {
53 }
54
55 QRect rect;
56 KisProjectionUpdateFlags flags;
57 };
58
61 FullRefreshRequest(const QRect &_rect, const QRect &_cropRect, KisProjectionUpdateFlags _flags)
62 : rect(_rect), cropRect(_cropRect), flags(_flags)
63 {
64 }
65
66 QRect rect;
67 QRect cropRect;
68 KisProjectionUpdateFlags flags;
69 };
70
71 typedef QHash<KisNodeSP, QVector<Request> > UpdatesHash;
72 typedef QHash<KisNodeSP, QVector<FullRefreshRequest> > RefreshesHash;
73 public:
75 {
76 }
77
78 void addExplicitUIUpdateRect(const QRect &rc) override
79 {
81 }
82
85 }
86
87 bool filter(KisImage *image, KisNode *node, const QVector<QRect> &rects, KisProjectionUpdateFlags flags) override {
88 if (image->currentLevelOfDetail() > 0) return false;
89
90 QMutexLocker l(&m_mutex);
91
92 Q_FOREACH(const QRect &rc, rects) {
93 m_requestsHash[KisNodeSP(node)].append(Request(rc, flags));
94 }
95
96 return true;
97 }
98
99 bool filterRefreshGraph(KisImage *image, KisNode *node, const QVector<QRect> &rects, const QRect &cropRect, KisProjectionUpdateFlags flags) override {
100 if (image->currentLevelOfDetail() > 0) return false;
101
102 QMutexLocker l(&m_mutex);
103
104 Q_FOREACH(const QRect &rc, rects) {
105 m_refreshesHash[KisNodeSP(node)].append(FullRefreshRequest(rc, cropRect, flags));
106 }
107
108 return true;
109 }
110
112 const int step = 64;
113
114 auto processRefreshes = [&] (bool noFilthyUpdates) {
115 RefreshesHash::const_iterator it = m_refreshesHash.constBegin();
116 RefreshesHash::const_iterator end = m_refreshesHash.constEnd();
117
118 for (; it != end; ++it) {
119 KisNodeSP node = it.key();
120
121 QHash<QRect, QVector<QRect>> fullRefreshRequests;
122
123 bool invalidateFrames = false;
124
125 Q_FOREACH (const FullRefreshRequest &req, it.value()) {
126 if (req.flags.testFlag(KisProjectionUpdateFlag::NoFilthy) == noFilthyUpdates) {
127 fullRefreshRequests[req.cropRect] += req.rect;
128 invalidateFrames |= !req.flags.testFlag(KisProjectionUpdateFlag::DontInvalidateFrames);
129 }
130 }
131
132 KisProjectionUpdateFlags finalFlags(KisProjectionUpdateFlag::None);
133 finalFlags.setFlag(KisProjectionUpdateFlag::DontInvalidateFrames, !invalidateFrames);
134 finalFlags.setFlag(KisProjectionUpdateFlag::NoFilthy, noFilthyUpdates);
135
136 auto reqIt = fullRefreshRequests.begin();
137 for (; reqIt != fullRefreshRequests.end(); ++reqIt) {
138 const QVector<QRect> simplifiedRects = KisRegion::fromOverlappingRects(reqIt.value(), step).rects();
139 image->refreshGraphAsync(node, simplifiedRects, reqIt.key(), finalFlags);
140 }
141 }
142 };
143
144 processRefreshes(false);
145 processRefreshes(true);
146
147 auto processUpdates = [&] (bool noFilthyUpdates) {
148 UpdatesHash::const_iterator it = m_requestsHash.constBegin();
149 UpdatesHash::const_iterator end = m_requestsHash.constEnd();
150
151 for (; it != end; ++it) {
152 KisNodeSP node = it.key();
153
154 QVector<QRect> dirtyRects;
155
156 bool invalidateFrames = false;
157
158 Q_FOREACH (const Request &req, it.value()) {
159 if (req.flags.testFlag(KisProjectionUpdateFlag::NoFilthy) == noFilthyUpdates) {
160 dirtyRects += req.rect;
161 invalidateFrames |= !req.flags.testFlag(KisProjectionUpdateFlag::DontInvalidateFrames);
162 }
163 }
164
165 KisProjectionUpdateFlags finalFlags(KisProjectionUpdateFlag::None);
166 finalFlags.setFlag(KisProjectionUpdateFlag::DontInvalidateFrames, !invalidateFrames);
167 finalFlags.setFlag(KisProjectionUpdateFlag::NoFilthy, noFilthyUpdates);
168
169 const QVector<QRect> simplifiedRects = KisRegion::fromOverlappingRects(dirtyRects, step).rects();
170
171 // FIXME: constness: port requestProjectionUpdate to shared pointers
172 image->requestProjectionUpdate(const_cast<KisNode*>(node.data()), simplifiedRects, finalFlags);
173 }
174 };
175
176 processUpdates(false);
177 processUpdates(true);
178 }
179
180 private:
184 QMutex m_mutex;
185 };
186
188
189
201
203 {
206 m_command(command)
207 {
208 }
209
210 void run() override {
212 m_command->redo();
213 }
214
215 std::unique_ptr<StrokeJobCommand> m_command;
216 };
217
218 // Suspend job should be a barrier to ensure all
219 // previous lodN strokes reach the GUI. Otherwise,
220 // they will be blocked in
221 // KisImage::notifyProjectionUpdated()
251
252
283
285 {
286 UploadDataToUIData(const QRect &rc, int updateEpoch, KisSuspendProjectionUpdatesStrokeStrategy *strategy)
288 m_strategy(strategy),
289 m_rc(rc),
290 m_updateEpoch(updateEpoch)
291 {
292 }
293
294 void run() override {
295 // check if we've already started stinking...
296 if (m_strategy->m_d->updatesEpoch > m_updateEpoch) {
297 return;
298 }
299
300 KisImageSP image = m_strategy->m_d->image.toStrongRef();
302
304 }
305
307 QRect m_rc;
309 };
310
330
332 {
336
337 void redo() override {
338 KisImageSP image = m_strategy->m_d->image.toStrongRef();
340
345 const QVector<QRect> totalDirtyRects =
346 image->enableUIUpdates() + m_strategy->m_d->accumulatedDirtyRects;
347
348 const QRect totalRect =
349 image->bounds() &
350 std::accumulate(totalDirtyRects.begin(), totalDirtyRects.end(), QRect(), std::bit_or<QRect>());
351
352 m_strategy->m_d->accumulatedDirtyRects =
354 totalDirtyRects,
356
358
360 Q_FOREACH (const QRect &rc, m_strategy->m_d->accumulatedDirtyRects) {
361 jobsData << new Private::UploadDataToUIData(rc, m_strategy->m_d->updatesEpoch, m_strategy);
362 }
363
365
366 }
367
368 void undo() override {
369 KisImageSP image = m_strategy->m_d->image.toStrongRef();
371
374 }
375
377 };
378
380 {
384
385 void redo() override {
386 KisImageSP image = m_strategy->m_d->image.toStrongRef();
388
390 m_strategy->m_d->sanityResumingFinished = true;
391 m_strategy->m_d->accumulatedDirtyRects.clear();
392 KIS_SAFE_ASSERT_RECOVER_NOOP(m_strategy->m_d->usedFilters.isEmpty());
393 }
394
395 void undo() override {
408 KIS_SAFE_ASSERT_RECOVER_NOOP(m_strategy->m_d->usedFilters.isEmpty());
409 KIS_SAFE_ASSERT_RECOVER_NOOP(m_strategy->m_d->accumulatedDirtyRects.isEmpty());
410
411 m_strategy->m_d->sanityResumingFinished = false;
412
413 KisImageSP image = m_strategy->m_d->image.toStrongRef();
415
417 }
418
420 };
421
422
424};
425
428 QLatin1String("suspend_stroke_strategy") :
429 QLatin1String("resume_stroke_strategy")),
430 m_d(new Private)
431{
432 m_d->image = image;
433 m_d->suspend = suspend;
434 m_d->sharedData = sharedData;
435
443 enableJob(JOB_INIT, true);
444
445 enableJob(JOB_DOSTROKE, true);
446 enableJob(JOB_CANCEL, true);
447
450
453}
454
459
461{
463
464 if (m_d->suspend) {
466 } else {
468 jobs << new Private::BlockUILodSync(true, this);
471 jobs << new Private::BlockUILodSync(false, this);
472 }
473
475}
476
515{
516 KisRunnableStrokeJobDataBase *runnable = dynamic_cast<KisRunnableStrokeJobDataBase*>(data);
517 if (runnable) {
518 runnable->run();
519
520 if (Private::UndoableData *undoable = dynamic_cast<Private::UndoableData*>(data)) {
521 Private::StrokeJobCommand *command = undoable->m_command.release();
522 m_d->executedCommands.append(command);
523 }
524 }
525}
526
531
536
541
543{
544 if (!this->sharedData->installedFilterCookie) return;
545
547 this->sharedData->installedFilterCookie = KisProjectionUpdatesFilterCookie();
548
550
552 filter.dynamicCast<Private::SuspendLod0Updates>();
553
555
556 this->usedFilters.append(localFilter);
557}
558
560{
561 Q_FOREACH (QSharedPointer<Private::SuspendLod0Updates> filter, usedFilters) {
562 filter->notifyUpdates(image.data());
563
564 if (!filter->explicitUIUpdateRequest().isEmpty()) {
565 accumulatedDirtyRects.append(filter->explicitUIUpdateRequest());
566 }
567 }
568 usedFilters.clear();
569}
570
572{
573 KisImageSP image = m_d->image.toStrongRef();
574 if (!image) {
575 return;
576 }
577
578 for (auto it = m_d->executedCommands.rbegin(); it != m_d->executedCommands.rend(); ++it) {
579 (*it)->undo();
580 }
581
582 m_d->tryFetchUsedUpdatesFilter(image);
583
584 if (m_d->haveDisabledGUILodSync) {
586 }
587
595 if (!m_d->suspend) {
596 // FIXME: optimize
597 image->refreshGraphAsync();
598 }
599}
600
602{
610 !m_d->sanityResumingFinished ||
611 (m_d->sanityResumingFinished &&
612 m_d->usedFilters.isEmpty() &&
613 m_d->accumulatedDirtyRects.isEmpty()));
614
615 for (auto it = m_d->executedCommands.rbegin(); it != m_d->executedCommands.rend(); ++it) {
616 (*it)->undo();
617 }
618
619 // reset all the issued updates
620 m_d->updatesEpoch++;
621}
622
624{
626
627 Q_FOREACH (Private::StrokeJobCommand *command, m_d->executedCommands) {
628 jobs << new Private::UndoableData(command);
629 }
630 m_d->executedCommands.clear();
631
633}
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)