Skip to content

Commit

Permalink
examples of all three bitmap modes.
Browse files Browse the repository at this point in the history
  • Loading branch information
Lokathor committed May 15, 2024
1 parent 5168cd2 commit 305f01e
Show file tree
Hide file tree
Showing 7 changed files with 347 additions and 26 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ track_caller = []
robust_irq_handler = []

[dependencies]
voladdress = "1.3.0"
voladdress = "1.4.0"
bitfrob = "1"
critical-section = { version = "1.1.2", features = [
"restore-state-bool",
Expand Down
38 changes: 38 additions & 0 deletions examples/mode3.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#![no_std]
#![no_main]

use gba::{
bios::VBlankIntrWait,
mmio::{DISPCNT, DISPSTAT, IE, IME, KEYINPUT, MODE3_VRAM},
video::{Color, DisplayControl, DisplayStatus},
IrqBits,
};

gba::panic_handler!(empty_loop);

#[no_mangle]
pub extern "C" fn main() -> ! {
IME.write(true);
IE.write(IrqBits::new().with_vblank(true));
DISPSTAT.write(DisplayStatus::new().with_vblank_irq(true));

DISPCNT.write(DisplayControl::new().with_bg_mode(3).with_bg2(true));

let max_x = MODE3_VRAM.width() as i16 - 1;
let max_y = MODE3_VRAM.height() as i16 - 1;
let mut x: i16 = 5;
let mut y: i16 = 15;
let mut color = Color(0b0_11100_11110_11111);

loop {
VBlankIntrWait();
color.0 = color.0.rotate_left(1);

let keys = KEYINPUT.read();
x = (x + i16::from(keys.dx())).clamp(0, max_x);
y = (y + i16::from(keys.dy())).clamp(0, max_y);

let addr = MODE3_VRAM.index(x as usize, (max_y - y) as usize);
addr.write(color);
}
}
78 changes: 78 additions & 0 deletions examples/mode4.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#![no_std]
#![no_main]

use gba::{
bios::VBlankIntrWait,
mmio::{BG_PALRAM, DISPCNT, DISPSTAT, IE, IME, KEYINPUT, MODE4_VRAM},
video::{Color, DisplayControl, DisplayStatus},
IrqBits,
};

gba::panic_handler!(empty_loop);

#[no_mangle]
pub extern "C" fn main() -> ! {
IME.write(true);
IE.write(IrqBits::new().with_vblank(true));
DISPSTAT.write(DisplayStatus::new().with_vblank_irq(true));

DISPCNT.write(DisplayControl::new().with_bg_mode(4).with_bg2(true));

let max_x = 2 * MODE4_VRAM.get_frame(0).unwrap().width() as i16 - 1;
let max_y = MODE4_VRAM.get_frame(0).unwrap().height() as i16 - 1;
let mut x: i16 = 5;
let mut y: i16 = 15;
let mut frame: usize = 0;
let mut pal_index: u8 = 0;

{
let mut red = 1;
let mut green = 3;
let mut blue = 5;
BG_PALRAM.iter().for_each(|addr| {
red = (red + 3) % 256;
green = (green + 5) % 256;
blue = (blue + 7) % 256;
let color = Color::from_rgb(red, green, blue);
addr.write(color)
});
}

loop {
VBlankIntrWait();
pal_index = pal_index.wrapping_add(1);

// keypad moves the update point
let keys = KEYINPUT.read();
x = (x + i16::from(keys.dx())).clamp(0, max_x);
y = (y + i16::from(keys.dy())).clamp(0, max_y);

// L and R pick the frame
if keys.r() && frame == 0 {
frame = 1;
}
if keys.l() && frame == 1 {
frame = 0;
}
DISPCNT.write(
DisplayControl::new()
.with_bg_mode(4)
.with_bg2(true)
.with_frame1_active(frame > 0),
);

// we have to do some silly dancing here because we can't write just a `u8`
// in VRAM. We have to read a `u8x2` combo, update part of it, then write it
// back to VRAM.
let addr = MODE4_VRAM
.get_frame(frame)
.unwrap()
.index((x / 2) as usize, (max_y - y) as usize);
let old = addr.read();
addr.write(if (x & 1) != 0 {
old.with_high(pal_index)
} else {
old.with_low(pal_index)
});
}
}
57 changes: 57 additions & 0 deletions examples/mode5.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#![no_std]
#![no_main]

use gba::{
bios::VBlankIntrWait,
mmio::{DISPCNT, DISPSTAT, IE, IME, KEYINPUT, MODE5_VRAM},
video::{Color, DisplayControl, DisplayStatus},
IrqBits,
};

gba::panic_handler!(empty_loop);

#[no_mangle]
pub extern "C" fn main() -> ! {
IME.write(true);
IE.write(IrqBits::new().with_vblank(true));
DISPSTAT.write(DisplayStatus::new().with_vblank_irq(true));

DISPCNT.write(DisplayControl::new().with_bg_mode(5).with_bg2(true));

let max_x = MODE5_VRAM.get_frame(0).unwrap().width() as i16 - 1;
let max_y = MODE5_VRAM.get_frame(0).unwrap().height() as i16 - 1;
let mut x: i16 = 5;
let mut y: i16 = 15;
let mut frame: usize = 0;
let mut color = Color(0b0_11100_11110_11111);

loop {
VBlankIntrWait();
color.0 = color.0.rotate_left(1);

// keypad moves the update point
let keys = KEYINPUT.read();
x = (x + i16::from(keys.dx())).clamp(0, max_x);
y = (y + i16::from(keys.dy())).clamp(0, max_y);

// L and R pick the frame
if keys.r() && frame == 0 {
frame = 1;
}
if keys.l() && frame == 1 {
frame = 0;
}
DISPCNT.write(
DisplayControl::new()
.with_bg_mode(5)
.with_bg2(true)
.with_frame1_active(frame > 0),
);

let addr = MODE5_VRAM
.get_frame(frame)
.unwrap()
.index(x as usize, (max_y - y) as usize);
addr.write(color);
}
}
78 changes: 78 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,84 @@ impl KeyInput {
pub const fn a(self) -> bool {
!bitfrob::u16_get_bit(0, self.0)
}
/// If `b` is pressed (right primary button)
#[inline]
#[must_use]
pub const fn b(self) -> bool {
!bitfrob::u16_get_bit(1, self.0)
}
/// If `select` is pressed (lower/left secondary button)
#[inline]
#[must_use]
pub const fn select(self) -> bool {
!bitfrob::u16_get_bit(2, self.0)
}
/// If `start` is pressed (upper/right secondary button)
#[inline]
#[must_use]
pub const fn start(self) -> bool {
!bitfrob::u16_get_bit(3, self.0)
}
/// If `right` is pressed (d-pad)
#[inline]
#[must_use]
pub const fn right(self) -> bool {
!bitfrob::u16_get_bit(4, self.0)
}
/// If `left` is pressed (d-pad)
#[inline]
#[must_use]
pub const fn left(self) -> bool {
!bitfrob::u16_get_bit(5, self.0)
}
/// If `up` is pressed (d-pad)
#[inline]
#[must_use]
pub const fn up(self) -> bool {
!bitfrob::u16_get_bit(6, self.0)
}
/// If `down` is pressed (d-pad)
#[inline]
#[must_use]
pub const fn down(self) -> bool {
!bitfrob::u16_get_bit(7, self.0)
}
/// If `r` is pressed (right shoulder button)
#[inline]
#[must_use]
pub const fn r(self) -> bool {
!bitfrob::u16_get_bit(8, self.0)
}
/// If `l` is pressed (left shoulder button)
#[inline]
#[must_use]
pub const fn l(self) -> bool {
!bitfrob::u16_get_bit(9, self.0)
}
/// Delta X of the d-pad. right +1, left -1.
#[inline]
#[must_use]
pub const fn dx(self) -> i8 {
if self.right() {
1
} else if self.left() {
-1
} else {
0
}
}
/// Delta Y of the d-pad. up +1, down -1.
#[inline]
#[must_use]
pub const fn dy(self) -> i8 {
if self.up() {
1
} else if self.down() {
-1
} else {
0
}
}
}

/// Interrupt bit flags.
Expand Down
51 changes: 48 additions & 3 deletions src/mmio.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
//! Definitions for Memory-mapped IO (hardware control).

use bitfrob::u8x2;
#[allow(unused_imports)]
use voladdress::{Safe, Unsafe, VolAddress};
use voladdress::VolAddress;
use voladdress::{VolBlock, VolGrid2d, VolGrid2dStrided};

use crate::{
video::{Color, DisplayControl, DisplayStatus},
Expand All @@ -11,9 +13,9 @@ use crate::{
/// "safe on GBA", which is either Safe or Unsafe according to the `on_gba`
/// cargo feature.
#[cfg(feature = "on_gba")]
type SOGBA = Safe;
type SOGBA = voladdress::Safe;
#[cfg(not(feature = "on_gba"))]
type SOGBA = Unsafe;
type SOGBA = voladdress::Unsafe;

/// Responds "normally" to read/write, just holds a setting
type PlainAddr<T> = VolAddress<T, SOGBA, SOGBA>;
Expand Down Expand Up @@ -77,3 +79,46 @@ pub const IME: PlainAddr<bool> = unsafe { VolAddress::new(0x0400_0208) };
/// in a given pixel.
pub const BACKDROP_COLOR: PlainAddr<Color> =
unsafe { VolAddress::new(0x0500_0000) };

/// Palette data for the backgrounds
pub const BG_PALRAM: VolBlock<Color, SOGBA, SOGBA, 256> =
unsafe { VolBlock::new(0x0500_0000) };

/// Palette data for the objects.
pub const OBJ_PALRAM: VolBlock<Color, SOGBA, SOGBA, 256> =
unsafe { VolBlock::new(0x0500_0000) };

/// The VRAM's view in Video Mode 3.
///
/// Each location is a direct color value.
pub const MODE3_VRAM: VolGrid2d<Color, SOGBA, SOGBA, 240, 160> =
unsafe { VolGrid2d::new(0x0600_0000) };

/// The VRAM's view in Video Mode 4.
///
/// Each location is a pair of palette indexes into the background palette.
/// Because the VRAM can't be written with a single byte, we have to work with
/// this in units of [`u8x2`]. It's annoying, I know.
pub const MODE4_VRAM: VolGrid2dStrided<
u8x2,
SOGBA,
SOGBA,
{ 240 / 2 },
160,
2,
0xA000,
> = unsafe { VolGrid2dStrided::new(0x0600_0000) };

/// The VRAM's view in Video Mode 5.
///
/// Each location is a direct color value, but there's a lower image size to
/// allow for two frames.
pub const MODE5_VRAM: VolGrid2dStrided<
Color,
SOGBA,
SOGBA,
160,
128,
2,
0xA000,
> = unsafe { VolGrid2dStrided::new(0x0600_0000) };
Loading

0 comments on commit 305f01e

Please sign in to comment.