File indexing completed on 2024-05-12 16:01:25

0001 /*
0002  *  SPDX-FileCopyrightText: 2015 Jouni Pentikäinen <joupent@gmail.com>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "kis_animation_frame_cache.h"
0008 
0009 #include <QMap>
0010 
0011 #include "kis_debug.h"
0012 
0013 #include "kis_image.h"
0014 #include "kis_image_animation_interface.h"
0015 #include "kis_time_span.h"
0016 #include "KisPart.h"
0017 #include "kis_animation_cache_populator.h"
0018 
0019 #include <KisAbstractFrameCacheSwapper.h>
0020 #include "KisFrameCacheSwapper.h"
0021 #include "KisInMemoryFrameCacheSwapper.h"
0022 
0023 #include "kis_image_config.h"
0024 #include "kis_config_notifier.h"
0025 
0026 #include "opengl/kis_opengl_image_textures.h"
0027 
0028 #include <kis_algebra_2d.h>
0029 #include <cmath>
0030 
0031 
0032 struct KisAnimationFrameCache::Private
0033 {
0034     Private(KisOpenGLImageTexturesSP _textures)
0035         : textures(_textures)
0036     {
0037         image = textures->image();
0038     }
0039 
0040     ~Private()
0041     {
0042     }
0043 
0044     KisOpenGLImageTexturesSP textures;
0045     KisImageWSP image;
0046 
0047     QScopedPointer<KisAbstractFrameCacheSwapper> swapper;
0048     int frameSizeLimit = 777;
0049 
0050     KisOpenGLUpdateInfoSP fetchFrameDataImpl(KisImageSP image, const QRect &requestedRect, int lod);
0051 
0052     struct Frame
0053     {
0054         KisOpenGLUpdateInfoSP openGlFrame;
0055         int length;
0056 
0057         Frame(KisOpenGLUpdateInfoSP info, int length)
0058             : openGlFrame(info), length(length)
0059         {}
0060     };
0061 
0062     QMap<int, int> newFrames;
0063 
0064     int getFrameIdAtTime(int time) const
0065     {
0066         if (newFrames.isEmpty()) return -1;
0067 
0068         auto it = newFrames.upperBound(time);
0069 
0070         if (it != newFrames.constBegin()) it--;
0071 
0072         KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(it != newFrames.constEnd(), 0);
0073         const int start = it.key();
0074         const int length = it.value();
0075 
0076         bool foundFrameValid = false;
0077 
0078         if (length == -1) {
0079             if (start <= time) {
0080                 foundFrameValid = true;
0081             }
0082         } else {
0083             int end = start + length - 1;
0084             if (start <= time && time <= end) {
0085                 foundFrameValid = true;
0086             }
0087         }
0088 
0089         return foundFrameValid ? start : -1;
0090     }
0091 
0092     bool hasFrame(int time) const {
0093         return getFrameIdAtTime(time) >= 0;
0094     }
0095 
0096     KisOpenGLUpdateInfoSP getFrame(int time)
0097     {
0098         const int frameId = getFrameIdAtTime(time);
0099         return frameId >= 0 ? swapper->loadFrame(frameId) : 0;
0100     }
0101 
0102     void addFrame(KisOpenGLUpdateInfoSP info, const KisTimeSpan& range)
0103     {
0104         invalidate(range);
0105 
0106         const int length = range.isInfinite() ? -1 : range.end() - range.start() + 1;
0107         newFrames.insert(range.start(), length);
0108         swapper->saveFrame(range.start(), info, image->bounds());
0109     }
0110 
0111     /**
0112      * Invalidate any cached frames within the given time range.
0113      * @param range
0114      * @return true if frames were invalidated, false if nothing was changed
0115      */
0116     bool invalidate(const KisTimeSpan& range)
0117     {
0118         if (newFrames.isEmpty()) return false;
0119 
0120         bool cacheChanged = false;
0121 
0122         auto it = newFrames.lowerBound(range.start());
0123         if (it.key() != range.start() && it != newFrames.begin()) it--;
0124 
0125         while (it != newFrames.end()) {
0126             const int start = it.key();
0127             const int length = it.value();
0128             const bool frameIsInfinite = (length == -1);
0129             const int end = start + length - 1;
0130 
0131             if (start >= range.start()) {
0132                 if (!range.isInfinite() && start > range.end()) {
0133                     break;
0134                 }
0135 
0136                 if (!range.isInfinite() && (frameIsInfinite || end > range.end())) {
0137                     // Reinsert with a later start
0138                     int newStart = range.end() + 1;
0139                     int newLength = frameIsInfinite ? -1 : (end - newStart + 1);
0140 
0141                     newFrames.insert(newStart, newLength);
0142                     swapper->moveFrame(start, newStart);
0143                 } else {
0144                     swapper->forgetFrame(start);
0145                 }
0146 
0147                 it = newFrames.erase(it);
0148 
0149                 cacheChanged = true;
0150                 continue;
0151 
0152             } else if (frameIsInfinite || end >= range.start()) {
0153                 const int newEnd = range.start() - 1;
0154                 *it = newEnd - start + 1;
0155 
0156                 cacheChanged = true;
0157             }
0158 
0159             it++;
0160         }
0161 
0162         return cacheChanged;
0163     }
0164 
0165     int effectiveLevelOfDetail(const QRect &rc) const {
0166         if (!frameSizeLimit) return 0;
0167 
0168         const int maxDimension = KisAlgebra2D::maxDimension(rc);
0169 
0170         const qreal minLod = -std::log2(qreal(frameSizeLimit) / maxDimension);
0171         const int lodLimit = qMax(0, qCeil(minLod));
0172         return lodLimit;
0173     }
0174 
0175 
0176     // TODO: verify that we don't have any leak here!
0177     typedef QMap<KisOpenGLImageTexturesSP, KisAnimationFrameCache*> CachesMap;
0178     static CachesMap caches;
0179 };
0180 
0181 KisAnimationFrameCache::Private::CachesMap KisAnimationFrameCache::Private::caches;
0182 
0183 KisAnimationFrameCacheSP KisAnimationFrameCache::getFrameCache(KisOpenGLImageTexturesSP textures)
0184 {
0185     KisAnimationFrameCache *cache;
0186 
0187     Private::CachesMap::iterator it = Private::caches.find(textures);
0188     if (it == Private::caches.end()) {
0189         cache = new KisAnimationFrameCache(textures);
0190         Private::caches.insert(textures, cache);
0191     } else {
0192         cache = it.value();
0193     }
0194 
0195     return cache;
0196 }
0197 
0198 const QList<KisAnimationFrameCache *> KisAnimationFrameCache::caches()
0199 {
0200     return Private::caches.values();
0201 }
0202 
0203 const KisAnimationFrameCacheSP KisAnimationFrameCache::cacheForImage(KisImageWSP image)
0204 {
0205     auto it = std::find_if(Private::caches.begin(), Private::caches.end(),
0206                            [image] (KisAnimationFrameCache *cache) { return cache->image() == image; });
0207 
0208     return it != Private::caches.end() ? *it : nullptr;
0209 }
0210 
0211 KisAnimationFrameCache::KisAnimationFrameCache(KisOpenGLImageTexturesSP textures)
0212     : m_d(new Private(textures))
0213 {
0214     // create swapping backend
0215     slotConfigChanged();
0216 
0217     connect(m_d->image->animationInterface(), SIGNAL(sigFramesChanged(KisTimeSpan,QRect)), this, SLOT(framesChanged(KisTimeSpan,QRect)));
0218     connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged()));
0219 }
0220 
0221 KisAnimationFrameCache::~KisAnimationFrameCache()
0222 {
0223     Private::caches.remove(m_d->textures);
0224 }
0225 
0226 bool KisAnimationFrameCache::uploadFrame(int time)
0227 {
0228     KisOpenGLUpdateInfoSP info = m_d->getFrame(time);
0229 
0230     if (!info) {
0231         // Do nothing!
0232         //
0233         // Previously we were trying to start cache regeneration in this point,
0234         // but it caused even bigger slowdowns when scrubbing
0235     } else {
0236         m_d->textures->recalculateCache(info, false);
0237     }
0238 
0239     return bool(info);
0240 }
0241 
0242 bool KisAnimationFrameCache::shouldUploadNewFrame(int newTime, int oldTime) const
0243 {
0244     if (oldTime < 0) return true;
0245 
0246     const int oldKeyframeStart = m_d->getFrameIdAtTime(oldTime);
0247     if (oldKeyframeStart < 0) return true;
0248 
0249     const int oldKeyFrameLength = m_d->newFrames[oldKeyframeStart];
0250     return !(newTime >= oldKeyframeStart && (newTime < oldKeyframeStart + oldKeyFrameLength || oldKeyFrameLength == -1));
0251 }
0252 
0253 KisAnimationFrameCache::CacheStatus KisAnimationFrameCache::frameStatus(int time) const
0254 {
0255     return m_d->hasFrame(time) ? Cached : Uncached;
0256 }
0257 
0258 KisImageWSP KisAnimationFrameCache::image()
0259 {
0260     return m_d->image;
0261 }
0262 
0263 void KisAnimationFrameCache::framesChanged(const KisTimeSpan &range, const QRect &rect)
0264 {
0265     Q_UNUSED(rect);
0266 
0267     if (!range.isValid()) return;
0268 
0269     bool cacheChanged = m_d->invalidate(range);
0270 
0271     if (cacheChanged) {
0272         emit changed();
0273     }
0274 }
0275 
0276 void KisAnimationFrameCache::slotConfigChanged()
0277 {
0278     m_d->newFrames.clear();
0279 
0280     KisImageConfig cfg(true);
0281 
0282     if (cfg.useOnDiskAnimationCacheSwapping()) {
0283         m_d->swapper.reset(new KisFrameCacheSwapper(m_d->textures->updateInfoBuilder(), cfg.swapDir()));
0284     } else {
0285         m_d->swapper.reset(new KisInMemoryFrameCacheSwapper());
0286     }
0287 
0288     m_d->frameSizeLimit = cfg.useAnimationCacheFrameSizeLimit() ? cfg.animationCacheFrameSizeLimit() : 0;
0289     emit changed();
0290 }
0291 
0292 KisOpenGLUpdateInfoSP KisAnimationFrameCache::Private::fetchFrameDataImpl(KisImageSP image, const QRect &requestedRect, int lod)
0293 {
0294     if (lod > 0) {
0295         KisPaintDeviceSP tempDevice = new KisPaintDevice(image->projection()->colorSpace());
0296         tempDevice->prepareClone(image->projection());
0297         image->projection()->generateLodCloneDevice(tempDevice, image->projection()->extent(), lod);
0298 
0299         const QRect fetchRect = KisLodTransform::alignedRect(requestedRect, lod);
0300         return textures->updateInfoBuilder().buildUpdateInfo(fetchRect, tempDevice, image->bounds(), lod, true);
0301     } else {
0302         return textures->updateCache(requestedRect, image);
0303     }
0304 }
0305 
0306 KisOpenGLUpdateInfoSP KisAnimationFrameCache::fetchFrameData(int time, KisImageSP image, const KisRegion &requestedRegion) const
0307 {
0308     if (time != image->animationInterface()->currentTime()) {
0309         qWarning() << "WARNING: KisAnimationFrameCache::frameReady image's time doesn't coincide with the requested time!";
0310         qWarning() << "    "  << ppVar(image->animationInterface()->currentTime()) << ppVar(time);
0311     }
0312 
0313     // the frames are always generated at full scale
0314     KIS_SAFE_ASSERT_RECOVER_NOOP(image->currentLevelOfDetail() == 0);
0315 
0316     const int lod = m_d->effectiveLevelOfDetail(requestedRegion.boundingRect());
0317 
0318     KisOpenGLUpdateInfoSP totalInfo;
0319 
0320     Q_FOREACH (const QRect &rc, requestedRegion.rects()) {
0321         KisOpenGLUpdateInfoSP info = m_d->fetchFrameDataImpl(image, rc, lod);
0322         if (!totalInfo) {
0323             totalInfo = info;
0324         } else {
0325             const bool result = totalInfo->tryMergeWith(*info);
0326             KIS_SAFE_ASSERT_RECOVER_NOOP(result);
0327         }
0328     }
0329 
0330     return totalInfo;
0331 }
0332 
0333 void KisAnimationFrameCache::addConvertedFrameData(KisOpenGLUpdateInfoSP info, int time)
0334 {
0335     const KisTimeSpan identicalRange =
0336         KisTimeSpan::calculateIdenticalFramesRecursive(m_d->image->root(), time);
0337 
0338     m_d->addFrame(info, identicalRange);
0339 
0340     emit changed();
0341 }
0342 
0343 void KisAnimationFrameCache::dropLowQualityFrames(const KisTimeSpan &range, const QRect &regionOfInterest, const QRect &minimalRect)
0344 {
0345     KIS_SAFE_ASSERT_RECOVER_RETURN(!range.isInfinite());
0346     if (m_d->newFrames.isEmpty()) return;
0347 
0348     auto it = m_d->newFrames.upperBound(range.start());
0349 
0350     // the vector is guaranteed to be non-empty,
0351     // so decrementing iterator is safe
0352     if (it != m_d->newFrames.begin()) it--;
0353 
0354     while (it != m_d->newFrames.end() && it.key() <= range.end()) {
0355         const int frameId = it.key();
0356         const int frameLength = it.value();
0357 
0358         if (frameId + frameLength - 1 < range.start()) {
0359             ++it;
0360             continue;
0361         }
0362 
0363         const QRect frameRect = m_d->swapper->frameDirtyRect(frameId);
0364         const int frameLod = m_d->swapper->frameLevelOfDetail(frameId);
0365 
0366         if (frameLod > m_d->effectiveLevelOfDetail(regionOfInterest) || !frameRect.contains(minimalRect)) {
0367             m_d->swapper->forgetFrame(frameId);
0368             it = m_d->newFrames.erase(it);
0369         } else {
0370             ++it;
0371         }
0372     }
0373 }
0374 
0375 bool KisAnimationFrameCache::framesHaveValidRoi(const KisTimeSpan &range, const QRect &regionOfInterest)
0376 {
0377     KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!range.isInfinite(), false);
0378     if (m_d->newFrames.isEmpty()) return false;
0379 
0380     auto it = m_d->newFrames.upperBound(range.start());
0381 
0382     if (it != m_d->newFrames.begin()) it--;
0383 
0384     int expectedNextFrameStart = it.key();
0385 
0386     while (it.key() <= range.end()) {
0387         const int frameId = it.key();
0388         const int frameLength = it.value();
0389 
0390         if (frameId + frameLength - 1 < range.start()) {
0391             expectedNextFrameStart = frameId + frameLength;
0392             ++it;
0393             continue;
0394         }
0395 
0396         if (expectedNextFrameStart != frameId) {
0397             KIS_SAFE_ASSERT_RECOVER_NOOP(expectedNextFrameStart < frameId);
0398             return false;
0399         }
0400 
0401         if (!m_d->swapper->frameDirtyRect(frameId).contains(regionOfInterest)) {
0402             return false;
0403         }
0404 
0405         expectedNextFrameStart = frameId + frameLength;
0406         ++it;
0407     }
0408 
0409     return true;
0410 }