GP2X Gp2x Demo Development


Dzz

stmia r0!, {r2-r9}
Joined
Jan 30, 2006
Messages
1,098
Website
Visit site
As the sponsor of the gp2x demo competition, I am ineligible to win a prize, but just for fun I will develop my own demo over the next few months anyway. I'll document my progress in a series of articles, in case the things I discover will be of use to other developers.

Looking at the task, I definitely want to aim for a 64k demo. Preferably, it would be even smaller than that, because striving for tininess is a cool thing to do in a demo. There's nothing wrong with using conventional methods to make a bigger demo, of course, and I hope a lot of people give that a try! Just my personal interest. I'm not elite enough (yet) to contemplate something like a 4k demo on the gp2x, so I'll be satisfied with simply keeping it as small as I can.

Other goals: Create some cool graphics, have some music, and use the features of the machine as well as possible.

So, what's available on the machine that might be fun to use?
  • two 200mhz processors. That's a lot of power and it would be a shame to not put it all to good use.
  • graphical display. By default, 16 bits of color... but 24 bits could conceivably be available. It might be interesting to play around with that a little bit.
  • hardware blitter. It could be fun to play around with the capabilities of the blitter.
  • video decoder. Can any good use be made of the video decoding hardware?
  • audio. This looks fairly routine but it will at least have to be used properly.
So, pretty soon some exploration of these capabilities will be in order.

First, though, some thoughts about program size. There are two possible ways to go about keeping the size small:

1) dynamically link to features that can be found on the gp2x itself. the advantage of this approach is that I can leverage some big pieces of functionality in this way. A quick look at libs folder on the gp2x shows the basic standard libraries plus compression, ogg decoding, the extensive SDL library with sound and font support, and png file support. That's a lot of stuff!

2) do not use any external libraries -- instead, build all functionality into the program.

Approach (1) has some huge advantages obviously, but what are the disadvantages? The only one that springs to mind is that I'd have to use the particular versions of the libraries that exist on the gp2x firmware releases. So I'd have to research what versions are available in the different firmware builds and live with that. I can't see any other downside.

But approach (2) is more interesting to me personally, and might have the advantage of being more technically difficult. Also, if I get married to the SDL that comes with the firmware, it could be rather difficult to do things like use the hardware blitter if I want to. Plus, I've never thought about the issues involved with having no reliance on any library before which means there's a bunch of fun (?) stuff to learn.

So, having cast aside the stifling comfort of libgcc, libc, libm, libsdl, and other trifles, it's time to answer this question: How small of a "hello world" program can I make? It should print out "Hello, world!" to the serial port and cleanly exit back to the gp2x menu.

First, a blank program that doesn't do anything. To avoid fancy linking, I'll use "ld" directly and define my own entry point to stop it from whining about 'main'. So here's my entire first program:

Code:
void entry()
{
}

And to compile it:
Code:
C:/devkitGP2X/bin/arm-linux-gcc -Wall -Werror -O2 -c main.c
C:/devkitGP2X/bin/arm-linux-ld -e entry main.o -static -s -o demo.gpe

The development tools in use here are the standard devkit for Windows. There's a link to it on wiki.gp2x.org. It's just a plain vanilla devkit.

Sure enough, it produces an actual program, which crashes if I run it. Not to worry about that for now, I need to be friendly and exit in one of the proper ways.

Next, to get some output. This will come in handy later for debugging purposes as the demo gets built. To produce output, I'll write the string to file descriptor 1 (standard output), which will send it out the serial port where I can see it. So the linux call I want to make is:
Code:
write(1, "Hello, world!\n", strlen("Hello, world!\n"));

After much googling, experimenting, and fits of foul language, I worked out this routine (dzzstrlen() is a trivial string length computation function needed because I don't have a library any more that implements strlen()):
Code:
void PrintString(char *pszString)
{
	int nLen;
	nLen = dzzstrlen(pszString);

	asm volatile
	(
	  "mov r0, #1\n"	  // stdout
	  "mov r1, %0\n"	  // the string
		"mov r2, %1\n"	  // the length
		"swi #0x900004\n"   // write
		:  // no output
		: "r"(pszString), "r"(nLen)  // %0 and %1 input args
		: "r0", "r1", "r2"   // registers we clobber
	);
}
This illustrates the general method for making system calls, which turns out to be pretty easy.

Similarly, to restart the menu on exit:
Code:
void RestartMenu()
{
	char *pszMenuDir = "/usr/gp2x";
	char *pszMenuCmd = "/usr/gp2x/gp2xmenu";

	asm volatile
	(
	  "mov r0, %0\n"	  // directory
		"swi #0x90000C\n"   // chdir

	  "mov r0, %1\n"	  // program to execute
		"mov r1, #0\n"	  // arg 2 = NULL
		"mov r2, #0\n"	  // arg 3 = NULL
		"swi #0x90000B\n"   // execve
		:   // no output
		: "r"(pszMenuDir), "r"(pszMenuCmd)  // %0 and %1 inputs
		: "r0", "r1", "r2"	 // registers we clobber
	);
}

This leaves me with just the following "main" (which I call "entry" just for fun):
Code:
void entry()
{
	// print our string
	PrintString("Hello, world!\n");

	// restart the menu
	RestartMenu();
}

It works! Size so far of demo.gpe: 652 bytes.

In the next installment I'll look into getting access to the gp2x screen so I can show off my mad grafix skillz.
 
Another disadvantage of dynamically linking is that you can only use upto a certain version of GCC. The libraries on the 2x were built using GCC 2.95, so you can probably only go upto GCC 3.4. For static builds, you can use any version. I personally use GCC 4 as it seems to produce the fastest executables - in my experience the executables are approximately twice as fast as those created by 2.95.
 
Keep going! AWESOME compo and article!
Is your dzzstrlen something like this or is there a faster and smaller way? Given the case you have no dynamic text, counting the number of chars by hand will make dzzstrlen obsolete and will reduce executable size.

Code:
int i=0;
while(string[i] != '\n'))
{
  i++;
}
return i;
 
Dzz posted on Apr 11 2006 at 02:01 AM said:
After much googling, experimenting, and fits of foul language, I worked out this routine (dzzstrlen() is a trivial string length computation function needed because I don't have a library any more that implements strlen())
I don't understand what is dzzstrlen. Is it a routine you write yourself, or is it something available with the standard c lib ? I never heard of this (of course I'm totally inexperimented with C).
 
Last edited by a moderator:
He says he doesn't have a library anymore for strlen, so my guess would be a function he wrote himself.

synkro: Probably very similar to your routine, but replace '\n' with 0
 
Squidge posted on Apr 11 2006 at 12:11 PM said:
He says he doesn't have a library anymore for strlen, so my guess would be a function he wrote himself.
Thinking about it, it was quite evident as his pseudo is "dzz" :rolleyes: I was fooled because without the source for this method, your tutorial is not complet, dzz ;)
 
Last edited by a moderator:
Squidge posted on Apr 11 2006 at 12:11 PM said:
synkro: Probably very similar to your routine, but replace '\n' with 0

ah yeah sure, null terminated string. But I meant if he also did that one in ARM ASM....
 
Last edited by a moderator:
synkro posted on Apr 11 2006 at 05:40 AM said:
Squidge posted on Apr 11 2006 at 12:11 PM said:
synkro: Probably very similar to your routine, but replace '\n' with 0

ah yeah sure, null terminated string. But I meant if he also did that one in ARM ASM....
Sorry for the confusion, it is indeed exactly what squidge said. Next time I will start having a link to the entire source and .gpe for each entry in the series. I did not do the strlen in assembly. My intention for this project is to only use assembly for very time-critical routines where I can get a significant speedup using asm, or when I have no other choice, like the system call examples presented here.

If I were truly elite, I would code it all in assembly but I'm not that familiar yet with ARM assembly.
 
Last edited by a moderator:
Deed posted on Apr 11 2006 at 03:10 AM said:
I don't understand what is dzzstrlen. Is it a routine you write yourself, or is it something available with the standard c lib ?
To further clarify my apology for not including all the source with this first article: the development technique being used here does not use ANY libraries at all, which is why I don't just call strlen(). I'll have access to the standard linux sytem calls via the swi-instruction-based system call mechanism, but other than that all the code will have to be included.

This has some unexpected consequences; for example I will not be able right away to write code like:

int a, b, c;
...
a = b / c;

because integer division is supplied by a library (the program won't link) -- so I'll have to write my own integer division routine (or borrow one that somebody else already wrote).

The advantages to this approach are: 1) It's fun. 2) By not relying on any specific libraries, it should always work on all gp2x's that run a more or less normal linux. 3) Code that does not rely on any libraries is easy to get running on the second processor, so making use of the 940 shouldn't be too hard. 4) Like Squidge pointed out (thanks! I didn't think of it!), I can use whatever version of the compiler I like and not worry about whether the library formats are compatible.
 
Last edited by a moderator:
Dzz posted on Apr 11 2006 at 03:48 PM said:
If I were truly elite, I would code it all in assembly but I'm not that familiar yet with ARM assembly.
It would also be much more difficult for beginners to understand your tutorial :D
As I only know C and other high level languages, and not assembly, your tutorial would be of great interest for me, if you use both C and assembly, and clearly explain and comment the assembly part. This would teach me demo coding AND assembly, so cool ! B)
 
Last edited by a moderator:
Deed posted on Apr 11 2006 at 09:34 AM said:
Dzz posted on Apr 11 2006 at 03:48 PM said:
If I were truly elite, I would code it all in assembly but I'm not that familiar yet with ARM assembly.
It would also be much more difficult for beginners to understand your tutorial :D
As I only know C and other high level languages, and not assembly, your tutorial would be of great interest for me, if you use both C and assembly, and clearly explain and comment the assembly part. This would teach me demo coding AND assembly, so cool ! B)
I'll make sure to add some more comments to the assembly for the next installment. In particular, those funny lines at the end of the inline assembly that start with : took me a while to figure out so I should explain how to use them.
 
Last edited by a moderator:
And thanks for your kind answer to my question ! I read it after I wrote my previous post. I've got another one now ;)
Dzz posted on Apr 11 2006 at 05:33 PM said:
3)Code that does not rely on any libraries is easy to get running on the second processor , so making use of the 940 shouldn't be too hard
Why is code that does not rely on any libraries id more easy to get running on the 940 ?
 
Last edited by a moderator:
The 940 doesn't have access to Linux, and a lot of library routines assume Linux to be present. So doing without the library routines means that the only dependancy on Linux is the system calls you place in yourself.
 
but if dzz tries to prevent using libs then he will need own malloc, memcpy and stuff... those are needed even especially for a 64K demo.
 
synkro posted on Apr 11 2006 at 10:15 AM said:
but if dzz tries to prevent using libs then he will need own malloc, memcpy and stuff... those are needed even especially for a 64K demo.
That's correct. memcpy is easy. malloc is more interesting.
 
Last edited by a moderator:
Dzz posted on Apr 11 2006 at 06:29 PM said:
synkro posted on Apr 11 2006 at 10:15 AM said:
but if dzz tries to prevent using libs then he will need own malloc, memcpy and stuff... those are needed even especially for a 64K demo.
That's correct. memcpy is easy. malloc is more interesting.
Wow. Can't wait for the next part of your tutorial, dzz, I'll certainly learn a lot of things :)
 
Last edited by a moderator:
If you only want a piece of memory active for the life of a particular function, you can normally just grab it off the stack, as linux will automatically allocate you more ram for your stack as you use it up :) This is not really that portable, but then again, if your using arm assembler and Linux system calls in your code, then it's not going to be that portable anyway :D

Anyone who wants to know what's possible with just system calls (rather than libraries) should have a look at arch/arm/kernel/call.S in the Linux source tree.

I don't want to give away all of Dzz's tricks and techniques, but have a look at the 'brk' system call.

Note: System calls are called via SWI's, which generally means a switch to supervisor mode and running kernel code, before finally returning back to user mode and the process your code is running in. This can get processor intensive, which is why the C library wraps these functions up for you and optimizes program access to them.
 
@Squidge: As we aim for 64K demo here, we will need malloc to get mem for precalculates LUTs (I am thinking of a sine table) and even iterative generation of gfx as 64K is very little...

Damn, I have to admit those prizes are temtping, I hope DZZ's articles will feed me more. For everybody having trouble, here is the complete and working solution

Code:
unsigned int getStringLength(char *string);
void print(char *string);
void restartMenu(void);

void entry(void)
{
	// print our string
	print("Hello, world!\n");

	// restart the menu
	restartMenu();
}

void print(char *string)
{
	unsigned int length;
	length = getStringLength(string);

	asm volatile
	(
		"mov r0, #1\n"	  // stdout
		"mov r1, %0\n"	  // the string
		"mov r2, %1\n"	  // the length
		"swi #0x900004\n"   // write
		:  // no output
		: "r"(string), "r"(length)  // %0 and %1 input args
		: "r0", "r1", "r2"   // registers we clobber
	);
}

unsigned int getStringLength(char *string)
{
	unsigned int i = 0;
	while(string[i] != 0)
	{
		i++;
	}
	return i;
}

void restartMenu(void)
{
	char *pszMenuDir = "/usr/gp2x";
	char *pszMenuCmd = "/usr/gp2x/gp2xmenu";

	asm volatile
	(
		"mov r0, %0\n"	  // directory
		"swi #0x90000C\n"   // chdir

		"mov r0, %1\n"	  // program to execute
		"mov r1, #0\n"	  // arg 2 = NULL
		"mov r2, #0\n"	  // arg 3 = NULL
		"swi #0x90000B\n"   // execve
		:   // no output
		: "r"(pszMenuDir), "r"(pszMenuCmd)  // %0 and %1 inputs
		: "r0", "r1", "r2"	 // registers we clobber
	);
}
 
Squidge posted on Apr 11 2006 at 02:57 PM said:
Anyone who wants to know what's possible with just system calls (rather than libraries) should have a look at arch/arm/kernel/call.S in the Linux source tree.

I don't want to give away all of Dzz's tricks and techniques, but have a look at the 'brk' system call.

Note: System calls are called via SWI's, which generally means a switch to supervisor mode and running kernel code, before finally returning back to user mode and the process your code is running in. This can get processor intensive, which is why the C library wraps these functions up for you and optimizes program access to them.
Thanks for the great info! You're right, portability is not normally a priority for demos, although something flashy like for example the same binary running the demo on both a gp2x and a gp32 would be worth big techinical geek points.

As to giving away my tricks, I don't really have any tricks (I'm discovering them as I write in this thread), and really the purpose of this whole thing is to get as much useful info out there as possible, whether it comes from me or not, so if smart knowledgeable guys like you have info to share, please continue to do it!
 
Last edited by a moderator:
@Squidge: As we aim for 64K demo here, we will need malloc to get mem for precalculates LUTs (I am thinking of a sine table) and even iterative generation of gfx as 64K is very little...

Someone obviously didn't research what the system call 'brk' does :)
 
Back
Top