Programming for EyeTap systems

Steve Mann and Corey Manders, January 2001

mann@eecg.toronto.edu, corey@eyetap.org

(c) Copyleft, right, and center, by EXISTech Corporation, 2001, released under GPL Version 2

Twenty years ago, the notion of a computer system attached to the body, running off battery power, and operable while in motion, materialized and has continued to evolve toward the covert sunglasses rigs of the 1990s. From the early rigs, with a computer in a backpack, and a cathode ray tube on a helmet, the image was visible while walking around typing commands on a so-called "keyer". The basic features of this invention have remained roughly the same over this twenty year period, although the compute power has increased while the size has decreased. One aspect that has remained, throughout most of the systems, is a reversal of the image both on the input ("camera") side and the output ("display" or "viewfinder") side.

The image was viewed as a reflection in a mirrorlike surface, and this gave rise to a need to left-right reverse ("fliplr") the image, or to up-down ("flipud") the image.

The reflectional aspect is visible in this figure, where we can see that the "eye is a camera" (a reflection thereof), and of course the eye also sees a reflection of the aremac (the aremac is the device that synthesizes rays of light from the camera after processing by the computer).

An EyeTap system is a system that causes the eye itself to function, in effect, as if it were both a camera and a display. Rays of eyeward bound light are diverted to a sensor array, by the diverter (two-sided mirror or beamsplitter) and then re-constructed on the backside of the diverter, for being directed into the eye.

The functionality of EyeTap is illustrated in the figure, showing how the focus linking creates the illusion of transparancy even if the diverter is 100% silvered and passes no light:
Diagram of a focus linked eyetap

Of course the diverter can be partially silvered, and often is, in many EyeTaps.

Over the past few years, we have been working on a number of programming environments for EyeTap systems. A common feature of EyeTap systems is the diverter, which renders image contant as left-right reversed, or top-bottom reversed.

Accordingly, we have been developing a number of options and methodologies for dealing with this problem.

Although early cathode-ray-tube EyeTap systems relied on swapping deflection yoke wires (in color eyetaps one must be careful to swap only the horizontal yoke wiring, otherwise images are chopped off because of the critical timing of the filter wheel as synchronized with the vertical deflection), there arise certain conditions where it is inconventient to reverse the hardware.

Moreover, since the sensor apparatus (whether it be a camera or similar sensory system) also sees the world in reverse, it is often desired that the display actually display a backwards image to properly compensate, and thus the two will reverse each other and cancel out.

Although it is possible to reverse the computer screen in a variety of different ways, the focus of this article will be on X windows reversal. SVGATextMode can also be reversed, but then in addition to reversing the order of the data read out, we also need to define reversed fonts. The situation is greatly simplified using what we call ReverseX.

Without reversing X, images would appear to the viewer in the following manner:

One early attempt was a program called caplive, developed by Mann, and extended by Mann, Fung, and several others, into something called xcaplive, along with a set of utilities called glinaccess, meant for use with EyeTap systems.

Another attempt (a collaboration between Mann and Waites) involved a free running reversal of X that did not take advantage of individual X events to update only when necessary.

More recently, a thesis project proposed by Mann, and carried out by Manders, under Mann's supervision, included, among other things, the reversal of XF86, to facilitate use of XF86 with an EyeTap system.

With the restructuring of the XF86 code of late, XFree86 4.0.x has provided an easy means of left-right reversing or up-down reversing the Xserver. Specifically, new installations of XFree86 version 4.0.x have an option to make use of a "Shadow Buffer". In a regular install of XF 4.0, most chipsets will offer the option of rotating the xserver through an angle of pi/4 (90 degrees). This capability is due to the efforts of Mark Vojkovich .

The inital code provided in the XF4.0 distribution performs a harder task than what is needed for the left-right reverse or up-down reverse xserver. Any one of the drivers provided in XF 4.0 will have essentially the same structure as what will be discussed here on, but for the purpose of example, the Nvidia driver (nv) has been chosen. Note that this manipulation has been successfully preformed on almost all of the XF 4.0 drivers.

First, it is advantageous to examine the code to which the necessary modifications shall occur. After expanding the XF 4.0 source code (available at ftp://ftp.xfree86.org), we get the file .../xc/programs/Xserver/hw/xfree86/drivers/nv/nv_shadow.c.

The following code comes from the above file:

void
NVRefreshArea(ScrnInfoPtr pScrn, int num, BoxPtr pbox)
{
       NVPtr pNv = NVPTR(pScrn);
       int width, height, Bpp, FBPitch;
       unsigned char *src, *dst;

       Bpp = pScrn->bitsPerPixel >> 3;
       FBPitch = BitmapBytePad(pScrn->displayWidth * pScrn->bitsPerPixel);

       while(num--) {
          width = (pbox->x2 - pbox->x1) * Bpp;
          height = pbox->y2 - pbox->y1;

          src = pNv->ShadowPtr + 
	       (pbox->y1 * pNv->ShadowPitch) +
               (pbox->x1 * Bpp);
          dst = pNv->FbStart + (pbox->y1 * FBPitch) + (pbox->x1 * Bpp);

          while(height--) {
            memcpy(dst, src, width);
            dst += FBPitch;
            src += pNv->ShadowPitch;
          }

       pbox++;
    }
}

Now, this code is active when the option "Shadow Buffer" is selected in the XF86Config file. All it is doing is writing from allocated memory to the graphics card. Understanding this code is not all that difficult if we consider what some of the variables are.

The sections of the hardware memory-mapped regions (pointed to by pNv->FbStart) which need to be redrawn because of changes are outlined by pbox's. A pbox is just a set of 4 offsets, 2 x offsets and 2 y offsets. The variable "num" is the number of pboxes which need to be redrawn. The width and height of the pboxs may be calclated from these offsets.

The code above redraws one pbox at a time upon each iteration of the outer while loop. The variables "src" and "dst" are the memory addresses of the source and destination. The real changes which need to be made are that we must write the rgb information to the destination maintaining the order of the rgb bits, but one level of abstraction higher, these bit groups must be written in reverse order. This means we must write the pixels out in the destination starting from the opposite pbox x co-ordinate than the code above. This may be accomplished by changing

   dst = pNv->FbStart + (pbox->y1 * FBPitch) + (pbox->x1 * Bpp);

          to

   dst = pNv->FbStart + (pbox->y1 * FBPitch) + ((pScrn->virtualX - pbox->x1) * Bpp)
This will get us to the other end of the pbox in the representation in memory.

At this point, the code is beginning it's read of the source address from the correct location. To proceed in the correct manner, for each horizontal line, the actual groups of rgb bits must be written out in reverse. This implies that the inner while must change as well. Unfortunately, we can't write the bits out one "width" at a time (which is what the memcpy is doing in the original code). We must traverse the sections in finer increments, which leads use to change

   while(height--) {
   
       memcpy(dst,src,width); 

       dst += FBPitch;
       src += pNv->ShadowPitch;
   }
to
   while(height--) {

      for (i= 0; iShadowPitch;
   }

The code above takes care of the painting of the pixels. What is left to do is to correct the positioning of the mouse (which is not yet reversed). A subrountine at the start of nv_shadow.c name NVPointerMoved allows us to do this. If we look at the NVPointerMoved subroutine, the code is used to change x co-ordinates to y co-ordinates and similarly y to x. This is of course not needed to reverse the X server, but the code may be modified to perform the function needed. The next section of code has been commented to outline the necessary modifications:
void
NVPointerMoved(int index, int x, int y)
{
    ScrnInfoPtr pScrn = xf86Screens[index];
    NVPtr pNv = NVPTR(pScrn);
    int newX, newY;

    /*  new section   */

    if(pNv->Flip == 1) {
       newX = pScrn->pScreen-width - x - 1;
       newY = y;
    }

    else {

   /*   
    * old section 
    *   this section just does the x-y transform
    *   needed for rotating the X server
    */
        if(pNv->Rotate == 1) {
            newX = pScrn->pScreen->height - y - 1;
            newY = x;
        } 
        else {
             newX = y;
             newY = pScrn->pScreen->width - x - 1;
        }
    }

    (*pNv->PointerMoved)(index, newX, newY);
}
What has been done in the new section of code is to take the original y co-ordinate and leave it unchanged and subtract the original x co-ordinate from width of the screen. This is all that is needed for left-right reversing the mouse. The variable pNv->Flip is important and will be explained in what follows.

To further highlight all the changes which have been made to this point, a completely modified example of nv_shadow.c is available HERE

So, the changes so far will left-right reverse the Xserver, now all that is needed is to get the driver to call it. Certainly the easiest way is to simply add an option to the XF86Config file such that when called, the Xserver would be left-right reversed.

We need to get our new option (which we will call FlipLR) to get parsed by the driver. Specifically, when we add Option "FlipLR" to the device section of the XF86Config file we want to have out Xserver reversed.

Taking a careful look at nv_driver.c around line 299, we find the following structure:

static OptionInfoRec NVOptions[] = {
   { OPTION_SW_CURSOR,         "SWcursor",     OPTV_BOOLEAN,   {0}, FALSE },
   { OPTION_HW_CURSOR,         "HWcursor",     OPTV_BOOLEAN,   {0}, FALSE },
   { OPTION_NOACCEL,           "NoAccel",      OPTV_BOOLEAN,   {0}, FALSE },
   { OPTION_SHOWCACHE,         "ShowCache",    OPTV_BOOLEAN,   {0}, FALSE },
   { OPTION_SHADOW_FB,         "ShadowFB",     OPTV_BOOLEAN,   {0}, FALSE },
   { OPTION_FBDEV,             "UseFBDev",     OPTV_BOOLEAN,   {0}, FALSE },
   { OPTION_ROTATE,            "Rotate",       OPTV_ANYSTR,    {0}, FALSE },
   { -1,                       NULL,           OPTV_NONE,      {0}, FALSE }
};
These are the usual options available to the driver. These options will differ slightly from driver to driver, but some similar structure exists in each of the drivers. What is needed in the structure is a line which should appear as:
   /* our new cyborg enhanced option */
   { OPTION_FLIP,              "FlipLR",       OPTV_BOOLEAN,   {0}, FALSE },
   

The subroutine NVPreInit is used to parse the XF86Config file and set various structures parameters to the correct values. The lines which need to be added can occur within a wide range of the subroutine. In keeping with the overall design, with the Nvidia driver, it makes sense to add some code at line 1061 (right after the Rotate option is parsed. Adding:
   if ((s = xf86GetOptValString(NVOptions, OPTION_FLIP))){
      pNv->ShadowFB = TRUE;
      pNv->NoAccel = TRUE;
      pNv->HWCursor = FALSE;
      pNv->Flip = TRUE;
      xf86DrvMsg(pScrn->scrnIndex, X_CONFIG,
          "Left Right Reversing the screen - acceleration disabled\n");
   }	
   
will set the required variables in the pNv structure to the values they need to be for left-right reversing the Xserver. At this point, the field pNv->Flip doesn't exist. This needs to be added to the header file and will be dealt with later.

Aside from the variable pNv->Flip not existing yet, if it did in fact exist, we have now properly parsed the XF86Config file. Now what is needed is to have the function NVRefreshAreaFlip be used when needed. This is done in NVScreenInit. Within NVScreenInit, there is a section dealing with the "Shadow Buffer". Using a reasonable editor to make the changes, you can search for the string if(pNv->ShadowFB) in the Nvidia driver, this occurs around line 1779 of nv_driver.c. The original piece of code is the following:

if(pNv->ShadowFB) {
   RefreshAreaFuncPtr refreshArea = NVRefreshArea;

   if(pNv->Rotate) {
      pNv->PointerMoved = pScrn->PointerMoved;
      pScrn->PointerMoved = NVPointerMoved;

      switch(pScrn->bitsPerPixel) {
            case 8:  refreshArea = NVRefreshArea8;   break;
            case 16: refreshArea = NVRefreshArea16;  break;
            case 32: refreshArea = NVRefreshArea32;  break;
      }
   }

   ShadowFBInit(pScreen, refreshArea);
}

Since we want to add in the left-right reverse functionality, we can modify the above section of code to consider our reverse X cyborg option by changing the above code to :
if(pNv->ShadowFB) {
   RefreshAreaFuncPtr refreshArea = NVRefreshArea;
   if(pNv->Flip){
        pNv->PointerMoved = pScrn->PointerMoved;
        pScrn->PointerMoved = NVPointerMoved;

        refreshArea = NVRefreshAreaFlip;
   }
   else if(pNv->Rotate) {
        pNv->PointerMoved = pScrn->PointerMoved;
        pScrn->PointerMoved = NVPointerMoved;

        switch(pScrn->bitsPerPixel) {
             case 8:  refreshArea = NVRefreshArea8;   break;
             case 16: refreshArea = NVRefreshArea16;  break;
             case 32: refreshArea = NVRefreshArea32;  break;
        }
   }

   ShadowFBInit(pScreen, refreshArea);
}
All that is left to do is add the appropriate entries to the header file. The name of this file varies depending on the driver. Of course it is always a .h file which narrows the field greatly. All drivers will have a similar header, but for the Nvidia driver, the file is nv_type.h. If the modifications are on a different driver, it's probably best to look at the Nvidia .h file and grep for some similar entries in the directory of the driver which is being modified.

The pNv->Flip field must be defined. There is a struct which will be similar to:

typedef struct {
    RIVA_HW_INST        riva;
    RIVA_HW_STATE       SavedReg;
    RIVA_HW_STATE       ModeReg;
    EntityInfoPtr       pEnt;
    pciVideoPtr         PciInfo;
    PCITAG              PciTag;
      .		
      .
      .
    unsigned int        (*ddc1Read)(ScrnInfoPtr);
    void                (*DDC1SetSpeed)(ScrnInfoPtr, xf86ddcSpeed);
    Bool                (*i2cInit)(ScrnInfoPtr);
    I2CBusPtr           I2C;
    xf86Int10InfoPtr pInt;
} NVRec, *NVPtr;
To which we simply want to add the entry
    Bool                 Flip;
Lastly, we want to add our newly created function for left right reversing to the header. This probably belongs around
void NVRefreshArea(ScrnInfoPtr pScrn, int num, BoxPtr pbox);
and should be declared as
void NVRefreshAreaFlip(ScrnInfoPtr pScrn, int num, BoxPtr pbox);
Now, we may safely reverse the X server by adding the line
Option "FlipLR"
to the XFree4.0 XF86Config file, which should be located in the directory /etc/X11. Just in case any of the modifications above were not clear, a modified version of the nv_driver.c is available HERE and the header file nv_type.h is available HERE
Unfortunately the computers we're using to do these experiments with were vandalized both inside and out.

We believe that vandalism, tagging, or other attempts to deface our property is wrong, so we wrote a letter to the organization we suspected of tagging our computer.