mirror of
https://github.com/italicsjenga/gba.git
synced 2025-01-11 03:21:30 +11:00
example 2 complete
This commit is contained in:
parent
3761851df3
commit
c71878fe57
|
@ -12,4 +12,4 @@
|
|||
* [Ch 2: User Input](ch2/index.md)
|
||||
* [The Key Input Register](ch2/the_key_input_register.md)
|
||||
* [The VCount Register](ch2/the_vcount_register.md)
|
||||
* [gba_snake](ch2/gba_snake.md)
|
||||
* [light_cycle](ch2/light_cycle.md)
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
# gba_snake
|
||||
|
||||
TODO: make a "snake game" using arrow key input and vsync
|
|
@ -12,8 +12,11 @@ modern computer or console you do this with vsync info from the GPU and Monitor,
|
|||
and on the GBA we'll be using vsync info from an IO register that tracks what
|
||||
the display hardware is doing.
|
||||
|
||||
For this chapter we'll make a copy of `hello2.rs` named `snake.rs` and then fill
|
||||
it in as we go. Normally you might not place the entire program into a single
|
||||
source file, but since these are examples it's slightly better to have them be
|
||||
completely self contained than it is to have them be "properly organized" for
|
||||
the long term.
|
||||
As a way to apply our knowledge We'll make a simply "light cycle" game where
|
||||
your dot leaves a trail behind them and you die if you go off the screen or if
|
||||
you touch your own trail. We just make a copy of `hello2.rs` named
|
||||
`light_cycle.rs` and then fill it in as we go through the chapter. Normally you
|
||||
might not place the entire program into a single source file, particularly as it
|
||||
grows over time, but since these are small examples it's much better to have
|
||||
them be completely self contained than it is to have them be "properly
|
||||
organized" for the long term.
|
||||
|
|
119
book/src/ch2/light_cycle.md
Normal file
119
book/src/ch2/light_cycle.md
Normal file
|
@ -0,0 +1,119 @@
|
|||
# light_cycle
|
||||
|
||||
Now let's make a game of "light_cycle" with our new knowledge.
|
||||
|
||||
## Gameplay
|
||||
|
||||
`light_cycle` is pretty simple, and very obvious if you've ever seen Tron. The
|
||||
player moves around the screen with a trail left behind them. They die if they
|
||||
go off the screen or if they touch their own trail.
|
||||
|
||||
## Operations
|
||||
|
||||
We need some better drawing operations this time around.
|
||||
|
||||
```rust
|
||||
pub unsafe fn mode3_clear_screen(color: u16) {
|
||||
let color = color as u32;
|
||||
let bulk_color = color << 16 | color;
|
||||
let mut ptr = VRAM as *mut u32;
|
||||
for _ in 0..SCREEN_HEIGHT {
|
||||
for _ in 0..(SCREEN_WIDTH / 2) {
|
||||
ptr.write_volatile(bulk_color);
|
||||
ptr = ptr.offset(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn mode3_draw_pixel(col: isize, row: isize, color: u16) {
|
||||
(VRAM as *mut u16).offset(col + row * SCREEN_WIDTH).write_volatile(color);
|
||||
}
|
||||
|
||||
pub unsafe fn mode3_read_pixel(col: isize, row: isize) -> u16 {
|
||||
(VRAM as *mut u16).offset(col + row * SCREEN_WIDTH).read_volatile()
|
||||
}
|
||||
```
|
||||
|
||||
The draw pixel and read pixel are both pretty obvious. What's new is the clear
|
||||
screen operation. It changes the `u16` color into a `u32` and then packs the
|
||||
value in twice. Then we write out `u32` values the whole way through screen
|
||||
memory. This means we have to do less write operations overall, and so the
|
||||
screen clear is twice as fast.
|
||||
|
||||
Now we just have to fill in the main function:
|
||||
|
||||
```rust
|
||||
#[start]
|
||||
fn main(_argc: isize, _argv: *const *const u8) -> isize {
|
||||
unsafe {
|
||||
DISPCNT.write_volatile(MODE3 | BG2);
|
||||
}
|
||||
|
||||
let mut px = SCREEN_WIDTH / 2;
|
||||
let mut py = SCREEN_HEIGHT / 2;
|
||||
let mut color = rgb16(31, 0, 0);
|
||||
|
||||
loop {
|
||||
// read the input for this frame
|
||||
let this_frame_keys = read_key_input();
|
||||
|
||||
// adjust game state and wait for vblank
|
||||
px += 2 * this_frame_keys.column_direction() as isize;
|
||||
py += 2 * this_frame_keys.row_direction() as isize;
|
||||
wait_until_vblank();
|
||||
|
||||
// draw the new game and wait until the next frame starts.
|
||||
unsafe {
|
||||
if px < 0 || py < 0 || px == SCREEN_WIDTH || py == SCREEN_HEIGHT {
|
||||
// out of bounds, reset the screen and position.
|
||||
mode3_clear_screen(0);
|
||||
color = color.rotate_left(5);
|
||||
px = SCREEN_WIDTH / 2;
|
||||
py = SCREEN_HEIGHT / 2;
|
||||
} else {
|
||||
let color_here = mode3_read_pixel(px, py);
|
||||
if color_here != 0 {
|
||||
// crashed into our own line, reset the screen
|
||||
mode3_clear_screen(0);
|
||||
color = color.rotate_left(5);
|
||||
} else {
|
||||
// draw the new part of the line
|
||||
mode3_draw_pixel(px, py, color);
|
||||
mode3_draw_pixel(px, py + 1, color);
|
||||
mode3_draw_pixel(px + 1, py, color);
|
||||
mode3_draw_pixel(px + 1, py + 1, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
wait_until_vdraw();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Oh that's a lot more than before!
|
||||
|
||||
First we set Mode 3 and Background 2, we know about that.
|
||||
|
||||
Then we're going to store the player's x and y, along with a color value for
|
||||
their light cycle. Then we enter the core loop.
|
||||
|
||||
We read the keys for input, and then do as much as we can without touching video
|
||||
memory. Since we're using video memory as the place to store the player's light
|
||||
trail, we can't do much, we just update their position and wait for vblank to
|
||||
start. The player will be a 2x2 square, so the arrows will move you 2 pixels per
|
||||
frame.
|
||||
|
||||
Once we're in vblank we check to see what kind of drawing we're doing. If the
|
||||
player has gone out of bounds, we clear the screen, rotate their color, and then
|
||||
reset their position. Why rotate the color? Just because it's fun to have
|
||||
different colors.
|
||||
|
||||
Next, if the player is in bounds we read the video memory for their position. If
|
||||
it's not black that means we've been here before and the player has crashed into
|
||||
their own line. In this case, we reset the game without moving them to a new
|
||||
location.
|
||||
|
||||
Finally, if the player is in bounds and they haven't crashed, we write their color into memory at this position.
|
||||
|
||||
Regardless of how it worked out, we hold here until vdraw starts before going to
|
||||
the next loop.
|
|
@ -64,7 +64,7 @@ pub const KEYINPUT: *mut u16 = 0x400_0130 as *mut u16;
|
|||
pub struct KeyInputSetting(u16);
|
||||
|
||||
pub fn read_key_input() -> KeyInputSetting {
|
||||
unsafe { KeyInputSetting(KEYINPUT.volatile_read()) }
|
||||
unsafe { KeyInputSetting(KEYINPUT.read_volatile()) }
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -88,15 +88,16 @@ something. Also, we've set the target to `thumbv6m-none-eabi`, which is a
|
|||
slightly later version of ARM than the actual GBA, but it's close enough for
|
||||
just checking. Of course, in a full program small functions like these will
|
||||
probably get inlined into the calling code and disappear entirely as they're
|
||||
folded and refolded by the compiler, and so on. Still, we can see that in the
|
||||
simple case when the compiler doesn't know any extra context, the `!=0` test is
|
||||
4 instructions and the `==0` test is six instructions. Since we want to get
|
||||
saving where we can, we'll always use a `!=0` test and we'll adjust how we
|
||||
initially read the register to compensate.
|
||||
folded and refolded by the compiler, but we can just check.
|
||||
|
||||
It turns out that the `!=0` test is 4 instructions and the `==0` test is 6
|
||||
instructions. Since we want to get savings where we can, and we'll probably
|
||||
check the keys of an input often enough, we'll just always use a `!=0` test and
|
||||
then adjust how we initially read the register to compensate.
|
||||
|
||||
```rust
|
||||
pub fn read_key_input() -> KeyInputSetting {
|
||||
unsafe { KeyInputSetting(KEYINPUT.volatile_read() ^ 0b1111_1111_1111_1111) }
|
||||
unsafe { KeyInputSetting(KEYINPUT.read_volatile() ^ 0b1111_1111_1111_1111) }
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -201,9 +202,9 @@ somehow later on.
|
|||
}
|
||||
```
|
||||
|
||||
So then every frame we can check for `column_direction` and `row_direction` and
|
||||
then apply those to the snake's current position to make it move around the
|
||||
screen.
|
||||
So then in our game, every frame we can check for `column_direction` and
|
||||
`row_direction` and then apply those to the player's current position to make
|
||||
them move around the screen.
|
||||
|
||||
With that settled I think we're all done with user input for now. There's some
|
||||
other things to eventually know about like key interrupts that you can set and
|
||||
|
|
|
@ -1,8 +1,71 @@
|
|||
# The VCount Register
|
||||
|
||||
TODO: describe all the stuff about vcount
|
||||
There's an IO register called
|
||||
[VCOUNT](http://problemkaputt.de/gbatek.htm#lcdiointerruptsandstatus) that shows
|
||||
you, what else, the Vertical (row) COUNT(er). It's a `u16` at address
|
||||
`0x0400_0006`, and it's how we'll be doing our very poor quality vertical sync
|
||||
code to start.
|
||||
|
||||
TODO: mention vblank and hblank
|
||||
* **What makes it poor?** Well, we're just going to read from the vcount value as
|
||||
often as possible every time we need to wait for a specific value to come up,
|
||||
and then proceed once it hits the point we're looking for.
|
||||
* **Why is this bad?** Because we're making the CPU do a lot of useless work,
|
||||
which uses a lot more power that necessary. Even if you're not on an actual
|
||||
GBA you might be running inside an emulator on a phone or other handheld. You
|
||||
wanna try to save battery if all you're doing with that power use is waiting
|
||||
instead of making a game actually do something.
|
||||
* **Can we do better?** We can, but not yet. The better way to do things is to
|
||||
use a BIOS call to put the CPU into low power mode until a VBlank interrupt
|
||||
happens. However, we don't know about interrupts yet, and we don't know about
|
||||
BIOS calls yet, so we'll do the basic thing for now and then upgrade later.
|
||||
|
||||
TODO: mention that this just using this is a BAD way to vsync that will draw a
|
||||
ton of extra power
|
||||
So the way that display hardware actually displays each frame is that it moves a
|
||||
tiny pointer left to right across each pixel row one pixel at a time. When it's
|
||||
within the actual screen width (240px) it's drawing out those pixels. Then it
|
||||
goes _past_ the edge of the screen for 68px during a period known as the
|
||||
"horizontal blank" (hblank). Then it starts on the next row and does that loop
|
||||
over again. This happens for the whole screen height (160px) and then once again
|
||||
it goes past the last row for another 68px into a "vertical blank" (vblank)
|
||||
period.
|
||||
|
||||
* One pixel is 4 CPU cycles
|
||||
* HDraw is 240 pixels, HBlank is 68 pixels (1,232 cycles per full scanline)
|
||||
* VDraw is 150 scanlines, VBlank is 68 scanlines (280,896 cycles per full refresh)
|
||||
|
||||
Now you may remember some stuff from the display control register section where
|
||||
it was mentioned that some parts of memory are best accessed during vblank, and
|
||||
also during hblank with a setting applied. These blanking periods are what was
|
||||
being talked about. At other times if you attempt to access video or object
|
||||
memory you (the CPU) might try touching the same memory that the display device
|
||||
is trying to use, in which case you get bumped back a cycle so that the display
|
||||
can finish what it's doing. Also, if you really insist on doing video memory
|
||||
changes while the screen is being drawn then you might get some visual glitches.
|
||||
If you can, just prepare all your changes ahead of time and then assign then all
|
||||
quickly during the blank period.
|
||||
|
||||
So first we want a way to check the vcount value at all:
|
||||
|
||||
```rust
|
||||
pub const VCOUNT: *mut u16 = 0x0400_0006 as *mut u16;
|
||||
|
||||
pub fn read_vcount() -> u16 {
|
||||
unsafe { VCOUNT.read_volatile() }
|
||||
}
|
||||
```
|
||||
|
||||
Then we want two little helper functions to wait until vblank and vdraw.
|
||||
|
||||
```rust
|
||||
pub const SCREEN_HEIGHT: isize = 160;
|
||||
|
||||
pub fn wait_until_vblank() {
|
||||
while read_vcount() < SCREEN_HEIGHT as u16 {}
|
||||
}
|
||||
|
||||
pub fn wait_until_vdraw() {
|
||||
while read_vcount() >= SCREEN_HEIGHT as u16 {}
|
||||
}
|
||||
```
|
||||
|
||||
And... that's it. No special types to be made this time around, it's just a
|
||||
number we read out of memory.
|
||||
|
|
|
@ -13,3 +13,6 @@ If you want to read more about developing on the GBA there are some other good r
|
|||
* [GBATEK](http://problemkaputt.de/gbatek.htm), a homebrew tech manual for
|
||||
GBA/NDS/DSi. We will regularly link to parts of it when talking about various
|
||||
bits of the GBA.
|
||||
* [CowBite](https://www.cs.rit.edu/~tjh8300/CowBite/CowBiteSpec.htm) is another
|
||||
tech specification that's more GBA specific. It's sometimes got more ASCII
|
||||
art diagrams and example C struct layouts than GBATEK does.
|
||||
|
|
|
@ -72,7 +72,7 @@
|
|||
</script>
|
||||
|
||||
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
||||
<ol class="chapter"><li><a href="../introduction.html"><strong aria-hidden="true">1.</strong> Introduction</a></li><li><a href="../ch0/index.html" class="active"><strong aria-hidden="true">2.</strong> Ch 0: Development Setup</a></li><li><a href="../ch1/index.html"><strong aria-hidden="true">3.</strong> Ch 1: Hello GBA</a></li><li><ol class="section"><li><a href="../ch1/hello1.html"><strong aria-hidden="true">3.1.</strong> hello1</a></li><li><a href="../ch1/io_registers.html"><strong aria-hidden="true">3.2.</strong> IO Registers</a></li><li><a href="../ch1/the_display_control_register.html"><strong aria-hidden="true">3.3.</strong> The Display Control Register</a></li><li><a href="../ch1/video_memory_intro.html"><strong aria-hidden="true">3.4.</strong> Video Memory Intro</a></li><li><a href="../ch1/hello2.html"><strong aria-hidden="true">3.5.</strong> hello2</a></li></ol></li><li><a href="../ch2/index.html"><strong aria-hidden="true">4.</strong> Ch 2: User Input</a></li><li><ol class="section"><li><a href="../ch2/the_key_input_register.html"><strong aria-hidden="true">4.1.</strong> The Key Input Register</a></li><li><a href="../ch2/the_vcount_register.html"><strong aria-hidden="true">4.2.</strong> The VCount Register</a></li><li><a href="../ch2/gba_snake.html"><strong aria-hidden="true">4.3.</strong> gba_snake</a></li></ol></li></ol>
|
||||
<ol class="chapter"><li><a href="../introduction.html"><strong aria-hidden="true">1.</strong> Introduction</a></li><li><a href="../ch0/index.html" class="active"><strong aria-hidden="true">2.</strong> Ch 0: Development Setup</a></li><li><a href="../ch1/index.html"><strong aria-hidden="true">3.</strong> Ch 1: Hello GBA</a></li><li><ol class="section"><li><a href="../ch1/hello1.html"><strong aria-hidden="true">3.1.</strong> hello1</a></li><li><a href="../ch1/io_registers.html"><strong aria-hidden="true">3.2.</strong> IO Registers</a></li><li><a href="../ch1/the_display_control_register.html"><strong aria-hidden="true">3.3.</strong> The Display Control Register</a></li><li><a href="../ch1/video_memory_intro.html"><strong aria-hidden="true">3.4.</strong> Video Memory Intro</a></li><li><a href="../ch1/hello2.html"><strong aria-hidden="true">3.5.</strong> hello2</a></li></ol></li><li><a href="../ch2/index.html"><strong aria-hidden="true">4.</strong> Ch 2: User Input</a></li><li><ol class="section"><li><a href="../ch2/the_key_input_register.html"><strong aria-hidden="true">4.1.</strong> The Key Input Register</a></li><li><a href="../ch2/the_vcount_register.html"><strong aria-hidden="true">4.2.</strong> The VCount Register</a></li><li><a href="../ch2/light_cycle.html"><strong aria-hidden="true">4.3.</strong> light_cycle</a></li></ol></li></ol>
|
||||
</nav>
|
||||
|
||||
<div id="page-wrapper" class="page-wrapper">
|
||||
|
|
|
@ -72,7 +72,7 @@
|
|||
</script>
|
||||
|
||||
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
||||
<ol class="chapter"><li><a href="../introduction.html"><strong aria-hidden="true">1.</strong> Introduction</a></li><li><a href="../ch0/index.html"><strong aria-hidden="true">2.</strong> Ch 0: Development Setup</a></li><li><a href="../ch1/index.html"><strong aria-hidden="true">3.</strong> Ch 1: Hello GBA</a></li><li><ol class="section"><li><a href="../ch1/hello1.html" class="active"><strong aria-hidden="true">3.1.</strong> hello1</a></li><li><a href="../ch1/io_registers.html"><strong aria-hidden="true">3.2.</strong> IO Registers</a></li><li><a href="../ch1/the_display_control_register.html"><strong aria-hidden="true">3.3.</strong> The Display Control Register</a></li><li><a href="../ch1/video_memory_intro.html"><strong aria-hidden="true">3.4.</strong> Video Memory Intro</a></li><li><a href="../ch1/hello2.html"><strong aria-hidden="true">3.5.</strong> hello2</a></li></ol></li><li><a href="../ch2/index.html"><strong aria-hidden="true">4.</strong> Ch 2: User Input</a></li><li><ol class="section"><li><a href="../ch2/the_key_input_register.html"><strong aria-hidden="true">4.1.</strong> The Key Input Register</a></li><li><a href="../ch2/the_vcount_register.html"><strong aria-hidden="true">4.2.</strong> The VCount Register</a></li><li><a href="../ch2/gba_snake.html"><strong aria-hidden="true">4.3.</strong> gba_snake</a></li></ol></li></ol>
|
||||
<ol class="chapter"><li><a href="../introduction.html"><strong aria-hidden="true">1.</strong> Introduction</a></li><li><a href="../ch0/index.html"><strong aria-hidden="true">2.</strong> Ch 0: Development Setup</a></li><li><a href="../ch1/index.html"><strong aria-hidden="true">3.</strong> Ch 1: Hello GBA</a></li><li><ol class="section"><li><a href="../ch1/hello1.html" class="active"><strong aria-hidden="true">3.1.</strong> hello1</a></li><li><a href="../ch1/io_registers.html"><strong aria-hidden="true">3.2.</strong> IO Registers</a></li><li><a href="../ch1/the_display_control_register.html"><strong aria-hidden="true">3.3.</strong> The Display Control Register</a></li><li><a href="../ch1/video_memory_intro.html"><strong aria-hidden="true">3.4.</strong> Video Memory Intro</a></li><li><a href="../ch1/hello2.html"><strong aria-hidden="true">3.5.</strong> hello2</a></li></ol></li><li><a href="../ch2/index.html"><strong aria-hidden="true">4.</strong> Ch 2: User Input</a></li><li><ol class="section"><li><a href="../ch2/the_key_input_register.html"><strong aria-hidden="true">4.1.</strong> The Key Input Register</a></li><li><a href="../ch2/the_vcount_register.html"><strong aria-hidden="true">4.2.</strong> The VCount Register</a></li><li><a href="../ch2/light_cycle.html"><strong aria-hidden="true">4.3.</strong> light_cycle</a></li></ol></li></ol>
|
||||
</nav>
|
||||
|
||||
<div id="page-wrapper" class="page-wrapper">
|
||||
|
|
|
@ -72,7 +72,7 @@
|
|||
</script>
|
||||
|
||||
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
||||
<ol class="chapter"><li><a href="../introduction.html"><strong aria-hidden="true">1.</strong> Introduction</a></li><li><a href="../ch0/index.html"><strong aria-hidden="true">2.</strong> Ch 0: Development Setup</a></li><li><a href="../ch1/index.html"><strong aria-hidden="true">3.</strong> Ch 1: Hello GBA</a></li><li><ol class="section"><li><a href="../ch1/hello1.html"><strong aria-hidden="true">3.1.</strong> hello1</a></li><li><a href="../ch1/io_registers.html"><strong aria-hidden="true">3.2.</strong> IO Registers</a></li><li><a href="../ch1/the_display_control_register.html"><strong aria-hidden="true">3.3.</strong> The Display Control Register</a></li><li><a href="../ch1/video_memory_intro.html"><strong aria-hidden="true">3.4.</strong> Video Memory Intro</a></li><li><a href="../ch1/hello2.html" class="active"><strong aria-hidden="true">3.5.</strong> hello2</a></li></ol></li><li><a href="../ch2/index.html"><strong aria-hidden="true">4.</strong> Ch 2: User Input</a></li><li><ol class="section"><li><a href="../ch2/the_key_input_register.html"><strong aria-hidden="true">4.1.</strong> The Key Input Register</a></li><li><a href="../ch2/the_vcount_register.html"><strong aria-hidden="true">4.2.</strong> The VCount Register</a></li><li><a href="../ch2/gba_snake.html"><strong aria-hidden="true">4.3.</strong> gba_snake</a></li></ol></li></ol>
|
||||
<ol class="chapter"><li><a href="../introduction.html"><strong aria-hidden="true">1.</strong> Introduction</a></li><li><a href="../ch0/index.html"><strong aria-hidden="true">2.</strong> Ch 0: Development Setup</a></li><li><a href="../ch1/index.html"><strong aria-hidden="true">3.</strong> Ch 1: Hello GBA</a></li><li><ol class="section"><li><a href="../ch1/hello1.html"><strong aria-hidden="true">3.1.</strong> hello1</a></li><li><a href="../ch1/io_registers.html"><strong aria-hidden="true">3.2.</strong> IO Registers</a></li><li><a href="../ch1/the_display_control_register.html"><strong aria-hidden="true">3.3.</strong> The Display Control Register</a></li><li><a href="../ch1/video_memory_intro.html"><strong aria-hidden="true">3.4.</strong> Video Memory Intro</a></li><li><a href="../ch1/hello2.html" class="active"><strong aria-hidden="true">3.5.</strong> hello2</a></li></ol></li><li><a href="../ch2/index.html"><strong aria-hidden="true">4.</strong> Ch 2: User Input</a></li><li><ol class="section"><li><a href="../ch2/the_key_input_register.html"><strong aria-hidden="true">4.1.</strong> The Key Input Register</a></li><li><a href="../ch2/the_vcount_register.html"><strong aria-hidden="true">4.2.</strong> The VCount Register</a></li><li><a href="../ch2/light_cycle.html"><strong aria-hidden="true">4.3.</strong> light_cycle</a></li></ol></li></ol>
|
||||
</nav>
|
||||
|
||||
<div id="page-wrapper" class="page-wrapper">
|
||||
|
|
|
@ -72,7 +72,7 @@
|
|||
</script>
|
||||
|
||||
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
||||
<ol class="chapter"><li><a href="../introduction.html"><strong aria-hidden="true">1.</strong> Introduction</a></li><li><a href="../ch0/index.html"><strong aria-hidden="true">2.</strong> Ch 0: Development Setup</a></li><li><a href="../ch1/index.html" class="active"><strong aria-hidden="true">3.</strong> Ch 1: Hello GBA</a></li><li><ol class="section"><li><a href="../ch1/hello1.html"><strong aria-hidden="true">3.1.</strong> hello1</a></li><li><a href="../ch1/io_registers.html"><strong aria-hidden="true">3.2.</strong> IO Registers</a></li><li><a href="../ch1/the_display_control_register.html"><strong aria-hidden="true">3.3.</strong> The Display Control Register</a></li><li><a href="../ch1/video_memory_intro.html"><strong aria-hidden="true">3.4.</strong> Video Memory Intro</a></li><li><a href="../ch1/hello2.html"><strong aria-hidden="true">3.5.</strong> hello2</a></li></ol></li><li><a href="../ch2/index.html"><strong aria-hidden="true">4.</strong> Ch 2: User Input</a></li><li><ol class="section"><li><a href="../ch2/the_key_input_register.html"><strong aria-hidden="true">4.1.</strong> The Key Input Register</a></li><li><a href="../ch2/the_vcount_register.html"><strong aria-hidden="true">4.2.</strong> The VCount Register</a></li><li><a href="../ch2/gba_snake.html"><strong aria-hidden="true">4.3.</strong> gba_snake</a></li></ol></li></ol>
|
||||
<ol class="chapter"><li><a href="../introduction.html"><strong aria-hidden="true">1.</strong> Introduction</a></li><li><a href="../ch0/index.html"><strong aria-hidden="true">2.</strong> Ch 0: Development Setup</a></li><li><a href="../ch1/index.html" class="active"><strong aria-hidden="true">3.</strong> Ch 1: Hello GBA</a></li><li><ol class="section"><li><a href="../ch1/hello1.html"><strong aria-hidden="true">3.1.</strong> hello1</a></li><li><a href="../ch1/io_registers.html"><strong aria-hidden="true">3.2.</strong> IO Registers</a></li><li><a href="../ch1/the_display_control_register.html"><strong aria-hidden="true">3.3.</strong> The Display Control Register</a></li><li><a href="../ch1/video_memory_intro.html"><strong aria-hidden="true">3.4.</strong> Video Memory Intro</a></li><li><a href="../ch1/hello2.html"><strong aria-hidden="true">3.5.</strong> hello2</a></li></ol></li><li><a href="../ch2/index.html"><strong aria-hidden="true">4.</strong> Ch 2: User Input</a></li><li><ol class="section"><li><a href="../ch2/the_key_input_register.html"><strong aria-hidden="true">4.1.</strong> The Key Input Register</a></li><li><a href="../ch2/the_vcount_register.html"><strong aria-hidden="true">4.2.</strong> The VCount Register</a></li><li><a href="../ch2/light_cycle.html"><strong aria-hidden="true">4.3.</strong> light_cycle</a></li></ol></li></ol>
|
||||
</nav>
|
||||
|
||||
<div id="page-wrapper" class="page-wrapper">
|
||||
|
|
|
@ -72,7 +72,7 @@
|
|||
</script>
|
||||
|
||||
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
||||
<ol class="chapter"><li><a href="../introduction.html"><strong aria-hidden="true">1.</strong> Introduction</a></li><li><a href="../ch0/index.html"><strong aria-hidden="true">2.</strong> Ch 0: Development Setup</a></li><li><a href="../ch1/index.html"><strong aria-hidden="true">3.</strong> Ch 1: Hello GBA</a></li><li><ol class="section"><li><a href="../ch1/hello1.html"><strong aria-hidden="true">3.1.</strong> hello1</a></li><li><a href="../ch1/io_registers.html" class="active"><strong aria-hidden="true">3.2.</strong> IO Registers</a></li><li><a href="../ch1/the_display_control_register.html"><strong aria-hidden="true">3.3.</strong> The Display Control Register</a></li><li><a href="../ch1/video_memory_intro.html"><strong aria-hidden="true">3.4.</strong> Video Memory Intro</a></li><li><a href="../ch1/hello2.html"><strong aria-hidden="true">3.5.</strong> hello2</a></li></ol></li><li><a href="../ch2/index.html"><strong aria-hidden="true">4.</strong> Ch 2: User Input</a></li><li><ol class="section"><li><a href="../ch2/the_key_input_register.html"><strong aria-hidden="true">4.1.</strong> The Key Input Register</a></li><li><a href="../ch2/the_vcount_register.html"><strong aria-hidden="true">4.2.</strong> The VCount Register</a></li><li><a href="../ch2/gba_snake.html"><strong aria-hidden="true">4.3.</strong> gba_snake</a></li></ol></li></ol>
|
||||
<ol class="chapter"><li><a href="../introduction.html"><strong aria-hidden="true">1.</strong> Introduction</a></li><li><a href="../ch0/index.html"><strong aria-hidden="true">2.</strong> Ch 0: Development Setup</a></li><li><a href="../ch1/index.html"><strong aria-hidden="true">3.</strong> Ch 1: Hello GBA</a></li><li><ol class="section"><li><a href="../ch1/hello1.html"><strong aria-hidden="true">3.1.</strong> hello1</a></li><li><a href="../ch1/io_registers.html" class="active"><strong aria-hidden="true">3.2.</strong> IO Registers</a></li><li><a href="../ch1/the_display_control_register.html"><strong aria-hidden="true">3.3.</strong> The Display Control Register</a></li><li><a href="../ch1/video_memory_intro.html"><strong aria-hidden="true">3.4.</strong> Video Memory Intro</a></li><li><a href="../ch1/hello2.html"><strong aria-hidden="true">3.5.</strong> hello2</a></li></ol></li><li><a href="../ch2/index.html"><strong aria-hidden="true">4.</strong> Ch 2: User Input</a></li><li><ol class="section"><li><a href="../ch2/the_key_input_register.html"><strong aria-hidden="true">4.1.</strong> The Key Input Register</a></li><li><a href="../ch2/the_vcount_register.html"><strong aria-hidden="true">4.2.</strong> The VCount Register</a></li><li><a href="../ch2/light_cycle.html"><strong aria-hidden="true">4.3.</strong> light_cycle</a></li></ol></li></ol>
|
||||
</nav>
|
||||
|
||||
<div id="page-wrapper" class="page-wrapper">
|
||||
|
|
|
@ -72,7 +72,7 @@
|
|||
</script>
|
||||
|
||||
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
||||
<ol class="chapter"><li><a href="../introduction.html"><strong aria-hidden="true">1.</strong> Introduction</a></li><li><a href="../ch0/index.html"><strong aria-hidden="true">2.</strong> Ch 0: Development Setup</a></li><li><a href="../ch1/index.html"><strong aria-hidden="true">3.</strong> Ch 1: Hello GBA</a></li><li><ol class="section"><li><a href="../ch1/hello1.html"><strong aria-hidden="true">3.1.</strong> hello1</a></li><li><a href="../ch1/io_registers.html"><strong aria-hidden="true">3.2.</strong> IO Registers</a></li><li><a href="../ch1/the_display_control_register.html" class="active"><strong aria-hidden="true">3.3.</strong> The Display Control Register</a></li><li><a href="../ch1/video_memory_intro.html"><strong aria-hidden="true">3.4.</strong> Video Memory Intro</a></li><li><a href="../ch1/hello2.html"><strong aria-hidden="true">3.5.</strong> hello2</a></li></ol></li><li><a href="../ch2/index.html"><strong aria-hidden="true">4.</strong> Ch 2: User Input</a></li><li><ol class="section"><li><a href="../ch2/the_key_input_register.html"><strong aria-hidden="true">4.1.</strong> The Key Input Register</a></li><li><a href="../ch2/the_vcount_register.html"><strong aria-hidden="true">4.2.</strong> The VCount Register</a></li><li><a href="../ch2/gba_snake.html"><strong aria-hidden="true">4.3.</strong> gba_snake</a></li></ol></li></ol>
|
||||
<ol class="chapter"><li><a href="../introduction.html"><strong aria-hidden="true">1.</strong> Introduction</a></li><li><a href="../ch0/index.html"><strong aria-hidden="true">2.</strong> Ch 0: Development Setup</a></li><li><a href="../ch1/index.html"><strong aria-hidden="true">3.</strong> Ch 1: Hello GBA</a></li><li><ol class="section"><li><a href="../ch1/hello1.html"><strong aria-hidden="true">3.1.</strong> hello1</a></li><li><a href="../ch1/io_registers.html"><strong aria-hidden="true">3.2.</strong> IO Registers</a></li><li><a href="../ch1/the_display_control_register.html" class="active"><strong aria-hidden="true">3.3.</strong> The Display Control Register</a></li><li><a href="../ch1/video_memory_intro.html"><strong aria-hidden="true">3.4.</strong> Video Memory Intro</a></li><li><a href="../ch1/hello2.html"><strong aria-hidden="true">3.5.</strong> hello2</a></li></ol></li><li><a href="../ch2/index.html"><strong aria-hidden="true">4.</strong> Ch 2: User Input</a></li><li><ol class="section"><li><a href="../ch2/the_key_input_register.html"><strong aria-hidden="true">4.1.</strong> The Key Input Register</a></li><li><a href="../ch2/the_vcount_register.html"><strong aria-hidden="true">4.2.</strong> The VCount Register</a></li><li><a href="../ch2/light_cycle.html"><strong aria-hidden="true">4.3.</strong> light_cycle</a></li></ol></li></ol>
|
||||
</nav>
|
||||
|
||||
<div id="page-wrapper" class="page-wrapper">
|
||||
|
|
|
@ -72,7 +72,7 @@
|
|||
</script>
|
||||
|
||||
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
||||
<ol class="chapter"><li><a href="../introduction.html"><strong aria-hidden="true">1.</strong> Introduction</a></li><li><a href="../ch0/index.html"><strong aria-hidden="true">2.</strong> Ch 0: Development Setup</a></li><li><a href="../ch1/index.html"><strong aria-hidden="true">3.</strong> Ch 1: Hello GBA</a></li><li><ol class="section"><li><a href="../ch1/hello1.html"><strong aria-hidden="true">3.1.</strong> hello1</a></li><li><a href="../ch1/io_registers.html"><strong aria-hidden="true">3.2.</strong> IO Registers</a></li><li><a href="../ch1/the_display_control_register.html"><strong aria-hidden="true">3.3.</strong> The Display Control Register</a></li><li><a href="../ch1/video_memory_intro.html" class="active"><strong aria-hidden="true">3.4.</strong> Video Memory Intro</a></li><li><a href="../ch1/hello2.html"><strong aria-hidden="true">3.5.</strong> hello2</a></li></ol></li><li><a href="../ch2/index.html"><strong aria-hidden="true">4.</strong> Ch 2: User Input</a></li><li><ol class="section"><li><a href="../ch2/the_key_input_register.html"><strong aria-hidden="true">4.1.</strong> The Key Input Register</a></li><li><a href="../ch2/the_vcount_register.html"><strong aria-hidden="true">4.2.</strong> The VCount Register</a></li><li><a href="../ch2/gba_snake.html"><strong aria-hidden="true">4.3.</strong> gba_snake</a></li></ol></li></ol>
|
||||
<ol class="chapter"><li><a href="../introduction.html"><strong aria-hidden="true">1.</strong> Introduction</a></li><li><a href="../ch0/index.html"><strong aria-hidden="true">2.</strong> Ch 0: Development Setup</a></li><li><a href="../ch1/index.html"><strong aria-hidden="true">3.</strong> Ch 1: Hello GBA</a></li><li><ol class="section"><li><a href="../ch1/hello1.html"><strong aria-hidden="true">3.1.</strong> hello1</a></li><li><a href="../ch1/io_registers.html"><strong aria-hidden="true">3.2.</strong> IO Registers</a></li><li><a href="../ch1/the_display_control_register.html"><strong aria-hidden="true">3.3.</strong> The Display Control Register</a></li><li><a href="../ch1/video_memory_intro.html" class="active"><strong aria-hidden="true">3.4.</strong> Video Memory Intro</a></li><li><a href="../ch1/hello2.html"><strong aria-hidden="true">3.5.</strong> hello2</a></li></ol></li><li><a href="../ch2/index.html"><strong aria-hidden="true">4.</strong> Ch 2: User Input</a></li><li><ol class="section"><li><a href="../ch2/the_key_input_register.html"><strong aria-hidden="true">4.1.</strong> The Key Input Register</a></li><li><a href="../ch2/the_vcount_register.html"><strong aria-hidden="true">4.2.</strong> The VCount Register</a></li><li><a href="../ch2/light_cycle.html"><strong aria-hidden="true">4.3.</strong> light_cycle</a></li></ol></li></ol>
|
||||
</nav>
|
||||
|
||||
<div id="page-wrapper" class="page-wrapper">
|
||||
|
|
|
@ -72,7 +72,7 @@
|
|||
</script>
|
||||
|
||||
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
||||
<ol class="chapter"><li><a href="../introduction.html"><strong aria-hidden="true">1.</strong> Introduction</a></li><li><a href="../ch0/index.html"><strong aria-hidden="true">2.</strong> Ch 0: Development Setup</a></li><li><a href="../ch1/index.html"><strong aria-hidden="true">3.</strong> Ch 1: Hello GBA</a></li><li><ol class="section"><li><a href="../ch1/hello1.html"><strong aria-hidden="true">3.1.</strong> hello1</a></li><li><a href="../ch1/io_registers.html"><strong aria-hidden="true">3.2.</strong> IO Registers</a></li><li><a href="../ch1/the_display_control_register.html"><strong aria-hidden="true">3.3.</strong> The Display Control Register</a></li><li><a href="../ch1/video_memory_intro.html"><strong aria-hidden="true">3.4.</strong> Video Memory Intro</a></li><li><a href="../ch1/hello2.html"><strong aria-hidden="true">3.5.</strong> hello2</a></li></ol></li><li><a href="../ch2/index.html" class="active"><strong aria-hidden="true">4.</strong> Ch 2: User Input</a></li><li><ol class="section"><li><a href="../ch2/the_key_input_register.html"><strong aria-hidden="true">4.1.</strong> The Key Input Register</a></li><li><a href="../ch2/the_vcount_register.html"><strong aria-hidden="true">4.2.</strong> The VCount Register</a></li><li><a href="../ch2/gba_snake.html"><strong aria-hidden="true">4.3.</strong> gba_snake</a></li></ol></li></ol>
|
||||
<ol class="chapter"><li><a href="../introduction.html"><strong aria-hidden="true">1.</strong> Introduction</a></li><li><a href="../ch0/index.html"><strong aria-hidden="true">2.</strong> Ch 0: Development Setup</a></li><li><a href="../ch1/index.html"><strong aria-hidden="true">3.</strong> Ch 1: Hello GBA</a></li><li><ol class="section"><li><a href="../ch1/hello1.html"><strong aria-hidden="true">3.1.</strong> hello1</a></li><li><a href="../ch1/io_registers.html"><strong aria-hidden="true">3.2.</strong> IO Registers</a></li><li><a href="../ch1/the_display_control_register.html"><strong aria-hidden="true">3.3.</strong> The Display Control Register</a></li><li><a href="../ch1/video_memory_intro.html"><strong aria-hidden="true">3.4.</strong> Video Memory Intro</a></li><li><a href="../ch1/hello2.html"><strong aria-hidden="true">3.5.</strong> hello2</a></li></ol></li><li><a href="../ch2/index.html" class="active"><strong aria-hidden="true">4.</strong> Ch 2: User Input</a></li><li><ol class="section"><li><a href="../ch2/the_key_input_register.html"><strong aria-hidden="true">4.1.</strong> The Key Input Register</a></li><li><a href="../ch2/the_vcount_register.html"><strong aria-hidden="true">4.2.</strong> The VCount Register</a></li><li><a href="../ch2/light_cycle.html"><strong aria-hidden="true">4.3.</strong> light_cycle</a></li></ol></li></ol>
|
||||
</nav>
|
||||
|
||||
<div id="page-wrapper" class="page-wrapper">
|
||||
|
@ -146,11 +146,14 @@ SNES had. As you can guess, we get key state info from an IO register.</p>
|
|||
modern computer or console you do this with vsync info from the GPU and Monitor,
|
||||
and on the GBA we'll be using vsync info from an IO register that tracks what
|
||||
the display hardware is doing.</p>
|
||||
<p>For this chapter we'll make a copy of <code>hello2.rs</code> named <code>snake.rs</code> and then fill
|
||||
it in as we go. Normally you might not place the entire program into a single
|
||||
source file, but since these are examples it's slightly better to have them be
|
||||
completely self contained than it is to have them be "properly organized" for
|
||||
the long term.</p>
|
||||
<p>As a way to apply our knowledge We'll make a simply "light cycle" game where
|
||||
your dot leaves a trail behind them and you die if you go off the screen or if
|
||||
you touch your own trail. We just make a copy of <code>hello2.rs</code> named
|
||||
<code>light_cycle.rs</code> and then fill it in as we go through the chapter. Normally you
|
||||
might not place the entire program into a single source file, particularly as it
|
||||
grows over time, but since these are small examples it's much better to have
|
||||
them be completely self contained than it is to have them be "properly
|
||||
organized" for the long term.</p>
|
||||
|
||||
</main>
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<head>
|
||||
<!-- Book generated using mdBook -->
|
||||
<meta charset="UTF-8">
|
||||
<title>gba_snake - Rust GBA Tutorials</title>
|
||||
<title>light_cycle - Rust GBA Tutorials</title>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
|
||||
<meta name="description" content="">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
@ -72,7 +72,7 @@
|
|||
</script>
|
||||
|
||||
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
||||
<ol class="chapter"><li><a href="../introduction.html"><strong aria-hidden="true">1.</strong> Introduction</a></li><li><a href="../ch0/index.html"><strong aria-hidden="true">2.</strong> Ch 0: Development Setup</a></li><li><a href="../ch1/index.html"><strong aria-hidden="true">3.</strong> Ch 1: Hello GBA</a></li><li><ol class="section"><li><a href="../ch1/hello1.html"><strong aria-hidden="true">3.1.</strong> hello1</a></li><li><a href="../ch1/io_registers.html"><strong aria-hidden="true">3.2.</strong> IO Registers</a></li><li><a href="../ch1/the_display_control_register.html"><strong aria-hidden="true">3.3.</strong> The Display Control Register</a></li><li><a href="../ch1/video_memory_intro.html"><strong aria-hidden="true">3.4.</strong> Video Memory Intro</a></li><li><a href="../ch1/hello2.html"><strong aria-hidden="true">3.5.</strong> hello2</a></li></ol></li><li><a href="../ch2/index.html"><strong aria-hidden="true">4.</strong> Ch 2: User Input</a></li><li><ol class="section"><li><a href="../ch2/the_key_input_register.html"><strong aria-hidden="true">4.1.</strong> The Key Input Register</a></li><li><a href="../ch2/the_vcount_register.html"><strong aria-hidden="true">4.2.</strong> The VCount Register</a></li><li><a href="../ch2/gba_snake.html" class="active"><strong aria-hidden="true">4.3.</strong> gba_snake</a></li></ol></li></ol>
|
||||
<ol class="chapter"><li><a href="../introduction.html"><strong aria-hidden="true">1.</strong> Introduction</a></li><li><a href="../ch0/index.html"><strong aria-hidden="true">2.</strong> Ch 0: Development Setup</a></li><li><a href="../ch1/index.html"><strong aria-hidden="true">3.</strong> Ch 1: Hello GBA</a></li><li><ol class="section"><li><a href="../ch1/hello1.html"><strong aria-hidden="true">3.1.</strong> hello1</a></li><li><a href="../ch1/io_registers.html"><strong aria-hidden="true">3.2.</strong> IO Registers</a></li><li><a href="../ch1/the_display_control_register.html"><strong aria-hidden="true">3.3.</strong> The Display Control Register</a></li><li><a href="../ch1/video_memory_intro.html"><strong aria-hidden="true">3.4.</strong> Video Memory Intro</a></li><li><a href="../ch1/hello2.html"><strong aria-hidden="true">3.5.</strong> hello2</a></li></ol></li><li><a href="../ch2/index.html"><strong aria-hidden="true">4.</strong> Ch 2: User Input</a></li><li><ol class="section"><li><a href="../ch2/the_key_input_register.html"><strong aria-hidden="true">4.1.</strong> The Key Input Register</a></li><li><a href="../ch2/the_vcount_register.html"><strong aria-hidden="true">4.2.</strong> The VCount Register</a></li><li><a href="../ch2/light_cycle.html" class="active"><strong aria-hidden="true">4.3.</strong> light_cycle</a></li></ol></li></ol>
|
||||
</nav>
|
||||
|
||||
<div id="page-wrapper" class="page-wrapper">
|
||||
|
@ -136,8 +136,109 @@
|
|||
|
||||
<div id="content" class="content">
|
||||
<main>
|
||||
<a class="header" href="#gba_snake" id="gba_snake"><h1>gba_snake</h1></a>
|
||||
<p>TODO: make a "snake game" using arrow key input and vsync</p>
|
||||
<a class="header" href="#light_cycle" id="light_cycle"><h1>light_cycle</h1></a>
|
||||
<p>Now let's make a game of "light_cycle" with our new knowledge.</p>
|
||||
<a class="header" href="#gameplay" id="gameplay"><h2>Gameplay</h2></a>
|
||||
<p><code>light_cycle</code> is pretty simple, and very obvious if you've ever seen Tron. The
|
||||
player moves around the screen with a trail left behind them. They die if they
|
||||
go off the screen or if they touch their own trail.</p>
|
||||
<a class="header" href="#operations" id="operations"><h2>Operations</h2></a>
|
||||
<p>We need some better drawing operations this time around.</p>
|
||||
<pre><pre class="playpen"><code class="language-rust">
|
||||
# #![allow(unused_variables)]
|
||||
#fn main() {
|
||||
pub unsafe fn mode3_clear_screen(color: u16) {
|
||||
let color = color as u32;
|
||||
let bulk_color = color << 16 | color;
|
||||
let mut ptr = VRAM as *mut u32;
|
||||
for _ in 0..SCREEN_HEIGHT {
|
||||
for _ in 0..(SCREEN_WIDTH / 2) {
|
||||
ptr.write_volatile(bulk_color);
|
||||
ptr = ptr.offset(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn mode3_draw_pixel(col: isize, row: isize, color: u16) {
|
||||
(VRAM as *mut u16).offset(col + row * SCREEN_WIDTH).write_volatile(color);
|
||||
}
|
||||
|
||||
pub unsafe fn mode3_read_pixel(col: isize, row: isize) -> u16 {
|
||||
(VRAM as *mut u16).offset(col + row * SCREEN_WIDTH).read_volatile()
|
||||
}
|
||||
#}</code></pre></pre>
|
||||
<p>The draw pixel and read pixel are both pretty obvious. What's new is the clear
|
||||
screen operation. It changes the <code>u16</code> color into a <code>u32</code> and then packs the
|
||||
value in twice. Then we write out <code>u32</code> values the whole way through screen
|
||||
memory. This means we have to do less write operations overall, and so the
|
||||
screen clear is twice as fast.</p>
|
||||
<p>Now we just have to fill in the main function:</p>
|
||||
<pre><pre class="playpen"><code class="language-rust">#[start]
|
||||
fn main(_argc: isize, _argv: *const *const u8) -> isize {
|
||||
unsafe {
|
||||
DISPCNT.write_volatile(MODE3 | BG2);
|
||||
}
|
||||
|
||||
let mut px = SCREEN_WIDTH / 2;
|
||||
let mut py = SCREEN_HEIGHT / 2;
|
||||
let mut color = rgb16(31, 0, 0);
|
||||
|
||||
loop {
|
||||
// read the input for this frame
|
||||
let this_frame_keys = read_key_input();
|
||||
|
||||
// adjust game state and wait for vblank
|
||||
px += 2 * this_frame_keys.column_direction() as isize;
|
||||
py += 2 * this_frame_keys.row_direction() as isize;
|
||||
wait_until_vblank();
|
||||
|
||||
// draw the new game and wait until the next frame starts.
|
||||
unsafe {
|
||||
if px < 0 || py < 0 || px == SCREEN_WIDTH || py == SCREEN_HEIGHT {
|
||||
// out of bounds, reset the screen and position.
|
||||
mode3_clear_screen(0);
|
||||
color = color.rotate_left(5);
|
||||
px = SCREEN_WIDTH / 2;
|
||||
py = SCREEN_HEIGHT / 2;
|
||||
} else {
|
||||
let color_here = mode3_read_pixel(px, py);
|
||||
if color_here != 0 {
|
||||
// crashed into our own line, reset the screen
|
||||
mode3_clear_screen(0);
|
||||
color = color.rotate_left(5);
|
||||
} else {
|
||||
// draw the new part of the line
|
||||
mode3_draw_pixel(px, py, color);
|
||||
mode3_draw_pixel(px, py + 1, color);
|
||||
mode3_draw_pixel(px + 1, py, color);
|
||||
mode3_draw_pixel(px + 1, py + 1, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
wait_until_vdraw();
|
||||
}
|
||||
}
|
||||
</code></pre></pre>
|
||||
<p>Oh that's a lot more than before!</p>
|
||||
<p>First we set Mode 3 and Background 2, we know about that.</p>
|
||||
<p>Then we're going to store the player's x and y, along with a color value for
|
||||
their light cycle. Then we enter the core loop.</p>
|
||||
<p>We read the keys for input, and then do as much as we can without touching video
|
||||
memory. Since we're using video memory as the place to store the player's light
|
||||
trail, we can't do much, we just update their position and wait for vblank to
|
||||
start. The player will be a 2x2 square, so the arrows will move you 2 pixels per
|
||||
frame.</p>
|
||||
<p>Once we're in vblank we check to see what kind of drawing we're doing. If the
|
||||
player has gone out of bounds, we clear the screen, rotate their color, and then
|
||||
reset their position. Why rotate the color? Just because it's fun to have
|
||||
different colors.</p>
|
||||
<p>Next, if the player is in bounds we read the video memory for their position. If
|
||||
it's not black that means we've been here before and the player has crashed into
|
||||
their own line. In this case, we reset the game without moving them to a new
|
||||
location.</p>
|
||||
<p>Finally, if the player is in bounds and they haven't crashed, we write their color into memory at this position.</p>
|
||||
<p>Regardless of how it worked out, we hold here until vdraw starts before going to
|
||||
the next loop.</p>
|
||||
|
||||
</main>
|
||||
|
|
@ -72,7 +72,7 @@
|
|||
</script>
|
||||
|
||||
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
||||
<ol class="chapter"><li><a href="../introduction.html"><strong aria-hidden="true">1.</strong> Introduction</a></li><li><a href="../ch0/index.html"><strong aria-hidden="true">2.</strong> Ch 0: Development Setup</a></li><li><a href="../ch1/index.html"><strong aria-hidden="true">3.</strong> Ch 1: Hello GBA</a></li><li><ol class="section"><li><a href="../ch1/hello1.html"><strong aria-hidden="true">3.1.</strong> hello1</a></li><li><a href="../ch1/io_registers.html"><strong aria-hidden="true">3.2.</strong> IO Registers</a></li><li><a href="../ch1/the_display_control_register.html"><strong aria-hidden="true">3.3.</strong> The Display Control Register</a></li><li><a href="../ch1/video_memory_intro.html"><strong aria-hidden="true">3.4.</strong> Video Memory Intro</a></li><li><a href="../ch1/hello2.html"><strong aria-hidden="true">3.5.</strong> hello2</a></li></ol></li><li><a href="../ch2/index.html"><strong aria-hidden="true">4.</strong> Ch 2: User Input</a></li><li><ol class="section"><li><a href="../ch2/the_key_input_register.html" class="active"><strong aria-hidden="true">4.1.</strong> The Key Input Register</a></li><li><a href="../ch2/the_vcount_register.html"><strong aria-hidden="true">4.2.</strong> The VCount Register</a></li><li><a href="../ch2/gba_snake.html"><strong aria-hidden="true">4.3.</strong> gba_snake</a></li></ol></li></ol>
|
||||
<ol class="chapter"><li><a href="../introduction.html"><strong aria-hidden="true">1.</strong> Introduction</a></li><li><a href="../ch0/index.html"><strong aria-hidden="true">2.</strong> Ch 0: Development Setup</a></li><li><a href="../ch1/index.html"><strong aria-hidden="true">3.</strong> Ch 1: Hello GBA</a></li><li><ol class="section"><li><a href="../ch1/hello1.html"><strong aria-hidden="true">3.1.</strong> hello1</a></li><li><a href="../ch1/io_registers.html"><strong aria-hidden="true">3.2.</strong> IO Registers</a></li><li><a href="../ch1/the_display_control_register.html"><strong aria-hidden="true">3.3.</strong> The Display Control Register</a></li><li><a href="../ch1/video_memory_intro.html"><strong aria-hidden="true">3.4.</strong> Video Memory Intro</a></li><li><a href="../ch1/hello2.html"><strong aria-hidden="true">3.5.</strong> hello2</a></li></ol></li><li><a href="../ch2/index.html"><strong aria-hidden="true">4.</strong> Ch 2: User Input</a></li><li><ol class="section"><li><a href="../ch2/the_key_input_register.html" class="active"><strong aria-hidden="true">4.1.</strong> The Key Input Register</a></li><li><a href="../ch2/the_vcount_register.html"><strong aria-hidden="true">4.2.</strong> The VCount Register</a></li><li><a href="../ch2/light_cycle.html"><strong aria-hidden="true">4.3.</strong> light_cycle</a></li></ol></li></ol>
|
||||
</nav>
|
||||
|
||||
<div id="page-wrapper" class="page-wrapper">
|
||||
|
@ -193,7 +193,7 @@ pub const KEYINPUT: *mut u16 = 0x400_0130 as *mut u16;
|
|||
pub struct KeyInputSetting(u16);
|
||||
|
||||
pub fn read_key_input() -> KeyInputSetting {
|
||||
unsafe { KeyInputSetting(KEYINPUT.volatile_read()) }
|
||||
unsafe { KeyInputSetting(KEYINPUT.read_volatile()) }
|
||||
}
|
||||
#}</code></pre></pre>
|
||||
<p>Now we want a way to check if a key is <em>being pressed</em>, since that's normally
|
||||
|
@ -214,16 +214,16 @@ something. Also, we've set the target to <code>thumbv6m-none-eabi</code>, which
|
|||
slightly later version of ARM than the actual GBA, but it's close enough for
|
||||
just checking. Of course, in a full program small functions like these will
|
||||
probably get inlined into the calling code and disappear entirely as they're
|
||||
folded and refolded by the compiler, and so on. Still, we can see that in the
|
||||
simple case when the compiler doesn't know any extra context, the <code>!=0</code> test is
|
||||
4 instructions and the <code>==0</code> test is six instructions. Since we want to get
|
||||
saving where we can, we'll always use a <code>!=0</code> test and we'll adjust how we
|
||||
initially read the register to compensate.</p>
|
||||
folded and refolded by the compiler, but we can just check.</p>
|
||||
<p>It turns out that the <code>!=0</code> test is 4 instructions and the <code>==0</code> test is 6
|
||||
instructions. Since we want to get savings where we can, and we'll probably
|
||||
check the keys of an input often enough, we'll just always use a <code>!=0</code> test and
|
||||
then adjust how we initially read the register to compensate.</p>
|
||||
<pre><pre class="playpen"><code class="language-rust">
|
||||
# #![allow(unused_variables)]
|
||||
#fn main() {
|
||||
pub fn read_key_input() -> KeyInputSetting {
|
||||
unsafe { KeyInputSetting(KEYINPUT.volatile_read() ^ 0b1111_1111_1111_1111) }
|
||||
unsafe { KeyInputSetting(KEYINPUT.read_volatile() ^ 0b1111_1111_1111_1111) }
|
||||
}
|
||||
#}</code></pre></pre>
|
||||
<p>Now we add a method for seeing if a key is pressed. In the full library there's
|
||||
|
@ -327,9 +327,9 @@ somehow later on.</p>
|
|||
}
|
||||
}
|
||||
#}</code></pre></pre>
|
||||
<p>So then every frame we can check for <code>column_direction</code> and <code>row_direction</code> and
|
||||
then apply those to the snake's current position to make it move around the
|
||||
screen.</p>
|
||||
<p>So then in our game, every frame we can check for <code>column_direction</code> and
|
||||
<code>row_direction</code> and then apply those to the player's current position to make
|
||||
them move around the screen.</p>
|
||||
<p>With that settled I think we're all done with user input for now. There's some
|
||||
other things to eventually know about like key interrupts that you can set and
|
||||
stuff, but we'll cover that later on because it's not necessary right now.</p>
|
||||
|
|
|
@ -72,7 +72,7 @@
|
|||
</script>
|
||||
|
||||
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
||||
<ol class="chapter"><li><a href="../introduction.html"><strong aria-hidden="true">1.</strong> Introduction</a></li><li><a href="../ch0/index.html"><strong aria-hidden="true">2.</strong> Ch 0: Development Setup</a></li><li><a href="../ch1/index.html"><strong aria-hidden="true">3.</strong> Ch 1: Hello GBA</a></li><li><ol class="section"><li><a href="../ch1/hello1.html"><strong aria-hidden="true">3.1.</strong> hello1</a></li><li><a href="../ch1/io_registers.html"><strong aria-hidden="true">3.2.</strong> IO Registers</a></li><li><a href="../ch1/the_display_control_register.html"><strong aria-hidden="true">3.3.</strong> The Display Control Register</a></li><li><a href="../ch1/video_memory_intro.html"><strong aria-hidden="true">3.4.</strong> Video Memory Intro</a></li><li><a href="../ch1/hello2.html"><strong aria-hidden="true">3.5.</strong> hello2</a></li></ol></li><li><a href="../ch2/index.html"><strong aria-hidden="true">4.</strong> Ch 2: User Input</a></li><li><ol class="section"><li><a href="../ch2/the_key_input_register.html"><strong aria-hidden="true">4.1.</strong> The Key Input Register</a></li><li><a href="../ch2/the_vcount_register.html" class="active"><strong aria-hidden="true">4.2.</strong> The VCount Register</a></li><li><a href="../ch2/gba_snake.html"><strong aria-hidden="true">4.3.</strong> gba_snake</a></li></ol></li></ol>
|
||||
<ol class="chapter"><li><a href="../introduction.html"><strong aria-hidden="true">1.</strong> Introduction</a></li><li><a href="../ch0/index.html"><strong aria-hidden="true">2.</strong> Ch 0: Development Setup</a></li><li><a href="../ch1/index.html"><strong aria-hidden="true">3.</strong> Ch 1: Hello GBA</a></li><li><ol class="section"><li><a href="../ch1/hello1.html"><strong aria-hidden="true">3.1.</strong> hello1</a></li><li><a href="../ch1/io_registers.html"><strong aria-hidden="true">3.2.</strong> IO Registers</a></li><li><a href="../ch1/the_display_control_register.html"><strong aria-hidden="true">3.3.</strong> The Display Control Register</a></li><li><a href="../ch1/video_memory_intro.html"><strong aria-hidden="true">3.4.</strong> Video Memory Intro</a></li><li><a href="../ch1/hello2.html"><strong aria-hidden="true">3.5.</strong> hello2</a></li></ol></li><li><a href="../ch2/index.html"><strong aria-hidden="true">4.</strong> Ch 2: User Input</a></li><li><ol class="section"><li><a href="../ch2/the_key_input_register.html"><strong aria-hidden="true">4.1.</strong> The Key Input Register</a></li><li><a href="../ch2/the_vcount_register.html" class="active"><strong aria-hidden="true">4.2.</strong> The VCount Register</a></li><li><a href="../ch2/light_cycle.html"><strong aria-hidden="true">4.3.</strong> light_cycle</a></li></ol></li></ol>
|
||||
</nav>
|
||||
|
||||
<div id="page-wrapper" class="page-wrapper">
|
||||
|
@ -137,10 +137,74 @@
|
|||
<div id="content" class="content">
|
||||
<main>
|
||||
<a class="header" href="#the-vcount-register" id="the-vcount-register"><h1>The VCount Register</h1></a>
|
||||
<p>TODO: describe all the stuff about vcount</p>
|
||||
<p>TODO: mention vblank and hblank</p>
|
||||
<p>TODO: mention that this just using this is a BAD way to vsync that will draw a
|
||||
ton of extra power</p>
|
||||
<p>There's an IO register called
|
||||
<a href="http://problemkaputt.de/gbatek.htm#lcdiointerruptsandstatus">VCOUNT</a> that shows
|
||||
you, what else, the Vertical (row) COUNT(er). It's a <code>u16</code> at address
|
||||
<code>0x0400_0006</code>, and it's how we'll be doing our very poor quality vertical sync
|
||||
code to start.</p>
|
||||
<ul>
|
||||
<li><strong>What makes it poor?</strong> Well, we're just going to read from the vcount value as
|
||||
often as possible every time we need to wait for a specific value to come up,
|
||||
and then proceed once it hits the point we're looking for.</li>
|
||||
<li><strong>Why is this bad?</strong> Because we're making the CPU do a lot of useless work,
|
||||
which uses a lot more power that necessary. Even if you're not on an actual
|
||||
GBA you might be running inside an emulator on a phone or other handheld. You
|
||||
wanna try to save battery if all you're doing with that power use is waiting
|
||||
instead of making a game actually do something.</li>
|
||||
<li><strong>Can we do better?</strong> We can, but not yet. The better way to do things is to
|
||||
use a BIOS call to put the CPU into low power mode until a VBlank interrupt
|
||||
happens. However, we don't know about interrupts yet, and we don't know about
|
||||
BIOS calls yet, so we'll do the basic thing for now and then upgrade later.</li>
|
||||
</ul>
|
||||
<p>So the way that display hardware actually displays each frame is that it moves a
|
||||
tiny pointer left to right across each pixel row one pixel at a time. When it's
|
||||
within the actual screen width (240px) it's drawing out those pixels. Then it
|
||||
goes <em>past</em> the edge of the screen for 68px during a period known as the
|
||||
"horizontal blank" (hblank). Then it starts on the next row and does that loop
|
||||
over again. This happens for the whole screen height (160px) and then once again
|
||||
it goes past the last row for another 68px into a "vertical blank" (vblank)
|
||||
period.</p>
|
||||
<ul>
|
||||
<li>One pixel is 4 CPU cycles</li>
|
||||
<li>HDraw is 240 pixels, HBlank is 68 pixels (1,232 cycles per full scanline)</li>
|
||||
<li>VDraw is 150 scanlines, VBlank is 68 scanlines (280,896 cycles per full refresh)</li>
|
||||
</ul>
|
||||
<p>Now you may remember some stuff from the display control register section where
|
||||
it was mentioned that some parts of memory are best accessed during vblank, and
|
||||
also during hblank with a setting applied. These blanking periods are what was
|
||||
being talked about. At other times if you attempt to access video or object
|
||||
memory you (the CPU) might try touching the same memory that the display device
|
||||
is trying to use, in which case you get bumped back a cycle so that the display
|
||||
can finish what it's doing. Also, if you really insist on doing video memory
|
||||
changes while the screen is being drawn then you might get some visual glitches.
|
||||
If you can, just prepare all your changes ahead of time and then assign then all
|
||||
quickly during the blank period.</p>
|
||||
<p>So first we want a way to check the vcount value at all:</p>
|
||||
<pre><pre class="playpen"><code class="language-rust">
|
||||
# #![allow(unused_variables)]
|
||||
#fn main() {
|
||||
pub const VCOUNT: *mut u16 = 0x0400_0006 as *mut u16;
|
||||
|
||||
pub fn read_vcount() -> u16 {
|
||||
unsafe { VCOUNT.read_volatile() }
|
||||
}
|
||||
#}</code></pre></pre>
|
||||
<p>Then we want two little helper functions to wait until vblank and vdraw.</p>
|
||||
<pre><pre class="playpen"><code class="language-rust">
|
||||
# #![allow(unused_variables)]
|
||||
#fn main() {
|
||||
pub const SCREEN_HEIGHT: isize = 160;
|
||||
|
||||
pub fn wait_until_vblank() {
|
||||
while read_vcount() < SCREEN_HEIGHT as u16 {}
|
||||
}
|
||||
|
||||
pub fn wait_until_vdraw() {
|
||||
while read_vcount() >= SCREEN_HEIGHT as u16 {}
|
||||
}
|
||||
#}</code></pre></pre>
|
||||
<p>And... that's it. No special types to be made this time around, it's just a
|
||||
number we read out of memory.</p>
|
||||
|
||||
</main>
|
||||
|
||||
|
@ -153,7 +217,7 @@ ton of extra power</p>
|
|||
|
||||
|
||||
|
||||
<a rel="next" href="../ch2/gba_snake.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||
<a rel="next" href="../ch2/light_cycle.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
|
||||
|
@ -171,7 +235,7 @@ ton of extra power</p>
|
|||
|
||||
|
||||
|
||||
<a href="../ch2/gba_snake.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||
<a href="../ch2/light_cycle.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
|
||||
|
|
|
@ -72,7 +72,7 @@
|
|||
</script>
|
||||
|
||||
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
||||
<ol class="chapter"><li><a href="introduction.html"><strong aria-hidden="true">1.</strong> Introduction</a></li><li><a href="ch0/index.html"><strong aria-hidden="true">2.</strong> Ch 0: Development Setup</a></li><li><a href="ch1/index.html"><strong aria-hidden="true">3.</strong> Ch 1: Hello GBA</a></li><li><ol class="section"><li><a href="ch1/hello1.html"><strong aria-hidden="true">3.1.</strong> hello1</a></li><li><a href="ch1/io_registers.html"><strong aria-hidden="true">3.2.</strong> IO Registers</a></li><li><a href="ch1/the_display_control_register.html"><strong aria-hidden="true">3.3.</strong> The Display Control Register</a></li><li><a href="ch1/video_memory_intro.html"><strong aria-hidden="true">3.4.</strong> Video Memory Intro</a></li><li><a href="ch1/hello2.html"><strong aria-hidden="true">3.5.</strong> hello2</a></li></ol></li><li><a href="ch2/index.html"><strong aria-hidden="true">4.</strong> Ch 2: User Input</a></li><li><ol class="section"><li><a href="ch2/the_key_input_register.html"><strong aria-hidden="true">4.1.</strong> The Key Input Register</a></li><li><a href="ch2/the_vcount_register.html"><strong aria-hidden="true">4.2.</strong> The VCount Register</a></li><li><a href="ch2/gba_snake.html"><strong aria-hidden="true">4.3.</strong> gba_snake</a></li></ol></li></ol>
|
||||
<ol class="chapter"><li><a href="introduction.html"><strong aria-hidden="true">1.</strong> Introduction</a></li><li><a href="ch0/index.html"><strong aria-hidden="true">2.</strong> Ch 0: Development Setup</a></li><li><a href="ch1/index.html"><strong aria-hidden="true">3.</strong> Ch 1: Hello GBA</a></li><li><ol class="section"><li><a href="ch1/hello1.html"><strong aria-hidden="true">3.1.</strong> hello1</a></li><li><a href="ch1/io_registers.html"><strong aria-hidden="true">3.2.</strong> IO Registers</a></li><li><a href="ch1/the_display_control_register.html"><strong aria-hidden="true">3.3.</strong> The Display Control Register</a></li><li><a href="ch1/video_memory_intro.html"><strong aria-hidden="true">3.4.</strong> Video Memory Intro</a></li><li><a href="ch1/hello2.html"><strong aria-hidden="true">3.5.</strong> hello2</a></li></ol></li><li><a href="ch2/index.html"><strong aria-hidden="true">4.</strong> Ch 2: User Input</a></li><li><ol class="section"><li><a href="ch2/the_key_input_register.html"><strong aria-hidden="true">4.1.</strong> The Key Input Register</a></li><li><a href="ch2/the_vcount_register.html"><strong aria-hidden="true">4.2.</strong> The VCount Register</a></li><li><a href="ch2/light_cycle.html"><strong aria-hidden="true">4.3.</strong> light_cycle</a></li></ol></li></ol>
|
||||
</nav>
|
||||
|
||||
<div id="page-wrapper" class="page-wrapper">
|
||||
|
@ -147,6 +147,9 @@ for C, but it's what I based the ordering of this book's sections on.</li>
|
|||
<li><a href="http://problemkaputt.de/gbatek.htm">GBATEK</a>, a homebrew tech manual for
|
||||
GBA/NDS/DSi. We will regularly link to parts of it when talking about various
|
||||
bits of the GBA.</li>
|
||||
<li><a href="https://www.cs.rit.edu/%7Etjh8300/CowBite/CowBiteSpec.htm">CowBite</a> is another
|
||||
tech specification that's more GBA specific. It's sometimes got more ASCII
|
||||
art diagrams and example C struct layouts than GBATEK does.</li>
|
||||
</ul>
|
||||
|
||||
</main>
|
||||
|
|
|
@ -72,7 +72,7 @@
|
|||
</script>
|
||||
|
||||
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
||||
<ol class="chapter"><li><a href="introduction.html" class="active"><strong aria-hidden="true">1.</strong> Introduction</a></li><li><a href="ch0/index.html"><strong aria-hidden="true">2.</strong> Ch 0: Development Setup</a></li><li><a href="ch1/index.html"><strong aria-hidden="true">3.</strong> Ch 1: Hello GBA</a></li><li><ol class="section"><li><a href="ch1/hello1.html"><strong aria-hidden="true">3.1.</strong> hello1</a></li><li><a href="ch1/io_registers.html"><strong aria-hidden="true">3.2.</strong> IO Registers</a></li><li><a href="ch1/the_display_control_register.html"><strong aria-hidden="true">3.3.</strong> The Display Control Register</a></li><li><a href="ch1/video_memory_intro.html"><strong aria-hidden="true">3.4.</strong> Video Memory Intro</a></li><li><a href="ch1/hello2.html"><strong aria-hidden="true">3.5.</strong> hello2</a></li></ol></li><li><a href="ch2/index.html"><strong aria-hidden="true">4.</strong> Ch 2: User Input</a></li><li><ol class="section"><li><a href="ch2/the_key_input_register.html"><strong aria-hidden="true">4.1.</strong> The Key Input Register</a></li><li><a href="ch2/the_vcount_register.html"><strong aria-hidden="true">4.2.</strong> The VCount Register</a></li><li><a href="ch2/gba_snake.html"><strong aria-hidden="true">4.3.</strong> gba_snake</a></li></ol></li></ol>
|
||||
<ol class="chapter"><li><a href="introduction.html" class="active"><strong aria-hidden="true">1.</strong> Introduction</a></li><li><a href="ch0/index.html"><strong aria-hidden="true">2.</strong> Ch 0: Development Setup</a></li><li><a href="ch1/index.html"><strong aria-hidden="true">3.</strong> Ch 1: Hello GBA</a></li><li><ol class="section"><li><a href="ch1/hello1.html"><strong aria-hidden="true">3.1.</strong> hello1</a></li><li><a href="ch1/io_registers.html"><strong aria-hidden="true">3.2.</strong> IO Registers</a></li><li><a href="ch1/the_display_control_register.html"><strong aria-hidden="true">3.3.</strong> The Display Control Register</a></li><li><a href="ch1/video_memory_intro.html"><strong aria-hidden="true">3.4.</strong> Video Memory Intro</a></li><li><a href="ch1/hello2.html"><strong aria-hidden="true">3.5.</strong> hello2</a></li></ol></li><li><a href="ch2/index.html"><strong aria-hidden="true">4.</strong> Ch 2: User Input</a></li><li><ol class="section"><li><a href="ch2/the_key_input_register.html"><strong aria-hidden="true">4.1.</strong> The Key Input Register</a></li><li><a href="ch2/the_vcount_register.html"><strong aria-hidden="true">4.2.</strong> The VCount Register</a></li><li><a href="ch2/light_cycle.html"><strong aria-hidden="true">4.3.</strong> light_cycle</a></li></ol></li></ol>
|
||||
</nav>
|
||||
|
||||
<div id="page-wrapper" class="page-wrapper">
|
||||
|
@ -147,6 +147,9 @@ for C, but it's what I based the ordering of this book's sections on.</li>
|
|||
<li><a href="http://problemkaputt.de/gbatek.htm">GBATEK</a>, a homebrew tech manual for
|
||||
GBA/NDS/DSi. We will regularly link to parts of it when talking about various
|
||||
bits of the GBA.</li>
|
||||
<li><a href="https://www.cs.rit.edu/%7Etjh8300/CowBite/CowBiteSpec.htm">CowBite</a> is another
|
||||
tech specification that's more GBA specific. It's sometimes got more ASCII
|
||||
art diagrams and example C struct layouts than GBATEK does.</li>
|
||||
</ul>
|
||||
|
||||
</main>
|
||||
|
|
215
docs/print.html
215
docs/print.html
|
@ -72,7 +72,7 @@
|
|||
</script>
|
||||
|
||||
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
||||
<ol class="chapter"><li><a href="introduction.html"><strong aria-hidden="true">1.</strong> Introduction</a></li><li><a href="ch0/index.html"><strong aria-hidden="true">2.</strong> Ch 0: Development Setup</a></li><li><a href="ch1/index.html"><strong aria-hidden="true">3.</strong> Ch 1: Hello GBA</a></li><li><ol class="section"><li><a href="ch1/hello1.html"><strong aria-hidden="true">3.1.</strong> hello1</a></li><li><a href="ch1/io_registers.html"><strong aria-hidden="true">3.2.</strong> IO Registers</a></li><li><a href="ch1/the_display_control_register.html"><strong aria-hidden="true">3.3.</strong> The Display Control Register</a></li><li><a href="ch1/video_memory_intro.html"><strong aria-hidden="true">3.4.</strong> Video Memory Intro</a></li><li><a href="ch1/hello2.html"><strong aria-hidden="true">3.5.</strong> hello2</a></li></ol></li><li><a href="ch2/index.html"><strong aria-hidden="true">4.</strong> Ch 2: User Input</a></li><li><ol class="section"><li><a href="ch2/the_key_input_register.html"><strong aria-hidden="true">4.1.</strong> The Key Input Register</a></li><li><a href="ch2/the_vcount_register.html"><strong aria-hidden="true">4.2.</strong> The VCount Register</a></li><li><a href="ch2/gba_snake.html"><strong aria-hidden="true">4.3.</strong> gba_snake</a></li></ol></li></ol>
|
||||
<ol class="chapter"><li><a href="introduction.html"><strong aria-hidden="true">1.</strong> Introduction</a></li><li><a href="ch0/index.html"><strong aria-hidden="true">2.</strong> Ch 0: Development Setup</a></li><li><a href="ch1/index.html"><strong aria-hidden="true">3.</strong> Ch 1: Hello GBA</a></li><li><ol class="section"><li><a href="ch1/hello1.html"><strong aria-hidden="true">3.1.</strong> hello1</a></li><li><a href="ch1/io_registers.html"><strong aria-hidden="true">3.2.</strong> IO Registers</a></li><li><a href="ch1/the_display_control_register.html"><strong aria-hidden="true">3.3.</strong> The Display Control Register</a></li><li><a href="ch1/video_memory_intro.html"><strong aria-hidden="true">3.4.</strong> Video Memory Intro</a></li><li><a href="ch1/hello2.html"><strong aria-hidden="true">3.5.</strong> hello2</a></li></ol></li><li><a href="ch2/index.html"><strong aria-hidden="true">4.</strong> Ch 2: User Input</a></li><li><ol class="section"><li><a href="ch2/the_key_input_register.html"><strong aria-hidden="true">4.1.</strong> The Key Input Register</a></li><li><a href="ch2/the_vcount_register.html"><strong aria-hidden="true">4.2.</strong> The VCount Register</a></li><li><a href="ch2/light_cycle.html"><strong aria-hidden="true">4.3.</strong> light_cycle</a></li></ol></li></ol>
|
||||
</nav>
|
||||
|
||||
<div id="page-wrapper" class="page-wrapper">
|
||||
|
@ -147,6 +147,9 @@ for C, but it's what I based the ordering of this book's sections on.</li>
|
|||
<li><a href="http://problemkaputt.de/gbatek.htm">GBATEK</a>, a homebrew tech manual for
|
||||
GBA/NDS/DSi. We will regularly link to parts of it when talking about various
|
||||
bits of the GBA.</li>
|
||||
<li><a href="https://www.cs.rit.edu/%7Etjh8300/CowBite/CowBiteSpec.htm">CowBite</a> is another
|
||||
tech specification that's more GBA specific. It's sometimes got more ASCII
|
||||
art diagrams and example C struct layouts than GBATEK does.</li>
|
||||
</ul>
|
||||
<a class="header" href="#chapter-0-development-setup" id="chapter-0-development-setup"><h1>Chapter 0: Development Setup</h1></a>
|
||||
<p>Before you can build a GBA game you'll have to follow some special steps to
|
||||
|
@ -789,11 +792,14 @@ SNES had. As you can guess, we get key state info from an IO register.</p>
|
|||
modern computer or console you do this with vsync info from the GPU and Monitor,
|
||||
and on the GBA we'll be using vsync info from an IO register that tracks what
|
||||
the display hardware is doing.</p>
|
||||
<p>For this chapter we'll make a copy of <code>hello2.rs</code> named <code>snake.rs</code> and then fill
|
||||
it in as we go. Normally you might not place the entire program into a single
|
||||
source file, but since these are examples it's slightly better to have them be
|
||||
completely self contained than it is to have them be "properly organized" for
|
||||
the long term.</p>
|
||||
<p>As a way to apply our knowledge We'll make a simply "light cycle" game where
|
||||
your dot leaves a trail behind them and you die if you go off the screen or if
|
||||
you touch your own trail. We just make a copy of <code>hello2.rs</code> named
|
||||
<code>light_cycle.rs</code> and then fill it in as we go through the chapter. Normally you
|
||||
might not place the entire program into a single source file, particularly as it
|
||||
grows over time, but since these are small examples it's much better to have
|
||||
them be completely self contained than it is to have them be "properly
|
||||
organized" for the long term.</p>
|
||||
<a class="header" href="#the-key-input-register" id="the-key-input-register"><h1>The Key Input Register</h1></a>
|
||||
<p>The Key Input Register is our next IO register. Its shorthand name is
|
||||
<a href="http://problemkaputt.de/gbatek.htm#gbakeypadinput">KEYINPUT</a> and it's a <code>u16</code>
|
||||
|
@ -851,7 +857,7 @@ pub const KEYINPUT: *mut u16 = 0x400_0130 as *mut u16;
|
|||
pub struct KeyInputSetting(u16);
|
||||
|
||||
pub fn read_key_input() -> KeyInputSetting {
|
||||
unsafe { KeyInputSetting(KEYINPUT.volatile_read()) }
|
||||
unsafe { KeyInputSetting(KEYINPUT.read_volatile()) }
|
||||
}
|
||||
#}</code></pre></pre>
|
||||
<p>Now we want a way to check if a key is <em>being pressed</em>, since that's normally
|
||||
|
@ -872,16 +878,16 @@ something. Also, we've set the target to <code>thumbv6m-none-eabi</code>, which
|
|||
slightly later version of ARM than the actual GBA, but it's close enough for
|
||||
just checking. Of course, in a full program small functions like these will
|
||||
probably get inlined into the calling code and disappear entirely as they're
|
||||
folded and refolded by the compiler, and so on. Still, we can see that in the
|
||||
simple case when the compiler doesn't know any extra context, the <code>!=0</code> test is
|
||||
4 instructions and the <code>==0</code> test is six instructions. Since we want to get
|
||||
saving where we can, we'll always use a <code>!=0</code> test and we'll adjust how we
|
||||
initially read the register to compensate.</p>
|
||||
folded and refolded by the compiler, but we can just check.</p>
|
||||
<p>It turns out that the <code>!=0</code> test is 4 instructions and the <code>==0</code> test is 6
|
||||
instructions. Since we want to get savings where we can, and we'll probably
|
||||
check the keys of an input often enough, we'll just always use a <code>!=0</code> test and
|
||||
then adjust how we initially read the register to compensate.</p>
|
||||
<pre><pre class="playpen"><code class="language-rust">
|
||||
# #![allow(unused_variables)]
|
||||
#fn main() {
|
||||
pub fn read_key_input() -> KeyInputSetting {
|
||||
unsafe { KeyInputSetting(KEYINPUT.volatile_read() ^ 0b1111_1111_1111_1111) }
|
||||
unsafe { KeyInputSetting(KEYINPUT.read_volatile() ^ 0b1111_1111_1111_1111) }
|
||||
}
|
||||
#}</code></pre></pre>
|
||||
<p>Now we add a method for seeing if a key is pressed. In the full library there's
|
||||
|
@ -985,19 +991,184 @@ somehow later on.</p>
|
|||
}
|
||||
}
|
||||
#}</code></pre></pre>
|
||||
<p>So then every frame we can check for <code>column_direction</code> and <code>row_direction</code> and
|
||||
then apply those to the snake's current position to make it move around the
|
||||
screen.</p>
|
||||
<p>So then in our game, every frame we can check for <code>column_direction</code> and
|
||||
<code>row_direction</code> and then apply those to the player's current position to make
|
||||
them move around the screen.</p>
|
||||
<p>With that settled I think we're all done with user input for now. There's some
|
||||
other things to eventually know about like key interrupts that you can set and
|
||||
stuff, but we'll cover that later on because it's not necessary right now.</p>
|
||||
<a class="header" href="#the-vcount-register" id="the-vcount-register"><h1>The VCount Register</h1></a>
|
||||
<p>TODO: describe all the stuff about vcount</p>
|
||||
<p>TODO: mention vblank and hblank</p>
|
||||
<p>TODO: mention that this just using this is a BAD way to vsync that will draw a
|
||||
ton of extra power</p>
|
||||
<a class="header" href="#gba_snake" id="gba_snake"><h1>gba_snake</h1></a>
|
||||
<p>TODO: make a "snake game" using arrow key input and vsync</p>
|
||||
<p>There's an IO register called
|
||||
<a href="http://problemkaputt.de/gbatek.htm#lcdiointerruptsandstatus">VCOUNT</a> that shows
|
||||
you, what else, the Vertical (row) COUNT(er). It's a <code>u16</code> at address
|
||||
<code>0x0400_0006</code>, and it's how we'll be doing our very poor quality vertical sync
|
||||
code to start.</p>
|
||||
<ul>
|
||||
<li><strong>What makes it poor?</strong> Well, we're just going to read from the vcount value as
|
||||
often as possible every time we need to wait for a specific value to come up,
|
||||
and then proceed once it hits the point we're looking for.</li>
|
||||
<li><strong>Why is this bad?</strong> Because we're making the CPU do a lot of useless work,
|
||||
which uses a lot more power that necessary. Even if you're not on an actual
|
||||
GBA you might be running inside an emulator on a phone or other handheld. You
|
||||
wanna try to save battery if all you're doing with that power use is waiting
|
||||
instead of making a game actually do something.</li>
|
||||
<li><strong>Can we do better?</strong> We can, but not yet. The better way to do things is to
|
||||
use a BIOS call to put the CPU into low power mode until a VBlank interrupt
|
||||
happens. However, we don't know about interrupts yet, and we don't know about
|
||||
BIOS calls yet, so we'll do the basic thing for now and then upgrade later.</li>
|
||||
</ul>
|
||||
<p>So the way that display hardware actually displays each frame is that it moves a
|
||||
tiny pointer left to right across each pixel row one pixel at a time. When it's
|
||||
within the actual screen width (240px) it's drawing out those pixels. Then it
|
||||
goes <em>past</em> the edge of the screen for 68px during a period known as the
|
||||
"horizontal blank" (hblank). Then it starts on the next row and does that loop
|
||||
over again. This happens for the whole screen height (160px) and then once again
|
||||
it goes past the last row for another 68px into a "vertical blank" (vblank)
|
||||
period.</p>
|
||||
<ul>
|
||||
<li>One pixel is 4 CPU cycles</li>
|
||||
<li>HDraw is 240 pixels, HBlank is 68 pixels (1,232 cycles per full scanline)</li>
|
||||
<li>VDraw is 150 scanlines, VBlank is 68 scanlines (280,896 cycles per full refresh)</li>
|
||||
</ul>
|
||||
<p>Now you may remember some stuff from the display control register section where
|
||||
it was mentioned that some parts of memory are best accessed during vblank, and
|
||||
also during hblank with a setting applied. These blanking periods are what was
|
||||
being talked about. At other times if you attempt to access video or object
|
||||
memory you (the CPU) might try touching the same memory that the display device
|
||||
is trying to use, in which case you get bumped back a cycle so that the display
|
||||
can finish what it's doing. Also, if you really insist on doing video memory
|
||||
changes while the screen is being drawn then you might get some visual glitches.
|
||||
If you can, just prepare all your changes ahead of time and then assign then all
|
||||
quickly during the blank period.</p>
|
||||
<p>So first we want a way to check the vcount value at all:</p>
|
||||
<pre><pre class="playpen"><code class="language-rust">
|
||||
# #![allow(unused_variables)]
|
||||
#fn main() {
|
||||
pub const VCOUNT: *mut u16 = 0x0400_0006 as *mut u16;
|
||||
|
||||
pub fn read_vcount() -> u16 {
|
||||
unsafe { VCOUNT.read_volatile() }
|
||||
}
|
||||
#}</code></pre></pre>
|
||||
<p>Then we want two little helper functions to wait until vblank and vdraw.</p>
|
||||
<pre><pre class="playpen"><code class="language-rust">
|
||||
# #![allow(unused_variables)]
|
||||
#fn main() {
|
||||
pub const SCREEN_HEIGHT: isize = 160;
|
||||
|
||||
pub fn wait_until_vblank() {
|
||||
while read_vcount() < SCREEN_HEIGHT as u16 {}
|
||||
}
|
||||
|
||||
pub fn wait_until_vdraw() {
|
||||
while read_vcount() >= SCREEN_HEIGHT as u16 {}
|
||||
}
|
||||
#}</code></pre></pre>
|
||||
<p>And... that's it. No special types to be made this time around, it's just a
|
||||
number we read out of memory.</p>
|
||||
<a class="header" href="#light_cycle" id="light_cycle"><h1>light_cycle</h1></a>
|
||||
<p>Now let's make a game of "light_cycle" with our new knowledge.</p>
|
||||
<a class="header" href="#gameplay" id="gameplay"><h2>Gameplay</h2></a>
|
||||
<p><code>light_cycle</code> is pretty simple, and very obvious if you've ever seen Tron. The
|
||||
player moves around the screen with a trail left behind them. They die if they
|
||||
go off the screen or if they touch their own trail.</p>
|
||||
<a class="header" href="#operations" id="operations"><h2>Operations</h2></a>
|
||||
<p>We need some better drawing operations this time around.</p>
|
||||
<pre><pre class="playpen"><code class="language-rust">
|
||||
# #![allow(unused_variables)]
|
||||
#fn main() {
|
||||
pub unsafe fn mode3_clear_screen(color: u16) {
|
||||
let color = color as u32;
|
||||
let bulk_color = color << 16 | color;
|
||||
let mut ptr = VRAM as *mut u32;
|
||||
for _ in 0..SCREEN_HEIGHT {
|
||||
for _ in 0..(SCREEN_WIDTH / 2) {
|
||||
ptr.write_volatile(bulk_color);
|
||||
ptr = ptr.offset(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn mode3_draw_pixel(col: isize, row: isize, color: u16) {
|
||||
(VRAM as *mut u16).offset(col + row * SCREEN_WIDTH).write_volatile(color);
|
||||
}
|
||||
|
||||
pub unsafe fn mode3_read_pixel(col: isize, row: isize) -> u16 {
|
||||
(VRAM as *mut u16).offset(col + row * SCREEN_WIDTH).read_volatile()
|
||||
}
|
||||
#}</code></pre></pre>
|
||||
<p>The draw pixel and read pixel are both pretty obvious. What's new is the clear
|
||||
screen operation. It changes the <code>u16</code> color into a <code>u32</code> and then packs the
|
||||
value in twice. Then we write out <code>u32</code> values the whole way through screen
|
||||
memory. This means we have to do less write operations overall, and so the
|
||||
screen clear is twice as fast.</p>
|
||||
<p>Now we just have to fill in the main function:</p>
|
||||
<pre><pre class="playpen"><code class="language-rust">#[start]
|
||||
fn main(_argc: isize, _argv: *const *const u8) -> isize {
|
||||
unsafe {
|
||||
DISPCNT.write_volatile(MODE3 | BG2);
|
||||
}
|
||||
|
||||
let mut px = SCREEN_WIDTH / 2;
|
||||
let mut py = SCREEN_HEIGHT / 2;
|
||||
let mut color = rgb16(31, 0, 0);
|
||||
|
||||
loop {
|
||||
// read the input for this frame
|
||||
let this_frame_keys = read_key_input();
|
||||
|
||||
// adjust game state and wait for vblank
|
||||
px += 2 * this_frame_keys.column_direction() as isize;
|
||||
py += 2 * this_frame_keys.row_direction() as isize;
|
||||
wait_until_vblank();
|
||||
|
||||
// draw the new game and wait until the next frame starts.
|
||||
unsafe {
|
||||
if px < 0 || py < 0 || px == SCREEN_WIDTH || py == SCREEN_HEIGHT {
|
||||
// out of bounds, reset the screen and position.
|
||||
mode3_clear_screen(0);
|
||||
color = color.rotate_left(5);
|
||||
px = SCREEN_WIDTH / 2;
|
||||
py = SCREEN_HEIGHT / 2;
|
||||
} else {
|
||||
let color_here = mode3_read_pixel(px, py);
|
||||
if color_here != 0 {
|
||||
// crashed into our own line, reset the screen
|
||||
mode3_clear_screen(0);
|
||||
color = color.rotate_left(5);
|
||||
} else {
|
||||
// draw the new part of the line
|
||||
mode3_draw_pixel(px, py, color);
|
||||
mode3_draw_pixel(px, py + 1, color);
|
||||
mode3_draw_pixel(px + 1, py, color);
|
||||
mode3_draw_pixel(px + 1, py + 1, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
wait_until_vdraw();
|
||||
}
|
||||
}
|
||||
</code></pre></pre>
|
||||
<p>Oh that's a lot more than before!</p>
|
||||
<p>First we set Mode 3 and Background 2, we know about that.</p>
|
||||
<p>Then we're going to store the player's x and y, along with a color value for
|
||||
their light cycle. Then we enter the core loop.</p>
|
||||
<p>We read the keys for input, and then do as much as we can without touching video
|
||||
memory. Since we're using video memory as the place to store the player's light
|
||||
trail, we can't do much, we just update their position and wait for vblank to
|
||||
start. The player will be a 2x2 square, so the arrows will move you 2 pixels per
|
||||
frame.</p>
|
||||
<p>Once we're in vblank we check to see what kind of drawing we're doing. If the
|
||||
player has gone out of bounds, we clear the screen, rotate their color, and then
|
||||
reset their position. Why rotate the color? Just because it's fun to have
|
||||
different colors.</p>
|
||||
<p>Next, if the player is in bounds we read the video memory for their position. If
|
||||
it's not black that means we've been here before and the player has crashed into
|
||||
their own line. In this case, we reset the game without moving them to a new
|
||||
location.</p>
|
||||
<p>Finally, if the player is in bounds and they haven't crashed, we write their color into memory at this position.</p>
|
||||
<p>Regardless of how it worked out, we hold here until vdraw starts before going to
|
||||
the next loop.</p>
|
||||
|
||||
</main>
|
||||
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
161
examples/light_cycle.rs
Normal file
161
examples/light_cycle.rs
Normal file
|
@ -0,0 +1,161 @@
|
|||
#![feature(start)]
|
||||
#![no_std]
|
||||
|
||||
#[cfg(not(test))]
|
||||
#[panic_handler]
|
||||
fn panic(_info: &core::panic::PanicInfo) -> ! {
|
||||
loop {}
|
||||
}
|
||||
|
||||
#[start]
|
||||
fn main(_argc: isize, _argv: *const *const u8) -> isize {
|
||||
unsafe {
|
||||
DISPCNT.write_volatile(MODE3 | BG2);
|
||||
}
|
||||
|
||||
let mut px = SCREEN_WIDTH / 2;
|
||||
let mut py = SCREEN_HEIGHT / 2;
|
||||
let mut color = rgb16(31, 0, 0);
|
||||
|
||||
loop {
|
||||
// read the input for this frame
|
||||
let this_frame_keys = read_key_input();
|
||||
|
||||
// adjust game state and wait for vblank
|
||||
px += 2 * this_frame_keys.column_direction() as isize;
|
||||
py += 2 * this_frame_keys.row_direction() as isize;
|
||||
wait_until_vblank();
|
||||
|
||||
// draw the new game and wait until the next frame starts.
|
||||
unsafe {
|
||||
if px < 0 || py < 0 || px == SCREEN_WIDTH || py == SCREEN_HEIGHT {
|
||||
// out of bounds, reset the screen and position.
|
||||
mode3_clear_screen(0);
|
||||
color = color.rotate_left(5);
|
||||
px = SCREEN_WIDTH / 2;
|
||||
py = SCREEN_HEIGHT / 2;
|
||||
} else {
|
||||
let color_here = mode3_read_pixel(px, py);
|
||||
if color_here != 0 {
|
||||
// crashed into our own line, reset the screen
|
||||
mode3_clear_screen(0);
|
||||
color = color.rotate_left(5);
|
||||
} else {
|
||||
// draw the new part of the line
|
||||
mode3_draw_pixel(px, py, color);
|
||||
mode3_draw_pixel(px, py + 1, color);
|
||||
mode3_draw_pixel(px + 1, py, color);
|
||||
mode3_draw_pixel(px + 1, py + 1, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
wait_until_vdraw();
|
||||
}
|
||||
}
|
||||
|
||||
pub const DISPCNT: *mut u16 = 0x04000000 as *mut u16;
|
||||
pub const MODE3: u16 = 3;
|
||||
pub const BG2: u16 = 0b100_0000_0000;
|
||||
|
||||
pub const VRAM: usize = 0x06000000;
|
||||
pub const SCREEN_WIDTH: isize = 240;
|
||||
pub const SCREEN_HEIGHT: isize = 160;
|
||||
|
||||
pub const fn rgb16(red: u16, green: u16, blue: u16) -> u16 {
|
||||
blue << 10 | green << 5 | red
|
||||
}
|
||||
|
||||
pub unsafe fn mode3_clear_screen(color: u16) {
|
||||
let color = color as u32;
|
||||
let bulk_color = color << 16 | color;
|
||||
let mut ptr = VRAM as *mut u32;
|
||||
for _ in 0..SCREEN_HEIGHT {
|
||||
for _ in 0..(SCREEN_WIDTH / 2) {
|
||||
ptr.write_volatile(bulk_color);
|
||||
ptr = ptr.offset(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn mode3_draw_pixel(col: isize, row: isize, color: u16) {
|
||||
(VRAM as *mut u16).offset(col + row * SCREEN_WIDTH).write_volatile(color);
|
||||
}
|
||||
|
||||
pub unsafe fn mode3_read_pixel(col: isize, row: isize) -> u16 {
|
||||
(VRAM as *mut u16).offset(col + row * SCREEN_WIDTH).read_volatile()
|
||||
}
|
||||
|
||||
pub const KEYINPUT: *mut u16 = 0x400_0130 as *mut u16;
|
||||
|
||||
/// A newtype over the key input state of the GBA.
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
#[repr(transparent)]
|
||||
pub struct KeyInputSetting(u16);
|
||||
|
||||
/// A "tribool" value helps us interpret the arrow pad.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(i32)]
|
||||
pub enum TriBool {
|
||||
Minus = -1,
|
||||
Neutral = 0,
|
||||
Plus = 1,
|
||||
}
|
||||
|
||||
pub fn read_key_input() -> KeyInputSetting {
|
||||
unsafe { KeyInputSetting(KEYINPUT.read_volatile() ^ 0b1111_1111_1111_1111) }
|
||||
}
|
||||
|
||||
pub const KEY_A: u16 = 1 << 0;
|
||||
pub const KEY_B: u16 = 1 << 1;
|
||||
pub const KEY_SELECT: u16 = 1 << 2;
|
||||
pub const KEY_START: u16 = 1 << 3;
|
||||
pub const KEY_RIGHT: u16 = 1 << 4;
|
||||
pub const KEY_LEFT: u16 = 1 << 5;
|
||||
pub const KEY_UP: u16 = 1 << 6;
|
||||
pub const KEY_DOWN: u16 = 1 << 7;
|
||||
pub const KEY_R: u16 = 1 << 8;
|
||||
pub const KEY_L: u16 = 1 << 9;
|
||||
|
||||
impl KeyInputSetting {
|
||||
pub fn contains(&self, key: u16) -> bool {
|
||||
(self.0 & key) != 0
|
||||
}
|
||||
|
||||
pub fn difference(&self, other: KeyInputSetting) -> KeyInputSetting {
|
||||
KeyInputSetting(self.0 ^ other.0)
|
||||
}
|
||||
|
||||
pub fn column_direction(&self) -> TriBool {
|
||||
if self.contains(KEY_RIGHT) {
|
||||
TriBool::Plus
|
||||
} else if self.contains(KEY_LEFT) {
|
||||
TriBool::Minus
|
||||
} else {
|
||||
TriBool::Neutral
|
||||
}
|
||||
}
|
||||
|
||||
pub fn row_direction(&self) -> TriBool {
|
||||
if self.contains(KEY_DOWN) {
|
||||
TriBool::Plus
|
||||
} else if self.contains(KEY_UP) {
|
||||
TriBool::Minus
|
||||
} else {
|
||||
TriBool::Neutral
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub const VCOUNT: *mut u16 = 0x0400_0006 as *mut u16;
|
||||
|
||||
pub fn read_vcount() -> u16 {
|
||||
unsafe { VCOUNT.read_volatile() }
|
||||
}
|
||||
|
||||
pub fn wait_until_vblank() {
|
||||
while read_vcount() < SCREEN_HEIGHT as u16 {}
|
||||
}
|
||||
|
||||
pub fn wait_until_vdraw() {
|
||||
while read_vcount() >= SCREEN_HEIGHT as u16 {}
|
||||
}
|
|
@ -1,95 +0,0 @@
|
|||
#![feature(start)]
|
||||
#![no_std]
|
||||
|
||||
#[cfg(not(test))]
|
||||
#[panic_handler]
|
||||
fn panic(_info: &core::panic::PanicInfo) -> ! {
|
||||
loop {}
|
||||
}
|
||||
|
||||
#[start]
|
||||
fn main(_argc: isize, _argv: *const *const u8) -> isize {
|
||||
unsafe {
|
||||
DISPCNT.write_volatile(MODE3 | BG2);
|
||||
mode3_pixel(120, 80, rgb16(31, 0, 0));
|
||||
mode3_pixel(136, 80, rgb16(0, 31, 0));
|
||||
mode3_pixel(120, 96, rgb16(0, 0, 31));
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
|
||||
pub const DISPCNT: *mut u16 = 0x04000000 as *mut u16;
|
||||
pub const MODE3: u16 = 3;
|
||||
pub const BG2: u16 = 0b100_0000_0000;
|
||||
|
||||
pub const VRAM: usize = 0x06000000;
|
||||
pub const SCREEN_WIDTH: isize = 240;
|
||||
|
||||
pub const fn rgb16(red: u16, green: u16, blue: u16) -> u16 {
|
||||
blue << 10 | green << 5 | red
|
||||
}
|
||||
|
||||
pub unsafe fn mode3_pixel(col: isize, row: isize, color: u16) {
|
||||
(VRAM as *mut u16).offset(col + row * SCREEN_WIDTH).write_volatile(color);
|
||||
}
|
||||
|
||||
pub const KEYINPUT: *mut u16 = 0x400_0130 as *mut u16;
|
||||
|
||||
/// A newtype over the key input state of the GBA.
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
#[repr(transparent)]
|
||||
pub struct KeyInputSetting(u16);
|
||||
|
||||
/// A "tribool" value helps us interpret the arrow pad.
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
#[repr(i32)]
|
||||
pub enum TriBool {
|
||||
Minus = -1,
|
||||
Neutral = 0,
|
||||
Plus = 1,
|
||||
}
|
||||
|
||||
pub fn read_key_input() -> KeyInputSetting {
|
||||
unsafe { KeyInputSetting(KEYINPUT.volatile_read() ^ 0b1111_1111_1111_1111) }
|
||||
}
|
||||
|
||||
pub const KEY_A: u16 = 1 << 0;
|
||||
pub const KEY_B: u16 = 1 << 1;
|
||||
pub const KEY_SELECT: u16 = 1 << 2;
|
||||
pub const KEY_START: u16 = 1 << 3;
|
||||
pub const KEY_RIGHT: u16 = 1 << 4;
|
||||
pub const KEY_LEFT: u16 = 1 << 5;
|
||||
pub const KEY_UP: u16 = 1 << 6;
|
||||
pub const KEY_DOWN: u16 = 1 << 7;
|
||||
pub const KEY_R: u16 = 1 << 8;
|
||||
pub const KEY_L: u16 = 1 << 9;
|
||||
|
||||
impl KeyInputSetting {
|
||||
pub fn contains(&self, key: u16) -> bool {
|
||||
(self.0 & key) != 0
|
||||
}
|
||||
|
||||
pub fn difference(&self, other: KeyInputSetting) -> KeyInputSetting {
|
||||
KeyInputSetting(self.0 ^ other.0)
|
||||
}
|
||||
|
||||
pub fn column_direction(&self) -> TriBool {
|
||||
if self.contains(KEY_RIGHT) {
|
||||
TriBool::Plus
|
||||
} else if self.contains(KEY_LEFT) {
|
||||
TriBool::Minus
|
||||
} else {
|
||||
TriBool::Neutral
|
||||
}
|
||||
}
|
||||
|
||||
pub fn row_direction(&self) -> TriBool {
|
||||
if self.contains(KEY_DOWN) {
|
||||
TriBool::Plus
|
||||
} else if self.contains(KEY_UP) {
|
||||
TriBool::Minus
|
||||
} else {
|
||||
TriBool::Neutral
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue