Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_animation_cache_populator.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2015 Jouni Pentikäinen <joupent@gmail.com>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
8
9#include <functional>
10
11#include <QTimer>
12#include <QStack>
13
14#include "kis_config.h"
15#include "kis_config_notifier.h"
16#include "KisPart.h"
17#include "KisDocument.h"
18#include "kis_image.h"
20#include "kis_canvas2.h"
21#include "kis_time_span.h"
23#include "kis_update_info.h"
25#include "kis_idle_watcher.h"
26#include "KisViewManager.h"
27#include "kis_node_manager.h"
29#include "KisMainWindow.h"
30
34
35
37{
40
41 QTimer timer;
42
48 static const int IDLE_COUNT_THRESHOLD = 4;
49 static const int IDLE_CHECK_INTERVAL = 500;
50 static const int BETWEEN_FRAMES_INTERVAL = 10;
51
54
62
68
70 : q(_q),
71 part(_part),
72 idleCounter(0),
75 {
76 timer.setSingleShot(true);
77 }
78
79 void timerTimeout() {
80 switch (state) {
81 case WaitingForIdle:
82 case BetweenFrames:
84 break;
85 case WaitingForFrame:
86 KIS_ASSERT_RECOVER_NOOP(0 && "WaitingForFrame cannot have a timeout. Just skip this message and report a bug");
87 break;
89 KIS_ASSERT_RECOVER_NOOP(0 && "NotWaitingForAnything cannot have a timeout. Just skip this message and report a bug");
90 break;
91 }
92 }
93
95 {
96 if (part->idleWatcher()->isIdle()) {
98
101
102 if (result == RequestPostponed) {
104 } else if (result == RequestRejected) {
106 }
107
108 return;
109 }
110 } else {
111 idleCounter = 0;
112 }
113
115 }
116
117
119 {
120 while (!priorityFrames.isEmpty()) {
121 KisImageSP image = priorityFrames.top().first;
122 const int priorityFrame = priorityFrames.top().second;
123 priorityFrames.pop();
124
129 if (!image) continue;
130
131 if (!image->animationInterface()->hasAnimation()) continue;
133
140 if (!cache) {
141 // a small sanity check that our guess it right...
143 auto it = std::find_if(views.constBegin(), views.constEnd(),
144 [image] (QPointer<KisView> view) {
145 return view && KisImageSP(view->image()) == image;
146 });
147 const bool foundExistingViewForImage = it != views.constEnd();
148
149 KIS_SAFE_ASSERT_RECOVER(!foundExistingViewForImage) {
150 priorityFrames.erase(
151 std::remove_if(priorityFrames.begin(), priorityFrames.end(),
152 [image] (auto requestPair) {
153 return KisImageSP(requestPair.first) == image;
154 }), priorityFrames.end());
155 }
156
157 continue;
158 }
159
163 if (cache->frameStatus(priorityFrame) == KisAnimationFrameCache::Cached) {
164 continue;
165 }
166
168 tryRequestGeneration(cache, KisTimeSpan(), priorityFrame);
169 if (result == RequestSuccessful) return result;
170 }
171
172 // Prioritize the active document
173 KisAnimationFrameCacheSP activeDocumentCache = KisAnimationFrameCacheSP(0);
174
175 KisMainWindow *activeWindow = part->currentMainwindow();
176 if (activeWindow && activeWindow->activeView()) {
177 KisCanvas2 *activeCanvas = activeWindow->activeView()->canvasBase();
178
179 if (activeCanvas && activeCanvas->frameCache() &&
180 activeCanvas->image()->animationInterface()->hasAnimation()) {
181
182 activeDocumentCache = activeCanvas->frameCache();
183
184 // Let's skip frames affected by changes to the active node (on the active document)
185 // This avoids constant invalidation and regeneration while drawing
186 KisNodeSP activeNode = activeCanvas->viewManager()->nodeManager()->activeNode();
187 KisTimeSpan skipRange;
188 if (activeNode) {
189 const int currentTime = activeCanvas->currentImage()->animationInterface()->currentUITime();
190
191 if (!activeNode->keyframeChannels().isEmpty()) {
192 Q_FOREACH (const KisKeyframeChannel *channel, activeNode->keyframeChannels()) {
193 skipRange |= channel->affectedFrames(currentTime);
194 }
195 } else {
196 skipRange = KisTimeSpan::infinite(0);
197 }
198 }
199
201 tryRequestGeneration(activeDocumentCache, skipRange, -1);
202 if (result == RequestSuccessful) return result;
203 }
204 }
205
208 Q_FOREACH (cache, caches) {
209 if (cache == activeDocumentCache.data()) {
210 // We already handled this one...
211 continue;
212 }
213
214 if (!cache->image()->animationInterface()->hasAnimation()) {
215 // This image is not animated
216 continue;
217 }
218
220 tryRequestGeneration(cache, KisTimeSpan(), -1);
221 if (result == RequestSuccessful) return result;
222 }
223
224 return RequestRejected;
225 }
226
228 {
229 KisImageSP image = cache->image();
230 if (!image) return RequestRejected;
231
233
234 if (animation->backgroundFrameGenerationBlocked()) {
235 return RequestPostponed;
236 }
237
238 // the higher levels of code should have caught the case when
239 // the image is not animated
241
242 KisTimeSpan currentRange = animation->documentPlaybackRange();
243
244 const int frame = priorityFrame >= 0 ? priorityFrame : KisAsyncAnimationCacheRenderDialog::calcFirstDirtyFrame(cache, currentRange, skipRange);
245
246 if (frame >= 0) {
247 return regenerate(cache, frame);
248 }
249
250 return RequestRejected;
251 }
252
254 {
255 if (state == WaitingForFrame) {
256 // Already busy, deny request
257 return RequestRejected;
258 }
259
260 KisLockFrameGenerationLock lock(cache->image()->animationInterface(), std::try_to_lock);
261
262 if (!lock.owns_lock()) {
263 return RequestPostponed;
264 }
265
272
274
275 // if we ever decide to add ROI to background cache
276 // regeneration, it should be added here :)
278
279 return RequestSuccessful;
280 }
281
282 QString debugStateToString(State newState) {
283 QString str = "<unknown>";
284
285 switch (newState) {
286 case WaitingForIdle:
287 str = "WaitingForIdle";
288 break;
289 case WaitingForFrame:
290 str = "WaitingForFrame";
291 break;
293 str = "NotWaitingForAnything";
294 break;
295 case BetweenFrames:
296 str = "BetweenFrames";
297 break;
298 }
299
300 return str;
301 }
302
303 void enterState(State newState)
304 {
305 //ENTER_FUNCTION() << debugStateToString(state) << "->" << debugStateToString(newState);
306
307 state = newState;
308 int timerTimeout = -1;
309
310 switch (state) {
311 case WaitingForIdle:
313 break;
314 case WaitingForFrame:
315 // the timeout is handled by the regenerator now
316 timerTimeout = -1;
317 break;
319 // frame conversion cannot be cancelled,
320 // so there is no timeout
321 timerTimeout = -1;
322 break;
323 case BetweenFrames:
325 break;
326 }
327
328 if (timerTimeout >= 0) {
329 timer.start(timerTimeout);
330 } else {
331 timer.stop();
332 }
333 }
334};
335
337 : m_d(new Private(this, part))
338{
339 connect(&m_d->timer, SIGNAL(timeout()), this, SLOT(slotTimer()));
340
341 connect(&m_d->regenerator, SIGNAL(sigFrameCancelled(int, KisAsyncAnimationRendererBase::CancelReason)), SLOT(slotRegeneratorFrameCancelled()));
342 connect(&m_d->regenerator, SIGNAL(sigFrameCompleted(int)), SLOT(slotRegeneratorFrameReady()));
343
344 connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged()));
346}
347
349{
350 m_d->priorityFrames.clear();
351}
352
354{
355 return m_d->regenerate(cache, frame);
356}
357
359{
360 if (!m_d->calculateAnimationCacheInBackground) return;
361 if (!KisAnimationFrameCache::cacheForImage(image)) return;
362 if (!image->animationInterface()->hasAnimation()) return;
363
364 m_d->priorityFrames.append(qMakePair(image, frameIndex));
365
366 if (m_d->state == Private::NotWaitingForAnything) {
367 m_d->generateIfIdle();
368 }
369}
370
372{
373 m_d->timerTimeout();
374}
375
377{
378 // skip if the user forbade background regeneration
379 if (!m_d->calculateAnimationCacheInBackground) return;
380
381 m_d->enterState(Private::WaitingForIdle);
382}
383
389
394
396{
397 KisConfig cfg(true);
398 m_d->calculateAnimationCacheInBackground = cfg.calculateAnimationCacheInBackground();
399 QTimer::singleShot(1000, Qt::CoarseTimer, this, SLOT(slotRequestRegeneration()));
400}
quint64 part(quint64 n1, quint64 n2, int p)
connect(this, SIGNAL(optionsChanged()), this, SLOT(saveOptions()))
void requestRegenerationWithPriorityFrame(KisImageSP image, int frameIndex)
bool regenerate(KisAnimationFrameCacheSP cache, int frame)
static const QList< KisAnimationFrameCache * > caches()
CacheStatus frameStatus(int time) const
static const KisAnimationFrameCacheSP cacheForImage(KisImageWSP image)
static int calcFirstDirtyFrame(KisAnimationFrameCacheSP cache, const KisTimeSpan &playbackRange, const KisTimeSpan &skipRange)
void setFrameCache(KisAnimationFrameCacheSP cache)
KisAnimationFrameCacheSP frameCache
KisImageWSP currentImage() const
KisImageWSP image() const
KisViewManager * viewManager() const
static KisConfigNotifier * instance()
bool calculateAnimationCacheInBackground(bool defaultValue=false) const
bool isIdle() const
const KisTimeSpan & documentPlaybackRange() const
documentPlaybackRange
KisImageAnimationInterface * animationInterface() const
KisKeyframeChannel stores and manages KisKeyframes. Maps units of time to virtual keyframe values....
virtual KisTimeSpan affectedFrames(int time) const
Get the set of frames affected by any changes to the value or content of the active keyframe at the g...
Main window for Krita.
QPointer< KisView > activeView
KisNodeSP activeNode()
Convenience function to get the active layer or mask.
static KisPart * instance()
Definition KisPart.cpp:131
KisMainWindow * currentMainwindow() const
Definition KisPart.cpp:483
KisIdleWatcher idleWatcher
Definition KisPart.cpp:109
QList< QPointer< KisView > > views
Definition KisPart.cpp:106
static KisTimeSpan infinite(int start)
KisNodeManager * nodeManager() const
The node manager handles everything about nodes.
#define KIS_SAFE_ASSERT_RECOVER(cond)
Definition kis_assert.h:126
#define KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(cond, val)
Definition kis_assert.h:129
#define KIS_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:75
#define KIS_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:97
KisSharedPtr< KisAnimationFrameCache > KisAnimationFrameCacheSP
Definition kis_types.h:185
RegenerationRequestResult tryRequestGeneration(KisAnimationFrameCacheSP cache, KisTimeSpan skipRange, int priorityFrame)
QStack< QPair< KisImageWSP, int > > priorityFrames
RegenerationRequestResult regenerate(KisAnimationFrameCacheSP cache, int frame)
Private(KisAnimationCachePopulator *_q, KisPart *_part)
void startFrameRegeneration(KisImageSP image, int frame, const KisRegion &regionOfInterest, Flags flags, KisLockFrameGenerationLock &&frameGenerationLock)
QMap< QString, KisKeyframeChannel * > keyframeChannels