Clear some cruft

This commit is contained in:
Lokathor 2018-12-15 23:05:22 -07:00
parent b9d965410e
commit 1fcfbb00e8
5 changed files with 0 additions and 349 deletions

View file

@ -1,115 +0,0 @@
## A basic hello1 explanation
So, what just happened? Even if you're used to Rust that might look pretty
strange. We'll go over most of the little parts right here, and then bigger
parts will get their own sections.
```rust
#![feature(start)]
```
This enables the [start
feature](https://doc.rust-lang.org/beta/unstable-book/language-features/start.html),
which you would normally be able to read about in the unstable book, except that
the book tells you nothing at all except to look at the [tracking
issue](https://github.com/rust-lang/rust/issues/29633).
Basically, a GBA game is even more low-level than the _normal_ amount of
low-level that you get from Rust, so we have to tell the compiler to account for
that by specifying a `#[start]`, and we need this feature on to do that.
```rust
#![no_std]
```
There's no standard library available on the GBA, so we'll have to live a core
only life.
```rust
#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
loop {}
}
```
This sets our [panic
handler](https://doc.rust-lang.org/nightly/nomicon/panic-handler.html).
Basically, if we somehow trigger a panic, this is where the program goes.
However, right now we don't know how to get any sort of message out to the user
so... we do nothing at all. We _can't even return_ from here, so we just sit in
an infinite loop. The player will have to reset the universe from the outside.
```rust
#[start]
fn main(_argc: isize, _argv: *const *const u8) -> isize {
```
This is our `#[start]`. We call it `main`, but it's not like a `main` that you'd
see in a Rust program. It's _more like_ the sort of `main` that you'd see in a C
program, but it's still **not** that either. If you compile a `#[start]` program
for a target with an OS such as `arm-none-eabi-nm` you can open up the debug
info and see that your result will have the symbol for the C `main` along side
the symbol for the start `main` that we write here. Our start `main` is just its
own unique thing, and the inputs and outputs have to be like that because that's
how `#[start]` is specified to work in Rust.
If you think about it for a moment you'll probably realize that, those inputs
and outputs are totally useless to us on a GBA. There's no OS on the GBA to call
our program, and there's no place for our program to "return to" when it's done.
Side note: if you want to learn more about stuff "before main gets called" you
can watch a great [CppCon talk](https://www.youtube.com/watch?v=dOfucXtyEsU) by
Matt Godbolt (yes, that Godbolt) where he delves into quite a bit of it. The
talk doesn't really apply to the GBA, but it's pretty good.
```rust
unsafe {
```
I hope you're all set for some `unsafe`, because there's a lot of it to be had.
```rust
(0x04000000 as *mut u16).write_volatile(0x0403);
```
Sure!
```rust
(0x06000000 as *mut u16).offset(120 + 80 * 240).write_volatile(0x001F);
(0x06000000 as *mut u16).offset(136 + 80 * 240).write_volatile(0x03E0);
(0x06000000 as *mut u16).offset(120 + 96 * 240).write_volatile(0x7C00);
```
Ah, of course.
```rust
loop {}
}
}
```
And, as mentioned above, there's no place for a GBA program to "return to", so
we can't ever let `main` try to return there. Instead, we go into an infinite
`loop` that does nothing. The fact that this doesn't ever return an `isize`
value doesn't seem to bother Rust, because I guess we're at least not returning
any other type of thing instead.
Fun fact: unlike in C++, an infinite loop with no side effects isn't Undefined
Behavior for us rustaceans... _semantically_. In truth LLVM has a [known
bug](https://github.com/rust-lang/rust/issues/28728) in this area, so we won't
actually be relying on empty loops in any future programs.
## All Those Magic Numbers
Alright, I cheated quite a bit in the middle there. The program works, but I
didn't really tell you why because I didn't really tell you what any of those
magic numbers mean or do.
* `0x04000000` is the address of an IO Register called the Display Control.
* `0x06000000` is the start of Video RAM.
So we write some magic to the display control register once, then we write some
other magic to three magic locations in the Video RAM. Somehow that shows three
dots. Gotta read on to find out why!

View file

@ -1,132 +0,0 @@
# hello2
Okay so let's have a look again:
`hello1`
```rust
#![feature(start)]
#![no_std]
#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
loop {}
}
#[start]
fn main(_argc: isize, _argv: *const *const u8) -> isize {
unsafe {
(0x04000000 as *mut u16).write_volatile(0x0403);
(0x06000000 as *mut u16).offset(120 + 80 * 240).write_volatile(0x001F);
(0x06000000 as *mut u16).offset(136 + 80 * 240).write_volatile(0x03E0);
(0x06000000 as *mut u16).offset(120 + 96 * 240).write_volatile(0x7C00);
loop {}
}
}
```
Now let's clean this up so that it's clearer what's going on.
First we'll label that display control stuff, including using the `VolatilePtr`
type from the volatile explanation:
```rust
pub const DISPCNT: VolatilePtr<u16> = VolatilePtr(0x04000000 as *mut u16);
pub const MODE3: u16 = 3;
pub const BG2: u16 = 0b100_0000_0000;
```
Next we make some const values for the actual pixel drawing
```rust
pub const VRAM: usize = 0x06000000;
pub const SCREEN_WIDTH: isize = 240;
```
Note that VRAM has to be interpreted in different ways depending on mode, so we
just leave it as `usize` and we'll cast it into the right form closer to the
actual use.
Next we want a small helper function for putting together a color value.
Happily, this one can even be declared as a `const` function. At the time of
writing, we've got the "minimal const fn" support in nightly. It really is quite
limited, but I'm happy to let rustc and LLVM pre-compute as much as they can
when it comes to the GBA's tiny CPU.
```rust
pub const fn rgb16(red: u16, green: u16, blue: u16) -> u16 {
blue << 10 | green << 5 | red
}
```
Finally, we'll make a function for drawing a pixel in Mode 3. Even though it's
just a one-liner, having the "important parts" be labeled as function arguments
usually helps you think about it a lot better.
```rust
pub unsafe fn mode3_pixel(col: isize, row: isize, color: u16) {
VolatilePtr(VRAM as *mut u16).offset(col + row * SCREEN_WIDTH).write(color);
}
```
So now we've got this:
`hello2`
```rust
#![feature(start)]
#![no_std]
#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
loop {}
}
#[start]
fn main(_argc: isize, _argv: *const *const u8) -> isize {
unsafe {
DISPCNT.write(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 {}
}
}
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
#[repr(transparent)]
pub struct VolatilePtr<T>(pub *mut T);
impl<T> VolatilePtr<T> {
pub unsafe fn read(&self) -> T {
core::ptr::read_volatile(self.0)
}
pub unsafe fn write(&self, data: T) {
core::ptr::write_volatile(self.0, data);
}
pub unsafe fn offset(self, count: isize) -> Self {
VolatilePtr(self.0.wrapping_offset(count))
}
}
pub const DISPCNT: VolatilePtr<u16> = VolatilePtr(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) {
VolatilePtr(VRAM as *mut u16).offset(col + row * SCREEN_WIDTH).write(color);
}
```
Exact same program that we started with, but much easier to read.
Of course, in the full `gba` crate that this book is a part of we have these and
other elements all labeled and sorted out for you (not identically, but
similarly). Still, for educational purposes it's often best to do it yourself at
least once.

View file

@ -1,10 +0,0 @@
# Ch 1: Hello GBA
Traditionally a person writes a "hello, world" program so that they can test
that their development environment is setup properly and to just get a feel for
using the tools involved. To get an idea of what a small part of a source file
will look like. All that stuff.
Normally, you write a program that prints "hello, world" to the terminal. The
GBA has no terminal, but it does have a screen, so instead we're going to draw
three dots to the screen.

View file

@ -1,70 +0,0 @@
# Volatile
Before we focus on what the numbers mean, first let's ask ourselves: Why are we
doing _volatile_ writes? You've probably never used that keywords before at all.
What _is_ volatile anyway?
Well, the optimizer is pretty aggressive, and so it'll skip reads and writes
when it thinks can. Like if you write to a pointer once, and then again a moment
later, and it didn't see any other reads in between, it'll think that it can
just skip doing that first write since it'll get overwritten anyway. Sometimes
that's correct, but sometimes it's not.
Marking a read or write as _volatile_ tells the compiler that it really must do
that action, and in the exact order that we wrote it out. It says that there
might even be special hardware side effects going on that the compiler isn't
aware of. In this case, the write to the display control register sets a video
mode, and the writes to the Video RAM set pixels that will show up on the
screen.
Similar to "atomic" operations you might have heard about, all volatile
operations are enforced to happen in the exact order that you specify them, but
only relative to other volatile operations. So something like
```rust
c.write_volatile(5);
a += b;
d.write_volatile(7);
```
might end up changing `a` either before or after the change to `c` (since the
value of `a` doesn't affect the write to `c`), but the write to `d` will
_always_ happen after the write to `c`, even though the compiler doesn't see any
direct data dependency there.
If you ever go on to use volatile stuff on other platforms it's important to
note that volatile doesn't make things thread-safe, you still need atomic for
that. However, the GBA doesn't have threads, so we don't have to worry about
those sorts of thread safety concerns (there's interrupts, but that's another
matter).
## Volatile by default
Of course, writing out `volatile_write` every time is more than we wanna do.
There's clarity and then there's excessive. This is a chance to write our first
[newtype](https://doc.rust-lang.org/1.0.0/style/features/types/newtype.html).
Basically a type that's got the exact same binary representation as some other
type, but new methods and trait implementations.
We want a `*mut T` that's volatile by default, and also when we offset it...
well the verdict is slightly unclear on how `offset` vs `wrapping_offset` work
when you're using pointers that you made up out of nowhere. I've asked the
experts and they genuinely weren't sure, so we'll make an `offset` method that
does a `wrapping_offset` just to be careful.
```rust
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
#[repr(transparent)]
pub struct VolatilePtr<T>(pub *mut T);
impl<T> VolatilePtr<T> {
pub unsafe fn read(&self) -> T {
core::ptr::read_volatile(self.0)
}
pub unsafe fn write(&self, data: T) {
core::ptr::write_volatile(self.0, data);
}
pub unsafe fn offset(self, count: isize) -> Self {
VolatilePtr(self.0.wrapping_offset(count))
}
}
```

View file

@ -1,22 +0,0 @@
# Ch 2: User Input
It's all well and good to draw three pixels, but they don't do anything yet. We
want them to do something, and for that we need to get some input from the user.
The GBA, as I'm sure you know, has an arrow pad, A and B, L and R, Start and
Select. That's a little more than the NES/GB/CGB had, and a little less than the
SNES had. As you can guess, we get key state info from an IO register.
Also, we will need a way to keep the program from running "too fast". On a
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.
As a way to apply our knowledge We'll make a simple "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.