File indexing completed on 2024-05-12 15:58:42

0001 /*
0002  *  SPDX-FileCopyrightText: 2010 Dmitry Kazakov <dimula73@gmail.com>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "kis_simple_update_queue.h"
0008 
0009 #include <QMutexLocker>
0010 #include <QVector>
0011 
0012 #include "kis_image_config.h"
0013 #include "kis_full_refresh_walker.h"
0014 #include "kis_spontaneous_job.h"
0015 
0016 
0017 //#define ENABLE_DEBUG_JOIN
0018 //#define ENABLE_ACCUMULATOR
0019 
0020 #ifdef ENABLE_DEBUG_JOIN
0021     #define DEBUG_JOIN(baseRect, newRect, alpha)                     \
0022         dbgKrita << "Two rects were joined:\t"                       \
0023                  << (baseRect) << "+" << (newRect) << "->"           \
0024                  << ((baseRect) | (newRect)) << "(" << alpha << ")"
0025 
0026 #else
0027     #define DEBUG_JOIN(baseRect, newRect, alpha)
0028 #endif /* ENABLE_DEBUG_JOIN */
0029 
0030 
0031 #ifdef ENABLE_ACCUMULATOR
0032     #define DECLARE_ACCUMULATOR() static qreal _baseAmount=0, _newAmount=0
0033     #define ACCUMULATOR_ADD(baseAmount, newAmount) \
0034         do {_baseAmount += baseAmount; _newAmount += newAmount;} while (0)
0035     #define ACCUMULATOR_DEBUG() \
0036         dbgKrita << "Accumulated alpha:" << _newAmount / _baseAmount
0037 #else
0038     #define DECLARE_ACCUMULATOR()
0039     #define ACCUMULATOR_ADD(baseAmount, newAmount)
0040     #define ACCUMULATOR_DEBUG()
0041 #endif /* ENABLE_ACCUMULATOR */
0042 
0043 
0044 KisSimpleUpdateQueue::KisSimpleUpdateQueue()
0045     : m_overrideLevelOfDetail(-1)
0046 {
0047     updateSettings();
0048 }
0049 
0050 KisSimpleUpdateQueue::~KisSimpleUpdateQueue()
0051 {
0052     QMutexLocker locker(&m_lock);
0053 
0054     while (!m_spontaneousJobsList.isEmpty()) {
0055         delete m_spontaneousJobsList.takeLast();
0056     }
0057 }
0058 
0059 void KisSimpleUpdateQueue::updateSettings()
0060 {
0061     QMutexLocker locker(&m_lock);
0062 
0063     KisImageConfig config(true);
0064 
0065     m_patchWidth = config.updatePatchWidth();
0066     m_patchHeight = config.updatePatchHeight();
0067 
0068     m_maxCollectAlpha = config.maxCollectAlpha();
0069     m_maxMergeAlpha = config.maxMergeAlpha();
0070     m_maxMergeCollectAlpha = config.maxMergeCollectAlpha();
0071 }
0072 
0073 int KisSimpleUpdateQueue::overrideLevelOfDetail() const
0074 {
0075     return m_overrideLevelOfDetail;
0076 }
0077 
0078 void KisSimpleUpdateQueue::processQueue(KisUpdaterContext &updaterContext)
0079 {
0080     updaterContext.lock();
0081 
0082     while(updaterContext.hasSpareThread() &&
0083           processOneJob(updaterContext));
0084 
0085     updaterContext.unlock();
0086 }
0087 
0088 bool KisSimpleUpdateQueue::processOneJob(KisUpdaterContext &updaterContext)
0089 {
0090     QMutexLocker locker(&m_lock);
0091 
0092     KisBaseRectsWalkerSP item;
0093     KisMutableWalkersListIterator iter(m_updatesList);
0094     bool jobAdded = false;
0095 
0096     int currentLevelOfDetail = updaterContext.currentLevelOfDetail();
0097 
0098     while(iter.hasNext()) {
0099         item = iter.next();
0100 
0101         if ((currentLevelOfDetail < 0 || currentLevelOfDetail == item->levelOfDetail()) &&
0102             !item->checksumValid()) {
0103 
0104             m_overrideLevelOfDetail = item->levelOfDetail();
0105             item->recalculate(item->requestedRect());
0106             m_overrideLevelOfDetail = -1;
0107         }
0108 
0109         if ((currentLevelOfDetail < 0 || currentLevelOfDetail == item->levelOfDetail()) &&
0110             updaterContext.isJobAllowed(item)) {
0111 
0112             updaterContext.addMergeJob(item);
0113             iter.remove();
0114             jobAdded = true;
0115             break;
0116         }
0117     }
0118 
0119     if (jobAdded) return true;
0120 
0121     if (!m_spontaneousJobsList.isEmpty()) {
0122         /**
0123          * WARNING: Please note that this still doesn't guarantee that
0124          * the spontaneous jobs are exclusive, since updates and/or
0125          * strokes can be added after them. The only thing it
0126          * guarantees that two spontaneous jobs will not be executed
0127          * in parallel.
0128          *
0129          * Right now it works as it is. Probably will need to be fixed
0130          * in the future.
0131          */
0132         qint32 numMergeJobs;
0133         qint32 numStrokeJobs;
0134         updaterContext.getJobsSnapshot(numMergeJobs, numStrokeJobs);
0135 
0136         KisSpontaneousJob *job = m_spontaneousJobsList.first();
0137         if (!numMergeJobs && !numStrokeJobs &&
0138             (currentLevelOfDetail < 0 || currentLevelOfDetail == job->levelOfDetail())) {
0139 
0140             updaterContext.addSpontaneousJob(job);
0141             m_spontaneousJobsList.removeFirst();
0142             jobAdded = true;
0143         }
0144     }
0145 
0146     return jobAdded;
0147 }
0148 
0149 void KisSimpleUpdateQueue::addUpdateJob(KisNodeSP node, const QVector<QRect> &rects, const QRect& cropRect, int levelOfDetail)
0150 {
0151     addJob(node, rects, cropRect, levelOfDetail, KisBaseRectsWalker::UPDATE);
0152 }
0153 
0154 void KisSimpleUpdateQueue::addUpdateJob(KisNodeSP node, const QRect &rc, const QRect& cropRect, int levelOfDetail)
0155 {
0156     addJob(node, {rc}, cropRect, levelOfDetail, KisBaseRectsWalker::UPDATE);
0157 }
0158 
0159 void KisSimpleUpdateQueue::addUpdateNoFilthyJob(KisNodeSP node, const QVector<QRect>& rects, const QRect& cropRect, int levelOfDetail)
0160 {
0161     addJob(node, rects, cropRect, levelOfDetail, KisBaseRectsWalker::UPDATE_NO_FILTHY);
0162 }
0163 
0164 void KisSimpleUpdateQueue::addUpdateNoFilthyJob(KisNodeSP node, const QRect& rc, const QRect& cropRect, int levelOfDetail)
0165 {
0166     addJob(node, {rc}, cropRect, levelOfDetail, KisBaseRectsWalker::UPDATE_NO_FILTHY);
0167 }
0168 
0169 void KisSimpleUpdateQueue::addFullRefreshJob(KisNodeSP node, const QRect &rc, const QRect& cropRect, int levelOfDetail)
0170 {
0171     addJob(node, {rc}, cropRect, levelOfDetail, KisBaseRectsWalker::FULL_REFRESH);
0172 }
0173 
0174 void KisSimpleUpdateQueue::addFullRefreshJob(KisNodeSP node, const QVector<QRect>& rects, const QRect& cropRect, int levelOfDetail)
0175 {
0176     addJob(node, rects, cropRect, levelOfDetail, KisBaseRectsWalker::FULL_REFRESH);
0177 }
0178 
0179 void KisSimpleUpdateQueue::addFullRefreshNoFilthyJob(KisNodeSP node, const QVector<QRect> &rects, const QRect &cropRect, int levelOfDetail)
0180 {
0181     addJob(node, rects, cropRect, levelOfDetail, KisBaseRectsWalker::FULL_REFRESH_NO_FILTHY);
0182 }
0183 
0184 void KisSimpleUpdateQueue::addJob(KisNodeSP node, const QVector<QRect> &rects,
0185                                   const QRect& cropRect,
0186                                   int levelOfDetail,
0187                                   KisBaseRectsWalker::UpdateType type)
0188 {
0189     QList<KisBaseRectsWalkerSP> walkers;
0190 
0191     Q_FOREACH (const QRect &rc, rects) {
0192         if (rc.isEmpty()) continue;
0193 
0194         KisBaseRectsWalkerSP walker;
0195 
0196         if(trySplitJob(node, rc, cropRect, levelOfDetail, type)) continue;
0197         if(tryMergeJob(node, rc, cropRect, levelOfDetail, type)) continue;
0198 
0199         if (type == KisBaseRectsWalker::UPDATE) {
0200             walker = new KisMergeWalker(cropRect, KisMergeWalker::DEFAULT);
0201         }
0202         else if (type == KisBaseRectsWalker::FULL_REFRESH)  {
0203             walker = new KisFullRefreshWalker(cropRect);
0204         }
0205         else if (type == KisBaseRectsWalker::UPDATE_NO_FILTHY) {
0206             walker = new KisMergeWalker(cropRect, KisMergeWalker::NO_FILTHY);
0207         }
0208         else if (type == KisBaseRectsWalker::FULL_REFRESH_NO_FILTHY)  {
0209             walker = new KisFullRefreshWalker(cropRect, KisFullRefreshWalker::NoFilthyMode);
0210         }
0211         /* else if(type == KisBaseRectsWalker::UNSUPPORTED) fatalKrita; */
0212 
0213         walker->collectRects(node, rc);
0214         walkers.append(walker);
0215     }
0216 
0217     if (!walkers.isEmpty()) {
0218         m_lock.lock();
0219         m_updatesList.append(walkers);
0220         m_lock.unlock();
0221     }
0222 }
0223 
0224 void KisSimpleUpdateQueue::addSpontaneousJob(KisSpontaneousJob *spontaneousJob)
0225 {
0226     QMutexLocker locker(&m_lock);
0227 
0228     KisSpontaneousJob *item;
0229     KisMutableSpontaneousJobsListIterator iter(m_spontaneousJobsList);
0230 
0231     iter.toBack();
0232 
0233     while(iter.hasPrevious()) {
0234         item = iter.previous();
0235 
0236         if (spontaneousJob->overrides(item)) {
0237             iter.remove();
0238             delete item;
0239         }
0240     }
0241 
0242     m_spontaneousJobsList.append(spontaneousJob);
0243 }
0244 
0245 bool KisSimpleUpdateQueue::isEmpty() const
0246 {
0247     QMutexLocker locker(&m_lock);
0248     return m_updatesList.isEmpty() && m_spontaneousJobsList.isEmpty();
0249 }
0250 
0251 qint32 KisSimpleUpdateQueue::sizeMetric() const
0252 {
0253     QMutexLocker locker(&m_lock);
0254     return m_updatesList.size() + m_spontaneousJobsList.size();
0255 }
0256 
0257 bool KisSimpleUpdateQueue::trySplitJob(KisNodeSP node, const QRect& rc,
0258                                        const QRect& cropRect,
0259                                        int levelOfDetail,
0260                                        KisBaseRectsWalker::UpdateType type)
0261 {
0262     if(rc.width() <= m_patchWidth || rc.height() <= m_patchHeight)
0263         return false;
0264 
0265     // a bit of recursive splitting...
0266 
0267     qint32 firstCol = rc.x() / m_patchWidth;
0268     qint32 firstRow = rc.y() / m_patchHeight;
0269 
0270     qint32 lastCol = (rc.x() + rc.width()) / m_patchWidth;
0271     qint32 lastRow = (rc.y() + rc.height()) / m_patchHeight;
0272 
0273     QVector<QRect> splitRects;
0274 
0275     for(qint32 i = firstRow; i <= lastRow; i++) {
0276         for(qint32 j = firstCol; j <= lastCol; j++) {
0277             QRect maxPatchRect(j * m_patchWidth, i * m_patchHeight,
0278                                m_patchWidth, m_patchHeight);
0279             QRect patchRect = rc & maxPatchRect;
0280             splitRects.append(patchRect);
0281         }
0282     }
0283 
0284     KIS_SAFE_ASSERT_RECOVER_NOOP(!splitRects.isEmpty());
0285     addJob(node, splitRects, cropRect, levelOfDetail, type);
0286 
0287     return true;
0288 }
0289 
0290 bool KisSimpleUpdateQueue::tryMergeJob(KisNodeSP node, const QRect& rc,
0291                                        const QRect& cropRect,
0292                                        int levelOfDetail,
0293                                        KisBaseRectsWalker::UpdateType type)
0294 {
0295     QMutexLocker locker(&m_lock);
0296 
0297     QRect baseRect = rc;
0298 
0299     KisBaseRectsWalkerSP goodCandidate;
0300     KisBaseRectsWalkerSP item;
0301     KisWalkersListIterator iter(m_updatesList);
0302 
0303     /**
0304      * We add new jobs to the tail of the list,
0305      * so it's more probable to find a good candidate here.
0306      */
0307 
0308     iter.toBack();
0309 
0310     while(iter.hasPrevious()) {
0311         item = iter.previous();
0312 
0313         if(item->startNode() != node) continue;
0314         if(item->type() != type) continue;
0315         if(item->cropRect() != cropRect) continue;
0316         if(item->levelOfDetail() != levelOfDetail) continue;
0317 
0318         if(joinRects(baseRect, item->requestedRect(), m_maxMergeAlpha)) {
0319             goodCandidate = item;
0320             break;
0321         }
0322     }
0323 
0324     if(goodCandidate)
0325         collectJobs(goodCandidate, baseRect, m_maxMergeCollectAlpha);
0326 
0327     return (bool)goodCandidate;
0328 }
0329 
0330 void KisSimpleUpdateQueue::optimize()
0331 {
0332     QMutexLocker locker(&m_lock);
0333 
0334     if(m_updatesList.size() <= 1) return;
0335 
0336     KisBaseRectsWalkerSP baseWalker = m_updatesList.first();
0337     QRect baseRect = baseWalker->requestedRect();
0338 
0339     collectJobs(baseWalker, baseRect, m_maxCollectAlpha);
0340 }
0341 
0342 void KisSimpleUpdateQueue::collectJobs(KisBaseRectsWalkerSP &baseWalker,
0343                                        QRect baseRect,
0344                                        const qreal maxAlpha)
0345 {
0346     KisBaseRectsWalkerSP item;
0347     KisMutableWalkersListIterator iter(m_updatesList);
0348 
0349     while(iter.hasNext()) {
0350         item = iter.next();
0351 
0352         if(item == baseWalker) continue;
0353         if(item->type() != baseWalker->type()) continue;
0354         if(item->startNode() != baseWalker->startNode()) continue;
0355         if(item->cropRect() != baseWalker->cropRect()) continue;
0356         if(item->levelOfDetail() != baseWalker->levelOfDetail()) continue;
0357 
0358         if(joinRects(baseRect, item->requestedRect(), maxAlpha)) {
0359             iter.remove();
0360         }
0361     }
0362 
0363     if(baseWalker->requestedRect() != baseRect) {
0364         baseWalker->collectRects(baseWalker->startNode(), baseRect);
0365     }
0366 }
0367 
0368 bool KisSimpleUpdateQueue::joinRects(QRect& baseRect,
0369                                      const QRect& newRect, qreal maxAlpha)
0370 {
0371     QRect unitedRect = baseRect | newRect;
0372     if(unitedRect.width() > m_patchWidth || unitedRect.height() > m_patchHeight)
0373         return false;
0374 
0375     bool result = false;
0376     qint64 baseWork = qint64(baseRect.width()) * baseRect.height() +
0377         qint64(newRect.width()) * newRect.height();
0378 
0379     qint64 newWork = qint64(unitedRect.width()) * unitedRect.height();
0380 
0381     qreal alpha = qreal(newWork) / baseWork;
0382 
0383     if(alpha < maxAlpha) {
0384         DEBUG_JOIN(baseRect, newRect, alpha);
0385 
0386         DECLARE_ACCUMULATOR();
0387         ACCUMULATOR_ADD(baseWork, newWork);
0388         ACCUMULATOR_DEBUG();
0389 
0390         baseRect = unitedRect;
0391         result = true;
0392     }
0393 
0394     return result;
0395 }
0396 
0397 KisWalkersList& KisTestableSimpleUpdateQueue::getWalkersList()
0398 {
0399     return m_updatesList;
0400 }
0401 
0402 KisSpontaneousJobsList& KisTestableSimpleUpdateQueue::getSpontaneousJobsList()
0403 {
0404     return m_spontaneousJobsList;
0405 }