Copyright (c) Hyperion Entertainment and contributors.

Optimized Window Refreshing

From AmigaOS Documentation Wiki
Revision as of 20:22, 19 June 2013 by Steven Solie (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

Maintaining the graphical contents of an Amiga window can be difficult. There are many subtleties associated with the process known as window refreshing. At present, many applications refresh their windows in suboptimal ways, or fail to refresh correctly under all conditions. This article attempts to explore and resolve the window refreshing problems commonly encountered by applications.

Damaging Information

Although many people think of Intuition as the Amiga's windowing system, the lower-level Layers library actually handles most of the work involved in maintaining Intuition's windowed environment. The Layers library divides a single physical display (a BitMap) into multiple virtual displays. Each of these virtual displays is known as a layer. Intuition uses the functions in the Layers library to move, resize, and depth arrange these layers.

Normally, each Intuition window consists of a single layer. Intuition adds borders and gadgetry to the layer to give it the familiar Intuition window appearance. Intuition also takes care of monitoring user input to let the user move, resize, and depth arrange windows. When Intuition wishes to change the size or position of a window, it calls functions in the Layers library to do most of the grunt work.

One of the main reasons for a layered rendering system is to provide multiple independent logical rendering regions to applications. The layer is the basis for the Intuition Window. The functions in the Layers library allow several applications to render into the same physical display (an Intuition Screen for example) without worrying about interfering with each other. As far as the application is concerned, it has an entire display all to itself.

There is a limit to the isolation that this layered environment offers applications. Because layers can overlap, by moving, resizing, or deleting a layer, a program can uncover portions of underlying layers. These newly exposed portions are called "damage regions". A layer can sustain damage when a task performs layers operations on it or other layers in the same display. When the Layers library damages a layer, it sets the layer's LAYERREFRESH bit in the Flags field of the Layer structure.

When damage occurs to a layer, the damaged region of the layer must be repaired by redrawing it. The entity responsible for redrawing the damaged region depends on the type of layer. The Layers library offers three types of layers: simple refresh, smart refresh, and superbitmap. Subsequently, Intuition bases its three types of windows on these three types of layers. The difference between each type has to do with the way in which each handles damage repair.

When a layers operation damages a simple refresh layer, the entire burden of repairing the layer's damage rests on the application that created the layer (if the application is using Intuition windows, which are built on top of layers, Intuition shares the burden of damage repair with the application. More on this later). This is because a simple refresh layer does not preserve its contents. The advantage of this type of layer is that it doesn't use much memory. The disadvantage is that every time a layers operation reveals a portion of a simple refresh layer, the application must explicitly rerender the exposed damaged regions.

Smart refresh layers help the application by doing some of the refreshing automatically. If a layer operation conceals a portion of a layer, that portion is automatically copied to an off-screen buffer. If a layer operation later reveals that portion of the layer, the Layers library uses the temporary buffer to update the revealed region. The Layers library does not leave the LAYERREFRESH bit set in this case because the Layers library took care of the damage. The application still needs to refresh the smart refresh layer whenever it is made larger, as the Layers library has no idea what should be in the newly exposed areas. In this case, the Layers library will leave the LAYERREFRESH bit set.

Finally, superbitmap layers totally eliminate the need for an application to refresh the display. The Layers library maintains a complete off-screen buffer representing the layer's contents. When a layer operation exposes new portions of a layer, the Layers library automatically updates these regions by copying from the off-screen buffer. The Layers library will never leave the LAYERREFRESH bit set for a superbitmap layer.

For the application programmer, a superbitmap layer offers the simplest approach to refreshing the layer as the entire burden of repairing damaged regions falls on the Layers library. This added convenience does have a cost--because of its off-screen buffer, the superbitmap layer requires a significantly larger amount of memory compared to the simple and smart refresh layers. The Layers library allocates this memory even if the layer never sustains damage.

When to Refresh

The way in which an application determines that it needs to refresh a layer depends on whether an application deals with layers directly via the Layers library, or indirectly via Intuition. It is important to keep in mind that each Intuition window has a Layer at its core. If an application uses Intuition windows it may not use the Layers library to create, delete, move, resize, or update the window layers. The application must use the corresponding Intuition functions instead.

If an application uses layers directly, it must look at a layer's LAYERREFRESH bit to tell if the layer sustained damage and needs repair. Since the application created and maintains its layers, it knows when damage can occur, so it checks for damage at those times.

Intuition manages an arbitrary number of layers on behalf of any number of client applications. All windowing operations happen on Intuition's time frame. Since other applications and the user can ask Intuition to manipulate window layers around at any time, it is not possible for a window-based application to know by itself when to refresh its window. That would involve polling the window layer's LAYERREFRESH bit, which is a big no-no in a multitasking system. Instead, Intuition provides a refresh notification mechanism through the IDCMP system.

Whenever Intuition performs a layer operation that can damage a window layer, it checks the damage state of each layer in the current screen by inspecting each layer's LAYERREFRESH bit. If Intuition finds that a layer's LAYERREFRESH bit is set, Intuition takes care of refreshing the damaged areas of the window that are Intuition's responsibility (for example, the window borders and gadgets). Following that, Intuition looks at the WFLG_NOCAREREFRESH bit in the Window structure. If this bit is set, Intuition's refresh processing for the window is complete, and all damage region information is discarded. However, If the bit is clear, Intuition sends an IDCMP_REFRESHWINDOW message to the window's IDCMP port, essentially asking the window to refresh itself.

So, instead of having to poll the LAYERREFRESH bit, an application can just wait for Intuition to tell it to refresh its window. This is convenient and fits in well with the IDCMP mechanism.

Scrolling Your Life Away

A scrolling display is a part of many user interfaces. An easy way to make people think an application is slow is to give it a sluggish user interface. Regardless of the actual speed at which an application performs its job, if its user interface management is slow, the user will get the impression the whole application is slow.

The ScrollRaster() and ClipBlit() routines are the two principal ways of scrolling data within a layer. ScrollRaster() moves the data within a RastPort. ClipBlit() moves data from one RastPort to the next, but works equally well if the source and destination RastPorts are the same.

Smart refresh layers are relatively easy to scroll with either ScrollRaster() or ClipBlit(). Although ClipBlit() has a theoretical advantage over ScrollRaster(), the performance of these functions is generally equivalent. ScrollRaster() does a minimum of two Blitter operations: one to scroll the data, and a generally smaller one to erase the area that was scrolled away. ClipBlit() can do a single blit to move the data in the layer, and leaves the rest of the display alone. Thus, while scrolling large amounts of data, an application can get better CPU/Blitter processing overlap by using ClipBlit(). Consider the following pseudo-code of what happens in ScrollRaster() versus ClipBlit():

Within the ScrollRaster() function:

  1. Wait for any outstanding Blitter operation to complete
  2. Initiate a Blitter operation to move the data in the window
  3. Wait for the Blitter operation to complete
  4. Initiate a second small Blitter operation to clear the scrolled region
  5. Return to the caller while the small blit is still in progress

Within the ClipBlit() function:

  1. Wait for any outstanding Blitter operation to complete
  2. Initiate a Blitter operation to move the data in the window
  3. Return to the caller while the blit is still in progress

Since the Blitter and the CPU can run concurrently, the second approach provides the most throughput, as the Blitter performs most of the scrolling work at the same time the application code runs after ClipBlit(). In practice though, ScrollRaster() and ClipBlit() run at about the same speed.

For a smart refresh window, ScrollRaster() and ClipBlit() do not present a problem. As long as the application only scrolls the contents of the window and not the window borders, the Layers library will take care of fixing any damage. Intuition will never know what happened. For a simple refresh window however, there is a problem. The reason has to do with the following:

      +--------------+           +--------------+
      |aaaaaaaaaaaaaa|           |bbbbaaaaaabbbb|
      |bbbb+----+bbbb|           |cccc+----+cccc|
      |cccc|    |cccc|           |dddd|    |dddd|
      |dddd+----+dddd|           |eeee+----+eeee|
      |eeeeeeeeeeeeee|           |              |
      +--------------+           +--------------+
         Figure  1a                 Figure  1b

Figure 1a represents a small window that the user placed on top of a larger simple refresh window. When an application scrolls the larger window's contents upwards with ScrollRaster(), the result looks like Figure 1b. This operation exposes two portions of the larger window, the section at the bottom of the large window (the part ScrollRaster() clears) and also the area above the smaller window (which ScrollRaster() leaves intact).

The application that owns the larger window has to refresh the two portions exposed by ScrollRaster(). The application knows to refresh the bottom section of the window because the damage is a direct result of the application calling ScrollRaster(). The problem is the area above the smaller window. The application does not know that there is a window overlapping its larger window, so it does not directly know about any damage resulting from other overlapping layers. Because the application used a graphics.library function to manipulate the window, Intuition does not know about the damage either. The result is that the larger window is damaged and no one knows it.

If the larger window had been a smart refresh window, the Layers library would have cached this portion of the larger window and ScrollRaster() would have taken action to make sure the cached region got restored. For a simple refresh window, this portion only gets added to the window layer's damage list and no rendering actually occurs.

The solution to this problem is for the application to act as Intuition would if it were scrolling. When Intuition manipulates a layer, it checks the LAYERREFRESH bit of each of its layers to see if any of them was damaged. After the application calls ScrollRaster() on its window, the application has to look at the LAYERREFRESH bit of the window's layer. If the bit is set, damage exists and the application needs to repair the window. As this window is the only window that can sustain layer damage as a result of the call to ScrollRaster(), the application needs to check only its own window's layer for damage.

Note that to scroll the contents of a simple refresh window, an application has to use ScrollRaster() rather than ClipBlit(). The reason is ClipBlit() does not add anything to the window layer's damage region, so an application will never know about the damage. Also, unlike ScrollRaster(), ClipBlit() does not scroll the window layer's damage region. If the layer already has damage when an application calls ScrollRaster(), the position of the damage region will move along with the data in the layer.

Faster Rendering

When rendering to a RastPort, an application can improve its rendering performance if it renders using a limited number of colors. Each RastPort contains a mask field which specifies the writeable bitplanes of the RastPort's BitMap. If a RastPort's bitplane is write-protected, the system ignores that bitplane when rendering to the RastPort.

An application can control the BitMap mask by using the SetWrMsk() macro (defined in <graphics/gfxmacros.h>). This macro accepts a pointer to a RastPort, and a new mask. The mask is an 8 bit value. Each bit of the value represents a BitMap bitplane. For example, a mask of value 5, which is 00000101 in binary, restricts rendering in that BitMap to planes 0 and 2 which are the only two bits set in the binary value.

One type of application that can improve its performance by using the RastPort Mask is the text editor. Typically, a text editor needs to render its text in a single color, generally color 1. This means the editor only needs to render to bitplane 0. All other planes will always remain blank. If these planes are going to remain blank, why should the editor bother to render to them or scroll them?

When scrolling large sections of the display, ScrollRaster() can make the display look unstable because it continuously clears portions of the display. ClipBlit() eliminates this visual nuisance as it does not clear the display. Unfortunately, ClipBlit() by itself is useless for scrolling a simple refresh window because it does not deal with damage regions. The solution is sneaky but quite effective. An application can do the following each time it scrolls a simple refresh layer:

  1. Set the RastPort Mask to limit the number of writeable bitplanes.
  2. Use ClipBlit() to scroll the data.
  3. Set the RastPort Mask to 0.
  4. Use ScrollRaster() to scroll the same data.

In the procedure above, ClipBlit() scrolls the window contents but not the damage regions. ScrollRaster() scrolls the damage region, but because the RastPort mask is 0, ScrollRaster() does not affect the window contents. Setting the RastPort mask to 0 prevents the system from disturbing the data in any of the planes of the BitMap.

The above trick can come in quite handy. It is fairly fast as well, although it can involve some hidden overhead. Even though ScrollRaster() doesn't move data on the display, it still needs to go through all the layer's clipping regions, which can be time consuming.

Using Multiple RastPorts

A RastPort specifies attributes needed to perform many rendering operations. These include the RastPort's foreground, background, and drawing mode. The Graphics library functions SetAPen(), SetBPen(), and SetDrMd() set each of these attributes, respectively.

Although these functions appear to have fairly simple purposes in life, the SetAPen(), SetBPen(), and SetDrMd() are quite CPU intensive routines. These functions require recalculating values that the OS caches in private parts of the RastPort. If an application only requires a few different combinations of foreground/background/draw mode, it can improve its performance by using a different RastPort structure for each combination. An application sets the attributes of several RastPorts only once which is generally more efficient that setting the attributes of one RastPort every time the rendering attributes change.

Assume an application has a window in which it renders all of its data in pen 1, and clears any part of its display using color 0. Such an application can improve its rendering performance by doing the following:

    struct RastPort dataRP;
    struct RastPort clrRP;
 
    dataRP = *window->RPort;
    IGraphics->SetAPen(&dataRP,1);
    IGraphics->SetBPen(&dataRP,0);
 
    clrRP = *window->RPort;
    IGraphics->SetAPen(&clrRP,0);
 
    /* renders to the window's RastPort in color 1 */
    IGraphics->Text(&dataRP,"hello",5);
 
    /* clears a section of the window's RastPort in color 0 */
    IGraphics->RectFill(&clrRPort,0,0,10,10);

Refreshing a Sizable Window

A sizable window is by far the trickiest to refresh correctly. The most important and often overlooked point is that an application needs to ensure the size of its window does not change while it is refreshing the window. If the application doesn't and the user changes the window size, the application will refresh the window at the window's old size rather than its new size. This can severely corrupt the appearance of the window.

There are two main ways an application can keep the size of its window stable while refreshing the window. The first method is to keep the size of the window locked most of the time, unlocking the window only when the user tries to size the window. The other approach is to lock the window size only while rendering to the window.

The first method is part of Intuition's IDCMP mechanism. To lock a window's size, an application sets the window's IDCMP_SIZEVERIFY IDCMP flag. When the user attempts to size the window by clicking on the window's sizing gadget, Intuition notifies the application by sending an IDCMP_SIZEVERIFY message to the window's IDCMP port. Intuition will keep the window's size locked until the application returns the IDCMP_SIZEVERIFY message.

In general this scheme works well, but it does have two problems. First, if the application is busy doing some processing, such as recalculating a spreadsheet, it may not notice that the message arrived until it is done with its current processing. The result is the user will not be able to size the window until the application is finished processing, which might more time than the user wants.

The second problem occurs when the application is waiting for input from Intuition, such as in the middle of an EasyRequest() call. If the user clicks on the sizing gadget of the application's window while the requester is up, Intuition will wait for the system will enter a deadlock. Intuition will wait for the application to send back will not see the IDCMP_SIZEVERIFY event until the user satisfies the EasyRequest(). The result will be a system deadlock. Many applications suffer from this problem.

The second problem occurs when the application is waiting for input from Intuition, such as in the middle of an EasyRequest() call. If the user clicks on the sizing gadget of the application's window while the requester is up, the system will enter a deadlock. When the user clicks the sizing gadget, Intuition sends the IDCMP_SIZEVERIFY message and waits for a reply. Because the application is already waiting for the EasyRequest() to return, the application cannot send back the reply. Many applications suffer from this problem.

Intuition avoids these deadlocks. Intuition will time out the sizing operation if the application does not process the IDCMP_SIZEVERIFY message within a given time period. Although the user can no longer deadlock the system, this situation can still confuse the user because clicking on the window's sizing gadget no longer sizes the window. That does not mean an application should rely upon Intuition to avoid the deadlock. An application should always avoid these conditions.

In general, it is simpler and safer for the application to lock a window only during the rendering process. The application can do this by surrounding all rendering operations with calls to the Layers library functions LockLayer() and UnlockLayer(). LockLayer() locks the size and position of a layer. While a window's layer is locked, an application can safely look at the window's current size and render to it without any danger of the size changing. Once the application finishes rendering, it unlocks the window's layer by calling UnlockLayer(). When using this method, an application must not set the window's IDCMP_SIZEVERIFY bit. Be careful which system functions an application calls while it has a layer locked. Only use the Graphics library rendering functions and the simple Intuition rendering functions (i.e., PrintIText(), DrawImage(), etc.). In particular, avoid calls that deal with gadgets (including RefreshGList()) and other locks (i.e., LockIBase() and LockLayerInfo()).

BeginRefresh() and EndRefresh()

To improve the performance of repairing damage to simple and smart refresh windows, Intuition can put a window into a special refresh state using Intuition's BeginRefresh() function:

   VOID BeginRefresh(struct Window *window)

While a window is in this state, the system restricts attempts to render into the window to the window's damaged regions. Because the system ignores rendering operations outside the window's damage regions, an application only refreshes the parts of a window that need refreshing. This can significant decrease the amount of time necessary to refresh the window. This also reduces the possibility of visual flicker that can happen if an application has to redraw the entire contents of a window.

To end a window's special refresh state, use EndRefresh():

   VOID EndRefresh(struct Window *window, BOOL Complete)

Compared to BeginRefresh(), EndRefresh() has an extra parameter, a boolean value. If this value is TRUE, Intuition assumes that all of the refreshing for this window is finished and removes all of the window layer's damaged regions. In most cases, this value should be TRUE. See the Autodoc for BeginRefresh() and EndRefresh() for more information.

Backfill Hook

Whenever a layer sustains damage, the damaged region is cleared to color 0. Clearing the region requires an often lengthy Blitter operation which is unnecessary if the application plans to redraw the damaged region anyway. Such an application can improve its performance by preventing the Layers library from clearing the damaged regions. This is what layer backfill hooks are for.

A backfill hook is a custom function that an application attaches to a specific layer. Whenever damage occurs to a layer, the Layers library calls that layer's custom backfill hook. The Layers library passes the position and dimensions of the damage area to the backfill hook. The backfill hook can render into the damage area, refreshing it. Theoretically, the backfill hook can redraw a layer's damaged regions instead of clearing the damage area to color 0.

Unfortunately, refreshing a layer from a backfill hook can be quite difficult. The backfill hook code is usually called from a different task than the main application. If the backfill hook and the application do not properly arbitrate access to the application's data, dangerous race conditions can occur if the hook and the application try to access the same data. Another problem with rendering through the backfill hook is that no clipping is available. The backfill hook must do its own clipping to ensure that no rendering goes outside the dimensions specified when the hook is called, which in itself can be quite difficult.

For many situations, the best use of a backfill hook is to have it do nothing. Whenever the hook is called, it simply returns without doing any rendering. This has the effect of eliminating the extra blit done to clear the damage region to color 0. This can speed layer operations quite a bit.

There is one problem with a no-op backfill hook. If the application is busy doing some processing and damage occurs to its layer, the display will remain dirty until the application finishes its processing and notices that the layer is damaged. This damage can include remnants of system imagery, like window borders, which can confuse the user.

One way to overcome this problem is to use a backfill hook that changes its behavior depending on the state of the application. If the application is not too busy to notice damage to its window's layer, the backfill hook does not erase the display. However, if the application is too busy to refresh its damaged window, the backfill hook clears the damaged portion of the display. Using this method, the backfill hook will not waste time erasing the damage when the application will update it immediately, but the backfill hook will erase the damage if the application can't refresh for a while.

Optimrefresh.c

;/* Optimrefresh.c - Execute me to compile me with SAS/C 6.56
sc NMINC STRMERGE STREQ MCCONS COMNEST UNSCHAR NOSTKCHK SAVEDS IGNORE=73 optimrefresh.c
slink FROM LIB:c.o,optimrefresh.o TO optimrefresh LIBRARY LIB:sc.lib,LIB:amiga.lib
quit ;*/
 
/* Copyright © 1992 Martin Taillefer.   All rights reserved.              */
/* The information contained herein is subject to change without notice,  */
/* and is provided "as is" without warranty of any kind, either expressed */
/* or implied.  The entire risk as to the use of this information is      */
/* assumed by the user.                                                   */
 
/* This program demonstrates optimal window refreshing using a scrolling text
 * display as a sample.
 */
 
#include <exec/types.h>
#include <exec/libraries.h>
#include <exec/memory.h>
#include <utility/hooks.h>
#include <utility/tagitem.h>
#include <graphics/gfxmacros.h>
#include <intuition/intuition.h>
#include <intuition/screens.h>
#include <intuition/gadgetclass.h>
#include <dos.h>
 
#include <clib/exec_protos.h>
#include <clib/intuition_protos.h>
#include <clib/graphics_protos.h>
#include <clib/layers_protos.h>
#include <clib/alib_protos.h>
#include <clib/dos_protos.h>
 
/*****************************************************************************/
 
 
/* There is one Line structure for every line of text in our fictional
 * document.
 */
struct Line
{
    struct MinNode ln_Link;     /* to link the lines together           */
    STRPTR         ln_Text;     /* pointer to the text for this line    */
    ULONG          ln_TextLen;  /* the length of the text for this line */
};
 
 
/*****************************************************************************/
 
 
/* system libraries */
struct Library  *IntuitionBase;
struct Library  *GfxBase;
struct Library  *LayersBase;
 
/* global display handles */
struct Screen   *screen;
struct Window   *window;
struct Gadget   *scroller;
struct Hook      refreshHook;
 
struct RastPort render;
struct RastPort clear;
 
/* our document along with associated view information */
struct MinList   document;
ULONG            numLines;
ULONG            topLine;
ULONG            oldTopLine;
ULONG            linesVisible;
ULONG            columnsVisible;
ULONG            fontHeight;
ULONG            fontWidth;
ULONG            viewHeight;
ULONG            viewWidth;
ULONG            usefulWidth;
ULONG            usefulHeight;
 
/* a state flag indicating whether the main application is busy */
BOOL             taskBusy;
/*****************************************************************************/
 
VOID InitDocument(VOID);
VOID FreeDocument(VOID);
VOID EventLoop(VOID);
VOID __asm BackFillHook(register __a2 struct RastPort    *rp,
                        register __a1 struct BackFillMsg *bfm);
 
/*****************************************************************************/
 
 
/* This is where it all begins.
 */
ULONG main(void)
{
    /* open the system libraries we need.
     */
    IntuitionBase = OpenLibrary("intuition.library",37);
    GfxBase       = OpenLibrary("graphics.library",37);
    LayersBase    = OpenLibrary("layers.library",37);
 
    if (IntuitionBase && GfxBase && LayersBase)
    {
        /* get a pointer to the default public screen */
        if (screen = LockPubScreen(NULL))
        {
            /* allocate and initialize a scroller as a BOOPSI object */
            if (scroller = NewObject(NULL,"propgclass",
                  GA_RelRight,    -13,
                  GA_Top,         1+screen->WBorTop+screen->Font->ta_YSize+1,
                  GA_Width,       10,
                  GA_RelHeight,   -12-(screen->WBorTop+screen->Font->ta_YSize+1),
                  GA_RelVerify,   TRUE,
                  GA_Immediate,   TRUE,
                  GA_FollowMouse, TRUE,
                  GA_RightBorder, TRUE,
                  PGA_Borderless, TRUE,
                  PGA_Freedom,    FREEVERT,
                  PGA_Total,      1,
                  PGA_Visible,    1,
                  PGA_Top,        0,
                  PGA_NewLook,    TRUE,
                  TAG_END))
            {
                /* initialize data used by the backfill hook */
                refreshHook.h_Entry = ( ULONG (*)() )BackFillHook;  /* point the */
                taskBusy            = TRUE;               /* hook to our routine. */
 
                /* open the window */
                if (window = OpenWindowTags(NULL,
                      WA_Left,          0,
                      WA_Top,           0,
                      WA_PubScreen,     screen,
                      WA_AutoAdjust,    TRUE,
                      WA_CloseGadget,   TRUE,
                      WA_DepthGadget,   TRUE,
                      WA_DragBar,       TRUE,
                      WA_SizeGadget,    TRUE,
                      WA_SizeBRight,    TRUE,
                      WA_Title,         "Optimized Refresh Sample",
                      WA_SimpleRefresh, TRUE,
                      WA_Activate,      TRUE,
                      WA_Gadgets,       scroller,
                      WA_MinWidth,      32,
                      WA_MinHeight,     10+12+(screen->Font->ta_YSize+1),
                      WA_MaxWidth,      -1,
                      WA_MaxHeight,     -1,
                      WA_IDCMP,         IDCMP_CLOSEWINDOW | IDCMP_NEWSIZE
                                        | IDCMP_REFRESHWINDOW | IDCMP_GADGETUP
                                        | IDCMP_GADGETDOWN | IDCMP_MOUSEMOVE
                                        | IDCMP_VANILLAKEY,
                      WA_BackFill,      &refreshHook,
                      TAG_END))
                {
                    /* initialize our document structure */
                    InitDocument();
 
                    /* process user events in the window */
                    EventLoop();
 
                    /* free our document structure */
                    FreeDocument();
 
                    /* close up shop */
                    CloseWindow(window);
                }
                /* free the scroller BOOPSI object */
                DisposeObject(scroller);
            }
            /* unlock the default public screem */
            UnlockPubScreen(NULL,screen);
        }
    }
 
    /* close the libraries we opened */
    CloseLibrary(LayersBase);
    CloseLibrary(GfxBase);
    CloseLibrary(IntuitionBase);
 
    /* tell the shell everything is all right */
    return(0);
}
 
 
/*****************************************************************************/
 
 
/* This function initializes our document. That means allocating 100
 * Line structures and linking them together in an Exec list. The lines
 * are filled with a pattern of text so we have something to display
 * in our window
 */
VOID InitDocument(VOID)
{
struct Line *line;
UWORD        i,j;
 
    NewList((struct List *)&document);
    numLines = 0;
    i        = 100;
    while (i--)
    {
        if (line = AllocVec(sizeof(struct Line)+91,MEMF_CLEAR|MEMF_PUBLIC))
        {
            line->ln_Text    = (STRPTR)((ULONG)line + sizeof(struct Line));
            line->ln_TextLen = 40;
            AddTail((struct List *)&document,(struct Node *)line);
            numLines++;
 
            j = 0;
            while (j < 90)
            {
                line->ln_Text[j] = (numLines % 96) + 32;
                j++;
            }
        }
    }
}
 
 
/*****************************************************************************/
 
 
/* This function frees all the memory allocated by InitDocument() */
VOID FreeDocument(VOID)
{
struct Line *line;
 
    while (line = (struct Line *)RemHead((struct List *)&document))
        FreeVec(line);
}
 
 
/*****************************************************************************/
/* This is the message packet passed by layers.library to a backfill hook.
 * It contains a pointer to the layer that has been damaged, a Rectangle
 * structure that defines the bounds of the damage. No rendering can occur
 * outside of these coordinates.
 *
 * The backfill hook is also passed a RastPort in which the rendering
 * should be performed.
 */
struct BackFillMsg
{
    struct Layer     *bf_Layer;
    struct Rectangle  bf_Bounds;
    LONG              bf_OffsetX;
    LONG              bf_OffsetY;
};
 
 
VOID __asm BackFillHook(register __a2 struct RastPort    *rp,
                        register __a1 struct BackFillMsg *bfm)
{
struct RastPort crp;
 
    crp       = *rp;            /* copy the rastport                      */
    crp.Layer = NULL;           /* eliminate bogus clipping from our copy */
 
    if (taskBusy)
    {
        SetWrMsk(&crp,0xff);    /* if the main task is busy, clear all planes */
    }
    else
    {
        SetWrMsk(&crp,0xfe);    /* otherwise, clear all planes except plane 0 */
    }
 
    SetAPen(&crp,0);                     /* set the pen to color 0         */
    SetDrMd(&crp,JAM2);                  /* set the rendering mode we need */
    RectFill(&crp,bfm->bf_Bounds.MinX,   /* clear the whole area           */
                  bfm->bf_Bounds.MinY,
                  bfm->bf_Bounds.MaxX,
                  bfm->bf_Bounds.MaxY);
}
 
 
/*****************************************************************************/
 
 
/* Adjust the scroller object to reflect the current window size and
 * scroll offset within our document
 */
VOID SetScroller(struct Window *window, struct Gadget *scroller,
                 ULONG linesVisible, ULONG numLines, ULONG topLines)
{
    SetGadgetAttrs(scroller,window,NULL,PGA_Visible, linesVisible,
                                        PGA_Total,   numLines,
                                        PGA_Top,     topLine,
                                        TAG_END);
}
 
 
/*****************************************************************************/
 
 
/* Render a single line of text at a given position */
VOID RenderLine(UWORD x, UWORD y, UWORD w, STRPTR text, ULONG len)
{
    Move(&render,x,y);                  /* move the cursor to the position */
 
    if (len > columnsVisible)           /* is line is longer than allowed? */
        len = columnsVisible;           /* yes, so reduce its length       */
 
    Text(&render,text,len);             /* write to the window             */
 
    if (len < columnsVisible)
        RectFill(&clear,render.cp_x,y-render.TxBaseline,
                        x+w-1,y-render.TxBaseline+fontHeight-1);
 
}
/*****************************************************************************/
 
/* This function performs most of the rendering work needed by our sample.
 * It first locks the window's layer to insure it doesn't get sized during
 * the rendering process. It then looks at the current window size and
 * adjusts its rendering variables in consequence. If the damage parameter
 * is set to TRUE, the routine then proceeds to explicitly erase any area
 * of the display to which we will not be rendering in the rendering loop.
 * This erases any left over characters that could be left if the user sizes
 * the window smaller. Finally, the routine determines which lines of the
 * display need to be updated and goes on to do it.
 */
VOID RefreshView(BOOL damage)
{
ULONG        i;
struct Line *line;
UWORD        x,y;
 
    /* lock the window's layer so its size will not change */
    LockLayer(NULL,window->WLayer);
 
    /* determine various values based on the current size of the window */
    viewWidth      = window->Width - window->BorderLeft - window->BorderRight;
    fontWidth      = window->RPort->Font->tf_XSize;
    columnsVisible = viewWidth / fontWidth;
 
    viewHeight     = window->Height - window->BorderTop - window->BorderBottom;
    fontHeight     = window->RPort->Font->tf_YSize;
    linesVisible   = viewHeight / fontHeight;
 
    usefulWidth = columnsVisible * fontWidth;
 
    if (linesVisible > numLines)
    {
        usefulHeight = numLines * fontHeight;
        topLine      = 0;
    }
    else if (topLine + linesVisible > numLines)
    {
        topLine      = (numLines - linesVisible);
        usefulHeight = (numLines - topLine) * fontHeight;
    }
    else
    {
        usefulHeight = linesVisible * fontHeight;
    }
 
    /* if we were called because of damage, we must erase any left over
     * garbage
     */
    if (damage)
    {
        /* erase anything left over on the right side of the window */
        if ((window->BorderLeft + usefulWidth < window->Width - window->BorderRight)
        &&   usefulHeight)
        {
            RectFill(&clear,window->BorderLeft + usefulWidth,
                            window->BorderTop,
                            window->Width - window->BorderRight - 1,
                            window->BorderTop + usefulHeight - 1);
        }
 
        /* erase anything left over on the bottom of the window */
        if ((window->BorderLeft < window->Width - window->BorderRight)
        &&  (window->BorderTop + usefulHeight < window->Height - window->BorderBottom))
        {
            RectFill(&clear,window->BorderLeft,
                            window->BorderTop + usefulHeight,
                            window->Width - window->BorderRight - 1,
                            window->Height - window->BorderBottom - 1);
        }
    }
 
    /* if we have at least one line and one column to render... */
    if (usefulHeight && usefulWidth)
    {
        /* get a pointer to the first line currently visible */
        line = (struct Line *)document.mlh_Head;
        i    = topLine;
        while (line->ln_Link.mln_Succ && i--)
            line = (struct Line *)line->ln_Link.mln_Succ;
 
        if (damage
        || (topLine >= oldTopLine + linesVisible - 1)
        || ((oldTopLine > linesVisible)
        && (topLine <= oldTopLine - linesVisible + 1)))
        {
            /* the whole display must be redrawn */
            x = window->BorderLeft;
            y = window->BorderTop + window->RPort->Font->tf_Baseline;
            i = linesVisible;
        }
        else if (topLine < oldTopLine)
        {
            /* we just need to scroll the text */
            ScrollRaster(&render,0,-(LONG)((oldTopLine - topLine) * fontHeight),
                         window->BorderLeft,
                         window->BorderTop,
                         window->BorderLeft+usefulWidth-1,
                         window->BorderTop+usefulHeight-1);
 
            /* indicates what section needs to be redrawn */
            x = window->BorderLeft;
            y = window->BorderTop + window->RPort->Font->tf_Baseline;
            i = oldTopLine - topLine;
        }
        else if (topLine > oldTopLine)
        {
            /* we just need to scroll the text */
            ScrollRaster(&render,0,(topLine - oldTopLine) * fontHeight,
                         window->BorderLeft,
                         window->BorderTop,
                         window->BorderLeft+usefulWidth-1,
                         window->BorderTop+usefulHeight-1);
 
            /* indicates what section needs to be redrawn */
            i = linesVisible - (topLine - oldTopLine);
            while (line->ln_Link.mln_Succ && i--)
                line = (struct Line *)line->ln_Link.mln_Succ;
 
            x = window->BorderLeft;
            y = window->BorderTop + window->RPort->Font->tf_Baseline
                + (fontHeight * (linesVisible - (topLine - oldTopLine)));
            i = topLine - oldTopLine;
        }
        else
        {
            /* we don't need to render anything */
            i = 0;
        }
 
        /* render all the lines we need */
        while (i-- && line->ln_Link.mln_Succ)
        {
            RenderLine(x,y,usefulWidth,line->ln_Text,line->ln_TextLen);
            y    += fontHeight;
            line  = (struct Line *)line->ln_Link.mln_Succ;
        }
    }
 
    /* unlock the layer so normal operations can resume */
    UnlockLayer(window->WLayer);
 
    /* keep track of what the current top line is. That way, when we
     * come back in this routine later, and "topLine" has changed, we
     * can tell how many lines we need to scroll in order to sync up the
     * display
     */
    oldTopLine = topLine;
}
 
 
/*****************************************************************************/
 
/* Whenever the application is busy, this function is called. It will
 * change the behavior of the backfill hook in order to improve the
 * appearance of the display until the application completes its lengthy
 * task.
 *
 * You could also set a busy pointer in the document window in this routine
 * to tell the user you are not listening to him for awhile.
 */
VOID BusyState(BOOL makeBusy)
{
    taskBusy = makeBusy;
 
    if (LAYERREFRESH & window->WLayer->Flags)
    {
        BeginRefresh(window);
        RefreshView(TRUE);
        EndRefresh(window,TRUE);
    }
}
 
 
/*****************************************************************************/
 
 
/* This routine is a typical event processor. It looks and acts on all events
 * arriving at the window's port.
 */
VOID EventLoop(VOID)
{
struct IntuiMessage *intuiMsg;
ULONG                class;
 
    topLine    = 0;
    oldTopLine = 0;
 
    /* initialize rendering attributes we are going to use */
    render = *window->RPort;
    SetDrMd(&render,JAM2);
    SetWrMsk(&render,1);         /* we only want to render in the first plane */
    SetAPen(&render,1);
 
    /* initialize clearing attributes we are going to use */
    clear = *window->RPort;
    SetDrMd(&clear,JAM2);
    SetWrMsk(&clear,1);          /* we only want to clear the first plane */
    SetAPen(&clear,0);
 
    /* render the initial display */
    RefreshView(TRUE);
 
    /* set the initial scroller position and size */
    SetScroller(window,scroller,linesVisible,numLines,topLine);
 
    /* we aren't busy, so register that fact */
    BusyState(FALSE);
 
    while (TRUE)
    {
        /* if the LAYERREFRESH flag is set in the window's layer, it
         * means the layer has some damage we should repair.
         */
        if (LAYERREFRESH & window->WLayer->Flags)
        {
            /* enter optimized repair state */
            BeginRefresh(window);
 
            /* redraw the whole display through the optimized repair
             * region
             */
            RefreshView(TRUE);
 
            /* tell the system we are done repairing the window
             */
            EndRefresh(window,TRUE);
        }
 
 
        /* nothing left to do but wait for user input */
        WaitPort(window->UserPort);
        intuiMsg = (struct IntuiMessage *)GetMsg(window->UserPort);
        class    = intuiMsg->Class;
        ReplyMsg(intuiMsg);
 
        /* we got a message, so act on it */
        switch (class)
        {
            /* user clicked on the close gadget, exit the program */
            case IDCMP_CLOSEWINDOW  : return;
 
            /* user sized the window. We need to redraw the whole
             * display in order to eliminate any garbage. Start by
             * calling BeginRefresh() and EndRefresh() to eliminate
             * the window's damage regions then completely redraw
             * the window contents.
             */
            case IDCMP_NEWSIZE      : BeginRefresh(window);
                                      EndRefresh(window,TRUE);
                                      RefreshView(TRUE);
                                      SetScroller(window,
                                                  scroller,
                                                  linesVisible,
                                                  numLines,
                                                  topLine);
                                      break;
 
            /* Intuition is telling us damage occured to our layer.
             * Don't bother doing anything, the check at the top of the
             * loop will catch this fact and refresh the display
             *
             * Even though we don't do anything with these events, we
             * still need them to be sent to us so we will wake up and
             * look at the LAYERREFRESH bit.
             */
            case IDCMP_REFRESHWINDOW: break;
 
            /* user is playing with the scroller. Get the scroller's current
             * top line and synchronize the display to match it
             */
            case IDCMP_GADGETUP     :
            case IDCMP_GADGETDOWN   :
            case IDCMP_MOUSEMOVE    : GetAttr(PGA_Top,scroller,&topLine);
                                      RefreshView(FALSE);
                                      break;
 
            /* whenever a key is hit, we fake becoming busy for 4
             * seconds. During that time, try to size and depth arrange
             * the window to see what happens to its contents
             */
            case IDCMP_VANILLAKEY   : BusyState(TRUE);
                                      Delay(200);
                                      BusyState(FALSE);
                                      break;
        }
    }
}