mirror of
https://github.com/italicsjenga/gba.git
synced 2025-01-11 03:21:30 +11:00
Merge pull request #40 from rust-console/lokathor
Big Wintertime Happy Fun PR
This commit is contained in:
commit
8595ffbff0
|
@ -2,7 +2,9 @@ language: rust
|
|||
sudo: false
|
||||
|
||||
cache:
|
||||
- cargo
|
||||
# Cache ONLY .cargo, not target/ like usual
|
||||
directories:
|
||||
- $HOME/.cargo
|
||||
|
||||
rust:
|
||||
- nightly
|
||||
|
@ -17,8 +19,6 @@ before_script:
|
|||
- cargo install-update -a
|
||||
|
||||
script:
|
||||
# Travis seems to cache for some dumb reason, but we don't want that at all.
|
||||
- rm -fr target
|
||||
# Obtain the devkitPro tools, using `target/` as a temp directory
|
||||
- mkdir -p target
|
||||
- cd target
|
||||
|
|
|
@ -13,7 +13,7 @@ publish = false
|
|||
|
||||
[dependencies]
|
||||
typenum = "1.10"
|
||||
gba-proc-macro = "0.3"
|
||||
gba-proc-macro = "0.4.1"
|
||||
|
||||
#[dev-dependencies]
|
||||
#quickcheck="0.7"
|
||||
|
|
|
@ -91,29 +91,70 @@ good fit for the GBA (I honestly haven't looked into it).
|
|||
|
||||
## Bare Metal Panic
|
||||
|
||||
TODO: expand this
|
||||
If our code panics, we usually want to see that panic message. Unfortunately,
|
||||
without a way to access something like `stdout` or `stderr` we've gotta do
|
||||
something a little weirder.
|
||||
|
||||
* Write `0xC0DE` to `0x4fff780` (`u16`) to enable mGBA logging. Write any other
|
||||
value to disable it.
|
||||
* Read `0x4fff780` (`u16`) to check mGBA logging status.
|
||||
* You get `0x1DEA` if debugging is active.
|
||||
* Otherwise you get standard open bus nonsense values.
|
||||
* Write your message into the virtual `[u8; 255]` array starting at `0x4fff600`.
|
||||
mGBA will interpret these bytes as a CString value.
|
||||
* Write `0x100` PLUS the message level to `0x4fff700` (`u16`) when you're ready
|
||||
to send a message line:
|
||||
* 0: Fatal (halts execution with a popup)
|
||||
* 1: Error
|
||||
* 2: Warning
|
||||
* 3: Info
|
||||
* 4: Debug
|
||||
* Sending the message also automatically zeroes the output buffer.
|
||||
* View the output within the "Tools" menu, "View Logs...". Note that the Fatal
|
||||
message, if any doesn't get logged.
|
||||
If our program is running within the `mGBA` emulator, version 0.7 or later, we
|
||||
can access a special set of addresses that allow us to send out `CString`
|
||||
values, which then appear within a message log that you can check.
|
||||
|
||||
TODO: this will probably fail without a `__clzsi2` implementation, which is a
|
||||
good seg for the next section
|
||||
We can capture this behavior by making an `MGBADebug` type, and then implement
|
||||
`core::fmt::Write` for that type. Once done, the `write!` macro will let us
|
||||
target the mGBA debug output channel.
|
||||
|
||||
When used, it looks like this:
|
||||
|
||||
```rust
|
||||
#[panic_handler]
|
||||
fn panic(info: &core::panic::PanicInfo) -> ! {
|
||||
use core::fmt::Write;
|
||||
use gba::mgba::{MGBADebug, MGBADebugLevel};
|
||||
|
||||
if let Some(mut mgba) = MGBADebug::new() {
|
||||
let _ = write!(mgba, "{}", info);
|
||||
mgba.send(MGBADebugLevel::Fatal);
|
||||
}
|
||||
loop {}
|
||||
}
|
||||
```
|
||||
|
||||
If you want to follow the particulars you can check the `MGBADebug` source in
|
||||
the `gba` crate. Basically, there's one address you can use to try and activate
|
||||
the debug output, and if it works you write your message into the "array" at
|
||||
another address, and then finally write a send value to a third address. You'll
|
||||
need to have read the [volatile](03-volatile_destination.md) section for the
|
||||
details to make sense.
|
||||
|
||||
## LLVM Intrinsics
|
||||
|
||||
TODO: explain that we'll occasionally have to provide some intrinsics.
|
||||
The above code will make your program fail to build in debug mode, saying that
|
||||
`__clzsi2` can't be found. This is a special builtin function that LLVM attempts
|
||||
to use when there's no hardware version of an operation it wants to do (in this
|
||||
case, counting the leading zeros). It's not _actually_ necessary in this case,
|
||||
which is why you only need it in debug mode. The higher optimization level of
|
||||
release mode makes LLVM pre-compute more and fold more constants or whatever and
|
||||
then it stops trying to call `__clzsi2`.
|
||||
|
||||
Unfortunately, sometimes a build will fail with a missing intrinsic even in
|
||||
release mode.
|
||||
|
||||
If LLVM wants _core_ to have that intrinsic then you're in
|
||||
trouble, you'll have to send a PR to the
|
||||
[compiler-builtins](https://github.com/rust-lang-nursery/compiler-builtins)
|
||||
repository and hope to get it into rust itself.
|
||||
|
||||
If LLVM wants _your code_ to have the intrinsic then you're in less trouble. You
|
||||
can look up the details and then implement it yourself. It can go anywhere in
|
||||
your program, as long as it has the right ABI and name. In the case of
|
||||
`__clzsi2` it takes a `usize` and returns a `usize`, so you'd write something
|
||||
like:
|
||||
|
||||
```rust
|
||||
#[no_mangle]
|
||||
pub extern "C" fn __clzsi2(mut x: usize) -> usize {
|
||||
//
|
||||
}
|
||||
```
|
||||
|
||||
And so on for whatever other missing intrinsic.
|
||||
|
|
|
@ -96,6 +96,11 @@ style, but there are some rules and considerations here:
|
|||
* Parentheses macro use mostly gets treated like a function call.
|
||||
* Bracket macro use mostly gets treated like an array declaration.
|
||||
|
||||
**As a reminder:** remember that `macro_rules` macros have to appear _before_
|
||||
they're invoked in your source, so the `newtype` macro will always have to be at
|
||||
the very top of your file, or if you put it in a module within your project
|
||||
you'll need to declare the module before anything that uses it.
|
||||
|
||||
## Upgrade That Macro!
|
||||
|
||||
We also want to be able to add `derive` stuff and doc comments to our newtype.
|
||||
|
@ -124,34 +129,78 @@ newtype! {
|
|||
}
|
||||
```
|
||||
|
||||
And that's about all we'll need for the examples.
|
||||
Next, we can allow for the wrapping of types that aren't just a single
|
||||
identifier by changing `$old_name` from `:ident` to `:ty`. We can't _also_ do
|
||||
this for the `$new_type` part because declaring a new struct expects a valid
|
||||
identifier that's _not_ already declared (obviously), and `:ty` is intended for
|
||||
capturing types that already exist.
|
||||
|
||||
**As a reminder:** remember that `macro_rules` macros have to appear _before_
|
||||
they're invoked in your source, so the `newtype` macro will always have to be at
|
||||
the very top of your file, or if you put it in a module within your project
|
||||
you'll need to declare the module before anything that uses it.
|
||||
```rust
|
||||
#[macro_export]
|
||||
macro_rules! newtype {
|
||||
($(#[$attr:meta])* $new_name:ident, $old_name:ty) => {
|
||||
$(#[$attr])*
|
||||
#[repr(transparent)]
|
||||
pub struct $new_name($old_name);
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Potential Homework
|
||||
Next of course we'll want to usually have a `new` method that's const and just
|
||||
gives a 0 value. We won't always be making a newtype over a number value, but we
|
||||
often will. It's usually silly to have a `new` method with no arguments since we
|
||||
might as well just impl `Default`, but `Default::default` isn't `const`, so
|
||||
having `pub const fn new() -> Self` is justified here.
|
||||
|
||||
If you wanted to keep going and get really fancy with it, you could potentially
|
||||
add a lot more:
|
||||
Here, the token `0` is given the `{integer}` type, which can be converted into
|
||||
any of the integer types as needed, but it still can't be converted into an
|
||||
array type or a pointer or things like that. Accordingly we've added the "no
|
||||
frills" option which declares the struct and no `new` method.
|
||||
|
||||
* Make a `pub const fn new() -> Self` method that outputs the base value in a
|
||||
const way. Combine this with builder style "setter" methods that are also
|
||||
const and you can get the compiler to do quite a bit of the value building
|
||||
work at compile time.
|
||||
* Making the macro optionally emit a `From` impl to unwrap it back into the base
|
||||
type.
|
||||
* Allow for visibility modifiers to be applied to the inner field and the newly
|
||||
generated type.
|
||||
* Allowing for generic newtypes. You already saw the need for this once in the
|
||||
volatile section. Unfortunately, this particular part gets really tricky if
|
||||
you're using `macro_rules!`, so you might need to move up to a full
|
||||
`proc_macro`. Having a `proc_macro` isn't bad except that they have to be
|
||||
defined in a crate of their own and they're compiled before use. You can't
|
||||
ever use them in the crate that defines them, so we won't be using them in any
|
||||
of our single file examples.
|
||||
* Allowing for optional `Deref` and `DerefMut` of the inner value. This takes
|
||||
away most all the safety aspect of doing the newtype, but there may be times
|
||||
for it. As an example, you could make a newtype with a different form of
|
||||
Display impl that you want to otherwise treat as the base type in all places.
|
||||
```rust
|
||||
#[macro_export]
|
||||
macro_rules! newtype {
|
||||
($(#[$attr:meta])* $new_name:ident, $old_name:ty) => {
|
||||
$(#[$attr])*
|
||||
#[repr(transparent)]
|
||||
pub struct $new_name($old_name);
|
||||
impl $new_name {
|
||||
/// A `const` "zero value" constructor
|
||||
pub const fn new() -> Self {
|
||||
$new_name(0)
|
||||
}
|
||||
}
|
||||
};
|
||||
($(#[$attr:meta])* $new_name:ident, $old_name:ty, no frills) => {
|
||||
$(#[$attr])*
|
||||
#[repr(transparent)]
|
||||
pub struct $new_name($old_name);
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
Finally, we usually want to have the wrapped value be totally private, but there
|
||||
_are_ occasions where that's not the case. For this, we can allow the wrapped
|
||||
field to accept a visibility modifier.
|
||||
|
||||
```rust
|
||||
#[macro_export]
|
||||
macro_rules! newtype {
|
||||
($(#[$attr:meta])* $new_name:ident, $v:vis $old_name:ty) => {
|
||||
$(#[$attr])*
|
||||
#[repr(transparent)]
|
||||
pub struct $new_name($v $old_name);
|
||||
impl $new_name {
|
||||
/// A `const` "zero value" constructor
|
||||
pub const fn new() -> Self {
|
||||
$new_name(0)
|
||||
}
|
||||
}
|
||||
};
|
||||
($(#[$attr:meta])* $new_name:ident, $v:vis $old_name:ty, no frills) => {
|
||||
$(#[$attr])*
|
||||
#[repr(transparent)]
|
||||
pub struct $new_name($v $old_name);
|
||||
};
|
||||
}
|
||||
```
|
||||
|
|
|
@ -16,8 +16,8 @@ the larger 16-bit location. This doesn't really affect us much with PALRAM,
|
|||
because palette values are all supposed to be `u16` anyway.
|
||||
|
||||
The palette memory actually contains not one, but _two_ sets of palettes. First
|
||||
there's 256 entries for the background palette data (starting at `0x5000000`),
|
||||
and then there's 256 entries for object palette data (starting at `0x5000200`).
|
||||
there's 256 entries for the background palette data (starting at `0x500_0000`),
|
||||
and then there's 256 entries for object palette data (starting at `0x500_0200`).
|
||||
|
||||
The GBA also has two modes for palette access: 8-bits-per-pixel (8bpp) and
|
||||
4-bits-per-pixel (4bpp).
|
||||
|
|
|
@ -1,6 +1,16 @@
|
|||
#![no_std]
|
||||
#![feature(start)]
|
||||
|
||||
use gba::{
|
||||
io::{
|
||||
background::{BackgroundControlSetting, BG0CNT},
|
||||
display::{DisplayControlSetting, DISPCNT},
|
||||
},
|
||||
palram::index_palram_bg_4bpp,
|
||||
vram::{text::TextScreenblockEntry, Tile4bpp, CHAR_BASE_BLOCKS, SCREEN_BASE_BLOCKS},
|
||||
Color,
|
||||
};
|
||||
|
||||
#[panic_handler]
|
||||
fn panic(_info: &core::panic::PanicInfo) -> ! {
|
||||
loop {}
|
||||
|
@ -8,148 +18,52 @@ fn panic(_info: &core::panic::PanicInfo) -> ! {
|
|||
|
||||
#[start]
|
||||
fn main(_argc: isize, _argv: *const *const u8) -> isize {
|
||||
pub const WHITE: Color = Color::from_rgb(31, 31, 31);
|
||||
pub const LIGHT_GRAY: Color = Color::from_rgb(25, 25, 25);
|
||||
pub const DARK_GRAY: Color = Color::from_rgb(15, 15, 15);
|
||||
// bg palette
|
||||
set_bg_palette_4bpp(0, 1, WHITE);
|
||||
set_bg_palette_4bpp(0, 2, LIGHT_GRAY);
|
||||
set_bg_palette_4bpp(0, 3, DARK_GRAY);
|
||||
index_palram_bg_4bpp(0, 1).write(WHITE);
|
||||
index_palram_bg_4bpp(0, 2).write(LIGHT_GRAY);
|
||||
index_palram_bg_4bpp(0, 3).write(DARK_GRAY);
|
||||
// bg tiles
|
||||
set_bg_tile_4bpp(0, 0, ALL_TWOS);
|
||||
set_bg_tile_4bpp(0, 1, ALL_THREES);
|
||||
// screenblock
|
||||
let light_entry = RegularScreenblockEntry::from_tile_id(0);
|
||||
let dark_entry = RegularScreenblockEntry::from_tile_id(1);
|
||||
let light_entry = TextScreenblockEntry::from_tile_index(0);
|
||||
let dark_entry = TextScreenblockEntry::from_tile_index(1);
|
||||
checker_screenblock(8, light_entry, dark_entry);
|
||||
// bg0 control
|
||||
unsafe { BG0CNT.write(BackgroundControlSetting::from_base_block(8)) };
|
||||
BG0CNT.write(BackgroundControlSetting::new().with_screen_base_block(8));
|
||||
// Display Control
|
||||
unsafe { DISPCNT.write(DisplayControlSetting::JUST_ENABLE_BG0) };
|
||||
loop {
|
||||
// TODO the whole thing
|
||||
}
|
||||
DISPCNT.write(DisplayControlSetting::new().with_bg0(true));
|
||||
loop {}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[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 fn offset(self, count: isize) -> Self {
|
||||
VolatilePtr(self.0.wrapping_offset(count))
|
||||
}
|
||||
pub fn cast<Z>(self) -> VolatilePtr<Z> {
|
||||
VolatilePtr(self.0 as *mut Z)
|
||||
}
|
||||
}
|
||||
pub const ALL_TWOS: Tile4bpp = Tile4bpp([
|
||||
0x22222222, 0x22222222, 0x22222222, 0x22222222, 0x22222222, 0x22222222, 0x22222222, 0x22222222,
|
||||
]);
|
||||
|
||||
pub const BACKGROUND_PALETTE: VolatilePtr<u16> = VolatilePtr(0x500_0000 as *mut u16);
|
||||
|
||||
pub fn set_bg_palette_4bpp(palbank: usize, slot: usize, color: u16) {
|
||||
assert!(palbank < 16);
|
||||
assert!(slot > 0 && slot < 16);
|
||||
unsafe {
|
||||
BACKGROUND_PALETTE
|
||||
.cast::<[u16; 16]>()
|
||||
.offset(palbank as isize)
|
||||
.cast::<u16>()
|
||||
.offset(slot as isize)
|
||||
.write(color);
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn rgb16(red: u16, green: u16, blue: u16) -> u16 {
|
||||
blue << 10 | green << 5 | red
|
||||
}
|
||||
|
||||
pub const WHITE: u16 = rgb16(31, 31, 31);
|
||||
pub const LIGHT_GRAY: u16 = rgb16(25, 25, 25);
|
||||
pub const DARK_GRAY: u16 = rgb16(15, 15, 15);
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
#[repr(transparent)]
|
||||
pub struct Tile4bpp {
|
||||
pub data: [u32; 8],
|
||||
}
|
||||
|
||||
pub const ALL_TWOS: Tile4bpp = Tile4bpp {
|
||||
data: [
|
||||
0x22222222, 0x22222222, 0x22222222, 0x22222222, 0x22222222, 0x22222222, 0x22222222, 0x22222222,
|
||||
],
|
||||
};
|
||||
|
||||
pub const ALL_THREES: Tile4bpp = Tile4bpp {
|
||||
data: [
|
||||
0x33333333, 0x33333333, 0x33333333, 0x33333333, 0x33333333, 0x33333333, 0x33333333, 0x33333333,
|
||||
],
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(transparent)]
|
||||
pub struct Charblock4bpp {
|
||||
pub data: [Tile4bpp; 512],
|
||||
}
|
||||
|
||||
pub const VRAM: VolatilePtr<Charblock4bpp> = VolatilePtr(0x0600_0000 as *mut Charblock4bpp);
|
||||
pub const ALL_THREES: Tile4bpp = Tile4bpp([
|
||||
0x33333333, 0x33333333, 0x33333333, 0x33333333, 0x33333333, 0x33333333, 0x33333333, 0x33333333,
|
||||
]);
|
||||
|
||||
pub fn set_bg_tile_4bpp(charblock: usize, index: usize, tile: Tile4bpp) {
|
||||
assert!(charblock < 4);
|
||||
assert!(index < 512);
|
||||
unsafe { VRAM.offset(charblock as isize).cast::<Tile4bpp>().offset(index as isize).write(tile) }
|
||||
unsafe { CHAR_BASE_BLOCKS.index(charblock).cast::<Tile4bpp>().offset(index as isize).write(tile) }
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
#[repr(transparent)]
|
||||
pub struct RegularScreenblockEntry(u16);
|
||||
|
||||
impl RegularScreenblockEntry {
|
||||
pub const SCREENBLOCK_ENTRY_TILE_ID_MASK: u16 = 0b11_1111_1111;
|
||||
pub const fn from_tile_id(id: u16) -> Self {
|
||||
RegularScreenblockEntry(id & Self::SCREENBLOCK_ENTRY_TILE_ID_MASK)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(transparent)]
|
||||
pub struct RegularScreenblock {
|
||||
pub data: [RegularScreenblockEntry; 32 * 32],
|
||||
}
|
||||
|
||||
pub fn checker_screenblock(slot: usize, a_entry: RegularScreenblockEntry, b_entry: RegularScreenblockEntry) {
|
||||
let mut p = VRAM.cast::<RegularScreenblock>().offset(slot as isize).cast::<RegularScreenblockEntry>();
|
||||
pub fn checker_screenblock(slot: usize, a_entry: TextScreenblockEntry, b_entry: TextScreenblockEntry) {
|
||||
let mut p = unsafe { SCREEN_BASE_BLOCKS.index(slot).cast::<TextScreenblockEntry>() };
|
||||
let mut checker = true;
|
||||
for _row in 0..32 {
|
||||
for _col in 0..32 {
|
||||
unsafe { p.write(if checker { a_entry } else { b_entry }) };
|
||||
p = p.offset(1);
|
||||
unsafe {
|
||||
p.write(if checker { a_entry } else { b_entry });
|
||||
p = p.offset(1);
|
||||
}
|
||||
checker = !checker;
|
||||
}
|
||||
checker = !checker;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Default, PartialEq, Eq)]
|
||||
#[repr(transparent)]
|
||||
pub struct BackgroundControlSetting(u16);
|
||||
|
||||
impl BackgroundControlSetting {
|
||||
pub const SCREEN_BASE_BLOCK_MASK: u16 = 0b1_1111;
|
||||
pub const fn from_base_block(sbb: u16) -> Self {
|
||||
BackgroundControlSetting((sbb & Self::SCREEN_BASE_BLOCK_MASK) << 8)
|
||||
}
|
||||
}
|
||||
|
||||
pub const BG0CNT: VolatilePtr<BackgroundControlSetting> = VolatilePtr(0x400_0008 as *mut BackgroundControlSetting);
|
||||
|
||||
#[derive(Clone, Copy, Default, PartialEq, Eq)]
|
||||
#[repr(transparent)]
|
||||
pub struct DisplayControlSetting(u16);
|
||||
|
||||
impl DisplayControlSetting {
|
||||
pub const JUST_ENABLE_BG0: DisplayControlSetting = DisplayControlSetting(1 << 8);
|
||||
}
|
||||
|
||||
pub const DISPCNT: VolatilePtr<DisplayControlSetting> = VolatilePtr(0x0400_0000 as *mut DisplayControlSetting);
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
#![forbid(unsafe_code)]
|
||||
|
||||
use gba::{
|
||||
io::display::{DisplayControlMode, DisplayControlSetting, DISPCNT},
|
||||
video::Mode3,
|
||||
io::display::{DisplayControlSetting, DisplayMode, DISPCNT},
|
||||
vram::bitmap::Mode3,
|
||||
Color,
|
||||
};
|
||||
|
||||
|
@ -15,7 +15,7 @@ fn panic(_info: &core::panic::PanicInfo) -> ! {
|
|||
|
||||
#[start]
|
||||
fn main(_argc: isize, _argv: *const *const u8) -> isize {
|
||||
const SETTING: DisplayControlSetting = DisplayControlSetting::new().with_mode(DisplayControlMode::Bitmap3).with_display_bg2(true);
|
||||
const SETTING: DisplayControlSetting = DisplayControlSetting::new().with_mode(DisplayMode::Mode3).with_bg2(true);
|
||||
DISPCNT.write(SETTING);
|
||||
Mode3::write_pixel(120, 80, Color::from_rgb(31, 0, 0));
|
||||
Mode3::write_pixel(136, 80, Color::from_rgb(0, 31, 0));
|
||||
|
|
|
@ -4,10 +4,10 @@
|
|||
|
||||
use gba::{
|
||||
io::{
|
||||
display::{spin_until_vblank, spin_until_vdraw, DisplayControlMode, DisplayControlSetting, DISPCNT},
|
||||
display::{spin_until_vblank, spin_until_vdraw, DisplayControlSetting, DisplayMode, DISPCNT},
|
||||
keypad::read_key_input,
|
||||
},
|
||||
video::Mode3,
|
||||
vram::bitmap::Mode3,
|
||||
Color,
|
||||
};
|
||||
|
||||
|
@ -18,7 +18,7 @@ fn panic(_info: &core::panic::PanicInfo) -> ! {
|
|||
|
||||
#[start]
|
||||
fn main(_argc: isize, _argv: *const *const u8) -> isize {
|
||||
const SETTING: DisplayControlSetting = DisplayControlSetting::new().with_mode(DisplayControlMode::Bitmap3).with_display_bg2(true);
|
||||
const SETTING: DisplayControlSetting = DisplayControlSetting::new().with_mode(DisplayMode::Mode3).with_bg2(true);
|
||||
DISPCNT.write(SETTING);
|
||||
|
||||
let mut px = Mode3::SCREEN_WIDTH / 2;
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
#![forbid(unsafe_code)]
|
||||
|
||||
use gba::{
|
||||
io::display::{DisplayControlMode, DisplayControlSetting, DISPCNT},
|
||||
video::Mode3,
|
||||
io::display::{DisplayControlSetting, DisplayMode, DISPCNT},
|
||||
vram::bitmap::Mode3,
|
||||
Color,
|
||||
};
|
||||
|
||||
|
@ -12,6 +12,7 @@ use gba::{
|
|||
fn panic(info: &core::panic::PanicInfo) -> ! {
|
||||
use core::fmt::Write;
|
||||
use gba::mgba::{MGBADebug, MGBADebugLevel};
|
||||
|
||||
if let Some(mut mgba) = MGBADebug::new() {
|
||||
let _ = write!(mgba, "{}", info);
|
||||
mgba.send(MGBADebugLevel::Fatal);
|
||||
|
@ -21,7 +22,7 @@ fn panic(info: &core::panic::PanicInfo) -> ! {
|
|||
|
||||
#[start]
|
||||
fn main(_argc: isize, _argv: *const *const u8) -> isize {
|
||||
const SETTING: DisplayControlSetting = DisplayControlSetting::new().with_mode(DisplayControlMode::Bitmap3).with_display_bg2(true);
|
||||
const SETTING: DisplayControlSetting = DisplayControlSetting::new().with_mode(DisplayMode::Mode3).with_bg2(true);
|
||||
DISPCNT.write(SETTING);
|
||||
Mode3::write_pixel(120, 80, Color::from_rgb(31, 0, 0));
|
||||
Mode3::write_pixel(136, 80, Color::from_rgb(0, 31, 0));
|
||||
|
|
|
@ -230,7 +230,7 @@ fixed_point_unsigned_division! {u32}
|
|||
pub type fx8_8 = Fx<i16, U8>;
|
||||
|
||||
#[cfg(test)]
|
||||
mod fixed_tests {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
use core::{cmp::Ordering, iter::FusedIterator, marker::PhantomData, num::NonZeroUsize};
|
||||
|
||||
// TODO: striding block/iter
|
||||
|
||||
/// Abstracts the use of a volatile hardware address.
|
||||
///
|
||||
/// If you're trying to do anything other than abstract a volatile hardware
|
||||
|
@ -284,7 +286,7 @@ impl<T> VolAddressBlock<T> {
|
|||
if slot < self.slots {
|
||||
unsafe { self.vol_address.offset(slot as isize) }
|
||||
} else {
|
||||
panic!("Index Requested: {} >= Bound: {}", slot, self.slots)
|
||||
panic!("Index Requested: {} >= Slot Count: {}", slot, self.slots)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
398
src/bios.rs
398
src/bios.rs
|
@ -8,6 +8,8 @@
|
|||
//! whatever value is necessary for that function). Some functions also perform
|
||||
//! necessary checks to save you from yourself, such as not dividing by zero.
|
||||
|
||||
use super::bool_bits;
|
||||
|
||||
//TODO: ALL functions in this module should have `if cfg!(test)` blocks. The
|
||||
//functions that never return must panic, the functions that return nothing
|
||||
//should just do so, and the math functions should just return the correct math
|
||||
|
@ -49,13 +51,17 @@
|
|||
/// perform UB, but such a scenario might exist.
|
||||
#[inline(always)]
|
||||
pub unsafe fn soft_reset() -> ! {
|
||||
asm!(/* ASM */ "swi 0x00"
|
||||
:/* OUT */ // none
|
||||
:/* INP */ // none
|
||||
:/* CLO */ // none
|
||||
:/* OPT */ "volatile"
|
||||
);
|
||||
core::hint::unreachable_unchecked()
|
||||
if cfg!(test) {
|
||||
panic!("Attempted soft reset during testing");
|
||||
} else {
|
||||
asm!(/* ASM */ "swi 0x00"
|
||||
:/* OUT */ // none
|
||||
:/* INP */ // none
|
||||
:/* CLO */ // none
|
||||
:/* OPT */ "volatile"
|
||||
);
|
||||
core::hint::unreachable_unchecked()
|
||||
}
|
||||
}
|
||||
|
||||
/// (`swi 0x01`) RegisterRamReset.
|
||||
|
@ -84,15 +90,39 @@ pub unsafe fn soft_reset() -> ! {
|
|||
/// memory, except in the case that you were executing out of EWRAM and clear
|
||||
/// that. If you do then you return to nothing and have a bad time.
|
||||
#[inline(always)]
|
||||
pub unsafe fn register_ram_reset(flags: u8) {
|
||||
asm!(/* ASM */ "swi 0x01"
|
||||
:/* OUT */ // none
|
||||
:/* INP */ "{r0}"(flags)
|
||||
:/* CLO */ // none
|
||||
:/* OPT */ "volatile"
|
||||
pub unsafe fn register_ram_reset(flags: RegisterRAMResetFlags) {
|
||||
if cfg!(test) {
|
||||
// do nothing in test mode
|
||||
} else {
|
||||
asm!(/* ASM */ "swi 0x01"
|
||||
:/* OUT */ // none
|
||||
:/* INP */ "{r0}"(flags.0)
|
||||
:/* CLO */ // none
|
||||
:/* OPT */ "volatile"
|
||||
);
|
||||
}
|
||||
}
|
||||
newtype! {
|
||||
/// Flags for use with `register_ram_reset`.
|
||||
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)]
|
||||
RegisterRAMResetFlags, u8
|
||||
}
|
||||
#[allow(missing_docs)]
|
||||
impl RegisterRAMResetFlags {
|
||||
bool_bits!(
|
||||
u8,
|
||||
[
|
||||
(0, ewram),
|
||||
(1, iwram),
|
||||
(2, palram),
|
||||
(3, vram),
|
||||
(4, oam),
|
||||
(5, sio),
|
||||
(6, sound),
|
||||
(7, other_io),
|
||||
]
|
||||
);
|
||||
}
|
||||
//TODO(lokathor): newtype this flag business.
|
||||
|
||||
/// (`swi 0x02`) Halts the CPU until an interrupt occurs.
|
||||
///
|
||||
|
@ -100,13 +130,17 @@ pub unsafe fn register_ram_reset(flags: u8) {
|
|||
/// any enabled interrupt triggers.
|
||||
#[inline(always)]
|
||||
pub fn halt() {
|
||||
unsafe {
|
||||
asm!(/* ASM */ "swi 0x02"
|
||||
:/* OUT */ // none
|
||||
:/* INP */ // none
|
||||
:/* CLO */ // none
|
||||
:/* OPT */ "volatile"
|
||||
);
|
||||
if cfg!(test) {
|
||||
// do nothing in test mode
|
||||
} else {
|
||||
unsafe {
|
||||
asm!(/* ASM */ "swi 0x02"
|
||||
:/* OUT */ // none
|
||||
:/* INP */ // none
|
||||
:/* CLO */ // none
|
||||
:/* OPT */ "volatile"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -120,13 +154,17 @@ pub fn halt() {
|
|||
/// optional externals such as rumble and infra-red.
|
||||
#[inline(always)]
|
||||
pub fn stop() {
|
||||
unsafe {
|
||||
asm!(/* ASM */ "swi 0x03"
|
||||
:/* OUT */ // none
|
||||
:/* INP */ // none
|
||||
:/* CLO */ // none
|
||||
:/* OPT */ "volatile"
|
||||
);
|
||||
if cfg!(test) {
|
||||
// do nothing in test mode
|
||||
} else {
|
||||
unsafe {
|
||||
asm!(/* ASM */ "swi 0x03"
|
||||
:/* OUT */ // none
|
||||
:/* INP */ // none
|
||||
:/* CLO */ // none
|
||||
:/* OPT */ "volatile"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -145,13 +183,17 @@ pub fn stop() {
|
|||
/// acknowledgement.
|
||||
#[inline(always)]
|
||||
pub fn interrupt_wait(ignore_current_flags: bool, target_flags: u16) {
|
||||
unsafe {
|
||||
asm!(/* ASM */ "swi 0x04"
|
||||
:/* OUT */ // none
|
||||
:/* INP */ "{r0}"(ignore_current_flags), "{r1}"(target_flags)
|
||||
:/* CLO */ // none
|
||||
:/* OPT */ "volatile"
|
||||
);
|
||||
if cfg!(test) {
|
||||
// do nothing in test mode
|
||||
} else {
|
||||
unsafe {
|
||||
asm!(/* ASM */ "swi 0x04"
|
||||
:/* OUT */ // none
|
||||
:/* INP */ "{r0}"(ignore_current_flags), "{r1}"(target_flags)
|
||||
:/* CLO */ // none
|
||||
:/* OPT */ "volatile"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
//TODO(lokathor): newtype this flag business.
|
||||
|
@ -162,13 +204,17 @@ pub fn interrupt_wait(ignore_current_flags: bool, target_flags: u16) {
|
|||
/// must follow the same guidelines that `interrupt_wait` outlines.
|
||||
#[inline(always)]
|
||||
pub fn vblank_interrupt_wait() {
|
||||
unsafe {
|
||||
asm!(/* ASM */ "swi 0x04"
|
||||
:/* OUT */ // none
|
||||
:/* INP */ // none
|
||||
:/* CLO */ "r0", "r1" // both set to 1 by the routine
|
||||
:/* OPT */ "volatile"
|
||||
);
|
||||
if cfg!(test) {
|
||||
// do nothing in test mode
|
||||
} else {
|
||||
unsafe {
|
||||
asm!(/* ASM */ "swi 0x04"
|
||||
:/* OUT */ // none
|
||||
:/* INP */ // none
|
||||
:/* CLO */ "r0", "r1" // both set to 1 by the routine
|
||||
:/* OPT */ "volatile"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -219,16 +265,20 @@ pub fn rem(numerator: i32, denominator: i32) -> i32 {
|
|||
/// by `2n` bits to get `n` more bits of fractional precision in your output.
|
||||
#[inline(always)]
|
||||
pub fn sqrt(val: u32) -> u16 {
|
||||
let out: u16;
|
||||
unsafe {
|
||||
asm!(/* ASM */ "swi 0x08"
|
||||
:/* OUT */ "={r0}"(out)
|
||||
:/* INP */ "{r0}"(val)
|
||||
:/* CLO */ "r1", "r3"
|
||||
:/* OPT */
|
||||
);
|
||||
if cfg!(test) {
|
||||
0 // TODO: simulate this properly during testing builds.
|
||||
} else {
|
||||
let out: u16;
|
||||
unsafe {
|
||||
asm!(/* ASM */ "swi 0x08"
|
||||
:/* OUT */ "={r0}"(out)
|
||||
:/* INP */ "{r0}"(val)
|
||||
:/* CLO */ "r1", "r3"
|
||||
:/* OPT */
|
||||
);
|
||||
}
|
||||
out
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// (`swi 0x09`) Gives the arctangent of `theta`.
|
||||
|
@ -239,16 +289,20 @@ pub fn sqrt(val: u32) -> u16 {
|
|||
/// Accuracy suffers if `theta` is less than `-pi/4` or greater than `pi/4`.
|
||||
#[inline(always)]
|
||||
pub fn atan(theta: i16) -> i16 {
|
||||
let out: i16;
|
||||
unsafe {
|
||||
asm!(/* ASM */ "swi 0x09"
|
||||
:/* OUT */ "={r0}"(out)
|
||||
:/* INP */ "{r0}"(theta)
|
||||
:/* CLO */ "r1", "r3"
|
||||
:/* OPT */
|
||||
);
|
||||
if cfg!(test) {
|
||||
0 // TODO: simulate this properly during testing builds.
|
||||
} else {
|
||||
let out: i16;
|
||||
unsafe {
|
||||
asm!(/* ASM */ "swi 0x09"
|
||||
:/* OUT */ "={r0}"(out)
|
||||
:/* INP */ "{r0}"(theta)
|
||||
:/* CLO */ "r1", "r3"
|
||||
:/* OPT */
|
||||
);
|
||||
}
|
||||
out
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// (`swi 0x0A`) Gives the atan2 of `y` over `x`.
|
||||
|
@ -260,16 +314,20 @@ pub fn atan(theta: i16) -> i16 {
|
|||
/// integral, 14 bits for fractional.
|
||||
#[inline(always)]
|
||||
pub fn atan2(y: i16, x: i16) -> u16 {
|
||||
let out: u16;
|
||||
unsafe {
|
||||
asm!(/* ASM */ "swi 0x0A"
|
||||
:/* OUT */ "={r0}"(out)
|
||||
:/* INP */ "{r0}"(x), "{r1}"(y)
|
||||
:/* CLO */ "r3"
|
||||
:/* OPT */
|
||||
);
|
||||
if cfg!(test) {
|
||||
0 // TODO: simulate this properly during testing builds.
|
||||
} else {
|
||||
let out: u16;
|
||||
unsafe {
|
||||
asm!(/* ASM */ "swi 0x0A"
|
||||
:/* OUT */ "={r0}"(out)
|
||||
:/* INP */ "{r0}"(x), "{r1}"(y)
|
||||
:/* CLO */ "r3"
|
||||
:/* OPT */
|
||||
);
|
||||
}
|
||||
out
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// (`swi 0x0B`) "CpuSet", `u16` memory copy.
|
||||
|
@ -283,13 +341,17 @@ pub fn atan2(y: i16, x: i16) -> u16 {
|
|||
/// * Both pointers must be aligned
|
||||
#[inline(always)]
|
||||
pub unsafe fn cpu_set16(src: *const u16, dest: *mut u16, count: u32, fixed_source: bool) {
|
||||
let control = count + ((fixed_source as u32) << 24);
|
||||
asm!(/* ASM */ "swi 0x0B"
|
||||
:/* OUT */ // none
|
||||
:/* INP */ "{r0}"(src), "{r1}"(dest), "{r2}"(control)
|
||||
:/* CLO */ // none
|
||||
:/* OPT */ "volatile"
|
||||
);
|
||||
if cfg!(test) {
|
||||
// do nothing in test mode
|
||||
} else {
|
||||
let control = count + ((fixed_source as u32) << 24);
|
||||
asm!(/* ASM */ "swi 0x0B"
|
||||
:/* OUT */ // none
|
||||
:/* INP */ "{r0}"(src), "{r1}"(dest), "{r2}"(control)
|
||||
:/* CLO */ // none
|
||||
:/* OPT */ "volatile"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// (`swi 0x0B`) "CpuSet", `u32` memory copy/fill.
|
||||
|
@ -303,13 +365,17 @@ pub unsafe fn cpu_set16(src: *const u16, dest: *mut u16, count: u32, fixed_sourc
|
|||
/// * Both pointers must be aligned
|
||||
#[inline(always)]
|
||||
pub unsafe fn cpu_set32(src: *const u32, dest: *mut u32, count: u32, fixed_source: bool) {
|
||||
let control = count + ((fixed_source as u32) << 24) + (1 << 26);
|
||||
asm!(/* ASM */ "swi 0x0B"
|
||||
:/* OUT */ // none
|
||||
:/* INP */ "{r0}"(src), "{r1}"(dest), "{r2}"(control)
|
||||
:/* CLO */ // none
|
||||
:/* OPT */ "volatile"
|
||||
);
|
||||
if cfg!(test) {
|
||||
// do nothing in test mode
|
||||
} else {
|
||||
let control = count + ((fixed_source as u32) << 24) + (1 << 26);
|
||||
asm!(/* ASM */ "swi 0x0B"
|
||||
:/* OUT */ // none
|
||||
:/* INP */ "{r0}"(src), "{r1}"(dest), "{r2}"(control)
|
||||
:/* CLO */ // none
|
||||
:/* OPT */ "volatile"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// (`swi 0x0C`) "CpuFastSet", copies memory in 32 byte chunks.
|
||||
|
@ -324,13 +390,17 @@ pub unsafe fn cpu_set32(src: *const u32, dest: *mut u32, count: u32, fixed_sourc
|
|||
/// * Both pointers must be aligned
|
||||
#[inline(always)]
|
||||
pub unsafe fn cpu_fast_set(src: *const u32, dest: *mut u32, count: u32, fixed_source: bool) {
|
||||
let control = count + ((fixed_source as u32) << 24);
|
||||
asm!(/* ASM */ "swi 0x0C"
|
||||
:/* OUT */ // none
|
||||
:/* INP */ "{r0}"(src), "{r1}"(dest), "{r2}"(control)
|
||||
:/* CLO */ // none
|
||||
:/* OPT */ "volatile"
|
||||
);
|
||||
if cfg!(test) {
|
||||
// do nothing in test mode
|
||||
} else {
|
||||
let control = count + ((fixed_source as u32) << 24);
|
||||
asm!(/* ASM */ "swi 0x0C"
|
||||
:/* OUT */ // none
|
||||
:/* INP */ "{r0}"(src), "{r1}"(dest), "{r2}"(control)
|
||||
:/* CLO */ // none
|
||||
:/* OPT */ "volatile"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// (`swi 0x0C`) "GetBiosChecksum" (Undocumented)
|
||||
|
@ -343,16 +413,20 @@ pub unsafe fn cpu_fast_set(src: *const u32, dest: *mut u32, count: u32, fixed_so
|
|||
/// some other value I guess you're probably running on an emulator that just
|
||||
/// broke the fourth wall.
|
||||
pub fn get_bios_checksum() -> u32 {
|
||||
let out: u32;
|
||||
unsafe {
|
||||
asm!(/* ASM */ "swi 0x0D"
|
||||
:/* OUT */ "={r0}"(out)
|
||||
:/* INP */ // none
|
||||
:/* CLO */ // none
|
||||
:/* OPT */ // none
|
||||
);
|
||||
if cfg!(test) {
|
||||
0
|
||||
} else {
|
||||
let out: u32;
|
||||
unsafe {
|
||||
asm!(/* ASM */ "swi 0x0D"
|
||||
:/* OUT */ "={r0}"(out)
|
||||
:/* INP */ // none
|
||||
:/* CLO */ // none
|
||||
:/* OPT */ // none
|
||||
);
|
||||
}
|
||||
out
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
// TODO: these things will require that we build special structs
|
||||
|
@ -376,13 +450,17 @@ pub fn get_bios_checksum() -> u32 {
|
|||
///
|
||||
/// The final sound level setting will be `level` * `0x200`.
|
||||
pub fn sound_bias(level: u32) {
|
||||
unsafe {
|
||||
asm!(/* ASM */ "swi 0x19"
|
||||
:/* OUT */ // none
|
||||
:/* INP */ "{r0}"(level)
|
||||
:/* CLO */ // none
|
||||
:/* OPT */ "volatile"
|
||||
);
|
||||
if cfg!(test) {
|
||||
// do nothing in test mode
|
||||
} else {
|
||||
unsafe {
|
||||
asm!(/* ASM */ "swi 0x19"
|
||||
:/* OUT */ // none
|
||||
:/* INP */ "{r0}"(level)
|
||||
:/* CLO */ // none
|
||||
:/* OPT */ "volatile"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -414,13 +492,17 @@ pub fn sound_bias(level: u32) {
|
|||
/// * 10: 40137
|
||||
/// * 11: 42048
|
||||
pub fn sound_driver_mode(mode: u32) {
|
||||
unsafe {
|
||||
asm!(/* ASM */ "swi 0x1B"
|
||||
:/* OUT */ // none
|
||||
:/* INP */ "{r0}"(mode)
|
||||
:/* CLO */ // none
|
||||
:/* OPT */ "volatile"
|
||||
);
|
||||
if cfg!(test) {
|
||||
// do nothing in test mode
|
||||
} else {
|
||||
unsafe {
|
||||
asm!(/* ASM */ "swi 0x1B"
|
||||
:/* OUT */ // none
|
||||
:/* INP */ "{r0}"(mode)
|
||||
:/* CLO */ // none
|
||||
:/* OPT */ "volatile"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
//TODO(lokathor): newtype this mode business.
|
||||
|
@ -434,13 +516,17 @@ pub fn sound_driver_mode(mode: u32) {
|
|||
/// executed." --what?
|
||||
#[inline(always)]
|
||||
pub fn sound_driver_main() {
|
||||
unsafe {
|
||||
asm!(/* ASM */ "swi 0x1C"
|
||||
:/* OUT */ // none
|
||||
:/* INP */ // none
|
||||
:/* CLO */ // none
|
||||
:/* OPT */ "volatile"
|
||||
);
|
||||
if cfg!(test) {
|
||||
// do nothing in test mode
|
||||
} else {
|
||||
unsafe {
|
||||
asm!(/* ASM */ "swi 0x1C"
|
||||
:/* OUT */ // none
|
||||
:/* INP */ // none
|
||||
:/* CLO */ // none
|
||||
:/* OPT */ "volatile"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -450,13 +536,17 @@ pub fn sound_driver_main() {
|
|||
/// vblank interrupt (every 1/60th of a second).
|
||||
#[inline(always)]
|
||||
pub fn sound_driver_vsync() {
|
||||
unsafe {
|
||||
asm!(/* ASM */ "swi 0x1D"
|
||||
:/* OUT */ // none
|
||||
:/* INP */ // none
|
||||
:/* CLO */ // none
|
||||
:/* OPT */ "volatile"
|
||||
);
|
||||
if cfg!(test) {
|
||||
// do nothing in test mode
|
||||
} else {
|
||||
unsafe {
|
||||
asm!(/* ASM */ "swi 0x1D"
|
||||
:/* OUT */ // none
|
||||
:/* INP */ // none
|
||||
:/* CLO */ // none
|
||||
:/* OPT */ "volatile"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -468,13 +558,17 @@ pub fn sound_driver_vsync() {
|
|||
/// --what?
|
||||
#[inline(always)]
|
||||
pub fn sound_channel_clear() {
|
||||
unsafe {
|
||||
asm!(/* ASM */ "swi 0x1E"
|
||||
:/* OUT */ // none
|
||||
:/* INP */ // none
|
||||
:/* CLO */ // none
|
||||
:/* OPT */ "volatile"
|
||||
);
|
||||
if cfg!(test) {
|
||||
// do nothing in test mode
|
||||
} else {
|
||||
unsafe {
|
||||
asm!(/* ASM */ "swi 0x1E"
|
||||
:/* OUT */ // none
|
||||
:/* INP */ // none
|
||||
:/* CLO */ // none
|
||||
:/* OPT */ "volatile"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -489,13 +583,17 @@ pub fn sound_channel_clear() {
|
|||
/// noise.
|
||||
#[inline(always)]
|
||||
pub fn sound_driver_vsync_off() {
|
||||
unsafe {
|
||||
asm!(/* ASM */ "swi 0x28"
|
||||
:/* OUT */ // none
|
||||
:/* INP */ // none
|
||||
:/* CLO */ // none
|
||||
:/* OPT */ "volatile"
|
||||
);
|
||||
if cfg!(test) {
|
||||
// do nothing in test mode
|
||||
} else {
|
||||
unsafe {
|
||||
asm!(/* ASM */ "swi 0x28"
|
||||
:/* OUT */ // none
|
||||
:/* INP */ // none
|
||||
:/* CLO */ // none
|
||||
:/* OPT */ "volatile"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -506,12 +604,16 @@ pub fn sound_driver_vsync_off() {
|
|||
/// interrupt followed by a `sound_driver_vsync` within 2/60th of a second.
|
||||
#[inline(always)]
|
||||
pub fn sound_driver_vsync_on() {
|
||||
unsafe {
|
||||
asm!(/* ASM */ "swi 0x29"
|
||||
:/* OUT */ // none
|
||||
:/* INP */ // none
|
||||
:/* CLO */ // none
|
||||
:/* OPT */ "volatile"
|
||||
);
|
||||
if cfg!(test) {
|
||||
// do nothing in test mode
|
||||
} else {
|
||||
unsafe {
|
||||
asm!(/* ASM */ "swi 0x29"
|
||||
:/* OUT */ // none
|
||||
:/* INP */ // none
|
||||
:/* CLO */ // none
|
||||
:/* OPT */ "volatile"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,8 +8,8 @@
|
|||
|
||||
use super::*;
|
||||
|
||||
use gba_proc_macro::register_bit;
|
||||
|
||||
pub mod background;
|
||||
pub mod display;
|
||||
pub mod dma;
|
||||
pub mod keypad;
|
||||
pub mod timers;
|
||||
|
|
115
src/io/background.rs
Normal file
115
src/io/background.rs
Normal file
|
@ -0,0 +1,115 @@
|
|||
//! Module for Background controls
|
||||
|
||||
use super::*;
|
||||
|
||||
/// BG0 Control. Read/Write. Display Mode 0/1 only.
|
||||
pub const BG0CNT: VolAddress<BackgroundControlSetting> = unsafe { VolAddress::new_unchecked(0x400_0008) };
|
||||
/// BG1 Control. Read/Write. Display Mode 0/1 only.
|
||||
pub const BG1CNT: VolAddress<BackgroundControlSetting> = unsafe { VolAddress::new_unchecked(0x400_000A) };
|
||||
/// BG2 Control. Read/Write. Display Mode 0/1/2 only.
|
||||
pub const BG2CNT: VolAddress<BackgroundControlSetting> = unsafe { VolAddress::new_unchecked(0x400_000C) };
|
||||
/// BG3 Control. Read/Write. Display Mode 0/2 only.
|
||||
pub const BG3CNT: VolAddress<BackgroundControlSetting> = unsafe { VolAddress::new_unchecked(0x400_000E) };
|
||||
|
||||
newtype! {
|
||||
/// Allows configuration of a background layer.
|
||||
///
|
||||
/// Bits 0-1: BG Priority (lower number is higher priority, like an index)
|
||||
/// Bits 2-3: Character Base Block (0 through 3, 16k each)
|
||||
/// Bit 6: Mosaic mode
|
||||
/// Bit 7: is 8bpp
|
||||
/// Bit 8-12: Screen Base Block (0 through 31, 2k each)
|
||||
/// Bit 13: Display area overflow wraps (otherwise transparent, affine BG only)
|
||||
/// Bit 14-15: Screen Size
|
||||
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)]
|
||||
BackgroundControlSetting, u16
|
||||
}
|
||||
impl BackgroundControlSetting {
|
||||
bool_bits!(u16, [(6, mosaic), (7, is_8bpp), (13, display_overflow_wrapping)]);
|
||||
|
||||
multi_bits!(
|
||||
u16,
|
||||
[
|
||||
(0, 2, bg_priority),
|
||||
(2, 2, char_base_block),
|
||||
(8, 5, screen_base_block),
|
||||
(2, 2, size, BGSize, Zero, One, Two, Three),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/// The size of a background.
|
||||
///
|
||||
/// The meaning changes depending on if the background is Text or Affine mode.
|
||||
///
|
||||
/// * In text mode, the screen base block determines where to start reading the
|
||||
/// tile arrangement data (2k). Size Zero gives one screen block of use. Size
|
||||
/// One and Two cause two of them to be used (horizontally or vertically,
|
||||
/// respectively). Size Three is four blocks used, [0,1] above and then [2,3]
|
||||
/// below. Each screen base block used is always a 32x32 tile grid.
|
||||
/// * In affine mode, the screen base block determines where to start reading
|
||||
/// data followed by the size of data as shown. The number of tiles varies
|
||||
/// according to the size used.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u16)]
|
||||
pub enum BGSize {
|
||||
/// * Text: 256x256px (2k)
|
||||
/// * Affine: 128x128px (256b)
|
||||
Zero = 0,
|
||||
/// * Text: 512x256px (4k)
|
||||
/// * Affine: 256x256px (1k)
|
||||
One = 1,
|
||||
/// * Text: 256x512px (4k)
|
||||
/// * Affine: 512x512px (4k)
|
||||
Two = 2,
|
||||
/// * Text: 512x512px (8k)
|
||||
/// * Affine: 1024x1024px (16k)
|
||||
Three = 3,
|
||||
}
|
||||
|
||||
/// BG0 X-Offset. Write only. Text mode only. 9 bits.
|
||||
pub const BG0HOFS: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_0010) };
|
||||
/// BG0 Y-Offset. Write only. Text mode only. 9 bits.
|
||||
pub const BG0VOFS: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_0012) };
|
||||
|
||||
/// BG1 X-Offset. Write only. Text mode only. 9 bits.
|
||||
pub const BG1HOFS: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_0012) };
|
||||
/// BG1 Y-Offset. Write only. Text mode only. 9 bits.
|
||||
pub const BG1VOFS: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_0012) };
|
||||
|
||||
/// BG2 X-Offset. Write only. Text mode only. 9 bits.
|
||||
pub const BG2HOFS: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_0018) };
|
||||
/// BG2 Y-Offset. Write only. Text mode only. 9 bits.
|
||||
pub const BG2VOFS: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_001A) };
|
||||
|
||||
/// BG3 X-Offset. Write only. Text mode only. 9 bits.
|
||||
pub const BG3HOFS: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_001C) };
|
||||
/// BG3 Y-Offset. Write only. Text mode only. 9 bits.
|
||||
pub const BG3VOFS: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_001E) };
|
||||
|
||||
// TODO: affine backgrounds
|
||||
// BG2X_L
|
||||
// BG2X_H
|
||||
// BG2Y_L
|
||||
// BG2Y_H
|
||||
// BG2PA
|
||||
// BG2PB
|
||||
// BG2PC
|
||||
// BG2PD
|
||||
// BG3PA
|
||||
// BG3PB
|
||||
// BG3PC
|
||||
// BG3PD
|
||||
|
||||
// TODO: windowing
|
||||
// pub const WIN0H: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_0040) };
|
||||
// pub const WIN1H: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_0042) };
|
||||
// pub const WIN0V: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_0044) };
|
||||
// pub const WIN1V: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_0046) };
|
||||
// pub const WININ: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_0048) };
|
||||
// pub const WINOUT: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_004A) };
|
||||
|
||||
// TODO: blending
|
||||
// pub const BLDCNT: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_0050) };
|
||||
// pub const BLDALPHA: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_0052) };
|
||||
// pub const BLDY: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_0054) };
|
|
@ -4,11 +4,26 @@ use super::*;
|
|||
|
||||
/// LCD Control. Read/Write.
|
||||
///
|
||||
/// * [gbatek entry](http://problemkaputt.de/gbatek.htm#lcdiodisplaycontrol)
|
||||
/// The "force vblank" bit is always set when your Rust code first executes.
|
||||
pub const DISPCNT: VolAddress<DisplayControlSetting> = unsafe { VolAddress::new_unchecked(0x400_0000) };
|
||||
|
||||
newtype!(
|
||||
/// A newtype over the various display control options that you have on a GBA.
|
||||
/// Setting for the display control register.
|
||||
///
|
||||
/// * 0-2: `DisplayMode`
|
||||
/// * 3: CGB mode flag
|
||||
/// * 4: Display frame 1 (Modes 4/5 only)
|
||||
/// * 5: "hblank interval free", allows full access to OAM during hblank
|
||||
/// * 6: Object tile memory 1-dimensional
|
||||
/// * 7: Force vblank
|
||||
/// * 8: Display bg0 layer
|
||||
/// * 9: Display bg1 layer
|
||||
/// * 10: Display bg2 layer
|
||||
/// * 11: Display bg3 layer
|
||||
/// * 12: Display objects layer
|
||||
/// * 13: Window 0 display
|
||||
/// * 14: Window 1 display
|
||||
/// * 15: Object window
|
||||
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)]
|
||||
DisplayControlSetting,
|
||||
u16
|
||||
|
@ -16,61 +31,65 @@ newtype!(
|
|||
|
||||
#[allow(missing_docs)]
|
||||
impl DisplayControlSetting {
|
||||
pub const BG_MODE_MASK: u16 = 0b111;
|
||||
bool_bits!(
|
||||
u16,
|
||||
[
|
||||
(3, cgb_mode),
|
||||
(4, frame1),
|
||||
(5, hblank_interval_free),
|
||||
(6, oam_memory_1d),
|
||||
(7, force_vblank),
|
||||
(8, bg0),
|
||||
(9, bg1),
|
||||
(10, bg2),
|
||||
(11, bg3),
|
||||
(12, obj),
|
||||
(13, win0),
|
||||
(14, win1),
|
||||
(15, obj_window)
|
||||
]
|
||||
);
|
||||
|
||||
pub fn mode(self) -> DisplayControlMode {
|
||||
// TODO: constify
|
||||
match self.0 & Self::BG_MODE_MASK {
|
||||
0 => DisplayControlMode::Tiled0,
|
||||
1 => DisplayControlMode::Tiled1,
|
||||
2 => DisplayControlMode::Tiled2,
|
||||
3 => DisplayControlMode::Bitmap3,
|
||||
4 => DisplayControlMode::Bitmap4,
|
||||
5 => DisplayControlMode::Bitmap5,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
pub const fn with_mode(self, new_mode: DisplayControlMode) -> Self {
|
||||
Self((self.0 & !Self::BG_MODE_MASK) | (new_mode as u16))
|
||||
}
|
||||
|
||||
register_bit!(CGB_MODE_BIT, u16, 0b1000, cgb_mode);
|
||||
register_bit!(PAGE_SELECT_BIT, u16, 0b1_0000, page1_enabled);
|
||||
register_bit!(HBLANK_INTERVAL_FREE_BIT, u16, 0b10_0000, hblank_interval_free);
|
||||
register_bit!(OBJECT_MEMORY_1D, u16, 0b100_0000, object_memory_1d);
|
||||
register_bit!(FORCE_BLANK_BIT, u16, 0b1000_0000, force_blank);
|
||||
register_bit!(DISPLAY_BG0_BIT, u16, 0b1_0000_0000, display_bg0);
|
||||
register_bit!(DISPLAY_BG1_BIT, u16, 0b10_0000_0000, display_bg1);
|
||||
register_bit!(DISPLAY_BG2_BIT, u16, 0b100_0000_0000, display_bg2);
|
||||
register_bit!(DISPLAY_BG3_BIT, u16, 0b1000_0000_0000, display_bg3);
|
||||
register_bit!(DISPLAY_OBJECT_BIT, u16, 0b1_0000_0000_0000, display_object);
|
||||
register_bit!(DISPLAY_WINDOW0_BIT, u16, 0b10_0000_0000_0000, display_window0);
|
||||
register_bit!(DISPLAY_WINDOW1_BIT, u16, 0b100_0000_0000_0000, display_window1);
|
||||
register_bit!(OBJECT_WINDOW_BIT, u16, 0b1000_0000_0000_0000, display_object_window);
|
||||
multi_bits!(u16, [(0, 3, mode, DisplayMode, Mode0, Mode1, Mode2, Mode3, Mode4, Mode5)]);
|
||||
}
|
||||
|
||||
/// The six display modes available on the GBA.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u16)]
|
||||
pub enum DisplayControlMode {
|
||||
/// This basically allows for the most different things at once (all layers,
|
||||
/// 1024 tiles, two palette modes, etc), but you can't do affine
|
||||
/// transformations.
|
||||
Tiled0 = 0,
|
||||
/// This is a mix of `Tile0` and `Tile2`: BG0 and BG1 run as if in `Tiled0`,
|
||||
/// and BG2 runs as if in `Tiled2`.
|
||||
Tiled1 = 1,
|
||||
/// This allows affine transformations, but only uses BG2 and BG3.
|
||||
Tiled2 = 2,
|
||||
/// This is the basic bitmap draw mode. The whole screen is a single bitmap.
|
||||
/// Uses BG2 only.
|
||||
Bitmap3 = 3,
|
||||
/// This uses _paletted color_ so that there's enough space to have two pages
|
||||
/// at _full resolution_, allowing page flipping. Uses BG2 only.
|
||||
Bitmap4 = 4,
|
||||
/// This uses _reduced resolution_ so that there's enough space to have two
|
||||
/// pages with _full color_, allowing page flipping. Uses BG2 only.
|
||||
Bitmap5 = 5,
|
||||
pub enum DisplayMode {
|
||||
/// * Affine: No
|
||||
/// * Layers: 0/1/2/3
|
||||
/// * Size(px): 256x256 to 512x512
|
||||
/// * Tiles: 1024
|
||||
/// * Palette Modes: 4bpp or 8bpp
|
||||
Mode0 = 0,
|
||||
/// * BG0 / BG1: As Mode0
|
||||
/// * BG2: As Mode2
|
||||
Mode1 = 1,
|
||||
/// * Affine: Yes
|
||||
/// * Layers: 2/3
|
||||
/// * Size(px): 128x128 to 1024x1024
|
||||
/// * Tiles: 256
|
||||
/// * Palette Modes: 8bpp
|
||||
Mode2 = 2,
|
||||
/// * Affine: Yes
|
||||
/// * Layers: 2
|
||||
/// * Size(px): 240x160 (1 page)
|
||||
/// * Bitmap
|
||||
/// * Full Color
|
||||
Mode3 = 3,
|
||||
/// * Affine: Yes
|
||||
/// * Layers: 2
|
||||
/// * Size(px): 240x160 (2 pages)
|
||||
/// * Bitmap
|
||||
/// * Palette Modes: 8bpp
|
||||
Mode4 = 4,
|
||||
/// * Affine: Yes
|
||||
/// * Layers: 2
|
||||
/// * Size(px): 160x128 (2 pages)
|
||||
/// * Bitmap
|
||||
/// * Full Color
|
||||
Mode5 = 5,
|
||||
}
|
||||
|
||||
/// Assigns the given display control setting.
|
||||
|
@ -82,8 +101,32 @@ pub fn display_control() -> DisplayControlSetting {
|
|||
DISPCNT.read()
|
||||
}
|
||||
|
||||
/// If the `VCOUNT` register reads equal to or above this then you're in vblank.
|
||||
pub const VBLANK_SCANLINE: u16 = 160;
|
||||
/// Display Status and IRQ Control. Read/Write.
|
||||
pub const DISPSTAT: VolAddress<DisplayStatusSetting> = unsafe { VolAddress::new_unchecked(0x400_0004) };
|
||||
|
||||
newtype!(
|
||||
/// A newtype over display status and interrupt control values.
|
||||
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)]
|
||||
DisplayStatusSetting,
|
||||
u16
|
||||
);
|
||||
|
||||
#[allow(missing_docs)]
|
||||
impl DisplayStatusSetting {
|
||||
bool_bits!(
|
||||
u16,
|
||||
[
|
||||
(0, vblank_flag),
|
||||
(1, hblank_flag),
|
||||
(2, vcounter_flag),
|
||||
(3, vblank_irq_enable),
|
||||
(4, hblank_irq_enable),
|
||||
(5, vcounter_irq_enable),
|
||||
]
|
||||
);
|
||||
|
||||
multi_bits!(u16, [(8, 8, vcount_setting)]);
|
||||
}
|
||||
|
||||
/// Vertical Counter (LY). Read only.
|
||||
///
|
||||
|
@ -92,6 +135,9 @@ pub const VBLANK_SCANLINE: u16 = 160;
|
|||
/// is in a "vertical blank" period.
|
||||
pub const VCOUNT: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_0006) };
|
||||
|
||||
/// If the `VCOUNT` register reads equal to or above this then you're in vblank.
|
||||
pub const VBLANK_SCANLINE: u16 = 160;
|
||||
|
||||
/// Obtains the current `VCOUNT` value.
|
||||
pub fn vcount() -> u16 {
|
||||
VCOUNT.read()
|
||||
|
@ -108,3 +154,37 @@ pub fn spin_until_vdraw() {
|
|||
// TODO: make this the better version with BIOS and interrupts and such.
|
||||
while vcount() >= VBLANK_SCANLINE {}
|
||||
}
|
||||
|
||||
/// Global mosaic effect control. Write-only.
|
||||
pub const MOSAIC: VolAddress<MosaicSetting> = unsafe { VolAddress::new_unchecked(0x400_004C) };
|
||||
|
||||
newtype! {
|
||||
/// Allows control of the Mosaic effect.
|
||||
///
|
||||
/// Values are the _increase_ for each top-left pixel to be duplicated in the
|
||||
/// final result. If you want to duplicate some other pixel than the top-left,
|
||||
/// you can offset the background or object by an appropriate amount.
|
||||
///
|
||||
/// 0) No effect (1+0)
|
||||
/// 1) Each pixel becomes 2 pixels (1+1)
|
||||
/// 2) Each pixel becomes 3 pixels (1+2)
|
||||
/// 3) Each pixel becomes 4 pixels (1+3)
|
||||
///
|
||||
/// * Bits 0-3: BG mosaic horizontal increase
|
||||
/// * Bits 4-7: BG mosaic vertical increase
|
||||
/// * Bits 8-11: Object mosaic horizontal increase
|
||||
/// * Bits 12-15: Object mosaic vertical increase
|
||||
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)]
|
||||
MosaicSetting, u16
|
||||
}
|
||||
impl MosaicSetting {
|
||||
multi_bits!(
|
||||
u16,
|
||||
[
|
||||
(0, 4, bg_horizontal_inc),
|
||||
(4, 4, bg_vertical_inc),
|
||||
(8, 4, obj_horizontal_inc),
|
||||
(12, 4, obj_vertical_inc),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
|
|
@ -68,56 +68,25 @@ newtype! {
|
|||
}
|
||||
#[allow(missing_docs)]
|
||||
impl DMAControlSetting {
|
||||
pub const DEST_ADDR_CONTROL_MASK: u16 = 0b11 << 5;
|
||||
pub fn dest_address_control(self) -> DMADestAddressControl {
|
||||
// TODO: constify
|
||||
match (self.0 & Self::DEST_ADDR_CONTROL_MASK) >> 5 {
|
||||
0 => DMADestAddressControl::Increment,
|
||||
1 => DMADestAddressControl::Decrement,
|
||||
2 => DMADestAddressControl::Fixed,
|
||||
3 => DMADestAddressControl::IncrementReload,
|
||||
_ => unsafe { core::hint::unreachable_unchecked() },
|
||||
}
|
||||
}
|
||||
pub const fn with_dest_address_control(self, new_control: DMADestAddressControl) -> Self {
|
||||
Self((self.0 & !Self::DEST_ADDR_CONTROL_MASK) | ((new_control as u16) << 5))
|
||||
}
|
||||
bool_bits!(u16, [(9, dma_repeat), (10, use_32bit), (14, irq_when_done), (15, enabled)]);
|
||||
|
||||
pub const SRC_ADDR_CONTROL_MASK: u16 = 0b11 << 7;
|
||||
pub fn src_address_control(self) -> DMASrcAddressControl {
|
||||
// TODO: constify
|
||||
match (self.0 & Self::SRC_ADDR_CONTROL_MASK) >> 7 {
|
||||
0 => DMASrcAddressControl::Increment,
|
||||
1 => DMASrcAddressControl::Decrement,
|
||||
2 => DMASrcAddressControl::Fixed,
|
||||
_ => unreachable!(), // TODO: custom error message?
|
||||
}
|
||||
}
|
||||
pub const fn with_src_address_control(self, new_control: DMASrcAddressControl) -> Self {
|
||||
Self((self.0 & !Self::SRC_ADDR_CONTROL_MASK) | ((new_control as u16) << 7))
|
||||
}
|
||||
|
||||
register_bit!(REPEAT, u16, 1 << 9, repeat);
|
||||
register_bit!(TRANSFER_U32, u16, 1 << 10, transfer_u32);
|
||||
// TODO: Game Pak DRQ? (bit 11) DMA3 only, and requires specific hardware
|
||||
|
||||
pub const START_TIMING_MASK: u16 = 0b11 << 12;
|
||||
pub fn start_timing(self) -> DMAStartTiming {
|
||||
// TODO: constify
|
||||
match (self.0 & Self::DEST_ADDR_CONTROL_MASK) >> 12 {
|
||||
0 => DMAStartTiming::Immediate,
|
||||
1 => DMAStartTiming::VBlank,
|
||||
2 => DMAStartTiming::HBlank,
|
||||
3 => DMAStartTiming::Special,
|
||||
_ => unsafe { core::hint::unreachable_unchecked() },
|
||||
}
|
||||
}
|
||||
pub const fn with_start_timing(self, new_control: DMAStartTiming) -> Self {
|
||||
Self((self.0 & !Self::START_TIMING_MASK) | ((new_control as u16) << 12))
|
||||
}
|
||||
|
||||
register_bit!(IRQ_AT_END, u16, 1 << 14, irq_at_end);
|
||||
register_bit!(ENABLE, u16, 1 << 15, enable);
|
||||
multi_bits!(
|
||||
u16,
|
||||
[
|
||||
(
|
||||
5,
|
||||
2,
|
||||
dest_address_control,
|
||||
DMADestAddressControl,
|
||||
Increment,
|
||||
Decrement,
|
||||
Fixed,
|
||||
IncrementReload
|
||||
),
|
||||
(7, 2, source_address_control, DMASrcAddressControl, Increment, Decrement, Fixed),
|
||||
(12, 2, start_time, DMAStartTiming, Immediate, VBlank, HBlank, Special)
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/// Sets how the destination address should be adjusted per data transfer.
|
||||
|
@ -234,9 +203,9 @@ impl DMA3 {
|
|||
/// must be valid for writing.
|
||||
pub unsafe fn fill32(src: *const u32, dest: *mut u32, count: u16) {
|
||||
const FILL_CONTROL: DMAControlSetting = DMAControlSetting::new()
|
||||
.with_src_address_control(DMASrcAddressControl::Fixed)
|
||||
.with_transfer_u32(true)
|
||||
.with_enable(true);
|
||||
.with_source_address_control(DMASrcAddressControl::Fixed)
|
||||
.with_use_32bit(true)
|
||||
.with_enabled(true);
|
||||
// TODO: destination checking against SRAM
|
||||
Self::DMA3SAD.write(src);
|
||||
Self::DMA3DAD.write(dest);
|
||||
|
|
|
@ -13,10 +13,12 @@ pub const KEYINPUT: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_0
|
|||
/// A "tribool" value helps us interpret the arrow pad.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(i32)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum TriBool {
|
||||
/// -1
|
||||
Minus = -1,
|
||||
/// +0
|
||||
Neutral = 0,
|
||||
/// +1
|
||||
Plus = 1,
|
||||
}
|
||||
|
||||
|
@ -31,16 +33,21 @@ newtype! {
|
|||
|
||||
#[allow(missing_docs)]
|
||||
impl KeyInput {
|
||||
register_bit!(A_BIT, u16, 1, a_pressed);
|
||||
register_bit!(B_BIT, u16, 1 << 1, b_pressed);
|
||||
register_bit!(SELECT_BIT, u16, 1 << 2, select_pressed);
|
||||
register_bit!(START_BIT, u16, 1 << 3, start_pressed);
|
||||
register_bit!(RIGHT_BIT, u16, 1 << 4, right_pressed);
|
||||
register_bit!(LEFT_BIT, u16, 1 << 5, left_pressed);
|
||||
register_bit!(UP_BIT, u16, 1 << 6, up_pressed);
|
||||
register_bit!(DOWN_BIT, u16, 1 << 7, down_pressed);
|
||||
register_bit!(R_BIT, u16, 1 << 8, r_pressed);
|
||||
register_bit!(L_BIT, u16, 1 << 9, l_pressed);
|
||||
bool_bits!(
|
||||
u16,
|
||||
[
|
||||
(0, a),
|
||||
(1, b),
|
||||
(2, select),
|
||||
(3, start),
|
||||
(4, right),
|
||||
(5, left),
|
||||
(6, up),
|
||||
(7, down),
|
||||
(8, r),
|
||||
(9, l)
|
||||
]
|
||||
);
|
||||
|
||||
/// Takes the set difference between these keys and another set of keys.
|
||||
pub fn difference(self, other: Self) -> Self {
|
||||
|
@ -50,9 +57,9 @@ impl KeyInput {
|
|||
/// Gives the arrow pad value as a tribool, with Plus being increased column
|
||||
/// value (right).
|
||||
pub fn column_direction(self) -> TriBool {
|
||||
if self.right_pressed() {
|
||||
if self.right() {
|
||||
TriBool::Plus
|
||||
} else if self.left_pressed() {
|
||||
} else if self.left() {
|
||||
TriBool::Minus
|
||||
} else {
|
||||
TriBool::Neutral
|
||||
|
@ -62,9 +69,9 @@ impl KeyInput {
|
|||
/// Gives the arrow pad value as a tribool, with Plus being increased row
|
||||
/// value (down).
|
||||
pub fn row_direction(self) -> TriBool {
|
||||
if self.down_pressed() {
|
||||
if self.down() {
|
||||
TriBool::Plus
|
||||
} else if self.up_pressed() {
|
||||
} else if self.up() {
|
||||
TriBool::Minus
|
||||
} else {
|
||||
TriBool::Neutral
|
||||
|
@ -100,19 +107,23 @@ newtype! {
|
|||
}
|
||||
#[allow(missing_docs)]
|
||||
impl KeyInterruptSetting {
|
||||
register_bit!(A_BIT, u16, 1, a_pressed);
|
||||
register_bit!(B_BIT, u16, 1 << 1, b_pressed);
|
||||
register_bit!(SELECT_BIT, u16, 1 << 2, select_pressed);
|
||||
register_bit!(START_BIT, u16, 1 << 3, start_pressed);
|
||||
register_bit!(RIGHT_BIT, u16, 1 << 4, right_pressed);
|
||||
register_bit!(LEFT_BIT, u16, 1 << 5, left_pressed);
|
||||
register_bit!(UP_BIT, u16, 1 << 6, up_pressed);
|
||||
register_bit!(DOWN_BIT, u16, 1 << 7, down_pressed);
|
||||
register_bit!(R_BIT, u16, 1 << 8, r_pressed);
|
||||
register_bit!(L_BIT, u16, 1 << 9, l_pressed);
|
||||
//
|
||||
register_bit!(IRQ_ENABLE_BIT, u16, 1 << 14, irq_enabled);
|
||||
register_bit!(IRQ_AND_BIT, u16, 1 << 15, irq_logical_and);
|
||||
bool_bits!(
|
||||
u16,
|
||||
[
|
||||
(0, a),
|
||||
(1, b),
|
||||
(2, select),
|
||||
(3, start),
|
||||
(4, right),
|
||||
(5, left),
|
||||
(6, up),
|
||||
(7, down),
|
||||
(8, r),
|
||||
(9, l),
|
||||
(14, irq_enabled),
|
||||
(15, irq_logical_and)
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/// Use this to configure when a keypad interrupt happens.
|
||||
|
|
87
src/io/timers.rs
Normal file
87
src/io/timers.rs
Normal file
|
@ -0,0 +1,87 @@
|
|||
//! Module for timers.
|
||||
//!
|
||||
//! The timers are slightly funny in that reading and writing from them works
|
||||
//! somewhat differently than with basically any other part of memory.
|
||||
//!
|
||||
//! When you read a timer's counter you read the current value.
|
||||
//!
|
||||
//! When you write a timer's counter you write _the counter's reload value_.
|
||||
//! This is used whenever you enable the timer or any time the timer overflows.
|
||||
//! You cannot set a timer to a given counter value, but you can set a timer to
|
||||
//! start at some particular value every time it reloads.
|
||||
//!
|
||||
//! The timer counters are `u16`, so if you want to set them to run for a
|
||||
//! certain number of ticks before overflow you would write something like
|
||||
//!
|
||||
//! ```rust
|
||||
//! let init_val: u16 = u32::wrapping_sub(0x1_0000, ticks) as u16;
|
||||
//! ```
|
||||
//!
|
||||
//! A timer reloads any time it overflows _or_ goes from disabled to enabled. If
|
||||
//! you want to "pause" a timer _without_ making it reload when resumed then you
|
||||
//! should not disable it. Instead, you should set its `TimerTickRate` to
|
||||
//! `Cascade` and disable _the next lower timer_ so that it won't overflow into
|
||||
//! the timer you have on hold.
|
||||
|
||||
use super::*;
|
||||
|
||||
// TODO: striding blocks?
|
||||
|
||||
/// Timer 0 Counter/Reload. Special (see module).
|
||||
pub const TM0CNT_L: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_0100) };
|
||||
|
||||
/// Timer 1 Counter/Reload. Special (see module).
|
||||
pub const TM1CNT_L: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_0104) };
|
||||
|
||||
/// Timer 2 Counter/Reload. Special (see module).
|
||||
pub const TM2CNT_L: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_0108) };
|
||||
|
||||
/// Timer 3 Counter/Reload. Special (see module).
|
||||
pub const TM3CNT_L: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_010C) };
|
||||
|
||||
/// Timer 0 Control. Read/Write.
|
||||
pub const TM0CNT_H: VolAddress<TimerControlSetting> = unsafe { VolAddress::new_unchecked(0x400_0102) };
|
||||
|
||||
/// Timer 1 Control. Read/Write.
|
||||
pub const TM1CNT_H: VolAddress<TimerControlSetting> = unsafe { VolAddress::new_unchecked(0x400_0106) };
|
||||
|
||||
/// Timer 2 Control. Read/Write.
|
||||
pub const TM2CNT_H: VolAddress<TimerControlSetting> = unsafe { VolAddress::new_unchecked(0x400_010A) };
|
||||
|
||||
/// Timer 3 Control. Read/Write.
|
||||
pub const TM3CNT_H: VolAddress<TimerControlSetting> = unsafe { VolAddress::new_unchecked(0x400_010E) };
|
||||
|
||||
newtype! {
|
||||
/// Allows control of a timer unit.
|
||||
///
|
||||
/// * Bits 0-2: How often the timer should tick up one unit. You can either
|
||||
/// specify a number of CPU cycles or "cascade" mode, where there's a single
|
||||
/// tick per overflow of the next lower timer. For example, Timer 1 would
|
||||
/// tick up once per overflow of Timer 0 if it were in cascade mode. Cascade
|
||||
/// mode naturally does nothing when used with Timer 0.
|
||||
/// * Bit 6: Raise a timer interrupt upon overflow.
|
||||
/// * Bit 7: Enable the timer.
|
||||
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)]
|
||||
TimerControlSetting, u16
|
||||
}
|
||||
impl TimerControlSetting {
|
||||
bool_bits!(u16, [(6, overflow_irq), (7, enabled)]);
|
||||
|
||||
multi_bits!(u16, [(0, 3, tick_rate, TimerTickRate, CPU1, CPU64, CPU256, CPU1024, Cascade),]);
|
||||
}
|
||||
|
||||
/// Controls how often an enabled timer ticks upward.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u16)]
|
||||
pub enum TimerTickRate {
|
||||
/// Once every CPU cycle
|
||||
CPU1 = 0,
|
||||
/// Once per 64 CPU cycles
|
||||
CPU64 = 1,
|
||||
/// Once per 256 CPU cycles
|
||||
CPU256 = 2,
|
||||
/// Once per 1,024 CPU cycles
|
||||
CPU1024 = 3,
|
||||
/// Once per overflow of the next lower timer. (Useless with Timer 0)
|
||||
Cascade = 4,
|
||||
}
|
14
src/lib.rs
14
src/lib.rs
|
@ -20,6 +20,8 @@
|
|||
//! **Do not** use this crate in programs that aren't running on the GBA. If you
|
||||
//! do, it's a giant bag of Undefined Behavior.
|
||||
|
||||
pub(crate) use gba_proc_macro::{bool_bits, multi_bits};
|
||||
|
||||
/// Assists in defining a newtype wrapper over some base type.
|
||||
///
|
||||
/// Note that rustdoc and derives are all the "meta" stuff, so you can write all
|
||||
|
@ -40,10 +42,10 @@
|
|||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! newtype {
|
||||
($(#[$attr:meta])* $new_name:ident, $old_name:ident) => {
|
||||
($(#[$attr:meta])* $new_name:ident, $v:vis $old_name:ty) => {
|
||||
$(#[$attr])*
|
||||
#[repr(transparent)]
|
||||
pub struct $new_name($old_name);
|
||||
pub struct $new_name($v $old_name);
|
||||
impl $new_name {
|
||||
/// A `const` "zero value" constructor
|
||||
pub const fn new() -> Self {
|
||||
|
@ -51,10 +53,10 @@ macro_rules! newtype {
|
|||
}
|
||||
}
|
||||
};
|
||||
($(#[$attr:meta])* $new_name:ident, $old_name:ident, no frills) => {
|
||||
($(#[$attr:meta])* $new_name:ident, $v:vis $old_name:ty, no frills) => {
|
||||
$(#[$attr])*
|
||||
#[repr(transparent)]
|
||||
pub struct $new_name($old_name);
|
||||
pub struct $new_name($v $old_name);
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -63,7 +65,9 @@ pub(crate) use self::base::*;
|
|||
pub mod bios;
|
||||
pub mod io;
|
||||
pub mod mgba;
|
||||
pub mod video;
|
||||
pub mod oam;
|
||||
pub mod palram;
|
||||
pub mod vram;
|
||||
|
||||
newtype! {
|
||||
/// A color on the GBA is an RGB 5.5.5 within a `u16`
|
||||
|
|
131
src/oam.rs
Normal file
131
src/oam.rs
Normal file
|
@ -0,0 +1,131 @@
|
|||
//! Types and declarations for the Object Attribute Memory (`OAM`).
|
||||
|
||||
use super::*;
|
||||
|
||||
newtype! {
|
||||
/// 0th part of an object's attributes.
|
||||
///
|
||||
/// * Bits 0-7: row-coordinate
|
||||
/// * Bits 8-9: Rendering style: Normal, Affine, Disabled, Double Area Affine
|
||||
/// * Bits 10-11: Object mode: Normal, SemiTransparent, Object Window
|
||||
/// * Bit 12: Mosaic
|
||||
/// * Bit 13: is 8bpp
|
||||
/// * Bits 14-15: Object Shape: Square, Horizontal, Vertical
|
||||
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)]
|
||||
OBJAttr0, u16
|
||||
}
|
||||
impl OBJAttr0 {
|
||||
bool_bits!(u16, [(12, mosaic), (13, is_8bpp),]);
|
||||
|
||||
multi_bits!(
|
||||
u16,
|
||||
[
|
||||
(0, 8, row_coordinate),
|
||||
(8, 2, obj_rendering, ObjectRender, Normal, Affine, Disabled, DoubleAreaAffine),
|
||||
(10, 2, obj_mode, ObjectMode, Normal, SemiTransparent, OBJWindow),
|
||||
(14, 2, obj_shape, ObjectShape, Square, Horizontal, Vertical),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/// What style of rendering for this object
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u16)]
|
||||
pub enum ObjectRender {
|
||||
/// Standard, non-affine rendering
|
||||
Normal = 0,
|
||||
/// Affine rendering
|
||||
Affine = 1,
|
||||
/// Object disabled (saves cycles for elsewhere!)
|
||||
Disabled = 2,
|
||||
/// Affine with double render space (helps prevent clipping)
|
||||
DoubleAreaAffine = 3,
|
||||
}
|
||||
|
||||
/// What mode to ues for the object.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u16)]
|
||||
pub enum ObjectMode {
|
||||
/// Show the object normally
|
||||
Normal = 0,
|
||||
/// The object becomes the "Alpha Blending 1st target" (see Alpha Blending)
|
||||
SemiTransparent = 1,
|
||||
/// Use the object's non-transparent pixels as part of a mask for the object
|
||||
/// window (see Windows).
|
||||
OBJWindow = 2,
|
||||
}
|
||||
|
||||
/// What shape the object's appearance should be.
|
||||
///
|
||||
/// The specifics also depend on the `ObjectSize` set.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u16)]
|
||||
pub enum ObjectShape {
|
||||
/// Equal parts wide and tall
|
||||
Square = 0,
|
||||
/// Wider than tall
|
||||
Horizontal = 1,
|
||||
/// Taller than wide
|
||||
Vertical = 2,
|
||||
}
|
||||
|
||||
newtype! {
|
||||
/// 1st part of an object's attributes.
|
||||
///
|
||||
/// * Bits 0-8: column coordinate
|
||||
/// * Bits 9-13:
|
||||
/// * Normal render: Bit 12 holds hflip and 13 holds vflip.
|
||||
/// * Affine render: The affine parameter selection.
|
||||
/// * Bits 14-15: Object Size
|
||||
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)]
|
||||
OBJAttr1, u16
|
||||
}
|
||||
impl OBJAttr1 {
|
||||
bool_bits!(u16, [(12, hflip), (13, vflip),]);
|
||||
|
||||
multi_bits!(
|
||||
u16,
|
||||
[
|
||||
(0, 9, col_coordinate),
|
||||
(9, 5, affine_index),
|
||||
(14, 2, obj_size, ObjectSize, Zero, One, Two, Three),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/// The object's size.
|
||||
///
|
||||
/// Also depends on the `ObjectShape` set.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u16)]
|
||||
pub enum ObjectSize {
|
||||
/// * Square: 8x8px
|
||||
/// * Horizontal: 16x8px
|
||||
/// * Vertical: 8x16px
|
||||
Zero = 0,
|
||||
/// * Square: 16x16px
|
||||
/// * Horizontal: 32x8px
|
||||
/// * Vertical: 8x32px
|
||||
One = 1,
|
||||
/// * Square: 32x32px
|
||||
/// * Horizontal: 32x16px
|
||||
/// * Vertical: 16x32px
|
||||
Two = 2,
|
||||
/// * Square: 64x64px
|
||||
/// * Horizontal: 64x32px
|
||||
/// * Vertical: 32x64px
|
||||
Three = 3,
|
||||
}
|
||||
|
||||
newtype! {
|
||||
/// 2nd part of an object's attributes.
|
||||
///
|
||||
/// * Bits 0-9: Base Tile Index (tile offset from CBB4)
|
||||
/// * Bits 10-11: Priority
|
||||
/// * Bits 12-15: Palbank (if using 4bpp)
|
||||
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)]
|
||||
OBJAttr2, u16
|
||||
}
|
||||
impl OBJAttr2 {
|
||||
multi_bits!(u16, [(0, 10, tile_id), (10, 2, priority), (12, 4, palbank),]);
|
||||
}
|
71
src/palram.rs
Normal file
71
src/palram.rs
Normal file
|
@ -0,0 +1,71 @@
|
|||
//! Module that allows interacting with palette memory, (`PALRAM`).
|
||||
//!
|
||||
//! The `PALRAM` contains 256 `Color` values for Background use, and 256 `Color`
|
||||
//! values for Object use.
|
||||
//!
|
||||
//! Each block of `PALRAM` can be viewed as "8 bits per pixel" (8bpp), where
|
||||
//! there's a single palette of 256 entries. It can also be viewed as "4 bits
|
||||
//! per pixel" (4bpp), where there's 16 "palbank" entries that each have 16
|
||||
//! slots. **Both** interpretations are correct, simultaneously. If you're a
|
||||
//! real palette wizard you can carefully arrange for some things to use 4bpp
|
||||
//! mode while other things use 8bpp mode and have it all look good.
|
||||
//!
|
||||
//! ## Transparency
|
||||
//!
|
||||
//! In 8bpp mode the 0th palette index is "transparent" when used in an image
|
||||
//! (giving you 255 usable slots). In 4bpp mode the 0th palbank index _of each
|
||||
//! palbank_ is considered a transparency pixel (giving you 15 usable slots per
|
||||
//! palbank).
|
||||
//!
|
||||
//! ## Clear Color
|
||||
//!
|
||||
//! The 0th palette index of the background palette holds the color that the
|
||||
//! display will show if no background or object draws over top of a given pixel
|
||||
//! during rendering.
|
||||
|
||||
use super::{
|
||||
base::volatile::{VolAddress, VolAddressBlock},
|
||||
Color,
|
||||
};
|
||||
|
||||
// TODO: PalIndex newtypes?
|
||||
|
||||
/// The `PALRAM` for background colors, 256 slot view.
|
||||
pub const PALRAM_BG: VolAddressBlock<Color> = unsafe { VolAddressBlock::new_unchecked(VolAddress::new_unchecked(0x500_0000), 256) };
|
||||
|
||||
/// The `PALRAM` for object colors, 256 slot view.
|
||||
pub const PALRAM_OBJ: VolAddressBlock<Color> = unsafe { VolAddressBlock::new_unchecked(VolAddress::new_unchecked(0x500_0200), 256) };
|
||||
|
||||
/// Obtains the address of the specified 8bpp background palette slot.
|
||||
pub fn index_palram_bg_8bpp(slot: u8) -> VolAddress<Color> {
|
||||
// TODO: const this
|
||||
// Note(Lokathor): because of the `u8` limit we can't go out of bounds here.
|
||||
unsafe { PALRAM_BG.index_unchecked(slot as usize) }
|
||||
}
|
||||
|
||||
/// Obtains the address of the specified 8bpp object palette slot.
|
||||
pub fn index_palram_obj_8bpp(slot: u8) -> VolAddress<Color> {
|
||||
// TODO: const this
|
||||
// Note(Lokathor): because of the `u8` limit we can't go out of bounds here.
|
||||
unsafe { PALRAM_OBJ.index_unchecked(slot as usize) }
|
||||
}
|
||||
|
||||
/// Obtains the address of the specified 4bpp background palbank and palslot.
|
||||
///
|
||||
/// Accesses `palbank * 16 + palslot`, if this is out of bounds the computation
|
||||
/// will wrap.
|
||||
pub fn index_palram_bg_4bpp(palbank: u8, palslot: u8) -> VolAddress<Color> {
|
||||
// TODO: const this
|
||||
// Note(Lokathor): because of the `u8` limit we can't go out of bounds here.
|
||||
unsafe { PALRAM_BG.index_unchecked(palbank.wrapping_mul(16).wrapping_add(palslot) as usize) }
|
||||
}
|
||||
|
||||
/// Obtains the address of the specified 4bpp object palbank and palslot.
|
||||
///
|
||||
/// Accesses `palbank * 16 + palslot`, if this is out of bounds the computation
|
||||
/// will wrap.
|
||||
pub fn index_palram_obj_4bpp(palbank: u8, palslot: u8) -> VolAddress<Color> {
|
||||
// TODO: const this
|
||||
// Note(Lokathor): because of the `u8` limit we can't go out of bounds here.
|
||||
unsafe { PALRAM_OBJ.index_unchecked(palbank.wrapping_mul(16).wrapping_add(palslot) as usize) }
|
||||
}
|
92
src/video.rs
92
src/video.rs
|
@ -1,92 +0,0 @@
|
|||
//! Module for all things relating to the Video RAM.
|
||||
//!
|
||||
//! Note that the GBA has six different display modes available, and the
|
||||
//! _meaning_ of Video RAM depends on which display mode is active. In all
|
||||
//! cases, Video RAM is **96kb** from `0x0600_0000` to `0x0601_7FFF`.
|
||||
//!
|
||||
//! # Safety
|
||||
//!
|
||||
//! Note that all possible bit patterns are technically allowed within Video
|
||||
//! Memory. If you write the "wrong" thing into video memory you don't crash the
|
||||
//! GBA, instead you just get graphical glitches (or perhaps nothing at all).
|
||||
//! Accordingly, the "safe" functions here will check that you're in bounds, but
|
||||
//! they won't bother to check that you've set the video mode they're designed
|
||||
//! for.
|
||||
|
||||
pub use super::*;
|
||||
|
||||
/// The start of VRAM.
|
||||
///
|
||||
/// Depending on what display mode is currently set there's different ways that
|
||||
/// your program should interpret the VRAM space. Accordingly, we give the raw
|
||||
/// value as just being a `usize`. Specific video mode types then wrap this as
|
||||
/// being the correct thing.
|
||||
pub const VRAM_BASE_USIZE: usize = 0x600_0000;
|
||||
|
||||
/// Mode 3 is a bitmap mode with full color and full resolution.
|
||||
///
|
||||
/// * **Width:** 240
|
||||
/// * **Height:** 160
|
||||
///
|
||||
/// Because the memory requirements are so large, there's only a single page
|
||||
/// available instead of two pages like the other video modes have.
|
||||
///
|
||||
/// As with all bitmap modes, the bitmap itself utilizes BG2 for display, so you
|
||||
/// must have that BG enabled in addition to being within Mode 3.
|
||||
pub struct Mode3;
|
||||
impl Mode3 {
|
||||
/// The physical width in pixels of the GBA screen.
|
||||
pub const SCREEN_WIDTH: usize = 240;
|
||||
|
||||
/// The physical height in pixels of the GBA screen.
|
||||
pub const SCREEN_HEIGHT: usize = 160;
|
||||
|
||||
/// The Mode 3 VRAM.
|
||||
///
|
||||
/// Use `col + row * SCREEN_WIDTH` to get the address of an individual pixel,
|
||||
/// or use the helpers provided in this module.
|
||||
pub const VRAM: VolAddressBlock<Color> =
|
||||
unsafe { VolAddressBlock::new_unchecked(VolAddress::new_unchecked(VRAM_BASE_USIZE), Self::SCREEN_WIDTH * Self::SCREEN_HEIGHT) };
|
||||
|
||||
const MODE3_U32_COUNT: u16 = (Self::SCREEN_WIDTH * Self::SCREEN_HEIGHT / 2) as u16;
|
||||
|
||||
/// private iterator over the pixels, two at a time
|
||||
const BULK_ITER: VolAddressIter<u32> =
|
||||
unsafe { VolAddressBlock::new_unchecked(VolAddress::new_unchecked(VRAM_BASE_USIZE), Self::MODE3_U32_COUNT as usize).iter() };
|
||||
|
||||
/// Reads the pixel at the given (col,row).
|
||||
///
|
||||
/// # Failure
|
||||
///
|
||||
/// Gives `None` if out of bounds.
|
||||
pub fn read_pixel(col: usize, row: usize) -> Option<Color> {
|
||||
Self::VRAM.get(col + row * Self::SCREEN_WIDTH).map(VolAddress::read)
|
||||
}
|
||||
|
||||
/// Writes the pixel at the given (col,row).
|
||||
///
|
||||
/// # Failure
|
||||
///
|
||||
/// Gives `None` if out of bounds.
|
||||
pub fn write_pixel(col: usize, row: usize, color: Color) -> Option<()> {
|
||||
Self::VRAM.get(col + row * Self::SCREEN_WIDTH).map(|va| va.write(color))
|
||||
}
|
||||
|
||||
/// Clears the whole screen to the desired color.
|
||||
pub fn clear_to(color: Color) {
|
||||
let color32 = color.0 as u32;
|
||||
let bulk_color = color32 << 16 | color32;
|
||||
for va in Self::BULK_ITER {
|
||||
va.write(bulk_color)
|
||||
}
|
||||
}
|
||||
|
||||
/// Clears the whole screen to the desired color using DMA3.
|
||||
pub fn dma_clear_to(color: Color) {
|
||||
use crate::io::dma::DMA3;
|
||||
|
||||
let color32 = color.0 as u32;
|
||||
let bulk_color = color32 << 16 | color32;
|
||||
unsafe { DMA3::fill32(&bulk_color, VRAM_BASE_USIZE as *mut u32, Self::MODE3_U32_COUNT) };
|
||||
}
|
||||
}
|
57
src/vram.rs
Normal file
57
src/vram.rs
Normal file
|
@ -0,0 +1,57 @@
|
|||
//! Module for all things relating to the Video RAM.
|
||||
//!
|
||||
//! Note that the GBA has six different display modes available, and the
|
||||
//! _meaning_ of Video RAM depends on which display mode is active. In all
|
||||
//! cases, Video RAM is **96kb** from `0x0600_0000` to `0x0601_7FFF`.
|
||||
//!
|
||||
//! # Safety
|
||||
//!
|
||||
//! Note that all possible bit patterns are technically allowed within Video
|
||||
//! Memory. If you write the "wrong" thing into video memory you don't crash the
|
||||
//! GBA, instead you just get graphical glitches (or perhaps nothing at all).
|
||||
//! Accordingly, the "safe" functions here will check that you're in bounds, but
|
||||
//! they won't bother to check that you've set the video mode they're designed
|
||||
//! for.
|
||||
|
||||
pub(crate) use super::*;
|
||||
|
||||
pub mod affine;
|
||||
pub mod bitmap;
|
||||
pub mod text;
|
||||
|
||||
/// The start of VRAM.
|
||||
///
|
||||
/// Depending on what display mode is currently set there's different ways that
|
||||
/// your program should interpret the VRAM space. Accordingly, we give the raw
|
||||
/// value as just being a `usize`. Specific video mode types then wrap this as
|
||||
/// being the correct thing.
|
||||
pub const VRAM_BASE_USIZE: usize = 0x600_0000;
|
||||
|
||||
/// The character base blocks.
|
||||
pub const CHAR_BASE_BLOCKS: VolAddressBlock<[u8; 0x4000]> = unsafe { VolAddressBlock::new_unchecked(VolAddress::new_unchecked(VRAM_BASE_USIZE), 6) };
|
||||
|
||||
/// The screen entry base blocks.
|
||||
pub const SCREEN_BASE_BLOCKS: VolAddressBlock<[u8; 0x800]> =
|
||||
unsafe { VolAddressBlock::new_unchecked(VolAddress::new_unchecked(VRAM_BASE_USIZE), 32) };
|
||||
|
||||
newtype! {
|
||||
/// An 8x8 tile with 4bpp, packed as `u32` values for proper alignment.
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
Tile4bpp, pub [u32; 8], no frills
|
||||
}
|
||||
|
||||
newtype! {
|
||||
/// An 8x8 tile with 8bpp, packed as `u32` values for proper alignment.
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
Tile8bpp, pub [u32; 16], no frills
|
||||
}
|
||||
|
||||
/// Gives the specified charblock in 4bpp view.
|
||||
pub fn get_4bpp_character_block(slot: usize) -> VolAddressBlock<Tile4bpp> {
|
||||
unsafe { VolAddressBlock::new_unchecked(CHAR_BASE_BLOCKS.index(slot).cast::<Tile4bpp>(), 512) }
|
||||
}
|
||||
|
||||
/// Gives the specified charblock in 8bpp view.
|
||||
pub fn get_8bpp_character_block(slot: usize) -> VolAddressBlock<Tile8bpp> {
|
||||
unsafe { VolAddressBlock::new_unchecked(CHAR_BASE_BLOCKS.index(slot).cast::<Tile8bpp>(), 256) }
|
||||
}
|
33
src/vram/affine.rs
Normal file
33
src/vram/affine.rs
Normal file
|
@ -0,0 +1,33 @@
|
|||
//! Module for affine things.
|
||||
|
||||
use super::*;
|
||||
|
||||
newtype! {
|
||||
/// A screenblock entry for use in Affine mode.
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
AffineScreenblockEntry, u8
|
||||
}
|
||||
|
||||
newtype! {
|
||||
/// A 16x16 screenblock for use in Affine mode.
|
||||
#[derive(Clone, Copy)]
|
||||
AffineScreenblock16x16, [AffineScreenblockEntry; 16*16], no frills
|
||||
}
|
||||
|
||||
newtype! {
|
||||
/// A 32x32 screenblock for use in Affine mode.
|
||||
#[derive(Clone, Copy)]
|
||||
AffineScreenblock32x32, [AffineScreenblockEntry; 32*32], no frills
|
||||
}
|
||||
|
||||
newtype! {
|
||||
/// A 64x64 screenblock for use in Affine mode.
|
||||
#[derive(Clone, Copy)]
|
||||
AffineScreenblock64x64, [AffineScreenblockEntry; 64*64], no frills
|
||||
}
|
||||
|
||||
newtype! {
|
||||
/// A 128x128 screenblock for use in Affine mode.
|
||||
#[derive(Clone, Copy)]
|
||||
AffineScreenblock128x128, [AffineScreenblockEntry; 128*128], no frills
|
||||
}
|
309
src/vram/bitmap.rs
Normal file
309
src/vram/bitmap.rs
Normal file
|
@ -0,0 +1,309 @@
|
|||
//! Module for the Bitmap video modes.
|
||||
|
||||
use super::*;
|
||||
|
||||
/// Mode 3 is a bitmap mode with full color and full resolution.
|
||||
///
|
||||
/// * **Width:** 240
|
||||
/// * **Height:** 160
|
||||
///
|
||||
/// Because the memory requirements are so large, there's only a single page
|
||||
/// available instead of two pages like the other video modes have.
|
||||
///
|
||||
/// As with all bitmap modes, the image itself utilizes BG2 for display, so you
|
||||
/// must have BG2 enabled in addition to being within Mode 3.
|
||||
pub struct Mode3;
|
||||
impl Mode3 {
|
||||
/// The physical width in pixels of the GBA screen.
|
||||
pub const SCREEN_WIDTH: usize = 240;
|
||||
|
||||
/// The physical height in pixels of the GBA screen.
|
||||
pub const SCREEN_HEIGHT: usize = 160;
|
||||
|
||||
/// The number of pixels on the screen.
|
||||
pub const SCREEN_PIXEL_COUNT: usize = Self::SCREEN_WIDTH * Self::SCREEN_HEIGHT;
|
||||
|
||||
/// The Mode 3 VRAM.
|
||||
///
|
||||
/// Use `col + row * SCREEN_WIDTH` to get the address of an individual pixel,
|
||||
/// or use the helpers provided in this module.
|
||||
pub const VRAM: VolAddressBlock<Color> =
|
||||
unsafe { VolAddressBlock::new_unchecked(VolAddress::new_unchecked(VRAM_BASE_USIZE), Self::SCREEN_PIXEL_COUNT) };
|
||||
|
||||
/// private iterator over the pixels, two at a time
|
||||
const BULK_ITER: VolAddressIter<u32> =
|
||||
unsafe { VolAddressBlock::new_unchecked(VolAddress::new_unchecked(VRAM_BASE_USIZE), Self::SCREEN_PIXEL_COUNT / 2).iter() };
|
||||
|
||||
/// Reads the pixel at the given (col,row).
|
||||
///
|
||||
/// # Failure
|
||||
///
|
||||
/// Gives `None` if out of bounds.
|
||||
pub fn read_pixel(col: usize, row: usize) -> Option<Color> {
|
||||
Self::VRAM.get(col + row * Self::SCREEN_WIDTH).map(VolAddress::read)
|
||||
}
|
||||
|
||||
/// Writes the pixel at the given (col,row).
|
||||
///
|
||||
/// # Failure
|
||||
///
|
||||
/// Gives `None` if out of bounds.
|
||||
pub fn write_pixel(col: usize, row: usize, color: Color) -> Option<()> {
|
||||
Self::VRAM.get(col + row * Self::SCREEN_WIDTH).map(|va| va.write(color))
|
||||
}
|
||||
|
||||
/// Clears the whole screen to the desired color.
|
||||
pub fn clear_to(color: Color) {
|
||||
let color32 = color.0 as u32;
|
||||
let bulk_color = color32 << 16 | color32;
|
||||
for va in Self::BULK_ITER {
|
||||
va.write(bulk_color)
|
||||
}
|
||||
}
|
||||
|
||||
/// Clears the whole screen to the desired color using DMA3.
|
||||
pub fn dma_clear_to(color: Color) {
|
||||
use crate::io::dma::DMA3;
|
||||
|
||||
let color32 = color.0 as u32;
|
||||
let bulk_color = color32 << 16 | color32;
|
||||
unsafe { DMA3::fill32(&bulk_color, VRAM_BASE_USIZE as *mut u32, (Self::SCREEN_PIXEL_COUNT / 2) as u16) };
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: Mode3 Iter Scanlines / Pixels?
|
||||
//TODO: Mode3 Line Drawing?
|
||||
|
||||
/// Mode 4 is a bitmap mode with 8bpp paletted color.
|
||||
///
|
||||
/// * **Width:** 240
|
||||
/// * **Height:** 160
|
||||
/// * **Pages:** 2
|
||||
///
|
||||
/// VRAM has a minimum write size of 2 bytes at a time, so writing individual
|
||||
/// palette entries for the pixels is more costly than with the other bitmap
|
||||
/// modes.
|
||||
///
|
||||
/// As with all bitmap modes, the image itself utilizes BG2 for display, so you
|
||||
/// must have BG2 enabled in addition to being within Mode 4.
|
||||
pub struct Mode4;
|
||||
impl Mode4 {
|
||||
/// The physical width in pixels of the GBA screen.
|
||||
pub const SCREEN_WIDTH: usize = 240;
|
||||
|
||||
/// The physical height in pixels of the GBA screen.
|
||||
pub const SCREEN_HEIGHT: usize = 160;
|
||||
|
||||
/// The number of pixels on the screen.
|
||||
pub const SCREEN_PIXEL_COUNT: usize = Self::SCREEN_WIDTH * Self::SCREEN_HEIGHT;
|
||||
|
||||
/// Used for bulk clearing operations.
|
||||
const SCREEN_U32_COUNT: usize = Self::SCREEN_PIXEL_COUNT / 4;
|
||||
|
||||
// TODO: newtype this?
|
||||
const PAGE0_BASE: VolAddress<u8> = unsafe { VolAddress::new_unchecked(VRAM_BASE_USIZE) };
|
||||
|
||||
// TODO: newtype this?
|
||||
const PAGE0_BLOCK: VolAddressBlock<u8> = unsafe { VolAddressBlock::new_unchecked(Self::PAGE0_BASE, Self::SCREEN_PIXEL_COUNT) };
|
||||
|
||||
// TODO: newtype this?
|
||||
const PAGE1_BASE: VolAddress<u8> = unsafe { VolAddress::new_unchecked(VRAM_BASE_USIZE + 0xA000) };
|
||||
|
||||
// TODO: newtype this?
|
||||
const PAGE1_BLOCK: VolAddressBlock<u8> = unsafe { VolAddressBlock::new_unchecked(Self::PAGE1_BASE, Self::SCREEN_PIXEL_COUNT) };
|
||||
|
||||
/// private iterator over the page0 pixels, four at a time
|
||||
const BULK_ITER0: VolAddressIter<u32> = unsafe { VolAddressBlock::new_unchecked(Self::PAGE0_BASE.cast::<u32>(), Self::SCREEN_U32_COUNT).iter() };
|
||||
|
||||
/// private iterator over the page1 pixels, four at a time
|
||||
const BULK_ITER1: VolAddressIter<u32> = unsafe { VolAddressBlock::new_unchecked(Self::PAGE1_BASE.cast::<u32>(), Self::SCREEN_U32_COUNT).iter() };
|
||||
|
||||
/// Reads the pixel at the given (col,row).
|
||||
///
|
||||
/// # Failure
|
||||
///
|
||||
/// Gives `None` if out of bounds.
|
||||
pub fn read_pixel(page1: bool, col: usize, row: usize) -> Option<u8> {
|
||||
// Note(Lokathor): byte _reads_ from VRAM are okay.
|
||||
if page1 {
|
||||
Self::PAGE1_BLOCK.get(col + row * Self::SCREEN_WIDTH).map(VolAddress::read)
|
||||
} else {
|
||||
Self::PAGE0_BLOCK.get(col + row * Self::SCREEN_WIDTH).map(VolAddress::read)
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes the pixel at the given (col,row).
|
||||
///
|
||||
/// # Failure
|
||||
///
|
||||
/// Gives `None` if out of bounds.
|
||||
pub fn write_pixel(page1: bool, col: usize, row: usize, pal8bpp: u8) -> Option<()> {
|
||||
// Note(Lokathor): byte _writes_ to VRAM are not permitted. We must jump
|
||||
// through hoops when we attempt to write just a single byte.
|
||||
if col < Self::SCREEN_WIDTH && row < Self::SCREEN_HEIGHT {
|
||||
let real_index = col + row * Self::SCREEN_WIDTH;
|
||||
let rounded_down_index = real_index & !1;
|
||||
let address: VolAddress<u16> = unsafe {
|
||||
if page1 {
|
||||
Self::PAGE1_BASE.offset(rounded_down_index as isize).cast()
|
||||
} else {
|
||||
Self::PAGE0_BASE.offset(rounded_down_index as isize).cast()
|
||||
}
|
||||
};
|
||||
if real_index == rounded_down_index {
|
||||
// even byte, change the high bits
|
||||
let old_val = address.read();
|
||||
address.write((old_val & 0xFF) | ((pal8bpp as u16) << 8));
|
||||
} else {
|
||||
// odd byte, change the low bits
|
||||
let old_val = address.read();
|
||||
address.write((old_val & 0xFF00) | pal8bpp as u16);
|
||||
}
|
||||
Some(())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes a "wide" pairing of palette entries to the location specified.
|
||||
///
|
||||
/// The page is imagined to be a series of `u16` values rather than `u8`
|
||||
/// values, allowing you to write two palette entries side by side as a single
|
||||
/// write operation.
|
||||
pub fn write_wide_pixel(page1: bool, wide_col: usize, row: usize, wide_pal8bpp: u16) -> Option<()> {
|
||||
if wide_col < Self::SCREEN_WIDTH / 2 && row < Self::SCREEN_HEIGHT {
|
||||
let wide_index = wide_col + row * Self::SCREEN_WIDTH / 2;
|
||||
let address: VolAddress<u16> = unsafe {
|
||||
if page1 {
|
||||
Self::PAGE1_BASE.cast::<u16>().offset(wide_index as isize)
|
||||
} else {
|
||||
Self::PAGE0_BASE.cast::<u16>().offset(wide_index as isize)
|
||||
}
|
||||
};
|
||||
Some(address.write(wide_pal8bpp))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Clears the page to the desired color.
|
||||
pub fn clear_page_to(page1: bool, pal8bpp: u8) {
|
||||
let pal8bpp_32 = pal8bpp as u32;
|
||||
let bulk_color = (pal8bpp_32 << 24) | (pal8bpp_32 << 16) | (pal8bpp_32 << 8) | pal8bpp_32;
|
||||
for va in if page1 { Self::BULK_ITER1 } else { Self::BULK_ITER0 } {
|
||||
va.write(bulk_color)
|
||||
}
|
||||
}
|
||||
|
||||
/// Clears the page to the desired color using DMA3.
|
||||
pub fn dma_clear_page_to(page1: bool, pal8bpp: u8) {
|
||||
use crate::io::dma::DMA3;
|
||||
|
||||
let pal8bpp_32 = pal8bpp as u32;
|
||||
let bulk_color = (pal8bpp_32 << 24) | (pal8bpp_32 << 16) | (pal8bpp_32 << 8) | pal8bpp_32;
|
||||
let write_target = if page1 {
|
||||
VRAM_BASE_USIZE as *mut u32
|
||||
} else {
|
||||
(VRAM_BASE_USIZE + 0xA000) as *mut u32
|
||||
};
|
||||
unsafe { DMA3::fill32(&bulk_color, write_target, Self::SCREEN_U32_COUNT as u16) };
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: Mode4 Iter Scanlines / Pixels?
|
||||
//TODO: Mode4 Line Drawing?
|
||||
|
||||
/// Mode 5 is a bitmap mode with full color and reduced resolution.
|
||||
///
|
||||
/// * **Width:** 160
|
||||
/// * **Height:** 128
|
||||
/// * **Pages:** 2
|
||||
///
|
||||
/// Because of the reduced resolution, we're allowed two pages for display.
|
||||
///
|
||||
/// As with all bitmap modes, the image itself utilizes BG2 for display, so you
|
||||
/// must have BG2 enabled in addition to being within Mode 3.
|
||||
pub struct Mode5;
|
||||
impl Mode5 {
|
||||
/// The physical width in pixels of the GBA screen.
|
||||
pub const SCREEN_WIDTH: usize = 160;
|
||||
|
||||
/// The physical height in pixels of the GBA screen.
|
||||
pub const SCREEN_HEIGHT: usize = 128;
|
||||
|
||||
/// The number of pixels on the screen.
|
||||
pub const SCREEN_PIXEL_COUNT: usize = Self::SCREEN_WIDTH * Self::SCREEN_HEIGHT;
|
||||
|
||||
/// Used for bulk clearing operations.
|
||||
const SCREEN_U32_COUNT: usize = Self::SCREEN_PIXEL_COUNT / 2;
|
||||
|
||||
// TODO: newtype this?
|
||||
const PAGE0_BASE: VolAddress<Color> = unsafe { VolAddress::new_unchecked(VRAM_BASE_USIZE) };
|
||||
|
||||
// TODO: newtype this?
|
||||
const PAGE0_BLOCK: VolAddressBlock<Color> = unsafe { VolAddressBlock::new_unchecked(Self::PAGE0_BASE, Self::SCREEN_PIXEL_COUNT) };
|
||||
|
||||
// TODO: newtype this?
|
||||
const PAGE1_BASE: VolAddress<Color> = unsafe { VolAddress::new_unchecked(VRAM_BASE_USIZE + 0xA000) };
|
||||
|
||||
// TODO: newtype this?
|
||||
const PAGE1_BLOCK: VolAddressBlock<Color> = unsafe { VolAddressBlock::new_unchecked(Self::PAGE1_BASE, Self::SCREEN_PIXEL_COUNT) };
|
||||
|
||||
/// private iterator over the page0 pixels, four at a time
|
||||
const BULK_ITER0: VolAddressIter<u32> = unsafe { VolAddressBlock::new_unchecked(Self::PAGE0_BASE.cast::<u32>(), Self::SCREEN_U32_COUNT).iter() };
|
||||
|
||||
/// private iterator over the page1 pixels, four at a time
|
||||
const BULK_ITER1: VolAddressIter<u32> = unsafe { VolAddressBlock::new_unchecked(Self::PAGE1_BASE.cast::<u32>(), Self::SCREEN_U32_COUNT).iter() };
|
||||
|
||||
/// Reads the pixel at the given (col,row).
|
||||
///
|
||||
/// # Failure
|
||||
///
|
||||
/// Gives `None` if out of bounds.
|
||||
pub fn read_pixel(page1: bool, col: usize, row: usize) -> Option<Color> {
|
||||
if page1 {
|
||||
Self::PAGE1_BLOCK.get(col + row * Self::SCREEN_WIDTH).map(VolAddress::read)
|
||||
} else {
|
||||
Self::PAGE0_BLOCK.get(col + row * Self::SCREEN_WIDTH).map(VolAddress::read)
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes the pixel at the given (col,row).
|
||||
///
|
||||
/// # Failure
|
||||
///
|
||||
/// Gives `None` if out of bounds.
|
||||
pub fn write_pixel(page1: bool, col: usize, row: usize, color: Color) -> Option<()> {
|
||||
if page1 {
|
||||
Self::PAGE1_BLOCK.get(col + row * Self::SCREEN_WIDTH).map(|va| va.write(color))
|
||||
} else {
|
||||
Self::PAGE0_BLOCK.get(col + row * Self::SCREEN_WIDTH).map(|va| va.write(color))
|
||||
}
|
||||
}
|
||||
|
||||
/// Clears the whole screen to the desired color.
|
||||
pub fn clear_page_to(page1: bool, color: Color) {
|
||||
let color32 = color.0 as u32;
|
||||
let bulk_color = color32 << 16 | color32;
|
||||
for va in if page1 { Self::BULK_ITER1 } else { Self::BULK_ITER0 } {
|
||||
va.write(bulk_color)
|
||||
}
|
||||
}
|
||||
|
||||
/// Clears the whole screen to the desired color using DMA3.
|
||||
pub fn dma_clear_page_to(page1: bool, color: Color) {
|
||||
use crate::io::dma::DMA3;
|
||||
|
||||
let color32 = color.0 as u32;
|
||||
let bulk_color = color32 << 16 | color32;
|
||||
let write_target = if page1 {
|
||||
VRAM_BASE_USIZE as *mut u32
|
||||
} else {
|
||||
(VRAM_BASE_USIZE + 0xA000) as *mut u32
|
||||
};
|
||||
unsafe { DMA3::fill32(&bulk_color, write_target, Self::SCREEN_U32_COUNT as u16) };
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: Mode5 Iter Scanlines / Pixels?
|
||||
//TODO: Mode5 Line Drawing?
|
30
src/vram/text.rs
Normal file
30
src/vram/text.rs
Normal file
|
@ -0,0 +1,30 @@
|
|||
//! Module for tiled mode types and operations.
|
||||
|
||||
use super::*;
|
||||
|
||||
newtype! {
|
||||
/// A screenblock entry for use in Text mode.
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
TextScreenblockEntry, u16
|
||||
}
|
||||
impl TextScreenblockEntry {
|
||||
/// Generates a default entry with the specified tile index.
|
||||
pub const fn from_tile_index(index: u16) -> Self {
|
||||
Self::new().with_tile_index(index)
|
||||
}
|
||||
|
||||
bool_bits!(u16, [(10, hflip), (11, vflip)]);
|
||||
|
||||
multi_bits!(u16, [(0, 10, tile_index), (12, 4, palbank)]);
|
||||
}
|
||||
|
||||
newtype! {
|
||||
/// A screenblock for use in Text mode.
|
||||
#[derive(Clone, Copy)]
|
||||
TextScreenblock, [TextScreenblockEntry; 32 * 32], no frills
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_text_screen_block_size() {
|
||||
assert_eq!(core::mem::size_of::<TextScreenblock>(), 0x800);
|
||||
}
|
Loading…
Reference in a new issue