diff --git a/libs/image/kis_image_config.cpp b/libs/image/kis_image_config.cpp index 22784c4eef..3d70145f00 100644 --- a/libs/image/kis_image_config.cpp +++ b/libs/image/kis_image_config.cpp @@ -1,480 +1,505 @@ /* * 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_image_config.h" #include #include #include #include #include #include "kis_debug.h" #include #include #include #include #include "kis_global.h" #include #ifdef Q_OS_OSX #include #endif KisImageConfig::KisImageConfig(bool readOnly) : m_config( KSharedConfig::openConfig()->group(QString())), m_readOnly(readOnly) { +#ifdef Q_OS_OSX + // clear /var/folders/ swap path set by old broken Krita swap implementation in order to use new default swap dir. + QString swap = m_config.readEntry("swaplocation", ""); + if (swap.startsWith("/var/folders/")) { + m_config.deleteEntry("swaplocation"); + } +#endif } KisImageConfig::~KisImageConfig() { if (m_readOnly) return; if (qApp->thread() != QThread::currentThread()) { dbgKrita << "KisImageConfig: requested config synchronization from nonGUI thread! Called from" << kisBacktrace(); return; } m_config.sync(); } bool KisImageConfig::enableProgressReporting(bool requestDefault) const { return !requestDefault ? m_config.readEntry("enableProgressReporting", true) : true; } void KisImageConfig::setEnableProgressReporting(bool value) { m_config.writeEntry("enableProgressReporting", value); } bool KisImageConfig::enablePerfLog(bool requestDefault) const { return !requestDefault ? m_config.readEntry("enablePerfLog", false) :false; } void KisImageConfig::setEnablePerfLog(bool value) { m_config.writeEntry("enablePerfLog", value); } qreal KisImageConfig::transformMaskOffBoundsReadArea() const { return m_config.readEntry("transformMaskOffBoundsReadArea", 0.5); } int KisImageConfig::updatePatchHeight() const { return m_config.readEntry("updatePatchHeight", 512); } void KisImageConfig::setUpdatePatchHeight(int value) { m_config.writeEntry("updatePatchHeight", value); } int KisImageConfig::updatePatchWidth() const { return m_config.readEntry("updatePatchWidth", 512); } void KisImageConfig::setUpdatePatchWidth(int value) { m_config.writeEntry("updatePatchWidth", value); } qreal KisImageConfig::maxCollectAlpha() const { return m_config.readEntry("maxCollectAlpha", 2.5); } qreal KisImageConfig::maxMergeAlpha() const { return m_config.readEntry("maxMergeAlpha", 1.); } qreal KisImageConfig::maxMergeCollectAlpha() const { return m_config.readEntry("maxMergeCollectAlpha", 1.5); } qreal KisImageConfig::schedulerBalancingRatio() const { /** * updates-queue-size / strokes-queue-size */ return m_config.readEntry("schedulerBalancingRatio", 100.); } void KisImageConfig::setSchedulerBalancingRatio(qreal value) { m_config.writeEntry("schedulerBalancingRatio", value); } int KisImageConfig::maxSwapSize(bool requestDefault) const { return !requestDefault ? m_config.readEntry("maxSwapSize", 4096) : 4096; // in MiB } void KisImageConfig::setMaxSwapSize(int value) { m_config.writeEntry("maxSwapSize", value); } int KisImageConfig::swapSlabSize() const { return m_config.readEntry("swapSlabSize", 64); // in MiB } void KisImageConfig::setSwapSlabSize(int value) { m_config.writeEntry("swapSlabSize", value); } int KisImageConfig::swapWindowSize() const { return m_config.readEntry("swapWindowSize", 16); // in MiB } void KisImageConfig::setSwapWindowSize(int value) { m_config.writeEntry("swapWindowSize", value); } int KisImageConfig::tilesHardLimit() const { qreal hp = qreal(memoryHardLimitPercent()) / 100.0; qreal pp = qreal(memoryPoolLimitPercent()) / 100.0; return totalRAM() * hp * (1 - pp); } int KisImageConfig::tilesSoftLimit() const { qreal sp = qreal(memorySoftLimitPercent()) / 100.0; return tilesHardLimit() * sp; } int KisImageConfig::poolLimit() const { qreal hp = qreal(memoryHardLimitPercent()) / 100.0; qreal pp = qreal(memoryPoolLimitPercent()) / 100.0; return totalRAM() * hp * pp; } qreal KisImageConfig::memoryHardLimitPercent(bool requestDefault) const { return !requestDefault ? m_config.readEntry("memoryHardLimitPercent", 50.) : 50.; } void KisImageConfig::setMemoryHardLimitPercent(qreal value) { m_config.writeEntry("memoryHardLimitPercent", value); } qreal KisImageConfig::memorySoftLimitPercent(bool requestDefault) const { return !requestDefault ? m_config.readEntry("memorySoftLimitPercent", 2.) : 2.; } void KisImageConfig::setMemorySoftLimitPercent(qreal value) { m_config.writeEntry("memorySoftLimitPercent", value); } qreal KisImageConfig::memoryPoolLimitPercent(bool requestDefault) const { return !requestDefault ? m_config.readEntry("memoryPoolLimitPercent", 0.0) : 0.0; } void KisImageConfig::setMemoryPoolLimitPercent(qreal value) { m_config.writeEntry("memoryPoolLimitPercent", value); } QString KisImageConfig::swapDir(bool requestDefault) { +#ifdef Q_OS_OSX + // On OSX, QDir::tempPath() gives us a folder we cannot reply upon (usually + // something like /var/folders/.../...) and that will have vanished when we + // try to create the tmp file in KisMemoryWindow::KisMemoryWindow using + // swapFileTemplate. thus, we just pick the home folder if swapDir does not + // tell us otherwise. + + // the other option here would be to use a "garbled name" temp file (i.e. no name + // KRITA_SWAP_FILE_XXXXXX) in an obsure /var/folders place, which is not + // nice to the user. having a clearly named swap file in the home folder is + // much nicer to Krita's users. + + // furthermore, this is just a default and swapDir can always be configured + // to another location. + + QString swap = QDir::homePath(); +#else QString swap = QDir::tempPath(); +#endif return !requestDefault ? m_config.readEntry("swaplocation", swap) : swap; } void KisImageConfig::setSwapDir(const QString &swapDir) { m_config.writeEntry("swaplocation", swapDir); } int KisImageConfig::numberOfOnionSkins() const { return m_config.readEntry("numberOfOnionSkins", 10); } void KisImageConfig::setNumberOfOnionSkins(int value) { m_config.writeEntry("numberOfOnionSkins", value); } int KisImageConfig::onionSkinTintFactor() const { return m_config.readEntry("onionSkinTintFactor", 192); } void KisImageConfig::setOnionSkinTintFactor(int value) { m_config.writeEntry("onionSkinTintFactor", value); } int KisImageConfig::onionSkinOpacity(int offset) const { int value = m_config.readEntry("onionSkinOpacity_" + QString::number(offset), -1); if (value < 0) { const int num = numberOfOnionSkins(); const qreal dx = qreal(qAbs(offset)) / num; value = 0.7 * exp(-pow2(dx) / 0.5) * 255; } return value; } void KisImageConfig::setOnionSkinOpacity(int offset, int value) { m_config.writeEntry("onionSkinOpacity_" + QString::number(offset), value); } bool KisImageConfig::onionSkinState(int offset) const { bool enableByDefault = (qAbs(offset) <= 2); return m_config.readEntry("onionSkinState_" + QString::number(offset), enableByDefault); } void KisImageConfig::setOnionSkinState(int offset, bool value) { m_config.writeEntry("onionSkinState_" + QString::number(offset), value); } QColor KisImageConfig::onionSkinTintColorBackward() const { return m_config.readEntry("onionSkinTintColorBackward", QColor(Qt::red)); } void KisImageConfig::setOnionSkinTintColorBackward(const QColor &value) { m_config.writeEntry("onionSkinTintColorBackward", value); } QColor KisImageConfig::onionSkinTintColorForward() const { return m_config.readEntry("oninSkinTintColorForward", QColor(Qt::green)); } void KisImageConfig::setOnionSkinTintColorForward(const QColor &value) { m_config.writeEntry("oninSkinTintColorForward", value); } bool KisImageConfig::lazyFrameCreationEnabled(bool requestDefault) const { return !requestDefault ? m_config.readEntry("lazyFrameCreationEnabled", true) : true; } void KisImageConfig::setLazyFrameCreationEnabled(bool value) { m_config.writeEntry("lazyFrameCreationEnabled", value); } #if defined Q_OS_LINUX #include #elif defined Q_OS_FREEBSD || defined Q_OS_NETBSD || defined Q_OS_OPENBSD #include #elif defined Q_OS_WIN #include #elif defined Q_OS_OSX #include #include #endif #include int KisImageConfig::totalRAM() { // let's think that default memory size is 1000MiB int totalMemory = 1000; // MiB int error = 1; #if defined Q_OS_LINUX struct sysinfo info; error = sysinfo(&info); if(!error) { totalMemory = info.totalram * info.mem_unit / (1UL << 20); } #elif defined Q_OS_FREEBSD || defined Q_OS_NETBSD || defined Q_OS_OPENBSD u_long physmem; # if defined HW_PHYSMEM64 // NetBSD only int mib[] = {CTL_HW, HW_PHYSMEM64}; # else int mib[] = {CTL_HW, HW_PHYSMEM}; # endif size_t len = sizeof(physmem); error = sysctl(mib, 2, &physmem, &len, 0, 0); if(!error) { totalMemory = physmem >> 20; } #elif defined Q_OS_WIN MEMORYSTATUSEX status; status.dwLength = sizeof(status); error = !GlobalMemoryStatusEx(&status); if (!error) { totalMemory = status.ullTotalPhys >> 20; } // For 32 bit windows, the total memory available is at max the 2GB per process memory limit. # if defined ENV32BIT totalMemory = qMin(totalMemory, 2000); # endif #elif defined Q_OS_OSX int mib[2] = { CTL_HW, HW_MEMSIZE }; u_int namelen = sizeof(mib) / sizeof(mib[0]); uint64_t size; size_t len = sizeof(size); errno = 0; if (sysctl(mib, namelen, &size, &len, 0, 0) >= 0) { totalMemory = size >> 20; error = 0; } else { dbgKrita << "sysctl(\"hw.memsize\") raised error" << strerror(errno); } #endif if (error) { warnKrita << "Cannot get the size of your RAM. Using 1 GiB by default."; } return totalMemory; } bool KisImageConfig::showAdditionalOnionSkinsSettings(bool requestDefault) const { return !requestDefault ? m_config.readEntry("showAdditionalOnionSkinsSettings", true) : true; } void KisImageConfig::setShowAdditionalOnionSkinsSettings(bool value) { m_config.writeEntry("showAdditionalOnionSkinsSettings", value); } int KisImageConfig::defaultFrameColorLabel() const { return m_config.readEntry("defaultFrameColorLabel", 0); } void KisImageConfig::setDefaultFrameColorLabel(int label) { m_config.writeEntry("defaultFrameColorLabel", label); } KisProofingConfigurationSP KisImageConfig::defaultProofingconfiguration() { KisProofingConfiguration *proofingConfig= new KisProofingConfiguration(); proofingConfig->proofingProfile = m_config.readEntry("defaultProofingProfileName", "Chemical proof"); proofingConfig->proofingModel = m_config.readEntry("defaultProofingProfileModel", "CMYKA"); proofingConfig->proofingDepth = m_config.readEntry("defaultProofingProfileDepth", "U8"); proofingConfig->intent = (KoColorConversionTransformation::Intent)m_config.readEntry("defaultProofingProfileIntent", 3); if (m_config.readEntry("defaultProofingBlackpointCompensation", true)) { proofingConfig->conversionFlags |= KoColorConversionTransformation::ConversionFlag::BlackpointCompensation; } else { proofingConfig->conversionFlags = proofingConfig->conversionFlags & ~KoColorConversionTransformation::ConversionFlag::BlackpointCompensation; } QColor def(Qt::green); m_config.readEntry("defaultProofingGamutwarning", def); KoColor col(KoColorSpaceRegistry::instance()->rgb8()); col.fromQColor(def); col.setOpacity(1.0); proofingConfig->warningColor = col; proofingConfig->adaptationState = (double)m_config.readEntry("defaultProofingAdaptationState", 1.0); return toQShared(proofingConfig); } void KisImageConfig::setDefaultProofingConfig(const KoColorSpace *proofingSpace, int proofingIntent, bool blackPointCompensation, KoColor warningColor, double adaptationState) { m_config.writeEntry("defaultProofingProfileName", proofingSpace->profile()->name()); m_config.writeEntry("defaultProofingProfileModel", proofingSpace->colorModelId().id()); m_config.writeEntry("defaultProofingProfileDepth", proofingSpace->colorDepthId().id()); m_config.writeEntry("defaultProofingProfileIntent", proofingIntent); m_config.writeEntry("defaultProofingBlackpointCompensation", blackPointCompensation); QColor c; c = warningColor.toQColor(); m_config.writeEntry("defaultProofingGamutwarning", c); m_config.writeEntry("defaultProofingAdaptationState",adaptationState); } bool KisImageConfig::useLodForColorizeMask(bool requestDefault) const { return !requestDefault ? m_config.readEntry("useLodForColorizeMask", false) : false; } void KisImageConfig::setUseLodForColorizeMask(bool value) { m_config.writeEntry("useLodForColorizeMask", value); } int KisImageConfig::maxNumberOfThreads(bool defaultValue) const { return (defaultValue ? QThread::idealThreadCount() : m_config.readEntry("maxNumberOfThreads", QThread::idealThreadCount())); } void KisImageConfig::setMaxNumberOfThreads(int value) { if (value == QThread::idealThreadCount()) { m_config.deleteEntry("maxNumberOfThreads"); } else { m_config.writeEntry("maxNumberOfThreads", value); } } int KisImageConfig::frameRenderingClones(bool defaultValue) const { const int defaultClonesCount = qMax(1, maxNumberOfThreads(defaultValue) / 2); return defaultValue ? defaultClonesCount : m_config.readEntry("frameRenderingClones", defaultClonesCount); } void KisImageConfig::setFrameRenderingClones(int value) { m_config.writeEntry("frameRenderingClones", value); } diff --git a/libs/image/tiles3/kis_tile_data_store.cc b/libs/image/tiles3/kis_tile_data_store.cc index 4b2c1c5ad3..a541b6c045 100644 --- a/libs/image/tiles3/kis_tile_data_store.cc +++ b/libs/image/tiles3/kis_tile_data_store.cc @@ -1,359 +1,363 @@ /* * 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. */ // to disable assert when the leak tracker is active #include "config-memory-leak-tracker.h" #include #include "kis_tile_data_store.h" #include "kis_tile_data.h" #include "kis_debug.h" #include "kis_tile_data_store_iterators.h" Q_GLOBAL_STATIC(KisTileDataStore, s_instance) //#define DEBUG_PRECLONE #ifdef DEBUG_PRECLONE #include #define DEBUG_PRECLONE_ACTION(action, oldTD, newTD) \ printf("!!! %s:\t\t\t 0x%X -> 0x%X \t\t!!!\n", \ action, (quintptr)oldTD, (quintptr) newTD) #define DEBUG_FREE_ACTION(td) \ printf("Tile data free'd \t(0x%X)\n", td) #else #define DEBUG_PRECLONE_ACTION(action, oldTD, newTD) #define DEBUG_FREE_ACTION(td) #endif #ifdef DEBUG_HIT_MISS qint64 __preclone_miss = 0; qint64 __preclone_hit = 0; qint64 __preclone_miss_user_count = 0; qint64 __preclone_miss_age = 0; #define DEBUG_COUNT_PRECLONE_HIT(td) __preclone_hit++ #define DEBUG_COUNT_PRECLONE_MISS(td) __preclone_miss++; __preclone_miss_user_count+=td->numUsers(); __preclone_miss_age+=td->age() #define DEBUG_REPORT_PRECLONE_EFFICIENCY() \ dbgKrita << "Hits:" << __preclone_hit \ << "of" << __preclone_hit + __preclone_miss \ << "(" \ << qreal(__preclone_hit) / (__preclone_hit + __preclone_miss) \ << ")" \ << "miss users" << qreal(__preclone_miss_user_count) / __preclone_miss \ << "miss age" << qreal(__preclone_miss_age) / __preclone_miss #else #define DEBUG_COUNT_PRECLONE_HIT(td) #define DEBUG_COUNT_PRECLONE_MISS(td) #define DEBUG_REPORT_PRECLONE_EFFICIENCY() #endif KisTileDataStore::KisTileDataStore() : m_pooler(this), m_swapper(this), m_numTiles(0), m_memoryMetric(0) { m_clockIterator = m_tileDataList.end(); m_pooler.start(); m_swapper.start(); } KisTileDataStore::~KisTileDataStore() { m_pooler.terminatePooler(); m_swapper.terminateSwapper(); if(numTiles() > 0) { errKrita << "Warning: some tiles have leaked:"; errKrita << "\tTiles in memory:" << numTilesInMemory() << "\n" << "\tTotal tiles:" << numTiles(); } } KisTileDataStore* KisTileDataStore::instance() { return s_instance; } KisTileDataStore::MemoryStatistics KisTileDataStore::memoryStatistics() { // in case the pooler is disabled, we should force it // to update the stats if (!m_pooler.isRunning()) { m_pooler.forceUpdateMemoryStats(); } QMutexLocker lock(&m_listLock); MemoryStatistics stats; const qint64 metricCoeff = KisTileData::WIDTH * KisTileData::HEIGHT; stats.realMemorySize = m_pooler.lastRealMemoryMetric() * metricCoeff; stats.historicalMemorySize = m_pooler.lastHistoricalMemoryMetric() * metricCoeff; stats.poolSize = m_pooler.lastPoolMemoryMetric() * metricCoeff; stats.totalMemorySize = memoryMetric() * metricCoeff + stats.poolSize; stats.swapSize = m_swappedStore.totalMemoryMetric() * metricCoeff; return stats; } inline void KisTileDataStore::registerTileDataImp(KisTileData *td) { td->m_listIterator = m_tileDataList.insert(m_tileDataList.end(), td); m_numTiles++; m_memoryMetric += td->pixelSize(); } void KisTileDataStore::registerTileData(KisTileData *td) { QMutexLocker lock(&m_listLock); registerTileDataImp(td); } inline void KisTileDataStore::unregisterTileDataImp(KisTileData *td) { KisTileDataListIterator tempIterator = td->m_listIterator; if(m_clockIterator == tempIterator) { m_clockIterator = tempIterator + 1; } td->m_listIterator = m_tileDataList.end(); m_tileDataList.erase(tempIterator); m_numTiles--; m_memoryMetric -= td->pixelSize(); } void KisTileDataStore::unregisterTileData(KisTileData *td) { QMutexLocker lock(&m_listLock); unregisterTileDataImp(td); } KisTileData *KisTileDataStore::allocTileData(qint32 pixelSize, const quint8 *defPixel) { KisTileData *td = new KisTileData(pixelSize, defPixel, this); registerTileData(td); return td; } KisTileData *KisTileDataStore::duplicateTileData(KisTileData *rhs) { KisTileData *td = 0; if (rhs->m_clonesStack.pop(td)) { DEBUG_PRECLONE_ACTION("+ Pre-clone HIT", rhs, td); DEBUG_COUNT_PRECLONE_HIT(rhs); } else { rhs->blockSwapping(); td = new KisTileData(*rhs); rhs->unblockSwapping(); DEBUG_PRECLONE_ACTION("- Pre-clone #MISS#", rhs, td); DEBUG_COUNT_PRECLONE_MISS(rhs); } registerTileData(td); return td; } void KisTileDataStore::freeTileData(KisTileData *td) { Q_ASSERT(td->m_store == this); DEBUG_FREE_ACTION(td); m_listLock.lock(); td->m_swapLock.lockForWrite(); if(!td->data()) { m_swappedStore.forgetTileData(td); } else { unregisterTileDataImp(td); } td->m_swapLock.unlock(); m_listLock.unlock(); delete td; } void KisTileDataStore::ensureTileDataLoaded(KisTileData *td) { // dbgKrita << "#### SWAP MISS! ####" << td << ppVar(td->mementoed()) << ppVar(td->age()) << ppVar(td->numUsers()); checkFreeMemory(); td->m_swapLock.lockForRead(); while(!td->data()) { td->m_swapLock.unlock(); /** * The order of this heavy locking is very important. * Change it only in case, you really know what you are doing. */ m_listLock.lock(); /** * If someone has managed to load the td from swap, then, most * probably, they have already taken the swap lock. This may * lead to a deadlock, because COW mechanism breaks lock * ordering rules in duplicateTileData() (it takes m_listLock * while the swap lock is held). In our case it is enough just * to check whether the other thread has already fetched the * data. Please notice that we do not take both of the locks * while checking this, because holding m_listLock is * enough. Nothing can happen to the tile while we hold * m_listLock. */ if(!td->data()) { td->m_swapLock.lockForWrite(); m_swappedStore.swapInTileData(td); registerTileDataImp(td); td->m_swapLock.unlock(); } m_listLock.unlock(); /** * <-- In theory, livelock is possible here... */ td->m_swapLock.lockForRead(); } } bool KisTileDataStore::trySwapTileData(KisTileData *td) { /** * This function is called with m_listLock acquired */ bool result = false; if(!td->m_swapLock.tryLockForWrite()) return result; if(td->data()) { unregisterTileDataImp(td); - m_swappedStore.swapOutTileData(td); - result = true; + if (m_swappedStore.trySwapOutTileData(td)) { + result = true; + } else { + result = false; + registerTileDataImp(td); + } } td->m_swapLock.unlock(); return result; } KisTileDataStoreIterator* KisTileDataStore::beginIteration() { m_listLock.lock(); return new KisTileDataStoreIterator(m_tileDataList, this); } void KisTileDataStore::endIteration(KisTileDataStoreIterator* iterator) { delete iterator; m_listLock.unlock(); } KisTileDataStoreReverseIterator* KisTileDataStore::beginReverseIteration() { m_listLock.lock(); return new KisTileDataStoreReverseIterator(m_tileDataList, this); } void KisTileDataStore::endIteration(KisTileDataStoreReverseIterator* iterator) { delete iterator; m_listLock.unlock(); DEBUG_REPORT_PRECLONE_EFFICIENCY(); } KisTileDataStoreClockIterator* KisTileDataStore::beginClockIteration() { m_listLock.lock(); return new KisTileDataStoreClockIterator(m_clockIterator, m_tileDataList, this); } void KisTileDataStore::endIteration(KisTileDataStoreClockIterator* iterator) { m_clockIterator = iterator->getFinalPosition(); delete iterator; m_listLock.unlock(); } void KisTileDataStore::debugPrintList() { KisTileData *item; Q_FOREACH (item, m_tileDataList) { dbgTiles << "-------------------------\n" << "TileData:\t\t\t" << item << "\n refCount:\t" << item->m_refCount; } } void KisTileDataStore::debugSwapAll() { KisTileDataStoreIterator* iter = beginIteration(); KisTileData *item; while(iter->hasNext()) { item = iter->next(); iter->trySwapOut(item); } endIteration(iter); // dbgKrita << "Number of tiles:" << numTiles(); // dbgKrita << "Tiles in memory:" << numTilesInMemory(); // m_swappedStore.debugStatistics(); } void KisTileDataStore::debugClear() { QMutexLocker lock(&m_listLock); Q_FOREACH (KisTileData *item, m_tileDataList) { delete item; } m_tileDataList.clear(); m_clockIterator = m_tileDataList.end(); m_numTiles = 0; m_memoryMetric = 0; } void KisTileDataStore::testingRereadConfig() { m_pooler.testingRereadConfig(); m_swapper.testingRereadConfig(); kickPooler(); } void KisTileDataStore::testingSuspendPooler() { m_pooler.terminatePooler(); } void KisTileDataStore::testingResumePooler() { m_pooler.start(); } diff --git a/libs/image/tiles3/swap/kis_memory_window.cpp b/libs/image/tiles3/swap/kis_memory_window.cpp index 5e36690078..8948bc8175 100644 --- a/libs/image/tiles3/swap/kis_memory_window.cpp +++ b/libs/image/tiles3/swap/kis_memory_window.cpp @@ -1,124 +1,150 @@ /* * 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_debug.h" #include "kis_memory_window.h" #include #define SWP_PREFIX "KRITA_SWAP_FILE_XXXXXX" KisMemoryWindow::KisMemoryWindow(const QString &swapDir, quint64 writeWindowSize) : m_readWindowEx(writeWindowSize / 4), m_writeWindowEx(writeWindowSize) { - QString swapFileTemplate = (swapDir.isEmpty() ? QDir::tempPath() : swapDir) + QDir::separator() + SWP_PREFIX; - QDir d(swapDir.isEmpty() ? QDir::tempPath() : swapDir); + m_valid = true; + + // swapDir will never be empty, as KisImageConfig::swapDir() always provides + // us with a (platform specific) default directory, even if none is explicity + // configured by the user; also we do not want any logic that determines the + // default swap dir here. + Q_ASSERT(!swapDir.isEmpty()); + + QDir d(swapDir); if (!d.exists()) { - d.mkpath(swapDir.isEmpty() ? QDir::tempPath() : swapDir); + m_valid = d.mkpath(swapDir); } - m_file.setFileTemplate(swapFileTemplate); - bool res = m_file.open(); - Q_ASSERT(res); - Q_ASSERT(!m_file.fileName().isEmpty()); - if (!res || m_file.fileName().isEmpty()) { - qWarning() << "Could not create or open swapfile"; + + const QString swapFileTemplate = swapDir + QDir::separator() + SWP_PREFIX; + + if (m_valid) { + m_file.setFileTemplate(swapFileTemplate); + bool res = m_file.open(); + if (!res || m_file.fileName().isEmpty()) { + m_valid = false; + } + } + + if (!m_valid) { + qWarning() << "Could not create or open swapfile; disabling swapfile" << swapFileTemplate; } } KisMemoryWindow::~KisMemoryWindow() { } quint8* KisMemoryWindow::getReadChunkPtr(const KisChunkData &readChunk) { - adjustWindow(readChunk, &m_readWindowEx, &m_writeWindowEx); + if (!adjustWindow(readChunk, &m_readWindowEx, &m_writeWindowEx)) { + return nullptr; + } return m_readWindowEx.calculatePointer(readChunk); } quint8* KisMemoryWindow::getWriteChunkPtr(const KisChunkData &writeChunk) { - adjustWindow(writeChunk, &m_writeWindowEx, &m_readWindowEx); + if (!adjustWindow(writeChunk, &m_writeWindowEx, &m_readWindowEx)) { + return nullptr; + } return m_writeWindowEx.calculatePointer(writeChunk); } -void KisMemoryWindow::adjustWindow(const KisChunkData &requestedChunk, +bool KisMemoryWindow::adjustWindow(const KisChunkData &requestedChunk, MappingWindow *adjustingWindow, MappingWindow *otherWindow) { if(!(adjustingWindow->window) || !(requestedChunk.m_begin >= adjustingWindow->chunk.m_begin && requestedChunk.m_end <= adjustingWindow->chunk.m_end)) { m_file.unmap(adjustingWindow->window); quint64 windowSize = adjustingWindow->defaultSize; if(requestedChunk.size() > windowSize) { warnKrita << "KisMemoryWindow: the requested chunk is too " "big to fit into the mapping! " "Adjusting mapping to avoid SIGSEGV..."; windowSize = requestedChunk.size(); } adjustingWindow->chunk.setChunk(requestedChunk.m_begin, windowSize); if(adjustingWindow->chunk.m_end >= (quint64)m_file.size()) { // Align by 32 bytes quint64 newSize = (adjustingWindow->chunk.m_end + 1 + 32) & (~31ULL); #ifdef Q_OS_WIN32 /** * Workaround for Qt's "feature" * * On windows QFSEnginePrivate caches the value of * mapHandle which is limited to the size of the file at * the moment of its (handle's) creation. That is we will * not be able to use it after resizing the file. The * only way to free the handle is to release all the * mappings we have. Sad but true. */ if (otherWindow->chunk.size()) { m_file.unmap(otherWindow->window); } #else Q_UNUSED(otherWindow); #endif - m_file.resize(newSize); + if (!m_file.resize(newSize)) { + return false; + } #ifdef Q_OS_WIN32 if (otherWindow->chunk.size()) { otherWindow->window = m_file.map(otherWindow->chunk.m_begin, otherWindow->chunk.size()); } #endif } #ifdef Q_OS_UNIX // A workaround for https://bugreports.qt-project.org/browse/QTBUG-6330 m_file.exists(); #endif adjustingWindow->window = m_file.map(adjustingWindow->chunk.m_begin, adjustingWindow->chunk.size()); + + if (!adjustingWindow->window) { + return false; + } } + + return true; } diff --git a/libs/image/tiles3/swap/kis_memory_window.h b/libs/image/tiles3/swap/kis_memory_window.h index b52ea17dda..4a80fab833 100644 --- a/libs/image/tiles3/swap/kis_memory_window.h +++ b/libs/image/tiles3/swap/kis_memory_window.h @@ -1,81 +1,82 @@ /* * 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_MEMORY_WINDOW_H #define __KIS_MEMORY_WINDOW_H #include #include "kis_chunk_allocator.h" #define DEFAULT_WINDOW_SIZE (16*MiB) class KisMemoryWindow { public: /** * @param swapDir. If the dir doesn't exist, it'll be created, if it's empty QDir::tempPath will be used. */ KisMemoryWindow(const QString &swapDir, quint64 writeWindowSize = DEFAULT_WINDOW_SIZE); ~KisMemoryWindow(); inline quint8* getReadChunkPtr(KisChunk readChunk) { return getReadChunkPtr(readChunk.data()); } inline quint8* getWriteChunkPtr(KisChunk writeChunk) { return getWriteChunkPtr(writeChunk.data()); } quint8* getReadChunkPtr(const KisChunkData &readChunk); quint8* getWriteChunkPtr(const KisChunkData &writeChunk); private: struct MappingWindow { MappingWindow(quint64 _defaultSize) : chunk(0,0), window(0), defaultSize(_defaultSize) { } quint8* calculatePointer(const KisChunkData &other) const { return window + other.m_begin - chunk.m_begin; } KisChunkData chunk; quint8 *window; const quint64 defaultSize; }; private: - void adjustWindow(const KisChunkData &requestedChunk, + bool adjustWindow(const KisChunkData &requestedChunk, MappingWindow *adjustingWindow, MappingWindow *otherWindow); private: QTemporaryFile m_file; + bool m_valid; MappingWindow m_readWindowEx; MappingWindow m_writeWindowEx; }; #endif /* __KIS_MEMORY_WINDOW_H */ diff --git a/libs/image/tiles3/swap/kis_swapped_data_store.cpp b/libs/image/tiles3/swap/kis_swapped_data_store.cpp index 6e82b6c63f..0962f33fe1 100644 --- a/libs/image/tiles3/swap/kis_swapped_data_store.cpp +++ b/libs/image/tiles3/swap/kis_swapped_data_store.cpp @@ -1,124 +1,131 @@ /* * 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_debug.h" #include "kis_swapped_data_store.h" #include "kis_memory_window.h" #include "kis_image_config.h" #include "kis_tile_compressor_2.h" //#define COMPRESSOR_VERSION 2 KisSwappedDataStore::KisSwappedDataStore() : m_memoryMetric(0) { KisImageConfig config; const quint64 maxSwapSize = config.maxSwapSize() * MiB; const quint64 swapSlabSize = config.swapSlabSize() * MiB; const quint64 swapWindowSize = config.swapWindowSize() * MiB; m_allocator = new KisChunkAllocator(swapSlabSize, maxSwapSize); m_swapSpace = new KisMemoryWindow(config.swapDir(), swapWindowSize); // FIXME: use a factory after the patch is committed m_compressor = new KisTileCompressor2(); } KisSwappedDataStore::~KisSwappedDataStore() { delete m_compressor; delete m_swapSpace; delete m_allocator; } quint64 KisSwappedDataStore::numTiles() const { // We are not acquiring the lock here... // Hope QLinkedList will ensure atomic access to it's size... return m_allocator->numChunks(); } -void KisSwappedDataStore::swapOutTileData(KisTileData *td) +bool KisSwappedDataStore::trySwapOutTileData(KisTileData *td) { Q_ASSERT(td->data()); QMutexLocker locker(&m_lock); /** * We are expecting that the lock of KisTileData * has already been taken by the caller for us. * So we can modify the tile data freely. */ const qint32 expectedBufferSize = m_compressor->tileDataBufferSize(td); if(m_buffer.size() < expectedBufferSize) m_buffer.resize(expectedBufferSize); qint32 bytesWritten; m_compressor->compressTileData(td, (quint8*) m_buffer.data(), m_buffer.size(), bytesWritten); KisChunk chunk = m_allocator->getChunk(bytesWritten); quint8 *ptr = m_swapSpace->getWriteChunkPtr(chunk); + if (!ptr) { + qWarning() << "swap out of tile failed"; + return false; + } memcpy(ptr, m_buffer.data(), bytesWritten); td->releaseMemory(); td->setSwapChunk(chunk); m_memoryMetric += td->pixelSize(); + + return true; } void KisSwappedDataStore::swapInTileData(KisTileData *td) { Q_ASSERT(!td->data()); QMutexLocker locker(&m_lock); // see comment in swapOutTileData() KisChunk chunk = td->swapChunk(); td->allocateMemory(); td->setSwapChunk(KisChunk()); quint8 *ptr = m_swapSpace->getReadChunkPtr(chunk); + Q_ASSERT(ptr); m_compressor->decompressTileData(ptr, chunk.size(), td); m_allocator->freeChunk(chunk); m_memoryMetric -= td->pixelSize(); } void KisSwappedDataStore::forgetTileData(KisTileData *td) { QMutexLocker locker(&m_lock); m_allocator->freeChunk(td->swapChunk()); td->setSwapChunk(KisChunk()); m_memoryMetric -= td->pixelSize(); } qint64 KisSwappedDataStore::totalMemoryMetric() const { return m_memoryMetric; } void KisSwappedDataStore::debugStatistics() { m_allocator->sanityCheck(); m_allocator->debugFragmentation(); } diff --git a/libs/image/tiles3/swap/kis_swapped_data_store.h b/libs/image/tiles3/swap/kis_swapped_data_store.h index 5862203784..ebf96788da 100644 --- a/libs/image/tiles3/swap/kis_swapped_data_store.h +++ b/libs/image/tiles3/swap/kis_swapped_data_store.h @@ -1,92 +1,92 @@ /* * 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_SWAPPED_DATA_STORE_H #define __KIS_SWAPPED_DATA_STORE_H #include "kritaimage_export.h" #include #include class QMutex; class KisTileData; class KisAbstractTileCompressor; class KisChunkAllocator; class KisMemoryWindow; class KRITAIMAGE_EXPORT KisSwappedDataStore { public: KisSwappedDataStore(); ~KisSwappedDataStore(); /** * Returns number of swapped out tile data objects */ quint64 numTiles() const; /** * Swap out the data stored in the \a td to the swap file * and free memory occupied by td->data(). * LOCKING: the lock on the tile data should be taken * by the caller before making a call. */ - void swapOutTileData(KisTileData *td); + bool trySwapOutTileData(KisTileData *td); /** * Restore the data of a \a td basing on information * stored in the swap file. * LOCKING: the lock on the tile data should be taken * by the caller before making a call. */ void swapInTileData(KisTileData *td); /** * Forget all the information linked with the tile data. * This should be done before deleting of the tile data, * whose actual data is swapped-out */ void forgetTileData(KisTileData *td); /** * Retorns the metric of the total memory stored in the swap * in *uncompressed* form! */ qint64 totalMemoryMetric() const; /** * Some debugging output */ void debugStatistics(); private: QByteArray m_buffer; KisAbstractTileCompressor *m_compressor; KisChunkAllocator *m_allocator; KisMemoryWindow *m_swapSpace; QMutex m_lock; qint64 m_memoryMetric; }; #endif /* __KIS_SWAPPED_DATA_STORE_H */ diff --git a/libs/image/tiles3/tests/kis_swapped_data_store_test.cpp b/libs/image/tiles3/tests/kis_swapped_data_store_test.cpp index 1f4c1afcd6..35c55033b5 100644 --- a/libs/image/tiles3/tests/kis_swapped_data_store_test.cpp +++ b/libs/image/tiles3/tests/kis_swapped_data_store_test.cpp @@ -1,135 +1,135 @@ /* * 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_swapped_data_store_test.h" #include #include "kis_debug.h" #include "kis_image_config.h" #include "tiles3/kis_tile_data.h" #include "tiles_test_utils.h" #include "tiles3/kis_tile_data_store.h" #define COLUMN2COLOR(col) (col%255) void KisSwappedDataStoreTest::testRoundTrip() { const qint32 pixelSize = 1; const quint8 defaultPixel = 128; const qint32 NUM_TILES = 10000; KisImageConfig config; config.setMaxSwapSize(4); config.setSwapSlabSize(1); config.setSwapWindowSize(1); KisSwappedDataStore store; QList tileDataList; for(qint32 i = 0; i < NUM_TILES; i++) tileDataList.append(new KisTileData(pixelSize, &defaultPixel, KisTileDataStore::instance())); for(qint32 i = 0; i < NUM_TILES; i++) { KisTileData *td = tileDataList[i]; QVERIFY(memoryIsFilled(defaultPixel, td->data(), TILESIZE)); memset(td->data(), COLUMN2COLOR(i), TILESIZE); QVERIFY(memoryIsFilled(COLUMN2COLOR(i), td->data(), TILESIZE)); // FIXME: take a lock of the tile data - store.swapOutTileData(td); + QVERIFY(store.trySwapOutTileData(td)); } store.debugStatistics(); for(qint32 i = 0; i < NUM_TILES; i++) { KisTileData *td = tileDataList[i]; QVERIFY(!td->data()); // TODO: check num clones // FIXME: take a lock of the tile data store.swapInTileData(td); QVERIFY(memoryIsFilled(COLUMN2COLOR(i), td->data(), TILESIZE)); } store.debugStatistics(); for(qint32 i = 0; i < NUM_TILES; i++) delete tileDataList[i]; } void KisSwappedDataStoreTest::processTileData(qint32 column, KisTileData *td, KisSwappedDataStore &store) { if(td->data()) { memset(td->data(), COLUMN2COLOR(column), TILESIZE); QVERIFY(memoryIsFilled(COLUMN2COLOR(column), td->data(), TILESIZE)); // FIXME: take a lock of the tile data - store.swapOutTileData(td); + QVERIFY(store.trySwapOutTileData(td)); } else { // TODO: check num clones // FIXME: take a lock of the tile data store.swapInTileData(td); QVERIFY(memoryIsFilled(COLUMN2COLOR(column), td->data(), TILESIZE)); } } void KisSwappedDataStoreTest::testRandomAccess() { qsrand(10); const qint32 pixelSize = 1; const quint8 defaultPixel = 128; const qint32 NUM_CYCLES = 50000; const qint32 NUM_TILES = 10000; KisImageConfig config; config.setMaxSwapSize(40); config.setSwapSlabSize(1); config.setSwapWindowSize(1); KisSwappedDataStore store; QList tileDataList; for(qint32 i = 0; i < NUM_TILES; i++) tileDataList.append(new KisTileData(pixelSize, &defaultPixel, KisTileDataStore::instance())); for(qint32 i = 0; i < NUM_CYCLES; i++) { if(!(i%5000)) dbgKrita << i << "of" << NUM_CYCLES; qint32 col = qrand() % NUM_TILES; KisTileData *td = tileDataList[col]; processTileData(col, td, store); } store.debugStatistics(); for(qint32 i = 0; i < NUM_TILES; i++) delete tileDataList[i]; } QTEST_MAIN(KisSwappedDataStoreTest)