From 6ba28c5347b1a0fb2ddf8d6780e741b39e28c79e Mon Sep 17 00:00:00 2001 From: lokathor Date: Mon, 28 Nov 2022 16:33:47 -0700 Subject: [PATCH] we can make a little cyan guy show up on the screen. --- examples/foo.txt | 2 +- examples/game.rs | 47 ++++++++++++++++++++++++++++++++++ examples/hello.rs | 2 +- examples/instruction_inline.rs | 1 - src/asm_runtime.rs | 12 +++++++++ src/bios.rs | 1 + src/fixed.rs | 2 ++ src/gba_cell.rs | 1 + src/lib.rs | 17 +++++++----- src/mgba.rs | 3 +++ src/mmio.rs | 11 +++++--- src/video/mod.rs | 29 +++++++++++++++++++++ 12 files changed, 114 insertions(+), 14 deletions(-) create mode 100644 examples/game.rs diff --git a/examples/foo.txt b/examples/foo.txt index c246d3c..1910281 100644 --- a/examples/foo.txt +++ b/examples/foo.txt @@ -1 +1 @@ -foo_ \ No newline at end of file +foo \ No newline at end of file diff --git a/examples/game.rs b/examples/game.rs new file mode 100644 index 0000000..e1f7e07 --- /dev/null +++ b/examples/game.rs @@ -0,0 +1,47 @@ +#![no_std] +#![no_main] + +use core::fmt::Write; +use gba::prelude::*; + +#[panic_handler] +fn panic_handler(info: &core::panic::PanicInfo) -> ! { + if let Ok(mut logger) = MgbaBufferedLogger::try_new(MgbaMessageLevel::Fatal) { + writeln!(logger, "{info}").ok(); + } + loop {} +} + +#[no_mangle] +extern "C" fn main() -> ! { + DISPSTAT.write(DisplayStatus::new().with_irq_vblank(true)); + IE.write(IrqBits::VBLANK); + IME.write(true); + + TIMER0_CONTROL.write(TimerControl::new().with_enabled(true)); + + Cga8x8Thick.bitunpack_4bpp(CHARBLOCK0_4BPP.as_region(), 0); + Cga8x8Thick.bitunpack_4bpp(OBJ_TILES.as_region(), 0); + BG_PALETTE.index(1).write(Color::MAGENTA); + OBJ_PALETTE.index(1).write(Color::CYAN); + + let no_display = ObjAttr0::new().with_style(ObjDisplayStyle::NotDisplayed); + OBJ_ATTR0.iter().for_each(|va| va.write(no_display)); + + let mut obj = ObjAttr::new(); + obj.set_x(13); + obj.set_y(37); + obj.set_tile_id(1); + OBJ_ATTR_ALL.index(0).write(obj); + + DISPCNT.write(DisplayControl::new().with_show_obj(true)); + + loop { + VBlankIntrWait(); + let keys = KEYINPUT.read(); + if keys.a() { + let t = TIMER0_COUNT.read(); + BACKDROP_COLOR.write(Color(t)); + } + } +} diff --git a/examples/hello.rs b/examples/hello.rs index 11a00c3..1a94d13 100644 --- a/examples/hello.rs +++ b/examples/hello.rs @@ -13,7 +13,7 @@ fn panic_handler(info: &core::panic::PanicInfo) -> ! { } #[allow(dead_code)] -const FOO_: Align4<[u8; 4]> = include_aligned_bytes!("foo.txt"); +const FOO: Align4<[u8; 3]> = include_aligned_bytes!("foo.txt"); #[link_section = ".ewram"] static FRAME_KEYS: GbaCell = GbaCell::new(KeyInput::new()); diff --git a/examples/instruction_inline.rs b/examples/instruction_inline.rs index 9ae4bf8..94577bc 100644 --- a/examples/instruction_inline.rs +++ b/examples/instruction_inline.rs @@ -1,6 +1,5 @@ #![no_std] #![no_main] -#![feature(isa_attribute)] use gba::prelude::*; diff --git a/src/asm_runtime.rs b/src/asm_runtime.rs index dd9cffe..921a92a 100644 --- a/src/asm_runtime.rs +++ b/src/asm_runtime.rs @@ -610,6 +610,11 @@ unsafe extern "C" fn __aeabi_uwrite8(value: u64, address: *mut c_void) { /// function when possible. /// /// * **Returns:** the original `dest` pointer. +/// +/// ## Safety +/// * `src` must be readable for `byte_count` bytes. +/// * `dest` must be writable for `byte_count` bytes. +/// * The `src` and `dest` regions must not overlap. #[inline] #[no_mangle] pub unsafe extern "C" fn memcpy( @@ -626,6 +631,10 @@ pub unsafe extern "C" fn memcpy( /// function when possible. /// /// * **Returns:** the original `dest` pointer. +/// +/// ## Safety +/// * `src` must be readable for `byte_count` bytes. +/// * `dest` must be writable for `byte_count` bytes. #[inline] #[no_mangle] pub unsafe extern "C" fn memmove( @@ -644,6 +653,9 @@ pub unsafe extern "C" fn memmove( /// it up like might happen in C. /// /// * **Returns:** the original `dest` pointer. +/// +/// ## Safety +/// * `dest` must be writable for `byte_count` bytes. #[inline] #[no_mangle] pub unsafe extern "C" fn memset( diff --git a/src/bios.rs b/src/bios.rs index 0980bdd..0690512 100644 --- a/src/bios.rs +++ b/src/bios.rs @@ -1,4 +1,5 @@ #![allow(non_snake_case)] +#![allow(clippy::missing_safety_doc)] //! The GBA's BIOS provides limited built-in utility functions. //! diff --git a/src/fixed.rs b/src/fixed.rs index 66d1c05..3250c70 100644 --- a/src/fixed.rs +++ b/src/fixed.rs @@ -262,6 +262,7 @@ macro_rules! impl_signed_fixed_ops { } impl_trait_op_unit!($t, Neg, neg); impl core::fmt::Debug for Fixed<$t, B> { + #[inline] fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { let whole: $t = self.trunc().into_raw() >> B; let fract: $t = self.fract().into_raw(); @@ -315,6 +316,7 @@ macro_rules! impl_unsigned_fixed_ops { } } impl core::fmt::Debug for Fixed<$t, B> { + #[inline] fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { let whole: $t = self.trunc().into_raw() >> B; let fract: $t = self.fract().into_raw(); diff --git a/src/gba_cell.rs b/src/gba_cell.rs index 173c236..698aa43 100644 --- a/src/gba_cell.rs +++ b/src/gba_cell.rs @@ -64,6 +64,7 @@ impl Debug for GbaCell where T: GbaCellSafe + Debug, { + #[inline] fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { ::fmt(&self.read(), f) } diff --git a/src/lib.rs b/src/lib.rs index 72f6af0..677c645 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,9 @@ #![no_std] -#![feature(asm_sym)] #![feature(asm_const)] -#![feature(isa_attribute)] #![feature(naked_functions)] +#![warn(clippy::missing_inline_in_public_items)] +#![allow(clippy::let_and_return)] +#![allow(clippy::result_unit_err)] //#![warn(missing_docs)] //! A crate for GBA development. @@ -72,9 +73,10 @@ //! //! ## Other GBA-related Crates //! -//! This crate provides a largely "unmanaged" interaction with the GBA's -//! hardware. If you would like an API that use the borrow checker to guide you -//! more, the [agb](https://docs.rs/agb) crate might be what you want. +//! This crate provides an API to interact with the GBA that is safe, but with +//! minimal restrictions on what components can be changed when. If you'd like +//! an API where the borrow checker provides stronger control over component +//! access then the [agb](https://docs.rs/agb) crate might be what you want. //! //! ## Safety //! @@ -114,6 +116,7 @@ impl Align4<[u8; N]> { /// Views these bytes as a slice of `u32` /// ## Panics /// * If the number of bytes isn't a multiple of 4 + #[inline] pub fn as_u32_slice(&self) -> &[u32] { assert!(self.0.len() % 4 == 0); // Safety: our struct is aligned to 4, so the pointer will already be @@ -128,6 +131,7 @@ impl Align4<[u8; N]> { /// Views these bytes as a slice of `u16` /// ## Panics /// * If the number of bytes isn't a multiple of 2 + #[inline] pub fn as_u16_slice(&self) -> &[u16] { assert!(self.0.len() % 2 == 0); // Safety: our struct is aligned to 4, so the pointer will already be @@ -144,7 +148,6 @@ impl Align4<[u8; N]> { #[macro_export] macro_rules! include_aligned_bytes { ($file:expr $(,)?) => {{ - let LONG_NAME_THAT_DOES_NOT_CLASH = *include_bytes!($file); - Align4(LONG_NAME_THAT_DOES_NOT_CLASH) + Align4(*include_bytes!($file)) }}; } diff --git a/src/mgba.rs b/src/mgba.rs index 4e88b43..bec167b 100644 --- a/src/mgba.rs +++ b/src/mgba.rs @@ -71,6 +71,7 @@ pub struct MgbaBufferedLogger { pub message_level: MgbaMessageLevel, } impl MgbaBufferedLogger { + #[inline] pub fn try_new(message_level: MgbaMessageLevel) -> Result { if mgba_logging_available() { Ok(Self { byte_count: 0, message_level }) @@ -84,6 +85,7 @@ impl MgbaBufferedLogger { } } impl Drop for MgbaBufferedLogger { + #[inline] fn drop(&mut self) { if self.byte_count != 0 { self.flush(); @@ -91,6 +93,7 @@ impl Drop for MgbaBufferedLogger { } } impl core::fmt::Write for MgbaBufferedLogger { + #[inline] fn write_str(&mut self, s: &str) -> core::fmt::Result { for b in s.as_bytes().iter().copied() { if b == b'\n' { diff --git a/src/mmio.rs b/src/mmio.rs index 0433b73..a78ad31 100644 --- a/src/mmio.rs +++ b/src/mmio.rs @@ -35,9 +35,10 @@ use crate::{ }, dma::DmaControl, sound::{ - SweepControl, TonePattern, ToneFrequency, WaveBank, WaveLenVolume, WaveFrequency, NoiseLenEnvelope, NoiseFrequency, LeftRightVolume, SoundMix, SoundEnable, SoundBias + SweepControl, TonePattern, ToneFrequency, WaveBank, WaveLenVolume, WaveFrequency, + NoiseLenEnvelope, NoiseFrequency, LeftRightVolume, SoundMix, SoundEnable, SoundBias }, - timers::TimerControl, keys::{KeyInput, KeyControl}, mgba::MgbaMessageLevel, fixed::{i16fx8, i32fx8}, + timers::TimerControl, keys::{KeyInput, KeyControl}, mgba::MgbaMessageLevel, fixed::{i16fx8, i32fx8}, prelude::ObjAttr, }; // Note(Lokathor): This macro lets us stick each address at the start of the @@ -214,14 +215,14 @@ def_mmio!(0x0500_0200 = OBJ_PALETTE: VolBlock; "Object t // Video RAM (VRAM) -/// The VRAM offset per screenblock index. +/// The VRAM byte offset per screenblock index. /// /// This is the same for all background types and sizes. pub const SCREENBLOCK_INDEX_OFFSET: usize = 2 * 1_024; /// The size of the background tile region of VRAM. /// -/// Background tile index use can cross between charblocks, but not past the end +/// Background tile index use will work between charblocks, but not past the end /// of BG tile memory into OBJ tile memory. pub const BG_TILE_REGION_SIZE: usize = 64 * 1_024; @@ -475,6 +476,8 @@ def_mmio!(0x0700_0000 = OBJ_ATTR0: VolSeries()}>; "Object attributes 1."); def_mmio!(0x0700_0004 = OBJ_ATTR2: VolSeries()}>; "Object attributes 2."); +def_mmio!(0x0700_0000 = OBJ_ATTR_ALL: VolSeries()}>; "Object attributes (all in one)."); + def_mmio!(0x0700_0006 = AFFINE_PARAM_A: VolSeries()}>; "Affine parameters A."); def_mmio!(0x0700_000E = AFFINE_PARAM_B: VolSeries()}>; "Affine parameters B."); def_mmio!(0x0700_0016 = AFFINE_PARAM_C: VolSeries()}>; "Affine parameters C."); diff --git a/src/video/mod.rs b/src/video/mod.rs index 76ef22a..a2c90c4 100644 --- a/src/video/mod.rs +++ b/src/video/mod.rs @@ -107,7 +107,17 @@ use crate::prelude::*; #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(transparent)] pub struct Color(pub u16); +#[allow(clippy::unusual_byte_groupings)] impl Color { + pub const BLACK: Color = Color(0b0_00000_00000_00000); + pub const RED: Color = Color(0b0_00000_00000_11111); + pub const GREEN: Color = Color(0b0_00000_11111_00000); + pub const YELLOW: Color = Color(0b0_00000_11111_11111); + pub const BLUE: Color = Color(0b0_11111_00000_00000); + pub const MAGENTA: Color = Color(0b0_11111_00000_11111); + pub const CYAN: Color = Color(0b0_11111_11111_00000); + pub const WHITE: Color = Color(0b0_11111_11111_11111); + pub_const_fn_new_zeroed!(); u16_int_field!(0 - 4, red, with_red); u16_int_field!(5 - 9, green, with_green); @@ -358,3 +368,22 @@ impl ObjAttr2 { u16_int_field!(10 - 11, priority, with_priority); u16_int_field!(12 - 15, palbank, with_palbank); } + +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[repr(C)] +pub struct ObjAttr(pub ObjAttr0, pub ObjAttr1, pub ObjAttr2); +impl ObjAttr { + #[inline] + pub const fn new() -> Self { + Self(ObjAttr0(0), ObjAttr1(0), ObjAttr2(0)) + } + pub fn set_x(&mut self, x: u16) { + self.1 = self.1.with_x(x); + } + pub fn set_y(&mut self, y: u16) { + self.0 = self.0.with_y(y); + } + pub fn set_tile_id(&mut self, id: u16) { + self.2 = self.2.with_tile_id(id); + } +}