1 /*
2 * Copyright (c) 2011 Dmitry Kazakov <dimula73@gmail.com>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 */
18
19 #include "freehand_stroke.h"
20
21 #include <QElapsedTimer>
22
23 #include "kis_canvas_resource_provider.h"
24 #include <brushengine/kis_paintop_preset.h>
25 #include <brushengine/kis_paintop_settings.h>
26 #include "kis_painter.h"
27 #include "kis_paintop.h"
28
29 #include "kis_update_time_monitor.h"
30
31 #include <brushengine/kis_stroke_random_source.h>
32 #include <KisRunnableStrokeJobsInterface.h>
33 #include "FreehandStrokeRunnableJobDataWithUpdate.h"
34 #include <mutex>
35
36 #include "KisStrokeEfficiencyMeasurer.h"
37 #include <KisStrokeSpeedMonitor.h>
38 #include <strokes/KisFreehandStrokeInfo.h>
39 #include <strokes/KisMaskedFreehandStrokePainter.h>
40
41 #include "brushengine/kis_paintop_utils.h"
42 #include "KisAsyncronousStrokeUpdateHelper.h"
43
44 struct FreehandStrokeStrategy::Private
45 {
PrivateFreehandStrokeStrategy::Private46 Private(KisResourcesSnapshotSP _resources)
47 : resources(_resources),
48 needsAsynchronousUpdates(_resources->presetNeedsAsynchronousUpdates())
49 {
50 if (needsAsynchronousUpdates) {
51 timeSinceLastUpdate.start();
52 }
53 }
54
PrivateFreehandStrokeStrategy::Private55 Private(const Private &rhs)
56 : randomSource(rhs.randomSource),
57 resources(rhs.resources),
58 needsAsynchronousUpdates(rhs.needsAsynchronousUpdates)
59 {
60 if (needsAsynchronousUpdates) {
61 timeSinceLastUpdate.start();
62 }
63 }
64
65 KisStrokeRandomSource randomSource;
66 KisResourcesSnapshotSP resources;
67
68 KisStrokeEfficiencyMeasurer efficiencyMeasurer;
69
70 QElapsedTimer timeSinceLastUpdate;
71 int currentUpdatePeriod = 40;
72
73 const bool needsAsynchronousUpdates = false;
74 std::mutex updateEntryMutex;
75 };
76
FreehandStrokeStrategy(KisResourcesSnapshotSP resources,KisFreehandStrokeInfo * strokeInfo,const KUndo2MagicString & name)77 FreehandStrokeStrategy::FreehandStrokeStrategy(KisResourcesSnapshotSP resources,
78 KisFreehandStrokeInfo *strokeInfo,
79 const KUndo2MagicString &name)
80 : KisPainterBasedStrokeStrategy(QLatin1String("FREEHAND_STROKE"), name,
81 resources, strokeInfo),
82 m_d(new Private(resources))
83 {
84 init();
85 }
86
FreehandStrokeStrategy(KisResourcesSnapshotSP resources,QVector<KisFreehandStrokeInfo * > strokeInfos,const KUndo2MagicString & name)87 FreehandStrokeStrategy::FreehandStrokeStrategy(KisResourcesSnapshotSP resources,
88 QVector<KisFreehandStrokeInfo*> strokeInfos,
89 const KUndo2MagicString &name)
90 : KisPainterBasedStrokeStrategy(QLatin1String("FREEHAND_STROKE"), name,
91 resources, strokeInfos),
92 m_d(new Private(resources))
93 {
94 init();
95 }
96
FreehandStrokeStrategy(const FreehandStrokeStrategy & rhs,int levelOfDetail)97 FreehandStrokeStrategy::FreehandStrokeStrategy(const FreehandStrokeStrategy &rhs, int levelOfDetail)
98 : KisPainterBasedStrokeStrategy(rhs, levelOfDetail),
99 m_d(new Private(*rhs.m_d))
100 {
101 m_d->randomSource.setLevelOfDetail(levelOfDetail);
102 }
103
~FreehandStrokeStrategy()104 FreehandStrokeStrategy::~FreehandStrokeStrategy()
105 {
106 KisStrokeSpeedMonitor::instance()->notifyStrokeFinished(m_d->efficiencyMeasurer.averageCursorSpeed(),
107 m_d->efficiencyMeasurer.averageRenderingSpeed(),
108 m_d->efficiencyMeasurer.averageFps(),
109 m_d->resources->currentPaintOpPreset());
110
111 KisUpdateTimeMonitor::instance()->endStrokeMeasure();
112 }
113
init()114 void FreehandStrokeStrategy::init()
115 {
116 setSupportsWrapAroundMode(true);
117 setSupportsMaskingBrush(true);
118 setSupportsIndirectPainting(true);
119 enableJob(KisSimpleStrokeStrategy::JOB_DOSTROKE);
120
121 if (m_d->needsAsynchronousUpdates) {
122 /**
123 * In case the paintop uses asynchronous updates, we should set priority to it,
124 * because FPS is controlled separately, not by the queue's merging algorithm.
125 */
126 setBalancingRatioOverride(0.01); // set priority to updates
127 }
128
129 KisUpdateTimeMonitor::instance()->startStrokeMeasure();
130 m_d->efficiencyMeasurer.setEnabled(KisStrokeSpeedMonitor::instance()->haveStrokeSpeedMeasurement());
131 }
132
initStrokeCallback()133 void FreehandStrokeStrategy::initStrokeCallback()
134 {
135 KisPainterBasedStrokeStrategy::initStrokeCallback();
136 m_d->efficiencyMeasurer.notifyRenderingStarted();
137 }
138
finishStrokeCallback()139 void FreehandStrokeStrategy::finishStrokeCallback()
140 {
141 m_d->efficiencyMeasurer.notifyRenderingFinished();
142 KisPainterBasedStrokeStrategy::finishStrokeCallback();
143 }
144
145
doStrokeCallback(KisStrokeJobData * data)146 void FreehandStrokeStrategy::doStrokeCallback(KisStrokeJobData *data)
147 {
148 if (KisAsyncronousStrokeUpdateHelper::UpdateData *d =
149 dynamic_cast<KisAsyncronousStrokeUpdateHelper::UpdateData*>(data)) {
150
151 // this job is lod-clonable in contrast to FreehandStrokeRunnableJobDataWithUpdate!
152 tryDoUpdate(d->forceUpdate);
153
154 } else if (Data *d = dynamic_cast<Data*>(data)) {
155 KisMaskedFreehandStrokePainter *maskedPainter = this->maskedPainter(d->strokeInfoId);
156
157 KisUpdateTimeMonitor::instance()->reportPaintOpPreset(maskedPainter->preset());
158 KisRandomSourceSP rnd = m_d->randomSource.source();
159 KisPerStrokeRandomSourceSP strokeRnd = m_d->randomSource.perStrokeSource();
160
161 switch(d->type) {
162 case Data::POINT:
163 d->pi1.setRandomSource(rnd);
164 d->pi1.setPerStrokeRandomSource(strokeRnd);
165 maskedPainter->paintAt(d->pi1);
166 m_d->efficiencyMeasurer.addSample(d->pi1.pos());
167 break;
168 case Data::LINE:
169 d->pi1.setRandomSource(rnd);
170 d->pi2.setRandomSource(rnd);
171 d->pi1.setPerStrokeRandomSource(strokeRnd);
172 d->pi2.setPerStrokeRandomSource(strokeRnd);
173 maskedPainter->paintLine(d->pi1, d->pi2);
174 m_d->efficiencyMeasurer.addSample(d->pi2.pos());
175 break;
176 case Data::CURVE:
177 d->pi1.setRandomSource(rnd);
178 d->pi2.setRandomSource(rnd);
179 d->pi1.setPerStrokeRandomSource(strokeRnd);
180 d->pi2.setPerStrokeRandomSource(strokeRnd);
181 maskedPainter->paintBezierCurve(d->pi1,
182 d->control1,
183 d->control2,
184 d->pi2);
185 m_d->efficiencyMeasurer.addSample(d->pi2.pos());
186 break;
187 case Data::POLYLINE:
188 maskedPainter->paintPolyline(d->points, 0, d->points.size());
189 m_d->efficiencyMeasurer.addSamples(d->points);
190 break;
191 case Data::POLYGON:
192 maskedPainter->paintPolygon(d->points);
193 m_d->efficiencyMeasurer.addSamples(d->points);
194 break;
195 case Data::RECT:
196 maskedPainter->paintRect(d->rect);
197 m_d->efficiencyMeasurer.addSample(d->rect.topLeft());
198 m_d->efficiencyMeasurer.addSample(d->rect.topRight());
199 m_d->efficiencyMeasurer.addSample(d->rect.bottomRight());
200 m_d->efficiencyMeasurer.addSample(d->rect.bottomLeft());
201 break;
202 case Data::ELLIPSE:
203 maskedPainter->paintEllipse(d->rect);
204 // TODO: add speed measures
205 break;
206 case Data::PAINTER_PATH:
207 maskedPainter->paintPainterPath(d->path);
208 // TODO: add speed measures
209 break;
210 case Data::QPAINTER_PATH:
211 maskedPainter->drawPainterPath(d->path, d->pen);
212 break;
213 case Data::QPAINTER_PATH_FILL:
214 maskedPainter->drawAndFillPainterPath(d->path, d->pen, d->customColor);
215 break;
216 };
217
218 tryDoUpdate();
219 } else {
220 KisPainterBasedStrokeStrategy::doStrokeCallback(data);
221
222 FreehandStrokeRunnableJobDataWithUpdate *dataWithUpdate =
223 dynamic_cast<FreehandStrokeRunnableJobDataWithUpdate*>(data);
224
225 if (dataWithUpdate) {
226 tryDoUpdate();
227 }
228 }
229 }
230
tryDoUpdate(bool forceEnd)231 void FreehandStrokeStrategy::tryDoUpdate(bool forceEnd)
232 {
233 // we should enter this function only once!
234 std::unique_lock<std::mutex> entryLock(m_d->updateEntryMutex, std::try_to_lock);
235 if (!entryLock.owns_lock()) return;
236
237 if (m_d->needsAsynchronousUpdates) {
238 if (forceEnd || m_d->timeSinceLastUpdate.elapsed() > m_d->currentUpdatePeriod) {
239 m_d->timeSinceLastUpdate.restart();
240
241 for (int i = 0; i < numMaskedPainters(); i++) {
242 KisMaskedFreehandStrokePainter *maskedPainter = this->maskedPainter(i);
243
244 // TODO: well, we should count all N simultaneous painters for FPS rate!
245 QVector<KisRunnableStrokeJobData*> jobs;
246
247 bool needsMoreUpdates = false;
248
249 std::tie(m_d->currentUpdatePeriod, needsMoreUpdates) =
250 maskedPainter->doAsyncronousUpdate(jobs);
251
252 if (!jobs.isEmpty() ||
253 maskedPainter->hasDirtyRegion() ||
254 (forceEnd && needsMoreUpdates)) {
255
256 jobs.append(new KisRunnableStrokeJobData(
257 [this] () {
258 this->issueSetDirtySignals();
259 },
260 KisStrokeJobData::SEQUENTIAL));
261
262 if (forceEnd && needsMoreUpdates) {
263 jobs.append(new KisRunnableStrokeJobData(
264 [this] () {
265 this->tryDoUpdate(true);
266 },
267 KisStrokeJobData::SEQUENTIAL));
268 }
269
270
271 runnableJobsInterface()->addRunnableJobs(jobs);
272 m_d->efficiencyMeasurer.notifyFrameRenderingStarted();
273 }
274
275 }
276 }
277 } else {
278 issueSetDirtySignals();
279 }
280
281
282 }
283
issueSetDirtySignals()284 void FreehandStrokeStrategy::issueSetDirtySignals()
285 {
286 QVector<QRect> dirtyRects;
287
288 for (int i = 0; i < numMaskedPainters(); i++) {
289 KisMaskedFreehandStrokePainter *maskedPainter = this->maskedPainter(i);
290 dirtyRects.append(maskedPainter->takeDirtyRegion());
291 }
292
293 if (needsMaskingUpdates()) {
294
295 // optimize the rects so that they would never intersect with each other!
296 // that is a mandatory step for the multithreaded execution of merging jobs
297
298 // sanity check: updates from the brush should have already been normalized
299 // to the wrapping rect
300 const KisDefaultBoundsBaseSP defaultBounds = targetNode()->projection()->defaultBounds();
301 if (defaultBounds->wrapAroundMode()) {
302 const QRect wrapRect = defaultBounds->imageBorderRect();
303 for (auto it = dirtyRects.begin(); it != dirtyRects.end(); ++it) {
304 KIS_SAFE_ASSERT_RECOVER(wrapRect.contains(*it)) {
305 ENTER_FUNCTION() << ppVar(*it) << ppVar(wrapRect);
306 *it = *it & wrapRect;
307 }
308 }
309 }
310
311 const int maxPatchSizeForMaskingUpdates = 64;
312 const QRect totalRect =
313 std::accumulate(dirtyRects.constBegin(), dirtyRects.constEnd(), QRect(), std::bit_or<QRect>());
314
315 dirtyRects = KisPaintOpUtils::splitAndFilterDabRect(totalRect, dirtyRects, maxPatchSizeForMaskingUpdates);
316
317 QVector<KisRunnableStrokeJobData*> jobs = doMaskingBrushUpdates(dirtyRects);
318
319 jobs.append(new KisRunnableStrokeJobData(
320 [this, dirtyRects] () {
321 this->targetNode()->setDirty(dirtyRects);
322 },
323 KisStrokeJobData::SEQUENTIAL));
324
325 runnableJobsInterface()->addRunnableJobs(jobs);
326
327 } else {
328 targetNode()->setDirty(dirtyRects);
329 }
330
331 //KisUpdateTimeMonitor::instance()->reportJobFinished(data, dirtyRects);
332 }
333
createLodClone(int levelOfDetail)334 KisStrokeStrategy* FreehandStrokeStrategy::createLodClone(int levelOfDetail)
335 {
336 if (!m_d->resources->presetAllowsLod()) return 0;
337 if (!m_d->resources->currentNode()->supportsLodPainting()) return 0;
338
339 FreehandStrokeStrategy *clone = new FreehandStrokeStrategy(*this, levelOfDetail);
340 return clone;
341 }
342
notifyUserStartedStroke()343 void FreehandStrokeStrategy::notifyUserStartedStroke()
344 {
345 m_d->efficiencyMeasurer.notifyCursorMoveStarted();
346 }
347
notifyUserEndedStroke()348 void FreehandStrokeStrategy::notifyUserEndedStroke()
349 {
350 m_d->efficiencyMeasurer.notifyCursorMoveFinished();
351 }
352