diff --git a/plugins/paintops/defaultpaintops/brush/KisDabRenderingQueue.cpp b/plugins/paintops/defaultpaintops/brush/KisDabRenderingQueue.cpp index cf5ebca6f7..26d1d01383 100644 --- a/plugins/paintops/defaultpaintops/brush/KisDabRenderingQueue.cpp +++ b/plugins/paintops/defaultpaintops/brush/KisDabRenderingQueue.cpp @@ -1,453 +1,459 @@ /* * 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 "KisDabRenderingQueue.h" #include "KisDabRenderingJob.h" #include "KisRenderedDab.h" #include "kis_painter.h" #include #include #include #include #include "kis_algebra_2d.h" struct KisDabRenderingQueue::Private { struct DumbCacheInterface : public CacheInterface { void getDabType(bool hasDabInCache, KisDabCacheUtils::DabRenderingResources *resources, const KisDabCacheUtils::DabRequestInfo &request, /* out */ KisDabCacheUtils::DabGenerationInfo *di, bool *shouldUseCache) override { Q_UNUSED(hasDabInCache); Q_UNUSED(resources); Q_UNUSED(request); di->needsPostprocessing = false; *shouldUseCache = false; } bool hasSeparateOriginal(KisDabCacheUtils::DabRenderingResources *resources) const override { Q_UNUSED(resources); return false; } }; Private(const KoColorSpace *_colorSpace, KisDabCacheUtils::ResourcesFactory _resourcesFactory) : cacheInterface(new DumbCacheInterface), colorSpace(_colorSpace), resourcesFactory(_resourcesFactory), avgExecutionTime(50), avgDabSize(50) { KIS_SAFE_ASSERT_RECOVER_NOOP(resourcesFactory); } ~Private() { qDeleteAll(cachedResources); cachedResources.clear(); } QList jobs; int nextSeqNoToUse = 0; int lastPaintedJob = -1; int lastDabJobInQueue = -1; QScopedPointer cacheInterface; const KoColorSpace *colorSpace; qreal averageOpacity = 0.0; KisDabCacheUtils::ResourcesFactory resourcesFactory; QList cachedResources; QSet cachedPaintDevices; QMutex mutex; KisRollingMeanAccumulatorWrapper avgExecutionTime; KisRollingMeanAccumulatorWrapper avgDabSize; int calculateLastDabJobIndex(int startSearchIndex); void cleanPaintedDabs(); bool dabsHaveSeparateOriginal(); KisDabCacheUtils::DabRenderingResources* fetchResourcesFromCache(); void putResourcesToCache(KisDabCacheUtils::DabRenderingResources *resources); }; KisDabRenderingQueue::KisDabRenderingQueue(const KoColorSpace *cs, KisDabCacheUtils::ResourcesFactory resourcesFactory) : m_d(new Private(cs, resourcesFactory)) { } KisDabRenderingQueue::~KisDabRenderingQueue() { } int KisDabRenderingQueue::Private::calculateLastDabJobIndex(int startSearchIndex) { if (startSearchIndex < 0) { startSearchIndex = jobs.size() - 1; } // try to use cached value if (startSearchIndex >= lastDabJobInQueue) { return lastDabJobInQueue; } // if we are below the cached value, just iterate through the dabs // (which is extremely(!) slow) for (int i = startSearchIndex; i >= 0; i--) { if (jobs[i]->type == KisDabRenderingJob::Dab) { return i; } } return -1; } KisDabRenderingJobSP KisDabRenderingQueue::addDab(const KisDabCacheUtils::DabRequestInfo &request, qreal opacity, qreal flow) { QMutexLocker l(&m_d->mutex); const int seqNo = m_d->nextSeqNoToUse++; KisDabCacheUtils::DabRenderingResources *resources = m_d->fetchResourcesFromCache(); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(resources, KisDabRenderingJobSP()); // We should sync the cached brush into the current seqNo resources->syncResourcesToSeqNo(seqNo, request.info); const int lastDabJobIndex = m_d->lastDabJobInQueue; KisDabRenderingJobSP job(new KisDabRenderingJob()); bool shouldUseCache = false; m_d->cacheInterface->getDabType(lastDabJobIndex >= 0, resources, request, &job->generationInfo, &shouldUseCache); m_d->putResourcesToCache(resources); resources = 0; // TODO: initialize via c-tor job->seqNo = seqNo; job->type = !shouldUseCache ? KisDabRenderingJob::Dab : job->generationInfo.needsPostprocessing ? KisDabRenderingJob::Postprocess : KisDabRenderingJob::Copy; job->opacity = opacity; job->flow = flow; if (job->type == KisDabRenderingJob::Dab) { job->status = KisDabRenderingJob::Running; } else if (job->type == KisDabRenderingJob::Postprocess || job->type == KisDabRenderingJob::Copy) { KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(lastDabJobIndex >= 0, KisDabRenderingJobSP()); if (m_d->jobs[lastDabJobIndex]->status == KisDabRenderingJob::Completed) { if (job->type == KisDabRenderingJob::Postprocess) { job->status = KisDabRenderingJob::Running; job->originalDevice = m_d->jobs[lastDabJobIndex]->originalDevice; } else if (job->type == KisDabRenderingJob::Copy) { job->status = KisDabRenderingJob::Completed; job->originalDevice = m_d->jobs[lastDabJobIndex]->originalDevice; job->postprocessedDevice = m_d->jobs[lastDabJobIndex]->postprocessedDevice; } } } m_d->jobs.append(job); KisDabRenderingJobSP jobToRun; if (job->status == KisDabRenderingJob::Running) { jobToRun = job; } if (job->type == KisDabRenderingJob::Dab) { m_d->lastDabJobInQueue = m_d->jobs.size() - 1; + m_d->cleanPaintedDabs(); } // collect some statistics about the dab m_d->avgDabSize(KisAlgebra2D::maxDimension(job->generationInfo.dstDabRect)); return jobToRun; } QList KisDabRenderingQueue::notifyJobFinished(int seqNo, int usecsTime) { QMutexLocker l(&m_d->mutex); QList dependentJobs; /** * Here we use binary search for locating the necessary original dab */ auto finishedJobIt = std::lower_bound(m_d->jobs.begin(), m_d->jobs.end(), seqNo, [] (KisDabRenderingJobSP job, int seqNo) { return job->seqNo < seqNo; }); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(finishedJobIt != m_d->jobs.end(), dependentJobs); KisDabRenderingJobSP finishedJob = *finishedJobIt; KIS_SAFE_ASSERT_RECOVER_NOOP(finishedJob->status == KisDabRenderingJob::Running); KIS_SAFE_ASSERT_RECOVER_NOOP(finishedJob->seqNo == seqNo); KIS_SAFE_ASSERT_RECOVER_NOOP(finishedJob->originalDevice); KIS_SAFE_ASSERT_RECOVER_NOOP(finishedJob->postprocessedDevice); finishedJob->status = KisDabRenderingJob::Completed; if (finishedJob->type == KisDabRenderingJob::Dab) { for (auto it = finishedJobIt + 1; it != m_d->jobs.end(); ++it) { KisDabRenderingJobSP j = *it; // next dab job closes the chain if (j->type == KisDabRenderingJob::Dab) break; // the non 'dab'-type job couldn't have // been started before the source ob was completed KIS_SAFE_ASSERT_RECOVER_BREAK(j->status == KisDabRenderingJob::New); if (j->type == KisDabRenderingJob::Copy) { j->originalDevice = finishedJob->originalDevice; j->postprocessedDevice = finishedJob->postprocessedDevice; j->status = KisDabRenderingJob::Completed; } else if (j->type == KisDabRenderingJob::Postprocess) { j->originalDevice = finishedJob->originalDevice; j->status = KisDabRenderingJob::Running; dependentJobs << j; } } } if (usecsTime >= 0) { m_d->avgExecutionTime(usecsTime); } return dependentJobs; } void KisDabRenderingQueue::Private::cleanPaintedDabs() { const int nextToBePainted = lastPaintedJob + 1; const int lastSourceJob = calculateLastDabJobIndex(qMin(nextToBePainted, jobs.size() - 1)); if (lastPaintedJob >= 0) { int numRemovedJobs = 0; + int numRemovedJobsBeforeLastSource = 0; auto it = jobs.begin(); for (int i = 0; i <= lastPaintedJob; i++) { KisDabRenderingJobSP job = *it; if (i < lastSourceJob || job->type != KisDabRenderingJob::Dab){ // cache unique 'original' devices if (job->type == KisDabRenderingJob::Dab && job->postprocessedDevice != job->originalDevice) { cachedPaintDevices << job->originalDevice; job->originalDevice = 0; } it = jobs.erase(it); numRemovedJobs++; + if (i < lastSourceJob) { + numRemovedJobsBeforeLastSource++; + } + } else { ++it; } } KIS_SAFE_ASSERT_RECOVER_RETURN(jobs.size() > 0); lastPaintedJob -= numRemovedJobs; - lastDabJobInQueue -= numRemovedJobs; + lastDabJobInQueue -= numRemovedJobsBeforeLastSource; } } QList KisDabRenderingQueue::takeReadyDabs(bool returnMutableDabs) { QMutexLocker l(&m_d->mutex); QList renderedDabs; if (m_d->jobs.isEmpty()) return renderedDabs; KIS_SAFE_ASSERT_RECOVER_NOOP( m_d->jobs.isEmpty() || m_d->jobs.first()->type == KisDabRenderingJob::Dab); const int copyJobAfterInclusive = returnMutableDabs && !m_d->dabsHaveSeparateOriginal() ? m_d->lastDabJobInQueue : std::numeric_limits::max(); for (int i = 0; i < m_d->jobs.size(); i++) { KisDabRenderingJobSP j = m_d->jobs[i]; if (j->status != KisDabRenderingJob::Completed) break; if (i <= m_d->lastPaintedJob) continue; KisRenderedDab dab; KisFixedPaintDeviceSP resultDevice = j->postprocessedDevice; if (i >= copyJobAfterInclusive) { resultDevice = new KisFixedPaintDevice(*resultDevice); } dab.device = resultDevice; dab.offset = j->dstDabOffset(); dab.opacity = j->opacity; dab.flow = j->flow; m_d->averageOpacity = KisPainter::blendAverageOpacity(j->opacity, m_d->averageOpacity); dab.averageOpacity = m_d->averageOpacity; renderedDabs.append(dab); m_d->lastPaintedJob = i; } m_d->cleanPaintedDabs(); return renderedDabs; } bool KisDabRenderingQueue::hasPreparedDabs() const { QMutexLocker l(&m_d->mutex); const int nextToBePainted = m_d->lastPaintedJob + 1; return nextToBePainted >= 0 && nextToBePainted < m_d->jobs.size() && m_d->jobs[nextToBePainted]->status == KisDabRenderingJob::Completed; } void KisDabRenderingQueue::setCacheInterface(KisDabRenderingQueue::CacheInterface *interface) { m_d->cacheInterface.reset(interface); } KisFixedPaintDeviceSP KisDabRenderingQueue::fetchCachedPaintDevce() { QMutexLocker l(&m_d->mutex); KisFixedPaintDeviceSP result; if (m_d->cachedPaintDevices.isEmpty()) { result = new KisFixedPaintDevice(m_d->colorSpace); } else { // there is no difference from which side to take elements from QSet auto it = m_d->cachedPaintDevices.begin(); result = *it; m_d->cachedPaintDevices.erase(it); } return result; } void KisDabRenderingQueue::recyclePaintDevicesForCache(const QVector devices) { QMutexLocker l(&m_d->mutex); Q_FOREACH (KisFixedPaintDeviceSP device, devices) { // the set automatically checks if the device is unique in the set m_d->cachedPaintDevices << device; } } int KisDabRenderingQueue::averageExecutionTime() const { QMutexLocker l(&m_d->mutex); return qRound(m_d->avgExecutionTime.rollingMean()); } int KisDabRenderingQueue::averageDabSize() const { QMutexLocker l(&m_d->mutex); return qRound(m_d->avgDabSize.rollingMean()); } bool KisDabRenderingQueue::Private::dabsHaveSeparateOriginal() { KisDabCacheUtils::DabRenderingResources *resources = fetchResourcesFromCache(); const bool result = cacheInterface->hasSeparateOriginal(resources); putResourcesToCache(resources); return result; } KisDabCacheUtils::DabRenderingResources *KisDabRenderingQueue::Private::fetchResourcesFromCache() { KisDabCacheUtils::DabRenderingResources *resources = 0; // fetch/create a temporary resources object if (!cachedResources.isEmpty()) { resources = cachedResources.takeLast(); } else { resources = resourcesFactory(); } return resources; } void KisDabRenderingQueue::Private::putResourcesToCache(KisDabCacheUtils::DabRenderingResources *resources) { cachedResources << resources; } KisDabCacheUtils::DabRenderingResources *KisDabRenderingQueue::fetchResourcesFromCache() { // TODO: make a separate lock for that QMutexLocker l(&m_d->mutex); return m_d->fetchResourcesFromCache(); } void KisDabRenderingQueue::putResourcesToCache(KisDabCacheUtils::DabRenderingResources *resources) { QMutexLocker l(&m_d->mutex); m_d->putResourcesToCache(resources); } int KisDabRenderingQueue::testingGetQueueSize() const { QMutexLocker l(&m_d->mutex); return m_d->jobs.size(); } diff --git a/plugins/paintops/defaultpaintops/brush/tests/KisDabRenderingQueueTest.cpp b/plugins/paintops/defaultpaintops/brush/tests/KisDabRenderingQueueTest.cpp index ceea76d2f2..c72f7603b4 100644 --- a/plugins/paintops/defaultpaintops/brush/tests/KisDabRenderingQueueTest.cpp +++ b/plugins/paintops/defaultpaintops/brush/tests/KisDabRenderingQueueTest.cpp @@ -1,549 +1,541 @@ /* * 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 "KisDabRenderingQueueTest.h" #include #include #include #include <../KisDabRenderingQueue.h> #include <../KisRenderedDab.h> #include <../KisDabRenderingJob.h> struct SurrogateCacheInterface : public KisDabRenderingQueue::CacheInterface { void getDabType(bool hasDabInCache, KisDabCacheUtils::DabRenderingResources *resources, const KisDabCacheUtils::DabRequestInfo &request, /* out */ KisDabCacheUtils::DabGenerationInfo *di, bool *shouldUseCache) override { Q_UNUSED(resources); Q_UNUSED(request); if (!hasDabInCache || typeOverride == KisDabRenderingJob::Dab) { di->needsPostprocessing = false; *shouldUseCache = false; } else if (typeOverride == KisDabRenderingJob::Copy) { di->needsPostprocessing = false; *shouldUseCache = true; } else if (typeOverride == KisDabRenderingJob::Postprocess) { di->needsPostprocessing = true; *shouldUseCache = true; } di->info = request.info; } bool hasSeparateOriginal(KisDabCacheUtils::DabRenderingResources *resources) const override { Q_UNUSED(resources); return typeOverride == KisDabRenderingJob::Postprocess; } KisDabRenderingJob::JobType typeOverride = KisDabRenderingJob::Dab; }; #include #include "kis_auto_brush.h" KisDabCacheUtils::DabRenderingResources *testResourcesFactory() { KisDabCacheUtils::DabRenderingResources *resources = new KisDabCacheUtils::DabRenderingResources(); KisCircleMaskGenerator* circle = new KisCircleMaskGenerator(10, 1.0, 1.0, 1.0, 2, false); KisBrushSP brush = new KisAutoBrush(circle, 0.0, 0.0); resources->brush = brush; return resources; } -void autoDeleteJob(KisDabRenderingJob * & job) -{ - delete job; - job = 0; -} - void KisDabRenderingQueueTest::testCachedDabs() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); SurrogateCacheInterface *cacheInterface = new SurrogateCacheInterface(); KisDabRenderingQueue queue(cs, testResourcesFactory); queue.setCacheInterface(cacheInterface); KoColor color; QPointF pos1(10,10); QPointF pos2(20,20); KisDabShape shape; KisPaintInformation pi1(pos1); KisPaintInformation pi2(pos2); KisDabCacheUtils::DabRequestInfo request1(color, pos1, shape, pi1, 1.0); KisDabCacheUtils::DabRequestInfo request2(color, pos2, shape, pi2, 1.0); cacheInterface->typeOverride = KisDabRenderingJob::Dab; KisDabRenderingJobSP job0 = queue.addDab(request1, OPACITY_OPAQUE_F, OPACITY_OPAQUE_F); QVERIFY(job0); QCOMPARE(job0->seqNo, 0); QCOMPARE(job0->generationInfo.info.pos(), request1.info.pos()); QCOMPARE(job0->type, KisDabRenderingJob::Dab); QVERIFY(!job0->originalDevice); QVERIFY(!job0->postprocessedDevice); cacheInterface->typeOverride = KisDabRenderingJob::Dab; KisDabRenderingJobSP job1 = queue.addDab(request2, OPACITY_OPAQUE_F, OPACITY_OPAQUE_F); QVERIFY(job1); QCOMPARE(job1->seqNo, 1); QCOMPARE(job1->generationInfo.info.pos(), request2.info.pos()); QCOMPARE(job1->type, KisDabRenderingJob::Dab); QVERIFY(!job1->originalDevice); QVERIFY(!job1->postprocessedDevice); cacheInterface->typeOverride = KisDabRenderingJob::Copy; KisDabRenderingJobSP job2 = queue.addDab(request2, OPACITY_OPAQUE_F, OPACITY_OPAQUE_F); QVERIFY(!job2); cacheInterface->typeOverride = KisDabRenderingJob::Copy; KisDabRenderingJobSP job3 = queue.addDab(request2, OPACITY_OPAQUE_F, OPACITY_OPAQUE_F); QVERIFY(!job3); // we only added the dabs, but we haven't completed them yet QVERIFY(!queue.hasPreparedDabs()); QCOMPARE(queue.testingGetQueueSize(), 4); QList jobs; QList renderedDabs; { // we've completed job0 job0->originalDevice = new KisFixedPaintDevice(cs); job0->postprocessedDevice = job0->originalDevice; jobs = queue.notifyJobFinished(job0->seqNo); QVERIFY(jobs.isEmpty()); // now we should have at least one job in prepared state QVERIFY(queue.hasPreparedDabs()); // take the prepared dabs renderedDabs = queue.takeReadyDabs(); QCOMPARE(renderedDabs.size(), 1); // the list should be empty again QVERIFY(!queue.hasPreparedDabs()); QCOMPARE(queue.testingGetQueueSize(), 3); } { // we've completed job1 job1->originalDevice = new KisFixedPaintDevice(cs); job1->postprocessedDevice = job1->originalDevice; jobs = queue.notifyJobFinished(job1->seqNo); QVERIFY(jobs.isEmpty()); // now we should have at least one job in prepared state QVERIFY(queue.hasPreparedDabs()); // take the prepared dabs renderedDabs = queue.takeReadyDabs(); QCOMPARE(renderedDabs.size(), 3); // since they are copies, they should be the same QCOMPARE(renderedDabs[1].device, renderedDabs[0].device); QCOMPARE(renderedDabs[2].device, renderedDabs[0].device); // the list should be empty again QVERIFY(!queue.hasPreparedDabs()); - // we do not delete the queue of jobs until the next 'dab' - // job arrives - QCOMPARE(queue.testingGetQueueSize(), 3); + // we delete all the painted jobs except the latest 'dab' job + QCOMPARE(queue.testingGetQueueSize(), 1); } { // add one more cached job and take it cacheInterface->typeOverride = KisDabRenderingJob::Copy; KisDabRenderingJobSP job = queue.addDab(request2, OPACITY_OPAQUE_F, OPACITY_OPAQUE_F); QVERIFY(!job); // now we should have at least one job in prepared state QVERIFY(queue.hasPreparedDabs()); // take the prepared dabs renderedDabs = queue.takeReadyDabs(); QCOMPARE(renderedDabs.size(), 1); // the list should be empty again QVERIFY(!queue.hasPreparedDabs()); - // we do not delete the queue of jobs until the next 'dab' - // job arrives - QCOMPARE(queue.testingGetQueueSize(), 4); + // we delete all the painted jobs except the latest 'dab' job + QCOMPARE(queue.testingGetQueueSize(), 1); } { // add a 'dab' job and complete it cacheInterface->typeOverride = KisDabRenderingJob::Dab; KisDabRenderingJobSP job = queue.addDab(request1, OPACITY_OPAQUE_F, OPACITY_OPAQUE_F); QVERIFY(job); QCOMPARE(job->seqNo, 5); QCOMPARE(job->generationInfo.info.pos(), request1.info.pos()); QCOMPARE(job->type, KisDabRenderingJob::Dab); QVERIFY(!job->originalDevice); QVERIFY(!job->postprocessedDevice); // now the queue can be cleared from the completed dabs! QCOMPARE(queue.testingGetQueueSize(), 1); job->originalDevice = new KisFixedPaintDevice(cs); job->postprocessedDevice = job->originalDevice; jobs = queue.notifyJobFinished(job->seqNo); QVERIFY(jobs.isEmpty()); // now we should have at least one job in prepared state QVERIFY(queue.hasPreparedDabs()); // take the prepared dabs renderedDabs = queue.takeReadyDabs(); QCOMPARE(renderedDabs.size(), 1); // the list should be empty again QVERIFY(!queue.hasPreparedDabs()); // we do not delete the queue of jobs until the next 'dab' // job arrives QCOMPARE(queue.testingGetQueueSize(), 1); } } void KisDabRenderingQueueTest::testPostprocessedDabs() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); SurrogateCacheInterface *cacheInterface = new SurrogateCacheInterface(); KisDabRenderingQueue queue(cs, testResourcesFactory); queue.setCacheInterface(cacheInterface); KoColor color; QPointF pos1(10,10); QPointF pos2(20,20); KisDabShape shape; KisPaintInformation pi1(pos1); KisPaintInformation pi2(pos2); KisDabCacheUtils::DabRequestInfo request1(color, pos1, shape, pi1, 1.0); KisDabCacheUtils::DabRequestInfo request2(color, pos2, shape, pi2, 1.0); cacheInterface->typeOverride = KisDabRenderingJob::Dab; KisDabRenderingJobSP job0 = queue.addDab(request1, OPACITY_OPAQUE_F, OPACITY_OPAQUE_F); QVERIFY(job0); QCOMPARE(job0->seqNo, 0); QCOMPARE(job0->generationInfo.info.pos(), request1.info.pos()); QCOMPARE(job0->type, KisDabRenderingJob::Dab); QVERIFY(!job0->originalDevice); QVERIFY(!job0->postprocessedDevice); cacheInterface->typeOverride = KisDabRenderingJob::Dab; KisDabRenderingJobSP job1 = queue.addDab(request2, OPACITY_OPAQUE_F, OPACITY_OPAQUE_F); QVERIFY(job1); QCOMPARE(job1->seqNo, 1); QCOMPARE(job1->generationInfo.info.pos(), request2.info.pos()); QCOMPARE(job1->type, KisDabRenderingJob::Dab); QVERIFY(!job1->originalDevice); QVERIFY(!job1->postprocessedDevice); cacheInterface->typeOverride = KisDabRenderingJob::Postprocess; KisDabRenderingJobSP job2 = queue.addDab(request2, OPACITY_OPAQUE_F, OPACITY_OPAQUE_F); QVERIFY(!job2); cacheInterface->typeOverride = KisDabRenderingJob::Postprocess; KisDabRenderingJobSP job3 = queue.addDab(request2, OPACITY_OPAQUE_F, OPACITY_OPAQUE_F); QVERIFY(!job3); // we only added the dabs, but we haven't completed them yet QVERIFY(!queue.hasPreparedDabs()); QCOMPARE(queue.testingGetQueueSize(), 4); QList jobs; QList renderedDabs; { // we've completed job0 job0->originalDevice = new KisFixedPaintDevice(cs); job0->postprocessedDevice = job0->originalDevice; jobs = queue.notifyJobFinished(job0->seqNo); QVERIFY(jobs.isEmpty()); // now we should have at least one job in prepared state QVERIFY(queue.hasPreparedDabs()); // take the prepared dabs renderedDabs = queue.takeReadyDabs(); QCOMPARE(renderedDabs.size(), 1); // the list should be empty again QVERIFY(!queue.hasPreparedDabs()); QCOMPARE(queue.testingGetQueueSize(), 3); } { // we've completed job1 job1->originalDevice = new KisFixedPaintDevice(cs); job1->postprocessedDevice = job1->originalDevice; jobs = queue.notifyJobFinished(job1->seqNo); QCOMPARE(jobs.size(), 2); QCOMPARE(jobs[0]->seqNo, 2); QCOMPARE(jobs[1]->seqNo, 3); QVERIFY(jobs[0]->originalDevice); QVERIFY(!jobs[0]->postprocessedDevice); QVERIFY(jobs[1]->originalDevice); QVERIFY(!jobs[1]->postprocessedDevice); // pretend we have created a postprocessed device jobs[0]->postprocessedDevice = new KisFixedPaintDevice(cs); jobs[1]->postprocessedDevice = new KisFixedPaintDevice(cs); // now we should have at least one job in prepared state QVERIFY(queue.hasPreparedDabs()); // take the prepared dabs renderedDabs = queue.takeReadyDabs(); QCOMPARE(renderedDabs.size(), 1); // the list should be empty again QVERIFY(!queue.hasPreparedDabs()); // return back two postprocessed dabs QList emptyJobs; emptyJobs = queue.notifyJobFinished(jobs[0]->seqNo); QVERIFY(emptyJobs.isEmpty()); emptyJobs = queue.notifyJobFinished(jobs[1]->seqNo); QVERIFY(emptyJobs.isEmpty()); // now we should have at least one job in prepared state QVERIFY(queue.hasPreparedDabs()); // take the prepared dabs renderedDabs = queue.takeReadyDabs(); QCOMPARE(renderedDabs.size(), 2); // the list should be empty again QVERIFY(!queue.hasPreparedDabs()); - // we do not delete the queue of jobs until the next 'dab' - // job arrives - QCOMPARE(queue.testingGetQueueSize(), 3); + // we delete all the painted jobs except the latest 'dab' job + QCOMPARE(queue.testingGetQueueSize(), 1); } { // add one more postprocessed job and take it cacheInterface->typeOverride = KisDabRenderingJob::Postprocess; KisDabRenderingJobSP job = queue.addDab(request2, OPACITY_OPAQUE_F, OPACITY_OPAQUE_F); QVERIFY(job); QCOMPARE(job->seqNo, 4); QCOMPARE(job->generationInfo.info.pos(), request2.info.pos()); + ENTER_FUNCTION() << ppVar(job->type); + QCOMPARE(job->type, KisDabRenderingJob::Postprocess); QVERIFY(job->originalDevice); QVERIFY(!job->postprocessedDevice); // the list should still be empty QVERIFY(!queue.hasPreparedDabs()); // pretend we have created a postprocessed device job->postprocessedDevice = new KisFixedPaintDevice(cs); // return back the postprocessed dab QList emptyJobs; emptyJobs = queue.notifyJobFinished(job->seqNo); QVERIFY(emptyJobs.isEmpty()); // now we should have at least one job in prepared state QVERIFY(queue.hasPreparedDabs()); // take the prepared dabs renderedDabs = queue.takeReadyDabs(); QCOMPARE(renderedDabs.size(), 1); // the list should be empty again QVERIFY(!queue.hasPreparedDabs()); - // we do not delete the queue of jobs until the next 'dab' - // job arrives - QCOMPARE(queue.testingGetQueueSize(), 4); + // we delete all the painted jobs except the latest 'dab' job + QCOMPARE(queue.testingGetQueueSize(), 1); } { // add a 'dab' job and complete it. That will clear the queue! cacheInterface->typeOverride = KisDabRenderingJob::Dab; KisDabRenderingJobSP job = queue.addDab(request1, OPACITY_OPAQUE_F, OPACITY_OPAQUE_F); QVERIFY(job); QCOMPARE(job->seqNo, 5); QCOMPARE(job->generationInfo.info.pos(), request1.info.pos()); QCOMPARE(job->type, KisDabRenderingJob::Dab); QVERIFY(!job->originalDevice); QVERIFY(!job->postprocessedDevice); // now the queue can be cleared from the completed dabs! QCOMPARE(queue.testingGetQueueSize(), 1); job->originalDevice = new KisFixedPaintDevice(cs); job->postprocessedDevice = job->originalDevice; jobs = queue.notifyJobFinished(job->seqNo); QVERIFY(jobs.isEmpty()); // now we should have at least one job in prepared state QVERIFY(queue.hasPreparedDabs()); // take the prepared dabs renderedDabs = queue.takeReadyDabs(); QCOMPARE(renderedDabs.size(), 1); // the list should be empty again QVERIFY(!queue.hasPreparedDabs()); // we do not delete the queue of jobs until the next 'dab' // job arrives QCOMPARE(queue.testingGetQueueSize(), 1); } } #include <../KisDabRenderingQueueCache.h> void KisDabRenderingQueueTest::testRunningJobs() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisDabRenderingQueueCache *cacheInterface = new KisDabRenderingQueueCache(); // we do *not* initialize any options yet! KisDabRenderingQueue queue(cs, testResourcesFactory); queue.setCacheInterface(cacheInterface); KoColor color(Qt::red, cs); QPointF pos1(10,10); QPointF pos2(20,20); KisDabShape shape; KisPaintInformation pi1(pos1); KisPaintInformation pi2(pos2); KisDabCacheUtils::DabRequestInfo request1(color, pos1, shape, pi1, 1.0); KisDabCacheUtils::DabRequestInfo request2(color, pos2, shape, pi2, 1.0); KisDabRenderingJobSP job0 = queue.addDab(request1, OPACITY_OPAQUE_F, OPACITY_OPAQUE_F); QVERIFY(job0); QCOMPARE(job0->seqNo, 0); QCOMPARE(job0->generationInfo.info.pos(), request1.info.pos()); QCOMPARE(job0->type, KisDabRenderingJob::Dab); QVERIFY(!job0->originalDevice); QVERIFY(!job0->postprocessedDevice); KisDabRenderingJobRunner runner(job0, &queue, 0); runner.run(); QVERIFY(job0->originalDevice); QVERIFY(job0->postprocessedDevice); QCOMPARE(job0->originalDevice, job0->postprocessedDevice); QVERIFY(!job0->originalDevice->bounds().isEmpty()); KisDabRenderingJobSP job1 = queue.addDab(request2, OPACITY_OPAQUE_F, OPACITY_OPAQUE_F); QVERIFY(!job1); QList renderedDabs = queue.takeReadyDabs(); QCOMPARE(renderedDabs.size(), 2); // we did the caching QVERIFY(renderedDabs[0].device == renderedDabs[1].device); QCOMPARE(renderedDabs[0].offset, QPoint(5,5)); QCOMPARE(renderedDabs[1].offset, QPoint(15,15)); } #include "../KisDabRenderingExecutor.h" #include "KisFakeRunnableStrokeJobsExecutor.h" void KisDabRenderingQueueTest::testExecutor() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); QScopedPointer runner(new KisFakeRunnableStrokeJobsExecutor()); KisDabRenderingExecutor executor(cs, testResourcesFactory, runner.data()); KoColor color(Qt::red, cs); QPointF pos1(10,10); QPointF pos2(20,20); KisDabShape shape; KisPaintInformation pi1(pos1); KisPaintInformation pi2(pos2); KisDabCacheUtils::DabRequestInfo request1(color, pos1, shape, pi1, 1.0); KisDabCacheUtils::DabRequestInfo request2(color, pos2, shape, pi2, 1.0); executor.addDab(request1, 0.5, 0.25); executor.addDab(request2, 0.125, 1.0); QList renderedDabs = executor.takeReadyDabs(); QCOMPARE(renderedDabs.size(), 2); // we did the caching QVERIFY(renderedDabs[0].device == renderedDabs[1].device); QCOMPARE(renderedDabs[0].offset, QPoint(5,5)); QCOMPARE(renderedDabs[1].offset, QPoint(15,15)); QCOMPARE(renderedDabs[0].opacity, 0.5); QCOMPARE(renderedDabs[0].flow, 0.25); QCOMPARE(renderedDabs[1].opacity, 0.125); QCOMPARE(renderedDabs[1].flow, 1.0); } QTEST_MAIN(KisDabRenderingQueueTest)