File indexing completed on 2024-05-12 15:58:28

0001 /*
0002  *  SPDX-FileCopyrightText: 2015 Dmitry Kazakov <dimula73@gmail.com>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "kis_memory_statistics_server.h"
0008 
0009 #include <QGlobalStatic>
0010 #include <QApplication>
0011 
0012 #include "kis_image.h"
0013 #include "kis_image_config.h"
0014 #include "kis_signal_compressor.h"
0015 
0016 #include "tiles3/kis_tile_data_store.h"
0017 
0018 Q_GLOBAL_STATIC(KisMemoryStatisticsServer, s_instance)
0019 
0020 struct Q_DECL_HIDDEN KisMemoryStatisticsServer::Private
0021 {
0022     Private(KisMemoryStatisticsServer *q)
0023         : updateCompressor(1000 /* ms */, KisSignalCompressor::POSTPONE, q)
0024     {
0025     }
0026 
0027     KisSignalCompressor updateCompressor;
0028 };
0029 
0030 
0031 KisMemoryStatisticsServer::KisMemoryStatisticsServer()
0032     : m_d(new Private(this))
0033 {
0034     /**
0035      * The first instance() call may happen from non-gui thread,
0036      * so we should ensure the signals and timers are running in the
0037      * correct (GUI) thread.
0038      */
0039     moveToThread(qApp->thread());
0040     connect(&m_d->updateCompressor, SIGNAL(timeout()), SIGNAL(sigUpdateMemoryStatistics()));
0041 }
0042 
0043 KisMemoryStatisticsServer::~KisMemoryStatisticsServer()
0044 {
0045 }
0046 
0047 KisMemoryStatisticsServer* KisMemoryStatisticsServer::instance()
0048 {
0049     return s_instance;
0050 }
0051 
0052 inline void addDevice(KisPaintDeviceSP dev,
0053                       bool isProjection,
0054                       QSet<KisPaintDevice*> &devices,
0055                       qint64 &memBound,
0056                       qint64 &layersSize,
0057                       qint64 &projectionsSize,
0058                       qint64 &lodSize)
0059 {
0060     if (dev && !devices.contains(dev.data())) {
0061         devices.insert(dev.data());
0062 
0063         qint64 imageData = 0;
0064         qint64 temporaryData = 0;
0065         qint64 lodData = 0;
0066 
0067         dev->estimateMemoryStats(imageData, temporaryData, lodData);
0068         memBound += imageData + temporaryData + lodData;
0069 
0070         KIS_SAFE_ASSERT_RECOVER_NOOP(!temporaryData || isProjection);
0071 
0072         if (!isProjection) {
0073             layersSize += imageData + temporaryData;
0074         } else {
0075             projectionsSize += imageData + temporaryData;
0076         }
0077 
0078         lodSize += lodData;
0079     }
0080 }
0081 
0082 qint64 calculateNodeMemoryHiBoundStep(KisNodeSP node,
0083                                       QSet<KisPaintDevice*> &devices,
0084                                       qint64 &layersSize,
0085                                       qint64 &projectionsSize,
0086                                       qint64 &lodSize)
0087 {
0088     qint64 memBound = 0;
0089 
0090     const bool originalIsProjection =
0091             node->inherits("KisGroupLayer") ||
0092             node->inherits("KisAdjustmentLayer");
0093 
0094 
0095     addDevice(node->paintDevice(), false, devices, memBound, layersSize, projectionsSize, lodSize);
0096     addDevice(node->original(), originalIsProjection, devices, memBound, layersSize, projectionsSize, lodSize);
0097     addDevice(node->projection(), true, devices, memBound, layersSize, projectionsSize, lodSize);
0098 
0099     node = node->firstChild();
0100     while (node) {
0101         memBound += calculateNodeMemoryHiBoundStep(node, devices,
0102                                                    layersSize, projectionsSize, lodSize);
0103         node = node->nextSibling();
0104     }
0105 
0106     return memBound;
0107 }
0108 
0109 qint64 calculateNodeMemoryHiBound(KisNodeSP node,
0110                                   qint64 &layersSize,
0111                                   qint64 &projectionsSize,
0112                                   qint64 &lodSize)
0113 {
0114     layersSize = 0;
0115     projectionsSize = 0;
0116     lodSize = 0;
0117 
0118     QSet<KisPaintDevice*> devices;
0119     return calculateNodeMemoryHiBoundStep(node,
0120                                           devices,
0121                                           layersSize,
0122                                           projectionsSize,
0123                                           lodSize);
0124 }
0125 
0126 
0127 KisMemoryStatisticsServer::Statistics
0128 KisMemoryStatisticsServer::fetchMemoryStatistics(KisImageSP image) const
0129 {
0130     KisTileDataStore::MemoryStatistics tileStats =
0131         KisTileDataStore::instance()->memoryStatistics();
0132 
0133     Statistics stats;
0134     if (image) {
0135         stats.imageSize =
0136             calculateNodeMemoryHiBound(image->root(),
0137                                        stats.layersSize,
0138                                        stats.projectionsSize,
0139                                        stats.lodSize);
0140     }
0141     stats.totalMemorySize = tileStats.totalMemorySize;
0142     stats.realMemorySize = tileStats.realMemorySize;
0143     stats.historicalMemorySize = tileStats.historicalMemorySize;
0144     stats.poolSize = tileStats.poolSize;
0145 
0146     stats.swapSize = tileStats.swapSize;
0147 
0148     KisImageConfig cfg(true);
0149 
0150     stats.tilesHardLimit = cfg.tilesHardLimit() * MiB;
0151     stats.tilesSoftLimit = cfg.tilesSoftLimit() * MiB;
0152     stats.tilesPoolLimit = cfg.poolLimit() * MiB;
0153     stats.totalMemoryLimit = stats.tilesHardLimit + stats.tilesPoolLimit;
0154 
0155     return stats;
0156 }
0157 
0158 void KisMemoryStatisticsServer::tryForceUpdateMemoryStatisticsWhileIdle()
0159 {
0160     KisTileDataStore::instance()->tryForceUpdateMemoryStatisticsWhileIdle();
0161     notifyImageChanged();
0162 }
0163 
0164 void KisMemoryStatisticsServer::notifyImageChanged()
0165 {
0166     m_d->updateCompressor.start();
0167 }
0168 
0169