GP32 Partially Offscreen Sprites


As are mine. Mr.Mirko has done a great job with his SDK replacement, but the sprite routines are not optimised.

-- snip --

But those are some impressive results you have already!

Yeah, they're not quite as good as I'd like, but even so, 15-20 fps is quite playable for a platformer. We'll see how it does if/when I find time to encorporate Synchro's mmap blitting function, I expect it'll be quite a bit faster... I just wish I had a faster replacement for the alpha sprite drawing function as well, as that's where the real slowdowns come in.

... I wonder, could you implement a 1-bit c buffer inside of Synchro's blitting function? Have a global cbuffer pointer (or pass it in as a parameter), and then have it copy a 1-bit mask of the sprite being drawn to the c-buffer using DMA (ie, in the background), and then start drawing the full colour version to screen. Is this a plausible idea?
 
Last edited by a moderator:
I have no idea what a c-buffer is... synchro :D

But - I believe that synchros routines are a direct replacement for Mr.Mirkos function (is this correct, synchro?), whereas mine use a custom graphics struct, which still has the data rotated in the same way, but is just inside a struct, along with the width and height of the sprite.

The difference in speed between the alpha and non-alpha, as well as the transparent and non-transparent, blitting routines is always going to be marked. This is because using the non-alpha, non-transparent blitting routine you can simply dump masses of pixels in one hit using memcpy, but using any other versions you need to do per-pixel processing, so need to handle each pixel at a time.

I have always been planning to give Mr.Mirko my code once it has been thoroughly tested (if you'll have it Mr.Mirko?) because when you are making a huge project like an SDK replacement, you hardly have time to optimise everything!
 
c-buffer is explained here: http://www.flipcode.com/articles/article_cbuffer.shtml
it is some kind of strcuture to keep track what parts of the screen have been rendered and what not. for multiple layers it could help to prevent the major overdraw to give a slight speed boost... I never coded something like this, my guess is that with more than 3 layers something like that could help, with less layers some specific quicker hack will do the tricks. I was just asking because you were talking about multiple layers...

well my code is the same like pea's but mine uses rotated data with header, to use the "normal" data with my function you have just to change the offsets...
 
c-buffer is explained here: http://www.flipcode.com/articles/article_cbuffer.shtml
it is some kind of strcuture to keep track what parts of the screen have been rendered and what not. for multiple layers it could help to prevent the major overdraw to give a slight speed boost... I never coded something like this, my guess is that with more than 3 layers something like that could help, with less layers some specific quicker hack will do the tricks. I was just asking because you were talking about multiple layers...

well my code is the same like pea's but mine uses rotated data with header, to use the "normal" data with my function you have just to change the offsets...
Yeah, I know generically how a C-Buffer would work, I just haven't come up with a good routing to actually do it. I'm thinking of creating a 1-bit memory buffer for the C-Buffer, and then have it check on each scanline before it draws if any place its drawing to is overlapped by the C-buffer. I've just got to figure out the most efficient algorithm for checking this on the fly, and even so, I'll probably end up coding it so it's only used when there's 3+ layers.

About your mmap function, can you advise on how to adapt it to use the same memory structure Mr.Mirko's SDK uses?
 
Last edited by a moderator:
Here is a quick hack (not tested, but should be ok) to work with Mr.Mirko SDK directly (16bpp only, non-transparent) with the same function prototype:

Code:
gp_drawSpriteH( u16 *sprite, short put_x, short put_y, u16 *framebuffer){
	unsigned short xx,yy;
	unsigned short vw,vh;
	unsigned long doff;
	unsigned long coff;
	unsigned short px,py;
	short tx,ty;
	unsigned width, height;
	SHEADER *sheader;

	sheader = (SHEADER*) sprite;
	height = sheader->size_y;
	width = sheader->size_x;
     
	// If rect is off the screen, exit
	if ((x>=LCD_WIDTH) || (y>=LCD_HEIGHT)){ return; }
	tx = put_x+width; ty = put_y+height;
	if ((tx<0) || (ty<0)){ return; }
	
	// The rest of the coordinates are calculated based on a screen rotation
	// of 90 degrees clockwise.
	ty = put_y;
	put_y = put_x;
	put_x = LCD_HEIGHT - ty - height;
	tx = put_x+height; ty = put_y+width;
	
  // Positional offsets
  px = max( 0, put_x );
  py = max( 0, put_y );
  // Visible height
	if (put_y<0){ 
  vh = min( ty, LCD_WIDTH );
	}else{
  if (ty>LCD_WIDTH){
  	vh = width - (ty-LCD_WIDTH);
  }else{
  	vh = width;
  }
  }
  // Visible width
	if (put_x<0){
  vw = min( tx, LCD_HEIGHT );
	}else{
  if (tx>LCD_HEIGHT){
  	vw = height - (tx-LCD_HEIGHT);
  }else{
  	vw = height;
  }
  }
  // Data offset
  doff = ( height * max( 0, -put_y ) ) + max( 0, -put_x ) + 6; // +6 to jump the header data
  // Canvas data offset
  coff = py*LCD_HEIGHT + px;

  // Draw visible pixels only
	for ( yy=0; yy<vh; yy++ ){
  memcpy( framebuffer[coff], sprite[doff], vw*2 );
  doff += height;
  coff += LCD_HEIGHT;
	}
}

Please let me know if it doesn't work, and I will check for typos....
 
Please let me know if it doesn't work, and I will check for typos....

Here's what I had to do to get it working:

I was getting "game_layers.cpp:172: error: no matching function for call to `max(int, short int&)'" and "px = max( 0, put_x );", used explicit casts in your min/max functions to fix.

I was also getting:
Code:
game_layers.cpp:201: error: invalid conversion from `short unsigned int' to `void*'
game_layers.cpp:201: error:   initializing argument 1 of `void* memcpy(void*, const void*, size_t)'
game_layers.cpp:201: error: invalid conversion from `short unsigned int' to `const void*'
game_layers.cpp:201: error:   initializing argument 2 of `void* memcpy(void*, const void*, size_t)'

Changing the memcopy line to "memcpy( &framebuffer[coff], &sprite[doff], vw*2 );" resolved it.

Even with these changes, the sprites blit to screen properly, but they're all rotated 90 degrees counter-clockwise. I'll muck around with your code to see if I can get it to use the same orientation as Mirko's SDK, but if there's a simple fix, I'd love to hear it. :)

Now, for benchmarking, same app as last time, 150 animated sprites per layer, 2 layers:

22MHz = 9-10fps
33MHz = 15fps
40MHz = 17fps
50MHz = 22fps
66MHz = 23fps
100MHz = 34-35fps
133MHz = 44-45fps

So, that's a good 15fps increase in performance over the SpriteHT function from Mirko's SDK when running at 133MHz. Things are very smooth. :) Of course, I'll still need to use Mr.Mirko's functions for sprites that have transparency or alpha blending, but for most platform games the majority of of the layers and backgrounds will be solid block tiles, so this should provide a nice speed boost.

As well, I think that the c-buffer idea has a lot of potential, so that's probably the next thing I'll look at implementing, to see if it results in any measurable speed increases with a larger number of layers.

Also, I've registered a sourceforge project for this lib, so hopefully in the next couple days I'll put a 0.1 release up on Sourceforge for everyone to check out. :)
 
Last edited by a moderator:
hmm. I designed it with pre-rotated data in mind from the start. I was under the impression that Mirkos sprite data was pre-rotated too :( If I adjust the function for non pre-rotated data, it will be much slower because the data is now the same orientation as the framebuffer.

I have another version coming soon for transparent and alpha blending, which will be faster than Mr.Mirkos, but not as marked as these. Maybe 5fps rather than 15...

great results though..
 
hmm. I designed it with pre-rotated data in mind from the start. I was under the impression that Mirkos sprite data was pre-rotated too :( If I adjust the function for non pre-rotated data, it will be much slower because the data is now the same orientation as the framebuffer.

moment... I thought the data should be aligned like the framebuffer so data can be cached easily, if we step through data on the x-axis (normal gp32 orientation) the we would get a cache miss on every column, because the gp32 LCD has been oriented 90° counter-clockwise, or do I get something wrong ????

looking forward for your new graphic routines pea !
 
Last edited by a moderator:
Synchro - not too sure what you mean, but my routines just won't work with non rotated data. The reason is that when the sprite data is rotated, you can simply memcpy each vertical line of the sprite to the frame buffer because the framebuffer is aligned vertically. However, if the sprite data is not rotated, to create one vertical line of the sprite you can't just copy a chunk - you need to copy one pixel from the top of each horizontal row, so basically you have to process each pixel at a time, which is slow(er).

e.g.
Code:
Pre-rotated
SPRITE   FRAMEBUFFER    BLIT
AAAA     Abcd        chunk(AAAA) chunk (bbbb) chunk(cccc) chunk(dddd)
bbbb     Abcd
cccc     Abcd
dddd     Abcd

Non-rotated
SPRITE   FRAMEBUFFER    BLIT
ABCD     Abcd        copy(A,B,C,D), copy(a,b,c,d), copy(a,b,c,d), copy(a,b,c,d)
abcd     Abcd
abcd     Abcd
abcd     Abcd


unit3 - are you using the asm version of memcpy that has been talked about on other threads? You simply add the unit to your code, and it provides an optimised version of memcpy (no need to recode your graphics routines) which is a few fps faster, but more importantly it stabilises the framerate so that it is more constant than when using the normal memcpy. Let me know and I'll upload it...
 
not too sure what you mean, but my routines just won't work with non rotated data. The reason is that when the sprite data is rotated, you can simply memcpy each vertical line of the sprite to the frame buffer because the framebuffer is aligned vertically. However, if the sprite data is not rotated, to create one vertical line of the sprite you can't just copy a chunk - you need to copy one pixel from the top of each horizontal row, so basically you have to process each pixel at a time, which is slow(er).

that's exactly what I meant :)
 
sweet :)
Hahaha

Ok, so I'm just going to have a lot less trouble if I use pre-rotated sprites, I guess. What's the tool you were saying will generate these? And more importantly, does it run on the Linux commandline? :) If you've got more optimized versions of the drawSpriteHT and HTB functions, than I'll probably want to make use of them.

Mr.Mirko, if you're still following this thread, would it make sense to modify your SDK to use prerotated sprites, so that you could encorporate faster routines transparently sometime in the future? Or would this be too much of a concern about breaking compatibility with people who've previously used your library?


Also, no, I'm not using the ASM-optimized version of memcopy, I'm just using the default GCC version. Like I was saying initially, I sortof pop in and out of the scene (ie, when I have time!), so I miss a lot of these important details. ;)

This C++ SDK project is something I started last year, actually, and then I ended up moving to a different city and switching jobs twice, so it's been on hold for quite a while. At least my basic design still makes sense to me a year later! :lol:
 
Last edited by a moderator:
the only tool I know is Dalto's imageconverter that can create pre-rotate AND add the MSDK header, but t´hat one is for windows. Just ask him to release the sources ....
 
ok, I'll upload the memcpy source, plus instructions to my site and let you know when thats up.

I'll also upload my transparent sprite routines, but they will need some testing/benchmarking before they are used, to make sure they are ok/faster.

In terms of Mr.Mirko SDK, what I will do is create a new set of functions (with different names) to handle pre-rotated data, perhaps with a 'P' suffix? If this is then added to the SDK, it would still be backwards compatible:

drawSpritePH (draw solid sprite)
drawSpritePHB (draw solid sprite at constant alpha)
drawSpritePHT (draw transparent sprite)
drawSpritePHTB (draw transparent sprite at constant alpha)
drawSpritePHM (draw sprite with a blending mask)

A blending mask will be a pre-rotated image also, but unsigned char (rather than unsigned short) which contains the values 0-255 for each pixel (0=transparent, 255=fully visible).

What do you think?
 
ok, I'll upload the memcpy source, plus instructions to my site and let you know when thats up.

I'll also upload my transparent sprite routines, but they will need some testing/benchmarking before they are used, to make sure they are ok/faster.

Sure, but since I've already got a benchmarking app and a devel framework, I can probably test that as soon as you post them. :)

pea said:
In terms of Mr.Mirko SDK, what I will do is create a new set of functions (with different names) to handle pre-rotated data, perhaps with a 'P' suffix? If this is then added to the SDK, it would still be backwards compatible:

drawSpritePH (draw solid sprite)
drawSpritePHB (draw solid sprite at constant alpha)
drawSpritePHT (draw transparent sprite)
drawSpritePHTB (draw transparent sprite at constant alpha)
drawSpritePHM (draw sprite with a blending mask)

A blending mask will be a pre-rotated image also, but unsigned char (rather than unsigned short) which contains the values 0-255 for each pixel (0=transparent, 255=fully visible).

What do you think?

That sounds great to me, a full solution!

Mr.Mirko's SDK actually only uses values from 0-31 (> 31 is considered same as 31) for blending values, since that's about the precision you get per colour channel in 15-bit colour. Depending on how you're doing your blending, just dealing with 0-31 may lead to a faster algorithm?

Also, I've been meaning to ask you, but do you mind me including your routines in my library? I'm releasing it under the LGPL (lib/lesser GPL) license, so I want to make sure that's cool with you before I put any of your code into it.
 
Last edited by a moderator:
Including my code in your lib is fine. If it helps people make more games and software for the GP, its all good.

Good idea about 0-31 on the alpha. Will try that...makes sense with 16bit colour anyhow...

EDIT: Have uploaded the asm memcpy code to my site http://www.pea.co.nz/gp32/gpd_downloads.php - sorry, not the transparent blitting code yet.

unit3 - are you able to post the final version of the blitting code that you adapted from mine, so I can be sure it works properly with Mr.Mirko's SDK - or email it to me: peter (at) pixelthis (dot) co (dot) nz
 
I've been following this thread from the first post and I think some of my code could be usefull for you, at least to see my alpha blending implementation as I've been working on the same problems as you, but with the oficial SDK. That means with pre-rotated data.
So here is one function that draws a mask on screen, with the color you want and the opacity you want. You can clip the mask as in oficial SDK.

Code:
int TransFadeMask16(GPDRAWTAG *gptag, GPDRAWSURFACE *ptgpds, int dx, int dy, int width, int height,
                    unsigned char *mask, int maskx, int masky,
                    int maskWidth, int maskHeight, unsigned short color, int opacity)
{
  GPDRAWTAG limits;
  int i, j;
  unsigned short *msk, *screen;
  unsigned short level;
  int red, green, blue;
  int colorRed, colorGreen, colorBlue;

  if (opacity == 0)
    return 0;


  // Clip mask
  if (gptag)
    limits = *gptag;
  else
  {
    limits.clip_x = 0;
    limits.clip_y = 0;
    limits.clip_w = 320;
    limits.clip_h = 240;
  }

  //- If completly out of limits, return
  if ((dx >= limits.clip_x + limits.clip_w) || (dy >= limits.clip_y + limits.clip_h))
    return 0;
  if ((dx + width - 1 < limits.clip_x) || (dy + height - 1 < limits.clip_y))
    return 0;

  //- Clip X and Width
  if (dx < limits.clip_x)
  {
    width -= limits.clip_x - dx;
    maskx += limits.clip_x - dx;
    dx = limits.clip_x;
  }
  if (dx + width > limits.clip_x + limits.clip_w)
    width = limits.clip_x + limits.clip_w - dx;

  //- Clip Y and Height
  if (dy < limits.clip_y)
  {
    height -= limits.clip_y - dy;
    masky += limits.clip_y - dy;
    dy = limits.clip_y;
  }
  if (dy + height> limits.clip_y + limits.clip_h)
    height = limits.clip_y + limits.clip_h - dy;


  // Adjust to rotated screen
  //- Source coordinates
  i = maskx;
  maskx = maskHeight - (masky + height);
  masky = i;
  //- Destination coordinates
  i = dx;
  dx = GPC_LCD_HEIGHT - (dy + height);
  dy = i;
  //- Sizes
  maskWidth = maskHeight;
  i = width;
  width = height;
  height = i;


  // Separate color components
  colorRed   = GetRGBRed(color);
  colorGreen = GetRGBGreen(color);
  colorBlue  = GetRGBBlue(color);

  
  // Draw bitmap using mask & color & opacity
  screen = (unsigned short *) (ptgpds->ptbuffer) + dy * GPC_LCD_PHYSICAL_W + dx;
  msk = (unsigned short *) mask + masky * maskWidth + maskx;
  for (j=0; j < height; j++)
  {
    for (i=0; i < width; i++, screen++, msk++)
    {
      // Get mask pixel level
      level = (*msk >> 1) & 0x1F;

      if (level > 0)
      {
        // Adjust level of opacity
        if (opacity < 31)
          level = (level * opacity) / 31;
          
        if (level == 31)
        {
            // Put pixel, as is
            *screen = color;
        }
        else
          if (level > 0)
          {
            // Merge color with background
            red   = ((colorRed   * level) + (GetRGBRed  (*screen) * (31 - level))) / 31;
            green = ((colorGreen * level) + (GetRGBGreen(*screen) * (31 - level))) / 31;
            blue  = ((colorBlue  * level) + (GetRGBBlue (*screen) * (31 - level))) / 31;

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

    // Jump to next scan line
    screen += GPC_LCD_PHYSICAL_W - width;
    msk += maskWidth - width;
  }

  return 0;
}

And here is a function from my OKF Font Engine that blits a bitmap using a mask for alpha blending and with some other options as opacity, color overlay...
okf is a global structure with the state of the Font Engine.

Code:
static int OkfRenderMask16(int dx, int dy, int width, int height,
                           unsigned char *bitmap, int bitmapx,
                           int bitmapy, int bitmapWidth, int bitmapHeight,
                           unsigned char *mask)
{
  int i, j;
  unsigned short *bmp, *screen;
  unsigned char *msk;
  unsigned char opacity;
  int red, green, blue;
  
  // Clip bitmap
  //- If completly out of limits, return
  if ((dx > okf.clip.x2) || (dy > okf.clip.y2))
    return 0;
  if ((dx + width - 1 < okf.clip.x1) || (dy + height - 1 < okf.clip.y1))
    return 0;

  //- Clip X and Width
  if (dx < okf.clip.x1)
  {
    width -= okf.clip.x1 - dx;
    bitmapx += okf.clip.x1 - dx;
    dx = okf.clip.x1;
  }
  if (dx + width - 1 > okf.clip.x2)
    width = okf.clip.x2 - dx + 1;

  //- Clip Y and Height
  if (dy < okf.clip.y1)
  {
    height -= okf.clip.y1 - dy;
    bitmapy += okf.clip.y1 - dy;
    dy = okf.clip.y1;
  }
  if (dy + height - 1 > okf.clip.y2)
    height = okf.clip.y2 - dy + 1;


  // Adjust to rotated screen
  //- Source coordinates
  i = bitmapx;
  bitmapx = bitmapHeight - (bitmapy + height);
  bitmapy = i;
  //- Destination coordinates
  i = dx;
  dx = GPC_LCD_HEIGHT - (dy + height);
  dy = i;
  //- Sizes
  bitmapWidth = bitmapHeight;
  i = width;
  width = height;
  height = i;


  // Blit bitmap
  screen = (unsigned short *) (okf.pSurfaces[*okf.pCurSurface].ptbuffer) + dy * GPC_LCD_PHYSICAL_W + dx;
  bmp = (unsigned short *) bitmap + bitmapy * bitmapWidth + bitmapx;
  msk = mask + bitmapy * bitmapWidth + bitmapx;
  for (j=0; j < height; j++)
  {
    for (i=0; i < width; i++, screen++, bmp++, msk++)
    {
      opacity = *msk;

      if (opacity > 0)
      {
        // Modify opacity, if required
        if (okf.opacity < 31)
          opacity = (opacity * okf.opacity) / 31;

        if (opacity > 0)
        {
          // Separate color components
          red   = GetRGBRed(*bmp);
          green = GetRGBGreen(*bmp);
          blue  = GetRGBBlue(*bmp);

          // Modify color
          switch (okf.effect.type)
          {
            case okfOverlay:
              OkfOverlayColor(red, green, blue, okf.effect.color, okf.effect.intensity);
              break;
            case okfAddColor:
              OkfAddColor(red, green, blue, okf.effect.color);
              OkfAdjustColorComponents(red, green, blue);
              break;
            case okfSubtractColor:
              OkfSubtractColor(red, green, blue, okf.effect.color);
              OkfAdjustColorComponents(red, green, blue);
              break;
            case okfGrayScale:
              OkfGrayScaleColor(red, green, blue);
              break;
          }

          // Merge color with background, if required
          if (opacity < 31)
            OkfUnderlayColor(red, green, blue, *screen, opacity);

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

    screen += GPC_LCD_PHYSICAL_W - width;
    bmp += bitmapWidth - width;
    msk += bitmapWidth - width;
  }

  return 0;
}

Code:
#define RGB(Red, Green, Blue) (((unsigned int) ((Red)  << 11)) + \
                               ((unsigned int) ((Green) << 6)) + \
                               ((unsigned int) ((Blue)  << 1)))

#define GetRGBRed(color)   (((color) >> 11) & 0x1F)
#define GetRGBGreen(color) (((color) >>  6) & 0x1F)
#define GetRGBBlue(color)  (((color) >>  1) & 0x1F)

#define OkfOverlayColor(red, green, blue, color, intensity) \
{ \
  if ((intensity) == 31) \
  { \
    red   = GetRGBRed  (color); \
    green = GetRGBGreen(color); \
    blue  = GetRGBBlue (color); \
  } \
  else if ((intensity) > 0) \
  { \
    red   = ((GetRGBRed  (color) * (intensity)) + ((red)   * (31 - (intensity)))) / 31; \
    green = ((GetRGBGreen(color) * (intensity)) + ((green) * (31 - (intensity)))) / 31; \
    blue  = ((GetRGBBlue (color) * (intensity)) + ((blue)  * (31 - (intensity)))) / 31; \
  } \
}

#define OkfUnderlayColor(red, green, blue, color, intensity) \
{ \
  if ((intensity) == 0) \
  { \
    red   = GetRGBRed  (color); \
    green = GetRGBGreen(color); \
    blue  = GetRGBBlue (color); \
  } \
  else if ((intensity) < 31) \
  { \
    red   = (((red)   * (intensity)) + (GetRGBRed  (color) * (31 - (intensity)))) / 31; \
    green = (((green) * (intensity)) + (GetRGBGreen(color) * (31 - (intensity)))) / 31; \
    blue  = (((blue)  * (intensity)) + (GetRGBBlue (color) * (31 - (intensity)))) / 31; \
  } \
}

#define OkfAddColor(red, green, blue, color) \
{ \
  red   += GetRGBRed(color); \
  green += GetRGBGreen(color); \
  blue  += GetRGBBlue(color); \
}

#define OkfSubtractColor(red, green, blue, color) \
{ \
  red   -= GetRGBRed(color); \
  green -= GetRGBGreen(color); \
  blue  -= GetRGBBlue(color); \
}

#define OkfGrayScaleColor(red, green, blue) \
{ \
  red = green = blue = ((red) + (green) + (blue)) / 3; \
}

#define OkfAdjustColorComponents(red, green, blue) \
{ \
  if ((red) < 0)    red = 0; \
  if ((red) > 31)   red = 31; \
  if ((green) < 0)  green = 0; \
  if ((green) > 31) green = 31; \
  if ((blue) < 0)   blue = 0; \
  if ((blue) > 31)  blue = 31; \
}

Code has been optimized for pre-rotated data, but not for the division by 31. If you have a solution for this, let me know.

In terms of Mr.Mirko SDK, what I will do is create a new set of functions (with different names) to handle pre-rotated data, perhaps with a 'P' suffix? If this is then added to the SDK, it would still be backwards compatible:

drawSpritePH (draw solid sprite)
drawSpritePHB (draw solid sprite at constant alpha)
drawSpritePHT (draw transparent sprite)
drawSpritePHTB (draw transparent sprite at constant alpha)
drawSpritePHM (draw sprite with a blending mask)

I would use R instead of P as R seems to me more clear in terms of Rotated data then P.

You can find more of my code at www.Nekanium.com
The website is a little out of date but I'm planning to make very big update, soon.

Oankali.
 
Last edited by a moderator:
funny, I started yesterday to have a look on alpha blenfing....

1.) it would be faster to have a LookUpTable for the alpha blending instead of calculating every pixel.

2.) what's faster? storing sprite and alphamask in two structure, or to have an array of u32 (16bit color, 16bit alpha)?

3.) how to genrate such data? from PNGs?
 
1.) it would be faster to have a LookUpTable for the alpha blending instead of calculating every pixel.
Do you mean a tridimensional 32K LookUpTable[screenColor][bitmapColor][opacity]?
I have to investigate, but it don't seems to be a bad idea.

2.) what's faster? storing sprite and alphamask in two structure, or to have an array of u32 (16bit color, 16bit alpha)?
I don't know, but 2 structures takes less memory.

3.) how to genrate such data? from PNGs?
For the fonts, I generate the initial bitmap to work on with my font generator, then PhotoShop, then another time my font generator to compress (similar to RLE) the 2 bitmaps in one file.
For mask bitmaps, I use grayscale bitmaps generated with PhotoShop, and then converted with GP32Converter. But I'm planning to make a tool similar as above to compress mask bitmaps, and other bitmaps as well.
 
Last edited by a moderator:
Back
Top