diff --git a/book/src/04-non-video/01-buttons.md b/book/src/04-non-video/01-buttons.md index 8eb4e80..d78abf5 100644 --- a/book/src/04-non-video/01-buttons.md +++ b/book/src/04-non-video/01-buttons.md @@ -3,3 +3,4 @@ It's all well and good to just show a picture, even to show an animation, but if we want a game we have to let the user interact with something. +TODO diff --git a/examples/hello_world.rs b/examples/hello_world.rs index 549569a..4258b12 100644 --- a/examples/hello_world.rs +++ b/examples/hello_world.rs @@ -31,21 +31,166 @@ macro_rules! const_rgb { }}; } -// TODO: kill this -#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] -#[repr(transparent)] -pub struct VolatilePtr(pub *mut T); -impl VolatilePtr { - pub unsafe fn read(&self) -> T { - core::ptr::read_volatile(self.0) +mod vol_address { + #![allow(unused)] + use core::{cmp::Ordering, iter::FusedIterator, marker::PhantomData, num::NonZeroUsize}; + /// VolAddress + #[derive(Debug)] + #[repr(transparent)] + pub struct VolAddress { + address: NonZeroUsize, + marker: PhantomData<*mut T>, } - pub unsafe fn write(&self, data: T) { - core::ptr::write_volatile(self.0, data); + impl Clone for VolAddress { + fn clone(&self) -> Self { + *self + } } - pub unsafe fn offset(self, count: isize) -> Self { - VolatilePtr(self.0.wrapping_offset(count)) + impl Copy for VolAddress {} + impl PartialEq for VolAddress { + fn eq(&self, other: &Self) -> bool { + self.address == other.address + } + } + impl Eq for VolAddress {} + impl PartialOrd for VolAddress { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.address.cmp(&other.address)) + } + } + impl Ord for VolAddress { + fn cmp(&self, other: &Self) -> Ordering { + self.address.cmp(&other.address) + } + } + impl VolAddress { + pub const unsafe fn new_unchecked(address: usize) -> Self { + VolAddress { + address: NonZeroUsize::new_unchecked(address), + marker: PhantomData, + } + } + pub const unsafe fn cast(self) -> VolAddress { + VolAddress { + address: self.address, + marker: PhantomData, + } + } + pub unsafe fn offset(self, offset: isize) -> Self { + // TODO: const this + VolAddress { + address: NonZeroUsize::new_unchecked(self.address.get().wrapping_add(offset as usize * core::mem::size_of::())), + marker: PhantomData, + } + } + pub fn is_aligned(self) -> bool { + // TODO: const this + self.address.get() % core::mem::align_of::() == 0 + } + pub const unsafe fn iter_slots(self, slots: usize) -> VolAddressIter { + VolAddressIter { vol_address: self, slots } + } + pub fn read(self) -> T + where + T: Copy, + { + unsafe { (self.address.get() as *mut T).read_volatile() } + } + pub unsafe fn read_non_copy(self) -> T { + (self.address.get() as *mut T).read_volatile() + } + pub fn write(self, val: T) { + unsafe { (self.address.get() as *mut T).write_volatile(val) } + } + } + /// VolAddressIter + #[derive(Debug)] + pub struct VolAddressIter { + vol_address: VolAddress, + slots: usize, + } + impl Clone for VolAddressIter { + fn clone(&self) -> Self { + VolAddressIter { + vol_address: self.vol_address, + slots: self.slots, + } + } + } + impl PartialEq for VolAddressIter { + fn eq(&self, other: &Self) -> bool { + self.vol_address == other.vol_address && self.slots == other.slots + } + } + impl Eq for VolAddressIter {} + impl Iterator for VolAddressIter { + type Item = VolAddress; + + fn next(&mut self) -> Option { + if self.slots > 0 { + let out = self.vol_address; + unsafe { + self.slots -= 1; + self.vol_address = self.vol_address.offset(1); + } + Some(out) + } else { + None + } + } + } + impl FusedIterator for VolAddressIter {} + /// VolAddressBlock + #[derive(Debug)] + pub struct VolAddressBlock { + vol_address: VolAddress, + slots: usize, + } + impl Clone for VolAddressBlock { + fn clone(&self) -> Self { + VolAddressBlock { + vol_address: self.vol_address, + slots: self.slots, + } + } + } + impl PartialEq for VolAddressBlock { + fn eq(&self, other: &Self) -> bool { + self.vol_address == other.vol_address && self.slots == other.slots + } + } + impl Eq for VolAddressBlock {} + impl VolAddressBlock { + pub const unsafe fn new_unchecked(vol_address: VolAddress, slots: usize) -> Self { + VolAddressBlock { vol_address, slots } + } + pub const fn iter(self) -> VolAddressIter { + VolAddressIter { + vol_address: self.vol_address, + slots: self.slots, + } + } + pub unsafe fn index_unchecked(self, slot: usize) -> VolAddress { + // TODO: const this + self.vol_address.offset(slot as isize) + } + pub fn index(self, slot: usize) -> VolAddress { + if slot < self.slots { + unsafe { self.vol_address.offset(slot as isize) } + } else { + panic!("Index Requested: {} >= Bound: {}", slot, self.slots) + } + } + pub fn get(self, slot: usize) -> Option> { + if slot < self.slots { + unsafe { Some(self.vol_address.offset(slot as isize)) } + } else { + None + } + } } } +use self::vol_address::*; newtype! { #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -64,7 +209,7 @@ newtype! { DisplayControlSetting, u16 } -pub const DISPLAY_CONTROL: VolatilePtr = VolatilePtr(0x0400_0000 as *mut DisplayControlSetting); +pub const DISPLAY_CONTROL: VolAddress = unsafe { VolAddress::new_unchecked(0x0400_0000) }; pub const JUST_MODE3: DisplayControlSetting = DisplayControlSetting(3); pub const JUST_BG2: DisplayControlSetting = DisplayControlSetting(0b100_0000_0000); pub const JUST_MODE3_AND_BG2: DisplayControlSetting = DisplayControlSetting(JUST_MODE3.0 | JUST_BG2.0); @@ -72,10 +217,12 @@ pub const JUST_MODE3_AND_BG2: DisplayControlSetting = DisplayControlSetting(JUST pub struct Mode3; impl Mode3 { const SCREEN_WIDTH: isize = 240; - const PIXELS: VolatilePtr = VolatilePtr(0x600_0000 as *mut Color); + const SCREEN_HEIGHT: isize = 160; + const PIXELS: VolAddressBlock = + unsafe { VolAddressBlock::new_unchecked(VolAddress::new_unchecked(0x600_0000), (Self::SCREEN_WIDTH * Self::SCREEN_HEIGHT) as usize) }; - pub unsafe fn draw_pixel_unchecked(col: isize, row: isize, color: Color) { - Self::PIXELS.offset(col + row * Self::SCREEN_WIDTH).write(color); + pub unsafe fn draw_pixel(col: usize, row: usize, color: Color) { + Self::PIXELS.index(col + row * Self::SCREEN_WIDTH as usize).write(color); } } @@ -88,9 +235,9 @@ fn panic(_info: &core::panic::PanicInfo) -> ! { fn main(_argc: isize, _argv: *const *const u8) -> isize { unsafe { DISPLAY_CONTROL.write(JUST_MODE3_AND_BG2); - Mode3::draw_pixel_unchecked(120, 80, const_rgb!(31, 0, 0)); - Mode3::draw_pixel_unchecked(136, 80, const_rgb!(0, 31, 0)); - Mode3::draw_pixel_unchecked(120, 96, const_rgb!(0, 0, 31)); + Mode3::draw_pixel(120, 80, const_rgb!(31, 0, 0)); + Mode3::draw_pixel(136, 80, const_rgb!(0, 31, 0)); + Mode3::draw_pixel(120, 96, const_rgb!(0, 0, 31)); loop {} } } diff --git a/src/base.rs b/src/base.rs new file mode 100644 index 0000000..4c7b530 --- /dev/null +++ b/src/base.rs @@ -0,0 +1,10 @@ +//! Holds fundamental types/ops which the rest of the crate it built on. + +pub mod fixed_point; +//pub(crate) use self::fixed_point::*; + +pub mod volatile; +pub(crate) use self::volatile::*; + +pub mod builtins; +//pub(crate) use self::builtins::*; diff --git a/src/builtins.rs b/src/base/builtins.rs similarity index 100% rename from src/builtins.rs rename to src/base/builtins.rs diff --git a/src/fixed.rs b/src/base/fixed_point.rs similarity index 100% rename from src/fixed.rs rename to src/base/fixed_point.rs diff --git a/src/core_extras.rs b/src/base/volatile.rs similarity index 98% rename from src/core_extras.rs rename to src/base/volatile.rs index 679b42f..5e12e28 100644 --- a/src/core_extras.rs +++ b/src/base/volatile.rs @@ -1,6 +1,4 @@ -//! Things that I wish were in core, but aren't. - -//TODO(Lokathor): reorganize as gba::core_extras::fixed_point and gba::core_extras::volatile ? +//! Holds types for correct handling of volatile memory. use core::{cmp::Ordering, iter::FusedIterator, marker::PhantomData, num::NonZeroUsize}; diff --git a/src/io_registers.rs b/src/io_registers.rs deleted file mode 100644 index 4ba89dc..0000000 --- a/src/io_registers.rs +++ /dev/null @@ -1,126 +0,0 @@ -//! The module for all things relating to the IO Register portion of the GBA's -//! memory map. -//! -//! Here we define many constants for the volatile pointers to the various IO -//! registers. Each raw register constant is named according to the name given -//! to it in GBATEK's [GBA I/O -//! Map](http://problemkaputt.de/gbatek.htm#gbaiomap). They program in C, and so -//! of course all the names terrible and missing as many vowels as possible. -//! However, being able to look it up online is the most important thing here, -//! so oh well. -//! -//! In addition to the const `VolatilePtr` values, we will over time be adding -//! safe wrappers around each register, including newtypes and such so that you -//! can easily work with whatever each specific register is doing. - -// TODO(lokathor): IO Register newtypes. - -use gba_proc_macro::register_bit; - -use super::*; - -/// LCD Control. Read/Write. -/// -/// * [gbatek entry](http://problemkaputt.de/gbatek.htm#lcdiodisplaycontrol) -pub const DISPCNT: VolAddress = unsafe { VolAddress::new_unchecked(0x400_0000) }; - -newtype!( - /// A newtype over the various display control options that you have on a GBA. - #[derive(Debug, Copy, Clone, Default, PartialEq, Eq)] - DisplayControlSetting, - u16 -); - -#[allow(missing_docs)] -impl DisplayControlSetting { - pub const BG_MODE_MASK: u16 = 0b111; - - pub fn mode(self) -> DisplayControlMode { - 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 fn set_mode(&mut self, new_mode: DisplayControlMode) { - self.0 &= !Self::BG_MODE_MASK; - self.0 |= match new_mode { - DisplayControlMode::Tiled0 => 0, - DisplayControlMode::Tiled1 => 1, - DisplayControlMode::Tiled2 => 2, - DisplayControlMode::Bitmap3 => 3, - DisplayControlMode::Bitmap4 => 4, - DisplayControlMode::Bitmap5 => 5, - }; - } - - 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); -} - -/// The six display modes available on the GBA. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -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, - /// This is a mix of `Tile0` and `Tile2`: BG0 and BG1 run as if in `Tiled0`, - /// and BG2 runs as if in `Tiled2`. - Tiled1, - /// This allows affine transformations, but only uses BG2 and BG3. - Tiled2, - /// This is the basic bitmap draw mode. The whole screen is a single bitmap. - /// Uses BG2 only. - Bitmap3, - /// This uses _paletted color_ so that there's enough space to have two pages - /// at _full resolution_, allowing page flipping. Uses BG2 only. - Bitmap4, - /// This uses _reduced resolution_ so that there's enough space to have two - /// pages with _full color_, allowing page flipping. Uses BG2 only. - Bitmap5, -} - -/// Assigns the given display control setting. -pub fn set_display_control(setting: DisplayControlSetting) { - DISPCNT.write(setting); -} -/// Obtains the current display control setting. -pub fn display_control() -> DisplayControlSetting { - DISPCNT.read() -} - -/// Vertical Counter (LY) -pub const VCOUNT: VolAddress = unsafe { VolAddress::new_unchecked(0x400_0006) }; - -/// Obtains the current VCount value. -pub fn vcount() -> u16 { - VCOUNT.read() -} - -/// Performs a busy loop until VBlank starts. -pub fn wait_until_vblank() { - // TODO: make this the better version with BIOS and interrupts and such. - while vcount() < SCREEN_HEIGHT as u16 {} -} - -/// Performs a busy loop until VDraw starts. -pub fn wait_until_vdraw() { - // TODO: make this the better version with BIOS and interrupts and such. - while vcount() >= SCREEN_HEIGHT as u16 {} -} diff --git a/src/lib.rs b/src/lib.rs index be313d4..ce9c46a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -57,15 +57,9 @@ macro_rules! newtype { }; } -pub mod builtins; - -pub mod fixed; - +pub mod base; +pub(crate) use self::base::*; pub mod bios; - -pub mod core_extras; -pub(crate) use crate::core_extras::*; - pub mod io; pub mod video_ram; @@ -226,3 +220,111 @@ mod tests { } } */ + +use gba_proc_macro::register_bit; + +/// LCD Control. Read/Write. +/// +/// * [gbatek entry](http://problemkaputt.de/gbatek.htm#lcdiodisplaycontrol) +pub const DISPCNT: VolAddress = unsafe { VolAddress::new_unchecked(0x400_0000) }; + +newtype!( + /// A newtype over the various display control options that you have on a GBA. + #[derive(Debug, Copy, Clone, Default, PartialEq, Eq)] + DisplayControlSetting, + u16 +); + +#[allow(missing_docs)] +impl DisplayControlSetting { + pub const BG_MODE_MASK: u16 = 0b111; + + pub fn mode(self) -> DisplayControlMode { + 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 fn set_mode(&mut self, new_mode: DisplayControlMode) { + self.0 &= !Self::BG_MODE_MASK; + self.0 |= match new_mode { + DisplayControlMode::Tiled0 => 0, + DisplayControlMode::Tiled1 => 1, + DisplayControlMode::Tiled2 => 2, + DisplayControlMode::Bitmap3 => 3, + DisplayControlMode::Bitmap4 => 4, + DisplayControlMode::Bitmap5 => 5, + }; + } + + 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); +} + +/// The six display modes available on the GBA. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +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, + /// This is a mix of `Tile0` and `Tile2`: BG0 and BG1 run as if in `Tiled0`, + /// and BG2 runs as if in `Tiled2`. + Tiled1, + /// This allows affine transformations, but only uses BG2 and BG3. + Tiled2, + /// This is the basic bitmap draw mode. The whole screen is a single bitmap. + /// Uses BG2 only. + Bitmap3, + /// This uses _paletted color_ so that there's enough space to have two pages + /// at _full resolution_, allowing page flipping. Uses BG2 only. + Bitmap4, + /// This uses _reduced resolution_ so that there's enough space to have two + /// pages with _full color_, allowing page flipping. Uses BG2 only. + Bitmap5, +} + +/// Assigns the given display control setting. +pub fn set_display_control(setting: DisplayControlSetting) { + DISPCNT.write(setting); +} +/// Obtains the current display control setting. +pub fn display_control() -> DisplayControlSetting { + DISPCNT.read() +} + +/// Vertical Counter (LY) +pub const VCOUNT: VolAddress = unsafe { VolAddress::new_unchecked(0x400_0006) }; + +/// Obtains the current VCount value. +pub fn vcount() -> u16 { + VCOUNT.read() +} + +/// Performs a busy loop until VBlank starts. +pub fn wait_until_vblank() { + // TODO: make this the better version with BIOS and interrupts and such. + while vcount() < crate::video_ram::SCREEN_HEIGHT as u16 {} +} + +/// Performs a busy loop until VDraw starts. +pub fn wait_until_vdraw() { + // TODO: make this the better version with BIOS and interrupts and such. + while vcount() >= crate::video_ram::SCREEN_HEIGHT as u16 {} +}