This is the first in a series of articles focused on programming the Playstation 2. In order to follow along with this tutorial, you will need a PS2 and the PS2 Linux Kit. Information on the Linux kit can be found at www.playstation2-linux.com, and can be purchased at www.playstation.com.
Programming the PS2 is initially very difficult, and at the time that I am writing this there are no comprehensive tutorials geared toward beginners. This article starts with the simplest possible program, and explains in detail what is happening in each step. However, I won't be going into detail on the C programming language or computer graphics, so it's best if you are already familiar with them.
The "Simple" First Program
To begin we will be writing a program that uses the Playstation 2's Graphics Synthesizer (referred to as GS from here on out) to display a single color on the screen.
Next a pointer to a ps2_gs_gparam structure is declared. Things like the resolution, video mode, width and height of drawing area, pixel ration, and other general parameters are stored in the ps2_gs_gparam structure. The structure ps2_gs_dbuff is used for double buffering, and the ps2_gs_finish structure contains the data used to make the GS wait until all commands are completed and all pixels are rasterized. Finally, two functions are declared to acquire and release the GS.
As you hopefully recall, g_gp is a pointer to a structure that holds the parameters to the GS. What we want to do is make that pointer point at the actual parameters in use. That is where the ps2_gs_get_gparam function comes in. It returns a pointer to the parameters in use by the GS. By making our g_gp structure point to the actual parameters, we can later use other functions to alter those parameters. Now lets jump over to the acquire function definition, which is called next.
If ps2_gs_open returns a number less then zero, an error has occurred. Otherwise, we call ps2_gs_reset, which changes the settings in our ps2_gs_gparam *g_gp. The format for the ps2_gs_reset function is as follows.
inter can be PS2_GS_NOINTERLACE or PS2_GS_INTERLACE. It is used to tell the GS whether to display in interlace or non-interlace mode.
omode can be PS2_GS_VESA, for output to a computer monitor; PS2_GS_DTV, for output to Digital TV; PS2_GS_NTSC, for output to a NTSC television (US and Japan); or PS2_GS_PAL, for output to a PAL television (Europe).
ffmode is used if only if you are in interlace mode. It can be PS2_GS_FIELD, which scans every other line; or PS2_GS_FRAME, which scans every line.
resolution can be PS2_GS_640x480, PS2_GS_800x600, PS2_GS_1024x768, or PS2_GS_1280x1024 for VESA mode; PS2_GS_480P, PS2_GS_1080I, or PS2_GS_720P for DTV mode.
refresh_rate can be 0 for default (75Hz), PS2_GS_60Hz, or PS2_GS_75Hz.
For this example, if you want to output to a computer monitor, leave it as it is above. If you want to output to a TV, change PS2_GS_VESA to PS2_GS_NTSC (or PS2_GS_PAL if your in Europe). Feel free to play around with the other settings (or read up on them on some video related websites), but since all that is being displayed is a colored screen you won't notice much of a difference. The other settings will be explained further when we begin drawing other things to the screen and the settings make a difference. Below the ps2_gs_reset function, you can see that acquire then returns success. Now, lets return to main where acquire left off.
psm stands for Pixel Storage Mode. It's basically the same as color depth on a PC. The psm defines how many colors there are, and how they are stored in memory. The list is as follows. PS2_GS_PSMCT32, PS2_GS_PSMCT24, PS2_GS_PSMCT16, PS2_GS_PSM16S.
w, h guess what, these are the width and height of the drawing area. Since the ps2_gs_gparam structure contains these values, you pass g_gp->width and g_gp->height.
ztest is the depth test method. PS2_GS_TEST_ZTST_NEVER doesn't do a depth test, PS2_GS_TEST_ZTST_ALWAYS will always draw a pixel regardless of what it's Z value is, PS2_GS_TEST_ZTST_GEQUAL will draw a pixel if its Z value is greater than or equal to the pixels in the Z buffer, and PS2_GS_TEST_ZTST_GREATER will draw a pixel if its Z value is greater than the pixels in the Z buffer.
zpsm is the storage format for the Z buffer. It defines the size and storage method of the Z buffer. PS2_GS_PSMZ32, PS2_GS_PSMZ24, PS2_GS_PSMZ16, and PS2_GS_PSMZ16S each set the storage format of the z buffer according to their name.
clear can be 0 to not clear, or 1 clear. The zero or one specifies whether or not to clear the frame and z buffer when ps2_gs_swap_dbuff() is called to swap buffers.
Considering that this program only displays a single colored screen, the color and zbuffer formats don't really make a difference. For this program, what we are mainly concerned with is sending the function our ps2_gs_dbuff structure, setting the width and height of the drawing area, and clearing the buffers when the double buffer is swapped. The Z and pixel storage modes, as well as the depth test will become very important once we start drawing primitives.
After resetting all the parameters of the GS with ps2_gs_reset() the display is stopped. A call to the ps2_gs_start_display(0) is made to start it back up again The only two arguments it takes are 1 and 0. 0 starts the display, 1 stops it.
Ps2_gs_sync_v blocks the program until the V-Blank period begins. This is the period between the bottom of one display and the top of the next. During V-Blank any changes to the display are not visible. You want the viewer to see change in the display (otherwise there would be no animation) but you don't want them to see the "act" of changing the display (if you swap buffers while display is visible, it will cause image distortion).
Now that the program is in sync with V-Blank, we can safely swap buffers without any visual defects. The ps2_gs_swap_dbuff function does that for us. It is defined as follows:
id is the ID of the buffer we want to display. Only its Least Significant Bit (LSB) is effective, because of this we can use a counter to swap buffers:
decimal binary frame = 0 0 1 1 2 10 3 11 4 100 As you can see the LSB changes between zero and one as the counter goes up, effectively switching between the first and second buffer.
The simplest action of the program is the most difficult to understand. The clearing color of the two buffers is set with PS2_GS_SETREG_RGBAQ, which takes our 3 colors, and alpha value, and q which is used for texture mapping. Since we aren't doing any blending or texture mapping, these values are unimportant and left at zero. What is with the *(__u64*)& ? See the explanation below.
The SETREGRGBAQ macro simply takes the colors used and the essentially lines up the data so that it can be stored in the rgbaq register correctly (or memory until sent to the GS). Now, SETREGRGBAQ aligns the bits to fit into 64-bit space, which is correct but structures have no type, so you typecast to a 64-bit area of memory. In this way, the structure will be stored just like a 64-bit variable. Since structures cannot be typecast, you take the address of the structure and make it a pointer to an u64. So this means that the address is no longer the address of the structure, but to a 64-bit variable. Now, we don't want to assign the value from SETRGBAQ to the address, so it is dereferenced, and then assigned.
Next up, the ps2_gs_put_drawenv draws the red background from the double buffer up to the screen. It uses a giftag (&g_db.giftag1 : &g_db.giftag0) to draw the screen. A giftag is a structure that contains information about what is being drawn. It is placed right before the actual data that is being drawn, it is then sent to the GS and the data following it is drawn to the screen. Our double buffer structure sets the data in the giftag automatically, we just send it to the ps2_gs_put_drawenv so it draws the red screen. The LSB of the frame variable is checked to be either true or false (1 or 0). If it is true then the red screen of buffer1 is drawn, if it is false the red screen of buffer 0 is drawn.
Finally, the end of the program. Ps2_gs_set_finish is used to tell the GS that we want to use our ps2_gs_finish structure to have it wait until all pixels are drawn. Then ps2_gs_wait_finish actually causes everything to stop until all the pixels are drawn to the screen, once everything is drawn the program continues. This really doesn't make much of a difference for now, considering all that is being drawn is a red background.
Lastly, the release function is called which simply checks if the GS has been opened, and if so closes it. It then assigns -1 to g_fd_gs and returns PS2_GS_VC_REL_SUCCESS. The actual code can be seen below in the entire code posting.
The program in this example was made to be the simplest possible. In order to do so, a few minor enhancements were cut out. These extra procedures are used in the examples included with the kit, as well as in many example programs floating around the internet. In order for you to better understand more advanced programs, I will include these additions in the entire code posting below. The extra features will appear in bold, along with all the code from above. A short explanation of each will be given after the code posting.
Odev receives the opposite of the return value from ps2_gs_sync_v. In interlace mode ps2_gs_sync_v returns 0 for an even field or 1 for an odd field. Then the opposite of that is passed to ps2_gs_set_half_offset. If when it gets to set offset it is a 0, nothing happens; if it is a 1, the image is shifted half a pixel. Doing this makes the resolution look to be doubled. The drawing environments from the double buffer are also alternately passed to ps2_gs_set_half_offset so that the may be offset.
Please note that the above additions are not required in order to program ps2 games. Using half offset will probably be the most important later, but using the virtual console simply isn't important when it comes to just PS2 programming.