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

0001 /*
0002  *  SPDX-FileCopyrightText: 2004 Bart Coppens <kde@bartcoppens.be>
0003  *  SPDX-FileCopyrightText: 2010 Dmitry Kazakov <dimula73@gmail.com>
0004  *
0005  *  SPDX-License-Identifier: GPL-2.0-or-later
0006  */
0007 
0008 #include "kis_indirect_painting_support.h"
0009 
0010 #include <QMutex>
0011 #include <QMutexLocker>
0012 #include <QReadWriteLock>
0013 
0014 #include <KoCompositeOp.h>
0015 #include "kis_layer.h"
0016 #include "kis_paint_layer.h"
0017 #include "kis_paint_device.h"
0018 #include "kis_selection.h"
0019 #include "kis_painter.h"
0020 #include <KisFakeRunnableStrokeJobsExecutor.h>
0021 #include "KisRunnableStrokeJobData.h"
0022 #include "KisRunnableStrokeJobUtils.h"
0023 #include "kis_transaction.h"
0024 #include "kis_pointer_utils.h"
0025 
0026 
0027 struct Q_DECL_HIDDEN KisIndirectPaintingSupport::Private {
0028     // To simulate the indirect painting
0029     KisPaintDeviceSP temporaryTarget;
0030     QString compositeOp;
0031     quint8 compositeOpacity;
0032     QBitArray channelFlags;
0033     KisSelectionSP selection;
0034 
0035     QReadWriteLock lock;
0036     bool finalMergeInProgress = true;
0037 };
0038 
0039 
0040 KisIndirectPaintingSupport::KisIndirectPaintingSupport()
0041     : d(new Private)
0042 {
0043 }
0044 
0045 KisIndirectPaintingSupport::~KisIndirectPaintingSupport()
0046 {
0047     delete d;
0048 }
0049 
0050 void KisIndirectPaintingSupport::setCurrentColor(const KoColor &color)
0051 {
0052     Q_UNUSED(color);
0053 }
0054 
0055 void KisIndirectPaintingSupport::setTemporaryTarget(KisPaintDeviceSP t)
0056 {
0057     d->temporaryTarget = t;
0058 }
0059 
0060 void KisIndirectPaintingSupport::setTemporaryCompositeOp(const QString &id)
0061 {
0062     d->compositeOp = id;
0063 }
0064 
0065 void KisIndirectPaintingSupport::setTemporaryOpacity(quint8 o)
0066 {
0067     d->compositeOpacity = o;
0068 }
0069 
0070 void KisIndirectPaintingSupport::setTemporaryChannelFlags(const QBitArray& channelFlags)
0071 {
0072     d->channelFlags = channelFlags;
0073 }
0074 
0075 void KisIndirectPaintingSupport::setTemporarySelection(KisSelectionSP selection)
0076 {
0077     d->selection = selection;
0078 }
0079 
0080 void KisIndirectPaintingSupport::lockTemporaryTarget() const
0081 {
0082     d->lock.lockForRead();
0083 }
0084 
0085 void KisIndirectPaintingSupport::lockTemporaryTargetForWrite() const
0086 {
0087     d->lock.lockForWrite();
0088     d->finalMergeInProgress = true;
0089 }
0090 
0091 void KisIndirectPaintingSupport::unlockTemporaryTarget() const
0092 {
0093     d->lock.unlock();
0094     d->finalMergeInProgress = false;
0095 }
0096 
0097 KisPaintDeviceSP KisIndirectPaintingSupport::temporaryTarget() const
0098 {
0099     return d->temporaryTarget;
0100 }
0101 
0102 bool KisIndirectPaintingSupport::supportsNonIndirectPainting() const
0103 {
0104     return true;
0105 }
0106 
0107 KisIndirectPaintingSupport::FinalMergeSuspenderSP KisIndirectPaintingSupport::trySuspendFinalMerge()
0108 {
0109     return toQShared(d->finalMergeInProgress ? new FinalMergeSuspender(this) : nullptr);
0110 }
0111 
0112 QString KisIndirectPaintingSupport::temporaryCompositeOp() const
0113 {
0114     return d->compositeOp;
0115 }
0116 
0117 KisSelectionSP KisIndirectPaintingSupport::temporarySelection() const
0118 {
0119     return d->selection;
0120 }
0121 
0122 bool KisIndirectPaintingSupport::hasTemporaryTarget() const
0123 {
0124     return d->temporaryTarget;
0125 }
0126 
0127 void KisIndirectPaintingSupport::setupTemporaryPainter(KisPainter *painter) const
0128 {
0129      painter->setOpacity(d->compositeOpacity);
0130      painter->setCompositeOpId(d->compositeOp);
0131      painter->setChannelFlags(d->channelFlags);
0132      painter->setSelection(d->selection);
0133 }
0134 
0135 void KisIndirectPaintingSupport::mergeToLayer(KisNodeSP layer, KUndo2Command *parentCommand, const KUndo2MagicString &transactionText, int timedID)
0136 {
0137     QVector<KisRunnableStrokeJobData*> jobs;
0138     mergeToLayerThreaded(layer, parentCommand, transactionText, timedID, &jobs);
0139 
0140     /**
0141      * When merging, we use barrier jobs only for ensuring that the merge jobs
0142      * are not split by the update jobs. Merge jobs hold the shared lock, so
0143      * forcinf them out of CPU will basically cause a deadlock. When running in
0144      * the fake executor, the jobs cannot be split anyway, so there is no danger
0145      * in that.
0146      */
0147     KisFakeRunnableStrokeJobsExecutor executor(KisFakeRunnableStrokeJobsExecutor::AllowBarrierJobs);
0148     executor.addRunnableJobs(implicitCastList<KisRunnableStrokeJobDataBase*>(jobs));
0149 }
0150 
0151 void KisIndirectPaintingSupport::mergeToLayerThreaded(KisNodeSP layer, KUndo2Command *parentCommand, const KUndo2MagicString &transactionText,int timedID, QVector<KisRunnableStrokeJobData*> *jobs)
0152 {
0153     /**
0154      * We create the lock in an unlocked state to avoid a deadlock, when
0155      * layer-stack updating jobs push out the stroke jobs from the CPU and
0156      * start sleeping on lockTemporaryTarget().
0157      */
0158 
0159     WriteLockerSP sharedWriteLock(new WriteLocker(this, std::defer_lock));
0160 
0161     /**
0162      * Now wait for all update jobs to finish and lock the indirect target
0163      */
0164     KritaUtils::addJobBarrier(*jobs,
0165         [sharedWriteLock] () {
0166             sharedWriteLock->relock();
0167         });
0168 
0169     mergeToLayerImpl(layer->paintDevice(), parentCommand, transactionText,
0170                      timedID, true, sharedWriteLock,
0171                      jobs);
0172 }
0173 
0174 void KisIndirectPaintingSupport::mergeToLayerImpl(KisPaintDeviceSP dst, KUndo2Command *parentCommand, const KUndo2MagicString &transactionText, int timedID, bool cleanResources,
0175                                                   WriteLockerSP sharedWriteLock, QVector<KisRunnableStrokeJobData*> *jobs)
0176 {
0177     struct SharedState {
0178         QScopedPointer<KisTransaction> transaction;
0179     };
0180 
0181     QSharedPointer<SharedState> sharedState(new SharedState());
0182 
0183     KritaUtils::addJobSequential(*jobs,
0184         [sharedState, sharedWriteLock, dst, parentCommand, transactionText, timedID] () {
0185             Q_UNUSED(sharedWriteLock); // just a RAII holder object for the lock
0186 
0187             /**
0188              * Move tool may not have an undo adapter
0189              */
0190              if (parentCommand) {
0191                  sharedState->transaction.reset(
0192                      new KisTransaction(transactionText, dst, parentCommand, timedID));
0193              }
0194         }
0195     );
0196 
0197     KisPaintDeviceSP src = d->temporaryTarget;
0198     Q_FOREACH (const QRect &rc, src->region().rects()) {
0199         KritaUtils::addJobConcurrent(*jobs,
0200             [this, rc, src, dst, sharedState, sharedWriteLock] () {
0201                 Q_UNUSED(sharedWriteLock); // just a RAII holder object for the lock
0202 
0203                 /**
0204                  * Brushes don't apply the selection, we apply that during the indirect
0205                  * painting merge operation. It is cheaper calculation-wise.
0206                  */
0207 
0208                 KisPainter gc(dst);
0209                 setupTemporaryPainter(&gc);
0210                 this->writeMergeData(&gc, src, rc);
0211             }
0212         );
0213     }
0214 
0215     KritaUtils::addJobSequential(*jobs,
0216         [this, sharedState, sharedWriteLock, cleanResources] () {
0217             Q_UNUSED(sharedWriteLock); // just a RAII holder object for the lock
0218 
0219             if (cleanResources) {
0220                 releaseResources();
0221             }
0222 
0223             if (sharedState->transaction) {
0224                 // the internal command is stored using
0225                 // the link to the parent command
0226                 (void) sharedState->transaction->endAndTake();
0227             }
0228         }
0229     );
0230 }
0231 
0232 void KisIndirectPaintingSupport::writeMergeData(KisPainter *painter, KisPaintDeviceSP src, const QRect &rc)
0233 {
0234     painter->bitBlt(rc.topLeft(), src, rc);
0235 }
0236 
0237 void KisIndirectPaintingSupport::releaseResources()
0238 {
0239     d->temporaryTarget = 0;
0240     d->selection = 0;
0241     d->compositeOp = COMPOSITE_OVER;
0242     d->compositeOpacity = OPACITY_OPAQUE_U8;
0243     d->channelFlags.clear();
0244 }
0245 
0246 KisIndirectPaintingSupport::FinalMergeSuspender::FinalMergeSuspender(KisIndirectPaintingSupport *indirect)
0247     : m_lock(indirect)
0248 {
0249     m_lock->unlockTemporaryTarget();
0250 }
0251 
0252 KisIndirectPaintingSupport::FinalMergeSuspender::~FinalMergeSuspender()
0253 {
0254     m_lock->lockTemporaryTargetForWrite();
0255 }