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