diff --git a/libs/image/kis_time_range.cpp b/libs/image/kis_time_range.cpp index bd550138ec..0274c644f6 100644 --- a/libs/image/kis_time_range.cpp +++ b/libs/image/kis_time_range.cpp @@ -1,360 +1,376 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_time_range.h" #include #include "kis_keyframe_channel.h" #include "kis_node.h" #include "kis_layer_utils.h" struct KisTimeRangeStaticRegistrar { KisTimeRangeStaticRegistrar() { qRegisterMetaType("KisTimeRange"); qRegisterMetaType("KisTimeSpan"); qRegisterMetaType("KisFrameSet"); } }; static KisTimeRangeStaticRegistrar __registrar; QDebug operator<<(QDebug dbg, const KisTimeRange &r) { dbg.nospace() << "KisTimeRange(" << r.start() << ", " << r.end() << ")"; return dbg.space(); } QDebug operator<<(QDebug dbg, const KisTimeSpan &r) { dbg.nospace() << "KisTimeSpan(" << r.start() << ", " << r.end() << ")"; return dbg.space(); } QDebug operator<<(QDebug dbg, const KisFrameSet &r) { const QVector &spans = r.finiteSpans(); dbg.nospace() << "KisFrameSet("; for (int i = 0; i < spans.size(); i++) { if (i > 0) dbg.nospace() << ", "; dbg.nospace() << spans[i].start() << ".." << spans[i].end(); } if (r.isInfinite()) dbg.nospace() << ", " << r.firstFrameOfInfinity() << "..."; dbg.nospace() << ")"; return dbg.space(); } KisFrameSet& KisFrameSet::operator|=(const KisFrameSet &rhs) { if (rhs.isEmpty()) return *this; if (isEmpty()) { *this = rhs; return *this; } QVector spans; int lIndex = 0, rIndex = 0; KisTimeSpan currentSpan; int firstOfInfinite = !isInfinite() ? rhs.m_firstFrameOfInfinity : (!rhs.isInfinite()) ? m_firstFrameOfInfinity : qMin(m_firstFrameOfInfinity, rhs.m_firstFrameOfInfinity); while (lIndex < m_spans.size() || rIndex < rhs.m_spans.size()) { const bool leftRemaining = (lIndex < m_spans.size()); const bool rightRemaining = (rIndex < rhs.m_spans.size()); const bool leftFirst = leftRemaining && (!rightRemaining || m_spans[lIndex].start() < rhs.m_spans[rIndex].start()); KisTimeSpan first; if (leftFirst) { first = m_spans[lIndex++]; } else { first = rhs.m_spans[rIndex++]; } if (isInfinite() && firstOfInfinite <= first.end()) { currentSpan = KisTimeSpan(); firstOfInfinite = qMin(first.start(), firstOfInfinite); break; } else if (first.start() <= currentSpan.end() || currentSpan.isEmpty()) { currentSpan = currentSpan | first; } else { spans.append(currentSpan); currentSpan = first; } } if (!currentSpan.isEmpty()) { spans.append(currentSpan); } m_spans = spans; m_firstFrameOfInfinity = firstOfInfinite; return *this; } void addIntersectionAgainstInfinity(const QVector &src, int firstIndex , QVector &dst, int firstFrameOfInfinity) { for (int index = firstIndex; index < src.size(); index++) { const KisTimeSpan span = src[index].truncateRight(firstFrameOfInfinity); if (!span.isEmpty()) dst.append(span); } } KisFrameSet& KisFrameSet::operator&=(const KisFrameSet &rhs) { if (isEmpty() || rhs.isEmpty()) { *this = KisFrameSet(); return *this; } QVector spans; int lIndex = 0, rIndex = 0; while (lIndex < m_spans.size() && rIndex < rhs.m_spans.size()) { KisTimeSpan span; const KisTimeSpan rSpan = rhs.m_spans[rIndex]; const KisTimeSpan lSpan = m_spans[lIndex]; span = lSpan & rSpan; if (!span.isEmpty()) { spans.append(span); } if (lSpan.start() < rSpan.start()) { lIndex++; } else { rIndex++; } } if (isInfinite()) addIntersectionAgainstInfinity(rhs.m_spans, rIndex, spans, m_firstFrameOfInfinity); if (rhs.isInfinite()) addIntersectionAgainstInfinity(m_spans, lIndex, spans, rhs.m_firstFrameOfInfinity); int firstOfInfinite = (isInfinite() && rhs.isInfinite()) ? qMax(m_firstFrameOfInfinity, rhs.m_firstFrameOfInfinity) : -1; m_spans = spans; m_firstFrameOfInfinity = firstOfInfinite; return *this; } KisFrameSet& KisFrameSet::operator-=(const KisFrameSet &rhs) { if (rhs.isEmpty()) return *this; if (isEmpty()) { *this = KisFrameSet(); return *this; } QVector spans; int firstOfInfinite = (isInfinite() && !rhs.isInfinite()) ? qMax(m_firstFrameOfInfinity, rhs.m_spans.last().end() + 1) : -1; KisTimeSpan currentSpan = m_spans.first(); int lIndex = 0, rIndex = 0; while (lIndex < m_spans.size() && rIndex < rhs.m_spans.size()) { const KisTimeSpan rSpan = rhs.m_spans[rIndex]; if (currentSpan.isEmpty() || currentSpan.end() < rSpan.start()) { if (!currentSpan.isEmpty()) { spans.append(currentSpan); } lIndex++; currentSpan = (lIndex < m_spans.size()) ? m_spans[lIndex] : KisTimeSpan(); } else { const KisTimeSpan tail = currentSpan.truncateRight(rSpan.end() + 1); const KisTimeSpan head = currentSpan.truncateLeft(rSpan.start() - 1); if (!head.isEmpty()) { spans.append(head); } currentSpan = tail; rIndex++; } } while (!currentSpan.isEmpty()) { if (rhs.isInfinite() && currentSpan.end() >= rhs.firstFrameOfInfinity()) { currentSpan = currentSpan.truncateLeft(rhs.firstFrameOfInfinity() - 1); if (!currentSpan.isEmpty()) spans.append(currentSpan); break; } spans.append(currentSpan); lIndex++; currentSpan = (lIndex < m_spans.size()) ? m_spans[lIndex] : KisTimeSpan(); } m_spans = spans; m_firstFrameOfInfinity = firstOfInfinite; return *this; } KisTimeRange KisTimeRange::calculateIdenticalFramesRecursive(const KisNode *node, int time) { KisTimeRange range = KisTimeRange::infinite(0); KisLayerUtils::recursiveApplyNodes(node, [&range, time] (const KisNode *node) { if (node->visible()) { range &= calculateNodeIdenticalFrames(node, time); } }); return range; } KisTimeRange KisTimeRange::calculateAffectedFramesRecursive(const KisNode *node, int time) { KisTimeRange range; KisLayerUtils::recursiveApplyNodes(node, [&range, time] (const KisNode *node) { if (node->visible()) { range |= calculateNodeIdenticalFrames(node, time); } }); return range; } +int KisFrameSet::firstExcludedSince(int time) const +{ + if (isEmpty()) return time; + if (0 <= m_firstFrameOfInfinity && m_firstFrameOfInfinity <= time) return -1; + if (time < start()) return time; + if (time > m_spans.last().end()) return time; + + Q_FOREACH(const KisTimeSpan &span, m_spans) { + if (span.start() > time) return time; + if (span.end() >= time) return span.end() + 1; + } + + KIS_SAFE_ASSERT_RECOVER_NOOP(false); + return -1; +} + KisTimeRange KisTimeRange::calculateNodeIdenticalFrames(const KisNode *node, int time) { KisTimeRange range = KisTimeRange::infinite(0); const QMap channels = node->keyframeChannels(); Q_FOREACH (const KisKeyframeChannel *channel, channels) { // Intersection range &= channel->identicalFrames(time); } return range; } KisTimeRange KisTimeRange::calculateNodeAffectedFrames(const KisNode *node, int time) { KisTimeRange range; if (!node->visible()) return range; const QMap channels = node->keyframeChannels(); // TODO: channels should report to the image which channel exactly has changed // to avoid the dirty range to be stretched into infinity! if (channels.isEmpty() || !channels.contains(KisKeyframeChannel::Content.id())) { range = KisTimeRange::infinite(0); return range; } Q_FOREACH (const KisKeyframeChannel *channel, channels) { // Union range |= channel->affectedFrames(time); } return range; } namespace KisDomUtils { void saveValue(QDomElement *parent, const QString &tag, const KisTimeRange &range) { QDomDocument doc = parent->ownerDocument(); QDomElement e = doc.createElement(tag); parent->appendChild(e); e.setAttribute("type", "timerange"); if (range.isValid()) { e.setAttribute("from", toString(range.start())); if (!range.isInfinite()) { e.setAttribute("to", toString(range.end())); } } } bool loadValue(const QDomElement &parent, const QString &tag, KisTimeRange *range) { QDomElement e; if (!findOnlyElement(parent, tag, &e)) return false; if (!Private::checkType(e, "timerange")) return false; int start = toInt(e.attribute("from", "-1")); int end = toInt(e.attribute("to", "-1")); if (start == -1) { range = new KisTimeRange(); } else if (end == -1) { *range = KisTimeRange::infinite(start); } else { *range = KisTimeRange::fromTime(start, end); } return true; } void saveValue(QDomElement *parent, const QString &tag, const KisTimeSpan &range) { QDomDocument doc = parent->ownerDocument(); QDomElement e = doc.createElement(tag); parent->appendChild(e); e.setAttribute("type", "timerange"); if (!range.isEmpty()) { e.setAttribute("from", toString(range.start())); e.setAttribute("to", toString(range.end())); } } bool loadValue(const QDomElement &parent, const QString &tag, KisTimeSpan *range) { QDomElement e; if (!findOnlyElement(parent, tag, &e)) return false; if (!Private::checkType(e, "timerange")) return false; int start = toInt(e.attribute("from", "-1")); int end = toInt(e.attribute("to", "-1")); if (start < 0 || end < 0) { *range = KisTimeSpan(); } else { *range = KisTimeSpan(start, end); } return true; } } diff --git a/libs/image/kis_time_range.h b/libs/image/kis_time_range.h index f3f7ba2e47..0b3b3aefb2 100644 --- a/libs/image/kis_time_range.h +++ b/libs/image/kis_time_range.h @@ -1,344 +1,352 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_TIME_RANGE_H #define __KIS_TIME_RANGE_H #include "kritaimage_export.h" #include #include #include #include #include "kis_types.h" #include /** * Represents a finite, continuous span of time between two frames. * Start and end frames are both included in the span. */ class KRITAIMAGE_EXPORT KisTimeSpan : public boost::equality_comparable { public: inline KisTimeSpan() : m_start(-1) , m_end(-2) {} inline KisTimeSpan(int start, int end) : m_start(start), m_end(end) {} inline bool isEmpty() const { return m_end < m_start; } inline int start() const { return m_start; } inline int end() const { return m_end; } inline bool isValid() const { return isEmpty(); } inline int duration() const { return !isEmpty() ? (m_end - m_start + 1) : 0; } inline bool contains(int time) const { return !isEmpty() ? (m_start <= time && time <= m_end) : false; } inline bool contains(const KisTimeSpan rhs) const { return rhs.isEmpty() || (m_start <= rhs.start() && rhs.end() <= m_end); } inline bool overlaps(const KisTimeSpan &other) const { return m_start <= other.m_end && other.m_start <= m_end; } inline KisTimeSpan truncateLeft(int newEnd) const { if (newEnd < m_start) return KisTimeSpan(); if (m_end <= newEnd) return *this; return KisTimeSpan(m_start, newEnd); } inline KisTimeSpan truncateRight(int newStart) const { if (m_end < newStart) return KisTimeSpan(); if (newStart <= m_start) return *this; return KisTimeSpan(newStart, m_end); } bool operator==(const KisTimeSpan &rhs) const { return rhs.m_start == m_start && rhs.m_end == m_end; } KisTimeSpan operator|(const KisTimeSpan &rhs) const { if (isEmpty()) return rhs; if (rhs.isEmpty()) return *this; int start = qMin(m_start, rhs.m_start); int end = qMax(m_end, rhs.m_end); return KisTimeSpan(start, end); } KisTimeSpan operator&(const KisTimeSpan &rhs) const { if (isEmpty() || rhs.isEmpty()) return KisTimeSpan(); int start = qMax(m_start, rhs.m_start); int end = qMin(m_end, rhs.m_end); if (end < start) return KisTimeSpan(); return KisTimeSpan(start, end); } private: int m_start, m_end; }; /** * Represents an arbitrary set of frames, possibly stretching to infinity. * */ class KRITAIMAGE_EXPORT KisFrameSet : public boost::equality_comparable, public boost::andable, public boost::orable, public boost::subtractable { public: static KisFrameSet between(int start, int end) { return KisFrameSet(KisTimeSpan(start, end)); } static KisFrameSet infiniteFrom(int start) { return KisFrameSet({}, start); } KisFrameSet() = default; inline explicit KisFrameSet(QVector spans, int firstFrameOfInfinity = -1) : m_spans(spans) , m_firstFrameOfInfinity(firstFrameOfInfinity) { // Normalize std::sort(m_spans.begin(), m_spans.end(), [](const KisTimeSpan &a, const KisTimeSpan &b) { return a.start() < b.start(); } ); mergeOverlappingSpans(); } explicit KisFrameSet(const KisTimeSpan span) : m_spans({span}) {} inline bool isInfinite() const { return m_firstFrameOfInfinity >= 0; } bool isEmpty() const { return m_spans.isEmpty() && !isInfinite(); } int start() const { if (m_spans.isEmpty()) return m_firstFrameOfInfinity; return m_spans.first().start(); } /** * List of the finite, continuous spans this set consists of. * Note: this list does not contain the tail of infinite sets. See firstFrameOfInfinity(). */ inline const QVector finiteSpans() const { return m_spans; } /** * If the set is infinite, the frame from which the infinite tail begins. Returns -1 if the set is finite. */ inline int firstFrameOfInfinity() const { return m_firstFrameOfInfinity; } inline bool contains(int frame) const { if (0 <= m_firstFrameOfInfinity && m_firstFrameOfInfinity <= frame) return true; Q_FOREACH(const KisTimeSpan &span, m_spans) { if (span.contains(frame)) return true; } return false; } + int firstExcludedSince(int time) const; + bool operator==(const KisFrameSet &rhs) const { return rhs.m_firstFrameOfInfinity == m_firstFrameOfInfinity && rhs.m_spans == m_spans; } KisFrameSet& operator|=(const KisFrameSet &rhs); KisFrameSet& operator&=(const KisFrameSet &rhs); KisFrameSet& operator-=(const KisFrameSet &rhs); private: inline void mergeOverlappingSpans() { int dst = 0; for (int src = 1; src < m_spans.length(); src++) { if (isInfinite() && m_firstFrameOfInfinity <= m_spans[src].end()) { m_firstFrameOfInfinity = qMin(m_spans[src].start(), m_firstFrameOfInfinity); break; } if (m_spans[src].overlaps(m_spans[dst])) { m_spans[dst] = m_spans[dst] | m_spans[src]; } else { dst++; if (dst != src) { m_spans[dst] = m_spans[src]; } } } if (dst < m_spans.length() - 1) { m_spans.resize(dst - 1); } } QVector m_spans; int m_firstFrameOfInfinity = -1; }; class KRITAIMAGE_EXPORT KisTimeRange : public boost::equality_comparable { public: inline KisTimeRange() : m_start(0), m_end(-1) { } inline KisTimeRange(int start, int duration) : m_start(start), m_end(start + duration - 1) { } inline KisTimeRange(int start, int end, bool) : m_start(start), m_end(end) { } bool operator==(const KisTimeRange &rhs) const { return rhs.m_start == m_start && rhs.m_end == m_end; } KisTimeRange& operator|=(const KisTimeRange &rhs) { if (!isValid()) { m_start = rhs.start(); } else if (rhs.isValid()) { m_start = std::min(m_start, rhs.start()); } if (rhs.isInfinite() || isInfinite()) { m_end = std::numeric_limits::min(); } else if (!isValid()) { m_end = rhs.m_end; } else { m_end = std::max(m_end, rhs.m_end); } return *this; } KisTimeRange& operator&=(const KisTimeRange &rhs) { if (!isValid()) { return *this; } else if (!rhs.isValid()) { m_start = rhs.start(); m_end = rhs.m_end; return *this; } else { m_start = std::max(m_start, rhs.start()); } if (isInfinite()) { m_end = rhs.m_end; } else if (!rhs.isInfinite()) { m_end = std::min(m_end, rhs.m_end); } return *this; } inline int start() const { return m_start; } inline int end() const { return m_end; } inline int duration() const { return m_end >= m_start ? m_end - m_start + 1 : 0; } inline bool isInfinite() const { return m_end == std::numeric_limits::min(); } inline bool isValid() const { return (m_end >= m_start) || (m_end == std::numeric_limits::min() && m_start >= 0); } inline bool contains(int time) const { if (m_end == std::numeric_limits::min()) { return m_start <= time; } return m_start <= time && time <= m_end; } + // Temporary adapter + inline KisFrameSet asFrameSet() const { + if (isInfinite()) return KisFrameSet::infiniteFrom(m_start); + return KisFrameSet::between(m_start, m_end); + } + static inline KisTimeRange fromTime(int start, int end) { return KisTimeRange(start, end, true); } static inline KisTimeRange infinite(int start) { return KisTimeRange(start, std::numeric_limits::min(), true); } static KisTimeRange calculateIdenticalFramesRecursive(const KisNode *node, int time); static KisTimeRange calculateAffectedFramesRecursive(const KisNode *node, int time); static KisTimeRange calculateNodeIdenticalFrames(const KisNode *node, int time); static KisTimeRange calculateNodeAffectedFrames(const KisNode *node, int time); private: int m_start; int m_end; }; namespace KisDomUtils { void KRITAIMAGE_EXPORT saveValue(QDomElement *parent, const QString &tag, const KisTimeRange &range); bool KRITAIMAGE_EXPORT loadValue(const QDomElement &parent, const QString &tag, KisTimeRange *range); void KRITAIMAGE_EXPORT saveValue(QDomElement *parent, const QString &tag, const KisTimeSpan &range); bool KRITAIMAGE_EXPORT loadValue(const QDomElement &parent, const QString &tag, KisTimeSpan *range); } Q_DECLARE_METATYPE(KisTimeRange); Q_DECLARE_METATYPE(KisTimeSpan); Q_DECLARE_METATYPE(KisFrameSet); KRITAIMAGE_EXPORT QDebug operator<<(QDebug dbg, const KisTimeRange &r); KRITAIMAGE_EXPORT QDebug operator<<(QDebug dbg, const KisTimeSpan &r); KRITAIMAGE_EXPORT QDebug operator<<(QDebug dbg, const KisFrameSet &r); #endif /* __KIS_TIME_RANGE_H */ diff --git a/libs/ui/dialogs/KisAsyncAnimationCacheRenderDialog.cpp b/libs/ui/dialogs/KisAsyncAnimationCacheRenderDialog.cpp index ae18c4b5d7..e5d484c9de 100644 --- a/libs/ui/dialogs/KisAsyncAnimationCacheRenderDialog.cpp +++ b/libs/ui/dialogs/KisAsyncAnimationCacheRenderDialog.cpp @@ -1,143 +1,101 @@ /* * Copyright (c) 2017 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KisAsyncAnimationCacheRenderDialog.h" #include "KisAsyncAnimationCacheRenderer.h" #include "kis_animation_frame_cache.h" #include #include #include namespace { QList calcDirtyFramesList(KisAnimationFrameCacheSP cache, const KisTimeSpan &playbackRange) { - QList result; + QList framesToRegenerate; KisImageSP image = cache->image(); - if (!image) return result; + if (!image) return framesToRegenerate; KisImageAnimationInterface *animation = image->animationInterface(); - if (!animation->hasAnimation()) return result; - - if (!playbackRange.isEmpty()) { - // TODO: optimize check for fully-cached case - for (int frame = playbackRange.start(); frame <= playbackRange.end(); frame++) { - const KisTimeRange stillFrameRange = - KisTimeRange::calculateIdenticalFramesRecursive(image->root(), frame); - - KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(stillFrameRange.isValid(), result); - - if (cache->frameStatus(stillFrameRange.start()) == KisAnimationFrameCache::Uncached) { - result.append(stillFrameRange.start()); - } - - if (stillFrameRange.isInfinite()) { - break; - } else { - frame = stillFrameRange.end(); - } - } - } - - return result; -} + if (!animation->hasAnimation()) return framesToRegenerate; -} + KisFrameSet dirtyFrames = cache->dirtyFramesWithin(playbackRange); -int KisAsyncAnimationCacheRenderDialog::calcFirstDirtyFrame(KisAnimationFrameCacheSP cache, const KisTimeSpan &playbackRange, const KisTimeRange &skipRange) -{ - int result = -1; + while (!dirtyFrames.isEmpty()) { + int first = dirtyFrames.start(); - KisImageSP image = cache->image(); - if (!image) return result; + framesToRegenerate.append(first); - KisImageAnimationInterface *animation = image->animationInterface(); - if (!animation->hasAnimation()) return result; - - if (playbackRange.isValid()) { - // TODO: optimize check for fully-cached case - for (int frame = playbackRange.start(); frame <= playbackRange.end(); frame++) { - if (skipRange.contains(frame)) { - if (skipRange.isInfinite()) { - break; - } else { - frame = skipRange.end(); - continue; - } - } - - if (cache->frameStatus(frame) != KisAnimationFrameCache::Cached) { - result = frame; - break; - } - } + KisFrameSet duplicates = KisTimeRange::calculateIdenticalFramesRecursive(image->root(), first).asFrameSet(); + dirtyFrames -= duplicates; } - return result; + return framesToRegenerate; } +} struct KisAsyncAnimationCacheRenderDialog::Private { Private(KisAnimationFrameCacheSP _cache, const KisTimeSpan &_range) : cache(_cache), range(_range) { } KisAnimationFrameCacheSP cache; KisTimeSpan range; }; KisAsyncAnimationCacheRenderDialog::KisAsyncAnimationCacheRenderDialog(KisAnimationFrameCacheSP cache, const KisTimeSpan &range, int busyWait) : KisAsyncAnimationRenderDialogBase(i18n("Regenerating cache..."), cache->image(), busyWait), m_d(new Private(cache, range)) { } KisAsyncAnimationCacheRenderDialog::~KisAsyncAnimationCacheRenderDialog() { } QList KisAsyncAnimationCacheRenderDialog::calcDirtyFrames() const { return calcDirtyFramesList(m_d->cache, m_d->range); } KisAsyncAnimationRendererBase *KisAsyncAnimationCacheRenderDialog::createRenderer(KisImageSP image) { Q_UNUSED(image); return new KisAsyncAnimationCacheRenderer(); } void KisAsyncAnimationCacheRenderDialog::initializeRendererForFrame(KisAsyncAnimationRendererBase *renderer, KisImageSP image, int frame) { Q_UNUSED(image); Q_UNUSED(frame); KisAsyncAnimationCacheRenderer *cacheRenderer = dynamic_cast(renderer); KIS_SAFE_ASSERT_RECOVER_RETURN(cacheRenderer); cacheRenderer->setFrameCache(m_d->cache); } diff --git a/libs/ui/dialogs/KisAsyncAnimationCacheRenderDialog.h b/libs/ui/dialogs/KisAsyncAnimationCacheRenderDialog.h index 3aedda0483..1e7db436ef 100644 --- a/libs/ui/dialogs/KisAsyncAnimationCacheRenderDialog.h +++ b/libs/ui/dialogs/KisAsyncAnimationCacheRenderDialog.h @@ -1,47 +1,46 @@ /* * Copyright (c) 2017 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KISASYNCANIMATIONCACHERENDERDIALOG_H #define KISASYNCANIMATIONCACHERENDERDIALOG_H #include "KisAsyncAnimationRenderDialogBase.h" #include "kis_types.h" class KisTimeSpan; -class KisTimeRange; class KisAsyncAnimationCacheRenderDialog : public KisAsyncAnimationRenderDialogBase { public: KisAsyncAnimationCacheRenderDialog(KisAnimationFrameCacheSP cache, const KisTimeSpan &range, int busyWait = 200); ~KisAsyncAnimationCacheRenderDialog(); - static int calcFirstDirtyFrame(KisAnimationFrameCacheSP cache, const KisTimeSpan &playbackRange, const KisTimeRange &skipRange); + static int calcFirstDirtyFrame(KisAnimationFrameCacheSP cache, const KisTimeSpan &playbackRange, const KisTimeSpan &skipRange); protected: QList calcDirtyFrames() const override; KisAsyncAnimationRendererBase* createRenderer(KisImageSP image) override; void initializeRendererForFrame(KisAsyncAnimationRendererBase *renderer, KisImageSP image, int frame) override; private: struct Private; const QScopedPointer m_d; }; #endif // KISASYNCANIMATIONCACHERENDERDIALOG_H diff --git a/libs/ui/kis_animation_cache_populator.cpp b/libs/ui/kis_animation_cache_populator.cpp index ba3201403a..228d962da5 100644 --- a/libs/ui/kis_animation_cache_populator.cpp +++ b/libs/ui/kis_animation_cache_populator.cpp @@ -1,317 +1,317 @@ /* * Copyright (c) 2015 Jouni Pentikäinen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_animation_cache_populator.h" #include #include #include #include #include "kis_config.h" #include "kis_config_notifier.h" #include "KisPart.h" #include "KisDocument.h" #include "kis_image.h" #include "kis_image_animation_interface.h" #include "kis_canvas2.h" #include "kis_time_range.h" #include "kis_animation_frame_cache.h" #include "kis_update_info.h" #include "kis_signal_auto_connection.h" #include "kis_idle_watcher.h" #include "KisViewManager.h" #include "kis_node_manager.h" #include "kis_keyframe_channel.h" #include "KisAsyncAnimationCacheRenderer.h" #include "dialogs/KisAsyncAnimationCacheRenderDialog.h" struct KisAnimationCachePopulator::Private { KisAnimationCachePopulator *q; KisPart *part; QTimer timer; /** * Counts up the number of subsequent times Krita has been detected idle. */ int idleCounter; static const int IDLE_COUNT_THRESHOLD = 4; static const int IDLE_CHECK_INTERVAL = 500; static const int BETWEEN_FRAMES_INTERVAL = 10; int requestedFrame; KisAnimationFrameCacheSP requestCache; KisOpenGLUpdateInfoSP requestInfo; KisSignalAutoConnectionsStore imageRequestConnections; QFutureWatcher infoConversionWatcher; KisAsyncAnimationCacheRenderer regenerator; bool calculateAnimationCacheInBackground = true; enum State { NotWaitingForAnything, WaitingForIdle, WaitingForFrame, BetweenFrames }; State state; Private(KisAnimationCachePopulator *_q, KisPart *_part) : q(_q), part(_part), idleCounter(0), requestedFrame(-1), state(WaitingForIdle) { timer.setSingleShot(true); } void timerTimeout() { switch (state) { case WaitingForIdle: case BetweenFrames: generateIfIdle(); break; case WaitingForFrame: KIS_ASSERT_RECOVER_NOOP(0 && "WaitingForFrame cannot have a timeout. Just skip this message and report a bug"); break; case NotWaitingForAnything: KIS_ASSERT_RECOVER_NOOP(0 && "NotWaitingForAnything cannot have a timeout. Just skip this message and report a bug"); break; } } void generateIfIdle() { if (part->idleWatcher()->isIdle()) { idleCounter++; if (idleCounter >= IDLE_COUNT_THRESHOLD) { if (!tryRequestGeneration()) { enterState(NotWaitingForAnything); } return; } } else { idleCounter = 0; } enterState(WaitingForIdle); } bool tryRequestGeneration() { // Prioritize the active document KisAnimationFrameCacheSP activeDocumentCache = KisAnimationFrameCacheSP(0); KisMainWindow *activeWindow = part->currentMainwindow(); if (activeWindow && activeWindow->activeView()) { KisCanvas2 *activeCanvas = activeWindow->activeView()->canvasBase(); if (activeCanvas && activeCanvas->frameCache()) { activeDocumentCache = activeCanvas->frameCache(); // Let's skip frames affected by changes to the active node (on the active document) // This avoids constant invalidation and regeneration while drawing KisNodeSP activeNode = activeCanvas->viewManager()->nodeManager()->activeNode(); - KisTimeRange skipRange; + KisFrameSet skipRange; if (activeNode) { int currentTime = activeCanvas->currentImage()->animationInterface()->currentUITime(); if (!activeNode->keyframeChannels().isEmpty()) { Q_FOREACH (const KisKeyframeChannel *channel, activeNode->keyframeChannels()) { - skipRange |= channel->affectedFrames(currentTime); + skipRange |= channel->affectedFrames(currentTime).asFrameSet(); } } else { - skipRange = KisTimeRange::infinite(0); + skipRange = KisFrameSet::infiniteFrom(0); } } bool requested = tryRequestGeneration(activeDocumentCache, skipRange); if (requested) return true; } } QList caches = KisAnimationFrameCache::caches(); KisAnimationFrameCache *cache; Q_FOREACH (cache, caches) { if (cache == activeDocumentCache.data()) { // We already handled this one... continue; } - bool requested = tryRequestGeneration(cache, KisTimeRange()); + bool requested = tryRequestGeneration(cache, KisFrameSet()); if (requested) return true; } return false; } - bool tryRequestGeneration(KisAnimationFrameCacheSP cache, KisTimeRange skipRange) + bool tryRequestGeneration(KisAnimationFrameCacheSP cache, KisFrameSet skipRange) { KisImageSP image = cache->image(); if (!image) return false; KisImageAnimationInterface *animation = image->animationInterface(); KisTimeSpan currentRange = animation->fullClipRange(); - const int frame = KisAsyncAnimationCacheRenderDialog::calcFirstDirtyFrame(cache, currentRange, skipRange); + const int frame = cache->firstDirtyFrameWithin(currentRange, &skipRange); if (frame >= 0) { return regenerate(cache, frame); } return false; } bool regenerate(KisAnimationFrameCacheSP cache, int frame) { if (state == WaitingForFrame) { // Already busy, deny request return false; } /** * We should enter the state before the frame is * requested. Otherwise the signal may come earlier than we * enter it. */ enterState(WaitingForFrame); regenerator.setFrameCache(cache); // if we ever decide to add ROI to background cache // regeneration, it should be added here :) regenerator.startFrameRegeneration(cache->image(), frame); return true; } QString debugStateToString(State newState) { QString str = ""; switch (newState) { case WaitingForIdle: str = "WaitingForIdle"; break; case WaitingForFrame: str = "WaitingForFrame"; break; case NotWaitingForAnything: str = "NotWaitingForAnything"; break; case BetweenFrames: str = "BetweenFrames"; break; } return str; } void enterState(State newState) { //ENTER_FUNCTION() << debugStateToString(state) << "->" << debugStateToString(newState); state = newState; int timerTimeout = -1; switch (state) { case WaitingForIdle: timerTimeout = IDLE_CHECK_INTERVAL; break; case WaitingForFrame: // the timeout is handled by the regenerator now timerTimeout = -1; break; case NotWaitingForAnything: // frame conversion cannot be cancelled, // so there is no timeout timerTimeout = -1; break; case BetweenFrames: timerTimeout = BETWEEN_FRAMES_INTERVAL; break; } if (timerTimeout >= 0) { timer.start(timerTimeout); } else { timer.stop(); } } }; KisAnimationCachePopulator::KisAnimationCachePopulator(KisPart *part) : m_d(new Private(this, part)) { connect(&m_d->timer, SIGNAL(timeout()), this, SLOT(slotTimer())); connect(&m_d->regenerator, SIGNAL(sigFrameCancelled(int)), SLOT(slotRegeneratorFrameCancelled())); connect(&m_d->regenerator, SIGNAL(sigFrameCompleted(int)), SLOT(slotRegeneratorFrameReady())); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); slotConfigChanged(); } KisAnimationCachePopulator::~KisAnimationCachePopulator() {} bool KisAnimationCachePopulator::regenerate(KisAnimationFrameCacheSP cache, int frame) { return m_d->regenerate(cache, frame); } void KisAnimationCachePopulator::slotTimer() { m_d->timerTimeout(); } void KisAnimationCachePopulator::slotRequestRegeneration() { // skip if the user forbade background regeneration if (!m_d->calculateAnimationCacheInBackground) return; m_d->enterState(Private::WaitingForIdle); } void KisAnimationCachePopulator::slotRegeneratorFrameCancelled() { KIS_ASSERT_RECOVER_RETURN(m_d->state == Private::WaitingForFrame); m_d->enterState(Private::NotWaitingForAnything); } void KisAnimationCachePopulator::slotRegeneratorFrameReady() { m_d->enterState(Private::BetweenFrames); } void KisAnimationCachePopulator::slotConfigChanged() { KisConfig cfg(true); m_d->calculateAnimationCacheInBackground = cfg.calculateAnimationCacheInBackground(); QTimer::singleShot(1000, this, SLOT(slotRequestRegeneration())); } diff --git a/libs/ui/kis_animation_frame_cache.cpp b/libs/ui/kis_animation_frame_cache.cpp index 2d9f74c1ff..18fbc48705 100644 --- a/libs/ui/kis_animation_frame_cache.cpp +++ b/libs/ui/kis_animation_frame_cache.cpp @@ -1,413 +1,475 @@ /* * Copyright (c) 2015 Jouni Pentikäinen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_animation_frame_cache.h" #include +#include #include "kis_debug.h" #include "kis_image.h" #include "kis_image_animation_interface.h" #include "kis_time_range.h" #include "KisPart.h" #include "kis_animation_cache_populator.h" #include #include "KisFrameCacheSwapper.h" #include "KisInMemoryFrameCacheSwapper.h" #include "kis_image_config.h" #include "kis_config_notifier.h" #include "opengl/kis_opengl_image_textures.h" #include #include struct KisAnimationFrameCache::Private { Private(KisOpenGLImageTexturesSP _textures) : textures(_textures) { image = textures->image(); } ~Private() { } KisOpenGLImageTexturesSP textures; KisImageWSP image; QScopedPointer swapper; int frameSizeLimit = 777; KisOpenGLUpdateInfoSP fetchFrameDataImpl(KisImageSP image, const QRect &requestedRect, int lod); - struct Frame + struct CacheEntry { - KisOpenGLUpdateInfoSP openGlFrame; + int frameId; int length; - Frame(KisOpenGLUpdateInfoSP info, int length) - : openGlFrame(info), length(length) + CacheEntry(int id, int length) + : frameId(id), length(length) {} + + bool isInfinite() const { + return length < 0; + } }; - QMap newFrames; + int nextFreeId = 0; + /// Map of cache entries by beginning time of the entry + QMap cachedFrames; + /// Maps a frame ID to a list of times, where entries with said frame ID begin + QMap> entriesById; - int getFrameIdAtTime(int time) const - { - if (newFrames.isEmpty()) return -1; + void addFrame(int start, int length, int id) { + cachedFrames.insert(start, CacheEntry(id, length)); + entriesById[id].append(start); + } - auto it = newFrames.upperBound(time); + QMap::iterator iteratorFrom(int time) { + if (cachedFrames.isEmpty()) return cachedFrames.end(); - if (it != newFrames.constBegin()) it--; + auto it = cachedFrames.upperBound(time); + if (it != cachedFrames.begin()) { + auto previous = it - 1; + if (previous.value().isInfinite() || time <= previous.key() + previous.value().length) { + return previous; + } + + } + return it; + } + + QMap::const_iterator constIteratorFrom(int time) const { + if (cachedFrames.isEmpty()) return cachedFrames.constEnd(); + + auto it = cachedFrames.upperBound(time); + if (it != cachedFrames.constBegin()) { + auto previous = it - 1; + if (previous.value().isInfinite() || time <= previous.key() + previous.value().length) { + return previous; + } + + } + return it; + } + + int getFrameIdAtTime(int time) const + { + auto it = constIteratorFrom(time); + if (it == cachedFrames.constEnd()) return -1; - KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(it != newFrames.constEnd(), 0); const int start = it.key(); - const int length = it.value(); + const CacheEntry &frame = it.value(); bool foundFrameValid = false; - - if (length == -1) { + if (frame.isInfinite()) { if (start <= time) { foundFrameValid = true; } } else { - int end = start + length - 1; + int end = start + frame.length - 1; if (start <= time && time <= end) { foundFrameValid = true; } } - return foundFrameValid ? start : -1; + return foundFrameValid ? frame.frameId : -1; } bool hasFrame(int time) const { return getFrameIdAtTime(time) >= 0; } KisOpenGLUpdateInfoSP getFrame(int time) { const int frameId = getFrameIdAtTime(time); return frameId >= 0 ? swapper->loadFrame(frameId) : 0; } - void addFrame(KisOpenGLUpdateInfoSP info, const KisTimeRange& range) + void addFrame(KisOpenGLUpdateInfoSP info, const KisFrameSet& targetFrames) { - invalidate(range); + invalidate(targetFrames); - const int length = range.isInfinite() ? -1 : range.end() - range.start() + 1; - newFrames.insert(range.start(), length); - swapper->saveFrame(range.start(), info, image->bounds()); + const int id = nextFreeId++; + swapper->saveFrame(id, info, image->bounds()); + + for (const KisTimeSpan span : targetFrames.finiteSpans()) { + addFrame(span.start(), span.duration(), id); + } + + if (targetFrames.isInfinite()) { + addFrame(targetFrames.firstFrameOfInfinity(), -1, id); + } } /** * Invalidate any cached frames within the given time range. * @param range * @return true if frames were invalidated, false if nothing was changed */ - bool invalidate(const KisTimeRange& range) + bool invalidate(const KisFrameSet& range) { - if (newFrames.isEmpty()) return false; + if (cachedFrames.isEmpty()) return false; bool cacheChanged = false; - auto it = newFrames.lowerBound(range.start()); - if (it.key() != range.start() && it != newFrames.begin()) it--; + for (const KisTimeSpan span : range.finiteSpans()) { + cacheChanged |= invalidate(span.start(), span.end()); + } + if (range.isInfinite()) { + cacheChanged |= invalidate(range.firstFrameOfInfinity(), -1); + } - while (it != newFrames.end()) { - const int start = it.key(); - const int length = it.value(); - const bool frameIsInfinite = (length == -1); - const int end = start + length - 1; + return cacheChanged; + } - if (start >= range.start()) { - if (!range.isInfinite() && start > range.end()) { - break; - } + bool invalidate(int invalidateFrom, int invalidateTo) { + const bool infinite = invalidateTo < 0; - if (!range.isInfinite() && (frameIsInfinite || end > range.end())) { - // Reinsert with a later start - int newStart = range.end() + 1; - int newLength = frameIsInfinite ? -1 : (end - newStart + 1); + bool cacheChanged = false; - newFrames.insert(newStart, newLength); - swapper->moveFrame(start, newStart); - } else { - swapper->forgetFrame(start); + auto it = iteratorFrom(invalidateFrom); + while (it != cachedFrames.end()) { + const int start = it.key(); + const CacheEntry frame = it.value(); + const bool frameIsInfinite = (frame.length == -1); + const int end = start + frame.length - 1; + + if (start >= invalidateFrom) { + if (!infinite) { + if (start > invalidateTo) { + break; + } + + if (frameIsInfinite || end > invalidateTo) { + // Shorten the entry from the beginning + int newStart = invalidateTo + 1; + int newLength = frameIsInfinite ? -1 : (end - newStart + 1); + + cachedFrames.insert(newStart, CacheEntry(frame.frameId, newLength)); + addFrame(newStart, newLength, frame.frameId); + } } - it = newFrames.erase(it); + QVector &instances = entriesById[frame.frameId]; + instances.removeAll(it.key()); + if (instances.isEmpty()) swapper->forgetFrame(frame.frameId); + it = cachedFrames.erase(it); cacheChanged = true; continue; - } else if (frameIsInfinite || end >= range.start()) { - const int newEnd = range.start() - 1; - *it = newEnd - start + 1; + } else if (frameIsInfinite || end >= invalidateFrom) { + const int newEnd = invalidateFrom - 1; + const int newLength = newEnd - start + 1; + *it = CacheEntry(frame.frameId, newLength); cacheChanged = true; } it++; } return cacheChanged; } int effectiveLevelOfDetail(const QRect &rc) const { if (!frameSizeLimit) return 0; const int maxDimension = KisAlgebra2D::maxDimension(rc); const qreal minLod = -std::log2(qreal(frameSizeLimit) / maxDimension); const int lodLimit = qMax(0, qCeil(minLod)); return lodLimit; } // TODO: verify that we don't have any leak here! typedef QMap CachesMap; static CachesMap caches; }; KisAnimationFrameCache::Private::CachesMap KisAnimationFrameCache::Private::caches; KisAnimationFrameCacheSP KisAnimationFrameCache::getFrameCache(KisOpenGLImageTexturesSP textures) { KisAnimationFrameCache *cache; Private::CachesMap::iterator it = Private::caches.find(textures); if (it == Private::caches.end()) { cache = new KisAnimationFrameCache(textures); Private::caches.insert(textures, cache); } else { cache = it.value(); } return cache; } const QList KisAnimationFrameCache::caches() { return Private::caches.values(); } KisAnimationFrameCache::KisAnimationFrameCache(KisOpenGLImageTexturesSP textures) : m_d(new Private(textures)) { // create swapping backend slotConfigChanged(); - connect(m_d->image->animationInterface(), SIGNAL(sigFramesChanged(KisTimeRange,QRect)), this, SLOT(framesChanged(KisTimeRange,QRect))); + connect(m_d->image->animationInterface(), SIGNAL(sigFramesChanged(KisFrameSet, QRect)), this, SLOT(framesChanged(KisFrameSet, QRect))); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); } KisAnimationFrameCache::~KisAnimationFrameCache() { Private::caches.remove(m_d->textures); } bool KisAnimationFrameCache::uploadFrame(int time) { KisOpenGLUpdateInfoSP info = m_d->getFrame(time); if (!info) { // Do nothing! // // Previously we were trying to start cache regeneration in this point, // but it caused even bigger slowdowns when scrubbing } else { m_d->textures->recalculateCache(info); } return bool(info); } bool KisAnimationFrameCache::shouldUploadNewFrame(int newTime, int oldTime) const { if (oldTime < 0) return true; - const int oldKeyframeStart = m_d->getFrameIdAtTime(oldTime); - if (oldKeyframeStart < 0) return true; - - const int oldKeyFrameLength = m_d->newFrames[oldKeyframeStart]; - return !(newTime >= oldKeyframeStart && (newTime < oldKeyframeStart + oldKeyFrameLength || oldKeyFrameLength == -1)); + const int oldFrameId = m_d->getFrameIdAtTime(oldTime); + const int newFrameId = m_d->getFrameIdAtTime(newTime); + return (oldFrameId < 0) || oldFrameId != newFrameId; } KisAnimationFrameCache::CacheStatus KisAnimationFrameCache::frameStatus(int time) const { return m_d->hasFrame(time) ? Cached : Uncached; } KisImageWSP KisAnimationFrameCache::image() { return m_d->image; } -void KisAnimationFrameCache::framesChanged(const KisTimeRange &range, const QRect &rect) +void KisAnimationFrameCache::framesChanged(const KisFrameSet &range, const QRect &rect) { Q_UNUSED(rect); - if (!range.isValid()) return; + if (range.isEmpty()) return; bool cacheChanged = m_d->invalidate(range); if (cacheChanged) { emit changed(); } } void KisAnimationFrameCache::slotConfigChanged() { - m_d->newFrames.clear(); + m_d->cachedFrames.clear(); KisImageConfig cfg(true); if (cfg.useOnDiskAnimationCacheSwapping()) { m_d->swapper.reset(new KisFrameCacheSwapper(m_d->textures->updateInfoBuilder(), cfg.swapDir())); } else { m_d->swapper.reset(new KisInMemoryFrameCacheSwapper()); } m_d->frameSizeLimit = cfg.useAnimationCacheFrameSizeLimit() ? cfg.animationCacheFrameSizeLimit() : 0; emit changed(); } KisOpenGLUpdateInfoSP KisAnimationFrameCache::Private::fetchFrameDataImpl(KisImageSP image, const QRect &requestedRect, int lod) { if (lod > 0) { KisPaintDeviceSP tempDevice = new KisPaintDevice(image->projection()->colorSpace()); tempDevice->prepareClone(image->projection()); image->projection()->generateLodCloneDevice(tempDevice, image->projection()->extent(), lod); const QRect fetchRect = KisLodTransform::alignedRect(requestedRect, lod); return textures->updateInfoBuilder().buildUpdateInfo(fetchRect, tempDevice, image->bounds(), lod, true); } else { return textures->updateCache(requestedRect, image); } } KisOpenGLUpdateInfoSP KisAnimationFrameCache::fetchFrameData(int time, KisImageSP image, const QRegion &requestedRegion) const { if (time != image->animationInterface()->currentTime()) { qWarning() << "WARNING: KisAnimationFrameCache::frameReady image's time doesn't coincide with the requested time!"; qWarning() << " " << ppVar(image->animationInterface()->currentTime()) << ppVar(time); } // the frames are always generated at full scale KIS_SAFE_ASSERT_RECOVER_NOOP(image->currentLevelOfDetail() == 0); const int lod = m_d->effectiveLevelOfDetail(requestedRegion.boundingRect()); KisOpenGLUpdateInfoSP totalInfo; Q_FOREACH (const QRect &rc, requestedRegion.rects()) { KisOpenGLUpdateInfoSP info = m_d->fetchFrameDataImpl(image, rc, lod); if (!totalInfo) { totalInfo = info; } else { const bool result = totalInfo->tryMergeWith(*info); KIS_SAFE_ASSERT_RECOVER_NOOP(result); } } return totalInfo; } void KisAnimationFrameCache::addConvertedFrameData(KisOpenGLUpdateInfoSP info, int time) { - const KisTimeRange identicalRange = - KisTimeRange::calculateIdenticalFramesRecursive(m_d->image->root(), time); + const KisFrameSet identicalFrames = KisTimeRange::calculateIdenticalFramesRecursive(m_d->image->root(), time).asFrameSet(); - m_d->addFrame(info, identicalRange); + m_d->addFrame(info, identicalFrames); emit changed(); } void KisAnimationFrameCache::dropLowQualityFrames(const KisTimeSpan &range, const QRect ®ionOfInterest, const QRect &minimalRect) { - if (m_d->newFrames.isEmpty()) return; + if (m_d->cachedFrames.isEmpty()) return; - auto it = m_d->newFrames.upperBound(range.start()); + QVector framesToDrop; - // the vector is guaranteed to be non-empty, - // so decrementing iterator is safe - if (it != m_d->newFrames.begin()) it--; + for (auto it = m_d->constIteratorFrom(range.start()); it != m_d->cachedFrames.constEnd() && it.key() <= range.end(); it++) { + const Private::CacheEntry &frame = it.value(); - while (it != m_d->newFrames.end() && it.key() <= range.end()) { - const int frameId = it.key(); - const int frameLength = it.value(); + const QRect frameRect = m_d->swapper->frameDirtyRect(frame.frameId); + const int frameLod = m_d->swapper->frameLevelOfDetail(frame.frameId); - if (frameId + frameLength - 1 < range.start()) { - ++it; - continue; + if (frameLod > m_d->effectiveLevelOfDetail(regionOfInterest) || !frameRect.contains(minimalRect)) { + if (!framesToDrop.contains(frame.frameId)) framesToDrop.append(frame.frameId); } + } - const QRect frameRect = m_d->swapper->frameDirtyRect(frameId); - const int frameLod = m_d->swapper->frameLevelOfDetail(frameId); - - if (frameLod > m_d->effectiveLevelOfDetail(regionOfInterest) || !frameRect.contains(minimalRect)) { - m_d->swapper->forgetFrame(frameId); - it = m_d->newFrames.erase(it); - } else { - ++it; + Q_FOREACH(int frameId, framesToDrop) { + Q_FOREACH(int time, m_d->entriesById[frameId]) { + m_d->cachedFrames.remove(time); } + + m_d->entriesById.remove(frameId); + m_d->swapper->forgetFrame(frameId); } } -bool KisAnimationFrameCache::framesHaveValidRoi(const KisTimeRange &range, const QRect ®ionOfInterest) +KisFrameSet KisAnimationFrameCache::dirtyFramesWithin(const KisTimeSpan range) { - KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!range.isInfinite(), false); - if (m_d->newFrames.isEmpty()) return false; + auto it = m_d->constIteratorFrom(range.start()); + if (it == m_d->cachedFrames.constEnd()) return KisFrameSet(range); - auto it = m_d->newFrames.upperBound(range.start()); + QVector cachedSpans; + int firstOfInfinite = -1; - if (it != m_d->newFrames.begin()) it--; + for (; it != m_d->cachedFrames.constEnd() && it.key() <= range.end(); it++) { + const int start = it.key(); + Private::CacheEntry entry = it.value(); - int expectedNextFrameStart = it.key(); + if (entry.isInfinite()) { + firstOfInfinite = start; + } else { + cachedSpans.append(KisTimeSpan(start, start + entry.length - 1)); + } - while (it.key() <= range.end()) { - const int frameId = it.key(); - const int frameLength = it.value(); + } - if (frameId + frameLength - 1 < range.start()) { - expectedNextFrameStart = frameId + frameLength; - ++it; - continue; - } + return KisFrameSet(range) - KisFrameSet(cachedSpans, firstOfInfinite); +} - if (expectedNextFrameStart != frameId) { - KIS_SAFE_ASSERT_RECOVER_NOOP(expectedNextFrameStart < frameId); - return false; - } +int KisAnimationFrameCache::firstDirtyFrameWithin(const KisTimeSpan range, const KisFrameSet *ignoredFrames) +{ + int candidate = range.start(); - if (!m_d->swapper->frameDirtyRect(frameId).contains(regionOfInterest)) { - return false; + for (auto it = m_d->constIteratorFrom(range.start()); it != m_d->cachedFrames.constEnd(); it++) { + const int start = it.key(); + const int end = start + it.value().length - 1; + + if (ignoredFrames) { + candidate = ignoredFrames->firstExcludedSince(candidate); } - expectedNextFrameStart = frameId + frameLength; - ++it; + if (candidate < start) { + return candidate; + } else if (candidate <= end) { + candidate = end + 1; + } } - return true; + return -1; } diff --git a/libs/ui/kis_animation_frame_cache.h b/libs/ui/kis_animation_frame_cache.h index aadecc9eb2..174c275a39 100644 --- a/libs/ui/kis_animation_frame_cache.h +++ b/libs/ui/kis_animation_frame_cache.h @@ -1,90 +1,90 @@ /* * Copyright (c) 2015 Jouni Pentikäinen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_ANIMATION_FRAME_CACHE_H #define KIS_ANIMATION_FRAME_CACHE_H #include #include #include "kritaui_export.h" #include "kis_types.h" #include "kis_shared.h" class KisImage; class KisImageAnimationInterface; -class KisTimeRange; class KisTimeSpan; +class KisFrameSet; class KisOpenGLImageTextures; typedef KisSharedPtr KisOpenGLImageTexturesSP; class KisOpenGLUpdateInfo; typedef KisSharedPtr KisOpenGLUpdateInfoSP; class KRITAUI_EXPORT KisAnimationFrameCache : public QObject, public KisShared { Q_OBJECT public: static KisAnimationFrameCacheSP getFrameCache(KisOpenGLImageTexturesSP textures); static const QList caches(); KisAnimationFrameCache(KisOpenGLImageTexturesSP textures); ~KisAnimationFrameCache() override; QImage getFrame(int time); bool uploadFrame(int time); bool shouldUploadNewFrame(int newTime, int oldTime) const; enum CacheStatus { Cached, Uncached, }; CacheStatus frameStatus(int time) const; + KisFrameSet dirtyFramesWithin(KisTimeSpan range); + int firstDirtyFrameWithin(KisTimeSpan range, const KisFrameSet *ignoredFrames = 0); KisImageWSP image(); KisOpenGLUpdateInfoSP fetchFrameData(int time, KisImageSP image, const QRegion &requestedRegion) const; void addConvertedFrameData(KisOpenGLUpdateInfoSP info, int time); /** * Drops all the frames with worse level of detail values than the current * desired level of detail. */ void dropLowQualityFrames(const KisTimeSpan &range, const QRect ®ionOfInterest, const QRect &minimalRect); - bool framesHaveValidRoi(const KisTimeRange &range, const QRect ®ionOfInterest); - Q_SIGNALS: void changed(); private: struct Private; QScopedPointer m_d; private Q_SLOTS: - void framesChanged(const KisTimeRange &range, const QRect &rect); + void framesChanged(const KisFrameSet &range, const QRect &rect); void slotConfigChanged(); }; #endif