diff --git a/agb/examples/animated_background.rs b/agb/examples/animated_background.rs index 2c2a3392..d20213d9 100644 --- a/agb/examples/animated_background.rs +++ b/agb/examples/animated_background.rs @@ -3,7 +3,7 @@ use agb::{ display::{ - tiled::{TileFormat, TileSet, TileSetting}, + tiled::{RegularBackgroundSize, TileFormat, TileSet, TileSetting}, Priority, }, include_gfx, @@ -20,7 +20,7 @@ fn main(mut gba: agb::Gba) -> ! { vram.set_background_palettes(water_tiles::water_tiles.palettes); - let mut bg = gfx.background(Priority::P0); + let mut bg = gfx.background(Priority::P0, RegularBackgroundSize::Background32x32); for y in 0..20u16 { for x in 0..30u16 { diff --git a/agb/examples/chicken.rs b/agb/examples/chicken.rs index d0782e15..02536887 100644 --- a/agb/examples/chicken.rs +++ b/agb/examples/chicken.rs @@ -6,6 +6,7 @@ use agb::{ display::{ object::{Object, ObjectController, Size, Sprite}, palette16::Palette16, + tiled::RegularBackgroundSize, HEIGHT, WIDTH, }, input::Button, @@ -54,7 +55,10 @@ fn main(mut gba: agb::Gba) -> ! { vram.set_background_palette_raw(&MAP_PALETTE); let tileset = TileSet::new(&MAP_TILES, TileFormat::FourBpp); - let mut background = gfx.background(agb::display::Priority::P0); + let mut background = gfx.background( + agb::display::Priority::P0, + RegularBackgroundSize::Background32x32, + ); for (i, &tile) in MAP_MAP.iter().enumerate() { let i = i as u16; diff --git a/agb/examples/dynamic_tiles.rs b/agb/examples/dynamic_tiles.rs index 1d746f9f..53b013a4 100644 --- a/agb/examples/dynamic_tiles.rs +++ b/agb/examples/dynamic_tiles.rs @@ -1,7 +1,11 @@ #![no_std] #![no_main] -use agb::display::{palette16::Palette16, tiled::TileSetting, Priority}; +use agb::display::{ + palette16::Palette16, + tiled::{RegularBackgroundSize, TileSetting}, + Priority, +}; #[agb::entry] fn main(mut gba: agb::Gba) -> ! { @@ -13,7 +17,7 @@ fn main(mut gba: agb::Gba) -> ! { 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, ])]); - let mut bg = gfx.background(Priority::P0); + let mut bg = gfx.background(Priority::P0, RegularBackgroundSize::Background32x32); for y in 0..20u32 { for x in 0..30u32 { diff --git a/agb/examples/test_logo.rs b/agb/examples/test_logo.rs index 674867c9..464e4d3e 100644 --- a/agb/examples/test_logo.rs +++ b/agb/examples/test_logo.rs @@ -1,13 +1,16 @@ #![no_std] #![no_main] -use agb::display::example_logo; +use agb::display::{example_logo, tiled::RegularBackgroundSize}; #[agb::entry] fn main(mut gba: agb::Gba) -> ! { let (gfx, mut vram) = gba.display.video.tiled0(); - let mut map = gfx.background(agb::display::Priority::P0); + let mut map = gfx.background( + agb::display::Priority::P0, + RegularBackgroundSize::Background32x32, + ); example_logo::display_logo(&mut map, &mut vram); diff --git a/agb/examples/text_render.rs b/agb/examples/text_render.rs index 6584e9e7..fa398a1d 100644 --- a/agb/examples/text_render.rs +++ b/agb/examples/text_render.rs @@ -2,7 +2,10 @@ #![no_main] use agb::{ - display::{tiled::TileSetting, Font, Priority}, + display::{ + tiled::{RegularBackgroundSize, TileSetting}, + Font, Priority, + }, include_font, }; @@ -22,7 +25,7 @@ fn main(mut gba: agb::Gba) -> ! { let background_tile = vram.new_dynamic_tile().fill_with(0); - let mut bg = gfx.background(Priority::P0); + let mut bg = gfx.background(Priority::P0, RegularBackgroundSize::Background32x32); for y in 0..20u16 { for x in 0..30u16 { diff --git a/agb/examples/wave.rs b/agb/examples/wave.rs index 8fffaddc..7571eef8 100644 --- a/agb/examples/wave.rs +++ b/agb/examples/wave.rs @@ -4,7 +4,7 @@ use core::cell::RefCell; use agb::{ - display::example_logo, + display::{example_logo, tiled::RegularBackgroundSize}, fixnum::FixedNum, interrupt::{free, Interrupt}, }; @@ -19,7 +19,10 @@ struct BackCosines { fn main(mut gba: agb::Gba) -> ! { let (gfx, mut vram) = gba.display.video.tiled0(); - let mut background = gfx.background(agb::display::Priority::P0); + let mut background = gfx.background( + agb::display::Priority::P0, + RegularBackgroundSize::Background32x32, + ); example_logo::display_logo(&mut background, &mut vram); diff --git a/agb/src/display/example_logo.rs b/agb/src/display/example_logo.rs index d39389b8..bec4df92 100644 --- a/agb/src/display/example_logo.rs +++ b/agb/src/display/example_logo.rs @@ -23,13 +23,15 @@ pub fn display_logo(map: &mut RegularMap, vram: &mut VRamManager) { } #[cfg(test)] mod tests { + use crate::display::{tiled::RegularBackgroundSize, Priority}; + use super::*; #[test_case] fn logo_display(gba: &mut crate::Gba) { let (gfx, mut vram) = gba.display.video.tiled0(); - let mut map = gfx.background(crate::display::Priority::P0); + let mut map = gfx.background(Priority::P0, RegularBackgroundSize::Background32x32); display_logo(&mut map, &mut vram); diff --git a/agb/src/display/font.rs b/agb/src/display/font.rs index a7058b72..8bbe9ac2 100644 --- a/agb/src/display/font.rs +++ b/agb/src/display/font.rs @@ -213,7 +213,10 @@ mod tests { fn font_display(gba: &mut crate::Gba) { let (gfx, mut vram) = gba.display.video.tiled0(); - let mut bg = gfx.background(crate::display::Priority::P0); + let mut bg = gfx.background( + crate::display::Priority::P0, + crate::display::tiled::RegularBackgroundSize::Background32x32, + ); vram.set_background_palette_raw(&[ 0x0000, 0x0ff0, 0x00ff, 0xf00f, 0xf0f0, 0x0f0f, 0xaaaa, 0x5555, 0x0000, 0x0000, 0x0000, diff --git a/agb/src/display/tiled/infinite_scrolled_map.rs b/agb/src/display/tiled/infinite_scrolled_map.rs index 48cc66ba..41dc67d7 100644 --- a/agb/src/display/tiled/infinite_scrolled_map.rs +++ b/agb/src/display/tiled/infinite_scrolled_map.rs @@ -63,8 +63,8 @@ impl<'a> InfiniteScrolledMap<'a> { let offset = self.current_pos - (x_start * 8, y_start * 8).into(); let offset_scroll = ( - offset.x.rem_euclid(32 * 8) as u16, - offset.y.rem_euclid(32 * 8) as u16, + self.map.size().tile_pos_x(offset.x), + self.map.size().tile_pos_y(offset.y), ) .into(); @@ -120,6 +120,8 @@ impl<'a> InfiniteScrolledMap<'a> { let difference_tile_x = div_ceil(difference.x, 8); let difference_tile_y = div_ceil(difference.y, 8); + let size = self.map.size(); + let vertical_rect_to_update: Rect = if div_floor(old_pos.x, 8) != new_tile_x { // need to update the x line // calculate which direction we need to update @@ -177,8 +179,8 @@ impl<'a> InfiniteScrolledMap<'a> { self.map.set_tile( vram, ( - (tile_x - self.offset.x).rem_euclid(32) as u16, - (tile_y - self.offset.y).rem_euclid(32) as u16, + size.tile_pos_x(tile_x - self.offset.x), + size.tile_pos_y(tile_y - self.offset.y), ) .into(), tileset, @@ -188,8 +190,8 @@ impl<'a> InfiniteScrolledMap<'a> { let current_scroll = self.map.scroll_pos(); let new_scroll = ( - (current_scroll.x as i32 + difference.x).rem_euclid(32 * 8) as u16, - (current_scroll.y as i32 + difference.y).rem_euclid(32 * 8) as u16, + size.px_offset_x(current_scroll.x as i32 + difference.x), + size.px_offset_y(current_scroll.y as i32 + difference.y), ) .into(); diff --git a/agb/src/display/tiled/map.rs b/agb/src/display/tiled/map.rs index 9d6c9790..34cdc77a 100644 --- a/agb/src/display/tiled/map.rs +++ b/agb/src/display/tiled/map.rs @@ -7,7 +7,9 @@ use crate::dma::dma_copy16; use crate::fixnum::Vector2D; use crate::memory_mapped::MemoryMapped; -use super::{Tile, TileSet, TileSetting, VRamManager}; +use super::{RegularBackgroundSize, Tile, TileSet, TileSetting, VRamManager}; + +use alloc::{vec, vec::Vec}; pub struct RegularMap { background_id: u8, @@ -17,14 +19,21 @@ pub struct RegularMap { y_scroll: u16, priority: Priority, - tiles: [Tile; 32 * 32], + tiles: Vec, tiles_dirty: bool, + + size: RegularBackgroundSize, } pub const TRANSPARENT_TILE_INDEX: u16 = (1 << 10) - 1; impl RegularMap { - pub(crate) fn new(background_id: u8, screenblock: u8, priority: Priority) -> Self { + pub(crate) fn new( + background_id: u8, + screenblock: u8, + priority: Priority, + size: RegularBackgroundSize, + ) -> Self { Self { background_id, @@ -33,8 +42,10 @@ impl RegularMap { y_scroll: 0, priority, - tiles: [Tile::default(); 32 * 32], + tiles: vec![Default::default(); size.num_tiles()], tiles_dirty: true, + + size, } } @@ -45,7 +56,7 @@ impl RegularMap { tileset: &TileSet<'_>, tile_setting: TileSetting, ) { - let pos = (pos.x + pos.y * 32) as usize; + let pos = self.size.gba_offset(pos); let old_tile = self.tiles[pos]; if old_tile != Tile::default() { @@ -93,7 +104,9 @@ impl RegularMap { } pub fn commit(&mut self, vram: &mut VRamManager) { - let new_bg_control_value = (self.priority as u16) | ((self.screenblock as u16) << 8); + let new_bg_control_value = (self.priority as u16) + | ((self.screenblock as u16) << 8) + | (self.size.size_flag() << 14); self.bg_control_register().set(new_bg_control_value); self.bg_h_offset().set(self.x_scroll); @@ -111,7 +124,7 @@ impl RegularMap { dma_copy16( self.tiles.as_ptr() as *const u16, screenblock_memory, - 32 * 32, + self.size.num_tiles(), ); } @@ -127,6 +140,10 @@ impl RegularMap { (self.x_scroll, self.y_scroll).into() } + pub(crate) fn size(&self) -> RegularBackgroundSize { + self.size + } + const fn bg_control_register(&self) -> MemoryMapped { unsafe { MemoryMapped::new(0x0400_0008 + 2 * self.background_id as usize) } } @@ -147,7 +164,10 @@ impl RegularMap { pub struct MapLoan<'a, T> { map: T, background_id: u8, + screenblock_id: u8, + screenblock_length: u8, regular_map_list: &'a RefCell>, + screenblock_list: &'a RefCell>, } impl<'a, T> Deref for MapLoan<'a, T> { @@ -168,12 +188,18 @@ impl<'a, T> MapLoan<'a, T> { pub(crate) fn new( map: T, background_id: u8, + screenblock_id: u8, + screenblock_length: u8, regular_map_list: &'a RefCell>, + screenblock_list: &'a RefCell>, ) -> Self { MapLoan { map, background_id, + screenblock_id, + screenblock_length, regular_map_list, + screenblock_list, } } } @@ -183,5 +209,11 @@ impl<'a, T> Drop for MapLoan<'a, T> { self.regular_map_list .borrow_mut() .set(self.background_id as usize, false); + + let mut screenblock_list = self.screenblock_list.borrow_mut(); + + for i in self.screenblock_id..self.screenblock_id + self.screenblock_length { + screenblock_list.set(i as usize, false); + } } } diff --git a/agb/src/display/tiled/mod.rs b/agb/src/display/tiled/mod.rs index a0ec32d8..15907798 100644 --- a/agb/src/display/tiled/mod.rs +++ b/agb/src/display/tiled/mod.rs @@ -3,11 +3,86 @@ mod map; mod tiled0; mod vram_manager; +use agb_fixnum::Vector2D; pub use infinite_scrolled_map::{InfiniteScrolledMap, PartialUpdateStatus}; pub use map::{MapLoan, RegularMap}; pub use tiled0::Tiled0; pub use vram_manager::{DynamicTile, TileFormat, TileIndex, TileSet, VRamManager}; +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum RegularBackgroundSize { + Background32x32, + Background64x32, + Background32x64, + Background64x64, +} + +impl RegularBackgroundSize { + pub fn width(&self) -> u32 { + match self { + RegularBackgroundSize::Background32x32 => 32, + RegularBackgroundSize::Background64x32 => 64, + RegularBackgroundSize::Background32x64 => 32, + RegularBackgroundSize::Background64x64 => 64, + } + } + + pub fn height(&self) -> u32 { + match self { + RegularBackgroundSize::Background32x32 => 32, + RegularBackgroundSize::Background64x32 => 32, + RegularBackgroundSize::Background32x64 => 64, + RegularBackgroundSize::Background64x64 => 64, + } + } + + pub(crate) fn size_flag(&self) -> u16 { + match self { + RegularBackgroundSize::Background32x32 => 0, + RegularBackgroundSize::Background64x32 => 1, + RegularBackgroundSize::Background32x64 => 2, + RegularBackgroundSize::Background64x64 => 3, + } + } + + pub(crate) fn num_tiles(&self) -> usize { + (self.width() * self.height()) as usize + } + + pub(crate) fn num_screen_blocks(&self) -> usize { + self.num_tiles() / (32 * 32) + } + + // This is hilariously complicated due to how the GBA stores the background screenblocks. + // See https://www.coranac.com/tonc/text/regbg.htm#sec-map for an explanation + pub(crate) fn gba_offset(&self, pos: Vector2D) -> usize { + let x_mod = pos.x & (self.width() as u16 - 1); + let y_mod = pos.y & (self.height() as u16 - 1); + + let screenblock = (x_mod / 32) + (y_mod / 32) * (self.width() as u16 / 32); + + let pos = screenblock * 32 * 32 + (x_mod % 32 + 32 * (y_mod % 32)); + + pos as usize + } + + pub(crate) fn tile_pos_x(&self, x: i32) -> u16 { + ((x as u32) & (self.width() - 1)) as u16 + } + + pub(crate) fn tile_pos_y(&self, y: i32) -> u16 { + ((y as u32) & (self.height() - 1)) as u16 + } + + pub(crate) fn px_offset_x(&self, x: i32) -> u16 { + ((x as u32) & (self.width() * 8 - 1)) as u16 + } + + pub(crate) fn px_offset_y(&self, y: i32) -> u16 { + ((y as u32) & (self.height() * 8 - 1)) as u16 + } +} + #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] #[repr(transparent)] struct Tile(u16); @@ -47,3 +122,31 @@ impl TileSetting { self.0 & !((1 << 10) - 1) } } + +#[cfg(test)] +mod test { + use super::*; + + #[test_case] + fn rem_euclid_width_works(_gba: &mut crate::Gba) { + use RegularBackgroundSize::*; + + let sizes = [ + Background32x32, + Background32x64, + Background64x32, + Background64x64, + ]; + + for size in sizes.iter() { + let width = size.width() as i32; + + assert_eq!(size.tile_pos_x(8), 8); + assert_eq!(size.tile_pos_x(3 + width), 3); + assert_eq!(size.tile_pos_x(7 + width * 9), 7); + + assert_eq!(size.tile_pos_x(-8), (size.width() - 8) as u16); + assert_eq!(size.tile_pos_x(-17 - width * 8), (size.width() - 17) as u16); + } + } +} diff --git a/agb/src/display/tiled/tiled0.rs b/agb/src/display/tiled/tiled0.rs index ad5da7fb..fbc523e2 100644 --- a/agb/src/display/tiled/tiled0.rs +++ b/agb/src/display/tiled/tiled0.rs @@ -5,10 +5,11 @@ use crate::{ display::{set_graphics_mode, DisplayMode, Priority}, }; -use super::{MapLoan, RegularMap}; +use super::{MapLoan, RegularBackgroundSize, RegularMap}; pub struct Tiled0 { regular: RefCell>, + screenblocks: RefCell>, } impl Tiled0 { @@ -17,20 +18,61 @@ impl Tiled0 { Self { regular: Default::default(), + screenblocks: Default::default(), } } - pub fn background(&self, priority: Priority) -> MapLoan<'_, RegularMap> { + pub fn background( + &self, + priority: Priority, + size: RegularBackgroundSize, + ) -> MapLoan<'_, RegularMap> { let mut regular = self.regular.borrow_mut(); let new_background = regular.first_zero().unwrap(); if new_background >= 4 { panic!("can only have 4 active backgrounds"); } - let bg = RegularMap::new(new_background as u8, (new_background + 16) as u8, priority); + let num_screenblocks = size.num_screen_blocks(); + let mut screenblocks = self.screenblocks.borrow_mut(); + + let screenblock = find_screenblock_gap(&screenblocks, num_screenblocks); + for id in screenblock..(screenblock + num_screenblocks) { + screenblocks.set(id, true); + } + + let bg = RegularMap::new(new_background as u8, screenblock as u8 + 16, priority, size); regular.set(new_background, true); - MapLoan::new(bg, new_background as u8, &self.regular) + MapLoan::new( + bg, + new_background as u8, + screenblock as u8, + num_screenblocks as u8, + &self.regular, + &self.screenblocks, + ) } } + +fn find_screenblock_gap(screenblocks: &Bitarray<1>, gap: usize) -> usize { + let mut candidate = 0; + + 'outer: while candidate < 16 - gap { + let starting_point = candidate; + for attempt in starting_point..(starting_point + gap) { + if screenblocks.get(attempt) == Some(true) { + candidate = attempt + 1; + continue 'outer; + } + } + + return candidate; + } + + panic!( + "Failed to find screenblock gap of at least {} elements", + gap + ); +} diff --git a/examples/the-hat-chooses-the-wizard/src/main.rs b/examples/the-hat-chooses-the-wizard/src/main.rs index de8f3ea9..578fc7dc 100644 --- a/examples/the-hat-chooses-the-wizard/src/main.rs +++ b/examples/the-hat-chooses-the-wizard/src/main.rs @@ -7,7 +7,8 @@ use agb::{ display::{ object::{Graphics, Object, ObjectController, Tag, TagMap}, tiled::{ - InfiniteScrolledMap, PartialUpdateStatus, TileFormat, TileSet, TileSetting, VRamManager, + InfiniteScrolledMap, PartialUpdateStatus, RegularBackgroundSize, TileFormat, TileSet, + TileSetting, VRamManager, }, Priority, HEIGHT, WIDTH, }, @@ -777,8 +778,8 @@ impl<'a, 'b, 'c> PlayingLevel<'a, 'b> { fn main(mut agb: agb::Gba) -> ! { let (tiled, mut vram) = agb.display.video.tiled0(); vram.set_background_palettes(tile_sheet::background.palettes); - let mut splash_screen = tiled.background(Priority::P0); - let mut world_display = tiled.background(Priority::P0); + let mut splash_screen = tiled.background(Priority::P0, RegularBackgroundSize::Background32x32); + let mut world_display = tiled.background(Priority::P0, RegularBackgroundSize::Background32x32); let tileset = TileSet::new(tile_sheet::background.tiles, TileFormat::FourBpp); @@ -845,7 +846,7 @@ fn main(mut agb: agb::Gba) -> ! { let map_current_level = current_level; let mut background = InfiniteScrolledMap::new( - tiled.background(Priority::P2), + tiled.background(Priority::P2, RegularBackgroundSize::Background32x64), Box::new(|pos: Vector2D| { let level = &map_tiles::LEVELS[map_current_level as usize]; ( @@ -860,7 +861,7 @@ fn main(mut agb: agb::Gba) -> ! { }), ); let mut foreground = InfiniteScrolledMap::new( - tiled.background(Priority::P0), + tiled.background(Priority::P0, RegularBackgroundSize::Background64x32), Box::new(|pos: Vector2D| { let level = &map_tiles::LEVELS[map_current_level as usize]; ( diff --git a/examples/the-purple-night/src/main.rs b/examples/the-purple-night/src/main.rs index 930f7cd9..6bb170ab 100644 --- a/examples/the-purple-night/src/main.rs +++ b/examples/the-purple-night/src/main.rs @@ -12,7 +12,10 @@ use alloc::{boxed::Box, vec::Vec}; use agb::{ display::{ object::{Graphics, Object, ObjectController, Sprite, Tag, TagMap}, - tiled::{InfiniteScrolledMap, TileFormat, TileSet, TileSetting, VRamManager}, + tiled::{ + InfiniteScrolledMap, RegularBackgroundSize, TileFormat, TileSet, TileSetting, + VRamManager, + }, Priority, HEIGHT, WIDTH, }, fixnum::{FixedNum, Rect, Vector2D}, @@ -2221,7 +2224,7 @@ fn game_with_level(gba: &mut agb::Gba) { let object = gba.display.object.get(); let backdrop = InfiniteScrolledMap::new( - background.background(Priority::P2), + background.background(Priority::P2, RegularBackgroundSize::Background32x32), Box::new(|pos| { ( &tileset, @@ -2235,7 +2238,7 @@ fn game_with_level(gba: &mut agb::Gba) { ); let foreground = InfiniteScrolledMap::new( - background.background(Priority::P0), + background.background(Priority::P0, RegularBackgroundSize::Background32x32), Box::new(|pos| { ( &tileset, @@ -2249,7 +2252,7 @@ fn game_with_level(gba: &mut agb::Gba) { ); let clouds = InfiniteScrolledMap::new( - background.background(Priority::P3), + background.background(Priority::P3, RegularBackgroundSize::Background32x32), Box::new(|pos| { ( &tileset,