diff --git a/libs/image/kis_layer.cc b/libs/image/kis_layer.cc index b39306a92f..17ae7908d6 100644 --- a/libs/image/kis_layer.cc +++ b/libs/image/kis_layer.cc @@ -1,973 +1,986 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2005 C. Boemann * Copyright (c) 2009 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 "kis_layer.h" #include #include #include #include #include #include +#include #include #include #include #include #include #include "kis_debug.h" #include "kis_image.h" #include "kis_painter.h" #include "kis_mask.h" #include "kis_effect_mask.h" #include "kis_selection_mask.h" #include "kis_meta_data_store.h" #include "kis_selection.h" #include "kis_paint_layer.h" #include "kis_raster_keyframe_channel.h" #include "kis_clone_layer.h" #include "kis_psd_layer_style.h" #include "kis_layer_projection_plane.h" #include "layerstyles/kis_layer_style_projection_plane.h" #include "krita_utils.h" #include "kis_layer_properties_icons.h" #include "kis_layer_utils.h" class KisSafeProjection { public: KisPaintDeviceSP getDeviceLazy(KisPaintDeviceSP prototype) { - QMutexLocker locker(&m_lock); +// QMutexLocker locker(&m_lock); + QReadLocker rl(&m_rwLock); if (!m_reusablePaintDevice) { + rl.unlock(); + QWriteLocker wl(&m_rwLock); m_reusablePaintDevice = new KisPaintDevice(*prototype); } + + rl.relock(); + if(!m_projection || *m_projection->colorSpace() != *prototype->colorSpace()) { + rl.unlock(); + QWriteLocker wl(&m_rwLock); m_projection = m_reusablePaintDevice; m_projection->makeCloneFromRough(prototype, prototype->extent()); m_projection->setProjectionDevice(true); } + rl.relock(); return m_projection; } void tryCopyFrom(const KisSafeProjection &rhs) { - QMutexLocker locker(&m_lock); +// QMutexLocker locker(&m_lock); + QWriteLocker l(&m_rwLock); if (!rhs.m_projection) return; if (!m_reusablePaintDevice) { m_reusablePaintDevice = new KisPaintDevice(*rhs.m_projection); m_projection = m_reusablePaintDevice; } else { m_projection = m_reusablePaintDevice; m_projection->makeCloneFromRough(rhs.m_projection, rhs.m_projection->extent()); } } void freeDevice() { - QMutexLocker locker(&m_lock); +// QMutexLocker locker(&m_lock); + QWriteLocker l(&m_rwLock); m_projection = 0; if(m_reusablePaintDevice) { m_reusablePaintDevice->clear(); } } private: - QMutex m_lock; +// QMutex m_lock; + QReadWriteLock m_rwLock; KisPaintDeviceSP m_projection; KisPaintDeviceSP m_reusablePaintDevice; }; class KisCloneLayersList { public: void addClone(KisCloneLayerWSP cloneLayer) { m_clonesList.append(cloneLayer); } void removeClone(KisCloneLayerWSP cloneLayer) { m_clonesList.removeOne(cloneLayer); } void setDirty(const QRect &rect) { Q_FOREACH (KisCloneLayerSP clone, m_clonesList) { if (clone) { clone->setDirtyOriginal(rect); } } } const QList registeredClones() const { return m_clonesList; } bool hasClones() const { return !m_clonesList.isEmpty(); } private: QList m_clonesList; }; struct Q_DECL_HIDDEN KisLayer::Private { KisImageWSP image; QBitArray channelFlags; KisMetaData::Store* metaDataStore; KisSafeProjection safeProjection; KisCloneLayersList clonesList; KisPSDLayerStyleSP layerStyle; KisLayerStyleProjectionPlaneSP layerStyleProjectionPlane; KisAbstractProjectionPlaneSP projectionPlane; KisSelectionMaskSP selectionMask; QList effectMasks; }; KisLayer::KisLayer(KisImageWSP image, const QString &name, quint8 opacity) : KisNode() , m_d(new Private) { setName(name); setOpacity(opacity); m_d->image = image; m_d->metaDataStore = new KisMetaData::Store(); m_d->projectionPlane = toQShared(new KisLayerProjectionPlane(this)); notifyChildMaskChanged(KisNodeSP()); } KisLayer::KisLayer(const KisLayer& rhs) : KisNode(rhs) , m_d(new Private()) { if (this != &rhs) { m_d->image = rhs.m_d->image; m_d->metaDataStore = new KisMetaData::Store(*rhs.m_d->metaDataStore); m_d->channelFlags = rhs.m_d->channelFlags; m_d->safeProjection.tryCopyFrom(rhs.m_d->safeProjection); setName(rhs.name()); m_d->projectionPlane = toQShared(new KisLayerProjectionPlane(this)); if (rhs.m_d->layerStyle) { m_d->layerStyle = rhs.m_d->layerStyle->clone(); KIS_SAFE_ASSERT_RECOVER_NOOP(rhs.m_d->layerStyleProjectionPlane); if (rhs.m_d->layerStyleProjectionPlane) { m_d->layerStyleProjectionPlane = toQShared( new KisLayerStyleProjectionPlane(*rhs.m_d->layerStyleProjectionPlane, this, m_d->layerStyle)); } } notifyChildMaskChanged(KisNodeSP()); } } KisLayer::~KisLayer() { delete m_d->metaDataStore; delete m_d; } const KoColorSpace * KisLayer::colorSpace() const { KisImageSP image = m_d->image.toStrongRef(); if (!image) { return nullptr; } return image->colorSpace(); } const KoCompositeOp * KisLayer::compositeOp() const { /** * FIXME: This function duplicates the same function from * KisMask. We can't move it to KisBaseNode as it doesn't * know anything about parent() method of KisNode * Please think it over... */ KisNodeSP parentNode = parent(); if (!parentNode) return 0; if (!parentNode->colorSpace()) return 0; const KoCompositeOp* op = parentNode->colorSpace()->compositeOp(compositeOpId()); return op ? op : parentNode->colorSpace()->compositeOp(COMPOSITE_OVER); } KisPSDLayerStyleSP KisLayer::layerStyle() const { return m_d->layerStyle; } void KisLayer::setLayerStyle(KisPSDLayerStyleSP layerStyle) { if (layerStyle) { m_d->layerStyle = layerStyle; KisLayerStyleProjectionPlaneSP plane = !layerStyle->isEmpty() ? KisLayerStyleProjectionPlaneSP(new KisLayerStyleProjectionPlane(this)) : KisLayerStyleProjectionPlaneSP(0); m_d->layerStyleProjectionPlane = plane; } else { m_d->layerStyleProjectionPlane.clear(); m_d->layerStyle.clear(); } } KisBaseNode::PropertyList KisLayer::sectionModelProperties() const { KisBaseNode::PropertyList l = KisBaseNode::sectionModelProperties(); l << KisBaseNode::Property(KoID("opacity", i18n("Opacity")), i18n("%1%", percentOpacity())); const KoCompositeOp * compositeOp = this->compositeOp(); if (compositeOp) { l << KisBaseNode::Property(KoID("compositeop", i18n("Blending Mode")), compositeOp->description()); } if (m_d->layerStyle && !m_d->layerStyle->isEmpty()) { l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::layerStyle, m_d->layerStyle->isEnabled()); } l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::inheritAlpha, alphaChannelDisabled()); return l; } void KisLayer::setSectionModelProperties(const KisBaseNode::PropertyList &properties) { KisBaseNode::setSectionModelProperties(properties); Q_FOREACH (const KisBaseNode::Property &property, properties) { if (property.id == KisLayerPropertiesIcons::inheritAlpha.id()) { disableAlphaChannel(property.state.toBool()); } if (property.id == KisLayerPropertiesIcons::layerStyle.id()) { if (m_d->layerStyle && m_d->layerStyle->isEnabled() != property.state.toBool()) { m_d->layerStyle->setEnabled(property.state.toBool()); baseNodeChangedCallback(); baseNodeInvalidateAllFramesCallback(); } } } } void KisLayer::disableAlphaChannel(bool disable) { QBitArray newChannelFlags = m_d->channelFlags; if(newChannelFlags.isEmpty()) newChannelFlags = colorSpace()->channelFlags(true, true); if(disable) newChannelFlags &= colorSpace()->channelFlags(true, false); else newChannelFlags |= colorSpace()->channelFlags(false, true); setChannelFlags(newChannelFlags); } bool KisLayer::alphaChannelDisabled() const { QBitArray flags = colorSpace()->channelFlags(false, true) & m_d->channelFlags; return flags.count(true) == 0 && !m_d->channelFlags.isEmpty(); } void KisLayer::setChannelFlags(const QBitArray & channelFlags) { Q_ASSERT(channelFlags.isEmpty() ||((quint32)channelFlags.count() == colorSpace()->channelCount())); if (KritaUtils::compareChannelFlags(channelFlags, this->channelFlags())) { return; } if (!channelFlags.isEmpty() && channelFlags == QBitArray(channelFlags.size(), true)) { m_d->channelFlags.clear(); } else { m_d->channelFlags = channelFlags; } baseNodeChangedCallback(); baseNodeInvalidateAllFramesCallback(); } QBitArray & KisLayer::channelFlags() const { return m_d->channelFlags; } bool KisLayer::temporary() const { return nodeProperties().boolProperty("temporary", false); } void KisLayer::setTemporary(bool t) { setNodeProperty("temporary", t); } KisImageWSP KisLayer::image() const { return m_d->image; } void KisLayer::setImage(KisImageWSP image) { m_d->image = image; // we own the projection device, so we should take care about it KisPaintDeviceSP projection = this->projection(); if (projection && projection != original()) { projection->setDefaultBounds(new KisDefaultBounds(image)); } KisNodeSP node = firstChild(); while (node) { KisLayerUtils::recursiveApplyNodes(node, [image] (KisNodeSP node) { node->setImage(image); }); node = node->nextSibling(); } } bool KisLayer::canMergeAndKeepBlendOptions(KisLayerSP otherLayer) { return this->compositeOpId() == otherLayer->compositeOpId() && this->opacity() == otherLayer->opacity() && this->channelFlags() == otherLayer->channelFlags() && !this->layerStyle() && !otherLayer->layerStyle() && (this->colorSpace() == otherLayer->colorSpace() || *this->colorSpace() == *otherLayer->colorSpace()); } KisLayerSP KisLayer::createMergedLayerTemplate(KisLayerSP prevLayer) { const bool keepBlendingOptions = canMergeAndKeepBlendOptions(prevLayer); KisLayerSP newLayer = new KisPaintLayer(image(), prevLayer->name(), OPACITY_OPAQUE_U8); if (keepBlendingOptions) { newLayer->setCompositeOpId(compositeOpId()); newLayer->setOpacity(opacity()); newLayer->setChannelFlags(channelFlags()); } return newLayer; } void KisLayer::fillMergedLayerTemplate(KisLayerSP dstLayer, KisLayerSP prevLayer) { const bool keepBlendingOptions = canMergeAndKeepBlendOptions(prevLayer); QRect layerProjectionExtent = this->projection()->extent(); QRect prevLayerProjectionExtent = prevLayer->projection()->extent(); bool alphaDisabled = this->alphaChannelDisabled(); bool prevAlphaDisabled = prevLayer->alphaChannelDisabled(); KisPaintDeviceSP mergedDevice = dstLayer->paintDevice(); if (!keepBlendingOptions) { KisPainter gc(mergedDevice); KisImageSP imageSP = image().toStrongRef(); if (!imageSP) { return; } //Copy the pixels of previous layer with their actual alpha value prevLayer->disableAlphaChannel(false); prevLayer->projectionPlane()->apply(&gc, prevLayerProjectionExtent | imageSP->bounds()); //Restore the previous prevLayer disableAlpha status for correct undo/redo prevLayer->disableAlphaChannel(prevAlphaDisabled); //Paint the pixels of the current layer, using their actual alpha value if (alphaDisabled == prevAlphaDisabled) { this->disableAlphaChannel(false); } this->projectionPlane()->apply(&gc, layerProjectionExtent | imageSP->bounds()); //Restore the layer disableAlpha status for correct undo/redo this->disableAlphaChannel(alphaDisabled); } else { //Copy prevLayer KisPaintDeviceSP srcDev = prevLayer->projection(); mergedDevice->makeCloneFrom(srcDev, srcDev->extent()); //Paint layer on the copy KisPainter gc(mergedDevice); gc.bitBlt(layerProjectionExtent.topLeft(), this->projection(), layerProjectionExtent); } } void KisLayer::registerClone(KisCloneLayerWSP clone) { m_d->clonesList.addClone(clone); } void KisLayer::unregisterClone(KisCloneLayerWSP clone) { m_d->clonesList.removeClone(clone); } const QList KisLayer::registeredClones() const { return m_d->clonesList.registeredClones(); } bool KisLayer::hasClones() const { return m_d->clonesList.hasClones(); } void KisLayer::updateClones(const QRect &rect) { m_d->clonesList.setDirty(rect); } void KisLayer::notifyChildMaskChanged(KisNodeSP changedChildMask) { Q_UNUSED(changedChildMask); updateSelectionMask(); updateEffectMasks(); } KisSelectionMaskSP KisLayer::selectionMask() const { return m_d->selectionMask; } void KisLayer::updateSelectionMask() { KoProperties properties; properties.setProperty("active", true); properties.setProperty("visible", true); QList masks = childNodes(QStringList("KisSelectionMask"), properties); // return the first visible mask Q_FOREACH (KisNodeSP mask, masks) { if (mask) { m_d->selectionMask = dynamic_cast(mask.data()); return; } } m_d->selectionMask = KisSelectionMaskSP(); } KisSelectionSP KisLayer::selection() const { KisSelectionMaskSP mask = selectionMask(); if (mask) { return mask->selection(); } KisImageSP image = m_d->image.toStrongRef(); if (image) { return image->globalSelection(); } return KisSelectionSP(); } /////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////// const QList &KisLayer::effectMasks() const { return m_d->effectMasks; } QList KisLayer::effectMasks(KisNodeSP lastNode) const { if (lastNode.isNull()) { return effectMasks(); } else { // happens rarely. return searchEffectMasks(lastNode); } } void KisLayer::updateEffectMasks() { m_d->effectMasks = searchEffectMasks(KisNodeSP()); } QList KisLayer::searchEffectMasks(KisNodeSP lastNode) const { QList masks; if (childCount() > 0) { KoProperties properties; properties.setProperty("visible", true); QList nodes = childNodes(QStringList("KisEffectMask"), properties); Q_FOREACH (const KisNodeSP& node, nodes) { if (node == lastNode) break; KisEffectMaskSP mask = dynamic_cast(const_cast(node.data())); if (mask) masks.append(mask); } } return masks; } bool KisLayer::hasEffectMasks() const { return !m_d->effectMasks.empty(); } QRect KisLayer::masksChangeRect(const QList &masks, const QRect &requestedRect, bool &rectVariesFlag) const { rectVariesFlag = false; QRect prevChangeRect = requestedRect; /** * We set default value of the change rect for the case * when there is no mask at all */ QRect changeRect = requestedRect; Q_FOREACH (const KisEffectMaskSP& mask, masks) { changeRect = mask->changeRect(prevChangeRect); if (changeRect != prevChangeRect) rectVariesFlag = true; prevChangeRect = changeRect; } return changeRect; } QRect KisLayer::masksNeedRect(const QList &masks, const QRect &changeRect, QStack &applyRects, bool &rectVariesFlag) const { rectVariesFlag = false; QRect prevNeedRect = changeRect; QRect needRect; for (qint32 i = masks.size() - 1; i >= 0; i--) { applyRects.push(prevNeedRect); needRect = masks[i]->needRect(prevNeedRect); if (prevNeedRect != needRect) rectVariesFlag = true; prevNeedRect = needRect; } return needRect; } KisNode::PositionToFilthy calculatePositionToFilthy(KisNodeSP nodeInQuestion, KisNodeSP filthy, KisNodeSP parent) { if (parent == filthy || parent != filthy->parent()) { return KisNode::N_ABOVE_FILTHY; } if (nodeInQuestion == filthy) { return KisNode::N_FILTHY; } KisNodeSP node = nodeInQuestion->prevSibling(); while (node) { if (node == filthy) { return KisNode::N_ABOVE_FILTHY; } node = node->prevSibling(); } return KisNode::N_BELOW_FILTHY; } QRect KisLayer::applyMasks(const KisPaintDeviceSP source, KisPaintDeviceSP destination, const QRect &requestedRect, KisNodeSP filthyNode, KisNodeSP lastNode) const { Q_ASSERT(source); Q_ASSERT(destination); QList masks = effectMasks(lastNode); QRect changeRect; QRect needRect; if (masks.isEmpty()) { changeRect = requestedRect; if (source != destination) { copyOriginalToProjection(source, destination, requestedRect); } } else { QStack applyRects; bool changeRectVaries; bool needRectVaries; /** * FIXME: Assume that varying of the changeRect has already * been taken into account while preparing walkers */ changeRectVaries = false; changeRect = requestedRect; //changeRect = masksChangeRect(masks, requestedRect, // changeRectVaries); needRect = masksNeedRect(masks, changeRect, applyRects, needRectVaries); if (!changeRectVaries && !needRectVaries) { /** * A bit of optimization: * All filters will read/write exactly from/to the requested * rect so we needn't create temporary paint device, * just apply it onto destination */ Q_ASSERT(needRect == requestedRect); if (source != destination) { copyOriginalToProjection(source, destination, needRect); } Q_FOREACH (const KisEffectMaskSP& mask, masks) { const QRect maskApplyRect = applyRects.pop(); const QRect maskNeedRect = applyRects.isEmpty() ? needRect : applyRects.top(); PositionToFilthy maskPosition = calculatePositionToFilthy(mask, filthyNode, const_cast(this)); mask->apply(destination, maskApplyRect, maskNeedRect, maskPosition); } Q_ASSERT(applyRects.isEmpty()); } else { /** * We can't eliminate additional copy-op * as filters' behaviour may be quite insane here, * so let them work on their own paintDevice =) */ KisPaintDeviceSP tempDevice = new KisPaintDevice(colorSpace()); tempDevice->prepareClone(source); copyOriginalToProjection(source, tempDevice, needRect); QRect maskApplyRect = applyRects.pop(); QRect maskNeedRect = needRect; Q_FOREACH (const KisEffectMaskSP& mask, masks) { PositionToFilthy maskPosition = calculatePositionToFilthy(mask, filthyNode, const_cast(this)); mask->apply(tempDevice, maskApplyRect, maskNeedRect, maskPosition); if (!applyRects.isEmpty()) { maskNeedRect = maskApplyRect; maskApplyRect = applyRects.pop(); } } Q_ASSERT(applyRects.isEmpty()); KisPainter::copyAreaOptimized(changeRect.topLeft(), tempDevice, destination, changeRect); } } return changeRect; } QRect KisLayer::updateProjection(const QRect& rect, KisNodeSP filthyNode) { QRect updatedRect = rect; KisPaintDeviceSP originalDevice = original(); if (!rect.isValid() || !visible() || !originalDevice) return QRect(); if (!needProjection() && !hasEffectMasks()) { m_d->safeProjection.freeDevice(); } else { if (!updatedRect.isEmpty()) { KisPaintDeviceSP projection = m_d->safeProjection.getDeviceLazy(originalDevice); updatedRect = applyMasks(originalDevice, projection, updatedRect, filthyNode, 0); } } return updatedRect; } QRect KisLayer::partialChangeRect(KisNodeSP lastNode, const QRect& rect) { bool changeRectVaries = false; QRect changeRect = outgoingChangeRect(rect); changeRect = masksChangeRect(effectMasks(lastNode), changeRect, changeRectVaries); return changeRect; } /** * \p rect is a dirty rect in layer's original() coordinates! */ void KisLayer::buildProjectionUpToNode(KisPaintDeviceSP projection, KisNodeSP lastNode, const QRect& rect) { QRect changeRect = partialChangeRect(lastNode, rect); KisPaintDeviceSP originalDevice = original(); KIS_ASSERT_RECOVER_RETURN(needProjection() || hasEffectMasks()); if (!changeRect.isEmpty()) { applyMasks(originalDevice, projection, changeRect, this, lastNode); } } bool KisLayer::needProjection() const { return false; } void KisLayer::copyOriginalToProjection(const KisPaintDeviceSP original, KisPaintDeviceSP projection, const QRect& rect) const { KisPainter::copyAreaOptimized(rect.topLeft(), original, projection, rect); } KisAbstractProjectionPlaneSP KisLayer::projectionPlane() const { return m_d->layerStyleProjectionPlane ? KisAbstractProjectionPlaneSP(m_d->layerStyleProjectionPlane) : m_d->projectionPlane; } KisAbstractProjectionPlaneSP KisLayer::internalProjectionPlane() const { return m_d->projectionPlane; } KisPaintDeviceSP KisLayer::projection() const { KisPaintDeviceSP originalDevice = original(); return needProjection() || hasEffectMasks() ? m_d->safeProjection.getDeviceLazy(originalDevice) : originalDevice; } QRect KisLayer::changeRect(const QRect &rect, PositionToFilthy pos) const { QRect changeRect = rect; changeRect = incomingChangeRect(changeRect); if(pos == KisNode::N_FILTHY) { QRect projectionToBeUpdated = projection()->exactBoundsAmortized() & changeRect; bool changeRectVaries; changeRect = outgoingChangeRect(changeRect); changeRect = masksChangeRect(effectMasks(), changeRect, changeRectVaries); /** * If the projection contains some dirty areas we should also * add them to the change rect, because they might have * changed. E.g. when a visibility of the mask has chnaged * while the parent layer was invinisble. */ if (!projectionToBeUpdated.isEmpty() && !changeRect.contains(projectionToBeUpdated)) { changeRect |= projectionToBeUpdated; } } // TODO: string comparizon: optimize! if (pos != KisNode::N_FILTHY && pos != KisNode::N_FILTHY_PROJECTION && compositeOpId() != COMPOSITE_COPY) { changeRect |= rect; } return changeRect; } void KisLayer::childNodeChanged(KisNodeSP changedChildNode) { if (dynamic_cast(changedChildNode.data())) { notifyChildMaskChanged(changedChildNode); } } QRect KisLayer::incomingChangeRect(const QRect &rect) const { return rect; } QRect KisLayer::outgoingChangeRect(const QRect &rect) const { return rect; } QImage KisLayer::createThumbnail(qint32 w, qint32 h) { if (w == 0 || h == 0) { return QImage(); } KisPaintDeviceSP originalDevice = original(); return originalDevice ? originalDevice->createThumbnail(w, h, 1, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()) : QImage(); } QImage KisLayer::createThumbnailForFrame(qint32 w, qint32 h, int time) { if (w == 0 || h == 0) { return QImage(); } KisPaintDeviceSP originalDevice = original(); if (originalDevice) { KisRasterKeyframeChannel *channel = originalDevice->keyframeChannel(); if (channel) { KisPaintDeviceSP targetDevice = new KisPaintDevice(colorSpace()); KisKeyframeSP keyframe = channel->activeKeyframeAt(time); channel->fetchFrame(keyframe, targetDevice); return targetDevice->createThumbnail(w, h, 1, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); } } return createThumbnail(w, h); } qint32 KisLayer::x() const { KisPaintDeviceSP originalDevice = original(); return originalDevice ? originalDevice->x() : 0; } qint32 KisLayer::y() const { KisPaintDeviceSP originalDevice = original(); return originalDevice ? originalDevice->y() : 0; } void KisLayer::setX(qint32 x) { KisPaintDeviceSP originalDevice = original(); if (originalDevice) originalDevice->setX(x); } void KisLayer::setY(qint32 y) { KisPaintDeviceSP originalDevice = original(); if (originalDevice) originalDevice->setY(y); } QRect KisLayer::layerExtentImpl(bool needExactBounds) const { QRect additionalMaskExtent = QRect(); QList effectMasks = this->effectMasks(); Q_FOREACH(KisEffectMaskSP mask, effectMasks) { additionalMaskExtent |= mask->nonDependentExtent(); } KisPaintDeviceSP originalDevice = original(); QRect layerExtent; if (originalDevice) { layerExtent = needExactBounds ? originalDevice->exactBounds() : originalDevice->extent(); } QRect additionalCompositeOpExtent; if (compositeOpId() == COMPOSITE_DESTINATION_IN || compositeOpId() == COMPOSITE_DESTINATION_ATOP) { additionalCompositeOpExtent = originalDevice->defaultBounds()->bounds(); } return layerExtent | additionalMaskExtent | additionalCompositeOpExtent; } QRect KisLayer::extent() const { return layerExtentImpl(false); } QRect KisLayer::exactBounds() const { return layerExtentImpl(true); } KisLayerSP KisLayer::parentLayer() const { return qobject_cast(parent().data()); } KisMetaData::Store* KisLayer::metaData() { return m_d->metaDataStore; } diff --git a/libs/image/kis_simple_update_queue.cpp b/libs/image/kis_simple_update_queue.cpp index 1e367714a7..16804a2bf1 100644 --- a/libs/image/kis_simple_update_queue.cpp +++ b/libs/image/kis_simple_update_queue.cpp @@ -1,397 +1,409 @@ /* * Copyright (c) 2010 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 "kis_simple_update_queue.h" #include #include #include "kis_image_config.h" #include "kis_full_refresh_walker.h" #include "kis_spontaneous_job.h" //#define ENABLE_DEBUG_JOIN //#define ENABLE_ACCUMULATOR #ifdef ENABLE_DEBUG_JOIN #define DEBUG_JOIN(baseRect, newRect, alpha) \ dbgKrita << "Two rects were joined:\t" \ << (baseRect) << "+" << (newRect) << "->" \ << ((baseRect) | (newRect)) << "(" << alpha << ")" #else #define DEBUG_JOIN(baseRect, newRect, alpha) #endif /* ENABLE_DEBUG_JOIN */ #ifdef ENABLE_ACCUMULATOR #define DECLARE_ACCUMULATOR() static qreal _baseAmount=0, _newAmount=0 #define ACCUMULATOR_ADD(baseAmount, newAmount) \ do {_baseAmount += baseAmount; _newAmount += newAmount;} while (0) #define ACCUMULATOR_DEBUG() \ dbgKrita << "Accumulated alpha:" << _newAmount / _baseAmount #else #define DECLARE_ACCUMULATOR() #define ACCUMULATOR_ADD(baseAmount, newAmount) #define ACCUMULATOR_DEBUG() #endif /* ENABLE_ACCUMULATOR */ KisSimpleUpdateQueue::KisSimpleUpdateQueue() : m_overrideLevelOfDetail(-1) { updateSettings(); } KisSimpleUpdateQueue::~KisSimpleUpdateQueue() { - QMutexLocker locker(&m_lock); +// QMutexLocker locker(&m_lock); + QWriteLocker l(&m_rwLock); while (!m_spontaneousJobsList.isEmpty()) { delete m_spontaneousJobsList.takeLast(); } } void KisSimpleUpdateQueue::updateSettings() { - QMutexLocker locker(&m_lock); +// QMutexLocker locker(&m_lock); + QWriteLocker l(&m_rwLock); KisImageConfig config(true); m_patchWidth = config.updatePatchWidth(); m_patchHeight = config.updatePatchHeight(); m_maxCollectAlpha = config.maxCollectAlpha(); m_maxMergeAlpha = config.maxMergeAlpha(); m_maxMergeCollectAlpha = config.maxMergeCollectAlpha(); } int KisSimpleUpdateQueue::overrideLevelOfDetail() const { return m_overrideLevelOfDetail; } void KisSimpleUpdateQueue::processQueue(KisUpdaterContext &updaterContext) { updaterContext.lock(); +// QMutexLocker locker(&m_lock); + QWriteLocker l(&m_rwLock); while(updaterContext.hasSpareThread() && processOneJob(updaterContext)); + l.unlock(); +// locker.unlock(); updaterContext.unlock(); } bool KisSimpleUpdateQueue::processOneJob(KisUpdaterContext &updaterContext) { - QMutexLocker locker(&m_lock); +// QMutexLocker locker(&m_lock); KisBaseRectsWalkerSP item; KisMutableWalkersListIterator iter(m_updatesList); bool jobAdded = false; int currentLevelOfDetail = updaterContext.currentLevelOfDetail(); while(iter.hasNext()) { item = iter.next(); if ((currentLevelOfDetail < 0 || currentLevelOfDetail == item->levelOfDetail()) && !item->checksumValid()) { m_overrideLevelOfDetail = item->levelOfDetail(); item->recalculate(item->requestedRect()); m_overrideLevelOfDetail = -1; } if ((currentLevelOfDetail < 0 || currentLevelOfDetail == item->levelOfDetail()) && updaterContext.isJobAllowed(item)) { updaterContext.addMergeJob(item); iter.remove(); jobAdded = true; break; } } if (jobAdded) return true; if (!m_spontaneousJobsList.isEmpty()) { /** * WARNING: Please note that this still doesn't guarantee that * the spontaneous jobs are exclusive, since updates and/or * strokes can be added after them. The only thing it * guarantees that two spontaneous jobs will not be executed * in parallel. * * Right now it works as it is. Probably will need to be fixed * in the future. */ qint32 numMergeJobs; qint32 numStrokeJobs; updaterContext.getJobsSnapshot(numMergeJobs, numStrokeJobs); if (!numMergeJobs && !numStrokeJobs) { KisSpontaneousJob *job = m_spontaneousJobsList.takeFirst(); updaterContext.addSpontaneousJob(job); jobAdded = true; } } return jobAdded; } void KisSimpleUpdateQueue::addUpdateJob(KisNodeSP node, const QVector &rects, const QRect& cropRect, int levelOfDetail) { addJob(node, rects, cropRect, levelOfDetail, KisBaseRectsWalker::UPDATE); } void KisSimpleUpdateQueue::addUpdateJob(KisNodeSP node, const QRect &rc, const QRect& cropRect, int levelOfDetail) { addJob(node, {rc}, cropRect, levelOfDetail, KisBaseRectsWalker::UPDATE); } void KisSimpleUpdateQueue::addUpdateNoFilthyJob(KisNodeSP node, const QRect& rc, const QRect& cropRect, int levelOfDetail) { addJob(node, {rc}, cropRect, levelOfDetail, KisBaseRectsWalker::UPDATE_NO_FILTHY); } void KisSimpleUpdateQueue::addFullRefreshJob(KisNodeSP node, const QRect& rc, const QRect& cropRect, int levelOfDetail) { addJob(node, {rc}, cropRect, levelOfDetail, KisBaseRectsWalker::FULL_REFRESH); } void KisSimpleUpdateQueue::addJob(KisNodeSP node, const QVector &rects, const QRect& cropRect, int levelOfDetail, KisBaseRectsWalker::UpdateType type) { QList walkers; Q_FOREACH (const QRect &rc, rects) { if (rc.isEmpty()) continue; KisBaseRectsWalkerSP walker; if(trySplitJob(node, rc, cropRect, levelOfDetail, type)) continue; if(tryMergeJob(node, rc, cropRect, levelOfDetail, type)) continue; if (type == KisBaseRectsWalker::UPDATE) { walker = new KisMergeWalker(cropRect, KisMergeWalker::DEFAULT); } else if (type == KisBaseRectsWalker::FULL_REFRESH) { walker = new KisFullRefreshWalker(cropRect); } else if (type == KisBaseRectsWalker::UPDATE_NO_FILTHY) { walker = new KisMergeWalker(cropRect, KisMergeWalker::NO_FILTHY); } /* else if(type == KisBaseRectsWalker::UNSUPPORTED) fatalKrita; */ walker->collectRects(node, rc); walkers.append(walker); } if (!walkers.isEmpty()) { - m_lock.lock(); +// m_lock.lock(); + QWriteLocker l(&m_rwLock); m_updatesList.append(walkers); - m_lock.unlock(); +// m_lock.unlock(); } } void KisSimpleUpdateQueue::addSpontaneousJob(KisSpontaneousJob *spontaneousJob) { - QMutexLocker locker(&m_lock); +// QMutexLocker locker(&m_lock); + QWriteLocker l(&m_rwLock); KisSpontaneousJob *item; KisMutableSpontaneousJobsListIterator iter(m_spontaneousJobsList); iter.toBack(); while(iter.hasPrevious()) { item = iter.previous(); if (spontaneousJob->overrides(item)) { iter.remove(); delete item; } } m_spontaneousJobsList.append(spontaneousJob); } bool KisSimpleUpdateQueue::isEmpty() const { - QMutexLocker locker(&m_lock); +// QMutexLocker locker(&m_lock); + QReadLocker l(&m_rwLock); return m_updatesList.isEmpty() && m_spontaneousJobsList.isEmpty(); } qint32 KisSimpleUpdateQueue::sizeMetric() const { - QMutexLocker locker(&m_lock); +// QMutexLocker locker(&m_lock); + QReadLocker l(&m_rwLock); return m_updatesList.size() + m_spontaneousJobsList.size(); } bool KisSimpleUpdateQueue::trySplitJob(KisNodeSP node, const QRect& rc, const QRect& cropRect, int levelOfDetail, KisBaseRectsWalker::UpdateType type) { if(rc.width() <= m_patchWidth || rc.height() <= m_patchHeight) return false; // a bit of recursive splitting... qint32 firstCol = rc.x() / m_patchWidth; qint32 firstRow = rc.y() / m_patchHeight; qint32 lastCol = (rc.x() + rc.width()) / m_patchWidth; qint32 lastRow = (rc.y() + rc.height()) / m_patchHeight; QVector splitRects; for(qint32 i = firstRow; i <= lastRow; i++) { for(qint32 j = firstCol; j <= lastCol; j++) { QRect maxPatchRect(j * m_patchWidth, i * m_patchHeight, m_patchWidth, m_patchHeight); QRect patchRect = rc & maxPatchRect; splitRects.append(patchRect); } } KIS_SAFE_ASSERT_RECOVER_NOOP(!splitRects.isEmpty()); addJob(node, splitRects, cropRect, levelOfDetail, type); return true; } bool KisSimpleUpdateQueue::tryMergeJob(KisNodeSP node, const QRect& rc, const QRect& cropRect, int levelOfDetail, KisBaseRectsWalker::UpdateType type) { - QMutexLocker locker(&m_lock); +// QMutexLocker locker(&m_lock); + QWriteLocker l(&m_rwLock); QRect baseRect = rc; KisBaseRectsWalkerSP goodCandidate; KisBaseRectsWalkerSP item; KisWalkersListIterator iter(m_updatesList); /** * We add new jobs to the tail of the list, * so it's more probable to find a good candidate here. */ iter.toBack(); while(iter.hasPrevious()) { item = iter.previous(); if(item->startNode() != node) continue; if(item->type() != type) continue; if(item->cropRect() != cropRect) continue; if(item->levelOfDetail() != levelOfDetail) continue; if(joinRects(baseRect, item->requestedRect(), m_maxMergeAlpha)) { goodCandidate = item; break; } } if(goodCandidate) collectJobs(goodCandidate, baseRect, m_maxMergeCollectAlpha); return (bool)goodCandidate; } void KisSimpleUpdateQueue::optimize() { - QMutexLocker locker(&m_lock); +// QMutexLocker locker(&m_lock); + QWriteLocker l(&m_rwLock); if(m_updatesList.size() <= 1) return; KisBaseRectsWalkerSP baseWalker = m_updatesList.first(); QRect baseRect = baseWalker->requestedRect(); collectJobs(baseWalker, baseRect, m_maxCollectAlpha); } void KisSimpleUpdateQueue::collectJobs(KisBaseRectsWalkerSP &baseWalker, QRect baseRect, const qreal maxAlpha) { KisBaseRectsWalkerSP item; KisMutableWalkersListIterator iter(m_updatesList); while(iter.hasNext()) { item = iter.next(); if(item == baseWalker) continue; if(item->type() != baseWalker->type()) continue; if(item->startNode() != baseWalker->startNode()) continue; if(item->cropRect() != baseWalker->cropRect()) continue; if(item->levelOfDetail() != baseWalker->levelOfDetail()) continue; if(joinRects(baseRect, item->requestedRect(), maxAlpha)) { iter.remove(); } } if(baseWalker->requestedRect() != baseRect) { baseWalker->collectRects(baseWalker->startNode(), baseRect); } } bool KisSimpleUpdateQueue::joinRects(QRect& baseRect, const QRect& newRect, qreal maxAlpha) { QRect unitedRect = baseRect | newRect; if(unitedRect.width() > m_patchWidth || unitedRect.height() > m_patchHeight) return false; bool result = false; qint64 baseWork = baseRect.width() * baseRect.height() + newRect.width() * newRect.height(); qint64 newWork = unitedRect.width() * unitedRect.height(); qreal alpha = qreal(newWork) / baseWork; if(alpha < maxAlpha) { DEBUG_JOIN(baseRect, newRect, alpha); DECLARE_ACCUMULATOR(); ACCUMULATOR_ADD(baseWork, newWork); ACCUMULATOR_DEBUG(); baseRect = unitedRect; result = true; } return result; } KisWalkersList& KisTestableSimpleUpdateQueue::getWalkersList() { return m_updatesList; } KisSpontaneousJobsList& KisTestableSimpleUpdateQueue::getSpontaneousJobsList() { return m_spontaneousJobsList; } diff --git a/libs/image/kis_simple_update_queue.h b/libs/image/kis_simple_update_queue.h index e51065c53d..1906ee874a 100644 --- a/libs/image/kis_simple_update_queue.h +++ b/libs/image/kis_simple_update_queue.h @@ -1,116 +1,117 @@ /* * Copyright (c) 2010 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. */ #ifndef __KIS_SIMPLE_UPDATE_QUEUE_H #define __KIS_SIMPLE_UPDATE_QUEUE_H #include #include "kis_updater_context.h" typedef QList KisWalkersList; typedef QListIterator KisWalkersListIterator; typedef QMutableListIterator KisMutableWalkersListIterator; typedef QList KisSpontaneousJobsList; typedef QListIterator KisSpontaneousJobsListIterator; typedef QMutableListIterator KisMutableSpontaneousJobsListIterator; class KRITAIMAGE_EXPORT KisSimpleUpdateQueue { public: KisSimpleUpdateQueue(); virtual ~KisSimpleUpdateQueue(); void processQueue(KisUpdaterContext &updaterContext); void addUpdateJob(KisNodeSP node, const QVector &rects, const QRect& cropRect, int levelOfDetail); void addUpdateJob(KisNodeSP node, const QRect &rc, const QRect& cropRect, int levelOfDetail); void addUpdateNoFilthyJob(KisNodeSP node, const QRect& rc, const QRect& cropRect, int levelOfDetail); void addFullRefreshJob(KisNodeSP node, const QRect& rc, const QRect& cropRect, int levelOfDetail); void addSpontaneousJob(KisSpontaneousJob *spontaneousJob); void optimize(); bool isEmpty() const; qint32 sizeMetric() const; void updateSettings(); int overrideLevelOfDetail() const; protected: void addJob(KisNodeSP node, const QVector &rects, const QRect& cropRect, int levelOfDetail, KisBaseRectsWalker::UpdateType type); bool processOneJob(KisUpdaterContext &updaterContext); bool trySplitJob(KisNodeSP node, const QRect& rc, const QRect& cropRect, int levelOfDetail, KisBaseRectsWalker::UpdateType type); bool tryMergeJob(KisNodeSP node, const QRect& rc, const QRect& cropRect, int levelOfDetail, KisBaseRectsWalker::UpdateType type); void collectJobs(KisBaseRectsWalkerSP &baseWalker, QRect baseRect, const qreal maxAlpha); bool joinRects(QRect& baseRect, const QRect& newRect, qreal maxAlpha); protected: - mutable QMutex m_lock; +// mutable QMutex m_lock; + mutable QReadWriteLock m_rwLock; KisWalkersList m_updatesList; KisSpontaneousJobsList m_spontaneousJobsList; /** * Parameters of optimization * (loaded from a configuration file) */ /** * Big update areas are split into a set of smaller * ones, m_patchWidth and m_patchHeight represent the * size of these areas. */ qint32 m_patchWidth; qint32 m_patchHeight; /** * Maximum coefficient of work while regular optimization() */ qreal m_maxCollectAlpha; /** * Maximum coefficient of work when to rects are considered * similar and are merged in tryMergeJob() */ qreal m_maxMergeAlpha; /** * The coefficient of work used while collecting phase of tryToMerge() */ qreal m_maxMergeCollectAlpha; int m_overrideLevelOfDetail; }; class KRITAIMAGE_EXPORT KisTestableSimpleUpdateQueue : public KisSimpleUpdateQueue { public: KisWalkersList& getWalkersList(); KisSpontaneousJobsList& getSpontaneousJobsList(); }; #endif /* __KIS_SIMPLE_UPDATE_QUEUE_H */ diff --git a/libs/image/kis_strokes_queue.cpp b/libs/image/kis_strokes_queue.cpp index 1004c8d93c..0901139159 100644 --- a/libs/image/kis_strokes_queue.cpp +++ b/libs/image/kis_strokes_queue.cpp @@ -1,834 +1,855 @@ /* * Copyright (c) 2011 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 "kis_strokes_queue.h" #include #include #include #include "kis_stroke.h" #include "kis_updater_context.h" #include "kis_stroke_job_strategy.h" #include "kis_stroke_strategy.h" #include "kis_undo_stores.h" #include "kis_post_execution_undo_adapter.h" typedef QQueue StrokesQueue; typedef QQueue::iterator StrokesQueueIterator; #include "kis_image_interfaces.h" class KisStrokesQueue::LodNUndoStrokesFacade : public KisStrokesFacade { public: LodNUndoStrokesFacade(KisStrokesQueue *_q) : q(_q) {} KisStrokeId startStroke(KisStrokeStrategy *strokeStrategy) override { return q->startLodNUndoStroke(strokeStrategy); } void addJob(KisStrokeId id, KisStrokeJobData *data) override { KisStrokeSP stroke = id.toStrongRef(); KIS_SAFE_ASSERT_RECOVER_NOOP(stroke); KIS_SAFE_ASSERT_RECOVER_NOOP(!stroke->lodBuddy()); KIS_SAFE_ASSERT_RECOVER_NOOP(stroke->type() == KisStroke::LODN); q->addJob(id, data); } void endStroke(KisStrokeId id) override { KisStrokeSP stroke = id.toStrongRef(); KIS_SAFE_ASSERT_RECOVER_NOOP(stroke); KIS_SAFE_ASSERT_RECOVER_NOOP(!stroke->lodBuddy()); KIS_SAFE_ASSERT_RECOVER_NOOP(stroke->type() == KisStroke::LODN); q->endStroke(id); } bool cancelStroke(KisStrokeId id) override { Q_UNUSED(id); qFatal("Not implemented"); return false; } private: KisStrokesQueue *q; }; struct Q_DECL_HIDDEN KisStrokesQueue::Private { Private(KisStrokesQueue *_q) : q(_q), openedStrokesCounter(0), needsExclusiveAccess(false), wrapAroundModeSupported(false), balancingRatioOverride(-1.0), currentStrokeLoaded(false), lodNNeedsSynchronization(true), desiredLevelOfDetail(0), nextDesiredLevelOfDetail(0), lodNStrokesFacade(_q), lodNPostExecutionUndoAdapter(&lodNUndoStore, &lodNStrokesFacade) {} KisStrokesQueue *q; StrokesQueue strokesQueue; int openedStrokesCounter; bool needsExclusiveAccess; bool wrapAroundModeSupported; qreal balancingRatioOverride; bool currentStrokeLoaded; bool lodNNeedsSynchronization; int desiredLevelOfDetail; int nextDesiredLevelOfDetail; - QMutex mutex; +// QMutex mutex; + QReadWriteLock m_rwLock; KisLodSyncStrokeStrategyFactory lod0ToNStrokeStrategyFactory; KisSuspendResumeStrategyFactory suspendUpdatesStrokeStrategyFactory; KisSuspendResumeStrategyFactory resumeUpdatesStrokeStrategyFactory; KisSurrogateUndoStore lodNUndoStore; LodNUndoStrokesFacade lodNStrokesFacade; KisPostExecutionUndoAdapter lodNPostExecutionUndoAdapter; void cancelForgettableStrokes(); void startLod0ToNStroke(int levelOfDetail, bool forgettable); bool canUseLodN() const; StrokesQueueIterator findNewLod0Pos(); StrokesQueueIterator findNewLodNPos(KisStrokeSP lodN); bool shouldWrapInSuspendUpdatesStroke() const; void switchDesiredLevelOfDetail(bool forced); bool hasUnfinishedStrokes() const; void tryClearUndoOnStrokeCompletion(KisStrokeSP finishingStroke); }; KisStrokesQueue::KisStrokesQueue() : m_d(new Private(this)) { } KisStrokesQueue::~KisStrokesQueue() { Q_FOREACH (KisStrokeSP stroke, m_d->strokesQueue) { stroke->cancelStroke(); } delete m_d; } template typename StrokesQueue::iterator executeStrokePair(const StrokePair &pair, StrokesQueue &queue, typename StrokesQueue::iterator it, KisStroke::Type type, int levelOfDetail, KisStrokesQueueMutatedJobInterface *mutatedJobsInterface) { KisStrokeStrategy *strategy = pair.first; QList jobsData = pair.second; KisStrokeSP stroke(new KisStroke(strategy, type, levelOfDetail)); strategy->setCancelStrokeId(stroke); strategy->setMutatedJobsInterface(mutatedJobsInterface); it = queue.insert(it, stroke); Q_FOREACH (KisStrokeJobData *jobData, jobsData) { stroke->addJob(jobData); } stroke->endStroke(); return it; } void KisStrokesQueue::Private::startLod0ToNStroke(int levelOfDetail, bool forgettable) { // precondition: lock held! // precondition: lod > 0 KIS_ASSERT_RECOVER_RETURN(levelOfDetail); if (!this->lod0ToNStrokeStrategyFactory) return; KisLodSyncPair syncPair = this->lod0ToNStrokeStrategyFactory(forgettable); executeStrokePair(syncPair, this->strokesQueue, this->strokesQueue.end(), KisStroke::LODN, levelOfDetail, q); this->lodNNeedsSynchronization = false; } void KisStrokesQueue::Private::cancelForgettableStrokes() { if (!strokesQueue.isEmpty() && !hasUnfinishedStrokes()) { Q_FOREACH (KisStrokeSP stroke, strokesQueue) { KIS_ASSERT_RECOVER_NOOP(stroke->isEnded()); if (stroke->canForgetAboutMe()) { stroke->cancelStroke(); } } } } bool KisStrokesQueue::Private::canUseLodN() const { Q_FOREACH (KisStrokeSP stroke, strokesQueue) { if (stroke->type() == KisStroke::LEGACY) { return false; } } return true; } bool KisStrokesQueue::Private::shouldWrapInSuspendUpdatesStroke() const { Q_FOREACH (KisStrokeSP stroke, strokesQueue) { if (stroke->isCancelled()) continue; if (stroke->type() == KisStroke::RESUME) { /** * Resuming process is long-running and consists of * multiple actions, therefore, if it has already started, * we cannot use it to guard our new stroke, so just skip it. * see https://phabricator.kde.org/T2542 */ if (stroke->isInitialized()) continue; return false; } } return true; } StrokesQueueIterator KisStrokesQueue::Private::findNewLod0Pos() { StrokesQueueIterator it = strokesQueue.begin(); StrokesQueueIterator end = strokesQueue.end(); for (; it != end; ++it) { if ((*it)->isCancelled()) continue; if ((*it)->type() == KisStroke::RESUME) { // \see a comment in shouldWrapInSuspendUpdatesStroke() if ((*it)->isInitialized()) continue; return it; } } return it; } StrokesQueueIterator KisStrokesQueue::Private::findNewLodNPos(KisStrokeSP lodN) { StrokesQueueIterator it = strokesQueue.begin(); StrokesQueueIterator end = strokesQueue.end(); for (; it != end; ++it) { if ((*it)->isCancelled()) continue; if (((*it)->type() == KisStroke::SUSPEND || (*it)->type() == KisStroke::RESUME) && (*it)->isInitialized()) { // \see a comment in shouldWrapInSuspendUpdatesStroke() continue; } if ((*it)->type() == KisStroke::LOD0 || (*it)->type() == KisStroke::SUSPEND || (*it)->type() == KisStroke::RESUME) { if (it != end && it == strokesQueue.begin()) { KisStrokeSP head = *it; if (head->supportsSuspension()) { head->suspendStroke(lodN); } } return it; } } return it; } KisStrokeId KisStrokesQueue::startLodNUndoStroke(KisStrokeStrategy *strokeStrategy) { - QMutexLocker locker(&m_d->mutex); +// QMutexLocker locker(&m_d->mutex); + QWriteLocker l(&m_d->m_rwLock); KIS_SAFE_ASSERT_RECOVER_NOOP(!m_d->lodNNeedsSynchronization); KIS_SAFE_ASSERT_RECOVER_NOOP(m_d->desiredLevelOfDetail > 0); KisStrokeSP buddy(new KisStroke(strokeStrategy, KisStroke::LODN, m_d->desiredLevelOfDetail)); strokeStrategy->setCancelStrokeId(buddy); strokeStrategy->setMutatedJobsInterface(this); m_d->strokesQueue.insert(m_d->findNewLodNPos(buddy), buddy); KisStrokeId id(buddy); m_d->openedStrokesCounter++; return id; } KisStrokeId KisStrokesQueue::startStroke(KisStrokeStrategy *strokeStrategy) { - QMutexLocker locker(&m_d->mutex); +// QMutexLocker locker(&m_d->mutex); + QWriteLocker l(&m_d->m_rwLock); KisStrokeSP stroke; KisStrokeStrategy* lodBuddyStrategy; m_d->cancelForgettableStrokes(); if (m_d->desiredLevelOfDetail && m_d->canUseLodN() && (lodBuddyStrategy = strokeStrategy->createLodClone(m_d->desiredLevelOfDetail))) { if (m_d->lodNNeedsSynchronization) { m_d->startLod0ToNStroke(m_d->desiredLevelOfDetail, false); } stroke = KisStrokeSP(new KisStroke(strokeStrategy, KisStroke::LOD0, 0)); KisStrokeSP buddy(new KisStroke(lodBuddyStrategy, KisStroke::LODN, m_d->desiredLevelOfDetail)); lodBuddyStrategy->setCancelStrokeId(buddy); lodBuddyStrategy->setMutatedJobsInterface(this); stroke->setLodBuddy(buddy); m_d->strokesQueue.insert(m_d->findNewLodNPos(buddy), buddy); if (m_d->shouldWrapInSuspendUpdatesStroke()) { KisSuspendResumePair suspendPair = m_d->suspendUpdatesStrokeStrategyFactory(); KisSuspendResumePair resumePair = m_d->resumeUpdatesStrokeStrategyFactory(); StrokesQueueIterator it = m_d->findNewLod0Pos(); it = executeStrokePair(resumePair, m_d->strokesQueue, it, KisStroke::RESUME, 0, this); it = m_d->strokesQueue.insert(it, stroke); it = executeStrokePair(suspendPair, m_d->strokesQueue, it, KisStroke::SUSPEND, 0, this); } else { m_d->strokesQueue.insert(m_d->findNewLod0Pos(), stroke); } } else { stroke = KisStrokeSP(new KisStroke(strokeStrategy, KisStroke::LEGACY, 0)); m_d->strokesQueue.enqueue(stroke); } KisStrokeId id(stroke); strokeStrategy->setCancelStrokeId(id); strokeStrategy->setMutatedJobsInterface(this); m_d->openedStrokesCounter++; if (stroke->type() == KisStroke::LEGACY) { m_d->lodNNeedsSynchronization = true; } return id; } void KisStrokesQueue::addJob(KisStrokeId id, KisStrokeJobData *data) { - QMutexLocker locker(&m_d->mutex); +// QMutexLocker locker(&m_d->mutex); + QWriteLocker l(&m_d->m_rwLock); KisStrokeSP stroke = id.toStrongRef(); KIS_SAFE_ASSERT_RECOVER_RETURN(stroke); KisStrokeSP buddy = stroke->lodBuddy(); if (buddy) { KisStrokeJobData *clonedData = data->createLodClone(buddy->worksOnLevelOfDetail()); KIS_ASSERT_RECOVER_RETURN(clonedData); buddy->addJob(clonedData); } stroke->addJob(data); } void KisStrokesQueue::addMutatedJobs(KisStrokeId id, const QVector list) { - QMutexLocker locker(&m_d->mutex); +// QMutexLocker locker(&m_d->mutex); + QWriteLocker l(&m_d->m_rwLock); KisStrokeSP stroke = id.toStrongRef(); KIS_SAFE_ASSERT_RECOVER_RETURN(stroke); stroke->addMutatedJobs(list); } void KisStrokesQueue::endStroke(KisStrokeId id) { - QMutexLocker locker(&m_d->mutex); +// QMutexLocker locker(&m_d->mutex); + QWriteLocker l(&m_d->m_rwLock); KisStrokeSP stroke = id.toStrongRef(); KIS_SAFE_ASSERT_RECOVER_RETURN(stroke); stroke->endStroke(); m_d->openedStrokesCounter--; KisStrokeSP buddy = stroke->lodBuddy(); if (buddy) { buddy->endStroke(); } } bool KisStrokesQueue::cancelStroke(KisStrokeId id) { - QMutexLocker locker(&m_d->mutex); +// QMutexLocker locker(&m_d->mutex); + QWriteLocker l(&m_d->m_rwLock); KisStrokeSP stroke = id.toStrongRef(); if(stroke) { stroke->cancelStroke(); m_d->openedStrokesCounter--; KisStrokeSP buddy = stroke->lodBuddy(); if (buddy) { buddy->cancelStroke(); } } return stroke; } bool KisStrokesQueue::Private::hasUnfinishedStrokes() const { Q_FOREACH (KisStrokeSP stroke, strokesQueue) { if (!stroke->isEnded()) { return true; } } return false; } bool KisStrokesQueue::tryCancelCurrentStrokeAsync() { bool anythingCanceled = false; - QMutexLocker locker(&m_d->mutex); +// QMutexLocker locker(&m_d->mutex); + QWriteLocker l(&m_d->m_rwLock); /** * We cancel only ended strokes. This is done to avoid * handling dangling pointers problem (KisStrokeId). The owner * of a stroke will cancel the stroke itself if needed. */ if (!m_d->strokesQueue.isEmpty() && !m_d->hasUnfinishedStrokes()) { anythingCanceled = true; Q_FOREACH (KisStrokeSP currentStroke, m_d->strokesQueue) { KIS_ASSERT_RECOVER_NOOP(currentStroke->isEnded()); currentStroke->cancelStroke(); // we shouldn't cancel buddies... if (currentStroke->type() == KisStroke::LOD0) { /** * If the buddy has already finished, we cannot undo it because * it doesn't store any undo data. Therefore we just regenerate * the LOD caches. */ m_d->lodNNeedsSynchronization = true; } } } /** * NOTE: We do not touch the openedStrokesCounter here since * we work with closed id's only here */ return anythingCanceled; } UndoResult KisStrokesQueue::tryUndoLastStrokeAsync() { UndoResult result = UNDO_FAIL; - QMutexLocker locker(&m_d->mutex); +// QMutexLocker locker(&m_d->mutex); + QWriteLocker l(&m_d->m_rwLock); std::reverse_iterator it(m_d->strokesQueue.constEnd()); std::reverse_iterator end(m_d->strokesQueue.constBegin()); KisStrokeSP lastStroke; KisStrokeSP lastBuddy; bool buddyFound = false; for (; it != end; ++it) { if ((*it)->type() == KisStroke::LEGACY) { break; } if (!lastStroke && (*it)->type() == KisStroke::LOD0 && !(*it)->isCancelled()) { lastStroke = *it; lastBuddy = lastStroke->lodBuddy(); KIS_SAFE_ASSERT_RECOVER(lastBuddy) { lastStroke.clear(); lastBuddy.clear(); break; } } KIS_SAFE_ASSERT_RECOVER(!lastStroke || *it == lastBuddy || (*it)->type() != KisStroke::LODN) { lastStroke.clear(); lastBuddy.clear(); break; } if (lastStroke && *it == lastBuddy) { KIS_SAFE_ASSERT_RECOVER(lastBuddy->type() == KisStroke::LODN) { lastStroke.clear(); lastBuddy.clear(); break; } buddyFound = true; break; } } if (!lastStroke) return UNDO_FAIL; if (!lastStroke->isEnded()) return UNDO_FAIL; if (lastStroke->isCancelled()) return UNDO_FAIL; KIS_SAFE_ASSERT_RECOVER_NOOP(!buddyFound || lastStroke->isCancelled() == lastBuddy->isCancelled()); KIS_SAFE_ASSERT_RECOVER_NOOP(lastBuddy->isEnded()); if (!lastStroke->canCancel()) { return UNDO_WAIT; } lastStroke->cancelStroke(); if (buddyFound && lastBuddy->canCancel()) { lastBuddy->cancelStroke(); } else { // TODO: assert that checks that there is no other lodn strokes - locker.unlock(); +// locker.unlock(); + l.unlock(); m_d->lodNUndoStore.undo(); m_d->lodNUndoStore.purgeRedoState(); - locker.relock(); + l.relock(); +// locker.relock(); } result = UNDO_OK; return result; } void KisStrokesQueue::Private::tryClearUndoOnStrokeCompletion(KisStrokeSP finishingStroke) { if (finishingStroke->type() != KisStroke::RESUME) return; bool hasResumeStrokes = false; bool hasLod0Strokes = false; Q_FOREACH (KisStrokeSP stroke, strokesQueue) { if (stroke == finishingStroke) continue; hasLod0Strokes |= stroke->type() == KisStroke::LOD0; hasResumeStrokes |= stroke->type() == KisStroke::RESUME; } KIS_SAFE_ASSERT_RECOVER_NOOP(!hasLod0Strokes || hasResumeStrokes); if (!hasResumeStrokes && !hasLod0Strokes) { lodNUndoStore.clear(); } } void KisStrokesQueue::processQueue(KisUpdaterContext &updaterContext, bool externalJobsPending) { updaterContext.lock(); - m_d->mutex.lock(); +// m_d->mutex.lock(); + m_d->m_rwLock.lockForWrite(); while(updaterContext.hasSpareThread() && processOneJob(updaterContext, externalJobsPending)); - m_d->mutex.unlock(); + m_d->m_rwLock.unlock(); +// m_d->mutex.unlock(); updaterContext.unlock(); } bool KisStrokesQueue::needsExclusiveAccess() const { return m_d->needsExclusiveAccess; } bool KisStrokesQueue::wrapAroundModeSupported() const { return m_d->wrapAroundModeSupported; } qreal KisStrokesQueue::balancingRatioOverride() const { return m_d->balancingRatioOverride; } bool KisStrokesQueue::isEmpty() const { - QMutexLocker locker(&m_d->mutex); +// QMutexLocker locker(&m_d->mutex); + QReadLocker l(&m_d->m_rwLock); return m_d->strokesQueue.isEmpty(); } qint32 KisStrokesQueue::sizeMetric() const { - QMutexLocker locker(&m_d->mutex); +// QMutexLocker locker(&m_d->mutex); + QReadLocker l(&m_d->m_rwLock); if(m_d->strokesQueue.isEmpty()) return 0; // just a rough approximation return qMax(1, m_d->strokesQueue.head()->numJobs()) * m_d->strokesQueue.size(); } void KisStrokesQueue::Private::switchDesiredLevelOfDetail(bool forced) { if (forced || nextDesiredLevelOfDetail != desiredLevelOfDetail) { Q_FOREACH (KisStrokeSP stroke, strokesQueue) { if (stroke->type() != KisStroke::LEGACY) return; } const bool forgettable = forced && !lodNNeedsSynchronization && desiredLevelOfDetail == nextDesiredLevelOfDetail; desiredLevelOfDetail = nextDesiredLevelOfDetail; lodNNeedsSynchronization |= !forgettable; if (desiredLevelOfDetail) { startLod0ToNStroke(desiredLevelOfDetail, forgettable); } } } void KisStrokesQueue::explicitRegenerateLevelOfDetail() { - QMutexLocker locker(&m_d->mutex); +// QMutexLocker locker(&m_d->mutex); + QWriteLocker l(&m_d->m_rwLock); m_d->switchDesiredLevelOfDetail(true); } void KisStrokesQueue::setDesiredLevelOfDetail(int lod) { - QMutexLocker locker(&m_d->mutex); +// QMutexLocker locker(&m_d->mutex); + QWriteLocker l(&m_d->m_rwLock); if (lod == m_d->nextDesiredLevelOfDetail) return; m_d->nextDesiredLevelOfDetail = lod; m_d->switchDesiredLevelOfDetail(false); } void KisStrokesQueue::notifyUFOChangedImage() { - QMutexLocker locker(&m_d->mutex); +// QMutexLocker locker(&m_d->mutex); + QWriteLocker l(&m_d->m_rwLock); m_d->lodNNeedsSynchronization = true; } void KisStrokesQueue::debugDumpAllStrokes() { - QMutexLocker locker(&m_d->mutex); +// QMutexLocker locker(&m_d->mutex); + QReadLocker l(&m_d->m_rwLock); dbgImage <<"==="; Q_FOREACH (KisStrokeSP stroke, m_d->strokesQueue) { dbgImage << ppVar(stroke->name()) << ppVar(stroke->type()) << ppVar(stroke->numJobs()) << ppVar(stroke->isInitialized()) << ppVar(stroke->isCancelled()); } dbgImage <<"==="; } void KisStrokesQueue::setLod0ToNStrokeStrategyFactory(const KisLodSyncStrokeStrategyFactory &factory) { m_d->lod0ToNStrokeStrategyFactory = factory; } void KisStrokesQueue::setSuspendUpdatesStrokeStrategyFactory(const KisSuspendResumeStrategyFactory &factory) { m_d->suspendUpdatesStrokeStrategyFactory = factory; } void KisStrokesQueue::setResumeUpdatesStrokeStrategyFactory(const KisSuspendResumeStrategyFactory &factory) { m_d->resumeUpdatesStrokeStrategyFactory = factory; } KisPostExecutionUndoAdapter *KisStrokesQueue::lodNPostExecutionUndoAdapter() const { return &m_d->lodNPostExecutionUndoAdapter; } KUndo2MagicString KisStrokesQueue::currentStrokeName() const { - QMutexLocker locker(&m_d->mutex); +// QMutexLocker locker(&m_d->mutex); + QReadLocker l(&m_d->m_rwLock); if(m_d->strokesQueue.isEmpty()) return KUndo2MagicString(); return m_d->strokesQueue.head()->name(); } bool KisStrokesQueue::hasOpenedStrokes() const { - QMutexLocker locker(&m_d->mutex); +// QMutexLocker locker(&m_d->mutex); + QReadLocker l(&m_d->m_rwLock); return m_d->openedStrokesCounter; } bool KisStrokesQueue::processOneJob(KisUpdaterContext &updaterContext, bool externalJobsPending) { if(m_d->strokesQueue.isEmpty()) return false; bool result = false; const int levelOfDetail = updaterContext.currentLevelOfDetail(); const KisUpdaterContextSnapshotEx snapshot = updaterContext.getContextSnapshotEx(); const bool hasStrokeJobs = !(snapshot == ContextEmpty || snapshot == HasMergeJob); const bool hasMergeJobs = snapshot & HasMergeJob; if(checkStrokeState(hasStrokeJobs, levelOfDetail) && checkExclusiveProperty(hasMergeJobs, hasStrokeJobs) && checkSequentialProperty(snapshot, externalJobsPending)) { KisStrokeSP stroke = m_d->strokesQueue.head(); updaterContext.addStrokeJob(stroke->popOneJob()); result = true; } return result; } bool KisStrokesQueue::checkStrokeState(bool hasStrokeJobsRunning, int runningLevelOfDetail) { KisStrokeSP stroke = m_d->strokesQueue.head(); bool result = false; /** * We cannot start/continue a stroke if its LOD differs from * the one that is running on CPU */ bool hasLodCompatibility = checkLevelOfDetailProperty(runningLevelOfDetail); bool hasJobs = stroke->hasJobs(); /** * The stroke may be cancelled very fast. In this case it will * end up in the state: * * !stroke->isInitialized() && stroke->isEnded() && !stroke->hasJobs() * * This means that !isInitialised() doesn't imply there are any * jobs present. */ if(!stroke->isInitialized() && hasJobs && hasLodCompatibility) { /** * It might happen that the stroke got initialized, but its job was not * started due to some other reasons like exclusivity. Therefore the * stroke might end up in loaded, but uninitialized state. */ if (!m_d->currentStrokeLoaded) { m_d->needsExclusiveAccess = stroke->isExclusive(); m_d->wrapAroundModeSupported = stroke->supportsWrapAroundMode(); m_d->balancingRatioOverride = stroke->balancingRatioOverride(); m_d->currentStrokeLoaded = true; } result = true; } else if(hasJobs && hasLodCompatibility) { /** * If the stroke has no initialization phase, then it can * arrive here unloaded. */ if (!m_d->currentStrokeLoaded) { m_d->needsExclusiveAccess = stroke->isExclusive(); m_d->wrapAroundModeSupported = stroke->supportsWrapAroundMode(); m_d->balancingRatioOverride = stroke->balancingRatioOverride(); m_d->currentStrokeLoaded = true; } result = true; } else if(stroke->isEnded() && !hasJobs && !hasStrokeJobsRunning) { m_d->tryClearUndoOnStrokeCompletion(stroke); m_d->strokesQueue.dequeue(); // deleted by shared pointer m_d->needsExclusiveAccess = false; m_d->wrapAroundModeSupported = false; m_d->balancingRatioOverride = -1.0; m_d->currentStrokeLoaded = false; m_d->switchDesiredLevelOfDetail(false); if(!m_d->strokesQueue.isEmpty()) { result = checkStrokeState(false, runningLevelOfDetail); } } return result; } bool KisStrokesQueue::checkExclusiveProperty(bool hasMergeJobs, bool hasStrokeJobs) { Q_UNUSED(hasStrokeJobs); if(!m_d->strokesQueue.head()->isExclusive()) return true; return hasMergeJobs == 0; } bool KisStrokesQueue::checkSequentialProperty(KisUpdaterContextSnapshotEx snapshot, bool externalJobsPending) { KisStrokeSP stroke = m_d->strokesQueue.head(); if (snapshot & HasSequentialJob || snapshot & HasBarrierJob) { return false; } KisStrokeJobData::Sequentiality nextSequentiality = stroke->nextJobSequentiality(); if (nextSequentiality == KisStrokeJobData::UNIQUELY_CONCURRENT && snapshot & HasUniquelyConcurrentJob) { return false; } if (nextSequentiality == KisStrokeJobData::SEQUENTIAL && (snapshot & HasUniquelyConcurrentJob || snapshot & HasConcurrentJob)) { return false; } if (nextSequentiality == KisStrokeJobData::BARRIER && (snapshot & HasUniquelyConcurrentJob || snapshot & HasConcurrentJob || snapshot & HasMergeJob || externalJobsPending)) { return false; } return true; } bool KisStrokesQueue::checkLevelOfDetailProperty(int runningLevelOfDetail) { KisStrokeSP stroke = m_d->strokesQueue.head(); return runningLevelOfDetail < 0 || stroke->worksOnLevelOfDetail() == runningLevelOfDetail; } diff --git a/libs/image/kis_update_job_item.h b/libs/image/kis_update_job_item.h index 01f7947545..38c67a3b1f 100644 --- a/libs/image/kis_update_job_item.h +++ b/libs/image/kis_update_job_item.h @@ -1,258 +1,258 @@ /* * Copyright (c) 2011 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. */ #ifndef __KIS_UPDATE_JOB_ITEM_H #define __KIS_UPDATE_JOB_ITEM_H #include #include #include #include "kis_stroke_job.h" #include "kis_spontaneous_job.h" #include "kis_base_rects_walker.h" #include "kis_async_merger.h" class KisUpdateJobItem : public QObject, public QRunnable { Q_OBJECT public: enum class Type : int { EMPTY = 0, WAITING, MERGE, STROKE, SPONTANEOUS }; public: KisUpdateJobItem(QReadWriteLock *exclusiveJobLock) : m_exclusiveJobLock(exclusiveJobLock), m_atomicType(Type::EMPTY), m_runnableJob(0) { setAutoDelete(false); KIS_SAFE_ASSERT_RECOVER_NOOP(m_atomicType.is_lock_free()); } ~KisUpdateJobItem() override { delete m_runnableJob; } void run() override { if (!isRunning()) return; /** * Here we break the idea of QThreadPool a bit. Ideally, we should split the * jobs into distinct QRunnable objects and pass all of them to QThreadPool. * That is a nice idea, but it doesn't work well when the jobs are small enough * and the number of available cores is high (>4 cores). It this case the * threads just tend to execute the job very quickly and go to sleep, which is * an expensive operation. * * To overcome this problem we try to bulk-process the jobs. In sigJobFinished() * signal (which is DirectConnection), the context may add the job to ourselves(!!!), * so we switch from "done" state into "running" again. */ while (1) { KIS_SAFE_ASSERT_RECOVER_RETURN(isRunning()); if(m_exclusive) { m_exclusiveJobLock->lockForWrite(); } else { m_exclusiveJobLock->lockForRead(); } if(m_atomicType == Type::MERGE) { runMergeJob(); } else { KIS_ASSERT(m_atomicType == Type::STROKE || m_atomicType == Type::SPONTANEOUS); m_runnableJob->run(); } setDone(); - emit sigDoSomeUsefulWork(); +// emit sigDoSomeUsefulWork(); // may flip the current state from Waiting -> Running again emit sigJobFinished(); m_exclusiveJobLock->unlock(); // try to exit the loop. Please note, that no one can flip the state from // WAITING to EMPTY except ourselves! Type expectedValue = Type::WAITING; if (m_atomicType.compare_exchange_strong(expectedValue, Type::EMPTY)) { break; } } } inline void runMergeJob() { KIS_SAFE_ASSERT_RECOVER_RETURN(m_atomicType == Type::MERGE); KIS_SAFE_ASSERT_RECOVER_RETURN(m_walker); // dbgKrita << "Executing merge job" << m_walker->changeRect() // << "on thread" << QThread::currentThreadId(); m_merger.startMerge(*m_walker); QRect changeRect = m_walker->changeRect(); emit sigContinueUpdate(changeRect); } // return true if the thread should actually be started inline bool setWalker(KisBaseRectsWalkerSP walker) { KIS_ASSERT(m_atomicType <= Type::WAITING); m_accessRect = walker->accessRect(); m_changeRect = walker->changeRect(); m_walker = walker; m_exclusive = false; m_runnableJob = 0; const Type oldState = m_atomicType.exchange(Type::MERGE); return oldState == Type::EMPTY; } // return true if the thread should actually be started inline bool setStrokeJob(KisStrokeJob *strokeJob) { KIS_ASSERT(m_atomicType <= Type::WAITING); m_runnableJob = strokeJob; m_strokeJobSequentiality = strokeJob->sequentiality(); m_exclusive = strokeJob->isExclusive(); m_walker = 0; m_accessRect = m_changeRect = QRect(); const Type oldState = m_atomicType.exchange(Type::STROKE); return oldState == Type::EMPTY; } // return true if the thread should actually be started inline bool setSpontaneousJob(KisSpontaneousJob *spontaneousJob) { KIS_ASSERT(m_atomicType <= Type::WAITING); m_runnableJob = spontaneousJob; m_exclusive = spontaneousJob->isExclusive(); m_walker = 0; m_accessRect = m_changeRect = QRect(); const Type oldState = m_atomicType.exchange(Type::SPONTANEOUS); return oldState == Type::EMPTY; } inline void setDone() { m_walker = 0; delete m_runnableJob; m_runnableJob = 0; m_atomicType = Type::WAITING; } inline bool isRunning() const { return m_atomicType >= Type::MERGE; } inline Type type() const { return m_atomicType; } inline const QRect& accessRect() const { return m_accessRect; } inline const QRect& changeRect() const { return m_changeRect; } inline KisStrokeJobData::Sequentiality strokeJobSequentiality() const { return m_strokeJobSequentiality; } Q_SIGNALS: void sigContinueUpdate(const QRect& rc); void sigDoSomeUsefulWork(); void sigJobFinished(); private: /** * Open walker and stroke job for the testing suite. * Please, do not use it in production code. */ friend class KisSimpleUpdateQueueTest; friend class KisStrokesQueueTest; friend class KisUpdateSchedulerTest; friend class KisTestableUpdaterContext; inline KisBaseRectsWalkerSP walker() const { return m_walker; } inline KisStrokeJob* strokeJob() const { KisStrokeJob *job = dynamic_cast(m_runnableJob); Q_ASSERT(job); return job; } inline void testingSetDone() { setDone(); } private: /** * \see KisUpdaterContext::m_exclusiveJobLock */ QReadWriteLock *m_exclusiveJobLock; bool m_exclusive; std::atomic m_atomicType; volatile KisStrokeJobData::Sequentiality m_strokeJobSequentiality; /** * Runnable jobs part * The job is owned by the context and deleted after completion */ KisRunnable *m_runnableJob; /** * Merge jobs part */ KisBaseRectsWalkerSP m_walker; KisAsyncMerger m_merger; /** * These rects cache actual values from the walker * to eliminate concurrent access to a walker structure */ QRect m_accessRect; QRect m_changeRect; }; #endif /* __KIS_UPDATE_JOB_ITEM_H */