gba/book/src-bak/ch03/tile_data.md
2018-12-07 17:06:11 -07:00

131 lines
5.6 KiB
Markdown

# Tile Data
When using the GBA's hardware graphics, if you want to let the hardware do most
of the work you have to use Modes 0, 1 or 2. However, to do that we first have
to learn about how tile data works inside of the GBA.
## Tiles
Fundamentally, a tile is an 8x8 image. If you want anything bigger than 8x8 you
need to arrange several tiles so that it looks like whatever you're trying to
draw.
As was already mentioned, the GBA supports two different color modes: 4 bits per
pixel and 8 bits per pixel. This means that we have two types of tile that we
need to model. The pixel bits always represent an index into the PALRAM.
* With 4 bits per pixel, the PALRAM is imagined to be 16 **palbank** sections of
16 palette entries each. The image data selects the index within the palbank,
and an external configuration selects which palbank is used.
* With 8 bits per pixel, the PALRAM is imagined to be a single 256 entry array
and the index just directly picks which of the 256 colors is used.
Knowing this, we can write the following definitions:
```rust
#[derive(Debug, Clone, Copy, Default)]
#[repr(transparent)]
pub struct Tile4bpp {
pub data: [u32; 8]
}
#[derive(Debug, Clone, Copy, Default)]
#[repr(transparent)]
pub struct Tile8bpp {
pub data: [u32; 16]
}
```
I hope this makes sense so far. At 4bpp, we have 4 bits per pixel, times 8
pixels per line, times 8 lines: 256 bits required. Similarly, at 8 bits per
pixel we'll need 512 bits. Why are we defining them as arrays of `u32` values?
Because when it comes time to do bulk copies the fastest way to it will be to go
one whole machine word at a time. If we make the data inside the type be an
array of `u32` then it'll already be aligned for fast `u32` bulk copies.
Keeping track of the current color depth is naturally the _programmer's_
problem. If you get it wrong you'll see a whole ton of garbage pixels all over
the screen, and you'll probably be able to guess why. You know, unless you did
one of the other things that can make a bunch of garbage pixels show up all over
the screen. Graphics programming is fun like that.
## Charblocks
Tiles don't just sit on their own, they get grouped into **charblocks**. Long
ago in the distant past, video games were built with hardware that was also used
to make text terminals. So tile image data was called "character data". In fact
some guides will even call the regular mode for the background layers "text
mode", despite the fact that you obviously don't have to show text at all.
A charblock is 16kb long (`0x4000` bytes), which means that the number of tiles
that fit into a charblock depends on your color depth. With 4bpp you get 512
tiles, and with 8bpp there's 256 tiles. So they'd be something like this:
```rust
#[derive(Clone, Copy)]
#[repr(transparent)]
pub struct Charblock4bpp {
pub data: [Tile4bpp; 512],
}
#[derive(Clone, Copy)]
#[repr(transparent)]
pub struct Charblock8bpp {
pub data: [Tile8bpp; 256],
}
```
You'll note that we can't even derive `Debug` or `Default` any more because the
arrays are so big. Rust supports Clone and Copy for arrays of any size, but the
rest is still size 32 or less. We won't generally be making up an entire
Charblock on the fly though, so it's not a big deal. If we _absolutely_ had to,
we could call `core::mem::zeroed()`, but we really don't want to be trying to
build a whole charblock at runtime. We'll usually want to define our tile data
as `const` charblock values (or even parts of charblock values) that we then
load out of the game pak ROM at runtime.
Anyway, with 16k per charblock and only 96k total in VRAM, it's easy math to see
that there's 6 different charblocks in VRAM when in a tiled mode. The first four
of these are for backgrounds, and the other two are for objects. There's rules
for how a tile ID on a background or object selects a tile within a charblock,
but since they're different between backgrounds and objects we'll cover that on
their own pages.
## Image Editing
It's very important to note that if you use a normal image editor you'll get
very bad results if you translate that directly into GBA memory.
Imagine you have part of an image that's 16 by 16 pixels, aka 2 tiles by 2
tiles. The data for that bitmap is the 1st row of the 1st tile, then the 1st row
of the 2nd tile. However, when we translate that into the GBA, the first 8
pixels will indeed be the first 8 tile pixels, but then the next 8 pixels in
memory will be used as the _2nd row of the first tile_, not the 1st row of the
2nd tile.
So, how do we fix this?
Well, the simple but annoying way is to edit your tile image as being an 8 pixel
wide image and then have the image get super tall as you add more and more
tiles. It can work, but it's really impractical if you have any multi-tile
things that you're trying to do.
Instead, there are some image conversion tools that devkitpro provides in their
gba-dev section. They let you take normal images and then repackage them and
export it in various formats that you can then compile into your project.
Ketsuban uses the [grit](http://www.coranac.com/projects/grit/) tool, with the
following suggestions:
1) Include an actual resource file and a file describing it somewhere in your
project (see [the grit
manual](http://www.coranac.com/man/grit/html/index.htm) for all details
involved here).
2) In a `build.rs` you run `grit` on each resource+description pair, such as in
this [old gist
example](https://gist.github.com/ketsuban/526fa55fbef0a3ccd4c7cd6204f29f94)
3) Then within your rust code you use the
[include_bytes!](https://doc.rust-lang.org/core/macro.include_bytes.html)
macro to have the formatted resource be available as a const value you can
load at runtime.