PS2 Linux Programming

 

2D Texture Mapping

 

 

Introduction

 

This tutorial will provide guidance on the use of 2D texture mapping. A 24-bit bitmap files of size 256x256 pixels will be load into the main memory of the PS2 then transferred to GS memory. The use of transparency and alpha blending will also be investigated.

 

 

Background

 

The PS2 comes with 4MB (4096K) of video memory, often called VRAM, GSMem, or VMEM. This is a relatively small amount of memory and using it requires careful thought and design. Within the architecture of the PS2, however, it is possible to transfer textures from main memory to VRAM at high speed (maximum rate of 2.4Gb/sec) and this transfer can be maintained successfully within the game loop. As will become evident, the architectural structure of the PS2 has implications on how graphics/games application are designed - considerable differences exit for example, between applications written for the PS2 and applications written for a PC.

 

Under SPS2 in VESA graphics mode, the front and back frame buffers are 640*480*4 = 1200K making a total of 2400K of video memory being used for the frame buffers. The Z Buffer is also 640*480*4 = 1200K, so out of the 4096K of video memory 3600K is already used up, leaving just 496K for textures. The scenario is even worse if the PAL video mode is used. PAL buffers are 640 *512*4 = 1280K. This means that 3840K of video memory is used for the frame and z buffers leaving only 256K for textures. 256k just so happens to be the size of a single 256 by 256, 32-bit colour image. As mentioned above, the big advantage of the PS2 is its memory bandwidth, so constantly moving textures from RAM into VRAM is not too much of a problem.

 

In the sample code provided with this tutorial, routines are provided for loading 256*256 24-bit windows .bmp files. Normally, it is not necessary to use such a high-resolution texture (a 24 bit colour depth is wasted on a TV) but since a monitor is possibly being used, and since rendering is in 2D, the method is acceptable. Usually several palletised 8-bit or even 4-bit images can be loaded into VRAM at one time, but the use of a palette is beyond the scope of this tutorial and will be discussed in a later tutorial. For now, the use of 24-bit images is a simple way of getting textures loaded and on to the screen.

 

 

Texture Loading Code

 

The texture loading code is contained in the files texture.cpp and texture.h. The loading code is in two parts: the first function LoadBitmap() loads a bitmap file from disk into SPS2 allocated memory; the second function UpLoad() uploads or transfers the image data from main memory into graphics memory so that it can be used by the graphics synthesiser.

 

Looking at main.cpp, it can be seen that at the start of this file some #defines are used to help partition the SPS2 memory that will be allocated. PRIMITIVEMEM_4K points to the start of a 4k block that will be used to store the graphics primitive packets; TEXTUREMEM_256K point to the start of a 256k area that will be used to store the texture image; TEXTUREDMAMEM_4K points to the start of a 4k block that will be used to store the DMA tags necessary to control the transfer of the image into VRAM. TOTALMEMNEEDED is the total memory that needs to be allocated, which in this case is 4+256+4 = 264K. The memory layout is illustrated in Table 1.

 

 

4k Primative memory

256k Texture memory

4k Texture DMAC tag memory

 

Table 1 SPS2 Memory Layout

 

 

Still within main.cpp, the function sps2Allocate() is used to allocate 264k of SPS2 memory. The LoadBitmap("test.bmp", &pcMemory[TEXTUREMEM_256K], false) function takes three parameters; the first is the bitmap file to be loaded, the second is a pointer to the area of SPS2 memory that the file is to be loaded into and the final parameter indicates whether or not RGB of (r=0, g=0, b=0) should be made transparent or not. Note that it is possible to make colours other than black act as transparent if required.

 

 

The LoadBitmap() Function

 

Look at texture.h it can be seen that the structure BitmapFile contains two headers that are laid out in exactly the same way as in an actual bitmap file, so it is possible to read them directly from the disk file into this structure. The compiler directive __attribute__((packed)) tells G++ not to insert any padding into the structure; otherwise the struct would not be laid out in exactly the same format as in the file. The reason G++ inserts padding by default is because aligned data is faster on the PS2, but since loading an image will only be done at load time performance is not too important here.

 

LoadBitmap() opens the bitmap file and reads in the bitmap file header and the bitmap info header. Details on the meaning of the parameters in the file and info headers are contained in texture.h. Checks are made to verify that the file being read is a bitmap file and that the bitmap is the correct dimensions and colour depth. The pixel data from the file is then read in from the file and stored in SPS2 memory. Bitmaps are stored upside-down and in BGR format. This means that the R and B components must be swapped to get the correct RGB format. Also a bitmap file stores the image upside down, so the image data is read in and written into SPS2 memory in reverse order or the correct way up. Note also that an 8-bit alpha channel is added to each pixel and if transparent black is to be used, the alpha channel for pure black pixels is set to zero – the implications of this will be discussed later in the tutorial.

 

Once this function has completed, an image of dimensions 256x256 pixels of 24 bit colour depth with an 8 bit alpha channel (32 bits per pixel in total) is situated in SPS2 memory.

 

Note that this function should only be called once at the start of the program. The image file should generally remain in main memory for the duration of the program and can be uploaded into graphics memory when required for rendering.

 

 

The Upload() Function

 

The function prototype for Upload() is as follows:

 

void Upload(uint32 uGSPage, char * pcTexture, void * pDMAMem)

 

 

This function uploads a texture from SPS2 memory into GS memory. The GS Page address (uGSPage) tells the function where to put the texture in GSmem. GS memory is split up into a number of pages, each page being 8192 bytes (8k) long. This means that in total there are 512 pages in GS memory. The function sps2UScreenGetFirstFreeGSPage() is used to get the page that comes directly after the z-buffer.

 

The pcTexture parameter should point to the start of the texture data in SPS2 memory that was loaded with LoadBitmap(). Finally, the pDMAMem pointer should point to 4k of SPS2 memory that the function can use to build its DMAC chain. The DMAC chain is used to control the data transfer and 4k is sufficient memory for the size of image to be transferred.

 

In principle, uploading the texture is simply a case of setting 4 transmission registers on the GS, then putting the image data into a GS Packet and firing it off. A full description of this process is described in Chapter 4 of the GS Users manual but the main points will be investigated here.

 

The first thing is to set the 4 transmission registers. This is done using A+D mode in a GS packet. A+D (address plus data) mode tells the GS that the data following the GIFtag contains both the address of the register to be loaded and the data to be loaded into the register. In packed mode, the bottom 64 bits contains the data and the register address is contained within the top 64 bits.

 

The first register to be set is BITBLTBUF. A so-called host to local transfer (i.e. EE to GS memory) is being done so only the second half of this register, which deals with the destination of the transfer, needs to be set. The first parameter DBP, is the address of where to put the texture in GSmem. This address was passed into the Upload() function as a GSPage address. However, DBP must be given in units of “word address / 64”. This unit is rather confusing and it is easier to leave “word addresses” out and just say (address / 256). Therefore each unit of DBP is equal to 256 bytes. To put it another way,

 

1 Page address             = 8192 bytes

1 (word-address / 64)    = 256 bytes

 

From this it can be seen that 32 (word-address / 64)’s make up 1 page address. So to convert our page address to the format required by DBP we multiply it by 32 – this is done at the start of the Upload() function.

 

The next field of BITBLTBUF to be set is DBW (destination buffer width). This is given in units of pixels / 64. Since a 256 pixel wide texture is to be loaded a texture buffer of at least 256 pixel wide is needed - so the DBW parameter is set to (256 / 64) = 4. Finally the DPSM parameter, which is the destination pixel storage format, must be set. A 32 bits per pixel texture is being used, so DPSM is set to PSMCT32, which is defined as 0.

 

The next register to be set is TRXPOS. This register specifies the offset coordinates at the upper left point of the transmission area in the transmission buffer and the pixel transmission direction. When transmitting from host to local, only the settings for the destination buffer are of concern along with the DIR parameter, which specifies the order that the pixels are transmitted. The first two fields (SSAX and SSAY) are the X and Y coordinates of the top left source rectangle – these are not needed in this case and can both be set to default values of 0. The next two parameters are the X and Y coordinates of the top left destination rectangle (DSAX and DSAY), which are both set to 0. The final parameter (DIR) is set to 0, which specifies that the transmission order be from upper left to lower right.

 

The next register is TRXREG. This register is set with the width and height of the texture in pixels.

 

Finally there is TRXDIR. As was pointed out above, a host to local transfer is being done, so the XDIR parameter of this register is set to 0.

 

Now that the texture transfer registers have been configured it is possible to send a Packet containing the texture to the GS. This is done by configuring a GIFTag in image transfer mode (GS_FLG_IMAGE). In image mode, all of the parameters of the GIFtag have no meaning apart from NLOOP and QWC. The qword count (QWC) is set to the size of the texture in quad words and the FLG parameter is set to GS_FLG_IMAGE; all of the other GIFtag parameters are set to zero. The texture data then ends the packet.

 

Unfortunately, due to the SPS2 4k memory boundary things are not quite as easy as this in practice. REF tags are used to point the DMAC to each individual 4K (256 qword) chunk of the texture. Remember, the DMAC reads the REF tag then goes off and transfers the specified amount of data from memory, then comes back to its original position and reads the next tag.

 

The final part of this function sets 4 GS registers using A+D mode. The first register to be set is TEXFLUSH. It doesn’t matter what is written to this register since when the GS detects that data is written to this register it flushes the texture cache. This is similar to flushing the SPS2 cache, except here the texture data is to be written through to GS memory instead of a DMA chain to SPS2 memory.

 

Next is the ALPHA_1 register. The alpha value of a pixel determines how transparent a pixel is. An alpha value of 0x80 is fully opaque, and an alpha of 0 is fully transparent. A number of different blending equations can be used by setting the different fields of ALPHA_1. Creating custom blending equations is out of the scope of this tutorial, but details can be found on page 100 of the GS Users manual. The equation used in this tutorial is as follows:

 

Output colour = (Cs – Cd) * As >> 7 + Cd

 

Where:

 

Cs = Source colour or texture colour,

Cd = Destination colour or colour presently in the frame buffer,

As = Source alpha or texture alpha value.

 

As can be seen, the multiplication is only shifted right 7 bits, which is why 0x80 is fully blended. This means that alpha values of 0xFF will double the source colour. Overloading both the alpha value and the vertex colours will allow the brightness of a texture to be increased by up to 4 times. Note that to enable alpha blending on a primitive, the ABE field (parameter 5) of the PRIM register must be set to 1 (see later).

 

Next is TEX1. This register sets the texture filtering mode. Mipmapping is not being used so the first parameter (LCM) is set to 1 (fixed mipmap value). Parameters 3 and 4 are the texture magnification and minification filters respectively. Both are set to 1 to indicate linear sampling. This means that the texture unit will sample the four pixels surrounding a position and average them together. This should produce nicer looking images than simple point sampling would do.

 

Now for TEX0. The first two parameters should be familiar, as they are the same fields that were set in BITBLTBUF. They should have the same values as those set in BITBLTBUF so that the texture unit uses the texture that was just uploaded. The next field PSM should be set to 0 (PSMCT32) as 32 bit textures are being used. TW and TH are the texture width and height given as 2 to the power of X. Since we are using 256 by 256 textures we want to set these both to 8 (2^8 = 256). The implication of this is that the PS2 only supports texture sizes that are powers of two. To load a texture that is an uneven power it is necessary to fill the rest of the texture with black and load it as a power of two. The TCC field is set to one, which means that the GS will use the alpha component from the texture. The last parameter of interest is the TFX parameter. This is set this to MODULATE, which means that the texture colour is multiplied by the vertex colour to obtain the final colour. The PS2 does colour multiplication in a slightly different way to normal:

 

Ordinary colour multiplication:

        Final colour = (col1 * col2) >> 8;

        0xFF = (0xFF * 0xFF) >> 8;

 

PS2 colour multiplication:

        Final colour = (col1 * col2) >> 7;

        0xFF = (0xFF * 0x80) >> 7

 

The reason the PS2 does this is so that it is easy to “double” the brightness of a texture to simulate a bright light shining on it by specifying vertex colours larger than 0x80. If a final colour is worked out to be larger than 0xFF it is clamped to 0xFF. What this means is that if the texture is to be display at normal brightness on a sprite, the vertex colour should be set to RGB (0x80, 0x80, 0x80).

 

As mentioned previously, LoadTexture() should only be called once at the start of the program (or at load time if you wish to use a loading screen). Upload() should be called each time a different texture is to be loaded into GSmem, this being because there is only room in GSmem to hold one texture. If more than one texture could be put in GS memory at a time, it would only be necessary to set TEX0 to point to the other texture before drawing. TEX0 could be set using A+D mode as done in the upload function. Alternatively it is possible to put TEX0 into a GS batch.

 

Now that all of the registers have been configures and the DMAC chain is complete, the DMAC chain is flushed to SPS2 memory then the packet is fired off to the GS. Once the packet has been transferred to GSmem the function returns and is complete.

 

 

The Main Program Code

 

Returning now to the main program code (main.cpp) it can be seen that after the texture is uploaded in to GSmem, a GS packet is built to display a textured sprite on screen. The additional flags in the PRIM field of the GIFtag that must be set are the TME (parameter 3: texture map enable), which is set to ON, or 1. Since 2D drawing is being done U and V coordinates are used to specify the texture coordinates. The default setting is STQ, so to change this the FST field (parameter 7) of the PRIM register is set to 1 for UV. The GS batch is configured to contain the vertex colour first (GIF_REG_RGBAQ) then GIF_REG_UV before GIF_REG_XYZ2. The appropriate information is put into the primitive data, which comes after the GIFtag.

 

UV coordinates are very simple. UV (0, 0) corresponds to the top left of the texture. Each UV coordinate corresponds to a pixel right and down respectively. So with the 256 * 256 pixel textures the UV coordinate (255, 255) would be the pixel in the bottom right. UV coordinates should be given to the GS in 12:4 fixed point number format, so the numbers must be shifted left by 4 (this is done in the function in AddRegisters.h).

 

The final part of the code is the render loop which has been seen before. In essence, the primitive packet is fired off to the GS every frame using the DMAC in normal mode.

 

 

Conclusions

 

Texture mapping has been illustrated using only 24 bit bitmap files with dimensions of 256 by 256 pixel. This may not be the most efficient method (for use in commercial games) but for the purposes of this tutorial it helps explain many of the required techniques and concepts. It also allows for the creation of PS2 Linux based games using texturing and other techniques such as texture animation, which will be described in later tutorials.

 

 

Postscript

 

The last parameter passed with the LoadBitmap() function determines whether true black (rgb = 000) is transparent or not. In the code supplied the parameter is set to “false” and the text, which is written in true black, appears black. Change the final parameter in LoadBitmap() to “true” and the text will be transparent – it will be possible to “see” the background colour through the text. The use of transparency is fundamental to the creation of many of the effects found in 2D and 3D games.

 

 

Dr Henry S Fortuna

University of Abertay Dundee

h.s.fortuna@abertay.ac.uk