PROGRAMMING THE AMIGA IN GRAPHICS. Overview: This article describes the graphics capabilities of the Amiga, both in terms of the data structures and ROM Kernel routines which allow the programmer to control the Amiga's display, and the custom graphics circuitry which enables the system to create the displays requested by the programmer. Although it is intended to give a prospective programmer sufficient information (along with the appropriate Reference Manuals) to tackle most facets of Amiga graphics programming, this article concentrates on providing a conceptual understanding of the interrelationships between various components of the graphics system. It can therefore be read be profitably read by any Amiga user who wishes to gain an insight into the workings of the Amiga's remarkable graphics. Amiga graphics may be approached on three levels by the programmer, which I shall call - 1. Intuition 2. Kernel 3. Copper These names are purely of my choice, and may conflict with those chosen by others for the same purpose. Intuition is the name of the Amiga's user interface (which includes screens,windows,menus etc), but I also use it to refer to a set of data structures and routines which are designed to give the programmer the easiest possible access to graphics while remaining within the confines of the Intuition user interface. Intuition is designed to allow the user to make effective use of the Amiga's multitasking capabilities, so programs intending to co-operate with Intuition should restrict themselves to Intuition-level graphics where possible. Although facilities available at the Kernel and Copper levels may be used by such programs, care must be taken that the Intuition interface is not destroyed by their use. Kernel is shorthand for "ROM Kernel", which is commonly used to describe the routines in ROM which form the major part of the Amiga's operating system. While some of the graphics routines in ROM support Intuition-level graphics, the majority allow the user to control the Amiga's graphics with more flexibility and provide the programmer with much more power. The use of some Kernel-level features precludes the existence (or at least availability to the user) of the Intuition interface, hence programs which use these facilities are often referred to as "nasty" or as "taking over the machine". Kernel graphics allows the programmer to exercise very close control over the appearance of the Amiga's display, providing easy access to the capabilities of the graphics hardware; it generally provides the optimum environment as regards simplicity, flexibility and speed of operation. Copper is short for "Coprocessor", a simple microprocessor contained in one of the custom chips which directly controls the Amiga's video display using a list of simple instructions which form a rudimentary "program". While this list is normally created and maintained by Kernel routines, it is possible for the programmer to create a Copper list directly - i.e. Copper-level programming. While more laborious and complex than Kernel-level programming, Copper programming provides the maximum possible control over the Amiga's graphics, making possible effects and animations unobtainable with Kernel programming. The Kernel provides a mechanism for safely integrating Copper programs into Kernel or even Intuition displays; this will be discussed in conjunction with direct Copper control. Note that the distinctions between these "levels" of programming are somewhat blurred - the Intuition interface is built directly on the Kernel routines and structures, and the Kernel relies, ultimately, on the Copper to create your display. The material from here on becomes more technically detailed. If you are not familiar with such concepts as "data structures","routines" and "pointers" please read Appendix A. The concepts explained there are fundamental to the rest of this article. Amiga Graphic Fundamentals: To draw and display graphics on the Amiga, the first requirement is that you have a block of screen memory of the appropriate size to work with. This memory block may be anywhere in the lowest-addressed 512 Kbytes of the Amiga's memory. Since the operating system maintains internally a list of memory blocks which are free (i.e. not in use by any other tasks running in the system), you should call one of the memory-allocations routines to have the system allocate to you a block of screen memory of the size you require. The routine AllocRaster is designed specifically for allocating screen memory, and returns the address of a block of memory of the size you specify which you may safely use. This screen memory is organised, from the programmer's point of view, as a rectangular "bit plane", which has a width and height (in pixels) specified by you. The first bit of the memory block is the upper left corner pixel of the rectangle, while the last bit is the lower right corner pixel; the leftmost pixel of each line and the rightmost of the previous line correspond to adjacent bits in your memory block. It is apparent that a given memory block may serve as a bit plane of any width and height, provided the overall area remains the same. If you wish to display more than two colours in your graphics region you must allocate memory for more than one bit plane. Imagine that these planes are directly overlaid on each other; the colour of each pixel is determined by the combination of the corresponding bits in each bit-plane. Each plane you use doubles the number of available colours, up to a limit of 6 planes (see Graphics Modes). Since these planes are conventionally numbered from zero upward, each pixel in the region can have a "colour number" ranging from 0 to an upper limit determined by the number of bit planes used. The means by which the colour number of a pixel determines the displayed colour is explained in Graphics Modes and Colours. The combination of a number of bit planes of given width and height is known as a "bit map", and is described by a data structure called a "BitMap". This structure specifies the number of bytes in each row (horizontal line) of the bit map (there are eight bits or pixels in one byte), the height of the bit map in screen lines and the number of planes used (or "depth"). It also contains a pointer to each bit plane. Note that a bit map has maximum dimensions of 1024 by 1024 pixels. Rendering versus displaying. The facilities provided by any computer's graphics system may be, in general, divided into "Rendering" and "Displaying". Rendering into a bit map is the act of modifying its pixels, either singly or in groups. It is important to understand that on the Amiga there is no requirement that the bit map you render into be visible on the screen, or that a bit map you display has been rendered into. The rendering and displaying mechanisms are entirely separate, and the relationship between what is rendered and what is visible on the screen is entirely under the control of the programmer. The only common point of both mechanisms is the BitMap structure, which is used for both rendering and display. Graphics Rendering. We shall deal with graphics rendering first, since rendering into a bitmap is much simpler and more straightforward than displaying it. There are two structures used to control the rendering process - the BitMap (described above) and the "RastPort". The RastPort contains a large amount of information which determines how rendering into the BitMap (to which the RastPort must point) will be performed. "Foreground", "Background" and "Outline" pen colours determine the colours used to render lines, rectangles, text etc. and affect area-filling operations. Line and area patterns are stored in the RastPort, as are the characteristics (font, spacing etc.) of text rendered. The Kernel routine InitRastPort is used to initialise the RastPort's fields to useful default values. The Kernel provides a large number of routines for graphics rendering, most of which require the address of a RastPort. These include routines to set the foreground, background and outline colours, and draw lines, outline and filled polygons, filled rectangles, ellipses and circles in the current foreground colour and line and area patterns. Also provided are routines to draw text into the bitmap and move rectangular blocks of pixels from one bit map to another. Graphics Displaying. Graphics Modes and Colors. The programmer has a number of choices as to how a given bit map may be displayed. He may select: - low or high resolution (320 or 640 pixels per line respectively); - non-interlaced or interlaced display (200 or 400 screen lines); - one of several "special" modes, namely HAM (Hold and Modify), extra half bright, genlock video and dual playfield. Each of these modes may be applied to the whole screen or a selected portion of it. If no special display mode is selected, the colour which will be displayed on the screen for each pixel of the bit map is determined by its colour number and the corresponding entry in the "colour palette", an array which contains the actual red, green and blue intensities of the screen colour which is displayed for each colour number. This palette can have a maximum of 32 entries, which is fully utilized by the low-resolution display of a 5 plane bit map. However, a high-resolution display may only use a 4 plane bitmap. Some of the special graphics modes can use 6 bit planes, for example HAM and dual-playfield. However, these do not directly use the colour palette to generate displays. The following sections describe the three "levels" at which graphics may be displayed - Kernel, Intuition and Copper. We shall begin with Kernel graphics, since this knowledge provides the best basis for understanding the rest. Kernel Graphics. The appearance of the Amiga's screen is described by a group of data structures which are created and manipulated by the programmer. These structures are used by Kernel routines to create the displays desired by the programmer. Variables affecting the entire screen are stored in the "View" structure. These are: - the offset of the top left corner of the visible screen area from a defined position, allowing the display as a whole to be easily moved; - the addresses of the hardware copper lists for the display (see Copper Graphics); - flags describing various display parameters; - a pointer to a ViewPort structure (see below). The screen display created by use of the View structure is also termed a "view". The Amiga's screen can be subdivided into any number of horizontal slices (at least one must be present) known as "viewports", each of which is described by a ViewPort structure. This structure contains: - the width and height of the viewport in pixels; - the offset in pixels of the top left corner of the viewport from the top left corner of the view; - a pointer to a "RasInfo" structure (to be discussed below), which contains information about the bit map to be displayed in this viewport; - a pointer to a colour map. The colour map is a structure which contains a palette and related information; - flags indicating the graphics modes to be used in this viewport (as described in Graphics Modes); - pointers to user and system intermediate copper lists (see Copper Graphics); - a pointer to the next viewport in the view, which is zero of this is the last. Note that the viewports must not overlap, and a gap of one to three screen lines, depending on the graphics modes used in each viewport, must be left between them. Each ViewPort points to a RasInfo structure, which contains the following information: - a pointer to a BitMap structure which describes the bit map to be displayed in this viewport; - the offset of the top left corner of the rectangular region displayed in the viewport from the top left corner of the bit map. By changing this offset you can easily display different portions of a bit map too large to fit on the screen, for example to smoothly scroll a game background across the screen. - a pointer to another RasInfo structure, required when the dual playfield graphics mode is used for this viewport. In summary, then, the Kernel data structures needed to create a full display are: - View : describes the screen as a whole, points to first ViewPort; - ViewPort : describes a horizontal slice of the screen; - RasInfo : describes the region to be displayed; - BitMap : describes the bit map containing the region to be displayed. A typical display containing three viewports, the second of which uses dual-playfield mode, would require the following structures to be set up by the programmer: ( -> indicates "points to") View | V ViewPort -> ColorMap | -> RasInfo -> BitMap | V ViewPort -> ColorMap | -> RasInfo -> BitMap | | | V | RasInfo -> BitMap | V ViewPort -> ColorMap -> RasInfo -> BitMap The View and ViewPort structures are initialised by the InitView and InitPort routines respectively. The routines GetColorMap and FreeColorMap may be used to allocate and deallocate a ColorMap structure. Once these structures are correctly initialised and linked to each other, Kernel routines are used to generate a display from them: - MakeVPort generates an intermediate copper list (see Copper Graphics) for a ViewPort from a View and ViewPort structure; this should be performed for each ViewPort which is new or whose parameters have changed in any way; - MrgCop generates hardware copper lists (see Copper Graphics) for a view, using the View structure to collect all the ViewPort intermediate copper lists and merge them together; MrgCop must be called whenever a change occurs in any viewport parameter. - LoadView causes the copper to execute the copper list created by MrgCop, thus displaying the view. LoadView is generally called immediately after MrgCop. As an example of the use of these routines, consider a view containing two viewports. The first uses dual-playfield mode to scroll two bit maps independently, the second switches back and forth slowly between two separate pictures on different bit maps, each with its own BitMap structure: - initialise and link structures; - call MakeVPort for each ViewPort; Loop start: - set offsets in both RasInfo structures for first viewport; - call MakeVPort for first viewport - if it's time to switch pictures in the second viewport, do: - set BitMap pointer in RasInfo to point to desired BitMap structure - call MakeVPort for second viewport; - call MrgCop and LoadView; - go back to loop start. Intuition Graphics. This sections explains the relationship between the Intuition user interface and the Kernel graphics described above, and how to ensure that your programs do not destroy this interface. Intuition is a task which provides an interface between the user and the operating system AmigaDOS, allowing the user to communicate his desires to the programs using the interface via objects intended to appear "concrete" to some extent. Intuition maintains two types of screen memory through which the program and the user interact - the "screen" and the "window". Screens. A screen is a viewport which can be easily controlled by the user. If a screen has a "title bar" along the top (rendered into the screen's bit map by Intuition) the user can pull the screen down with the mouse - i.e. alter the vertical offset of the viewport. If there is a screen "behind" this one (Intuition maintains a depth-arranged list of screens) a viewport is created for it which increases in size as the first screen is pulled lower. On the right-hand side of the title bar are two gadgets with which the user can push the screen to the back and bring it to the front. If the title bar of a screen is not present the user cannot move it. To create a screen, a "NewScreen" structure must be initialised. It contains information about both the viewport (e.g. graphics mode, height and width) and bit map (e.g. height, width, depth) needed for a screen. This structure is then used by the routine OpenScreen to create (open) a screen. OpenScreen returns a pointer to a structure, known as a Screen, which contains a complete ViewPort structure, a pointer to a RastPort, a pointer to the first Window on the Screen (see below) and various information used by Intuition to manage its collection of Screens. The RastPort created by OpenScreen may be used in the manner described in Graphics Rendering, with one important caveat: since Intuition renders the Screen's title bar and menus into the same bit map as that pointed to by the RastPort, you may overwrite them by using the Screen's RastPort. This is quite safe, but disconcerting to the user ! Windows. A window is a "virtual" piece of display memory which uses a rectangular region of a Screen's bit map, but can be made to appear to the programmer and user as though it has its own private screen memory. A window can be set up such that when another window overlaps it, the obscured portion is saved in an off-screen bit map and restored when that region of the window is restored. windows (if permitted by the programmer) may be moved, resized, depth-arranged and closed (removed), and are the most powerful element of Intuition. Each window has associated with it a "Layer" structure, which Intuition uses to ensure that all rendering into a window is clipped at it boundaries, and at edges where overlaying windows cover it. To create a window, a "NewWindow" structure must be initialised to indicate which options, from the large number available, the window is to have. The routine OpenWindow is then called to create the window; it returns the address of a "Window" structure, which contains information about the status of the window and its environment, in particular a pointer to the window's RastPort. It is possible to create a window which covers an entire screen and yet is invisible by specifying the BACKDROP and BORDERLESS flags when creating the window. Although this might appear to have little point, drawing to the RastPort of this window rather than that of the screen has two advantages - any rendering outside the window (and thus the screen) is clipped, which protects you from altering memory you haven't allocated, and the title bar and menus cannot be overwritten. What you can get away with. It is quite safe to use most of the facilities of Kernel and Copper graphics as long as several important points are borne in mind: - Intuition uses a view for its display, which is accessible to the programmer via the IntuitionBase data structure. If you alter the viewport of your screen in such a way that a MakeVPort-MrgCop-LoadView sequence is required to update the display, resist the temptation to do this yourself with Intuition's view. If a screen is being moved by the user while you are calling these routines the system will crash, since Intuition is simultaneously doing the same to update the same view. If you really must do this, call Forbid before and Permit after these calls to lock out Intuition. It is far better to use the Intuition routines MakeScreen, RemakeDisplay and ReThinkDisplay, which perform the same operations in co-operation with Intuition; - Rendering with a screen's RastPort is dangerous, since there is no protection against overwriting the title bar, menus, windows or off-screen memory. Unless there are no menus, windows or title bar on the screen use a backdrop-borderless window instead; - Don't make any assumptions about the position of your screen on the Amiga's display or its visibility. This applies when using copper list instructions, whose positional origin bears to relation to your screen's viewport or even the view. Using the User Copper List mechanism adjusts copper instructions to stay in position relative to your screen. Copper Graphics. What the Copper does. The copper is a simple, fast microprocessor designed to control the video display of the Amiga, although it can manipulate other system hardware. It does this by modifying word-length "hardware registers", which control the Amiga's custom chips, at times when the video beam is at a given position on the display. These hardware registers are mapped into the Amiga's address space, and may thus be read and written directly by the programmer as well. The copper has three instructions, each of which occupies two words. They are: - WAIT puts the copper into an inactive state until the specified beam position is reached, whereupon the next instruction in the copper list (see below) is executed; - MOVE places a data word into a hardware register; - SKIP jumps over the next instruction in the copper list if the video beam is already past the specified position. Note that since the address of the current copper instruction is itself contained in a hardware register, the MOVE instruction can perform a jump by writing to this register. It is important to note that the beam origin point used by the WAIT instruction is not the top left corner of a viewport or view, but the point (probably off the screen) relative to which the view is positioned. This makes it tedious to calculate beam positions, particularly when dealing with Kernel or Intuition displays. Direct Copper Programming. The program executed by the copper is termed a "copper list", and consists of a contiguous series of MOVE, WAIT and SKIP instructions in memory. There may be two copper lists - the "long frame" list which is executed while the beam is visible and an optional "short frame" list which is executed while the beam is in flyback (i.e. returning to the top of the screen). The addresses of theses lists are stored in hardware registers, and are automatically loaded into the copper instruction address register at the appropriate time so that the same lists are executed for each video frame. Using a hardware copper list is very straightforward; you need to: - allocate chip memory for your list; - write your instructions; - set the copper list hardware registers. User Copper Programming. Although the copper can be programmed at the hardware level as described so far, the user must take complete control of the display. To allow copper lists to be integrated with existing Kernel or Intuition displays, the data structures, CopIns, UCopList and CopList are provided. The copper actions specified using these structures are made relative to a specified viewport rather than the whole display, which allows them to be properly co-ordinated with Kernel or Intuition graphics. The CopIns data structure corresponds to a hardware copper instruction, and is termed an "intermediate copper instruction". It contains the following data: - an opcode to mark this CopIns as a MOVE or WAIT instruction or the end of a CopIns block (see below). SKIP instructions are not allowed; - Vertical and Horizontal beam positions (for a WAIT) or a hardware register address and data (for a MOVE). The CopList structure points to a contiguous block of CopIns structures (and specifies how many may be in the block), and to the next CopList if it exists. The UCopList structure, which is pointed to by a ViewPort, likewise points to a CopList and the next UCopList. The structure of a complex user copper list may thus be: ViewPort | V UCopList -> CopList -> CopIns,CopIns,CopIns... (adjacent) | | | V | CopList -> CopIns,CopIns,CopIns... V UCopList -> CopList -> CopIns,CopIns,CopIns... Fortunately for the user, Kernel routines are provided to create and manage these structures: - CMOVE and CWAIT add a MOVE or WAIT instruction to a specified UCopList; - CEND adds a WAIT instruction to a UCopList with a beam position which cannot be attained, thus effectively "stopping" the copper.The user must provide a UCopList structure, but CMOVE, CWAIT and CEND handle the creation and manipulation of the CopList and UCopList structures. When the intermediate copper list is no longer needed, it may be deallocated with a call to the routine FreeVPortCopLists. Once the user copper list is complete, MrgCop is used to created a hardware copper list for the view from the intermediate copper lists of each viewport, which include the user copper lists and those created by MakeVPort. MrgCop creates a cprlist structure which points to a hardware copper list. This list, which is pointed to by the View structure, is used by LoadView to set the appropriate hardware registers. Direct Vs User Copper Programming. The major advantage of direct programming over user programming is of speed and efficiency - the creation and deallocation of a user copper list take time, as does the execution of MrgCop. Thus a game, where speed is the major consideration, might use direct copper programming even though more work is required of the programmer to set up the list. On the other hand, if the copper list is to be executed within a Kernel or Intuition display, the user programming approach must be used. Appendices. Appendix A Basic Concepts. DATA STRUCTURE (or Structure): a group of data items which is known by a collective name. The address of the structure is the same as the address of its first field; FIELD or ITEM: An element of a data structure; POINTER: the address of another object, generally a data structure; "POINTS TO" - if one structure points to another, one of its fields is a pointer to that structure. Appendix B Important Kernel Routines. AllocMem: Allocates a block of memory whose size is specified in bytes, returning the address of the block. The user specifies whether FAST or CHIP memory is desired. AllocRaster: As for AllocMem, except that the size of the bit map (in pixels height and width) is given. The memory is allocated from CHIP ram. CloseScreen: Closes an Intuition screen, deallocating all structures associated with the screen's viewport. Calls RemakeDisplay to rebuild the Intuition display. CloseWindow: Closes an Intuition window. CopperListInit: Initialises a UCopList structure. FreeColorMap: Deallocates a ColorMap structure and its associated color table (viewport palette). FreeCprList: Deallocates a cprlist structure and hardware copper list (normally created by MrgCop). FreeMem: Frees a block of memory whose size in bytes and starting address is given. FreeRaster: As for FreeMem, except that bit map dimensions are given to specify the block size. GetColorMap: Allocates and initialises a ColorMap structure and color table, whose size is specified. InitBitMap: Initialises a BitMap structure. InitRastPort: Initialises a RastPort structure. InitVPort: Initialises a ViewPort structure. InitView: Initialises a View structure. Note that the dxOffset and dyOffset fields are set to the DEFAULT Preferences values, not the current ones. LoadRGB4: Copies an array of colour values into a viewport's color table, and then calls MakeVPort, MrgCop and LoadView to update the colours on the display. LoadView: Sets the hardware registers COP1LCx and COP2LCx (two registers each) to the start of the hardware copper lists created by MrgCop. MakeScreen: Calls MakeVPort for the viewport of an Intuition screen, ensuring that the call does not clash with Intuition's own manipulation of the viewport. MakeVPort: Creates an intermediate copper list from data in the ViewPort structure and the ColorMap and RasInfo structures it points to. The list contains instructions to set the colour, graphics mode and bit plane hardware registers, among others. The address of the CopList structure describing the list is placed in the dspIns pointer of the ViewPort; if dspIns is not zero at the time MakeVPort is called (i.e. there is already a dspIns list for this viewport) the copper list it points to is deallocated. MrgCop: Merges the intermediate copper lists of each viewport in the specified view into one hardware list, adjusting the beam position specified in each WAIT instruction so that it occurs at the correct position relative to the viewport's desired position on the display. MrgCop also creates a cprlist structure which points to the hardware list. The address of the cprlist structure is stored in the LOFCprList field of the View structure (MrgCop does not create a short form copper list). As for MakeVPort, if LOFCprList is non-zero the current cprlist and hardware list will be deallocated before the new list is created. OpenScreen: Creates an Intuition screen. Allocates a Screen structure (which contains a ViewPort structure), a RastPort, and all other structures required by the viewport. Calls RemakeDisplay to rebuild the Intuition Display. OpenWIndow: Creates an Intuition window on the specified screen, allocating a Window structure, a RastPort and other structures required for Intuition's management of the window. RemakeDisplay: Rebuilds the entire Intuition display. Calls MakeScreen for ALL screens, and then calls RethinkDisplay. ReThinkDisplay: Determines the position and size of each Intuition screen's viewport on the display, updating the appropriate fields in the ViewPort structure contained in each Screen structure. Then calls MrgCop and LoadView to update the Intuition display accordingly. ScrollVPort: Moves the position of a bit map within a viewport by altering the offset fields in the RasInfo pointed to by the ViewPort by the x and y values specified, and then calling MakeVPort, MrgCop and LoadView to update the display. SetRGB4: As for LoadRGB4, except that only specified colour is altered. ^^^^^^^^^^^^^^^^^^^^^^^^ END OF GFX_PROG ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^