gba/book/src/ch1/video_memory_intro.md

114 lines
4.5 KiB
Markdown

# Video Memory Intro
The GBA's Video RAM is 96k stretching from `0x0600_0000` to `0x0601_7FFF`.
The Video RAM can only be accessed totally freely during a Vertical Blank (aka
"VBlank", though sometimes I forget and don't capitalize it properly). At other
times, if the CPU tries to touch the same part of video memory as the display
controller is accessing then the CPU gets bumped by a cycle to avoid a clash.
Annoyingly, VRAM can only be properly written to in 16 and 32 bit segments (same
with PALRAM and OAM). If you try to write just an 8 bit segment, then both parts
of the 16 bit segment get the same value written to them. In other words, if you
write the byte `5` to `0x0600_0000`, then both `0x0600_0000` and ALSO
`0x0600_0001` will have the byte `5` in them. We have to be extra careful when
trying to set an individual byte, and we also have to be careful if we use
`memcopy` or `memset` as well, because they're byte oriented by default and
don't know to follow the special rules.
## RGB15
As I said before, RGB15 stores a color within a `u16` value using 5 bits for
each color channel.
```rust
pub const RED: u16 = 0b0_00000_00000_11111;
pub const GREEN: u16 = 0b0_00000_11111_00000;
pub const BLUE: u16 = 0b0_11111_00000_00000;
```
In Mode 3 and Mode 5 we write direct color values into VRAM, and in Mode 4 we
write palette index values, and then the color values go into the PALRAM.
## Mode 3
Mode 3 is pretty easy. We have a full resolution grid of rgb15 pixels. There's
160 rows of 240 pixels each, with the base address being the top left corner. A
particular pixel uses normal "2d indexing" math:
```rust
let row_five_col_seven = 5 + (7 * SCREEN_WIDTH);
```
To draw a pixel, we just write a value at the address for the row and col that
we want to draw to.
## Mode 4
Mode 4 introduces page flipping. Instead of one giant page at `0x0600_0000`,
there's Page 0 at `0x0600_0000` and then Page 1 at `0x0600_A000`. The resolution
for each page is the same as above, but instead of writing `u16` values, the
memory is treated as `u8` indexes into PALRAM. The PALRAM starts at
`0x0500_0000`, and there's enough space for 256 palette entries (each a `u16`).
To set the color of a palette entry we just do a normal `u16` write_volatile.
```rust
(0x0500_0000 as *mut u16).offset(target_index).write_volatile(new_color)
```
To draw a pixel we set the palette entry that we want the pixel to use. However,
we must remember the "minimum size" write limitation that applies to VRAM. So,
if we want to change just a single pixel at a time we must
1) Read the full `u16` it's a part of.
2) Clear the half of the `u16` we're going to replace
3) Write the half of the `u16` we're going to replace with the new value
4) Write that result back to the address.
So, the math for finding a byte offset is the same as Mode 3 (since they're both
a 2d grid). If the byte offset is EVEN it'll be the high bits of the `u16` at
half the byte offset rounded down. If the offset is ODD it'll be the low bits of
the `u16` at half the byte.
Does that make sense?
* If we want to write pixel (0,0) the byte offset is 0, so we change the high
bits of `u16` offset 0. Then we want to write to (1,0), so the byte offset is
1, so we change the low bits of `u16` offset 0. The pixels are next to each
other, and the target bytes are next to each other, good so far.
* If we want to write to (5,6) that'd be byte `5 + 6 * 240 = 1445`, so we'd
target the low bits of `u16` offset `floor(1445/2) = 722`.
As you can see, trying to write individual pixels in Mode 4 is mostly a bad
time. Fret not! We don't _have_ to write individual bytes. If our data is
arranged correctly ahead of time we can just write `u16` or `u32` values
directly. The video hardware doesn't care, it'll get along just fine.
## Mode 5
Mode 5 is also a two page mode, but instead of compressing the size of a pixel's
data to fit in two pages, we compress the resolution.
Mode 5 is full `u16` color, but only 160w x 128h per page.
## In Conclusion...
So what got written into VRAM in `hello1`?
```rust
(0x06000000 as *mut u16).offset(120 + 80 * 240).write_volatile(0x001F);
(0x06000000 as *mut u16).offset(136 + 80 * 240).write_volatile(0x03E0);
(0x06000000 as *mut u16).offset(120 + 96 * 240).write_volatile(0x7C00);
```
So at pixels `(120,80)`, `(136,80)`, and `(120,96)` we write three values. Once
again we probably need to [convert them](https://www.wolframalpha.com/) into
binary to make sense of it.
* 0x001F: 0b11111
* 0x03E0: 0b11111_00000
* 0x7C00: 0b11111_00000_00000
Ah, of course, a red pixel, a green pixel, and a blue pixel.