1 /*
2  *  Copyright (c) 2012 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 "kis_low_memory_benchmark.h"
20 
21 #include <QTest>
22 
23 #include "kis_benchmark_values.h"
24 
25 #include <KoColor.h>
26 #include <KoColorSpace.h>
27 #include <KoColorSpaceRegistry.h>
28 
29 #include <kis_image.h>
30 #include <kis_layer.h>
31 #include <kis_paint_layer.h>
32 #include "kis_paint_device.h"
33 #include "kis_painter.h"
34 
35 #include <brushengine/kis_paint_information.h>
36 #include <brushengine/kis_paintop_registry.h>
37 #include <brushengine/kis_paintop_preset.h>
38 
39 #include "tiles3/kis_tile_data_store.h"
40 #include "kis_surrogate_undo_adapter.h"
41 #include "kis_image_config.h"
42 #define LOAD_PRESET_OR_RETURN(preset, fileName)                         \
43     if(!preset->load()) { dbgKrita << "Preset" << fileName << "was NOT loaded properly. Done."; return; } \
44     else dbgKrita << "Loaded preset:" << fileName
45 
46 #define HUGE_IMAGE_SIZE 8000
47 
48 /**
49  * This benchmark runs a series of huge strokes on a canvas with a
50  * particular configuration of the swapper/pooler and history
51  * management. After the test is done you can visualize the results
52  * with the GNU Octave. Please use kis_low_memory_show_report.m file
53  * for that.
54  */
benchmarkWideArea(const QString presetFileName,const QRectF & rect,qreal vstep,int numCycles,bool createTransaction,int hardLimitMiB,int softLimitMiB,int poolLimitMiB,int index)55 void KisLowMemoryBenchmark::benchmarkWideArea(const QString presetFileName,
56                                               const QRectF &rect, qreal vstep,
57                                               int numCycles,
58                                               bool createTransaction,
59                                               int hardLimitMiB,
60                                               int softLimitMiB,
61                                               int poolLimitMiB,
62                                               int index)
63 {
64     KisPaintOpPresetSP preset = new KisPaintOpPreset(QString(FILES_DATA_DIR) + '/' + presetFileName);
65     LOAD_PRESET_OR_RETURN(preset, presetFileName);
66 
67 
68     /**
69      * Initialize image and painter
70      */
71     const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->rgb8();
72     KisImageSP image = new KisImage(0, HUGE_IMAGE_SIZE, HUGE_IMAGE_SIZE, colorSpace, "stroke sample image");
73     KisLayerSP layer = new KisPaintLayer(image, "temporary for stroke sample", OPACITY_OPAQUE_U8, colorSpace);
74     KisLayerSP layerExtra = new KisPaintLayer(image, "temporary for threading", OPACITY_OPAQUE_U8, colorSpace);
75 
76     image->addNode(layer, image->root());
77     image->addNode(layerExtra, image->root());
78 
79     KisPainter *painter = new KisPainter(layer->paintDevice());
80 
81     painter->setPaintColor(KoColor(Qt::black, colorSpace));
82     painter->setPaintOpPreset(preset, layer, image);
83 
84     /**
85      * A simple adapter that will store all the transactions for us
86      */
87     KisSurrogateUndoAdapter undoAdapter;
88 
89     /**
90      * Reset configuration to the desired settings
91      */
92     KisImageConfig config(false);
93     qreal oldHardLimit = config.memoryHardLimitPercent();
94     qreal oldSoftLimit = config.memorySoftLimitPercent();
95     qreal oldPoolLimit = config.memoryPoolLimitPercent();
96     const qreal _MiB = 100.0 / KisImageConfig::totalRAM();
97 
98     config.setMemoryHardLimitPercent(hardLimitMiB * _MiB);
99     config.setMemorySoftLimitPercent(softLimitMiB * _MiB);
100     config.setMemoryPoolLimitPercent(poolLimitMiB * _MiB);
101 
102     KisTileDataStore::instance()->testingRereadConfig();
103 
104     /**
105      * Create an empty the log file
106      */
107     QString fileName;
108     fileName = QString("log_%1_%2_%3_%4_%5.txt")
109         .arg(createTransaction)
110         .arg(hardLimitMiB)
111         .arg(softLimitMiB)
112         .arg(poolLimitMiB)
113         .arg(index);
114 
115     QFile logFile(fileName);
116     logFile.open(QFile::WriteOnly | QFile::Truncate);
117     QTextStream logStream(&logFile);
118     logStream.setFieldWidth(10);
119     logStream.setFieldAlignment(QTextStream::AlignRight);
120 
121     /**
122      * Start painting on the image
123      */
124 
125     QTime cycleTime;
126     QTime lineTime;
127     cycleTime.start();
128     lineTime.start();
129 
130     qreal rectBottom = rect.y() + rect.height();
131 
132     for (int i = 0; i < numCycles; i++) {
133         cycleTime.restart();
134 
135         QLineF line(rect.topLeft(), rect.topLeft() + QPointF(rect.width(), 0));
136         if (createTransaction) {
137             painter->beginTransaction();
138         }
139 
140         KisDistanceInformation currentDistance;
141 
142         while(line.y1() < rectBottom) {
143             lineTime.restart();
144 
145             KisPaintInformation pi1(line.p1(), 0.0);
146             KisPaintInformation pi2(line.p2(), 1.0);
147             painter->paintLine(pi1, pi2, &currentDistance);
148             painter->device()->setDirty(painter->takeDirtyRegion());
149 
150             logStream << "L 1" << i << lineTime.elapsed()
151                       << KisTileDataStore::instance()->numTilesInMemory() * 16
152                       << KisTileDataStore::instance()->numTiles() * 16
153                       << createTransaction << endl;
154 
155             line.translate(0, vstep);
156         }
157 
158         painter->device()->setDirty(painter->takeDirtyRegion());
159 
160         if (createTransaction) {
161             painter->endTransaction(&undoAdapter);
162         }
163 
164         // comment/uncomment to emulate user waiting after the stroke
165         QTest::qSleep(1000);
166 
167         logStream << "C 2" << i << cycleTime.elapsed()
168                   << KisTileDataStore::instance()->numTilesInMemory() * 16
169                   << KisTileDataStore::instance()->numTiles() * 16
170                   << createTransaction
171                   << config.memoryHardLimitPercent() / _MiB
172                   << config.memorySoftLimitPercent() / _MiB
173                   << config.memoryPoolLimitPercent() / _MiB  << endl;
174     }
175 
176     config.setMemoryHardLimitPercent(oldHardLimit * _MiB);
177     config.setMemorySoftLimitPercent(oldSoftLimit * _MiB);
178     config.setMemoryPoolLimitPercent(oldPoolLimit * _MiB);
179 
180     delete painter;
181 }
182 
unlimitedMemoryNoHistoryNoPool()183 void KisLowMemoryBenchmark::unlimitedMemoryNoHistoryNoPool()
184 {
185     QString presetFileName = "autobrush_300px.kpp";
186     // one cycle takes about 48 MiB of memory (total 960 MiB)
187     QRectF rect(150,150,4000,4000);
188     qreal step = 250;
189     int numCycles = 20;
190 
191     benchmarkWideArea(presetFileName, rect, step, numCycles, false,
192                       3000, 3000, 0, 0);
193 }
194 
unlimitedMemoryHistoryNoPool()195 void KisLowMemoryBenchmark::unlimitedMemoryHistoryNoPool()
196 {
197     QString presetFileName = "autobrush_300px.kpp";
198     // one cycle takes about 48 MiB of memory (total 960 MiB)
199     QRectF rect(150,150,4000,4000);
200     qreal step = 250;
201     int numCycles = 20;
202 
203     benchmarkWideArea(presetFileName, rect, step, numCycles, true,
204                       3000, 3000, 0, 0);
205 }
206 
unlimitedMemoryHistoryPool50()207 void KisLowMemoryBenchmark::unlimitedMemoryHistoryPool50()
208 {
209     QString presetFileName = "autobrush_300px.kpp";
210     // one cycle takes about 48 MiB of memory (total 960 MiB)
211     QRectF rect(150,150,4000,4000);
212     qreal step = 250;
213     int numCycles = 20;
214 
215     benchmarkWideArea(presetFileName, rect, step, numCycles, true,
216                       3000, 3000, 50, 0);
217 }
218 
memory2000History100Pool500HugeBrush()219 void KisLowMemoryBenchmark::memory2000History100Pool500HugeBrush()
220 {
221     QString presetFileName = "BIG_TESTING.kpp";
222     // one cycle takes about 316 MiB of memory (total 3+ GiB)
223     QRectF rect(150,150,7850,7850);
224     qreal step = 250;
225     int numCycles = 10;
226 
227     benchmarkWideArea(presetFileName, rect, step, numCycles, true,
228                       2000, 600, 500, 0);
229 }
230 
231 QTEST_MAIN(KisLowMemoryBenchmark)
232