diff --git a/agb-fixnum/src/lib.rs b/agb-fixnum/src/lib.rs index 2385fc36..a7a84f68 100644 --- a/agb-fixnum/src/lib.rs +++ b/agb-fixnum/src/lib.rs @@ -587,7 +587,7 @@ impl From> for Vector2 } } -#[derive(PartialEq, Eq, Clone)] +#[derive(Debug, PartialEq, Eq, Clone)] pub struct Rect { pub position: Vector2D, pub size: Vector2D, @@ -653,19 +653,21 @@ impl Rect { impl Rect { pub fn iter(self) -> impl Iterator { - let mut x = self.position.x - T::one(); + let mut x = self.position.x; let mut y = self.position.y; core::iter::from_fn(move || { - x = x + T::one(); - if x > self.position.x + self.size.x { + if x >= self.position.x + self.size.x { x = self.position.x; y = y + T::one(); - if y > self.position.y + self.size.y { + if y >= self.position.y + self.size.y { return None; } } - Some((x, y)) + let ret_x = x; + x = x + T::one(); + + Some((ret_x, y)) }) } } diff --git a/agb/examples/chicken.rs b/agb/examples/chicken.rs index df868d21..630dd162 100644 --- a/agb/examples/chicken.rs +++ b/agb/examples/chicken.rs @@ -2,8 +2,8 @@ #![no_main] use agb::{ + display::tiled::{TileFormat, TileSet, TileSetting}, display::{ - background::Map, object::{Object, ObjectController, Size, Sprite}, palette16::Palette16, HEIGHT, WIDTH, @@ -47,15 +47,26 @@ fn main(mut gba: agb::Gba) -> ! { .unwrap() }; - let mut gfx = gba.display.video.tiled0(); + let (gfx, mut vram) = gba.display.video.tiled0(); let vblank = agb::interrupt::VBlank::get(); let mut input = agb::input::ButtonController::new(); - gfx.set_background_palette_raw(&MAP_PALETTE); - gfx.set_background_tilemap(0, &MAP_TILES); + vram.set_background_palette_raw(&MAP_PALETTE); + let tileset = TileSet::new(&MAP_TILES, TileFormat::FourBpp); + let tileset_ref = vram.add_tileset(tileset); + + let mut background = gfx.background(agb::display::Priority::P0); + + for (i, &tile) in MAP_MAP.iter().enumerate() { + let i = i as u16; + background.set_tile( + &mut vram, + (i % 32, i / 32).into(), + tileset_ref, + TileSetting::from_raw(tile), + ); + } - let mut background = gfx.get_regular().unwrap(); - background.set_map(Map::new(&MAP_MAP, (32_u32, 32_u32).into(), 0)); background.show(); background.commit(); diff --git a/agb/examples/test_logo.rs b/agb/examples/test_logo.rs index 8cfc8164..674867c9 100644 --- a/agb/examples/test_logo.rs +++ b/agb/examples/test_logo.rs @@ -5,9 +5,11 @@ use agb::display::example_logo; #[agb::entry] fn main(mut gba: agb::Gba) -> ! { - let mut gfx = gba.display.video.tiled0(); + let (gfx, mut vram) = gba.display.video.tiled0(); - example_logo::display_logo(&mut gfx); + let mut map = gfx.background(agb::display::Priority::P0); + + example_logo::display_logo(&mut map, &mut vram); loop {} } diff --git a/agb/examples/wave.rs b/agb/examples/wave.rs index 9036eb9a..fd513367 100644 --- a/agb/examples/wave.rs +++ b/agb/examples/wave.rs @@ -17,9 +17,11 @@ struct BackCosines { #[agb::entry] fn main(mut gba: agb::Gba) -> ! { - let mut gfx = gba.display.video.tiled0(); + let (gfx, mut vram) = gba.display.video.tiled0(); - example_logo::display_logo(&mut gfx); + let mut background = gfx.background(agb::display::Priority::P0); + + example_logo::display_logo(&mut background, &mut vram); let mut time = 0; let cosines = [0_u16; 32]; diff --git a/agb/src/bitarray.rs b/agb/src/bitarray.rs index 25c99e16..f953ac15 100644 --- a/agb/src/bitarray.rs +++ b/agb/src/bitarray.rs @@ -1,3 +1,4 @@ +#[derive(Debug)] pub struct Bitarray { a: [u32; N], } @@ -21,7 +22,26 @@ impl Bitarray { let value_mask = value << (index % 32); self.a[index / 32] = self.a[index / 32] & !mask | value_mask } + + pub fn first_zero(&self) -> Option { + for index in 0..N * 32 { + if let Some(bit) = self.get(index) { + if !bit { + return Some(index); + } + } + } + + None + } } + +impl Default for Bitarray { + fn default() -> Self { + Self::new() + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/agb/src/display/background.rs b/agb/src/display/background.rs deleted file mode 100644 index 4f6ab336..00000000 --- a/agb/src/display/background.rs +++ /dev/null @@ -1,484 +0,0 @@ -use core::ops::Index; - -use crate::{ - fixnum::{Rect, Vector2D}, - memory_mapped::{MemoryMapped, MemoryMapped1DArray}, -}; - -use super::{ - palette16, set_graphics_mode, set_graphics_settings, DisplayMode, GraphicsSettings, Priority, - DISPLAY_CONTROL, -}; - -const PALETTE_BACKGROUND: MemoryMapped1DArray = - unsafe { MemoryMapped1DArray::new(0x0500_0000) }; - -const TILE_BACKGROUND: MemoryMapped1DArray = - unsafe { MemoryMapped1DArray::new(0x06000000) }; - -const MAP: *mut [[[u16; 32]; 32]; 32] = 0x0600_0000 as *mut _; - -pub enum ColourMode { - FourBitPerPixel = 0, - EightBitPerPixel = 1, -} - -#[derive(Clone, Copy)] -pub enum BackgroundSize { - S32x32 = 0, - S64x32 = 1, - S32x64 = 2, - S64x64 = 3, -} - -#[derive(PartialEq, Eq, Clone, Copy)] -enum Mutability { - Immutable, - Mutable, -} - -struct MapStorage<'a> { - s: *const [u16], - mutability: Mutability, - _phantom: core::marker::PhantomData<&'a ()>, -} - -impl<'a> Index for MapStorage<'a> { - type Output = u16; - fn index(&self, index: usize) -> &Self::Output { - &self.get()[index] - } -} - -impl<'a> MapStorage<'a> { - fn new(store: &[u16]) -> MapStorage { - MapStorage { - s: store as *const _, - mutability: Mutability::Immutable, - _phantom: core::marker::PhantomData, - } - } - fn new_mutable(store: &mut [u16]) -> MapStorage { - MapStorage { - s: store as *const _, - mutability: Mutability::Mutable, - _phantom: core::marker::PhantomData, - } - } - fn get(&self) -> &[u16] { - unsafe { &*self.s } - } - fn get_mut(&mut self) -> &mut [u16] { - assert!( - self.mutability == Mutability::Mutable, - "backing storage must be mutable in order to get internal storage mutably" - ); - unsafe { &mut *(self.s as *mut _) } - } -} - -/// The map background is the method of drawing game maps to the screen. It -/// automatically handles copying the correct portion of a provided map to the -/// assigned block depending on given coordinates. -#[allow(dead_code)] -pub struct BackgroundRegular<'a> { - register: BackgroundRegister, - commited_position: Vector2D, - shadowed_position: Vector2D, - poisoned: bool, - copy_size: Vector2D, - map: Option>, -} - -pub struct Map<'a> { - store: MapStorage<'a>, - pub dimensions: Vector2D, - pub default: u16, -} - -impl<'a> Map<'a> { - pub fn new(map: &[u16], dimensions: Vector2D, default: u16) -> Map { - Map { - store: MapStorage::new(map), - dimensions, - default, - } - } - pub fn new_mutable(map: &mut [u16], dimensions: Vector2D, default: u16) -> Map { - Map { - store: MapStorage::new_mutable(map), - dimensions, - default, - } - } - fn get_position(&self, x: i32, y: i32) -> u16 { - if x < 0 || x as u32 >= self.dimensions.x || y < 0 || y as u32 >= self.dimensions.y { - self.default - } else { - self.store[y as usize * self.dimensions.x as usize + x as usize] - } - } - pub fn get_store(&self) -> &[u16] { - self.store.get() - } - pub fn get_mutable_store(&mut self) -> &mut [u16] { - self.store.get_mut() - } -} - -pub struct BackgroundRegister { - background: u8, - block: u8, - shadowed_register: u16, -} - -impl<'a> BackgroundRegister { - unsafe fn new(background: u8, block: u8, background_size: BackgroundSize) -> Self { - let mut b = Self { - background, - block, - shadowed_register: 0, - }; - b.set_block(block); - b.set_colour_mode(ColourMode::FourBitPerPixel); - b.set_background_size(background_size); - b.write_register(); - b - } - - /// Sets the background to be shown on screen. Requires the background to - /// have a map enabled otherwise a panic is caused. - pub fn show(&mut self) { - let mode = DISPLAY_CONTROL.get(); - let new_mode = mode | (1 << (self.background + 0x08)); - DISPLAY_CONTROL.set(new_mode); - } - - /// Hides the background, nothing from this background is rendered to screen. - pub fn hide(&mut self) { - let mode = DISPLAY_CONTROL.get(); - let new_mode = mode & !(1 << (self.background + 0x08)); - DISPLAY_CONTROL.set(new_mode); - } - - pub fn set_priority(&mut self, p: Priority) { - unsafe { self.set_shadowed_register_bits(p as u16, 0x2, 0x0) }; - } - - unsafe fn set_shadowed_register_bits(&mut self, value: u16, length: u16, shift: u16) { - let mask = !(((1 << length) - 1) << shift); - let new = (self.shadowed_register & mask) | (value << shift); - self.shadowed_register = new; - } - - pub fn write_register(&self) { - unsafe { self.get_register().set(self.shadowed_register) }; - } - - unsafe fn get_register(&self) -> MemoryMapped { - MemoryMapped::new(0x0400_0008 + 2 * self.background as usize) - } - - unsafe fn set_block(&mut self, block: u8) { - self.set_shadowed_register_bits(block as u16, 5, 0x8); - } - - unsafe fn set_colour_mode(&mut self, mode: ColourMode) { - self.set_shadowed_register_bits(mode as u16, 0x1, 0x7); - } - - unsafe fn set_background_size(&mut self, size: BackgroundSize) { - self.set_shadowed_register_bits(size as u16, 0x2, 0xE); - } - - unsafe fn set_position_x_register(&self, x: u16) { - *((0x0400_0010 + 4 * self.background as usize) as *mut u16) = x - } - unsafe fn set_position_y_register(&self, y: u16) { - *((0x0400_0012 + 4 * self.background as usize) as *mut u16) = y - } - - pub fn set_position(&self, position: Vector2D) { - unsafe { - self.set_position_x_register((position.x % (32 * 8)) as u16); - self.set_position_y_register((position.y % (32 * 8)) as u16); - } - } - - pub fn get_block(&mut self) -> &mut [[u16; 32]; 32] { - unsafe { &mut (*MAP)[self.block as usize] } - } - - pub fn clear_partial(&'a mut self, tile: u16) -> impl Iterator + 'a { - self.get_block() - .iter_mut() - .flatten() - .map(move |t| unsafe { (t as *mut u16).write_volatile(tile) }) - } - - pub fn clear(&mut self, tile: u16) { - self.clear_partial(tile).count(); - } -} - -impl<'a, 'b> BackgroundRegular<'a> { - unsafe fn new( - background: u8, - block: u8, - background_size: BackgroundSize, - ) -> BackgroundRegular<'a> { - BackgroundRegular { - register: BackgroundRegister::new(background, block, background_size), - commited_position: (0, 0).into(), - shadowed_position: (0, 0).into(), - copy_size: (30_u16, 20_u16).into(), - poisoned: true, - map: None, - } - } - - /// Sets the background to be shown on screen. Requires the background to - /// have a map enabled otherwise a panic is caused. - pub fn show(&mut self) { - assert!(self.map.is_some()); - self.register.show(); - } - - /// Hides the background, nothing from this background is rendered to screen. - pub fn hide(&mut self) { - self.register.hide(); - } - - pub fn set_priority(&mut self, p: Priority) { - self.register.set_priority(p); - } - - pub fn set_position(&mut self, position: Vector2D) { - self.shadowed_position = position; - } - - pub fn get_map(&mut self) -> Option<&mut Map<'a>> { - self.poisoned = true; - self.map.as_mut() - } - - pub fn set_map(&mut self, map: Map<'a>) { - self.poisoned = true; - self.map = Some(map); - } - - pub fn commit_partial(&'b mut self) -> impl Iterator + 'b { - // commit shadowed register - self.register.write_register(); - - let map = self.map.as_ref().unwrap(); - - let commited_screen = Rect::new(self.commited_position, self.copy_size.change_base()); - let shadowed_screen = Rect::new(self.shadowed_position, self.copy_size.change_base()); - - let iter = if self.poisoned || !shadowed_screen.touches(commited_screen) { - let positions_to_be_updated = Rect::new( - self.shadowed_position / 8 - (1, 1).into(), - self.copy_size.change_base() + (1, 1).into(), - ) - .iter(); - - positions_to_be_updated.chain(Rect::new((0, 0).into(), (0, 0).into()).iter()) - } else { - let commited_block = self.commited_position / 8; - let shadowed_block = self.shadowed_position / 8; - - let top_bottom_rect: Rect = { - let top_bottom_height = commited_block.y - shadowed_block.y; - let new_y = if top_bottom_height < 0 { - commited_block.y + self.copy_size.y as i32 - } else { - shadowed_block.y - 1 - }; - Rect::new( - (shadowed_block.x - 1, new_y).into(), - (32, top_bottom_height.abs()).into(), - ) - }; - - let left_right_rect: Rect = { - let left_right_width = commited_block.x - shadowed_block.x; - let new_x = if left_right_width < 0 { - commited_block.x + self.copy_size.x as i32 - } else { - shadowed_block.x - 1 - }; - Rect::new( - (new_x, shadowed_block.y - 1).into(), - (left_right_width.abs(), 22).into(), - ) - }; - - top_bottom_rect.iter().chain(left_right_rect.iter()) - }; - - // update commited position - - self.commited_position = self.shadowed_position; - - self.poisoned = false; - - // update position in registers - - self.register.set_position(self.commited_position); - let block = self.register.get_block(); - iter.map(move |(x, y)| { - block[y.rem_euclid(32) as usize][x.rem_euclid(32) as usize] = map.get_position(x, y) - }) - } - - pub fn commit(&mut self) { - self.commit_partial().count(); - } -} - -fn decide_background_mode(num_regular: u8, num_affine: u8) -> Option { - if num_affine == 0 && num_regular <= 4 { - Some(DisplayMode::Tiled0) - } else if num_affine == 1 && num_regular <= 2 { - Some(DisplayMode::Tiled1) - } else if num_affine == 2 && num_regular == 0 { - Some(DisplayMode::Tiled2) - } else { - None - } -} - -pub struct BackgroundDistributor { - used_blocks: u32, - num_regular: u8, - num_affine: u8, -} - -impl<'b> BackgroundDistributor { - pub(crate) unsafe fn new() -> Self { - set_graphics_settings(GraphicsSettings::empty() | GraphicsSettings::SPRITE1_D); - set_graphics_mode(DisplayMode::Tiled0); - BackgroundDistributor { - used_blocks: 0, - num_regular: 0, - num_affine: 0, - } - } - - fn set_background_tilemap_entry(&mut self, index: u32, data: u32) { - TILE_BACKGROUND.set(index as usize, data); - } - - /// Copies raw palettes to the background palette without any checks. - pub fn set_background_palette_raw(&mut self, palette: &[u16]) { - for (index, &colour) in palette.iter().enumerate() { - PALETTE_BACKGROUND.set(index, colour); - } - } - - fn set_background_palette(&mut self, pal_index: u8, palette: &palette16::Palette16) { - for (colour_index, &colour) in palette.colours.iter().enumerate() { - PALETTE_BACKGROUND.set(pal_index as usize * 16 + colour_index, colour); - } - } - - /// Copies palettes to the background palettes without any checks. - pub fn set_background_palettes(&mut self, palettes: &[palette16::Palette16]) { - for (palette_index, entry) in palettes.iter().enumerate() { - self.set_background_palette(palette_index as u8, entry) - } - } - - /// Gets a map background if possible and assigns an unused block to it. - pub fn get_regular(&mut self) -> Result, &'static str> { - let new_mode = decide_background_mode(self.num_regular + 1, self.num_affine) - .ok_or("there is no mode compatible with the requested backgrounds")?; - - unsafe { set_graphics_mode(new_mode) }; - - if !self.used_blocks == 0 { - return Err("all blocks are used"); - } - - let mut availiable_block = u8::MAX; - - for i in 0..32 { - if (1 << i) & self.used_blocks == 0 { - availiable_block = i; - break; - } - } - - assert!( - availiable_block != u8::MAX, - "should be able to find a block" - ); - - self.used_blocks |= 1 << availiable_block; - - let background = self.num_regular; - self.num_regular += 1; - Ok(unsafe { BackgroundRegular::new(background, availiable_block, BackgroundSize::S32x32) }) - } - - pub fn get_raw_regular(&mut self) -> Result { - let new_mode = decide_background_mode(self.num_regular + 1, self.num_affine) - .ok_or("there is no mode compatible with the requested backgrounds")?; - - unsafe { set_graphics_mode(new_mode) }; - - if !self.used_blocks == 0 { - return Err("all blocks are used"); - } - - let mut availiable_block = u8::MAX; - - for i in 0..32 { - if (1 << i) & self.used_blocks == 0 { - availiable_block = i; - break; - } - } - - assert!( - availiable_block != u8::MAX, - "should be able to find a block" - ); - - self.used_blocks |= 1 << availiable_block; - - let background = self.num_regular; - self.num_regular += 1; - Ok( - unsafe { - BackgroundRegister::new(background, availiable_block, BackgroundSize::S32x32) - }, - ) - } - - /// Copies tiles to tilemap starting at the starting tile. Cannot overwrite - /// blocks that are already written to, panic is caused if this is attempted. - pub fn set_background_tilemap(&mut self, start_tile: u32, tiles: &[u32]) { - let u32_per_block = 512; - - let start_block = (start_tile * 8) / u32_per_block; - // round up rather than down - let end_block = (start_tile * 8 + tiles.len() as u32 + u32_per_block - 1) / u32_per_block; - - let blocks_to_use: u32 = ((1 << (end_block - start_block)) - 1) << start_block; - - assert!( - self.used_blocks & blocks_to_use == 0, - "blocks {} to {} should be unused for this copy to succeed", - start_block, - end_block - ); - - self.used_blocks |= blocks_to_use; - - for (index, &tile) in tiles.iter().enumerate() { - self.set_background_tilemap_entry(start_tile * 8 + index as u32, tile) - } - } -} diff --git a/agb/src/display/example_logo.rs b/agb/src/display/example_logo.rs index 3e72ad5e..46db1afc 100644 --- a/agb/src/display/example_logo.rs +++ b/agb/src/display/example_logo.rs @@ -1,23 +1,31 @@ -use crate::display::background::BackgroundDistributor; +use super::tiled::{RegularMap, TileFormat, TileSet, TileSetting, VRamManager}; crate::include_gfx!("gfx/agb_logo.toml"); -pub fn display_logo(gfx: &mut BackgroundDistributor) { - use super::background::Map; - gfx.set_background_palettes(agb_logo::test_logo.palettes); - gfx.set_background_tilemap(0, agb_logo::test_logo.tiles); +pub fn display_logo(map: &mut RegularMap, vram: &mut VRamManager) { + vram.set_background_palettes(agb_logo::test_logo.palettes); - let mut back = gfx.get_regular().unwrap(); + let background_tilemap = TileSet::new(agb_logo::test_logo.tiles, TileFormat::FourBpp); + let background_tilemap_reference = vram.add_tileset(background_tilemap); - let mut entries: [u16; 30 * 20] = [0; 30 * 20]; - for tile_id in 0..(30 * 20) { - let palette_entry = agb_logo::test_logo.palette_assignments[tile_id as usize] as u16; - entries[tile_id as usize] = tile_id | (palette_entry << 12); + for y in 0..20 { + for x in 0..30 { + let tile_id = y * 30 + x; + + let palette_entry = agb_logo::test_logo.palette_assignments[tile_id as usize]; + let tile_setting = TileSetting::new(tile_id, false, false, palette_entry); + + map.set_tile( + vram, + (x, y).into(), + background_tilemap_reference, + tile_setting, + ); + } } - back.set_map(Map::new(&entries, (30_u32, 20_u32).into(), 0)); - back.show(); - back.commit(); + map.commit(); + map.show(); } #[cfg(test)] mod tests { @@ -25,9 +33,11 @@ mod tests { #[test_case] fn logo_display(gba: &mut crate::Gba) { - let mut gfx = gba.display.video.tiled0(); + let (gfx, mut vram) = gba.display.video.tiled0(); - display_logo(&mut gfx); + let mut map = gfx.background(crate::display::Priority::P0); + + display_logo(&mut map, &mut vram); crate::test_runner::assert_image_output("gfx/test_logo.png"); } diff --git a/agb/src/display/mod.rs b/agb/src/display/mod.rs index fff057bb..c18ec604 100644 --- a/agb/src/display/mod.rs +++ b/agb/src/display/mod.rs @@ -6,8 +6,6 @@ use video::Video; use self::object::ObjectController; -/// Graphics mode 0. Four regular backgrounds. -pub mod background; /// Graphics mode 3. Bitmap mode that provides a 16-bit colour framebuffer. pub mod bitmap3; /// Graphics mode 4. Bitmap 4 provides two 8-bit paletted framebuffers with page switching. @@ -20,6 +18,8 @@ pub mod object; pub mod palette16; /// Data produced by agb-image-converter pub mod tile_data; +/// Graphics mode 0. Four regular backgrounds. +pub mod tiled; /// Giving out graphics mode. pub mod video; @@ -110,7 +110,7 @@ pub fn busy_wait_for_vblank() { while VCOUNT.get() < 160 {} } -#[derive(BitfieldSpecifier)] +#[derive(BitfieldSpecifier, Clone, Copy)] pub enum Priority { P0 = 0, P1 = 1, diff --git a/agb/src/display/tiled/infinite_scrolled_map.rs b/agb/src/display/tiled/infinite_scrolled_map.rs new file mode 100644 index 00000000..01bce594 --- /dev/null +++ b/agb/src/display/tiled/infinite_scrolled_map.rs @@ -0,0 +1,236 @@ +use alloc::boxed::Box; + +use super::{MapLoan, RegularMap, TileSetReference, TileSetting, VRamManager}; + +use crate::{ + display, + fixnum::{Rect, Vector2D}, +}; + +pub struct InfiniteScrolledMap<'a> { + map: MapLoan<'a, RegularMap>, + get_tile: Box) -> (TileSetReference, TileSetting)>, + + current_pos: Vector2D, + offset: Vector2D, + + copied_up_to: i32, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum PartialUpdateStatus { + Done, + Continue, +} + +impl<'a> InfiniteScrolledMap<'a> { + pub fn new( + map: MapLoan<'a, RegularMap>, + get_tile: Box) -> (TileSetReference, TileSetting)>, + ) -> Self { + Self { + map, + get_tile, + current_pos: (0, 0).into(), + offset: (0, 0).into(), + copied_up_to: 0, + } + } + + pub fn init( + &mut self, + vram: &mut VRamManager, + pos: Vector2D, + between_updates: &mut impl FnMut(), + ) { + while self.init_partial(vram, pos) != PartialUpdateStatus::Done { + between_updates(); + } + } + + pub fn init_partial( + &mut self, + vram: &mut VRamManager, + pos: Vector2D, + ) -> PartialUpdateStatus { + self.current_pos = pos; + + let x_start = div_floor(self.current_pos.x, 8); + let y_start = div_floor(self.current_pos.y, 8); + + let x_end = div_ceil(self.current_pos.x + display::WIDTH, 8) + 1; + let y_end = div_ceil(self.current_pos.y + display::HEIGHT, 8) + 1; + + 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, + ) + .into(); + + self.map.set_scroll_pos(offset_scroll); + self.offset = (x_start, y_start).into(); + + let copy_from = self.copied_up_to; + const ROWS_TO_COPY: i32 = 2; + + for (y_idx, y) in + ((y_start + copy_from)..(y_end.min(y_start + copy_from + ROWS_TO_COPY))).enumerate() + { + for (x_idx, x) in (x_start..x_end).enumerate() { + let pos = (x, y).into(); + let (tile_set_ref, tile_setting) = (self.get_tile)(pos); + + self.map.set_tile( + vram, + (x_idx as u16, (y_idx + copy_from as usize) as u16).into(), + tile_set_ref, + tile_setting, + ); + } + } + + if copy_from + ROWS_TO_COPY >= y_end - y_start { + self.copied_up_to = 0; + PartialUpdateStatus::Done + } else { + self.copied_up_to = copy_from + ROWS_TO_COPY; + PartialUpdateStatus::Continue + } + } + + pub fn set_pos( + &mut self, + vram: &mut VRamManager, + new_pos: Vector2D, + ) -> PartialUpdateStatus { + let old_pos = self.current_pos; + + let difference = new_pos - old_pos; + + if difference.x.abs() > 10 * 8 || difference.y.abs() > 10 * 8 { + return self.init_partial(vram, new_pos); + } + + self.current_pos = new_pos; + + let new_tile_x = div_floor(new_pos.x, 8); + let new_tile_y = div_floor(new_pos.y, 8); + + let difference_tile_x = div_ceil(difference.x, 8); + let difference_tile_y = div_ceil(difference.y, 8); + + 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 + let direction = difference.x.signum(); + + // either need to update 20 or 21 tiles depending on whether the y coordinate is a perfect multiple + let y_tiles_to_update = 22; + + let line_to_update = if direction < 0 { + // moving to the left, so need to update the left most position + new_tile_x + } else { + // moving to the right, so need to update the right most position + new_tile_x + 30 // TODO is this correct? + }; + + Rect::new( + (line_to_update, new_tile_y - 1).into(), + (difference_tile_x, y_tiles_to_update).into(), + ) + } else { + Rect::new((0i32, 0).into(), (0i32, 0).into()) + }; + + let horizontal_rect_to_update: Rect = if div_floor(old_pos.y, 8) != new_tile_y { + // need to update the y line + // calculate which direction we need to update + let direction = difference.y.signum(); + + // either need to update 30 or 31 tiles depending on whether the x coordinate is a perfect multiple + let x_tiles_to_update: i32 = 32; + + let line_to_update = if direction < 0 { + // moving up so need to update the top + new_tile_y + } else { + // moving down so need to update the bottom + new_tile_y + 20 // TODO is this correct? + }; + + Rect::new( + (new_tile_x - 1, line_to_update).into(), + (x_tiles_to_update, difference_tile_y).into(), + ) + } else { + Rect::new((0i32, 0).into(), (0i32, 0).into()) + }; + + for (tile_x, tile_y) in vertical_rect_to_update + .iter() + .chain(horizontal_rect_to_update.iter()) + { + let (tile_set_ref, tile_setting) = (self.get_tile)((tile_x, tile_y).into()); + + 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, + ) + .into(), + tile_set_ref, + tile_setting, + ); + } + + let current_scroll = self.map.get_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, + ) + .into(); + + self.map.set_scroll_pos(new_scroll); + + PartialUpdateStatus::Done + } + + pub fn show(&mut self) { + self.map.show(); + } + + pub fn hide(&mut self) { + self.map.hide(); + } + + pub fn commit(&mut self) { + self.map.commit(); + } + + pub fn clear(&mut self, vram: &mut VRamManager) { + self.map.clear(vram); + } +} + +fn div_floor(x: i32, y: i32) -> i32 { + if x > 0 && y < 0 { + (x - 1) / y - 1 + } else if x < 0 && y > 0 { + (x + 1) / y - 1 + } else { + x / y + } +} + +fn div_ceil(x: i32, y: i32) -> i32 { + if x > 0 && y > 0 { + (x - 1) / y + 1 + } else if x < 0 && y < 0 { + (x + 1) / y + 1 + } else { + x / y + } +} diff --git a/agb/src/display/tiled/map.rs b/agb/src/display/tiled/map.rs new file mode 100644 index 00000000..f86f8049 --- /dev/null +++ b/agb/src/display/tiled/map.rs @@ -0,0 +1,185 @@ +use core::cell::RefCell; +use core::ops::{Deref, DerefMut}; + +use crate::bitarray::Bitarray; +use crate::display::{Priority, DISPLAY_CONTROL}; +use crate::dma::dma_copy; +use crate::fixnum::Vector2D; +use crate::memory_mapped::MemoryMapped; + +use super::{Tile, TileSetReference, TileSetting, VRamManager}; + +pub struct RegularMap { + background_id: u8, + + screenblock: u8, + x_scroll: u16, + y_scroll: u16, + priority: Priority, + + tiles: [Tile; 32 * 32], + tiles_dirty: bool, +} + +pub const TRANSPARENT_TILE_INDEX: u16 = (1 << 10) - 1; + +impl RegularMap { + pub(crate) fn new(background_id: u8, screenblock: u8, priority: Priority) -> Self { + Self { + background_id, + + screenblock, + x_scroll: 0, + y_scroll: 0, + priority, + + tiles: [Tile::default(); 32 * 32], + tiles_dirty: true, + } + } + + pub fn set_tile( + &mut self, + vram: &mut VRamManager, + pos: Vector2D, + tileset_ref: TileSetReference, + tile_setting: TileSetting, + ) { + let pos = (pos.x + pos.y * 32) as usize; + + let old_tile = self.tiles[pos]; + if old_tile != Tile::default() { + vram.remove_tile(old_tile.tile_index()); + } + + let tile_index = tile_setting.index(); + + let new_tile = if tile_index != TRANSPARENT_TILE_INDEX { + let new_tile_idx = vram.add_tile(tileset_ref, tile_index); + Tile::new(new_tile_idx, tile_setting) + } else { + Tile::default() + }; + + if old_tile == new_tile { + // no need to mark as dirty if nothing changes + return; + } + + self.tiles[pos] = new_tile; + self.tiles_dirty = true; + } + + pub fn clear(&mut self, vram: &mut VRamManager) { + for tile in self.tiles.iter_mut() { + if *tile != Tile::default() { + vram.remove_tile(tile.tile_index()); + } + + *tile = Tile::default(); + } + } + + pub fn show(&mut self) { + let mode = DISPLAY_CONTROL.get(); + let new_mode = mode | (1 << (self.background_id + 0x08)); + DISPLAY_CONTROL.set(new_mode); + } + + pub fn hide(&mut self) { + let mode = DISPLAY_CONTROL.get(); + let new_mode = mode & !(1 << (self.background_id + 0x08)); + DISPLAY_CONTROL.set(new_mode); + } + + pub fn commit(&mut self) { + let new_bg_control_value = (self.priority as u16) | ((self.screenblock as u16) << 8); + + self.bg_control_register().set(new_bg_control_value); + self.bg_h_offset().set(self.x_scroll); + self.bg_v_offset().set(self.y_scroll); + + if !self.tiles_dirty { + return; + } + + let screenblock_memory = self.screenblock_memory(); + + unsafe { + dma_copy( + self.tiles.as_ptr() as *const u16, + screenblock_memory, + 32 * 32, + ); + } + + self.tiles_dirty = false; + } + + pub fn set_scroll_pos(&mut self, pos: Vector2D) { + self.x_scroll = pos.x; + self.y_scroll = pos.y; + } + + pub fn get_scroll_pos(&self) -> Vector2D { + (self.x_scroll, self.y_scroll).into() + } + + const fn bg_control_register(&self) -> MemoryMapped { + unsafe { MemoryMapped::new(0x0400_0008 + 2 * self.background_id as usize) } + } + + const fn bg_h_offset(&self) -> MemoryMapped { + unsafe { MemoryMapped::new(0x0400_0010 + 4 * self.background_id as usize) } + } + + const fn bg_v_offset(&self) -> MemoryMapped { + unsafe { MemoryMapped::new(0x0400_0012 + 4 * self.background_id as usize) } + } + + const fn screenblock_memory(&self) -> *mut u16 { + (0x0600_0000 + 0x1000 * self.screenblock as usize / 2) as *mut u16 + } +} + +pub struct MapLoan<'a, T> { + map: T, + background_id: u8, + regular_map_list: &'a RefCell>, +} + +impl<'a, T> Deref for MapLoan<'a, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.map + } +} + +impl<'a, T> DerefMut for MapLoan<'a, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.map + } +} + +impl<'a, T> MapLoan<'a, T> { + pub(crate) fn new( + map: T, + background_id: u8, + regular_map_list: &'a RefCell>, + ) -> Self { + MapLoan { + map, + background_id, + regular_map_list, + } + } +} + +impl<'a, T> Drop for MapLoan<'a, T> { + fn drop(&mut self) { + self.regular_map_list + .borrow_mut() + .set(self.background_id as usize, false); + } +} diff --git a/agb/src/display/tiled/mod.rs b/agb/src/display/tiled/mod.rs new file mode 100644 index 00000000..0e2e042a --- /dev/null +++ b/agb/src/display/tiled/mod.rs @@ -0,0 +1,49 @@ +mod infinite_scrolled_map; +mod map; +mod tiled0; +mod vram_manager; + +pub use infinite_scrolled_map::{InfiniteScrolledMap, PartialUpdateStatus}; +pub use map::{MapLoan, RegularMap}; +pub use tiled0::Tiled0; +pub use vram_manager::{TileFormat, TileIndex, TileSet, TileSetReference, VRamManager}; + +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +#[repr(transparent)] +struct Tile(u16); + +impl Tile { + fn new(idx: TileIndex, setting: TileSetting) -> Self { + Self(idx.index() | setting.setting()) + } + + fn tile_index(self) -> TileIndex { + TileIndex::new(self.0 as usize & ((1 << 10) - 1)) + } +} + +#[derive(Clone, Copy, Debug, Default)] +pub struct TileSetting(u16); + +impl TileSetting { + pub const fn new(tile_id: u16, hflip: bool, vflip: bool, palette_id: u8) -> Self { + Self( + (tile_id & ((1 << 10) - 1)) + | ((hflip as u16) << 10) + | ((vflip as u16) << 11) + | ((palette_id as u16) << 12), + ) + } + + pub const fn from_raw(raw: u16) -> Self { + Self(raw) + } + + fn index(self) -> u16 { + self.0 & ((1 << 10) - 1) + } + + fn setting(self) -> u16 { + self.0 & !((1 << 10) - 1) + } +} diff --git a/agb/src/display/tiled/tiled0.rs b/agb/src/display/tiled/tiled0.rs new file mode 100644 index 00000000..86b73876 --- /dev/null +++ b/agb/src/display/tiled/tiled0.rs @@ -0,0 +1,37 @@ +use core::cell::RefCell; + +use crate::{ + bitarray::Bitarray, + display::{set_graphics_mode, set_graphics_settings, DisplayMode, GraphicsSettings, Priority}, +}; + +use super::{MapLoan, RegularMap}; + +pub struct Tiled0 { + regular: RefCell>, +} + +impl Tiled0 { + pub(crate) unsafe fn new() -> Self { + set_graphics_settings(GraphicsSettings::empty() | GraphicsSettings::SPRITE1_D); + set_graphics_mode(DisplayMode::Tiled0); + + Self { + regular: Default::default(), + } + } + + pub fn background(&self, priority: Priority) -> 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); + + regular.set(new_background, true); + + MapLoan::new(bg, new_background as u8, &self.regular) + } +} diff --git a/agb/src/display/tiled/vram_manager.rs b/agb/src/display/tiled/vram_manager.rs new file mode 100644 index 00000000..76be9999 --- /dev/null +++ b/agb/src/display/tiled/vram_manager.rs @@ -0,0 +1,280 @@ +use core::{alloc::Layout, ptr::NonNull}; + +use alloc::vec; +use alloc::vec::Vec; + +use crate::{ + agb_alloc::{block_allocator::BlockAllocator, bump_allocator::StartEnd}, + display::palette16, + dma::dma_copy, + memory_mapped::MemoryMapped1DArray, +}; + +const TILE_RAM_START: usize = 0x0600_0000; + +const PALETTE_BACKGROUND: MemoryMapped1DArray = + unsafe { MemoryMapped1DArray::new(0x0500_0000) }; + +static TILE_ALLOCATOR: BlockAllocator = unsafe { + BlockAllocator::new(StartEnd { + start: || TILE_RAM_START, + end: || TILE_RAM_START + 0x8000, + }) +}; + +const TILE_LAYOUT: Layout = unsafe { Layout::from_size_align_unchecked(8 * 8 / 2, 8 * 8 / 2) }; + +#[cfg(debug_assertions)] +unsafe fn debug_unreachable_unchecked(message: &'static str) -> ! { + unreachable!("{}", message); +} + +#[cfg(not(debug_assertions))] +const unsafe fn debug_unreachable_unchecked(_message: &'static str) -> ! { + use core::hint::unreachable_unchecked; + + unreachable_unchecked(); +} + +#[derive(Clone, Copy, Debug)] +pub enum TileFormat { + FourBpp, +} + +impl TileFormat { + /// Returns the size of the tile in bytes + fn tile_size(self) -> usize { + match self { + TileFormat::FourBpp => 8 * 8 / 2, + } + } +} + +pub struct TileSet<'a> { + tiles: &'a [u32], + format: TileFormat, +} + +impl<'a> TileSet<'a> { + pub fn new(tiles: &'a [u32], format: TileFormat) -> Self { + Self { tiles, format } + } + + fn num_tiles(&self) -> usize { + self.tiles.len() / self.format.tile_size() * 4 + } +} + +#[derive(Clone, Copy, PartialEq, Eq)] +pub struct TileSetReference { + id: u16, + generation: u16, +} + +impl TileSetReference { + fn new(id: u16, generation: u16) -> Self { + Self { id, generation } + } +} + +#[derive(Debug)] +pub struct TileIndex(u16); + +impl TileIndex { + pub(crate) const fn new(index: usize) -> Self { + Self(index as u16) + } + + pub(crate) const fn index(&self) -> u16 { + self.0 + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +struct TileReference(NonNull); + +enum ArenaStorageItem { + EndOfFreeList, + NextFree(usize), + Data(T, u16), +} + +pub struct VRamManager<'a> { + tilesets: Vec>>, + generation: u16, + free_pointer: Option, + + tile_set_to_vram: Vec>>, + reference_counts: Vec<(u16, Option<(TileSetReference, u16)>)>, +} + +impl<'a> VRamManager<'a> { + pub fn new() -> Self { + Self { + tilesets: Vec::new(), + generation: 0, + free_pointer: None, + + tile_set_to_vram: Default::default(), + reference_counts: Default::default(), + } + } + + pub fn add_tileset(&mut self, tileset: TileSet<'a>) -> TileSetReference { + let generation = self.generation; + self.generation = self.generation.wrapping_add(1); + + let num_tiles = tileset.num_tiles(); + let tileset = ArenaStorageItem::Data(tileset, generation); + + let index = if let Some(ptr) = self.free_pointer.take() { + match self.tilesets[ptr] { + ArenaStorageItem::EndOfFreeList => { + self.tilesets[ptr] = tileset; + ptr + } + ArenaStorageItem::NextFree(next_free) => { + self.free_pointer = Some(next_free); + self.tilesets[ptr] = tileset; + ptr + } + _ => unsafe { debug_unreachable_unchecked("Free pointer cannot point to data") }, + } + } else { + self.tilesets.push(tileset); + self.tilesets.len() - 1 + }; + + self.tile_set_to_vram + .resize(self.tilesets.len(), Default::default()); + self.tile_set_to_vram[index] = vec![Default::default(); num_tiles]; + + TileSetReference::new(index as u16, generation) + } + + pub fn remove_tileset(&mut self, tile_set_ref: TileSetReference) { + let tileset = &self.tilesets[tile_set_ref.id as usize]; + + match tileset { + ArenaStorageItem::Data(_, generation) => { + debug_assert_eq!( + *generation, tile_set_ref.generation, + "Tileset generation must be the same when removing" + ); + + self.tilesets[tile_set_ref.id as usize] = if let Some(ptr) = self.free_pointer { + ArenaStorageItem::NextFree(ptr) + } else { + ArenaStorageItem::EndOfFreeList + }; + + self.free_pointer = Some(tile_set_ref.id as usize); + } + _ => panic!("Must remove valid tileset"), + } + } + + fn index_from_reference(reference: TileReference) -> usize { + let difference = reference.0.as_ptr() as usize - TILE_RAM_START; + difference / (8 * 8 / 2) + } + + fn reference_from_index(index: TileIndex) -> TileReference { + let ptr = (index.index() * (8 * 8 / 2)) as usize + TILE_RAM_START; + TileReference(NonNull::new(ptr as *mut _).unwrap()) + } + + pub(crate) fn add_tile(&mut self, tile_set_ref: TileSetReference, tile: u16) -> TileIndex { + let reference = self.tile_set_to_vram[tile_set_ref.id as usize][tile as usize]; + + if let Some(reference) = reference { + let index = Self::index_from_reference(reference); + self.reference_counts[index].0 += 1; + return TileIndex::new(index); + } + + let new_reference: NonNull = + unsafe { TILE_ALLOCATOR.alloc(TILE_LAYOUT) }.unwrap().cast(); + + let tile_slice = if let ArenaStorageItem::Data(data, generation) = + &self.tilesets[tile_set_ref.id as usize] + { + debug_assert_eq!( + *generation, tile_set_ref.generation, + "Stale tile data requested" + ); + + let tile_offset = (tile as usize) * data.format.tile_size() / 4; + &data.tiles[tile_offset..(tile_offset + data.format.tile_size() / 4)] + } else { + panic!("Tile set ref must point to existing tile set"); + }; + + let tile_size_in_half_words = TileFormat::FourBpp.tile_size() / 2; + + unsafe { + dma_copy( + tile_slice.as_ptr() as *const u16, + new_reference.as_ptr() as *mut u16, + tile_size_in_half_words, + ); + } + + let tile_reference = TileReference(new_reference); + + let index = Self::index_from_reference(tile_reference); + + self.tile_set_to_vram[tile_set_ref.id as usize][tile as usize] = Some(tile_reference); + + self.reference_counts + .resize(self.reference_counts.len().max(index + 1), (0, None)); + + self.reference_counts[index] = (1, Some((tile_set_ref, tile))); + + TileIndex::new(index) + } + + pub(crate) fn remove_tile(&mut self, tile_index: TileIndex) { + let index = tile_index.index() as usize; + assert!( + self.reference_counts[index].0 > 0, + "Trying to decrease the reference count of {} below 0", + index + ); + + self.reference_counts[index].0 -= 1; + + if self.reference_counts[index].0 != 0 { + return; + } + + let tile_reference = Self::reference_from_index(tile_index); + unsafe { + TILE_ALLOCATOR.dealloc(tile_reference.0.cast().as_ptr(), TILE_LAYOUT); + } + + let tile_ref = self.reference_counts[index].1.unwrap(); + self.tile_set_to_vram[tile_ref.0.id as usize][tile_ref.1 as usize] = None; + self.reference_counts[index].1 = None; + } + + /// Copies raw palettes to the background palette without any checks. + pub fn set_background_palette_raw(&mut self, palette: &[u16]) { + for (index, &colour) in palette.iter().enumerate() { + PALETTE_BACKGROUND.set(index, colour); + } + } + + fn set_background_palette(&mut self, pal_index: u8, palette: &palette16::Palette16) { + for (colour_index, &colour) in palette.colours.iter().enumerate() { + PALETTE_BACKGROUND.set(pal_index as usize * 16 + colour_index, colour); + } + } + + /// Copies palettes to the background palettes without any checks. + pub fn set_background_palettes(&mut self, palettes: &[palette16::Palette16]) { + for (palette_index, entry) in palettes.iter().enumerate() { + self.set_background_palette(palette_index as u8, entry) + } + } +} diff --git a/agb/src/display/video.rs b/agb/src/display/video.rs index 2652b8a2..046ff12c 100644 --- a/agb/src/display/video.rs +++ b/agb/src/display/video.rs @@ -1,4 +1,8 @@ -use super::{background::BackgroundDistributor, bitmap3::Bitmap3, bitmap4::Bitmap4}; +use super::{ + bitmap3::Bitmap3, + bitmap4::Bitmap4, + tiled::{Tiled0, VRamManager}, +}; #[non_exhaustive] pub struct Video {} @@ -14,7 +18,7 @@ impl Video { unsafe { Bitmap4::new() } } - pub fn tiled0(&mut self) -> BackgroundDistributor { - unsafe { BackgroundDistributor::new() } + pub fn tiled0(&mut self) -> (Tiled0, VRamManager<'_>) { + (unsafe { Tiled0::new() }, VRamManager::new()) } } diff --git a/agb/src/dma.rs b/agb/src/dma.rs new file mode 100644 index 00000000..a7c9c6d0 --- /dev/null +++ b/agb/src/dma.rs @@ -0,0 +1,26 @@ +use crate::memory_mapped::MemoryMapped; + +const fn dma_source_addr(dma: usize) -> usize { + 0x0400_00b0 + 0x0c * dma +} + +const fn dma_dest_addr(dma: usize) -> usize { + 0x0400_00b4 + 0x0c * dma +} + +const fn dma_control_addr(dma: usize) -> usize { + 0x0400_00b8 + 0x0c * dma +} + +const DMA3_SOURCE_ADDR: MemoryMapped = unsafe { MemoryMapped::new(dma_source_addr(3)) }; +const DMA3_DEST_ADDR: MemoryMapped = unsafe { MemoryMapped::new(dma_dest_addr(3)) }; +const DMA3_CONTROL: MemoryMapped = unsafe { MemoryMapped::new(dma_control_addr(3)) }; + +pub(crate) unsafe fn dma_copy(src: *const u16, dest: *mut u16, count: usize) { + assert!(count < u16::MAX as usize); + + DMA3_SOURCE_ADDR.set(src as u32); + DMA3_DEST_ADDR.set(dest as u32); + + DMA3_CONTROL.set(count as u32 | (1 << 31)); +} diff --git a/agb/src/lib.rs b/agb/src/lib.rs index 075b776a..9eb98eea 100644 --- a/agb/src/lib.rs +++ b/agb/src/lib.rs @@ -142,6 +142,7 @@ mod arena; mod bitarray; /// Implements everything relating to things that are displayed on screen. pub mod display; +mod dma; /// Button inputs to the system. pub mod input; #[doc(hidden)] // hide for now as the implementation in here is unsound diff --git a/agb/src/memory_mapped.rs b/agb/src/memory_mapped.rs index 730de380..940f3d77 100644 --- a/agb/src/memory_mapped.rs +++ b/agb/src/memory_mapped.rs @@ -49,9 +49,11 @@ impl MemoryMapped1DArray { array: address as *mut [T; N], } } + pub fn get(&self, n: usize) -> T { unsafe { (&mut (*self.array)[n] as *mut T).read_volatile() } } + pub fn set(&self, n: usize, val: T) { unsafe { (&mut (*self.array)[n] as *mut T).write_volatile(val) } } diff --git a/examples/the-hat-chooses-the-wizard/Cargo.toml b/examples/the-hat-chooses-the-wizard/Cargo.toml index 0ec26446..691a4493 100644 --- a/examples/the-hat-chooses-the-wizard/Cargo.toml +++ b/examples/the-hat-chooses-the-wizard/Cargo.toml @@ -7,7 +7,7 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -agb = { version = "0.8.0", path = "../../agb", default-features = false } +agb = { version = "0.8.0", path = "../../agb" } [build-dependencies] serde = { version = "1.0", features = ["derive"] } diff --git a/examples/the-hat-chooses-the-wizard/src/level_display.rs b/examples/the-hat-chooses-the-wizard/src/level_display.rs index 86b5a362..867a5244 100644 --- a/examples/the-hat-chooses-the-wizard/src/level_display.rs +++ b/examples/the-hat-chooses-the-wizard/src/level_display.rs @@ -1,26 +1,45 @@ -use agb::display::{background::BackgroundRegister, HEIGHT, WIDTH}; +use agb::display::{ + tiled::{RegularMap, TileSetReference, TileSetting, VRamManager}, + HEIGHT, WIDTH, +}; const LEVEL_START: u16 = 12 * 28; const NUMBERS_START: u16 = 12 * 28 + 3; const HYPHEN: u16 = 12 * 28 + 11; pub const BLANK: u16 = 11 * 28; -pub fn write_level(background: &mut BackgroundRegister, world: u32, level: u32) { - let map = background.get_block(); - let mut counter = 0; +pub fn write_level( + map: &mut RegularMap, + world: u32, + level: u32, + tile_set_ref: TileSetReference, + vram: &mut VRamManager, +) { + for (i, &tile) in [ + LEVEL_START, + LEVEL_START + 1, + LEVEL_START + 2, + BLANK, + world as u16 + NUMBERS_START - 1, + HYPHEN, + level as u16 + NUMBERS_START - 1, + ] + .iter() + .enumerate() + { + map.set_tile( + vram, + (i as u16, 0).into(), + tile_set_ref, + TileSetting::from_raw(tile), + ); + } - map[0][0] = LEVEL_START; - map[0][1] = LEVEL_START + 1; - map[0][2] = LEVEL_START + 2; - - counter += 4; - - map[0][counter] = world as u16 + NUMBERS_START - 1; - counter += 1; - map[0][counter] = HYPHEN; - counter += 1; - map[0][counter] = level as u16 + NUMBERS_START - 1; - counter += 1; - - background.set_position((-(WIDTH / 2 - counter as i32 * 8 / 2), -(HEIGHT / 2 - 4)).into()); + map.set_scroll_pos( + ( + -(WIDTH / 2 - 7 as i32 * 8 / 2) as u16, + -(HEIGHT / 2 - 4) as u16, + ) + .into(), + ); } diff --git a/examples/the-hat-chooses-the-wizard/src/main.rs b/examples/the-hat-chooses-the-wizard/src/main.rs index 974c6869..fcf74ed7 100644 --- a/examples/the-hat-chooses-the-wizard/src/main.rs +++ b/examples/the-hat-chooses-the-wizard/src/main.rs @@ -1,6 +1,21 @@ #![no_std] #![no_main] +extern crate alloc; + +use agb::{ + display::{ + object::{Graphics, Object, ObjectController, Sprite, Tag, TagMap}, + tiled::{ + InfiniteScrolledMap, PartialUpdateStatus, TileFormat, TileSet, TileSetting, VRamManager, + }, + Priority, HEIGHT, WIDTH, + }, + fixnum::{FixedNum, Vector2D}, + input::{self, Button, ButtonController}, +}; +use alloc::boxed::Box; + mod enemies; mod level_display; mod sfx; @@ -82,16 +97,6 @@ mod map_tiles { agb::include_gfx!("gfx/tile_sheet.toml"); -use agb::{ - display::{ - background::BackgroundRegular, - object::{Graphics, Object, ObjectController, Sprite, Tag, TagMap}, - Priority, HEIGHT, WIDTH, - }, - fixnum::{FixedNum, Vector2D}, - input::{self, Button, ButtonController}, -}; - const GRAPHICS: &Graphics = agb::include_aseprite!("gfx/sprites.aseprite"); const TAG_MAP: &TagMap = GRAPHICS.tags(); @@ -258,40 +263,27 @@ impl<'a> Entity<'a> { } struct Map<'a, 'b> { - background: &'a mut BackgroundRegular<'b>, - foreground: &'a mut BackgroundRegular<'b>, + background: &'a mut InfiniteScrolledMap<'b>, + foreground: &'a mut InfiniteScrolledMap<'b>, position: Vector2D, level: &'a Level, } -impl<'a, 'b, 'c> Map<'a, 'b> { - pub fn commit_position(&mut self) { - self.background.set_position(self.position.floor()); - self.foreground.set_position(self.position.floor()); +impl<'a, 'b> Map<'a, 'b> { + pub fn commit_position(&mut self, vram: &mut VRamManager) { + self.background.set_pos(vram, self.position.floor()); + self.foreground.set_pos(vram, self.position.floor()); self.background.commit(); self.foreground.commit(); } - fn load_foreground(&'c mut self) -> impl Iterator + 'c { - self.background.set_position(self.position.floor()); - self.background.set_map(agb::display::background::Map::new( - self.level.foreground, - self.level.dimensions, - 0, - )); - self.background.commit_partial() + pub fn init_background(&mut self, vram: &mut VRamManager) -> PartialUpdateStatus { + self.background.init_partial(vram, self.position.floor()) } - fn load_background(&'c mut self) -> impl Iterator + 'c { - self.foreground.set_position(self.position.floor()); - self.foreground.set_map(agb::display::background::Map::new( - self.level.background, - self.level.dimensions, - 0, - )); - self.foreground.set_priority(Priority::P2); - self.foreground.commit_partial() + pub fn init_foreground(&mut self, vram: &mut VRamManager) -> PartialUpdateStatus { + self.foreground.init_partial(vram, self.position.floor()) } } @@ -621,8 +613,8 @@ impl<'a, 'b, 'c> PlayingLevel<'a, 'b> { fn open_level( level: &'a Level, object_control: &'a ObjectController, - background: &'a mut BackgroundRegular<'b>, - foreground: &'a mut BackgroundRegular<'b>, + background: &'a mut InfiniteScrolledMap<'b>, + foreground: &'a mut InfiniteScrolledMap<'b>, input: ButtonController, ) -> Self { let mut e: [enemies::Enemy<'a>; 16] = Default::default(); @@ -661,19 +653,21 @@ impl<'a, 'b, 'c> PlayingLevel<'a, 'b> { } } - fn load_1(&'c mut self) -> impl Iterator + 'c { - self.background.load_background() - } - - fn load_2(&'c mut self) -> impl Iterator + 'c { - self.background.load_foreground() - } - fn show_backgrounds(&mut self) { self.background.background.show(); self.background.foreground.show(); } + fn hide_backgrounds(&mut self) { + self.background.background.hide(); + self.background.foreground.hide(); + } + + fn clear_backgrounds(&mut self, vram: &mut VRamManager) { + self.background.background.clear(vram); + self.background.foreground.clear(vram); + } + fn dead_start(&mut self) { self.player.wizard.velocity = (0, -1).into(); self.player.wizard.sprite.set_priority(Priority::P0); @@ -696,8 +690,9 @@ impl<'a, 'b, 'c> PlayingLevel<'a, 'b> { fn update_frame( &mut self, - controller: &'a ObjectController, sfx_player: &mut sfx::SfxPlayer, + vram: &mut VRamManager, + controller: &'a ObjectController, ) -> UpdateState { self.timer += 1; self.input.update(); @@ -728,7 +723,7 @@ impl<'a, 'b, 'c> PlayingLevel<'a, 'b> { } self.background.position = self.get_next_map_position(); - self.background.commit_position(); + self.background.commit_position(vram); self.player.wizard.commit_position(self.background.position); self.player.hat.commit_position(self.background.position); @@ -783,24 +778,45 @@ impl<'a, 'b, 'c> PlayingLevel<'a, 'b> { #[agb::entry] fn main(mut agb: agb::Gba) -> ! { - splash_screen::show_splash_screen(&mut agb, splash_screen::SplashScreen::Start, None, None); + 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 tile_set_ref = vram.add_tileset(TileSet::new( + tile_sheet::background.tiles, + TileFormat::FourBpp, + )); + + for y in 0..32u16 { + for x in 0..32u16 { + world_display.set_tile( + &mut vram, + (x, y).into(), + tile_set_ref, + TileSetting::from_raw(level_display::BLANK), + ); + } + } + + world_display.commit(); + world_display.show(); + + splash_screen::show_splash_screen( + splash_screen::SplashScreen::Start, + None, + None, + &mut splash_screen, + &mut vram, + ); loop { - let mut tiled = agb.display.video.tiled0(); + vram.set_background_palettes(tile_sheet::background.palettes); + let mut object = agb.display.object.get(); let mut timer_controller = agb.timers.timers(); let mut mixer = agb.mixer.mixer(&mut timer_controller.timer0); - tiled.set_background_palettes(tile_sheet::background.palettes); - tiled.set_background_tilemap(0, tile_sheet::background.tiles); - - let mut world_display = tiled.get_raw_regular().unwrap(); - world_display.clear(level_display::BLANK); - world_display.show(); - - let mut background = tiled.get_regular().unwrap(); - let mut foreground = tiled.get_regular().unwrap(); - mixer.enable(); let mut music_box = sfx::MusicBox::new(); @@ -821,8 +837,11 @@ fn main(mut agb: agb::Gba) -> ! { &mut world_display, current_level / 8 + 1, current_level % 8 + 1, + tile_set_ref, + &mut vram, ); + world_display.commit(); world_display.show(); music_box.before_frame(&mut mixer); @@ -830,6 +849,37 @@ fn main(mut agb: agb::Gba) -> ! { vblank.wait_for_vblank(); mixer.after_vblank(); + let mut background = InfiniteScrolledMap::new( + tiled.background(Priority::P2), + Box::new(move |pos: Vector2D| { + let level = &map_tiles::LEVELS[current_level as usize]; + ( + tile_set_ref, + TileSetting::from_raw( + *level + .background + .get((pos.y * level.dimensions.x as i32 + pos.x) as usize) + .unwrap_or(&0), + ), + ) + }), + ); + let mut foreground = InfiniteScrolledMap::new( + tiled.background(Priority::P0), + Box::new(move |pos: Vector2D| { + let level = &map_tiles::LEVELS[current_level as usize]; + ( + tile_set_ref, + TileSetting::from_raw( + *level + .foreground + .get((pos.y * level.dimensions.x as i32 + pos.x) as usize) + .unwrap_or(&0), + ), + ) + }), + ); + let mut level = PlayingLevel::open_level( &map_tiles::LEVELS[current_level as usize], &object, @@ -837,33 +887,38 @@ fn main(mut agb: agb::Gba) -> ! { &mut foreground, agb::input::ButtonController::new(), ); - let mut level_load = level.load_1().step_by(24); - for _ in 0..30 { + + while level.background.init_background(&mut vram) != PartialUpdateStatus::Done { music_box.before_frame(&mut mixer); mixer.frame(); vblank.wait_for_vblank(); mixer.after_vblank(); - - level_load.next(); } - level_load.count(); - let mut level_load = level.load_2().step_by(24); - for _ in 0..30 { + + while level.background.init_foreground(&mut vram) != PartialUpdateStatus::Done { music_box.before_frame(&mut mixer); mixer.frame(); vblank.wait_for_vblank(); mixer.after_vblank(); - - level_load.next(); } - level_load.count(); + + for _ in 0..20 { + music_box.before_frame(&mut mixer); + mixer.frame(); + vblank.wait_for_vblank(); + mixer.after_vblank(); + } + level.show_backgrounds(); world_display.hide(); loop { - match level.update_frame(&object, &mut sfx::SfxPlayer::new(&mut mixer, &music_box)) - { + match level.update_frame( + &mut sfx::SfxPlayer::new(&mut mixer, &music_box), + &mut vram, + &object, + ) { UpdateState::Normal => {} UpdateState::Dead => { level.dead_start(); @@ -886,13 +941,17 @@ fn main(mut agb: agb::Gba) -> ! { vblank.wait_for_vblank(); mixer.after_vblank(); } + + level.hide_backgrounds(); + level.clear_backgrounds(&mut vram); } splash_screen::show_splash_screen( - &mut agb, splash_screen::SplashScreen::End, Some(&mut mixer), Some(&mut music_box), + &mut splash_screen, + &mut vram, ); } } diff --git a/examples/the-hat-chooses-the-wizard/src/splash_screen.rs b/examples/the-hat-chooses-the-wizard/src/splash_screen.rs index 56552fef..4605d74d 100644 --- a/examples/the-hat-chooses-the-wizard/src/splash_screen.rs +++ b/examples/the-hat-chooses-the-wizard/src/splash_screen.rs @@ -1,5 +1,8 @@ use super::sfx::MusicBox; -use agb::sound::mixer::Mixer; +use agb::{ + display::tiled::{RegularMap, TileFormat, TileSet, TileSetting, VRamManager}, + sound::mixer::Mixer, +}; agb::include_gfx!("gfx/splash_screens.toml"); @@ -9,39 +12,77 @@ pub enum SplashScreen { } pub fn show_splash_screen( - agb: &mut agb::Gba, which: SplashScreen, mut mixer: Option<&mut Mixer>, mut music_box: Option<&mut MusicBox>, + map: &mut RegularMap, + vram: &mut VRamManager, ) { - let mut tiled = agb.display.video.tiled0(); - - match which { + map.set_scroll_pos((0u16, 0u16).into()); + let (tile_set_ref, palette) = match which { SplashScreen::Start => { - tiled.set_background_tilemap(0, splash_screens::splash.tiles); - tiled.set_background_palettes(splash_screens::splash.palettes); + let tile_set_ref = vram.add_tileset(TileSet::new( + splash_screens::splash.tiles, + TileFormat::FourBpp, + )); + + (tile_set_ref, splash_screens::splash.palettes) } SplashScreen::End => { - tiled.set_background_tilemap(0, splash_screens::thanks_for_playing.tiles); - tiled.set_background_palettes(splash_screens::thanks_for_playing.palettes); + let tile_set_ref = vram.add_tileset(TileSet::new( + splash_screens::thanks_for_playing.tiles, + TileFormat::FourBpp, + )); + + (tile_set_ref, splash_screens::thanks_for_playing.palettes) + } + }; + + let vblank = agb::interrupt::VBlank::get(); + + let mut input = agb::input::ButtonController::new(); + + if let Some(ref mut mixer) = mixer { + if let Some(ref mut music_box) = music_box { + music_box.before_frame(mixer); + } + mixer.frame(); + } + + vblank.wait_for_vblank(); + + if let Some(ref mut mixer) = mixer { + mixer.after_vblank(); + } + + for y in 0..20u16 { + for x in 0..30u16 { + map.set_tile( + vram, + (x, y).into(), + tile_set_ref, + TileSetting::from_raw(y * 30 + x), + ); + } + + if let Some(ref mut mixer) = mixer { + if let Some(ref mut music_box) = music_box { + music_box.before_frame(mixer); + } + mixer.frame(); + } + + vblank.wait_for_vblank(); + + if let Some(ref mut mixer) = mixer { + mixer.after_vblank(); } } - let vblank = agb::interrupt::VBlank::get(); - let mut splash_screen_display = tiled.get_regular().unwrap(); - let mut entries: [u16; 30 * 20] = [0; 30 * 20]; - for tile_id in 0..(30 * 20) { - entries[tile_id as usize] = tile_id; - } - let mut input = agb::input::ButtonController::new(); - splash_screen_display.set_map(agb::display::background::Map::new( - &entries, - (30_u32, 20_u32).into(), - 0, - )); - splash_screen_display.set_position((0, 0).into()); - splash_screen_display.commit(); - splash_screen_display.show(); + map.commit(); + vram.set_background_palettes(palette); + map.show(); + loop { input.update(); if input.is_just_pressed( @@ -64,5 +105,9 @@ pub fn show_splash_screen( mixer.after_vblank(); } } - splash_screen_display.hide(); + + map.hide(); + map.clear(vram); + + vram.remove_tileset(tile_set_ref); } diff --git a/examples/the-purple-night/build.rs b/examples/the-purple-night/build.rs index 94914e8e..3d933036 100644 --- a/examples/the-purple-night/build.rs +++ b/examples/the-purple-night/build.rs @@ -45,8 +45,8 @@ fn main() { pub const CLOUD_MAP: &[u16] = &[#(#cloud_tiles),*]; pub const BACKGROUND_MAP: &[u16] = &[#(#background_tiles),*]; pub const FOREGROUND_MAP: &[u16] = &[#(#foreground_tiles),*]; - pub const WIDTH: u32 = #width; - pub const HEIGHT: u32 = #height; + pub const WIDTH: i32 = #width as i32; + pub const HEIGHT: i32 = #height as i32; pub const SLIME_SPAWNS_X: &[u16] = &[#(#slimes_x),*]; pub const SLIME_SPAWNS_Y: &[u16] = &[#(#slimes_y),*]; diff --git a/examples/the-purple-night/src/main.rs b/examples/the-purple-night/src/main.rs index ad2d4f59..20df7df0 100644 --- a/examples/the-purple-night/src/main.rs +++ b/examples/the-purple-night/src/main.rs @@ -8,20 +8,22 @@ mod sfx; use core::cmp::Ordering; -use alloc::vec::Vec; +use alloc::{boxed::Box, vec::Vec}; use rng::get_random; use agb::{ display::{ - background::{BackgroundDistributor, BackgroundRegular}, object::{Graphics, Object, ObjectController, Sprite, Tag, TagMap}, + tiled::{InfiniteScrolledMap, TileFormat, TileSet, TileSetting, VRamManager}, Priority, HEIGHT, WIDTH, }, fixnum::{FixedNum, Rect, Vector2D}, input::{Button, ButtonController, Tri}, + interrupt::VBlank, }; use generational_arena::Arena; +use sfx::Sfx; const GRAPHICS: &Graphics = agb::include_aseprite!("gfx/objects.aseprite", "gfx/boss.aseprite"); const TAG_MAP: &TagMap = GRAPHICS.tags(); @@ -54,45 +56,36 @@ agb::include_gfx!("gfx/background.toml"); type Number = FixedNum<8>; -struct Level { - background: BackgroundRegular<'static>, - foreground: BackgroundRegular<'static>, - clouds: BackgroundRegular<'static>, +struct Level<'a> { + background: InfiniteScrolledMap<'a>, + foreground: InfiniteScrolledMap<'a>, + clouds: InfiniteScrolledMap<'a>, slime_spawns: Vec<(u16, u16)>, bat_spawns: Vec<(u16, u16)>, emu_spawns: Vec<(u16, u16)>, } -impl Level { +impl<'a> Level<'a> { fn load_level( - mut backdrop: BackgroundRegular<'static>, - mut foreground: BackgroundRegular<'static>, - mut clouds: BackgroundRegular<'static>, + mut backdrop: InfiniteScrolledMap<'a>, + mut foreground: InfiniteScrolledMap<'a>, + mut clouds: InfiniteScrolledMap<'a>, + start_pos: Vector2D, + vram: &mut VRamManager, + sfx: &mut Sfx, ) -> Self { - backdrop.set_position(Vector2D::new(0, 0)); - backdrop.set_map(agb::display::background::Map::new( - tilemap::BACKGROUND_MAP, - Vector2D::new(tilemap::WIDTH, tilemap::HEIGHT), - 0, - )); - backdrop.set_priority(Priority::P2); + let vblank = VBlank::get(); - foreground.set_position(Vector2D::new(0, 0)); - foreground.set_map(agb::display::background::Map::new( - tilemap::FOREGROUND_MAP, - Vector2D::new(tilemap::WIDTH, tilemap::HEIGHT), - 0, - )); - foreground.set_priority(Priority::P0); + let mut between_updates = || { + sfx.frame(); + vblank.wait_for_vblank(); + sfx.after_vblank(); + }; - clouds.set_position(Vector2D::new(0, -5)); - clouds.set_map(agb::display::background::Map::new( - tilemap::CLOUD_MAP, - Vector2D::new(tilemap::WIDTH, tilemap::HEIGHT), - 0, - )); - clouds.set_priority(Priority::P3); + backdrop.init(vram, start_pos, &mut between_updates); + foreground.init(vram, start_pos, &mut between_updates); + clouds.init(vram, start_pos / 4, &mut between_updates); backdrop.commit(); foreground.commit(); @@ -150,6 +143,12 @@ impl Level { None } } + + fn clear(&mut self, vram: &mut VRamManager) { + self.background.clear(vram); + self.foreground.clear(vram); + self.clouds.clear(vram); + } } struct Entity<'a> { @@ -392,10 +391,10 @@ impl SwordState { } fn attack_frame(self, timer: u16) -> u16 { match self { - SwordState::LongSword => (self.attack_duration() - timer) / 8, - SwordState::ShortSword => (self.attack_duration() - timer) / 8, - SwordState::Dagger => (self.attack_duration() - timer) / 8, - SwordState::Swordless => (self.attack_duration() - timer) / 8, + SwordState::LongSword => (self.attack_duration().saturating_sub(timer)) / 8, + SwordState::ShortSword => (self.attack_duration().saturating_sub(timer)) / 8, + SwordState::Dagger => (self.attack_duration().saturating_sub(timer)) / 8, + SwordState::Swordless => (self.attack_duration().saturating_sub(timer)) / 8, } } fn jump_attack_tag(self) -> &'static Tag { @@ -407,7 +406,7 @@ impl SwordState { } } fn jump_attack_frame(self, timer: u16) -> u16 { - (self.jump_attack_duration() - timer) / 8 + (self.jump_attack_duration().saturating_sub(timer)) / 8 } fn hold_frame(self) -> u16 { 7 @@ -1870,7 +1869,7 @@ struct Game<'a> { player: Player<'a>, input: ButtonController, frame_count: u32, - level: Level, + level: Level<'a>, offset: Vector2D, shake_time: u16, sunrise_timer: u16, @@ -1883,8 +1882,6 @@ struct Game<'a> { boss: BossState<'a>, move_state: MoveState, fade_count: u16, - - background_distributor: &'a mut BackgroundDistributor, } enum MoveState { @@ -1902,9 +1899,14 @@ impl<'a> Game<'a> { } } + fn clear(&mut self, vram: &mut VRamManager) { + self.level.clear(vram); + } + fn advance_frame( &mut self, object_controller: &'a ObjectController, + vram: &mut VRamManager, sfx: &mut sfx::Sfx, ) -> GameStatus { let mut state = GameStatus::Continue; @@ -1924,7 +1926,7 @@ impl<'a> Game<'a> { self.offset.x = (tilemap::WIDTH as i32 * 8 - 248).into(); } MoveState::FollowingPlayer => { - Game::update_sunrise(self.background_distributor, self.sunrise_timer); + Game::update_sunrise(vram, self.sunrise_timer); if self.sunrise_timer < 120 { self.sunrise_timer += 1; } else { @@ -1946,7 +1948,7 @@ impl<'a> Game<'a> { if boss.gone { self.fade_count += 1; self.fade_count = self.fade_count.min(600); - Game::update_fade_out(self.background_distributor, self.fade_count); + Game::update_fade_out(vram, self.fade_count); } } } @@ -2049,18 +2051,11 @@ impl<'a> Game<'a> { self.player.commit(this_frame_offset); self.boss.commit(this_frame_offset); - self.level - .background - .set_position(this_frame_offset.floor()); - self.level - .foreground - .set_position(this_frame_offset.floor()); - self.level - .clouds - .set_position(this_frame_offset.floor() / 4); - self.level.background.commit(); - self.level.foreground.commit(); - self.level.clouds.commit(); + let background_offset = (this_frame_offset.floor().x, 8).into(); + + self.level.background.set_pos(vram, background_offset); + self.level.foreground.set_pos(vram, background_offset); + self.level.clouds.set_pos(vram, background_offset / 4); for i in remove { self.enemies.remove(i); @@ -2105,6 +2100,10 @@ impl<'a> Game<'a> { .commit_with_fudge(this_frame_offset, (0, 0).into()); } + self.level.background.commit(); + self.level.foreground.commit(); + self.level.clouds.commit(); + for i in remove { self.particles.remove(i); } @@ -2162,7 +2161,7 @@ impl<'a> Game<'a> { } } - fn update_sunrise(background_distributor: &'a mut BackgroundDistributor, time: u16) { + fn update_sunrise(vram: &mut VRamManager, time: u16) { let mut modified_palette = background::background.palettes[0].clone(); let a = modified_palette.get_colour(0); @@ -2173,10 +2172,10 @@ impl<'a> Game<'a> { let modified_palettes = [modified_palette]; - background_distributor.set_background_palettes(&modified_palettes); + vram.set_background_palettes(&modified_palettes); } - fn update_fade_out(background_distributor: &'a mut BackgroundDistributor, time: u16) { + fn update_fade_out(vram: &mut VRamManager, time: u16) { let mut modified_palette = background::background.palettes[0].clone(); let c = modified_palette.get_colour(2); @@ -2187,15 +2186,10 @@ impl<'a> Game<'a> { let modified_palettes = [modified_palette]; - background_distributor.set_background_palettes(&modified_palettes); + vram.set_background_palettes(&modified_palettes); } - fn new( - object: &'a ObjectController, - level: Level, - background_distributor: &'a mut BackgroundDistributor, - start_at_boss: bool, - ) -> Self { + fn new(object: &'a ObjectController, level: Level<'a>, start_at_boss: bool) -> Self { let mut player = Player::new(object); let mut offset = (8, 8).into(); if start_at_boss { @@ -2219,8 +2213,6 @@ impl<'a> Game<'a> { move_state: MoveState::Advancing, sunrise_timer: 0, fade_count: 0, - - background_distributor, } } } @@ -2239,19 +2231,68 @@ fn game_with_level(gba: &mut agb::Gba) { let mut start_at_boss = false; loop { - let mut background = gba.display.video.tiled0(); - background.set_background_palettes(background::background.palettes); - background.set_background_tilemap(0, background::background.tiles); + let (background, mut vram) = gba.display.video.tiled0(); + + vram.set_background_palettes(background::background.palettes); + + let tileset_ref = vram.add_tileset(TileSet::new( + background::background.tiles, + TileFormat::FourBpp, + )); + let object = gba.display.object.get(); + let backdrop = InfiniteScrolledMap::new( + background.background(Priority::P2), + Box::new(move |pos| { + ( + tileset_ref, + TileSetting::from_raw( + *tilemap::BACKGROUND_MAP + .get((pos.x + tilemap::WIDTH * pos.y) as usize) + .unwrap_or(&0), + ), + ) + }), + ); + + let foreground = InfiniteScrolledMap::new( + background.background(Priority::P0), + Box::new(move |pos| { + ( + tileset_ref, + TileSetting::from_raw( + *tilemap::FOREGROUND_MAP + .get((pos.x + tilemap::WIDTH * pos.y) as usize) + .unwrap_or(&0), + ), + ) + }), + ); + + let clouds = InfiniteScrolledMap::new( + background.background(Priority::P3), + Box::new(move |pos| { + ( + tileset_ref, + TileSetting::from_raw( + *tilemap::CLOUD_MAP + .get((pos.x + tilemap::WIDTH * pos.y) as usize) + .unwrap_or(&0), + ), + ) + }), + ); + + let start_pos = if start_at_boss { + (130 * 8, 8).into() + } else { + (8, 8).into() + }; + let mut game = Game::new( &object, - Level::load_level( - background.get_regular().unwrap(), - background.get_regular().unwrap(), - background.get_regular().unwrap(), - ), - &mut background, + Level::load_level(backdrop, foreground, clouds, start_pos, &mut vram, &mut sfx), start_at_boss, ); @@ -2259,7 +2300,7 @@ fn game_with_level(gba: &mut agb::Gba) { sfx.frame(); vblank.wait_for_vblank(); sfx.after_vblank(); - match game.advance_frame(&object, &mut sfx) { + match game.advance_frame(&object, &mut vram, &mut sfx) { GameStatus::Continue => {} GameStatus::Lost => { break false; @@ -2270,7 +2311,9 @@ fn game_with_level(gba: &mut agb::Gba) { } get_random(); // advance RNG to make it less predictable between runs - } + }; + + game.clear(&mut vram); } }