Merge pull request #40 from rust-console/lokathor

Big Wintertime Happy Fun PR
This commit is contained in:
Lokathor 2018-12-29 00:06:30 -07:00 committed by GitHub
commit 8595ffbff0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 1479 additions and 565 deletions

View file

@ -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

View file

@ -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"

View file

@ -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.

View file

@ -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);
};
}
```

View file

@ -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).

View file

@ -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);

View file

@ -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));

View file

@ -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;

View file

@ -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));

View file

@ -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]

View file

@ -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)
}
}

View file

@ -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"
);
}
}
}

View file

@ -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
View 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) };

View file

@ -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),
]
);
}

View file

@ -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);

View file

@ -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
View 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,
}

View file

@ -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
View 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
View 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) }
}

View file

@ -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
View 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
View 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
View 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
View 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);
}