From 5ea6eb0f1dac40649465948c9e264e155a6e3cde Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Mon, 17 Jan 2022 21:54:36 +0000 Subject: [PATCH 01/76] Complete re-do of tile storage management in backgrounds --- agb/Cargo.lock | 33 ++ agb/Cargo.toml | 1 + agb/src/display/background.rs | 589 +++++++++------------------------- 3 files changed, 180 insertions(+), 443 deletions(-) diff --git a/agb/Cargo.lock b/agb/Cargo.lock index 16115991..ff15a265 100644 --- a/agb/Cargo.lock +++ b/agb/Cargo.lock @@ -24,6 +24,7 @@ dependencies = [ "agb_sound_converter", "bare-metal", "bitflags", + "hashbrown", ] [[package]] @@ -65,6 +66,17 @@ dependencies = [ "syn", ] +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -136,6 +148,15 @@ dependencies = [ "wasi", ] +[[package]] +name = "hashbrown" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c21d40587b92fa6a6c6e3c1bdbf87d75511db5672f9c93175574b3a00df1758" +dependencies = [ + "ahash", +] + [[package]] name = "hound" version = "3.4.0" @@ -213,6 +234,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "once_cell" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" + [[package]] name = "png" version = "0.17.4" @@ -325,6 +352,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + [[package]] name = "wasi" version = "0.10.2+wasi-snapshot-preview1" diff --git a/agb/Cargo.toml b/agb/Cargo.toml index dbd7d298..781aea3c 100644 --- a/agb/Cargo.toml +++ b/agb/Cargo.toml @@ -21,6 +21,7 @@ freq18157 = ["agb_sound_converter/freq18157"] [dependencies] bitflags = "1.3" +hashbrown = "0.12.0" agb_image_converter = { version = "0.6.0", path = "../agb-image-converter" } agb_sound_converter = { version = "0.1.0", path = "../agb-sound-converter" } agb_macros = { version = "0.1.0", path = "../agb-macros" } diff --git a/agb/src/display/background.rs b/agb/src/display/background.rs index 4f6ab336..00fb9d77 100644 --- a/agb/src/display/background.rs +++ b/agb/src/display/background.rs @@ -1,484 +1,187 @@ -use core::ops::Index; +use alloc::vec::Vec; +use hashbrown::HashMap; -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) }; +use crate::memory_mapped::MemoryMapped1DArray; 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, Debug)] +enum TileFormat { + FourBpp, } -#[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, +impl TileFormat { + /// Returns the size of the tile in bytes + fn tile_size(self) -> usize { + match self { + TileFormat::FourBpp => 8 * 8 / 2, } } - 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>, +struct TileSet<'a> { + tiles: &'a [u8], + format: TileFormat, } -pub struct Map<'a> { - store: MapStorage<'a>, - pub dimensions: Vector2D, - pub default: u16, +#[derive(Clone, Copy, PartialEq, Eq)] +struct TileSetReference { + id: u16, + generation: u32, } -impl<'a> Map<'a> { - pub fn new(map: &[u16], dimensions: Vector2D, default: u16) -> Map { - Map { - store: MapStorage::new(map), - dimensions, - default, +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +struct TileIndex(u16); + +enum ArenaStorageItem { + EndOfFreeList, + NextFree(usize), + Data(T, u32), +} + +struct VRamManager<'a> { + tilesets: Vec>>, + generation: u32, + free_pointer: Option, + + tile_set_to_vram: HashMap<(u16, u16), u16>, + references: Vec, + vram_free_pointer: Option, +} + +const END_OF_FREE_LIST_REFERENCE: u16 = u16::MAX; + +impl<'a> VRamManager<'a> { + pub fn new() -> Self { + Self { + tilesets: Vec::new(), + generation: 0, + free_pointer: None, + + tile_set_to_vram: HashMap::new(), + references: Vec::new(), + vram_free_pointer: None, } } - 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 + + pub fn add_tileset(&mut self, tileset: TileSet<'a>) -> TileSetReference { + let generation = self.generation; + self.generation += 1; + + 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 + } + _ => panic!("Free pointer shouldn't point to valid data"), + } } else { - self.store[y as usize * self.dimensions.x as usize + x as usize] + self.tilesets.push(tileset); + self.tilesets.len() - 1 + }; + + TileSetReference { + id: index as u16, + generation, } } - 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, -} + pub fn remove_tileset(&mut self, tile_set_ref: TileSetReference) { + let tileset = self.tilesets[tile_set_ref.id as usize]; -impl<'a> BackgroundRegister { - unsafe fn new(background: u8, block: u8, background_size: BackgroundSize) -> Self { - let mut b = Self { - background, - block, - shadowed_register: 0, + match tileset { + ArenaStorageItem::Data(_, generation) => { + 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!("Already freed, probably a double free?"), + } + } + + pub fn add_tile(&mut self, tile_set_ref: TileSetReference, tile: u16) -> TileIndex { + if let Some(&reference) = self.tile_set_to_vram.get(&(tile_set_ref.id, tile)) { + self.references[reference as usize] += 1; + return TileIndex(reference as u16); + } + + let index_to_copy_into = if let Some(ptr) = self.vram_free_pointer.take() { + if self.references[ptr] != END_OF_FREE_LIST_REFERENCE { + self.vram_free_pointer = Some(self.references[ptr] as usize); + } + + self.references[ptr] = 1; + ptr + } else { + self.references.push(1); + self.references.len() - 1 }; - 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); - } + let tile_slice = if let ArenaStorageItem::Data(data, generation) = + self.tilesets[tile_set_ref.id as usize] + { + assert_eq!( + generation, tile_set_ref.generation, + "Stale tile data requested" + ); - /// 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); - } + let tile_offset = (tile as usize) * data.format.tile_size(); + &data.tiles[tile_offset..(tile_offset + data.format.tile_size())] + } else { + panic!("Cannot find tile data at given reference"); + }; - pub fn set_priority(&mut self, p: Priority) { - unsafe { self.set_shadowed_register_bits(p as u16, 0x2, 0x0) }; - } + let tile_size_in_words = TileFormat::FourBpp.tile_size() / 4; - 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); + let (_, tile_data, _) = tile_slice.align_to::(); + + for (i, &word) in tile_data.iter().enumerate() { + TILE_BACKGROUND.set(index_to_copy_into * tile_size_in_words + i, word); + } } + + TileIndex(index_to_copy_into as u16) } - pub fn get_block(&mut self) -> &mut [[u16; 32]; 32] { - unsafe { &mut (*MAP)[self.block as usize] } - } + pub fn remove_tile(&mut self, tile_index: TileIndex) { + let index = tile_index.0 as usize; + self.references[index] -= 1; - 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, + if self.references[index] != 0 { + return; } - } - /// 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()) + if let Some(ptr) = self.vram_free_pointer { + self.references[index] = ptr as u16; } else { - let commited_block = self.commited_position / 8; - let shadowed_block = self.shadowed_position / 8; + self.references[index] = END_OF_FREE_LIST_REFERENCE; + } - 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(); + self.vram_free_pointer = Some(index); } } -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) - } +impl TileSetReference { + fn new(id: u16, generation: u32) -> Self { + Self { id, generation } } } From effaac38855b0d88b6eaf042e27140b65a830daf Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Mon, 17 Jan 2022 22:38:37 +0000 Subject: [PATCH 02/76] hopefully enough to actually maybe display something? --- agb/src/display/background.rs | 149 ++++++++++++++++++++++++++++---- agb/src/display/example_logo.rs | 4 +- agb/src/display/video.rs | 8 +- 3 files changed, 140 insertions(+), 21 deletions(-) diff --git a/agb/src/display/background.rs b/agb/src/display/background.rs index 00fb9d77..6c00ded4 100644 --- a/agb/src/display/background.rs +++ b/agb/src/display/background.rs @@ -1,13 +1,18 @@ use alloc::vec::Vec; use hashbrown::HashMap; -use crate::memory_mapped::MemoryMapped1DArray; +use crate::memory_mapped::{MemoryMapped, MemoryMapped1DArray}; + +use super::{set_graphics_mode, set_graphics_settings, DisplayMode, GraphicsSettings}; const TILE_BACKGROUND: MemoryMapped1DArray = unsafe { MemoryMapped1DArray::new(0x06000000) }; +const PALETTE_BACKGROUND: MemoryMapped1DArray = + unsafe { MemoryMapped1DArray::new(0x0500_0000) }; + #[derive(Clone, Copy, Debug)] -enum TileFormat { +pub enum TileFormat { FourBpp, } @@ -20,19 +25,19 @@ impl TileFormat { } } -struct TileSet<'a> { +pub struct TileSet<'a> { tiles: &'a [u8], format: TileFormat, } #[derive(Clone, Copy, PartialEq, Eq)] -struct TileSetReference { +pub struct TileSetReference { id: u16, generation: u32, } -#[derive(Copy, Clone, PartialEq, Eq, Debug)] -struct TileIndex(u16); +#[derive(Debug)] +pub struct TileIndex(u16); enum ArenaStorageItem { EndOfFreeList, @@ -40,7 +45,7 @@ enum ArenaStorageItem { Data(T, u32), } -struct VRamManager<'a> { +pub struct VRamManager<'a> { tilesets: Vec>>, generation: u32, free_pointer: Option, @@ -89,19 +94,16 @@ impl<'a> VRamManager<'a> { self.tilesets.len() - 1 }; - TileSetReference { - id: index as u16, - generation, - } + 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]; + let tileset = &self.tilesets[tile_set_ref.id as usize]; match tileset { ArenaStorageItem::Data(_, generation) => { assert_eq!( - generation, tile_set_ref.generation, + *generation, tile_set_ref.generation, "Tileset generation must be the same when removing" ); @@ -136,10 +138,10 @@ impl<'a> VRamManager<'a> { }; let tile_slice = if let ArenaStorageItem::Data(data, generation) = - self.tilesets[tile_set_ref.id as usize] + &self.tilesets[tile_set_ref.id as usize] { assert_eq!( - generation, tile_set_ref.generation, + *generation, tile_set_ref.generation, "Stale tile data requested" ); @@ -178,6 +180,123 @@ impl<'a> VRamManager<'a> { self.vram_free_pointer = Some(index); } + + /// 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); + } + } +} + +#[derive(Clone, Copy, Debug)] +#[repr(C)] +pub struct Tile(u16); + +impl Tile { + pub fn new(tid: TileIndex, hflip: bool, vflip: bool, palette_id: u16) -> Self { + Self(tid.0 | ((hflip as u16) << 10) | ((vflip as u16) << 11) | (palette_id << 12)) + } +} + +pub struct RegularMap { + background_id: u8, + + screenblock: u8, + x_scroll: u16, + y_scroll: u16, + priority: u8, + + tiles: [Tile; 32 * 32], + tiles_dirty: bool, +} + +impl RegularMap { + fn new(background_id: u8, screenblock: u8) -> Self { + Self { + background_id, + + screenblock, + x_scroll: 0, + y_scroll: 0, + priority: 0, + + tiles: [Tile(0); 32 * 32], + tiles_dirty: true, + } + } + + pub fn set_tile(&mut self, x: u16, y: u16, tile: Tile) { + self.tiles[(x + y * 32) as usize] = tile; + self.tiles_dirty = true; + } + + 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(); + for (i, tile) in self.tiles.iter().enumerate() { + screenblock_memory.set(i, tile.0); + } + } + + 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) -> MemoryMapped1DArray { + unsafe { MemoryMapped1DArray::new(0x0600_0000 + 0x1000 * self.screenblock as usize) } + } +} + +pub struct Tiled0<'a> { + num_regular: u8, + next_screenblock: u8, + + pub vram: VRamManager<'a>, +} + +impl Tiled0<'_> { + pub(crate) unsafe fn new() -> Self { + set_graphics_settings(GraphicsSettings::empty() | GraphicsSettings::SPRITE1_D); + set_graphics_mode(DisplayMode::Tiled0); + + Self { + num_regular: 0, + next_screenblock: 16, + + vram: VRamManager::new(), + } + } + + pub fn background(&mut self) -> RegularMap { + if self.num_regular == 4 { + panic!("Can only create 4 backgrounds"); + } + + let bg = RegularMap::new(self.num_regular, self.next_screenblock); + + self.num_regular += 1; + self.next_screenblock += 1; + + bg + } } impl TileSetReference { diff --git a/agb/src/display/example_logo.rs b/agb/src/display/example_logo.rs index 3e72ad5e..47166642 100644 --- a/agb/src/display/example_logo.rs +++ b/agb/src/display/example_logo.rs @@ -1,4 +1,4 @@ -use crate::display::background::BackgroundDistributor; +/*use crate::display::background::BackgroundDistributor; crate::include_gfx!("gfx/agb_logo.toml"); @@ -31,4 +31,4 @@ mod tests { crate::test_runner::assert_image_output("gfx/test_logo.png"); } -} +}*/ diff --git a/agb/src/display/video.rs b/agb/src/display/video.rs index 2652b8a2..a8d16a33 100644 --- a/agb/src/display/video.rs +++ b/agb/src/display/video.rs @@ -1,4 +1,4 @@ -use super::{background::BackgroundDistributor, bitmap3::Bitmap3, bitmap4::Bitmap4}; +use super::{bitmap3::Bitmap3, bitmap4::Bitmap4}; #[non_exhaustive] pub struct Video {} @@ -14,7 +14,7 @@ impl Video { unsafe { Bitmap4::new() } } - pub fn tiled0(&mut self) -> BackgroundDistributor { - unsafe { BackgroundDistributor::new() } - } + // pub fn tiled0(&mut self) -> BackgroundDistributor { + // unsafe { BackgroundDistributor::new() } + // } } From b2c16f754b0f8224b590d0cd9138d9f30a247768 Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Mon, 17 Jan 2022 22:38:58 +0000 Subject: [PATCH 03/76] Add some room to breathe --- agb/src/memory_mapped.rs | 2 ++ 1 file changed, 2 insertions(+) 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) } } From 93d82f309e4024d4e017c48ed66540735a0f6ed6 Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Mon, 17 Jan 2022 23:11:14 +0000 Subject: [PATCH 04/76] Test logo now displays --- agb/src/display/background.rs | 52 ++++++++++++++++++++++++--------- agb/src/display/example_logo.rs | 34 ++++++++++++--------- agb/src/display/mod.rs | 1 + agb/src/display/video.rs | 8 ++--- 4 files changed, 65 insertions(+), 30 deletions(-) diff --git a/agb/src/display/background.rs b/agb/src/display/background.rs index 6c00ded4..5537793f 100644 --- a/agb/src/display/background.rs +++ b/agb/src/display/background.rs @@ -3,7 +3,10 @@ use hashbrown::HashMap; use crate::memory_mapped::{MemoryMapped, MemoryMapped1DArray}; -use super::{set_graphics_mode, set_graphics_settings, DisplayMode, GraphicsSettings}; +use super::{ + palette16, set_graphics_mode, set_graphics_settings, DisplayMode, GraphicsSettings, Priority, + DISPLAY_CONTROL, +}; const TILE_BACKGROUND: MemoryMapped1DArray = unsafe { MemoryMapped1DArray::new(0x06000000) }; @@ -26,10 +29,16 @@ impl TileFormat { } pub struct TileSet<'a> { - tiles: &'a [u8], + tiles: &'a [u32], format: TileFormat, } +impl<'a> TileSet<'a> { + pub fn new(tiles: &'a [u32], format: TileFormat) -> Self { + Self { tiles, format } + } +} + #[derive(Clone, Copy, PartialEq, Eq)] pub struct TileSetReference { id: u16, @@ -145,20 +154,16 @@ impl<'a> VRamManager<'a> { "Stale tile data requested" ); - let tile_offset = (tile as usize) * data.format.tile_size(); - &data.tiles[tile_offset..(tile_offset + data.format.tile_size())] + let tile_offset = (tile as usize) * data.format.tile_size() / 4; + &data.tiles[tile_offset..(tile_offset + data.format.tile_size() / 4)] } else { panic!("Cannot find tile data at given reference"); }; let tile_size_in_words = TileFormat::FourBpp.tile_size() / 4; - unsafe { - let (_, tile_data, _) = tile_slice.align_to::(); - - for (i, &word) in tile_data.iter().enumerate() { - TILE_BACKGROUND.set(index_to_copy_into * tile_size_in_words + i, word); - } + for (i, &word) in tile_slice.iter().enumerate() { + TILE_BACKGROUND.set(index_to_copy_into * tile_size_in_words + i, word); } TileIndex(index_to_copy_into as u16) @@ -187,6 +192,19 @@ impl<'a> VRamManager<'a> { 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) + } + } } #[derive(Clone, Copy, Debug)] @@ -205,7 +223,7 @@ pub struct RegularMap { screenblock: u8, x_scroll: u16, y_scroll: u16, - priority: u8, + priority: Priority, tiles: [Tile; 32 * 32], tiles_dirty: bool, @@ -219,7 +237,7 @@ impl RegularMap { screenblock, x_scroll: 0, y_scroll: 0, - priority: 0, + priority: Priority::P0, tiles: [Tile(0); 32 * 32], tiles_dirty: true, @@ -231,6 +249,12 @@ impl RegularMap { self.tiles_dirty = true; } + 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 commit(&mut self) { let new_bg_control_value = (self.priority as u16) | ((self.screenblock as u16) << 8); @@ -246,6 +270,8 @@ impl RegularMap { for (i, tile) in self.tiles.iter().enumerate() { screenblock_memory.set(i, tile.0); } + + self.tiles_dirty = false; } const fn bg_control_register(&self) -> MemoryMapped { @@ -261,7 +287,7 @@ impl RegularMap { } const fn screenblock_memory(&self) -> MemoryMapped1DArray { - unsafe { MemoryMapped1DArray::new(0x0600_0000 + 0x1000 * self.screenblock as usize) } + unsafe { MemoryMapped1DArray::new(0x0600_0000 + 0x1000 * self.screenblock as usize / 2) } } } diff --git a/agb/src/display/example_logo.rs b/agb/src/display/example_logo.rs index 47166642..0f65a178 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 crate::display::background::Tiled0; + +use super::background::{Tile, TileFormat, TileSet}; 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(gfx: &mut Tiled0) { + gfx.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 = gfx.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); + let mut back = gfx.background(); + + 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] as u16; + let tile = gfx.vram.add_tile(background_tilemap_reference, tile_id); + + back.set_tile(x, y, Tile::new(tile, false, false, palette_entry)) + } } - back.set_map(Map::new(&entries, (30_u32, 20_u32).into(), 0)); - back.show(); back.commit(); + back.show(); } #[cfg(test)] mod tests { @@ -31,4 +39,4 @@ mod tests { 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 e4ce5206..e16ec663 100644 --- a/agb/src/display/mod.rs +++ b/agb/src/display/mod.rs @@ -109,6 +109,7 @@ pub fn busy_wait_for_vblank() { while VCOUNT.get() < 160 {} } +#[derive(Clone, Copy, PartialEq, Eq)] pub enum Priority { P0 = 0, P1 = 1, diff --git a/agb/src/display/video.rs b/agb/src/display/video.rs index a8d16a33..d4f61270 100644 --- a/agb/src/display/video.rs +++ b/agb/src/display/video.rs @@ -1,4 +1,4 @@ -use super::{bitmap3::Bitmap3, bitmap4::Bitmap4}; +use super::{background::Tiled0, bitmap3::Bitmap3, bitmap4::Bitmap4}; #[non_exhaustive] pub struct Video {} @@ -14,7 +14,7 @@ impl Video { unsafe { Bitmap4::new() } } - // pub fn tiled0(&mut self) -> BackgroundDistributor { - // unsafe { BackgroundDistributor::new() } - // } + pub fn tiled0(&mut self) -> Tiled0 { + unsafe { Tiled0::new() } + } } From 7c3725497c479ea33460d73a5fa51c760813525b Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Mon, 17 Jan 2022 23:12:37 +0000 Subject: [PATCH 05/76] Add method to hide background --- agb/src/display/background.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/agb/src/display/background.rs b/agb/src/display/background.rs index 5537793f..951ff9e1 100644 --- a/agb/src/display/background.rs +++ b/agb/src/display/background.rs @@ -255,6 +255,12 @@ impl RegularMap { 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); From 1562e63beea20efcb39fc382ddfe273c0e69b572 Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Sun, 23 Jan 2022 18:58:45 +0000 Subject: [PATCH 06/76] Marker is a better name for this probably --- agb/src/display/background.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/agb/src/display/background.rs b/agb/src/display/background.rs index 951ff9e1..dafd9893 100644 --- a/agb/src/display/background.rs +++ b/agb/src/display/background.rs @@ -64,7 +64,7 @@ pub struct VRamManager<'a> { vram_free_pointer: Option, } -const END_OF_FREE_LIST_REFERENCE: u16 = u16::MAX; +const END_OF_FREE_LIST_MARKER: u16 = u16::MAX; impl<'a> VRamManager<'a> { pub fn new() -> Self { @@ -135,7 +135,7 @@ impl<'a> VRamManager<'a> { } let index_to_copy_into = if let Some(ptr) = self.vram_free_pointer.take() { - if self.references[ptr] != END_OF_FREE_LIST_REFERENCE { + if self.references[ptr] != END_OF_FREE_LIST_MARKER { self.vram_free_pointer = Some(self.references[ptr] as usize); } @@ -180,7 +180,7 @@ impl<'a> VRamManager<'a> { if let Some(ptr) = self.vram_free_pointer { self.references[index] = ptr as u16; } else { - self.references[index] = END_OF_FREE_LIST_REFERENCE; + self.references[index] = END_OF_FREE_LIST_MARKER; } self.vram_free_pointer = Some(index); From 0ebd2c2e408229cdce6116ac84148c7922b0a6ad Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Sun, 23 Jan 2022 19:00:09 +0000 Subject: [PATCH 07/76] transparent is probably more correct --- agb/src/display/background.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agb/src/display/background.rs b/agb/src/display/background.rs index dafd9893..60d61d2a 100644 --- a/agb/src/display/background.rs +++ b/agb/src/display/background.rs @@ -208,7 +208,7 @@ impl<'a> VRamManager<'a> { } #[derive(Clone, Copy, Debug)] -#[repr(C)] +#[repr(transparent)] pub struct Tile(u16); impl Tile { From 2044cbf379717bac6dba4946e32b1e484823b088 Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Sun, 23 Jan 2022 19:04:09 +0000 Subject: [PATCH 08/76] Generation makes more sense to be a u16 for better data storage --- agb/src/display/background.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/agb/src/display/background.rs b/agb/src/display/background.rs index 60d61d2a..b35ed879 100644 --- a/agb/src/display/background.rs +++ b/agb/src/display/background.rs @@ -42,7 +42,7 @@ impl<'a> TileSet<'a> { #[derive(Clone, Copy, PartialEq, Eq)] pub struct TileSetReference { id: u16, - generation: u32, + generation: u16, } #[derive(Debug)] @@ -51,12 +51,12 @@ pub struct TileIndex(u16); enum ArenaStorageItem { EndOfFreeList, NextFree(usize), - Data(T, u32), + Data(T, u16), } pub struct VRamManager<'a> { tilesets: Vec>>, - generation: u32, + generation: u16, free_pointer: Option, tile_set_to_vram: HashMap<(u16, u16), u16>, @@ -81,7 +81,7 @@ impl<'a> VRamManager<'a> { pub fn add_tileset(&mut self, tileset: TileSet<'a>) -> TileSetReference { let generation = self.generation; - self.generation += 1; + self.generation = self.generation.wrapping_add(1); let tileset = ArenaStorageItem::Data(tileset, generation); @@ -332,7 +332,7 @@ impl Tiled0<'_> { } impl TileSetReference { - fn new(id: u16, generation: u32) -> Self { + fn new(id: u16, generation: u16) -> Self { Self { id, generation } } } From bdeaf346cda21162c3eacc1d577a6419e3bf41af Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Sun, 23 Jan 2022 19:19:33 +0000 Subject: [PATCH 09/76] Remove the old tile before creating the new one --- agb/src/display/background.rs | 70 ++++++++++++++++++++++++++++++----- 1 file changed, 61 insertions(+), 9 deletions(-) diff --git a/agb/src/display/background.rs b/agb/src/display/background.rs index b35ed879..ad795587 100644 --- a/agb/src/display/background.rs +++ b/agb/src/display/background.rs @@ -1,7 +1,10 @@ use alloc::vec::Vec; use hashbrown::HashMap; -use crate::memory_mapped::{MemoryMapped, MemoryMapped1DArray}; +use crate::{ + fixnum::Vector2D, + memory_mapped::{MemoryMapped, MemoryMapped1DArray}, +}; use super::{ palette16, set_graphics_mode, set_graphics_settings, DisplayMode, GraphicsSettings, Priority, @@ -128,7 +131,7 @@ impl<'a> VRamManager<'a> { } } - pub fn add_tile(&mut self, tile_set_ref: TileSetReference, tile: u16) -> TileIndex { + fn add_tile(&mut self, tile_set_ref: TileSetReference, tile: u16) -> TileIndex { if let Some(&reference) = self.tile_set_to_vram.get(&(tile_set_ref.id, tile)) { self.references[reference as usize] += 1; return TileIndex(reference as u16); @@ -169,7 +172,7 @@ impl<'a> VRamManager<'a> { TileIndex(index_to_copy_into as u16) } - pub fn remove_tile(&mut self, tile_index: TileIndex) { + fn remove_tile(&mut self, tile_index: TileIndex) { let index = tile_index.0 as usize; self.references[index] -= 1; @@ -207,14 +210,38 @@ impl<'a> VRamManager<'a> { } } -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] #[repr(transparent)] -pub struct Tile(u16); +struct Tile(u16); impl Tile { - pub fn new(tid: TileIndex, hflip: bool, vflip: bool, palette_id: u16) -> Self { + fn new(tid: TileIndex, setting: TileSetting) -> Self { + let hflip = setting.hflip; + let vflip = setting.vflip; + let palette_id = setting.palette_id as u16; Self(tid.0 | ((hflip as u16) << 10) | ((vflip as u16) << 11) | (palette_id << 12)) } + + fn tile_index(self) -> TileIndex { + TileIndex(self.0 & ((1 << 10) - 1)) + } +} + +#[derive(Clone, Copy, Debug, Default)] +pub struct TileSetting { + hflip: bool, + vflip: bool, + palette_id: u8, +} + +impl TileSetting { + pub fn new(hflip: bool, vflip: bool, palette_id: u8) -> Self { + Self { + hflip, + vflip, + palette_id, + } + } } pub struct RegularMap { @@ -239,16 +266,41 @@ impl RegularMap { y_scroll: 0, priority: Priority::P0, - tiles: [Tile(0); 32 * 32], + tiles: [Tile::default(); 32 * 32], tiles_dirty: true, } } - pub fn set_tile(&mut self, x: u16, y: u16, tile: Tile) { - self.tiles[(x + y * 32) as usize] = tile; + pub fn set_tile( + &mut self, + vram: &mut VRamManager, + pos: Vector2D, + tileset_ref: TileSetReference, + tile_index: u16, + 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 new_tile_idx = vram.add_tile(tileset_ref, tile_index); + let new_tile = Tile::new(new_tile_idx, tile_setting); + + self.tiles[pos] = new_tile; self.tiles_dirty = true; } + pub fn clear(&mut self, vram: &mut VRamManager) { + for tile in self.tiles.iter_mut() { + 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)); From 8fcb9e607da50b3ff076776f78a21eb6498ef3d3 Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Sun, 23 Jan 2022 19:21:42 +0000 Subject: [PATCH 10/76] Special case the first element in references --- agb/src/display/background.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/agb/src/display/background.rs b/agb/src/display/background.rs index ad795587..287733d4 100644 --- a/agb/src/display/background.rs +++ b/agb/src/display/background.rs @@ -1,3 +1,4 @@ +use alloc::vec; use alloc::vec::Vec; use hashbrown::HashMap; @@ -77,7 +78,7 @@ impl<'a> VRamManager<'a> { free_pointer: None, tile_set_to_vram: HashMap::new(), - references: Vec::new(), + references: vec![1], vram_free_pointer: None, } } From 06c988e952fcbd087079c8e465b2904d9e99bc06 Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Sun, 23 Jan 2022 19:28:08 +0000 Subject: [PATCH 11/76] Fix the test logo and the example --- agb/examples/test_logo.rs | 5 ++++- agb/src/display/example_logo.rs | 33 ++++++++++++++++++--------------- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/agb/examples/test_logo.rs b/agb/examples/test_logo.rs index 8cfc8164..e61da840 100644 --- a/agb/examples/test_logo.rs +++ b/agb/examples/test_logo.rs @@ -7,7 +7,10 @@ use agb::display::example_logo; fn main(mut gba: agb::Gba) -> ! { let mut gfx = gba.display.video.tiled0(); - example_logo::display_logo(&mut gfx); + let mut map = gfx.background(); + let mut vram = gfx.vram; + + example_logo::display_logo(&mut map, &mut vram); loop {} } diff --git a/agb/src/display/example_logo.rs b/agb/src/display/example_logo.rs index 0f65a178..93f571bc 100644 --- a/agb/src/display/example_logo.rs +++ b/agb/src/display/example_logo.rs @@ -1,31 +1,32 @@ -use crate::display::background::Tiled0; - -use super::background::{Tile, TileFormat, TileSet}; +use super::background::{RegularMap, TileFormat, TileSet, TileSetting, VRamManager}; crate::include_gfx!("gfx/agb_logo.toml"); -pub fn display_logo(gfx: &mut Tiled0) { - gfx.vram - .set_background_palettes(agb_logo::test_logo.palettes); +pub fn display_logo(map: &mut RegularMap, vram: &mut VRamManager) { + vram.set_background_palettes(agb_logo::test_logo.palettes); let background_tilemap = TileSet::new(agb_logo::test_logo.tiles, TileFormat::FourBpp); - let background_tilemap_reference = gfx.vram.add_tileset(background_tilemap); - - let mut back = gfx.background(); + let background_tilemap_reference = vram.add_tileset(background_tilemap); 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] as u16; - let tile = gfx.vram.add_tile(background_tilemap_reference, tile_id); + let palette_entry = agb_logo::test_logo.palette_assignments[tile_id as usize]; + let tile_setting = TileSetting::new(false, false, palette_entry); - back.set_tile(x, y, Tile::new(tile, false, false, palette_entry)) + map.set_tile( + vram, + (x, y).into(), + background_tilemap_reference, + tile_id, + tile_setting, + ); } } - back.commit(); - back.show(); + map.commit(); + map.show(); } #[cfg(test)] mod tests { @@ -35,7 +36,9 @@ mod tests { fn logo_display(gba: &mut crate::Gba) { let mut gfx = gba.display.video.tiled0(); - display_logo(&mut gfx); + let mut map = gfx.background(); + + display_logo(&mut map, &mut gfx.vram); crate::test_runner::assert_image_output("gfx/test_logo.png"); } From 0081d1c6ee16da53147352350c83e7245530a943 Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Sun, 23 Jan 2022 19:35:24 +0000 Subject: [PATCH 12/76] Update the chicken example --- agb/examples/chicken.rs | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/agb/examples/chicken.rs b/agb/examples/chicken.rs index 359a4811..8f3c6d70 100644 --- a/agb/examples/chicken.rs +++ b/agb/examples/chicken.rs @@ -2,7 +2,11 @@ #![no_main] use agb::{ - display::{background::Map, object::ObjectStandard, HEIGHT, WIDTH}, + display::{ + background::{RegularMap, TileFormat, TileSet, TileSetting}, + object::ObjectStandard, + HEIGHT, WIDTH, + }, input::Button, }; use core::convert::TryInto; @@ -46,11 +50,23 @@ fn main(mut gba: agb::Gba) -> ! { 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); + gfx.vram.set_background_palette_raw(&MAP_PALETTE); + let tileset = TileSet::new(&MAP_TILES, TileFormat::FourBpp); + let tileset_ref = gfx.vram.add_tileset(tileset); + + let mut background = gfx.background(); + + for (i, tile) in MAP_MAP.iter().enumerate() { + let i = i as u16; + background.set_tile( + &mut gfx.vram, + (i % 32, i / 32).into(), + tileset_ref, + tile & ((1 << 10) - 1), + TileSetting::default(), + ); + } - let mut background = gfx.get_regular().unwrap(); - background.set_map(Map::new(&MAP_MAP, (32_u32, 32_u32).into(), 0)); background.show(); background.commit(); From 1286a585118fd1a8d261ade72dc533e73fb4b8dd Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Sun, 23 Jan 2022 19:39:08 +0000 Subject: [PATCH 13/76] Ensure that we don't copy twice --- agb/src/display/background.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/agb/src/display/background.rs b/agb/src/display/background.rs index 287733d4..96dcc8a0 100644 --- a/agb/src/display/background.rs +++ b/agb/src/display/background.rs @@ -170,6 +170,9 @@ impl<'a> VRamManager<'a> { TILE_BACKGROUND.set(index_to_copy_into * tile_size_in_words + i, word); } + self.tile_set_to_vram + .insert((tile_set_ref.id, tile), index_to_copy_into as u16); + TileIndex(index_to_copy_into as u16) } From cf72a9331add8c3537a09d5d67a33584bedab06d Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Sun, 23 Jan 2022 19:43:08 +0000 Subject: [PATCH 14/76] Add API for setting tile data from just a number --- agb/examples/chicken.rs | 4 ++-- agb/src/display/background.rs | 21 +++++++++++++++++---- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/agb/examples/chicken.rs b/agb/examples/chicken.rs index 8f3c6d70..6f141f3a 100644 --- a/agb/examples/chicken.rs +++ b/agb/examples/chicken.rs @@ -56,14 +56,14 @@ fn main(mut gba: agb::Gba) -> ! { let mut background = gfx.background(); - for (i, tile) in MAP_MAP.iter().enumerate() { + for (i, &tile) in MAP_MAP.iter().enumerate() { let i = i as u16; background.set_tile( &mut gfx.vram, (i % 32, i / 32).into(), tileset_ref, tile & ((1 << 10) - 1), - TileSetting::default(), + TileSetting::from_raw(tile), ); } diff --git a/agb/src/display/background.rs b/agb/src/display/background.rs index 96dcc8a0..a3c764f3 100644 --- a/agb/src/display/background.rs +++ b/agb/src/display/background.rs @@ -220,10 +220,7 @@ struct Tile(u16); impl Tile { fn new(tid: TileIndex, setting: TileSetting) -> Self { - let hflip = setting.hflip; - let vflip = setting.vflip; - let palette_id = setting.palette_id as u16; - Self(tid.0 | ((hflip as u16) << 10) | ((vflip as u16) << 11) | (palette_id << 12)) + Self(tid.0 | setting.as_raw()) } fn tile_index(self) -> TileIndex { @@ -246,6 +243,22 @@ impl TileSetting { palette_id, } } + + pub fn from_raw(raw: u16) -> Self { + let hflip = raw & (1 << 10) != 0; + let vflip = raw & (1 << 11) != 0; + let palette_id = raw >> 12; + + Self { + hflip, + vflip, + palette_id: palette_id as u8, + } + } + + fn as_raw(self) -> u16 { + ((self.hflip as u16) << 10) | ((self.vflip as u16) << 11) | ((self.palette_id as u16) << 12) + } } pub struct RegularMap { From 38e57489a3c896b0e5fe266bf17bdba8b03f57ba Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Sun, 23 Jan 2022 20:03:09 +0000 Subject: [PATCH 15/76] Rework the settings a little to allow for raw tile ids --- agb/examples/chicken.rs | 3 +-- agb/src/display/background.rs | 45 ++++++++++++++------------------- agb/src/display/example_logo.rs | 3 +-- 3 files changed, 21 insertions(+), 30 deletions(-) diff --git a/agb/examples/chicken.rs b/agb/examples/chicken.rs index 6f141f3a..af2a7294 100644 --- a/agb/examples/chicken.rs +++ b/agb/examples/chicken.rs @@ -3,7 +3,7 @@ use agb::{ display::{ - background::{RegularMap, TileFormat, TileSet, TileSetting}, + background::{TileFormat, TileSet, TileSetting}, object::ObjectStandard, HEIGHT, WIDTH, }, @@ -62,7 +62,6 @@ fn main(mut gba: agb::Gba) -> ! { &mut gfx.vram, (i % 32, i / 32).into(), tileset_ref, - tile & ((1 << 10) - 1), TileSetting::from_raw(tile), ); } diff --git a/agb/src/display/background.rs b/agb/src/display/background.rs index a3c764f3..a47876bb 100644 --- a/agb/src/display/background.rs +++ b/agb/src/display/background.rs @@ -219,8 +219,8 @@ impl<'a> VRamManager<'a> { struct Tile(u16); impl Tile { - fn new(tid: TileIndex, setting: TileSetting) -> Self { - Self(tid.0 | setting.as_raw()) + fn new(idx: TileIndex, setting: TileSetting) -> Self { + Self(idx.0 | setting.setting()) } fn tile_index(self) -> TileIndex { @@ -229,35 +229,28 @@ impl Tile { } #[derive(Clone, Copy, Debug, Default)] -pub struct TileSetting { - hflip: bool, - vflip: bool, - palette_id: u8, -} +pub struct TileSetting(u16); impl TileSetting { - pub fn new(hflip: bool, vflip: bool, palette_id: u8) -> Self { - Self { - hflip, - vflip, - palette_id, - } + 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 fn from_raw(raw: u16) -> Self { - let hflip = raw & (1 << 10) != 0; - let vflip = raw & (1 << 11) != 0; - let palette_id = raw >> 12; - - Self { - hflip, - vflip, - palette_id: palette_id as u8, - } + pub const fn from_raw(raw: u16) -> Self { + Self(raw) } - fn as_raw(self) -> u16 { - ((self.hflip as u16) << 10) | ((self.vflip as u16) << 11) | ((self.palette_id as u16) << 12) + fn index(self) -> u16 { + self.0 & ((1 << 10) - 1) + } + + fn setting(self) -> u16 { + self.0 & !((1 << 10) - 1) } } @@ -293,7 +286,6 @@ impl RegularMap { vram: &mut VRamManager, pos: Vector2D, tileset_ref: TileSetReference, - tile_index: u16, tile_setting: TileSetting, ) { let pos = (pos.x + pos.y * 32) as usize; @@ -303,6 +295,7 @@ impl RegularMap { vram.remove_tile(old_tile.tile_index()); } + let tile_index = tile_setting.index(); let new_tile_idx = vram.add_tile(tileset_ref, tile_index); let new_tile = Tile::new(new_tile_idx, tile_setting); diff --git a/agb/src/display/example_logo.rs b/agb/src/display/example_logo.rs index 93f571bc..a4c30119 100644 --- a/agb/src/display/example_logo.rs +++ b/agb/src/display/example_logo.rs @@ -13,13 +13,12 @@ pub fn display_logo(map: &mut RegularMap, vram: &mut VRamManager) { let tile_id = y * 30 + x; let palette_entry = agb_logo::test_logo.palette_assignments[tile_id as usize]; - let tile_setting = TileSetting::new(false, false, palette_entry); + let tile_setting = TileSetting::new(tile_id, false, false, palette_entry); map.set_tile( vram, (x, y).into(), background_tilemap_reference, - tile_id, tile_setting, ); } From 472875edac48e2213d5113fa1f29242fc8e1cc4d Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Sun, 23 Jan 2022 20:03:59 +0000 Subject: [PATCH 16/76] Update the wave example --- agb/examples/wave.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/agb/examples/wave.rs b/agb/examples/wave.rs index 9036eb9a..e237a13c 100644 --- a/agb/examples/wave.rs +++ b/agb/examples/wave.rs @@ -19,7 +19,9 @@ struct BackCosines { fn main(mut gba: agb::Gba) -> ! { let mut gfx = gba.display.video.tiled0(); - example_logo::display_logo(&mut gfx); + let mut background = gfx.background(); + + example_logo::display_logo(&mut background, &mut gfx.vram); let mut time = 0; let cosines = [0_u16; 32]; From f785d65057f3c76935fa85f00bf184db12e3db58 Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Mon, 24 Jan 2022 20:50:27 +0000 Subject: [PATCH 17/76] Something that seems like it is close to working --- agb-fixnum/src/lib.rs | 6 +- agb/examples/test_logo.rs | 2 +- agb/examples/wave.rs | 2 +- agb/src/display/background.rs | 207 +++++++++++++++++++++++++- agb/src/display/example_logo.rs | 2 +- examples/the-purple-night/Cargo.lock | 43 +++++- examples/the-purple-night/build.rs | 4 +- examples/the-purple-night/src/main.rs | 139 +++++++++-------- 8 files changed, 319 insertions(+), 86 deletions(-) diff --git a/agb-fixnum/src/lib.rs b/agb-fixnum/src/lib.rs index 2385fc36..6128be88 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, @@ -657,10 +657,10 @@ impl Rect { 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; } } diff --git a/agb/examples/test_logo.rs b/agb/examples/test_logo.rs index e61da840..b7436e08 100644 --- a/agb/examples/test_logo.rs +++ b/agb/examples/test_logo.rs @@ -7,7 +7,7 @@ use agb::display::example_logo; fn main(mut gba: agb::Gba) -> ! { let mut gfx = gba.display.video.tiled0(); - let mut map = gfx.background(); + let mut map = gfx.background(agb::display::Priority::P0); let mut vram = gfx.vram; example_logo::display_logo(&mut map, &mut vram); diff --git a/agb/examples/wave.rs b/agb/examples/wave.rs index e237a13c..af1d5049 100644 --- a/agb/examples/wave.rs +++ b/agb/examples/wave.rs @@ -19,7 +19,7 @@ struct BackCosines { fn main(mut gba: agb::Gba) -> ! { let mut gfx = gba.display.video.tiled0(); - let mut background = gfx.background(); + let mut background = gfx.background(agb::display::Priority::P0); example_logo::display_logo(&mut background, &mut gfx.vram); diff --git a/agb/src/display/background.rs b/agb/src/display/background.rs index a47876bb..2f59ffd9 100644 --- a/agb/src/display/background.rs +++ b/agb/src/display/background.rs @@ -1,9 +1,10 @@ -use alloc::vec; use alloc::vec::Vec; +use alloc::{boxed::Box, vec}; use hashbrown::HashMap; use crate::{ - fixnum::Vector2D, + display, + fixnum::{Rect, Vector2D}, memory_mapped::{MemoryMapped, MemoryMapped1DArray}, }; @@ -267,14 +268,14 @@ pub struct RegularMap { } impl RegularMap { - fn new(background_id: u8, screenblock: u8) -> Self { + fn new(background_id: u8, screenblock: u8, priority: Priority) -> Self { Self { background_id, screenblock, x_scroll: 0, y_scroll: 0, - priority: Priority::P0, + priority, tiles: [Tile::default(); 32 * 32], tiles_dirty: true, @@ -335,13 +336,35 @@ impl RegularMap { } let screenblock_memory = self.screenblock_memory(); - for (i, tile) in self.tiles.iter().enumerate() { - screenblock_memory.set(i, tile.0); + + let scroll_pos = self.get_scroll_pos(); + let x_scroll = scroll_pos.x % display::WIDTH as u16; + let y_scroll = scroll_pos.y % display::HEIGHT as u16; + let start_x = x_scroll / 8; + let end_x = (x_scroll + display::WIDTH as u16 + 8 - 1) / 8; // divide by 8 rounding up + + let start_y = y_scroll / 8; + let end_y = (y_scroll + display::HEIGHT as u16 + 8 - 1) / 8; + + for y in start_y..end_y { + for x in start_x..end_x { + let id = y.rem_euclid(32) * 32 + x.rem_euclid(32); + screenblock_memory.set(id as usize, self.tiles[id as usize].0); + } } 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) } } @@ -359,6 +382,174 @@ impl RegularMap { } } +pub struct InfiniteScrolledMap { + map: RegularMap, + get_tile: Box) -> (TileSetReference, TileSetting)>, + + current_pos: Vector2D, + offset: Vector2D, +} + +impl InfiniteScrolledMap { + pub fn new( + map: RegularMap, + get_tile: Box) -> (TileSetReference, TileSetting)>, + ) -> Self { + Self { + map, + get_tile, + current_pos: (0, 0).into(), + offset: (0, 0).into(), + } + } + + pub fn init(&mut self, vram: &mut VRamManager, pos: Vector2D) { + 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); + let y_end = div_ceil(self.current_pos.y + display::HEIGHT, 8); + + for (y_idx, y) in (y_start..y_end).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 as u16).into(), + tile_set_ref, + tile_setting, + ); + } + } + + let offset = self.current_pos - (x_start * 8, y_start * 8).into(); + let offset_scroll = ( + if offset.x < 0 { + (offset.x + 32 * 8) as u16 + } else { + offset.x as u16 + }, + if offset.y < 0 { + (offset.y + 32 * 8) as u16 + } else { + offset.y as u16 + }, + ) + .into(); + + self.map.set_scroll_pos(offset_scroll); + self.offset = offset; + } + + pub fn set_pos(&mut self, vram: &mut VRamManager, new_pos: Vector2D) { + let old_pos = self.current_pos; + + let difference = new_pos - old_pos; + + if difference.x.abs() > 8 || difference.y.abs() > 8 { + self.init(vram, new_pos); + return; + } + + let is_new_y_mod_8 = new_pos.y % 8 == 0; + let is_new_x_mod_8 = new_pos.x % 8 == 0; + + let top_bottom_rect: Rect = { + let top_bottom_height = difference.y.signum(); + let new_tile_y = if top_bottom_height > 0 { + div_ceil(new_pos.y, 8) + if is_new_y_mod_8 { 20 } else { 22 } + } else { + div_floor(new_pos.y, 8) + }; + + Rect::new( + (div_floor(new_pos.x, 8), new_tile_y).into(), + ( + if is_new_x_mod_8 { 30 } else { 32 }, + top_bottom_height.abs(), + ) + .into(), + ) + }; + + let left_right_rect: Rect = { + let left_right_width = difference.x.signum(); + let new_tile_x = if left_right_width > 0 { + div_ceil(new_pos.x, 8) + if is_new_x_mod_8 { 30 } else { 32 } + } else { + div_floor(new_pos.x, 8) + }; + + Rect::new( + (new_tile_x, div_floor(new_pos.y, 8)).into(), + (left_right_width.abs(), if is_new_y_mod_8 { 20 } else { 22 }).into(), + ) + }; + + self.offset += difference; + self.current_pos = new_pos; + let offset_block = self.offset / 8; + + for (x, y) in top_bottom_rect.iter().chain(left_right_rect.iter()) { + let (tileset_ref, tile_setting) = (self.get_tile)((x, y).into()); + + let tile_pos = ( + (x + offset_block.x).rem_euclid(32) as u16, + (y + offset_block.y).rem_euclid(32) as u16, + ) + .into(); + + self.map.set_tile(vram, tile_pos, tileset_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); + } + + pub fn show(&mut self) { + self.map.show(); + } + + pub fn hide(&mut self) { + self.map.hide(); + } + + pub fn commit(&mut self) { + self.map.commit(); + } +} + +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 + } +} + pub struct Tiled0<'a> { num_regular: u8, next_screenblock: u8, @@ -379,12 +570,12 @@ impl Tiled0<'_> { } } - pub fn background(&mut self) -> RegularMap { + pub fn background(&mut self, priority: Priority) -> RegularMap { if self.num_regular == 4 { panic!("Can only create 4 backgrounds"); } - let bg = RegularMap::new(self.num_regular, self.next_screenblock); + let bg = RegularMap::new(self.num_regular, self.next_screenblock, priority); self.num_regular += 1; self.next_screenblock += 1; diff --git a/agb/src/display/example_logo.rs b/agb/src/display/example_logo.rs index a4c30119..4d94208e 100644 --- a/agb/src/display/example_logo.rs +++ b/agb/src/display/example_logo.rs @@ -35,7 +35,7 @@ mod tests { fn logo_display(gba: &mut crate::Gba) { let mut gfx = gba.display.video.tiled0(); - let mut map = gfx.background(); + let mut map = gfx.background(crate::display::Priority::P0); display_logo(&mut map, &mut gfx.vram); diff --git a/examples/the-purple-night/Cargo.lock b/examples/the-purple-night/Cargo.lock index 7a6b1cdb..ebe8e942 100644 --- a/examples/the-purple-night/Cargo.lock +++ b/examples/the-purple-night/Cargo.lock @@ -24,6 +24,7 @@ dependencies = [ "agb_sound_converter", "bare-metal", "bitflags", + "hashbrown", ] [[package]] @@ -65,6 +66,17 @@ dependencies = [ "syn", ] +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -94,9 +106,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bytemuck" -version = "1.7.3" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439989e6b8c38d1b6570a384ef1e49c8848128f5a97f3914baef02920842712f" +checksum = "0e851ca7c24871e7336801608a4797d7376545b6928a10d32d75685687141ead" [[package]] name = "byteorder" @@ -160,6 +172,15 @@ dependencies = [ "wasi", ] +[[package]] +name = "hashbrown" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c21d40587b92fa6a6c6e3c1bdbf87d75511db5672f9c93175574b3a00df1758" +dependencies = [ + "ahash", +] + [[package]] name = "hound" version = "3.4.0" @@ -250,10 +271,16 @@ dependencies = [ ] [[package]] -name = "png" -version = "0.17.4" +name = "once_cell" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02cd7d51cea7e2fa6bbcb8af5fbcad15b871451bfc2d20ed72dff2f4ae072a84" +checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" + +[[package]] +name = "png" +version = "0.17.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc38c0ad57efb786dd57b9864e5b18bae478c00c824dc55a38bbc9da95dde3ba" dependencies = [ "bitflags", "crc32fast", @@ -394,6 +421,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + [[package]] name = "wasi" version = "0.10.2+wasi-snapshot-preview1" 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 ea00124f..f4ca137e 100644 --- a/examples/the-purple-night/src/main.rs +++ b/examples/the-purple-night/src/main.rs @@ -8,13 +8,13 @@ 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}, + background::{InfiniteScrolledMap, TileFormat, TileSet, TileSetting, VRamManager}, object::{ObjectControl, ObjectStandard}, Priority, HEIGHT, WIDTH, }, @@ -29,9 +29,9 @@ agb::include_gfx!("gfx/background.toml"); type Number = FixedNum<8>; struct Level { - background: BackgroundRegular<'static>, - foreground: BackgroundRegular<'static>, - clouds: BackgroundRegular<'static>, + background: InfiniteScrolledMap, + foreground: InfiniteScrolledMap, + clouds: InfiniteScrolledMap, slime_spawns: Vec<(u16, u16)>, bat_spawns: Vec<(u16, u16)>, @@ -40,33 +40,14 @@ struct Level { impl Level { fn load_level( - mut backdrop: BackgroundRegular<'static>, - mut foreground: BackgroundRegular<'static>, - mut clouds: BackgroundRegular<'static>, + mut backdrop: InfiniteScrolledMap, + mut foreground: InfiniteScrolledMap, + mut clouds: InfiniteScrolledMap, + vram: &mut VRamManager, ) -> 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); - - 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); - - 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, (8, 8).into()); + foreground.init(vram, (8, 8).into()); + clouds.init(vram, (2, 2).into()); backdrop.commit(); foreground.commit(); @@ -1804,8 +1785,6 @@ struct Game<'a> { boss: BossState<'a>, move_state: MoveState, fade_count: u16, - - background_distributor: &'a mut BackgroundDistributor, } enum MoveState { @@ -1826,6 +1805,7 @@ impl<'a> Game<'a> { fn advance_frame( &mut self, object_controller: &'a ObjectControl, + vram: &mut VRamManager, sfx: &mut sfx::Sfx, ) -> GameStatus { let mut state = GameStatus::Continue; @@ -1845,7 +1825,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 { @@ -1867,7 +1847,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); } } } @@ -1969,15 +1949,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); + let background_offset = (this_frame_offset.floor().x + 8, 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); self.level.background.commit(); self.level.foreground.commit(); self.level.clouds.commit(); @@ -2082,7 +2058,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); @@ -2093,10 +2069,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); @@ -2107,15 +2083,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 ObjectControl, - level: Level, - background_distributor: &'a mut BackgroundDistributor, - start_at_boss: bool, - ) -> Self { + fn new(object: &'a ObjectControl, level: Level, start_at_boss: bool) -> Self { let mut player = Player::new(object); let mut offset = (8, 8).into(); if start_at_boss { @@ -2139,8 +2110,6 @@ impl<'a> Game<'a> { move_state: MoveState::Advancing, sunrise_timer: 0, fade_count: 0, - - background_distributor, } } } @@ -2170,19 +2139,59 @@ fn game_with_level(gba: &mut agb::Gba) { loop { let mut background = gba.display.video.tiled0(); - background.set_background_palettes(background::background.palettes); - background.set_background_tilemap(0, background::background.tiles); + background + .vram + .set_background_palettes(background::background.palettes); + + let tileset_ref = background.vram.add_tileset(TileSet::new( + background::background.tiles, + TileFormat::FourBpp, + )); + let mut object = gba.display.object.get(); object.enable(); + let backdrop = InfiniteScrolledMap::new( + background.background(Priority::P2), + Box::new(move |pos| { + ( + tileset_ref, + TileSetting::from_raw( + tilemap::BACKGROUND_MAP[(pos.x + tilemap::WIDTH * pos.y) as usize], + ), + ) + }), + ); + + let foreground = InfiniteScrolledMap::new( + background.background(Priority::P0), + Box::new(move |pos| { + ( + tileset_ref, + TileSetting::from_raw( + tilemap::FOREGROUND_MAP[(pos.x + tilemap::WIDTH * pos.y) as usize], + ), + ) + }), + ); + + let clouds = InfiniteScrolledMap::new( + background.background(Priority::P3), + Box::new(move |pos| { + agb::println!("CLOUDS: {}, {}", pos.x, pos.y); + + ( + tileset_ref, + TileSetting::from_raw( + tilemap::CLOUD_MAP[(pos.x + tilemap::WIDTH * pos.y) as usize], + ), + ) + }), + ); + 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, &mut background.vram), start_at_boss, ); @@ -2190,7 +2199,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 background.vram, &mut sfx) { GameStatus::Continue => {} GameStatus::Lost => { break false; From 7f0d7757c5e45dcbafd5378b71285052c41ce00e Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Mon, 24 Jan 2022 22:52:20 +0000 Subject: [PATCH 18/76] Really trying hard to get this working --- agb/src/display/background.rs | 107 +++++++++++++++----------- examples/the-purple-night/src/main.rs | 2 +- 2 files changed, 65 insertions(+), 44 deletions(-) diff --git a/agb/src/display/background.rs b/agb/src/display/background.rs index 2f59ffd9..cf108be8 100644 --- a/agb/src/display/background.rs +++ b/agb/src/display/background.rs @@ -442,7 +442,7 @@ impl InfiniteScrolledMap { .into(); self.map.set_scroll_pos(offset_scroll); - self.offset = offset; + self.offset = pos * -1; } pub fn set_pos(&mut self, vram: &mut VRamManager, new_pos: Vector2D) { @@ -455,59 +455,80 @@ impl InfiniteScrolledMap { return; } - let is_new_y_mod_8 = new_pos.y % 8 == 0; - let is_new_x_mod_8 = new_pos.x % 8 == 0; + self.current_pos = new_pos; - let top_bottom_rect: Rect = { - let top_bottom_height = difference.y.signum(); - let new_tile_y = if top_bottom_height > 0 { - div_ceil(new_pos.y, 8) + if is_new_y_mod_8 { 20 } else { 22 } + let new_tile_x = div_floor(new_pos.x, 8); + let new_tile_y = div_floor(new_pos.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: i32 = if new_pos.y % 8 == 0 { 20 } else { 21 }; + + let line_to_update = if direction < 0 { + // moving to the left, so need to update the left most position + new_tile_x } else { - div_floor(new_pos.y, 8) + // moving to the right, so need to update the right most position + new_tile_x + 30 // TODO is this correct? }; Rect::new( - (div_floor(new_pos.x, 8), new_tile_y).into(), + (line_to_update, new_tile_y).into(), + (1, 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 = if new_pos.x % 8 == 0 { 30 } else { 31 }; + + 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, line_to_update).into(), + (x_tiles_to_update, 1).into(), + ) + } else { + Rect::new((0i32, 0).into(), (0i32, 0).into()) + }; + + let tile_offset = Vector2D::new(div_floor(self.offset.x, 8), div_floor(self.offset.y, 8)); + + 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, ( - if is_new_x_mod_8 { 30 } else { 32 }, - top_bottom_height.abs(), + (tile_x + tile_offset.x).rem_euclid(32) as u16, + (tile_y + tile_offset.y).rem_euclid(32) as u16, ) .into(), - ) - }; - - let left_right_rect: Rect = { - let left_right_width = difference.x.signum(); - let new_tile_x = if left_right_width > 0 { - div_ceil(new_pos.x, 8) + if is_new_x_mod_8 { 30 } else { 32 } - } else { - div_floor(new_pos.x, 8) - }; - - Rect::new( - (new_tile_x, div_floor(new_pos.y, 8)).into(), - (left_right_width.abs(), if is_new_y_mod_8 { 20 } else { 22 }).into(), - ) - }; - - self.offset += difference; - self.current_pos = new_pos; - let offset_block = self.offset / 8; - - for (x, y) in top_bottom_rect.iter().chain(left_right_rect.iter()) { - let (tileset_ref, tile_setting) = (self.get_tile)((x, y).into()); - - let tile_pos = ( - (x + offset_block.x).rem_euclid(32) as u16, - (y + offset_block.y).rem_euclid(32) as u16, - ) - .into(); - - self.map.set_tile(vram, tile_pos, tileset_ref, tile_setting) + 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, diff --git a/examples/the-purple-night/src/main.rs b/examples/the-purple-night/src/main.rs index f4ca137e..bc684fbf 100644 --- a/examples/the-purple-night/src/main.rs +++ b/examples/the-purple-night/src/main.rs @@ -1949,7 +1949,7 @@ impl<'a> Game<'a> { self.player.commit(this_frame_offset); self.boss.commit(this_frame_offset); - let background_offset = (this_frame_offset.floor().x + 8, 8).into(); + 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); From bf10d9a5ffb26fa71efbdca79f944fd8f3a59190 Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Mon, 31 Jan 2022 19:43:13 +0000 Subject: [PATCH 19/76] Try copying the correct amount --- agb/src/display/background.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/agb/src/display/background.rs b/agb/src/display/background.rs index cf108be8..cd1f1bf9 100644 --- a/agb/src/display/background.rs +++ b/agb/src/display/background.rs @@ -341,10 +341,10 @@ impl RegularMap { let x_scroll = scroll_pos.x % display::WIDTH as u16; let y_scroll = scroll_pos.y % display::HEIGHT as u16; let start_x = x_scroll / 8; - let end_x = (x_scroll + display::WIDTH as u16 + 8 - 1) / 8; // divide by 8 rounding up + let end_x = (x_scroll + display::WIDTH as u16 + 8 - 1) / 8 + 1; // divide by 8 rounding up let start_y = y_scroll / 8; - let end_y = (y_scroll + display::HEIGHT as u16 + 8 - 1) / 8; + let end_y = (y_scroll + display::HEIGHT as u16 + 8 - 1) / 8 + 1; for y in start_y..end_y { for x in start_x..end_x { @@ -409,8 +409,8 @@ impl InfiniteScrolledMap { 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); - let y_end = div_ceil(self.current_pos.y + display::HEIGHT, 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; for (y_idx, y) in (y_start..y_end).enumerate() { for (x_idx, x) in (x_start..x_end).enumerate() { From a2c6c6a70c0b839ad48e6d2131b600d49ea6fe5b Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Mon, 31 Jan 2022 20:00:06 +0000 Subject: [PATCH 20/76] Make the tile references clearer --- agb/src/display/background.rs | 51 ++++++++++++++++++++++++++--------- 1 file changed, 39 insertions(+), 12 deletions(-) diff --git a/agb/src/display/background.rs b/agb/src/display/background.rs index cd1f1bf9..5c6a4516 100644 --- a/agb/src/display/background.rs +++ b/agb/src/display/background.rs @@ -53,6 +53,11 @@ pub struct TileSetReference { #[derive(Debug)] pub struct TileIndex(u16); +enum TileReference { + ReferenceCounted(u16, (u16, u16)), + Free(u16), +} + enum ArenaStorageItem { EndOfFreeList, NextFree(usize), @@ -65,7 +70,7 @@ pub struct VRamManager<'a> { free_pointer: Option, tile_set_to_vram: HashMap<(u16, u16), u16>, - references: Vec, + references: Vec, vram_free_pointer: Option, } @@ -79,7 +84,7 @@ impl<'a> VRamManager<'a> { free_pointer: None, tile_set_to_vram: HashMap::new(), - references: vec![1], + references: vec![TileReference::Free(0)], vram_free_pointer: None, } } @@ -134,20 +139,35 @@ impl<'a> VRamManager<'a> { } fn add_tile(&mut self, tile_set_ref: TileSetReference, tile: u16) -> TileIndex { - if let Some(&reference) = self.tile_set_to_vram.get(&(tile_set_ref.id, tile)) { - self.references[reference as usize] += 1; + let tile_ref = (tile_set_ref.id, tile); + if let Some(&reference) = self.tile_set_to_vram.get(&tile_ref) { + if let TileReference::ReferenceCounted(count, tile_ref) = + self.references[reference as usize] + { + self.references[reference as usize] = + TileReference::ReferenceCounted(count + 1, tile_ref); + } else { + panic!("Corrupted tile reference state"); + } + return TileIndex(reference as u16); } let index_to_copy_into = if let Some(ptr) = self.vram_free_pointer.take() { - if self.references[ptr] != END_OF_FREE_LIST_MARKER { - self.vram_free_pointer = Some(self.references[ptr] as usize); + match self.references[ptr] { + TileReference::Free(next_free) => { + if next_free != END_OF_FREE_LIST_MARKER { + self.vram_free_pointer = Some(next_free as usize); + } + } + TileReference::ReferenceCounted(_, _) => panic!("Corrupted tile reference state"), } - self.references[ptr] = 1; + self.references[ptr] = TileReference::ReferenceCounted(1, tile_ref); ptr } else { - self.references.push(1); + self.references + .push(TileReference::ReferenceCounted(1, tile_ref)); self.references.len() - 1 }; @@ -179,16 +199,23 @@ impl<'a> VRamManager<'a> { fn remove_tile(&mut self, tile_index: TileIndex) { let index = tile_index.0 as usize; - self.references[index] -= 1; - if self.references[index] != 0 { + let (new_count, tile_ref) = match self.references[index] { + TileReference::ReferenceCounted(count, tile_ref) => { + self.references[index] = TileReference::ReferenceCounted(count - 1, tile_ref); + (count - 1, tile_ref) + } + _ => panic!("Corrupted tile reference state"), + }; + + if new_count != 0 { return; } if let Some(ptr) = self.vram_free_pointer { - self.references[index] = ptr as u16; + self.references[index] = TileReference::Free(ptr as u16); } else { - self.references[index] = END_OF_FREE_LIST_MARKER; + self.references[index] = TileReference::Free(END_OF_FREE_LIST_MARKER); } self.vram_free_pointer = Some(index); From 5533c9c052e45e61731764addf93f816a573eea5 Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Mon, 31 Jan 2022 20:00:20 +0000 Subject: [PATCH 21/76] Fix reuse issue --- agb/src/display/background.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/agb/src/display/background.rs b/agb/src/display/background.rs index 5c6a4516..daa4f5be 100644 --- a/agb/src/display/background.rs +++ b/agb/src/display/background.rs @@ -218,6 +218,8 @@ impl<'a> VRamManager<'a> { self.references[index] = TileReference::Free(END_OF_FREE_LIST_MARKER); } + self.tile_set_to_vram.remove(&tile_ref); + self.vram_free_pointer = Some(index); } From 2a04ebd88429cc3c448fe8ff20c241ce4b2a89c1 Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Mon, 31 Jan 2022 20:01:26 +0000 Subject: [PATCH 22/76] Rename TileReference to VRamState --- agb/src/display/background.rs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/agb/src/display/background.rs b/agb/src/display/background.rs index daa4f5be..07f86911 100644 --- a/agb/src/display/background.rs +++ b/agb/src/display/background.rs @@ -53,7 +53,7 @@ pub struct TileSetReference { #[derive(Debug)] pub struct TileIndex(u16); -enum TileReference { +enum VRamState { ReferenceCounted(u16, (u16, u16)), Free(u16), } @@ -70,7 +70,7 @@ pub struct VRamManager<'a> { free_pointer: Option, tile_set_to_vram: HashMap<(u16, u16), u16>, - references: Vec, + references: Vec, vram_free_pointer: Option, } @@ -84,7 +84,7 @@ impl<'a> VRamManager<'a> { free_pointer: None, tile_set_to_vram: HashMap::new(), - references: vec![TileReference::Free(0)], + references: vec![VRamState::Free(0)], vram_free_pointer: None, } } @@ -141,11 +141,11 @@ impl<'a> VRamManager<'a> { fn add_tile(&mut self, tile_set_ref: TileSetReference, tile: u16) -> TileIndex { let tile_ref = (tile_set_ref.id, tile); if let Some(&reference) = self.tile_set_to_vram.get(&tile_ref) { - if let TileReference::ReferenceCounted(count, tile_ref) = + if let VRamState::ReferenceCounted(count, tile_ref) = self.references[reference as usize] { self.references[reference as usize] = - TileReference::ReferenceCounted(count + 1, tile_ref); + VRamState::ReferenceCounted(count + 1, tile_ref); } else { panic!("Corrupted tile reference state"); } @@ -155,19 +155,19 @@ impl<'a> VRamManager<'a> { let index_to_copy_into = if let Some(ptr) = self.vram_free_pointer.take() { match self.references[ptr] { - TileReference::Free(next_free) => { + VRamState::Free(next_free) => { if next_free != END_OF_FREE_LIST_MARKER { self.vram_free_pointer = Some(next_free as usize); } } - TileReference::ReferenceCounted(_, _) => panic!("Corrupted tile reference state"), + VRamState::ReferenceCounted(_, _) => panic!("Corrupted tile reference state"), } - self.references[ptr] = TileReference::ReferenceCounted(1, tile_ref); + self.references[ptr] = VRamState::ReferenceCounted(1, tile_ref); ptr } else { self.references - .push(TileReference::ReferenceCounted(1, tile_ref)); + .push(VRamState::ReferenceCounted(1, tile_ref)); self.references.len() - 1 }; @@ -201,8 +201,8 @@ impl<'a> VRamManager<'a> { let index = tile_index.0 as usize; let (new_count, tile_ref) = match self.references[index] { - TileReference::ReferenceCounted(count, tile_ref) => { - self.references[index] = TileReference::ReferenceCounted(count - 1, tile_ref); + VRamState::ReferenceCounted(count, tile_ref) => { + self.references[index] = VRamState::ReferenceCounted(count - 1, tile_ref); (count - 1, tile_ref) } _ => panic!("Corrupted tile reference state"), @@ -213,9 +213,9 @@ impl<'a> VRamManager<'a> { } if let Some(ptr) = self.vram_free_pointer { - self.references[index] = TileReference::Free(ptr as u16); + self.references[index] = VRamState::Free(ptr as u16); } else { - self.references[index] = TileReference::Free(END_OF_FREE_LIST_MARKER); + self.references[index] = VRamState::Free(END_OF_FREE_LIST_MARKER); } self.tile_set_to_vram.remove(&tile_ref); From 5793d6b6abfd115dfd7a6432ddfe616cfcb6eb40 Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Mon, 31 Jan 2022 20:07:20 +0000 Subject: [PATCH 23/76] Introduce methods for increasing and decreasing the reference --- agb/src/display/background.rs | 38 ++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/agb/src/display/background.rs b/agb/src/display/background.rs index 07f86911..b922d462 100644 --- a/agb/src/display/background.rs +++ b/agb/src/display/background.rs @@ -58,6 +58,26 @@ enum VRamState { Free(u16), } +impl VRamState { + fn increase_reference(&mut self) -> u16 { + if let VRamState::ReferenceCounted(count, _) = self { + *count += 1; + *count + } else { + panic!("Corrupted vram state"); + } + } + + fn decrease_reference(&mut self) -> (u16, (u16, u16)) { + if let VRamState::ReferenceCounted(count, tile_ref) = self { + *count -= 1; + (*count, *tile_ref) + } else { + panic!("Corrupted vram state"); + } + } +} + enum ArenaStorageItem { EndOfFreeList, NextFree(usize), @@ -141,15 +161,7 @@ impl<'a> VRamManager<'a> { fn add_tile(&mut self, tile_set_ref: TileSetReference, tile: u16) -> TileIndex { let tile_ref = (tile_set_ref.id, tile); if let Some(&reference) = self.tile_set_to_vram.get(&tile_ref) { - if let VRamState::ReferenceCounted(count, tile_ref) = - self.references[reference as usize] - { - self.references[reference as usize] = - VRamState::ReferenceCounted(count + 1, tile_ref); - } else { - panic!("Corrupted tile reference state"); - } - + self.references[reference as usize].increase_reference(); return TileIndex(reference as u16); } @@ -200,13 +212,7 @@ impl<'a> VRamManager<'a> { fn remove_tile(&mut self, tile_index: TileIndex) { let index = tile_index.0 as usize; - let (new_count, tile_ref) = match self.references[index] { - VRamState::ReferenceCounted(count, tile_ref) => { - self.references[index] = VRamState::ReferenceCounted(count - 1, tile_ref); - (count - 1, tile_ref) - } - _ => panic!("Corrupted tile reference state"), - }; + let (new_count, tile_ref) = self.references[index].decrease_reference(); if new_count != 0 { return; From cd05fee360985cd927a90d21d1659d1640c2e2b6 Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Mon, 31 Jan 2022 20:09:49 +0000 Subject: [PATCH 24/76] Use a concrete type for the TileReference --- agb/src/display/background.rs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/agb/src/display/background.rs b/agb/src/display/background.rs index b922d462..da86088e 100644 --- a/agb/src/display/background.rs +++ b/agb/src/display/background.rs @@ -53,22 +53,24 @@ pub struct TileSetReference { #[derive(Debug)] pub struct TileIndex(u16); +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +struct TileReference(u16, u16); + enum VRamState { - ReferenceCounted(u16, (u16, u16)), + ReferenceCounted(u16, TileReference), Free(u16), } impl VRamState { - fn increase_reference(&mut self) -> u16 { + fn increase_reference(&mut self) { if let VRamState::ReferenceCounted(count, _) = self { *count += 1; - *count } else { panic!("Corrupted vram state"); } } - fn decrease_reference(&mut self) -> (u16, (u16, u16)) { + fn decrease_reference(&mut self) -> (u16, TileReference) { if let VRamState::ReferenceCounted(count, tile_ref) = self { *count -= 1; (*count, *tile_ref) @@ -89,7 +91,7 @@ pub struct VRamManager<'a> { generation: u16, free_pointer: Option, - tile_set_to_vram: HashMap<(u16, u16), u16>, + tile_set_to_vram: HashMap, references: Vec, vram_free_pointer: Option, } @@ -159,7 +161,7 @@ impl<'a> VRamManager<'a> { } fn add_tile(&mut self, tile_set_ref: TileSetReference, tile: u16) -> TileIndex { - let tile_ref = (tile_set_ref.id, tile); + let tile_ref = TileReference(tile_set_ref.id, tile); if let Some(&reference) = self.tile_set_to_vram.get(&tile_ref) { self.references[reference as usize].increase_reference(); return TileIndex(reference as u16); @@ -203,8 +205,10 @@ impl<'a> VRamManager<'a> { TILE_BACKGROUND.set(index_to_copy_into * tile_size_in_words + i, word); } - self.tile_set_to_vram - .insert((tile_set_ref.id, tile), index_to_copy_into as u16); + self.tile_set_to_vram.insert( + TileReference(tile_set_ref.id, tile), + index_to_copy_into as u16, + ); TileIndex(index_to_copy_into as u16) } From 2d99d017fc90c03796a899ecb6dafab7bd95fbd2 Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Mon, 31 Jan 2022 20:10:45 +0000 Subject: [PATCH 25/76] Stop printing the cloud positions --- examples/the-purple-night/src/main.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/examples/the-purple-night/src/main.rs b/examples/the-purple-night/src/main.rs index bc684fbf..3db060b3 100644 --- a/examples/the-purple-night/src/main.rs +++ b/examples/the-purple-night/src/main.rs @@ -2178,8 +2178,6 @@ fn game_with_level(gba: &mut agb::Gba) { let clouds = InfiniteScrolledMap::new( background.background(Priority::P3), Box::new(move |pos| { - agb::println!("CLOUDS: {}, {}", pos.x, pos.y); - ( tileset_ref, TileSetting::from_raw( From 2c8fce40d320e5b72a53ded49bcefaadb889ef4d Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Mon, 31 Jan 2022 20:53:33 +0000 Subject: [PATCH 26/76] Store the used backgrounds in a bit array --- agb/src/bitarray.rs | 20 ++++++++++ agb/src/display/background.rs | 20 +++++----- .../the-hat-chooses-the-wizard/Cargo.lock | 39 +++++++++++++++++++ 3 files changed, 70 insertions(+), 9 deletions(-) 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 index da86088e..275f8629 100644 --- a/agb/src/display/background.rs +++ b/agb/src/display/background.rs @@ -1,7 +1,10 @@ +use core::cell::RefCell; + use alloc::vec::Vec; use alloc::{boxed::Box, vec}; use hashbrown::HashMap; +use crate::bitarray::Bitarray; use crate::{ display, fixnum::{Rect, Vector2D}, @@ -611,8 +614,7 @@ fn div_ceil(x: i32, y: i32) -> i32 { } pub struct Tiled0<'a> { - num_regular: u8, - next_screenblock: u8, + regular: RefCell>, pub vram: VRamManager<'a>, } @@ -623,22 +625,22 @@ impl Tiled0<'_> { set_graphics_mode(DisplayMode::Tiled0); Self { - num_regular: 0, - next_screenblock: 16, + regular: Default::default(), vram: VRamManager::new(), } } pub fn background(&mut self, priority: Priority) -> RegularMap { - if self.num_regular == 4 { - panic!("Can only create 4 backgrounds"); + 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(self.num_regular, self.next_screenblock, priority); + let bg = RegularMap::new(new_background as u8, (new_background + 16) as u8, priority); - self.num_regular += 1; - self.next_screenblock += 1; + regular.set(new_background, true); bg } diff --git a/examples/the-hat-chooses-the-wizard/Cargo.lock b/examples/the-hat-chooses-the-wizard/Cargo.lock index 72ded3c8..aed684ac 100644 --- a/examples/the-hat-chooses-the-wizard/Cargo.lock +++ b/examples/the-hat-chooses-the-wizard/Cargo.lock @@ -24,6 +24,7 @@ dependencies = [ "agb_sound_converter", "bare-metal", "bitflags", + "hashbrown", ] [[package]] @@ -65,6 +66,17 @@ dependencies = [ "syn", ] +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -77,6 +89,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8fe8f5a8a398345e52358e18ff07cc17a568fbca5c6f73873d3a62056309603" +[[package]] +name = "bare-metal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fe8f5a8a398345e52358e18ff07cc17a568fbca5c6f73873d3a62056309603" + [[package]] name = "bitflags" version = "1.3.2" @@ -136,6 +154,15 @@ dependencies = [ "wasi", ] +[[package]] +name = "hashbrown" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c21d40587b92fa6a6c6e3c1bdbf87d75511db5672f9c93175574b3a00df1758" +dependencies = [ + "ahash", +] + [[package]] name = "hound" version = "3.4.0" @@ -219,6 +246,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "once_cell" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" + [[package]] name = "png" version = "0.17.4" @@ -357,6 +390,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + [[package]] name = "wasi" version = "0.10.2+wasi-snapshot-preview1" From 10c97f48d890b798dd95731a3da060e642ff9576 Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Mon, 31 Jan 2022 21:23:12 +0000 Subject: [PATCH 27/76] Introduce the MapLoan to infinite scrolled map --- agb/examples/chicken.rs | 10 ++--- agb/examples/test_logo.rs | 3 +- agb/examples/wave.rs | 4 +- agb/src/display/background.rs | 59 +++++++++++++++++++++------ agb/src/display/example_logo.rs | 4 +- agb/src/display/video.rs | 10 +++-- examples/the-purple-night/src/main.rs | 33 ++++++++------- 7 files changed, 80 insertions(+), 43 deletions(-) diff --git a/agb/examples/chicken.rs b/agb/examples/chicken.rs index af2a7294..344952e1 100644 --- a/agb/examples/chicken.rs +++ b/agb/examples/chicken.rs @@ -46,20 +46,20 @@ 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.vram.set_background_palette_raw(&MAP_PALETTE); + vram.set_background_palette_raw(&MAP_PALETTE); let tileset = TileSet::new(&MAP_TILES, TileFormat::FourBpp); - let tileset_ref = gfx.vram.add_tileset(tileset); + let tileset_ref = vram.add_tileset(tileset); - let mut background = gfx.background(); + 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 gfx.vram, + &mut vram, (i % 32, i / 32).into(), tileset_ref, TileSetting::from_raw(tile), diff --git a/agb/examples/test_logo.rs b/agb/examples/test_logo.rs index b7436e08..674867c9 100644 --- a/agb/examples/test_logo.rs +++ b/agb/examples/test_logo.rs @@ -5,10 +5,9 @@ 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(); let mut map = gfx.background(agb::display::Priority::P0); - let mut vram = gfx.vram; example_logo::display_logo(&mut map, &mut vram); diff --git a/agb/examples/wave.rs b/agb/examples/wave.rs index af1d5049..fd513367 100644 --- a/agb/examples/wave.rs +++ b/agb/examples/wave.rs @@ -17,11 +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(); let mut background = gfx.background(agb::display::Priority::P0); - example_logo::display_logo(&mut background, &mut gfx.vram); + example_logo::display_logo(&mut background, &mut vram); let mut time = 0; let cosines = [0_u16; 32]; diff --git a/agb/src/display/background.rs b/agb/src/display/background.rs index 275f8629..2c1b2070 100644 --- a/agb/src/display/background.rs +++ b/agb/src/display/background.rs @@ -1,4 +1,5 @@ use core::cell::RefCell; +use core::ops::{Deref, DerefMut}; use alloc::vec::Vec; use alloc::{boxed::Box, vec}; @@ -424,17 +425,17 @@ impl RegularMap { } } -pub struct InfiniteScrolledMap { - map: RegularMap, +pub struct InfiniteScrolledMap<'a> { + map: MapLoan<'a, RegularMap>, get_tile: Box) -> (TileSetReference, TileSetting)>, current_pos: Vector2D, offset: Vector2D, } -impl InfiniteScrolledMap { +impl<'a> InfiniteScrolledMap<'a> { pub fn new( - map: RegularMap, + map: MapLoan<'a, RegularMap>, get_tile: Box) -> (TileSetReference, TileSetting)>, ) -> Self { Self { @@ -613,25 +614,21 @@ fn div_ceil(x: i32, y: i32) -> i32 { } } -pub struct Tiled0<'a> { +pub struct Tiled0 { regular: RefCell>, - - pub vram: VRamManager<'a>, } -impl Tiled0<'_> { +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(), - - vram: VRamManager::new(), } } - pub fn background(&mut self, priority: Priority) -> RegularMap { + 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 { @@ -642,7 +639,7 @@ impl Tiled0<'_> { regular.set(new_background, true); - bg + MapLoan::new(bg, new_background as u8, &self.regular) } } @@ -651,3 +648,41 @@ impl TileSetReference { Self { id, generation } } } + +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> { + 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/example_logo.rs b/agb/src/display/example_logo.rs index 4d94208e..21ef97d5 100644 --- a/agb/src/display/example_logo.rs +++ b/agb/src/display/example_logo.rs @@ -33,11 +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(); let mut map = gfx.background(crate::display::Priority::P0); - display_logo(&mut map, &mut gfx.vram); + display_logo(&mut map, &mut vram); crate::test_runner::assert_image_output("gfx/test_logo.png"); } diff --git a/agb/src/display/video.rs b/agb/src/display/video.rs index d4f61270..fce2b572 100644 --- a/agb/src/display/video.rs +++ b/agb/src/display/video.rs @@ -1,4 +1,8 @@ -use super::{background::Tiled0, bitmap3::Bitmap3, bitmap4::Bitmap4}; +use super::{ + background::{Tiled0, VRamManager}, + bitmap3::Bitmap3, + bitmap4::Bitmap4, +}; #[non_exhaustive] pub struct Video {} @@ -14,7 +18,7 @@ impl Video { unsafe { Bitmap4::new() } } - pub fn tiled0(&mut self) -> Tiled0 { - unsafe { Tiled0::new() } + pub fn tiled0(&mut self) -> (Tiled0, VRamManager<'_>) { + (unsafe { Tiled0::new() }, VRamManager::new()) } } diff --git a/examples/the-purple-night/src/main.rs b/examples/the-purple-night/src/main.rs index 3db060b3..abca5419 100644 --- a/examples/the-purple-night/src/main.rs +++ b/examples/the-purple-night/src/main.rs @@ -28,21 +28,21 @@ agb::include_gfx!("gfx/background.toml"); type Number = FixedNum<8>; -struct Level { - background: InfiniteScrolledMap, - foreground: InfiniteScrolledMap, - clouds: InfiniteScrolledMap, +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: InfiniteScrolledMap, - mut foreground: InfiniteScrolledMap, - mut clouds: InfiniteScrolledMap, + mut backdrop: InfiniteScrolledMap<'a>, + mut foreground: InfiniteScrolledMap<'a>, + mut clouds: InfiniteScrolledMap<'a>, vram: &mut VRamManager, ) -> Self { backdrop.init(vram, (8, 8).into()); @@ -1772,7 +1772,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, @@ -2086,7 +2086,7 @@ impl<'a> Game<'a> { vram.set_background_palettes(&modified_palettes); } - fn new(object: &'a ObjectControl, level: Level, start_at_boss: bool) -> Self { + fn new(object: &'a ObjectControl, level: Level<'a>, start_at_boss: bool) -> Self { let mut player = Player::new(object); let mut offset = (8, 8).into(); if start_at_boss { @@ -2138,12 +2138,11 @@ fn game_with_level(gba: &mut agb::Gba) { let mut start_at_boss = false; loop { - let mut background = gba.display.video.tiled0(); - background - .vram - .set_background_palettes(background::background.palettes); + let (background, mut vram) = gba.display.video.tiled0(); - let tileset_ref = background.vram.add_tileset(TileSet::new( + vram.set_background_palettes(background::background.palettes); + + let tileset_ref = vram.add_tileset(TileSet::new( background::background.tiles, TileFormat::FourBpp, )); @@ -2189,7 +2188,7 @@ fn game_with_level(gba: &mut agb::Gba) { let mut game = Game::new( &object, - Level::load_level(backdrop, foreground, clouds, &mut background.vram), + Level::load_level(backdrop, foreground, clouds, &mut vram), start_at_boss, ); @@ -2197,7 +2196,7 @@ fn game_with_level(gba: &mut agb::Gba) { sfx.frame(); vblank.wait_for_vblank(); sfx.after_vblank(); - match game.advance_frame(&object, &mut background.vram, &mut sfx) { + match game.advance_frame(&object, &mut vram, &mut sfx) { GameStatus::Continue => {} GameStatus::Lost => { break false; From 5197f5b37d36396880d1ffa3a7477a95c36c7eee Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Mon, 31 Jan 2022 21:25:18 +0000 Subject: [PATCH 28/76] Move uses to the top and allow default features --- .../the-hat-chooses-the-wizard/Cargo.toml | 2 +- .../the-hat-chooses-the-wizard/src/main.rs | 20 +++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) 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/main.rs b/examples/the-hat-chooses-the-wizard/src/main.rs index 589db875..0575c720 100644 --- a/examples/the-hat-chooses-the-wizard/src/main.rs +++ b/examples/the-hat-chooses-the-wizard/src/main.rs @@ -1,6 +1,16 @@ #![no_std] #![no_main] +use agb::{ + display::{ + background::BackgroundRegular, + object::{ObjectControl, ObjectStandard, Size}, + Priority, HEIGHT, WIDTH, + }, + fixnum::{FixedNum, Vector2D}, + input::{self, Button, ButtonController}, +}; + mod enemies; mod level_display; mod sfx; @@ -103,16 +113,6 @@ mod map_tiles { agb::include_gfx!("gfx/tile_sheet.toml"); -use agb::{ - display::{ - background::BackgroundRegular, - object::{ObjectControl, ObjectStandard, Size}, - Priority, HEIGHT, WIDTH, - }, - fixnum::{FixedNum, Vector2D}, - input::{self, Button, ButtonController}, -}; - type FixedNumberType = FixedNum<10>; pub struct Entity<'a> { From f655e08665c059b5c0e708e5c34cb1390d1a94c2 Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Mon, 31 Jan 2022 22:23:53 +0000 Subject: [PATCH 29/76] At least it compiles... --- .../src/level_display.rs | 55 +++++-- .../the-hat-chooses-the-wizard/src/main.rs | 154 +++++++++++------- .../src/splash_screen.rs | 71 +++++--- 3 files changed, 175 insertions(+), 105 deletions(-) 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..599594ca 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::{ + background::{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 0575c720..f511f967 100644 --- a/examples/the-hat-chooses-the-wizard/src/main.rs +++ b/examples/the-hat-chooses-the-wizard/src/main.rs @@ -1,15 +1,18 @@ #![no_std] #![no_main] +extern crate alloc; + use agb::{ display::{ - background::BackgroundRegular, + background::{InfiniteScrolledMap, TileFormat, TileSet, TileSetting, VRamManager}, object::{ObjectControl, ObjectStandard, Size}, Priority, HEIGHT, WIDTH, }, fixnum::{FixedNum, Vector2D}, input::{self, Button, ButtonController}, }; +use alloc::boxed::Box; mod enemies; mod level_display; @@ -267,40 +270,24 @@ 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() - } - - 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(&mut self, vram: &mut VRamManager) { + self.background.init(vram, self.position.floor()); + self.foreground.init(vram, self.position.floor()); } } @@ -618,8 +605,8 @@ impl<'a, 'b, 'c> PlayingLevel<'a, 'b> { fn open_level( level: &'a Level, object_control: &'a ObjectControl, - 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(); @@ -658,14 +645,6 @@ 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(); @@ -691,7 +670,11 @@ impl<'a, 'b, 'c> PlayingLevel<'a, 'b> { self.player.wizard.position.y - self.background.position.y < (HEIGHT + 8).into() } - fn update_frame(&mut self, sfx_player: &mut sfx::SfxPlayer) -> UpdateState { + fn update_frame( + &mut self, + sfx_player: &mut sfx::SfxPlayer, + vram: &mut VRamManager, + ) -> UpdateState { self.timer += 1; self.input.update(); @@ -719,7 +702,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); @@ -774,25 +757,44 @@ 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 world_display = tiled.background(Priority::P0); + + let tile_set_ref = vram.add_tileset(TileSet::new( + tile_sheet::background.tiles, + TileFormat::FourBpp, + )); + + splash_screen::show_splash_screen( + splash_screen::SplashScreen::Start, + None, + None, + &mut world_display, + &mut vram, + ); loop { - let mut tiled = agb.display.video.tiled0(); 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); object.set_sprite_palettes(object_sheet::object_sheet.palettes); object.set_sprite_tilemap(object_sheet::object_sheet.tiles); - let mut world_display = tiled.get_raw_regular().unwrap(); - world_display.clear(level_display::BLANK); + for y in 0..20u16 { + for x in 0..30u16 { + world_display.set_tile( + &mut vram, + (x, y).into(), + tile_set_ref, + TileSetting::from_raw(level_display::BLANK), + ); + } + } + world_display.show(); - let mut background = tiled.get_regular().unwrap(); - let mut foreground = tiled.get_regular().unwrap(); object.enable(); mixer.enable(); @@ -815,6 +817,8 @@ fn main(mut agb: agb::Gba) -> ! { &mut world_display, current_level / 8 + 1, current_level % 8 + 1, + tile_set_ref, + &mut vram, ); world_display.show(); @@ -824,6 +828,39 @@ fn main(mut agb: agb::Gba) -> ! { vblank.wait_for_vblank(); mixer.after_vblank(); + vram.set_background_palettes(tile_sheet::background.palettes); + + let mut background = InfiniteScrolledMap::new( + tiled.background(Priority::P1), + 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::P2), + 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, @@ -831,32 +868,24 @@ 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 { + + level.background.init(&mut vram); + + for _ in 0..60 { 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 { - music_box.before_frame(&mut mixer); - mixer.frame(); - vblank.wait_for_vblank(); - mixer.after_vblank(); - level_load.next(); - } - level_load.count(); level.show_backgrounds(); world_display.hide(); loop { - match level.update_frame(&mut sfx::SfxPlayer::new(&mut mixer, &music_box)) { + match level + .update_frame(&mut sfx::SfxPlayer::new(&mut mixer, &music_box), &mut vram) + { UpdateState::Normal => {} UpdateState::Dead => { level.dead_start(); @@ -882,10 +911,11 @@ fn main(mut agb: agb::Gba) -> ! { } splash_screen::show_splash_screen( - &mut agb, splash_screen::SplashScreen::End, Some(&mut mixer), Some(&mut music_box), + &mut world_display, + &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..3575941e 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::background::{RegularMap, TileFormat, TileSet, TileSetting, VRamManager}, + sound::mixer::Mixer, +}; agb::include_gfx!("gfx/splash_screens.toml"); @@ -9,39 +12,54 @@ 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 = 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, + )); + + vram.set_background_palettes(splash_screens::splash.palettes); + + tile_set_ref } 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, + )); + + vram.set_background_palettes(splash_screens::thanks_for_playing.palettes); + + tile_set_ref + } + }; + + let vblank = agb::interrupt::VBlank::get(); + + let mut input = agb::input::ButtonController::new(); + + 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), + ); } } - 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(); + map.show(); + loop { input.update(); if input.is_just_pressed( @@ -64,5 +82,8 @@ pub fn show_splash_screen( mixer.after_vblank(); } } - splash_screen_display.hide(); + + map.hide(); + + vram.remove_tileset(tile_set_ref); } From 7d870163c316d32a80d9bc0c03a8f7e445e811fa Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Mon, 7 Feb 2022 20:25:30 +0000 Subject: [PATCH 30/76] Hide backgrounds after each level --- examples/the-hat-chooses-the-wizard/src/main.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/examples/the-hat-chooses-the-wizard/src/main.rs b/examples/the-hat-chooses-the-wizard/src/main.rs index f511f967..f1f88326 100644 --- a/examples/the-hat-chooses-the-wizard/src/main.rs +++ b/examples/the-hat-chooses-the-wizard/src/main.rs @@ -650,6 +650,11 @@ impl<'a, 'b, 'c> PlayingLevel<'a, 'b> { self.background.foreground.show(); } + fn hide_backgrounds(&mut self) { + self.background.background.hide(); + self.background.background.hide(); + } + fn dead_start(&mut self) { self.player.wizard.velocity = (0, -1).into(); self.player.wizard.sprite.set_priority(Priority::P0); @@ -908,6 +913,8 @@ fn main(mut agb: agb::Gba) -> ! { vblank.wait_for_vblank(); mixer.after_vblank(); } + + level.hide_backgrounds(); } splash_screen::show_splash_screen( From 666867e933f87fbaeb400a2221479f88f03d3f62 Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Mon, 7 Feb 2022 20:39:05 +0000 Subject: [PATCH 31/76] Track the generation along with the tile data --- agb/src/display/background.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/agb/src/display/background.rs b/agb/src/display/background.rs index 2c1b2070..b9b33e52 100644 --- a/agb/src/display/background.rs +++ b/agb/src/display/background.rs @@ -95,7 +95,7 @@ pub struct VRamManager<'a> { generation: u16, free_pointer: Option, - tile_set_to_vram: HashMap, + tile_set_to_vram: HashMap, references: Vec, vram_free_pointer: Option, } @@ -167,8 +167,10 @@ impl<'a> VRamManager<'a> { fn add_tile(&mut self, tile_set_ref: TileSetReference, tile: u16) -> TileIndex { let tile_ref = TileReference(tile_set_ref.id, tile); if let Some(&reference) = self.tile_set_to_vram.get(&tile_ref) { - self.references[reference as usize].increase_reference(); - return TileIndex(reference as u16); + if reference.1 == tile_set_ref.generation { + self.references[reference.0 as usize].increase_reference(); + return TileIndex(reference.0 as u16); + } } let index_to_copy_into = if let Some(ptr) = self.vram_free_pointer.take() { @@ -211,7 +213,7 @@ impl<'a> VRamManager<'a> { self.tile_set_to_vram.insert( TileReference(tile_set_ref.id, tile), - index_to_copy_into as u16, + (index_to_copy_into as u16, tile_set_ref.generation), ); TileIndex(index_to_copy_into as u16) From 66a61e33e610f38b8fb49b91a717b388a2ebc02c Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Mon, 7 Feb 2022 20:44:43 +0000 Subject: [PATCH 32/76] More correctly special case tile 0 --- agb/src/display/background.rs | 9 +++++++-- examples/the-hat-chooses-the-wizard/src/main.rs | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/agb/src/display/background.rs b/agb/src/display/background.rs index b9b33e52..7e43ac04 100644 --- a/agb/src/display/background.rs +++ b/agb/src/display/background.rs @@ -342,8 +342,13 @@ impl RegularMap { } let tile_index = tile_setting.index(); - let new_tile_idx = vram.add_tile(tileset_ref, tile_index); - let new_tile = Tile::new(new_tile_idx, tile_setting); + + let new_tile = if tile_index != 0 { + let new_tile_idx = vram.add_tile(tileset_ref, tile_index); + Tile::new(new_tile_idx, tile_setting) + } else { + Tile::default() + }; self.tiles[pos] = new_tile; self.tiles_dirty = true; diff --git a/examples/the-hat-chooses-the-wizard/src/main.rs b/examples/the-hat-chooses-the-wizard/src/main.rs index f1f88326..063f6de0 100644 --- a/examples/the-hat-chooses-the-wizard/src/main.rs +++ b/examples/the-hat-chooses-the-wizard/src/main.rs @@ -806,7 +806,7 @@ fn main(mut agb: agb::Gba) -> ! { let mut music_box = sfx::MusicBox::new(); let vblank = agb::interrupt::VBlank::get(); - let mut current_level = 0; + let mut current_level = 8; loop { if current_level == map_tiles::LEVELS.len() as u32 { From aad539924058721a22e69a3310513d575a21a939 Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Mon, 7 Feb 2022 20:47:55 +0000 Subject: [PATCH 33/76] Get the priorities correct (and start at level 8) --- examples/the-hat-chooses-the-wizard/src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/the-hat-chooses-the-wizard/src/main.rs b/examples/the-hat-chooses-the-wizard/src/main.rs index 063f6de0..b7ffd747 100644 --- a/examples/the-hat-chooses-the-wizard/src/main.rs +++ b/examples/the-hat-chooses-the-wizard/src/main.rs @@ -836,7 +836,7 @@ fn main(mut agb: agb::Gba) -> ! { vram.set_background_palettes(tile_sheet::background.palettes); let mut background = InfiniteScrolledMap::new( - tiled.background(Priority::P1), + tiled.background(Priority::P2), Box::new(move |pos: Vector2D| { let level = &map_tiles::LEVELS[current_level as usize]; ( @@ -851,7 +851,7 @@ fn main(mut agb: agb::Gba) -> ! { }), ); let mut foreground = InfiniteScrolledMap::new( - tiled.background(Priority::P2), + tiled.background(Priority::P0), Box::new(move |pos: Vector2D| { let level = &map_tiles::LEVELS[current_level as usize]; ( From ebf21f4add095e1d9761a7a5f969b5b012247cf5 Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Mon, 7 Feb 2022 21:16:03 +0000 Subject: [PATCH 34/76] Avoid pointless copies --- agb/src/display/background.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/agb/src/display/background.rs b/agb/src/display/background.rs index 7e43ac04..b87176a2 100644 --- a/agb/src/display/background.rs +++ b/agb/src/display/background.rs @@ -350,6 +350,11 @@ impl RegularMap { 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; } From a998ae31a44f532f0017475e4e2c9350ee7d865b Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Mon, 7 Feb 2022 21:16:11 +0000 Subject: [PATCH 35/76] Correctly use the map width and not the display width --- agb/src/display/background.rs | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/agb/src/display/background.rs b/agb/src/display/background.rs index b87176a2..87be8a0f 100644 --- a/agb/src/display/background.rs +++ b/agb/src/display/background.rs @@ -393,8 +393,8 @@ impl RegularMap { let screenblock_memory = self.screenblock_memory(); let scroll_pos = self.get_scroll_pos(); - let x_scroll = scroll_pos.x % display::WIDTH as u16; - let y_scroll = scroll_pos.y % display::HEIGHT as u16; + let x_scroll = scroll_pos.x % (32 * 8) as u16; + let y_scroll = scroll_pos.y % (32 * 8) as u16; let start_x = x_scroll / 8; let end_x = (x_scroll + display::WIDTH as u16 + 8 - 1) / 8 + 1; // divide by 8 rounding up @@ -483,16 +483,8 @@ impl<'a> InfiniteScrolledMap<'a> { let offset = self.current_pos - (x_start * 8, y_start * 8).into(); let offset_scroll = ( - if offset.x < 0 { - (offset.x + 32 * 8) as u16 - } else { - offset.x as u16 - }, - if offset.y < 0 { - (offset.y + 32 * 8) as u16 - } else { - offset.y as u16 - }, + offset.x.rem_euclid(32 * 8) as u16, + offset.y.rem_euclid(32 * 8) as u16, ) .into(); From 610be70b922657fdc9799a37fb0d74bb09048420 Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Mon, 7 Feb 2022 21:30:52 +0000 Subject: [PATCH 36/76] Correctly set the initial offset value in init() --- agb/src/display/background.rs | 12 +++++------- examples/the-hat-chooses-the-wizard/src/main.rs | 2 +- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/agb/src/display/background.rs b/agb/src/display/background.rs index 87be8a0f..530d628a 100644 --- a/agb/src/display/background.rs +++ b/agb/src/display/background.rs @@ -396,10 +396,10 @@ impl RegularMap { let x_scroll = scroll_pos.x % (32 * 8) as u16; let y_scroll = scroll_pos.y % (32 * 8) as u16; let start_x = x_scroll / 8; - let end_x = (x_scroll + display::WIDTH as u16 + 8 - 1) / 8 + 1; // divide by 8 rounding up + let end_x = div_ceil(x_scroll as i32 + display::WIDTH, 8) as u16 + 1; // divide by 8 rounding up let start_y = y_scroll / 8; - let end_y = (y_scroll + display::HEIGHT as u16 + 8 - 1) / 8 + 1; + let end_y = div_ceil(y_scroll as i32 + display::HEIGHT, 8) as u16 + 1; for y in start_y..end_y { for x in start_x..end_x { @@ -489,7 +489,7 @@ impl<'a> InfiniteScrolledMap<'a> { .into(); self.map.set_scroll_pos(offset_scroll); - self.offset = pos * -1; + self.offset = (x_start, y_start).into(); } pub fn set_pos(&mut self, vram: &mut VRamManager, new_pos: Vector2D) { @@ -555,8 +555,6 @@ impl<'a> InfiniteScrolledMap<'a> { Rect::new((0i32, 0).into(), (0i32, 0).into()) }; - let tile_offset = Vector2D::new(div_floor(self.offset.x, 8), div_floor(self.offset.y, 8)); - for (tile_x, tile_y) in vertical_rect_to_update .iter() .chain(horizontal_rect_to_update.iter()) @@ -566,8 +564,8 @@ impl<'a> InfiniteScrolledMap<'a> { self.map.set_tile( vram, ( - (tile_x + tile_offset.x).rem_euclid(32) as u16, - (tile_y + tile_offset.y).rem_euclid(32) as u16, + (tile_x - self.offset.x).rem_euclid(32) as u16, + (tile_y - self.offset.y).rem_euclid(32) as u16, ) .into(), tile_set_ref, diff --git a/examples/the-hat-chooses-the-wizard/src/main.rs b/examples/the-hat-chooses-the-wizard/src/main.rs index b7ffd747..fc475f8c 100644 --- a/examples/the-hat-chooses-the-wizard/src/main.rs +++ b/examples/the-hat-chooses-the-wizard/src/main.rs @@ -806,7 +806,7 @@ fn main(mut agb: agb::Gba) -> ! { let mut music_box = sfx::MusicBox::new(); let vblank = agb::interrupt::VBlank::get(); - let mut current_level = 8; + let mut current_level = 10; loop { if current_level == map_tiles::LEVELS.len() as u32 { From 3a795fea8f59f9a66e16bdd30d40860c02955965 Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Mon, 7 Feb 2022 21:40:56 +0000 Subject: [PATCH 37/76] Handle moving up to 10 tiles at once --- agb/src/display/background.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/agb/src/display/background.rs b/agb/src/display/background.rs index 530d628a..1be22ba2 100644 --- a/agb/src/display/background.rs +++ b/agb/src/display/background.rs @@ -497,7 +497,7 @@ impl<'a> InfiniteScrolledMap<'a> { let difference = new_pos - old_pos; - if difference.x.abs() > 8 || difference.y.abs() > 8 { + if difference.x.abs() > 10 * 8 || difference.y.abs() > 10 * 8 { self.init(vram, new_pos); return; } @@ -507,6 +507,9 @@ impl<'a> InfiniteScrolledMap<'a> { 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 @@ -525,7 +528,7 @@ impl<'a> InfiniteScrolledMap<'a> { Rect::new( (line_to_update, new_tile_y).into(), - (1, y_tiles_to_update).into(), + (difference_tile_x, y_tiles_to_update).into(), ) } else { Rect::new((0i32, 0).into(), (0i32, 0).into()) @@ -549,7 +552,7 @@ impl<'a> InfiniteScrolledMap<'a> { Rect::new( (new_tile_x, line_to_update).into(), - (x_tiles_to_update, 1).into(), + (x_tiles_to_update, difference_tile_y).into(), ) } else { Rect::new((0i32, 0).into(), (0i32, 0).into()) From a365c149177dcfdfc27d04aef3ae6d82522354c3 Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Mon, 7 Feb 2022 21:58:19 +0000 Subject: [PATCH 38/76] Small fixes --- agb-fixnum/src/lib.rs | 8 +++++--- agb/src/display/background.rs | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/agb-fixnum/src/lib.rs b/agb-fixnum/src/lib.rs index 6128be88..a7a84f68 100644 --- a/agb-fixnum/src/lib.rs +++ b/agb-fixnum/src/lib.rs @@ -653,10 +653,9 @@ 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 { x = self.position.x; y = y + T::one(); @@ -665,7 +664,10 @@ impl Rect { } } - Some((x, y)) + let ret_x = x; + x = x + T::one(); + + Some((ret_x, y)) }) } } diff --git a/agb/src/display/background.rs b/agb/src/display/background.rs index 1be22ba2..8f1b957d 100644 --- a/agb/src/display/background.rs +++ b/agb/src/display/background.rs @@ -516,7 +516,7 @@ impl<'a> InfiniteScrolledMap<'a> { 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: i32 = if new_pos.y % 8 == 0 { 20 } else { 21 }; + let y_tiles_to_update = 21; let line_to_update = if direction < 0 { // moving to the left, so need to update the left most position @@ -540,7 +540,7 @@ impl<'a> InfiniteScrolledMap<'a> { 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 = if new_pos.x % 8 == 0 { 30 } else { 31 }; + let x_tiles_to_update: i32 = 31; let line_to_update = if direction < 0 { // moving up so need to update the top From 530b71e0df1091e1b21f9e0cc8f53397c1e9c383 Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Mon, 7 Feb 2022 22:17:24 +0000 Subject: [PATCH 39/76] Copy slightly too much --- agb/src/display/background.rs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/agb/src/display/background.rs b/agb/src/display/background.rs index 8f1b957d..bc675092 100644 --- a/agb/src/display/background.rs +++ b/agb/src/display/background.rs @@ -393,13 +393,11 @@ impl RegularMap { let screenblock_memory = self.screenblock_memory(); let scroll_pos = self.get_scroll_pos(); - let x_scroll = scroll_pos.x % (32 * 8) as u16; - let y_scroll = scroll_pos.y % (32 * 8) as u16; - let start_x = x_scroll / 8; - let end_x = div_ceil(x_scroll as i32 + display::WIDTH, 8) as u16 + 1; // divide by 8 rounding up + let start_x = scroll_pos.x / 8; + let end_x = div_ceil(scroll_pos.x as i32 + display::WIDTH, 8) as u16 + 1; - let start_y = y_scroll / 8; - let end_y = div_ceil(y_scroll as i32 + display::HEIGHT, 8) as u16 + 1; + let start_y = scroll_pos.y / 8; + let end_y = div_ceil(scroll_pos.y as i32 + display::HEIGHT, 8) as u16 + 1; for y in start_y..end_y { for x in start_x..end_x { @@ -516,7 +514,7 @@ impl<'a> InfiniteScrolledMap<'a> { 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 = 21; + 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 @@ -527,7 +525,7 @@ impl<'a> InfiniteScrolledMap<'a> { }; Rect::new( - (line_to_update, new_tile_y).into(), + (line_to_update, new_tile_y - 1).into(), (difference_tile_x, y_tiles_to_update).into(), ) } else { @@ -540,7 +538,7 @@ impl<'a> InfiniteScrolledMap<'a> { 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 = 31; + let x_tiles_to_update: i32 = 32; let line_to_update = if direction < 0 { // moving up so need to update the top @@ -551,7 +549,7 @@ impl<'a> InfiniteScrolledMap<'a> { }; Rect::new( - (new_tile_x, line_to_update).into(), + (new_tile_x - 1, line_to_update).into(), (x_tiles_to_update, difference_tile_y).into(), ) } else { From 06901b3543c5a7ff46771cdab1b3a7bf63423953 Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Mon, 7 Feb 2022 22:17:57 +0000 Subject: [PATCH 40/76] Move post-work to pre-work --- agb/src/display/background.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/agb/src/display/background.rs b/agb/src/display/background.rs index bc675092..b94aa143 100644 --- a/agb/src/display/background.rs +++ b/agb/src/display/background.rs @@ -465,6 +465,16 @@ impl<'a> InfiniteScrolledMap<'a> { 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(); + for (y_idx, y) in (y_start..y_end).enumerate() { for (x_idx, x) in (x_start..x_end).enumerate() { let pos = (x, y).into(); @@ -478,16 +488,6 @@ 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, - ) - .into(); - - self.map.set_scroll_pos(offset_scroll); - self.offset = (x_start, y_start).into(); } pub fn set_pos(&mut self, vram: &mut VRamManager, new_pos: Vector2D) { From 3941117aa79cf68186a5bf25fe36e21c04ef2ab3 Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Mon, 7 Feb 2022 23:22:19 +0000 Subject: [PATCH 41/76] Switch to rustc-hash --- agb/Cargo.lock | 19 +++++++++++++------ agb/Cargo.toml | 1 + agb/src/display/background.rs | 6 ++++-- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/agb/Cargo.lock b/agb/Cargo.lock index ff15a265..3ea5f3c7 100644 --- a/agb/Cargo.lock +++ b/agb/Cargo.lock @@ -25,6 +25,7 @@ dependencies = [ "bare-metal", "bitflags", "hashbrown", + "rustc-hash", ] [[package]] @@ -97,9 +98,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bytemuck" -version = "1.7.3" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439989e6b8c38d1b6570a384ef1e49c8848128f5a97f3914baef02920842712f" +checksum = "0e851ca7c24871e7336801608a4797d7376545b6928a10d32d75685687141ead" [[package]] name = "byteorder" @@ -236,15 +237,15 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" +checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" [[package]] name = "png" -version = "0.17.4" +version = "0.17.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02cd7d51cea7e2fa6bbcb8af5fbcad15b871451bfc2d20ed72dff2f4ae072a84" +checksum = "dc38c0ad57efb786dd57b9864e5b18bae478c00c824dc55a38bbc9da95dde3ba" dependencies = [ "bitflags", "crc32fast", @@ -306,6 +307,12 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "serde" version = "1.0.136" diff --git a/agb/Cargo.toml b/agb/Cargo.toml index 781aea3c..55af71f6 100644 --- a/agb/Cargo.toml +++ b/agb/Cargo.toml @@ -27,6 +27,7 @@ agb_sound_converter = { version = "0.1.0", path = "../agb-sound-converter" } agb_macros = { version = "0.1.0", path = "../agb-macros" } agb_fixnum = { version = "0.1.0", path = "../agb-fixnum" } bare-metal = "1.0" +rustc-hash = { version = "1.0", default-features = false } [package.metadata.docs.rs] default-target = "thumbv6m-none-eabi" diff --git a/agb/src/display/background.rs b/agb/src/display/background.rs index b94aa143..78f55751 100644 --- a/agb/src/display/background.rs +++ b/agb/src/display/background.rs @@ -1,9 +1,11 @@ use core::cell::RefCell; +use core::hash::BuildHasherDefault; use core::ops::{Deref, DerefMut}; use alloc::vec::Vec; use alloc::{boxed::Box, vec}; use hashbrown::HashMap; +use rustc_hash::FxHasher; use crate::bitarray::Bitarray; use crate::{ @@ -95,7 +97,7 @@ pub struct VRamManager<'a> { generation: u16, free_pointer: Option, - tile_set_to_vram: HashMap, + tile_set_to_vram: HashMap>, references: Vec, vram_free_pointer: Option, } @@ -109,7 +111,7 @@ impl<'a> VRamManager<'a> { generation: 0, free_pointer: None, - tile_set_to_vram: HashMap::new(), + tile_set_to_vram: HashMap::default(), references: vec![VRamState::Free(0)], vram_free_pointer: None, } From 08aeb56f1e5950782db8ad89887b6dee93d7618b Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Mon, 7 Feb 2022 23:24:59 +0000 Subject: [PATCH 42/76] Update background and foreground independentently --- agb/src/display/background.rs | 28 +++++++++++++------ .../the-hat-chooses-the-wizard/Cargo.lock | 25 +++++++++-------- .../the-hat-chooses-the-wizard/src/main.rs | 13 +++++++-- 3 files changed, 44 insertions(+), 22 deletions(-) diff --git a/agb/src/display/background.rs b/agb/src/display/background.rs index 78f55751..e64b5323 100644 --- a/agb/src/display/background.rs +++ b/agb/src/display/background.rs @@ -25,6 +25,18 @@ const TILE_BACKGROUND: MemoryMapped1DArray = const PALETTE_BACKGROUND: MemoryMapped1DArray = unsafe { MemoryMapped1DArray::new(0x0500_0000) }; +#[cfg(debug_assertions)] +const unsafe fn debug_unreachable_unchecked() -> ! { + unreachable!(); +} + +#[cfg(not(debug_assertions))] +const unsafe fn debug_unreachable_unchecked() -> ! { + use core::hint::unreachable_unchecked; + + unreachable_unchecked(); +} + #[derive(Clone, Copy, Debug)] pub enum TileFormat { FourBpp, @@ -72,7 +84,7 @@ impl VRamState { if let VRamState::ReferenceCounted(count, _) = self { *count += 1; } else { - panic!("Corrupted vram state"); + unsafe { debug_unreachable_unchecked() }; } } @@ -81,7 +93,7 @@ impl VRamState { *count -= 1; (*count, *tile_ref) } else { - panic!("Corrupted vram state"); + unsafe { debug_unreachable_unchecked() }; } } } @@ -134,7 +146,7 @@ impl<'a> VRamManager<'a> { self.tilesets[ptr] = tileset; ptr } - _ => panic!("Free pointer shouldn't point to valid data"), + _ => unsafe { debug_unreachable_unchecked() }, } } else { self.tilesets.push(tileset); @@ -149,7 +161,7 @@ impl<'a> VRamManager<'a> { match tileset { ArenaStorageItem::Data(_, generation) => { - assert_eq!( + debug_assert_eq!( *generation, tile_set_ref.generation, "Tileset generation must be the same when removing" ); @@ -162,7 +174,7 @@ impl<'a> VRamManager<'a> { self.free_pointer = Some(tile_set_ref.id as usize); } - _ => panic!("Already freed, probably a double free?"), + _ => unsafe { debug_unreachable_unchecked() }, } } @@ -182,7 +194,7 @@ impl<'a> VRamManager<'a> { self.vram_free_pointer = Some(next_free as usize); } } - VRamState::ReferenceCounted(_, _) => panic!("Corrupted tile reference state"), + VRamState::ReferenceCounted(_, _) => unsafe { debug_unreachable_unchecked() }, } self.references[ptr] = VRamState::ReferenceCounted(1, tile_ref); @@ -196,7 +208,7 @@ impl<'a> VRamManager<'a> { let tile_slice = if let ArenaStorageItem::Data(data, generation) = &self.tilesets[tile_set_ref.id as usize] { - assert_eq!( + debug_assert_eq!( *generation, tile_set_ref.generation, "Stale tile data requested" ); @@ -204,7 +216,7 @@ impl<'a> VRamManager<'a> { let tile_offset = (tile as usize) * data.format.tile_size() / 4; &data.tiles[tile_offset..(tile_offset + data.format.tile_size() / 4)] } else { - panic!("Cannot find tile data at given reference"); + unsafe { debug_unreachable_unchecked() }; }; let tile_size_in_words = TileFormat::FourBpp.tile_size() / 4; diff --git a/examples/the-hat-chooses-the-wizard/Cargo.lock b/examples/the-hat-chooses-the-wizard/Cargo.lock index aed684ac..c6a6fad7 100644 --- a/examples/the-hat-chooses-the-wizard/Cargo.lock +++ b/examples/the-hat-chooses-the-wizard/Cargo.lock @@ -25,6 +25,7 @@ dependencies = [ "bare-metal", "bitflags", "hashbrown", + "rustc-hash", ] [[package]] @@ -89,12 +90,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8fe8f5a8a398345e52358e18ff07cc17a568fbca5c6f73873d3a62056309603" -[[package]] -name = "bare-metal" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8fe8f5a8a398345e52358e18ff07cc17a568fbca5c6f73873d3a62056309603" - [[package]] name = "bitflags" version = "1.3.2" @@ -103,9 +98,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bytemuck" -version = "1.7.3" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439989e6b8c38d1b6570a384ef1e49c8848128f5a97f3914baef02920842712f" +checksum = "0e851ca7c24871e7336801608a4797d7376545b6928a10d32d75685687141ead" [[package]] name = "byteorder" @@ -248,15 +243,15 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" +checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" [[package]] name = "png" -version = "0.17.4" +version = "0.17.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02cd7d51cea7e2fa6bbcb8af5fbcad15b871451bfc2d20ed72dff2f4ae072a84" +checksum = "dc38c0ad57efb786dd57b9864e5b18bae478c00c824dc55a38bbc9da95dde3ba" dependencies = [ "bitflags", "crc32fast", @@ -318,6 +313,12 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "ryu" version = "1.0.9" diff --git a/examples/the-hat-chooses-the-wizard/src/main.rs b/examples/the-hat-chooses-the-wizard/src/main.rs index fc475f8c..3a44e8ba 100644 --- a/examples/the-hat-chooses-the-wizard/src/main.rs +++ b/examples/the-hat-chooses-the-wizard/src/main.rs @@ -285,8 +285,11 @@ impl<'a, 'b> Map<'a, 'b> { self.foreground.commit(); } - pub fn init(&mut self, vram: &mut VRamManager) { + pub fn init_background(&mut self, vram: &mut VRamManager) { self.background.init(vram, self.position.floor()); + } + + pub fn init_foreground(&mut self, vram: &mut VRamManager) { self.foreground.init(vram, self.position.floor()); } } @@ -874,7 +877,13 @@ fn main(mut agb: agb::Gba) -> ! { agb::input::ButtonController::new(), ); - level.background.init(&mut vram); + level.background.init_background(&mut vram); + music_box.before_frame(&mut mixer); + mixer.frame(); + vblank.wait_for_vblank(); + mixer.after_vblank(); + + level.background.init_foreground(&mut vram); for _ in 0..60 { music_box.before_frame(&mut mixer); From d94d9dbcf327bf2f919d637d637d0acafcb79b64 Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Mon, 7 Feb 2022 23:36:46 +0000 Subject: [PATCH 43/76] Print number of cycles for background initialisation --- examples/the-hat-chooses-the-wizard/src/main.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/examples/the-hat-chooses-the-wizard/src/main.rs b/examples/the-hat-chooses-the-wizard/src/main.rs index 3a44e8ba..f2b8fbc2 100644 --- a/examples/the-hat-chooses-the-wizard/src/main.rs +++ b/examples/the-hat-chooses-the-wizard/src/main.rs @@ -787,6 +787,9 @@ fn main(mut agb: agb::Gba) -> ! { let mut timer_controller = agb.timers.timers(); let mut mixer = agb.mixer.mixer(&mut timer_controller.timer0); + let mut timer = timer_controller.timer1; + timer.set_enabled(true); + object.set_sprite_palettes(object_sheet::object_sheet.palettes); object.set_sprite_tilemap(object_sheet::object_sheet.tiles); @@ -877,12 +880,17 @@ fn main(mut agb: agb::Gba) -> ! { agb::input::ButtonController::new(), ); + let before_init_cycles = timer.get_value(); level.background.init_background(&mut vram); + let after_init_cycles = timer.get_value(); + music_box.before_frame(&mut mixer); mixer.frame(); vblank.wait_for_vblank(); mixer.after_vblank(); + agb::println!("cycles for init {}", after_init_cycles - before_init_cycles); + level.background.init_foreground(&mut vram); for _ in 0..60 { From 06b6075a6a2988bd09894b082db7f7c0cd182741 Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Mon, 14 Feb 2022 20:17:28 +0000 Subject: [PATCH 44/76] Display the number of cycles needed to load the frame --- .../the-hat-chooses-the-wizard/src/main.rs | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/examples/the-hat-chooses-the-wizard/src/main.rs b/examples/the-hat-chooses-the-wizard/src/main.rs index f2b8fbc2..e081e6e3 100644 --- a/examples/the-hat-chooses-the-wizard/src/main.rs +++ b/examples/the-hat-chooses-the-wizard/src/main.rs @@ -788,6 +788,9 @@ fn main(mut agb: agb::Gba) -> ! { let mut mixer = agb.mixer.mixer(&mut timer_controller.timer0); let mut timer = timer_controller.timer1; + let mut timer_cascade = timer_controller.timer2; + timer_cascade.set_cascade(true); + timer_cascade.set_enabled(true); timer.set_enabled(true); object.set_sprite_palettes(object_sheet::object_sheet.palettes); @@ -880,16 +883,28 @@ fn main(mut agb: agb::Gba) -> ! { agb::input::ButtonController::new(), ); - let before_init_cycles = timer.get_value(); - level.background.init_background(&mut vram); - let after_init_cycles = timer.get_value(); - music_box.before_frame(&mut mixer); mixer.frame(); vblank.wait_for_vblank(); mixer.after_vblank(); + let (before_init_cycles, before_init_cycles_2) = + (timer.get_value(), timer_cascade.get_value()); + level.background.init_background(&mut vram); + + let (after_init_cycles, after_init_cycles_2) = + (timer.get_value(), timer_cascade.get_value()); + let after_init_cycles = + (after_init_cycles as u32) | ((after_init_cycles_2 as u32) << 16); + let before_init_cycles = + (before_init_cycles as u32) | ((before_init_cycles_2 as u32) << 16); + + music_box.before_frame(&mut mixer); + mixer.frame(); agb::println!("cycles for init {}", after_init_cycles - before_init_cycles); + vblank.wait_for_vblank(); + + mixer.after_vblank(); level.background.init_foreground(&mut vram); From 898b06110f36cb6fb1948bd8779d010221ca0179 Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Mon, 14 Feb 2022 20:20:25 +0000 Subject: [PATCH 45/76] Correctly commit --- examples/the-hat-chooses-the-wizard/src/main.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/the-hat-chooses-the-wizard/src/main.rs b/examples/the-hat-chooses-the-wizard/src/main.rs index e081e6e3..73f3aa2b 100644 --- a/examples/the-hat-chooses-the-wizard/src/main.rs +++ b/examples/the-hat-chooses-the-wizard/src/main.rs @@ -807,6 +807,7 @@ fn main(mut agb: agb::Gba) -> ! { } } + world_display.commit(); world_display.show(); object.enable(); @@ -835,6 +836,7 @@ fn main(mut agb: agb::Gba) -> ! { &mut vram, ); + world_display.commit(); world_display.show(); music_box.before_frame(&mut mixer); From 63dd8375189dc92a081af58ed6716297410b909a Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Mon, 14 Feb 2022 20:57:12 +0000 Subject: [PATCH 46/76] Tiny performance improvement with dma to copy the tile data --- agb/src/display/background.rs | 39 +++++++++++++++++-- .../the-hat-chooses-the-wizard/src/main.rs | 4 +- 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/agb/src/display/background.rs b/agb/src/display/background.rs index e64b5323..712450e8 100644 --- a/agb/src/display/background.rs +++ b/agb/src/display/background.rs @@ -20,7 +20,7 @@ use super::{ }; const TILE_BACKGROUND: MemoryMapped1DArray = - unsafe { MemoryMapped1DArray::new(0x06000000) }; + unsafe { MemoryMapped1DArray::new(0x0600_0000) }; const PALETTE_BACKGROUND: MemoryMapped1DArray = unsafe { MemoryMapped1DArray::new(0x0500_0000) }; @@ -219,10 +219,16 @@ impl<'a> VRamManager<'a> { unsafe { debug_unreachable_unchecked() }; }; - let tile_size_in_words = TileFormat::FourBpp.tile_size() / 4; + let tile_size_in_half_words = TileFormat::FourBpp.tile_size() / 2; - for (i, &word) in tile_slice.iter().enumerate() { - TILE_BACKGROUND.set(index_to_copy_into * tile_size_in_words + i, word); + const TILE_BACKGROUND_ADDRESS: usize = 0x0600_0000; + unsafe { + dma_copy( + tile_slice.as_ptr() as *const u16, + (TILE_BACKGROUND_ADDRESS as *mut u16) + .add(index_to_copy_into * tile_size_in_half_words), + tile_size_in_half_words, + ); } self.tile_set_to_vram.insert( @@ -703,3 +709,28 @@ impl<'a, T> Drop for MapLoan<'a, T> { .set(self.background_id as usize, false); } } + +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)) }; + +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/examples/the-hat-chooses-the-wizard/src/main.rs b/examples/the-hat-chooses-the-wizard/src/main.rs index 73f3aa2b..5904bbc2 100644 --- a/examples/the-hat-chooses-the-wizard/src/main.rs +++ b/examples/the-hat-chooses-the-wizard/src/main.rs @@ -796,8 +796,8 @@ fn main(mut agb: agb::Gba) -> ! { object.set_sprite_palettes(object_sheet::object_sheet.palettes); object.set_sprite_tilemap(object_sheet::object_sheet.tiles); - for y in 0..20u16 { - for x in 0..30u16 { + for y in 0..32u16 { + for x in 0..32u16 { world_display.set_tile( &mut vram, (x, y).into(), From b0b8ec55c472cc31329d746d69bbc38eba4d75d1 Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Mon, 14 Feb 2022 20:59:45 +0000 Subject: [PATCH 47/76] Remove unused TILE_BACKGROUND --- agb/src/display/background.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/agb/src/display/background.rs b/agb/src/display/background.rs index 712450e8..c017dde7 100644 --- a/agb/src/display/background.rs +++ b/agb/src/display/background.rs @@ -19,9 +19,6 @@ use super::{ DISPLAY_CONTROL, }; -const TILE_BACKGROUND: MemoryMapped1DArray = - unsafe { MemoryMapped1DArray::new(0x0600_0000) }; - const PALETTE_BACKGROUND: MemoryMapped1DArray = unsafe { MemoryMapped1DArray::new(0x0500_0000) }; From 3a913f142ad2c2379519d1b360fc3fa0bd54f355 Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Mon, 14 Feb 2022 21:53:09 +0000 Subject: [PATCH 48/76] Remove pop while loading a level --- agb/src/display/background.rs | 54 ++++++++++++++++--- .../the-hat-chooses-the-wizard/src/main.rs | 49 +++++++---------- 2 files changed, 68 insertions(+), 35 deletions(-) diff --git a/agb/src/display/background.rs b/agb/src/display/background.rs index c017dde7..ecc5309f 100644 --- a/agb/src/display/background.rs +++ b/agb/src/display/background.rs @@ -458,6 +458,14 @@ pub struct InfiniteScrolledMap<'a> { current_pos: Vector2D, offset: Vector2D, + + copied_up_to: i32, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum PartialUpdateStatus { + Done, + Continue, } impl<'a> InfiniteScrolledMap<'a> { @@ -470,10 +478,26 @@ impl<'a> InfiniteScrolledMap<'a> { 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) { + pub fn init( + &mut self, + vram: &mut VRamManager, + pos: Vector2D, + between_updates: Box, + ) { + 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); @@ -492,29 +516,45 @@ impl<'a> InfiniteScrolledMap<'a> { self.map.set_scroll_pos(offset_scroll); self.offset = (x_start, y_start).into(); - for (y_idx, y) in (y_start..y_end).enumerate() { + 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 as u16).into(), + (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) { + 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 { - self.init(vram, new_pos); - return; + return self.init_partial(vram, new_pos); } self.current_pos = new_pos; @@ -599,6 +639,8 @@ impl<'a> InfiniteScrolledMap<'a> { .into(); self.map.set_scroll_pos(new_scroll); + + PartialUpdateStatus::Done } pub fn show(&mut self) { diff --git a/examples/the-hat-chooses-the-wizard/src/main.rs b/examples/the-hat-chooses-the-wizard/src/main.rs index 5904bbc2..c7f08fcd 100644 --- a/examples/the-hat-chooses-the-wizard/src/main.rs +++ b/examples/the-hat-chooses-the-wizard/src/main.rs @@ -5,7 +5,9 @@ extern crate alloc; use agb::{ display::{ - background::{InfiniteScrolledMap, TileFormat, TileSet, TileSetting, VRamManager}, + background::{ + InfiniteScrolledMap, PartialUpdateStatus, TileFormat, TileSet, TileSetting, VRamManager, + }, object::{ObjectControl, ObjectStandard, Size}, Priority, HEIGHT, WIDTH, }, @@ -285,12 +287,12 @@ impl<'a, 'b> Map<'a, 'b> { self.foreground.commit(); } - pub fn init_background(&mut self, vram: &mut VRamManager) { - self.background.init(vram, self.position.floor()); + pub fn init_background(&mut self, vram: &mut VRamManager) -> PartialUpdateStatus { + self.background.init_partial(vram, self.position.floor()) } - pub fn init_foreground(&mut self, vram: &mut VRamManager) { - self.foreground.init(vram, self.position.floor()); + pub fn init_foreground(&mut self, vram: &mut VRamManager) -> PartialUpdateStatus { + self.foreground.init_partial(vram, self.position.floor()) } } @@ -885,32 +887,21 @@ fn main(mut agb: agb::Gba) -> ! { agb::input::ButtonController::new(), ); - music_box.before_frame(&mut mixer); - mixer.frame(); - vblank.wait_for_vblank(); - mixer.after_vblank(); - let (before_init_cycles, before_init_cycles_2) = - (timer.get_value(), timer_cascade.get_value()); + 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.background.init_background(&mut vram); + while level.background.init_foreground(&mut vram) != PartialUpdateStatus::Done { + music_box.before_frame(&mut mixer); + mixer.frame(); + vblank.wait_for_vblank(); + mixer.after_vblank(); + } - let (after_init_cycles, after_init_cycles_2) = - (timer.get_value(), timer_cascade.get_value()); - let after_init_cycles = - (after_init_cycles as u32) | ((after_init_cycles_2 as u32) << 16); - let before_init_cycles = - (before_init_cycles as u32) | ((before_init_cycles_2 as u32) << 16); - - music_box.before_frame(&mut mixer); - mixer.frame(); - agb::println!("cycles for init {}", after_init_cycles - before_init_cycles); - vblank.wait_for_vblank(); - - mixer.after_vblank(); - - level.background.init_foreground(&mut vram); - - for _ in 0..60 { + for _ in 0..20 { music_box.before_frame(&mut mixer); mixer.frame(); vblank.wait_for_vblank(); From de594ad362ebe9ee585fe1ab4770db7d0f50d0ba Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Mon, 14 Feb 2022 21:56:08 +0000 Subject: [PATCH 49/76] Make `init` take an `impl Fn` rather than a `Box` --- agb/src/display/background.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/agb/src/display/background.rs b/agb/src/display/background.rs index ecc5309f..a21ab812 100644 --- a/agb/src/display/background.rs +++ b/agb/src/display/background.rs @@ -482,12 +482,7 @@ impl<'a> InfiniteScrolledMap<'a> { } } - pub fn init( - &mut self, - vram: &mut VRamManager, - pos: Vector2D, - between_updates: Box, - ) { + pub fn init(&mut self, vram: &mut VRamManager, pos: Vector2D, between_updates: impl Fn()) { while self.init_partial(vram, pos) != PartialUpdateStatus::Done { between_updates(); } From 94662eb98279dc51f7f2a1c2173f7f7e3ad888fe Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Mon, 14 Feb 2022 21:57:29 +0000 Subject: [PATCH 50/76] Remove the timers --- examples/the-hat-chooses-the-wizard/src/main.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/examples/the-hat-chooses-the-wizard/src/main.rs b/examples/the-hat-chooses-the-wizard/src/main.rs index c7f08fcd..54ce0fc7 100644 --- a/examples/the-hat-chooses-the-wizard/src/main.rs +++ b/examples/the-hat-chooses-the-wizard/src/main.rs @@ -789,12 +789,6 @@ fn main(mut agb: agb::Gba) -> ! { let mut timer_controller = agb.timers.timers(); let mut mixer = agb.mixer.mixer(&mut timer_controller.timer0); - let mut timer = timer_controller.timer1; - let mut timer_cascade = timer_controller.timer2; - timer_cascade.set_cascade(true); - timer_cascade.set_enabled(true); - timer.set_enabled(true); - object.set_sprite_palettes(object_sheet::object_sheet.palettes); object.set_sprite_tilemap(object_sheet::object_sheet.tiles); From bdcd5b5e6ddc89fa08f695709f4a0ff1338408b8 Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Mon, 14 Feb 2022 22:14:31 +0000 Subject: [PATCH 51/76] Smear the loading of the splash screens --- .../src/splash_screen.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) 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 3575941e..58b487ce 100644 --- a/examples/the-hat-chooses-the-wizard/src/splash_screen.rs +++ b/examples/the-hat-chooses-the-wizard/src/splash_screen.rs @@ -55,6 +55,19 @@ pub fn show_splash_screen( 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(); + } } map.commit(); @@ -84,6 +97,7 @@ pub fn show_splash_screen( } map.hide(); + map.clear(vram); vram.remove_tileset(tile_set_ref); } From d896adfad536248a838489c5681ffebc85a6a752 Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Mon, 14 Feb 2022 22:23:02 +0000 Subject: [PATCH 52/76] Clear the backgrounds once they aren't used any more --- agb/src/display/background.rs | 4 ++++ examples/the-hat-chooses-the-wizard/src/main.rs | 8 +++++++- .../the-hat-chooses-the-wizard/src/splash_screen.rs | 11 ++++------- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/agb/src/display/background.rs b/agb/src/display/background.rs index a21ab812..f786b9ce 100644 --- a/agb/src/display/background.rs +++ b/agb/src/display/background.rs @@ -649,6 +649,10 @@ impl<'a> InfiniteScrolledMap<'a> { 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 { diff --git a/examples/the-hat-chooses-the-wizard/src/main.rs b/examples/the-hat-chooses-the-wizard/src/main.rs index 54ce0fc7..535b5eec 100644 --- a/examples/the-hat-chooses-the-wizard/src/main.rs +++ b/examples/the-hat-chooses-the-wizard/src/main.rs @@ -660,6 +660,11 @@ impl<'a, 'b, 'c> PlayingLevel<'a, 'b> { self.background.background.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); @@ -812,7 +817,7 @@ fn main(mut agb: agb::Gba) -> ! { let mut music_box = sfx::MusicBox::new(); let vblank = agb::interrupt::VBlank::get(); - let mut current_level = 10; + let mut current_level = 11; loop { if current_level == map_tiles::LEVELS.len() as u32 { @@ -934,6 +939,7 @@ fn main(mut agb: agb::Gba) -> ! { } level.hide_backgrounds(); + level.clear_backgrounds(&mut vram); } splash_screen::show_splash_screen( 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 58b487ce..61b7c681 100644 --- a/examples/the-hat-chooses-the-wizard/src/splash_screen.rs +++ b/examples/the-hat-chooses-the-wizard/src/splash_screen.rs @@ -19,16 +19,14 @@ pub fn show_splash_screen( vram: &mut VRamManager, ) { map.set_scroll_pos((0u16, 0u16).into()); - let tile_set_ref = match which { + let (tile_set_ref, palette) = match which { SplashScreen::Start => { let tile_set_ref = vram.add_tileset(TileSet::new( splash_screens::splash.tiles, TileFormat::FourBpp, )); - vram.set_background_palettes(splash_screens::splash.palettes); - - tile_set_ref + (tile_set_ref, splash_screens::splash.palettes) } SplashScreen::End => { let tile_set_ref = vram.add_tileset(TileSet::new( @@ -36,9 +34,7 @@ pub fn show_splash_screen( TileFormat::FourBpp, )); - vram.set_background_palettes(splash_screens::thanks_for_playing.palettes); - - tile_set_ref + (tile_set_ref, splash_screens::thanks_for_playing.palettes) } }; @@ -71,6 +67,7 @@ pub fn show_splash_screen( } map.commit(); + vram.set_background_palettes(palette); map.show(); loop { From b61f774061d51da128fcab1ed463ca36a3eb7e54 Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Mon, 14 Feb 2022 22:26:37 +0000 Subject: [PATCH 53/76] Rename world_display as splash_screen --- .../the-hat-chooses-the-wizard/src/main.rs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/examples/the-hat-chooses-the-wizard/src/main.rs b/examples/the-hat-chooses-the-wizard/src/main.rs index 535b5eec..563d9796 100644 --- a/examples/the-hat-chooses-the-wizard/src/main.rs +++ b/examples/the-hat-chooses-the-wizard/src/main.rs @@ -774,7 +774,7 @@ 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 world_display = tiled.background(Priority::P0); + let mut splash_screen = tiled.background(Priority::P0); let tile_set_ref = vram.add_tileset(TileSet::new( tile_sheet::background.tiles, @@ -785,7 +785,7 @@ fn main(mut agb: agb::Gba) -> ! { splash_screen::SplashScreen::Start, None, None, - &mut world_display, + &mut splash_screen, &mut vram, ); @@ -799,7 +799,7 @@ fn main(mut agb: agb::Gba) -> ! { for y in 0..32u16 { for x in 0..32u16 { - world_display.set_tile( + splash_screen.set_tile( &mut vram, (x, y).into(), tile_set_ref, @@ -808,8 +808,8 @@ fn main(mut agb: agb::Gba) -> ! { } } - world_display.commit(); - world_display.show(); + splash_screen.commit(); + splash_screen.show(); object.enable(); @@ -830,15 +830,15 @@ fn main(mut agb: agb::Gba) -> ! { mixer.after_vblank(); level_display::write_level( - &mut world_display, + &mut splash_screen, current_level / 8 + 1, current_level % 8 + 1, tile_set_ref, &mut vram, ); - world_display.commit(); - world_display.show(); + splash_screen.commit(); + splash_screen.show(); music_box.before_frame(&mut mixer); mixer.frame(); @@ -909,7 +909,7 @@ fn main(mut agb: agb::Gba) -> ! { level.show_backgrounds(); - world_display.hide(); + splash_screen.hide(); loop { match level @@ -946,7 +946,7 @@ fn main(mut agb: agb::Gba) -> ! { splash_screen::SplashScreen::End, Some(&mut mixer), Some(&mut music_box), - &mut world_display, + &mut splash_screen, &mut vram, ); } From 983b56975c938e4225ff0fac0bc91750267b2493 Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Mon, 14 Feb 2022 22:52:10 +0000 Subject: [PATCH 54/76] Try to fix splash screen crackle --- .../the-hat-chooses-the-wizard/src/main.rs | 43 ++++++++++--------- .../src/splash_screen.rs | 13 ++++++ 2 files changed, 35 insertions(+), 21 deletions(-) diff --git a/examples/the-hat-chooses-the-wizard/src/main.rs b/examples/the-hat-chooses-the-wizard/src/main.rs index 563d9796..ad548088 100644 --- a/examples/the-hat-chooses-the-wizard/src/main.rs +++ b/examples/the-hat-chooses-the-wizard/src/main.rs @@ -657,7 +657,7 @@ impl<'a, 'b, 'c> PlayingLevel<'a, 'b> { fn hide_backgrounds(&mut self) { self.background.background.hide(); - self.background.background.hide(); + self.background.foreground.hide(); } fn clear_backgrounds(&mut self, vram: &mut VRamManager) { @@ -775,12 +775,27 @@ 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 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, @@ -790,6 +805,8 @@ fn main(mut agb: agb::Gba) -> ! { ); loop { + 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); @@ -797,20 +814,6 @@ fn main(mut agb: agb::Gba) -> ! { object.set_sprite_palettes(object_sheet::object_sheet.palettes); object.set_sprite_tilemap(object_sheet::object_sheet.tiles); - for y in 0..32u16 { - for x in 0..32u16 { - splash_screen.set_tile( - &mut vram, - (x, y).into(), - tile_set_ref, - TileSetting::from_raw(level_display::BLANK), - ); - } - } - - splash_screen.commit(); - splash_screen.show(); - object.enable(); mixer.enable(); @@ -830,23 +833,21 @@ fn main(mut agb: agb::Gba) -> ! { mixer.after_vblank(); level_display::write_level( - &mut splash_screen, + &mut world_display, current_level / 8 + 1, current_level % 8 + 1, tile_set_ref, &mut vram, ); - splash_screen.commit(); - splash_screen.show(); + world_display.commit(); + world_display.show(); music_box.before_frame(&mut mixer); mixer.frame(); vblank.wait_for_vblank(); mixer.after_vblank(); - vram.set_background_palettes(tile_sheet::background.palettes); - let mut background = InfiniteScrolledMap::new( tiled.background(Priority::P2), Box::new(move |pos: Vector2D| { @@ -909,7 +910,7 @@ fn main(mut agb: agb::Gba) -> ! { level.show_backgrounds(); - splash_screen.hide(); + world_display.hide(); loop { match level 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 61b7c681..cdf185ca 100644 --- a/examples/the-hat-chooses-the-wizard/src/splash_screen.rs +++ b/examples/the-hat-chooses-the-wizard/src/splash_screen.rs @@ -42,6 +42,19 @@ pub fn show_splash_screen( 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( From 2c01ed1690ab407fb0150302886b46fc53c8da33 Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Mon, 14 Feb 2022 23:00:16 +0000 Subject: [PATCH 55/76] Update the purple night --- examples/the-purple-night/Cargo.lock | 7 +++++++ examples/the-purple-night/src/main.rs | 18 ++++++++++++------ 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/examples/the-purple-night/Cargo.lock b/examples/the-purple-night/Cargo.lock index ebe8e942..49150aff 100644 --- a/examples/the-purple-night/Cargo.lock +++ b/examples/the-purple-night/Cargo.lock @@ -25,6 +25,7 @@ dependencies = [ "bare-metal", "bitflags", "hashbrown", + "rustc-hash", ] [[package]] @@ -348,6 +349,12 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3582f63211428f83597b51b2ddb88e2a91a9d52d12831f9d08f5e624e8977422" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "serde" version = "1.0.136" diff --git a/examples/the-purple-night/src/main.rs b/examples/the-purple-night/src/main.rs index abca5419..64e2c1a8 100644 --- a/examples/the-purple-night/src/main.rs +++ b/examples/the-purple-night/src/main.rs @@ -45,9 +45,9 @@ impl<'a> Level<'a> { mut clouds: InfiniteScrolledMap<'a>, vram: &mut VRamManager, ) -> Self { - backdrop.init(vram, (8, 8).into()); - foreground.init(vram, (8, 8).into()); - clouds.init(vram, (2, 2).into()); + backdrop.init(vram, (8, 8).into(), || {}); + foreground.init(vram, (8, 8).into(), || {}); + clouds.init(vram, (2, 2).into(), || {}); backdrop.commit(); foreground.commit(); @@ -2156,7 +2156,9 @@ fn game_with_level(gba: &mut agb::Gba) { ( tileset_ref, TileSetting::from_raw( - tilemap::BACKGROUND_MAP[(pos.x + tilemap::WIDTH * pos.y) as usize], + *tilemap::BACKGROUND_MAP + .get((pos.x + tilemap::WIDTH * pos.y) as usize) + .unwrap_or(&0), ), ) }), @@ -2168,7 +2170,9 @@ fn game_with_level(gba: &mut agb::Gba) { ( tileset_ref, TileSetting::from_raw( - tilemap::FOREGROUND_MAP[(pos.x + tilemap::WIDTH * pos.y) as usize], + *tilemap::FOREGROUND_MAP + .get((pos.x + tilemap::WIDTH * pos.y) as usize) + .unwrap_or(&0), ), ) }), @@ -2180,7 +2184,9 @@ fn game_with_level(gba: &mut agb::Gba) { ( tileset_ref, TileSetting::from_raw( - tilemap::CLOUD_MAP[(pos.x + tilemap::WIDTH * pos.y) as usize], + *tilemap::CLOUD_MAP + .get((pos.x + tilemap::WIDTH * pos.y) as usize) + .unwrap_or(&0), ), ) }), From f1bc09d6cda612b02b190e3ae54239c279fd315e Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Mon, 14 Feb 2022 23:22:04 +0000 Subject: [PATCH 56/76] Avoid crackle on load --- agb/src/display/background.rs | 7 +++- examples/the-purple-night/src/main.rs | 49 ++++++++++++++++++++++----- 2 files changed, 46 insertions(+), 10 deletions(-) diff --git a/agb/src/display/background.rs b/agb/src/display/background.rs index f786b9ce..0eed5d3a 100644 --- a/agb/src/display/background.rs +++ b/agb/src/display/background.rs @@ -482,7 +482,12 @@ impl<'a> InfiniteScrolledMap<'a> { } } - pub fn init(&mut self, vram: &mut VRamManager, pos: Vector2D, between_updates: impl Fn()) { + 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(); } diff --git a/examples/the-purple-night/src/main.rs b/examples/the-purple-night/src/main.rs index 64e2c1a8..661eaf6d 100644 --- a/examples/the-purple-night/src/main.rs +++ b/examples/the-purple-night/src/main.rs @@ -20,8 +20,10 @@ use agb::{ }, fixnum::{FixedNum, Rect, Vector2D}, input::{Button, ButtonController, Tri}, + interrupt::VBlank, }; use generational_arena::Arena; +use sfx::Sfx; agb::include_gfx!("gfx/objects.toml"); agb::include_gfx!("gfx/background.toml"); @@ -43,11 +45,21 @@ impl<'a> Level<'a> { mut backdrop: InfiniteScrolledMap<'a>, mut foreground: InfiniteScrolledMap<'a>, mut clouds: InfiniteScrolledMap<'a>, + start_pos: Vector2D, vram: &mut VRamManager, + sfx: &mut Sfx, ) -> Self { - backdrop.init(vram, (8, 8).into(), || {}); - foreground.init(vram, (8, 8).into(), || {}); - clouds.init(vram, (2, 2).into(), || {}); + let vblank = VBlank::get(); + + let mut between_updates = || { + sfx.frame(); + vblank.wait_for_vblank(); + sfx.after_vblank(); + }; + + 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(); @@ -105,6 +117,12 @@ impl<'a> Level<'a> { None } } + + fn clear(&mut self, vram: &mut VRamManager) { + self.background.clear(vram); + self.foreground.clear(vram); + self.clouds.clear(vram); + } } struct Entity<'a> { @@ -1802,6 +1820,10 @@ impl<'a> Game<'a> { } } + fn clear(&mut self, vram: &mut VRamManager) { + self.level.clear(vram); + } + fn advance_frame( &mut self, object_controller: &'a ObjectControl, @@ -1954,9 +1976,6 @@ impl<'a> Game<'a> { 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); - self.level.background.commit(); - self.level.foreground.commit(); - self.level.clouds.commit(); for i in remove { self.enemies.remove(i); @@ -2001,6 +2020,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); } @@ -2135,7 +2158,7 @@ fn game_with_level(gba: &mut agb::Gba) { let mut sfx = sfx::Sfx::new(&mut mixer); sfx.purple_night(); - let mut start_at_boss = false; + let mut start_at_boss = true; loop { let (background, mut vram) = gba.display.video.tiled0(); @@ -2192,9 +2215,15 @@ fn game_with_level(gba: &mut agb::Gba) { }), ); + let start_pos = if start_at_boss { + (130 * 8, 8).into() + } else { + (8, 8).into() + }; + let mut game = Game::new( &object, - Level::load_level(backdrop, foreground, clouds, &mut vram), + Level::load_level(backdrop, foreground, clouds, start_pos, &mut vram, &mut sfx), start_at_boss, ); @@ -2213,7 +2242,9 @@ fn game_with_level(gba: &mut agb::Gba) { } get_random(); // advance RNG to make it less predictable between runs - } + }; + + game.clear(&mut vram); } } From 05e8acd93c956bc45140c794b74cc5d6b6a8aee7 Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Mon, 14 Feb 2022 23:39:06 +0000 Subject: [PATCH 57/76] Don't start at boss by default --- examples/the-purple-night/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/the-purple-night/src/main.rs b/examples/the-purple-night/src/main.rs index 661eaf6d..5f15ba22 100644 --- a/examples/the-purple-night/src/main.rs +++ b/examples/the-purple-night/src/main.rs @@ -2158,7 +2158,7 @@ fn game_with_level(gba: &mut agb::Gba) { let mut sfx = sfx::Sfx::new(&mut mixer); sfx.purple_night(); - let mut start_at_boss = true; + let mut start_at_boss = false; loop { let (background, mut vram) = gba.display.video.tiled0(); From ed2af0d57ede1b7ee4aa2233597f1e7fbde73d75 Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Sat, 19 Feb 2022 20:50:24 +0000 Subject: [PATCH 58/76] Extract VRamManager to the `tiled` module --- agb/src/display/background.rs | 304 +------------------------ agb/src/display/example_logo.rs | 5 +- agb/src/display/mod.rs | 2 + agb/src/display/tiled/mod.rs | 3 + agb/src/display/tiled/vram_manager.rs | 311 ++++++++++++++++++++++++++ 5 files changed, 327 insertions(+), 298 deletions(-) create mode 100644 agb/src/display/tiled/mod.rs create mode 100644 agb/src/display/tiled/vram_manager.rs diff --git a/agb/src/display/background.rs b/agb/src/display/background.rs index 0eed5d3a..d486987b 100644 --- a/agb/src/display/background.rs +++ b/agb/src/display/background.rs @@ -1,11 +1,10 @@ use core::cell::RefCell; -use core::hash::BuildHasherDefault; use core::ops::{Deref, DerefMut}; -use alloc::vec::Vec; -use alloc::{boxed::Box, vec}; -use hashbrown::HashMap; -use rustc_hash::FxHasher; +use alloc::boxed::Box; + +pub use super::tiled::VRamManager; +use super::tiled::{TileIndex, TileSetReference}; use crate::bitarray::Bitarray; use crate::{ @@ -15,279 +14,21 @@ use crate::{ }; use super::{ - palette16, set_graphics_mode, set_graphics_settings, DisplayMode, GraphicsSettings, Priority, + set_graphics_mode, set_graphics_settings, DisplayMode, GraphicsSettings, Priority, DISPLAY_CONTROL, }; -const PALETTE_BACKGROUND: MemoryMapped1DArray = - unsafe { MemoryMapped1DArray::new(0x0500_0000) }; - -#[cfg(debug_assertions)] -const unsafe fn debug_unreachable_unchecked() -> ! { - unreachable!(); -} - -#[cfg(not(debug_assertions))] -const unsafe fn debug_unreachable_unchecked() -> ! { - 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 } - } -} - -#[derive(Clone, Copy, PartialEq, Eq)] -pub struct TileSetReference { - id: u16, - generation: u16, -} - -#[derive(Debug)] -pub struct TileIndex(u16); - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -struct TileReference(u16, u16); - -enum VRamState { - ReferenceCounted(u16, TileReference), - Free(u16), -} - -impl VRamState { - fn increase_reference(&mut self) { - if let VRamState::ReferenceCounted(count, _) = self { - *count += 1; - } else { - unsafe { debug_unreachable_unchecked() }; - } - } - - fn decrease_reference(&mut self) -> (u16, TileReference) { - if let VRamState::ReferenceCounted(count, tile_ref) = self { - *count -= 1; - (*count, *tile_ref) - } else { - unsafe { debug_unreachable_unchecked() }; - } - } -} - -enum ArenaStorageItem { - EndOfFreeList, - NextFree(usize), - Data(T, u16), -} - -pub struct VRamManager<'a> { - tilesets: Vec>>, - generation: u16, - free_pointer: Option, - - tile_set_to_vram: HashMap>, - references: Vec, - vram_free_pointer: Option, -} - -const END_OF_FREE_LIST_MARKER: u16 = u16::MAX; - -impl<'a> VRamManager<'a> { - pub fn new() -> Self { - Self { - tilesets: Vec::new(), - generation: 0, - free_pointer: None, - - tile_set_to_vram: HashMap::default(), - references: vec![VRamState::Free(0)], - vram_free_pointer: None, - } - } - - pub fn add_tileset(&mut self, tileset: TileSet<'a>) -> TileSetReference { - let generation = self.generation; - self.generation = self.generation.wrapping_add(1); - - 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() }, - } - } else { - self.tilesets.push(tileset); - self.tilesets.len() - 1 - }; - - 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); - } - _ => unsafe { debug_unreachable_unchecked() }, - } - } - - fn add_tile(&mut self, tile_set_ref: TileSetReference, tile: u16) -> TileIndex { - let tile_ref = TileReference(tile_set_ref.id, tile); - if let Some(&reference) = self.tile_set_to_vram.get(&tile_ref) { - if reference.1 == tile_set_ref.generation { - self.references[reference.0 as usize].increase_reference(); - return TileIndex(reference.0 as u16); - } - } - - let index_to_copy_into = if let Some(ptr) = self.vram_free_pointer.take() { - match self.references[ptr] { - VRamState::Free(next_free) => { - if next_free != END_OF_FREE_LIST_MARKER { - self.vram_free_pointer = Some(next_free as usize); - } - } - VRamState::ReferenceCounted(_, _) => unsafe { debug_unreachable_unchecked() }, - } - - self.references[ptr] = VRamState::ReferenceCounted(1, tile_ref); - ptr - } else { - self.references - .push(VRamState::ReferenceCounted(1, tile_ref)); - self.references.len() - 1 - }; - - 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 { - unsafe { debug_unreachable_unchecked() }; - }; - - let tile_size_in_half_words = TileFormat::FourBpp.tile_size() / 2; - - const TILE_BACKGROUND_ADDRESS: usize = 0x0600_0000; - unsafe { - dma_copy( - tile_slice.as_ptr() as *const u16, - (TILE_BACKGROUND_ADDRESS as *mut u16) - .add(index_to_copy_into * tile_size_in_half_words), - tile_size_in_half_words, - ); - } - - self.tile_set_to_vram.insert( - TileReference(tile_set_ref.id, tile), - (index_to_copy_into as u16, tile_set_ref.generation), - ); - - TileIndex(index_to_copy_into as u16) - } - - fn remove_tile(&mut self, tile_index: TileIndex) { - let index = tile_index.0 as usize; - - let (new_count, tile_ref) = self.references[index].decrease_reference(); - - if new_count != 0 { - return; - } - - if let Some(ptr) = self.vram_free_pointer { - self.references[index] = VRamState::Free(ptr as u16); - } else { - self.references[index] = VRamState::Free(END_OF_FREE_LIST_MARKER); - } - - self.tile_set_to_vram.remove(&tile_ref); - - self.vram_free_pointer = Some(index); - } - - /// 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) - } - } -} - #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] #[repr(transparent)] struct Tile(u16); impl Tile { fn new(idx: TileIndex, setting: TileSetting) -> Self { - Self(idx.0 | setting.setting()) + Self(idx.index() | setting.setting()) } fn tile_index(self) -> TileIndex { - TileIndex(self.0 & ((1 << 10) - 1)) + TileIndex::new(self.0 & ((1 << 10) - 1)) } } @@ -709,12 +450,6 @@ impl Tiled0 { } } -impl TileSetReference { - fn new(id: u16, generation: u16) -> Self { - Self { id, generation } - } -} - pub struct MapLoan<'a, T> { map: T, background_id: u8, @@ -752,28 +487,3 @@ impl<'a, T> Drop for MapLoan<'a, T> { .set(self.background_id as usize, false); } } - -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)) }; - -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/display/example_logo.rs b/agb/src/display/example_logo.rs index 21ef97d5..03ba2753 100644 --- a/agb/src/display/example_logo.rs +++ b/agb/src/display/example_logo.rs @@ -1,4 +1,7 @@ -use super::background::{RegularMap, TileFormat, TileSet, TileSetting, VRamManager}; +use super::{ + background::{RegularMap, TileSetting}, + tiled::{TileFormat, TileSet, VRamManager}, +}; crate::include_gfx!("gfx/agb_logo.toml"); diff --git a/agb/src/display/mod.rs b/agb/src/display/mod.rs index e16ec663..b5c2ed73 100644 --- a/agb/src/display/mod.rs +++ b/agb/src/display/mod.rs @@ -5,6 +5,8 @@ use video::Video; use self::object::ObjectControl; +mod tiled; + /// Graphics mode 0. Four regular backgrounds. pub mod background; /// Graphics mode 3. Bitmap mode that provides a 16-bit colour framebuffer. diff --git a/agb/src/display/tiled/mod.rs b/agb/src/display/tiled/mod.rs new file mode 100644 index 00000000..be39b223 --- /dev/null +++ b/agb/src/display/tiled/mod.rs @@ -0,0 +1,3 @@ +mod vram_manager; + +pub use vram_manager::{TileFormat, TileIndex, TileSet, TileSetReference, VRamManager}; diff --git a/agb/src/display/tiled/vram_manager.rs b/agb/src/display/tiled/vram_manager.rs new file mode 100644 index 00000000..10f61e3c --- /dev/null +++ b/agb/src/display/tiled/vram_manager.rs @@ -0,0 +1,311 @@ +use alloc::vec::Vec; + +use core::hash::BuildHasherDefault; +use hashbrown::HashMap; +use rustc_hash::FxHasher; + +use alloc::vec; + +use crate::{ + display::palette16, + memory_mapped::{MemoryMapped, MemoryMapped1DArray}, +}; + +const PALETTE_BACKGROUND: MemoryMapped1DArray = + unsafe { MemoryMapped1DArray::new(0x0500_0000) }; + +#[cfg(debug_assertions)] +const unsafe fn debug_unreachable_unchecked() -> ! { + unreachable!(); +} + +#[cfg(not(debug_assertions))] +const unsafe fn debug_unreachable_unchecked() -> ! { + 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 } + } +} + +#[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: u16) -> Self { + Self(index) + } + + pub(crate) const fn index(&self) -> u16 { + self.0 + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +struct TileReference(u16, u16); + +enum VRamState { + ReferenceCounted(u16, TileReference), + Free(u16), +} + +impl VRamState { + fn increase_reference(&mut self) { + if let VRamState::ReferenceCounted(count, _) = self { + *count += 1; + } else { + unsafe { debug_unreachable_unchecked() }; + } + } + + fn decrease_reference(&mut self) -> (u16, TileReference) { + if let VRamState::ReferenceCounted(count, tile_ref) = self { + *count -= 1; + (*count, *tile_ref) + } else { + unsafe { debug_unreachable_unchecked() }; + } + } +} + +enum ArenaStorageItem { + EndOfFreeList, + NextFree(usize), + Data(T, u16), +} + +pub struct VRamManager<'a> { + tilesets: Vec>>, + generation: u16, + free_pointer: Option, + + tile_set_to_vram: HashMap>, + references: Vec, + vram_free_pointer: Option, +} + +const END_OF_FREE_LIST_MARKER: u16 = u16::MAX; + +impl<'a> VRamManager<'a> { + pub fn new() -> Self { + Self { + tilesets: Vec::new(), + generation: 0, + free_pointer: None, + + tile_set_to_vram: HashMap::default(), + references: vec![VRamState::Free(0)], + vram_free_pointer: None, + } + } + + pub fn add_tileset(&mut self, tileset: TileSet<'a>) -> TileSetReference { + let generation = self.generation; + self.generation = self.generation.wrapping_add(1); + + 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() }, + } + } else { + self.tilesets.push(tileset); + self.tilesets.len() - 1 + }; + + 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); + } + _ => unsafe { debug_unreachable_unchecked() }, + } + } + + pub(crate) fn add_tile(&mut self, tile_set_ref: TileSetReference, tile: u16) -> TileIndex { + let tile_ref = TileReference(tile_set_ref.id, tile); + if let Some(&reference) = self.tile_set_to_vram.get(&tile_ref) { + if reference.1 == tile_set_ref.generation { + self.references[reference.0 as usize].increase_reference(); + return TileIndex(reference.0 as u16); + } + } + + let index_to_copy_into = if let Some(ptr) = self.vram_free_pointer.take() { + match self.references[ptr] { + VRamState::Free(next_free) => { + if next_free != END_OF_FREE_LIST_MARKER { + self.vram_free_pointer = Some(next_free as usize); + } + } + VRamState::ReferenceCounted(_, _) => unsafe { debug_unreachable_unchecked() }, + } + + self.references[ptr] = VRamState::ReferenceCounted(1, tile_ref); + ptr + } else { + self.references + .push(VRamState::ReferenceCounted(1, tile_ref)); + self.references.len() - 1 + }; + + 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 { + unsafe { debug_unreachable_unchecked() }; + }; + + let tile_size_in_half_words = TileFormat::FourBpp.tile_size() / 2; + + const TILE_BACKGROUND_ADDRESS: usize = 0x0600_0000; + unsafe { + dma_copy( + tile_slice.as_ptr() as *const u16, + (TILE_BACKGROUND_ADDRESS as *mut u16) + .add(index_to_copy_into * tile_size_in_half_words), + tile_size_in_half_words, + ); + } + + self.tile_set_to_vram.insert( + TileReference(tile_set_ref.id, tile), + (index_to_copy_into as u16, tile_set_ref.generation), + ); + + TileIndex(index_to_copy_into as u16) + } + + pub(crate) fn remove_tile(&mut self, tile_index: TileIndex) { + let index = tile_index.0 as usize; + + let (new_count, tile_ref) = self.references[index].decrease_reference(); + + if new_count != 0 { + return; + } + + if let Some(ptr) = self.vram_free_pointer { + self.references[index] = VRamState::Free(ptr as u16); + } else { + self.references[index] = VRamState::Free(END_OF_FREE_LIST_MARKER); + } + + self.tile_set_to_vram.remove(&tile_ref); + + self.vram_free_pointer = Some(index); + } + + /// 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) + } + } +} + +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)) }; + +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)); +} From 742231fbc589fe22ca538339392d90069f638952 Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Sat, 19 Feb 2022 20:59:59 +0000 Subject: [PATCH 59/76] Try a different transparent tile then 0 --- agb/src/display/background.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/agb/src/display/background.rs b/agb/src/display/background.rs index d486987b..db2724fa 100644 --- a/agb/src/display/background.rs +++ b/agb/src/display/background.rs @@ -70,6 +70,8 @@ pub struct RegularMap { tiles_dirty: bool, } +pub const TRANSPARENT_TILE_INDEX: u16 = 1 << 10 - 1; + impl RegularMap { fn new(background_id: u8, screenblock: u8, priority: Priority) -> Self { Self { @@ -101,7 +103,7 @@ impl RegularMap { let tile_index = tile_setting.index(); - let new_tile = if tile_index != 0 { + 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 { From a35119fdbdfa3012e2fd5f29fa23bcfc46eb983d Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Sat, 19 Feb 2022 21:00:09 +0000 Subject: [PATCH 60/76] Fix hat chooses the wizard --- agb/examples/chicken.rs | 7 ++----- agb/src/display/mod.rs | 2 +- examples/the-hat-chooses-the-wizard/src/level_display.rs | 3 ++- examples/the-hat-chooses-the-wizard/src/main.rs | 5 ++--- examples/the-hat-chooses-the-wizard/src/splash_screen.rs | 5 ++++- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/agb/examples/chicken.rs b/agb/examples/chicken.rs index 344952e1..bcb48aaf 100644 --- a/agb/examples/chicken.rs +++ b/agb/examples/chicken.rs @@ -2,11 +2,8 @@ #![no_main] use agb::{ - display::{ - background::{TileFormat, TileSet, TileSetting}, - object::ObjectStandard, - HEIGHT, WIDTH, - }, + display::tiled::{TileFormat, TileSet}, + display::{background::TileSetting, object::ObjectStandard, HEIGHT, WIDTH}, input::Button, }; use core::convert::TryInto; diff --git a/agb/src/display/mod.rs b/agb/src/display/mod.rs index b5c2ed73..03453d10 100644 --- a/agb/src/display/mod.rs +++ b/agb/src/display/mod.rs @@ -5,7 +5,7 @@ use video::Video; use self::object::ObjectControl; -mod tiled; +pub mod tiled; /// Graphics mode 0. Four regular backgrounds. pub mod background; 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 599594ca..ccf45fa8 100644 --- a/examples/the-hat-chooses-the-wizard/src/level_display.rs +++ b/examples/the-hat-chooses-the-wizard/src/level_display.rs @@ -1,5 +1,6 @@ use agb::display::{ - background::{RegularMap, TileSetReference, TileSetting, VRamManager}, + background::{RegularMap, TileSetting, VRamManager}, + tiled::TileSetReference, HEIGHT, WIDTH, }; diff --git a/examples/the-hat-chooses-the-wizard/src/main.rs b/examples/the-hat-chooses-the-wizard/src/main.rs index ad548088..c91a8165 100644 --- a/examples/the-hat-chooses-the-wizard/src/main.rs +++ b/examples/the-hat-chooses-the-wizard/src/main.rs @@ -5,10 +5,9 @@ extern crate alloc; use agb::{ display::{ - background::{ - InfiniteScrolledMap, PartialUpdateStatus, TileFormat, TileSet, TileSetting, VRamManager, - }, + background::{InfiniteScrolledMap, PartialUpdateStatus, TileSetting, VRamManager}, object::{ObjectControl, ObjectStandard, Size}, + tiled::{TileFormat, TileSet}, Priority, HEIGHT, WIDTH, }, fixnum::{FixedNum, Vector2D}, 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 cdf185ca..584ba5eb 100644 --- a/examples/the-hat-chooses-the-wizard/src/splash_screen.rs +++ b/examples/the-hat-chooses-the-wizard/src/splash_screen.rs @@ -1,6 +1,9 @@ use super::sfx::MusicBox; use agb::{ - display::background::{RegularMap, TileFormat, TileSet, TileSetting, VRamManager}, + display::{ + background::{RegularMap, TileSetting, VRamManager}, + tiled::{TileFormat, TileSet}, + }, sound::mixer::Mixer, }; From e88602b570be52cfbb51ce0d2bea6fd918d82e24 Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Sat, 19 Feb 2022 21:03:42 +0000 Subject: [PATCH 61/76] Get my brackets correct --- agb/src/display/background.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agb/src/display/background.rs b/agb/src/display/background.rs index db2724fa..9d703eae 100644 --- a/agb/src/display/background.rs +++ b/agb/src/display/background.rs @@ -70,7 +70,7 @@ pub struct RegularMap { tiles_dirty: bool, } -pub const TRANSPARENT_TILE_INDEX: u16 = 1 << 10 - 1; +pub const TRANSPARENT_TILE_INDEX: u16 = (1 << 10) - 1; impl RegularMap { fn new(background_id: u8, screenblock: u8, priority: Priority) -> Self { From 78fb706c9aa5344c7e89ac0c291797de787136ba Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Sat, 19 Feb 2022 21:09:56 +0000 Subject: [PATCH 62/76] Extract map to its own file --- agb/examples/chicken.rs | 4 +- agb/src/display/background.rs | 185 +------------------------------- agb/src/display/example_logo.rs | 5 +- agb/src/display/tiled/mod.rs | 42 ++++++++ 4 files changed, 47 insertions(+), 189 deletions(-) diff --git a/agb/examples/chicken.rs b/agb/examples/chicken.rs index bcb48aaf..4d5d2e14 100644 --- a/agb/examples/chicken.rs +++ b/agb/examples/chicken.rs @@ -2,8 +2,8 @@ #![no_main] use agb::{ - display::tiled::{TileFormat, TileSet}, - display::{background::TileSetting, object::ObjectStandard, HEIGHT, WIDTH}, + display::tiled::{TileFormat, TileSet, TileSetting}, + display::{object::ObjectStandard, HEIGHT, WIDTH}, input::Button, }; use core::convert::TryInto; diff --git a/agb/src/display/background.rs b/agb/src/display/background.rs index 9d703eae..8bbafc2d 100644 --- a/agb/src/display/background.rs +++ b/agb/src/display/background.rs @@ -4,196 +4,15 @@ use core::ops::{Deref, DerefMut}; use alloc::boxed::Box; pub use super::tiled::VRamManager; -use super::tiled::{TileIndex, TileSetReference}; +use super::tiled::{RegularMap, TileSetReference, TileSetting}; use crate::bitarray::Bitarray; use crate::{ display, fixnum::{Rect, Vector2D}, - memory_mapped::{MemoryMapped, MemoryMapped1DArray}, }; -use super::{ - set_graphics_mode, set_graphics_settings, DisplayMode, GraphicsSettings, Priority, - DISPLAY_CONTROL, -}; - -#[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 & ((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) - } -} - -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 { - 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() { - 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(); - - let scroll_pos = self.get_scroll_pos(); - let start_x = scroll_pos.x / 8; - let end_x = div_ceil(scroll_pos.x as i32 + display::WIDTH, 8) as u16 + 1; - - let start_y = scroll_pos.y / 8; - let end_y = div_ceil(scroll_pos.y as i32 + display::HEIGHT, 8) as u16 + 1; - - for y in start_y..end_y { - for x in start_x..end_x { - let id = y.rem_euclid(32) * 32 + x.rem_euclid(32); - screenblock_memory.set(id as usize, self.tiles[id as usize].0); - } - } - - 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) -> MemoryMapped1DArray { - unsafe { MemoryMapped1DArray::new(0x0600_0000 + 0x1000 * self.screenblock as usize / 2) } - } -} +use super::{set_graphics_mode, set_graphics_settings, DisplayMode, GraphicsSettings, Priority}; pub struct InfiniteScrolledMap<'a> { map: MapLoan<'a, RegularMap>, diff --git a/agb/src/display/example_logo.rs b/agb/src/display/example_logo.rs index 03ba2753..46db1afc 100644 --- a/agb/src/display/example_logo.rs +++ b/agb/src/display/example_logo.rs @@ -1,7 +1,4 @@ -use super::{ - background::{RegularMap, TileSetting}, - tiled::{TileFormat, TileSet, VRamManager}, -}; +use super::tiled::{RegularMap, TileFormat, TileSet, TileSetting, VRamManager}; crate::include_gfx!("gfx/agb_logo.toml"); diff --git a/agb/src/display/tiled/mod.rs b/agb/src/display/tiled/mod.rs index be39b223..d88ad609 100644 --- a/agb/src/display/tiled/mod.rs +++ b/agb/src/display/tiled/mod.rs @@ -1,3 +1,45 @@ +mod map; mod vram_manager; +pub use map::RegularMap; 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 & ((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) + } +} From 6b492c59562abaf81a9e6dee64d2b12e98ac03b8 Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Sat, 19 Feb 2022 21:15:09 +0000 Subject: [PATCH 63/76] Extract more to new files --- agb/src/display/background.rs | 75 +----------- agb/src/display/tiled/map.rs | 198 ++++++++++++++++++++++++++++++++ agb/src/display/tiled/mod.rs | 4 +- agb/src/display/tiled/tiled0.rs | 37 ++++++ agb/src/display/video.rs | 2 +- 5 files changed, 240 insertions(+), 76 deletions(-) create mode 100644 agb/src/display/tiled/map.rs create mode 100644 agb/src/display/tiled/tiled0.rs diff --git a/agb/src/display/background.rs b/agb/src/display/background.rs index 8bbafc2d..7ee8b6b3 100644 --- a/agb/src/display/background.rs +++ b/agb/src/display/background.rs @@ -1,19 +1,13 @@ -use core::cell::RefCell; -use core::ops::{Deref, DerefMut}; - use alloc::boxed::Box; pub use super::tiled::VRamManager; -use super::tiled::{RegularMap, TileSetReference, TileSetting}; +use super::tiled::{MapLoan, RegularMap, TileSetReference, TileSetting}; -use crate::bitarray::Bitarray; use crate::{ display, fixnum::{Rect, Vector2D}, }; -use super::{set_graphics_mode, set_graphics_settings, DisplayMode, GraphicsSettings, Priority}; - pub struct InfiniteScrolledMap<'a> { map: MapLoan<'a, RegularMap>, get_tile: Box) -> (TileSetReference, TileSetting)>, @@ -241,70 +235,3 @@ fn div_ceil(x: i32, y: i32) -> i32 { x / y } } - -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) - } -} - -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> { - 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/map.rs b/agb/src/display/tiled/map.rs new file mode 100644 index 00000000..f7271dc6 --- /dev/null +++ b/agb/src/display/tiled/map.rs @@ -0,0 +1,198 @@ +use core::cell::RefCell; +use core::ops::{Deref, DerefMut}; + +use crate::bitarray::Bitarray; +use crate::display::{self, Priority, DISPLAY_CONTROL}; +use crate::fixnum::Vector2D; +use crate::memory_mapped::{MemoryMapped, MemoryMapped1DArray}; + +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() { + 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(); + + let scroll_pos = self.get_scroll_pos(); + let start_x = scroll_pos.x / 8; + let end_x = div_ceil(scroll_pos.x as i32 + display::WIDTH, 8) as u16 + 1; + + let start_y = scroll_pos.y / 8; + let end_y = div_ceil(scroll_pos.y as i32 + display::HEIGHT, 8) as u16 + 1; + + for y in start_y..end_y { + for x in start_x..end_x { + let id = y.rem_euclid(32) * 32 + x.rem_euclid(32); + screenblock_memory.set(id as usize, self.tiles[id as usize].0); + } + } + + 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) -> MemoryMapped1DArray { + unsafe { MemoryMapped1DArray::new(0x0600_0000 + 0x1000 * self.screenblock as usize / 2) } + } +} + +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); + } +} + +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/mod.rs b/agb/src/display/tiled/mod.rs index d88ad609..e8908e42 100644 --- a/agb/src/display/tiled/mod.rs +++ b/agb/src/display/tiled/mod.rs @@ -1,7 +1,9 @@ mod map; +mod tiled0; mod vram_manager; -pub use map::RegularMap; +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)] 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/video.rs b/agb/src/display/video.rs index fce2b572..046ff12c 100644 --- a/agb/src/display/video.rs +++ b/agb/src/display/video.rs @@ -1,7 +1,7 @@ use super::{ - background::{Tiled0, VRamManager}, bitmap3::Bitmap3, bitmap4::Bitmap4, + tiled::{Tiled0, VRamManager}, }; #[non_exhaustive] From 22c6e37c8881ead6b29d97f4fb8af1a0056dec6c Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Sat, 19 Feb 2022 21:16:43 +0000 Subject: [PATCH 64/76] Finally extract InfiniteScrolledMap --- agb/src/display/mod.rs | 6 ++---- .../{background.rs => tiled/infinite_scrolled_map.rs} | 3 +-- agb/src/display/tiled/mod.rs | 2 ++ 3 files changed, 5 insertions(+), 6 deletions(-) rename agb/src/display/{background.rs => tiled/infinite_scrolled_map.rs} (98%) diff --git a/agb/src/display/mod.rs b/agb/src/display/mod.rs index 03453d10..fe6b5579 100644 --- a/agb/src/display/mod.rs +++ b/agb/src/display/mod.rs @@ -5,10 +5,6 @@ use video::Video; use self::object::ObjectControl; -pub mod tiled; - -/// 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. @@ -21,6 +17,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; diff --git a/agb/src/display/background.rs b/agb/src/display/tiled/infinite_scrolled_map.rs similarity index 98% rename from agb/src/display/background.rs rename to agb/src/display/tiled/infinite_scrolled_map.rs index 7ee8b6b3..01bce594 100644 --- a/agb/src/display/background.rs +++ b/agb/src/display/tiled/infinite_scrolled_map.rs @@ -1,7 +1,6 @@ use alloc::boxed::Box; -pub use super::tiled::VRamManager; -use super::tiled::{MapLoan, RegularMap, TileSetReference, TileSetting}; +use super::{MapLoan, RegularMap, TileSetReference, TileSetting, VRamManager}; use crate::{ display, diff --git a/agb/src/display/tiled/mod.rs b/agb/src/display/tiled/mod.rs index e8908e42..cc09c015 100644 --- a/agb/src/display/tiled/mod.rs +++ b/agb/src/display/tiled/mod.rs @@ -1,7 +1,9 @@ +mod infinite_scrolled_map; mod map; mod tiled0; mod vram_manager; +pub use infinite_scrolled_map::InfiniteScrolledMap; pub use map::{MapLoan, RegularMap}; pub use tiled0::Tiled0; pub use vram_manager::{TileFormat, TileIndex, TileSet, TileSetReference, VRamManager}; From ceb17a018683b9882b3950cc4b6b97a86bb812f6 Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Sat, 19 Feb 2022 21:28:59 +0000 Subject: [PATCH 65/76] Update hat chooses wizard and purple night --- agb/src/display/tiled/mod.rs | 2 +- examples/the-hat-chooses-the-wizard/src/level_display.rs | 3 +-- examples/the-hat-chooses-the-wizard/src/main.rs | 5 +++-- examples/the-hat-chooses-the-wizard/src/splash_screen.rs | 5 +---- examples/the-purple-night/src/main.rs | 2 +- 5 files changed, 7 insertions(+), 10 deletions(-) diff --git a/agb/src/display/tiled/mod.rs b/agb/src/display/tiled/mod.rs index cc09c015..72c6c8ef 100644 --- a/agb/src/display/tiled/mod.rs +++ b/agb/src/display/tiled/mod.rs @@ -3,7 +3,7 @@ mod map; mod tiled0; mod vram_manager; -pub use infinite_scrolled_map::InfiniteScrolledMap; +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}; 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 ccf45fa8..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,6 +1,5 @@ use agb::display::{ - background::{RegularMap, TileSetting, VRamManager}, - tiled::TileSetReference, + tiled::{RegularMap, TileSetReference, TileSetting, VRamManager}, HEIGHT, WIDTH, }; diff --git a/examples/the-hat-chooses-the-wizard/src/main.rs b/examples/the-hat-chooses-the-wizard/src/main.rs index c91a8165..14f0e54c 100644 --- a/examples/the-hat-chooses-the-wizard/src/main.rs +++ b/examples/the-hat-chooses-the-wizard/src/main.rs @@ -5,9 +5,10 @@ extern crate alloc; use agb::{ display::{ - background::{InfiniteScrolledMap, PartialUpdateStatus, TileSetting, VRamManager}, object::{ObjectControl, ObjectStandard, Size}, - tiled::{TileFormat, TileSet}, + tiled::{ + InfiniteScrolledMap, PartialUpdateStatus, TileFormat, TileSet, TileSetting, VRamManager, + }, Priority, HEIGHT, WIDTH, }, fixnum::{FixedNum, Vector2D}, 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 584ba5eb..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,9 +1,6 @@ use super::sfx::MusicBox; use agb::{ - display::{ - background::{RegularMap, TileSetting, VRamManager}, - tiled::{TileFormat, TileSet}, - }, + display::tiled::{RegularMap, TileFormat, TileSet, TileSetting, VRamManager}, sound::mixer::Mixer, }; diff --git a/examples/the-purple-night/src/main.rs b/examples/the-purple-night/src/main.rs index 5f15ba22..02013e28 100644 --- a/examples/the-purple-night/src/main.rs +++ b/examples/the-purple-night/src/main.rs @@ -14,8 +14,8 @@ use rng::get_random; use agb::{ display::{ - background::{InfiniteScrolledMap, TileFormat, TileSet, TileSetting, VRamManager}, object::{ObjectControl, ObjectStandard}, + tiled::{InfiniteScrolledMap, TileFormat, TileSet, TileSetting, VRamManager}, Priority, HEIGHT, WIDTH, }, fixnum::{FixedNum, Rect, Vector2D}, From aa7823232b5cf46564bfb36d0e2e6661128f50d1 Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Sat, 19 Feb 2022 22:06:06 +0000 Subject: [PATCH 66/76] Try using a vec of vecs rather than a hashmap --- agb/src/display/tiled/vram_manager.rs | 33 +++++++++++++++------------ 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/agb/src/display/tiled/vram_manager.rs b/agb/src/display/tiled/vram_manager.rs index 10f61e3c..249023ba 100644 --- a/agb/src/display/tiled/vram_manager.rs +++ b/agb/src/display/tiled/vram_manager.rs @@ -1,10 +1,5 @@ -use alloc::vec::Vec; - -use core::hash::BuildHasherDefault; -use hashbrown::HashMap; -use rustc_hash::FxHasher; - use alloc::vec; +use alloc::vec::Vec; use crate::{ display::palette16, @@ -49,6 +44,10 @@ 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)] @@ -114,7 +113,7 @@ pub struct VRamManager<'a> { generation: u16, free_pointer: Option, - tile_set_to_vram: HashMap>, + tile_set_to_vram: Vec>, references: Vec, vram_free_pointer: Option, } @@ -128,7 +127,7 @@ impl<'a> VRamManager<'a> { generation: 0, free_pointer: None, - tile_set_to_vram: HashMap::default(), + tile_set_to_vram: Default::default(), references: vec![VRamState::Free(0)], vram_free_pointer: None, } @@ -138,6 +137,7 @@ impl<'a> VRamManager<'a> { 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() { @@ -158,6 +158,10 @@ impl<'a> VRamManager<'a> { 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) } @@ -185,10 +189,13 @@ impl<'a> VRamManager<'a> { pub(crate) fn add_tile(&mut self, tile_set_ref: TileSetReference, tile: u16) -> TileIndex { let tile_ref = TileReference(tile_set_ref.id, tile); - if let Some(&reference) = self.tile_set_to_vram.get(&tile_ref) { + let reference = self.tile_set_to_vram[tile_set_ref.id as usize][tile as usize]; + if reference != Default::default() { if reference.1 == tile_set_ref.generation { self.references[reference.0 as usize].increase_reference(); return TileIndex(reference.0 as u16); + } else { + panic!("Tileset unloaded but not cleared from vram"); } } @@ -236,10 +243,8 @@ impl<'a> VRamManager<'a> { ); } - self.tile_set_to_vram.insert( - TileReference(tile_set_ref.id, tile), - (index_to_copy_into as u16, tile_set_ref.generation), - ); + self.tile_set_to_vram[tile_set_ref.id as usize][tile as usize] = + (index_to_copy_into as u16, tile_set_ref.generation); TileIndex(index_to_copy_into as u16) } @@ -259,7 +264,7 @@ impl<'a> VRamManager<'a> { self.references[index] = VRamState::Free(END_OF_FREE_LIST_MARKER); } - self.tile_set_to_vram.remove(&tile_ref); + self.tile_set_to_vram[tile_ref.0 as usize][tile_ref.1 as usize] = Default::default(); self.vram_free_pointer = Some(index); } From 7b43debd3e20aba14a8af3f2cd0a08248e2adc55 Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Thu, 24 Feb 2022 20:13:03 +0000 Subject: [PATCH 67/76] WIP: Try removing the hashmap --- agb/Cargo.lock | 40 ------------------- agb/Cargo.toml | 2 - agb/src/display/tiled/vram_manager.rs | 20 +++++----- .../the-hat-chooses-the-wizard/Cargo.lock | 40 ------------------- examples/the-purple-night/Cargo.lock | 40 ------------------- 5 files changed, 11 insertions(+), 131 deletions(-) diff --git a/agb/Cargo.lock b/agb/Cargo.lock index 3ea5f3c7..472cef4a 100644 --- a/agb/Cargo.lock +++ b/agb/Cargo.lock @@ -24,8 +24,6 @@ dependencies = [ "agb_sound_converter", "bare-metal", "bitflags", - "hashbrown", - "rustc-hash", ] [[package]] @@ -67,17 +65,6 @@ dependencies = [ "syn", ] -[[package]] -name = "ahash" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" -dependencies = [ - "getrandom", - "once_cell", - "version_check", -] - [[package]] name = "autocfg" version = "1.1.0" @@ -149,15 +136,6 @@ dependencies = [ "wasi", ] -[[package]] -name = "hashbrown" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c21d40587b92fa6a6c6e3c1bdbf87d75511db5672f9c93175574b3a00df1758" -dependencies = [ - "ahash", -] - [[package]] name = "hound" version = "3.4.0" @@ -235,12 +213,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "once_cell" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" - [[package]] name = "png" version = "0.17.5" @@ -307,12 +279,6 @@ dependencies = [ "getrandom", ] -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - [[package]] name = "serde" version = "1.0.136" @@ -359,12 +325,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - [[package]] name = "wasi" version = "0.10.2+wasi-snapshot-preview1" diff --git a/agb/Cargo.toml b/agb/Cargo.toml index 55af71f6..dbd7d298 100644 --- a/agb/Cargo.toml +++ b/agb/Cargo.toml @@ -21,13 +21,11 @@ freq18157 = ["agb_sound_converter/freq18157"] [dependencies] bitflags = "1.3" -hashbrown = "0.12.0" agb_image_converter = { version = "0.6.0", path = "../agb-image-converter" } agb_sound_converter = { version = "0.1.0", path = "../agb-sound-converter" } agb_macros = { version = "0.1.0", path = "../agb-macros" } agb_fixnum = { version = "0.1.0", path = "../agb-fixnum" } bare-metal = "1.0" -rustc-hash = { version = "1.0", default-features = false } [package.metadata.docs.rs] default-target = "thumbv6m-none-eabi" diff --git a/agb/src/display/tiled/vram_manager.rs b/agb/src/display/tiled/vram_manager.rs index 249023ba..1da86d9f 100644 --- a/agb/src/display/tiled/vram_manager.rs +++ b/agb/src/display/tiled/vram_manager.rs @@ -10,12 +10,12 @@ const PALETTE_BACKGROUND: MemoryMapped1DArray = unsafe { MemoryMapped1DArray::new(0x0500_0000) }; #[cfg(debug_assertions)] -const unsafe fn debug_unreachable_unchecked() -> ! { - unreachable!(); +unsafe fn debug_unreachable_unchecked(message: &'static str) -> ! { + unreachable!(message); } #[cfg(not(debug_assertions))] -const unsafe fn debug_unreachable_unchecked() -> ! { +const unsafe fn debug_unreachable_unchecked(message: &'static str) -> ! { use core::hint::unreachable_unchecked; unreachable_unchecked(); @@ -88,7 +88,7 @@ impl VRamState { if let VRamState::ReferenceCounted(count, _) = self { *count += 1; } else { - unsafe { debug_unreachable_unchecked() }; + unsafe { debug_unreachable_unchecked("Cannot increase reference count of free item") }; } } @@ -97,7 +97,7 @@ impl VRamState { *count -= 1; (*count, *tile_ref) } else { - unsafe { debug_unreachable_unchecked() }; + unsafe { debug_unreachable_unchecked("Cannot decrease reference count of free item") }; } } } @@ -151,7 +151,7 @@ impl<'a> VRamManager<'a> { self.tilesets[ptr] = tileset; ptr } - _ => unsafe { debug_unreachable_unchecked() }, + _ => unsafe { debug_unreachable_unchecked("Free pointer cannot point to data") }, } } else { self.tilesets.push(tileset); @@ -183,7 +183,7 @@ impl<'a> VRamManager<'a> { self.free_pointer = Some(tile_set_ref.id as usize); } - _ => unsafe { debug_unreachable_unchecked() }, + _ => panic!("Must remove valid tileset"), } } @@ -206,7 +206,9 @@ impl<'a> VRamManager<'a> { self.vram_free_pointer = Some(next_free as usize); } } - VRamState::ReferenceCounted(_, _) => unsafe { debug_unreachable_unchecked() }, + VRamState::ReferenceCounted(_, _) => unsafe { + debug_unreachable_unchecked("Free pointer must point to free item") + }, } self.references[ptr] = VRamState::ReferenceCounted(1, tile_ref); @@ -228,7 +230,7 @@ impl<'a> VRamManager<'a> { let tile_offset = (tile as usize) * data.format.tile_size() / 4; &data.tiles[tile_offset..(tile_offset + data.format.tile_size() / 4)] } else { - unsafe { debug_unreachable_unchecked() }; + panic!("Tile set ref must point to existing tile set"); }; let tile_size_in_half_words = TileFormat::FourBpp.tile_size() / 2; diff --git a/examples/the-hat-chooses-the-wizard/Cargo.lock b/examples/the-hat-chooses-the-wizard/Cargo.lock index c6a6fad7..5b924d8d 100644 --- a/examples/the-hat-chooses-the-wizard/Cargo.lock +++ b/examples/the-hat-chooses-the-wizard/Cargo.lock @@ -24,8 +24,6 @@ dependencies = [ "agb_sound_converter", "bare-metal", "bitflags", - "hashbrown", - "rustc-hash", ] [[package]] @@ -67,17 +65,6 @@ dependencies = [ "syn", ] -[[package]] -name = "ahash" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" -dependencies = [ - "getrandom", - "once_cell", - "version_check", -] - [[package]] name = "autocfg" version = "1.1.0" @@ -149,15 +136,6 @@ dependencies = [ "wasi", ] -[[package]] -name = "hashbrown" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c21d40587b92fa6a6c6e3c1bdbf87d75511db5672f9c93175574b3a00df1758" -dependencies = [ - "ahash", -] - [[package]] name = "hound" version = "3.4.0" @@ -241,12 +219,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "once_cell" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" - [[package]] name = "png" version = "0.17.5" @@ -313,12 +285,6 @@ dependencies = [ "getrandom", ] -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - [[package]] name = "ryu" version = "1.0.9" @@ -391,12 +357,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - [[package]] name = "wasi" version = "0.10.2+wasi-snapshot-preview1" diff --git a/examples/the-purple-night/Cargo.lock b/examples/the-purple-night/Cargo.lock index 49150aff..cbb2dcc7 100644 --- a/examples/the-purple-night/Cargo.lock +++ b/examples/the-purple-night/Cargo.lock @@ -24,8 +24,6 @@ dependencies = [ "agb_sound_converter", "bare-metal", "bitflags", - "hashbrown", - "rustc-hash", ] [[package]] @@ -67,17 +65,6 @@ dependencies = [ "syn", ] -[[package]] -name = "ahash" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" -dependencies = [ - "getrandom", - "once_cell", - "version_check", -] - [[package]] name = "autocfg" version = "1.1.0" @@ -173,15 +160,6 @@ dependencies = [ "wasi", ] -[[package]] -name = "hashbrown" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c21d40587b92fa6a6c6e3c1bdbf87d75511db5672f9c93175574b3a00df1758" -dependencies = [ - "ahash", -] - [[package]] name = "hound" version = "3.4.0" @@ -271,12 +249,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "once_cell" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" - [[package]] name = "png" version = "0.17.5" @@ -349,12 +321,6 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3582f63211428f83597b51b2ddb88e2a91a9d52d12831f9d08f5e624e8977422" -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - [[package]] name = "serde" version = "1.0.136" @@ -428,12 +394,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - [[package]] name = "wasi" version = "0.10.2+wasi-snapshot-preview1" From ed3b075b924ff434fdf78fadd24131d74026975a Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Sat, 5 Mar 2022 19:34:47 +0000 Subject: [PATCH 68/76] Fix issue if you are damaged while doing a jump attack --- examples/the-purple-night/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/the-purple-night/src/main.rs b/examples/the-purple-night/src/main.rs index 02013e28..462ad01c 100644 --- a/examples/the-purple-night/src/main.rs +++ b/examples/the-purple-night/src/main.rs @@ -375,7 +375,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 From a3e5187457b5a7b30c928d05c36df51db619c82f Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Sat, 5 Mar 2022 19:35:07 +0000 Subject: [PATCH 69/76] Use DMA to copy the entire internal map in one go --- agb/src/display/tiled/map.rs | 49 +++++++++++++++++++++++++----------- 1 file changed, 34 insertions(+), 15 deletions(-) diff --git a/agb/src/display/tiled/map.rs b/agb/src/display/tiled/map.rs index f7271dc6..ce32312b 100644 --- a/agb/src/display/tiled/map.rs +++ b/agb/src/display/tiled/map.rs @@ -4,7 +4,7 @@ use core::ops::{Deref, DerefMut}; use crate::bitarray::Bitarray; use crate::display::{self, Priority, DISPLAY_CONTROL}; use crate::fixnum::Vector2D; -use crate::memory_mapped::{MemoryMapped, MemoryMapped1DArray}; +use crate::memory_mapped::MemoryMapped; use super::{Tile, TileSetReference, TileSetting, VRamManager}; @@ -102,18 +102,12 @@ impl RegularMap { let screenblock_memory = self.screenblock_memory(); - let scroll_pos = self.get_scroll_pos(); - let start_x = scroll_pos.x / 8; - let end_x = div_ceil(scroll_pos.x as i32 + display::WIDTH, 8) as u16 + 1; - - let start_y = scroll_pos.y / 8; - let end_y = div_ceil(scroll_pos.y as i32 + display::HEIGHT, 8) as u16 + 1; - - for y in start_y..end_y { - for x in start_x..end_x { - let id = y.rem_euclid(32) * 32 + x.rem_euclid(32); - screenblock_memory.set(id as usize, self.tiles[id as usize].0); - } + unsafe { + dma_copy( + self.tiles.as_ptr() as *const u16, + screenblock_memory, + 32 * 32, + ); } self.tiles_dirty = false; @@ -140,8 +134,8 @@ impl RegularMap { unsafe { MemoryMapped::new(0x0400_0012 + 4 * self.background_id as usize) } } - const fn screenblock_memory(&self) -> MemoryMapped1DArray { - unsafe { MemoryMapped1DArray::new(0x0600_0000 + 0x1000 * self.screenblock as usize / 2) } + const fn screenblock_memory(&self) -> *mut u16 { + (0x0600_0000 + 0x1000 * self.screenblock as usize / 2) as *mut u16 } } @@ -196,3 +190,28 @@ fn div_ceil(x: i32, y: i32) -> i32 { x / y } } + +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)) }; + +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)); +} From 068bc95fe7d6aba43135c8f5b874d318e95a2c16 Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Sat, 5 Mar 2022 19:35:19 +0000 Subject: [PATCH 70/76] Fix warning with non-constant string --- agb/src/display/tiled/vram_manager.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agb/src/display/tiled/vram_manager.rs b/agb/src/display/tiled/vram_manager.rs index 1da86d9f..6253da75 100644 --- a/agb/src/display/tiled/vram_manager.rs +++ b/agb/src/display/tiled/vram_manager.rs @@ -11,7 +11,7 @@ const PALETTE_BACKGROUND: MemoryMapped1DArray = #[cfg(debug_assertions)] unsafe fn debug_unreachable_unchecked(message: &'static str) -> ! { - unreachable!(message); + unreachable!("{}", message); } #[cfg(not(debug_assertions))] From 09f6736497fa26a3fe19e394e3225ed2bd6f74a9 Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Sat, 5 Mar 2022 19:43:48 +0000 Subject: [PATCH 71/76] Extract dma_copy to its own module --- agb/src/display/tiled/map.rs | 26 +------------------------- agb/src/display/tiled/vram_manager.rs | 26 +------------------------- agb/src/dma.rs | 26 ++++++++++++++++++++++++++ agb/src/lib.rs | 1 + 4 files changed, 29 insertions(+), 50 deletions(-) create mode 100644 agb/src/dma.rs diff --git a/agb/src/display/tiled/map.rs b/agb/src/display/tiled/map.rs index ce32312b..c38bc293 100644 --- a/agb/src/display/tiled/map.rs +++ b/agb/src/display/tiled/map.rs @@ -3,6 +3,7 @@ use core::ops::{Deref, DerefMut}; use crate::bitarray::Bitarray; use crate::display::{self, Priority, DISPLAY_CONTROL}; +use crate::dma::dma_copy; use crate::fixnum::Vector2D; use crate::memory_mapped::MemoryMapped; @@ -190,28 +191,3 @@ fn div_ceil(x: i32, y: i32) -> i32 { x / y } } - -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)) }; - -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/display/tiled/vram_manager.rs b/agb/src/display/tiled/vram_manager.rs index 6253da75..0442751a 100644 --- a/agb/src/display/tiled/vram_manager.rs +++ b/agb/src/display/tiled/vram_manager.rs @@ -3,6 +3,7 @@ use alloc::vec::Vec; use crate::{ display::palette16, + dma::dma_copy, memory_mapped::{MemoryMapped, MemoryMapped1DArray}, }; @@ -291,28 +292,3 @@ impl<'a> VRamManager<'a> { } } } - -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)) }; - -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/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 db1e0c50..86a54434 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 From d97ceca6479aabfacbbb954316bdd70c339edea6 Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Sat, 5 Mar 2022 19:44:21 +0000 Subject: [PATCH 72/76] Remove unused function --- agb/src/display/tiled/map.rs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/agb/src/display/tiled/map.rs b/agb/src/display/tiled/map.rs index c38bc293..81893d75 100644 --- a/agb/src/display/tiled/map.rs +++ b/agb/src/display/tiled/map.rs @@ -181,13 +181,3 @@ impl<'a, T> Drop for MapLoan<'a, T> { .set(self.background_id as usize, false); } } - -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 - } -} From ba1c2e3eaf699c1463564df2323c81b341a7fe0e Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Sat, 5 Mar 2022 19:44:37 +0000 Subject: [PATCH 73/76] Remove unused import --- agb/src/display/tiled/map.rs | 2 +- agb/src/display/tiled/vram_manager.rs | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/agb/src/display/tiled/map.rs b/agb/src/display/tiled/map.rs index 81893d75..46d14c86 100644 --- a/agb/src/display/tiled/map.rs +++ b/agb/src/display/tiled/map.rs @@ -2,7 +2,7 @@ use core::cell::RefCell; use core::ops::{Deref, DerefMut}; use crate::bitarray::Bitarray; -use crate::display::{self, Priority, DISPLAY_CONTROL}; +use crate::display::{Priority, DISPLAY_CONTROL}; use crate::dma::dma_copy; use crate::fixnum::Vector2D; use crate::memory_mapped::MemoryMapped; diff --git a/agb/src/display/tiled/vram_manager.rs b/agb/src/display/tiled/vram_manager.rs index 0442751a..84d6ac17 100644 --- a/agb/src/display/tiled/vram_manager.rs +++ b/agb/src/display/tiled/vram_manager.rs @@ -1,11 +1,7 @@ use alloc::vec; use alloc::vec::Vec; -use crate::{ - display::palette16, - dma::dma_copy, - memory_mapped::{MemoryMapped, MemoryMapped1DArray}, -}; +use crate::{display::palette16, dma::dma_copy, memory_mapped::MemoryMapped1DArray}; const PALETTE_BACKGROUND: MemoryMapped1DArray = unsafe { MemoryMapped1DArray::new(0x0500_0000) }; From 803527be23ff881a492159d920892a728e38070d Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Sat, 5 Mar 2022 19:52:57 +0000 Subject: [PATCH 74/76] Fix warning in release mode --- agb/src/display/tiled/vram_manager.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agb/src/display/tiled/vram_manager.rs b/agb/src/display/tiled/vram_manager.rs index 84d6ac17..7ce6ba68 100644 --- a/agb/src/display/tiled/vram_manager.rs +++ b/agb/src/display/tiled/vram_manager.rs @@ -12,7 +12,7 @@ unsafe fn debug_unreachable_unchecked(message: &'static str) -> ! { } #[cfg(not(debug_assertions))] -const unsafe fn debug_unreachable_unchecked(message: &'static str) -> ! { +const unsafe fn debug_unreachable_unchecked(_message: &'static str) -> ! { use core::hint::unreachable_unchecked; unreachable_unchecked(); From f01d3bff36adf538360e58b018fcf763592a2fa5 Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Mon, 7 Mar 2022 22:56:05 +0000 Subject: [PATCH 75/76] Use saturating_sub rather than regular subtraction --- examples/the-purple-night/src/main.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/the-purple-night/src/main.rs b/examples/the-purple-night/src/main.rs index 59640239..047eb541 100644 --- a/examples/the-purple-night/src/main.rs +++ b/examples/the-purple-night/src/main.rs @@ -393,10 +393,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 { From 87c5d6ca6ab1c9d14d7b43f2a202dd30e4e25b82 Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Fri, 11 Mar 2022 21:49:23 +0000 Subject: [PATCH 76/76] Use the new allocator --- agb/src/display/tiled/map.rs | 4 +- agb/src/display/tiled/mod.rs | 2 +- agb/src/display/tiled/vram_manager.rs | 146 ++++++++---------- .../the-hat-chooses-the-wizard/src/main.rs | 2 +- 4 files changed, 73 insertions(+), 81 deletions(-) diff --git a/agb/src/display/tiled/map.rs b/agb/src/display/tiled/map.rs index 46d14c86..f86f8049 100644 --- a/agb/src/display/tiled/map.rs +++ b/agb/src/display/tiled/map.rs @@ -72,7 +72,9 @@ impl RegularMap { pub fn clear(&mut self, vram: &mut VRamManager) { for tile in self.tiles.iter_mut() { - vram.remove_tile(tile.tile_index()); + if *tile != Tile::default() { + vram.remove_tile(tile.tile_index()); + } *tile = Tile::default(); } diff --git a/agb/src/display/tiled/mod.rs b/agb/src/display/tiled/mod.rs index 72c6c8ef..0e2e042a 100644 --- a/agb/src/display/tiled/mod.rs +++ b/agb/src/display/tiled/mod.rs @@ -18,7 +18,7 @@ impl Tile { } fn tile_index(self) -> TileIndex { - TileIndex::new(self.0 & ((1 << 10) - 1)) + TileIndex::new(self.0 as usize & ((1 << 10) - 1)) } } diff --git a/agb/src/display/tiled/vram_manager.rs b/agb/src/display/tiled/vram_manager.rs index 7ce6ba68..76be9999 100644 --- a/agb/src/display/tiled/vram_manager.rs +++ b/agb/src/display/tiled/vram_manager.rs @@ -1,11 +1,29 @@ +use core::{alloc::Layout, ptr::NonNull}; + use alloc::vec; use alloc::vec::Vec; -use crate::{display::palette16, dma::dma_copy, memory_mapped::MemoryMapped1DArray}; +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); @@ -63,8 +81,8 @@ impl TileSetReference { pub struct TileIndex(u16); impl TileIndex { - pub(crate) const fn new(index: u16) -> Self { - Self(index) + pub(crate) const fn new(index: usize) -> Self { + Self(index as u16) } pub(crate) const fn index(&self) -> u16 { @@ -73,31 +91,7 @@ impl TileIndex { } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -struct TileReference(u16, u16); - -enum VRamState { - ReferenceCounted(u16, TileReference), - Free(u16), -} - -impl VRamState { - fn increase_reference(&mut self) { - if let VRamState::ReferenceCounted(count, _) = self { - *count += 1; - } else { - unsafe { debug_unreachable_unchecked("Cannot increase reference count of free item") }; - } - } - - fn decrease_reference(&mut self) -> (u16, TileReference) { - if let VRamState::ReferenceCounted(count, tile_ref) = self { - *count -= 1; - (*count, *tile_ref) - } else { - unsafe { debug_unreachable_unchecked("Cannot decrease reference count of free item") }; - } - } -} +struct TileReference(NonNull); enum ArenaStorageItem { EndOfFreeList, @@ -110,13 +104,10 @@ pub struct VRamManager<'a> { generation: u16, free_pointer: Option, - tile_set_to_vram: Vec>, - references: Vec, - vram_free_pointer: Option, + tile_set_to_vram: Vec>>, + reference_counts: Vec<(u16, Option<(TileSetReference, u16)>)>, } -const END_OF_FREE_LIST_MARKER: u16 = u16::MAX; - impl<'a> VRamManager<'a> { pub fn new() -> Self { Self { @@ -125,8 +116,7 @@ impl<'a> VRamManager<'a> { free_pointer: None, tile_set_to_vram: Default::default(), - references: vec![VRamState::Free(0)], - vram_free_pointer: None, + reference_counts: Default::default(), } } @@ -184,37 +174,27 @@ impl<'a> VRamManager<'a> { } } + 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 tile_ref = TileReference(tile_set_ref.id, tile); let reference = self.tile_set_to_vram[tile_set_ref.id as usize][tile as usize]; - if reference != Default::default() { - if reference.1 == tile_set_ref.generation { - self.references[reference.0 as usize].increase_reference(); - return TileIndex(reference.0 as u16); - } else { - panic!("Tileset unloaded but not cleared from vram"); - } + + if let Some(reference) = reference { + let index = Self::index_from_reference(reference); + self.reference_counts[index].0 += 1; + return TileIndex::new(index); } - let index_to_copy_into = if let Some(ptr) = self.vram_free_pointer.take() { - match self.references[ptr] { - VRamState::Free(next_free) => { - if next_free != END_OF_FREE_LIST_MARKER { - self.vram_free_pointer = Some(next_free as usize); - } - } - VRamState::ReferenceCounted(_, _) => unsafe { - debug_unreachable_unchecked("Free pointer must point to free item") - }, - } - - self.references[ptr] = VRamState::ReferenceCounted(1, tile_ref); - ptr - } else { - self.references - .push(VRamState::ReferenceCounted(1, tile_ref)); - self.references.len() - 1 - }; + 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] @@ -232,40 +212,50 @@ impl<'a> VRamManager<'a> { let tile_size_in_half_words = TileFormat::FourBpp.tile_size() / 2; - const TILE_BACKGROUND_ADDRESS: usize = 0x0600_0000; unsafe { dma_copy( tile_slice.as_ptr() as *const u16, - (TILE_BACKGROUND_ADDRESS as *mut u16) - .add(index_to_copy_into * tile_size_in_half_words), + new_reference.as_ptr() as *mut u16, tile_size_in_half_words, ); } - self.tile_set_to_vram[tile_set_ref.id as usize][tile as usize] = - (index_to_copy_into as u16, tile_set_ref.generation); + let tile_reference = TileReference(new_reference); - TileIndex(index_to_copy_into as u16) + 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.0 as usize; + let index = tile_index.index() as usize; + assert!( + self.reference_counts[index].0 > 0, + "Trying to decrease the reference count of {} below 0", + index + ); - let (new_count, tile_ref) = self.references[index].decrease_reference(); + self.reference_counts[index].0 -= 1; - if new_count != 0 { + if self.reference_counts[index].0 != 0 { return; } - if let Some(ptr) = self.vram_free_pointer { - self.references[index] = VRamState::Free(ptr as u16); - } else { - self.references[index] = VRamState::Free(END_OF_FREE_LIST_MARKER); + let tile_reference = Self::reference_from_index(tile_index); + unsafe { + TILE_ALLOCATOR.dealloc(tile_reference.0.cast().as_ptr(), TILE_LAYOUT); } - self.tile_set_to_vram[tile_ref.0 as usize][tile_ref.1 as usize] = Default::default(); - - self.vram_free_pointer = Some(index); + 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. diff --git a/examples/the-hat-chooses-the-wizard/src/main.rs b/examples/the-hat-chooses-the-wizard/src/main.rs index 158fc66b..9c299bdc 100644 --- a/examples/the-hat-chooses-the-wizard/src/main.rs +++ b/examples/the-hat-chooses-the-wizard/src/main.rs @@ -825,7 +825,7 @@ fn main(mut agb: agb::Gba) -> ! { let mut music_box = sfx::MusicBox::new(); let vblank = agb::interrupt::VBlank::get(); - let mut current_level = 11; + let mut current_level = 0; loop { if current_level == map_tiles::LEVELS.len() as u32 {