UNDERSTANDING DRAWING PRIMITIVES

So you're ready to draw something to screen. Even if you're planning to
use mainly sprites for your game, at the end of the day, the drawing
primitives prove to be highly efficient and there's no getting away from
using them. (Well, I did write my Tetris using nothing BUT these basic
primitives.)
In this document, my aim is to show how to draw lines (GsLINE),
gradating lines (GsGLINE), and filled boxes (GsBOXF).

Any suggestions or criticisms on this document welcome.

James Chow aka jc             6 June 1998
james@chowfam.demon.co.uk
---------------------------------------------------------------


Overview
--------
Recall that to draw to the screen, we hold an ordering table.
The way I like to think of it, is that the ordering table holds a
list of drawing commands. We insert our drawing commands to the
ordering table for each frame. And then when the display buffers
are switched, the drawing commands are executed and drawn to the
off screen buffer - ready to be displayed for the next switch.

GsSortLine, GsSortGLine, GsSortBoxFill
--------------------------------------
Actual creation and insertion into the ordering tables is really
quite simple.
Firstly we create the a structure of the 'thing' we want to draw.
And then set the fields to the desired values.
So for a blue diagonal line, we do,
    GsLINE aline;
    aline.attribute = 0;
    aline.x0 = 50; aline.x1 = 50;
    aline.y0 = 100; aline.y1 = 100;
    aline.r = aline.g = 0; aline.b = 255;

The functions to insert a drawing primitive into the ordering all
begin with GsSort----. Also, remember that our ordering tables
contain a number of planes (or depths) depending on the ordering
table length. Plane 0 is closest to the screen, going up to
Plane 2^OT_LENGTH-1 furthest away from the screen.
To insert into plane 0, we do,
    GsSortLine(&aline,&ordering_table,0);

And that's it! When the GsDrawOt command is called, the line will
magically appear on the screen. (Read "off-screen display buffer"
if you're using double buffering. That's probably most of us.)

Um, what's the attribute field for? I hear you cry. Well,
it determines a few properties of the primitive.
1. Whether you want it semi-transparent.
2. The type of semi-transparency, if any.
3. Whether you want the primtive to be displayed or not.
Check out the Library Reference manual for the exact bit
numbers.

WorkBase
--------
In order for the drawing commands to "execute", they need
some space in memory to work in. This is quite commonly
refered to in programs as the "GpuPacketArea", (or something
like that depending on how the programmer declares the
variables).
Now, let me guess how that was derived from.
GPU - Graphics Processing Unit. The place where nasty things
      happen to get nice things on screen.
Packet - is the "unit" of our drawing commands. The maximum
         a drawing command can have is 24 units. (The amount
         of units a drawing command takes is simply the
         sizeof() the struct that is being inserted).
Area - area, I suppose.

So before we insert any drawing commands into our ordering
tables we need to inform the GPU where the work space is.
We do this by,
    GsSetWorkBase((PACKET*)GpuPacketArea[buf]);
We must have one for each buffer, since we don't want them
to conflict.
How big should we make our GpuPacketArea then?
Sometimes you might see
  PACKET   gpuPktArea[2][8192];
 or
  PACKET   gpuPktArea[2][MAXOBJ * 24];

Of course, it depends on how many primitives you are using.
The exact amount is the number of primitives in that frame
you are inserting into the ordering table * the number of
packets that each require.
Most people, even myself, just declare more space than is
necessary, and that will be safe.
If you are declaring this statically, then make sure that
the space allocated will match (or be more than) the frame
requiring the most packets.

Problems
--------
You might find that lines, gradating lines, and filled boxes
does not satisfy your requirments. Circles, triangles, etc,
may be considered as basic shapes, but unfortunately,
they require more processing by the CPU and therefore are
supported as is. To overcome this, I think the most
efficient (in terms of time) method is to use sprites.
Of course, this requires more memory, but those routines,
will have been optimised for performance.

In setting the size of the GpuPacketArea it may be possible
to freeze your program. This is done by using a size that
will result in a misalignment of the memory in the working
area. Nope, I don't know exactly why, but just avoid
it.
Try something like  PACKET gpuPktArea[2][1023];
and draw anything and it will lock up.

Demonstration
-------------
To save bloating this file up, I'm reusing pad.c and pad.h
files from the "Reading the Controller" document.
To exit, press select and start.

/**********/
/* main.c */
/**********/

#include 
#include "pad.h"

#define OTLEN   (1)
#define SCNW    (320)
#define SCNH    (240)

GsLINE line;
GsGLINE gline;
GsBOXF box1;
GsBOXF box2;

void initGame(void) {
  FntLoad(960,256);
  FntOpen(0,0,SCNW,SCNH,0,512);
  
  line.attribute = 0;
  line.x0 = 50; line.y0 = 50;
  line.x1 = 100; line.y1 = 50;
  line.r = line.b = 0; line.g = 255;
  
  gline.attribute = 0;
  gline.x0 = 50; gline.y0 = 55;
  gline.x1 = 100; gline.y1 = 55;
  gline.r0 = 255; gline.g0 = gline.b0 = 0;
  gline.r1 = gline.g1 = 0; gline.b1 = 255;
  
  box1.attribute = 0;
  box1.x = 100; box1.y = 100;
  box1.w = 50; box1.h = 50;
  box1.r = 255; box1.g = box1.b = 0;
  
  box2.attribute = 0;
  box2.x = 125; box2.y = 125;
  box2.w = 50; box2.h = 50;
  box2.r = box2.g = 0; box2.b = 255;
}

int updateGame(void) {
  static int padstate;
  ulong attribute[5] = {0x00000000,0x40000000,0x50000000,
                        0x60000000,0x70000000};
  static int transtype = 0;
  if (padConnected(0) == PADin)
    if ((padType(0) == PADstandard)||
        (padType(0) == PADanalogue)||
        (padType(0) == PADdualshck))
      if ((padState(0) & PADstart)&&
          (padState(0) & PADselect))
        return 1;
  if ((padState(0) & PADselect)&&(!(padstate & PADselect))) {
    transtype = (transtype+1)%5;
    box1.attribute = attribute[transtype];
  }
  padstate = padState(0);
  return 0;
}

void drawGame(GsOT *ot) {
  FntPrint("PRESS SELECT FOR RED BOX TO CYCLE\n");
  FntPrint("THROUGH DIFFERENT SEMI-TRANSPARENCIES");
  FntFlush(-1);
  GsSortLine(&line,ot,0);
  GsSortGLine(&gline,ot,0);
  GsSortBoxFill(&box1,ot,0);
  GsSortBoxFill(&box2,ot,0);
}

void main(void) {
  GsOT     ot[2];
  GsOT_TAG ottags[2][1<