Hands-on Projects for the Linux Graphics Subsystem
|
In this section we start putting the bricks together to see a complete example of an X Client's request dispatching.
We start with the Xlib programs of section 1.1. At those examples the XMapWindow() Xlib library routine was used to make visible a window on the screen.
From the Xlib Reference Manual we read:
XMapWindow() maps a window, making it eligible for display. Whether it becomes visible it depends on its stacking order among its siblings, the mapping status of its ancestors, and the placement of other visible windows. If all the ancestors are mapped, and it is not obscured by siblings higher in the stacking order, and it is not obscured by unrelated windows (children of ancestors), then the window and all of its mapped subwindows are displayed. |
At section 1.2 we saw how the client builds the request message for the XServer. The request type was X_MapWindow, defined in Xproto.h as:
#define X_MapWindow 8The XServer reads the protocol message with ReadRequestFromClient() and uses the request type (now given as MAJOROP) as an index to the client's requestVector[] table:
result = (* client->requestVector[MAJOROP])(client);This process was described in section 3.2.5.3. We saw there that the requestVector[] can be either ProcVector[] or SwappedProcVector[] according to the Big or Small Endianess of the system and that both are defined in tables.c.
The function with index 8 at ProcVector[] is ProcMapWindow(). This routine is implemented as:
int ProcMapWindow(register ClientPtr client) { register WindowPtr pWin; REQUEST(xResourceReq); REQUEST_SIZE_MATCH(xResourceReq); pWin = (WindowPtr)SecurityLookupWindow(stuff->id, client, SecurityReadAccess); if (!pWin) return(BadWindow); MapWindow(pWin, client); /* update cache to say it is mapped */ return(client->noClientException); } |
Notice that 'stuff', which finds the current window, is explained in section 2.3.
ProcMapWindow() is actually an encapsulation of MapWindow(). This routine is examined in detail at section 3.2.2.13, where the final result was the frame buffer fbSolid() routine that painted the window in the screen. However this time we will follow a different path. The reason is the following 'if' condition, we find at the beginning of MapWindow(), which at the current example is TRUE:
if ( (pParent = pWin->parent) )When we initially examined this condition at section 3.2.2.13 because we developed the Root Window, i.e. the initial screen window, it didn't have a parent, however this time that we consider a common window, which has another window or Root window as a parent.
In the current case the iportant part is the following MapWindow() instruction:
if (anyMarked) { (*pScreen->ValidateTree)(pLayerWin->parent, pLayerWin, VTMap); (*pScreen->HandleExposures)(pLayerWin->parent); } |
MapWindow callsMarkOverlappedWindows to flag those windows which need to be reclipped. It then calls ValidateTree to calculate the new clip lists, and determine newly exposed areas of the screen. On return from ValidateTree, MapWindow calls HandleExposures to draw background in any windows that have exposure regions. |
The boolean variable anyMarked was filled from the next instruction:
if (pWin->viewable) { anyMarked = (*pScreen->MarkOverlappedWindows)(pWin, pWin, &pLayerWin);The MarkOverlappedWindows specific routine as seen in section 3.2.2.12 is miMarkOverlappedWindows(): pScreen->MarkOverlappedWindows = miMarkOverlappedWindows;This is implemented as:
Example: The following figure illustrates a window landscape where the grey is the Root window and the other are numbered from 1 to 5, with window 1 being the parent of 2 and 5 and 2 being the parent of 3 and 4:
With the miMarkOverlappedWindows() call with window 1 as the first argument the arrows present the window order for whom (* MarkWindow)() is called:
Notice that at the end of this process the parent window, in this example the root window is also marked. MarkWindow is defined in miMarkOverlappedWindows() as: MarkWindowProcPtr MarkWindow = pWin->drawable.pScreen->MarkWindow;and for the examples we follow this is determined in section 3.2.2.12 as: pScreen->MarkWindow = miMarkWindow; miMarkWindow() is implemented as:
The drawable, which can be either a window or a pixmap is defined in pixmapstr.h as:
x and y are the x, y dimensions of the window from the upper-left corner of the screen. By filling pWin->valdata the window is marked as "needs reclipping". Notice that valdata is filled with the 'before' part of the ValidateRec union (for ValidateRec see 'What is valdata' bollow).
|
We continue here with ValidateTree and HandleExposures
As we see in section 3.2.2.12 the screen specific ValidateTree routine is miValidateTree() and the screen specific HandleExposures is miHandleValidateExposures():
pScreen->ValidateTree = miValidateTree; . . . pScreen->HandleExposures = miHandleValidateExposures; |
miValidateTree() is implemented as:
/* *----------------------------------------------------------------------- * miValidateTree -- * Recomputes the clip list for pParent and all its inferiors. * * Results: * Always returns 1. * * Side Effects: * The clipList, borderClip, exposed, and borderExposed regions for * each marked window are altered. * * Notes: * This routine assumes that all affected windows have been marked * (valdata created) and their winSize and borderSize regions * adjusted to correspond to their new positions. The borderClip and * clipList regions should not have been touched. * * The top-most level is treated differently from all lower levels * because pParent is unchanged. For the top level, we merge the * regions taken up by the marked children back into the clipList * for pParent, thus forming a region from which the marked children * can claim their areas. For lower levels, where the old clipList * and borderClip are invalid, we can't do this and have to do the * extra operations done in miComputeClips, but this is much faster * e.g. when only one child has moved... * *----------------------------------------------------------------------- */ /*ARGSUSED*/ int miValidateTree (pParent, pChild, kind) WindowPtr pParent; /* Parent to validate */ WindowPtr pChild; /* First child of pParent that was * affected */ VTKind kind; /* What kind of configuration caused call */ { RegionRec totalClip; /* Total clipping region available to * the marked children. pParent's clipList * merged with the borderClips of all * the marked children. */ RegionRec childClip; /* The new borderClip for the current * child */ RegionRec childUnion; /* the space covered by borderSize for * all marked children */ RegionRec exposed; /* For intermediate calculations */ register ScreenPtr pScreen; register WindowPtr pWin; Bool overlap; int viewvals; Bool forward; pScreen = pParent->drawable.pScreen; if (pChild == NullWindow) pChild = pParent->firstChild; REGION_NULL(pScreen, &childClip); REGION_NULL(pScreen, &exposed); /* * compute the area of the parent window occupied * by the marked children + the parent itself. This * is the area which can be divied up among the marked * children in their new configuration. */ REGION_NULL(pScreen, &totalClip); viewvals = 0; if (REGION_BROKEN (pScreen, &pParent->clipList) && !REGION_BROKEN (pScreen, &pParent->borderClip)) { kind = VTBroken; /* * When rebuilding clip lists after out of memory, * assume everything is busted. */ forward = TRUE; REGION_COPY (pScreen, &totalClip, &pParent->borderClip); REGION_INTERSECT (pScreen, &totalClip, &totalClip, &pParent->winSize); for (pWin = pParent->firstChild; pWin != pChild; pWin = pWin->nextSib) { if (pWin->viewable) REGION_SUBTRACT (pScreen, &totalClip, &totalClip, &pWin->borderSize); } for (pWin = pChild; pWin; pWin = pWin->nextSib) if (pWin->valdata && pWin->viewable) viewvals++; REGION_EMPTY (pScreen, &pParent->clipList); } else { if ((pChild->drawable.y < pParent->lastChild->drawable.y) || ((pChild->drawable.y == pParent->lastChild->drawable.y) && (pChild->drawable.x < pParent->lastChild->drawable.x))) { forward = TRUE; for (pWin = pChild; pWin; pWin = pWin->nextSib) { if (pWin->valdata) { RegionPtr pBorderClip = &pWin->borderClip; #ifdef COMPOSITE if (pWin->redirectDraw && miGetRedirectBorderClipProc) pBorderClip = (*miGetRedirectBorderClipProc)(pWin); #endif REGION_APPEND( pScreen, &totalClip, pBorderClip ); if (pWin->viewable) viewvals++; } } } else { forward = FALSE; pWin = pParent->lastChild; while (1) { if (pWin->valdata) { RegionPtr pBorderClip = &pWin->borderClip; #ifdef COMPOSITE if (pWin->redirectDraw && miGetRedirectBorderClipProc) pBorderClip = (*miGetRedirectBorderClipProc)(pWin); #endif REGION_APPEND( pScreen, &totalClip, pBorderClip ); if (pWin->viewable) viewvals++; } if (pWin == pChild) break; pWin = pWin->prevSib; } } REGION_VALIDATE( pScreen, &totalClip, &overlap); } /* * Now go through the children of the root and figure their new * borderClips from the totalClip, passing that off to miComputeClips * to handle recursively. Once that's done, we remove the child * from the totalClip to clip any siblings below it. */ overlap = TRUE; if (kind != VTStack) { REGION_UNION( pScreen, &totalClip, &totalClip, &pParent->clipList); if (viewvals > 1) { /* * precompute childUnion to discover whether any of them * overlap. This seems redundant, but performance studies * have demonstrated that the cost of this loop is * lower than the cost of multiple Subtracts in the * loop below. */ REGION_NULL(pScreen, &childUnion); if (forward) { for (pWin = pChild; pWin; pWin = pWin->nextSib) if (pWin->valdata && pWin->viewable) REGION_APPEND( pScreen, &childUnion, &pWin->borderSize); } else { pWin = pParent->lastChild; while (1) { if (pWin->valdata && pWin->viewable) REGION_APPEND( pScreen, &childUnion, &pWin->borderSize); if (pWin == pChild) break; pWin = pWin->prevSib; } } REGION_VALIDATE(pScreen, &childUnion, &overlap); if (overlap) REGION_UNINIT(pScreen, &childUnion); } } for (pWin = pChild; pWin != NullWindow; pWin = pWin->nextSib) { if (pWin->viewable) { if (pWin->valdata) { REGION_INTERSECT( pScreen, &childClip, &totalClip, &pWin->borderSize); miComputeClips (pWin, pScreen, &childClip, kind, &exposed); if (overlap) { REGION_SUBTRACT( pScreen, &totalClip, &totalClip, &pWin->borderSize); } } else if (pWin->visibility == VisibilityNotViewable) { miTreeObscured(pWin); } } else { if (pWin->valdata) { REGION_EMPTY( pScreen, &pWin->clipList); if (pScreen->ClipNotify) (* pScreen->ClipNotify) (pWin, 0, 0); REGION_EMPTY( pScreen, &pWin->borderClip); pWin->valdata = (ValidatePtr)NULL; } } } REGION_UNINIT( pScreen, &childClip); if (!overlap) { REGION_SUBTRACT(pScreen, &totalClip, &totalClip, &childUnion); REGION_UNINIT(pScreen, &childUnion); } REGION_NULL(pScreen, &pParent->valdata->after.exposed); REGION_NULL(pScreen, &pParent->valdata->after.borderExposed); /* * each case below is responsible for updating the * clipList and serial number for the parent window */ switch (kind) { case VTStack: break; default: /* * totalClip contains the new clipList for the parent. Figure out * exposures and obscures as per miComputeClips and reset the parent's * clipList. */ REGION_SUBTRACT( pScreen, &pParent->valdata->after.exposed, &totalClip, &pParent->clipList); /* fall through */ case VTMap: if (pParent->backStorage) { REGION_SUBTRACT( pScreen, &exposed, &pParent->clipList, &totalClip); (* pScreen->SaveDoomedAreas)(pParent, &exposed, 0, 0); } REGION_COPY( pScreen, &pParent->clipList, &totalClip); pParent->drawable.serialNumber = NEXT_SERIAL_NUMBER; break; } REGION_UNINIT( pScreen, &totalClip); REGION_UNINIT( pScreen, &exposed); if (pScreen->ClipNotify) (*pScreen->ClipNotify) (pParent, 0, 0); return (1); } |
miValidateTree() commentary
As seen bellow in the _Window struct firstChild is the top-most child and lastChild is the bottom-most child. firstChild and lastChild are determined by the 'Window stacking order'. When one window overlaps one of its sibling windows the one of the top obscures the other. It is the stacking order that makes the one window appear on the top. firstChild and lastChild are compared in the miValidateTree() source by its drawable.x and drawable.y fields. We remind that x and y are defined in the _Drawable struct as the coordinates of the upper-left corner.
The following condition is used to find the order that REGION_APPEND, an encapsulation of miRegionAppend(), appends the address of pWin->borderClip of all child windows along with the parent's clipList to the totalClip. If the condition is true it appends from top-most to bottom-most otherwise the order is reversed. if ((pChild->drawable.y < pParent->lastChild->drawable.y) || ((pChild->drawable.y == pParent->lastChild->drawable.y) && (pChild->drawable.x < pParent->lastChild->drawable.x))) REGION_APPEND is defined as:
This like other macros found in regionstr.h discard their first argument since they are resolved to comma expressions. Those expressions are evaluated left to right and as an expression is evaluated its value is discarded. The value of the rightmost expression is the value of the comma expression. The miRegionAppend() is what finally is called. We read at its comment:
After the REGION_APPEND a REGION_VALIDATE is used. This is actually resolved to miRegionValidate():
For miRegionValidate() we read at its comment:
The following instruction then uses REGION_UNION, which resolves to a miUnion() call. if (kind != VTStack) { REGION_UNION( pScreen, &totalClip, &totalClip, &pParent->clipList);miUnion() merges &totalClip and &pParent->clipList and saves the result back to &totalClip. Recall that totalClip has already included the borderClip of each child window and this region is now merged with the parent's clipList. Next the whole children list is traversed via the for loop and REGION_INTERSECT (an encapsulation of miIntersect) is used to find the intesection between the totalClip and the borderSize of each. This is passed to miComputeClips() to find the new clipping region for each child and its possible children. This routine is explained in more detail in section 7.1. REGION_SUBTRACT, which is resolved to miSubtract() subtracts the borderSize of the current child from totalClip, constrained thus the region that another sibling window could occupy and therefore allowing for visibly overlapping among the sibling windows. Notice that the previous apply only to the visible windows. totalClip after the children spaces are removed are copied to the pParent->clipList. This can differ from the old clipList, since new windows are mapped, etc.
|
What is valdata
valdata are used mainly for two reasons: to mark the affected windows and to return the areas that need to be redrawn. valdata is a field of the _Window struct: union _Validate *valdata; _Validate and ValidateRec are defined in mivalidate.h as:
Also ValidatePtr is defined as:
typedef union _Validate *ValidatePtr; Because many other _Window fields are used in this section we remind the _Window struct:
|
What is a Region
As we saw in section 3.2.2.13 struct _Region is defined in regionstr.h as:
where BoxRec is defined in miscstruct.h as:
From the starting comments of miregion.c and Region.c we read:
A Region is simply a set of disjoint * (non-overlapping) rectangles, plus an "extent" rectangle which is the * smallest single rectangle that contains all the non-overlapping rectangles. A Region is simply an area, as the name * implies, and is implemented as a "y-x-banded" array of rectangles. This array * imposes two degrees of order. First, all rectangles are sorted by top side * y coordinate first (y1), and then by left side x coordinate (x1). To * explain: Each Region is made up of a certain number of rectangles sorted * by y coordinate first, and then by x coordinate. * Furthermore, the rectangles are grouped into "bands". Each rectangle in a * band has the same top y coordinate (y1), and each has the same bottom y * coordinate (y2). Thus all rectangles in a band differ only in their left * and right side (x1 and x2). Bands are implicit in the array of rectangles: * there is no separate list of band start pointers. * * The y-x band representation does not minimize rectangles. In particular, * if a rectangle vertically crosses a band (the rectangle has scanlines in * the y1 to y2 area spanned by the band), then the rectangle may be broken * down into two or more smaller rectangles stacked one atop the other. * * ----------- ----------- * | | | | band 0 * | | -------- ----------- -------- * | | | | in y-x banded | | | | band 1 * | | | | form is | | | | * ----------- | | ----------- -------- * | | | | band 2 * -------- -------- * * An added constraint on the rectangles is that they must cover as much * horizontal area as possible: no two rectangles within a band are allowed * to touch. * * Whenever possible, bands will be merged together to cover a greater vertical * distance (and thus reduce the number of rectangles). Two bands can be merged * only if the bottom of one touches the top of the other and they have * rectangles in the same places (of the same width, of course). |
miHandleValidateExposures() is implemented as:
/***** * miHandleValidateExposures(pWin) * starting at pWin, draw background in any windows that have exposure * regions, translate the regions, restore any backing store, * and then send any regions still exposed to the client *****/ void miHandleValidateExposures(pWin) WindowPtr pWin; { register WindowPtr pChild; register ValidatePtr val; ScreenPtr pScreen; WindowExposuresProcPtr WindowExposures; pScreen = pWin->drawable.pScreen; pChild = pWin; WindowExposures = pChild->drawable.pScreen->WindowExposures; while (1) { if ( (val = pChild->valdata) ) { if (REGION_NOTEMPTY(pScreen, &val->after.borderExposed)) (*pChild->drawable.pScreen->PaintWindowBorder)(pChild, &val->after.borderExposed, PW_BORDER); REGION_UNINIT(pScreen, &val->after.borderExposed); (*WindowExposures)(pChild, &val->after.exposed, NullRegion); REGION_UNINIT(pScreen, &val->after.exposed); xfree(val); pChild->valdata = (ValidatePtr)NULL; if (pChild->firstChild) { pChild = pChild->firstChild; continue; } } while (!pChild->nextSib && (pChild != pWin)) pChild = pChild->parent; if (pChild == pWin) break; pChild = pChild->nextSib; } } |
As seen in section 3.2.2.12 the PaintWindowBorder specific implementation for the configuration we followed in the previous sections is fbPaintWindow(). This function takes as argument the &val->after.borderExposed of the curent window, which is calculated in miComputeClips() (see section 7.1.1) to find the border parts that needs repair.
The next figure, like the miMarkOverlappedWindows() example, shows the Root window and the windows numbered from 1 to 5, with window 1 being the parent of 2 and 5 and 2 being the parent of 3 and 4:
Like miMarkOverlappedWindows() the same algorithm is used to traverse the children windows to implement this time fbPaintWindow(), if it needs to, and miWindowExposures(). For the current example the path is illustrated in the following figure:
We met fbPaintWindow() in section 3.2.2.13. In the current case the following lines are executed in the fbPaintWindow() 'case' statement:
case PW_BORDER: if (pWin->borderIsPixel) { fbFillRegionSolid (&pWin->drawable, pRegion, 0, fbReplicatePixel (pWin->border.pixel, pWin->drawable.bitsPerPixel)); |
where the formal argument pRegion is substituted by the the actual argument:
&val->after.borderExposed
fbFillRegionSolid() and fbReplicatePixel() were also discussed in section 3.2.2.13.
Next miHandleValidateExposures() applies WindowExposures on all marked window (pChild->valdata is set). As seen in section 3.2.2.12 the WindowExposures specific implementation is miWindowExposures():
pScreen->WindowExposures = miWindowExposures;miWindowExposures() is described in section 3.2.2.13. for the current configuration it mainly calls PaintWindowBackground as:
(*pWin->drawable.pScreen->PaintWindowBackground)(pWin, prgn, PW_BACKGROUND);PaintWindowBackground for this configuration is fbPaintWindow():
pScreen->PaintWindowBackground = fbPaintWindow;fbPaintWindow() is developed in section 3.2.2.12. The part implemented in the current flow is:
case PW_BACKGROUND: switch (pWin->backgroundState) { case None: break; case ParentRelative: do { pWin = pWin->parent; } while (pWin->backgroundState == ParentRelative); (*pWin->drawable.pScreen->PaintWindowBackground)(pWin, pRegion, what); break; case BackgroundPixmap: fbFillRegionTiled (&pWin->drawable, pRegion, pWin->background.pixmap); break; case BackgroundPixel: fbFillRegionSolid (&pWin->drawable, pRegion, 0, fbReplicatePixel (pWin->background.pixel, pWin->drawable.bitsPerPixel)); break; } break; |
For instance if the backgroundState is set to BackgroundPixmap the background is tiled with the background.pixmap, via fbFillRegionTiled().
REFERENCES
Definition of the Porting Layer for the X
X Window System Protocol
Mapping Windows
Xlib - C Language X Interface
Xlib Programming Manual