GP32 Trouble With My Bitblt Function's Speed


generalnmx

Playful/Fascist Mod
Joined
Apr 18, 2003
Messages
2,128
Age
43
Location
Maryland, USA
Website
www.matts-hosting.com
My BitBlt function runs VERY slow compared to Gamepark's SDK version (my FPS drops from about 9 to 1). Do I just need to go down to ASM, or is there anything else I can do to get a sizable speed increase? I'm still using Gamepark's SDK for flipping the surface, of course.

Code:
/* Creates a single pixel on the screen with the given colour 
 * x  	on screen
 * y  	on screen
 * colour  16-bit colour of pixel
 */
void_ IGfxDrawPixel(const_int32 x, const_int32 y, const_ushort16 colour)
{
	ushort16 *fbuffer;
	if ( !((x<0) || (x>319) || (y<0) || (y>239)) ) { /* check bounds */
  fbuffer = (ushort16*)(gpFramebuffer[pgFlip].ptbuffer); /* the framebuffer is actually 16-bit in 16-bit mode */
  *(fbuffer + (x*240) + (239-y)) = colour;	/* the framebuffer is structured oddly, so we need to index this way */
	} /* end if */
} /* end of IGfxDrawPixel() */

/* Draws a single image from inside an array of (an) image(s) on the screen without transparency
 *
 *      pixelArray              array of pixel data (where the image is stored)
 *  indX    	index into array for X (where to find the image in the array)
 *  indY    	index into array for Y
 *  xScreen    	x on screen
 *  yScreen    	y on screen
 *  imageWidth    width of the image inside the array (use arrayWidth and arrayHeight for total array size)
 *  imageHeight    height of the image inside the array
 *      arrayWidth    width of pixel array (total size of array is width * height)
 *      arrayHeight    height of pixel array
 *  transColour    16-bit colour of transparency (bit to skip)
 */
void_ IGfxDrawImage(ushort16* pixelArray,
    	const_int32 xScreen, const_int32 yScreen,
    	const_uint32 indX, const_uint32 indY,
    	const_uint32 imageWidth, const_uint32 imageHeight,
    	const_uint32 arrayWidth, const_uint32 arrayHeight)
{
	uint32 x,y;
	ushort16 colour;
	if (pixelArray) /* sanity check */
	{
  for (x = 0; x < imageWidth; x++) {
  	for (y = 0; y < imageHeight; y++) {
    colour = *(pixelArray+ ((indX+x) + ((indY+y) * arrayWidth)));
    IGfxDrawPixel(x+xScreen,y+yScreen,colour);
  	} /* end for */
  } /* end for */
	} /* end if */
} /* end of IGfxDrawImage() */
 
One main cause for the slowdown is that you're using 16 bit access to the memory, while the memory is 32 bit in hardware, so the processor needs to do something like read the ram, modify it with `and' and `or', maybe shift some data and write it back. So for writing one memory value (32 bit) you'll need like 4 accesses instead of one.

I had a similar problem with the my font library in SvOrbis, only that I was using 8 bit colors and the main showstopper wasn't speed but busload that croaked up sound (-> GP32 sound bug). I added a new drawtext routine that needed to paint the letters in 4 byte/pixel boundries. Boy, was that a speed up, it solved most of my problems.

Change the `ushort16' to something like `ulong32' or whatever you call an unsigned long/unsigned int and copy two pixels at the same time. This will give you some more speed, but you don't get to the performance of the SDK. I'm not sure on how they do it. If we were on a dedicated graphics chip, I'm sure they would use something like a two dimensional DMA (also known as `blitter'), but I'm pretty sure that's not available in the ARM cpu of the GP32.

Greetings,
SvOlli
 
Code:
transvalue2=transvalue<<16;
in=read32(data);
if (in=(transvalue|transvalue2)) continue; // both transparent
if (((in&0xffff0000)==transvalue2) || ((in&0xffff)==transvalue)) {
  // one of them transparent, get original
  in2=read32(origdata);
  if ((in&0xffff)==transvalue){
  // second pixel transparent, patch in old color
   in=(in&ffff0000)|(in2&ffff);
  } else {
  // first pixel transparent, patch in old color
   in=(in&ffff)|(in2&ffff0000);
 }
}
write32(origdata,in);

something like this should help
 
I've got some asm routines, if you want to convert them to 16bit (which is easy). Tell me if you're interested.
 
I've got some asm routines, if you want to convert them to 16bit (which is easy). Tell me if you're interested.

Sure, I'll give it a shot. Post it here or PM / Email me :)

With Kuwanger's help, I was able to seriously reduce the math in the two for loops, and now I have near Gamepark SDK speed. Right now a good bit of my slowdown is from my tilemap loops, which I sorely need to optimize too :)

Again with Kuwanger's help, we tried making a 32-bit routine, but there were some bad alignment issues.
 
"Every time I plot a pixel I call a function, and for some reason it's incredibly slow! How come?"

*sigh* :)
 
1) don't call a function for every pixel, the branch is slow (ARM9 has no branch prediction, and a 5 stage pipeline it needs to clear), and GCC saves r0-r2, r14 on the stack. Use inline if you MUST

2) with every pixel you plot you calculate the offset of that pixel in memory, using a mul. That stoopid! You simply calc the offset of the first pixel you draw in memory, and you can safely assume the next pixel you draw is right next to it. So if you draw a line of pixels you can simply make an inner loop like *(mem++)=pixel;

Well, optimizing in C is stupid anyway. If you really want to get this fast you have to write something in asm. A couple of tips:

1) look out for data hazards. ARM9 has no operating forwarding so

ADD r1,r2,r3
MOV r4,r1

Takes 3 cycles instead of 2, because the second instruction uses the result of the first. On the ARM7 (gba) this isn't a problem, but you have to watch out for this on the ARM9 (gp32)

2) don't draw horizontal lines. The memory is oriented rotated at a 90 degree angle, so you should draw VERTICAL lines, to make sure every consecutive write you do is at a consecutive memory adress. Also make sure your sprite data is oriented like that so that you don't get cache misses.

3) as said, branch instructions are the devil and should be avoided as much as possible. Since you never draw lines of >240 pixels (screen height) you can unroll a single pixel plot 240 times and simply skip the first 240-n plots by adding the number of instructions you're unrolling times 4 times 240-n to the PC. You can unroll something using the .REPT and .ENDR facility in GAS. Make sure you don't overdo the unrolling shit, because there's only 16k code cache, and cache misses are really pricey. But, if you draw all sprites at once most of the unrolled code will persist in code cache and you will only get a lot of cache misses on the first draw.

Well that's enough for today, I'm going to get something to eat.

ps: it might be an idea to store your sprites not as a bunch of pixels, but as a bunch of spans. That way you can avoid testing every pixel for transparency.
 
Inopia, I have a question:

Are you sure this
ADD r1,r2,r3
MOV r4,r1
takes 3 cycles ??? I know about the load-use interlock, which makes an instruction take 2 more cycles if one of the registers was loaded with the previous instruction (with a LDR, LDRB or LDRH). But I've never read anything like this...
 
"Every time I plot a pixel I call a function, and for some reason it's incredibly slow! How come?"

*sigh*

Yeah yeah I know, but removing the function call didn't give me any noticable speed increase ;)

Anyway, let me show everyone the function that me and Kuwanger came up with:


Code:
/* Draws a single image from inside an array of (an) image(s) on the screen with transparency
 *
 *      pixelArray              array of pixel data (where the image is stored)
 *  indX    	index into array for X (where to find the image in the array)
 *  indY    	index into array for Y
 *  xScreen    	x on screen
 *  yScreen    	y on screen
 *  imageWidth    width of the image inside the array (use arrayWidth and arrayHeight for total array size)
 *  imageHeight    height of the image inside the array
 *      arrayWidth    width of pixel array (total size of array is width * height)
 *      arrayHeight    height of pixel array
 *  transColour    16-bit colour of transparency (bit to skip)
 *
 * The framebuffer is the same orientation as the screen, which is rotated 90 degrees clockwise. So
 * any images passed into this function must be rotated 90 degrees (IGfxImageFix should do that)
 *
 * As said before, this function reads the images rotated. It starts 
 *
 * Mostly thanks to Kuwanger for this code :)
 */
boolean IGfxDrawImageT(ushort16* pixelArray,
      int32 xScreen, int32 yScreen,
      uint32 indX, uint32 indY,
      uint32 imageWidth, uint32 imageHeight,
      uint32 arrayWidth, uint32 arrayHeight,
      ushort16 transColour)
{
	ushort16 *src, *dst;
	int32 x, y, half_height, nextX_dst, nextX_src;

	// Verify it's on screen
	if (xScreen > 319 || yScreen > 239)
  return (false);
 
	// Verify we have some sane values
	if (indY < 0 || indX < 0 || imageWidth < 1 || imageHeight < 1 ||
  indY+imageHeight > arrayHeight || indX+imageWidth > arrayWidth)
  	return (false);

	// Fix one corner
	if (xScreen < 0) {
  indX -= xScreen;
  imageWidth += xScreen;
  xScreen = 0;
	} // end if

	if (yScreen < 0) {
  indY -= yScreen;
  imageHeight += yScreen;
  yScreen = 0;
	} // end if
 
	// And avoid going past screen
	if (xScreen + imageWidth > 319)
  imageWidth = 319 - xScreen;
 
	if (yScreen + imageHeight > 239)
  imageHeight = 239 - yScreen;

	src = (pixelArray + (arrayHeight - (indY + imageHeight)) + indX * arrayHeight);
	dst = (drawBuffer + (240 - (yScreen+imageHeight)) + xScreen * 240);
 
	nextX_dst = 240 - imageHeight;
	nextX_src = arrayHeight - imageHeight;
	for (x = 0; x < imageWidth; x++) {
  for (y = 0; y < imageHeight; y++, dst++, src++) {
  	if (*src != transColour)
    *dst = *src;
            } // end for y
            //Next line
            dst += nextX_dst;
            src += nextX_src;
        } // end for x
} /* end of IGfxDrawImageT() */
 
I'm told the ARM9 can suffer from data hazards. I'll read the technical reference manual to be 100% sure.
 
SvOlli posted on Aug 21 2004 at 07:58 AM said:
One main cause for the slowdown is that you're using 16 bit access to the memory, while the memory is 32 bit in hardware, so the processor needs to do something like read the ram, modify it with `and' and `or', maybe shift some data and write it back. So for writing one memory value (32 bit) you'll need like 4 accesses instead of one.

I had a similar problem with the my font library in SvOrbis, only that I was using 8 bit colors and the main showstopper wasn't speed but busload that croaked up sound (-> GP32 sound bug). I added a new drawtext routine that needed to paint the letters in 4 byte/pixel boundries. Boy, was that a speed up, it solved most of my problems.

Change the `ushort16' to something like `ulong32' or whatever you call an unsigned long/unsigned int and copy two pixels at the same time. This will give you some more speed, but you don't get to the performance of the SDK. I'm not sure on how they do it. If we were on a dedicated graphics chip, I'm sure they would use something like a two dimensional DMA (also known as `blitter'), but I'm pretty sure that's not available in the ARM cpu of the GP32.

Greetings,
SvOlli

that sounds nce, are those kind of optimizations already in the MirkoSDK?
 
Last edited by a moderator:
Back
Top