GP32 16bpp Blitting Speed


pea

developer
Joined
Oct 3, 2004
Messages
1,089
Age
45
Location
New Zealand
Website
www.projectitis.com
Hi all,

I decided to start a new thread with this. I have a large image (640x480) and I am moving it about the screen in geepee32.

Please note that I know I am using an emulator, and it only goes at 40MHz, and I also know that the image is larger than the framebuffer.


Using Mr Mirko SDK function I can get 1fps because it tries to process every pixel regardless if its on the srceen or not, and at 640x480, thats a lot of processing :) Also, the 1fps is constant, even if the sprite image is completely or mostly offscreen.

Code:
void gpd_drawCanvas( tGPD_canvas *canvas, short x, short y, u16 *framebuffer ){
     short xx,yy;
     int   i=0;

     for (yy=0; yy<canvas->height; yy++)
     for (xx=0; xx<canvas->width; xx++) gpd_setPixel(x+xx,y+yy,(u16)canvas->data[i++],framebuffer);
}

Using my own code, I first check whether the sprite is even on the screen at all, then I calculate an offset to start drawing the data if the sprite starts off the screen, and then I calculate a 'step' value to jump through the sprite data at the end of every line. In this way the sprite data is effectively 'cropped' before I try to draw it, and the function only processes those pixels that are actually drawn.

As well as this, since the pixels are all garanteed on the screen, I don't have to check (4 comparisons) that the pixel is on the screen before I draw it.

Using this method I get 7fps, and once the image moves closer to the edge of the screen, the framerate increases to about 20 or 30.

Code:
void gpd_drawCanvasFast( tGPD_canvas *canvas, short x, short y, u16 *framebuffer ) {
	unsigned short xx,yy;
	unsigned short vw,vh;
	unsigned short loff;
	unsigned long doff;
	unsigned short px,py;
	short tx,ty;
     
	// If canvas is off the screen, exit
	if ((x>=LCD_WIDTH) || (y>=LCD_HEIGHT)){ return; }
	tx = x+canvas->width; ty = y+canvas->height;
	if ((tx<0) || (ty<0)){ return; }
	// Positional offsets
  px = max( 0, x );
  py = max( 0, y );
  // Visible height
	if (y<0){ 
  vh = min( ty, LCD_HEIGHT );
	}else{
  if (ty>LCD_HEIGHT){
  	vh = canvas->height - (ty-LCD_HEIGHT);
  }else{
  	vh = canvas->height;
  }
  }
  // Visible width
	if (x<0){
  vw = min( tx, LCD_WIDTH );
	}else{
  if (tx>LCD_WIDTH){
  	vw = canvas->width - (tx-LCD_WIDTH);
  }else{
  	vw = canvas->width;
  }
  }
  // Data offset
  doff = ( canvas->width * max( 0, -y ) ) + max( 0, -x );
  // Line offset
  loff = max( 0, -x ) + max( 0, (tx-LCD_WIDTH) );

  // Draw visible pixels only
	for ( yy=0; yy<vh; yy++ ){
  for ( xx=0; xx<vw; xx++ ){
  	gp_drawPixel16(px+xx,py+yy, (u16)canvas->data[doff++], framebuffer ); //No bound checking
  }
  doff += loff;
	}
}

The reason for this is because Mr Mirko's sprite routines will work almost as fast with small sprites that are completely (or mostly) within the screen area, whereas my routine will work well with any, but especially larger images.

The question is:
Can anyone see how I can improve this routine, or can anyone with the knowledge convert this to assembler :)

Cheers,
Pea
 
This is amazing. I now use memcpy to copy complete rows at a time (but they are cropped first).

The following code works in geepee32, but is VERY bizarre - it has some big bugs because I just converted it to use rotated sprite data, and this isn't exactly right yet, so there is a split about 2/3 way down the screen, and the top half of the image is at the bottom, and the bottom half above!!

But I increased the frame rate to SEVENTY FIVE fps in geepee32! Amazing, unless my frame counter has gone whack. This is 10 times faster than my last attempt, and 75 times faster than the MrMirko original.

Code:
void gpd_drawCanvasFast2( tGPD_canvas *canvas, short x, short y, u16 *framebuffer ) {
	unsigned short xx,yy;
	unsigned short vw,vh;
	unsigned long doff;
	unsigned long foff;
	unsigned short px,py;
	short tx,ty;
     
	// If canvas is off the screen, exit
	if ((x>=LCD_WIDTH) || (y>=LCD_HEIGHT)){ return; }
	tx = x+canvas->width; ty = y+canvas->height;
	if ((tx<0) || (ty<0)){ return; }
	
	// The rest of the coordinates are calculated based on a screen rotation
	// of 90 degrees clockwise. The canvas data should already be rotated to match.
	// Positional offsets
  px = max( 0, y );
  py = max( 0, x );
  // Visible width
	if (y<0){ 
  vw = min( ty, LCD_HEIGHT );
	}else{
  if (ty>LCD_HEIGHT){
  	vw = canvas->height - (ty-LCD_HEIGHT);
  }else{
  	vw = canvas->height;
  }
  }
  // Visible height
	if (x<0){
  vh= min( tx, LCD_WIDTH );
	}else{
  if (tx>LCD_WIDTH){
  	vh = canvas->width - (tx-LCD_WIDTH);
  }else{
  	vh = canvas->width;
  }
  }
  // Data offset
  doff = ( canvas->height * max( 0, -x ) ) + max( 0, -y );
  // Framebuffer offset
  foff = py*LCD_HEIGHT + LCD_WIDTH;

  // Draw visible pixels only
	for ( yy=0; yy<vh; yy++ ){
  memcpy( framebuffer+foff, &canvas->data[doff], vw*2 );  
  doff += canvas->height;
  foff += LCD_HEIGHT;
	}
}

Once I iron the bugs out, I'll get MrMirko to add it to his SDK replacement, and I'll start on an ASM version.

Does anyone have know of a function (like memcpy) that copies 16bit numbers at once, and may be faster than memcpy?

Cheers,
Pea
 
pea posted on Dec 3 2004 at 07:23 AM said:
The following code works in geepee32

Moral of the story, don't EVER EVER EVER measure the speed of your code using geepee32, to say it is inaccurate is an understatement. :p

pea posted on Dec 3 2004 at 07:23 AM said:
Does anyone have know of a function (like memcpy) that copies 16bit numbers at once, and may be faster than memcpy?

It scares me that anyone would ask such a question without being able to write it themselves, but anyway... this is from my mp3 player source:

Code:
void mp3DisplayCopy(unsigned short *destination, unsigned short *source)
{
	int x;

	for (x = 0; x < (320 * 240); x++)
	{
  *destination = *source;
  destination++;
  source++;
	}
}
 
Last edited by a moderator:
RobertJ posted on Dec 3 2004 at 12:11 PM said:
[Moral of the story, don't EVER EVER EVER measure the speed of your code using geepee32, to say it is inaccurate is an understatement. :p

True. GeePee32 is fairly inaccurate regarding speed and it's results are variable.

RobertJ posted on Dec 3 2004 at 12:11 PM said:
It scares me that anyone would ask such a question without being able to write it themselves, but anyway... this is from my mp3 player source:

Code:
void mp3DisplayCopy(unsigned short *destination, unsigned short *source)
{
	int x;

	for (x = 0; x < (320 * 240); x++)
	{
  *destination = *source;
  destination++;
  source++;
	}
}

Well, I'm not sure this method is faster than a memcpy... This is a very simple and non optimized one. But I don't really think you can do better than memcpy on that (and the same problem appears here, you can't copy the whole screen because it won't fit in the frame buffer, you still have to copy line by line).
 
Last edited by a moderator:
If the moving (background) image is bigger than the visible are why not using the hardware scrolling capabilities of GP32?? That would like take few CPU cycles to move 640x480 image around...
 
mr.spiv posted on Dec 3 2004 at 03:07 PM said:
If the moving (background) image is bigger than the visible are why not using the hardware scrolling capabilities of GP32?? That would like take few CPU cycles to move 640x480 image around...

Is it possible to use these capabilities in pure C? or do we need to code it in assembler?
And what is the bigest image size you could scroll that way?
I must admit I'm very interested in these type of tricks but I don't want to have to learn ARM for the moment.
 
Last edited by a moderator:
Moral of the story, don't EVER EVER EVER measure the speed of your code using geepee32
Why is that the moral of the story? I don't get it...

It scares me that anyone would ask such a question without being able to write it themselves, but anyway... this is from my mp3 player source:
Sorry, I think you misunderstand me, I can easily write a function like you have just given me, but like matkeupon says, its obviously slower than memcpy itself. I thought perhaps someone knew of an existing, optimised function such as memcpy16 or something like that. p.s. Don't be so condescending :)

If the moving (background) image is bigger than the visible are why not using the hardware scrolling capabilities of GP32
I never even knew these existed! Thanks for the tip Mr. Spiv, I'll start looking that up right away. I'm new to coding for the GP, and am receiving my GP in a few weeks (my wife has wrapped it and I'm not allowed to open it until christmas) hence only testing using geepee32 at the moment. I am also fairly new to c - I am/was a programmer by occupation, but used mostly delphi and other pure OO languages (now I make online games and animations in flash - and no, I'm not old, I was only married last month :) ).

Cheers,
Pea
 
Here is the updated code that now seems to display correctly! Note that even though the sprite data is rotated (canvas->data) the width and the height (canvas->width and canvas->height) are still the width and the height of the un rotated sprite.

Question: If you scroll left and right, the image seems to scroll consistantly at 70+ fps, but if you move up or down by an ODD amount of pixels, the scrolling slows to about 11fps!!! Does anyone know why this happens?

Also, sometimes the scrolling will slow to 40fps for no apparent reason for a second or so. Maybe its the emu...

Code:
// Draw the canvas to the screen at specified location
// x,y are in normal (non rotated) coordinates from top left
void gpd_drawCanvasFast2( tGPD_canvas *canvas, short x, short y, u16 *framebuffer ) {
	unsigned short xx,yy;
	unsigned short vw,vh;
	unsigned long doff;
	unsigned long foff;
	unsigned short px,py;
	short tx,ty;
	unsigned short ROT_LCD_WIDTH = LCD_HEIGHT;
	unsigned short ROT_LCD_HEIGHT = LCD_WIDTH;
     
	// If canvas is off the screen, exit
	if ((x>=LCD_WIDTH) || (y>=LCD_HEIGHT)){ return; }
	tx = x+canvas->width; ty = y+canvas->height;
	if ((tx<0) || (ty<0)){ return; }
	
	// The rest of the coordinates are calculated based on a screen rotation
	// of 90 degrees clockwise. The canvas data should already be rotated to match.
	ty = y;
	y = x;
	x = ROT_LCD_WIDTH - ty - canvas->height;
	tx = x+canvas->height; ty = y+canvas->width;
	
  // Positional offsets
  px = max( 0, x );
  py = max( 0, y );
  // Visible height
	if (y<0){ 
  vh = min( ty, ROT_LCD_HEIGHT );
	}else{
  if (ty>ROT_LCD_HEIGHT){
  	vh = canvas->width - (ty-ROT_LCD_HEIGHT);
  }else{
  	vh = canvas->width;
  }
  }
  // Visible width
	if (x<0){
  vw = min( tx, ROT_LCD_WIDTH );
	}else{
  if (tx>ROT_LCD_WIDTH){
  	vw = canvas->height - (tx-ROT_LCD_WIDTH);
  }else{
  	vw = canvas->height;
  }
  }
  // Data offset
  doff = ( canvas->height * max( 0, -y ) ) + max( 0, -x );
  // Framebuffer offset
  foff = py*ROT_LCD_WIDTH + px;

  // Draw visible pixels only
	for ( yy=0; yy<vh; yy++ ){
  memcpy( framebuffer+foff, &canvas->data[doff], vw*2 );  
  doff += canvas->height;
  foff += ROT_LCD_WIDTH;
	}
}

Cheers,
Pea
 
If its not too much to ask, would someone be able to download the zip (fxe and gif) and run it on an actual gp32 to see what the speed and consistancy is like?

http://www.pea.co.nz/gp32/gpd_downloads.php

It should say 'init ok' and then wait a few seconds while it loads the gif from the smc, and then it should display an image. You can scroll it with the D-pad.

On geepee32, if you scroll L or R its ok, but if you go U or D by an odd number of pixels, and then start scrolling L or R again, the fps drops to a very slow rate.

p.s. What I have forgotten to mention in all of this, is that the CPU speed is set to 33MHz so that the limitation of the emulator wouldn't be as bad.

Cheers,
Peter
 
GpDesktop will be awesome if it ever happens! I was thinking of making
practically the same thing, but C code scares me. I have made little things that are
only slightly more complex than a "Hello World" thingy, but not anything else.

EDIT: A little note. Once you get past the top of the screen, it smears.
Should just have it stop doing anything at that point. BTW, It only goes
about 25 fps at the most on a real GP. (the version on your site that is).

I've seen some pretty fast JPEG viewers. You might not need to code
this part at all. Gif is probably out there, too.
 
Thanks Blah.

re: The smearing is because whenever the sprite is drawn, the screen isn't cleared first. This means that the line of pixels that is the top of the image is still there when the image is drawn one pixel lower. If this keeps happening, it looks like a smear :)

re: Gif and jpg - I have converted a gif routine to use MrMirkos sdk. Once it loads a gif, it decompresses it and stores it in memory in exactly the same way as a standard sprite. Then all you need are standard blitting routines to draw it (like any other graphic).

So it's working ok on your GP32? Is the 25fps pretty consistant, or is it jumpy? Remeber, its only running at 33MHz :)
 
pea posted on Dec 3 2004 at 08:10 PM said:
Sorry, I think you misunderstand me, I can easily write a function like you have just given me, but like matkeupon says, its obviously slower than memcpy itself. I thought perhaps someone knew of an existing, optimised function such as memcpy16 or something like that. p.s. Don't be so condescending :)
Um, forgive me for being so condescending, especially if I am about to be again. :)

Why is it obviously slower than memcpy itself? C isn't an interpreted language, it is compiled into ASM, and I can't think of anything easier for a modern compiler to optimise when compiling than the simplest of loops.
 
Last edited by a moderator:
If I'm not mistaken the memcpy is directly written in asm, so it's obviously faster than any c routine. Your c code inside the loop can be reduced to one line, and it won't make the compiler use ldms, nor copy 32 bit chunks.
 
Back
Top