PS2 Linux Programming
Palletised Textures
The loading of 256x256 pixel, 24 bit colour bitmap
files as texture was introduced in the tutorial “2D Texture Mapping” In this
tutorial the loading of both multiple texture sizes and 8 bit palletised
textures will be presented.
In order to simplify the management of textures,
the texture loading and uploading functions have been rewritten in the form of
a C++ class, with member variables that hold information about the texture. The
texture class is contained within the files ctexture.cpp and ctexture.h.
The image dimensions and colour depth can be read
from the bitmap info header and use in the loading process. The only tricky
parameter is in the setting of TEX0, which requires the image size to be given
as a power of 2 -this is where the function TextureLog2() is used. This
function returns the size that is passed in as a power of 2. This function also
has a hidden use, in that it checks to see that the bitmap is actually a supported
size. The supported sizes are: 32, 64, 128, and 256. The bitmap does not have
to be square, for example a 256 by 32 texture is fine. Note that the texture
buffer width has been left at 256. The texture buffer width effectively sets
the maximum width of a texture that can be loaded. Smaller images can be loaded
into a larger texture buffer without any problem. The texture buffer width
(TBW) should not be set too high though, as the wider the buffer, the shorter
it becomes (given the same amount of memory) meaning that the maximum height of
textures that can be load is reduced.
A palletised texture is different to a regular
“true-colour” texture in that instead of storing pixels as actual RGBA values,
they are stored in a table of RGBA colours and each pixel is used as an index
into that table. This table is called a colour look up table (CLUT). 8 bit
palletised textures will be used in this tutorial. An 8 bit texture consists of
a 256-entry CLUT, and the image data which is the array of 8 bit indices. On
the Playstation 2 the 8 bit CLUT can be considered as being a 16x16 32-bit
texture.
Looking at the new LoadBitmap method in ctexture.cpp
it can be seen that the code is basically the same as before, except there is
now code to load the CLUT if the texture has a colour depth of 8 bits. (Note
that there is also an additional parameter passed into the function [bool
bRedAsAlpha], but just set this to false and ignore it for the purposes of this
tutorial – it will be explained in the next tutorial when font rendering is
investigated.) If a bitmap file has a CLUT, it will be stored just after the
two headers and immediately before the image data. The CLUT is read as an array
of 256 32-bit integers. As always with bitmaps, the data is stored in BGR
format, so the R and B channels are swapped around to give the data in the
correct RGB format. Again, just like 32 bit mode if the colour is RGB(0,0,0) and
the parameter, bBlackTransparentand, passed into the method is true, the alpha
channel is set to 0. Looking at the code is can be seen that the pixels in the
palette must be “swizzled”. The reason for this is that the CLUT is not laid
out linearly in GS Memory. The diagram on page 32 of GS users manual gives
information on the layoutof the CLUT and it can be seen that pixels 0x08 to
0x0F must be swapped with pixels 0x10 to 0x17. This pattern repeats every 32
(0x20) pixels.
A operation must therefore be determined to convert an ordinary index into a CLUT memory
index.
It is known that 0x08 must map to 0x10:
0x08 = 000|01|000
-> 0x10 = 000|10|000
A few other examples:
0x09 = 000|01|001
-> 0x11 = 000|10|001
0x28 = 001|01|000
-> 0x30 = 001|10|000
0xF3 = 111|10|011
-> 0xEB = 111|01|011
As can be seen, by swapping bits 3 and 4 of the
index the appropriate “swizzled” index is obtained. The code use to do this is
as follows:
(p & 0xE7) + ((p & 0x08) << 1) + ((p
& 0x10) >> 1)
Doing a bitwise AND on the index with 0xE7 gives p
without bits 3 and 4. P & 0x08 gives the value of bit 3, shifting it left 1
place puts it into bit 4. P & 0x10 gives bit 4 which is shifting it right
into bit 3.
Before the bitmap file is copied into SPS2 memory the
colour depth of the image is checked. If the colour depth is not 8 bits then it
must be 24 bits. The code for reading in the 24 bit pixels has been presented
before. Reading in the 8 bit pixels is simply a matter of reading in the byte from
the file and putting it directly into SPS2 memory – remember each byte is an
index into the CLUT.
The final new addition to the LoadBitmap() method
is right at the end of the function where a check is made to determine if the
image has a CLUT. If it does, then the CLUT is copied into SPS2 memory. The
CLUT is added to the next free 4K page of SPS2 memory that the user passed in
to the class. Therefore when useing this function to load an 8 bit texture,
always remember to have enough memory free for the CLUT. To be safe, always add
8 kilobytes to the memory needed for the image.
Now for the new upload function: This function
hasn’t changed much, apart from the addition of the code to upload the CLUT.
Almost all of the new code is enclosed in the section “if(m_bHasCLUT)” and this
section uploads the CLUT into video memory. As was mentioned earlier a CLUT can
be thought of as a 16x16 32-bit image, therefore uploading it is exactly the
same as uploading a regular image. Checking the code it is seen that the method
used is exactly the same as for uploading a 32 bit image except for now the width
and height are set to 16. The code puts the CLUT into GS Page 511, which is the
very last page of GS memory. Since at this time the maximum texture size
supported is 256x256 pixels, with an 8 bit texture this page is guaranteed to
be free. This is not the most efficient use of GSmem but it get the job done
for the present – a tool to organise the textures in GSmem would be nice!
When the image is upload to GSmem, the DPSM parameter
fo the BITBLTBUF register (destination pixel storage format) is set to PSMT8
when an 8-bit palletised texture is loaded or PSMCT32 otherwise. The final
thing to change is TEX0. This register also requires the pixel storage format to
be set in its PSM field. The CBM field of TEX0 must point to the CLUT’s
position in GS memory, and finally the CLD field should be set to 1. The only
other thing to mention is that when working out the QWC fields they are multiplied
by 4 if when using 32 bit mode for the reason that 32 bit pixels take up 4
times more memory than 8 bit pixels.
This section deals with how to use the CTexture
class.
The first thing to be done is to set aside enough
SPS2 memory for the texture. An easy way to do this is just to use 256K for
every texture as this is guaranteed to be enough. This is fairly wasteful
however, so it might be better to work out exactly how much memory is need. For
a 24 bit bitmap this is simply WIDTH * HEIGHT * 4. For an 8 bit bitmap the
image data will be WIDTH * HEIGHT, but then additional free page is needed for
the CLUT. It is best to add on 8 kilobytes to the amount needed for the image
to give the total amount needed.
A CTexture object should be created for each
texture to be loaded and a pointer should be passed to the SPS2 memory that has
been set aside for the texture, into the constructor.
The LoadBitmap() method is called to load the image
from disk into SPS2 memory. This method should only be called once during the initialisation
phase. Before using the texture for drawing purposes, the CTexture’s Upload method
is called to upload the texture into GSmem. Remember to flush the SPS2 cache
before uploading a texture that has just been loaded.
CTexture is fairly wasteful in that it only allows one
texture in VMEM at a time. One way to increase the speed of an application
would be allow 2 or more textures to be in VMEM at the same time. This way drawing
could be performed using one texture, while another texture was being
uploading.
In this tutorial the use
of texture of different colour depth and size has been introduced. The methods
presented illustrate the techniques involved, but there are many improvements
that can be made, (some of which are outlined in the text) in order to optimise
the process.
Dr Henry S Fortuna
University of Abertay
Dundee