diff --git a/libs/image/kis_properties_configuration.cc b/libs/image/kis_properties_configuration.cc index 03bf545626..1f60cf3d25 100644 --- a/libs/image/kis_properties_configuration.cc +++ b/libs/image/kis_properties_configuration.cc @@ -1,465 +1,465 @@ /* * Copyright (c) 2006 Boudewijn Rempt * Copyright (c) 2007,2010 Cyrille Berger * * 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_properties_configuration.h" #include #include #include #include "kis_image.h" #include "kis_transaction.h" #include "kis_undo_adapter.h" #include "kis_painter.h" #include "kis_selection.h" #include "KoID.h" #include "kis_types.h" #include #include #include struct Q_DECL_HIDDEN KisPropertiesConfiguration::Private { QMap properties; QStringList notSavedProperties; }; KisPropertiesConfiguration::KisPropertiesConfiguration() : d(new Private) { } KisPropertiesConfiguration::~KisPropertiesConfiguration() { delete d; } KisPropertiesConfiguration::KisPropertiesConfiguration(const KisPropertiesConfiguration& rhs) : KisSerializableConfiguration(rhs) , d(new Private(*rhs.d)) { } KisPropertiesConfiguration &KisPropertiesConfiguration::operator=(const KisPropertiesConfiguration &rhs) { if (&rhs != this) { *d = *rhs.d; } return *this; } bool KisPropertiesConfiguration::fromXML(const QString & xml, bool clear) { if (clear) { clearProperties(); } QDomDocument doc; bool retval = doc.setContent(xml); if (retval) { QDomElement e = doc.documentElement(); fromXML(e); } return retval; } void KisPropertiesConfiguration::fromXML(const QDomElement& e) { QDomNode n = e.firstChild(); while (!n.isNull()) { // We don't nest elements in filter configuration. For now... QDomElement e = n.toElement(); if (!e.isNull()) { if (e.tagName() == "param") { // If the file contains the new type parameter introduced in Krita act on it // Else invoke old behaviour if(e.attributes().contains("type")) { QString type = e.attribute("type"); QString name = e.attribute("name"); QString value = e.text(); if (type == "bytearray") { d->properties[name] = QVariant(QByteArray::fromBase64(value.toLatin1())); } else { d->properties[name] = value; } } else { d->properties[e.attribute("name")] = QVariant(e.text()); } } } n = n.nextSibling(); } //dump(); } void KisPropertiesConfiguration::toXML(QDomDocument& doc, QDomElement& root) const { QMap::Iterator it; for (it = d->properties.begin(); it != d->properties.end(); ++it) { if(d->notSavedProperties.contains(it.key())) { continue; } QDomElement e = doc.createElement("param"); e.setAttribute("name", QString(it.key().toLatin1())); QString type = "string"; QVariant v = it.value(); QDomText text; if (v.type() == QVariant::UserType && v.userType() == qMetaTypeId()) { text = doc.createCDATASection(v.value().toString()); } else if (v.type() == QVariant::UserType && v.userType() == qMetaTypeId()) { QDomDocument cdataDoc = QDomDocument("color"); QDomElement cdataRoot = cdataDoc.createElement("color"); cdataDoc.appendChild(cdataRoot); v.value().toXML(cdataDoc, cdataRoot); text = cdataDoc.createCDATASection(cdataDoc.toString()); type = "color"; } else if(v.type() == QVariant::String ) { text = doc.createCDATASection(v.toString()); // XXX: Unittest this! type = "string"; } else if(v.type() == QVariant::ByteArray ) { text = doc.createTextNode(QString::fromLatin1(v.toByteArray().toBase64())); // Arbitrary Data type = "bytearray"; } else { text = doc.createTextNode(v.toString()); type = "internal"; } e.setAttribute("type", type); e.appendChild(text); root.appendChild(e); } } QString KisPropertiesConfiguration::toXML() const { QDomDocument doc = QDomDocument("params"); QDomElement root = doc.createElement("params"); doc.appendChild(root); toXML(doc, root); return doc.toString(); } bool KisPropertiesConfiguration::hasProperty(const QString& name) const { return d->properties.contains(name); } void KisPropertiesConfiguration::setProperty(const QString & name, const QVariant & value) { if (d->properties.find(name) == d->properties.end()) { d->properties.insert(name, value); } else { d->properties[name] = value; } } bool KisPropertiesConfiguration::getProperty(const QString & name, QVariant & value) const { if (d->properties.find(name) == d->properties.end()) { return false; } else { value = d->properties[name]; return true; } } QVariant KisPropertiesConfiguration::getProperty(const QString & name) const { if (d->properties.find(name) == d->properties.end()) { return QVariant(); } else { return d->properties[name]; } } int KisPropertiesConfiguration::getInt(const QString & name, int def) const { QVariant v = getProperty(name); if (v.isValid()) return v.toInt(); else return def; } double KisPropertiesConfiguration::getDouble(const QString & name, double def) const { QVariant v = getProperty(name); if (v.isValid()) return v.toDouble(); else return def; } float KisPropertiesConfiguration::getFloat(const QString & name, float def) const { QVariant v = getProperty(name); if (v.isValid()) return (float)v.toDouble(); else return def; } bool KisPropertiesConfiguration::getBool(const QString & name, bool def) const { QVariant v = getProperty(name); if (v.isValid()) return v.toBool(); else return def; } QString KisPropertiesConfiguration::getString(const QString & name, const QString & def) const { QVariant v = getProperty(name); if (v.isValid()) return v.toString(); else return def; } KisCubicCurve KisPropertiesConfiguration::getCubicCurve(const QString & name, const KisCubicCurve & curve) const { QVariant v = getProperty(name); if (v.isValid()) { if (v.type() == QVariant::UserType && v.userType() == qMetaTypeId()) { return v.value(); } else { KisCubicCurve c; c.fromString(v.toString()); return c; } } else return curve; } KoColor KisPropertiesConfiguration::getColor(const QString& name, const KoColor& color) const { QVariant v = getProperty(name); if (v.isValid()) { switch(v.type()) { case QVariant::UserType: { if (v.userType() == qMetaTypeId()) { return v.value(); } break; } case QVariant::String: { QDomDocument doc; if (doc.setContent(v.toString())) { QDomElement e = doc.documentElement().firstChild().toElement(); bool ok; KoColor c = KoColor::fromXML(e, Integer16BitsColorDepthID.id(), &ok); if (ok) { return c; } } else { QColor c(v.toString()); if (c.isValid()) { KoColor kc(c, KoColorSpaceRegistry::instance()->rgb8()); return kc; } } break; } case QVariant::Color: { QColor c = v.value(); KoColor kc(c, KoColorSpaceRegistry::instance()->rgb8()); return kc; } case QVariant::Int: { QColor c(v.toInt()); if (c.isValid()) { KoColor kc(c, KoColorSpaceRegistry::instance()->rgb8()); return kc; } break; } default: ; } } return color; } void KisPropertiesConfiguration::dump() const { - QMap::Iterator it; - for (it = d->properties.begin(); it != d->properties.end(); ++it) { - qDebug() << it.key() << " = " << it.value() << it.value().typeName(); - } +// QMap::Iterator it; +// for (it = d->properties.begin(); it != d->properties.end(); ++it) { +// qDebug() << it.key() << " = " << it.value() << it.value().typeName(); +// } } void KisPropertiesConfiguration::clearProperties() { d->properties.clear(); } void KisPropertiesConfiguration::setPropertyNotSaved(const QString& name) { d->notSavedProperties.append(name); } QMap KisPropertiesConfiguration::getProperties() const { return d->properties; } void KisPropertiesConfiguration::removeProperty(const QString & name) { d->properties.remove(name); } QList KisPropertiesConfiguration::getPropertiesKeys() const { return d->properties.keys(); } void KisPropertiesConfiguration::getPrefixedProperties(const QString &prefix, KisPropertiesConfiguration *config) const { const int prefixSize = prefix.size(); const QList keys = getPropertiesKeys(); Q_FOREACH (const QString &key, keys) { if (key.startsWith(prefix)) { config->setProperty(key.mid(prefixSize), getProperty(key)); } } } void KisPropertiesConfiguration::getPrefixedProperties(const QString &prefix, KisPropertiesConfigurationSP config) const { getPrefixedProperties(prefix, config.data()); } void KisPropertiesConfiguration::setPrefixedProperties(const QString &prefix, const KisPropertiesConfiguration *config) { const QList keys = config->getPropertiesKeys(); Q_FOREACH (const QString &key, keys) { this->setProperty(prefix + key, config->getProperty(key)); } } void KisPropertiesConfiguration::setPrefixedProperties(const QString &prefix, const KisPropertiesConfigurationSP config) { setPrefixedProperties(prefix, config.data()); } QString KisPropertiesConfiguration::escapeString(const QString &string) { QString result = string; result.replace(";", "\\;"); result.replace("]", "\\]"); result.replace(">", "\\>"); return result; } QString KisPropertiesConfiguration::unescapeString(const QString &string) { QString result = string; result.replace("\\;", ";"); result.replace("\\]", "]"); result.replace("\\>", ">"); return result; } void KisPropertiesConfiguration::setProperty(const QString &name, const QStringList &value) { QStringList escapedList; escapedList.reserve(value.size()); Q_FOREACH (const QString &str, value) { escapedList << escapeString(str); } setProperty(name, escapedList.join(';')); } QStringList KisPropertiesConfiguration::getStringList(const QString &name, const QStringList &defaultValue) const { if (!hasProperty(name)) return defaultValue; const QString joined = getString(name); QStringList result; int afterLastMatch = -1; for (int i = 0; i < joined.size(); i++) { const bool lastChunk = i == joined.size() - 1; const bool matchedSplitter = joined[i] == ';' && (i == 0 || joined[i - 1] != '\\'); if (lastChunk || matchedSplitter) { result << unescapeString(joined.mid(afterLastMatch, i - afterLastMatch + int(lastChunk && !matchedSplitter))); afterLastMatch = i + 1; } if (lastChunk && matchedSplitter) { result << QString(); } } return result; } QStringList KisPropertiesConfiguration::getPropertyLazy(const QString &name, const QStringList &defaultValue) const { return getStringList(name, defaultValue); } // --- factory --- struct Q_DECL_HIDDEN KisPropertiesConfigurationFactory::Private { }; KisPropertiesConfigurationFactory::KisPropertiesConfigurationFactory() : d(new Private) { } KisPropertiesConfigurationFactory::~KisPropertiesConfigurationFactory() { delete d; } KisSerializableConfigurationSP KisPropertiesConfigurationFactory::createDefault() { return new KisPropertiesConfiguration(); } KisSerializableConfigurationSP KisPropertiesConfigurationFactory::create(const QDomElement& e) { KisPropertiesConfigurationSP pc = new KisPropertiesConfiguration(); pc->fromXML(e); return pc; } diff --git a/libs/image/tiles3/kis_memento_manager.h b/libs/image/tiles3/kis_memento_manager.h index 5b9543983c..b68ee64865 100644 --- a/libs/image/tiles3/kis_memento_manager.h +++ b/libs/image/tiles3/kis_memento_manager.h @@ -1,164 +1,175 @@ /* * 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. */ #ifndef KIS_MEMENTO_MANAGER_ #define KIS_MEMENTO_MANAGER_ #include #include "kis_memento_item.h" -#include "kis_tile_hash_table.h" +//#include "kis_tile_hash_table.h" +#include "kis_tile_hash_table2.h" typedef QList KisMementoItemList; typedef QListIterator KisMementoItemListIterator; class KisMemento; struct KisHistoryItem { KisMemento* memento; KisMementoItemList itemList; }; typedef QList KisHistoryList; class KisMemento; typedef KisSharedPtr KisMementoSP; -typedef KisTileHashTableTraits KisMementoItemHashTable; -typedef KisTileHashTableIteratorTraits KisMementoItemHashTableIterator; -typedef KisTileHashTableIteratorTraits KisMementoItemHashTableIteratorConst; +template +class KisTileHashTableTraits2; + +template +class KisTileHashTableIteratorTraits2; + +//typedef KisTileHashTableTraits KisMementoItemHashTable; +//typedef KisTileHashTableIteratorTraits KisMementoItemHashTableIterator; +//typedef KisTileHashTableIteratorTraits KisMementoItemHashTableIteratorConst; + +typedef KisTileHashTableTraits2 KisMementoItemHashTable; +typedef KisTileHashTableIteratorTraits2 KisMementoItemHashTableIterator; +typedef KisTileHashTableIteratorTraits2 KisMementoItemHashTableIteratorConst; class KRITAIMAGE_EXPORT KisMementoManager { public: KisMementoManager(); KisMementoManager(const KisMementoManager& rhs); ~KisMementoManager(); /** * Most tricky part. This function is called by a tile, when it gets new * tile-data through COW. The Memento Manager wraps this tile-data into * KisMementoItem class and waits until commit() order given. By this * time KisMementoItem doesn't take part in COW mechanism. It only holds * tileData->m_refCount counter to ensure tile isn't deleted from memory. * When commit() comes, KisMementoItem grabs tileData->m_usersCount and * since that moment it is a rightful co-owner of the tileData and COW * participant. It means that tileData won't be ever changed since then. * Every write request to the original tile will lead to duplicating * tileData and registering it here again... */ void registerTileChange(KisTile *tile); /** * Called when a tile deleted. Creates empty KisMementoItem showing that * there was a tile one day */ void registerTileDeleted(KisTile *tile); /** * Commits changes, made in INDEX: appends m_index into m_revisions list * and owes all modified tileDatas. */ void commit(); /** * Undo and Redo stuff respectively. * * When calling them, INDEX list should be empty, so to say, "working * copy should be clean". */ void rollback(KisTileHashTable *ht); void rollforward(KisTileHashTable *ht); /** * Get old tile, whose memento is in the HEAD revision. * \p existingTile returns if the tile is actually an existing * non-default tile or it was created on the fly * from the default tile data */ KisTileSP getCommitedTile(qint32 col, qint32 row, bool &existingTile); KisMementoSP getMemento(); bool hasCurrentMemento() { return m_currentMemento; } KisMementoSP currentMemento(); void setDefaultTileData(KisTileData *defaultTileData); void debugPrintInfo(); /** * Removes all the history that preceds the revision * pointed by oldestMemento. That is after calling to * purgeHistory(someMemento) you won't be able to do * rollback(someMemento) anymore. */ void purgeHistory(KisMementoSP oldestMemento); protected: qint32 findRevisionByMemento(KisMementoSP memento) const; void resetRevisionHistory(KisMementoItemList list); protected: /** * INDEX of tiles to be committed with next commit() * We use a hash table to be able to check that * we have the only memento item for a tile * per commit efficiently */ KisMementoItemHashTable m_index; /** * Main list that stores every commit ever done */ KisHistoryList m_revisions; /** * List of revisions temporarily undone while rollback() */ KisHistoryList m_cancelledRevisions; /** * A hash table, that stores the most recently updated * versions of tiles. Say, HEAD revision :) */ KisMementoItemHashTable m_headsHashTable; /** * Stores extent of current INDEX. * It is the "name" of current named transaction */ KisMementoSP m_currentMemento; /** * The flag that blocks registration of changes on tiles. * This is a temporary state of the memento manager, that * is used for traveling in history * * \see rollback() * \see rollforward() */ bool m_registrationBlocked; }; #endif /* KIS_MEMENTO_MANAGER_ */ diff --git a/libs/image/tiles3/kis_tile_hash_table2.h b/libs/image/tiles3/kis_tile_hash_table2.h index e4f427af83..0478949781 100644 --- a/libs/image/tiles3/kis_tile_hash_table2.h +++ b/libs/image/tiles3/kis_tile_hash_table2.h @@ -1,309 +1,364 @@ #ifndef KIS_TILEHASHTABLE_2_H #define KIS_TILEHASHTABLE_2_H #include "kis_shared.h" #include "kis_shared_ptr.h" #include "3rdparty/lock_free_map/concurrent_map.h" -#include "kis_memento_manager.h" -#include +#include "kis_tile.h" template class KisTileHashTableTraits2 { static constexpr bool isInherited = std::is_convertible::value; Q_STATIC_ASSERT_X(isInherited, "Template must inherit KisShared"); public: typedef T TileType; typedef KisSharedPtr TileTypeSP; typedef KisWeakSharedPtr TileTypeWSP; KisTileHashTableTraits2(); KisTileHashTableTraits2(KisMementoManager *mm); KisTileHashTableTraits2(const KisTileHashTableTraits2 &ht, KisMementoManager *mm); ~KisTileHashTableTraits2(); TileTypeSP insert(qint32 key, TileTypeSP value) { m_rawPointerUsers.fetchAndAddRelaxed(1); TileTypeSP::ref(&value, value.data()); TileType *result = m_map.assign(key, value.data()); if (result) { MemoryReclaimer *tmp = new MemoryReclaimer(result); QSBR::instance().enqueue(&MemoryReclaimer::destroy, tmp); } else { m_numTiles.fetchAndAddRelaxed(1); } TileTypeSP ptr(result); m_rawPointerUsers.fetchAndSubRelaxed(1); return ptr; } TileTypeSP erase(qint32 key) { m_rawPointerUsers.fetchAndAddRelaxed(1); TileType *result = m_map.erase(key); TileTypeSP ptr(result); if (result) { m_numTiles.fetchAndSubRelaxed(1); MemoryReclaimer *tmp = new MemoryReclaimer(result); QSBR::instance().enqueue(&MemoryReclaimer::destroy, tmp); } if (m_rawPointerUsers == 1) { QSBR::instance().update(m_context); } m_rawPointerUsers.fetchAndSubRelaxed(1); return ptr; } TileTypeSP get(qint32 key) { m_rawPointerUsers.fetchAndAddRelaxed(1); TileTypeSP result(m_map.get(key)); m_rawPointerUsers.fetchAndSubRelaxed(1); return result; } - TileTypeSP getLazy(qint32 key, TileTypeSP value) + TileTypeSP getLazy(qint32 key, TileTypeSP value, bool &newTile) { m_rawPointerUsers.fetchAndAddRelaxed(1); typename ConcurrentMap::Mutator iter = m_map.insertOrFind(key); if (!iter.getValue()) { TileTypeSP::ref(&value, value.data()); if (iter.exchangeValue(value.data()) == value.data()) { TileTypeSP::deref(&value, value.data()); } else { m_numTiles.fetchAndAddRelaxed(1); } + + newTile = true; } TileTypeSP result(iter.getValue()); m_rawPointerUsers.fetchAndSubRelaxed(1); return result; } bool isEmpty() { return !m_numTiles; } bool tileExists(qint32 col, qint32 row); /** * Returns a tile in position (col,row). If no tile exists, * returns null. * \param col column of the tile * \param row row of the tile */ TileTypeSP getExistingTile(qint32 col, qint32 row); /** * Returns a tile in position (col,row). If no tile exists, * creates a new one, attaches it to the list and returns. * \param col column of the tile * \param row row of the tile * \param newTile out-parameter, returns true if a new tile * was created */ TileTypeSP getTileLazy(qint32 col, qint32 row, bool& newTile); /** * Returns a tile in position (col,row). If no tile exists, * creates nothing, but returns shared default tile object * of the table. Be careful, this object has column and row * parameters set to (qint32_MIN, qint32_MIN). * \param col column of the tile * \param row row of the tile * \param existingTile returns true if the tile actually exists in the table * and it is not a lazily created default wrapper tile */ TileTypeSP getReadOnlyTileLazy(qint32 col, qint32 row, bool &existingTile); void addTile(TileTypeSP tile); bool deleteTile(TileTypeSP tile); bool deleteTile(qint32 col, qint32 row); void clear(); void setDefaultTileData(KisTileData *defaultTileData); KisTileData* defaultTileData() const; qint32 numTiles() { return m_numTiles; } void debugPrintInfo(); void debugMaxListLength(qint32 &min, qint32 &max); + typename ConcurrentMap::Iterator iterator() + { + typename ConcurrentMap::Iterator iter(m_map); + return iter; + } + private: static inline quint32 calculateHash(qint32 col, qint32 row); struct MemoryReclaimer { MemoryReclaimer(TileType *data) : d(data) {} ~MemoryReclaimer() = default; void destroy() { TileTypeSP::deref(reinterpret_cast(this), d); this->MemoryReclaimer::~MemoryReclaimer(); delete this; } private: TileType *d; }; private: ConcurrentMap m_map; QSBR::Context m_context; QAtomicInt m_rawPointerUsers; QAtomicInt m_numTiles; KisTileData *m_defaultTileData; KisMementoManager *m_mementoManager; }; +template +class KisTileHashTableIteratorTraits2 +{ +public: + typedef T TileType; + typedef KisSharedPtr TileTypeSP; + + KisTileHashTableIteratorTraits2(KisTileHashTableTraits2 *ht) : m_ht(ht) + { + } + + void next() + { + m_ht->iterator().next(); + } + + TileTypeSP tile() const + { + return TileTypeSP(m_ht->iterator().getValue()); + } + + bool isDone() const + { + return m_ht->iterator().isValid(); + } + + void deleteCurrent() + { + m_ht->erase(m_ht->iterator().getKey()); + next(); + } + + void moveCurrentToHashTable(KisTileHashTableTraits2 *newHashTable) + { + TileTypeSP tile = m_ht->iterator().getValue(); + next(); + m_ht->deleteTile(tile); + newHashTable->addTile(tile); + } + +private: + KisTileHashTableTraits2 *m_ht; +}; + template KisTileHashTableTraits2::KisTileHashTableTraits2() : m_context(QSBR::instance().createContext()), m_rawPointerUsers(0), m_numTiles(0), m_defaultTileData(0), m_mementoManager(0) { } template KisTileHashTableTraits2::KisTileHashTableTraits2(KisMementoManager *mm) : KisTileHashTableTraits2() { m_mementoManager = mm; } template KisTileHashTableTraits2::KisTileHashTableTraits2(const KisTileHashTableTraits2 &ht, KisMementoManager *mm) : KisTileHashTableTraits2(mm) { setDefaultTileData(ht.m_defaultTileData); - typename ConcurrentMap::Iterator iter(ht); + typename ConcurrentMap::Iterator iter(const_cast &>(ht.m_map)); while (iter.isValid()) { insert(iter.getKey(), iter.getValue()); iter.next(); } } template KisTileHashTableTraits2::~KisTileHashTableTraits2() { clear(); QSBR::instance().destroyContext(m_context); } template bool KisTileHashTableTraits2::tileExists(qint32 col, qint32 row) { return get(calculateHash(col, row)) != nullptr; } template typename KisTileHashTableTraits2::TileTypeSP KisTileHashTableTraits2::getExistingTile(qint32 col, qint32 row) { return get(calculateHash(col, row)); } template typename KisTileHashTableTraits2::TileTypeSP KisTileHashTableTraits2::getTileLazy(qint32 col, qint32 row, bool &newTile) { TileTypeSP tile(new TileType(col, row, m_defaultTileData, m_mementoManager)); - return getLazy(calculateHash(col, row), tile); + return getLazy(calculateHash(col, row), tile, newTile); } template typename KisTileHashTableTraits2::TileTypeSP KisTileHashTableTraits2::getReadOnlyTileLazy(qint32 col, qint32 row, bool &existingTile) { m_rawPointerUsers.fetchAndAddRelaxed(1); TileTypeSP tile(m_map.get(calculateHash(col, row))); existingTile = tile; if (!existingTile) { tile = new TileType(col, row, m_defaultTileData, 0); } m_rawPointerUsers.fetchAndSubRelaxed(1); return tile; } template void KisTileHashTableTraits2::addTile(TileTypeSP value) { insert(m_numTiles.load() + 1, value); } template bool KisTileHashTableTraits2::deleteTile(TileTypeSP tile) { return deleteTile(tile->col(), tile->row()); } template bool KisTileHashTableTraits2::deleteTile(qint32 col, qint32 row) { return erase(calculateHash(col, row)) != nullptr; } template void KisTileHashTableTraits2::clear() { typename ConcurrentMap::Iterator iter(m_map); while (iter.isValid()) { erase(iter.getKey()); iter.next(); } } template inline void KisTileHashTableTraits2::setDefaultTileData(KisTileData *defaultTileData) { m_rawPointerUsers.fetchAndAddRelaxed(1); if (m_defaultTileData) { m_defaultTileData->release(); m_defaultTileData = 0; } if (defaultTileData) { defaultTileData->acquire(); m_defaultTileData = defaultTileData; } m_rawPointerUsers.fetchAndSubRelaxed(1); } template inline KisTileData* KisTileHashTableTraits2::defaultTileData() const { return m_defaultTileData; } template void KisTileHashTableTraits2::debugPrintInfo() { } template void KisTileHashTableTraits2::debugMaxListLength(qint32 &min, qint32 &max) { } template quint32 KisTileHashTableTraits2::calculateHash(qint32 col, qint32 row) { return ((row << 5) + (col & 0x1F)) & 0x3FF; } +typedef KisTileHashTableTraits2 KisTileHashTable; +typedef KisTileHashTableIteratorTraits2 KisTileHashTableIterator; +typedef KisTileHashTableIteratorTraits2 KisTileHashTableConstIterator; + #endif // KIS_TILEHASHTABLE_2_H diff --git a/libs/image/tiles3/kis_tiled_data_manager.h b/libs/image/tiles3/kis_tiled_data_manager.h index 8308965a0c..8524acff60 100644 --- a/libs/image/tiles3/kis_tiled_data_manager.h +++ b/libs/image/tiles3/kis_tiled_data_manager.h @@ -1,414 +1,415 @@ /* * Copyright (c) 2004 Boudewijn Rempt * (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. */ #ifndef KIS_TILEDDATAMANAGER_H_ #define KIS_TILEDDATAMANAGER_H_ #include #include #include #include #include //#include "kis_debug.h" #include "kritaimage_export.h" -#include "kis_tile_hash_table.h" +//#include "kis_tile_hash_table.h" +#include "kis_tile_hash_table2.h" #include "kis_memento_manager.h" #include "kis_memento.h" #include "KisTiledExtentManager.h" class KisTiledDataManager; typedef KisSharedPtr KisTiledDataManagerSP; class KisTiledIterator; class KisTiledRandomAccessor; class KisPaintDeviceWriter; class QIODevice; /** * KisTiledDataManager implements the interface that KisDataManager defines * * The interface definition is enforced by KisDataManager calling all the methods * which must also be defined in KisTiledDataManager. It is not allowed to change the interface * as other datamangers may also rely on the same interface. * * * Storing undo/redo data * * Offering ordered and unordered iterators over rects of pixels * * (eventually) efficiently loading and saving data in a format * that may allow deferred loading. * * A datamanager knows nothing about the type of pixel data except * how many quint8's a single pixel takes. */ class KRITAIMAGE_EXPORT KisTiledDataManager : public KisShared { private: static const qint32 LEGACY_VERSION = 1; static const qint32 CURRENT_VERSION = 2; protected: /*FIXME:*/ public: KisTiledDataManager(quint32 pixelSize, const quint8 *defPixel); virtual ~KisTiledDataManager(); KisTiledDataManager(const KisTiledDataManager &dm); KisTiledDataManager & operator=(const KisTiledDataManager &dm); protected: // Allow the baseclass of iterators access to the interior // derived iterator classes must go through KisTiledIterator friend class KisTiledIterator; friend class KisBaseIterator; friend class KisTiledRandomAccessor; friend class KisRandomAccessor2; friend class KisStressJob; public: void setDefaultPixel(const quint8 *defPixel); const quint8 *defaultPixel() const { return m_defaultPixel; } /** * Every iterator fetches both types of tiles all the time: old and new. * For projection devices these tiles are **always** the same, but doing * two distinct calls makes double pressure on the read-write lock in the * hash table. * * Merging two calls into one allows us to avoid additional tile fetch from * the hash table and therefore reduce waiting time. */ inline void getTilesPair(qint32 col, qint32 row, bool writable, KisTileSP *tile, KisTileSP *oldTile) { *tile = getTile(col, row, writable); bool unused; *oldTile = m_mementoManager->getCommitedTile(col, row, unused); if (!*oldTile) { *oldTile = *tile; } } inline KisTileSP getTile(qint32 col, qint32 row, bool writable) { if (writable) { bool newTile; KisTileSP tile = m_hashTable->getTileLazy(col, row, newTile); if (newTile) { m_extentManager.notifyTileAdded(col, row); } return tile; } else { bool unused; return m_hashTable->getReadOnlyTileLazy(col, row, unused); } } inline KisTileSP getReadOnlyTileLazy(qint32 col, qint32 row, bool &existingTile) { return m_hashTable->getReadOnlyTileLazy(col, row, existingTile); } inline KisTileSP getOldTile(qint32 col, qint32 row, bool &existingTile) { KisTileSP tile = m_mementoManager->getCommitedTile(col, row, existingTile); return tile ? tile : getReadOnlyTileLazy(col, row, existingTile); } inline KisTileSP getOldTile(qint32 col, qint32 row) { bool unused; return getOldTile(col, row, unused); } KisMementoSP getMemento() { QWriteLocker locker(&m_lock); KisMementoSP memento = m_mementoManager->getMemento(); memento->saveOldDefaultPixel(m_defaultPixel, m_pixelSize); return memento; } /** * Finishes having already started transaction */ void commit() { QWriteLocker locker(&m_lock); KisMementoSP memento = m_mementoManager->currentMemento(); if(memento) { memento->saveNewDefaultPixel(m_defaultPixel, m_pixelSize); } m_mementoManager->commit(); } void rollback(KisMementoSP memento) { commit(); QWriteLocker locker(&m_lock); m_mementoManager->rollback(m_hashTable); const quint8 *defaultPixel = memento->oldDefaultPixel(); if(memcmp(m_defaultPixel, defaultPixel, m_pixelSize)) { setDefaultPixelImpl(defaultPixel); } recalculateExtent(); } void rollforward(KisMementoSP memento) { commit(); QWriteLocker locker(&m_lock); m_mementoManager->rollforward(m_hashTable); const quint8 *defaultPixel = memento->newDefaultPixel(); if(memcmp(m_defaultPixel, defaultPixel, m_pixelSize)) { setDefaultPixelImpl(defaultPixel); } recalculateExtent(); } bool hasCurrentMemento() const { return m_mementoManager->hasCurrentMemento(); //return true; } /** * Removes all the history that preceds the revision * pointed by oldestMemento. That is after calling to * purgeHistory(someMemento) you won't be able to do * rollback(someMemento) anymore. */ void purgeHistory(KisMementoSP oldestMemento) { QWriteLocker locker(&m_lock); m_mementoManager->purgeHistory(oldestMemento); } static void releaseInternalPools(); protected: /** * Reads and writes the tiles */ bool write(KisPaintDeviceWriter &store); bool read(QIODevice *stream); void purge(const QRect& area); inline quint32 pixelSize() const { return m_pixelSize; } /* FIXME:*/ public: void extent(qint32 &x, qint32 &y, qint32 &w, qint32 &h) const; void setExtent(qint32 x, qint32 y, qint32 w, qint32 h); QRect extent() const; void setExtent(QRect newRect); QRegion region() const; void clear(QRect clearRect, quint8 clearValue); void clear(QRect clearRect, const quint8 *clearPixel); void clear(qint32 x, qint32 y, qint32 w, qint32 h, quint8 clearValue); void clear(qint32 x, qint32 y, qint32 w, qint32 h, const quint8 *clearPixel); void clear(); /** * Clones rect from another datamanager. The cloned area will be * shared between both datamanagers as much as possible using * copy-on-write. Parts of the rect that cannot be shared * (cross tiles) are deep-copied, */ void bitBlt(KisTiledDataManager *srcDM, const QRect &rect); /** * The same as \ref bitBlt(), but reads old data */ void bitBltOldData(KisTiledDataManager *srcDM, const QRect &rect); /** * Clones rect from another datamanager in a rough and fast way. * All the tiles touched by rect will be shared, between both * managers, that means it will copy a bigger area than was * requested. This method is supposed to be used for bitBlt'ing * into temporary paint devices. */ void bitBltRough(KisTiledDataManager *srcDM, const QRect &rect); /** * The same as \ref bitBltRough(), but reads old data */ void bitBltRoughOldData(KisTiledDataManager *srcDM, const QRect &rect); /** * write the specified data to x, y. There is no checking on pixelSize! */ void setPixel(qint32 x, qint32 y, const quint8 * data); /** * Copy the bytes in the specified rect to a vector. The caller is responsible * for managing the vector. * * \param dataRowStride is the step (in bytes) which should be * added to \p bytes pointer to get to the * next row */ void readBytes(quint8 * bytes, qint32 x, qint32 y, qint32 w, qint32 h, qint32 dataRowStride = -1) const; /** * Copy the bytes in the vector to the specified rect. If there are bytes left * in the vector after filling the rect, they will be ignored. If there are * not enough bytes, the rest of the rect will be filled with the default value * given (by default, 0); * * \param dataRowStride is the step (in bytes) which should be * added to \p bytes pointer to get to the * next row */ void writeBytes(const quint8 * bytes, qint32 x, qint32 y, qint32 w, qint32 h, qint32 dataRowStride = -1); /** * Copy the bytes in the paint device into a vector of arrays of bytes, * where the number of arrays is the number of channels in the * paint device. If the specified area is larger than the paint * device's extent, the default pixel will be read. */ QVector readPlanarBytes(QVector channelsizes, qint32 x, qint32 y, qint32 w, qint32 h) const; /** * Write the data in the separate arrays to the channels. If there * are less vectors than channels, the remaining channels will not * be copied. If any of the arrays points to 0, the channel in * that location will not be touched. If the specified area is * larger than the paint device, the paint device will be * extended. There are no guards: if the area covers more pixels * than there are bytes in the arrays, krita will happily fill * your paint device with areas of memory you never wanted to be * read. Krita may also crash. */ void writePlanarBytes(QVector planes, QVector channelsizes, qint32 x, qint32 y, qint32 w, qint32 h); /** * Get the number of contiguous columns starting at x, valid for all values * of y between minY and maxY. */ qint32 numContiguousColumns(qint32 x, qint32 minY, qint32 maxY) const; /** * Get the number of contiguous rows starting at y, valid for all values * of x between minX and maxX. */ qint32 numContiguousRows(qint32 y, qint32 minX, qint32 maxX) const; /** * Get the row stride at pixel (x, y). This is the number of bytes to add to a * pointer to pixel (x, y) to access (x, y + 1). */ qint32 rowStride(qint32 x, qint32 y) const; private: KisTileHashTable *m_hashTable; KisMementoManager *m_mementoManager; quint8* m_defaultPixel; qint32 m_pixelSize; KisTiledExtentManager m_extentManager; mutable QReadWriteLock m_lock; private: // Allow compression routines to calculate (col,row) coordinates // and pixel size friend class KisAbstractTileCompressor; friend class KisTileDataWrapper; qint32 xToCol(qint32 x) const; qint32 yToRow(qint32 y) const; private: void setDefaultPixelImpl(const quint8 *defPixel); bool writeTilesHeader(KisPaintDeviceWriter &store, quint32 numTiles); bool processTilesHeader(QIODevice *stream, quint32 &numTiles); qint32 divideRoundDown(qint32 x, const qint32 y) const; void recalculateExtent(); quint8* duplicatePixel(qint32 num, const quint8 *pixel); template void bitBltImpl(KisTiledDataManager *srcDM, const QRect &rect); template void bitBltRoughImpl(KisTiledDataManager *srcDM, const QRect &rect); void writeBytesBody(const quint8 *data, qint32 x, qint32 y, qint32 width, qint32 height, qint32 dataRowStride = -1); void readBytesBody(quint8 *data, qint32 x, qint32 y, qint32 width, qint32 height, qint32 dataRowStride = -1) const; template void writePlanarBytesBody(QVector planes, QVector channelsizes, qint32 x, qint32 y, qint32 w, qint32 h); QVector readPlanarBytesBody(QVector channelsizes, qint32 x, qint32 y, qint32 w, qint32 h) const; public: void debugPrintInfo() { m_mementoManager->debugPrintInfo(); } }; inline qint32 KisTiledDataManager::divideRoundDown(qint32 x, const qint32 y) const { /** * Equivalent to the following: * -(( -x + (y-1) ) / y) */ return x >= 0 ? x / y : -(((-x - 1) / y) + 1); } inline qint32 KisTiledDataManager::xToCol(qint32 x) const { return divideRoundDown(x, KisTileData::WIDTH); } inline qint32 KisTiledDataManager::yToRow(qint32 y) const { return divideRoundDown(y, KisTileData::HEIGHT); } // during development the following line helps to check the interface is correct // it should be safe to keep it here even during normal compilation //#include "kis_datamanager.h" #endif // KIS_TILEDDATAMANAGER_H_ diff --git a/libs/image/tiles3/tests/kis_lock_free_map_test.cpp b/libs/image/tiles3/tests/kis_lock_free_map_test.cpp index a702a9e2a2..a87087ee2f 100644 --- a/libs/image/tiles3/tests/kis_lock_free_map_test.cpp +++ b/libs/image/tiles3/tests/kis_lock_free_map_test.cpp @@ -1,187 +1,188 @@ #include "kis_lock_free_map_test.h" #include #include "kis_debug.h" #include "tiles3/kis_memento_item.h" #include "tiles3/kis_tile_hash_table2.h" #define NUM_THREADS 10 class Wrapper : public KisShared { public: Wrapper() : m_member(0) {} Wrapper(qint32 col, qint32 row, KisTileData *defaultTileData, KisMementoManager* mm) : m_member(col) {} qint32 member() { return m_member; } private: qint32 m_member; }; class StressJob : public QRunnable { public: StressJob(const std::function func) : m_func(func), m_eraseSum(0), m_insertSum(0) { } qint64 eraseSum() { return m_eraseSum; } qint64 insertSum() { return m_insertSum; } protected: void run() override { m_func(m_eraseSum, m_insertSum); } private: const std::function m_func; qint64 m_eraseSum; qint64 m_insertSum; }; void LockFreeMapTest::testMainOperations() { const qint32 numCycles = 60000; const qint32 numTypes = 3; QList jobs; KisTileHashTableTraits2 map; auto func = [&](qint64 & eraseSum, qint64 & insertSum) { for (qint32 i = 1; i < numCycles + 1; ++i) { auto type = i % numTypes; switch (type) { case 0: { auto result = map.erase(i - 2); if (result.data()) { eraseSum += result->member(); } break; } case 1: { auto result = map.insert(i, KisSharedPtr(new Wrapper(i, 0, 0, 0))); if (result.data()) { insertSum -= result->member(); } insertSum += i; break; } case 2: { map.get(i - 1); break; } } } }; for (qint32 i = 0; i < NUM_THREADS; ++i) { StressJob *job = new StressJob(func); job->setAutoDelete(false); jobs.append(job); } QThreadPool pool; pool.setMaxThreadCount(NUM_THREADS); QBENCHMARK { for (auto &job : jobs) { pool.start(job); } pool.waitForDone(); } qint64 insertSum = 0; qint64 eraseSum = 0; for (qint32 i = 0; i < NUM_THREADS; ++i) { StressJob *job = jobs.takeLast(); eraseSum += job->eraseSum(); insertSum += job->insertSum(); delete job; } QVERIFY(insertSum == eraseSum); } void LockFreeMapTest::testLazy() { const qint32 numCycles = 50000; const qint32 numTypes = 2; QList jobs; KisTileHashTableTraits2 map; auto func = [&](qint64 & eraseSum, qint64 & insertSum) { for (qint32 i = 1; i < numCycles + 1; ++i) { auto type = i % numTypes; switch (type) { case 0: { auto result = map.erase(i - 1); if (result.data()) { eraseSum += result->member(); } break; } case 1: { - auto result = map.getLazy(i, KisSharedPtr(new Wrapper())); + bool newTile = false; + auto result = map.getLazy(i, KisSharedPtr(new Wrapper()), newTile); if (result.data()) { insertSum += result->member(); } break; } } } }; for (qint32 i = 0; i < NUM_THREADS; ++i) { StressJob *job = new StressJob(func); job->setAutoDelete(false); jobs.append(job); } QThreadPool pool; pool.setMaxThreadCount(NUM_THREADS); QBENCHMARK { for (auto &job : jobs) { pool.start(job); } pool.waitForDone(); } qint64 insertSum = 0; qint64 eraseSum = 0; for (qint32 i = 0; i < NUM_THREADS; ++i) { StressJob *job = jobs.takeLast(); eraseSum += job->eraseSum(); insertSum += job->insertSum(); delete job; } QVERIFY(insertSum == eraseSum); } QTEST_GUILESS_MAIN(LockFreeMapTest) diff --git a/libs/ui/tests/FreehandStrokeBenchmark.cpp b/libs/ui/tests/FreehandStrokeBenchmark.cpp index ebc88596c1..d2bffb8032 100644 --- a/libs/ui/tests/FreehandStrokeBenchmark.cpp +++ b/libs/ui/tests/FreehandStrokeBenchmark.cpp @@ -1,141 +1,141 @@ /* * Copyright (c) 2017 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "FreehandStrokeBenchmark.h" #include #include #include #include "stroke_testing_utils.h" #include "strokes/freehand_stroke.h" #include "strokes/KisFreehandStrokeInfo.h" #include "kis_resources_snapshot.h" #include "kis_image.h" #include class FreehandStrokeBenchmarkTester : public utils::StrokeTester { public: FreehandStrokeBenchmarkTester(const QString &presetFilename) : StrokeTester("freehand_benchmark", QSize(5000, 5000), presetFilename) { setBaseFuzziness(3); } void setCpuCoresLimit(int value) { m_cpuCoresLimit = value; } protected: using utils::StrokeTester::initImage; void initImage(KisImageWSP image, KisNodeSP activeNode) override { Q_UNUSED(activeNode); if (m_cpuCoresLimit > 0) { image->setWorkingThreadsLimit(m_cpuCoresLimit); } } KisStrokeStrategy* createStroke(KisResourcesSnapshotSP resources, KisImageWSP image) override { Q_UNUSED(image); KisFreehandStrokeInfo *strokeInfo = new KisFreehandStrokeInfo(); QScopedPointer stroke( new FreehandStrokeStrategy(resources, strokeInfo, kundo2_noi18n("Freehand Stroke"))); return stroke.take(); } void addPaintingJobs(KisImageWSP image, KisResourcesSnapshotSP resources) override { addPaintingJobs(image, resources, 0); } void addPaintingJobs(KisImageWSP image, KisResourcesSnapshotSP resources, int iteration) override { Q_UNUSED(iteration); Q_UNUSED(resources); for (int y = 100; y < 4900; y += 300) { KisPaintInformation pi1; KisPaintInformation pi2; pi1 = KisPaintInformation(QPointF(100, y), 0.5); pi2 = KisPaintInformation(QPointF(4900, y + 100), 1.0); QScopedPointer data( new FreehandStrokeStrategy::Data(0, pi1, pi2)); image->addJob(strokeId(), data.take()); } image->addJob(strokeId(), new FreehandStrokeStrategy::UpdateData(true)); } private: KisFreehandStrokeInfo *m_strokeInfo; int m_cpuCoresLimit = -1; }; void benchmarkBrush(const QString &presetName) { FreehandStrokeBenchmarkTester tester(presetName); for (int i = 1; i <= QThread::idealThreadCount(); i++) { tester.setCpuCoresLimit(i); tester.benchmark(); qDebug() << qPrintable(QString("Cores: %1 Time: %2 (ms)").arg(i).arg(tester.lastStrokeTime())); } } #include void FreehandStrokeBenchmark::initTestCase() { KoResourcePaths::addResourceType("kis_brushes", "data", FILES_DATA_DIR); } void FreehandStrokeBenchmark::testDefaultTip() { - benchmarkBrush("testing_1000px_auto_deafult.kpp"); +// benchmarkBrush("testing_1000px_auto_deafult.kpp"); } void FreehandStrokeBenchmark::testSoftTip() { - benchmarkBrush("testing_1000px_auto_soft.kpp"); +// benchmarkBrush("testing_1000px_auto_soft.kpp"); } void FreehandStrokeBenchmark::testGaussianTip() { benchmarkBrush("testing_1000px_auto_gaussian.kpp"); } void FreehandStrokeBenchmark::testStampTip() { - benchmarkBrush("testing_1000px_stamp_450_rotated.kpp"); +// benchmarkBrush("testing_1000px_stamp_450_rotated.kpp"); } void FreehandStrokeBenchmark::testColorsmudgeDefaultTip() { - benchmarkBrush("testing_200px_colorsmudge_default.kpp"); +// benchmarkBrush("testing_200px_colorsmudge_default.kpp"); } QTEST_MAIN(FreehandStrokeBenchmark)