File indexing completed on 2024-05-19 04:28:51
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_cache_populator.h" 0008 0009 #include <functional> 0010 0011 #include <QTimer> 0012 #include <QMutex> 0013 #include <QtConcurrent> 0014 0015 #include "kis_config.h" 0016 #include "kis_config_notifier.h" 0017 #include "KisPart.h" 0018 #include "KisDocument.h" 0019 #include "kis_image.h" 0020 #include "kis_image_animation_interface.h" 0021 #include "kis_canvas2.h" 0022 #include "kis_time_span.h" 0023 #include "kis_animation_frame_cache.h" 0024 #include "kis_update_info.h" 0025 #include "kis_signal_auto_connection.h" 0026 #include "kis_idle_watcher.h" 0027 #include "KisViewManager.h" 0028 #include "kis_node_manager.h" 0029 #include "kis_keyframe_channel.h" 0030 #include "KisMainWindow.h" 0031 0032 #include <KisLockFrameGenerationLock.h> 0033 #include "KisAsyncAnimationCacheRenderer.h" 0034 #include "dialogs/KisAsyncAnimationCacheRenderDialog.h" 0035 0036 0037 struct KisAnimationCachePopulator::Private 0038 { 0039 KisAnimationCachePopulator *q; 0040 KisPart *part; 0041 0042 QTimer timer; 0043 0044 /** 0045 * Counts up the number of subsequent times Krita has been detected idle. 0046 */ 0047 int idleCounter; 0048 QStack<QPair<KisImageSP, int>> priorityFrames; 0049 static const int IDLE_COUNT_THRESHOLD = 4; 0050 static const int IDLE_CHECK_INTERVAL = 500; 0051 static const int BETWEEN_FRAMES_INTERVAL = 10; 0052 0053 KisAsyncAnimationCacheRenderer regenerator; 0054 bool calculateAnimationCacheInBackground = true; 0055 0056 enum State { 0057 NotWaitingForAnything, 0058 WaitingForIdle, 0059 WaitingForFrame, 0060 BetweenFrames 0061 }; 0062 State state; 0063 0064 enum RegenerationRequestResult { 0065 RequestSuccessful = 0, 0066 RequestRejected, 0067 RequestPostponed 0068 }; 0069 0070 Private(KisAnimationCachePopulator *_q, KisPart *_part) 0071 : q(_q), 0072 part(_part), 0073 idleCounter(0), 0074 priorityFrames(), 0075 state(WaitingForIdle) 0076 { 0077 timer.setSingleShot(true); 0078 } 0079 0080 void timerTimeout() { 0081 switch (state) { 0082 case WaitingForIdle: 0083 case BetweenFrames: 0084 generateIfIdle(); 0085 break; 0086 case WaitingForFrame: 0087 KIS_ASSERT_RECOVER_NOOP(0 && "WaitingForFrame cannot have a timeout. Just skip this message and report a bug"); 0088 break; 0089 case NotWaitingForAnything: 0090 KIS_ASSERT_RECOVER_NOOP(0 && "NotWaitingForAnything cannot have a timeout. Just skip this message and report a bug"); 0091 break; 0092 } 0093 } 0094 0095 void generateIfIdle() 0096 { 0097 if (part->idleWatcher()->isIdle()) { 0098 idleCounter++; 0099 0100 if (idleCounter >= IDLE_COUNT_THRESHOLD) { 0101 RegenerationRequestResult result = tryRequestGeneration(); 0102 0103 if (result == RequestPostponed) { 0104 enterState(WaitingForIdle); 0105 } else if (result == RequestRejected) { 0106 enterState(NotWaitingForAnything); 0107 } 0108 0109 return; 0110 } 0111 } else { 0112 idleCounter = 0; 0113 } 0114 0115 enterState(WaitingForIdle); 0116 } 0117 0118 0119 RegenerationRequestResult tryRequestGeneration() 0120 { 0121 if (!priorityFrames.isEmpty()) { 0122 KisImageSP image = priorityFrames.top().first; 0123 const int priorityFrame = priorityFrames.top().second; 0124 priorityFrames.pop(); 0125 0126 KisAnimationFrameCacheSP cache = KisAnimationFrameCache::cacheForImage(image); 0127 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(cache, RequestRejected); 0128 0129 RegenerationRequestResult result = 0130 tryRequestGeneration(cache, KisTimeSpan(), priorityFrame); 0131 if (result == RequestSuccessful) return result; 0132 } 0133 0134 // Prioritize the active document 0135 KisAnimationFrameCacheSP activeDocumentCache = KisAnimationFrameCacheSP(0); 0136 0137 KisMainWindow *activeWindow = part->currentMainwindow(); 0138 if (activeWindow && activeWindow->activeView()) { 0139 KisCanvas2 *activeCanvas = activeWindow->activeView()->canvasBase(); 0140 0141 if (activeCanvas && activeCanvas->frameCache()) { 0142 activeDocumentCache = activeCanvas->frameCache(); 0143 0144 // Let's skip frames affected by changes to the active node (on the active document) 0145 // This avoids constant invalidation and regeneration while drawing 0146 KisNodeSP activeNode = activeCanvas->viewManager()->nodeManager()->activeNode(); 0147 KisTimeSpan skipRange; 0148 if (activeNode) { 0149 const int currentTime = activeCanvas->currentImage()->animationInterface()->currentUITime(); 0150 0151 if (!activeNode->keyframeChannels().isEmpty()) { 0152 Q_FOREACH (const KisKeyframeChannel *channel, activeNode->keyframeChannels()) { 0153 skipRange |= channel->affectedFrames(currentTime); 0154 } 0155 } else { 0156 skipRange = KisTimeSpan::infinite(0); 0157 } 0158 } 0159 0160 RegenerationRequestResult result = 0161 tryRequestGeneration(activeDocumentCache, skipRange, -1); 0162 if (result == RequestSuccessful) return result; 0163 } 0164 } 0165 0166 QList<KisAnimationFrameCache*> caches = KisAnimationFrameCache::caches(); 0167 KisAnimationFrameCache *cache; 0168 Q_FOREACH (cache, caches) { 0169 if (cache == activeDocumentCache.data()) { 0170 // We already handled this one... 0171 continue; 0172 } 0173 0174 RegenerationRequestResult result = 0175 tryRequestGeneration(cache, KisTimeSpan(), -1); 0176 if (result == RequestSuccessful) return result; 0177 } 0178 0179 return RequestRejected; 0180 } 0181 0182 RegenerationRequestResult tryRequestGeneration(KisAnimationFrameCacheSP cache, KisTimeSpan skipRange, int priorityFrame) 0183 { 0184 KisImageSP image = cache->image(); 0185 if (!image) return RequestRejected; 0186 0187 KisImageAnimationInterface *animation = image->animationInterface(); 0188 0189 if (animation->backgroundFrameGenerationBlocked()) { 0190 return RequestPostponed; 0191 } 0192 0193 KisTimeSpan currentRange = animation->documentPlaybackRange(); 0194 0195 const int frame = priorityFrame >= 0 ? priorityFrame : KisAsyncAnimationCacheRenderDialog::calcFirstDirtyFrame(cache, currentRange, skipRange); 0196 0197 if (frame >= 0) { 0198 return regenerate(cache, frame); 0199 } 0200 0201 return RequestRejected; 0202 } 0203 0204 RegenerationRequestResult regenerate(KisAnimationFrameCacheSP cache, int frame) 0205 { 0206 if (state == WaitingForFrame) { 0207 // Already busy, deny request 0208 return RequestRejected; 0209 } 0210 0211 KisLockFrameGenerationLock lock(cache->image()->animationInterface(), std::try_to_lock); 0212 0213 if (!lock.owns_lock()) { 0214 return RequestPostponed; 0215 } 0216 0217 /** 0218 * We should enter the state before the frame is 0219 * requested. Otherwise the signal may come earlier than we 0220 * enter it. 0221 */ 0222 enterState(WaitingForFrame); 0223 0224 regenerator.setFrameCache(cache); 0225 0226 // if we ever decide to add ROI to background cache 0227 // regeneration, it should be added here :) 0228 regenerator.startFrameRegeneration(cache->image(), frame, KisAsyncAnimationRendererBase::Cancellable, std::move(lock)); 0229 0230 return RequestSuccessful; 0231 } 0232 0233 QString debugStateToString(State newState) { 0234 QString str = "<unknown>"; 0235 0236 switch (newState) { 0237 case WaitingForIdle: 0238 str = "WaitingForIdle"; 0239 break; 0240 case WaitingForFrame: 0241 str = "WaitingForFrame"; 0242 break; 0243 case NotWaitingForAnything: 0244 str = "NotWaitingForAnything"; 0245 break; 0246 case BetweenFrames: 0247 str = "BetweenFrames"; 0248 break; 0249 } 0250 0251 return str; 0252 } 0253 0254 void enterState(State newState) 0255 { 0256 //ENTER_FUNCTION() << debugStateToString(state) << "->" << debugStateToString(newState); 0257 0258 state = newState; 0259 int timerTimeout = -1; 0260 0261 switch (state) { 0262 case WaitingForIdle: 0263 timerTimeout = IDLE_CHECK_INTERVAL; 0264 break; 0265 case WaitingForFrame: 0266 // the timeout is handled by the regenerator now 0267 timerTimeout = -1; 0268 break; 0269 case NotWaitingForAnything: 0270 // frame conversion cannot be cancelled, 0271 // so there is no timeout 0272 timerTimeout = -1; 0273 break; 0274 case BetweenFrames: 0275 timerTimeout = BETWEEN_FRAMES_INTERVAL; 0276 break; 0277 } 0278 0279 if (timerTimeout >= 0) { 0280 timer.start(timerTimeout); 0281 } else { 0282 timer.stop(); 0283 } 0284 } 0285 }; 0286 0287 KisAnimationCachePopulator::KisAnimationCachePopulator(KisPart *part) 0288 : m_d(new Private(this, part)) 0289 { 0290 connect(&m_d->timer, SIGNAL(timeout()), this, SLOT(slotTimer())); 0291 0292 connect(&m_d->regenerator, SIGNAL(sigFrameCancelled(int, KisAsyncAnimationRendererBase::CancelReason)), SLOT(slotRegeneratorFrameCancelled())); 0293 connect(&m_d->regenerator, SIGNAL(sigFrameCompleted(int)), SLOT(slotRegeneratorFrameReady())); 0294 0295 connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); 0296 slotConfigChanged(); 0297 } 0298 0299 KisAnimationCachePopulator::~KisAnimationCachePopulator() 0300 { 0301 m_d->priorityFrames.clear(); 0302 } 0303 0304 bool KisAnimationCachePopulator::regenerate(KisAnimationFrameCacheSP cache, int frame) 0305 { 0306 return m_d->regenerate(cache, frame); 0307 } 0308 0309 void KisAnimationCachePopulator::requestRegenerationWithPriorityFrame(KisImageSP image, int frameIndex) 0310 { 0311 if (!m_d->calculateAnimationCacheInBackground) return; 0312 if (!KisAnimationFrameCache::cacheForImage(image)) return; 0313 0314 m_d->priorityFrames.append(qMakePair(image, frameIndex)); 0315 0316 if (m_d->state == Private::NotWaitingForAnything) { 0317 m_d->generateIfIdle(); 0318 } 0319 } 0320 0321 void KisAnimationCachePopulator::slotTimer() 0322 { 0323 m_d->timerTimeout(); 0324 } 0325 0326 void KisAnimationCachePopulator::slotRequestRegeneration() 0327 { 0328 // skip if the user forbade background regeneration 0329 if (!m_d->calculateAnimationCacheInBackground) return; 0330 0331 m_d->enterState(Private::WaitingForIdle); 0332 } 0333 0334 void KisAnimationCachePopulator::slotRegeneratorFrameCancelled() 0335 { 0336 KIS_ASSERT_RECOVER_RETURN(m_d->state == Private::WaitingForFrame); 0337 m_d->enterState(Private::NotWaitingForAnything); 0338 } 0339 0340 void KisAnimationCachePopulator::slotRegeneratorFrameReady() 0341 { 0342 m_d->enterState(Private::BetweenFrames); 0343 } 0344 0345 void KisAnimationCachePopulator::slotConfigChanged() 0346 { 0347 KisConfig cfg(true); 0348 m_d->calculateAnimationCacheInBackground = cfg.calculateAnimationCacheInBackground(); 0349 QTimer::singleShot(1000, this, SLOT(slotRequestRegeneration())); 0350 }