PS2 Linux Programming
Drawing 2D Primitive with a Dynamic Packet 1
Introduction
This tutorial will use the development framework described in the previous tutorial to draw a single static Gouraud shaded triangle, which is one of the most basic primitives that can be drawn. The primitive data will be built in in the Dynamic Packet area. Some prerequisite knowledge fundamental to the understanding of the program will also be provided.
Figure 1 shows the main internal data paths that exist within the PS2. It can be seen that the transfer of information to the Graphics Synthesiser (GS) is via the Graphics Synthesiser Interface (GIF). There are three ways to transfer information to the GS via the GIF: the only way that concerns us here is the transfer path which goes through VIF1 to the GIF, which is called Path 2. Paths 1 and 3 will be discussed in later tutorials.
In essence, the GIF is an interface between the EE core and the GS. The GIF functions as a pre-processor for the GS. The GIF performs a number of functions such as unpacking packed vertex data based on specifications that it has been sent through a GIFTag, and transfers the data directly into the registers of the GS. This process improves the throughput of the system.
Data that is sent to the GS is called a GS Packet. A GS Packet can be made up of a number of segments called GS Primitives. A GS Primitive is a GIFTag and the following primitive data. The overall layout of a GS Packet is illustrated in Table 1 which shows a GS packet consisting of three GS primitives.
GIF Tag 1 |
Primitive data |
GIF Tag 2 |
Primitive data |
GIF Tag 3 |
Primitive data |
Table 1
Note that in the example code a GS Packet consisting of only one GS Primitive is being sent to the GS per frame.
A GIFTag is a single quad word (128 bit) piece of data with a number of fields that tell the GS what type of primitive is to be drawn. The required primitive data, such as the position and colour of the vertices, immediately follows the GIFTag.
There are 7 different primitive types that can be drawn by the GS: point, line, line strip, triangle, triangle strip, triangle fan, and sprite. See pages 38 and 39 of the GS Users Manual for diagrams of how each primitive is built from the vertex data that is specified. Note that a triangle primitive is being drawn in the example code
The primitive data that follows the GIFTag can be stored in 3 different formats, PACKED mode, REGLIST mode, and IMAGE mode. PACKED mode is used in the example code and only this mode will be described at this time.
The GIFTag is 128 bits in length and has the following layout:
The parameters of the GIFTag will be describe below starting with the REGS field, but in order to gain an understanding of these fields, further information on how data is stored in a GS Packet in presented first.
Primitive data is stored in user defined “batches”. An example of a batch might be the colour and position of the vertex of a triangle. The REGS field is 64 bits long, which can be thought of as an array of 16 4-bit elements, with each element represents a different register. Page 152 of the EE Users Manual details all the registers that can be used in the REGS field. Note that in the example code only the XYZ2 and RGBAQ registers are used.
One thing to note when setting up a batch is that writing to XYZ2 performs a “Vertex Kick” and can also perform a “Drawing Kick”. A Vertex Kick causes the current contents of the GS registers set up so far to be placed in the vertex queue, which is the queue of vertex data waiting to be processed by the GS. A Drawing Kick is performed when the necessary vertex information is arranged in the vertex queue in such a way that drawing of a primitive can begin. In the example code, a Drawing Kick will be performed when the three pieces of vertex information necessary to draw the triangle are available in the vertex queue. The order that vertex information is given to the GIF is therefore very important. If a vertex is to be a specific colour, the colour register must be set before the XYZ2 register so that when the Vertex Kick is performed the correct vertex colour is in the RGBAQ register. If the register order sent to the GIF is reversed, the drawing kick will be performed before the RGBAQ register is set and the vertex will take the colour of whatever value was already in the RGBAQ register. See page 40 of the GS Users manual for more information on Vertex and Drawing Kicks.
In the example code a Gouraud shaded triangle is being drawn which has three vertices each with a position and colour. Thus the registers are specified in the REGS field in the following order: RGBAQ first, then XYZ2.
The following macro (which is defined in PS2Defines.h) is used to pack the register information into the REGS field:
GS_BATCH_2(GIF_REG_RGBAQ, GIF_REG_XYZ2)
RGBAQ is packed first, followed by XYZ2. GIF_REG_RGBAQ is the register address of RGBAQ, which is defined as 0x01, and GIF_REG_XYZ2 is the register address of XYZ2, which is 0x05. The GS_BATCH_2 macro automatically sets the NREG field of the GIFTag structure to 2 which in this case is the number of registers being used.
The FLG field specifies the data format that is being used to specify the primitive data that follows the GIFTag. Packed mode is being used so FLG is set to GIF_FLG_PACKED, which is defined as 0x00.
The PRIM register of the GS is a packed bit-field specifying the kind of primitive to be drawn. There are several fields in this register, which will not all be explained here. For the purposes of this tutorial only the first 3 bits (0-2) that specify the primitive type (PRIM) to be drawn, and bit 3 that specifies the primitive shading (IIP) will be used. See page 116 of GS Users manual for information on all of the fields of the PRIM register. In the case of the example code, a triangle primitive is to be drawn (PRIM = 011) and Gouraud shading is to be applied (IIP = 1) so the complete PRIM register is set to 1011 binary, 0x0B hexadecimal or 11 decimal.
The EOP field simply indicates if this is the last GIF Tag in the GS Packet. If EOP=1 the GIF will not expect any more GIFTags and primitive data in the graphics packet, effectively meaning that the current graphics packet has ended transmission. In the case of the example code EOP is set to 1 to indicate that this is the last (and only) packet that the GIF should expect.
The PRE field indicates whether or not to transfer the PRIM field of the GIFTag to the PRIM register of the GS. There are other methods available to set the PRIM register of the GS but for now PRE is set to 1 indicating that the information in the PRIM field of the GIFTag should be transferred to the PRIM register of the GS.
Finally NLOOP tells the GIF how many times to repeat reading the register data specified in the REGS field. In the example code there are two registers being used to specify each vertex and there are three vertices. NLOOP is set to 3 indicating that the register data should be read three times, once for each vertex. Note that this means that the GIF will be expecting a total of NLOOP x NREG (3 x 2 = 6) register values in the primitive data section after the GIFTag. It is important to get these calculations correct otherwise things will not work as expected.
The primitive data comes immediately after the GIFTag and in the case of the example code is specified in Packed mode. Packed mode uses a whole 128 bit quad word to store data for one 64 bit register within the GS. One advantage of this is that it is quick and easy to set the primitive data. The disadvantage of using packed mode is that twice as much data (as is necessary) is needed to specify each GS register.
The format used for packing each register is specified in section 7.3 of the EE users Manual. Macros are provided in PS2Defines.h to pack the register data used in this example:
PACKED_XYZ2(X, Y, Z, ADC)
PACKED_RGBA(R, G, B, A)
The fields of PACKED_RGBA are R, G, B and A, these being red, green, blue and alpha values in the range 0-255. The fields of XYZ2 are X, Y, Z, and ADC. if ADC is set to 1, the drawing kick is not performed for the corresponding vertex and this can be a used to prevent the primitive from being drawn. It is important in the case of the example code however that ADC is cleared to zero to allow drawing of the primitive. The PS2 uses a Z-Buffer to automatically sort out which pixels are closest to the viewer and should or should not be drawn. The Z value of a vertex is a 32 bit unsigned integer. The higher the Z value the closer the primitive is to the screen. If two pixels are to occupy the same position on screen, the pixel with the higher Z value will be drawn and the pixel with the lower Z value will not be drawn. This feature is used to sort out which objects (or parts of objects) are closest to the viewer in a 3 dimensional scene. Note that the Z value cannot be 0 or the primitive will not be drawn. The alpha value is set to a default value of 128.
The X and Y values are the horizontal and vertical coordinates of the vertex. The centre of the PS2 screen is at the coordinate (2048, 2048). The positive X axis goes from left to right across the screen, the positive Y axis goes from top to bottom down the screen. Therefore for a vertex 150 pixels to the left, and 150 pixels up from the centre of the screen the coordinates (2048 – 150, 2048 – 150) would be used. Finally, the X and Y coordinates are in 12:4 fixed point format which means that the coordinate data must be left shifted by 4.
The section of example code that packs the primitive data is repeated here for clarity:
for (int i=0;i<3;i++)
{
PACKED_RGBA( VertexColors[i][0], VertexColors[i][1], VertexColors[i][2], 0x80));
PACKED_XYZ2( (2048 + VertexData[i][0])<< 4, (2048 + VertexData[i][1])<< 4, 128, 0);
}
Note that the Z value is set to a nominal value of 128 for each vertex, which is fine for this example since only one triangle is being drawn. The packing process is repeated three times in a loop, once for each vertex, providing primitive data that consists of 6 packed registers.
The structure of the render loop is as follows:
while(g_bLoop)
{
// Fire off the previous frame's buffer (or init data if it's the first time)
VIFDynamicDMA.Fire();
// Update the pad structure
pad_update(PAD_0);
// Clear the screen
SPS2Manager.BeginScene();
// Start DIRECT mode transfer
VIFDynamicDMA.StartDirect();
// Add the GIFTag
VIFDynamicDMA.Add128(GS_GIFTAG_BATCH( 3, 1, 1, 11, GIF_FLG_PACKED,
GS_BATCH_2(GIF_REG_RGBAQ, GIF_REG_XYZ2)));
// Loop through all vertices
for (int i=0;i<3;i++)
{
// Add the colour
VIFDynamicDMA.Add128(PACKED_RGBA( VertexColors[i][0],
VertexColors[i][1],
VertexColors[i][2],
0x80));
// Add the position
VIFDynamicDMA.Add128(PACKED_XYZ2( (2048 + VertexData[i][0])<< 4,
(2048 + VertexData[i][1])<< 4,
128,
0));
}
// End the direct mode
VIFDynamicDMA.EndDirect();
// Swap the buffers
SPS2Manager.EndScene();
// Check for exit condition
if((pad[0].buttons & PAD_START)&&(pad[0].buttons & PAD_SELECT)) g_bLoop = false;
}
VIFDynamicDMA.StartDirect() starts the direct mode transfer by putting an appropriate VIFCode into memory. The three calls to VIFDynamicDMA.Add128() add the GIFTag and the vertex data into memory, and finally the call to VIFDynamicDMA.EndDirect() ends the direct mode transfer. This data is compiled by the CPU every frame and sent to VIF1 with the call to VIFDynamicDMA.Fire().
In order to enhance understanding of the techniques introduced in this tutorial, it is recommended that the reader alter the supplied program to, for example: draw the triangle in a different place; change the colours of the vertices; draw more than one triangle; draw a different kind of primitive; etc; etc. Experimentation leads to understanding.
Dr Henry S Fortuna
University of Abertay Dundee