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 }