From ee576597c2c974a23960627f40150629e49f2709 Mon Sep 17 00:00:00 2001 From: lifning <> Date: Sun, 7 Aug 2022 01:49:32 -0700 Subject: [PATCH] Add rudimentary affine background layer support. --- agb-fixnum/src/lib.rs | 10 + agb-image-converter/src/lib.rs | 67 ++-- agb-image-converter/src/rust_generator.rs | 33 +- agb/examples/animated_background.rs | 2 +- agb/examples/chicken.rs | 2 +- agb/examples/dynamic_tiles.rs | 2 +- agb/examples/stereo_sound.rs | 2 +- agb/examples/text_render.rs | 2 +- agb/src/display/example_logo.rs | 3 +- agb/src/display/font.rs | 1 + agb/src/display/object.rs | 35 +- .../display/tiled/infinite_scrolled_map.rs | 5 +- agb/src/display/tiled/map.rs | 363 ++++++++++++++---- agb/src/display/tiled/mod.rs | 270 +++++++++++-- agb/src/display/tiled/tiled0.rs | 58 +-- agb/src/display/tiled/tiled1.rs | 47 +++ agb/src/display/tiled/tiled2.rs | 53 +++ agb/src/display/tiled/vram_manager.rs | 131 +++++-- agb/src/display/video.rs | 12 +- agb/src/lib.rs | 5 +- agb/src/memory_mapped.rs | 4 +- agb/src/syscall.rs | 145 ++++--- examples/hyperspace-roll/build.rs | 2 +- examples/hyperspace-roll/src/background.rs | 2 +- examples/hyperspace-roll/src/battle.rs | 2 +- examples/hyperspace-roll/src/customise.rs | 2 +- examples/hyperspace-roll/src/main.rs | 2 +- .../src/level_display.rs | 2 +- .../the-hat-chooses-the-wizard/src/main.rs | 2 +- .../src/splash_screen.rs | 2 +- 30 files changed, 940 insertions(+), 328 deletions(-) create mode 100644 agb/src/display/tiled/tiled1.rs create mode 100644 agb/src/display/tiled/tiled2.rs diff --git a/agb-fixnum/src/lib.rs b/agb-fixnum/src/lib.rs index 47036f2..b8b8746 100644 --- a/agb-fixnum/src/lib.rs +++ b/agb-fixnum/src/lib.rs @@ -566,6 +566,7 @@ impl Debug for Num { /// A vector of two points: (x, y) represened by integers or fixed point numbers #[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[repr(C)] pub struct Vector2D { /// The x coordinate pub x: T, @@ -1211,6 +1212,15 @@ mod tests { } } + #[test] + fn test_only_frac_bits() { + let quarter: Num = num!(0.25); + let neg_quarter: Num = num!(-0.25); + + assert_eq!(quarter + quarter, num!(0.5)); + assert_eq!(neg_quarter + neg_quarter, num!(-0.5)); + } + #[test] fn test_vector_multiplication_and_division() { let a: Vector2D = (1, 2).into(); diff --git a/agb-image-converter/src/lib.rs b/agb-image-converter/src/lib.rs index 1cd8356..2f41c57 100644 --- a/agb-image-converter/src/lib.rs +++ b/agb-image-converter/src/lib.rs @@ -132,7 +132,7 @@ pub fn include_aseprite_inner(input: TokenStream) -> TokenStream { let optimised_results = optimiser.optimise_palettes(); - let (palette_data, tile_data, assignments) = palete_tile_data(&optimised_results, &images); + let (palette_data, tile_data, assignments) = palette_tile_data(&optimised_results, &images); let palette_data = palette_data.iter().map(|colours| { quote! { @@ -273,7 +273,7 @@ fn add_to_optimiser( } } -fn palete_tile_data( +fn palette_tile_data( optimiser: &Palette16OptimisationResults, images: &[Image], ) -> (Vec>, Vec, Vec) { @@ -293,41 +293,54 @@ fn palete_tile_data( .collect(); let mut tile_data = Vec::new(); + let tile_size = TileSize::Tile8; for image in images { - let tile_size = 8; - let tiles_x = image.width / tile_size; - let tiles_y = image.height / tile_size; + add_image_to_tile_data(&mut tile_data, image, tile_size, &optimiser) + } - for y in 0..tiles_y { - for x in 0..tiles_x { - let palette_index = optimiser.assignments[y * tiles_x + x]; - let palette = &optimiser.optimised_palettes[palette_index]; + let tile_data = collapse_to_4bpp(&tile_data); - for inner_y in 0..tile_size / 8 { - for inner_x in 0..tile_size / 8 { - for j in inner_y * 8..inner_y * 8 + 8 { - for i in inner_x * 8..inner_x * 8 + 8 { - let colour = image.colour(x * tile_size + i, y * tile_size + j); - tile_data.push( - palette.colour_index(colour, optimiser.transparent_colour), - ); - } + let assignments = optimiser.assignments.clone(); + + (palette_data, tile_data, assignments) +} + +fn collapse_to_4bpp(tile_data: &[u8]) -> Vec { + tile_data + .chunks(2) + .map(|chunk| chunk[0] | (chunk[1] << 4)) + .collect() +} + +fn add_image_to_tile_data( + tile_data: &mut Vec, + image: &Image, + tile_size: TileSize, + optimiser: &&Palette16OptimisationResults, +) { + let tile_size = tile_size.to_size(); + let tiles_x = image.width / tile_size; + let tiles_y = image.height / tile_size; + + for y in 0..tiles_y { + for x in 0..tiles_x { + let palette_index = optimiser.assignments[y * tiles_x + x]; + let palette = &optimiser.optimised_palettes[palette_index]; + + for inner_y in 0..tile_size / 8 { + for inner_x in 0..tile_size / 8 { + for j in inner_y * 8..inner_y * 8 + 8 { + for i in inner_x * 8..inner_x * 8 + 8 { + let colour = image.colour(x * tile_size + i, y * tile_size + j); + tile_data + .push(palette.colour_index(colour, optimiser.transparent_colour)); } } } } } } - - let tile_data = tile_data - .chunks(2) - .map(|chunk| chunk[0] | (chunk[1] << 4)) - .collect(); - - let assignments = optimiser.assignments.clone(); - - (palette_data, tile_data, assignments) } fn flatten_group(expr: &Expr) -> &Expr { diff --git a/agb-image-converter/src/rust_generator.rs b/agb-image-converter/src/rust_generator.rs index f289ffe..7f04b7e 100644 --- a/agb-image-converter/src/rust_generator.rs +++ b/agb-image-converter/src/rust_generator.rs @@ -1,5 +1,5 @@ use crate::palette16::Palette16OptimisationResults; -use crate::TileSize; +use crate::{add_image_to_tile_data, collapse_to_4bpp, TileSize}; use crate::{image_loader::Image, ByteString}; use proc_macro2::TokenStream; @@ -34,36 +34,11 @@ pub(crate) fn generate_code( } }); - let tile_size = tile_size.to_size(); + let mut tile_data = Vec::new(); - let tiles_x = image.width / tile_size; - let tiles_y = image.height / tile_size; + add_image_to_tile_data(&mut tile_data, image, tile_size, &results); - let mut tile_data = vec![]; - - for y in 0..tiles_y { - for x in 0..tiles_x { - let palette_index = results.assignments[y * tiles_x + x]; - let palette = &results.optimised_palettes[palette_index]; - - for inner_y in 0..tile_size / 8 { - for inner_x in 0..tile_size / 8 { - for j in inner_y * 8..inner_y * 8 + 8 { - for i in inner_x * 8..inner_x * 8 + 8 { - let colour = image.colour(x * tile_size + i, y * tile_size + j); - tile_data - .push(palette.colour_index(colour, results.transparent_colour)); - } - } - } - } - } - } - - let tile_data: Vec<_> = tile_data - .chunks(2) - .map(|chunk| (chunk[1] << 4) | chunk[0]) - .collect(); + let tile_data = collapse_to_4bpp(&tile_data); let data = ByteString(&tile_data); diff --git a/agb/examples/animated_background.rs b/agb/examples/animated_background.rs index d20213d..97ccf3c 100644 --- a/agb/examples/animated_background.rs +++ b/agb/examples/animated_background.rs @@ -3,7 +3,7 @@ use agb::{ display::{ - tiled::{RegularBackgroundSize, TileFormat, TileSet, TileSetting}, + tiled::{RegularBackgroundSize, TileFormat, TileSet, TileSetting, TiledMap}, Priority, }, include_gfx, diff --git a/agb/examples/chicken.rs b/agb/examples/chicken.rs index 0253688..72bb5e6 100644 --- a/agb/examples/chicken.rs +++ b/agb/examples/chicken.rs @@ -2,7 +2,7 @@ #![no_main] use agb::{ - display::tiled::{TileFormat, TileSet, TileSetting}, + display::tiled::{TileFormat, TileSet, TileSetting, TiledMap}, display::{ object::{Object, ObjectController, Size, Sprite}, palette16::Palette16, diff --git a/agb/examples/dynamic_tiles.rs b/agb/examples/dynamic_tiles.rs index 53b013a..171e3e0 100644 --- a/agb/examples/dynamic_tiles.rs +++ b/agb/examples/dynamic_tiles.rs @@ -3,7 +3,7 @@ use agb::display::{ palette16::Palette16, - tiled::{RegularBackgroundSize, TileSetting}, + tiled::{RegularBackgroundSize, TileSetting, TiledMap}, Priority, }; diff --git a/agb/examples/stereo_sound.rs b/agb/examples/stereo_sound.rs index d813a23..ca293cf 100644 --- a/agb/examples/stereo_sound.rs +++ b/agb/examples/stereo_sound.rs @@ -3,7 +3,7 @@ use agb::{ display::{ - tiled::{RegularBackgroundSize, RegularMap, TileSetting, VRamManager}, + tiled::{RegularBackgroundSize, RegularMap, TileSetting, TiledMap, VRamManager}, Font, Priority, }, include_font, include_wav, diff --git a/agb/examples/text_render.rs b/agb/examples/text_render.rs index fa398a1..79470d3 100644 --- a/agb/examples/text_render.rs +++ b/agb/examples/text_render.rs @@ -3,7 +3,7 @@ use agb::{ display::{ - tiled::{RegularBackgroundSize, TileSetting}, + tiled::{RegularBackgroundSize, TileSetting, TiledMap}, Font, Priority, }, include_font, diff --git a/agb/src/display/example_logo.rs b/agb/src/display/example_logo.rs index bec4df9..b21df02 100644 --- a/agb/src/display/example_logo.rs +++ b/agb/src/display/example_logo.rs @@ -1,4 +1,4 @@ -use super::tiled::{RegularMap, TileFormat, TileSet, TileSetting, VRamManager}; +use super::tiled::{RegularMap, TileFormat, TileSet, TileSetting, TiledMap, VRamManager}; crate::include_gfx!("gfx/agb_logo.toml"); @@ -21,6 +21,7 @@ pub fn display_logo(map: &mut RegularMap, vram: &mut VRamManager) { map.commit(vram); map.show(); } + #[cfg(test)] mod tests { use crate::display::{tiled::RegularBackgroundSize, Priority}; diff --git a/agb/src/display/font.rs b/agb/src/display/font.rs index cb589ec..ac10539 100644 --- a/agb/src/display/font.rs +++ b/agb/src/display/font.rs @@ -210,6 +210,7 @@ impl<'a> Drop for TextRenderer<'a> { #[cfg(test)] mod tests { use super::*; + use crate::display::tiled::TiledMap; const FONT: Font = crate::include_font!("examples/font/yoster.ttf", 12); #[test_case] diff --git a/agb/src/display/object.rs b/agb/src/display/object.rs index 3888a8a..744be03 100644 --- a/agb/src/display/object.rs +++ b/agb/src/display/object.rs @@ -18,7 +18,7 @@ use super::{Priority, DISPLAY_CONTROL}; use crate::agb_alloc::block_allocator::BlockAllocator; use crate::agb_alloc::bump_allocator::StartEnd; use crate::dma; -use crate::fixnum::Vector2D; +use crate::fixnum::{Num, Vector2D}; use crate::hash_map::HashMap; use attributes::*; @@ -627,7 +627,7 @@ impl ObjectController { unsafe { (OBJECT_ATTRIBUTE_MEMORY as *mut u16) - .add((i as usize) * 4) + .add(i * 4) .write_volatile(HIDDEN_VALUE); } @@ -776,7 +776,7 @@ impl ObjectController { attrs.a2.set_tile_index(sprite.sprite_location); let shape_size = sprite.id.sprite().size.shape_size(); - attrs.a2.set_palete_bank(sprite.palette_location as u8); + attrs.a2.set_palette_bank(sprite.palette_location as u8); attrs.a0.set_shape(shape_size.0); attrs.a1a.set_size(shape_size.1); attrs.a1s.set_size(shape_size.1); @@ -877,7 +877,7 @@ impl<'a> Object<'a> { object_inner .attrs .a2 - .set_palete_bank(sprite.palette_location as u8); + .set_palette_bank(sprite.palette_location as u8); object_inner.attrs.a0.set_shape(shape_size.0); object_inner.attrs.a1a.set_size(shape_size.1); object_inner.attrs.a1s.set_size(shape_size.1); @@ -1193,6 +1193,31 @@ enum ColourMode { Eight, } +/// The parameters used for the PPU's affine transformation function +/// that can apply to objects and background layers in modes 1 and 2. +/// This can be obtained from X/Y scale and rotation angle with +/// [`agb::syscall::affine_matrix`]. +#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)] +#[repr(C)] +pub struct AffineMatrixAttributes { + /// Adjustment made to *X* coordinate when drawing *horizontal* lines. + /// Also known as "dx". + /// Typically computed as `x_scale * cos(angle)`. + pub p_a: Num, + /// Adjustment made to *X* coordinate along *vertical* lines. + /// Also known as "dmx". + /// Typically computed as `y_scale * sin(angle)`. + pub p_b: Num, + /// Adjustment made to *Y* coordinate along *horizontal* lines. + /// Also known as "dy". + /// Typically computed as `-x_scale * sin(angle)`. + pub p_c: Num, + /// Adjustment made to *Y* coordinate along *vertical* lines. + /// Also known as "dmy". + /// Typically computed as `y_scale * cos(angle)`. + pub p_d: Num, +} + // this mod is not public, so the internal parts don't need documenting. #[allow(dead_code)] mod attributes { @@ -1232,7 +1257,7 @@ mod attributes { pub(super) struct ObjectAttribute2 { pub tile_index: B10, pub priority: Priority, - pub palete_bank: B4, + pub palette_bank: B4, } } diff --git a/agb/src/display/tiled/infinite_scrolled_map.rs b/agb/src/display/tiled/infinite_scrolled_map.rs index 4021494..8497802 100644 --- a/agb/src/display/tiled/infinite_scrolled_map.rs +++ b/agb/src/display/tiled/infinite_scrolled_map.rs @@ -1,6 +1,9 @@ use alloc::boxed::Box; -use super::{BackgroundID, MapLoan, RegularMap, TileSet, TileSetting, VRamManager}; +use super::{ + BackgroundID, BackgroundSizePrivate, MapLoan, RegularMap, TileSet, TileSetting, TiledMap, + VRamManager, +}; use crate::{ display, diff --git a/agb/src/display/tiled/map.rs b/agb/src/display/tiled/map.rs index b245ab2..0b5e513 100644 --- a/agb/src/display/tiled/map.rs +++ b/agb/src/display/tiled/map.rs @@ -2,31 +2,184 @@ use core::cell::RefCell; use core::ops::{Deref, DerefMut}; use crate::bitarray::Bitarray; -use crate::display::{Priority, DISPLAY_CONTROL}; +use crate::display::{object::AffineMatrixAttributes, Priority, DISPLAY_CONTROL}; use crate::dma::dma_copy16; -use crate::fixnum::Vector2D; +use crate::fixnum::{Num, Number, Vector2D}; use crate::memory_mapped::MemoryMapped; -use super::{BackgroundID, RegularBackgroundSize, Tile, TileSet, TileSetting, VRamManager}; +use super::{ + AffineBackgroundSize, BackgroundID, BackgroundSize, BackgroundSizePrivate, + RegularBackgroundSize, Tile, TileFormat, TileIndex, TileSet, TileSetting, VRamManager, +}; +use crate::syscall::BgAffineSetData; use alloc::{vec, vec::Vec}; +pub(super) trait TiledMapPrivateConst: TiledMapTypes { + type TileType: Into + Copy + Default + Eq + PartialEq; + type AffineMatrix; + fn x_scroll(&self) -> Self::Position; + fn y_scroll(&self) -> Self::Position; + fn affine_matrix(&self) -> Self::AffineMatrix; + fn background_id(&self) -> usize; + fn screenblock(&self) -> usize; + fn priority(&self) -> Priority; + fn map_size(&self) -> Self::Size; + fn bg_x(&self) -> MemoryMapped; + fn bg_y(&self) -> MemoryMapped; + fn bg_affine_matrix(&self) -> MemoryMapped; + fn bg_control_register(&self) -> MemoryMapped { + unsafe { MemoryMapped::new(0x0400_0008 + 2 * self.background_id()) } + } + fn screenblock_memory(&self) -> *mut u16 { + (0x0600_0000 + 0x1000 * self.screenblock() as usize / 2) as *mut u16 + } +} + +trait TiledMapPrivate: TiledMapPrivateConst { + fn tiles_mut(&mut self) -> &mut [Self::TileType]; + fn tiles_dirty(&mut self) -> &mut bool; + fn x_scroll_mut(&mut self) -> &mut Self::Position; + fn y_scroll_mut(&mut self) -> &mut Self::Position; +} + +pub trait TiledMapTypes { + type Position: Number; + type Size: BackgroundSize + Copy; +} + +pub trait TiledMap: TiledMapTypes { + fn clear(&mut self, vram: &mut VRamManager); + fn show(&mut self); + fn hide(&mut self); + fn commit(&mut self, vram: &mut VRamManager); + fn size(&self) -> Self::Size; + + #[must_use] + fn scroll_pos(&self) -> Vector2D; + fn set_scroll_pos(&mut self, pos: Vector2D); +} + +impl TiledMap for T +where + T: TiledMapPrivateConst + TiledMapPrivate + TiledMapTypes, + T::Size: BackgroundSizePrivate, +{ + fn clear(&mut self, vram: &mut VRamManager) { + for tile in self.tiles_mut() { + if *tile != Default::default() { + vram.remove_tile((*tile).into()); + } + + *tile = Default::default(); + } + } + + fn show(&mut self) { + let mode = DISPLAY_CONTROL.get(); + let new_mode = mode | (1 << (self.background_id() + 0x08)) as u16; + DISPLAY_CONTROL.set(new_mode); + } + + fn hide(&mut self) { + let mode = DISPLAY_CONTROL.get(); + let new_mode = mode & !(1 << (self.background_id() + 0x08)) as u16; + DISPLAY_CONTROL.set(new_mode); + } + + fn commit(&mut self, vram: &mut VRamManager) { + let new_bg_control_value = (self.priority() as u16) + | ((self.screenblock() as u16) << 8) + | (self.map_size().size_flag() << 14); + + self.bg_control_register().set(new_bg_control_value); + self.bg_x().set(self.x_scroll()); + self.bg_y().set(self.y_scroll()); + self.bg_affine_matrix().set(self.affine_matrix()); + + let screenblock_memory = self.screenblock_memory(); + let x: TileIndex = unsafe { *self.tiles_mut().get_unchecked(0) }.into(); + let x = x.format().tile_size() / TileFormat::FourBpp.tile_size(); + if *self.tiles_dirty() { + unsafe { + dma_copy16( + self.tiles_mut().as_ptr() as *const u16, + screenblock_memory, + self.map_size().num_tiles() / x, + ); + } + } + + vram.gc(); + + *self.tiles_dirty() = false; + } + + fn size(&self) -> Self::Size { + self.map_size() + } + + #[must_use] + fn scroll_pos(&self) -> Vector2D { + (self.x_scroll(), self.y_scroll()).into() + } + + fn set_scroll_pos(&mut self, pos: Vector2D) { + *self.x_scroll_mut() = pos.x; + *self.y_scroll_mut() = pos.y; + } +} + pub struct RegularMap { background_id: u8, - screenblock: u8, + priority: Priority, + size: RegularBackgroundSize, + x_scroll: u16, y_scroll: u16, - priority: Priority, tiles: Vec, tiles_dirty: bool, - - size: RegularBackgroundSize, } pub const TRANSPARENT_TILE_INDEX: u16 = (1 << 10) - 1; +#[rustfmt::skip] +impl TiledMapPrivate for RegularMap { + fn tiles_mut(&mut self) -> &mut [Self::TileType] { &mut self.tiles } + fn tiles_dirty(&mut self) -> &mut bool { &mut self.tiles_dirty } + fn x_scroll_mut(&mut self) -> &mut Self::Position { &mut self.x_scroll } + fn y_scroll_mut(&mut self) -> &mut Self::Position { &mut self.y_scroll } +} + +impl TiledMapTypes for RegularMap { + type Position = u16; + type Size = RegularBackgroundSize; +} + +#[rustfmt::skip] +impl const TiledMapPrivateConst for RegularMap { + type TileType = Tile; + type AffineMatrix = (); + fn x_scroll(&self) -> Self::Position { self.x_scroll } + fn y_scroll(&self) -> Self::Position { self.y_scroll } + fn affine_matrix(&self) -> Self::AffineMatrix {} + fn background_id(&self) -> usize { self.background_id as usize } + fn screenblock(&self) -> usize { self.screenblock as usize } + fn priority(&self) -> Priority { self.priority } + fn map_size(&self) -> Self::Size { self.size } + fn bg_x(&self) -> MemoryMapped { + unsafe { MemoryMapped::new(0x0400_0010 + 4 * self.background_id as usize) } + } + fn bg_y(&self) -> MemoryMapped { + unsafe { MemoryMapped::new(0x0400_0012 + 4 * self.background_id as usize) } + } + fn bg_affine_matrix(&self) -> MemoryMapped { + unsafe { MemoryMapped::new(0) } + } +} + impl RegularMap { pub(crate) fn new( background_id: u8, @@ -36,16 +189,15 @@ impl RegularMap { ) -> Self { Self { background_id, - screenblock, + priority, + size, + x_scroll: 0, y_scroll: 0, - priority, tiles: vec![Default::default(); size.num_tiles()], tiles_dirty: true, - - size, } } @@ -56,11 +208,11 @@ impl RegularMap { tileset: &TileSet<'_>, tile_setting: TileSetting, ) { - let pos = self.size.gba_offset(pos); + let pos = self.map_size().gba_offset(pos); - let old_tile = self.tiles[pos]; + let old_tile = self.tiles_mut()[pos]; if old_tile != Tile::default() { - vram.remove_tile(old_tile.tile_index()); + vram.remove_tile(old_tile.into()); } let tile_index = tile_setting.index(); @@ -77,86 +229,129 @@ impl RegularMap { return; } - self.tiles[pos] = new_tile; - self.tiles_dirty = true; + self.tiles_mut()[pos] = new_tile; + *self.tiles_dirty() = true; } +} - pub fn clear(&mut self, vram: &mut VRamManager) { - for tile in self.tiles.iter_mut() { - if *tile != Tile::default() { - vram.remove_tile(tile.tile_index()); - } +pub struct AffineMap { + background_id: u8, + screenblock: u8, + priority: Priority, + size: AffineBackgroundSize, - *tile = Tile::default(); + bg_center: Vector2D>, + transform: BgAffineSetData, + + tiles: Vec, + tiles_dirty: bool, +} + +#[rustfmt::skip] +impl TiledMapPrivate for AffineMap { + fn tiles_mut(&mut self) -> &mut [Self::TileType] { &mut self.tiles } + fn tiles_dirty(&mut self) -> &mut bool { &mut self.tiles_dirty } + fn x_scroll_mut(&mut self) -> &mut Self::Position { &mut self.transform.position.x } + fn y_scroll_mut(&mut self) -> &mut Self::Position { &mut self.transform.position.y } +} + +impl TiledMapTypes for AffineMap { + type Position = Num; + type Size = AffineBackgroundSize; +} + +#[rustfmt::skip] +impl const TiledMapPrivateConst for AffineMap { + type TileType = u8; + type AffineMatrix = AffineMatrixAttributes; + fn x_scroll(&self) -> Self::Position { self.transform.position.x } + fn y_scroll(&self) -> Self::Position { self.transform.position.y } + fn affine_matrix(&self) -> Self::AffineMatrix { self.transform.matrix } + fn background_id(&self) -> usize { self.background_id as usize } + fn screenblock(&self) -> usize { self.screenblock as usize } + fn priority(&self) -> Priority { self.priority } + fn map_size(&self) -> Self::Size { self.size } + fn bg_x(&self) -> MemoryMapped { + unsafe { MemoryMapped::new(0x0400_0008 + 0x10 * self.background_id()) } + } + fn bg_y(&self) -> MemoryMapped { + unsafe { MemoryMapped::new(0x0400_000c + 0x10 * self.background_id()) } + } + fn bg_affine_matrix(&self) -> MemoryMapped { + unsafe { MemoryMapped::new(0x0400_0000 + 0x10 * self.background_id()) } + } +} + +impl AffineMap { + pub(crate) fn new( + background_id: u8, + screenblock: u8, + priority: Priority, + size: AffineBackgroundSize, + bg_center: Vector2D>, + ) -> Self { + Self { + background_id, + screenblock, + priority, + size, + + bg_center, + transform: Default::default(), + + tiles: vec![Default::default(); size.num_tiles()], + 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 set_tile( + &mut self, + vram: &mut VRamManager, + pos: Vector2D, + tileset: &TileSet<'_>, + tile_id: u8, + ) { + let pos = self.map_size().gba_offset(pos); - 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, vram: &mut VRamManager) { - let new_bg_control_value = (self.priority as u16) - | (u16::from(self.screenblock) << 8) - | (self.size.size_flag() << 14); - - 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); - - let screenblock_memory = self.screenblock_memory(); - - if self.tiles_dirty { - unsafe { - dma_copy16( - self.tiles.as_ptr() as *const u16, - screenblock_memory, - self.size.num_tiles(), - ); - } + let old_tile = self.tiles_mut()[pos]; + if old_tile != 0 { + vram.remove_tile(old_tile.into()); } - vram.gc(); + let tile_index = tile_id as u16; - self.tiles_dirty = false; + let new_tile = if tile_index != TRANSPARENT_TILE_INDEX { + let new_tile_idx = vram.add_tile(tileset, tile_index); + new_tile_idx.raw_index() as u8 + } else { + 0 + }; + + if old_tile == new_tile { + // no need to mark as dirty if nothing changes + return; + } + + self.tiles_mut()[pos] = new_tile; + *self.tiles_dirty() = true; } - pub fn set_scroll_pos(&mut self, pos: Vector2D) { - self.x_scroll = pos.x; - self.y_scroll = pos.y; + pub fn set_transform_raw(&mut self, transform: BgAffineSetData) { + self.transform = transform; } - #[must_use] - pub fn scroll_pos(&self) -> Vector2D { - (self.x_scroll, self.y_scroll).into() - } - - pub(crate) fn size(&self) -> RegularBackgroundSize { - self.size - } - - const fn bg_control_register(&self) -> MemoryMapped { - unsafe { MemoryMapped::new(0x0400_0008 + 2 * self.background_id as usize) } - } - - const fn bg_h_offset(&self) -> MemoryMapped { - unsafe { MemoryMapped::new(0x0400_0010 + 4 * self.background_id as usize) } - } - - const fn bg_v_offset(&self) -> MemoryMapped { - unsafe { MemoryMapped::new(0x0400_0012 + 4 * self.background_id as usize) } - } - - const fn screenblock_memory(&self) -> *mut u16 { - (0x0600_0000 + 0x1000 * self.screenblock as usize / 2) as *mut u16 + pub fn set_transform( + &mut self, + display_center: Vector2D, + scale: Vector2D>, + rotation: Num, + ) { + self.set_transform_raw(crate::syscall::bg_affine_matrix( + self.bg_center, + display_center, + scale, + rotation, + )); } } @@ -165,7 +360,7 @@ pub struct MapLoan<'a, T> { background_id: u8, screenblock_id: u8, screenblock_length: u8, - regular_map_list: &'a RefCell>, + map_list: &'a RefCell>, screenblock_list: &'a RefCell>, } @@ -189,7 +384,7 @@ impl<'a, T> MapLoan<'a, T> { background_id: u8, screenblock_id: u8, screenblock_length: u8, - regular_map_list: &'a RefCell>, + map_list: &'a RefCell>, screenblock_list: &'a RefCell>, ) -> Self { MapLoan { @@ -197,7 +392,7 @@ impl<'a, T> MapLoan<'a, T> { background_id, screenblock_id, screenblock_length, - regular_map_list, + map_list, screenblock_list, } } @@ -210,7 +405,7 @@ impl<'a, T> MapLoan<'a, T> { impl<'a, T> Drop for MapLoan<'a, T> { fn drop(&mut self) { - self.regular_map_list + self.map_list .borrow_mut() .set(self.background_id as usize, false); diff --git a/agb/src/display/tiled/mod.rs b/agb/src/display/tiled/mod.rs index 529b868..aadba54 100644 --- a/agb/src/display/tiled/mod.rs +++ b/agb/src/display/tiled/mod.rs @@ -1,28 +1,76 @@ mod infinite_scrolled_map; mod map; mod tiled0; +mod tiled1; +mod tiled2; mod vram_manager; -use agb_fixnum::Vector2D; +use crate::bitarray::Bitarray; +use crate::display::Priority; +use agb_fixnum::{Num, Vector2D}; +use core::cell::RefCell; pub use infinite_scrolled_map::{InfiniteScrolledMap, PartialUpdateStatus}; -pub use map::{MapLoan, RegularMap}; +pub use map::{AffineMap, MapLoan, RegularMap, TiledMap}; pub use tiled0::Tiled0; +pub use tiled1::Tiled1; +pub use tiled2::Tiled2; pub use vram_manager::{DynamicTile, TileFormat, TileIndex, TileSet, VRamManager}; +// affine layers start at BG2 +pub(crate) const AFFINE_BG_ID_OFFSET: usize = 2; + #[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[repr(u16)] pub enum RegularBackgroundSize { - Background32x32, - Background64x32, - Background32x64, - Background64x64, + Background32x32 = 0, + Background64x32 = 1, + Background32x64 = 2, + Background64x64 = 3, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct BackgroundID(pub(crate) u8); -impl RegularBackgroundSize { +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[repr(u16)] +pub enum AffineBackgroundSize { + Background16x16 = 0, + Background32x32 = 1, + Background64x64 = 2, + Background128x128 = 3, +} + +pub trait BackgroundSize { #[must_use] - pub fn width(&self) -> u32 { + fn width(&self) -> u32; + #[must_use] + fn height(&self) -> u32; +} + +pub(super) trait BackgroundSizePrivate: BackgroundSize + Sized { + fn size_flag(self) -> u16; + fn num_tiles(&self) -> usize { + (self.width() * self.height()) as usize + } + fn num_screen_blocks(&self) -> usize; + fn gba_offset(&self, pos: Vector2D) -> usize; + fn tile_pos_x(&self, x: i32) -> u16 { + ((x as u32) & (self.width() - 1)) as u16 + } + fn tile_pos_y(&self, y: i32) -> u16 { + ((y as u32) & (self.height() - 1)) as u16 + } + fn px_offset_x(&self, x: i32) -> u16 { + ((x as u32) & (self.width() * 8 - 1)) as u16 + } + fn px_offset_y(&self, y: i32) -> u16 { + ((y as u32) & (self.height() * 8 - 1)) as u16 + } +} + +impl BackgroundSize for RegularBackgroundSize { + #[must_use] + fn width(&self) -> u32 { match self { RegularBackgroundSize::Background32x64 | RegularBackgroundSize::Background32x32 => 32, RegularBackgroundSize::Background64x64 | RegularBackgroundSize::Background64x32 => 64, @@ -30,33 +78,26 @@ impl RegularBackgroundSize { } #[must_use] - pub fn height(&self) -> u32 { + fn height(&self) -> u32 { match self { RegularBackgroundSize::Background32x32 | RegularBackgroundSize::Background64x32 => 32, RegularBackgroundSize::Background32x64 | RegularBackgroundSize::Background64x64 => 64, } } +} - pub(crate) fn size_flag(self) -> u16 { - match self { - RegularBackgroundSize::Background32x32 => 0, - RegularBackgroundSize::Background64x32 => 1, - RegularBackgroundSize::Background32x64 => 2, - RegularBackgroundSize::Background64x64 => 3, - } +impl BackgroundSizePrivate for RegularBackgroundSize { + fn size_flag(self) -> u16 { + self as u16 } - pub(crate) fn num_tiles(self) -> usize { - (self.width() * self.height()) as usize - } - - pub(crate) fn num_screen_blocks(self) -> usize { + fn num_screen_blocks(&self) -> usize { self.num_tiles() / (32 * 32) } // This is hilariously complicated due to how the GBA stores the background screenblocks. // See https://www.coranac.com/tonc/text/regbg.htm#sec-map for an explanation - pub(crate) fn gba_offset(self, pos: Vector2D) -> usize { + fn gba_offset(&self, pos: Vector2D) -> usize { let x_mod = pos.x & (self.width() as u16 - 1); let y_mod = pos.y & (self.height() as u16 - 1); @@ -66,21 +107,43 @@ impl RegularBackgroundSize { pos as usize } +} - pub(crate) fn tile_pos_x(self, x: i32) -> u16 { - ((x as u32) & (self.width() - 1)) as u16 +impl BackgroundSize for AffineBackgroundSize { + #[must_use] + fn width(&self) -> u32 { + match self { + AffineBackgroundSize::Background16x16 => 16, + AffineBackgroundSize::Background32x32 => 32, + AffineBackgroundSize::Background64x64 => 64, + AffineBackgroundSize::Background128x128 => 128, + } } - pub(crate) fn tile_pos_y(self, y: i32) -> u16 { - ((y as u32) & (self.height() - 1)) as u16 + #[must_use] + fn height(&self) -> u32 { + self.width() + } +} + +impl BackgroundSizePrivate for AffineBackgroundSize { + fn size_flag(self) -> u16 { + self as u16 } - pub(crate) fn px_offset_x(self, x: i32) -> u16 { - ((x as u32) & (self.width() * 8 - 1)) as u16 + fn num_screen_blocks(&self) -> usize { + // technically 16x16 and 32x32 only use the first 1/8 and 1/2 of the SB, respectively + 1.max(self.num_tiles() / 2048) } - pub(crate) fn px_offset_y(self, y: i32) -> u16 { - ((y as u32) & (self.height() * 8 - 1)) as u16 + // Affine modes don't do the convoluted staggered block layout + fn gba_offset(&self, pos: Vector2D) -> usize { + let x_mod = pos.x & (self.width() as u16 - 1); + let y_mod = pos.y & (self.height() as u16 - 1); + + let pos = x_mod + (self.width() as u16 * y_mod); + + pos as usize } } @@ -90,11 +153,11 @@ struct Tile(u16); impl Tile { fn new(idx: TileIndex, setting: TileSetting) -> Self { - Self(idx.index() | setting.setting()) + Self(idx.raw_index() | setting.setting()) } fn tile_index(self) -> TileIndex { - TileIndex::new(self.0 as usize & ((1 << 10) - 1)) + TileIndex::new(self.0 as usize & ((1 << 10) - 1), TileFormat::FourBpp) } } @@ -126,6 +189,149 @@ impl TileSetting { } } +pub(self) fn find_screenblock_gap(screenblocks: &Bitarray<1>, gap: usize) -> usize { + let mut candidate = 0; + + 'outer: while candidate < 16 - gap { + let starting_point = candidate; + for attempt in starting_point..(starting_point + gap) { + if screenblocks.get(attempt) == Some(true) { + candidate = attempt + 1; + continue 'outer; + } + } + + return candidate; + } + + panic!( + "Failed to find screenblock gap of at least {} elements", + gap + ); +} + +pub(self) trait TiledMode { + const REGULAR_BACKGROUNDS: usize; + const AFFINE_BACKGROUNDS: usize; + fn screenblocks(&self) -> &RefCell>; + fn regular(&self) -> &RefCell>; + fn affine(&self) -> &RefCell>; +} + +pub trait RegularTiledMode { + fn regular_background( + &self, + priority: Priority, + size: RegularBackgroundSize, + ) -> MapLoan<'_, RegularMap>; +} + +pub trait AffineTiledMode { + fn affine_background( + &self, + priority: Priority, + size: AffineBackgroundSize, + ) -> MapLoan<'_, AffineMap>; +} + +// withoutboats: https://internals.rust-lang.org/t/const-generics-where-restrictions/12742/6 +pub struct If; +pub trait True {} +impl True for If {} + +impl RegularTiledMode for T +where + T: TiledMode, + If<{ T::REGULAR_BACKGROUNDS > 0 }>: True, +{ + fn regular_background( + &self, + priority: Priority, + size: RegularBackgroundSize, + ) -> MapLoan<'_, RegularMap> { + let mut regular = self.regular().borrow_mut(); + let new_background = regular.first_zero().unwrap(); + if new_background >= T::REGULAR_BACKGROUNDS { + panic!( + "can only have {} active regular backgrounds", + T::REGULAR_BACKGROUNDS + ); + } + + let num_screenblocks = size.num_screen_blocks(); + let mut screenblocks = self.screenblocks().borrow_mut(); + + let screenblock = find_screenblock_gap(&screenblocks, num_screenblocks); + for id in screenblock..(screenblock + num_screenblocks) { + screenblocks.set(id, true); + } + + let bg = RegularMap::new(new_background as u8, screenblock as u8 + 16, priority, size); + + regular.set(new_background, true); + + MapLoan::new( + bg, + new_background as u8, + screenblock as u8, + num_screenblocks as u8, + self.regular(), + self.screenblocks(), + ) + } +} + +impl AffineTiledMode for T +where + T: TiledMode, + If<{ T::AFFINE_BACKGROUNDS > 0 }>: True, +{ + fn affine_background( + &self, + priority: Priority, + size: AffineBackgroundSize, + ) -> MapLoan<'_, AffineMap> { + let mut affine = self.affine().borrow_mut(); + let new_background = affine.first_zero().unwrap(); + if new_background >= T::AFFINE_BACKGROUNDS + AFFINE_BG_ID_OFFSET { + panic!( + "can only have {} active affine backgrounds", + T::AFFINE_BACKGROUNDS + ); + } + + let num_screenblocks = size.num_screen_blocks(); + let mut screenblocks = self.screenblocks().borrow_mut(); + + let screenblock = find_screenblock_gap(&screenblocks, num_screenblocks); + for id in screenblock..(screenblock + num_screenblocks) { + screenblocks.set(id, true); + } + + let center_dim = Num::new(size.width() as i32 * 8 / 2); + let default_bg_center = (center_dim, center_dim).into(); + + let bg = AffineMap::new( + new_background as u8, + screenblock as u8 + 16, + priority, + size, + default_bg_center, + ); + + affine.set(new_background, true); + + MapLoan::new( + bg, + new_background as u8, + screenblock as u8, + num_screenblocks as u8, + self.affine(), + self.screenblocks(), + ) + } +} + #[cfg(test)] mod test { use super::*; diff --git a/agb/src/display/tiled/tiled0.rs b/agb/src/display/tiled/tiled0.rs index fbc523e..e824a8e 100644 --- a/agb/src/display/tiled/tiled0.rs +++ b/agb/src/display/tiled/tiled0.rs @@ -1,12 +1,11 @@ use core::cell::RefCell; +use super::{MapLoan, RegularBackgroundSize, RegularMap, RegularTiledMode, TiledMode}; use crate::{ bitarray::Bitarray, display::{set_graphics_mode, DisplayMode, Priority}, }; -use super::{MapLoan, RegularBackgroundSize, RegularMap}; - pub struct Tiled0 { regular: RefCell>, screenblocks: RefCell>, @@ -27,52 +26,23 @@ impl Tiled0 { priority: Priority, size: RegularBackgroundSize, ) -> MapLoan<'_, RegularMap> { - let mut regular = self.regular.borrow_mut(); - let new_background = regular.first_zero().unwrap(); - if new_background >= 4 { - panic!("can only have 4 active backgrounds"); - } - - let num_screenblocks = size.num_screen_blocks(); - let mut screenblocks = self.screenblocks.borrow_mut(); - - let screenblock = find_screenblock_gap(&screenblocks, num_screenblocks); - for id in screenblock..(screenblock + num_screenblocks) { - screenblocks.set(id, true); - } - - let bg = RegularMap::new(new_background as u8, screenblock as u8 + 16, priority, size); - - regular.set(new_background, true); - - MapLoan::new( - bg, - new_background as u8, - screenblock as u8, - num_screenblocks as u8, - &self.regular, - &self.screenblocks, - ) + self.regular_background(priority, size) } } -fn find_screenblock_gap(screenblocks: &Bitarray<1>, gap: usize) -> usize { - let mut candidate = 0; +impl TiledMode for Tiled0 { + const REGULAR_BACKGROUNDS: usize = 4; + const AFFINE_BACKGROUNDS: usize = 0; - 'outer: while candidate < 16 - gap { - let starting_point = candidate; - for attempt in starting_point..(starting_point + gap) { - if screenblocks.get(attempt) == Some(true) { - candidate = attempt + 1; - continue 'outer; - } - } - - return candidate; + fn screenblocks(&self) -> &RefCell> { + &self.screenblocks } - panic!( - "Failed to find screenblock gap of at least {} elements", - gap - ); + fn regular(&self) -> &RefCell> { + &self.regular + } + + fn affine(&self) -> &RefCell> { + unimplemented!() + } } diff --git a/agb/src/display/tiled/tiled1.rs b/agb/src/display/tiled/tiled1.rs new file mode 100644 index 0000000..2b4af6e --- /dev/null +++ b/agb/src/display/tiled/tiled1.rs @@ -0,0 +1,47 @@ +use core::cell::RefCell; + +use super::TiledMode; +use crate::{ + bitarray::Bitarray, + display::{set_graphics_mode, tiled::AFFINE_BG_ID_OFFSET, DisplayMode}, +}; + +pub struct Tiled1 { + regular: RefCell>, + affine: RefCell>, + screenblocks: RefCell>, +} + +impl Tiled1 { + pub(crate) unsafe fn new() -> Self { + set_graphics_mode(DisplayMode::Tiled1); + + let affine = RefCell::new(Bitarray::new()); + for i in 0..AFFINE_BG_ID_OFFSET { + affine.borrow_mut().set(i, true); + } + + Self { + regular: Default::default(), + affine, + screenblocks: Default::default(), + } + } +} + +impl TiledMode for Tiled1 { + const REGULAR_BACKGROUNDS: usize = 2; + const AFFINE_BACKGROUNDS: usize = 1; + + fn screenblocks(&self) -> &RefCell> { + &self.screenblocks + } + + fn regular(&self) -> &RefCell> { + &self.regular + } + + fn affine(&self) -> &RefCell> { + &self.affine + } +} diff --git a/agb/src/display/tiled/tiled2.rs b/agb/src/display/tiled/tiled2.rs new file mode 100644 index 0000000..60a3c72 --- /dev/null +++ b/agb/src/display/tiled/tiled2.rs @@ -0,0 +1,53 @@ +use core::cell::RefCell; + +use super::{AffineBackgroundSize, AffineMap, AffineTiledMode, MapLoan, TiledMode}; +use crate::{ + bitarray::Bitarray, + display::{set_graphics_mode, tiled::AFFINE_BG_ID_OFFSET, DisplayMode, Priority}, +}; + +pub struct Tiled2 { + affine: RefCell>, + screenblocks: RefCell>, +} + +impl Tiled2 { + pub(crate) unsafe fn new() -> Self { + set_graphics_mode(DisplayMode::Tiled2); + + let affine = RefCell::new(Bitarray::new()); + for i in 0..AFFINE_BG_ID_OFFSET { + affine.borrow_mut().set(i, true); + } + + Self { + affine, + screenblocks: Default::default(), + } + } + + pub fn background( + &self, + priority: Priority, + size: AffineBackgroundSize, + ) -> MapLoan<'_, AffineMap> { + self.affine_background(priority, size) + } +} + +impl TiledMode for Tiled2 { + const REGULAR_BACKGROUNDS: usize = 0; + const AFFINE_BACKGROUNDS: usize = 2; + + fn screenblocks(&self) -> &RefCell> { + &self.screenblocks + } + + fn regular(&self) -> &RefCell> { + unimplemented!() + } + + fn affine(&self) -> &RefCell> { + &self.affine + } +} diff --git a/agb/src/display/tiled/vram_manager.rs b/agb/src/display/tiled/vram_manager.rs index 6caa895..b9ee9ff 100644 --- a/agb/src/display/tiled/vram_manager.rs +++ b/agb/src/display/tiled/vram_manager.rs @@ -2,6 +2,7 @@ use core::{alloc::Layout, ptr::NonNull}; use alloc::{slice, vec::Vec}; +use crate::display::tiled::Tile; use crate::{ agb_alloc::{block_allocator::BlockAllocator, bump_allocator::StartEnd}, display::palette16, @@ -22,18 +23,22 @@ static TILE_ALLOCATOR: BlockAllocator = unsafe { }) }; -const TILE_LAYOUT: Layout = unsafe { Layout::from_size_align_unchecked(8 * 8 / 2, 8 * 8 / 2) }; +const fn layout_of(format: TileFormat) -> Layout { + unsafe { Layout::from_size_align_unchecked(format.tile_size(), format.tile_size()) } +} #[derive(Clone, Copy, Debug)] pub enum TileFormat { FourBpp, + EightBpp, } impl TileFormat { /// Returns the size of the tile in bytes - fn tile_size(self) -> usize { + pub(crate) const fn tile_size(self) -> usize { match self { TileFormat::FourBpp => 8 * 8 / 2, + TileFormat::EightBpp => 8 * 8, } } } @@ -55,15 +60,50 @@ impl<'a> TileSet<'a> { } #[derive(Debug, Clone, Copy)] -pub struct TileIndex(u16); +pub enum TileIndex { + FourBpp(u16), + EightBpp(u8), +} impl TileIndex { - pub(crate) const fn new(index: usize) -> Self { - Self(index as u16) + pub(crate) const fn new(index: usize, format: TileFormat) -> Self { + match format { + TileFormat::FourBpp => Self::FourBpp(index as u16), + TileFormat::EightBpp => Self::EightBpp(index as u8), + } } - pub(crate) const fn index(self) -> u16 { - self.0 + pub(crate) const fn raw_index(self) -> u16 { + match self { + TileIndex::FourBpp(x) => x, + TileIndex::EightBpp(x) => x as u16, + } + } + + pub(crate) const fn format(self) -> TileFormat { + match self { + TileIndex::FourBpp(_) => TileFormat::FourBpp, + TileIndex::EightBpp(_) => TileFormat::EightBpp, + } + } + + fn refcount_key(self) -> usize { + match self { + TileIndex::FourBpp(x) => x as usize, + TileIndex::EightBpp(x) => x as usize * 2, + } + } +} + +impl From for TileIndex { + fn from(tile: Tile) -> Self { + tile.tile_index() + } +} + +impl From for TileIndex { + fn from(index: u8) -> TileIndex { + TileIndex::new(usize::from(index), TileFormat::EightBpp) } } @@ -159,7 +199,7 @@ impl DynamicTile<'_> { #[must_use] pub fn tile_index(&self) -> u16 { let difference = self.tile_data.as_ptr() as usize - TILE_RAM_START; - (difference / (8 * 8 / 2)) as u16 + (difference / TileFormat::FourBpp.tile_size()) as u16 } } @@ -182,24 +222,27 @@ impl VRamManager { } } - fn index_from_reference(reference: TileReference) -> usize { + fn index_from_reference(reference: TileReference, format: TileFormat) -> TileIndex { let difference = reference.0.as_ptr() as usize - TILE_RAM_START; - difference / (8 * 8 / 2) + TileIndex::new(difference / format.tile_size(), format) } fn reference_from_index(index: TileIndex) -> TileReference { - let ptr = (index.index() * (8 * 8 / 2)) as usize + TILE_RAM_START; + let ptr = (index.raw_index() as usize * index.format().tile_size()) + TILE_RAM_START; TileReference(NonNull::new(ptr as *mut _).unwrap()) } #[must_use] pub fn new_dynamic_tile<'a>(&mut self) -> DynamicTile<'a> { + // TODO: format param? let tile_format = TileFormat::FourBpp; - let new_reference: NonNull = - unsafe { TILE_ALLOCATOR.alloc(TILE_LAYOUT) }.unwrap().cast(); + let new_reference: NonNull = unsafe { TILE_ALLOCATOR.alloc(layout_of(tile_format)) } + .unwrap() + .cast(); let tile_reference = TileReference(new_reference); - let index = Self::index_from_reference(tile_reference); + let index = Self::index_from_reference(tile_reference, tile_format); + let key = index.refcount_key(); let tiles = unsafe { slice::from_raw_parts_mut(TILE_RAM_START as *mut u8, 1024 * tile_format.tile_size()) @@ -208,23 +251,21 @@ impl VRamManager { let tile_set = TileSet::new(tiles, tile_format); self.tile_set_to_vram.insert( - TileInTileSetReference::new(&tile_set, index as u16), + TileInTileSetReference::new(&tile_set, index.raw_index()), tile_reference, ); - self.reference_counts.resize( - self.reference_counts.len().max(index + 1), - Default::default(), - ); - self.reference_counts[index] = - TileReferenceCount::new(TileInTileSetReference::new(&tile_set, index as u16)); + self.reference_counts + .resize(self.reference_counts.len().max(key + 1), Default::default()); + self.reference_counts[key] = + TileReferenceCount::new(TileInTileSetReference::new(&tile_set, index.raw_index())); DynamicTile { tile_data: unsafe { slice::from_raw_parts_mut( tiles .as_mut_ptr() - .add((index * tile_format.tile_size()) as usize) + .add(index.raw_index() as usize * tile_format.tile_size()) .cast(), tile_format.tile_size() / core::mem::size_of::(), ) @@ -238,8 +279,9 @@ impl VRamManager { let pointer = NonNull::new(dynamic_tile.tile_data.as_mut_ptr() as *mut _).unwrap(); let tile_reference = TileReference(pointer); - let tile_index = Self::index_from_reference(tile_reference); - self.remove_tile(TileIndex::new(tile_index)); + // TODO: dynamic_tile.format? + let tile_index = Self::index_from_reference(tile_reference, TileFormat::FourBpp); + self.remove_tile(tile_index); } pub(crate) fn add_tile(&mut self, tile_set: &TileSet<'_>, tile: u16) -> TileIndex { @@ -248,37 +290,39 @@ impl VRamManager { .get(&TileInTileSetReference::new(tile_set, tile)); if let Some(reference) = reference { - let index = Self::index_from_reference(*reference); - self.reference_counts[index].increment_reference_count(); - return TileIndex::new(index); + let tile_index = Self::index_from_reference(*reference, tile_set.format); + let key = tile_index.refcount_key(); + self.reference_counts[key].increment_reference_count(); + return tile_index; } let new_reference: NonNull = - unsafe { TILE_ALLOCATOR.alloc(TILE_LAYOUT) }.unwrap().cast(); + unsafe { TILE_ALLOCATOR.alloc(layout_of(tile_set.format)) } + .unwrap() + .cast(); let tile_reference = TileReference(new_reference); self.copy_tile_to_location(tile_set, tile, tile_reference); - let index = Self::index_from_reference(tile_reference); + let index = Self::index_from_reference(tile_reference, tile_set.format); + let key = index.refcount_key(); self.tile_set_to_vram .insert(TileInTileSetReference::new(tile_set, tile), tile_reference); - self.reference_counts.resize( - self.reference_counts.len().max(index + 1), - Default::default(), - ); + self.reference_counts + .resize(self.reference_counts.len().max(key + 1), Default::default()); - self.reference_counts[index] = + self.reference_counts[key] = TileReferenceCount::new(TileInTileSetReference::new(tile_set, tile)); - TileIndex::new(index) + index } pub(crate) fn remove_tile(&mut self, tile_index: TileIndex) { - let index = tile_index.index() as usize; + let key = tile_index.refcount_key(); - let new_reference_count = self.reference_counts[index].decrement_reference_count(); + let new_reference_count = self.reference_counts[key].decrement_reference_count(); if new_reference_count != 0 { return; @@ -289,23 +333,26 @@ impl VRamManager { pub(crate) fn gc(&mut self) { for tile_index in self.indices_to_gc.drain(..) { - let index = tile_index.index() as usize; - if self.reference_counts[index].current_count() > 0 { + let key = tile_index.refcount_key() as usize; + if self.reference_counts[key].current_count() > 0 { continue; // it has since been added back } let tile_reference = Self::reference_from_index(tile_index); unsafe { - TILE_ALLOCATOR.dealloc_no_normalise(tile_reference.0.cast().as_ptr(), TILE_LAYOUT); + TILE_ALLOCATOR.dealloc_no_normalise( + tile_reference.0.cast().as_ptr(), + layout_of(tile_index.format()), + ); } - let tile_ref = self.reference_counts[index] + let tile_ref = self.reference_counts[key] .tile_in_tile_set .as_ref() .unwrap(); self.tile_set_to_vram.remove(tile_ref); - self.reference_counts[index].clear(); + self.reference_counts[key].clear(); } } diff --git a/agb/src/display/video.rs b/agb/src/display/video.rs index 9f62e3f..d0edf2e 100644 --- a/agb/src/display/video.rs +++ b/agb/src/display/video.rs @@ -1,7 +1,7 @@ use super::{ bitmap3::Bitmap3, bitmap4::Bitmap4, - tiled::{Tiled0, VRamManager}, + tiled::{Tiled0, Tiled1, Tiled2, VRamManager}, }; /// The video struct controls access to the video hardware. @@ -26,4 +26,14 @@ impl Video { pub fn tiled0(&mut self) -> (Tiled0, VRamManager) { (unsafe { Tiled0::new() }, VRamManager::new()) } + + /// Tiled 1 mode provides 2 regular tiled backgrounds and 1 affine tiled background + pub fn tiled1(&mut self) -> (Tiled1, VRamManager) { + (unsafe { Tiled1::new() }, VRamManager::new()) + } + + /// Tiled 2 mode provides 2 affine tiled backgrounds + pub fn tiled2(&mut self) -> (Tiled2, VRamManager) { + (unsafe { Tiled2::new() }, VRamManager::new()) + } } diff --git a/agb/src/lib.rs b/agb/src/lib.rs index b3cd8b4..35a14b6 100644 --- a/agb/src/lib.rs +++ b/agb/src/lib.rs @@ -13,6 +13,9 @@ #![feature(alloc_error_handler)] #![feature(allocator_api)] #![feature(asm_const)] +#![feature(const_trait_impl)] +#![allow(incomplete_features)] // generic_const_exprs, used for bg mode traits +#![feature(generic_const_exprs)] #![warn(clippy::all)] #![deny(clippy::must_use_candidate)] #![deny(clippy::trivially_copy_pass_by_ref)] @@ -80,7 +83,7 @@ /// # /// use agb::{ /// display::{ -/// tiled::{RegularBackgroundSize, TileFormat, TileSet, TileSetting, Tiled0, VRamManager}, +/// tiled::{RegularBackgroundSize, TileFormat, TileSet, TileSetting, Tiled0, TiledMap, VRamManager}, /// Priority, /// }, /// include_gfx, diff --git a/agb/src/memory_mapped.rs b/agb/src/memory_mapped.rs index 290d865..617dc48 100644 --- a/agb/src/memory_mapped.rs +++ b/agb/src/memory_mapped.rs @@ -16,7 +16,9 @@ impl MemoryMapped { } pub fn set(&self, val: T) { - unsafe { self.address.write_volatile(val) } + if core::mem::size_of::() != 0 { + unsafe { self.address.write_volatile(val) } + } } } diff --git a/agb/src/syscall.rs b/agb/src/syscall.rs index 69ff353..59d37a5 100644 --- a/agb/src/syscall.rs +++ b/agb/src/syscall.rs @@ -1,11 +1,14 @@ +use agb_fixnum::Vector2D; use core::arch::asm; +use core::mem::MaybeUninit; -// use crate::display::object::AffineMatrixAttributes; +use crate::display::object::AffineMatrixAttributes; +use crate::fixnum::Num; #[allow(non_snake_case)] const fn swi_map(thumb_id: u32) -> u32 { - if cfg!(target_feature="thumb-mode") { + if cfg!(target_feature = "thumb-mode") { thumb_id } else { thumb_id << 16 @@ -136,55 +139,103 @@ pub fn arc_tan2(x: i16, y: i32) -> i16 { result } -// pub fn affine_matrix( -// x_scale: Num, -// y_scale: Num, -// rotation: u8, -// ) -> AffineMatrixAttributes { -// let mut result = AffineMatrixAttributes { -// p_a: 0, -// p_b: 0, -// p_c: 0, -// p_d: 0, -// }; +#[repr(C)] +pub struct BgAffineSetData { + pub matrix: AffineMatrixAttributes, + pub position: Vector2D>, +} +impl Default for BgAffineSetData { + fn default() -> Self { + Self { + matrix: AffineMatrixAttributes::default(), + position: (0, 0).into(), + } + } +} -// #[allow(dead_code)] -// #[repr(C, packed)] -// struct Input { -// x_scale: i16, -// y_scale: i16, -// rotation: u16, -// } +/// `rotation` is in revolutions. +#[must_use] +pub fn bg_affine_matrix( + bg_center: Vector2D>, + display_center: Vector2D, + scale: Vector2D>, + rotation: Num, +) -> BgAffineSetData { + #[repr(C, packed)] + struct Input { + bg_center: Vector2D>, + display_center: Vector2D, + scale: Vector2D>, + rotation: u16, + } -// let input = Input { -// y_scale: x_scale.to_raw(), -// x_scale: y_scale.to_raw(), -// rotation: rotation as u16, -// }; + let input = Input { + bg_center, + display_center, + scale, + rotation: u16::from(rotation.to_raw()) << 8, + }; -// unsafe { -// asm!("swi 0x0F", -// in("r0") &input as *const Input as usize, -// in("r1") &mut result as *mut AffineMatrixAttributes as usize, -// in("r2") 1, -// in("r3") 2, -// ) -// } + let mut output = MaybeUninit::uninit(); -// result -// } + unsafe { + asm!( + "swi {SWI}", + SWI = const { swi_map(0x0E) }, + in("r0") &input as *const Input, + in("r1") output.as_mut_ptr(), + in("r2") 1, + ); + } -// #[cfg(test)] -// mod tests { -// use super::*; + unsafe { output.assume_init() } +} -// #[test_case] -// fn affine(_gba: &mut crate::Gba) { -// // expect identity matrix -// let one: Num = 1.into(); +/// `rotation` is in revolutions. +#[must_use] +pub fn obj_affine_matrix( + scale: Vector2D>, + rotation: Num, +) -> AffineMatrixAttributes { + #[allow(dead_code)] + #[repr(C, packed)] + struct Input { + scale: Vector2D>, + rotation: u16, + } -// let aff = affine_matrix(one, one, 0); -// assert_eq!(aff.p_a, one.to_raw()); -// assert_eq!(aff.p_d, one.to_raw()); -// } -// } + let input = Input { + scale, + rotation: u16::from(rotation.to_raw()) << 8, + }; + + let mut output = MaybeUninit::uninit(); + + unsafe { + asm!( + "swi {SWI}", + SWI = const { swi_map(0x0F) }, + in("r0") &input as *const Input, + in("r1") output.as_mut_ptr(), + in("r2") 1, + in("r3") 2, + ); + } + + unsafe { output.assume_init() } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test_case] + fn affine(_gba: &mut crate::Gba) { + // expect identity matrix + let one: Num = 1.into(); + + let aff = obj_affine_matrix((one, one).into(), Num::default()); + assert_eq!(aff.p_a, one); + assert_eq!(aff.p_d, one); + } +} diff --git a/examples/hyperspace-roll/build.rs b/examples/hyperspace-roll/build.rs index e71fdf5..f328e4d 100644 --- a/examples/hyperspace-roll/build.rs +++ b/examples/hyperspace-roll/build.rs @@ -1 +1 @@ -fn main() {} \ No newline at end of file +fn main() {} diff --git a/examples/hyperspace-roll/src/background.rs b/examples/hyperspace-roll/src/background.rs index 91d16c9..c67cc70 100644 --- a/examples/hyperspace-roll/src/background.rs +++ b/examples/hyperspace-roll/src/background.rs @@ -1,5 +1,5 @@ use agb::{ - display::tiled::{RegularMap, TileFormat, TileSet, TileSetting, VRamManager}, + display::tiled::{RegularMap, TileFormat, TileSet, TileSetting, TiledMap, VRamManager}, include_gfx, rng, }; diff --git a/examples/hyperspace-roll/src/battle.rs b/examples/hyperspace-roll/src/battle.rs index 3fd3b87..691c7a8 100644 --- a/examples/hyperspace-roll/src/battle.rs +++ b/examples/hyperspace-roll/src/battle.rs @@ -2,7 +2,7 @@ use crate::sfx::Sfx; use crate::{ graphics::SELECT_BOX, level_generation::generate_attack, Agb, EnemyAttackType, Face, PlayerDice, }; -use agb::display::tiled::RegularMap; +use agb::display::tiled::{RegularMap, TiledMap}; use agb::{hash_map::HashMap, input::Button}; use alloc::vec; use alloc::vec::Vec; diff --git a/examples/hyperspace-roll/src/customise.rs b/examples/hyperspace-roll/src/customise.rs index 852ff5a..c91f3cd 100644 --- a/examples/hyperspace-roll/src/customise.rs +++ b/examples/hyperspace-roll/src/customise.rs @@ -2,7 +2,7 @@ use agb::{ display::{ object::{Object, ObjectController}, palette16::Palette16, - tiled::{RegularMap, TileSet, TileSetting}, + tiled::{RegularMap, TileSet, TileSetting, TiledMap}, HEIGHT, WIDTH, }, include_gfx, diff --git a/examples/hyperspace-roll/src/main.rs b/examples/hyperspace-roll/src/main.rs index 9acf3c0..49e43b7 100644 --- a/examples/hyperspace-roll/src/main.rs +++ b/examples/hyperspace-roll/src/main.rs @@ -12,7 +12,7 @@ use agb::display; use agb::display::object::ObjectController; -use agb::display::tiled::VRamManager; +use agb::display::tiled::{TiledMap, VRamManager}; use agb::display::Priority; use agb::interrupt::VBlank; 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 990e763..10ac10e 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,5 @@ use agb::display::{ - tiled::{RegularMap, TileSet, TileSetting, VRamManager}, + tiled::{RegularMap, TileSet, TileSetting, TiledMap, 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 133afb4..031b5cd 100644 --- a/examples/the-hat-chooses-the-wizard/src/main.rs +++ b/examples/the-hat-chooses-the-wizard/src/main.rs @@ -11,7 +11,7 @@ use agb::{ object::{Graphics, Object, ObjectController, Tag, TagMap}, tiled::{ InfiniteScrolledMap, PartialUpdateStatus, RegularBackgroundSize, TileFormat, TileSet, - TileSetting, VRamManager, + TileSetting, TiledMap, VRamManager, }, Priority, HEIGHT, WIDTH, }, 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 64243b9..de6f9be 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,6 @@ use super::sfx::MusicBox; use agb::{ - display::tiled::{RegularMap, TileFormat, TileSet, TileSetting, VRamManager}, + display::tiled::{RegularMap, TiledMap, TileFormat, TileSet, TileSetting, VRamManager}, sound::mixer::Mixer, };