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);
}
}
}