Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_animation_frame_cache.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
9
10#include <QMap>
11
12#include "kis_debug.h"
13
14#include "kis_image.h"
16#include "kis_time_span.h"
17#include "KisPart.h"
19
23
24#include "kis_image_config.h"
25#include "kis_config_notifier.h"
26
28
29#include <kis_algebra_2d.h>
30#include <cmath>
31
32
34{
36 : textures(_textures)
37 {
38 image = textures->image();
39 }
40
42 {
43 }
44
47
48 QScopedPointer<KisAbstractFrameCacheSwapper> swapper;
49 int frameSizeLimit = 777;
50
51 KisOpenGLUpdateInfoSP fetchFrameDataImpl(KisImageSP image, const QRect &requestedRect, int lod);
52
62
63 QMap<int, int> newFrames;
64
65 int getFrameIdAtTime(int time) const
66 {
67 if (newFrames.isEmpty()) return -1;
68
69 auto it = newFrames.upperBound(time);
70
71 if (it != newFrames.constBegin()) it--;
72
74 const int start = it.key();
75 const int length = it.value();
76
77 bool foundFrameValid = false;
78
79 if (length == -1) {
80 if (start <= time) {
81 foundFrameValid = true;
82 }
83 } else {
84 int end = start + length - 1;
85 if (start <= time && time <= end) {
86 foundFrameValid = true;
87 }
88 }
89
90 return foundFrameValid ? start : -1;
91 }
92
93 bool hasFrame(int time) const {
94 return getFrameIdAtTime(time) >= 0;
95 }
96
98 {
99 const int frameId = getFrameIdAtTime(time);
100 return frameId >= 0 ? swapper->loadFrame(frameId) : 0;
101 }
102
104 {
105 invalidate(range);
106
107 const int length = range.isInfinite() ? -1 : range.end() - range.start() + 1;
108 newFrames.insert(range.start(), length);
109 swapper->saveFrame(range.start(), info, image->bounds());
110 }
111
117 bool invalidate(const KisTimeSpan& range)
118 {
119 if (newFrames.isEmpty()) return false;
120
121 bool cacheChanged = false;
122
123 auto it = newFrames.lowerBound(range.start());
124 if (it.key() != range.start() && it != newFrames.begin()) it--;
125
126 while (it != newFrames.end()) {
127 const int start = it.key();
128 const int length = it.value();
129 const bool frameIsInfinite = (length == -1);
130 const int end = start + length - 1;
131
132 if (start >= range.start()) {
133 if (!range.isInfinite() && start > range.end()) {
134 break;
135 }
136
137 if (!range.isInfinite() && (frameIsInfinite || end > range.end())) {
138 // Reinsert with a later start
139 int newStart = range.end() + 1;
140 int newLength = frameIsInfinite ? -1 : (end - newStart + 1);
141
142 newFrames.insert(newStart, newLength);
143 swapper->moveFrame(start, newStart);
144 } else {
145 swapper->forgetFrame(start);
146 }
147
148 it = newFrames.erase(it);
149
150 cacheChanged = true;
151 continue;
152
153 } else if (frameIsInfinite || end >= range.start()) {
154 const int newEnd = range.start() - 1;
155 *it = newEnd - start + 1;
156
157 cacheChanged = true;
158 }
159
160 it++;
161 }
162
163 return cacheChanged;
164 }
165
166 int effectiveLevelOfDetail(const QRect &rc) const {
167 if (!frameSizeLimit) return 0;
168
169 const int maxDimension = KisAlgebra2D::maxDimension(rc);
170
171 const qreal minLod = -std::log2(qreal(frameSizeLimit) / maxDimension);
172 const int lodLimit = qMax(0, qCeil(minLod));
173 return lodLimit;
174 }
175
176
177 // TODO: verify that we don't have any leak here!
178 typedef QMap<KisOpenGLImageTexturesSP, KisAnimationFrameCache*> CachesMap;
180};
181
183
185{
187
188 Private::CachesMap::iterator it = Private::caches.find(textures);
189 if (it == Private::caches.end()) {
190 cache = new KisAnimationFrameCache(textures);
191 Private::caches.insert(textures, cache);
192 } else {
193 cache = it.value();
194 }
195
196 return cache;
197}
198
203
205{
206 auto it = std::find_if(Private::caches.begin(), Private::caches.end(),
207 [image] (KisAnimationFrameCache *cache) { return cache->image() == image; });
208
209 return it != Private::caches.end() ? *it : nullptr;
210}
211
213 : m_d(new Private(textures))
214{
215 // create swapping backend
217
218 connect(m_d->image->animationInterface(), SIGNAL(sigFramesChanged(KisTimeSpan,QRect)), this, SLOT(framesChanged(KisTimeSpan,QRect)));
219 connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged()));
220}
221
226
228{
229 KisOpenGLUpdateInfoSP info = m_d->getFrame(time);
230
231 if (!info) {
232 // Do nothing!
233 //
234 // Previously we were trying to start cache regeneration in this point,
235 // but it caused even bigger slowdowns when scrubbing
236 } else {
237 m_d->textures->recalculateCache(info, false);
238 }
239
240 return bool(info);
241}
242
243bool KisAnimationFrameCache::shouldUploadNewFrame(int newTime, int oldTime) const
244{
245 if (oldTime < 0) return true;
246
247 const int oldKeyframeStart = m_d->getFrameIdAtTime(oldTime);
248 if (oldKeyframeStart < 0) return true;
249
250 const int oldKeyFrameLength = m_d->newFrames[oldKeyframeStart];
251 return !(newTime >= oldKeyframeStart && (newTime < oldKeyframeStart + oldKeyFrameLength || oldKeyFrameLength == -1));
252}
253
255{
256 return m_d->hasFrame(time) ? Cached : Uncached;
257}
258
262
264 bool framesChanged = false;
265
266 if (frames.isEmpty()) return framesChanged;
267
268 // find the first element, which `end` is greater or equal to `range.start()`
269 auto it = frames.begin();
270 for (; it != frames.end(); ++it) {
271 if (it.key() + it.value() - 1 >= range.start()) {
272 break;
273 }
274 }
275
276 if (it != frames.end()) {
277 if (it.key() > range.start()) {
278 // Reinsert with an earilier start
279 const int oldStart = it.key();
280 const int newStart = range.start();
281 const int newLength = range.isInfinite() ? -1 : range.duration();
282
283 it = frames.erase(it);
284 it = frames.insert(newStart, newLength);
285 this->moveFrame(oldStart, newStart);
286 framesChanged = true;
287 }
288
289 if (range.isInfinite()) {
290 it.value() = -1;
291 framesChanged = true;
292 } else if (it.value() != -1 && it.key() + it.value() - 1 < range.end()) {
293 it.value() = range.end() - it.key() + 1;
294 framesChanged = true;
295 }
296
297 it = std::next(it);
298
299 while (it != frames.end()) {
300 if (range.isInfinite() || (it.value() != -1 && it.key() + it.value() - 1 <= range.end())) {
301 this->forgetFrame(it.key());
302 it = frames.erase(it);
303 framesChanged = true;
304 } else if (it.key() > range.end()) {
305 break;
306 } else if (it.value() == -1 || it.key() + it.value() - 1 > range.end()) {
307 // Reinsert with a later start
308 int oldStart = it.key();
309 int newStart = range.end() + 1;
310 int newLength = it.value() == -1 ? -1 : (it.key() + it.value() - 1 - newStart + 1);
311
312 frames.erase(it);
313 frames.insert(newStart, newLength);
314 this->moveFrame(oldStart, newStart);
315 framesChanged = true;
316 break;
317 } else {
318 KIS_SAFE_ASSERT_RECOVER_BREAK(0 && "we should never get here");
319 }
320 }
321 }
322
323 return framesChanged;
324}
325
327{
328 struct FramesGluer : FramesGluerBase
329 {
330 KisAbstractFrameCacheSwapper *swapper {nullptr};
331
332 FramesGluer(KisAbstractFrameCacheSwapper *_swapper, QMap<int, int> &_frames)
333 : FramesGluerBase(_frames)
334 , swapper(_swapper)
335 {}
336
337 void moveFrame(int oldStart, int newStart) override {
338 swapper->moveFrame(oldStart, newStart);
339 }
340
341 void forgetFrame(int start) override{
342 swapper->forgetFrame(start);
343 }
344 };
345
346 FramesGluer gluer(m_d->swapper.data(), m_d->newFrames);
347
348 const bool cacheChanged = gluer.glueFrames(range);
349
350 if (cacheChanged) {
351 Q_EMIT changed();
352 }
353
354 return cacheChanged;
355}
356
358{
359 return m_d->image;
360}
361
363{
364 Q_UNUSED(rect);
365
366 if (!range.isValid()) return;
367
368 bool cacheChanged = m_d->invalidate(range);
369
370 if (cacheChanged) {
371 Q_EMIT changed();
372 }
373}
374
376{
377 m_d->newFrames.clear();
378
379 KisImageConfig cfg(true);
380
382 m_d->swapper.reset(new KisFrameCacheSwapper(m_d->textures->updateInfoBuilder(), cfg.swapDir()));
383 } else {
384 m_d->swapper.reset(new KisInMemoryFrameCacheSwapper());
385 }
386
387 m_d->frameSizeLimit = cfg.useAnimationCacheFrameSizeLimit() ? cfg.animationCacheFrameSizeLimit() : 0;
388 Q_EMIT changed();
389}
390
392{
393 if (lod > 0) {
395 tempDevice->prepareClone(image->projection());
396 image->projection()->generateLodCloneDevice(tempDevice, image->projection()->extent(), lod);
397
398 const QRect fetchRect = KisLodTransform::alignedRect(requestedRect, lod);
399 return textures->updateInfoBuilder().buildUpdateInfo(fetchRect, tempDevice, image->bounds(), lod, true);
400 } else {
401 return textures->updateCache(requestedRect, image);
402 }
403}
404
406{
407 if (time != image->animationInterface()->currentTime()) {
408 qWarning() << "WARNING: KisAnimationFrameCache::frameReady image's time doesn't coincide with the requested time!";
409 qWarning() << " " << ppVar(image->animationInterface()->currentTime()) << ppVar(time);
410 }
411
412 // the frames are always generated at full scale
414
415 const int lod = m_d->effectiveLevelOfDetail(requestedRegion.boundingRect());
416
417 KisOpenGLUpdateInfoSP totalInfo;
418
419 Q_FOREACH (const QRect &rc, requestedRegion.rects()) {
420 KisOpenGLUpdateInfoSP info = m_d->fetchFrameDataImpl(image, rc, lod);
421 if (!totalInfo) {
422 totalInfo = info;
423 } else {
424 const bool result = totalInfo->tryMergeWith(*info);
426 }
427 }
428
429 return totalInfo;
430}
431
433{
434 const KisTimeSpan identicalRange =
436
437 m_d->addFrame(info, identicalRange);
438
439 Q_EMIT changed();
440}
441
442void KisAnimationFrameCache::dropLowQualityFrames(const KisTimeSpan &range, const QRect &regionOfInterest, const QRect &minimalRect)
443{
445 if (m_d->newFrames.isEmpty()) return;
446
447 auto it = m_d->newFrames.upperBound(range.start());
448
449 // the vector is guaranteed to be non-empty,
450 // so decrementing iterator is safe
451 if (it != m_d->newFrames.begin()) it--;
452
453 while (it != m_d->newFrames.end() && it.key() <= range.end()) {
454 const int frameId = it.key();
455 const int frameLength = it.value();
456
457 if (frameId + frameLength - 1 < range.start()) {
458 ++it;
459 continue;
460 }
461
462 const QRect frameRect = m_d->swapper->frameDirtyRect(frameId);
463 const int frameLod = m_d->swapper->frameLevelOfDetail(frameId);
464
465 if (frameLod > m_d->effectiveLevelOfDetail(regionOfInterest) || !frameRect.contains(minimalRect)) {
466 m_d->swapper->forgetFrame(frameId);
467 it = m_d->newFrames.erase(it);
468 } else {
469 ++it;
470 }
471 }
472}
473
474bool KisAnimationFrameCache::framesHaveValidRoi(const KisTimeSpan &range, const QRect &regionOfInterest)
475{
477 if (m_d->newFrames.isEmpty()) return false;
478
479 auto it = m_d->newFrames.upperBound(range.start());
480
481 if (it != m_d->newFrames.begin()) it--;
482
483 int expectedNextFrameStart = it.key();
484
485 while (it.key() <= range.end()) {
486 const int frameId = it.key();
487 const int frameLength = it.value();
488
489 if (frameId + frameLength - 1 < range.start()) {
490 expectedNextFrameStart = frameId + frameLength;
491 ++it;
492 continue;
493 }
494
495 if (expectedNextFrameStart != frameId) {
496 KIS_SAFE_ASSERT_RECOVER_NOOP(expectedNextFrameStart < frameId);
497 return false;
498 }
499
500 if (!m_d->swapper->frameDirtyRect(frameId).contains(regionOfInterest)) {
501 return false;
502 }
503
504 expectedNextFrameStart = frameId + frameLength;
505 ++it;
506 }
507
508 return true;
509}
qreal length(const QPointF &vec)
Definition Ellipse.cc:82
connect(this, SIGNAL(optionsChanged()), this, SLOT(saveOptions()))
bool tryGlueSameFrames(const KisTimeSpan &range)
QScopedPointer< Private > m_d
static const QList< KisAnimationFrameCache * > caches()
CacheStatus frameStatus(int time) const
KisAnimationFrameCache(KisOpenGLImageTexturesSP textures)
KisOpenGLUpdateInfoSP fetchFrameData(int time, KisImageSP image, const KisRegion &requestedRegion) const
static KisAnimationFrameCacheSP getFrameCache(KisOpenGLImageTexturesSP textures)
void addConvertedFrameData(KisOpenGLUpdateInfoSP info, int time)
void dropLowQualityFrames(const KisTimeSpan &range, const QRect &regionOfInterest, const QRect &minimalRect)
bool framesHaveValidRoi(const KisTimeSpan &range, const QRect &regionOfInterest)
bool shouldUploadNewFrame(int newTime, int oldTime) const
static const KisAnimationFrameCacheSP cacheForImage(KisImageWSP image)
void framesChanged(const KisTimeSpan &range, const QRect &rect)
static KisConfigNotifier * instance()
bool useAnimationCacheFrameSizeLimit(bool defaultValue=false) const
int animationCacheFrameSizeLimit(bool defaultValue=false) const
QString swapDir(bool requestDefault=false)
bool useOnDiskAnimationCacheSwapping(bool defaultValue=false) const
KisImageAnimationInterface * animationInterface() const
KisPaintDeviceSP projection() const
int currentLevelOfDetail() const
QRect bounds() const override
static QRect alignedRect(const QRect &srcRect, int lod)
KisOpenGLUpdateInfoSP updateCache(const QRect &rect, KisImageSP srcImage)
KisOpenGLUpdateInfoBuilder & updateInfoBuilder()
bool tryMergeWith(const KisOpenGLUpdateInfo &rhs)
QRect extent() const
void generateLodCloneDevice(KisPaintDeviceSP dst, const QRect &originalRect, int lod)
const KoColorSpace * colorSpace() const
void prepareClone(KisPaintDeviceSP src)
QRect boundingRect() const
QVector< QRect > rects() const
int start() const
bool isInfinite() const
int duration() const
static KisTimeSpan calculateIdenticalFramesRecursive(const KisNode *node, int time)
int end() const
bool isValid() const
#define KIS_SAFE_ASSERT_RECOVER_BREAK(cond)
Definition kis_assert.h:127
#define KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(cond, val)
Definition kis_assert.h:129
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:128
#define KIS_SAFE_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:130
#define ppVar(var)
Definition kis_debug.h:155
auto maxDimension(Size size) -> decltype(size.width())
bool glueFrames(const KisTimeSpan &range)
virtual void moveFrame(int oldStart, int newStart)=0
virtual void forgetFrame(int start)=0
Frame(KisOpenGLUpdateInfoSP info, int length)
QScopedPointer< KisAbstractFrameCacheSwapper > swapper
bool invalidate(const KisTimeSpan &range)
QMap< KisOpenGLImageTexturesSP, KisAnimationFrameCache * > CachesMap
KisOpenGLUpdateInfoSP getFrame(int time)
Private(KisOpenGLImageTexturesSP _textures)
int effectiveLevelOfDetail(const QRect &rc) const
void addFrame(KisOpenGLUpdateInfoSP info, const KisTimeSpan &range)
KisOpenGLUpdateInfoSP fetchFrameDataImpl(KisImageSP image, const QRect &requestedRect, int lod)
KisOpenGLUpdateInfoSP buildUpdateInfo(const QRect &rect, KisImageSP srcImage, bool convertColorSpace)