1 /*******************************************************************************
2  * unixconsole.cpp
3  *
4  * Adapted from vfe/win/console/winconsole.cpp
5  *
6  * ---------------------------------------------------------------------------
7  * Persistence of Vision Ray Tracer ('POV-Ray') version 3.7.
8  * Copyright 1991-2013 Persistence of Vision Raytracer Pty. Ltd.
9  *
10  * POV-Ray is free software: you can redistribute it and/or modify
11  * it under the terms of the GNU Affero General Public License as
12  * published by the Free Software Foundation, either version 3 of the
13  * License, or (at your option) any later version.
14  *
15  * POV-Ray is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU Affero General Public License for more details.
19  *
20  * You should have received a copy of the GNU Affero General Public License
21  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
22  * ---------------------------------------------------------------------------
23  * POV-Ray is based on the popular DKB raytracer version 2.12.
24  * DKBTrace was originally written by David K. Buck.
25  * DKBTrace Ver 2.0-2.12 were written by David K. Buck & Aaron A. Collins.
26  * ---------------------------------------------------------------------------
27  * $File: //depot/public/povray/3.x/vfe/unix/unixconsole.cpp $
28  * $Revision: #1 $
29  * $Change: 6069 $
30  * $DateTime: 2013/11/06 11:59:40 $
31  * $Author: chrisc $
32  *******************************************************************************/
33 
34 #include <signal.h>
35 #include <sys/select.h>
36 #include <termios.h>
37 #include <unistd.h>
38 #include <boost/shared_ptr.hpp>
39 
40 #include "vfe.h"
41 #include "backend/control/benchmark.h"
42 #include "povray.h"
43 #include "disp.h"
44 #include "disp_text.h"
45 #include "disp_sdl.h"
46 
47 namespace pov_frontend
48 {
49 	shared_ptr<Display> gDisplay;
50 }
51 
52 using namespace vfe;
53 using namespace vfePlatform;
54 
55 enum DispMode
56 {
57 	DISP_MODE_NONE,
58 	DISP_MODE_TEXT,
59 	DISP_MODE_SDL
60 };
61 
62 DispMode gDisplayMode;
63 
64 enum ReturnValue
65 {
66 	RETURN_OK=0,
67 	RETURN_ERROR,
68 	RETURN_USER_ABORT
69 };
70 
71 bool gCancelRender = false;
72 
73 // for handling asynchronous (external) signals
74 int gSignalNumber = 0;
75 boost::mutex gSignalMutex;
76 
77 
SignalHandler(void)78 void SignalHandler (void)
79 {
80 	sigset_t sigset;
81 	int      signum;
82 
83 	while(true)
84 	{
85 		sigfillset(&sigset);
86 		sigwait(&sigset, &signum);  // wait till a signal is caught
87 		boost::mutex::scoped_lock lock(gSignalMutex);
88 		gSignalNumber = signum;
89 	}
90 }
91 
92 
ProcessSignal(void)93 void ProcessSignal (void)
94 {
95 	boost::mutex::scoped_lock lock(gSignalMutex);
96 
97 	switch (gSignalNumber)
98 	{
99 		case  0:
100 			break;
101 #ifdef SIGQUIT
102 		case SIGQUIT:
103 			fprintf(stderr, "\n%s: received signal SIGQUIT: Quit; requested render cancel\n", PACKAGE);
104 			gCancelRender = true;
105 			break;
106 #endif
107 #ifdef SIGTERM
108 		case SIGTERM:
109 			fprintf(stderr, "\n%s: received signal SIGTERM: Termination; requested render cancel\n", PACKAGE);
110 			gCancelRender = true;
111 			break;
112 #endif
113 #ifdef SIGINT
114 		case SIGINT:
115 			fprintf(stderr, "\n%s: received signal SIGINT: Interrupt; requested render cancel\n", PACKAGE);
116 			gCancelRender = true;
117 			break;
118 #endif
119 #ifdef SIGPIPE
120 		case SIGPIPE:
121 			fprintf(stderr, "\n%s: received signal SIGPIPE: Broken pipe; requested render cancel\n", PACKAGE);
122 			gCancelRender = true;
123 			break;
124 #endif
125 		case SIGCHLD:
126 			// for now, ignore this (side-effect of the shell-out code).
127 			// once properly implemented, the shell-out code would want to know this has happened, though.
128 			break;
129 
130 		default:
131 			// fprintf(stderr, "\n%s: received signal %d\n", PACKAGE, gSignalNumber);
132 			break;
133 	}
134 	gSignalNumber = 0;
135 }
136 
UnixDisplayCreator(unsigned int width,unsigned int height,GammaCurvePtr gamma,vfeSession * session,bool visible)137 vfeDisplay *UnixDisplayCreator (unsigned int width, unsigned int height, GammaCurvePtr gamma, vfeSession *session, bool visible)
138 {
139 	UnixDisplay *display = GetRenderWindow () ;
140 	switch (gDisplayMode)
141 	{
142 #ifdef HAVE_LIBSDL
143 		case DISP_MODE_SDL:
144 			if (display != NULL && display->GetWidth() == width && display->GetHeight() == height)
145 			{
146 				UnixDisplay *p = new UnixSDLDisplay (width, height, gamma, session, false) ;
147 				if (p->TakeOver (display))
148 					return p;
149 				delete p;
150 			}
151 			return new UnixSDLDisplay (width, height, gamma, session, visible) ;
152 			break;
153 #endif
154 		case DISP_MODE_TEXT:
155 			return new UnixTextDisplay (width, height, gamma, session, visible) ;
156 			break;
157 		default:
158 			return NULL;
159 	}
160 }
161 
PrintStatus(vfeSession * session)162 void PrintStatus (vfeSession *session)
163 {
164 	string str;
165 	vfeSession::MessageType type;
166 	static vfeSession::MessageType lastType = vfeSession::mUnclassified;
167 
168 	while (session->GetNextCombinedMessage (type, str))
169 	{
170 		if (type != vfeSession::mGenericStatus)
171 		{
172 			if (lastType == vfeSession::mGenericStatus)
173 				fprintf (stderr, "\n") ;
174 			fprintf (stderr, "%s\n", str.c_str());
175 		}
176 		else
177 			fprintf (stderr, "%s\r", str.c_str());
178 		lastType = type;
179 	}
180 }
181 
PrintStatusChanged(vfeSession * session,State force=kUnknown)182 void PrintStatusChanged (vfeSession *session, State force = kUnknown)
183 {
184 	if (force == kUnknown)
185 		force = session->GetBackendState();
186 	switch (force)
187 	{
188 		case kParsing:
189 			fprintf (stderr, "==== [Parsing...] ==========================================================\n");
190 			break;
191 		case kRendering:
192 			fprintf (stderr, "==== [Rendering...] ========================================================\n");
193 			break;
194 		case kPausedRendering:
195 			fprintf (stderr, "==== [Paused] ==============================================================\n");
196 			break;
197 	}
198 }
199 
PrintVersion(void)200 void PrintVersion(void)
201 {
202 	fprintf(stderr,
203 		"%s %s\n\n"
204 		"%s\n%s\n%s\n"
205 		"%s\n%s\n%s\n\n",
206 		PACKAGE_NAME, POV_RAY_VERSION,
207 		DISTRIBUTION_MESSAGE_1, DISTRIBUTION_MESSAGE_2, DISTRIBUTION_MESSAGE_3,
208 		POV_RAY_COPYRIGHT, DISCLAIMER_MESSAGE_1, DISCLAIMER_MESSAGE_2
209 	);
210 	fprintf(stderr,
211 		"Built-in features:\n"
212 		"  I/O restrictions:          %s\n"
213 		"  X Window display:          %s\n"
214 		"  Supported image formats:   %s\n"
215 		"  Unsupported image formats: %s\n\n",
216 		BUILTIN_IO_RESTRICTIONS, BUILTIN_XWIN_DISPLAY, BUILTIN_IMG_FORMATS, MISSING_IMG_FORMATS
217 	);
218 	fprintf(stderr,
219 		"Compilation settings:\n"
220 		"  Build architecture:  %s\n"
221 		"  Built/Optimized for: %s\n"
222 		"  Compiler vendor:     %s\n"
223 		"  Compiler version:    %s\n"
224 		"  Compiler flags:      %s\n",
225 		BUILD_ARCH, BUILT_FOR, COMPILER_VENDOR, COMPILER_VERSION, CXXFLAGS
226 	);
227 }
228 
ErrorExit(vfeSession * session)229 void ErrorExit(vfeSession *session)
230 {
231 	fprintf(stderr, "%s\n", session->GetErrorString());
232 	session->Shutdown();
233 	delete session;
234 	exit(RETURN_ERROR);
235 }
236 
CancelRender(vfeSession * session)237 void CancelRender(vfeSession *session)
238 {
239 	session->CancelRender();  // request the backend to cancel
240 	PrintStatus (session);
241 	while (session->GetBackendState() != kReady)  // wait for the render to effectively shut down
242 		Delay(10);
243 	PrintStatus (session);
244 }
245 
PauseWhenDone(vfeSession * session)246 void PauseWhenDone(vfeSession *session)
247 {
248 	GetRenderWindow()->UpdateScreen(true);
249 	GetRenderWindow()->PauseWhenDoneNotifyStart();
250 	while (GetRenderWindow()->PauseWhenDoneResumeIsRequested() == false)
251 	{
252 		ProcessSignal();
253 		if (gCancelRender)
254 			break;
255 		else
256 			Delay(10);
257 	}
258 	GetRenderWindow()->PauseWhenDoneNotifyEnd();
259 }
260 
PrepareBenchmark(vfeSession * session,vfeRenderOptions & opts,string & ini,string & pov,int argc,char ** argv)261 ReturnValue PrepareBenchmark(vfeSession *session, vfeRenderOptions& opts, string& ini, string& pov, int argc, char **argv)
262 {
263 	// parse command-line options
264 	while (*++argv)
265 	{
266 		string s = string(*argv);
267 		boost::to_lower(s);
268 		// set number of threads to run the benchmark
269 		if (boost::starts_with(s, "+wt") || boost::starts_with(s, "-wt"))
270 		{
271 			s.erase(0, 3);
272 			int n = atoi(s.c_str());
273 			if (n)
274 				opts.SetThreadCount(n);
275 			else
276 				fprintf(stderr, "%s: ignoring malformed '%s' command-line option\n", PACKAGE, *argv);
277 		}
278 		// add library path
279 		else if (boost::starts_with(s, "+l") || boost::starts_with(s, "-l"))
280 		{
281 			s.erase(0, 2);
282 			opts.AddLibraryPath(s);
283 		}
284 	}
285 
286 	int benchversion = pov::Get_Benchmark_Version();
287 	fprintf(stderr, "\
288 %s %s%s\n\n\
289 Entering the standard POV-Ray %s benchmark version %x.%02x.\n\n\
290 This built-in benchmark requires POV-Ray to be installed on your system\n\
291 before running it.  There will be neither display nor file output, and\n\
292 any additional command-line option except setting the number of render\n\
293 threads (+wtN for N threads) and library paths (+Lpath) will be ignored.\n\
294 To get an accurate benchmark result you might consider running POV-Ray\n\
295 with the Unix 'time' command (e.g. 'time povray -benchmark').\n\n\
296 The benchmark will run using %d render thread(s).\n\
297 Press <Enter> to continue or <Ctrl-C> to abort.\n\
298 ",
299 		PACKAGE_NAME, POV_RAY_VERSION, COMPILER_VER,
300 		VERSION_BASE, benchversion / 256, benchversion % 256,
301 		opts.GetThreadCount()
302 	);
303 
304 	// wait for user input from stdin (including abort signals)
305 	while (true)
306 	{
307 		ProcessSignal();
308 		if (gCancelRender)
309 		{
310 			fprintf(stderr, "Render cancelled by user\n");
311 			return RETURN_USER_ABORT;
312 		}
313 
314 		fd_set readset;
315 		struct timeval tv = {0,0};  // no timeout
316 		FD_ZERO(&readset);
317 		FD_SET(STDIN_FILENO, &readset);
318 		if (select(STDIN_FILENO+1, &readset, NULL, NULL, &tv) < 0)
319 			break;
320 		if (FD_ISSET(STDIN_FILENO, &readset))  // user input is available
321 		{
322 			char s[3];
323 			read(STDIN_FILENO, s, 1);         // read till <ENTER> is hit
324 			tcflush(STDIN_FILENO, TCIFLUSH);  // discard unread data
325 			break;
326 		}
327 		Delay(20);
328 	}
329 
330 	string basename = UCS2toASCIIString(session->CreateTemporaryFile());
331 	ini = basename + ".ini";
332 	pov = basename + ".pov";
333 	if (pov::Write_Benchmark_File(pov.c_str(), ini.c_str()))
334 	{
335 		fprintf(stderr, "%s: creating %s\n", PACKAGE, ini.c_str());
336 		fprintf(stderr, "%s: creating %s\n", PACKAGE, pov.c_str());
337 		fprintf(stderr, "Running standard POV-Ray benchmark version %x.%02x\n", benchversion / 256, benchversion % 256);
338 	}
339 	else
340 	{
341 		fprintf(stderr, "%s: failed to write temporary files for benchmark\n", PACKAGE);
342 		return RETURN_ERROR;
343 	}
344 
345 	return RETURN_OK;
346 }
347 
CleanupBenchmark(vfeUnixSession * session,string & ini,string & pov)348 void CleanupBenchmark(vfeUnixSession *session, string& ini, string& pov)
349 {
350 	fprintf(stderr, "%s: removing %s\n", PACKAGE, ini.c_str());
351 	session->DeleteTemporaryFile(ASCIItoUCS2String(ini.c_str()));
352 	fprintf(stderr, "%s: removing %s\n", PACKAGE, pov.c_str());
353 	session->DeleteTemporaryFile(ASCIItoUCS2String(pov.c_str()));
354 }
355 
main(int argc,char ** argv)356 int main (int argc, char **argv)
357 {
358 	vfeUnixSession   *session;
359 	vfeStatusFlags    flags;
360 	vfeRenderOptions  opts;
361 	ReturnValue       retval = RETURN_OK;
362 	bool              running_benchmark = false;
363 	string            bench_ini_name;
364 	string            bench_pov_name;
365 	sigset_t          sigset;
366 	boost::thread    *sigthread;
367 	char **           argv_copy=argv; /* because argv is updated later */
368 	int               argc_copy=argc; /* because it might also be updated */
369 
370 	/*fprintf(stderr, "%s: This is a RELEASE CANDIDATE version of POV-Ray. General distribution is discouraged.\n", PACKAGE);*/
371 
372 	// block some signals for this thread as well as those created afterwards
373 	sigemptyset(&sigset);
374 
375 #ifdef SIGQUIT
376 	sigaddset(&sigset, SIGQUIT);
377 #endif
378 #ifdef SIGTERM
379 	sigaddset(&sigset, SIGTERM);
380 #endif
381 #ifdef SIGINT
382 	sigaddset(&sigset, SIGINT);
383 #endif
384 #ifdef SIGPIPE
385 	sigaddset(&sigset, SIGPIPE);
386 #endif
387 #ifdef SIGCHLD
388 	sigaddset(&sigset, SIGCHLD);
389 #endif
390 
391 	pthread_sigmask(SIG_BLOCK, &sigset, NULL);
392 
393 	// create the signal handling thread
394 	sigthread = new boost::thread(SignalHandler);
395 
396 	session = new vfeUnixSession();
397 	if (session->Initialize(NULL, NULL) != vfeNoError)
398 		ErrorExit(session);
399 
400 	// display mode registration
401 #ifdef HAVE_LIBSDL
402 	if (UnixSDLDisplay::Register(session))
403 		gDisplayMode = DISP_MODE_SDL;
404 	else
405 #endif
406 	if (UnixTextDisplay::Register(session))
407 		gDisplayMode = DISP_MODE_TEXT;
408 	else
409 		gDisplayMode = DISP_MODE_NONE;
410 
411 	// default number of work threads: number of CPUs or 4
412 	int nthreads = 1;
413 #ifdef _SC_NPROCESSORS_ONLN  // online processors
414 	nthreads = sysconf(_SC_NPROCESSORS_ONLN);
415 #endif
416 #ifdef _SC_NPROCESSORS_CONF  // configured processors
417 	if (nthreads < 2)
418 		nthreads = sysconf(_SC_NPROCESSORS_CONF);
419 #endif
420 	if (nthreads < 2)
421 		nthreads = 4;
422 	opts.SetThreadCount(nthreads);
423 
424 	// process command-line options
425 	session->GetUnixOptions()->ProcessOptions(&argc, &argv);
426 	if (session->GetUnixOptions()->isOptionSet("general", "help"))
427 	{
428 		session->Shutdown() ;
429 		PrintStatus (session) ;
430 		// TODO: general usage display (not yet in core code)
431 		session->GetUnixOptions()->PrintOptions();
432 		delete sigthread;
433 		delete session;
434 		return RETURN_OK;
435 	}
436 	else if (session->GetUnixOptions()->isOptionSet("general", "version"))
437 	{
438 		session->Shutdown() ;
439 		PrintVersion();
440 		delete sigthread;
441 		delete session;
442 		return RETURN_OK;
443 	}
444 	else if (session->GetUnixOptions()->isOptionSet("general", "benchmark"))
445 	{
446 		retval = PrepareBenchmark(session, opts, bench_ini_name, bench_pov_name, argc, argv);
447 		if (retval == RETURN_OK)
448 			running_benchmark = true;
449 		else
450 		{
451 			session->Shutdown();
452 			delete sigthread;
453 			delete session;
454 			return retval;
455 		}
456 	}
457 
458 	// process INI settings
459 	if (running_benchmark)
460 	{
461 		// read only the provided INI file and set minimal lib paths
462 		opts.AddLibraryPath(string(POVLIBDIR "/include"));
463 		opts.AddINI(bench_ini_name.c_str());
464 		opts.SetSourceFile(bench_pov_name.c_str());
465 	}
466 	else
467 	{
468 		char *s = getenv ("POVINC");
469 		session->SetDisplayCreator(UnixDisplayCreator);
470 		session->GetUnixOptions()->Process_povray_ini(opts);
471 		if (s != NULL)
472 			opts.AddLibraryPath (s);
473 		while (*++argv)
474 			opts.AddCommand (*argv);
475 	}
476 
477 	// set all options and start rendering
478 	if (session->SetOptions(opts) != vfeNoError)
479 	{
480 		fprintf(stderr,"\nProblem with option setting\n");
481 		for(int loony=0;loony<argc_copy;loony++)
482 		{
483 			fprintf(stderr,"%s%c",argv_copy[loony],loony+1<argc_copy?' ':'\n');
484 		}
485 		ErrorExit(session);
486 	}
487 	if (session->StartRender() != vfeNoError)
488 		ErrorExit(session);
489 
490 	// set inter-frame pause for animation
491 	if (session->RenderingAnimation() && session->GetBoolOption("Pause_When_Done", false))
492 		session->PauseWhenDone(true);
493 
494 	// main render loop
495 	session->SetEventMask(stBackendStateChanged);  // immediatly notify this event
496 	while (((flags = session->GetStatus(true, 200)) & stRenderShutdown) == 0)
497 	{
498 		ProcessSignal();
499 		if (gCancelRender)
500 		{
501 			CancelRender(session);
502 			break;
503 		}
504 
505 		if (flags & stAnimationStatus)
506 			fprintf(stderr, "\nRendering frame %d of %d\n", session->GetCurrentFrame(), session->GetTotalFrames());
507 		if (flags & stAnyMessage)
508 			PrintStatus (session);
509 		if (flags & stBackendStateChanged)
510 			PrintStatusChanged (session);
511 
512 		if (GetRenderWindow() != NULL)
513 		{
514 			// early exit
515 			if (GetRenderWindow()->HandleEvents())
516 			{
517 				gCancelRender = true;  // will set proper return value
518 				CancelRender(session);
519 				break;
520 			}
521 
522 			GetRenderWindow()->UpdateScreen();
523 
524 			// inter-frame pause
525 			if (session->GetCurrentFrame() < session->GetTotalFrames()
526 			&& session->GetPauseWhenDone()
527 			&& (flags & stAnimationFrameCompleted) != 0
528 			&& session->Failed() == false)
529 			{
530 				PauseWhenDone(session);
531 				if (! gCancelRender)
532 					session->Resume();
533 			}
534 		}
535 	}
536 
537 	// pause when done for single or last frame of an animation
538 	if (session->Failed() == false && GetRenderWindow() != NULL && session->GetBoolOption("Pause_When_Done", false))
539 	{
540 		PrintStatusChanged(session, kPausedRendering);
541 		PauseWhenDone(session);
542 		gCancelRender = false;
543 	}
544 
545 	if (running_benchmark)
546 		CleanupBenchmark(session, bench_ini_name, bench_pov_name);
547 
548 	if (session->Succeeded() == false)
549 		retval = gCancelRender ? RETURN_USER_ABORT : RETURN_ERROR;
550 	session->Shutdown();
551 	PrintStatus (session);
552 	delete sigthread;
553 	delete session;
554 
555 	return retval;
556 }
557