Source

oo-browser / tree-x / dbl.c

/* ----------------------------------------------------------------------------
 *     Double buffering code
 * ----------------------------------------------------------------------------
 */


#include <stdlib.h>
#include "dbl.h"

struct {
    unsigned short red;
    unsigned short green;
    unsigned short blue;
} color[] = {
    { 65280, 65280, 65280 }, /* white  */
    {     0,     0, 65280 }, /* blue   */
    {     0, 65280,     0 }, /* green  */
    { 65280,     0,     0 }, /* red    */
    { 42240, 10752, 10752 }, /* brown  */
    { 65280, 32512,     0 }, /* orange */
    { 32512, 32512, 32512 }, /* gray   */
    {     0,     0,     0 }  /* black  */
};

/* ------------------------------------------------------------------------- */

DoubleBuffer *
DBLcreate_double_buffer (Display *display,
			 Window   window,
			 int      backing_store,
			 XColor  *colors,
			 int      num_colors)
{
   int i, j, k, l, offset, mask, size;
   int max_planes;

   char         *string;
   Surface     *surface;
   DoubleBuffer     *db;
   XGCValues       xgcv;
   unsigned long   xgcvmask;

/* allocate the double buffer structure,  and then open the display */

   if ((db = (DoubleBuffer *)calloc(1, sizeof(DoubleBuffer))) == 0) {
      printf("DBLopen_double_buffer : memory allocation error\n");
      return NULL;
   }

/* note the display */

   db->display  = display;

/* first some information about our display */

   db->screen   = DefaultScreenOfDisplay(db->display);
   db->window   = window;

/* now get some information on color resources */

   db->visual   = DefaultVisualOfScreen(db->screen);
   db->depth    = DefaultDepthOfScreen(db->screen);
   db->colormap = DefaultColormapOfScreen(db->screen);

/* set up colors */

   for (i = 0 ; i < num_colors; i++) {
      color[i].red   = colors[i].red;
      color[i].green = colors[i].green;
      color[i].blue  = colors[i].blue;
   }

/* see if the user wanted to limit the number of planes used
   then see how many are available,  make it a multiple of 2 */

   if ((string = getenv("DBL_MAX_PLANES")) == NULL)
      max_planes = DBL_MAX_PLANES;
   else {
      max_planes = atoi(string);
   }

   if ((db->num_planes = PlanesOfScreen(db->screen)) > max_planes) {
      db->num_planes = max_planes;
   }

   db->num_planes = (db->num_planes >> 1) << 1;


/* otherwise allocate contiguous planes to do double buffering */

   switch (db->visual->class) {
   case PseudoColor:
   case DirectColor:
   case GrayScale:
     /* XAllocColorCells cannot be used with TrueColor */
     while (db->num_planes >= DBL_MIN_PLANES) {
       if (XAllocColorCells (db->display, db->colormap, 1, db->planes,
			     db->num_planes, db->pixels, 1)) {
	 break;
       }

       db->num_planes -= 2;
     }
     break;
   case TrueColor:
     /* Disable double buffering */
     db->num_surfaces = 0;
     
     db->num_colors = DBL_MAX_COLORS;
     db->colors[0]  = WhitePixelOfScreen(db->screen);
     for (i = 1; i < num_colors; i++) {
       /* I have no idea how to fail gracefully.  If we're running in 24bit */
       /* this call can't fail, right? */
       if (XAllocColor(db->display, db->colormap, &colors[i])) {
	 db->colors[i] = colors[i].pixel;
       }
     }

     goto cont;
   default: /* Mono color? */
     db->num_planes = 2;
     break;
   }

/* if we have at least minimum planes, then we can do double
   buffering and we want to setup our surfaces and colormaps */

   if (db->num_planes < DBL_MIN_PLANES)
      db->num_surfaces = 0;
   else {
      db->num_colors   = 1 << (db->num_planes >> 1);
      db->num_surfaces = DBL_MAX_SURFACES;

   /* if the number of colors is less than DBL_MAX_COLORS,
      then we want to make sure black is the  last  color */

      for (i = db->num_colors - 1; i < DBL_MAX_COLORS; i++) {
         color[i].red   = color[DBL_MAX_COLORS - 1].red;
         color[i].green = color[DBL_MAX_COLORS - 1].green;
         color[i].blue  = color[DBL_MAX_COLORS - 1].blue;
      }

   /* we have a set of contiguous planes.  compute a mask for
      the planes,  and figure out the offset in the  hardware */

      for (i = 0; i < db->num_planes; i++) {
         db->mask |= db->planes[i];
      }

      mask   = db->mask;
      offset = 0;

      while ((mask & 1) == 0) {
         mask = mask >> 1;
         offset = offset + 1;
      }

      mask = (1 << (db->num_planes >> 1)) - 1;

   /* now create the surfaces that will contain plane mask and
      colormap information that we use to do double  buffering */

      for (i = 0; i < db->num_surfaces; i++) {
         size = sizeof(Surface) + sizeof(XColor) * (1 << db->num_planes);

         if ((surface = (Surface *)malloc(size)) != NULL)
            db->surface[i] = surface;
         else {
            printf("DBLcreate_double_buffer : memory allocation error\n");
            DBLdelete_double_buffer(db);
            return NULL;
         }

         surface->offset     = offset + i * (db->num_planes >> 1);
         surface->mask       = mask << surface->offset;
         surface->num_colors = 1 << db->num_planes;

      /* compute our pixel values by taking every permutation
         of the pixel and planes returned by XAllocColorCells */

         for (j = 0; j < (surface->num_colors); j++) {
            surface->color[j].pixel = db->pixels[0];
         }

         for (j = 0; j < db->num_planes; j++) {
            for (k = (1 << j); k < (surface->num_colors); k += (2 << j)) {
               for (l = k; l < (k + (1 << j)); l++) {
                  surface->color[l].pixel |= db->planes[j];
               }
            }
         }

       /* now populate those pixels with the proper colors so
          that we can do animation by banging in a new colormap */

         for (j = 0; j < surface->num_colors; j++) {
            k = (j & surface->mask) >> surface->offset;

            surface->color[j].red   = color[k].red;
            surface->color[j].green = color[k].green;
            surface->color[j].blue  = color[k].blue;

            surface->color[j].flags = DoRed | DoGreen | DoBlue;
         }
      }

      db->current_surface = 0;
   }


/* now figure out what pixel values we will use to draw with
   and store them in the double buffer structure */

   if (db->num_surfaces == 0) {
      db->num_colors = DBL_MAX_COLORS;
      db->colors[0]  = WhitePixelOfScreen(db->screen);

      for (i = 1; i < db->num_colors; i++) {
	 db->colors[i] = BlackPixelOfScreen(db->screen);
      }
   }
   else {
      for (i = 0; i < db->num_colors; i++) {
         j = (i << (db->num_planes >> 1)) + i;
         db->colors[i] = db->surface[0]->color[j].pixel;
      }
   }

/* fill out the remaining colors with the last color */

   for (; i < DBL_MAX_COLORS; i++) {
      db->colors[i] = db->colors[db->num_colors - 1];
   }

cont:
   /* Switched to micro size double buffer to eliminate major bug of window
      display inverting during startup.  This is just a workaround until
      a real solution is found.  -- Bob Weiner, 02/02/97. 
      db->width   = WidthOfScreen(db->screen);
      db->height  = HeightOfScreen(db->screen);
    */

   db->width   = 1;
   db->height  = 1;

/* if there are no surfaces then we are doing animation with
   a frame buffer,  so create a pixmap as our frame buffer   */

   if (db->num_surfaces > 0)
      db->drawable = db->window;
   else {
      db->frame = XCreatePixmap(db->display, db->window,
				db->width, db->height, db->depth);
      db->drawable = db->frame;
   }

/* if they have requested backing store,  then create an extra
   pixmap which we can use as backing store to handle exposures */

   if (backing_store) {
      db->backing = XCreatePixmap(db->display, db->window,
				  db->width, db->height, db->depth);
   }

/*  use the 0 pixel from one of the surfaces for the background */

   xgcv.background = DBLinq_background(db);
   xgcv.line_style = LineSolid;
   xgcv.line_width = 0;
   xgcv.cap_style  = CapButt;
   xgcv.join_style = JoinRound;
   xgcvmask = GCBackground | GCLineStyle | GCLineWidth | GCCapStyle |
              GCJoinStyle;

   db->gc = XCreateGC(db->display, db->drawable, xgcvmask, &xgcv);

/* do an initial frame to setup the colormap,  and return */

   DBLbegin_frame(db);
   DBLend_frame(db, 1);
   return db;
}

/* ------------------------------------------------------------------------- */

void
DBLdelete_double_buffer (DoubleBuffer *db)
{
  int i;

  /* remove and and all surfaces that are out there */

  for (i = 0; i < DBL_MAX_SURFACES; i++) {
    if (db->surface[i] != 0) {
      free(db->surface[i]);
    }
  }

  /* now clean up the various resources used for this double buffer */

  if (db->frame != 0) {
    XFreePixmap(db->display, db->frame);
  }

  if (db->backing != 0) {
    XFreePixmap(db->display, db->backing);
  }

  /* if we created our own private colormap,  then free the colormap */

  if (db->colormap != DefaultColormapOfScreen(db->screen)) {
    XFreeColormap(db->display, db->colormap);
  }

  free (db);
}

/* ------------------------------------------------------------------------- */

unsigned long
DBLinq_background(DoubleBuffer *db)
{
   if (db->num_surfaces > 0)
      return db->surface[0]->color[0].pixel;
   else
      return WhitePixelOfScreen(db->screen);
}

/* ------------------------------------------------------------------------- */

void
DBLbegin_frame(DoubleBuffer *db)
{
  /* there will be at most two surfaces optimized with "&"*/

  if (db->num_surfaces > 0) {
    db->current_surface = (db->current_surface + 1) & 1;

    /* clear the back surface of the window which may actually be a pixmap */
    
    XSetPlaneMask (db->display, db->gc, db->surface[db->current_surface]->mask);
  }

  /* clear out the back surface or frame buffer as appropriate */

  XSetFunction(db->display, db->gc, GXclear);
  XFillRectangle(db->display, db->drawable, db->gc,
		 0, 0, db->width, db->height);

  /* set writing mode back to copy */
  XSetFunction (db->display, db->gc, GXcopy);

  XSync(db->display, False);
}


/* ------------------------------------------------------------------------- */

void
DBLend_frame(DoubleBuffer *db, short init)
{
  Surface  *surface;

  /* if there are no drawing surfaces, then we are doing animation
     with a frame buffer, copy the frame buffers to their viewports */

  if (db->num_surfaces == 0) {
     if (! init)
        XCopyArea (db->display, db->frame, db->window,
	           db->gc, 0,0, db->width, db->height, 0,0);
  } else {

    /* otherwise,  we can flip the surface by banging in the new colormap */

    XSync(db->display, False);
    surface = db->surface[db->current_surface];
    XStoreColors (db->display, db->colormap,
		  surface->color, surface->num_colors);
  }

  if (db->backing != 0) {
    XCopyArea (db->display, db->window, db->backing,
	       db->gc, 0,0, db->width, db->height, 0,0);
  }

  /* make sure this all goes off to the server,  right away */

  XSync(db->display, False);
}