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 ®ionOfInterest, 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 ®ionOfInterest)
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