Commit 0bb48aac authored by Dmitry Kazakov's avatar Dmitry Kazakov

Fixed updates of clones

1) The KisBaseRectsWalker now saves notifications for the clones.
   It does this by calculating uncropped changeRect of the source
   layer and storing the node/rect pairs in a special array.
2) KisAsyncMerger takes the contents of this array and notifies the
   clones after the source layer's update is finished
3) If clone needs an area of the source layer that was not generated
   during normal update (it is placed outside the image), it calculates
   its contents in KisUpdateOriginalVisitor by recursively calling to
   KisRefreshSubtreeWalker/KisAsyncMerger.
parent 5e21afd8
......@@ -45,6 +45,7 @@
#include "kis_merge_walker.h"
#include "kis_refresh_subtree_walker.h"
//#define DEBUG_MERGER
......@@ -60,8 +61,10 @@
class KisUpdateOriginalVisitor : public KisNodeVisitor
{
public:
KisUpdateOriginalVisitor(QRect updateRect, KisPaintDeviceSP projection)
: m_updateRect(updateRect), m_projection(projection)
KisUpdateOriginalVisitor(QRect updateRect, KisPaintDeviceSP projection, QRect cropRect)
: m_updateRect(updateRect),
m_cropRect(cropRect),
m_projection(projection)
{
}
......@@ -127,7 +130,22 @@ public:
return true;
}
bool visit(KisCloneLayer*) {
bool visit(KisCloneLayer *layer) {
QRect emptyRect;
KisRefreshSubtreeWalker walker(emptyRect);
KisAsyncMerger merger;
KisLayerSP srcLayer = layer->copyFrom();
QRect srcRect = m_updateRect.translated(-layer->x(), -layer->y());
QRegion prepareRegion(srcRect);
prepareRegion -= m_cropRect;
foreach(const QRect &rect, prepareRegion.rects()) {
walker.collectRects(srcLayer, rect);
merger.startMerge(walker, false);
}
return true;
}
......@@ -146,6 +164,7 @@ public:
private:
QRect m_updateRect;
QRect m_cropRect;
KisPaintDeviceSP m_projection;
};
......@@ -157,7 +176,7 @@ private:
/**
* FIXME: Check node<->layer transitions
*/
void KisAsyncMerger::startMerge(KisBaseRectsWalker &walker) {
void KisAsyncMerger::startMerge(KisBaseRectsWalker &walker, bool notifyClones) {
KisMergeWalker::NodeStack &nodeStack = walker.nodeStack();
const bool useTempProjections = walker.needRectVaries();
......@@ -177,7 +196,8 @@ void KisAsyncMerger::startMerge(KisBaseRectsWalker &walker) {
DEBUG_NODE_ACTION("Updating", "N_EXTRA", currentNode, applyRect);
KisUpdateOriginalVisitor originalVisitor(applyRect,
m_currentProjection);
m_currentProjection,
walker.cropRect());
currentNode->accept(originalVisitor);
currentNode->updateProjection(applyRect);
......@@ -189,7 +209,8 @@ void KisAsyncMerger::startMerge(KisBaseRectsWalker &walker) {
setupProjection(currentNode, applyRect, useTempProjections);
KisUpdateOriginalVisitor originalVisitor(applyRect,
m_currentProjection);
m_currentProjection,
walker.cropRect());
if(item.m_position & KisMergeWalker::N_FILTHY) {
DEBUG_NODE_ACTION("Updating", "N_FILTHY", currentNode, applyRect);
......@@ -198,9 +219,10 @@ void KisAsyncMerger::startMerge(KisBaseRectsWalker &walker) {
}
else if(item.m_position & KisMergeWalker::N_ABOVE_FILTHY) {
DEBUG_NODE_ACTION("Updating", "N_ABOVE_FILTHY", currentNode, applyRect);
currentNode->accept(originalVisitor);
if(dependOnLowerNodes(currentNode))
if(dependOnLowerNodes(currentNode)) {
currentNode->accept(originalVisitor);
currentNode->updateProjection(applyRect);
}
}
else if(item.m_position & KisMergeWalker::N_FILTHY_PROJECTION) {
DEBUG_NODE_ACTION("Updating", "N_FILTHY_PROJECTION", currentNode, applyRect);
......@@ -218,6 +240,10 @@ void KisAsyncMerger::startMerge(KisBaseRectsWalker &walker) {
resetProjection();
}
}
if(notifyClones) {
doNotifyClones(walker);
}
}
bool KisAsyncMerger::isRootNode(KisNodeSP node) {
......@@ -326,3 +352,14 @@ bool KisAsyncMerger::compositeWithProjection(KisLayerSP layer, const QRect &rect
DEBUG_NODE_ACTION("Compositing projection", "", layer, needRect);
return true;
}
void KisAsyncMerger::doNotifyClones(KisBaseRectsWalker &walker) {
KisBaseRectsWalker::CloneNotificationsVector &vector =
walker.cloneNotifications();
KisBaseRectsWalker::CloneNotificationsVector::iterator it;
for(it = vector.begin(); it != vector.end(); ++it) {
(*it).notify();
}
}
......@@ -29,7 +29,7 @@ class KisBaseRectsWalker;
class KRITAIMAGE_EXPORT KisAsyncMerger
{
public:
void startMerge(KisBaseRectsWalker &walker);
void startMerge(KisBaseRectsWalker &walker, bool notifyClones = true);
private:
static inline bool isRootNode(KisNodeSP node);
......@@ -39,6 +39,7 @@ private:
inline void setupProjection(KisNodeSP currentNode, const QRect& rect, bool useTempProjection);
inline void writeProjection(KisNodeSP topmostNode, bool useTempProjection, QRect rect);
inline bool compositeWithProjection(KisLayerSP layer, const QRect &rect);
inline void doNotifyClones(KisBaseRectsWalker &walker);
private:
/**
......
......@@ -32,7 +32,8 @@ class KRITAIMAGE_EXPORT KisBaseRectsWalker : public KisShared
public:
enum UpdateType {
UPDATE,
FULL_REFRESH
FULL_REFRESH,
UNSUPPORTED
};
......@@ -59,6 +60,26 @@ public:
#define GRAPH_POSITION_MASK 0x07
#define POSITION_TO_FILTHY_MASK 0xF8
struct CloneNotification {
CloneNotification() {}
CloneNotification(KisNodeSP node, const QRect &dirtyRect)
: m_layer(qobject_cast<KisLayer*>(node.data())),
m_dirtyRect(dirtyRect) {}
void notify() {
Q_ASSERT(m_layer); // clones are possible for layers only
m_layer->updateClones(m_dirtyRect);
}
private:
friend class KisWalkersTest;
KisLayerSP m_layer;
QRect m_dirtyRect;
};
typedef QVector<CloneNotification> CloneNotificationsVector;
struct JobItem {
KisNodeSP m_node;
NodePosition m_position;
......@@ -82,6 +103,7 @@ public:
clear();
m_nodeChecksum = calculateChecksum(node, requestedRect);
m_resultChangeRect = requestedRect;
m_resultUncroppedChangeRect = requestedRect;
m_requestedRect = requestedRect;
m_startNode = node;
startTrip(node);
......@@ -110,6 +132,11 @@ public:
return m_mergeTask;
}
// return a reference for efficiency reasons
inline CloneNotificationsVector& cloneNotifications() {
return m_cloneNotifications;
}
inline const QRect& accessRect() const {
return m_resultAccessRect;
}
......@@ -118,6 +145,10 @@ public:
return m_resultChangeRect;
}
inline const QRect& uncroppedChangeRect() const {
return m_resultUncroppedChangeRect;
}
inline bool needRectVaries() const {
return m_needRectVaries;
}
......@@ -165,12 +196,18 @@ protected:
return qobject_cast<KisMask*>(node.data());
}
static inline bool hasClones(KisNodeSP node) {
KisLayer *layer = qobject_cast<KisLayer*>(node.data());
return layer && layer->hasClones();
}
inline void clear() {
m_resultAccessRect = m_resultNeedRect = /*m_resultChangeRect =*/
m_childNeedRect = m_lastNeedRect = QRect();
m_needRectVaries = m_changeRectVaries = false;
m_mergeTask.clear();
m_cloneNotifications.clear();
// Not needed really. Think over removing.
//m_startNode = 0;
......@@ -189,9 +226,11 @@ protected:
/**
* Used by KisFullRefreshWalker as it has a special changeRect strategy
*/
inline void setExplicitChangeRect(const QRect &changeRect, bool changeRectVaries) {
inline void setExplicitChangeRect(KisNodeSP node, const QRect &changeRect, bool changeRectVaries) {
m_resultChangeRect = changeRect;
m_resultUncroppedChangeRect = changeRect;
m_changeRectVaries = changeRectVaries;
registerCloneNotification(node, N_FILTHY);
}
/**
......@@ -209,6 +248,25 @@ protected:
m_changeRectVaries = currentChangeRect != m_resultChangeRect;
m_resultChangeRect = currentChangeRect;
m_resultUncroppedChangeRect = node->changeRect(m_resultUncroppedChangeRect,
getPositionToFilthy(position));
registerCloneNotification(node, position);
}
void registerCloneNotification(KisNodeSP node, NodePosition position) {
/**
* Note, we do not check for (N_ABOVE_FILTHY &&
* dependOnLowerNodes(node)) because it may lead to an
* infinite loop with filter layer. Activate it when it is
* guaranteed that it is not possible to create a filter layer
* avobe its own clone
*/
if(hasClones(node) && position & (N_FILTHY | N_FILTHY_PROJECTION)) {
m_cloneNotifications.append(
CloneNotification(node, m_resultUncroppedChangeRect));
}
}
/**
......@@ -310,9 +368,11 @@ private:
QRect m_resultAccessRect;
QRect m_resultNeedRect;
QRect m_resultChangeRect;
QRect m_resultUncroppedChangeRect;
bool m_needRectVaries;
bool m_changeRectVaries;
NodeStack m_mergeTask;
CloneNotificationsVector m_cloneNotifications;
/**
* Used by update optimization framework
......
......@@ -99,6 +99,10 @@ public:
return m_clonesList;
}
bool hasClones() const {
return !m_clonesList.isEmpty();
}
private:
QList<KisCloneLayerWSP> m_clonesList;
};
......@@ -237,12 +241,6 @@ void KisLayer::setImage(KisImageWSP image)
}
}
void KisLayer::setDirty(const QRect & rect)
{
KisNode::setDirty(rect);
m_d->clonesList.setDirty(rect);
}
void KisLayer::registerClone(KisCloneLayerWSP clone)
{
m_d->clonesList.addClone(clone);
......@@ -258,6 +256,16 @@ const QList<KisCloneLayerWSP> 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);
}
KisSelectionMaskSP KisLayer::selectionMask() const
{
KoProperties properties;
......
......@@ -184,6 +184,20 @@ public:
*/
const QList<KisCloneLayerWSP> registeredClones() const;
/**
* Returns whether we have a clone.
*
* Be careful with it. It is not thread safe to add/remove
* clone while checking hasClones(). So there should be no updates.
*/
bool hasClones() const;
/**
* It is calles by the async merger after projection update is done
*/
void updateClones(const QRect &rect);
public:
qint32 x() const;
qint32 y() const;
......@@ -204,12 +218,8 @@ public:
QRect exactBounds() const;
QImage createThumbnail(qint32 w, qint32 h);
public:
void setDirty(const QRect & rect);
using KisNode::setDirty;
public:
/**
* Returns true if there are any effect masks present
*/
......
......@@ -35,6 +35,10 @@ public:
setCropRect(cropRect);
}
UpdateType type() const {
return UNSUPPORTED;
}
virtual ~KisRefreshSubtreeWalker()
{
}
......@@ -52,14 +56,14 @@ protected:
QRect calculateChangeRect(KisNodeSP startWith,
const QRect &requestedRect,
bool *changeRectVaries) {
const QRect &requestedRect) {
if(!isLayer(startWith))
return requestedRect;
QRect childrenRect;
QRect tempRect = requestedRect;
bool changeRectVaries;
KisNodeSP currentNode = startWith->firstChild();
KisNodeSP prevNode;
......@@ -69,10 +73,10 @@ protected:
nextNode = currentNode->nextSibling();
if(isLayer(currentNode)) {
tempRect = calculateChangeRect(currentNode, tempRect, changeRectVaries);
tempRect = calculateChangeRect(currentNode, tempRect);
if(!*changeRectVaries)
*changeRectVaries = tempRect != requestedRect;
if(!changeRectVaries)
changeRectVaries = tempRect != requestedRect;
childrenRect = tempRect;
prevNode = currentNode;
......@@ -83,17 +87,16 @@ protected:
tempRect = startWith->changeRect(requestedRect | childrenRect);
if(!*changeRectVaries)
*changeRectVaries = tempRect != requestedRect;
if(!changeRectVaries)
changeRectVaries = tempRect != requestedRect;
setExplicitChangeRect(startWith, tempRect, changeRectVaries);
return tempRect;
}
void startTrip(KisNodeSP startWith) {
bool changeRectVaries = false;
QRect changeRect = calculateChangeRect(startWith, requestedRect(), &changeRectVaries);
setExplicitChangeRect(changeRect, changeRectVaries);
calculateChangeRect(startWith, requestedRect());
if(startWith == startNode()) {
NodePosition pos = N_FILTHY;
......@@ -121,31 +124,6 @@ protected:
startTrip(currentNode);
} while ((currentNode = currentNode->prevSibling()));
}
void registerNeedRect(KisNodeSP node, NodePosition position) {
KisBaseRectsWalker::registerNeedRect(node, position);
KisCloneLayerSP cloneLayer = qobject_cast<KisCloneLayer*>(node.data());
if(cloneLayer) {
/**
* We need to check whether the source of the clone is going
* to be updated after the clone itself. If so we need to
* pre-update it manually. This can be checked by the precedence
* of the source in the nodes stack
*/
if(isRegistered(cloneLayer->copyFrom())) {
registerNeedRect(cloneLayer->copyFrom(), N_EXTRA | N_FILTHY);
}
}
}
bool isRegistered(KisNodeSP node) {
foreach(const JobItem &item, nodeStack()) {
if(item.m_node == node)
return true;
}
return false;
}
};
......
......@@ -122,7 +122,7 @@ void KisSimpleUpdateQueue::addJob(KisNodeSP node, const QRect& rc,
KisBaseRectsWalker::UpdateType type)
{
if(trySplitJob(node, rc, cropRect, type)) return;
if(tryMergeJob(node, rc, type)) return;
if(tryMergeJob(node, rc, cropRect, type)) return;
KisBaseRectsWalkerSP walker;
......@@ -132,6 +132,7 @@ void KisSimpleUpdateQueue::addJob(KisNodeSP node, const QRect& rc,
else /* if(type == KisBaseRectsWalker::FULL_REFRESH) */ {
walker = new KisFullRefreshWalker(cropRect);
}
/* else if(type == KisBaseRectsWalker::UNSUPPORTED) qFatal(); */
walker->collectRects(node, rc);
......@@ -179,6 +180,7 @@ bool KisSimpleUpdateQueue::trySplitJob(KisNodeSP node, const QRect& rc,
}
bool KisSimpleUpdateQueue::tryMergeJob(KisNodeSP node, const QRect& rc,
const QRect& cropRect,
KisBaseRectsWalker::UpdateType type)
{
QMutexLocker locker(&m_lock);
......@@ -201,6 +203,7 @@ bool KisSimpleUpdateQueue::tryMergeJob(KisNodeSP node, const QRect& rc,
if(item->startNode() != node) continue;
if(item->type() != type) continue;
if(item->cropRect() != cropRect) continue;
if(joinRects(baseRect, item->requestedRect(), m_maxMergeAlpha)) {
goodCandidate = item;
......@@ -241,6 +244,7 @@ void KisSimpleUpdateQueue::collectJobs(KisBaseRectsWalkerSP &baseWalker,
if(item == baseWalker) continue;
if(item->type() != baseWalker->type()) continue;
if(item->startNode() != baseNode) continue;
if(item->cropRect() != baseWalker->cropRect()) continue;
if(joinRects(baseRect, item->requestedRect(), maxAlpha)) {
iter.remove();
......
......@@ -51,7 +51,7 @@ protected:
bool processOneJob(KisUpdaterContext &updaterContext);
bool trySplitJob(KisNodeSP node, const QRect& rc, const QRect& cropRect, KisBaseRectsWalker::UpdateType type);
bool tryMergeJob(KisNodeSP node, const QRect& rc, KisBaseRectsWalker::UpdateType type);
bool tryMergeJob(KisNodeSP node, const QRect& rc, const QRect& cropRect, KisBaseRectsWalker::UpdateType type);
void collectJobs(KisBaseRectsWalkerSP &baseWalker, QRect baseRect,
const KisNodeSP &baseNode, const qreal maxAlpha);
......
......@@ -20,11 +20,13 @@
#include <qtest_kde.h>
#include <KoColor.h>
#include <KoColorSpace.h>
#include <KoColorSpaceRegistry.h>
#include "kis_layer.h"
#include "kis_paint_device.h"
#include "kis_paint_layer.h"
#include "kis_group_layer.h"
#include "kis_clone_layer.h"
#include "kis_image.h"
......@@ -37,6 +39,113 @@ void KisCloneLayerTest::testCreation()
KisCloneLayer test(layer, image, "clonetest", OPACITY_OPAQUE_U8);
}
KisImageSP createImage()
{
/*
+-----------------+
|root |
| group 1 <-----+ |
| paint 3 | |
| paint 1 | |
| group 2 | |
| clone_of_g1 -+ |
| paint 2 |
+-----------------+
*/
const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->rgb8();
KisImageSP image = new KisImage(0, 128, 128, colorSpace, "clones test");
QRect fillRect(10,10,100,100);
KisPaintDeviceSP device1 = new KisPaintDevice(colorSpace);
device1->fill(fillRect, KoColor( Qt::white, colorSpace));
KisLayerSP paintLayer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8, device1);
KisLayerSP groupLayer1 = new KisGroupLayer(image, "group1", OPACITY_OPAQUE_U8);
KisPaintDeviceSP device2 = new KisPaintDevice(colorSpace);
KisLayerSP paintLayer2 = new KisPaintLayer(image, "paint2", OPACITY_OPAQUE_U8, device2);
KisPaintDeviceSP device3 = new KisPaintDevice(colorSpace);
KisLayerSP paintLayer3 = new KisPaintLayer(image, "paint3", OPACITY_OPAQUE_U8, device3);
KisLayerSP cloneLayer1 = new KisCloneLayer(groupLayer1, image, "clone_of_g1", OPACITY_OPAQUE_U8);
cloneLayer1->setX(10);
cloneLayer1->setY(10);
KisLayerSP groupLayer2 = new KisGroupLayer(image, "group2", OPACITY_OPAQUE_U8);
image->addNode(groupLayer2, image->rootLayer());
image->addNode(groupLayer1, image->rootLayer());
image->addNode(paintLayer2, groupLayer2);
image->addNode(cloneLayer1, groupLayer2);
image->addNode(paintLayer1, groupLayer1);
image->addNode(paintLayer3, groupLayer1);
return image;
}
KisNodeSP groupLayer1(KisImageSP image) {
KisNodeSP node = image->root()->lastChild();
Q_ASSERT(node->name() == "group1");
return node;
}
KisNodeSP paintLayer1(KisImageSP image) {
KisNodeSP node = groupLayer1(image)->firstChild();
Q_ASSERT(node->name() == "paint1");
return node;
}
void KisCloneLayerTest::testOriginalUpdates()
{
const QRect nullRect(qint32_MAX, qint32_MAX, 0, 0);
KisImageSP image = createImage();
KisNodeSP root = image->root();
QCOMPARE(root->projection()->exactBounds(), nullRect);
paintLayer1(image)->setDirty(image->bounds());
image->waitForDone();
const QRect expectedRect(10,10,110,110);
QCOMPARE(root->projection()->exactBounds(), expectedRect);
}
void KisCloneLayerTest::testOriginalUpdatesOutOfBounds()
{
const QRect nullRect(qint32_MAX, qint32_MAX, 0, 0);
KisImageSP image = createImage();
KisNodeSP root = image->root();
QCOMPARE(root->projection()->exactBounds(), nullRect);
QRect fillRect(-10,-10,10,10);
paintLayer1(image)->paintDevice()->fill(fillRect, KoColor(Qt::white, image->colorSpace()));
paintLayer1(image)->setDirty(fillRect);
image->waitForDone();
const QRect expectedRect(-10,-10,20,20);
QCOMPARE(root->projection()->exactBounds(), expectedRect);
QCOMPARE(groupLayer1(image)->projection()->exactBounds(), fillRect);
}
void KisCloneLayerTest::testOriginalRefresh()
{
const QRect nullRect(qint32_MAX, qint32_MAX, 0, 0);
KisImageSP image = createImage();
KisNodeSP root = image->root();
QCOMPARE(root->projection()->exactBounds(), nullRect);
image->refreshGraph();
image->waitForDone();
const QRect expectedRect(10,10,110,110);
QCOMPARE(root->projection()->exactBounds(), expectedRect);
}
QTEST_KDEMAIN(KisCloneLayerTest, GUI)
#include "kis_clone_layer_test.moc"
......@@ -27,7 +27,9 @@ class KisCloneLayerTest : public QObject
private slots:
void testCreation();
void testOriginalUpdates();
void testOriginalUpdatesOutOfBounds();
void testOriginalRefresh();
};
#endif
......@@ -181,7 +181,7 @@ void KisWalkersTest::verifyResult(KisBaseRectsWalker &walker, QStringList refere
<< nodeTypeString(item.m_position);
#endif
QVERIFY(item.m_node->name() == *iter);
QCOMPARE(item.m_node->name(), *iter);
iter++;
}
......@@ -363,6 +363,25 @@ void KisWalkersTest::testMergeVisiting()
verifyResult(walker, orderList, accessRect, true, true);
}
{
/**