ViewVC Help
View File | Revision Log | Show Annotations | Download File | Root Listing
root/radiance/ray/src/rt/raypcalls.c
Revision: 2.21
Committed: Sat Dec 12 00:03:42 2009 UTC (14 years, 4 months ago) by greg
Content type: text/plain
Branch: MAIN
Changes since 2.20: +15 -11 lines
Log Message:
Created RNUMBER type (#define) in preparation for 64-bit compiling

File Contents

# Content
1 #ifndef lint
2 static const char RCSid[] = "$Id: raypcalls.c,v 2.20 2008/12/02 23:28:34 greg Exp $";
3 #endif
4 /*
5 * raypcalls.c - interface for parallel rendering using Radiance
6 *
7 * External symbols declared in ray.h
8 */
9
10 #include "copyright.h"
11
12 /*
13 * These calls are designed similarly to the ones in raycalls.c,
14 * but allow for multiple rendering processes on the same host
15 * machine. There is no sense in specifying more child processes
16 * than you have processor cores, but one child may help by allowing
17 * asynchronous ray computation in an interactive program, and
18 * will protect the caller from fatal rendering errors.
19 *
20 * You should first read and understand the header in raycalls.c,
21 * as some things are explained there that are not repated here.
22 *
23 * The first step is opening one or more rendering processes
24 * with a call to ray_pinit(oct, nproc). Before calling fork(),
25 * ray_pinit() loads the octree and data structures into the
26 * caller's memory, and ray_popen() synchronizes the ambient
27 * file, if any. Shared memory permits all sorts of queries
28 * that wouldn't be possible otherwise without causing any real
29 * memory overhead, since all the static data are shared
30 * between processes. Rays are traced using a simple
31 * queuing mechanism, explained below.
32 *
33 * The ray queue buffers RAYQLEN rays before sending to
34 * children, each of which may internally buffer RAYQLEN rays
35 * during evaluation. Rays are not returned in the order
36 * they are sent when multiple processes are open.
37 *
38 * Rays are queued and returned by a single
39 * ray_pqueue() call. A ray_pqueue() return
40 * value of 0 indicates that no rays are ready
41 * and the queue is not yet full. A return value of 1
42 * indicates that a ray was returned, though it is probably
43 * not the one you just requested. Rays may be identified by
44 * the rno member of the RAY struct, which is incremented
45 * by the rayorigin() call, or may be set explicitly by
46 * the caller. Below is an example call sequence:
47 *
48 * myRay.rorg = ( ray origin point )
49 * myRay.rdir = ( normalized ray direction )
50 * myRay.rmax = ( maximum length, or zero for no limit )
51 * rayorigin(&myRay, PRIMARY, NULL, NULL);
52 * myRay.rno = ( my personal ray identifier )
53 * if (ray_pqueue(&myRay) == 1)
54 * { do something with results }
55 *
56 * Note the differences between this and the simpler ray_trace()
57 * call. In particular, the call may or may not return a value
58 * in the passed ray structure. Also, you need to call rayorigin()
59 * yourself, which is normally called for you by ray_trace(). The
60 * benefit is that ray_pqueue() will trace rays faster in
61 * proportion to the number of CPUs you have available on your
62 * system. If the ray queue is full before the call, ray_pqueue()
63 * will block until a result is ready so it can queue this one.
64 * The global int ray_pnidle indicates the number of currently idle
65 * children. If you want to check for completed rays without blocking,
66 * or get the results from rays that have been queued without
67 * queuing any new ones, the ray_presult() call is for you:
68 *
69 * if (ray_presult(&myRay, 1) == 1)
70 * { do something with results }
71 *
72 * If the second argument is 1, the call won't block when
73 * results aren't ready, but will immediately return 0.
74 * If the second argument is 0, the call will block
75 * until a value is available, returning 0 only if the
76 * queue is completely empty. A negative return value
77 * indicates that a rendering process died. If this
78 * happens, ray_pclose(0) is automatically called to close
79 * all child processes, and ray_pnprocs is set to zero.
80 *
81 * If you just want to fill the ray queue without checking for
82 * results, check ray_pnidle and call ray_psend():
83 *
84 * while (ray_pnidle) {
85 * ( set up ray )
86 * ray_psend(&myRay);
87 * }
88 *
89 * Note that it is a fatal error to call ra_psend() when
90 * ray_pnidle is zero. The ray_presult() and/or ray_pqueue()
91 * functions may be called subsequently to read back the results.
92 *
93 * When you are done, you may call ray_pdone(1) to close
94 * all child processes and clean up memory used by Radiance.
95 * Any queued ray calculations will be awaited and discarded.
96 * As with ray_done(), ray_pdone(0) hangs onto data files
97 * and fonts that are likely to be used in subsequent renderings.
98 * Whether you need to clean up memory or not, you should
99 * at least call ray_pclose(0) to await the child processes.
100 *
101 * Warning: You cannot affect any of the rendering processes
102 * by changing global parameter values onece ray_pinit() has
103 * been called. Changing global parameters will have no effect
104 * until the next call to ray_pinit(), which restarts everything.
105 * If you just want to reap children so that you can alter the
106 * rendering parameters without reloading the scene, use the
107 * ray_pclose(0) and ray_popen(nproc) calls to close
108 * then restart the child processes after the changes are made.
109 *
110 * Note: These routines are written to coordinate with the
111 * definitions in raycalls.c, and in fact depend on them.
112 * If you want to trace a ray and get a result synchronously,
113 * use the ray_trace() call to compute it in the parent process.
114 * This will not interfere with any subprocess calculations,
115 * but beware that a fatal error may end with a call to quit().
116 *
117 * Note: One of the advantages of using separate processes
118 * is that it gives the calling program some immunity from
119 * fatal rendering errors. As discussed in raycalls.c,
120 * Radiance tends to throw up its hands and exit at the
121 * first sign of trouble, calling quit() to return control
122 * to the top level. Although you can avoid exit() with
123 * your own longjmp() in quit(), the cleanup afterwards
124 * is always suspect. Through the use of subprocesses,
125 * we avoid this pitfall by closing the processes and
126 * returning a negative value from ray_pqueue() or
127 * ray_presult(). If you get a negative value from either
128 * of these calls, you can assume that the processes have
129 * been cleaned up with a call to ray_pclose(), though you
130 * will have to call ray_pdone() yourself if you want to
131 * free memory. Obviously, you cannot continue rendering
132 * without risking further errors, but otherwise your
133 * process should not be compromised.
134 */
135
136 #include "rtprocess.h"
137 #include "ray.h"
138 #include "ambient.h"
139 #include <sys/types.h>
140 #include <sys/wait.h>
141 #include "selcall.h"
142
143 #ifndef RAYQLEN
144 #define RAYQLEN 12 /* # rays to send at once */
145 #endif
146
147 #ifndef MAX_RPROCS
148 #if (FD_SETSIZE/2-4 < 64)
149 #define MAX_NPROCS (FD_SETSIZE/2-4)
150 #else
151 #define MAX_NPROCS 64 /* max. # rendering processes */
152 #endif
153 #endif
154
155 extern char *shm_boundary; /* boundary of shared memory */
156
157 int ray_pfifo = 0; /* maintain ray call order? */
158 int ray_pnprocs = 0; /* number of child processes */
159 int ray_pnidle = 0; /* number of idle children */
160
161 static struct child_proc {
162 int pid; /* child process id */
163 int fd_send; /* write to child here */
164 int fd_recv; /* read from child here */
165 int npending; /* # rays in process */
166 RNUMBER rno[RAYQLEN]; /* working on these rays */
167 } r_proc[MAX_NPROCS]; /* our child processes */
168
169 static RAY r_queue[2*RAYQLEN]; /* ray i/o buffer */
170 static int r_send_next; /* next send ray placement */
171 static int r_recv_first; /* position of first unreported ray */
172 static int r_recv_next; /* next receive ray placement */
173
174 #define sendq_full() (r_send_next >= RAYQLEN)
175
176 static int ray_pflush(void);
177 static void ray_pchild(int fd_in, int fd_out);
178
179
180 extern void
181 ray_pinit( /* initialize ray-tracing processes */
182 char *otnm,
183 int nproc
184 )
185 {
186 if (nobjects > 0) /* close old calculation */
187 ray_pdone(0);
188
189 ray_init(otnm); /* load the shared scene */
190
191 r_send_next = 0; /* set up queue */
192 r_recv_first = r_recv_next = RAYQLEN;
193
194 ray_popen(nproc); /* fork children */
195 }
196
197
198 static int
199 ray_pflush(void) /* send queued rays to idle children */
200 {
201 int nc, n, nw, i, sfirst;
202
203 if ((ray_pnidle <= 0) | (r_send_next <= 0))
204 return(0); /* nothing we can send */
205
206 sfirst = 0; /* divvy up labor */
207 nc = ray_pnidle;
208 for (i = ray_pnprocs; nc && i--; ) {
209 if (r_proc[i].npending > 0)
210 continue; /* child looks busy */
211 n = (r_send_next - sfirst)/nc--;
212 if (!n)
213 continue;
214 /* smuggle set size in crtype */
215 r_queue[sfirst].crtype = n;
216 nw = writebuf(r_proc[i].fd_send, (char *)&r_queue[sfirst],
217 sizeof(RAY)*n);
218 if (nw != sizeof(RAY)*n)
219 return(-1); /* write error */
220 r_proc[i].npending = n;
221 while (n--) /* record ray IDs */
222 r_proc[i].rno[n] = r_queue[sfirst+n].rno;
223 sfirst += r_proc[i].npending;
224 ray_pnidle--; /* now she's busy */
225 }
226 if (sfirst != r_send_next)
227 error(CONSISTENCY, "code screwup in ray_pflush");
228 r_send_next = 0;
229 return(sfirst); /* return total # sent */
230 }
231
232
233 extern void
234 ray_psend( /* add a ray to our send queue */
235 RAY *r
236 )
237 {
238 if (r == NULL)
239 return;
240 /* flush output if necessary */
241 if (sendq_full() && ray_pflush() <= 0)
242 error(INTERNAL, "ray_pflush failed in ray_psend");
243
244 r_queue[r_send_next++] = *r;
245 }
246
247
248 extern int
249 ray_pqueue( /* queue a ray for computation */
250 RAY *r
251 )
252 {
253 if (r == NULL)
254 return(0);
255 /* check for full send queue */
256 if (sendq_full()) {
257 RAY mySend = *r;
258 /* wait for a result */
259 if (ray_presult(r, 0) <= 0)
260 return(-1);
261 /* put new ray in queue */
262 r_queue[r_send_next++] = mySend;
263 /* XXX r_send_next may now be > RAYQLEN */
264 return(1);
265 }
266 /* else add ray to send queue */
267 r_queue[r_send_next++] = *r;
268 /* check for returned ray... */
269 if (r_recv_first >= r_recv_next)
270 return(0);
271 /* ...one is sitting in queue */
272 *r = r_queue[r_recv_first++];
273 return(1);
274 }
275
276
277 extern int
278 ray_presult( /* check for a completed ray */
279 RAY *r,
280 int poll
281 )
282 {
283 static struct timeval tpoll; /* zero timeval struct */
284 static fd_set readset, errset;
285 int n, ok;
286 register int pn;
287
288 if (r == NULL)
289 return(0);
290 /* check queued results first */
291 if (r_recv_first < r_recv_next) {
292 *r = r_queue[r_recv_first++];
293 return(1);
294 }
295 n = ray_pnprocs - ray_pnidle; /* pending before flush? */
296
297 if (ray_pflush() < 0) /* send new rays to process */
298 return(-1);
299 /* reset receive queue */
300 r_recv_first = r_recv_next = RAYQLEN;
301
302 if (!poll) /* count newly sent unless polling */
303 n = ray_pnprocs - ray_pnidle;
304 if (n <= 0) /* return if nothing to await */
305 return(0);
306 if (!poll && ray_pnprocs == 1) /* one process -> skip select() */
307 FD_SET(r_proc[0].fd_recv, &readset);
308
309 getready: /* any children waiting for us? */
310 for (pn = ray_pnprocs; pn--; )
311 if (FD_ISSET(r_proc[pn].fd_recv, &readset) ||
312 FD_ISSET(r_proc[pn].fd_recv, &errset))
313 break;
314 /* call select if we must */
315 if (pn < 0) {
316 FD_ZERO(&readset); FD_ZERO(&errset); n = 0;
317 for (pn = ray_pnprocs; pn--; ) {
318 if (r_proc[pn].npending > 0)
319 FD_SET(r_proc[pn].fd_recv, &readset);
320 FD_SET(r_proc[pn].fd_recv, &errset);
321 if (r_proc[pn].fd_recv >= n)
322 n = r_proc[pn].fd_recv + 1;
323 }
324 /* find out who is ready */
325 while ((n = select(n, &readset, (fd_set *)NULL, &errset,
326 poll ? &tpoll : (struct timeval *)NULL)) < 0)
327 if (errno != EINTR) {
328 error(WARNING,
329 "select call failed in ray_presult");
330 ray_pclose(0);
331 return(-1);
332 }
333 if (n > 0) /* go back and get it */
334 goto getready;
335 return(0); /* else poll came up empty */
336 }
337 if (r_recv_next + r_proc[pn].npending > sizeof(r_queue)/sizeof(RAY))
338 error(CONSISTENCY, "buffer shortage in ray_presult()");
339
340 /* read rendered ray data */
341 n = readbuf(r_proc[pn].fd_recv, (char *)&r_queue[r_recv_next],
342 sizeof(RAY)*r_proc[pn].npending);
343 if (n > 0) {
344 r_recv_next += n/sizeof(RAY);
345 ok = (n == sizeof(RAY)*r_proc[pn].npending);
346 } else
347 ok = 0;
348 /* reset child's status */
349 FD_CLR(r_proc[pn].fd_recv, &readset);
350 if (n <= 0)
351 FD_CLR(r_proc[pn].fd_recv, &errset);
352 r_proc[pn].npending = 0;
353 ray_pnidle++;
354 /* check for rendering errors */
355 if (!ok) {
356 ray_pclose(0); /* process died -- clean up */
357 return(-1);
358 }
359 /* preen returned rays */
360 for (n = r_recv_next - r_recv_first; n--; ) {
361 register RAY *rp = &r_queue[r_recv_first + n];
362 rp->rno = r_proc[pn].rno[n];
363 rp->parent = NULL;
364 rp->newcset = rp->clipset = NULL;
365 rp->rox = NULL;
366 rp->slights = NULL;
367 }
368 /* return first ray received */
369 *r = r_queue[r_recv_first++];
370 return(1);
371 }
372
373
374 extern void
375 ray_pdone( /* reap children and free data */
376 int freall
377 )
378 {
379 ray_pclose(0); /* close child processes */
380
381 if (shm_boundary != NULL) { /* clear shared memory boundary */
382 free((void *)shm_boundary);
383 shm_boundary = NULL;
384 }
385 ray_done(freall); /* free rendering data */
386 }
387
388
389 static void
390 ray_pchild( /* process rays (never returns) */
391 int fd_in,
392 int fd_out
393 )
394 {
395 int n;
396 register int i;
397 /* flag child process for quit() */
398 ray_pnprocs = -1;
399 /* read each ray request set */
400 while ((n = read(fd_in, (char *)r_queue, sizeof(r_queue))) > 0) {
401 int n2;
402 if (n < sizeof(RAY))
403 break;
404 /* get smuggled set length */
405 n2 = sizeof(RAY)*r_queue[0].crtype - n;
406 if (n2 < 0)
407 error(INTERNAL, "buffer over-read in ray_pchild");
408 if (n2 > 0) { /* read the rest of the set */
409 i = readbuf(fd_in, (char *)r_queue + n, n2);
410 if (i != n2)
411 break;
412 n += n2;
413 }
414 n /= sizeof(RAY);
415 /* evaluate rays */
416 for (i = 0; i < n; i++) {
417 r_queue[i].crtype = r_queue[i].rtype;
418 r_queue[i].parent = NULL;
419 r_queue[i].clipset = NULL;
420 r_queue[i].slights = NULL;
421 r_queue[i].rlvl = 0;
422 samplendx++;
423 rayclear(&r_queue[i]);
424 rayvalue(&r_queue[i]);
425 }
426 /* write back our results */
427 i = writebuf(fd_out, (char *)r_queue, sizeof(RAY)*n);
428 if (i != sizeof(RAY)*n)
429 error(SYSTEM, "write error in ray_pchild");
430 }
431 if (n)
432 error(SYSTEM, "read error in ray_pchild");
433 ambsync();
434 quit(0); /* normal exit */
435 }
436
437
438 extern void
439 ray_popen( /* open the specified # processes */
440 int nadd
441 )
442 {
443 /* check if our table has room */
444 if (ray_pnprocs + nadd > MAX_NPROCS)
445 nadd = MAX_NPROCS - ray_pnprocs;
446 if (nadd <= 0)
447 return;
448 ambsync(); /* load any new ambient values */
449 if (shm_boundary == NULL) { /* first child process? */
450 preload_objs(); /* preload auxiliary data */
451 /* set shared memory boundary */
452 shm_boundary = (char *)malloc(16);
453 strcpy(shm_boundary, "SHM_BOUNDARY");
454 }
455 fflush(NULL); /* clear pending output */
456 while (nadd--) { /* fork each new process */
457 int p0[2], p1[2];
458 if (pipe(p0) < 0 || pipe(p1) < 0)
459 error(SYSTEM, "cannot create pipe");
460 if ((r_proc[ray_pnprocs].pid = fork()) == 0) {
461 int pn; /* close others' descriptors */
462 for (pn = ray_pnprocs; pn--; ) {
463 close(r_proc[pn].fd_send);
464 close(r_proc[pn].fd_recv);
465 }
466 close(p0[0]); close(p1[1]);
467 /* following call never returns */
468 ray_pchild(p1[0], p0[1]);
469 }
470 if (r_proc[ray_pnprocs].pid < 0)
471 error(SYSTEM, "cannot fork child process");
472 close(p1[0]); close(p0[1]);
473 /*
474 * Close write stream on exec to avoid multiprocessing deadlock.
475 * No use in read stream without it, so set flag there as well.
476 */
477 fcntl(p1[1], F_SETFD, FD_CLOEXEC);
478 fcntl(p0[0], F_SETFD, FD_CLOEXEC);
479 r_proc[ray_pnprocs].fd_send = p1[1];
480 r_proc[ray_pnprocs].fd_recv = p0[0];
481 r_proc[ray_pnprocs].npending = 0;
482 ray_pnprocs++;
483 ray_pnidle++;
484 }
485 }
486
487
488 extern void
489 ray_pclose( /* close one or more child processes */
490 int nsub
491 )
492 {
493 static int inclose = 0;
494 RAY res;
495 /* check recursion */
496 if (inclose)
497 return;
498 inclose++;
499 /* check argument */
500 if ((nsub <= 0) | (nsub > ray_pnprocs))
501 nsub = ray_pnprocs;
502 /* clear our ray queue */
503 while (ray_presult(&res,0) > 0)
504 ;
505 /* clean up children */
506 while (nsub--) {
507 int status;
508 ray_pnprocs--;
509 close(r_proc[ray_pnprocs].fd_recv);
510 close(r_proc[ray_pnprocs].fd_send);
511 if (waitpid(r_proc[ray_pnprocs].pid, &status, 0) < 0)
512 status = 127<<8;
513 if (status) {
514 sprintf(errmsg,
515 "rendering process %d exited with code %d",
516 r_proc[ray_pnprocs].pid, status>>8);
517 error(WARNING, errmsg);
518 }
519 ray_pnidle--;
520 }
521 inclose--;
522 }
523
524
525 void
526 quit(ec) /* make sure exit is called */
527 int ec;
528 {
529 if (ray_pnprocs > 0) /* close children if any */
530 ray_pclose(0);
531 exit(ec);
532 }