File indexing completed on 2024-05-19 04:26:23
0001 /* 0002 * SPDX-FileCopyrightText: 2007 Boudewijn Rempt <boud@valdyas.org> 0003 * 0004 * SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "kis_node.h" 0008 0009 #include <QList> 0010 #include <QReadWriteLock> 0011 #include <QReadLocker> 0012 #include <QWriteLocker> 0013 #include <QPainterPath> 0014 #include <QRect> 0015 #include <QCoreApplication> 0016 0017 #include <KoProperties.h> 0018 0019 #include "kis_global.h" 0020 #include "kis_node_graph_listener.h" 0021 #include "kis_node_visitor.h" 0022 #include "kis_processing_visitor.h" 0023 #include "kis_node_progress_proxy.h" 0024 #include "kis_busy_progress_indicator.h" 0025 #include "KisFrameChangeUpdateRecipe.h" 0026 0027 #include "kis_clone_layer.h" 0028 0029 #include "kis_time_span.h" 0030 0031 #include "kis_safe_read_list.h" 0032 typedef KisSafeReadList<KisNodeSP> KisSafeReadNodeList; 0033 0034 #include "kis_abstract_projection_plane.h" 0035 #include "kis_projection_leaf.h" 0036 #include "kis_undo_adapter.h" 0037 #include "kis_keyframe_channel.h" 0038 #include "kis_image.h" 0039 #include "kis_layer_utils.h" 0040 #include "KisRegion.h" 0041 #include <KisStaticInitializer.h> 0042 0043 /** 0044 * The link between KisProjection and KisImageUpdater 0045 * uses queued signals with an argument of KisNodeSP type, 0046 * so we should register it beforehand 0047 */ 0048 KIS_DECLARE_STATIC_INITIALIZER { 0049 qRegisterMetaType<KisNodeSP>("KisNodeSP"); 0050 qRegisterMetaType<KisNodeList>("KisNodeList"); 0051 } 0052 0053 0054 /** 0055 * Note about "thread safety" of KisNode 0056 * 0057 * 1) One can *read* any information about node and node graph in any 0058 * number of threads concurrently. This operation is safe because 0059 * of the usage of KisSafeReadNodeList and will run concurrently 0060 * (lock-free). 0061 * 0062 * 2) One can *write* any information into the node or node graph in a 0063 * single thread only! Changing the graph concurrently is *not* 0064 * sane and therefore not supported. 0065 * 0066 * 3) One can *read and write* information about the node graph 0067 * concurrently, given that there is only *one* writer thread and 0068 * any number of reader threads. Please note that in this case the 0069 * node's code is just guaranteed *not to crash*, which is ensured 0070 * by nodeSubgraphLock. You need to ensure the sanity of the data 0071 * read by the reader threads yourself! 0072 */ 0073 0074 struct Q_DECL_HIDDEN KisNode::Private 0075 { 0076 public: 0077 Private(KisNode *node) 0078 : graphListener(0) 0079 , nodeProgressProxy(0) 0080 , busyProgressIndicator(0) 0081 , projectionLeaf(new KisProjectionLeaf(node)) 0082 { 0083 } 0084 0085 KisNodeWSP parent; 0086 KisNodeGraphListener *graphListener; 0087 KisSafeReadNodeList nodes; 0088 KisNodeProgressProxy *nodeProgressProxy; 0089 KisBusyProgressIndicator *busyProgressIndicator; 0090 QReadWriteLock nodeSubgraphLock; 0091 0092 KisProjectionLeafSP projectionLeaf; 0093 0094 const KisNode* findSymmetricClone(const KisNode *srcRoot, 0095 const KisNode *dstRoot, 0096 const KisNode *srcTarget); 0097 void processDuplicatedClones(const KisNode *srcDuplicationRoot, 0098 const KisNode *dstDuplicationRoot, 0099 KisNode *node); 0100 0101 std::optional<KisFrameChangeUpdateRecipe> frameRemovalUpdateRecipe; 0102 KisFrameChangeUpdateRecipe handleKeyframeChannelUpdateImpl(const KisKeyframeChannel *channel, int time); 0103 }; 0104 0105 /** 0106 * Finds the layer in \p dstRoot subtree, which has the same path as 0107 * \p srcTarget has in \p srcRoot 0108 */ 0109 const KisNode* KisNode::Private::findSymmetricClone(const KisNode *srcRoot, 0110 const KisNode *dstRoot, 0111 const KisNode *srcTarget) 0112 { 0113 if (srcRoot == srcTarget) return dstRoot; 0114 0115 KisSafeReadNodeList::const_iterator srcIter = srcRoot->m_d->nodes.constBegin(); 0116 KisSafeReadNodeList::const_iterator dstIter = dstRoot->m_d->nodes.constBegin(); 0117 0118 for (; srcIter != srcRoot->m_d->nodes.constEnd(); srcIter++, dstIter++) { 0119 0120 KIS_ASSERT_RECOVER_RETURN_VALUE((srcIter != srcRoot->m_d->nodes.constEnd()) == 0121 (dstIter != dstRoot->m_d->nodes.constEnd()), 0); 0122 0123 const KisNode *node = findSymmetricClone(srcIter->data(), dstIter->data(), srcTarget); 0124 if (node) return node; 0125 0126 } 0127 0128 return 0; 0129 } 0130 0131 /** 0132 * This function walks through a subtrees of old and new layers and 0133 * searches for clone layers. For each clone layer it checks whether 0134 * its copyFrom() lays inside the old subtree, and if it is so resets 0135 * it to the corresponding layer in the new subtree. 0136 * 0137 * That is needed when the user duplicates a group layer with all its 0138 * layer subtree. In such a case all the "internal" clones must stay 0139 * "internal" and not point to the layers of the older group. 0140 */ 0141 void KisNode::Private::processDuplicatedClones(const KisNode *srcDuplicationRoot, 0142 const KisNode *dstDuplicationRoot, 0143 KisNode *node) 0144 { 0145 if (KisCloneLayer *clone = dynamic_cast<KisCloneLayer*>(node)) { 0146 KIS_ASSERT_RECOVER_RETURN(clone->copyFrom()); 0147 const KisNode *newCopyFrom = findSymmetricClone(srcDuplicationRoot, 0148 dstDuplicationRoot, 0149 clone->copyFrom()); 0150 0151 if (newCopyFrom) { 0152 KisLayer *newCopyFromLayer = qobject_cast<KisLayer*>(const_cast<KisNode*>(newCopyFrom)); 0153 KIS_ASSERT_RECOVER_RETURN(newCopyFromLayer); 0154 0155 clone->setCopyFrom(newCopyFromLayer); 0156 } 0157 } 0158 0159 KisSafeReadNodeList::const_iterator iter; 0160 FOREACH_SAFE(iter, node->m_d->nodes) { 0161 KisNode *child = const_cast<KisNode*>((*iter).data()); 0162 processDuplicatedClones(srcDuplicationRoot, dstDuplicationRoot, child); 0163 } 0164 } 0165 0166 KisNode::KisNode(KisImageWSP image) 0167 : KisBaseNode(image), 0168 m_d(new Private(this)) 0169 { 0170 m_d->parent = 0; 0171 m_d->graphListener = 0; 0172 moveToThread(qApp->thread()); 0173 } 0174 0175 KisNode::KisNode(const KisNode & rhs) 0176 : KisBaseNode(rhs) 0177 , m_d(new Private(this)) 0178 { 0179 m_d->parent = 0; 0180 m_d->graphListener = 0; 0181 moveToThread(qApp->thread()); 0182 0183 // NOTE: the nodes are not supposed to be added/removed while 0184 // creation of another node, so we do *no* locking here! 0185 0186 KisSafeReadNodeList::const_iterator iter; 0187 FOREACH_SAFE(iter, rhs.m_d->nodes) { 0188 KisNodeSP child = (*iter)->clone(); 0189 child->createNodeProgressProxy(); 0190 m_d->nodes.append(child); 0191 child->setParent(this); 0192 } 0193 0194 m_d->processDuplicatedClones(&rhs, this, this); 0195 } 0196 0197 KisNode::~KisNode() 0198 { 0199 if (m_d->busyProgressIndicator) { 0200 m_d->busyProgressIndicator->prepareDestroying(); 0201 m_d->busyProgressIndicator->deleteLater(); 0202 } 0203 0204 if (m_d->nodeProgressProxy) { 0205 m_d->nodeProgressProxy->prepareDestroying(); 0206 m_d->nodeProgressProxy->deleteLater(); 0207 } 0208 0209 { 0210 QWriteLocker l(&m_d->nodeSubgraphLock); 0211 m_d->nodes.clear(); 0212 } 0213 0214 delete m_d; 0215 } 0216 0217 QRect KisNode::needRect(const QRect &rect, PositionToFilthy pos) const 0218 { 0219 Q_UNUSED(pos); 0220 return rect; 0221 } 0222 0223 QRect KisNode::changeRect(const QRect &rect, PositionToFilthy pos) const 0224 { 0225 Q_UNUSED(pos); 0226 return rect; 0227 } 0228 0229 QRect KisNode::accessRect(const QRect &rect, PositionToFilthy pos) const 0230 { 0231 Q_UNUSED(pos); 0232 return rect; 0233 } 0234 0235 void KisNode::childNodeChanged(KisNodeSP /*changedChildNode*/) 0236 { 0237 } 0238 0239 KisAbstractProjectionPlaneSP KisNode::projectionPlane() const 0240 { 0241 KIS_ASSERT_RECOVER_NOOP(0 && "KisNode::projectionPlane() is not defined!"); 0242 static KisAbstractProjectionPlaneSP plane = 0243 toQShared(new KisDumbProjectionPlane()); 0244 0245 return plane; 0246 } 0247 0248 KisProjectionLeafSP KisNode::projectionLeaf() const 0249 { 0250 return m_d->projectionLeaf; 0251 } 0252 0253 void KisNode::setImage(KisImageWSP newImage) 0254 { 0255 KisBaseNode::setImage(newImage); 0256 0257 KisNodeSP node = firstChild(); 0258 while (node) { 0259 KisLayerUtils::recursiveApplyNodes(node, 0260 [newImage] (KisNodeSP node) { 0261 node->setImage(newImage); 0262 }); 0263 0264 node = node->nextSibling(); 0265 } 0266 } 0267 0268 bool KisNode::accept(KisNodeVisitor &v) 0269 { 0270 return v.visit(this); 0271 } 0272 0273 void KisNode::accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter) 0274 { 0275 visitor.visit(this, undoAdapter); 0276 } 0277 0278 int KisNode::graphSequenceNumber() const 0279 { 0280 return m_d->graphListener ? m_d->graphListener->graphSequenceNumber() : -1; 0281 } 0282 0283 KisNodeGraphListener *KisNode::graphListener() const 0284 { 0285 return m_d->graphListener; 0286 } 0287 0288 void KisNode::setGraphListener(KisNodeGraphListener *graphListener) 0289 { 0290 m_d->graphListener = graphListener; 0291 0292 QReadLocker l(&m_d->nodeSubgraphLock); 0293 KisSafeReadNodeList::const_iterator iter; 0294 FOREACH_SAFE(iter, m_d->nodes) { 0295 KisNodeSP child = (*iter); 0296 child->setGraphListener(graphListener); 0297 } 0298 } 0299 0300 void KisNode::setParent(KisNodeWSP parent) 0301 { 0302 QWriteLocker l(&m_d->nodeSubgraphLock); 0303 m_d->parent = parent; 0304 } 0305 0306 KisNodeSP KisNode::parent() const 0307 { 0308 QReadLocker l(&m_d->nodeSubgraphLock); 0309 return m_d->parent.isValid() ? KisNodeSP(m_d->parent) : KisNodeSP(); 0310 } 0311 0312 KisBaseNodeSP KisNode::parentCallback() const 0313 { 0314 return parent(); 0315 } 0316 0317 void KisNode::notifyParentVisibilityChanged(bool value) 0318 { 0319 QReadLocker l(&m_d->nodeSubgraphLock); 0320 0321 KisSafeReadNodeList::const_iterator iter; 0322 FOREACH_SAFE(iter, m_d->nodes) { 0323 KisNodeSP child = (*iter); 0324 child->notifyParentVisibilityChanged(value); 0325 } 0326 } 0327 0328 void KisNode::baseNodeChangedCallback() 0329 { 0330 if(m_d->graphListener) { 0331 m_d->graphListener->nodeChanged(this); 0332 emit sigNodeChangedInternal(); 0333 } 0334 } 0335 0336 void KisNode::baseNodeInvalidateAllFramesCallback() 0337 { 0338 if(m_d->graphListener) { 0339 m_d->graphListener->invalidateAllFrames(); 0340 } 0341 } 0342 0343 void KisNode::baseNodeCollapsedChangedCallback() 0344 { 0345 if(m_d->graphListener) { 0346 m_d->graphListener->nodeCollapsedChanged(this); 0347 } 0348 } 0349 0350 void KisNode::addKeyframeChannel(KisKeyframeChannel *channel) 0351 { 0352 channel->setNode(this); 0353 KisBaseNode::addKeyframeChannel(channel); 0354 0355 if (m_d->graphListener) { 0356 m_d->graphListener->keyframeChannelHasBeenAdded(this, channel); 0357 } 0358 } 0359 0360 KisNodeSP KisNode::firstChild() const 0361 { 0362 QReadLocker l(&m_d->nodeSubgraphLock); 0363 return !m_d->nodes.isEmpty() ? m_d->nodes.first() : 0; 0364 } 0365 0366 KisNodeSP KisNode::lastChild() const 0367 { 0368 QReadLocker l(&m_d->nodeSubgraphLock); 0369 return !m_d->nodes.isEmpty() ? m_d->nodes.last() : 0; 0370 } 0371 0372 KisNodeSP KisNode::prevChildImpl(KisNodeSP child) 0373 { 0374 /** 0375 * Warning: mind locking policy! 0376 * 0377 * The graph locks must be *always* taken in descending 0378 * order. That is if you want to (or it implicitly happens that 0379 * you) take a lock of a parent and a chil, you must first take 0380 * the lock of a parent, and only after that ask a child to do the 0381 * same. Otherwise you'll get a deadlock. 0382 */ 0383 0384 QReadLocker l(&m_d->nodeSubgraphLock); 0385 0386 int i = m_d->nodes.indexOf(child) - 1; 0387 return i >= 0 ? m_d->nodes.at(i) : 0; 0388 } 0389 0390 KisNodeSP KisNode::nextChildImpl(KisNodeSP child) 0391 { 0392 /** 0393 * See a comment in KisNode::prevChildImpl() 0394 */ 0395 QReadLocker l(&m_d->nodeSubgraphLock); 0396 0397 int i = m_d->nodes.indexOf(child) + 1; 0398 return i > 0 && i < m_d->nodes.size() ? m_d->nodes.at(i) : 0; 0399 } 0400 0401 KisNodeSP KisNode::prevSibling() const 0402 { 0403 KisNodeSP parentNode = parent(); 0404 return parentNode ? parentNode->prevChildImpl(const_cast<KisNode*>(this)) : 0; 0405 } 0406 0407 KisNodeSP KisNode::nextSibling() const 0408 { 0409 KisNodeSP parentNode = parent(); 0410 return parentNode ? parentNode->nextChildImpl(const_cast<KisNode*>(this)) : 0; 0411 } 0412 0413 quint32 KisNode::childCount() const 0414 { 0415 QReadLocker l(&m_d->nodeSubgraphLock); 0416 return m_d->nodes.size(); 0417 } 0418 0419 0420 KisNodeSP KisNode::at(quint32 index) const 0421 { 0422 QReadLocker l(&m_d->nodeSubgraphLock); 0423 0424 if (!m_d->nodes.isEmpty() && index < (quint32)m_d->nodes.size()) { 0425 return m_d->nodes.at(index); 0426 } 0427 0428 return 0; 0429 } 0430 0431 int KisNode::index(const KisNodeSP node) const 0432 { 0433 QReadLocker l(&m_d->nodeSubgraphLock); 0434 0435 return m_d->nodes.indexOf(node); 0436 } 0437 0438 QList<KisNodeSP> KisNode::childNodes(const QStringList & nodeTypes, const KoProperties & properties) const 0439 { 0440 QReadLocker l(&m_d->nodeSubgraphLock); 0441 0442 QList<KisNodeSP> nodes; 0443 0444 KisSafeReadNodeList::const_iterator iter; 0445 FOREACH_SAFE(iter, m_d->nodes) { 0446 if (*iter) { 0447 if (properties.isEmpty() || (*iter)->check(properties)) { 0448 bool rightType = true; 0449 0450 if(!nodeTypes.isEmpty()) { 0451 rightType = false; 0452 Q_FOREACH (const QString &nodeType, nodeTypes) { 0453 if ((*iter)->inherits(nodeType.toLatin1())) { 0454 rightType = true; 0455 break; 0456 } 0457 } 0458 } 0459 if (rightType) { 0460 nodes.append(*iter); 0461 } 0462 } 0463 } 0464 } 0465 return nodes; 0466 } 0467 0468 bool KisNode::add(KisNodeSP newNode, KisNodeSP aboveThis) 0469 { 0470 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(newNode, false); 0471 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!aboveThis || aboveThis->parent().data() == this, false); 0472 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(allowAsChild(newNode), false); 0473 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!newNode->parent(), false); 0474 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(index(newNode) < 0, false); 0475 0476 int idx = aboveThis ? this->index(aboveThis) + 1 : 0; 0477 0478 // theoretical race condition may happen here ('idx' may become 0479 // deprecated until the write lock will be held). But we ignore 0480 // it, because it is not supported to add/remove nodes from two 0481 // concurrent threads simultaneously 0482 0483 if (m_d->graphListener) { 0484 m_d->graphListener->aboutToAddANode(this, idx); 0485 } 0486 0487 { 0488 QWriteLocker l(&m_d->nodeSubgraphLock); 0489 0490 newNode->createNodeProgressProxy(); 0491 0492 m_d->nodes.insert(idx, newNode); 0493 0494 newNode->setParent(this); 0495 newNode->setGraphListener(m_d->graphListener); 0496 } 0497 0498 newNode->setImage(image()); 0499 0500 if (m_d->graphListener) { 0501 m_d->graphListener->nodeHasBeenAdded(this, idx); 0502 } 0503 0504 childNodeChanged(newNode); 0505 0506 return true; 0507 } 0508 0509 bool KisNode::remove(quint32 index) 0510 { 0511 if (index < childCount()) { 0512 KisNodeSP removedNode = at(index); 0513 0514 if (m_d->graphListener) { 0515 m_d->graphListener->aboutToRemoveANode(this, index); 0516 } 0517 0518 removedNode->setImage(0); 0519 0520 { 0521 QWriteLocker l(&m_d->nodeSubgraphLock); 0522 0523 removedNode->setGraphListener(0); 0524 0525 removedNode->setParent(0); // after calling aboutToRemoveANode or then the model get broken according to TT's modeltest 0526 m_d->nodes.removeAt(index); 0527 } 0528 0529 if (m_d->graphListener) { 0530 m_d->graphListener->nodeHasBeenRemoved(this, index); 0531 } 0532 0533 childNodeChanged(removedNode); 0534 0535 return true; 0536 } 0537 return false; 0538 } 0539 0540 bool KisNode::remove(KisNodeSP node) 0541 { 0542 return node->parent().data() == this ? remove(index(node)) : false; 0543 } 0544 0545 KisNodeProgressProxy* KisNode::nodeProgressProxy() const 0546 { 0547 if (m_d->nodeProgressProxy) { 0548 return m_d->nodeProgressProxy; 0549 } else if (parent()) { 0550 return parent()->nodeProgressProxy(); 0551 } 0552 return 0; 0553 } 0554 0555 KisBusyProgressIndicator* KisNode::busyProgressIndicator() const 0556 { 0557 if (m_d->busyProgressIndicator) { 0558 return m_d->busyProgressIndicator; 0559 } else if (parent()) { 0560 return parent()->busyProgressIndicator(); 0561 } 0562 return 0; 0563 } 0564 0565 void KisNode::createNodeProgressProxy() 0566 { 0567 if (!m_d->nodeProgressProxy) { 0568 m_d->nodeProgressProxy = new KisNodeProgressProxy(this); 0569 m_d->busyProgressIndicator = new KisBusyProgressIndicator(m_d->nodeProgressProxy); 0570 0571 m_d->nodeProgressProxy->moveToThread(this->thread()); 0572 m_d->busyProgressIndicator->moveToThread(this->thread()); 0573 } 0574 } 0575 0576 void KisNode::setDirty() 0577 { 0578 setDirty(extent()); 0579 } 0580 0581 void KisNode::setDirty(const QVector<QRect> &rects) 0582 { 0583 if(m_d->graphListener) { 0584 m_d->graphListener->requestProjectionUpdate(this, rects, true); 0585 } 0586 } 0587 0588 void KisNode::setDirty(const KisRegion ®ion) 0589 { 0590 setDirty(region.rects()); 0591 } 0592 0593 void KisNode::setDirty(const QRect & rect) 0594 { 0595 setDirty(QVector<QRect>({rect})); 0596 } 0597 0598 void KisNode::setDirtyDontResetAnimationCache() 0599 { 0600 setDirtyDontResetAnimationCache(QVector<QRect>({extent()})); 0601 } 0602 0603 void KisNode::setDirtyDontResetAnimationCache(const QRect &rect) 0604 { 0605 setDirtyDontResetAnimationCache(QVector<QRect>({rect})); 0606 } 0607 0608 void KisNode::setDirtyDontResetAnimationCache(const QVector<QRect> &rects) 0609 { 0610 if(m_d->graphListener) { 0611 m_d->graphListener->requestProjectionUpdate(this, rects, false); 0612 } 0613 } 0614 0615 void KisNode::invalidateFrames(const KisTimeSpan &range, const QRect &rect) 0616 { 0617 if(m_d->graphListener) { 0618 m_d->graphListener->invalidateFrames(range, rect); 0619 } 0620 } 0621 0622 KisFrameChangeUpdateRecipe 0623 KisNode::Private::handleKeyframeChannelUpdateImpl(const KisKeyframeChannel *channel, int time) 0624 { 0625 KisFrameChangeUpdateRecipe recipe; 0626 0627 recipe.affectedRange = channel->affectedFrames(time); 0628 recipe.affectedRect = channel->affectedRect(time); 0629 0630 if (parent->image()) { 0631 KisDefaultBoundsSP bounds(new KisDefaultBounds(parent->image())); 0632 0633 if (recipe.affectedRange.contains(bounds->currentTime())) { 0634 recipe.totalDirtyRect = recipe.affectedRect; 0635 } 0636 } 0637 0638 return recipe; 0639 } 0640 0641 void KisNode::handleKeyframeChannelFrameChange(const KisKeyframeChannel *channel, int time) 0642 { 0643 m_d->handleKeyframeChannelUpdateImpl(channel, time).notify(this); 0644 } 0645 0646 void KisNode::handleKeyframeChannelFrameAdded(const KisKeyframeChannel *channel, int time) 0647 { 0648 m_d->handleKeyframeChannelUpdateImpl(channel, time).notify(this); 0649 } 0650 0651 KisFrameChangeUpdateRecipe KisNode::handleKeyframeChannelFrameAboutToBeRemovedImpl(const KisKeyframeChannel *channel, int time) 0652 { 0653 return m_d->handleKeyframeChannelUpdateImpl(channel, time); 0654 } 0655 0656 void KisNode::handleKeyframeChannelFrameAboutToBeRemoved(const KisKeyframeChannel *channel, int time) 0657 { 0658 KIS_SAFE_ASSERT_RECOVER_NOOP(!m_d->frameRemovalUpdateRecipe); 0659 m_d->frameRemovalUpdateRecipe = handleKeyframeChannelFrameAboutToBeRemovedImpl(channel, time); 0660 } 0661 0662 void KisNode::handleKeyframeChannelFrameHasBeenRemoved(const KisKeyframeChannel *channel, int time) 0663 { 0664 KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->frameRemovalUpdateRecipe); 0665 m_d->frameRemovalUpdateRecipe->notify(this); 0666 m_d->frameRemovalUpdateRecipe = std::nullopt; 0667 } 0668 0669 void KisNode::requestTimeSwitch(int time) 0670 { 0671 if(m_d->graphListener) { 0672 m_d->graphListener->requestTimeSwitch(time); 0673 } 0674 } 0675 0676 void KisNode::syncLodCache() 0677 { 0678 // noop. everything is done by getLodCapableDevices() 0679 } 0680 0681 KisPaintDeviceList KisNode::getLodCapableDevices() const 0682 { 0683 KisPaintDeviceList list; 0684 0685 KisPaintDeviceSP device = paintDevice(); 0686 if (device) { 0687 list << device; 0688 } 0689 0690 KisPaintDeviceSP originalDevice = original(); 0691 if (originalDevice && originalDevice != device) { 0692 list << originalDevice; 0693 } 0694 0695 list << projectionPlane()->getLodCapableDevices(); 0696 0697 return list; 0698 }