File indexing completed on 2025-01-26 04:11:17
0001 /* 0002 * SPDX-FileCopyrightText: 2017 Dmitry Kazakov <dimula73@gmail.com> 0003 * 0004 * SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "KisDabRenderingQueue.h" 0008 0009 #include "KisDabRenderingJob.h" 0010 #include "KisRenderedDab.h" 0011 #include "kis_painter.h" 0012 #include "KisOptimizedByteArray.h" 0013 0014 #include <QSet> 0015 #include <QMutex> 0016 #include <QMutexLocker> 0017 #include <KisRollingMeanAccumulatorWrapper.h> 0018 0019 #include "kis_algebra_2d.h" 0020 0021 struct KisDabRenderingQueue::Private 0022 { 0023 struct DumbCacheInterface : public CacheInterface { 0024 void getDabType(bool hasDabInCache, 0025 KisDabCacheUtils::DabRenderingResources *resources, 0026 const KisDabCacheUtils::DabRequestInfo &request, 0027 /* out */ 0028 KisDabCacheUtils::DabGenerationInfo *di, 0029 bool *shouldUseCache) override 0030 { 0031 Q_UNUSED(hasDabInCache); 0032 Q_UNUSED(resources); 0033 Q_UNUSED(request); 0034 0035 di->needsPostprocessing = false; 0036 *shouldUseCache = false; 0037 } 0038 0039 bool hasSeparateOriginal(KisDabCacheUtils::DabRenderingResources *resources) const override 0040 { 0041 Q_UNUSED(resources); 0042 return false; 0043 } 0044 0045 }; 0046 0047 Private(const KoColorSpace *_colorSpace, 0048 KisDabCacheUtils::ResourcesFactory _resourcesFactory) 0049 : cacheInterface(new DumbCacheInterface), 0050 colorSpace(_colorSpace), 0051 resourcesFactory(_resourcesFactory), 0052 paintDeviceAllocator(new KisOptimizedByteArray::PooledMemoryAllocator()), 0053 avgExecutionTime(50), 0054 avgDabSize(50) 0055 { 0056 KIS_SAFE_ASSERT_RECOVER_NOOP(resourcesFactory); 0057 } 0058 0059 ~Private() { 0060 // clear the jobs, so that they would not keep references to any 0061 // paint devices anymore 0062 jobs.clear(); 0063 0064 qDeleteAll(cachedResources); 0065 cachedResources.clear(); 0066 } 0067 0068 QList<KisDabRenderingJobSP> jobs; 0069 int nextSeqNoToUse = 0; 0070 int lastPaintedJob = -1; 0071 int lastDabJobInQueue = -1; 0072 QScopedPointer<CacheInterface> cacheInterface; 0073 const KoColorSpace *colorSpace; 0074 qreal averageOpacity = 0.0; 0075 0076 KisDabCacheUtils::ResourcesFactory resourcesFactory; 0077 0078 QList<KisDabCacheUtils::DabRenderingResources*> cachedResources; 0079 QSharedPointer<KisOptimizedByteArray::MemoryAllocator> paintDeviceAllocator; 0080 0081 QMutex mutex; 0082 0083 KisRollingMeanAccumulatorWrapper avgExecutionTime; 0084 KisRollingMeanAccumulatorWrapper avgDabSize; 0085 0086 int calculateLastDabJobIndex(int startSearchIndex); 0087 void cleanPaintedDabs(); 0088 bool dabsHaveSeparateOriginal(); 0089 bool hasPreparedDabsImpl() const; 0090 0091 KisDabCacheUtils::DabRenderingResources* fetchResourcesFromCache(); 0092 void putResourcesToCache(KisDabCacheUtils::DabRenderingResources *resources); 0093 }; 0094 0095 0096 KisDabRenderingQueue::KisDabRenderingQueue(const KoColorSpace *cs, 0097 KisDabCacheUtils::ResourcesFactory resourcesFactory) 0098 : m_d(new Private(cs, resourcesFactory)) 0099 { 0100 } 0101 0102 KisDabRenderingQueue::~KisDabRenderingQueue() 0103 { 0104 } 0105 0106 int KisDabRenderingQueue::Private::calculateLastDabJobIndex(int startSearchIndex) 0107 { 0108 if (startSearchIndex < 0) { 0109 startSearchIndex = jobs.size() - 1; 0110 } 0111 0112 // try to use cached value 0113 if (startSearchIndex >= lastDabJobInQueue) { 0114 return lastDabJobInQueue; 0115 } 0116 0117 // if we are below the cached value, just iterate through the dabs 0118 // (which is extremely(!) slow) 0119 for (int i = startSearchIndex; i >= 0; i--) { 0120 if (jobs[i]->type == KisDabRenderingJob::Dab) { 0121 return i; 0122 } 0123 } 0124 0125 return -1; 0126 } 0127 0128 KisDabRenderingJobSP KisDabRenderingQueue::addDab(const KisDabCacheUtils::DabRequestInfo &request, 0129 qreal opacity, qreal flow) 0130 { 0131 QMutexLocker l(&m_d->mutex); 0132 0133 const int seqNo = m_d->nextSeqNoToUse++; 0134 0135 KisDabCacheUtils::DabRenderingResources *resources = m_d->fetchResourcesFromCache(); 0136 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(resources, KisDabRenderingJobSP()); 0137 0138 // We should sync the cached brush into the current seqNo 0139 resources->syncResourcesToSeqNo(seqNo, request.info); 0140 0141 const int lastDabJobIndex = m_d->lastDabJobInQueue; 0142 0143 KisDabRenderingJobSP job(new KisDabRenderingJob(seqNo, KisDabRenderingJob::Dab, opacity, flow)); 0144 0145 bool shouldUseCache = false; 0146 m_d->cacheInterface->getDabType(lastDabJobIndex >= 0, resources, request, &job->generationInfo, &shouldUseCache); 0147 0148 m_d->putResourcesToCache(resources); 0149 resources = nullptr; 0150 0151 job->type = !shouldUseCache ? KisDabRenderingJob::Dab 0152 : job->generationInfo.needsPostprocessing ? KisDabRenderingJob::Postprocess 0153 : KisDabRenderingJob::Copy; 0154 0155 if (job->type == KisDabRenderingJob::Dab) { 0156 job->status = KisDabRenderingJob::Running; 0157 } else if (job->type == KisDabRenderingJob::Postprocess || 0158 job->type == KisDabRenderingJob::Copy) { 0159 0160 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(lastDabJobIndex >= 0, KisDabRenderingJobSP()); 0161 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(lastDabJobIndex < m_d->jobs.size(), KisDabRenderingJobSP()); 0162 0163 if (m_d->jobs[lastDabJobIndex]->status == KisDabRenderingJob::Completed) { 0164 if (job->type == KisDabRenderingJob::Postprocess) { 0165 job->status = KisDabRenderingJob::Running; 0166 job->originalDevice = m_d->jobs[lastDabJobIndex]->originalDevice; 0167 } else if (job->type == KisDabRenderingJob::Copy) { 0168 job->status = KisDabRenderingJob::Completed; 0169 job->originalDevice = m_d->jobs[lastDabJobIndex]->originalDevice; 0170 job->postprocessedDevice = m_d->jobs[lastDabJobIndex]->postprocessedDevice; 0171 m_d->avgExecutionTime(0); 0172 } 0173 } 0174 } 0175 0176 m_d->jobs.append(job); 0177 0178 KisDabRenderingJobSP jobToRun; 0179 if (job->status == KisDabRenderingJob::Running) { 0180 jobToRun = job; 0181 } 0182 0183 if (job->type == KisDabRenderingJob::Dab) { 0184 m_d->lastDabJobInQueue = m_d->jobs.size() - 1; 0185 m_d->cleanPaintedDabs(); 0186 } 0187 0188 // collect some statistics about the dab 0189 m_d->avgDabSize(KisAlgebra2D::maxDimension(job->generationInfo.dstDabRect)); 0190 0191 return jobToRun; 0192 } 0193 0194 QList<KisDabRenderingJobSP> KisDabRenderingQueue::notifyJobFinished(int seqNo, int usecsTime) 0195 { 0196 QMutexLocker l(&m_d->mutex); 0197 0198 QList<KisDabRenderingJobSP> dependentJobs; 0199 0200 /** 0201 * Here we use binary search for locating the necessary original dab 0202 */ 0203 auto finishedJobIt = 0204 std::lower_bound(m_d->jobs.begin(), m_d->jobs.end(), seqNo, 0205 kismpl::mem_less(&KisDabRenderingJob::seqNo)); 0206 0207 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(finishedJobIt != m_d->jobs.end(), dependentJobs); 0208 KisDabRenderingJobSP finishedJob = *finishedJobIt; 0209 0210 KIS_SAFE_ASSERT_RECOVER_NOOP(finishedJob->status == KisDabRenderingJob::Running); 0211 KIS_SAFE_ASSERT_RECOVER_NOOP(finishedJob->seqNo == seqNo); 0212 KIS_SAFE_ASSERT_RECOVER_NOOP(finishedJob->originalDevice); 0213 KIS_SAFE_ASSERT_RECOVER_NOOP(finishedJob->postprocessedDevice); 0214 0215 finishedJob->status = KisDabRenderingJob::Completed; 0216 0217 if (finishedJob->type == KisDabRenderingJob::Dab) { 0218 for (auto it = finishedJobIt + 1; it != m_d->jobs.end(); ++it) { 0219 KisDabRenderingJobSP j = *it; 0220 0221 // next dab job closes the chain 0222 if (j->type == KisDabRenderingJob::Dab) break; 0223 0224 // the non 'dab'-type job couldn't have 0225 // been started before the source ob was completed 0226 KIS_SAFE_ASSERT_RECOVER_BREAK(j->status == KisDabRenderingJob::New); 0227 0228 if (j->type == KisDabRenderingJob::Copy) { 0229 0230 j->originalDevice = finishedJob->originalDevice; 0231 j->postprocessedDevice = finishedJob->postprocessedDevice; 0232 j->status = KisDabRenderingJob::Completed; 0233 m_d->avgExecutionTime(0); 0234 0235 } else if (j->type == KisDabRenderingJob::Postprocess) { 0236 0237 j->originalDevice = finishedJob->originalDevice; 0238 j->status = KisDabRenderingJob::Running; 0239 dependentJobs << j; 0240 } 0241 } 0242 } 0243 0244 if (usecsTime >= 0) { 0245 m_d->avgExecutionTime(usecsTime); 0246 } 0247 0248 return dependentJobs; 0249 } 0250 0251 void KisDabRenderingQueue::Private::cleanPaintedDabs() 0252 { 0253 const int nextToBePainted = lastPaintedJob + 1; 0254 const int lastSourceJob = calculateLastDabJobIndex(qMin(nextToBePainted, jobs.size() - 1)); 0255 0256 if (lastPaintedJob >= 0) { 0257 int numRemovedJobs = 0; 0258 int numRemovedJobsBeforeLastSource = 0; 0259 0260 auto it = jobs.begin(); 0261 for (int i = 0; i <= lastPaintedJob; i++) { 0262 KisDabRenderingJobSP job = *it; 0263 0264 if (i < lastSourceJob || job->type != KisDabRenderingJob::Dab){ 0265 0266 KIS_ASSERT_RECOVER_NOOP(job->originalDevice); 0267 0268 it = jobs.erase(it); 0269 numRemovedJobs++; 0270 if (i < lastDabJobInQueue) { 0271 numRemovedJobsBeforeLastSource++; 0272 } 0273 0274 } else { 0275 ++it; 0276 } 0277 } 0278 0279 KIS_SAFE_ASSERT_RECOVER_RETURN(jobs.size() > 0); 0280 0281 lastPaintedJob -= numRemovedJobs; 0282 lastDabJobInQueue -= numRemovedJobsBeforeLastSource; 0283 } 0284 } 0285 0286 QList<KisRenderedDab> KisDabRenderingQueue::takeReadyDabs(bool returnMutableDabs, 0287 int oneTimeLimit, 0288 bool *someDabsLeft) 0289 { 0290 QMutexLocker l(&m_d->mutex); 0291 0292 QList<KisRenderedDab> renderedDabs; 0293 if (m_d->jobs.isEmpty()) return renderedDabs; 0294 0295 KIS_SAFE_ASSERT_RECOVER_NOOP( 0296 m_d->jobs.isEmpty() || 0297 m_d->jobs.first()->type == KisDabRenderingJob::Dab); 0298 0299 const int copyJobAfterInclusive = 0300 returnMutableDabs && !m_d->dabsHaveSeparateOriginal() ? 0301 m_d->lastDabJobInQueue : 0302 std::numeric_limits<int>::max(); 0303 0304 if (oneTimeLimit < 0) { 0305 oneTimeLimit = std::numeric_limits<int>::max(); 0306 } 0307 0308 for (int i = 0; i < m_d->jobs.size() && oneTimeLimit > 0; i++, oneTimeLimit--) { 0309 KisDabRenderingJobSP j = m_d->jobs[i]; 0310 0311 if (j->status != KisDabRenderingJob::Completed) break; 0312 0313 if (i <= m_d->lastPaintedJob) continue; 0314 0315 KisRenderedDab dab; 0316 KisFixedPaintDeviceSP resultDevice = j->postprocessedDevice; 0317 0318 if (i >= copyJobAfterInclusive) { 0319 resultDevice = new KisFixedPaintDevice(*resultDevice); 0320 } 0321 0322 dab.device = resultDevice; 0323 dab.offset = j->dstDabOffset(); 0324 dab.opacity = j->opacity; 0325 dab.flow = j->flow; 0326 0327 m_d->averageOpacity = KisPainter::blendAverageOpacity(j->opacity, m_d->averageOpacity); 0328 dab.averageOpacity = m_d->averageOpacity; 0329 0330 0331 renderedDabs.append(dab); 0332 0333 m_d->lastPaintedJob = i; 0334 } 0335 0336 m_d->cleanPaintedDabs(); 0337 0338 if (someDabsLeft) { 0339 *someDabsLeft = m_d->hasPreparedDabsImpl(); 0340 } 0341 0342 return renderedDabs; 0343 } 0344 0345 bool KisDabRenderingQueue::Private::hasPreparedDabsImpl() const 0346 { 0347 const int nextToBePainted = lastPaintedJob + 1; 0348 0349 return 0350 nextToBePainted >= 0 && 0351 nextToBePainted < jobs.size() && 0352 jobs[nextToBePainted]->status == KisDabRenderingJob::Completed; 0353 } 0354 0355 0356 bool KisDabRenderingQueue::hasPreparedDabs() const 0357 { 0358 QMutexLocker l(&m_d->mutex); 0359 return m_d->hasPreparedDabsImpl(); 0360 } 0361 0362 void KisDabRenderingQueue::setCacheInterface(KisDabRenderingQueue::CacheInterface *interface) 0363 { 0364 m_d->cacheInterface.reset(interface); 0365 } 0366 0367 KisFixedPaintDeviceSP KisDabRenderingQueue::fetchCachedPaintDevice() 0368 { 0369 /** 0370 * We create a special type of a fixed paint device that 0371 * uses a custom allocator for better efficiency. 0372 */ 0373 return new KisFixedPaintDevice(m_d->colorSpace, m_d->paintDeviceAllocator); 0374 } 0375 0376 qreal KisDabRenderingQueue::averageExecutionTime() const 0377 { 0378 QMutexLocker l(&m_d->mutex); 0379 return m_d->avgExecutionTime.rollingMean() / 1000.0; 0380 } 0381 0382 int KisDabRenderingQueue::averageDabSize() const 0383 { 0384 QMutexLocker l(&m_d->mutex); 0385 return qRound(m_d->avgDabSize.rollingMean()); 0386 } 0387 0388 bool KisDabRenderingQueue::Private::dabsHaveSeparateOriginal() 0389 { 0390 KisDabCacheUtils::DabRenderingResources *resources = fetchResourcesFromCache(); 0391 0392 const bool result = cacheInterface->hasSeparateOriginal(resources); 0393 0394 putResourcesToCache(resources); 0395 0396 return result; 0397 } 0398 0399 KisDabCacheUtils::DabRenderingResources *KisDabRenderingQueue::Private::fetchResourcesFromCache() 0400 { 0401 KisDabCacheUtils::DabRenderingResources *resources = 0; 0402 0403 // fetch/create a temporary resources object 0404 if (!cachedResources.isEmpty()) { 0405 resources = cachedResources.takeLast(); 0406 } else { 0407 resources = resourcesFactory(); 0408 } 0409 0410 return resources; 0411 } 0412 0413 void KisDabRenderingQueue::Private::putResourcesToCache(KisDabCacheUtils::DabRenderingResources *resources) 0414 { 0415 cachedResources << resources; 0416 } 0417 0418 KisDabCacheUtils::DabRenderingResources *KisDabRenderingQueue::fetchResourcesFromCache() 0419 { 0420 // TODO: make a separate lock for that 0421 QMutexLocker l(&m_d->mutex); 0422 return m_d->fetchResourcesFromCache(); 0423 } 0424 0425 void KisDabRenderingQueue::putResourcesToCache(KisDabCacheUtils::DabRenderingResources *resources) 0426 { 0427 QMutexLocker l(&m_d->mutex); 0428 m_d->putResourcesToCache(resources); 0429 } 0430 0431 int KisDabRenderingQueue::testingGetQueueSize() const 0432 { 0433 QMutexLocker l(&m_d->mutex); 0434 0435 return m_d->jobs.size(); 0436 } 0437