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 }