To use the blitter directly, you must first be familiar with how its
registers control its operation. This topic is covered thoroughly in the
Amiga Hardware Reference Manual and is not repeated here. There are two
basic approaches you can take to perform direct programming of the
blitter: synchronous and asynchronous.
* Synchronous programming of the blitter is used when you want to do a
job with the blitter right away. For synchronous programming, you
first get exclusive access to the blitter with OwnBlitter(). Next
call WaitBlit() to ensure that any previous blitter operation that
might have been in progress is completed. Then set up your blitter
operation by programming the blitter registers. Finally, start the
blit and call DisownBlitter().
* Asynchronous programming of the blitter is used when the blitter
operation you want to perform does not have to happen immediately.
In that case, you can use the QBlit() and QBSBlit() functions in
order to queue up requests for the use of the blitter on a
non-exclusive basis. You share the blitter with system tasks.
Whichever approach you take, there is one rule you should generally keep
in mind about using the blitter directly:
Don't Tie Up The Blitter.
-------------------------
The system uses the blitter extensively for disk and display
operation. While your task is using the blitter, many other system
processes will be locked out. Therefore, use it only for brief
periods and relinquish it as quickly as possible.
To use QBlit() and QBSBlit(), you must create a data structure called a
bltnode (blitter node) that contains a pointer to the blitter code you
want to execute. The system uses this structure to link blitter usage
requests into a first-in, first-out (FIFO) queue. When your turn comes,
your own blitter routine can be repeatedly called until your routine says
it is finished using the blitter.
Two separate blitter queues are maintained. One queue is for the QBlit()
routine. You use QBlit() when you simply want something done and you do
not necessarily care when it happens. This may be the case when you are
moving data in a memory area that is not currently being displayed.
The second queue is maintained for QBSBlit(). QBS stands for
"queue-beam-synchronized". QBSBlit() requests form a beam-synchronized
FIFO queue. When the video beam gets to a predetermined position, your
blitter routine is called. Beam synchronization takes precedence over the
simple FIFO. This means that if the beam sync matches, the
beam-synchronous blit will be done before the non-synchronous blit in the
first position in the queue. You might use QBSBlit() to draw into an area
of memory that is currently being displayed to modify memory that has
already been "passed-over" by the video beam. This avoids display flicker
as an area is being updated.
The sole input to both QBlit() and QBSBlit() is a pointer to a bltnode
data structure, defined in the include file <hardware/blit.h>. Here is a
copy of the structure, followed by details about the items you must
initialize:
struct bltnode
{
struct bltnode *n;
int (*function)();
char stat;
short blitsize;
short beamsync;
int (*cleanup)();
};
struct bltnode *n;
This is a pointer to the next bltnode, which, for most applications
will be zero. You should not link bltnodes together. This is to be
performed by the system in a separate call to QBlit() or QBSBlit().
int (*function)( );
This is the address of your blitter function that the blitter queuer
will call when your turn comes up. Your function must be formed as a
subroutine, with an RTS instruction at the end. Follow Amiga
programming conventions by placing the return value in D0 (or in C,
use return(value)).
If you return a nonzero value, the system will call your routine
again next time the blitter is idle until you finally return 0. This
is done so that you can maintain control over the blitter; for
example, it allows you to handle all five bitplanes if you are
blitting an object with 32 colors. For display purposes, if you are
blitting multiple objects and then saving and restoring the
background, you must be sure that all planes of the object are
positioned before another object is overlaid. This is the reason for
the lockup in the blitter queue; it allows all work per object to be
completed before going on to the next one.
Note:
-----
Not all C compilers can handle *function() properly! The system
actually tests the processor status codes for a condition of
equal-to-zero (Z flag set) or not-equal-to-zero (Z flag clear) when
your blitter routine returns. Some C compilers do not set the
processor status code properly (i.e., according to the value
returned), thus it is not possible to use such compilers to write the
(*function)()) routine. In that case assembly language should be
used. Blitter functions are normally written in assembly language
anyway so they can take advantage of the ability of QBlit() and
QBSBlit() to pass them parameters in processor registers.
The register passing conventions for these routines are as follows.
Register A0 receives a pointer to the system hardware registers so
that all hardware registers can be referenced as an offset from that
address. Register A1 contains a pointer to the current bltnode. You
may have queued up multiple blits, each of which perhaps uses the
same blitter routine. You can access the data for this particular
operation as an offset from the value in A1. For instance, a typical
user of these routines can precalculate the blitter register values
to be placed in the blitter registers and, when the routine is
called, simply copy them in. For example, you can create a new
structure such as the following:
INCLUDE "exec/types.i"
INCLUDE "hardware/blit.i"
STRUCTURE mybltnode,0
; Make this new structure compatible with a
; bltnode by making the first element a bltnode
; structure.
STRUCT bltnode,bn_SIZEOF
UWORD bltcon1 ; Blitter control register 1.
UWORD fwmask ; First and last word masks.
UWORD lwmask
UWORD bltmda ; Modulos for sources a, b,and c.
UWORD bltmdb
UWORD bltmdc
UWORD any_more_data ; add anything else you want
LABEL mbn_SIZEOF
Other forms of data structures are certainly possible, but this
should give you the general idea.
char stat;
Tells the system whether or not to execute the clean-up routine at
the end. This byte should be set to CLEANUP (0x40) if cleanup is to
be performed. If not, then the bltnode cleanup variable can be zero.
short beamsync;
The value that should be in the VBEAM counter for use during a
beam-synchronous blit before the function() is called. The system
cooperates with you in planning when to start a blit in the routine
QBSBlit() by not calling your routine until, for example, the video
beam has already passed by the area on the screen into which you are
writing. This is especially useful during single buffering of your
displays. There may be time enough to write the object between scans
of the video display. You will not be visibly writing while the beam
is trying to scan the object. This avoids flicker (part of an old
view of an object along with part of a new view of the object).
int (*cleanup)();
The address of a routine that is to be called after your last return
from the QBlit() routine. When you finally return a zero, the queuer
will call this subroutine (ends in RTS or return()) as the clean-up.
Your first entry to the function may have dynamically allocated some
memory or may have done something that must be undone to make for a
clean exit. This routine must be specified.