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

0001 /*
0002  *  SPDX-FileCopyrightText: 2019 Dmitry Kazakov <dimula73@gmail.com>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "KisSafeNodeProjectionStore.h"
0008 
0009 #include <QCoreApplication>
0010 #include <QVector>
0011 #include <KoColorSpace.h>
0012 
0013 #include "kis_image.h"
0014 #include "kis_paint_device.h"
0015 #include "kis_selection.h"
0016 #include "KisRecycleProjectionsJob.h"
0017 
0018 /**********************************************************************/
0019 /*     StoreImplementaionInterface                                    */
0020 /**********************************************************************/
0021 
0022 struct StoreImplementaionInterface
0023 {
0024     virtual ~StoreImplementaionInterface() {}
0025     virtual StoreImplementaionInterface* clone() const = 0;
0026     virtual bool releaseDevice() = 0;
0027     virtual void discardCaches() = 0;
0028     virtual void recycleProjectionsInSafety() = 0;
0029 };
0030 
0031 
0032 /**********************************************************************/
0033 /*     StoreImplementaion                                             */
0034 /**********************************************************************/
0035 
0036 template <typename DeviceSP>
0037 struct StoreImplementation : public StoreImplementaionInterface
0038 {
0039     bool releaseDevice() override {
0040         bool hasDeletedProjection = false;
0041 
0042         if (m_projection) {
0043             m_dirtyProjections.append(m_projection);
0044             m_projection = 0;
0045             hasDeletedProjection = true;
0046 
0047 //            qDebug() << "release a device";
0048         }
0049         return hasDeletedProjection;
0050     }
0051 
0052     virtual void discardCaches() override {
0053 //        qDebug() << "discard caches";
0054         m_dirtyProjections.clear();
0055     }
0056 
0057     virtual void recycleProjectionsInSafety() override {
0058 //        qDebug() << "recycle caches";
0059         Q_FOREACH (DeviceSP projection, m_dirtyProjections) {
0060             projection->clear();
0061             m_cleanProjections.append(projection);
0062         }
0063         m_dirtyProjections.clear();
0064     }
0065 
0066 protected:
0067     DeviceSP m_projection;
0068     QVector<DeviceSP> m_dirtyProjections;
0069     QVector<DeviceSP> m_cleanProjections;
0070 };
0071 
0072 
0073 /**********************************************************************/
0074 /*     StoreImplementaionForDevice                                    */
0075 /**********************************************************************/
0076 
0077 struct StoreImplementationForDevice : StoreImplementation<KisPaintDeviceSP>
0078 {
0079     StoreImplementationForDevice() {}
0080     StoreImplementationForDevice(const KisPaintDevice &prototype) {
0081         m_projection = new KisPaintDevice(prototype);
0082     }
0083 
0084     StoreImplementaionInterface* clone() const override {
0085         return m_projection ?
0086             new StoreImplementationForDevice(*m_projection) :
0087             new StoreImplementationForDevice();
0088     }
0089 
0090     KisPaintDeviceSP getDeviceLazy(KisPaintDeviceSP prototype) {
0091         if(!m_projection ||
0092            *m_projection->colorSpace() != *prototype->colorSpace()) {
0093 
0094             if (!m_cleanProjections.isEmpty()) {
0095                 m_projection = m_cleanProjections.takeLast();
0096                 m_projection->makeCloneFromRough(prototype, prototype->extent());
0097             } else {
0098                 m_projection = new KisPaintDevice(*prototype);
0099             }
0100 
0101             m_projection->setProjectionDevice(true);
0102         }
0103         return m_projection;
0104     }
0105 };
0106 
0107 
0108 /**********************************************************************/
0109 /*     StoreImplementaionForSelection                                 */
0110 /**********************************************************************/
0111 
0112 struct StoreImplementationForSelection : StoreImplementation<KisSelectionSP>
0113 {
0114     StoreImplementationForSelection() {}
0115     StoreImplementationForSelection(const KisSelection &prototype) {
0116         m_projection = new KisSelection(prototype);
0117     }
0118 
0119     StoreImplementaionInterface* clone() const override {
0120         return m_projection ?
0121             new StoreImplementationForSelection(*m_projection) :
0122             new StoreImplementationForSelection();
0123     }
0124 
0125     KisSelectionSP getDeviceLazy(KisSelectionSP prototype) {
0126         if(!m_projection) {
0127             if (!m_cleanProjections.isEmpty()) {
0128                 m_projection = m_cleanProjections.takeLast();
0129                 m_projection->pixelSelection()->makeCloneFromRough(prototype->pixelSelection(), prototype->selectedRect());
0130             } else {
0131                 m_projection = new KisSelection(*prototype);
0132             }
0133 
0134             m_projection->pixelSelection()->setProjectionDevice(true);
0135         }
0136         return m_projection;
0137     }
0138 };
0139 
0140 
0141 /**********************************************************************/
0142 /*     KisSafeNodeProjectionStoreBase                                 */
0143 /**********************************************************************/
0144 
0145 struct KisSafeNodeProjectionStoreBase::Private
0146 {
0147     mutable QMutex lock;
0148     KisImageWSP image;
0149     QScopedPointer<StoreImplementaionInterface> store;
0150 };
0151 
0152 KisSafeNodeProjectionStoreBase::KisSafeNodeProjectionStoreBase(StoreImplementaionInterface *storeImpl)
0153     : m_d(new Private)
0154 {
0155     m_d->store.reset(storeImpl);
0156     moveToThread(qApp->thread());
0157     connect(this, SIGNAL(internalInitiateProjectionsCleanup()), this, SLOT(slotInitiateProjectionsCleanup()));
0158 }
0159 
0160 KisSafeNodeProjectionStoreBase::KisSafeNodeProjectionStoreBase(const KisSafeNodeProjectionStoreBase &rhs)
0161     : QObject(),
0162       KisShared(),
0163       m_d(new Private)
0164 {
0165     {
0166         QMutexLocker rhsLocker(&rhs.m_d->lock);
0167 
0168         m_d->image = rhs.m_d->image;
0169         m_d->store.reset(rhs.m_d->store->clone());
0170     }
0171 
0172     moveToThread(qApp->thread());
0173     connect(this, SIGNAL(internalInitiateProjectionsCleanup()), this, SLOT(slotInitiateProjectionsCleanup()));
0174 }
0175 
0176 KisSafeNodeProjectionStoreBase::~KisSafeNodeProjectionStoreBase()
0177 {
0178 }
0179 
0180 void KisSafeNodeProjectionStoreBase::releaseDevice()
0181 {
0182     QMutexLocker locker(&m_d->lock);
0183     if (m_d->store->releaseDevice()) {
0184         locker.unlock();
0185         emit internalInitiateProjectionsCleanup();
0186     }
0187 }
0188 
0189 void KisSafeNodeProjectionStoreBase::setImage(KisImageWSP image)
0190 {
0191     m_d->image = image;
0192 }
0193 
0194 void KisSafeNodeProjectionStoreBase::slotInitiateProjectionsCleanup()
0195 {
0196     /**
0197      * After the projection has been used, we should clean it. But we cannot
0198      * clean it until all the workers accessing it have completed their job.
0199      *
0200      * Therefore we just schedule an exclusive job that will execute the
0201      * recycling action in an exclusive context, when no jobs are running.
0202      */
0203 
0204     KisImageSP image = m_d->image;
0205 
0206     if (image) {
0207         image->addSpontaneousJob(new KisRecycleProjectionsJob(this));
0208     } else {
0209         discardCaches();
0210     }
0211 }
0212 
0213 void KisSafeNodeProjectionStoreBase::discardCaches()
0214 {
0215     QMutexLocker locker(&m_d->lock);
0216     m_d->store->discardCaches();
0217 }
0218 
0219 void KisSafeNodeProjectionStoreBase::recycleProjectionsInSafety()
0220 {
0221     QMutexLocker locker(&m_d->lock);
0222     m_d->store->recycleProjectionsInSafety();
0223 }
0224 
0225 
0226 /**********************************************************************/
0227 /*     KisSafeNodeProjectionStore                                     */
0228 /**********************************************************************/
0229 
0230 KisSafeNodeProjectionStore::KisSafeNodeProjectionStore()
0231     : KisSafeNodeProjectionStoreBase(new StoreImplementationForDevice)
0232 {
0233 }
0234 
0235 KisSafeNodeProjectionStore::KisSafeNodeProjectionStore(const KisSafeNodeProjectionStore &rhs)
0236     : KisSafeNodeProjectionStoreBase(rhs)
0237 {
0238 }
0239 
0240 KisPaintDeviceSP KisSafeNodeProjectionStore::getDeviceLazy(KisPaintDeviceSP prototype)
0241 {
0242     QMutexLocker locker(&m_d->lock);
0243     StoreImplementationForDevice *store = dynamic_cast<StoreImplementationForDevice*>(m_d->store.data());
0244     KIS_ASSERT(store);
0245 
0246     return store->getDeviceLazy(prototype);
0247 }
0248 
0249 
0250 /**********************************************************************/
0251 /*     KisSafeSelectionNodeProjectionStore                            */
0252 /**********************************************************************/
0253 
0254 KisSafeSelectionNodeProjectionStore::KisSafeSelectionNodeProjectionStore()
0255     : KisSafeNodeProjectionStoreBase(new StoreImplementationForSelection)
0256 {
0257 }
0258 
0259 KisSafeSelectionNodeProjectionStore::KisSafeSelectionNodeProjectionStore(const KisSafeSelectionNodeProjectionStore &rhs)
0260     : KisSafeNodeProjectionStoreBase(rhs)
0261 {
0262 }
0263 
0264 KisSelectionSP KisSafeSelectionNodeProjectionStore::getDeviceLazy(KisSelectionSP prototype)
0265 {
0266     QMutexLocker locker(&m_d->lock);
0267     StoreImplementationForSelection *store = dynamic_cast<StoreImplementationForSelection*>(m_d->store.data());
0268     KIS_ASSERT(store);
0269 
0270     return store->getDeviceLazy(prototype);
0271 }
0272