GP32 Partially Offscreen Sprites


Hi guys,

Thanks for your routines Oankali. My implementations are based on trying to ellimintae all conditional statements from the drawing loop (no if...then or case statements) as these are very slow.

I have been looking at implementations for alpha blitting, and a lookup table to elliminate part of the muliplications/division is indeed a known and fast way to do it. However, tri-dimensional tables consume way too much memory. EDIT: ok, 32k isn't too bad. The article I read was based on 24bit colour, which means a 16MB lookup table. 32k is somewhat high, but not bad. Even if we halved the alpha resolution (16 steps) I think the effect would not be that noticeable and you could use a 16k table...

link:
http://www.gamedev.net/reference/articles/article1594.asp
 
Wow, lots of code ideas, excellent! I'd definately say that a 32k lookup table isn't too big of a price to pay for way faster alpha blending speed. And Synchro, yeah, I'd say it'd be nice to be able to load the data from a PNG, and then just mash it into a 20 bit colour space (5bits for each of rgba). If prerotation is necessary, we could have the loading functions do the rotation on file load. Then any tool that can produce a PNG can produce sprites for someone's apps. :)

Pea: as for the version of your blitting code I'm using, it currently doesn't handly non-pre-rotated sprites properly, but it does work (just sprites are rotated 90 degrees CCW). If you really want it back with my minor changes, I'll just post it onto this thread.



I think what I'm going to do is release the 0.1 version of my lib using only the default Mirko SDK drawing functions, then as we hash out these other ones, I'll add them to future releases. I'll try to get that up tonight.
 
@pea: if conditions are not as slow as modulo/division, it's even better you use an if-statement instead of a modulo operator.

@unit3: the function I posted before is for pre-rotated data...

okay, here is my function with constant alpha and transparency, it uses pre-rotated data and the MirkosSDK header and real screen coordinates, alpha 0-31. no real division but some left shifts...

Code:
#define	GETR(x)	( ((x) >> 11) &	0x1F )
#define	GETG(x)	( ((x) >> 6)  &	0x1F )
#define	GETB(x)	( ((x) >> 1)  &	0x1F )

*/
void syn_drawSpriteTB(u16 *sprite, short put_x, short put_y, u16 *framebuffer, u16 trans, u8 alpha)
{
  SHEADER	*sheader = (SHEADER*) sprite;
	u8  	r,g,b;

	unsigned short	xx, yy,
        	pstart_x = 0,        // pixel start position (sprite)
        	pstart_y = 0,
        	pend_x = sheader->size_x,  // pixel end position (sprite)
        	pend_y = sheader->size_y,
        	*fb, fb_div,        // framebuffer
        	*i, i_div;        	// sprite address and offset

	// calculate pixel position
	short	start_x = put_x,
    end_x   = start_x + sheader->size_x,
    start_y = put_y,
    end_y   = start_y + sheader->size_y;

	// check and calculate off screen boundaries
	if (start_x > 319) return;
	if (start_x < 0)
	{
  pstart_x = (u16)(0-start_x);
  start_x  = 0;
	}
	if (end_x > 319)
	{
  pend_x -= (end_x-320);
  end_x   =320;
	}
	if (end_x < 0) return;

	if (start_y > 239) return;
	if (start_y < 0)
	{
  pstart_y = (u16)(0-start_y);
  start_y  =0;
	}
	if (end_y > 239)
	{
  pend_y -= (end_y-240);
  end_y=240;
	}
	if (end_y < 0) return;

	// framebuffer start position and offset
	fb     = start_x * 240 + start_y + framebuffer;
	fb_div = 240 - (end_y - start_y);
	// sprite position and offset
	i      = sheader->size_y * pstart_x + pstart_y + sprite + 6;
	i_div  = sheader->size_y - (pend_y - pstart_y);
	if(alpha>31) alpha = 31;

	// draw pixel
	for (xx = start_x; xx<end_x; xx++)
	{
  for (yy = start_y; yy<end_y; yy++)
  {
  	if(*i != trans)
  	{
    //r = (GETR(*(fb)) + GETR(*i)) >> 1;
    //g = (GETG(*(fb)) + GETG(*i)) >> 1;
    //b = (GETB(*(fb)) + GETB(*i)) >> 1;

    r = ((alpha * (GETR(*i) - GETR(*fb))) >> 5) + GETR(*fb);
    g = ((alpha * (GETG(*i) - GETG(*fb))) >> 5) + GETG(*fb);
    b = ((alpha * (GETB(*i) - GETB(*fb))) >> 5) + GETB(*fb);

    *fb = GP32RGB(r,g,b);
  	}
  	i++;
  	fb++;
  }
  // next line
  fb += fb_div;
  i  += i_div;
	}
}
 
synkro - I agree that if..then is faster than division, which is why I also don't use division! If division is necessary I use inverse multiplication, or try to always divide by a power of 2, which is just a left shift.

unit3 - All my routines are going to be for pre-rotated data. I just need the code that you got working because you said you had to make some changes for it to work properly (with max and min etc).

re: PNG - I looked at getting a PNG library for GP32, but was beyond me at that stage. I got GIF going ok, and now pretty much know the structure of a GIF inside out! All my routines use GIF, but I would much rather use PNG.
 
Well, last night I've been trying the lookup table trick, and this formula:

Code:
red = alpha[opacity][GetRGBRed(*screen)][GetRGBRed(*bmp)]
seems to be ~4.5x faster then this one:

Code:
red = ((GetRGBRed(*bmp) * opacity) + (GetRGBRed(*screen) * (31 - opacity))) / 31;
and is very easy to implement.

@Syncro: I know that divisions are lot slower than right shifts, but because I wanted accurate values for my font engine I preferred to use / 31 instead of >> 5. If you watch carefully at the result on the screen with / 31 and >> 5 you will see that the result is not the same.
But now with the lookup table, this problem doesn’t apply.

@Pea: The case statement in my loops are because my font engine allows the programmer apply different effects on the font in real time. But when I need only one type of effect I don't use any case statement.

---

Well, I’ve been all the morning trying different settings and ways to implement the lookup table.
I think we should agree on the structure for the lookup table for those who want to use it as it would be interesting to be able to share the same lookup table among the different libraries, to save memory usage.

I have found that this structure:

Code:
unsigned char alphaLevels [opacity][bottomPixel][topPixel];
is the most versatile approach as it allows to have different precision lookup tables without having to change your code, as you can do things like that:

Code:
typedef enum { aph32Levels, aph16Levels, aph8Levels, aph4Levels } ALPHALEVELS;

unsigned char alpha4[4][32][32];
unsigned char alpha32[32][32][32];

void InitAlphaTable(unsigned char (*alpha)[32][32], int precision)
{
  int bottom, top, level, max;

  max = (32 >> precision) - 1;
  for (level=0; level < (32 >> precision); level++)
    for (bottom=0; bottom < 32; bottom++)
      for (top=0; top < 32; top++)
        alpha[level][bottom][top] = ((top * level) + (bottom * (max - level))) / max;
}

void BlendScreenBitmap(GPDRAWSURFACE *ptgpds, unsigned char *bitmap,
                       int opacity, unsigned char (*alpha)[32][32], int precision)
{
  int i, j;
  unsigned short *bmp, *screen;
  int red, green, blue;

  // Fade bitmap
  screen = (unsigned short *) (ptgpds->ptbuffer);
  bmp = (unsigned short *) bitmap;
  for (j=0; j < GPC_LCD_PHYSICAL_H; j++)
  {
    for (i=0; i < GPC_LCD_PHYSICAL_W; i++, screen++, bmp++)
    {
      // Merge color with background
      red   = alpha[opacity >> precision][GetRGBRed  (*screen)][GetRGBRed  (*bmp)];
      green = alpha[opacity >> precision][GetRGBGreen(*screen)][GetRGBGreen(*bmp)];
      blue  = alpha[opacity >> precision][GetRGBBlue (*screen)][GetRGBBlue (*bmp)];

      // Put pixel
      *screen = RGB(red, green, blue);
    }
  }
}

void Test(void)
{
  // 4 levels of alpha blending
  BlendScreenBitmap(&gtSurface[giSurface], (unsigned char *) bitmap_bitmap,
                    15, alpha4,  aph4Levels);
  SurfaceFlip();
  while (!GpKeyGet());
  while (GpKeyGet());
  
  // 32 levels of alpha blending
  BlendScreenBitmap(&gtSurface[giSurface], (unsigned char *) bitmap_bitmap,
                    15, alpha32, aph32Levels);
  SurfaceFlip();
  while (!GpKeyGet());
  while (GpKeyGet());
}

void GpMain (void * arg)
{
  InitGraphics(16);

  InitAlphaBlending(aph4Levels);
  InitAlphaBlending(aph32Levels);

  Test();

  GpAppExit();
}

That way, it would be possible to use in our functions a lookup table prepared by another library.
I wonder if it’s not possible to prepare a more accurate lookup table than mine, using more precise algorithms, because I find mine too simple :)

Oankali.
 
unit3 - All my routines are going to be for pre-rotated data. I just need the code that you got working because you said you had to make some changes for it to work properly (with max and min etc).

re: PNG - I looked at getting a PNG library for GP32, but was beyond me at that stage. I got GIF going ok, and now pretty much know the structure of a GIF inside out! All my routines use GIF, but I would much rather use PNG.

Ahh, I see. Well, I've just put my lib on Sourceforge, and your (modified) function is in there, as well as my little benchmarking app. Go test it out! :)

As for PNG, I had libpng compiling before, but it was part of a too ambitions project for porting ffmpeg that never went anywhere, so I never tested it standalone. After I've hit some more of my targets for the lib regarding file access, I'll look at working with it again.
 
Last edited by a moderator:
unit3 - I have a very cut down version of libpng which only includes the bare minimum for png loading (all formats) which is called glpng (for loading openGL textures). This looks very simple, and is a much smaller footprint than the entire lib png, and I am working on this at the moment.

oankali - re:case statement. It is more code, but much faster to place the case statement outside the main loop than inside it. That way you check for which effect to render the text with, and then run the corresponding render loop with the right function call in it.

Great result on the lookup table BTW...
 
I dunno how PNG works at the moment, but keep in mind that the data overhead can cause a slowdown. Oankali's way is (at moment complicated) but having RLE raw data of the bitmap and the alphamask sounds good to me.

The LUT is great! I think the size is acceptable!

@pea: okay, I see your point. I think we should first have a working solution and optimize it afterwards.

@Oankali: what do you mean by "precision" ? in 16 bit mode we can have only an alpha granularity of 5bit (0-31) anyway. I can't see any difference between >>5 and / 31, is that an saturation issue?!
 
@Oankali: what do you mean by "precision" ? in 16 bit mode we can have only an alpha granularity of 5bit (0-31) anyway. I can't see any difference between >>5 and / 31, is that an saturation issue?!

The maximum alpha granularity we can have is 32 levels, but you can have less if you want. In my example there is 2 LUT with different precision (well, I don’t know if it’s the correct word). One LUT with 32 alpha blending levels (32KB), and a smaller one with 4 alpha blending levels (4KB).
With this structure you can use either of the 2 LUT without changing your function.
Not all the programs will need 32 levels and will need more memory for other tasks.
For example, font antialiasing in Windows 2000 uses only 4 levels.
BTW I have noted that 4 levels LUT is faster then 32 levels LUT. I don’t know why.

Look at this 2 formulas, the result is not the same.

Code:
Using / 31

red = ((GetRGBRed(*bmp) * opacity) + (GetRGBRed(*screen) * (31 - opacity))) / 31;

red = ((31 * 31) + (31 * (31 - 31))) / 31 = 31
red = ((31 * 0 ) + (31 * (31 - 0 ))) / 31 = 31


Using >> 5 (/ 32)

red = ((GetRGBRed(*bmp) * opacity) + (GetRGBRed(*screen) * (31 - opacity))) >> 5;

red = ((31 * 31) + (31 * (31 - 31))) / 32 = 30.03125 = 30
red = ((31 * 0 ) + (31 * (31 - 0 ))) / 32 = 30.03125 = 30
 
Last edited by a moderator:
I know that different results will come out whether what formula I use, I meant that I don't see any difference on screen. the speed loss could be a cache issue: on the first attempt of reading, the smaller LUT can nearly completly loaded into the cache while the bigger LUT may cause multiple cache misses and flushes... that's just my guess ...

For fading effect a full 5bit LUT is needed, but for simple blending a smaller LUT or just simple 50% blending would do the job ...
 
Hi guys,

Finally got round to coding my LUT based implementation of the alphaBlended, Pre-rotated sprite routines. Please note that the following function uses my 'tGP_canvas' structure, which has a width, height and pointer to the data. Basically something like this (I cut it down a bit):

Code:
typedef struct tGP_canvas{
	unsigned short width;
	unsigned short height;
	unsigned short *data;
}tGP_canvas;

To convert this for Mr.Mirkos SDK, remember that the destination canvas->data is just the back-framebuffer, and canvas->width and canvas->height are 320 and 240 respectively.

Also note that the source canvas (sprite) is pre-rotated, but the width and height are still as they would be before rotation. In fact, this is true for all canvases.

My implementation uses an LUT broken down into 1024 byte chunks (one for each alpha level) in an attempt to speed up the routine by allowing the LUT chunk to be cached (as synchro hinted at).

Anyway, it should be pretty damn fast. Can this be benchmarked using the same program unit3, so that we can compare results? I have cut down all multiplication to a minimum, and don't even use it for the index into the LUT (use shifts, but reduced number).

Code:
typedef struct tGP_alphaLUT{
	unsigned char values[1024];
}tGP_alphaLUT;

static tGP_alphaLUT gp_alphaLUT[32];

void gp_createAlphaLUT( void ){
	tGP_alphaLUT *level;
	char a,d,s;

	// Create alpha look up table. The look up table is implemented
	// as a series of shorter tables in the hope that it is cached.
	for (a=0; a<32; a++){ // alpha
  level = &gp_alphaLUT[a];
  for (s=0; s<32; s++){ // source
  	for (d=0; d<32; d++){ // destination
    level->values[(s<<5)+d] = ((s*a)+(d*(31-a)))/31;
  	}
  }
	}
}

void gp_canvasPasteA( tGP_canvas *destination, tGP_canvas *source, short x, short y, unsigned char alpha ){
	// If alpha is max, render fast method
	if (alpha>30){
  gp_canvasPaste( destination, source, x, y );
  return;
	}
	// If no alpha, simply exit (none of source is rendered)
	if (alpha==0){
  return;
	}
	
	unsigned short xx,yy;
	unsigned short vw,vh;
	unsigned short *doff, *coff;
	short dstep, cstep;
	unsigned short px,py;
	short tx,ty;
	unsigned short scolour, dcolour;
	unsigned short rs, gs, bs;
	unsigned char rd, gd, bd;
	unsigned char ra, ga, ba;
	tGP_alphaLUT *level;
     
	// If rect is off the screen, exit
	if ((x>=destination->width) || (y>=destination->height)){ return; }
	tx = x+source->width; ty = y+source->height;
	if ((tx<0) || (ty<0)){ return; }
	
	// The rest of the coordinates are calculated based on a screen rotation
	// of 90 degrees clockwise.
	ty = y;
	y = x;
	x = destination->height - ty - source->height;
	tx = x+source->height; ty = y+source->width;
	
  // Positional offsets
  px = max( 0, x );
  py = max( 0, y );
  // Visible height
	if (y<0){ 
  vh = min( ty, destination->width );
	}else{
  if (ty>destination->width){
  	vh = source->width - (ty-destination->width);
  }else{
  	vh = source->width;
  }
  }
  // Visible width
	if (x<0){
  vw = min( tx, destination->height );
	}else{
  if (tx>destination->height){
  	vw = source->height - (tx-destination->height);
  }else{
  	vw = source->height;
  }
  }
  // Data offset
  doff = source->data + ( source->height * max( 0, -y ) ) + max( 0, -x );
  dstep = source->height - vw;
  // Canvas data offset
  coff = destination->data + (py*destination->height + px);
  cstep = destination->height - vw;
  
	// Get the row of the LUT we need
	level = &gp_alphaLUT[alpha];

  // Draw visible pixels only
	for ( yy=0; yy<vh; yy++ ){
  for (xx=0; xx<vw; xx++ ){
  	// Get source colour components pre-multiplied by 32 (for LUT)
  	scolour = *doff;
  	rs = (((scolour) >> 6) & 0x03E0);
  	gs = (((scolour) >> 1) & 0x03E0);
  	bs = (((scolour) << 4) & 0x03E0);
  	// Get existing (destination) colour components
  	dcolour = *coff;
  	rd = (((dcolour) >> 11) & 0x1F);
  	gd = (((dcolour) >> 6) & 0x1F);
  	bd = (((dcolour) >> 1) & 0x1F);
  	// Blend using LUT
  	ra = level->values[rs+rd];
  	ga = level->values[gs+gd];
  	ba = level->values[bs+bd];
  	// Combine colours and set
  	*coff = ((ra<<11)+(ga<<6)+(ba<<1));
  	coff++; doff++;
  }
  coff += cstep;
  doff += dstep;
	}
}

example (using my GIF loading routines too):

Code:
tGP_LCD *lcd;
tGP_canvas *titlescreen;
int res;

lcd = gp_lcdCreate( 85, 0xffff );
titlescreen = gp_canvasCreateFromGif( "dev0:\\gpmm\\mysprite.gif", 1, 0, &res );
gp_canvasPasteA( lcd->canvas, titlescreen, 0,0, 16 );
gp_lcdFlip( lcd );

EDIT: May need these
Code:
#define  max(a, b)   ((a) > (b) ? (a) : (b)) 
#define  min(a, b)   ((a) < (b) ? (a) : (b))
 
Well, inspired by the example of Pea, I've been benchmarking different solutions and I got very suprising results.
At the end you'll find the complete code of the winning solution.
But first, the strangest thing is that I got different results depending on the order I put the functions in the source, so I ended up compiling as many fxe as methods to try.

For each method I blit a full screen bitmap in the backbuffer 256 times with a varying opacity. I do it twice and compute the average.
The alpha LUT I use has this structure
Code:
unsigned char alpha32[32][32][32]
equivalent to Pea's alpha LUT (IMHO really to complicated).


Method 1 is the method I use currently.

Code:
// Method 1: 12114+11749 = 11931,5 ticks
unsigned char (*alpha)[32][32] = alpha32;

red   = alpha[opacity][(*screen >> 11) & 0x1F][(*bmp >> 11) & 0x1F];
green = alpha[opacity][(*screen >> 6)  & 0x1F][(*bmp >> 6)  & 0x1F];
blue  = alpha[opacity][(*screen >> 1)  & 0x1F][(*bmp >> 1)  & 0x1F];


Methods 2 to 4 are 3 different methods where I use Pea's solution to get only the needed portion of the alpha LUT.
3 methods with 3 different ways to multiply by 32: Pea's trick, my method, and a mix.
I'm sure you are as puzzled as me by the results :)

Code:
// Method 2: 12087+11986 = 12036,5 ticks
unsigned char *alpha = &alpha32[opacity][0][0];

red   = alpha[((*screen >> 6) & 0x03E0) + ((*bmp >> 11) & 0x1F)];
green = alpha[((*screen >> 1) & 0x03E0) + ((*bmp >> 6)  & 0x1F)];
blue  = alpha[((*screen << 4) & 0x03E0) + ((*bmp >> 1)  & 0x1F)];


// Method 3: 11521+11503 = 11512 ticks
unsigned char *alpha = &alpha32[opacity][0][0];

red   = alpha[((*screen >> 11) & 0x1F) * 32 + ((*bmp >> 11) & 0x1F)];
green = alpha[((*screen >> 6)  & 0x1F) * 32 + ((*bmp >> 6)  & 0x1F)];
blue  = alpha[((*screen >> 1)  & 0x1F) * 32 + ((*bmp >> 1)  & 0x1F)];


// Method 4: 12114+11751 = 11932,5 ticks
unsigned char *alpha = &alpha32[opacity][0][0];

red   = alpha[(((*screen >> 11) & 0x1F) << 5) + ((*bmp >> 11) & 0x1F)];
green = alpha[(((*screen >> 6)  & 0x1F) << 5) + ((*bmp >> 6)  & 0x1F)];
blue  = alpha[(((*screen >> 1)  & 0x1F) << 5) + ((*bmp >> 1)  & 0x1F)];


Method 5 was to try another way of using the LUT.
By it's structure I think it should be compared with method 3.

Code:
// Method 5: 12115+11752 = 11933,5 ticks
unsigned char (*alpha)[32] = &alpha32[opacity][0];

red   = alpha[(*screen >> 11) & 0x1F][(*bmp >> 11) & 0x1F];
green = alpha[(*screen >> 6)  & 0x1F][(*bmp >> 6)  & 0x1F];
blue  = alpha[(*screen >> 1)  & 0x1F][(*bmp >> 1)  & 0x1F];


Method 6 is Pea's method, using a lot more variables and no multiplications.

Code:
// Method 6: 12086+11987 = 12036,5 ticks
unsigned char *alpha = &alpha32[opacity][0][0];

// Get source colour components pre-multiplied by 32 (for LUT)
scolour = *screen;
rs = ((scolour >> 6) & 0x03E0);
gs = ((scolour >> 1) & 0x03E0);
bs = ((scolour << 4) & 0x03E0);
// Get existing (destination) colour components
dcolour = *bmp;
rd = ((dcolour >> 11) & 0x1F);
gd = ((dcolour >> 6) & 0x1F);
bd = ((dcolour >> 1) & 0x1F);
// Blend using LUT
ra = alpha[rs+rd];
ga = alpha[gs+gd];
ba = alpha[bs+bd];


Finally method 7 is Pea's method with some adjustments taking into account the results of method 3.

Code:
// Method 7: 11521+11504 = 11512,5 ticks
unsigned char *alpha = &alpha32[opacity][0][0];

// Get source colour components pre-multiplied by 32 (for LUT)
scolour = *screen;
rs = ((scolour >> 11) & 0x1F);
gs = ((scolour >> 6) & 0x1F);
bs = ((scolour >> 1) & 0x1F);
// Get existing (destination) colour components
dcolour = *bmp;
rd = ((dcolour >> 11) & 0x1F);
gd = ((dcolour >> 6) & 0x1F);
bd = ((dcolour >> 1) & 0x1F);
// Blend using LUT
ra = alpha[rs*32+rd];
ga = alpha[gs*32+gd];
ba = alpha[bs*32+bd];

Conclusion:

It seems that our GP32 sometimes prefers multiplication to left shifts. Not always as I have tried to change my RGB() macro without success.
Also it seems that is not a bad idea to prepare a pointer that points to a region of the alpha LUT.
In my opinion even if methods 3 and 7 gets the same result, method 3 uses less variables and is the winner as it's a lot simpler. And for a bunch of small bitmaps to blit, I'm sure method 3 would be faster then method 7.
Another thing to note: methods 3 and 7 seems to be more stable between cycles than the other methods (the cache ?).



And here is the complete function:

Code:
void Method3(GPDRAWSURFACE *ptgpds, unsigned char *bitmap, int opacity)
{
  int i, j;
  unsigned short *bmp, *screen;
  int red, green, blue;
  unsigned char *alpha;

  // Fade bitmap
  alpha = &alpha32[opacity][0][0];
  screen = (unsigned short *) (ptgpds->ptbuffer);
  bmp = (unsigned short *) bitmap;
  for (j=0; j < GPC_LCD_PHYSICAL_H; j++)
  {
    for (i=0; i < GPC_LCD_PHYSICAL_W; i++, screen++, bmp++)
    {
      // Merge color with background
      red   = alpha[((*screen >> 11) & 0x1F) * 32 + ((*bmp >> 11) & 0x1F)];
      green = alpha[((*screen >> 6)  & 0x1F) * 32 + ((*bmp >> 6)  & 0x1F)];
      blue  = alpha[((*screen >> 1)  & 0x1F) * 32 + ((*bmp >> 1)  & 0x1F)];

      // Put pixel
      *screen = RGB(red, green, blue);
    }
  }
}
 
Anyway, it should be pretty damn fast. Can this be benchmarked using the same program unit3, so that we can compare results? I have cut down all multiplication to a minimum, and don't even use it for the index into the LUT (use shifts, but reduced number).

Yep, I'll try to get to this in the next few days, but my day job is eating into my hobby programming time. ;)

If you think you have more free time than my, my little benchmarking app is included in my library, so you can go snag it from SourceForge and attempt to integrate it yourself. That being said, it's probably just easier for me to do it. :)

I'll probably just leave my test sprites non-prerotated, because it doesn't matter for benchmarking purposes if they get drawn to screen rotated 90 degrees CCW. All we really care about is the drawing speed with lots and lots of sprites.

@0ankali: Excellent work, man! I'm excited so many people are coming forward to work on this stuff, doubly so since Mr.Mirko just asked for more help with the core SDK... I'll see if I can't test your "method 3" as well, when I'm benching Pea's code.
 
Last edited by a moderator:
hmm, interesting results Oankali. I was under the impression that multiplication was very cycle-intensive. Perhaps mult is less intensive than larger bit shifts (like 11). How many cycles for a LSL per bit? Is it 1 or 2? What about multiplication? I think I read somewhere than mult by 8 was 18 cycles....

My initial implementation was very similar to Method 7 but had this (instead of multiplication) Maybe it is slightly faster?:
Code:
ra = alpha[(rs<<5)+rd];
ga = alpha[(gs<<5)+gd];
ba = alpha[(bs<<5)+bd];

Next up is alpha blitting with mask (variable alpha mask). However, in the case of a variable alpha mask I think it is useless to split the alpha LUT up into chunks, because we won't just be accessing a single chunk. What do you think?
 
My initial implementation was very similar to Method 7 but had this (instead of multiplication) Maybe it is slightly faster?:
Code:
ra = alpha[(rs<<5)+rd];
ga = alpha[(gs<<5)+gd];
ba = alpha[(bs<<5)+bd];
If we refer at methods 3 and 4 we can see that 3 is faster than 4. So *32 is faster than <<5.


Next up is alpha blitting with mask (variable alpha mask). However, in the case of a variable alpha mask I think it is useless to split the alpha LUT up into chunks, because we won't just be accessing a single chunk. What do you think?
In reality, I used the alpha LUT splitting trick only in two of my functions where the level of the alpha chanel is the same for all pixels, as in fadein-fadeout effects.
For al other functions I have a mask with alpha level different for every pixel, so no splitting.
And finally I used this LUT structure
Code:
unsigned char alphaLevels [opacity][bottomPixel][topPixel];
as it is very functional for all the cases I've found.
 
Last edited by a moderator:
I tried to do benchmarking stuff last night for everyone, but as I was fighting with altering everyone's functions to work with the basic SDK, my computer overheated and shut itself off. :p

So, until I can get a new case with better cooling, I can't do much work at home (I'm writing this message from work! ;) ). If you guys can post copies of the functions that'll work with SDK-style sprites-with-headers, that'll make it a lot easier for me.

Also, I was thinking about how to integrate this stuff into the official SDK, and it struck me that maybe we just want to create a new sort of "sprite with header" structure for prerotated sprites, and have a function that converts from normal (non-rotated) to pre-rotated format. Then, the more efficient functions we're making now (with LUTs and whatever else we come up with) can just use the newer format.

That retains backwards compatibility for apps/games already using the SDK, but it also makes it really easy to modify your code to start using the newer / faster functions.

How does that sound to everyone?
 
Personally I like my tCanvas structure, because my functions work by not just drawing to the framebuffer, but to any canvas. A sprite is a canvas, and the framebuffer can be wrapped in a canvas, so its all easy (so you can do things like create sprites on the fly by drawing to their canvases directly etc).

But if everyone would rather go with a structure simlar to the current Mr.Mirko sprite+header, I don't mind either, because I'll just keep using my functions as is :p
 
Also, I was thinking about how to integrate this stuff into the official SDK, and it struck me that maybe we just want to create a new sort of "sprite with header" structure for prerotated sprites, and have a function that converts from normal (non-rotated) to pre-rotated format. Then, the more efficient functions we're making now (with LUTs and whatever else we come up with) can just use the newer format.

That retains backwards compatibility for apps/games already using the SDK, but it also makes it really easy to modify your code to start using the newer / faster functions.

My functions are designed to work with the official SDK. I've done my best to make functions parameters list compatible with it.
For example, here is the declaration of the final function I've integrated in my graphisc lib:

Code:
int  TransFade16(GPDRAWTAG *gptag, GPDRAWSURFACE *ptgpds, int dx, int dy, int width, int height, unsigned char *bitmap, int bitmapx, int bitmapy, int bitmapWidth, int bitmapHeight, unsigned short transColor, int opacity);

IMO, I think it's a shame that Mr.Mirko didn't used the same bitmap structure as the official SDK. It would have been a lot easier to prepare libs compatible with the 2 SDK.
That's why I'm still using the official SDK, as I started to code my games before Mr.Mirko SDK appeared.
When you say that we could prepare some functions to convert normal bitmaps to pre-rotated bitmaps, I think you should say that we could prepare functions to convert non-rotated bitmaps to normal bitmaps as pre-rotated bitmaps are the native bitmaps for the GP32. But I understand you used this notation referring to normal bitmaps in general.
For some cases, the idea to rotate bitmaps could be helpful, but this will make the use of the libs more complicated to the final coder if he had to prepare all the bitmaps. When I prepare a function or a complete lib, I always try to make things as easy as possible for the final coder (as easy as I can ;p). I think that it would be better to have optimised functions prepared specifically for each SDK.


@pea: that's an interesting approach that reminds me windows programming. Stick with it, it will give you more flexibility than just working with the framebuffer. I'll have to think about implementing something similar for my GUI.


PS: If all goes as planned, I will release the new version of all my libs next week.
 
Last edited by a moderator:
@pea: that's an interesting approach that reminds me windows programming. Stick with it, it will give you more flexibility than just working with the framebuffer. I'll have to think about implementing something similar for my GUI.

Thanks Oankali. I find it very powerful. Its just as fast as writing to the framebuffer, because you still ARE writing directly to the frame buffer :) It reduces memory load because it lets you do powerful things like:

I have 4 sprite tiles:
1 plain grass
1 plain sand
1 water puddle (transparent edges)
1 few scattered rocks (also transparent)

With these 4 tiles, I can combine them by creating a new sprite, and drawing these directly to the new sprite. I can create (for example):
A grass tile with a puddle
A grass tile with a puddle with some rocks in it
A grass tile with some rocks on it
A sand tile " ..... etc

So instead of creating all these tiles that you may need in the game, you create just the ones you need before each level.

Oankali - I also agree with your approach of making everything as easy for the user as possible. I have functions to create sprites from GIF files on the SMC, which loads a standard GIF and (if you set the flag) will prerotate it for you. It also detects whether the GIF has a transparent colour, and will set that for you too. It makes every thing very simple.

This is the FULL code to load a transparent GIF file which is a long image with 8 frames of animation (NOT an animated GIF, but a long gif with equal sized tiles) and draw the 5th one:

Code:
#include "gp32.h"
#include "gp_graphics.h"

tGP_LCD *lcd;

int main(){
  tGP_canvas *sprite;
  int res;
  
  // Set CPU speed
  gp_setCpuspeed(66);
  // Init graphics and create LCD (with canvas) at 85Hz refresh, and clear to color white
  lcd = gp_lcdCreate( 85, 0xffff );
  // Load an 8-tile sprite from SMC, and rotate it. Auto-detects transparency
  sprite = gp_canvasCreateFromGif( "dev0:\\GPMM\\example.gif", 1, 8, &res );
  // Draw the fifth tile to the back-buffer at position 0,0 with transparency
  gp_canvasPasteT( lcd->canvas, sprite, 0,0, 5 );
  // Flip the framebuffers
  gp_lcdFlip( lcd );

  // Wait forever (hahaha)
  while(1){ }
}
 
uhm... the frambuffer pointer in Mirko's functions is just a pointer it can point anywhere you want! You allocate some mem und create a sprite like pea said and blit the mem to the screen... what do you think are the header less fucntions for?!

we should change to pre-rotated bitmap at any cost it's just the native mem alignment of the gp32... in my own functions I also use the real (0,0) in the bottom left because it's where mem of the framebuffer begins ....
 
Back
Top