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