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<