diff --git a/Cargo.toml b/Cargo.toml index 400c908..d3d18c9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ publish = false [dependencies] typenum = "1.10" -gba-proc-macro = "0.2.1" +gba-proc-macro = "0.3" #[dev-dependencies] #quickcheck="0.7" diff --git a/examples/hello_world.rs b/examples/hello_world.rs new file mode 100644 index 0000000..e1abae3 --- /dev/null +++ b/examples/hello_world.rs @@ -0,0 +1,23 @@ +#![no_std] +#![feature(start)] + +use gba::{ + io::display::{DisplayControlMode, DisplayControlSetting, DISPCNT}, + video::Mode3, + Color, +}; + +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + loop {} +} + +#[start] +fn main(_argc: isize, _argv: *const *const u8) -> isize { + const SETTING: DisplayControlSetting = DisplayControlSetting::new().with_mode(DisplayControlMode::Bitmap3).with_display_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)); + Mode3::write_pixel(120, 96, Color::from_rgb(0, 0, 31)); + loop {} +} diff --git a/src/io.rs b/src/io.rs index 5149b5a..c0f5424 100644 --- a/src/io.rs +++ b/src/io.rs @@ -10,4 +10,5 @@ use super::*; use gba_proc_macro::register_bit; +pub mod display; pub mod keypad; diff --git a/src/io/display.rs b/src/io/display.rs new file mode 100644 index 0000000..e3ada94 --- /dev/null +++ b/src/io/display.rs @@ -0,0 +1,110 @@ +//! Contains types and definitions for display related IO registers. + +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 { + // 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); +} + +/// 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, +} + +/// 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() +} + +/// If the `VCOUNT` register reads equal to or above this then you're in vblank. +pub const VBLANK_SCANLINE: u16 = 160; + +/// Vertical Counter (LY). +/// +/// Gives the current scanline that the display controller is working on. If +/// this is at or above the `VBLANK_SCANLINE` value then the display controller +/// is in a "vertical blank" period. +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 spin_until_vblank() { + // TODO: make this the better version with BIOS and interrupts and such. + while vcount() < VBLANK_SCANLINE {} +} + +/// Performs a busy loop until VDraw starts. +pub fn spin_until_vdraw() { + // TODO: make this the better version with BIOS and interrupts and such. + while vcount() >= VBLANK_SCANLINE {} +} diff --git a/src/lib.rs b/src/lib.rs index ce9c46a..aa57ef1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -62,7 +62,27 @@ pub(crate) use self::base::*; pub mod bios; pub mod io; -pub mod video_ram; +pub mod video; + +newtype! { + /// A color on the GBA is an RGB 5.5.5 within a `u16` + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] + Color, u16 +} + +impl Color { + /// Constructs a color from the channel values provided (should be 0..=31). + /// + /// No actual checks are performed, so illegal channel values can overflow + /// into each other and produce an unintended color. + pub const fn from_rgb(r: u16, g: u16, b: u16) -> Color { + Color(b << 10 | g << 5 | r) + } +} + +// +// After here is totally unsorted nonsense +// /// Performs unsigned divide and remainder, gives None if dividing by 0. pub fn divrem_u32(numer: u32, denom: u32) -> Option<(u32, u32)> { @@ -220,111 +240,3 @@ 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 {} -} diff --git a/src/video.rs b/src/video.rs new file mode 100644 index 0000000..d6bb6c7 --- /dev/null +++ b/src/video.rs @@ -0,0 +1,83 @@ +//! 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 = + unsafe { VolAddressBlock::new_unchecked(VolAddress::new_unchecked(VRAM_BASE_USIZE), Self::SCREEN_WIDTH * Self::SCREEN_HEIGHT) }; + + /// private iterator over the pixels, two at a time + const BULK_ITER: VolAddressIter = + unsafe { VolAddressBlock::new_unchecked(VolAddress::new_unchecked(VRAM_BASE_USIZE), Self::SCREEN_WIDTH * Self::SCREEN_HEIGHT / 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 { + 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) + } + } + + // TODO: dma_clear_to? +} diff --git a/src/video_ram.rs b/src/video_ram.rs deleted file mode 100644 index 232249f..0000000 --- a/src/video_ram.rs +++ /dev/null @@ -1,83 +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::*; - -// TODO: kill all this too - -/// The physical width in pixels of the GBA screen. -pub const SCREEN_WIDTH: isize = 240; - -/// The physical height in pixels of the GBA screen. -pub const SCREEN_HEIGHT: isize = 160; - -/// 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`. -pub const VRAM_BASE_ADDRESS: usize = 0x0600_0000; - -const MODE3_VRAM: VolAddress = unsafe { VolAddress::new_unchecked(VRAM_BASE_ADDRESS) }; - -/// Draws a pixel to the screen while in Display Mode 3, with bounds checks. -/// -/// # Panics -/// -/// If `col` or `row` are out of bounds this will panic. -pub fn mode3_draw_pixel(col: isize, row: isize, color: u16) { - assert!(col >= 0 && col < SCREEN_WIDTH); - assert!(row >= 0 && row < SCREEN_HEIGHT); - unsafe { mode3_draw_pixel_unchecked(col, row, color) } -} - -/// Draws a pixel to the screen while in Display Mode 3. -/// -/// Coordinates are relative to the top left corner. -/// -/// If you're in another mode you'll get something weird drawn, but it's memory -/// safe in the rust sense as long as you stay in bounds. -/// -/// # Safety -/// -/// * `col` must be in `0..SCREEN_WIDTH` -/// * `row` must be in `0..SCREEN_HEIGHT` -pub unsafe fn mode3_draw_pixel_unchecked(col: isize, row: isize, color: u16) { - MODE3_VRAM.offset(col + row * SCREEN_WIDTH).write(color); -} - -/// Reads the given pixel of video memory according to Mode 3 placement. -/// -/// # Failure -/// -/// If the location is out of bounds you get `None`. -pub fn mode3_read_pixel(col: isize, row: isize) -> Option { - if col >= 0 && col < SCREEN_WIDTH && row >= 0 && row < SCREEN_HEIGHT { - unsafe { Some(MODE3_VRAM.offset(col + row * SCREEN_WIDTH).read()) } - } else { - None - } -} - -/// Clears the entire screen to the color specified. -pub unsafe fn mode3_clear_screen(color: u16) { - // TODO: use DMA? - let color = color as u32; - let bulk_color = color << 16 | color; - let block: VolAddressBlock = VolAddressBlock::new_unchecked(MODE3_VRAM.cast::(), (SCREEN_HEIGHT * SCREEN_WIDTH / 2) as usize); - for b in block.iter() { - b.write(bulk_color); - } -}