Commit 0718fb65 authored by Wolthera van Hövell's avatar Wolthera van Hövell 🛍

Merge branch 'master' into krita-testing-wolthera

parents 541b596e bba9f1c8
krita/data/brushes/3_texture.png

100 KB | W: | H:

krita/data/brushes/3_texture.png

165 KB | W: | H:

krita/data/brushes/3_texture.png
krita/data/brushes/3_texture.png
krita/data/brushes/3_texture.png
krita/data/brushes/3_texture.png
  • 2-up
  • Swipe
  • Onion skin
......@@ -55,7 +55,6 @@ struct Q_DECL_HIDDEN KisUpdateScheduler::Private {
KisSimpleUpdateQueue updatesQueue;
KisStrokesQueue strokesQueue;
KisUpdaterContext updaterContext;
bool processingBlocked = false;
qreal balancingRatio = 1.0; // updates-queue-size/strokes-queue-size
KisProjectionUpdateListener *projectionUpdateListener;
......@@ -64,6 +63,11 @@ struct Q_DECL_HIDDEN KisUpdateScheduler::Private {
QAtomicInt updatesLockCounter;
QReadWriteLock updatesStartLock;
KisLazyWaitCondition updatesFinishedCondition;
// KisUpdaterContext can emit signals to KisUpdateScheduler in the dtor, so it
// must to be deleted before anything else.
// That means updaterContext must be declared last.
KisUpdaterContext updaterContext;
};
KisUpdateScheduler::KisUpdateScheduler(KisProjectionUpdateListener *projectionUpdateListener)
......@@ -163,9 +167,11 @@ void KisUpdateScheduler::fullRefresh(KisNodeSP root, const QRect& rc, const QRec
Q_ASSERT(m_d->updaterContext.isJobAllowed(walker));
m_d->updaterContext.addMergeJob(walker);
m_d->updaterContext.waitForDone();
m_d->updaterContext.unlock();
m_d->updaterContext.waitForDone();
if(needLock) unlock(true);
}
......@@ -408,7 +414,9 @@ bool KisUpdateScheduler::haveUpdatesRunning()
QWriteLocker locker(&m_d->updatesStartLock);
qint32 numMergeJobs, numStrokeJobs;
m_d->updaterContext.lock();
m_d->updaterContext.getJobsSnapshot(numMergeJobs, numStrokeJobs);
m_d->updaterContext.unlock();
return numMergeJobs;
}
......
......@@ -21,18 +21,14 @@
#include <QThread>
#include <QThreadPool>
#include "kis_safe_read_list.h"
#include "kis_update_job_item.h"
#include "kis_stroke_job.h"
KisUpdaterContext::KisUpdaterContext(qint32 threadCount)
KisUpdaterContext::KisUpdaterContext(qint32 threadCount):
m_jobs(threadCount > 0 ? threadCount : defaultThreadCount())
{
if(threadCount <= 0) {
threadCount = QThread::idealThreadCount();
threadCount = threadCount > 0 ? threadCount : 1;
}
m_jobs.resize(threadCount);
for(qint32 i = 0; i < m_jobs.size(); i++) {
m_jobs[i] = new KisUpdateJobItem(&m_exclusiveJobLock);
connect(m_jobs[i], SIGNAL(sigContinueUpdate(const QRect&)),
......@@ -45,6 +41,10 @@ KisUpdaterContext::KisUpdaterContext(qint32 threadCount)
connect(m_jobs[i], SIGNAL(sigJobFinished()),
SLOT(slotJobFinished()), Qt::DirectConnection);
}
#ifdef SANITY_CHECK_CONTEXT_LOCKING
m_lockedBy = (Qt::HANDLE) -1;
#endif
}
KisUpdaterContext::~KisUpdaterContext()
......@@ -54,9 +54,19 @@ KisUpdaterContext::~KisUpdaterContext()
delete m_jobs[i];
}
qint32 KisUpdaterContext::defaultThreadCount() const
{
int threadCount = QThread::idealThreadCount();
return threadCount > 0 ? threadCount : 1;
}
void KisUpdaterContext::getJobsSnapshot(qint32 &numMergeJobs,
qint32 &numStrokeJobs)
{
#ifdef SANITY_CHECK_CONTEXT_LOCKING
KIS_ASSERT(m_lockedBy == QThread::currentThreadId());
#endif
numMergeJobs = 0;
numStrokeJobs = 0;
......@@ -91,6 +101,10 @@ bool KisUpdaterContext::hasSpareThread()
bool KisUpdaterContext::isJobAllowed(KisBaseRectsWalkerSP walker)
{
#ifdef SANITY_CHECK_CONTEXT_LOCKING
KIS_ASSERT(m_lockedBy == QThread::currentThreadId());
#endif
int lod = this->currentLevelOfDetail();
if (lod >= 0 && walker->levelOfDetail() != lod) return false;
......@@ -116,9 +130,13 @@ bool KisUpdaterContext::isJobAllowed(KisBaseRectsWalkerSP walker)
*/
void KisUpdaterContext::addMergeJob(KisBaseRectsWalkerSP walker)
{
#ifdef SANITY_CHECK_CONTEXT_LOCKING
KIS_ASSERT(m_lockedBy == QThread::currentThreadId());
#endif
m_lodCounter.addLod(walker->levelOfDetail());
qint32 jobIndex = findSpareThread();
Q_ASSERT(jobIndex >= 0);
KIS_ASSERT(jobIndex >= 0);
m_jobs[jobIndex]->setWalker(walker);
m_threadPool.start(m_jobs[jobIndex]);
......@@ -129,9 +147,13 @@ void KisUpdaterContext::addMergeJob(KisBaseRectsWalkerSP walker)
*/
void KisTestableUpdaterContext::addMergeJob(KisBaseRectsWalkerSP walker)
{
#ifdef SANITY_CHECK_CONTEXT_LOCKING
KIS_ASSERT(m_lockedBy == QThread::currentThreadId());
#endif
m_lodCounter.addLod(walker->levelOfDetail());
qint32 jobIndex = findSpareThread();
Q_ASSERT(jobIndex >= 0);
KIS_ASSERT(jobIndex >= 0);
m_jobs[jobIndex]->setWalker(walker);
// HINT: Not calling start() here
......@@ -139,9 +161,13 @@ void KisTestableUpdaterContext::addMergeJob(KisBaseRectsWalkerSP walker)
void KisUpdaterContext::addStrokeJob(KisStrokeJob *strokeJob)
{
#ifdef SANITY_CHECK_CONTEXT_LOCKING
KIS_ASSERT(m_lockedBy == QThread::currentThreadId());
#endif
m_lodCounter.addLod(strokeJob->levelOfDetail());
qint32 jobIndex = findSpareThread();
Q_ASSERT(jobIndex >= 0);
KIS_ASSERT(jobIndex >= 0);
m_jobs[jobIndex]->setStrokeJob(strokeJob);
m_threadPool.start(m_jobs[jobIndex]);
......@@ -152,9 +178,13 @@ void KisUpdaterContext::addStrokeJob(KisStrokeJob *strokeJob)
*/
void KisTestableUpdaterContext::addStrokeJob(KisStrokeJob *strokeJob)
{
#ifdef SANITY_CHECK_CONTEXT_LOCKING
KIS_ASSERT(m_lockedBy == QThread::currentThreadId());
#endif
m_lodCounter.addLod(strokeJob->levelOfDetail());
qint32 jobIndex = findSpareThread();
Q_ASSERT(jobIndex >= 0);
KIS_ASSERT(jobIndex >= 0);
m_jobs[jobIndex]->setStrokeJob(strokeJob);
// HINT: Not calling start() here
......@@ -162,9 +192,13 @@ void KisTestableUpdaterContext::addStrokeJob(KisStrokeJob *strokeJob)
void KisUpdaterContext::addSpontaneousJob(KisSpontaneousJob *spontaneousJob)
{
#ifdef SANITY_CHECK_CONTEXT_LOCKING
KIS_ASSERT(m_lockedBy == QThread::currentThreadId());
#endif
m_lodCounter.addLod(spontaneousJob->levelOfDetail());
qint32 jobIndex = findSpareThread();
Q_ASSERT(jobIndex >= 0);
KIS_ASSERT(jobIndex >= 0);
m_jobs[jobIndex]->setSpontaneousJob(spontaneousJob);
m_threadPool.start(m_jobs[jobIndex]);
......@@ -177,7 +211,7 @@ void KisTestableUpdaterContext::addSpontaneousJob(KisSpontaneousJob *spontaneous
{
m_lodCounter.addLod(spontaneousJob->levelOfDetail());
qint32 jobIndex = findSpareThread();
Q_ASSERT(jobIndex >= 0);
KIS_ASSERT(jobIndex >= 0);
m_jobs[jobIndex]->setSpontaneousJob(spontaneousJob);
// HINT: Not calling start() here
......@@ -185,7 +219,35 @@ void KisTestableUpdaterContext::addSpontaneousJob(KisSpontaneousJob *spontaneous
void KisUpdaterContext::waitForDone()
{
m_threadPool.waitForDone();
lock();
while(true) {
bool allDone = true;
QVector<KisUpdateJobItem*>::const_iterator iter;
FOREACH_SAFE(iter, m_jobs) {
if ((*iter)->isRunning()) {
allDone = false;
break;
}
}
if (!allDone) {
#ifdef SANITY_CHECK_CONTEXT_LOCKING
m_lockedBy = (Qt::HANDLE) -1;
#endif
m_waitAllCond.wait(&m_lock);
#ifdef SANITY_CHECK_CONTEXT_LOCKING
m_lockedBy = QThread::currentThreadId();
#endif
} else {
break;
}
}
unlock();
}
bool KisUpdaterContext::walkerIntersectsJob(KisBaseRectsWalkerSP walker,
......@@ -208,6 +270,7 @@ void KisUpdaterContext::slotJobFinished()
{
m_lodCounter.removeLod();
m_waitAllCond.wakeOne();
// Be careful. This slot can be called asynchronously without locks.
emit sigSpareThreadAppeared();
}
......@@ -215,10 +278,22 @@ void KisUpdaterContext::slotJobFinished()
void KisUpdaterContext::lock()
{
m_lock.lock();
#ifdef SANITY_CHECK_CONTEXT_LOCKING
KIS_ASSERT_X(m_lockedBy == (Qt::HANDLE) -1, "KisUpdaterContext",
"context is already locked");
m_lockedBy = QThread::currentThreadId();
#endif
}
void KisUpdaterContext::unlock()
{
#ifdef SANITY_CHECK_CONTEXT_LOCKING
KIS_ASSERT(m_lockedBy == QThread::currentThreadId());
m_lockedBy = (Qt::HANDLE) -1;
#endif
m_lock.unlock();
}
......
......@@ -23,11 +23,16 @@
#include <QMutex>
#include <QReadWriteLock>
#include <QThreadPool>
#include <QWaitCondition>
#include "kis_base_rects_walker.h"
#include "kis_async_merger.h"
#include "kis_lock_free_lod_counter.h"
// TODO: uncomment ifndef for release on 3.0.1
// #ifndef QT_NO_DEBUG
#define SANITY_CHECK_CONTEXT_LOCKING
// #endif // QT_NO_DEBUG
class KisUpdateJobItem;
class KisSpontaneousJob;
......@@ -128,6 +133,9 @@ protected Q_SLOTS:
protected:
static bool walkerIntersectsJob(KisBaseRectsWalkerSP walker,
const KisUpdateJobItem* job);
qint32 defaultThreadCount() const;
qint32 findSpareThread();
protected:
......@@ -141,8 +149,14 @@ protected:
QMutex m_lock;
QVector<KisUpdateJobItem*> m_jobs;
QWaitCondition m_waitAllCond;
QThreadPool m_threadPool;
KisLockFreeLodCounter m_lodCounter;
#ifdef SANITY_CHECK_CONTEXT_LOCKING
// Thread ID of the owner or -1 if not locked
volatile Qt::HANDLE m_lockedBy;
#endif
};
class KRITAIMAGE_EXPORT KisTestableUpdaterContext : public KisUpdaterContext
......
......@@ -114,7 +114,9 @@ void KisStrokesQueueTest::testExclusiveStrokes()
KisTestableUpdaterContext context(2);
QVector<KisUpdateJobItem*> jobs;
context.lock();
context.addMergeJob(walker);
context.unlock();
queue.processQueue(context, false);
jobs = context.getJobs();
......@@ -139,7 +141,9 @@ void KisStrokesQueueTest::testExclusiveStrokes()
QCOMPARE(queue.needsExclusiveAccess(), true);
context.clear();
context.lock();
context.addMergeJob(walker);
context.unlock();
queue.processQueue(context, false);
COMPARE_WALKER(jobs[0], walker);
......@@ -201,7 +205,9 @@ void KisStrokesQueueTest::testBarrierStrokeJobs()
VERIFY_EMPTY(jobs[2]);
// Now some updates has come...
context.lock();
context.addMergeJob(walker);
context.unlock();
jobs = context.getJobs();
COMPARE_NAME(jobs[0], "nor_dab");
......@@ -239,7 +245,9 @@ void KisStrokesQueueTest::testBarrierStrokeJobs()
VERIFY_EMPTY(jobs[2]);
// Process the last update...
context.lock();
context.addMergeJob(walker);
context.unlock();
externalJobsPending = false;
// Yep, the queue is still waiting
......@@ -416,7 +424,9 @@ void KisStrokesQueueTest::testStrokesLevelOfDetail()
KisTestableUpdaterContext context(2);
QVector<KisUpdateJobItem*> jobs;
context.lock();
context.addMergeJob(walker);
context.unlock();
queue.processQueue(context, false);
jobs = context.getJobs();
......@@ -439,10 +449,14 @@ void KisStrokesQueueTest::testStrokesLevelOfDetail()
QCOMPARE(queue.needsExclusiveAccess(), false);
// walker of a different LOD must not be allowed
context.lock();
QCOMPARE(context.isJobAllowed(walker), false);
context.unlock();
context.clear();
context.lock();
context.addMergeJob(walker);
context.unlock();
queue.processQueue(context, false);
jobs = context.getJobs();
......
......@@ -223,7 +223,9 @@ void KisUpdaterContextTest::stressTestExclusiveJobs()
KisStrokeJobStrategy *strategy =
new ExclusivenessCheckerStrategy(counter, hadConcurrency);
context.lock();
context.addStrokeJob(new KisStrokeJob(strategy, data, 0, true));
context.unlock();
}
else {
QTest::qSleep(CHECK_DELAY);
......
......@@ -289,7 +289,7 @@ void KisTemplateCreateDia::slotOk() {
while (QFile(dest).exists());
}
bool ignore = false;
KisTemplate *t = new KisTemplate(d->m_name->text(), QString(), ".source/ "+ file + ext, tmpIcon, "", "", false, true);
KisTemplate *t = new KisTemplate(d->m_name->text(), QString(), ".source/"+ file + ext, tmpIcon, "", "", false, true);
if (!group->add(t)) {
KisTemplate *existingTemplate=group->find(d->m_name->text());
if (existingTemplate && !existingTemplate->isHidden()) {
......
......@@ -166,7 +166,17 @@ QRect KisNodeViewColorScheme::relExpandButtonRect() const
QColor KisNodeViewColorScheme::colorLabel(int index) const
{
return m_d->colorLabels[index % m_d->colorLabels.size()];
/**
* We should ensure that the index of the overflowing range
* will never be zero again.
*/
if (index >= m_d->colorLabels.size()) {
index = 1 + index % (m_d->colorLabels.size() - 1);
} else {
index = index % m_d->colorLabels.size();
}
return m_d->colorLabels[index];
}
QVector<QColor> KisNodeViewColorScheme::allColorLabels() const
......
......@@ -78,6 +78,7 @@
#include "kis_guides_config.h"
#include "kis_image_config.h"
#include "KisProofingConfiguration.h"
#include "kis_node_view_color_scheme.h"
/*
......@@ -652,7 +653,11 @@ KisNodeSP KisKraLoader::loadNode(const KoXmlElement& element, KisImageWSP image,
const bool visible = element.attribute(VISIBLE, "1") == "0" ? false : true;
const bool locked = element.attribute(LOCKED, "0") == "0" ? false : true;
const bool collapsed = element.attribute(COLLAPSED, "0") == "0" ? false : true;
const int colorLabelIndex = element.attribute(COLOR_LABEL, "0").toInt();
int colorLabelIndex = element.attribute(COLOR_LABEL, "0").toInt();
QVector<QColor> labels = KisNodeViewColorScheme::instance()->allColorLabels();
if (colorLabelIndex >= labels.size()) {
colorLabelIndex = labels.size() - 1;
}
// Now find out the layer type and do specific handling
QString nodeType;
......
......@@ -272,7 +272,7 @@ void KisPainterBasedStrokeStrategy::cancelStrokeCallback()
KisIndirectPaintingSupport *indirect =
dynamic_cast<KisIndirectPaintingSupport*>(node.data());
bool revert = true;
if (indirect) {
KisPaintDeviceSP t = indirect->temporaryTarget();
if (t) {
......@@ -282,8 +282,11 @@ void KisPainterBasedStrokeStrategy::cancelStrokeCallback()
QRegion region = t->region();
indirect->setTemporaryTarget(0);
node->setDirty(region);
revert = false;
}
} else {
}
if (revert) {
m_transaction->revert();
delete m_transaction;
deletePainters();
......
......@@ -52,72 +52,76 @@
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="lblXSpacing">
<property name="text">
<string>X spacing:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="KisIntParseSpinBox" name="intHSpacing">
<property name="suffix">
<string> px</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>500</number>
</property>
<property name="value">
<number>10</number>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="lblYSpacing">
<property name="text">
<string>Y spacing:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="KisIntParseSpinBox" name="intVSpacing">
<property name="suffix">
<string> px</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>500</number>
</property>
<property name="value">
<number>10</number>
</property>
</widget>
</item>
</layout>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="lblXSpacing">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>X spacing:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="KisIntParseSpinBox" name="intHSpacing">
<property name="suffix">
<string> px</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>500</number>
</property>
<property name="value">
<number>10</number>
</property>
</widget>
</item>
<item>
<item row="0" column="2" rowspan="2">
<widget class="KoAspectButton" name="spacingAspectButton" native="true"/>
</item>
</layout>
</item>
<item>
<layout class="QFormLayout" name="formLayout_3">
<item row="0" column="0">
<item row="1" column="0">
<widget class="QLabel" name="lblYSpacing">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Y spacing:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="KisIntParseSpinBox" name="intVSpacing">
<property name="suffix">
<string> px</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>500</number>
</property>
<property name="value">
<number>10</number>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="lblSubdivision">
<property name="text">
<string>Subdivision:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<item row="2" column="1" colspan="2">
<widget class="KisIntParseSpinBox" name="intSubdivision">
<property name="minimum">
<number>1</number>
......@@ -130,31 +134,30 @@
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="chkOffset">
<property name="text">
<string>Grid Offset</string>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="KoAspectButton" name="offsetAspectButton" native="true"/>
</item>
<item>
<layout class="QFormLayout" name="formLayout_2">
<item row="0" column="0">
<item row="3" column="0" colspan="3">
<widget class="QCheckBox" name="chkOffset">
<property name="text">
<string>Grid Offset</string>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="lblXOffset">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>X offset:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<item row="4" column="1">
<widget class="KisIntParseSpinBox" name="intXOffset">
<property name="suffix">
<string> px</string>
......@@ -167,14 +170,23 @@
</property>
</widget>
</item>
<item row="1" column="0">
<item row="4" column="2" rowspan="2">
<widget class="KoAspectButton" name="offsetAspectButton" native="true"/>
</item>
<item row="5" column="0">
<widget class="QLabel" name="lblYOffset">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Y offset:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<item row="5" column="1">
<widget class="KisIntParseSpinBox" name="intYOffset">
<property name="suffix">
<string> px</string>
......@@ -187,8 +199,14 @@
</property>
</widget>
</item>
<item row="2" column="0">
<item row="6" column="0">
<widget class="QLabel" name="lblMainStyle">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Main Style:</string>
</property>
......@@ -197,7 +215,7 @@
</property>
</widget>
</item>
<item row="2" column="1">
<item row="6" column="1" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="KComboBox" name="selectMainStyle">
......@@ -224,7 +242,7 @@
<item>
<widget class="KColorButton" name="colorMain">
<property name="sizePolicy">