[comp.windows.x] X double buffering

doug@wbcs.UUCP (Doug Kratky) (10/16/89)

Could someone give me hints, code fragments, or examples
on "double-buffering" with X?

-- 
Doug Kratky
Boeing Computer Services	...!scubed!ncr-sd!ncrwic!wbcs!doug
PO Box 7730, MS K79-32		...!bellcore!fenix!ncrlnk!ncrwic!wbcs!doug
Wichita, KS 67277-7730		...!hplabs!hp-sdd!ncr-sd!ncrwic!wbcs!doug

rick@hanauma.stanford.edu (Richard Ottolini) (10/17/89)

In article <181@wbcs.UUCP> doug@wbcs.UUCP (Doug Kratky) writes:
>Could someone give me hints, code fragments, or examples
>on "double-buffering" with X?

Draw into an offscreen pixmap, then copy it onto screen.
This works well on a fast X-server such as a DEC 3100 and not so well
on a slow server such as any Sun.
(Not all drawing operations that work on screens work on pixmaps such
as erase.)

klee@chico.pa.dec.com (Ken Lee) (10/17/89)

In article <181@wbcs.UUCP>, doug@wbcs.UUCP (Doug Kratky) writes:
> Could someone give me hints, code fragments, or examples
> on "double-buffering" with X?

Check out ico (-dbl option) in the demos directory of the X11R3 tape.

Ken Lee
DEC Western Software Laboratory, Palo Alto, Calif.
Internet: klee@decwrl.dec.com
uucp: uunet!decwrl!klee

klee@chico.pa.dec.com (Ken Lee) (10/17/89)

In article <170@med.Stanford.EDU>, rick@hanauma.stanford.edu (Richard
Ottolini) writes:
> In article <181@wbcs.UUCP> doug@wbcs.UUCP (Doug Kratky) writes:
> >Could someone give me hints, code fragments, or examples
> >on "double-buffering" with X?
> 
> Draw into an offscreen pixmap, then copy it onto screen.

This isn't really "double buffering".  Double buffering avoids the copy
by writing to 2 different planes (or groups of planes) of a multi-plane
display, then fiddling with the colormap to determine which planes
show.  The advantage of this approach is that, on most systems,
changing the colormap is much faster than copying pixmaps.  As I
mentioned earlier, some of the X11R3 demo programs, such as ico,
implement double buffering.

Ken Lee
DEC Western Software Laboratory, Palo Alto, Calif.
Internet: klee@decwrl.dec.com
uucp: uunet!decwrl!klee

markc@Solbourne.COM (Mark Connell) (10/17/89)

In article <181@wbcs.UUCP>, doug@wbcs.UUCP (Doug Kratky) writes:
> 
> Could someone give me hints, code fragments, or examples
> on "double-buffering" with X?
> -- 
> Doug Kratky

True double-buffering is not supported under X.  ico has an option (-dbl)
that will use colormap double-buffering.  See the source for your example.
You can also implement your own double-buffering by using 2 pixmaps for
your buffers and using CopyArea to display the appropriate buffers.  


                                        Mark A. Connell
                                        Solbourne Computer, Inc.
                                        1900 Pike Road
                                        Longmont, Co	80501
                                        (303) 772-3400
                                        markc@Solbourne.COM
                                         ...!uunet!stan!markc

bobtl@toolbox.WV.TEK.COM (10/17/89)

In article <1929@bacchus.dec.com> klee@decwrl.dec.com writes:
>In article <170@med.Stanford.EDU>, rick@hanauma.stanford.edu (Richard
>Ottolini) writes:

>The advantage of this approach is that, on most systems,
>changing the colormap is much faster than copying pixmaps.

Unless the window manager gets in the way.  Is the mechanism in ICCCM
fast enough for this?  I am asking because I have never seen this done
in a truly compliant application with a truly compliant window manager.

Whatever truly compliant means.

>Ken Lee
>DEC Western Software Laboratory, Palo Alto, Calif.
>Internet: klee@decwrl.dec.com
>uucp: uunet!decwrl!klee

Bob Toole

rws@EXPO.LCS.MIT.EDU (Bob Scheifler) (10/17/89)

        changing the colormap is much faster than copying pixmaps.

    Unless the window manager gets in the way.  Is the mechanism in ICCCM
    fast enough for this?

Independent of ICCCM.  We're talking about changing RGB values in a
writable colormap, not installing a new colormap.  The WM does't get
involved at all.

Note that there is a proposed double-buffering extension to X, which was
out for public review this summer.  We have a (very) sample implementation
which is expected to be in R4.

madd@bu-cs.BU.EDU (Jim Frost) (10/17/89)

In article <170@med.Stanford.EDU> rick@hanauma.UUCP (Richard Ottolini) writes:
|In article <181@wbcs.UUCP> doug@wbcs.UUCP (Doug Kratky) writes:
|>Could someone give me hints, code fragments, or examples
|>on "double-buffering" with X?
|
|Draw into an offscreen pixmap, then copy it onto screen.
|This works well on a fast X-server such as a DEC 3100 and not so well
|on a slow server such as any Sun.

Actually it works pretty well even on the 386i server (the SLOOOW
server), especially if compiled with gcc.  When working for my
previous employer we used this method to make graphics changes seem
atomic to the user; the copies were fast enough to be generally
unnoticable, especially if the copy is limited to the changed area.
We were using a fullscreen window for our display and it still worked
well.  On fast servers it works extremely well, although you're not
going to get real-time animation out of it as you can with true
double-buffering (our application worked much better under SGI GL with
or without double buffering than under X, imagine that :-).

|(Not all drawing operations that work on screens work on pixmaps such
|as erase.)

True, but it's simple enough to fill a rectangle to take care of this
case.

jim frost
software tool & die
madd@std.com

stroyan@hpfcdq.HP.COM (Mike Stroyan) (10/18/89)

> Could someone give me hints, code fragments, or examples
> on "double-buffering" with X?

> Doug Kratky

Below is a set of utility functions for double buffering in X.  The
functions use colormap manipulation; they therefore require a dynamic
visual such as GrayScale, PseudoColor or DirectColor.  The example
program will fail if the default visual is not dynamic or has less than
16 free colors.  There is also a more subtle requirement of grouping by
planes, so the program may fail even though 16 colors are free.  A
program could avoid many of these hurdles by creating a window with an
appropriate visual and a private colormap.  You then have to handle the
colormap focus issues.

Mike Stroyan, stroyan@hpfcla.hp.com


# This is a shell archive.  Remove anything before this line,
# then unpack it by saving it in a file and typing "sh file".
#
# Wrapped by Mike Stroyan <stroyan@hpstryn> on Tue Oct 17 16:48:15 1989
#
# This archive contains:
#	Makefile	dbuf.c		double_buf.c	double_buf.h	
#

LANG=""; export LANG
PATH=/bin:/usr/bin:$PATH; export PATH

echo x - Makefile
cat >Makefile <<'@EOF'
CFLAGS= -O
LDFLAGS= -lX11 -lm

dbuf: dbuf.c double_buf.o
	$(CC) $(CFLAGS) -o dbuf dbuf.c double_buf.o -lX11 -lm
@EOF

chmod 664 Makefile

echo x - dbuf.c
cat >dbuf.c <<'@EOF'
/* dbuf.c - a demonstration of double buffering under X windows */

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <malloc.h>
#include <math.h>
#include <stdio.h>
#include <time.h>
#include "double_buf.h"

#define POINTS 6

main(argc, argv)
int argc;
char *argv[];
{
	Display *display;
	Window w1, w2;
	GC gc1, gc2;
	XGCValues gcvalues;
	XColor colors[4];
	XPoint data[POINTS];
	double_buffer_state *dbuf_state;
	double start_angle, angle;
	register int i;
	struct timeval timeout;
	XEvent event;
	static XSizeHints xsh = {           /* Size hints for window manager */
		(PPosition | PSize | PMinSize), /* flags */
		400,                            /* height */
		400,                            /* width */
		200,                            /* minimum height */
		200,                            /* minimum width */
		300,                              /* x coordinate */
		200                               /* y coordinate */
	};
	static XWMHints xwmh = {      /* More hints for window manager */
		(InputHint | StateHint),  /* flags */
		False,                    /* input */
		NormalState,              /* initial_state */
		0,                        /* icon pixmap */
		0,                        /* icon window */
		0, 0,                     /* icon location */
		0,                        /* icon mask */
		0,                        /* Window group */
	};
	static XClassHint xch = {     /* Class hints for window manager */
		"Dbuf",                /* name */
		"Dbuf"                 /* class */
	};

	timeout.tv_sec = 0;
	timeout.tv_usec = 50000;

	if ((display = XOpenDisplay(NULL)) == NULL) {
		fprintf(stderr, "Can't open %s\n", XDisplayName(NULL));
		exit(1);
	}

	/*
	 * Create two windows.
	 * Window w1 will be double buffered.
	 * Window w2 won't be.
	 */

	w1 = XCreateSimpleWindow(display,
	    DefaultRootWindow(display),
	    xsh.x, xsh.y,   xsh.width, xsh.height,   2,
	    WhitePixel(display, DefaultScreen(display)),
	    BlackPixel(display, DefaultScreen(display)));

	if (!w1) {
		fprintf (stderr, "XCreateSimpleWindow failed\n");
		exit(1);
	}

	gc1 = XCreateGC(display, w1, 0, &gcvalues);

	XSetStandardProperties(display, w1, "double buffered", "double buffered",
	    None, argv, argc, &xsh);
	XSetWMHints(display, w1, &xwmh);
	XSetClassHint(display, w1, &xch);

	XSelectInput(display, w1, StructureNotifyMask | ButtonPressMask);
	XMapWindow(display, w1);
	XFlush(display);

	do {
		XNextEvent(display, &event);
	} while (event.type != MapNotify || event.xmap.window != w1);

	xsh.x = 600;
	w2 = XCreateSimpleWindow(display,
	    DefaultRootWindow(display),
	    xsh.x, xsh.y,   xsh.width, xsh.height,   2,
	    WhitePixel(display, DefaultScreen(display)),
	    BlackPixel(display, DefaultScreen(display)));

	if (!w2) {
		fprintf (stderr, "XCreateSimpleWindow failed\n");
		exit(1);
	}

	gc2 = XCreateGC(display, w2, 0, gcvalues);

	XSetStandardProperties(display, w2, "single buffered", "single buffered",
	    None, argv, argc, &xsh);
	XSetWMHints(display, w2, &xwmh);
	XSetClassHint(display, w2, &xch);

	XSelectInput(display, w2, StructureNotifyMask | ButtonPressMask);
	XMapWindow(display, w2);
	XFlush(display);

	do {
		XNextEvent(display, &event);
	} while (event.type != MapNotify || event.xmap.window != w2);

	/* black */
	colors[0].red = 0;
	colors[0].green = 0;
	colors[0].blue = 0;

	/* white */
	colors[1].red = 65535;
	colors[1].green = 65535;
	colors[1].blue = 65535;

	/* green */
	colors[2].red = 0;
	colors[2].green = 40000;
	colors[2].blue = 0;

	/* yellow */
	colors[3].red = 65535;
	colors[3].green = 65535;
	colors[3].blue = 0;

	dbuf_state = start_double_buffer(display,
		DefaultColormap(display, DefaultScreen(display)), 2, colors);
	if (dbuf_state == NULL) {
		fprintf(stderr, "Couldn't allocate resources for double buffering\n");
		exit(1);
	}

	XSetPlaneMask(display, gc1, dbuf_state->drawing_planes);

	start_angle = 0.0;

	while (XPending(display) == 0) {
		start_angle += M_PI / 50.0;
		angle = start_angle;
		for (i=0; i<POINTS; i++) {
			data[i].x = 100.0 + 80.0 * cos(angle);
			data[i].y = 100.0 + 80.0 * sin(angle);
			angle += 4.0 * M_PI / ((double) (POINTS-1));
		}

		XSetForeground(display, gc1, colors[0].pixel);
		XSetForeground(display, gc2, colors[0].pixel);
		XFillRectangle(display, w1, gc1, 0, 0, xsh.width, xsh.height);
		XFillRectangle(display, w2, gc2, 0, 0, xsh.width, xsh.height);
		XSetForeground(display, gc1, colors[2].pixel);
		XSetForeground(display, gc2, colors[2].pixel);
		XFillPolygon(display, w1, gc1, data, POINTS, Complex, CoordModeOrigin);
		XFillPolygon(display, w2, gc2, data, POINTS, Complex, CoordModeOrigin);
		XSetForeground(display, gc1, colors[1].pixel);
		XSetForeground(display, gc2, colors[1].pixel);
		XDrawLines(display, w1, gc1, data, POINTS, CoordModeOrigin);
		XDrawLines(display, w2, gc2, data, POINTS, CoordModeOrigin);

		double_buffer_switch(dbuf_state);
		XSetPlaneMask(display, gc1, dbuf_state->drawing_planes);

		XFlush(display);

		select(0, 0, 0, 0, &timeout);
	}

	end_double_buffer(dbuf_state);

	XCloseDisplay(display);

	exit(0);
}
@EOF

chmod 666 dbuf.c

echo x - double_buf.c
cat >double_buf.c <<'@EOF'
/*
 * double_buf.c - an Xlib double buffering utility.
 */

#include <X11/Xlib.h>
#include <malloc.h>
#include <stdio.h>
#include "double_buf.h"

static void release(state)
register double_buffer_state *state;
/*
 * Release a possibly partially allocated double buffer state record.
 */
{
	if (state != NULL) {
		if (state->colormaps[0] != NULL) free(state->colormaps[0]);
		if (state->colormaps[1] != NULL) free(state->colormaps[1]);
		if (state->planes != NULL) free(state->planes);
		free(state);
	}
}

static long color(state, simple_color)
register double_buffer_state *state;
register long simple_color;
/*
 * Map the supplied color into the equivalent color
 * using the double buffered planes.
 */
{
	register long i, plane, computed_color;

	computed_color = state->pixel;
	for (plane = 1, i = 0; simple_color != 0; plane <<= 1, i++) {
		if (plane & simple_color) {
			computed_color |= state->planes[i];
			simple_color &= ~plane;
		}
	}
	return(computed_color);
}

double_buffer_state *start_double_buffer(display, cmap, planes, colors)
Display *display;
Colormap cmap;
long planes;    /* how many planes for each buffer */
XColor *colors; /* color settings for buffers */
/*
 * Start double buffering in given number of planes per buffer.
 * If resources can be allocated, then set color pixels in colors parameter
 * and return the address of a double_buffer_state record.
 * Otherwise, return NULL.
 */
{
	register double_buffer_state *state;
	register long i, high_mask, low_mask;

	/* Allocate memory. */
	state = (double_buffer_state *) malloc(sizeof(double_buffer_state));
	if (state == NULL)
		return (NULL);

	state->map_size = 1 << (2 * planes);
	state->colormaps[0] = (XColor *) malloc(state->map_size * sizeof(XColor));
	state->colormaps[1] = (XColor *) malloc(state->map_size * sizeof(XColor));
	state->planes = (long *) malloc((2 * planes) * sizeof(long));
	if (state->colormaps[1] == NULL || state->colormaps[0] == NULL
		|| state->planes == NULL) {
		release(state);
		return(NULL);
	}
	state->display = display;
	state->cmap = cmap;

	/* Get colors to double buffer with. */
	if (XAllocColorCells(state->display, state->cmap, False,
		state->planes, 2*planes, &state->pixel, 1) == 0) {
		release(state);
		return(NULL);
	}

	/* Prepare the write enable masks. */
	state->masks[0] = AllPlanes;
	state->masks[1] = AllPlanes;
	/* Mask 0 won't write in the "low" planes. */
	/* Mask 1 won't write in the "high" planes. */
	for (i = 0; i < planes; i++) {
		state->masks[0] &= ~state->planes[i];
		state->masks[1] &= ~state->planes[planes + i];
	}

	/* Prepare the flags and pixel values for each color. */
	for (i = 0; i < (1 << planes); i++) {
		colors[i].pixel = color(state, i | (i << planes));
		colors[i].flags = DoRed | DoGreen | DoBlue;
	}

	/* Prepare the two color map settings. */
	/* Colormap 0 displays the "low" planes. */
	/* Colormap 1 displays the "high" planes. */
	low_mask = (1 << planes) - 1;
	high_mask = low_mask << planes;
	for (i = state->map_size - 1; i >= 0; i--) {
		state->colormaps[0][i] = colors[i & low_mask];
		state->colormaps[0][i].pixel = color(state, i);

		state->colormaps[1][i] = colors[(i & high_mask) >> planes];
		state->colormaps[1][i].pixel = color(state, i);
	}

	/* Set up initial color map and write_enable. */
	state->buffer = 0;
	state->drawing_planes = state->masks[state->buffer];
	XStoreColors(state->display, state->cmap,
		state->colormaps[state->buffer], state->map_size);

	return(state);
}

void double_buffer_switch(state)
register double_buffer_state *state;
/*
 * Change double buffering buffer.
 * Return the new planes mask for double buffering.
 */
{
	/* Toggle the buffers. */
	state->buffer ^= 1;

	/* Adjust the color map and write enable mask. */
	XStoreColors(state->display, state->cmap,
		state->colormaps[state->buffer], state->map_size);

	state->drawing_planes = state->masks[state->buffer];
}

void end_double_buffer(state)
register double_buffer_state *state;
{
	XFreeColors(state->display, state->cmap,
		&state->pixel, 1, ~(state->masks[0] & state->masks[1]));
	release(state);
}
@EOF

chmod 664 double_buf.c

echo x - double_buf.h
cat >double_buf.h <<'@EOF'
/*
 * double_buf.h - declarations for an Xlib double buffering utility.
 */

/* double buffering state record */
typedef struct {
	Display *display;
	Colormap cmap;
	long drawing_planes;     /* planes currently drawn to */
	int buffer;              /* which buffer to show, even or odd */
	XColor *colormaps[2];    /* color maps for even and odd buffers */
	int map_size;            /* number of entries in color maps */
	long masks[2];           /* write_enable masks for odd and even */
	long *planes;            /* individual planes */
	long pixel;              /* pixel base value of double buffering */
} double_buffer_state;

/* double buffering procedures */
extern double_buffer_state *start_double_buffer();
extern void double_buffer_switch();
extern void end_double_buffer();
@EOF

chmod 664 double_buf.h

exit 0

kent@gnomee.pa.dec.com (Christopher A. Kent) (10/20/89)

The alternative is to grab an array of pixels in the default colormap and repeatedly do StoreColors there. That's how clover works. Changing colormaps is pretty slow with a compliant window manager.

Chris Kent	Western Software Laboratory	Digital Equipment Corporation
kent@decwrl.dec.com	decwrl!kent			(415) 853-6639

jimmc@sci.UUCP (Jim McBeath) (10/27/89)

The standard distribution of the 'ico' program (in core.src/demos)
does double buffering.  It's actually not very difficult to do.
Take a look at the functions initDBufs, setBufColname, setBufColor,
setDrawBuf, and setDisplayBuf.  I find that approach pretty straightforward,
but then I might be a little bit biased, since I wrote the double buffering
for ico.

Jim McBeath    sci!jimmc@decwrl.dec.com
Silicon Compilers Systems Corporation   (408)371-2900
2045 Hamilton Avenue, San Jose CA 95125