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