[comp.windows.x] Why X10 notion of fetching bits doesn't work on X11

jim@dandelion.CI.COM (Jim Fulton) (11/04/87)

Recently, a terse discussion on the XTENSIONS mailing list described how the
X10 notion of grabbing bits off the RootWindow (e.g. for hardcopy or popping
temporary windows) no longer has any meaning under X11.  To help myself sort
things out I put together the following note describing how retrieving bits
from the screen has changed from X10 to X11.  I'm posting it in hopes that
other people who are making the transition to X11 might find it useful. 

Under X10, two of the more common uses of grabbing arbitrary regions of pixels
out of the frame buffer include generating hardcopy and saving the screen
underneath pop-up menus (or other temporary graphics).  Although the former
operation pulls the bits back to the client program and the latter leaves them
in the server, both operations are commonly implemented by calling the
appropriate Xlib routine (XPixmapGet{XY,Z} or XPixmapSave) with the RootWindow
as the window argument.  However, as has been pointed out, this approach is no
longer guaranteed to work under X11.

This is due to the fact that X11 windows have "depth" as well width and height.
This attribute, called a "Visual", is how X11 provides coherence among the
various color models (such as PseudoColor in which pixels values are indirected
through a color lookup table before being sent to the electron guns, and
TrueColor in which pixels values directly control the guns).  What makes this
somewhat tricky is that a display may support more than one type of visual at a
time.  X11 allows EACH WINDOW on a display to select the visual to be used for
the pixels that it contains.  Unlike clipping, this choice is not constrained
by the window hierarchy: a window may choose to inherit its parent's visual (by
passing CopyFromParent as the visual argument to XCreateWindow), but it is not
required. 

Most displays probably will only support windows of one depth and color model
(which is different from the requirement that all servers support single plane
pixmaps, i.e. bitmaps).  However, consider a sophisticated, deep frame buffer
(say, 32 bits per pixels) that supports 24 bit TrueColor windows for image
rendering applications, as well as 16 bit PseudoColor windows for applications
such as CAE or CAD that like to play colormap games.  For the sake of
discussion, let us assume that we have one of each of these windows arranged as
follows:


                +----------------------------------------+
                | RootWindow                             |
                |                                        |
                |    +---------------+                   |
                |    | 24 bits deep, |                   |
                |    | TrueColor     |                   |
                |    |               | +---------------+ |
                |    |               | | 16 bits deep, | |
                |    |    Image      | | PseudoColor   | |
                |    +---------------+ |               | |
                |                      |     MCAE      | |
                |                      +---------------+ |
                |                                        |
                +----------------------------------------+
    
    
An interesting question is: what is the type of the RootWindow?  It can't be
both TrueColor and PseudoColor at the same time.  In fact, it could even be of
yet a third type.  Furthermore, if we do graphics on the RootWindow that we
choose not to have clipped by the top-level windows (its children), what should
appear on the screen?  Under X10 there isn't any confusion since all windows 
are of the same type; under X11, there is.

For example, if we do an XGetImage to retrieve the bits from the whole screen
(to send to a printer, for example), what values should we get for pixels in 
windows that are not of the same type as the RootWindow?  Unfortunately, we
can't just assume that the pixels on the screen can be converted to the
appropriate type without losing information.  In the above diagram, if the
RootWindow were a 16 bit PseudoColor window, then pixels from the Image window
would have to drop 8 bits of data per pixel.

This is why the X11 protocol says that doing graphics "on a window of one depth
with mapped inferiors of differing depth is not illegal, but the semantics is
undefined by the core protocol" (p. 52 under ClipByChildren in the CreateGC
section).  This is not just to cut hardware designers some slack, but rather to
acknowledge that in many situations there isn't a uniform "correct" behavior.

One might think that if the RootWindow were of the maximum possible depth then
we'd be able to grab the bits and put them back without disturbing anything.
Unfortunately, this does not guarantee the right results since there may be
different modes for reading and writing pixels in the different types of
windows.  In addition, there might be additional state information stored with
each pixel that isn't part of the "visible" bit planes (in our hypothetical 32
bit display, we have at least 8 extra bits per pixel that can be used for other
things).  Just extracting the visible bit planes may not give you enough
information to allow the screen to be fully restored.

This potential loss of information is what prevents the X10 techniques for
grabbing pixmaps off the RootWindow from working properly under X11.  As a
reminder, both the protocol spec (p. 61) and the Xlib manual (p. 108) have a
caveat under the description of XGetImage that states that the routine is only
for "rudimentary" hardcopy support.

However, X11 does have a very nice solution for saving what is underneath
pop-up menus.  In X10, if you wanted to do temporary graphics you would
generally write something along the lines of:

	saved_area_pixmap = do_clipped_pixmap_save (RootWindow,
						    x, y, width, height);
	saved_area_dirty = (!saved_area_pixmap);
	XConfigureWindow (tmpw, x, y, width, height);
	XMapWindow (tmpw);
	do_temporary_graphics ();
	if (saved_area_dirty) {
	    XUnmapWindow (tmpw);		/* generate an expose event */
	} else {
	    XUnmapTransparent (tmpw);			       /* be sneaky */
	    XPixmapPut (RootWindow, 0, 0, x, y, width, height, 
			saved_area_pixmap, GXcopy, AllPlanes);
	}
	if (saved_area_pixmap) XFreePixmap (saved_area_pixmap);

where do_clipped_pixmap_save clips the requested region to the boundaries of
the screen so that an Error event isn't generated.  Even if you wrap
XGrabServer/XUngrabServer calls around the whole mess to prevent other clients
from writing to the screen, the do_temporary_graphics routine could be rather
complicated and cause the application to draw in some of the windows that are
obscured.  Then, when the saved pixmap is restored, the screen becomes invalid.
If this happens, we're forced to generate the very refresh we were trying to
avoid.

In English, the pseudocode above tries to do the following:

	Before making the temporary graphics window visible, save the
	pixels that it would obscure.  Then, when the window is moved
	or unmapped, restore the exposed pixels, if possible, or else
	generate Expose events.

One would expect that this function is something that would be best handled by
the server since it already has all of the appropriate information.  That is
why each window in X11 has a "save-unders" attribute that indicates whether or
not the server should try to stash away areas of the screen that the window
obscures. 

There is an important distinction between save-unders regions and images that
are retrieved using XGetImage: save-unders store the "whole" pixels, not just
the "visible" portions.  This ensures that there is enough information to allow
the server to properly restore the screen.  Furthermore, if the server
decides that it can't do the save-under (perhaps because the server ran out of
offscreen memory or some application drew into the regions), Expose events will
be generated automatically.  If we assume that the save-unders and
override-redirect attributes are set on the temporary window (the latter is to
let the server know that it should bypass any window manager that would
normally intercept any window manipulation requests), the pseudocode shown
above can be reduced in X11 to:

	XMoveResizeWindow (dpy, tmpw, x, y, width, height);	/* or merge */
	XMapRaised (dpy, tmpw);		  /* into one XConfigureWindow call */
	do_temporary_graphics ();
	XUnmapWindow (dpy, tmpw);

At this point, either the screen will have been repainted from the save-unders,
or else Expose events will have been generated for all of the appropriate
windows. 

Unfortunately, save-unders are not implemented in the X.V11R1 sample server.
They require hooks into the ddX layer (for saving the pixels) as well as the
rectangle management code (for invalidating saved regions on servers that don't
understand drawing into obscured regions).  Until someone implements
save-unders (as the various vendors undoubtedly will), applications will just
have to deal with extra expose events.

Anyway, I hope that this note can be of use to other members of the X community
who are switching over from X10 to X11. 

                                                           Jim Fulton
                                                           Cognition Inc.
uucp:    ...!{mit-eddie,talcott,necntc}!dandelion!jim      900 Tech Park Drive
domain:  jim@dandelion.ci.com, jim@athena.mit.edu,         Billerica, MA  01821
         fulton@eddie.mit.edu                              (617) 667-4800