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 }