DOCUMENT FOR TUTORIAL ONE

BASIC SKELETON CODE FOR NET YAROZE

BY SCOTT WARD 4/5/99

 

If, like me, you've struggled a little when trying to get to grips with the Net Yaroze system then this is hopefully the tutorial that will aid you in your first steps to becoming a Playstation coding wizard. There are already a few good tutorials out there and a number of Yaroze members who are willing to help you via the members websites so you're not as alone as you probably think. As the first in a line of tutorials that I intend to create this tutorial deals with the very basic functions that the Yaroze needs to display graphics and will provide you with the knowledge needed to grasp the fundamentals of Net Yaroze development. I'm nowhere near being a master at Playstation programming and therefore my source code should never be taken as gospel but hopefully if I can get people to understand how things work then this tutorial has done it's job.

As you look through these tutorials you'll probably notice that my method of teaching is different to most, you'll get used to it. Most tutorials are actually more like references; this tutorial should be more 'down-to-earth' and is specifically designed for novices. I would strongly recommend referring to the Library Reference as we work through this tutorial; it will help you understand things better. Ready? On we go.

 

GETTING STARTED

#include <libps.h>
This is the Yaroze header file that should be included ALWAYS. It contains all the functions that you'll need to use for basic Yaroze operation. After you've put this in, create an empty 'main()' function. So far, everything looks sufficiently 'C' and we're all happy. However, it does bugger all so it's about now that we start throwing in Yaroze functions to let the Yaroze know what it is that we want it to do.

GsInitGraph()
This is the first function that you will probably use in most of your Yaroze programs, it tells the Yaroze how to set up the graphics system. The first two arguments in this function are the screen width and height. I assume that you're working in PAL so for simplicity's sake initialise your screen size as 320 pixels by 256 pixels. This is low-resolution graphics and easy to work with for now. For the next argument put a '4' in, this states that we want non-interlaced and the GPU offset mode. Don't worry about not understanding this for now, it will all become apparent as you become familiar with the Yaroze. You may see things like 'GsGPUOFF' or something like that. These are built in macros, you don't have to use them. Use the bit patterns, again, these are covered in a later tutorial. Finally, for the last two arguments put a '0' in. For the terms 'dither' and 'vram' see the 'Glossary' section of my website. I know, things have already been brushed over but I assure you that these will all become clear in the future. For now we're just dealing with the basics.
You should now have something like this:
GsInitGraph(320,256,4,0,0);

 

FRAME BUFFER & DOUBLE BUFFERING

These things are covered pretty well in the manuals so I won't spend too much time on these subjects. The frame buffer is like a large piece of graph paper in the Yaroze's brain that it stores images on. Part of the frame buffer is used to hold the 'virtual' screens, by this I mean the Yaroze allocates space to hold two areas the same size as the screen size that you are using. It uses these to draw the images that you tell it to and the finished product is then displayed on the TV screen. For example, say you have a program that draws a ball and a cat. The Yaroze will use the 'virtual screen' or 'display/drawing buffer' to draw the ball and then it will draw the cat, however the ball and cat are shown at the same time on the TV. The TV only shows the finished drawing, not the picture as it is being drawn. The reason why there are two screen areas is to save time, this is called double buffering. The Yaroze will use one buffer for drawing as it's the using the other for displaying. It will then swap the roles of the buffers so that the buffer that was just being drawn is now being displayed and vice versa. To understand this better refer to the Sony manuals, this is one of the few subjects that these manuals cover pretty well.

GsDefDispBuff()
We now need to tell the Yaroze where in the frame buffer to create the display buffers as mentioned above. As standard, the first display buffer sits at point 0,0 with the second buffer sat directly beneath it. Our screen size is now set at 320x256 so the co-ordinates in GsDefDispBuff() will be (0,0,0,256). You see?

Okay, now that we've initialised our graphics system it's time to actually get the Yaroze to draw. To do this create a loop as you would normally in 'C', I just use an eternal 'for(;;)' loop. For now, it's empty though so we'll find something to put in it.

 

PACKETS

You'll have probably come across PACKETS already, if you don't understand them that's okay, packets are one of those Yaroze things that even the most experienced programmer must get their heads round. To be honest though, they really are simple when they're explained properly. A PACKET or 'primitive', by definition, is the smallest unit of drawing commands that can be dealt with by the GPU. The Yaroze uses packets to draw whatever it is it needs to draw. You might find tables here and there referring to certain things requiring a certain number of packets to draw. For example, and these figures are purely off the top of my head, a polygon from a 3D object may take up 15 packets, if the 3D object is made up of 20 polygons then it will need 300 packets to draw the object. Don't worry too much about how to work out how many packets you need, it really doesn't matter for now because we're not actually going to draw anything, we're just going to set up the skeleton code to do so.
In your 'for(;;)' loop you'll have to add this function: GsSetWorkBase();
This is needed to store the base address of the packets that you allocate so the Yaroze knows where to store all it's drawing commands. The GsSetWorkBase() function requires a packet address pointer like this 'PACKET *base_addr', don't let this panic you, all we do is create some packets so we have an address to give it.

Just above your 'main()' function we'll now add a global variable that will hold our packets. Enter this line:

PACKET packetaddress[2][100];

Firstly, if the 'PACKET' bit in capitals is bothering you it's no different to declaring any other type of variable, it's actually an 'unsigned char' variable if I remember correctly. Remember, by using the typedef statement in 'C' it's possible to have other names referring to variables, structures, etc... so somewhere in the Yaroze libraries is probably a line like this:
typedef u_char PACKET, all this does is keep things a little easier for you, you just refer to the u_char packet variables as PACKET variables. I hope this makes sense. Back to the case in hand, I've named my packet variables 'packetaddress' just to make things more understandable when you see them in the GsSetWorkBase(). The reason why a 2D array is set up is due to the double buffering thing. We want 2 lots of 100 packets, one lot for each display buffer, the hundred is just a random number of packets. We're not actually drawing anything so it doesn't matter what we put in, put 1000 in if you want.
So now, when we enter the GsSetWorkBase() function we give the pointer as:
GsSetWorkBase((PACKET *)packetaddress[currentbuffer]);
The (PACKET *) is added to typecast packetaddress as a PACKET pointer (*), it's probably easier just to declare packetaddress as PACKET *packetaddress[2][100]; It's up to you, we all have our own funny little ways.

 

WHICH BUFFER ARE WE USING?

Now you're wondering what the 'currentbuffer' thing is right? It's a variable that we set to hold an integer value relevant to which display buffer is being used. You'll see what I mean in a bit. There's a Yaroze function called 'GsGetActiveBuff()' which strangely enough gets the active buffer or 'current buffer', we need this so the Yaroze knows which display buffer it is currently supposed to be drawing on. The 'GsGetActiveBuff()' function returns a value of '0' or '1' depending on which buffer is currently being used. We can hold this value by storing it in an integer variable. First, add this line to your global variables:

int currentbuffer;

then, as the first line in your 'for(;;)' loop:

currentbuffer=GsGetActiveBuff();

We can now use the variable 'currentbuffer' whenever we need to tell a function which buffer we are currently using. In the line 'GsSetWorkBase((PACKET *) packetaddress[currentbuffer]);', the currentbuffer part is added to tell the Yaroze which of the two packetaddress arrays to use depending on which buffer is being used. Remember, currentbuffer will be holding '0' or '1' which are the numbers of the packetaddress arrays.

 

SO FAR...

Before we move onto the dreaded ordering tables lets see what we've got so far...

****************************************************

#include <libps.h>

PACKET packetaddress[2][100];
int currentbuffer;

void main()
{
GsInitGraph(320,256,4,0,0);
GsDefDispBuff(0,0,0,256);

for(;;)
{
currentbuffer=GsGetActiveBuff;
GsSetWorkBase((PACKET *)packetaddress[currentbuffer]);
}
}

***************************************************

Looks a bit pap doesn't it, never mind, once we get to grips with ordering tables we'll soon have more stuff to put in.

 

ORDERING TABLES

I would say that ordering tables and the setting up of ordering tables are probably what give most beginners the most problems when they first start out. Again, they're not that hard to understand once you know how.
An ordering table is what it sounds like, a table that puts things in order, it's that simple. In the Sony manuals it refers to a lot of 3D examples when explaining things, however, when starting out most Yaroze members work in 2D so they don't really provide much help. Whether you're using 3D or 2D the ordering tables do the same sort of thing, they provide an order for the GPU to draw each object in, this gives the effect of distance. For example, if you create a spaceship game in which there is a planet in the background you may draw a sprite for the spaceship and a sprite for the planet. This is all well and good but what if the GPU draws the spaceship first? The planet will be drawn over the top of the spaceship so you won't be able to see it. What if you draw one spaceship bigger than another giving the illusion of perspective only for the GPU to draw the big one first so the little one is over the top of the big one, thus smashing your cleverly created illusion of distance? These problems are easily solved with ordering tables. Already, ordering tables are changing from being evil concepts that confuse you to being helpful additions to aid your programs. Remember, ordering tables are your friends, they're nice and they help you.

We use 'GsOT' when dealing with ordering tables, 'GsOT' is a structure that holds the relevant information needed to use an ordering table. So, the first thing to do is to declare a 'GsOT' structure, just stick it in with the other global variables for now.

GsOT OTtable[2];

I know, there's only one 't' in table, it'll just have to do for now. Remember, because of the double buffering we need two of all our drawing functions so here we declare two 'GsOT' structures, like the PACKET thing we'll just use 'currentbuffer' to call the relevant GsOT structure when we need it. At this point you're probably a little worried about all the bits contained within the 'GsOT' structure, luckily when initialising a 'GsOT' structure you only have to deal with 'length' and 'org'.

 

THE LENGTH OF AN ORDERING TABLE

You've probably read bits here and there about the length of your ordering table, oo-er. You'll have to decide how long you want your ordering table. You already know that an ordering table decides what to draw first depending on which objects are supposed to be closer to or further away from your viewpoint. For this, the ordering table needs some kind of scale to work from, well, setting the ordering table length is like setting the scale. Imagine a ruler sticking out from your nose to the TV screen. If, like me, you're thinking of a 'shatter-resistant plastic 30cm ruler' quickly change it to a wooden ruler with no markings on at all so it's not so much a ruler, as a stick. Now, by setting the ordering table length you decide how many markings are situated on this ruler. Bearing in mind that this is a scale where your objects in your program will be situated on, thus, creating the illusion of distance. Why must you decide on a length? Well, the more markings the ruler has the more memory it takes up but it's more accurate when dealing with hundreds of polygons. It then figures that the less markings, the less memory but less accuracy. For 2D stuff you don't really need a long ordering table so for now we'll set our ordering table length to '2'.

#define OT_LENGTH 2

The reason most people define a macro for the ordering table is so it can be changed easily as the program changes, why else?
Now for every marking on the ruler we need to allocate a 'GsOT_TAG' tag, think of these as bookmarks that hold the position of the marks on the ruler. Use 'GsOT_TAG' to declare your ordering table tags.

GsOT_TAG OTtags[2][1<<OT_LENGTH];

Stick this underneath the 'GsOT' structures you've already declared. For now, forget about the variables contained in the 'GsOT_TAG' structure, you don't need to bother with them. Okay, you've probably already guessed that the 2 in the first lot of square brackets is for the double buffering, correct, because of double buffering we will need two lots of these. As for the '[1<<OT_LENGTH]' this will take some explaining.
Firstly though, you need to know how many markings on the ruler you've just initialised: two, right? Wrong. Even though our ordering table length is '2' there are actually 4 tags. This is because whatever you set as the ordering table length is actually used as a power of 2. It's not funny is it, just as you're getting the hang of it all this is thrown at you. The ordering table tags are set as '2 to the power OT_LENGTH' so if your OT_LENGTH is 3 you have actually created 8 tags. If you understand this, great, if you don't, it'll probably come to you in time, don't punish yourself by trying to understand it all now. As for the '[1<<OT_LENGTH]' bit, this is explained on my 'Questions and Answers' page on my website.

 

INITIALISING THE ORDERING TABLE

Now you've set up your ordering table you'll need to initialise it. As said before you only need to initialise the 'org' and 'length' parts of the GsOT structure. Just underneath the GsDefDispBuff() function put this in:

OTtable[0].length=OT_LENGTH;
OTtable[1].length=OT_LENGTH;
OTtable[0].org=OTtags[0];
OTtable[1].org=OTtags[1];

There's two ordering tables remember so you have to initialise both of them, hence the square brackets part. You simply set the 'length' as your pre-defined macro OT_LENGTH and set 'org' as the top address for all your ordering table tags, as far as I know the OTtags aren't set as pointers because they are arrays that are theoretically pointer addresses anyway even though you don't declare them as pointers. Like a string of text is actually an array of characters and therefore you don't need to declare them as pointers. Something like that anyway. Have a look through your C book, as I'm probably wrong. The other variables in the GsOT structure are initialised when you use the 'GsClearOt' function, which we will do soon.

 

CREATING THE LOOP

By now you've set everything up that you need to create the main loop, in this loop you will use the functions that:

Clear the ordering table - GsClearOt()
Wait for screen display to be completed before drawing next image - DrawSync(), VSync()
Swap the display buffers ready to draw/display new image - GsSwapDispBuff()
Draw clear screen to drawing buffer - GsSortClear()
Finally, draw the contents of ordering table to drawing buffer - GsDrawOt

Firstly, we must clear the ordering table ready to except a new set of instructions. Although we are not actually drawing any images we must still set up the basic drawing structure. Plus, the whole point of this tutorial is to get you used to the standard graphics system. Put these next list of commands after the GsSetWorkBase() function and the currentbuffer bit.

GsClearOt(0,0,&OTtable[currentbuffer]);

In the GsClearOt() function there are three arguments. First, the 'offset', just stick a '0' in, this is for more advanced stuff and as far as I can remember the Yaroze doesn't support this function. Second, the 'point', again, stick a '0' in. This is used when multiple ordering tables are being used so we don't have to bother with this either. The third value asks for the pointer to the ordering table, for this we use 'OTtable'. You have to put an '&' in front of it because it's not a pointer, this gets its address. The '[currentbuffer]' is used because you have to use the relevant ordering table that you are using with each buffer. After a while, you'll get used to the double buffering thing.

DrawSync(0);
VSync(0);

These two functions basically wait for the drawing to be completed (DrawSync) and the TV screen to finish displaying a full frame (VSync) before swapping the double buffers. If you think about it, it makes sense. Otherwise there's a risk of your buffers being displayed without finishing the drawing first, this makes your frames look messy and usually cause flickering. Refer to the Library Reference books to see the arguments that can be entered, it's pretty standard to just use '0's though, I think the other possible arguments are used for advanced programs.

GsSwapDispBuff();

Great, no arguments here, just enter the function to swap your display buffers. In case you're still wondering about the relevance of this here's why you swap them. You draw whatever you need to draw on your drawing buffer, when this is finished the buffers are then swapped so the buffer that was just drawn is now displayed to the screen. The buffer that was previously being displayed is now used for drawing on. If you didn't swap them your drawn buffers would never be displayed.

GsSortClear(255,0,0,&OTtable[currentbuffer]);

Okay, things should be ticking along nicely now. There's nothing here to confuse you. This function draws a clear screen onto your current drawing buffer, this is the most basic way we can use of checking to make sure everything's looping alright. The first three arguments are r, g, b values, these indicate the colour that you would like the screen to be. Like a TV screen the Yaroze uses red, green, blue values to create its colours. I'm not going to cover this here so if you need more info on this contact me via my website, I'm only too pleased help. For your r, g, b values put in (255,0,0), this makes red. The reason you shouldn't put a black screen in is that it tends to go black when your program crashes so it's hard to tell whether your code is working or not. Experiment with changing these values to get a feel of the colour values and to test to make sure the code is running correctly. The fourth argument asks for the ordering table pointer. If you still don't get why '&OTtable[currentbuffer]' is put in refer back to the paragraph that explains the GsClearOt() function.

GsDrawOt(&OTtable[currentbuffer]);

The final function to put in, GsDrawOt(). This draws whatever is in your ordering table to the drawing buffer, in our case it just draws a clear screen. All you need to put in is the current ordering table being used, by now I'm sure you know how to do this.

 

YOUR CODE SHOULD LOOK SOMETHING LIKE THIS...

**************************************************************************************

#include <libps.h>

#define OT_LENGTH 2

PACKET packetaddress[2][100];
int currentbuffer;
GsOT OTtable[2];
GsOT_TAG OTtags [2][1<<OT_LENGTH];

void main()
{
	GsInitGraph(320,256,4,0,0);
	GsDefDispBuff(0,0,0,256);

	OTtable[0].length=OT_LENGTH;
	OTtable[1].length=OT_LENGTH;
	OTtable[0].org=OTtags[0];
	OTtable[1].org=OTtags[1];

	for(;;)
	{
		currentbuffer=GsGetActiveBuff();
		GsSetWorkBase((PACKET *)packetaddress[currentbuffer]);
		GsClearOt(0,0,&OTtable[currentbuffer]);
		DrawSync(0);
		VSync(0);
		GsSwapDispBuff();
		GsSortClear(255,0,0,&OTtable[currentbuffer]);
		GsDrawOt(&OTtable[currentbuffer]);
	}
}

**************************************************************************************

If you want a better print out of the source code just edit the source code entitled 'blank.c' from the zip file and print it off from DOS. That source code is also commented which may be of more help to you.

If you're still having bother understanding any of this get in touch with me and I'll explain further. Hopefully, though, this tutorial should be thorough enough to get anyone up and running. Once you master this basic system of Yaroze graphics functions then everything comes to you a little easier. Anything that is brushed over in this tutorial will probably be covered in a later tutorial.

Feel free to copy any parts of this and say it's your own work. I want no recognition for this and I don't expect you to mention me if you use this source code in any of your programs, this is standard code, I wouldn't really expect anyone's code to be much different.

HAPPY CODING!