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