File indexing completed on 2024-12-22 04:12:56

0001 /*
0002  *  SPDX-FileCopyrightText: 2011 Dmitry Kazakov <dimula73@gmail.com>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "freehand_stroke.h"
0008 
0009 #include <QElapsedTimer>
0010 #include <QThread>
0011 #include <QApplication>
0012 
0013 #include "kis_canvas_resource_provider.h"
0014 #include <brushengine/kis_paintop_preset.h>
0015 #include <brushengine/kis_paintop_settings.h>
0016 #include "kis_painter.h"
0017 #include "kis_paintop.h"
0018 
0019 #include "kis_update_time_monitor.h"
0020 
0021 #include <brushengine/kis_stroke_random_source.h>
0022 #include <KisRunnableStrokeJobsInterface.h>
0023 #include <KisRunnableStrokeJobUtils.h>
0024 #include "FreehandStrokeRunnableJobDataWithUpdate.h"
0025 #include <mutex>
0026 
0027 #include "KisStrokeEfficiencyMeasurer.h"
0028 #include <KisStrokeSpeedMonitor.h>
0029 #include <strokes/KisFreehandStrokeInfo.h>
0030 #include <strokes/KisMaskedFreehandStrokePainter.h>
0031 
0032 #include "brushengine/kis_paintop_utils.h"
0033 #include "KisAsynchronousStrokeUpdateHelper.h"
0034 
0035 struct FreehandStrokeStrategy::Private
0036 {
0037     Private(KisResourcesSnapshotSP _resources)
0038         : resources(_resources),
0039           needsAsynchronousUpdates(_resources->presetNeedsAsynchronousUpdates())
0040     {
0041         if (needsAsynchronousUpdates) {
0042             timeSinceLastUpdate.start();
0043         }
0044     }
0045 
0046     Private(const Private &rhs)
0047         : randomSource(rhs.randomSource),
0048           resources(rhs.resources),
0049           needsAsynchronousUpdates(rhs.needsAsynchronousUpdates)
0050     {
0051         if (needsAsynchronousUpdates) {
0052             timeSinceLastUpdate.start();
0053         }
0054         efficiencyMeasurer.setEnabled(rhs.efficiencyMeasurer.isEnabled());
0055     }
0056 
0057     KisStrokeRandomSource randomSource;
0058     KisResourcesSnapshotSP resources;
0059 
0060     KisStrokeEfficiencyMeasurer efficiencyMeasurer;
0061 
0062     QElapsedTimer timeSinceLastUpdate;
0063     int currentUpdatePeriod = 40;
0064 
0065     const bool needsAsynchronousUpdates = false;
0066     std::mutex updateEntryMutex;
0067 };
0068 
0069 FreehandStrokeStrategy::FreehandStrokeStrategy(KisResourcesSnapshotSP resources,
0070                                                KisFreehandStrokeInfo *strokeInfo,
0071                                                const KUndo2MagicString &name,
0072                                                Flags flags)
0073     : KisPainterBasedStrokeStrategy(QLatin1String("FREEHAND_STROKE"), name,
0074                                     resources, strokeInfo),
0075       m_d(new Private(resources))
0076 {
0077     init(flags);
0078 }
0079 
0080 FreehandStrokeStrategy::FreehandStrokeStrategy(KisResourcesSnapshotSP resources,
0081                                                QVector<KisFreehandStrokeInfo*> strokeInfos,
0082                                                const KUndo2MagicString &name,
0083                                                Flags flags)
0084     : KisPainterBasedStrokeStrategy(QLatin1String("FREEHAND_STROKE"), name,
0085                                     resources, strokeInfos),
0086       m_d(new Private(resources))
0087 {
0088     init(flags);
0089 }
0090 
0091 FreehandStrokeStrategy::FreehandStrokeStrategy(const FreehandStrokeStrategy &rhs, int levelOfDetail)
0092     : KisPainterBasedStrokeStrategy(rhs, levelOfDetail),
0093       m_d(new Private(*rhs.m_d))
0094 {
0095     m_d->randomSource.setLevelOfDetail(levelOfDetail);
0096 }
0097 
0098 FreehandStrokeStrategy::~FreehandStrokeStrategy()
0099 {
0100     KisStrokeSpeedMonitor::instance()->notifyStrokeFinished(m_d->efficiencyMeasurer.averageCursorSpeed(),
0101                                                             m_d->efficiencyMeasurer.averageRenderingSpeed(),
0102                                                             m_d->efficiencyMeasurer.averageFps(),
0103                                                             m_d->resources->currentPaintOpPreset());
0104 
0105     KisUpdateTimeMonitor::instance()->endStrokeMeasure();
0106 }
0107 
0108 void FreehandStrokeStrategy::init(Flags flags)
0109 {
0110     setSupportsWrapAroundMode(true);
0111     setSupportsMaskingBrush(true);
0112     setSupportsIndirectPainting(true);
0113     setSupportsContinuedInterstrokeData(flags & SupportsContinuedInterstrokeData);
0114     setSupportsTimedMergeId(flags & SupportsTimedMergeId);
0115 
0116     enableJob(KisSimpleStrokeStrategy::JOB_DOSTROKE);
0117 
0118     if (m_d->needsAsynchronousUpdates) {
0119         /**
0120          * In case the paintop uses asynchronous updates, we should set priority to it,
0121          * because FPS is controlled separately, not by the queue's merging algorithm.
0122          */
0123         setBalancingRatioOverride(0.01); // set priority to updates
0124     }
0125 
0126     KisUpdateTimeMonitor::instance()->startStrokeMeasure();
0127     m_d->efficiencyMeasurer.setEnabled(KisStrokeSpeedMonitor::instance()->haveStrokeSpeedMeasurement());
0128 }
0129 
0130 void FreehandStrokeStrategy::initStrokeCallback()
0131 {
0132     KisPainterBasedStrokeStrategy::initStrokeCallback();
0133     m_d->efficiencyMeasurer.notifyRenderingStarted();
0134 }
0135 
0136 void FreehandStrokeStrategy::finishStrokeCallback()
0137 {
0138     m_d->efficiencyMeasurer.notifyRenderingFinished();
0139     KisPainterBasedStrokeStrategy::finishStrokeCallback();
0140 }
0141 
0142 
0143 void FreehandStrokeStrategy::doStrokeCallback(KisStrokeJobData *data)
0144 {
0145     if (KisAsynchronousStrokeUpdateHelper::UpdateData *d =
0146             dynamic_cast<KisAsynchronousStrokeUpdateHelper::UpdateData*>(data)) {
0147 
0148         // this job is lod-clonable in contrast to FreehandStrokeRunnableJobDataWithUpdate!
0149         tryDoUpdate(d->forceUpdate);
0150 
0151     } else if (Data *d = dynamic_cast<Data*>(data)) {
0152         KisMaskedFreehandStrokePainter *maskedPainter = this->maskedPainter(d->strokeInfoId);
0153 
0154         KisUpdateTimeMonitor::instance()->reportPaintOpPreset(maskedPainter->preset());
0155         KisRandomSourceSP rnd = m_d->randomSource.source();
0156         KisPerStrokeRandomSourceSP strokeRnd = m_d->randomSource.perStrokeSource();
0157 
0158         switch(d->type) {
0159         case Data::POINT:
0160             d->pi1.setRandomSource(rnd);
0161             d->pi1.setPerStrokeRandomSource(strokeRnd);
0162             maskedPainter->paintAt(d->pi1);
0163             m_d->efficiencyMeasurer.addSample(d->pi1.pos());
0164             break;
0165         case Data::LINE:
0166             d->pi1.setRandomSource(rnd);
0167             d->pi2.setRandomSource(rnd);
0168             d->pi1.setPerStrokeRandomSource(strokeRnd);
0169             d->pi2.setPerStrokeRandomSource(strokeRnd);
0170             maskedPainter->paintLine(d->pi1, d->pi2);
0171             m_d->efficiencyMeasurer.addSample(d->pi2.pos());
0172             break;
0173         case Data::CURVE:
0174             d->pi1.setRandomSource(rnd);
0175             d->pi2.setRandomSource(rnd);
0176             d->pi1.setPerStrokeRandomSource(strokeRnd);
0177             d->pi2.setPerStrokeRandomSource(strokeRnd);
0178             maskedPainter->paintBezierCurve(d->pi1,
0179                                          d->control1,
0180                                          d->control2,
0181                                          d->pi2);
0182             m_d->efficiencyMeasurer.addSample(d->pi2.pos());
0183             break;
0184         case Data::POLYLINE:
0185             maskedPainter->paintPolyline(d->points, 0, d->points.size());
0186             m_d->efficiencyMeasurer.addSamples(d->points);
0187             break;
0188         case Data::POLYGON:
0189             maskedPainter->paintPolygon(d->points);
0190             m_d->efficiencyMeasurer.addSamples(d->points);
0191             break;
0192         case Data::RECT:
0193             maskedPainter->paintRect(d->rect);
0194             m_d->efficiencyMeasurer.addSample(d->rect.topLeft());
0195             m_d->efficiencyMeasurer.addSample(d->rect.topRight());
0196             m_d->efficiencyMeasurer.addSample(d->rect.bottomRight());
0197             m_d->efficiencyMeasurer.addSample(d->rect.bottomLeft());
0198             break;
0199         case Data::ELLIPSE:
0200             maskedPainter->paintEllipse(d->rect);
0201             // TODO: add speed measures
0202             break;
0203         case Data::PAINTER_PATH:
0204             maskedPainter->paintPainterPath(d->path);
0205             // TODO: add speed measures
0206             break;
0207         case Data::QPAINTER_PATH:
0208             maskedPainter->drawPainterPath(d->path, d->pen);
0209             break;
0210         case Data::QPAINTER_PATH_FILL:
0211             maskedPainter->drawAndFillPainterPath(d->path, d->pen, d->customColor);
0212             break;
0213         };
0214 
0215         tryDoUpdate();
0216     } else {
0217         KisPainterBasedStrokeStrategy::doStrokeCallback(data);
0218 
0219         FreehandStrokeRunnableJobDataWithUpdate *dataWithUpdate =
0220             dynamic_cast<FreehandStrokeRunnableJobDataWithUpdate*>(data);
0221 
0222         if (dataWithUpdate) {
0223             tryDoUpdate();
0224         }
0225     }
0226 }
0227 
0228 void FreehandStrokeStrategy::tryDoUpdate(bool forceEnd)
0229 {
0230     // we should enter this function only once!
0231     std::unique_lock<std::mutex> entryLock(m_d->updateEntryMutex, std::try_to_lock);
0232     if (!entryLock.owns_lock()) return;
0233 
0234     if (m_d->needsAsynchronousUpdates) {
0235         if (forceEnd || m_d->timeSinceLastUpdate.elapsed() > m_d->currentUpdatePeriod) {
0236             m_d->timeSinceLastUpdate.restart();
0237 
0238             for (int i = 0; i < numMaskedPainters(); i++) {
0239                 KisMaskedFreehandStrokePainter *maskedPainter = this->maskedPainter(i);
0240 
0241                 // TODO: well, we should count all N simultaneous painters for FPS rate!
0242                 QVector<KisRunnableStrokeJobData*> jobs;
0243 
0244                 bool needsMoreUpdates = false;
0245 
0246                 std::tie(m_d->currentUpdatePeriod, needsMoreUpdates) =
0247                     maskedPainter->doAsynchronousUpdate(jobs);
0248 
0249                 if (!jobs.isEmpty() ||
0250                     maskedPainter->hasDirtyRegion() ||
0251                     (forceEnd && needsMoreUpdates)) {
0252 
0253                     KritaUtils::addJobSequential(jobs,
0254                         [this] () {
0255                             this->issueSetDirtySignals();
0256                         }
0257                     );
0258 
0259                     if (forceEnd && needsMoreUpdates) {
0260                         KritaUtils::addJobSequential(jobs,
0261                             [this] () {
0262                                 this->tryDoUpdate(true);
0263                             }
0264                         );
0265                     }
0266 
0267 
0268                     runnableJobsInterface()->addRunnableJobs(jobs);
0269                     m_d->efficiencyMeasurer.notifyFrameRenderingStarted();
0270                 }
0271 
0272             }
0273         }
0274     } else {
0275         issueSetDirtySignals();
0276     }
0277 
0278 
0279 }
0280 
0281 void FreehandStrokeStrategy::issueSetDirtySignals()
0282 {
0283     QVector<QRect> dirtyRects;
0284 
0285     for (int i = 0; i < numMaskedPainters(); i++) {
0286         KisMaskedFreehandStrokePainter *maskedPainter = this->maskedPainter(i);
0287         dirtyRects.append(maskedPainter->takeDirtyRegion());
0288     }
0289 
0290     if (needsMaskingUpdates()) {
0291 
0292         // optimize the rects so that they would never intersect with each other!
0293         // that is a mandatory step for the multithreaded execution of merging jobs
0294 
0295         // sanity check: updates from the brush should have already been normalized
0296         //               to the wrapping rect
0297         const KisDefaultBoundsBaseSP defaultBounds = targetNode()->projection()->defaultBounds();
0298         if (defaultBounds->wrapAroundMode()) {
0299             const QRect wrapRect = defaultBounds->imageBorderRect();
0300             for (auto it = dirtyRects.begin(); it != dirtyRects.end(); ++it) {
0301                 KIS_SAFE_ASSERT_RECOVER(wrapRect.contains(*it)) {
0302                     ENTER_FUNCTION() << ppVar(*it) << ppVar(wrapRect);
0303                     *it = *it & wrapRect;
0304                 }
0305             }
0306         }
0307 
0308         const int maxPatchSizeForMaskingUpdates = 64;
0309         const QRect totalRect =
0310             std::accumulate(dirtyRects.constBegin(), dirtyRects.constEnd(), QRect(), std::bit_or<QRect>());
0311 
0312         dirtyRects = KisPaintOpUtils::splitAndFilterDabRect(totalRect, dirtyRects, maxPatchSizeForMaskingUpdates);
0313 
0314         QVector<KisRunnableStrokeJobData*> jobs = doMaskingBrushUpdates(dirtyRects);
0315 
0316         KritaUtils::addJobSequential(jobs,
0317             [this, dirtyRects] () {
0318                 this->targetNode()->setDirty(dirtyRects);
0319             }
0320         );
0321 
0322         runnableJobsInterface()->addRunnableJobs(jobs);
0323 
0324     } else {
0325         targetNode()->setDirty(dirtyRects);
0326     }
0327 
0328     //KisUpdateTimeMonitor::instance()->reportJobFinished(data, dirtyRects);
0329 }
0330 
0331 KisStrokeStrategy* FreehandStrokeStrategy::createLodClone(int levelOfDetail)
0332 {
0333     if (!m_d->resources->presetAllowsLod()) return 0;
0334     if (!m_d->resources->currentNode()->supportsLodPainting()) return 0;
0335 
0336     FreehandStrokeStrategy *clone = new FreehandStrokeStrategy(*this, levelOfDetail);
0337     return clone;
0338 }
0339 
0340 void FreehandStrokeStrategy::notifyUserStartedStroke()
0341 {
0342     m_d->efficiencyMeasurer.notifyCursorMoveStarted();
0343 }
0344 
0345 void FreehandStrokeStrategy::notifyUserEndedStroke()
0346 {
0347     m_d->efficiencyMeasurer.notifyCursorMoveFinished();
0348 }