diff --git a/examples/hello_world.rs b/examples/hello_world.rs index e1abae3..1f5a360 100644 --- a/examples/hello_world.rs +++ b/examples/hello_world.rs @@ -1,5 +1,6 @@ #![no_std] #![feature(start)] +#![forbid(unsafe_code)] use gba::{ io::display::{DisplayControlMode, DisplayControlSetting, DISPCNT}, diff --git a/examples/light_cycle.rs b/examples/light_cycle.rs index c593c19..ee367f9 100644 --- a/examples/light_cycle.rs +++ b/examples/light_cycle.rs @@ -1,5 +1,15 @@ #![no_std] #![feature(start)] +#![forbid(unsafe_code)] + +use gba::{ + io::{ + display::{spin_until_vblank, spin_until_vdraw, DisplayControlMode, DisplayControlSetting, DISPCNT}, + keypad::read_key_input, + }, + video::Mode3, + Color, +}; #[panic_handler] fn panic(_info: &core::panic::PanicInfo) -> ! { @@ -8,168 +18,44 @@ fn panic(_info: &core::panic::PanicInfo) -> ! { #[start] fn main(_argc: isize, _argv: *const *const u8) -> isize { - unsafe { - DISPCNT.write(MODE3 | BG2); - } + const SETTING: DisplayControlSetting = DisplayControlSetting::new().with_mode(DisplayControlMode::Bitmap3).with_display_bg2(true); + DISPCNT.write(SETTING); - let mut px = SCREEN_WIDTH / 2; - let mut py = SCREEN_HEIGHT / 2; - let mut color = rgb16(31, 0, 0); + let mut px = Mode3::SCREEN_WIDTH / 2; + let mut py = Mode3::SCREEN_HEIGHT / 2; + let mut color = Color::from_rgb(31, 0, 0); loop { // read the input for this frame - let this_frame_keys = key_input(); + let this_frame_keys = read_key_input(); // adjust game state and wait for vblank - px += 2 * this_frame_keys.column_direction() as isize; - py += 2 * this_frame_keys.row_direction() as isize; - wait_until_vblank(); + px = px.wrapping_add(2 * this_frame_keys.column_direction() as usize); + py = py.wrapping_add(2 * this_frame_keys.row_direction() as usize); + spin_until_vblank(); // draw the new game and wait until the next frame starts. - unsafe { - if px < 0 || py < 0 || px == SCREEN_WIDTH || py == SCREEN_HEIGHT { - // out of bounds, reset the screen and position. - mode3_clear_screen(0); + const BLACK: Color = Color::from_rgb(0, 0, 0); + if px >= Mode3::SCREEN_WIDTH || py >= Mode3::SCREEN_HEIGHT { + // out of bounds, reset the screen and position. + Mode3::clear_to(BLACK); + color = color.rotate_left(5); + px = Mode3::SCREEN_WIDTH / 2; + py = Mode3::SCREEN_HEIGHT / 2; + } else { + let color_here = Mode3::read_pixel(px, py); + if color_here != Some(BLACK) { + // crashed into our own line, reset the screen + Mode3::clear_to(BLACK); color = color.rotate_left(5); - px = SCREEN_WIDTH / 2; - py = SCREEN_HEIGHT / 2; } else { - let color_here = mode3_read_pixel(px, py); - if color_here != 0 { - // crashed into our own line, reset the screen - mode3_clear_screen(0); - color = color.rotate_left(5); - } else { - // draw the new part of the line - mode3_draw_pixel(px, py, color); - mode3_draw_pixel(px, py + 1, color); - mode3_draw_pixel(px + 1, py, color); - mode3_draw_pixel(px + 1, py + 1, color); - } + // draw the new part of the line + Mode3::write_pixel(px, py, color); + Mode3::write_pixel(px, py + 1, color); + Mode3::write_pixel(px + 1, py, color); + Mode3::write_pixel(px + 1, py + 1, color); } } - wait_until_vdraw(); + spin_until_vdraw(); } } - -#[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) - } - pub unsafe fn write(&self, data: T) { - core::ptr::write_volatile(self.0, data); - } - pub unsafe fn offset(self, count: isize) -> Self { - VolatilePtr(self.0.wrapping_offset(count)) - } -} - -pub const DISPCNT: VolatilePtr = VolatilePtr(0x04000000 as *mut u16); -pub const MODE3: u16 = 3; -pub const BG2: u16 = 0b100_0000_0000; - -pub const VRAM: usize = 0x06000000; -pub const SCREEN_WIDTH: isize = 240; -pub const SCREEN_HEIGHT: isize = 160; - -pub const fn rgb16(red: u16, green: u16, blue: u16) -> u16 { - blue << 10 | green << 5 | red -} - -pub unsafe fn mode3_clear_screen(color: u16) { - let color = color as u32; - let bulk_color = color << 16 | color; - let mut ptr = VolatilePtr(VRAM as *mut u32); - for _ in 0..SCREEN_HEIGHT { - for _ in 0..(SCREEN_WIDTH / 2) { - ptr.write(bulk_color); - ptr = ptr.offset(1); - } - } -} - -pub unsafe fn mode3_draw_pixel(col: isize, row: isize, color: u16) { - VolatilePtr(VRAM as *mut u16).offset(col + row * SCREEN_WIDTH).write(color); -} - -pub unsafe fn mode3_read_pixel(col: isize, row: isize) -> u16 { - VolatilePtr(VRAM as *mut u16).offset(col + row * SCREEN_WIDTH).read() -} - -pub const KEYINPUT: VolatilePtr = VolatilePtr(0x400_0130 as *mut u16); - -/// A newtype over the key input state of the GBA. -#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] -#[repr(transparent)] -pub struct KeyInputSetting(u16); - -/// A "tribool" value helps us interpret the arrow pad. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[repr(i32)] -pub enum TriBool { - Minus = -1, - Neutral = 0, - Plus = 1, -} - -pub fn key_input() -> KeyInputSetting { - unsafe { KeyInputSetting(KEYINPUT.read() ^ 0b0000_0011_1111_1111) } -} - -pub const KEY_A: u16 = 1 << 0; -pub const KEY_B: u16 = 1 << 1; -pub const KEY_SELECT: u16 = 1 << 2; -pub const KEY_START: u16 = 1 << 3; -pub const KEY_RIGHT: u16 = 1 << 4; -pub const KEY_LEFT: u16 = 1 << 5; -pub const KEY_UP: u16 = 1 << 6; -pub const KEY_DOWN: u16 = 1 << 7; -pub const KEY_R: u16 = 1 << 8; -pub const KEY_L: u16 = 1 << 9; - -impl KeyInputSetting { - pub fn contains(&self, key: u16) -> bool { - (self.0 & key) != 0 - } - - pub fn difference(&self, other: KeyInputSetting) -> KeyInputSetting { - KeyInputSetting(self.0 ^ other.0) - } - - pub fn column_direction(&self) -> TriBool { - if self.contains(KEY_RIGHT) { - TriBool::Plus - } else if self.contains(KEY_LEFT) { - TriBool::Minus - } else { - TriBool::Neutral - } - } - - pub fn row_direction(&self) -> TriBool { - if self.contains(KEY_DOWN) { - TriBool::Plus - } else if self.contains(KEY_UP) { - TriBool::Minus - } else { - TriBool::Neutral - } - } -} - -pub const VCOUNT: VolatilePtr = VolatilePtr(0x0400_0006 as *mut u16); - -pub fn vcount() -> u16 { - unsafe { VCOUNT.read() } -} - -pub fn wait_until_vblank() { - while vcount() < SCREEN_HEIGHT as u16 {} -} - -pub fn wait_until_vdraw() { - while vcount() >= SCREEN_HEIGHT as u16 {} -} diff --git a/src/lib.rs b/src/lib.rs index aa57ef1..35d464c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,7 @@ #![cfg_attr(not(test), no_std)] #![feature(asm)] #![feature(const_int_wrapping)] +#![feature(const_int_rotate)] #![feature(min_const_unsafe_fn)] #![warn(missing_docs)] #![allow(clippy::cast_lossless)] @@ -78,6 +79,13 @@ impl Color { pub const fn from_rgb(r: u16, g: u16, b: u16) -> Color { Color(b << 10 | g << 5 | r) } + + /// Does a left rotate of the bits. + /// + /// This has no particular meaning but is a wild way to cycle colors. + pub const fn rotate_left(self, n: u32) -> Color { + Color(self.0.rotate_left(n)) + } } //