gbadev.org forum archive

This is a read-only mirror of the content originally found on forum.gbadev.org (now offline), salvaged from Wayback machine copies. A new forum can be found here.

Coding > Character Memory

#65240 - sparda - Sun Jan 01, 2006 8:25 am

Here again with another newbie question ;)

Ok, im having alot of problems understanding the whole character memory deal, like if i use a a 8x32 sprite using 256 colors i would set it up like so:

Code:

sprites[0].attribute0 = COLOR_256 | TALL // 0x8000 // | 50;
sprites[0].attribute1 = SIZE_16 | 50;
sprites[0].attribute2 = 512; // I know CHR memory starts 512 in 256 mode //


But how would I determine which character number goes next if i were to include an additional sprite? Is there some kind of fomula, that is simple to understand?

for example : 8x32 = 256 / 2--- since you can only write 16bits at a time...ect ect.

Anyways, can anyone help me out? thanks.
_________________
genius is 1% inspiration, 99% perspiration .

#65256 - tepples - Sun Jan 01, 2006 4:35 pm

The main rules for placing sprite cels in 1D mode:
  1. Sprite cel VRAM consists of 1,024 tiles, each 32 bytes in size. (The first half is unusable if you're in a bitmapped display mode.)
  2. A 16-color sprite cel uses one tile of sprite cel VRAM per 8x8 pixel tile. For instance, an 8x32 pixel sprite cel in 16 colors has four tiles and thus takes four tiles of sprite cel VRAM.
  3. A 256-color sprite cel uses twice as much memory, so don't use 256-color sprite cels if you can get away with 16-color sprite cels.
  4. Sprite cel numbers for use in OAM attribute 2 start at 0 (for background modes 0-2) or 512 (for background modes 3-5) and end at 1023.
  5. Sprite cel VRAM addresses corresponding to tile numbers are defined by this formula:
Code:
#define SPR_VRAM(tn) ((u32 *)(0x06010000 | ((tn) << 5)))

Tell me which rule you don't understand, and I'll go into more detail.
_________________
-- Where is he?
-- Who?
-- You know, the human.
-- I think he moved to Tilwick.

#65276 - sparda - Sun Jan 01, 2006 9:07 pm

Ok, so let me get this straight.

1. So i can use the full 64bytes per cel in Tile modes right, 8x8 = 64? or you saying its half of 32bytes?

2. So the easy way of figuring the amount of tiles used(in 16-color) is to divide 8 by what ever size of sprite is used? for example: (8x32 sprite) => 32/8 = 4 tiles. Also, (32x64 sprite)=> 32/8= 4 and 64/8 = 8 -- for a total of 12 tiles?

3. got it.
4. got it.

5.Im not too good with macros, or costant definitions (need to study the preprocessor command much more) but i'll get into that in a few days.
_________________
genius is 1% inspiration, 99% perspiration .

#65278 - tepples - Sun Jan 01, 2006 9:13 pm

sparda wrote:
1. So i can use the full 64bytes per cel in Tile modes right, 8x8 = 64? or you saying its half of 32bytes?

A 16-color cel is always 32 bytes per tile, or one tile number per tile. A 256-color cel is always 64 bytes, or two consecutive tile numbers per tile. You're getting this confused with the fact that in tiled background modes, you can access tiles 0-1023, but in bitmap background modes, you can access tiles 512-1023.

Quote:
2. So the easy way of figuring the amount of tiles used(in 16-color) is to divide 8 by what ever size of sprite is used? for example: (8x32 sprite) => 32/8 = 4 tiles. Also, (32x64 sprite)=> 32/8= 4 and 64/8 = 8 -- for a total of 12 tiles?

No. A 16-color sprite cel of 32 pixels across and 64 pixels down has 32/8 = 4 tiles across times 64/8 = 8 tiles down, or 32 tiles. Double this in 256-color mode.

Get the simplest case (8x8 pixels, 16 colors, tiled background mode) working, and then extend from there.
_________________
-- Where is he?
-- Who?
-- You know, the human.
-- I think he moved to Tilwick.

#65312 - Cearn - Mon Jan 02, 2006 12:26 am

Instead of thinking in terms of number of colors, consider thinking in bits per pixel (bpp). Mode 3 for example has 16 bpp, so each pixel uses 2 bytes, so 8x8 pixels there are 8x8x2= 128 bytes. 256 colors use 8bpp (or rather, the other way around), so that's 1 byte per pixel: a tile here is 8x8x1= 64 bytes. 16 color tiles use 4 bpp, or half a byte per pixel: 8x8x(1/2)= 32 bytes.
Another way of looking at it is not the number of pixels, but the number of tiles you use: 8x8 is one tile, 8x16 is two, 32x32 is sixteen, etc. Counting then becomes very easy, although there is one extra catch in object VRAM (which incidentally has nothing to do with OAM; if you've learned to use the name OAM_Data for it, change the name). Object VRAM indexing always works in chunks of 4bpp tiles, so for 8bpp tiles you'd have to double the number of slots.

#65341 - sparda - Mon Jan 02, 2006 4:54 am

Ok, i understand the tiles now. But this brings me to another question, how would i load the Data correctly if i were to write:


Code:

// CODE SNIPPLET //



#define OAM_DataChar   ((u16*)(0x06014000))
                                                             // I define character
                                                             //memory in bitmap mode
                                                             // which starts at 0x060140000

int main()
{
SetMode(MODE_4 | OBJ_MAP_1D | OBJ_ENABLE | BG2_ENABLE)


sprites[0].attribute0 = COLOR_256 | SQUARE  | 50;
sprites[0].attribute1 = SIZE_8 | 50;
sprites[0].attribute2 = 512;                   // sprite1 is 8x8

sprites[1].attribute0 = COLOR_256 | SQUARE  | 100;
sprites[1].attribute1 = SIZE_32 | 100;
sprites[1].attribute2 = 512 + 1;            // since 8x8 = 64 =1 /

int loop;

for(loop = 0 ; loop < 256 ; loop++)
    OBJ_PaletteMem[loop] = sprite1[loop]; // I load the sprite palette //

for(loop = 0 ; loop < 31 ; loop++)     // 8x8 = 64-- then --- 64/2  = 32 //
     OAM_DataChar[loop] = sprite1[loop];  // Since im writing to memory 16bits at a time //
                                   



 // How would i know where to start and end the next sprite data? //
 // I have a vague Idea, but the sprite appears currupted...//
 // for example I would try (and fail) to write:

for(loop = 65 ; loop < 577 ; loop++)  // since 32x32 = 1024 / 2 = 512 //
     OAM_DataChar[loop] = sprite2[loop];  // 512 +  65 = 577 //

return 0;
}


Now listen to me before you flame me for being stupid, i dont know what im doing with the last loop. Im just going with my gut. Also, i've seen memcpy() function, and its much simplier. But I think i hides alot of math, which i dont like. If im completely wrong in what im doing please help me out here. You wouldnt let a newbie coder go off in the wrong direction would you?
_________________
genius is 1% inspiration, 99% perspiration .

#65365 - Cearn - Mon Jan 02, 2006 3:56 pm

sparda wrote:
Code:
// CODE SNIPPLET //
// I define character
//memory in bitmap mode
// which starts at 0x06014000
#define OAM_DataChar   ((u16*)(0x06014000))

This is what I meant by not attaching the OAM name to anything in VRAM. You're allowed to do it, of course, but it tends to cause confusion, especially when you use it over here because people will expect something named with the name OAM to apply to, well, OAM. I was thinking more along the lines of ObjVRAM, ObjChars or something like that. Anything that doesn't use the term "OAM" should do.
Also, it's probably not a good idea to just start at 0x06014000 just because you can't use the first 512 tiles because of the bitmap modes. Bitmap modes are ok for introduction lessons, but not for any real work so I'd expect you to move to the tile modes at some point, in which case you can use all the object tiles.

Quote:
Code:
sprites[0].attribute0 = COLOR_256 | SQUARE  | 50;
sprites[0].attribute1 = SIZE_8 | 50;
sprites[0].attribute2 = 512;                   // sprite1 is 8x8

sprites[1].attribute0 = COLOR_256 | SQUARE  | 100;
sprites[1].attribute1 = SIZE_32 | 100;
sprites[1].attribute2 = 512 + 1;            // since 8x8 = 64 =1

The tile of sprite 0 may be 8x8, but it's an 8bit tile, which doubles the tile count. You need to start at 512+2.

Quote:
Code:
int loop;
Ints for loop variables instead of u16. Excellent. You just saved yourself a whole lot of cycles by doing so :) (I'm being totally serious here: use int/u32 instead of other types, unless you have no choice)

Quote:
Code:
for(loop = 0 ; loop < 31 ; loop++)     // 8x8 = 64-- then --- 64/2  = 32 //
     OAM_DataChar[loop] = sprite1[loop];  // Since im writing to memory 16bits at a time
This only runs for 31 iterations, not 32. Use "<32".
Quote:
Code:

 // How would i know where to start and end the next sprite data? //
 // I have a vague Idea, but the sprite appears corrupted...//
 // for example I would try (and fail) to write:

for(loop = 65 ; loop < 577 ; loop++)  // since 32x32 = 1024 / 2 = 512 //
     OAM_DataChar[loop] = sprite2[loop];  // 512 +  65 = 577 //

The math is good, but you're using it wrong. In C indexing is 0-based, so the first entry you want to use is 64, not 65. That is, you want to use byte 64, but you've defined OAM_DataChar as u16. so you need to half that as well: starting point is 32. The same applies to the end-point, which would be 32+512= 544. (You wouldn't come from a VB background, would you?)
The second problem is that this will give you the correct block of memory for the destination, but you're using the same offset for the source, which I expect starts its data at index 0. That being the case, the whole copy is screwed. You are not the first to run into this problem, and sadly won't be the last. Anyway, the correct version would look something like:
Code:
for(loop=0; loop<512; loop++)
    OAM_DataChar[32+loop]= sprite2[loop];

Notice what I'm doing. I'm structuring the copy around the number of items that need copying, not where it's going or where I'm getting it from. That I'm taking care of with offsets (I'd even prefer doing it with pointers, but this look easier and the compiler should be bright enough to optimize to pointers itself (it isn't, actually, but that shouldn't concern you at this time))

Here's an alternative method for copying tiles; one that, in my opinion, is easier and it's faster too. You define a couple of structs and typedefs and make a memory map that corresponds to the actual tile memory. Indexing and copying then becomes very, very simple:
Code:
// tile 8x8@4bpp: 32bytes; 8 ints
typedef struct { u32 data[8];  } TILE, TILE4;
// d-tile: double-sized tile (8bpp)
typedef struct { u32 data[16]; } TILE8;
// tile block: 32x16 tiles
typedef TILE CHARBLOCK[512];
typedef TILE8 CHARBLOCK8[256]

#define tile_mem    ((CHARBLOCK*)0x06000000)
#define tile8_mem   ((CHARBLOCK8*)0x06000000)

These two defines are now 2d arrays mapping the tiles of the whole of VRAM. In both cases, object VRAM (0x06010000) starts at charblock 4, and high object VRAM (0x06014000) is charblock 5.
Code:
// location of tile 12, obj vram:
&tile_mem[4][12]   // ('&' gives the address, remember?)

// second 8bpp tile of high obj vram:
&tile_mem[5][2]    // 5 for high obj vram, tile 1x2 for 8pp tiles
or
&tile_mem[4][512+2] // tile 512 for high obj vram, +2 for the index
or even
&tile8_mem[5][1]   // 5 for high 8bpp tiles, 1 for the second 8bpp tile.

No math necessary, just counting. You can even copy (fast) with this, though you'll need some casting and might run into trouble with data alignment (source data would need to be aligned to 32bit boundaries for it to work properly, which might not be the case for u16 arrays. You might run into this problem with other copying methods as well).
Code:
Copy one 8bpp tile from sprite1 to 8bpp high obj vram, tile 0:
TILE8 *src= *(TILE8*)sprite1;
tile8_mem[5][0]= src[0];

// copy 4x4=16 8bpp tiles from sprite2 to high obj vram, tile 1 and onward
TILE8 *src= *(TILE8*)sprite1;
for(loop=0; loop<16; loop++)
    tile8_mem[5][1+loop]= src[loop];

You can do that?!?. Oh yes. Struct copies are perfectly legal, and usually quite fast too.

Quote:

Now listen to me before you flame me for being stupid, I don't know what I'm doing with the last loop. I'm just going with my gut. Also, i've seen memcpy() function, and its much simplier. But I think it hides alot of math, which I don't like.

Well, at least you're admitting you didn't know what you were doing. gut feelings have a very long history of being wrong, so you might want to watch out for that.
On memcpy, yes it is easy, and it doesn't actually hide any math. yo ustill have to specify from and to where it needs to copy, and how much (in bytes). The tile8_mem thing actually uses memcpy for its work, though not optimally. Look up memcpy once and try it. But please remember two words for if it ever goes wrong: data alignment. If your graphics converter gives you u8 or u16 arrays, there is a chance it may screw up because of that.

#65436 - sparda - Tue Jan 03, 2006 7:24 am

Wow, talk about being overwhelmed with information (and corrections)
I have to admit, you cleared up a bunch of things for me. I think I under estimated programming for the GBA, i forgot how technical it could get.

Everything you said made alot of sence, (until you got to the structs section), but regardless, i have to go read the TONC tutorial. And i think i should read a little more than just the first few pages this time ;)

well thanks for your help, im sure when i post again i'll be alot more knowledgeable, so watch out! If i get stuck, i'll come here for the right help ;)

later
_________________
genius is 1% inspiration, 99% perspiration .