Playstation 2 Game Programming Part 2: Drawing Primitives
by Rob Louie


ADVERTISEMENT

Introduction

Part two of the series covers drawing primitives, enabling settings like Gouraud shading, and basic controller access. This tutorial builds upon the source code from Part 1, and while all the source code will be listed, only the new code will be explained.

The Triangle Structure

First we will draw a single triangle to the screen. The first thing we need is a structure to hold it.

#include 

int g_fd_gs;
ps2_gs_gparam *g_gp;
ps2_gs_finish g_finish;

int acquire();
int release();

typedef struct
{
   ps2_giftag giftag;
   __u64 prim;
   char  prim_adrs;
   __u64 rgb0;
   char  rgb0_adrs;
   __u64 v0;
   char  v0_adrs;
   __u64 rgb1;
   char rgb1_adrs;
   __u64 v1;
   char  v1_adrs;
   __u64 rgb2;
   char  rgb2_adrs;
   __u64 v2;
   char  v2_adrs;
} Triangle;

Triangle tri;

The structure Triangle is used to hold all the data for the triangle. The first variable in the structure is a GIFtag. The GIF is the interface between the PS2's main processor (Emotion Engine) and the Graphics Synthesizer (GS). Anytime a primitive is sent to the GS to be displayed at must have a GIFtag before it. The GIFtag specifies the structure, size, and format of the data following it. The GIFtag will be explained in detail when we get to the relevant code.

Next are the variables to hold the primitive structure. Prim is a 64-bit variable used to hold the settings for the primitive being drawn. Settings like primitive type, alpha blending, shading, and fog are stored here. Next is the variable to hold the address of the prim register. The rest of the data is stored in the same way. First a 64-bit variable to hold the data, and then an 8-bit (char) variable to store the register address. The data consists of the vertex color (rgb0 through rgb2), the address of the register used to set the colors (rgb0_adrs through rgb2_adrs), the vertex coordinates (v0 through v2), and the address of the register used to set the vertex coordinates (v0_adrs through v2_adrs).

The format of the Triangle structure follows the PS2 packing format A+D, it is one qword long(128 bits). The following diagram shows the A+D format.

1277164630
Address


Data

First, bits 0 through 63 hold the output data. Then bits 64 through 71 hold the address of the register. Because the format is 128 bits long, it is important to note that it is possible to make both the data and address variables 64 bits long. It is then easier to make large primitives like a triangle strip or fan. Instead of making a new set of data and address variables for each vertex, you can make a 64 bit array.

The Triangle Settings

Next in the source code is the setTriangle function. This function sets up the GIFtag and primitive data.

void setTriangle()
{
   PS2_GIFTAG_CLEAR_TAG(&tri.giftag);
   tri.giftag.NLOOP = sizeof(tri)/16 - 1;
   tri.giftag.EOP = 1;
   tri.giftag.PRE = 0;
   tri.giftag.FLG = 0;
   tri.giftag.NREG = 1;
   tri.giftag.REGS0 = PS2_GIFTAG_REGS_AD;

   tri.prim = PS2_GS_SETREG_PRIM(
      PS2_GS_PRIM_PRIM_TRIANGLE,
      PS2_GS_PRIM_IIP_FLAT,
      PS2_GS_PRIM_TME_OFF,
      PS2_GS_PRIM_FGE_OFF,
      PS2_GS_PRIM_ABE_OFF,
      PS2_GS_PRIM_AA1_OFF,
      PS2_GS_PRIM_FST_STQ,
      PS2_GS_PRIM_CTXT_CONTEXT1,
      PS2_GS_PRIM_FIX_NOFIXDDA);

   tri.prim_adrs = PS2_GS_PRIM;
// vertex 0 settings
   tri.rgb0 = PS2_GS_SETREG_RGBAQ(255, 0, 0, 0, 0);
   tri.rgb0_adrs = PS2_GS_RGBAQ;
   
   tri.v0 = PS2_GS_SETREG_XYZ((g_gp->center_x)<<4, 
                              (g_gp->center_y - 200)<<4, 1);
   tri.v0_adrs = PS2_GS_XYZ2;
   
//vertex 1 settings
   tri.rgb1 = PS2_GS_SETREG_RGBAQ(255, 0, 0, 0, 0);
   tri.rgb1_adrs = PS2_GS_RGBAQ;

   tri.v1 = PS2_GS_SETREG_XYZ((g_gp->center_x + 300)<<4,
                              (g_gp->center_y + 200)<<4, 1);
   tri.v1_adrs = PS2_GS_XYZ2;

//vertex 2 settings
   
   tri.rgb2 = PS2_GS_SETREG_RGBAQ(255, 0, 0, 0, 0);
   tri.rgb2_adrs = PS2_GS_RGBAQ;
   
   tri.v2 = PS2_GS_SETREG_XYZ((g_gp->center_x - 300)<<4,
                              (g_gp->center_y + 200)<<4, 1);
   tri.v2_adrs = PS2_GS_XYZ2;
}

The first function simply clears out the GIFtag so we can make our settings. Next the fields in the GIFtag are set. The GIFtag is one qword(128 bits) long and contains the following seven fields:

NLOOP occupies bits 0 through 14 and holds the data size of the primitive.

EOP occupies bit 15 and specifies whether the following data is the last primitive in a GS packet (End Of Packet). A value of 0 means that more GIFtags with more primitives will be sent after the current primitive. A value of 1 indicates that the following primitive data is the last in the packet.

PRE occupies bit 46 (note the empty space between EOP and PRE) and specifies whether the PRIM field in the GIFtag is enabled. A value of 0 ignores the prim field, while a value of 1 outputs the PRIM field to the PRIM register.

PRIM occupies bits 47 through 57 and contains data to be set in the PRIM register.

FLG occupies bits 58 through 59 and contains the data format for the primitive. 00 is PACKED, 01 is REGLIST, 10 is IMAGE, and 11 is Disabled.

NREG occupies bits 60 through 63 and holds the number of register descriptors in the next field, REGS.

REGS occupies bits 64 through 127 and holds up to 16 Register descriptors.

First the NLOOP field is set to the size of our data structure tri. What we are trying to find is the number of units of data. Because the tri structure follows the A+D format, we know that each unit of data consists of 128 bits (64 for the data, 8 for the address, with the remainder of the 128 bits empty), we need to find the size of the structure and then divide 128 bits. That will give us the number of data units in A+D format in the tri structure. We first use sizeof(tri) to find the size of our structure, which returns the size in bytes. We then must divide by 16 (16 bytes = 128 bits). PRIM register settings are not included in with the size of the data so 1 is subtracted from the final answer (this removes one qword from the size, eliminating prim and prim_adrs.

Since there is no primitive data or GIFtags following our triangle, EOP is set to zero to signal that the following data is the end of the GS packet.

Since all of our primitive data is stored inside the Triangle structure, the PRIM field of the GIFtag will not be used. Therefore, PRE is set to 0;

The data format mode PACKED uses the register descriptors in the REGS field to show the format of the data in every following qword. Since the data in each qword of our triangle is in A+D format, we must use PACKED mode so that we can set the A+D format in the REGS field. Packed mode is set by making FLG equal zero.

Only one register descriptor is in the REGS field so NREG is set to 0. The REGS0 field is set to PS2_GIFTAG_REGS_AD. This tells the GS that the data in each following qword is in A+D format. REGS0 is the first of 16 registers that may be set in the REGS field.

Primitive settings are initialized using the PS2_GS_SETREG_PRIM function. First the type of primitive is set followed by its drawing options.

The first field can be PS2_GS_PRIM_PRIM_POINT, PS2_GS_PRIM_PRIM_LINE, PS2_GS_PRIM_PRIM_LINESTRIP, PS2_GS_PRIM_PRIM_SPRITE, PS2_GS_PRIM_PRIM_TRIANGLE, PS2_GS_PRIM_PRIM_TRISTRIP, PS2_GS_PRIM_PRIM_TRIFAN. These should be self explanatory, with the possible exception of the sprite primitive. The sprite primitive takes only 2 vertices and draws a rectangle with a corner at each vertex. With the sprite and point primitives shading can be flat only and antialiasing is always off.

The next mode is for shading and can be PS2_GS_PRIM_IIP_FLAT or PS2_GS_PRIM_IIP_GOURAUD.

The following four settings are only set to on or off. PS2_GS_PRIM_TME_ON/OFF is for enabling texture mapping, PS2_GS_PRIM_FGE_ON/OFF is for enabling fog, PS2_GS_PRIM_ABE_ON/OFF is for enabling alpha blending, and PS2_GS_PRIM_AA1_ON/OFF is for enabling antialiasing.

The next field specifies what type of texture coordinates will be used and can be PS2_GS_PRIM_FST_STQ or PS2_GS_PRIM_FST_UV. At this point this field is irrelevant as we aren't doing any texture mapping.

Each register in the GS has to contexts. At this point we are only worried about setting the registers and we are using context 1 with PS2_GS_PRIM_CTXT_CONTEXT1.

Now that the primitive data has been set in the prim variable, we must follow the A+D format and set prim_adrs to the correct register address. The data in the prim variable is going to the PRIM register so prim_adrs is set equal to PS2_GS_PRIM.

We now get to setup the vertices of the triangle using the A+D format. First rgb0 is set using the PS2_GS_SETREG_RGBAQ function. This function takes three values for the vertex color, one for alpha blending, and one for normalized texture coordinates. The vertex is set to a solid red by making the first field in the function 255, and the other two zero. The last two fields are left at zero since no alpha blending or texture mapping is used. The register address is then set to PS2_GS_RGBAQ.

With the color set up it is time to set the vertex coordinates. This is done using the PS2_GS_SETREG_XYZ function. To find the center of the screen g_gp->center_x and g_gp->center_y are used. This structure contains the parameters of the GS, including the center of the screen. The triangle is then built around these values by adding and subtracting from them. After finding each XY coordinate for the triangle, the answer must then be shifted four bits to the left. The XYZF2 register stores coordinate data as 12 bits for integer values and 4 bits for decimal values. Since our triangle uses integer values only, the final value for each XY coordinate must be shifted out of the decimal place and into the integer place.

Finally the address for the vertex coordinates is set using PS2_GS_SETREG_XYZ2. The XYZ2 register holds each vertex value of a primitive. Once all the primitives coordinates are specified, the primitive is drawn to the screen. The number of coordinates that must be specified varies depending on what type of primitive is being drawn (a triangle requires 3, a sprite requires 2). The other vertex coordinate register, XYZ3, won't draw a primitive even after all of its coordinates are specified. Since we are only drawing one shape and we want it to be drawn right away, XYZ2 is used.

Since all three coordinates are all set as described, we will now move onto the final function of the program.

Drawing the Scene

The main function is used to draw the triangle to the screen. All of the new code (as opposed to the old code from tutorial 1) used to draw the triangle will appear in bold.

int main()
{
   int frame;
   int odev;
   int r = 0, g = 0, b = 255;

   g_gp = ps2_gs_get_gparam();
   acquire();
   ps2_gs_vc_graphicsmode();

   ps2_gs_set_dbuff(&g_db, PS2_GS_PSMCT32, g_gp->width, 
        g_gp->height, PS2_GS_ZGREATER, PS2_GS_PSMZ24, 1);

   *(__u64*)&g_db.clear0.rgbaq = PS2_GS_SETREG_RGBAQ(r,g,b,0,0);
   *(__u64*)&g_db.clear1.rgbaq = PS2_GS_SETREG_RGBAQ(r,g,b,0,0);

   setTriangle();

   ps2_gs_start_display(0);

   ps2_gs_vc_enablevcswitch(acquire, release);

   for(frame = 0; ;frame++)
   {
      ps2_gs_vc_lock();
      
      odev = !ps2_gs_sync_v(0);
      
      ps2_gs_set_half_offset((frame & 1) ? &g_db.draw1 :
                                           &g_db.draw0, odev);

      ps2_gs_swap_dbuff(&g_db, frame);

      ps2_gs_put_drawenv((frame & 1) ? &g_db.giftag1 : 
                                       &g_db.giftag0);

      ps2_gs_put_drawenv(&tri.giftag);

      ps2_gs_set_finish(&g_finish);
      ps2_gs_wait_finish(&g_finish);

      ps2_gs_vc_unlock();
   }
   release();
   return 0;
}

Only three changes have been made to the main function and one of them is old code moved to a new place. First *(__u64*)&g_db.clear0.rgbaq = PS2_GS_SETREG_RGBAQ(r,g,b,0,0) and the identical setting for buffer two have been moved out of the main loop and up into the initializing. This is done since there is no reason to set the clear color every frame.

Next up the setTriangle function is called to set up the triangle, and then in the main loop we send the triangle's GIFtag to the GS to be displayed on the screen. Don't forget to include the acquire and release functions, they were not included in this tutorial because they remain the same as in tutorial 1.

To add more color to the finished project we will add more colors and blend them throughout the triangle using gouraud shading. First, in setTriangle() change PS2_GS_PRIM_IIP_FLAT to PS2_GS_PRIM_IIP_GOURAUD to enable gouraud shading. Then make each vertex of the triangle a different color. To get the color triangle, leave vertex 0 full red (255,0,0,0,0), make vertex 1 full green(0,255,0,0,0), and vertex 2 full blue (0,0,255,0,0). To set the triangle off from the background, set the r,b, and b variables in main to zero. You should now have a perfectly blended triangle on a black background.

Discuss this article in the forums


Date this article was posted to GameDev.net: 12/18/2003
(Note that this date does not necessarily correspond to the date the article was written)

See Also:
Featured Articles
Playstation 2

© 1999-2011 Gamedev.net. All rights reserved. Terms of Use Privacy Policy
Comments? Questions? Feedback? Click here!