1 /*
2  *  Copyright (c) 2015 Jouni Pentikäinen <joupent@gmail.com>
3  *
4  *  This program is free software; you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation; either version 2 of the License, or
7  *  (at your option) any later version.
8  *
9  *  This program is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License
15  *  along with this program; if not, write to the Free Software
16  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  */
18 
19 #include "kis_animation_frame_cache.h"
20 
21 #include <QMap>
22 
23 #include "kis_debug.h"
24 
25 #include "kis_image.h"
26 #include "kis_image_animation_interface.h"
27 #include "kis_time_range.h"
28 #include "KisPart.h"
29 #include "kis_animation_cache_populator.h"
30 
31 #include <KisAbstractFrameCacheSwapper.h>
32 #include "KisFrameCacheSwapper.h"
33 #include "KisInMemoryFrameCacheSwapper.h"
34 
35 #include "kis_image_config.h"
36 #include "kis_config_notifier.h"
37 
38 #include "opengl/kis_opengl_image_textures.h"
39 
40 #include <kis_algebra_2d.h>
41 #include <cmath>
42 
43 
44 struct KisAnimationFrameCache::Private
45 {
PrivateKisAnimationFrameCache::Private46     Private(KisOpenGLImageTexturesSP _textures)
47         : textures(_textures)
48     {
49         image = textures->image();
50     }
51 
~PrivateKisAnimationFrameCache::Private52     ~Private()
53     {
54     }
55 
56     KisOpenGLImageTexturesSP textures;
57     KisImageWSP image;
58 
59     QScopedPointer<KisAbstractFrameCacheSwapper> swapper;
60     int frameSizeLimit = 777;
61 
62     KisOpenGLUpdateInfoSP fetchFrameDataImpl(KisImageSP image, const QRect &requestedRect, int lod);
63 
64     struct Frame
65     {
66         KisOpenGLUpdateInfoSP openGlFrame;
67         int length;
68 
FrameKisAnimationFrameCache::Private::Frame69         Frame(KisOpenGLUpdateInfoSP info, int length)
70             : openGlFrame(info), length(length)
71         {}
72     };
73 
74     QMap<int, int> newFrames;
75 
getFrameIdAtTimeKisAnimationFrameCache::Private76     int getFrameIdAtTime(int time) const
77     {
78         if (newFrames.isEmpty()) return -1;
79 
80         auto it = newFrames.upperBound(time);
81 
82         if (it != newFrames.constBegin()) it--;
83 
84         KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(it != newFrames.constEnd(), 0);
85         const int start = it.key();
86         const int length = it.value();
87 
88         bool foundFrameValid = false;
89 
90         if (length == -1) {
91             if (start <= time) {
92                 foundFrameValid = true;
93             }
94         } else {
95             int end = start + length - 1;
96             if (start <= time && time <= end) {
97                 foundFrameValid = true;
98             }
99         }
100 
101         return foundFrameValid ? start : -1;
102     }
103 
hasFrameKisAnimationFrameCache::Private104     bool hasFrame(int time) const {
105         return getFrameIdAtTime(time) >= 0;
106     }
107 
getFrameKisAnimationFrameCache::Private108     KisOpenGLUpdateInfoSP getFrame(int time)
109     {
110         const int frameId = getFrameIdAtTime(time);
111         return frameId >= 0 ? swapper->loadFrame(frameId) : 0;
112     }
113 
addFrameKisAnimationFrameCache::Private114     void addFrame(KisOpenGLUpdateInfoSP info, const KisTimeRange& range)
115     {
116         invalidate(range);
117 
118         const int length = range.isInfinite() ? -1 : range.end() - range.start() + 1;
119         newFrames.insert(range.start(), length);
120         swapper->saveFrame(range.start(), info, image->bounds());
121     }
122 
123     /**
124      * Invalidate any cached frames within the given time range.
125      * @param range
126      * @return true if frames were invalidated, false if nothing was changed
127      */
invalidateKisAnimationFrameCache::Private128     bool invalidate(const KisTimeRange& range)
129     {
130         if (newFrames.isEmpty()) return false;
131 
132         bool cacheChanged = false;
133 
134         auto it = newFrames.lowerBound(range.start());
135         if (it.key() != range.start() && it != newFrames.begin()) it--;
136 
137         while (it != newFrames.end()) {
138             const int start = it.key();
139             const int length = it.value();
140             const bool frameIsInfinite = (length == -1);
141             const int end = start + length - 1;
142 
143             if (start >= range.start()) {
144                 if (!range.isInfinite() && start > range.end()) {
145                     break;
146                 }
147 
148                 if (!range.isInfinite() && (frameIsInfinite || end > range.end())) {
149                     // Reinsert with a later start
150                     int newStart = range.end() + 1;
151                     int newLength = frameIsInfinite ? -1 : (end - newStart + 1);
152 
153                     newFrames.insert(newStart, newLength);
154                     swapper->moveFrame(start, newStart);
155                 } else {
156                     swapper->forgetFrame(start);
157                 }
158 
159                 it = newFrames.erase(it);
160 
161                 cacheChanged = true;
162                 continue;
163 
164             } else if (frameIsInfinite || end >= range.start()) {
165                 const int newEnd = range.start() - 1;
166                 *it = newEnd - start + 1;
167 
168                 cacheChanged = true;
169             }
170 
171             it++;
172         }
173 
174         return cacheChanged;
175     }
176 
effectiveLevelOfDetailKisAnimationFrameCache::Private177     int effectiveLevelOfDetail(const QRect &rc) const {
178         if (!frameSizeLimit) return 0;
179 
180         const int maxDimension = KisAlgebra2D::maxDimension(rc);
181 
182         const qreal minLod = -std::log2(qreal(frameSizeLimit) / maxDimension);
183         const int lodLimit = qMax(0, qCeil(minLod));
184         return lodLimit;
185     }
186 
187 
188     // TODO: verify that we don't have any leak here!
189     typedef QMap<KisOpenGLImageTexturesSP, KisAnimationFrameCache*> CachesMap;
190     static CachesMap caches;
191 };
192 
193 KisAnimationFrameCache::Private::CachesMap KisAnimationFrameCache::Private::caches;
194 
getFrameCache(KisOpenGLImageTexturesSP textures)195 KisAnimationFrameCacheSP KisAnimationFrameCache::getFrameCache(KisOpenGLImageTexturesSP textures)
196 {
197     KisAnimationFrameCache *cache;
198 
199     Private::CachesMap::iterator it = Private::caches.find(textures);
200     if (it == Private::caches.end()) {
201         cache = new KisAnimationFrameCache(textures);
202         Private::caches.insert(textures, cache);
203     } else {
204         cache = it.value();
205     }
206 
207     return cache;
208 }
209 
caches()210 const QList<KisAnimationFrameCache *> KisAnimationFrameCache::caches()
211 {
212     return Private::caches.values();
213 }
214 
cacheForImage(KisImageWSP image)215 const KisAnimationFrameCacheSP KisAnimationFrameCache::cacheForImage(KisImageWSP image)
216 {
217     auto it = std::find_if(Private::caches.begin(), Private::caches.end(),
218                            [image] (KisAnimationFrameCache *cache) { return cache->image() == image; });
219 
220     return it != Private::caches.end() ? *it : nullptr;
221 }
222 
KisAnimationFrameCache(KisOpenGLImageTexturesSP textures)223 KisAnimationFrameCache::KisAnimationFrameCache(KisOpenGLImageTexturesSP textures)
224     : m_d(new Private(textures))
225 {
226     // create swapping backend
227     slotConfigChanged();
228 
229     connect(m_d->image->animationInterface(), SIGNAL(sigFramesChanged(KisTimeRange,QRect)), this, SLOT(framesChanged(KisTimeRange,QRect)));
230     connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged()));
231 }
232 
~KisAnimationFrameCache()233 KisAnimationFrameCache::~KisAnimationFrameCache()
234 {
235     Private::caches.remove(m_d->textures);
236 }
237 
uploadFrame(int time)238 bool KisAnimationFrameCache::uploadFrame(int time)
239 {
240     KisOpenGLUpdateInfoSP info = m_d->getFrame(time);
241 
242     if (!info) {
243         // Do nothing!
244         //
245         // Previously we were trying to start cache regeneration in this point,
246         // but it caused even bigger slowdowns when scrubbing
247     } else {
248         m_d->textures->recalculateCache(info, false);
249     }
250 
251     return bool(info);
252 }
253 
shouldUploadNewFrame(int newTime,int oldTime) const254 bool KisAnimationFrameCache::shouldUploadNewFrame(int newTime, int oldTime) const
255 {
256     if (oldTime < 0) return true;
257 
258     const int oldKeyframeStart = m_d->getFrameIdAtTime(oldTime);
259     if (oldKeyframeStart < 0) return true;
260 
261     const int oldKeyFrameLength = m_d->newFrames[oldKeyframeStart];
262     return !(newTime >= oldKeyframeStart && (newTime < oldKeyframeStart + oldKeyFrameLength || oldKeyFrameLength == -1));
263 }
264 
frameStatus(int time) const265 KisAnimationFrameCache::CacheStatus KisAnimationFrameCache::frameStatus(int time) const
266 {
267     return m_d->hasFrame(time) ? Cached : Uncached;
268 }
269 
image()270 KisImageWSP KisAnimationFrameCache::image()
271 {
272     return m_d->image;
273 }
274 
framesChanged(const KisTimeRange & range,const QRect & rect)275 void KisAnimationFrameCache::framesChanged(const KisTimeRange &range, const QRect &rect)
276 {
277     Q_UNUSED(rect);
278 
279     if (!range.isValid()) return;
280 
281     bool cacheChanged = m_d->invalidate(range);
282 
283     if (cacheChanged) {
284         emit changed();
285     }
286 }
287 
slotConfigChanged()288 void KisAnimationFrameCache::slotConfigChanged()
289 {
290     m_d->newFrames.clear();
291 
292     KisImageConfig cfg(true);
293 
294     if (cfg.useOnDiskAnimationCacheSwapping()) {
295         m_d->swapper.reset(new KisFrameCacheSwapper(m_d->textures->updateInfoBuilder(), cfg.swapDir()));
296     } else {
297         m_d->swapper.reset(new KisInMemoryFrameCacheSwapper());
298     }
299 
300     m_d->frameSizeLimit = cfg.useAnimationCacheFrameSizeLimit() ? cfg.animationCacheFrameSizeLimit() : 0;
301     emit changed();
302 }
303 
fetchFrameDataImpl(KisImageSP image,const QRect & requestedRect,int lod)304 KisOpenGLUpdateInfoSP KisAnimationFrameCache::Private::fetchFrameDataImpl(KisImageSP image, const QRect &requestedRect, int lod)
305 {
306     if (lod > 0) {
307         KisPaintDeviceSP tempDevice = new KisPaintDevice(image->projection()->colorSpace());
308         tempDevice->prepareClone(image->projection());
309         image->projection()->generateLodCloneDevice(tempDevice, image->projection()->extent(), lod);
310 
311         const QRect fetchRect = KisLodTransform::alignedRect(requestedRect, lod);
312         return textures->updateInfoBuilder().buildUpdateInfo(fetchRect, tempDevice, image->bounds(), lod, true);
313     } else {
314         return textures->updateCache(requestedRect, image);
315     }
316 }
317 
fetchFrameData(int time,KisImageSP image,const KisRegion & requestedRegion) const318 KisOpenGLUpdateInfoSP KisAnimationFrameCache::fetchFrameData(int time, KisImageSP image, const KisRegion &requestedRegion) const
319 {
320     if (time != image->animationInterface()->currentTime()) {
321         qWarning() << "WARNING: KisAnimationFrameCache::frameReady image's time doesn't coincide with the requested time!";
322         qWarning() << "    "  << ppVar(image->animationInterface()->currentTime()) << ppVar(time);
323     }
324 
325     // the frames are always generated at full scale
326     KIS_SAFE_ASSERT_RECOVER_NOOP(image->currentLevelOfDetail() == 0);
327 
328     const int lod = m_d->effectiveLevelOfDetail(requestedRegion.boundingRect());
329 
330     KisOpenGLUpdateInfoSP totalInfo;
331 
332     Q_FOREACH (const QRect &rc, requestedRegion.rects()) {
333         KisOpenGLUpdateInfoSP info = m_d->fetchFrameDataImpl(image, rc, lod);
334         if (!totalInfo) {
335             totalInfo = info;
336         } else {
337             const bool result = totalInfo->tryMergeWith(*info);
338             KIS_SAFE_ASSERT_RECOVER_NOOP(result);
339         }
340     }
341 
342     return totalInfo;
343 }
344 
addConvertedFrameData(KisOpenGLUpdateInfoSP info,int time)345 void KisAnimationFrameCache::addConvertedFrameData(KisOpenGLUpdateInfoSP info, int time)
346 {
347     const KisTimeRange identicalRange =
348         KisTimeRange::calculateIdenticalFramesRecursive(m_d->image->root(), time);
349 
350     m_d->addFrame(info, identicalRange);
351 
352     emit changed();
353 }
354 
dropLowQualityFrames(const KisTimeRange & range,const QRect & regionOfInterest,const QRect & minimalRect)355 void KisAnimationFrameCache::dropLowQualityFrames(const KisTimeRange &range, const QRect &regionOfInterest, const QRect &minimalRect)
356 {
357     KIS_SAFE_ASSERT_RECOVER_RETURN(!range.isInfinite());
358     if (m_d->newFrames.isEmpty()) return;
359 
360     auto it = m_d->newFrames.upperBound(range.start());
361 
362     // the vector is guaranteed to be non-empty,
363     // so decrementing iterator is safe
364     if (it != m_d->newFrames.begin()) it--;
365 
366     while (it != m_d->newFrames.end() && it.key() <= range.end()) {
367         const int frameId = it.key();
368         const int frameLength = it.value();
369 
370         if (frameId + frameLength - 1 < range.start()) {
371             ++it;
372             continue;
373         }
374 
375         const QRect frameRect = m_d->swapper->frameDirtyRect(frameId);
376         const int frameLod = m_d->swapper->frameLevelOfDetail(frameId);
377 
378         if (frameLod > m_d->effectiveLevelOfDetail(regionOfInterest) || !frameRect.contains(minimalRect)) {
379             m_d->swapper->forgetFrame(frameId);
380             it = m_d->newFrames.erase(it);
381         } else {
382             ++it;
383         }
384     }
385 }
386 
framesHaveValidRoi(const KisTimeRange & range,const QRect & regionOfInterest)387 bool KisAnimationFrameCache::framesHaveValidRoi(const KisTimeRange &range, const QRect &regionOfInterest)
388 {
389     KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!range.isInfinite(), false);
390     if (m_d->newFrames.isEmpty()) return false;
391 
392     auto it = m_d->newFrames.upperBound(range.start());
393 
394     if (it != m_d->newFrames.begin()) it--;
395 
396     int expectedNextFrameStart = it.key();
397 
398     while (it.key() <= range.end()) {
399         const int frameId = it.key();
400         const int frameLength = it.value();
401 
402         if (frameId + frameLength - 1 < range.start()) {
403             expectedNextFrameStart = frameId + frameLength;
404             ++it;
405             continue;
406         }
407 
408         if (expectedNextFrameStart != frameId) {
409             KIS_SAFE_ASSERT_RECOVER_NOOP(expectedNextFrameStart < frameId);
410             return false;
411         }
412 
413         if (!m_d->swapper->frameDirtyRect(frameId).contains(regionOfInterest)) {
414             return false;
415         }
416 
417         expectedNextFrameStart = frameId + frameLength;
418         ++it;
419     }
420 
421     return true;
422 }
423