1 /**
2 * Mandelbulber v2, a 3D fractal generator ,=#MKNmMMKmmßMNWy,
3 * ,B" ]L,,p%%%,,,§;, "K
4 * Copyright (C) 2015-20 Mandelbulber Team §R-==%w["'~5]m%=L.=~5N
5 * ,=mm=§M ]=4 yJKA"/-Nsaj "Bw,==,,
6 * This file is part of Mandelbulber. §R.r= jw",M Km .mM FW ",§=ß., ,TN
7 * ,4R =%["w[N=7]J '"5=],""]]M,w,-; T=]M
8 * Mandelbulber is free software: §R.ß~-Q/M=,=5"v"]=Qf,'§"M= =,M.§ Rz]M"Kw
9 * you can redistribute it and/or §w "xDY.J ' -"m=====WeC=\ ""%""y=%"]"" §
10 * modify it under the terms of the "§M=M =D=4"N #"%==A%p M§ M6 R' #"=~.4M
11 * GNU General Public License as §W =, ][T"]C § § '§ e===~ U !§[Z ]N
12 * published by the 4M",,Jm=,"=e~ § § j]]""N BmM"py=ßM
13 * Free Software Foundation, ]§ T,M=& 'YmMMpM9MMM%=w=,,=MT]M m§;'§,
14 * either version 3 of the License, TWw [.j"5=~N[=§%=%W,T ]R,"=="Y[LFT ]N
15 * or (at your option) TW=,-#"%=;[ =Q:["V"" ],,M.m == ]N
16 * any later version. J§"mr"] ,=,," =="""J]= M"M"]==ß"
17 * §= "=C=4 §"eM "=B:m|4"]#F,§~
18 * Mandelbulber is distributed in "9w=,,]w em%wJ '"~" ,=,,ß"
19 * the hope that it will be useful, . "K= ,=RMMMßM"""
20 * but WITHOUT ANY WARRANTY; .'''
21 * without even the implied warranty
22 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
23 *
24 * See the GNU General Public License for more details.
25 * You should have received a copy of the GNU General Public License
26 * along with Mandelbulber. If not, see <http://www.gnu.org/licenses/>.
27 *
28 * ###########################################################################
29 *
30 * Authors: Krzysztof Marczak (buddhi1980@gmail.com), Sebastian Jennen (jenzebas@gmail.com)
31 *
32 * cHeadless - class to handle CLI instructions without GUI manipulation
33 */
34
35 #include "headless.h"
36
37 #include <memory>
38
39 #include "animation_flight.hpp"
40 #include "animation_keyframes.hpp"
41 #include "cimage.hpp"
42 #include "error_message.hpp"
43 #include "file_image.hpp"
44 #include "files.h"
45 #include "fractal_container.hpp"
46 #include "global_data.hpp"
47 #include "initparameters.hpp"
48 #include "interface.hpp"
49 #include "mesh_export.hpp"
50 #include "opencl_engine_render_fractal.h"
51 #include "opencl_engine_render_ssao.h"
52 #include "opencl_global.h"
53 #include "queue.hpp"
54 #include "render_job.hpp"
55 #include "rendering_configuration.hpp"
56 #include "system_data.hpp"
57 #include "voxel_export.hpp"
58 #include "wait.hpp"
59
cHeadless(QObject * parent)60 cHeadless::cHeadless(QObject *parent) : QObject(parent) {}
61
62 cHeadless::~cHeadless() = default;
63
RenderStillImage(QString filename,QString imageFileFormat)64 void cHeadless::RenderStillImage(QString filename, QString imageFileFormat)
65 {
66 std::shared_ptr<cImage> image(
67 new cImage(gPar->Get<int>("image_width"), gPar->Get<int>("image_height")));
68 std::unique_ptr<cRenderJob> renderJob(
69 new cRenderJob(gPar, gParFractal, image, &gMainInterface->stopRequest));
70
71 QObject::connect(renderJob.get(),
72 SIGNAL(updateProgressAndStatus(const QString &, const QString &, double)), this,
73 SLOT(slotUpdateProgressAndStatus(const QString &, const QString &, double)));
74 QObject::connect(renderJob.get(), SIGNAL(updateStatistics(cStatistics)), this,
75 SLOT(slotUpdateStatistics(cStatistics)));
76
77 #ifdef USE_OPENCL
78 // connect signal for progress bar update
79 connect(gOpenCl->openClEngineRenderFractal,
80 SIGNAL(updateProgressAndStatus(const QString &, const QString &, double)), this,
81 SLOT(slotUpdateProgressAndStatus(const QString &, const QString &, double)));
82 connect(gOpenCl->openClEngineRenderSSAO,
83 SIGNAL(updateProgressAndStatus(const QString &, const QString &, double)), this,
84 SLOT(slotUpdateProgressAndStatus(const QString &, const QString &, double)));
85 #endif
86
87 cRenderingConfiguration config;
88 config.DisableRefresh();
89 config.DisableProgressiveRender();
90 config.EnableNetRender();
91
92 renderJob->Init(cRenderJob::still, config);
93 renderJob->Execute();
94
95 QString filenameWithoutExtension = ImageFileSave::ImageNameWithoutExtension(filename);
96
97 QString ext;
98 if (imageFileFormat == "png16" || imageFileFormat == "png16alpha")
99 {
100 ext = ".png";
101 ImageFileSave::structSaveImageChannel saveImageChannel(
102 ImageFileSave::IMAGE_CONTENT_COLOR, ImageFileSave::IMAGE_CHANNEL_QUALITY_16, "");
103 ImageFileSave::ImageConfig imageConfig;
104 imageConfig.insert(ImageFileSave::IMAGE_CONTENT_COLOR, saveImageChannel);
105 bool appendAlpha = (imageFileFormat == "png16alpha");
106 ImageFileSavePNG imageSaver(filenameWithoutExtension, image, imageConfig);
107 imageSaver.SetAppendAlphaCustom(appendAlpha);
108 imageSaver.SaveImage();
109 }
110 else
111 {
112 ext = "." + imageFileFormat;
113 ImageFileSave::enumImageFileType imageFileType = ImageFileSave::ImageFileType(imageFileFormat);
114 SaveImage(filenameWithoutExtension + ext, imageFileType, image, this);
115 }
116
117 QTextStream out(stdout);
118 out << tr("Image saved to: %1\n").arg(filenameWithoutExtension + ext);
119
120 emit finished();
121 }
122
RenderQueue()123 void cHeadless::RenderQueue()
124 {
125 gQueue->slotQueueRender();
126
127 while (true)
128 {
129 do
130 {
131 gApplication->processEvents();
132 Wait(100);
133 } while (gQueue->GetQueueSize() > 0);
134
135 cErrorMessage::showMessage(
136 "Queue is empty. Waiting for something to do.", cErrorMessage::infoMessage);
137 int intProgress = 0;
138 QTextStream out(stdout);
139 do
140 {
141 gApplication->processEvents();
142 Wait(100);
143 if (!systemData.isOutputTty) continue;
144
145 QString progressChars = "|\\-/";
146 intProgress = (intProgress + 1) % progressChars.length();
147 QString text = colorize(progressChars.mid(intProgress, 1), ansiBlue, noExplicitColor, true)
148 + QString(" Waiting ...") + "\r";
149 out << text;
150 out.flush();
151 } while (gQueue->GetQueueSize() == 0);
152
153 gQueue->slotQueueRender();
154 }
155 }
156
RenderVoxel(QString voxelFormat)157 void cHeadless::RenderVoxel(QString voxelFormat)
158 {
159 CVector3 limitMin;
160 CVector3 limitMax;
161 if (gPar->Get<bool>("voxel_custom_limit_enabled"))
162 {
163 limitMin = gPar->Get<CVector3>("voxel_limit_min");
164 limitMax = gPar->Get<CVector3>("voxel_limit_max");
165 }
166 else
167 {
168 limitMin = gPar->Get<CVector3>("limit_min");
169 limitMax = gPar->Get<CVector3>("limit_max");
170 }
171 int maxIter = gPar->Get<int>("voxel_max_iter");
172 int samplesX = gPar->Get<int>("voxel_samples_x");
173 int samplesY = gPar->Get<int>("voxel_samples_y");
174 int samplesZ = gPar->Get<int>("voxel_samples_z");
175 bool greyscale = gPar->Get<bool>("voxel_greyscale_iterations");
176
177 if (samplesX > 0 && samplesY > 0 && samplesZ > 0)
178 {
179
180 if (voxelFormat == "slice")
181 {
182 QString folderString = gPar->Get<QString>("voxel_image_path");
183 QDir folder(folderString);
184 std::unique_ptr<cVoxelExport> voxelExport(new cVoxelExport(
185 samplesX, samplesY, samplesZ, limitMin, limitMax, folder, maxIter, greyscale));
186 QObject::connect(voxelExport.get(),
187 SIGNAL(updateProgressAndStatus(const QString &, const QString &, double)), this,
188 SLOT(slotUpdateProgressAndStatus(const QString &, const QString &, double)));
189 voxelExport->ProcessVolume();
190 }
191 else if (voxelFormat == "ply")
192 {
193 QString fileString = gPar->Get<QString>("mesh_output_filename");
194 QList<MeshFileSave::enumMeshContentType> meshContent({MeshFileSave::MESH_CONTENT_GEOMETRY});
195 if (gPar->Get<bool>("mesh_color")) meshContent << MeshFileSave::MESH_CONTENT_COLOR;
196 MeshFileSave::structSaveMeshConfig meshConfig(MeshFileSave::MESH_FILE_TYPE_PLY, meshContent,
197 MeshFileSave::enumMeshFileModeType(gPar->Get<int>("mesh_file_mode")));
198
199 std::unique_ptr<cMeshExport> meshExport(new cMeshExport(
200 samplesX, samplesY, samplesZ, limitMin, limitMax, fileString, maxIter, meshConfig));
201 QObject::connect(meshExport.get(),
202 SIGNAL(updateProgressAndStatus(const QString &, const QString &, double)), this,
203 SLOT(slotUpdateProgressAndStatus(const QString &, const QString &, double)));
204 meshExport->ProcessVolume();
205 }
206 }
207 emit finished();
208 }
209
RenderFlightAnimation()210 void cHeadless::RenderFlightAnimation()
211 {
212 std::shared_ptr<cImage> image(
213 new cImage(gPar->Get<int>("image_width"), gPar->Get<int>("image_height")));
214 gFlightAnimation =
215 new cFlightAnimation(gMainInterface, gAnimFrames, image, nullptr, gPar, gParFractal, this);
216 QObject::connect(gFlightAnimation,
217 SIGNAL(updateProgressAndStatus(
218 const QString &, const QString &, double, cProgressText::enumProgressType)),
219 this,
220 SLOT(slotUpdateProgressAndStatus(
221 const QString &, const QString &, double, cProgressText::enumProgressType)));
222 // QObject::connect(gFlightAnimation, SIGNAL(updateProgressHide(cProgressText::enumProgressType)),
223 // unused
224 // this, SLOT(slotUpdateProgressHide(cProgressText::enumProgressType)));
225 QObject::connect(gFlightAnimation, SIGNAL(updateStatistics(cStatistics)), this,
226 SLOT(slotUpdateStatistics(cStatistics)));
227 gFlightAnimation->slotRenderFlight();
228 delete gFlightAnimation;
229 gFlightAnimation = nullptr;
230 return;
231 }
232
RenderKeyframeAnimation()233 void cHeadless::RenderKeyframeAnimation()
234 {
235 std::shared_ptr<cImage> image(
236 new cImage(gPar->Get<int>("image_width"), gPar->Get<int>("image_height")));
237 gKeyframeAnimation =
238 new cKeyframeAnimation(gMainInterface, gKeyframes, image, nullptr, gPar, gParFractal, this);
239 QObject::connect(gKeyframeAnimation,
240 SIGNAL(updateProgressAndStatus(
241 const QString &, const QString &, double, cProgressText::enumProgressType)),
242 this,
243 SLOT(slotUpdateProgressAndStatus(
244 const QString &, const QString &, double, cProgressText::enumProgressType)));
245 // QObject::connect(gKeyframeAnimation,
246 // SIGNAL(updateProgressHide(cProgressText::enumProgressType)), unused
247 // this, SLOT(slotUpdateProgressHide(cProgressText::enumProgressType)));
248 QObject::connect(gKeyframeAnimation, SIGNAL(updateStatistics(cStatistics)), this,
249 SLOT(slotUpdateStatistics(cStatistics)));
250 gKeyframeAnimation->slotRenderKeyframes();
251 delete gKeyframeAnimation;
252 gKeyframeAnimation = nullptr;
253 return;
254 }
255
slotNetRender()256 void cHeadless::slotNetRender()
257 {
258 gMainInterface->stopRequest = true;
259 std::shared_ptr<cImage> image(
260 new cImage(gPar->Get<int>("image_width"), gPar->Get<int>("image_height")));
261 std::unique_ptr<cRenderJob> renderJob(
262 new cRenderJob(gPar, gParFractal, image, &gMainInterface->stopRequest));
263 QObject::connect(renderJob.get(),
264 SIGNAL(updateProgressAndStatus(const QString &, const QString &, double)), this,
265 SLOT(slotUpdateProgressAndStatus(const QString &, const QString &, double)));
266 QObject::connect(renderJob.get(), SIGNAL(updateStatistics(cStatistics)), this,
267 SLOT(slotUpdateStatistics(cStatistics)));
268
269 cRenderingConfiguration config;
270 config.DisableRefresh();
271 config.DisableProgressiveRender();
272 config.EnableNetRender();
273
274 renderJob->Init(cRenderJob::still, config);
275 renderJob->Execute();
276
277 emit finished();
278 }
279
slotUpdateProgressAndStatus(const QString & text,const QString & progressText,double progress,cProgressText::enumProgressType progressType)280 void cHeadless::slotUpdateProgressAndStatus(const QString &text, const QString &progressText,
281 double progress, cProgressText::enumProgressType progressType)
282 {
283 static bool firstCallProgressUpdate = true;
284 if (firstCallProgressUpdate)
285 {
286 firstCallProgressUpdate = false;
287 QTextStream out(stdout);
288 out << "\n\n\n";
289 if (systemData.statsOnCLI) out << "\n\n";
290 out.flush();
291 }
292 if (systemData.statsOnCLI) MoveCursor(0, -2);
293 if (gMainInterface->headless)
294 {
295 switch (progressType)
296 {
297 case cProgressText::progress_IMAGE: MoveCursor(0, -1); break;
298 case cProgressText::progress_ANIMATION: MoveCursor(0, -2); break;
299 case cProgressText::progress_QUEUE: MoveCursor(0, -3); break;
300 }
301
302 // not enough space to display information in animation bar
303 QString displayText = (progressType == cProgressText::progress_ANIMATION ? "" : text);
304
305 RenderingProgressOutput(displayText, progressText, progress);
306
307 switch (progressType)
308 {
309 case cProgressText::progress_QUEUE:
310 MoveCursor(0, 1);
311 EraseLine();
312 MoveCursor(0, 1);
313 EraseLine();
314 MoveCursor(0, 1);
315 EraseLine();
316 break;
317 case cProgressText::progress_ANIMATION:
318 MoveCursor(0, 1);
319 EraseLine();
320 MoveCursor(0, 1);
321 EraseLine();
322 break;
323 case cProgressText::progress_IMAGE: MoveCursor(0, 1); break;
324 }
325 if (systemData.statsOnCLI) MoveCursor(0, 2);
326 }
327 }
328
slotUpdateStatistics(const cStatistics & stat) const329 void cHeadless::slotUpdateStatistics(const cStatistics &stat) const
330 {
331 if (!systemData.statsOnCLI) return;
332 /*ui->label_histogram_de->SetBarColor(QColor(0, 255, 0));
333 ui->label_histogram_de->UpdateHistogram(stat.histogramStepCount);
334 ui->label_histogram_iter->UpdateHistogram(stat.histogramIterations);
335 */
336 if (systemData.statsOnCLI) MoveCursor(0, -2);
337 QTextStream out(stdout);
338 QString statsText = "";
339 statsText += tr("Total number of iters").leftJustified(25, ' ') + ": ";
340 statsText += colorize(QString::number(stat.GetTotalNumberOfIterations()).rightJustified(12, ' '),
341 ansiBlue, noExplicitColor, true)
342 + ", ";
343 statsText += tr("Number of iters / pixel").leftJustified(25, ' ') + ": ";
344 statsText +=
345 colorize(QString::number(stat.GetNumberOfIterationsPerPixel()).rightJustified(12, ' '),
346 ansiBlue, noExplicitColor, true)
347 + "\n";
348 statsText += tr("Number of iters / second").leftJustified(25, ' ') + ": ";
349 statsText +=
350 colorize(QString::number(stat.GetNumberOfIterationsPerSecond()).rightJustified(12, ' '),
351 ansiBlue, noExplicitColor, true)
352 + ", ";
353 statsText += tr("Percentage of wrong DE").leftJustified(25, ' ') + ": ";
354 statsText += colorize(QString::number(stat.GetMissedDEPercentage()).rightJustified(12, ' '),
355 ansiBlue, noExplicitColor, true)
356 + "\n";
357 out << statsText;
358 out.flush();
359 }
360
RenderingProgressOutput(const QString & header,const QString & progressTxt,double percentDone)361 void cHeadless::RenderingProgressOutput(
362 const QString &header, const QString &progressTxt, double percentDone)
363 {
364 QTextStream out(stdout);
365 QString formattedText = formatLine(progressTxt) + " ";
366 QString useHeader = header;
367 QString text;
368 if (systemData.terminalWidth > 0)
369 {
370 if (useHeader != "") useHeader += ": ";
371 int freeWidth = systemData.terminalWidth - progressTxt.length() - useHeader.length() - 4;
372 int intProgress = freeWidth * percentDone;
373 if (systemData.isOutputTty) text = "\r";
374 text += colorize(useHeader, ansiYellow, noExplicitColor, true);
375 text += formattedText;
376 text += colorize("[", ansiBlue, noExplicitColor, true);
377 text += colorize(QString(intProgress, '#'), ansiMagenta, noExplicitColor, true);
378 text += QString(freeWidth - intProgress, ' ');
379 text += colorize("]", ansiBlue, noExplicitColor, true);
380 text += "\n";
381 }
382 else
383 {
384 text += QString("\n");
385 }
386 out << text;
387 out.flush();
388 }
389
colorize(QString text,ansiColor foregroundColor,ansiColor backgroundColor,bool bold)390 QString cHeadless::colorize(
391 QString text, ansiColor foregroundColor, ansiColor backgroundColor, bool bold)
392 {
393 // more information on ANSI escape codes here: https://en.wikipedia.org/wiki/ANSI_escape_code
394 if (!systemData.isOutputTty) return text;
395 if (!systemData.useColor) return text;
396
397 QStringList ansiSequence;
398 if (foregroundColor != noExplicitColor) ansiSequence << QString::number(foregroundColor + 30);
399 if (backgroundColor != noExplicitColor) ansiSequence << QString::number(backgroundColor + 40);
400 if (bold) ansiSequence << "1";
401
402 if (ansiSequence.size() == 0) return text;
403
404 QString colorizedString = "\033["; // start ANSI escape sequence
405 colorizedString += ansiSequence.join(";");
406 colorizedString += "m"; // end ANSI escape sequence
407 colorizedString += text;
408 colorizedString += "\033[0m"; // reset color and bold after string
409 return colorizedString;
410 }
411
formatLine(const QString & text)412 QString cHeadless::formatLine(const QString &text)
413 {
414 if (!systemData.isOutputTty) return text;
415 if (!systemData.useColor) return text;
416 QList<QRegularExpression> reType;
417 reType.append(
418 QRegularExpression("^(Done )(.*?)(, )(elapsed: )(.*?)(, )(estimated to end: )(.*)"));
419 reType.append(QRegularExpression("^(Gotowe )(.*?)(, )(upłynęło: )(.*?)(, )(do końca: )(.*)"));
420 reType.append(QRegularExpression(
421 "^(Fortschritt )(.*?)(, )(vergangen: )(.*?)(, )(voraussichtlich noch: )(.*)"));
422
423 reType.append(QRegularExpression("^(.*?)( Done)(, )(total time: )(.*)"));
424 reType.append(QRegularExpression("^(.*?)( gotowe)(, )(całkowity czas: )(.*)"));
425 reType.append(QRegularExpression("^(.*?)( Fertig)(, )(Gesamtzeit: )(.*)"));
426
427 // animation
428 reType.append(QRegularExpression(
429 "^(Frame .*? of .*? Done )(.*?)(, )(elapsed: )(.*?)(, )(estimated to end: )(.*)"));
430 reType.append(QRegularExpression(
431 "^(Klatka .*? z .*? Gotowe )(.*?)(, )(upłynęło: )(.*?)(, )(do końca: )(.*)"));
432 reType.append(
433 QRegularExpression("^(Frame .*? von .*? Fortschritt )(.*?)(, )(vergangen: )(.*?)(, "
434 ")(voraussichtlich noch: )(.*)"));
435
436 QRegularExpressionMatch matchType;
437 for (const auto &i : reType)
438 {
439 matchType = i.match(text);
440 if (matchType.hasMatch()) break;
441 }
442
443 if (!matchType.hasMatch())
444 {
445 return text;
446 }
447
448 QString out = "";
449 out += colorize(matchType.captured(1), noExplicitColor, noExplicitColor, false);
450 out += colorize(matchType.captured(2), noExplicitColor, noExplicitColor, true);
451 out += colorize(matchType.captured(3), noExplicitColor, noExplicitColor, false);
452
453 out += colorize(matchType.captured(4), ansiGreen, noExplicitColor, false);
454 out += colorize(matchType.captured(5), ansiGreen, noExplicitColor, true);
455
456 if (matchType.lastCapturedIndex() == 8)
457 {
458 out += colorize(matchType.captured(6), noExplicitColor, noExplicitColor, false);
459 out += colorize(matchType.captured(7), ansiCyan, noExplicitColor, false);
460 out += colorize(matchType.captured(8), ansiCyan, noExplicitColor, true);
461 }
462
463 return out;
464 }
465
ConfirmMessage(QString message)466 bool cHeadless::ConfirmMessage(QString message)
467 {
468 QTextStream out(stdout);
469 QTextStream in(stdin);
470 out << message << " y/n\n";
471 out.flush();
472 QString response = in.readLine().toLower();
473 return (response == "y");
474 }
475
EraseLine()476 void cHeadless::EraseLine()
477 {
478 if (!systemData.isOutputTty) return;
479 QTextStream out(stdout);
480 out << "\033[2K";
481 out.flush();
482 }
483
MoveCursor(int leftRight,int downUp)484 void cHeadless::MoveCursor(int leftRight, int downUp)
485 {
486 if (!systemData.isOutputTty) return;
487 QTextStream out(stdout);
488 if (leftRight != 0)
489 {
490 QString code = "\033[";
491 code += (leftRight > 0) ? QString::number(leftRight) : QString::number(leftRight * -1);
492 code += (leftRight > 0) ? "C" : "D";
493 out << code;
494 }
495 if (downUp != 0)
496 {
497 QString code = "\033[";
498 code += (downUp > 0) ? QString::number(downUp) : QString::number(downUp * -1);
499 code += (downUp > 0) ? "B" : "A";
500 out << code;
501 }
502 out.flush();
503 }
504