module organization

This commit is contained in:
Lokathor 2018-12-20 18:08:54 -07:00
parent 2f3becee59
commit 4586d2d95f
8 changed files with 287 additions and 155 deletions

View file

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

View file

@ -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<T>(pub *mut T);
impl<T> VolatilePtr<T> {
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<T> {
address: NonZeroUsize,
marker: PhantomData<*mut T>,
}
pub unsafe fn write(&self, data: T) {
core::ptr::write_volatile(self.0, data);
impl<T> Clone for VolAddress<T> {
fn clone(&self) -> Self {
*self
}
}
pub unsafe fn offset(self, count: isize) -> Self {
VolatilePtr(self.0.wrapping_offset(count))
impl<T> Copy for VolAddress<T> {}
impl<T> PartialEq for VolAddress<T> {
fn eq(&self, other: &Self) -> bool {
self.address == other.address
}
}
impl<T> Eq for VolAddress<T> {}
impl<T> PartialOrd for VolAddress<T> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.address.cmp(&other.address))
}
}
impl<T> Ord for VolAddress<T> {
fn cmp(&self, other: &Self) -> Ordering {
self.address.cmp(&other.address)
}
}
impl<T> VolAddress<T> {
pub const unsafe fn new_unchecked(address: usize) -> Self {
VolAddress {
address: NonZeroUsize::new_unchecked(address),
marker: PhantomData,
}
}
pub const unsafe fn cast<Z>(self) -> VolAddress<Z> {
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::<T>())),
marker: PhantomData,
}
}
pub fn is_aligned(self) -> bool {
// TODO: const this
self.address.get() % core::mem::align_of::<T>() == 0
}
pub const unsafe fn iter_slots(self, slots: usize) -> VolAddressIter<T> {
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<T> {
vol_address: VolAddress<T>,
slots: usize,
}
impl<T> Clone for VolAddressIter<T> {
fn clone(&self) -> Self {
VolAddressIter {
vol_address: self.vol_address,
slots: self.slots,
}
}
}
impl<T> PartialEq for VolAddressIter<T> {
fn eq(&self, other: &Self) -> bool {
self.vol_address == other.vol_address && self.slots == other.slots
}
}
impl<T> Eq for VolAddressIter<T> {}
impl<T> Iterator for VolAddressIter<T> {
type Item = VolAddress<T>;
fn next(&mut self) -> Option<Self::Item> {
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<T> FusedIterator for VolAddressIter<T> {}
/// VolAddressBlock
#[derive(Debug)]
pub struct VolAddressBlock<T> {
vol_address: VolAddress<T>,
slots: usize,
}
impl<T> Clone for VolAddressBlock<T> {
fn clone(&self) -> Self {
VolAddressBlock {
vol_address: self.vol_address,
slots: self.slots,
}
}
}
impl<T> PartialEq for VolAddressBlock<T> {
fn eq(&self, other: &Self) -> bool {
self.vol_address == other.vol_address && self.slots == other.slots
}
}
impl<T> Eq for VolAddressBlock<T> {}
impl<T> VolAddressBlock<T> {
pub const unsafe fn new_unchecked(vol_address: VolAddress<T>, slots: usize) -> Self {
VolAddressBlock { vol_address, slots }
}
pub const fn iter(self) -> VolAddressIter<T> {
VolAddressIter {
vol_address: self.vol_address,
slots: self.slots,
}
}
pub unsafe fn index_unchecked(self, slot: usize) -> VolAddress<T> {
// TODO: const this
self.vol_address.offset(slot as isize)
}
pub fn index(self, slot: usize) -> VolAddress<T> {
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<VolAddress<T>> {
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<DisplayControlSetting> = VolatilePtr(0x0400_0000 as *mut DisplayControlSetting);
pub const DISPLAY_CONTROL: VolAddress<DisplayControlSetting> = 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<Color> = VolatilePtr(0x600_0000 as *mut Color);
const SCREEN_HEIGHT: isize = 160;
const PIXELS: VolAddressBlock<Color> =
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 {}
}
}

10
src/base.rs Normal file
View file

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

View file

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

View file

@ -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<DisplayControlSetting> = 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<u16> = 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 {}
}

View file

@ -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<DisplayControlSetting> = 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<u16> = 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 {}
}