File indexing completed on 2024-06-09 04:22:22

0001 /*
0002  *  SPDX-FileCopyrightText: 2009 Dmitry Kazakov <dimula73@gmail.com>
0003  *  SPDX-FileCopyrightText: 2018 Andrey Kamakin <a.kamakin@icloud.com>
0004  *
0005  *  SPDX-License-Identifier: GPL-2.0-or-later
0006  */
0007 
0008 
0009 #include "kis_tile_data.h"
0010 #include "kis_tile_data_store.h"
0011 
0012 #include <kis_debug.h>
0013 
0014 #include <boost/pool/singleton_pool.hpp>
0015 #include "kis_tile_data_store_iterators.h"
0016 
0017 // BPP == bytes per pixel
0018 #define TILE_SIZE_4BPP (4 * __TILE_DATA_WIDTH * __TILE_DATA_HEIGHT)
0019 #define TILE_SIZE_8BPP (8 * __TILE_DATA_WIDTH * __TILE_DATA_HEIGHT)
0020 
0021 typedef boost::singleton_pool<KisTileData, TILE_SIZE_4BPP, boost::default_user_allocator_new_delete, boost::details::pool::default_mutex, 256, 4096> BoostPool4BPP;
0022 typedef boost::singleton_pool<KisTileData, TILE_SIZE_8BPP, boost::default_user_allocator_new_delete, boost::details::pool::default_mutex, 128, 2048> BoostPool8BPP;
0023 
0024 const qint32 KisTileData::WIDTH = __TILE_DATA_WIDTH;
0025 const qint32 KisTileData::HEIGHT = __TILE_DATA_HEIGHT;
0026 
0027 SimpleCache KisTileData::m_cache;
0028 
0029 SimpleCache::~SimpleCache()
0030 {
0031     clear();
0032 }
0033 
0034 void SimpleCache::clear()
0035 {
0036     QWriteLocker l(&m_cacheLock);
0037     quint8 *ptr = 0;
0038 
0039     while (m_4Pool.pop(ptr)) {
0040         BoostPool4BPP::free(ptr);
0041     }
0042 
0043     while (m_8Pool.pop(ptr)) {
0044         BoostPool8BPP::free(ptr);
0045     }
0046 
0047     while (m_16Pool.pop(ptr)) {
0048         free(ptr);
0049     }
0050 }
0051 
0052 
0053 KisTileData::KisTileData(qint32 pixelSize, const quint8 *defPixel, KisTileDataStore *store, bool checkFreeMemory)
0054     : m_state(NORMAL),
0055       m_mementoFlag(0),
0056       m_age(0),
0057       m_usersCount(0),
0058       m_refCount(0),
0059       m_pixelSize(pixelSize),
0060       m_store(store)
0061 {
0062     if (checkFreeMemory) {
0063         m_store->checkFreeMemory();
0064     }
0065     m_data = allocateData(m_pixelSize);
0066 
0067     fillWithPixel(defPixel);
0068 }
0069 
0070 
0071 /**
0072  * Duplicating tiledata
0073  * + new object loaded in memory
0074  * + it's unlocked and has refCount==0
0075  *
0076  * NOTE: the memory allocated by the pooler for clones is not counted
0077  * by the store in memoryHardLimit. The pooler has it's own slice of
0078  * memory and keeps track of the its size itself. So we should be able
0079  * to disable the memory check with checkFreeMemory, otherwise, there
0080  * is a deadlock.
0081  */
0082 KisTileData::KisTileData(const KisTileData& rhs, bool checkFreeMemory)
0083     : m_state(NORMAL),
0084       m_mementoFlag(0),
0085       m_age(0),
0086       m_usersCount(0),
0087       m_refCount(0),
0088       m_pixelSize(rhs.m_pixelSize),
0089       m_store(rhs.m_store)
0090 {
0091     if (checkFreeMemory) {
0092         m_store->checkFreeMemory();
0093     }
0094     m_data = allocateData(m_pixelSize);
0095 
0096     memcpy(m_data, rhs.data(), m_pixelSize * WIDTH * HEIGHT);
0097 }
0098 
0099 
0100 KisTileData::~KisTileData()
0101 {
0102     releaseMemory();
0103 }
0104 
0105 void KisTileData::fillWithPixel(const quint8 *defPixel)
0106 {
0107     quint8 *it = m_data;
0108 
0109     for (int i = 0; i < WIDTH * HEIGHT; i++, it += m_pixelSize) {
0110         memcpy(it, defPixel, m_pixelSize);
0111     }
0112 }
0113 
0114 void KisTileData::releaseMemory()
0115 {
0116     if (m_data) {
0117         freeData(m_data, m_pixelSize);
0118         m_data = 0;
0119     }
0120 
0121     KisTileData *clone = 0;
0122     while (m_clonesStack.pop(clone)) {
0123         delete clone;
0124     }
0125 
0126     Q_ASSERT(m_clonesStack.isEmpty());
0127 }
0128 
0129 void KisTileData::allocateMemory()
0130 {
0131     Q_ASSERT(!m_data);
0132     m_data = allocateData(m_pixelSize);
0133 }
0134 
0135 quint8* KisTileData::allocateData(const qint32 pixelSize)
0136 {
0137     quint8 *ptr = 0;
0138 
0139     if (!m_cache.pop(pixelSize, ptr)) {
0140         switch (pixelSize) {
0141         case 4:
0142             ptr = (quint8*)BoostPool4BPP::malloc();
0143             break;
0144         case 8:
0145             ptr = (quint8*)BoostPool8BPP::malloc();
0146             break;
0147         default:
0148             ptr = (quint8*) malloc(pixelSize * WIDTH * HEIGHT);
0149             break;
0150         }
0151     }
0152 
0153     return ptr;
0154 }
0155 
0156 void KisTileData::freeData(quint8* ptr, const qint32 pixelSize)
0157 {
0158     if (!m_cache.push(pixelSize, ptr)) {
0159         switch (pixelSize) {
0160         case 4:
0161             BoostPool4BPP::free(ptr);
0162             break;
0163         case 8:
0164             BoostPool8BPP::free(ptr);
0165             break;
0166         default:
0167             free(ptr);
0168             break;
0169         }
0170     }
0171 }
0172 
0173 //#define DEBUG_POOL_RELEASE
0174 
0175 #ifdef DEBUG_POOL_RELEASE
0176 #include <unistd.h>
0177 #endif /* DEBUG_POOL_RELEASE */
0178 
0179 void KisTileData::releaseInternalPools()
0180 {
0181     const int maxMigratedTiles = 100;
0182 
0183     if (KisTileDataStore::instance()->numTilesInMemory() < maxMigratedTiles) {
0184 
0185         QVector<KisTileData*> dataObjects;
0186         QVector<QByteArray> memoryChunks;
0187         bool failedToLock = false;
0188 
0189         KisTileDataStoreIterator *iter = KisTileDataStore::instance()->beginIteration();
0190 
0191         while (iter->hasNext()) {
0192             KisTileData *item = iter->next();
0193 
0194             // first release all the clones
0195             KisTileData *clone = 0;
0196             while (item->m_clonesStack.pop(clone)) {
0197                 delete clone;
0198             }
0199 
0200             // check if the tile data has actually been pooled
0201             if (item->m_pixelSize != 4 &&
0202                 item->m_pixelSize != 8 &&
0203                 item->m_pixelSize != 16) {
0204 
0205                 continue;
0206             }
0207 
0208             // check if the tile has been swapped out
0209             if (item->m_data) {
0210                 const bool locked = item->m_swapLock.tryLockForWrite();
0211                 if (!locked) {
0212                     failedToLock = true;
0213                     break;
0214                 }
0215 
0216                 const int chunkSize = item->m_pixelSize * WIDTH * HEIGHT;
0217                 dataObjects << item;
0218                 memoryChunks << QByteArray((const char*)item->m_data, chunkSize);
0219             }
0220 
0221         }
0222 
0223         if (!failedToLock) {
0224             // purge the pools memory
0225             m_cache.clear();
0226             BoostPool4BPP::purge_memory();
0227             BoostPool8BPP::purge_memory();
0228 
0229             auto it = dataObjects.begin();
0230             auto chunkIt = memoryChunks.constBegin();
0231 
0232             for (; it != dataObjects.end(); ++it, ++chunkIt) {
0233                 KisTileData *item = *it;
0234                 const int chunkSize = item->m_pixelSize * WIDTH * HEIGHT;
0235 
0236                 item->m_data = allocateData(item->m_pixelSize);
0237                 memcpy(item->m_data, chunkIt->data(), chunkSize);
0238 
0239                 item->m_swapLock.unlock();
0240             }
0241         } else {
0242             Q_FOREACH (KisTileData *item, dataObjects) {
0243                 item->m_swapLock.unlock();
0244             }
0245 
0246             warnKrita << "WARNING: Failed to lock the tiles while trying to release the pooled memory";
0247         }
0248 
0249         KisTileDataStore::instance()->endIteration(iter);
0250 
0251 #ifdef DEBUG_POOL_RELEASE
0252         dbgKrita << "After purging unused memory:";
0253 
0254         char command[256];
0255         sprintf(command, "cat /proc/%d/status | grep -i vm", (int)getpid());
0256         printf("--- %s ---\n", command);
0257         (void)system(command);
0258 #endif /* DEBUG_POOL_RELEASE */
0259 
0260     } else {
0261         dbgKrita << "DEBUG: releasing of the pooled memory has been cancelled:"
0262                  << "there are still"
0263                  << KisTileDataStore::instance()->numTilesInMemory()
0264                  << "tiles in memory";
0265     }
0266 }