GP2X Command Buffer Design


MadDog

Member
Joined
Mar 4, 2006
Messages
262
Age
54
Location
UK
Website
www.maddoggames.com
I'm quite pleased with this, although Dzz, Rixed and Squidge have helped me out with my daft code. (sorry if I missed someone out)

The design has been borrowed from the Xbox360. ;)

GPU == 940

What I have, and this can be changed to suit, is 8 * 2k buffers. The CPU writes and the GPU reads. The last thing I write of a command is its ID. The first thing I write is a 'SLEEP' command to what will be the next command. This means that, and I have an assert to check, the current command being written is always flagged as a 'SLEEP' command until its ready to be read and also even before its ready the following command is a 'SLEEP' until written to. This stops the GPU getting ahead of the CPU and start reading old data.

When I add a command I check to see if the size of the command will fit in the current buffer, if it will not I write a command telling the GPU to move to the next command buffer and the start writing to the start of the next buffer. When I do this check if the buffer i'm about to move into is the one the GPU is currently reading I wait, this stops the CPU from catching up with the GPU. Also this check only has to happen when it moves to a new buffer so the cost of reading an uncached control var is minimal.

All commands are rounded up to multiples of 4 bytes, this helps to make sure I don't get unaligned memory problems. Every command has a header of four bytes defined as two shorts. One is the command ID and the second is the size in dwords (4bytes) This is because the GPU read and CPU write command buffer pos pointers are dword pointers. So to move to the next command I do pos += current_command->size;

As the write I write a SLEEP command thats defined as zero it makes the ID and size zero in one go. Also when I setup the memory space for the GPU I zero its ram and so when the GPU starts up it will just spin on a SLEEP command.

In the GPU code I just have a big switch statement.

Hope that all made sence, its a nice simple method that needs no semephores just an uncached var the GPU writes to when it moves to a new command buffer and the CPU reads when its going to move to the next command.

(I do use a semephore to stop the CPU getting too many frames ahead, but I think that its not to do with the command buffer)
 
I think I understood most of that :D good work ^_^

i'm totally interested in all this stuff. after ttx i'm definatly going to have a bit of a play with something simular :)
 
I'm planning a simpler method. Two command buffers. While the 920 is writing to one, the 940 is executing blitter and polygon commands from the other. When completed, each CPU waits until both are completed. The frame buffer is swapped, the CPUs are told to point at the other command buffer and it all starts again.
 
slygamer posted on May 27 2006 at 10:35 AM said:
I'm planning a simpler method. Two command buffers. While the 920 is writing to one, the 940 is executing blitter and polygon commands from the other. When completed, each CPU waits until both are completed. The frame buffer is swapped, the CPUs are told to point at the other command buffer and it all starts again.

sounds pretty simple, is there a way to measure the average idle time of both?
 
Last edited by a moderator:
rixed posted on May 27 2006 at 03:35 PM said:
Im not sure to have understood all correctly, but it seams very complicated to me.

Why not a simple, single circular buffer ?

If you use that then on every command write you have to be checking 'uncached' memory to make sure your not going to take over the 940. The code is very simple really, as always these things sound more complicated when put into english. ;)

My method means the cpu reads very seldom from the uncached data used for arbitration.
Also doing this will allow me to have the comand data cached for the 940 as I'll know the CPU is never writing to the same bit of buffer. When the 940 moves to a new buffer it will invalidate the cache lines for the block. Not done this and not sure if it would help.

Here is the add command func, returns a mem pointer for the CPU to write the data, the last bit the calling does is write the ID.

[Formmating all bust, looks ok in the editor] :(

//For now it blocks if the command ring buffer is full.
void *AddGpuCommand(u32 pCommand_sizeof)
{
assert( (pCommand_sizeof&0x3) == 0 );//Make sure its 4 byte aligned.
pCommand_sizeof >>= 2;//Command pointers point to 4 byte data.
u32 *cmd = command_buffer_write_pos;

//If we are at the end of the command buffer we need to add one more command
//to tell the gpu to move to the next command buffer its reading.
//Also, if the one we are abaout to start to fill is the one its reading we
//need to block.
//These vars are in 920 mem space, so are in the cache.
if( (cmd + pCommand_sizeof) > command_buffer_write_pos_end )
{
current_command_buffer++;
if( current_command_buffer >= NUM_COMMAND_BUFFERS )
{
current_command_buffer = 0;
}
//See if the buffer we are moving into is the one the GPU is reading, if so block.
//This block could call back to the APP for some AI processing etc.....
#ifdef _DEBUG
if( current_command_buffer == command_buffer->gpu_current_command_buffer )
{
K9_MSG("CPU has caught up with the GPU, blocking.\n");
}
#endif
//Is is the slow uncached checking.
u32 n = 0;
while( current_command_buffer == command_buffer->gpu_current_command_buffer )
{
n++;
};
SetActiveCommandBuffer(command_buffer->command_buffer[current_command_buffer]);

//Now we know that there is a sleep waiting in the next command we can
//add the command to tell the GPU to switch the command buffer its reading.
//This deals with the case when its chasing the CPU.
//And last of all write the goto command.
((Command_GOTO_NEXT_BUFFER*)cmd)->size = 1;
((Command_GOTO_NEXT_BUFFER*)cmd)->id = GOTO_NEXT_BUFFER;

//Re get where the new command is going to be written.
cmd = command_buffer_write_pos;
}
#ifdef _DEBUG
//Don't write command yet, as we don't want to process the command until its filled in.
if( command_buffer_write_pos[0] != SLEEP )
{
K9_MSG1("Error on frame %d\n",Screen::frame_number);
}
#endif
assert( command_buffer_write_pos[0] == SLEEP );//Make sure its a sleep, should always be.
//Write the size in so the GPU knows how many to skip.
((u16*)command_buffer_write_pos)[1] = pCommand_sizeof;
command_buffer_write_pos += pCommand_sizeof;//Move to the next command.

//Make sure the next command stalls the GPU till somethings written.
//Don't forget this buffer is in a ring, so could be old data here.
command_buffer_write_pos[0] = SLEEP;

return cmd;//Return where we are going to write the commands.
}
 
Last edited by a moderator:
Back
Top