--- ray/src/rt/ambient.c 1993/08/06 17:24:24 2.22 +++ ray/src/rt/ambient.c 1995/04/29 10:51:28 2.27 @@ -1,4 +1,4 @@ -/* Copyright (c) 1993 Regents of the University of California */ +/* Copyright (c) 1995 Regents of the University of California */ #ifndef lint static char SCCSid[] = "$SunId$ LBL"; @@ -27,6 +27,8 @@ typedef struct ambtree { extern CUBE thescene; /* contains space boundaries */ +extern char *shm_boundary; /* memory sharing boundary */ + #define MAXASET 511 /* maximum number of elements in ambient set */ OBJECT ambset[MAXASET+1]={0}; /* ambient include/exclude set */ @@ -38,15 +40,38 @@ static AMBTREE atrunk; /* our ambient trunk node */ static FILE *ambfp = NULL; /* ambient file pointer */ static int nunflshed = 0; /* number of unflushed ambient values */ +#ifndef SORT_THRESH +#ifdef BIGMEM +#define SORT_THRESH ((9L<<20)/sizeof(AMBVAL)) +#else +#define SORT_THRESH ((3L<<20)/sizeof(AMBVAL)) +#endif +#endif +#ifndef SORT_INTVL +#define SORT_INTVL (SORT_THRESH*8) +#endif + +static unsigned long ambclock = 0; /* ambient access clock */ +static unsigned int nambvals = 0; /* number of stored ambient values */ +static unsigned long lastsort = 0; /* time of last value sort */ +static long sortintvl = SORT_INTVL; /* time until next sort */ + +#define MAXACLOCK (1L<<30) /* clock turnover value */ + /* + * Track access times unless we are sharing ambient values + * through memory on a multiprocessor, when we want to avoid + * claiming our own memory (copy on write). + */ +#define tracktime (shm_boundary == NULL || ambfp == NULL) +#define checksort() if (ambclock > lastsort+sortintvl && \ + nambvals > SORT_THRESH) sortambvals(0) + #define AMBFLUSH (BUFSIZ/AMBVALSIZ) #define newambval() (AMBVAL *)bmalloc(sizeof(AMBVAL)) -#define newambtree() (AMBTREE *)calloc(8, sizeof(AMBTREE)) -#define freeambtree(t) free((char *)(t)) - extern long ftell(), lseek(); -static int initambfile(), avsave(), avinsert(), loadatree(); +static int initambfile(), avsave(), avinsert(), sortambvals(); static AMBVAL *avstore(); #ifdef F_SETLKW static aflock(); @@ -59,16 +84,18 @@ int ar; ambres = ar < 0 ? 0 : ar; /* may be done already */ /* set min & max radii */ if (ar <= 0) { - minarad = 0.0; + minarad = 0; maxarad = thescene.cusize / 2.0; } else { minarad = thescene.cusize / ar; - maxarad = 16.0 * minarad; /* heuristic */ + maxarad = 16 * minarad; /* heuristic */ if (maxarad > thescene.cusize / 2.0) maxarad = thescene.cusize / 2.0; } - if (maxarad <= FTINY) - maxarad = .001; + if (minarad <= FTINY) + minarad = 10*FTINY; + if (maxarad <= minarad) + maxarad = 64 * minarad; } @@ -76,7 +103,6 @@ setambacc(newa) /* set ambient accuracy */ double newa; { static double oldambacc = -1.0; - AMBTREE oldatrunk; ambacc = newa < 0.0 ? 0.0 : newa; /* may be done already */ if (oldambacc < -FTINY) @@ -86,10 +112,7 @@ double newa; if (ambacc <= FTINY) return; /* cannot build new tree */ /* else need to rebuild tree */ - copystruct(&oldatrunk, &atrunk); - atrunk.alist = NULL; - atrunk.kid = NULL; - loadatree(&oldatrunk); + sortambvals(1); oldambacc = ambacc; /* remeber setting for next call */ } @@ -176,6 +199,8 @@ register RAY *r; goto dumbamb; return; } + /* resort memory? */ + checksort(); /* get ambient value */ setcolor(acol, 0.0, 0.0, 0.0); d = sumambient(acol, r, rdepth, @@ -211,10 +236,14 @@ double s; /* do this node */ wsum = 0.0; for (av = at->alist; av != NULL; av = av->next) { + if (tracktime) + av->latick = ambclock++; /* * Ambient level test. */ - if (av->lvl > al || av->weight < r->rweight-FTINY) + if (av->lvl > al) /* list sorted, so this works */ + break; + if (av->weight < r->rweight-FTINY) continue; /* * Ambient radius test. @@ -349,6 +378,7 @@ int creat; #endif setbuf(ambfp, bmalloc(BUFSIZ+8)); if (creat) { /* new file */ + newheader("RADIANCE", ambfp); fprintf(ambfp, "%s -av %g %g %g -ab %d -aa %g ", progname, colval(ambval,RED), colval(ambval,GRN), colval(ambval,BLU), @@ -392,15 +422,57 @@ register AMBVAL *aval; if ((av = newambval()) == NULL) error(SYSTEM, "out of memory in avstore"); copystruct(av, aval); + av->latick = ambclock; + av->next = NULL; + nambvals++; return(av); } +#define ATALLOCSZ 512 /* #/8 trees to allocate at once */ + +static AMBTREE *atfreelist = NULL; /* free ambient tree structures */ + + static +AMBTREE * +newambtree() /* allocate 8 ambient tree structs */ +{ + register AMBTREE *atp, *upperlim; + + if (atfreelist == NULL) { /* get more nodes */ + atfreelist = (AMBTREE *)bmalloc(ATALLOCSZ*8*sizeof(AMBTREE)); + if (atfreelist == NULL) + return(NULL); + /* link new free list */ + upperlim = atfreelist + 8*(ATALLOCSZ-1); + for (atp = atfreelist; atp < upperlim; atp += 8) + atp->kid = atp + 8; + atp->kid = NULL; + } + atp = atfreelist; + atfreelist = atp->kid; + bzero((char *)atp, 8*sizeof(AMBTREE)); + return(atp); +} + + +static +freeambtree(atp) /* free 8 ambient tree structs */ +AMBTREE *atp; +{ + atp->kid = atfreelist; + atfreelist = atp; +} + + +static avinsert(av) /* insert ambient value in our tree */ register AMBVAL *av; { register AMBTREE *at; + register AMBVAL *ap; + AMBVAL avh; FVECT ck0; double s; int branch; @@ -424,27 +496,172 @@ register AMBVAL *av; } at = at->kid + branch; } - av->next = at->alist; - at->alist = av; + avh.next = at->alist; /* order by increasing level */ + for (ap = &avh; ap->next != NULL; ap = ap->next) + if (ap->next->lvl >= av->lvl) + break; + av->next = ap->next; + ap->next = av; + at->alist = avh.next; } static -loadatree(at) /* move tree to main store */ +unloadatree(at, f) /* unload an ambient value tree */ register AMBTREE *at; +int (*f)(); { register AMBVAL *av; register int i; /* transfer values at this node */ for (av = at->alist; av != NULL; av = at->alist) { at->alist = av->next; - avinsert(av); + (*f)(av); } if (at->kid == NULL) return; for (i = 0; i < 8; i++) /* transfer and free children */ - loadatree(at->kid+i); + unloadatree(at->kid+i, f); freeambtree(at->kid); + at->kid = NULL; +} + + +static AMBVAL **avlist1, **avlist2; /* ambient value lists for sorting */ +static int i_avlist; /* index for lists */ + + +static +av2list(av) +AMBVAL *av; +{ +#ifdef DEBUG + if (i_avlist >= nambvals) + error(CONSISTENCY, "too many ambient values in av2list1"); +#endif + avlist1[i_avlist] = avlist2[i_avlist] = av; + i_avlist++; +} + + +static int +alatcmp(avp1, avp2) /* compare ambient values for MRA */ +AMBVAL **avp1, **avp2; +{ + return((**avp2).latick - (**avp1).latick); +} + + +static int +aposcmp(avp1, avp2) /* compare ambient value positions */ +AMBVAL **avp1, **avp2; +{ + return(*avp1 - *avp2); +} + + +#ifdef DEBUG +static int +avlmemi(avaddr) /* find list position from address */ +AMBVAL *avaddr; +{ + register AMBVAL **avlpp; + + avlpp = (AMBVAL **)bsearch((char *)&avaddr, (char *)avlist2, + nambvals, sizeof(AMBVAL *), aposcmp); + if (avlpp == NULL) + error(CONSISTENCY, "address not found in avlmemi"); + return(avlpp - avlist2); +} +#else +#define avlmemi(avaddr) ((AMBVAL **)bsearch((char *)&avaddr,(char *)avlist2, \ + nambvals,sizeof(AMBVAL *),aposcmp) - avlist2) +#endif + + +static +sortambvals(always) /* resort ambient values */ +int always; +{ + AMBTREE oldatrunk; + AMBVAL tav, *tap, *pnext; + register int i, j; + /* + * The idea here is to minimize memory thrashing + * in VM systems by improving reference locality. + * We do this by periodically sorting our stored ambient + * values in memory in order of most recently to least + * recently accessed. This ordering was chosen so that new + * ambient values (which tend to be less important) go into + * higher memory with the infrequently accessed values. + * Since we expect our values to need sorting less + * frequently as the process continues, we double our + * waiting interval after each call. + * This routine is also called by setambacc() with + * the "always" parameter set to 1 so that the ambient + * tree will be rebuilt with the new accuracy parameter. + */ + if (tracktime) { /* allocate pointer arrays to sort */ + avlist1 = (AMBVAL **)malloc(nambvals*sizeof(AMBVAL *)); + avlist2 = (AMBVAL **)malloc(nambvals*sizeof(AMBVAL *)); + } else + avlist1 = avlist2 = NULL; + if (avlist2 == NULL) { /* no time tracking -- rebuild tree? */ + if (avlist1 != NULL) + free((char *)avlist1); + if (always) { /* rebuild without sorting */ + copystruct(&oldatrunk, &atrunk); + atrunk.alist = NULL; + atrunk.kid = NULL; + unloadatree(&oldatrunk, avinsert); + } + } else { /* sort memory by last access time */ + /* + * Sorting memory is tricky because it isn't contiguous. + * We have to sort an array of pointers by MRA and also + * by memory position. We then copy values in "loops" + * to minimize memory hits. Nevertheless, we will visit + * everyone at least twice, and this is an expensive process + * when we're thrashing, which is when we need to do it. + */ +#ifdef DEBUG + sprintf(errmsg, "sorting %u ambient values...", nambvals); + eputs(errmsg); +#endif + i_avlist = 0; + unloadatree(&atrunk, av2list); /* empty current tree */ +#ifdef DEBUG + if (i_avlist < nambvals) + error(CONSISTENCY, "missing ambient values in sortambvals"); +#endif + qsort((char *)avlist1, nambvals, sizeof(AMBVAL *), alatcmp); + qsort((char *)avlist2, nambvals, sizeof(AMBVAL *), aposcmp); + for (i = 0; i < nambvals; i++) { + if (avlist1[i] == NULL) + continue; + tap = avlist2[i]; + copystruct(&tav, tap); + for (j = i; (pnext = avlist1[j]) != tap; + j = avlmemi(pnext)) { + copystruct(avlist2[j], pnext); + avinsert(avlist2[j]); + avlist1[j] = NULL; + } + copystruct(avlist2[j], &tav); + avinsert(avlist2[j]); + avlist1[j] = NULL; + } + free((char *)avlist1); + free((char *)avlist2); + if (sortintvl < SORT_INTVL<<6) + sortintvl <<= 1; /* wait twice as long next */ +#ifdef DEBUG + eputs("done\n"); +#endif + } + if (ambclock >= MAXACLOCK) + ambclock = MAXACLOCK/2; + lastsort = ambclock; }