From ab082c59a1d53785c7125cc61e99eddf9ff12204 Mon Sep 17 00:00:00 2001 From: Corwin Date: Sun, 2 Apr 2023 03:10:07 +0100 Subject: [PATCH] a mad redo of how objects work --- agb/Cargo.toml | 1 + agb/examples/chicken.rs | 26 +- agb/examples/sprites.rs | 20 +- agb/src/display/mod.rs | 18 +- agb/src/display/object.rs | 1343 +---------------- agb/src/display/object/managed.rs | 386 +++++ agb/src/display/object/sprites.rs | 7 + agb/src/display/object/sprites/sprite.rs | 369 +++++ .../object/sprites/sprite_allocator.rs | 288 ++++ agb/src/display/object/unmanaged.rs | 5 + .../display/object/unmanaged/attributes.rs | 204 +++ agb/src/display/object/unmanaged/object.rs | 187 +++ agb/src/display/palette16.rs | 6 + 13 files changed, 1509 insertions(+), 1351 deletions(-) create mode 100644 agb/src/display/object/managed.rs create mode 100644 agb/src/display/object/sprites.rs create mode 100644 agb/src/display/object/sprites/sprite.rs create mode 100644 agb/src/display/object/sprites/sprite_allocator.rs create mode 100644 agb/src/display/object/unmanaged.rs create mode 100644 agb/src/display/object/unmanaged/attributes.rs create mode 100644 agb/src/display/object/unmanaged/object.rs diff --git a/agb/Cargo.toml b/agb/Cargo.toml index 02cdc17b..70797e43 100644 --- a/agb/Cargo.toml +++ b/agb/Cargo.toml @@ -30,6 +30,7 @@ agb_fixnum = { version = "0.13.0", path = "../agb-fixnum" } bare-metal = "1" modular-bitfield = "0.11" rustc-hash = { version = "1", default-features = false } +slotmap = { version = "1.0", default-features = false } [package.metadata.docs.rs] default-target = "thumbv6m-none-eabi" diff --git a/agb/examples/chicken.rs b/agb/examples/chicken.rs index 3615ee79..9bc7bfee 100644 --- a/agb/examples/chicken.rs +++ b/agb/examples/chicken.rs @@ -4,7 +4,7 @@ use agb::{ display::tiled::{TileFormat, TileSet, TileSetting, TiledMap}, display::{ - object::{Object, ObjectController, Size, Sprite}, + object::{Object, Size, Sprite, StaticSpriteLoader}, palette16::Palette16, tiled::RegularBackgroundSize, HEIGHT, WIDTH, @@ -74,11 +74,11 @@ fn main(mut gba: agb::Gba) -> ! { background.show(); background.commit(&mut vram); - let object = gba.display.object.get(); + let (object, mut sprites) = gba.display.object.get_managed(); - let sprite = object.sprite(&CHICKEN_SPRITES[0]); + let sprite = sprites.get_vram_sprite(&CHICKEN_SPRITES[0]); let mut chicken = Character { - object: object.object(sprite), + object: object.add_object(sprite), position: Vector2D { x: (6 * 8) << 8, y: ((7 * 8) - 4) << 8, @@ -137,15 +137,15 @@ fn main(mut gba: agb::Gba) -> ! { } restrict_to_screen(&mut chicken); - update_chicken_object(&mut chicken, &object, state, frame_count); + update_chicken_object(&mut chicken, &mut sprites, state, frame_count); object.commit(); } } -fn update_chicken_object<'a>( - chicken: &'_ mut Character<'a>, - object: &'a ObjectController, +fn update_chicken_object( + chicken: &'_ mut Character<'_>, + sprites: &mut StaticSpriteLoader, state: State, frame_count: u32, ) { @@ -158,19 +158,19 @@ fn update_chicken_object<'a>( State::Ground => { if chicken.velocity.x.abs() > 1 << 4 { chicken.object.set_sprite( - object.sprite(&CHICKEN_SPRITES[frame_ranger(frame_count, 1, 3, 10)]), + sprites.get_vram_sprite(&CHICKEN_SPRITES[frame_ranger(frame_count, 1, 3, 10)]), ); } else { chicken .object - .set_sprite(object.sprite(&CHICKEN_SPRITES[0])); + .set_sprite(sprites.get_vram_sprite(&CHICKEN_SPRITES[0])); } } State::Upwards => {} State::Flapping => { - chicken - .object - .set_sprite(object.sprite(&CHICKEN_SPRITES[frame_ranger(frame_count, 4, 5, 5)])); + chicken.object.set_sprite( + sprites.get_vram_sprite(&CHICKEN_SPRITES[frame_ranger(frame_count, 4, 5, 5)]), + ); } } diff --git a/agb/examples/sprites.rs b/agb/examples/sprites.rs index c5ea5ba8..41c4247c 100644 --- a/agb/examples/sprites.rs +++ b/agb/examples/sprites.rs @@ -3,7 +3,7 @@ extern crate alloc; -use agb::display::object::{Graphics, ObjectController, Sprite, TagMap}; +use agb::display::object::{Graphics, OAMManager, Sprite, StaticSpriteLoader, TagMap}; use alloc::vec::Vec; const GRAPHICS: &Graphics = agb::include_aseprite!( @@ -15,13 +15,13 @@ const GRAPHICS: &Graphics = agb::include_aseprite!( const SPRITES: &[Sprite] = GRAPHICS.sprites(); const TAG_MAP: &TagMap = GRAPHICS.tags(); -fn all_sprites(gfx: &ObjectController) { +fn all_sprites(gfx: &OAMManager, sprites: &mut StaticSpriteLoader) { let mut input = agb::input::ButtonController::new(); let mut objs = Vec::new(); for y in 0..9 { for x in 0..14 { - let mut obj = gfx.object(gfx.sprite(&SPRITES[0])); + let mut obj = gfx.add_object(sprites.get_vram_sprite(&SPRITES[0])); obj.show(); obj.set_position((x * 16 + 8, y * 16 + 8).into()); objs.push(obj); @@ -48,14 +48,14 @@ fn all_sprites(gfx: &ObjectController) { image %= SPRITES.len(); for (i, obj) in objs.iter_mut().enumerate() { let this_image = (image + i) % SPRITES.len(); - obj.set_sprite(gfx.sprite(&SPRITES[this_image])); + obj.set_sprite(sprites.get_vram_sprite(&SPRITES[this_image])); } gfx.commit(); } } } -fn all_tags(gfx: &ObjectController) { +fn all_tags(gfx: &OAMManager, sprites: &mut StaticSpriteLoader) { let mut input = agb::input::ButtonController::new(); let mut objs = Vec::new(); @@ -65,7 +65,7 @@ fn all_tags(gfx: &ObjectController) { let sprite = v.sprite(0); let (size_x, size_y) = sprite.size().to_width_height(); let (size_x, size_y) = (size_x as i32, size_y as i32); - let mut obj = gfx.object(gfx.sprite(sprite)); + let mut obj = gfx.add_object(sprites.get_vram_sprite(sprite)); obj.show(); obj.set_position((x * 32 + 16 - size_x / 2, y * 32 + 16 - size_y / 2).into()); objs.push((obj, v)); @@ -90,7 +90,7 @@ fn all_tags(gfx: &ObjectController) { if count % 5 == 0 { image += 1; for (obj, tag) in objs.iter_mut() { - obj.set_sprite(gfx.sprite(tag.animation_sprite(image))); + obj.set_sprite(sprites.get_vram_sprite(tag.animation_sprite(image))); } gfx.commit(); } @@ -99,12 +99,12 @@ fn all_tags(gfx: &ObjectController) { #[agb::entry] fn main(mut gba: agb::Gba) -> ! { - let gfx = gba.display.object.get(); + let (gfx, mut ssl) = gba.display.object.get_managed(); loop { - all_tags(&gfx); + all_tags(&gfx, &mut ssl); gfx.commit(); - all_sprites(&gfx); + all_sprites(&gfx, &mut ssl); gfx.commit(); } } diff --git a/agb/src/display/mod.rs b/agb/src/display/mod.rs index 939c22d3..5c30e9d0 100644 --- a/agb/src/display/mod.rs +++ b/agb/src/display/mod.rs @@ -4,7 +4,11 @@ use bitflags::bitflags; use modular_bitfield::BitfieldSpecifier; use video::Video; -use self::{blend::Blend, object::ObjectController, window::Windows}; +use self::{ + blend::Blend, + object::{initilise_oam, OAMManager, StaticSpriteLoader, UnmanagedOAM}, + window::Windows, +}; /// Graphics mode 3. Bitmap mode that provides a 16-bit colour framebuffer. pub mod bitmap3; @@ -80,8 +84,14 @@ pub struct Display { pub struct ObjectDistribution; impl ObjectDistribution { - pub fn get(&mut self) -> ObjectController<'_> { - ObjectController::new() + pub fn get_unmanaged(&mut self) -> (UnmanagedOAM<'_>, StaticSpriteLoader) { + unsafe { initilise_oam() }; + (UnmanagedOAM::new(), StaticSpriteLoader::new()) + } + + pub fn get_managed(&mut self) -> (OAMManager<'_>, StaticSpriteLoader) { + unsafe { initilise_oam() }; + (OAMManager::new(), StaticSpriteLoader::new()) } } @@ -143,7 +153,7 @@ pub fn busy_wait_for_vblank() { while VCOUNT.get() < 160 {} } -#[derive(BitfieldSpecifier, Clone, Copy)] +#[derive(BitfieldSpecifier, Clone, Copy, Debug)] pub enum Priority { P0 = 0, P1 = 1, diff --git a/agb/src/display/object.rs b/agb/src/display/object.rs index df1eb842..299c7734 100644 --- a/agb/src/display/object.rs +++ b/agb/src/display/object.rs @@ -1,1331 +1,26 @@ -#![deny(missing_docs)] -use alloc::rc::{Rc, Weak}; -use alloc::vec::Vec; -use core::alloc::Layout; +mod managed; +mod sprites; +mod unmanaged; -use core::cell::UnsafeCell; -use core::marker::PhantomData; -use core::mem::MaybeUninit; -use core::ops::DerefMut; -use core::ptr::NonNull; -use core::slice; -use modular_bitfield::prelude::{B10, B2, B3, B4, B5, B8, B9}; -use modular_bitfield::{bitfield, BitfieldSpecifier}; - -const BYTES_PER_TILE_4BPP: usize = 32; - -use super::palette16::Palette16; -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::hash_map::HashMap; - -use attributes::*; - -/// Include this type if you call `get_object_controller` in impl block. This -/// helps you use the right lifetimes and doesn't impl Sync (using from two -/// "threads" without synchronisation is not safe), but sending to another -/// "thread" is safe. -#[derive(Clone, Copy)] -struct ObjectControllerReference<'a> { - #[cfg(debug_assertions)] - reference: &'a core::cell::RefCell, - - _ref: PhantomData<&'a UnsafeCell<()>>, -} - -#[cfg(debug_assertions)] -static mut OBJECT_CONTROLLER: MaybeUninit> = - MaybeUninit::uninit(); -#[cfg(not(debug_assertions))] -static mut OBJECT_CONTROLLER: MaybeUninit = MaybeUninit::uninit(); - -impl<'a> ObjectControllerReference<'a> { - unsafe fn init() -> Self { - #[cfg(debug_assertions)] - OBJECT_CONTROLLER.write(core::cell::RefCell::new(ObjectControllerStatic::new())); - #[cfg(not(debug_assertions))] - OBJECT_CONTROLLER.write(ObjectControllerStatic::new()); - Self { - #[cfg(debug_assertions)] - reference: unsafe { OBJECT_CONTROLLER.assume_init_ref() }, - _ref: PhantomData, - } - } - - unsafe fn uninit() { - OBJECT_CONTROLLER.assume_init_drop(); - } - - #[track_caller] - #[cfg(debug_assertions)] - fn borrow_cell_ref(self) -> core::cell::RefMut<'a, ObjectControllerStatic> { - self.reference.borrow_mut() - } - #[track_caller] - #[cfg(not(debug_assertions))] - unsafe fn borrow_direct(self) -> &'a mut ObjectControllerStatic { - unsafe { OBJECT_CONTROLLER.assume_init_mut() } - } - - #[track_caller] - unsafe fn borrow_mut(self) -> impl DerefMut + 'a { - #[cfg(debug_assertions)] - { - self.reference.borrow_mut() - } - #[cfg(not(debug_assertions))] - unsafe { - OBJECT_CONTROLLER.assume_init_mut() - } - } -} - -static SPRITE_ALLOCATOR: BlockAllocator = unsafe { - BlockAllocator::new(StartEnd { - start: || TILE_SPRITE, - end: || TILE_SPRITE + 1024 * 8 * 4, - }) +pub use sprites::{ + include_aseprite, DynamicSprite, Graphics, Size, Sprite, SpriteVram, StaticSpriteLoader, Tag, + TagMap, }; -static PALETTE_ALLOCATOR: BlockAllocator = unsafe { - BlockAllocator::new(StartEnd { - start: || PALETTE_SPRITE, - end: || PALETTE_SPRITE + 0x200, - }) -}; +pub use managed::{OAMManager, Object}; +pub use unmanaged::{AffineMode, OAMIterator, OAMSlot, UnmanagedOAM, UnmanagedObject}; + +use super::DISPLAY_CONTROL; -const PALETTE_SPRITE: usize = 0x0500_0200; -const TILE_SPRITE: usize = 0x06010000; const OBJECT_ATTRIBUTE_MEMORY: usize = 0x0700_0000; -/// Sprite data. Refers to the palette, pixel data, and the size of the sprite. -pub struct Sprite { - palette: &'static Palette16, - data: &'static [u8], - size: Size, -} - -/// Sprite data that can be used to create sprites in vram. -pub struct DynamicSprite<'a> { - data: &'a [u8], - size: Size, -} - -impl DynamicSprite<'_> { - #[must_use] - /// Creates a new dynamic sprite from underlying bytes. Note that despite - /// being an array of u8, this must be aligned to at least a 2 byte - /// boundary. - pub fn new(data: &[u8], size: Size) -> DynamicSprite { - let ptr = &data[0] as *const _ as usize; - if ptr % 2 != 0 { - panic!("data is not aligned to a 2 byte boundary"); - } - if data.len() != size.number_of_tiles() * BYTES_PER_TILE_4BPP { - panic!( - "data is not of expected length, got {} expected {}", - data.len(), - size.number_of_tiles() * BYTES_PER_TILE_4BPP - ); - } - DynamicSprite { data, size } - } - - #[must_use] - /// Tries to copy the sprite to vram to be used to set object sprites. - /// Returns None if there is no room in sprite vram. - pub fn try_vram(&self, palette: PaletteVram) -> Option { - Some(SpriteBorrow { - sprite: unsafe { SpriteVram::new(self.data, self.size, palette)? }, - }) - } - - #[must_use] - /// Tries to copy the sprite to vram to be used to set object sprites. - /// Panics if there is no room in sprite vram. - pub fn to_vram(&self, palette: PaletteVram) -> SpriteBorrow { - self.try_vram(palette) - .expect("No slot for sprite available") - } -} - -/// The sizes of sprite supported by the GBA. -#[derive(Clone, Copy, PartialEq, Eq)] -#[allow(missing_docs)] -pub enum Size { - // stored as attr0 attr1 - S8x8 = 0b00_00, - S16x16 = 0b00_01, - S32x32 = 0b00_10, - S64x64 = 0b00_11, - - S16x8 = 0b01_00, - S32x8 = 0b01_01, - S32x16 = 0b01_10, - S64x32 = 0b01_11, - - S8x16 = 0b10_00, - S8x32 = 0b10_01, - S16x32 = 0b10_10, - S32x64 = 0b10_11, -} - -#[doc(hidden)] -#[repr(C)] // guarantee 'bytes' comes after '_align' -pub struct AlignedAs { - pub _align: [Align; 0], - pub bytes: Bytes, -} - -#[doc(hidden)] -#[macro_export] -macro_rules! align_bytes { - ($align_ty:ty, $data:literal) => {{ - use $crate::display::object::AlignedAs; - - const ALIGNED: &AlignedAs<$align_ty, [u8]> = &AlignedAs { - _align: [], - bytes: *$data, - }; - - &ALIGNED.bytes - }}; -} - -/// Includes sprites found in the referenced aseprite files. Can include -/// multiple at once and optimises palettes of all included in the single call -/// together. See [Size] for supported sizes. Returns a reference to [Graphics]. -/// -/// ```rust,no_run -/// # #![no_std] -/// # #![no_main] -/// # use agb::{display::object::Graphics, include_aseprite}; -/// const GRAPHICS: &Graphics = include_aseprite!( -/// "examples/gfx/boss.aseprite", -/// "examples/gfx/objects.aseprite" -/// ); -/// ``` -/// The tags from the aseprite file are included so you can refer to sprites by -/// name in code. You should ensure tags are unique as this is not enforced by -/// aseprite. -/// -#[macro_export] -macro_rules! include_aseprite { - ($($aseprite_path: expr),*) => {{ - use $crate::display::object::{Size, Sprite, Tag, TagMap, Graphics}; - use $crate::display::palette16::Palette16; - use $crate::align_bytes; - - $crate::include_aseprite_inner!($($aseprite_path),*); - - &Graphics::new(SPRITES, TAGS) - }}; -} - -/// Stores sprite and tag data returned by [include_aseprite]. -pub struct Graphics { - sprites: &'static [Sprite], - tag_map: &'static TagMap, -} - -impl Graphics { - #[doc(hidden)] - /// Creates graphics data from sprite data and a tag_map. This is used - /// internally by [include_aseprite] and would be otherwise difficult to - /// use. - #[must_use] - pub const fn new(sprites: &'static [Sprite], tag_map: &'static TagMap) -> Self { - Self { sprites, tag_map } - } - #[must_use] - /// Gets the tag map from the aseprite files. This allows reference to - /// sprite sequences by name. - pub const fn tags(&self) -> &TagMap { - self.tag_map - } - /// Gets a big list of the sprites themselves. Using tags is often easier. - #[must_use] - pub const fn sprites(&self) -> &[Sprite] { - self.sprites - } -} - -/// Stores aseprite tags. Can be used to refer to animation sequences by name. -/// ```rust,no_run -/// # #![no_std] -/// # #![no_main] -/// # use agb::{display::object::{Graphics, Tag}, include_aseprite}; -/// const GRAPHICS: &Graphics = include_aseprite!( -/// "examples/gfx/boss.aseprite", -/// "examples/gfx/objects.aseprite" -/// ); -/// -/// const EMU_WALK: &Tag = GRAPHICS.tags().get("emu-walk"); -/// ``` -/// This being the whole animation associated with the walk sequence of the emu. -/// See [Tag] for details on how to use this. -pub struct TagMap { - tags: &'static [(&'static str, Tag)], -} - -const fn const_byte_compare(a: &[u8], b: &[u8]) -> bool { - if a.len() != b.len() { - return false; - } - - let mut i = 0; - while i < a.len() { - if a[i] != b[i] { - return false; - } - i += 1; - } - true -} - -impl TagMap { - #[doc(hidden)] - /// Creates a new tag map from (name, Tag) pairs. Used internally by - /// [include_aseprite] and should not really be used outside of it. - #[must_use] - pub const fn new(tags: &'static [(&'static str, Tag)]) -> TagMap { - Self { tags } - } - - #[doc(hidden)] - /// Attempts to get a tag. Generally should not be used. - #[must_use] - pub const fn try_get(&'static self, tag: &str) -> Option<&'static Tag> { - let mut i = 0; - while i < self.tags.len() { - let s = self.tags[i].0; - if const_byte_compare(s.as_bytes(), tag.as_bytes()) { - return Some(&self.tags[i].1); - } - - i += 1; - } - - None - } - - /// Gets a tag associated with the name. A tag in aseprite refers to a - /// sequence of sprites with some metadata for how to animate it. You should - /// call this in a constant context so it is evaluated at compile time. It - /// is inefficient to call this elsewhere. - /// ```rust,no_run - /// # #![no_std] - /// # #![no_main] - /// # use agb::{display::object::{Graphics, Tag}, include_aseprite}; - /// const GRAPHICS: &Graphics = include_aseprite!( - /// "examples/gfx/boss.aseprite", - /// "examples/gfx/objects.aseprite" - /// ); - /// - /// const EMU_WALK: &Tag = GRAPHICS.tags().get("emu-walk"); - /// ``` - /// - /// See [Tag] for more details. - #[must_use] - pub const fn get(&'static self, tag: &str) -> &'static Tag { - let t = self.try_get(tag); - match t { - Some(t) => t, - None => panic!("The requested tag does not exist"), - } - } - - /// Takes an iterator over all the tags in the map. Not generally useful. - pub fn values(&self) -> impl Iterator { - self.tags.iter().map(|x| &x.1) - } -} - -#[derive(Clone, Copy)] -enum Direction { - Forward, - Backward, - PingPong, -} - -impl Direction { - const fn from_usize(a: usize) -> Self { - match a { - 0 => Direction::Forward, - 1 => Direction::Backward, - 2 => Direction::PingPong, - _ => panic!("Invalid direction, this is a bug in image converter or agb"), - } - } -} - -/// A sequence of sprites from aseprite. -pub struct Tag { - sprites: *const Sprite, - len: usize, - direction: Direction, -} - -impl Tag { - /// The individual sprites that make up the animation themselves. - #[must_use] - pub fn sprites(&self) -> &'static [Sprite] { - unsafe { slice::from_raw_parts(self.sprites, self.len) } - } - - /// A single sprite referred to by index in the animation sequence. - #[must_use] - pub const fn sprite(&self, idx: usize) -> &'static Sprite { - if idx >= self.len { - panic!("out of bounds access to sprite"); - } - unsafe { &*self.sprites.add(idx) } - } - - /// A sprite that follows the animation sequence. For instance, in aseprite - /// tags can be specified to animate: - /// * Forward - /// * Backward - /// * Ping pong - /// - /// This takes the animation type in account and returns the correct sprite - /// following these requirements. - #[inline] - #[must_use] - pub fn animation_sprite(&self, idx: usize) -> &'static Sprite { - let len_sub_1 = self.len - 1; - match self.direction { - Direction::Forward => self.sprite(idx % self.len), - Direction::Backward => self.sprite(len_sub_1 - (idx % self.len)), - Direction::PingPong => self.sprite( - (((idx + len_sub_1) % (len_sub_1 * 2)) as isize - len_sub_1 as isize) - .unsigned_abs(), - ), - } - } - - #[doc(hidden)] - /// Creates a new sprite from it's constituent parts. Used internally by - /// [include_aseprite] and should generally not be used elsewhere. - #[must_use] - pub const fn new(sprites: &'static [Sprite], from: usize, to: usize, direction: usize) -> Self { - assert!(from <= to); - assert!(to < sprites.len()); - Self { - sprites: &sprites[from] as *const Sprite, - len: to - from + 1, - direction: Direction::from_usize(direction), - } - } -} - -impl Size { - const fn number_of_tiles(self) -> usize { - match self { - Size::S8x8 => 1, - Size::S16x16 => 4, - Size::S32x32 => 16, - Size::S64x64 => 64, - Size::S16x8 => 2, - Size::S32x8 => 4, - Size::S32x16 => 8, - Size::S64x32 => 32, - Size::S8x16 => 2, - Size::S8x32 => 4, - Size::S16x32 => 8, - Size::S32x64 => 32, - } - } - const fn shape_size(self) -> (u8, u8) { - (self as u8 >> 2, self as u8 & 0b11) - } - - fn layout(self) -> Layout { - Layout::from_size_align(self.number_of_tiles() * BYTES_PER_TILE_4BPP, 8).unwrap() - } - - #[must_use] - /// Creates a size from width and height in pixels, panics if the width and - /// height is not representable by GBA sprites. - pub const fn from_width_height(width: usize, height: usize) -> Self { - match (width, height) { - (8, 8) => Size::S8x8, - (16, 16) => Size::S16x16, - (32, 32) => Size::S32x32, - (64, 64) => Size::S64x64, - (16, 8) => Size::S16x8, - (32, 8) => Size::S32x8, - (32, 16) => Size::S32x16, - (64, 32) => Size::S64x32, - (8, 16) => Size::S8x16, - (8, 32) => Size::S8x32, - (16, 32) => Size::S16x32, - (32, 64) => Size::S32x64, - (_, _) => panic!("Bad width and height!"), - } - } - - #[must_use] - /// Returns the width and height of the size in pixels. - pub const fn to_width_height(self) -> (usize, usize) { - match self { - Size::S8x8 => (8, 8), - Size::S16x16 => (16, 16), - Size::S32x32 => (32, 32), - Size::S64x64 => (64, 64), - Size::S16x8 => (16, 8), - Size::S32x8 => (32, 8), - Size::S32x16 => (32, 16), - Size::S64x32 => (64, 32), - Size::S8x16 => (8, 16), - Size::S8x32 => (8, 32), - Size::S16x32 => (16, 32), - Size::S32x64 => (32, 64), - } - } -} - -/// A reference to a sprite that is currently copied in vram. This is reference -/// counted and can be cloned to keep it in vram or otherwise manipulated. If -/// objects no longer refer to this sprite, then it's vram slot is freed for the -/// next sprite. This is obtained from the [ObjectController]. -#[derive(Clone)] -pub struct SpriteBorrow { - sprite: SpriteVram, -} - -#[derive(PartialEq, Eq)] -struct Attributes { - a0: ObjectAttribute0, - a1s: ObjectAttribute1Standard, - a1a: ObjectAttribute1Affine, - a2: ObjectAttribute2, -} - -impl Attributes { - fn new() -> Self { - Self { - a0: ObjectAttribute0::new(), - a1s: ObjectAttribute1Standard::new(), - a1a: ObjectAttribute1Affine::new(), - a2: ObjectAttribute2::new(), - } - } - - fn commit(&self, location: usize) { - let mode = self.a0.object_mode(); - let attrs: [[u8; 2]; 3] = match mode { - ObjectMode::Normal => [ - self.a0.into_bytes(), - self.a1s.into_bytes(), - self.a2.into_bytes(), - ], - _ => [ - self.a0.into_bytes(), - self.a1a.into_bytes(), - self.a2.into_bytes(), - ], - }; - - unsafe { - let attrs: [u16; 3] = core::mem::transmute(attrs); - let ptr = (OBJECT_ATTRIBUTE_MEMORY as *mut u16).add(location * 4); - - ptr.add(0).write_volatile(attrs[0]); - ptr.add(1).write_volatile(attrs[1]); - ptr.add(2).write_volatile(attrs[2]); - }; - } -} - -/// An object that may be displayed on screen. The object can be modified using -/// the available methods. This is obtained from the [ObjectController]. -pub struct Object<'a> { - loan: Loan<'a>, -} - -#[derive(Clone, Copy)] -struct Location(usize); - -impl Location { - fn from_sprite_ptr(d: NonNull) -> Self { - Self(((d.as_ptr() as usize) - TILE_SPRITE) / BYTES_PER_TILE_4BPP) - } - fn from_palette_ptr(d: NonNull) -> Self { - Self((d.as_ptr() as usize - PALETTE_SPRITE) / Palette16::layout().size()) - } - fn as_palette_ptr(self) -> *mut u8 { - (self.0 * Palette16::layout().size() + PALETTE_SPRITE) as *mut u8 - } - fn as_sprite_ptr(self) -> *mut u8 { - (self.0 * BYTES_PER_TILE_4BPP + TILE_SPRITE) as *mut u8 - } -} - -#[derive(Clone)] -/// The palette data in Vram, this is reference counted and the palette data is -/// removed and can be reused from vram when no strong references remain. -pub struct PaletteVram(Rc); - -#[derive(Clone)] -struct SpriteVram(Rc); - -struct PaletteData { - location: Location, -} - -impl PaletteVram { - /// Creates a palette in vram from the given palette. Can be used to create - /// sprites in vram in the [DynamicSprite] functions. - #[must_use] - pub fn new(palette: &Palette16) -> Option { - let dest = unsafe { PALETTE_ALLOCATOR.alloc(Palette16::layout())? }; - - unsafe { - dma::dma_copy16( - palette.colours.as_ptr().cast(), - dest.as_ptr().cast(), - palette.colours.len(), - ); - } - - Some(PaletteVram(Rc::new(PaletteData { - location: Location::from_palette_ptr(dest), - }))) - } -} - -impl SpriteVram { - /// # Safety - /// data should be aligned to a 2 byte boundary - unsafe fn new(data: &[u8], size: Size, palette: PaletteVram) -> Option { - let dest = unsafe { SPRITE_ALLOCATOR.alloc(size.layout())? }; - - unsafe { - dma::dma_copy16(data.as_ptr().cast(), dest.as_ptr().cast(), data.len() / 2); - } - - Some(SpriteVram(Rc::new(SpriteArena { - location: Location::from_sprite_ptr(dest), - size, - palette, - }))) - } -} - -impl Drop for PaletteData { - fn drop(&mut self) { - unsafe { PALETTE_ALLOCATOR.dealloc(self.location.as_palette_ptr(), Palette16::layout()) }; - } -} - -struct SpriteArena { - location: Location, - size: Size, - palette: PaletteVram, -} - -impl Drop for SpriteArena { - fn drop(&mut self) { - unsafe { SPRITE_ALLOCATOR.dealloc(self.location.as_sprite_ptr(), self.size.layout()) } - } -} - -#[derive(Default)] -struct SpriteControllerInner { - static_palette_map: HashMap>, - static_sprite_map: HashMap>, -} - -struct Loan<'a> { - index: u8, - controller: ObjectControllerReference<'a>, -} - -impl Drop for Loan<'_> { - fn drop(&mut self) { - let mut s = unsafe { self.controller.borrow_mut() }; - - unsafe { - s.shadow_oam[self.index as usize] - .as_mut() - .unwrap_unchecked() - .destroy = true; - }; - } -} - -struct ObjectInner { - attrs: Attributes, - sprite: SpriteBorrow, - previous_sprite: SpriteBorrow, - destroy: bool, - z: i32, -} - -struct ObjectControllerStatic { - _free_affine_matrices: Vec, - free_object: Vec, - shadow_oam: Vec>, - z_order: Vec, - sprite_controller: SpriteControllerInner, -} - -impl ObjectControllerStatic { - unsafe fn new() -> Self { - Self { - shadow_oam: (0..128).map(|_| None).collect(), - z_order: (0..128).collect(), - free_object: (0..128).collect(), - _free_affine_matrices: (0..32).collect(), - sprite_controller: SpriteControllerInner::new(), - } - } - - fn update_z_ordering(&mut self) { - let shadow_oam = &self.shadow_oam; - self.z_order - .sort_by_key(|&a| shadow_oam[a as usize].as_ref().map_or(i32::MAX, |s| s.z)); - } -} - -/// A controller that distributes objects and sprites. This controls sprites and -/// objects being copied to vram when it needs to be. -pub struct ObjectController<'gba> { - phantom: PhantomData<&'gba ()>, - inner: ObjectControllerReference<'static>, -} - -impl<'gba> Drop for ObjectController<'gba> { - fn drop(&mut self) { - unsafe { - ObjectControllerReference::uninit(); - } - } -} - -const HIDDEN_VALUE: u16 = 0b10 << 8; - -impl<'gba> ObjectController<'gba> { - /// Commits the objects to vram and delete sprites where possible. This - /// should be called shortly after having waited for the next vblank to - /// ensure what is displayed on screen doesn't change part way through. - pub fn commit(&self) { - let mut s = unsafe { self.inner.borrow_mut() }; - - let s = &mut *s; - - for (i, &z) in s.z_order.iter().enumerate() { - if let Some(o) = &mut s.shadow_oam[z as usize] { - if o.destroy { - s.free_object.push(z); - - unsafe { - (OBJECT_ATTRIBUTE_MEMORY as *mut u16) - .add(i * 4) - .write_volatile(HIDDEN_VALUE); - } - - let _ = unsafe { s.shadow_oam[z as usize].take().unwrap_unchecked() }; - } else { - o.attrs.commit(i); - o.previous_sprite = o.sprite.clone(); - } - } else { - unsafe { - (OBJECT_ATTRIBUTE_MEMORY as *mut u16) - .add(i * 4) - .write_volatile(HIDDEN_VALUE); - } - } - } - - s.sprite_controller.gc(); - } - - pub(crate) fn new() -> Self { - DISPLAY_CONTROL.set_bits(1, 1, 0x6); - DISPLAY_CONTROL.set_bits(1, 1, 0xC); - DISPLAY_CONTROL.set_bits(0, 1, 0x7); - - for i in 0..128 { - unsafe { - (OBJECT_ATTRIBUTE_MEMORY as *mut u16) - .add(i * 4) - .write_volatile(HIDDEN_VALUE); - } - } - - Self { - phantom: PhantomData, - inner: unsafe { ObjectControllerReference::init() }, - } - } - - #[must_use] - /// Creates an object with it's initial sprite being the sprite reference. - /// Panics if there is no space for the sprite or if there are no free - /// objects. This will reuse an existing copy of the sprite in vram if - /// possible. - /// ```rust,no_run - /// # #![no_std] - /// # #![no_main] - /// # use agb::{display::object::{Graphics, Tag}, include_aseprite}; - /// const GRAPHICS: &Graphics = include_aseprite!( - /// "examples/gfx/boss.aseprite", - /// "examples/gfx/objects.aseprite" - /// ); - /// - /// const EMU_WALK: &Tag = GRAPHICS.tags().get("emu-walk"); - /// - /// # fn foo(gba: &mut agb::Gba) { - /// # let object_controller = gba.display.object.get(); - /// let emu = object_controller.object_sprite(EMU_WALK.animation_sprite(0)); - /// # } - /// ``` - pub fn object_sprite<'a>(&'a self, sprite: &'static Sprite) -> Object<'a> { - let sprite = self.sprite(sprite); - self.object(sprite) - } - - #[must_use] - /// Creates an object with it's initial sprite being the sprite reference. - /// Returns [None] if the sprite or object could not be allocated. This will - /// reuse an existing copy of the sprite in vram if possible. - /// ```rust,no_run - /// # #![no_std] - /// # #![no_main] - /// # use agb::{display::object::{Graphics, Tag}, include_aseprite}; - /// const GRAPHICS: &Graphics = include_aseprite!( - /// "examples/gfx/boss.aseprite", - /// "examples/gfx/objects.aseprite" - /// ); - /// - /// const EMU_WALK: &Tag = GRAPHICS.tags().get("emu-walk"); - /// - /// # fn foo(gba: &mut agb::Gba) { - /// # let object_controller = gba.display.object.get(); - /// let emu = object_controller.try_get_object_sprite( - /// EMU_WALK.animation_sprite(0) - /// ).expect("the sprite or object could be allocated"); - /// # } - /// ``` - pub fn try_get_object_sprite<'a>(&'a self, sprite: &'static Sprite) -> Option> { - let sprite = self.try_get_sprite(sprite)?; - self.try_get_object(sprite) - } - - /// Creates an object with it's initial sprite being what is in the - /// [SpriteBorrow]. Panics if there are no objects left. A [SpriteBorrow] is - /// created using the [ObjectController::sprite] function. - /// ```rust,no_run - /// # #![no_std] - /// # #![no_main] - /// # use agb::{display::object::{Graphics, Tag}, include_aseprite}; - /// const GRAPHICS: &Graphics = include_aseprite!( - /// "examples/gfx/boss.aseprite", - /// "examples/gfx/objects.aseprite" - /// ); - /// - /// const EMU_WALK: &Tag = GRAPHICS.tags().get("emu-walk"); - /// - /// # fn foo(gba: &mut agb::Gba) { - /// # let object_controller = gba.display.object.get(); - /// let emu = object_controller.object(object_controller.sprite(EMU_WALK.animation_sprite(0))); - /// # } - /// ``` - #[must_use] - pub fn object(&self, sprite: SpriteBorrow) -> Object { - self.try_get_object(sprite).expect("No object available") - } - - /// Creates an object with it's initial sprite being what is in the - /// [SpriteBorrow]. A [SpriteBorrow] is created using the - /// [ObjectController::try_get_sprite] function. - /// ```rust,no_run - /// # #![no_std] - /// # #![no_main] - /// # use agb::{display::object::{Graphics, Tag}, include_aseprite}; - /// const GRAPHICS: &Graphics = include_aseprite!( - /// "examples/gfx/boss.aseprite", - /// "examples/gfx/objects.aseprite" - /// ); - /// - /// const EMU_WALK: &Tag = GRAPHICS.tags().get("emu-walk"); - /// - /// # fn foo(gba: &mut agb::Gba) { - /// # let object_controller = gba.display.object.get(); - /// let emu = object_controller.try_get_object( - /// object_controller.sprite(EMU_WALK.animation_sprite(0)) - /// ).expect("the object should be allocatable"); - /// # } - /// ``` - #[must_use] - pub fn try_get_object(&self, sprite: SpriteBorrow) -> Option { - let mut s = unsafe { self.inner.borrow_mut() }; - - let mut attrs = Attributes::new(); - - attrs.a2.set_tile_index(sprite.sprite.0.location.0 as u16); - let shape_size = sprite.sprite.0.size.shape_size(); - attrs - .a2 - .set_palette_bank((sprite.sprite.0.palette.0.location.0) as u8); - attrs.a0.set_shape(shape_size.0); - attrs.a1a.set_size(shape_size.1); - attrs.a1s.set_size(shape_size.1); - - let index = s.free_object.pop()?; - - s.shadow_oam[index as usize] = Some(ObjectInner { - attrs, - z: 0, - previous_sprite: sprite.clone(), - destroy: false, - sprite, - }); - - let loan = Loan { - index, - controller: self.inner, - }; - - s.update_z_ordering(); - - Some(Object { loan }) - } - - /// Creates a [SpriteBorrow] from the given sprite, panics if the sprite - /// could not be allocated. This will reuse an existing copy of the sprite - /// in vram if possible. - /// ```rust,no_run - /// # #![no_std] - /// # #![no_main] - /// # use agb::{display::object::{Graphics, Tag}, include_aseprite}; - /// const GRAPHICS: &Graphics = include_aseprite!( - /// "examples/gfx/boss.aseprite", - /// "examples/gfx/objects.aseprite" - /// ); - /// - /// const EMU_WALK: &Tag = GRAPHICS.tags().get("emu-walk"); - /// - /// # fn foo(gba: &mut agb::Gba) { - /// # let object_controller = gba.display.object.get(); - /// let sprite = object_controller.sprite(EMU_WALK.animation_sprite(0)); - /// # } - /// ``` - #[must_use] - pub fn sprite(&self, sprite: &'static Sprite) -> SpriteBorrow { - self.try_get_sprite(sprite) - .expect("No slot for sprite available") - } - - /// Creates a [SpriteBorrow] from the given sprite. This will reuse an - /// existing copy of the sprite in vram if possible. - /// ```rust,no_run - /// # #![no_std] - /// # #![no_main] - /// # use agb::{display::object::{Graphics, Tag}, include_aseprite}; - /// const GRAPHICS: &Graphics = include_aseprite!( - /// "examples/gfx/boss.aseprite", - /// "examples/gfx/objects.aseprite" - /// ); - /// - /// const EMU_WALK: &Tag = GRAPHICS.tags().get("emu-walk"); - /// - /// # fn foo(gba: &mut agb::Gba) { - /// # let object_controller = gba.display.object.get(); - /// let sprite = object_controller.try_get_sprite( - /// EMU_WALK.animation_sprite(0) - /// ).expect("the sprite should be allocatable"); - /// # } - /// ``` - #[must_use] - pub fn try_get_sprite(&self, sprite: &'static Sprite) -> Option { - unsafe { self.inner.borrow_mut() } - .sprite_controller - .try_get_sprite(sprite) - } -} - -impl<'a> Object<'a> { - #[inline(always)] - unsafe fn object_inner(&self) -> impl DerefMut + 'a { - #[cfg(debug_assertions)] - { - core::cell::RefMut::map(self.loan.controller.borrow_cell_ref(), |s| { - s.shadow_oam[self.loan.index as usize] - .as_mut() - .unwrap_unchecked() - }) - } - #[cfg(not(debug_assertions))] - { - self.loan.controller.borrow_direct().shadow_oam[self.loan.index as usize] - .as_mut() - .unwrap_unchecked() - } - } - - /// Swaps out the current sprite. This handles changing of size, palette, - /// etc. No change will be seen until [ObjectController::commit] is called. - pub fn set_sprite(&'_ mut self, sprite: SpriteBorrow) { - let mut object_inner = unsafe { self.object_inner() }; - object_inner - .attrs - .a2 - .set_tile_index(sprite.sprite.0.location.0 as u16); - let shape_size = sprite.sprite.0.size.shape_size(); - object_inner - .attrs - .a2 - .set_palette_bank(sprite.sprite.0.palette.0.location.0 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); - - object_inner.sprite = sprite; - } - - /// Shows the sprite. No change will be seen until - /// [ObjectController::commit] is called. - pub fn show(&mut self) -> &mut Self { - { - let mut object_inner = unsafe { self.object_inner() }; - object_inner.attrs.a0.set_object_mode(ObjectMode::Normal); - } - - self - } - - /// Controls whether the sprite is flipped horizontally, for example useful - /// for reusing the same sprite for the left and right walking directions. - /// No change will be seen until [ObjectController::commit] is called. - pub fn set_hflip(&mut self, flip: bool) -> &mut Self { - { - let mut object_inner = unsafe { self.object_inner() }; - object_inner.attrs.a1s.set_horizontal_flip(flip); - } - self - } - - /// Controls whether the sprite is flipped vertically, for example useful - /// for reusing the same sprite for the up and down walking directions. No - /// change will be seen until [ObjectController::commit] is called. - pub fn set_vflip(&mut self, flip: bool) -> &mut Self { - { - let mut object_inner = unsafe { self.object_inner() }; - object_inner.attrs.a1s.set_vertical_flip(flip); - } - self - } - - /// Sets the x position of the object. The coordinate refers to the top-left - /// corner of the sprite. No change will be seen until - /// [ObjectController::commit] is called. - pub fn set_x(&mut self, x: u16) -> &mut Self { - { - let mut object_inner = unsafe { self.object_inner() }; - object_inner.attrs.a1a.set_x(x.rem_euclid(1 << 9)); - object_inner.attrs.a1s.set_x(x.rem_euclid(1 << 9)); - } - self - } - - /// Sets the z priority of the sprite. Higher priority will be displayed - /// above background layers with lower priorities. No change will be seen - /// until [ObjectController::commit] is called. - pub fn set_priority(&mut self, priority: Priority) -> &mut Self { - { - let mut object_inner = unsafe { self.object_inner() }; - object_inner.attrs.a2.set_priority(priority); - } - self - } - - /// Hides the object. No change will be seen until - /// [ObjectController::commit] is called. - pub fn hide(&mut self) -> &mut Self { - { - let mut object_inner = unsafe { self.object_inner() }; - object_inner.attrs.a0.set_object_mode(ObjectMode::Disabled); - } - self - } - - /// Sets the y position of the sprite. The coordinate refers to the top-left - /// corner of the sprite. No change will be seen until - /// [ObjectController::commit] is called. - pub fn set_y(&mut self, y: u16) -> &mut Self { - { - let mut object_inner = unsafe { self.object_inner() }; - object_inner.attrs.a0.set_y(y as u8); - } - - self - } - - /// Sets the z position of the sprite, this controls which sprites are above - /// each other. No change will be seen until [ObjectController::commit] is - /// called. - pub fn set_z(&mut self, z: i32) -> &mut Self { - { - let mut object_inner = unsafe { self.object_inner() }; - object_inner.z = z; - } - unsafe { self.loan.controller.borrow_mut().update_z_ordering() }; - - self - } - - /// Sets the position of the sprite using a [Vector2D]. The coordinate - /// refers to the top-left corner of the sprite. No change will be seen - /// until [ObjectController::commit] is called. - pub fn set_position(&mut self, position: Vector2D) -> &mut Self { - { - let mut object_inner = unsafe { self.object_inner() }; - object_inner.attrs.a0.set_y(position.y as u8); - object_inner - .attrs - .a1a - .set_x(position.x.rem_euclid(1 << 9) as u16); - object_inner - .attrs - .a1s - .set_x(position.x.rem_euclid(1 << 9) as u16); - } - self - } -} - -/// The Sprite Id is a thin wrapper around the pointer to the sprite in -/// rom and is therefore a unique identifier to a sprite -#[derive(Clone, Copy, PartialEq, Eq, Hash)] -struct SpriteId(usize); - -/// The palette id is a thin wrapper around the pointer to the palette in rom -/// and is therefore a unique reference to a palette -#[derive(Copy, Clone, PartialEq, Eq, Hash)] -struct PaletteId(usize); - -impl Palette16 { - fn id(&'static self) -> PaletteId { - PaletteId(self as *const _ as usize) - } - const fn layout() -> Layout { - Layout::new::() - } -} - -impl Sprite { - fn id(&'static self) -> SpriteId { - SpriteId(self as *const _ as usize) - } - fn layout(&self) -> Layout { - Layout::from_size_align(self.size.number_of_tiles() * BYTES_PER_TILE_4BPP, 8).unwrap() - } - #[doc(hidden)] - /// Creates a sprite from it's constituent data, used internally by - /// [include_aseprite] and should generally not be used outside it. - /// - /// # Safety - /// The data should be aligned to a 2 byte boundary - #[must_use] - pub const unsafe fn new(palette: &'static Palette16, data: &'static [u8], size: Size) -> Self { - Self { - palette, - data, - size, - } - } - #[must_use] - /// The size of the sprite in it's form that is displayable on the GBA. - pub const fn size(&self) -> Size { - self.size - } -} - -impl SpriteControllerInner { - fn try_get_sprite(&mut self, sprite: &'static Sprite) -> Option { - let id = sprite.id(); - if let Some(storage) = self.static_sprite_map.get_mut(&id) { - if let Some(strong) = storage.upgrade() { - return Some(SpriteBorrow { - sprite: SpriteVram(strong), - }); - } - } - - // layout is non zero sized, so this is safe to call - - let palette_location = self.palette(sprite.palette); - let palette_location = match palette_location { - Some(a) => a, - None => { - return None; - } - }; - - let dest = unsafe { SPRITE_ALLOCATOR.alloc(sprite.layout())? }; - - unsafe { - dma::dma_copy16( - sprite.data.as_ptr().cast(), - dest.as_ptr().cast(), - sprite.data.len() / 2, - ); - } - - let sprite = SpriteVram(Rc::new(SpriteArena { - location: Location::from_sprite_ptr(dest), - size: sprite.size(), - palette: palette_location, - })); - - self.static_sprite_map.insert(id, Rc::downgrade(&sprite.0)); - - Some(SpriteBorrow { sprite }) - } - - /// Cleans up weak references to sprites and palettes no longer in vram - fn gc(&mut self) { - self.static_palette_map.retain(|_, v| v.strong_count() != 0); - self.static_sprite_map.retain(|_, v| v.strong_count() != 0); - } - - fn new() -> Self { - Default::default() - } - fn palette(&mut self, palette: &'static Palette16) -> Option { - let id = palette.id(); - if let Some(storage) = self.static_palette_map.get(&id) { - if let Some(up) = storage.upgrade() { - return Some(PaletteVram(up)); - } - } - - let palette_vram = PaletteVram::new(palette)?; - - self.static_palette_map - .insert(id, Rc::downgrade(&palette_vram.0)); - - Some(palette_vram) - } -} - -#[derive(BitfieldSpecifier, Clone, Copy)] -enum ObjectMode { - Normal, - Affine, - Disabled, - AffineDouble, -} - -#[derive(BitfieldSpecifier, Clone, Copy)] -#[bits = 2] -enum GraphicsMode { - Normal, - AlphaBlending, - Window, -} - -#[derive(BitfieldSpecifier, Clone, Copy)] -enum ColourMode { - Four, - Eight, -} - -// this mod is not public, so the internal parts don't need documenting. -#[allow(dead_code)] -mod attributes { - use super::*; - #[bitfield] - #[derive(Clone, Copy, PartialEq, Eq)] - pub(super) struct ObjectAttribute0 { - pub y: B8, - pub object_mode: ObjectMode, - pub graphics_mode: GraphicsMode, - pub mosaic: bool, - pub colour_mode: ColourMode, - pub shape: B2, - } - - #[bitfield] - #[derive(Clone, Copy, PartialEq, Eq)] - pub(super) struct ObjectAttribute1Standard { - pub x: B9, - #[skip] - __: B3, - pub horizontal_flip: bool, - pub vertical_flip: bool, - pub size: B2, - } - - #[bitfield] - #[derive(Clone, Copy, PartialEq, Eq)] - pub(super) struct ObjectAttribute1Affine { - pub x: B9, - pub affine_index: B5, - pub size: B2, - } - - #[bitfield] - #[derive(Clone, Copy, PartialEq, Eq)] - pub(super) struct ObjectAttribute2 { - pub tile_index: B10, - pub priority: Priority, - pub palette_bank: B4, - } -} - -#[cfg(test)] -mod tests { - use super::*; - use core::mem::size_of; - - #[test_case] - fn size_of_ObjectControllerReference(_: &mut crate::Gba) { - if !cfg!(debug_assertions) { - assert_eq!(size_of::(), 0); - } - } - - #[test_case] - fn object_usage(gba: &mut crate::Gba) { - const GRAPHICS: &Graphics = include_aseprite!( - "../examples/the-purple-night/gfx/objects.aseprite", - "../examples/the-purple-night/gfx/boss.aseprite" - ); - - const BOSS: &Tag = GRAPHICS.tags().get("Boss"); - const EMU: &Tag = GRAPHICS.tags().get("emu - idle"); - - let object = gba.display.object.get(); - - { - let mut objects: Vec<_> = alloc::vec![ - object.object(object.sprite(BOSS.sprite(0))), - object.object(object.sprite(EMU.sprite(0))), - ] - .into_iter() - .map(Some) - .collect(); - - object.commit(); - - let x = objects[0].as_mut().unwrap(); - x.set_hflip(true); - x.set_vflip(true); - x.set_position((1, 1).into()); - x.set_z(100); - x.set_sprite(object.sprite(BOSS.sprite(2))); - - object.commit(); - } - - object.commit(); - } +pub(super) unsafe fn initilise_oam() { + for i in 0..128 { + let ptr = (OBJECT_ATTRIBUTE_MEMORY as *mut u16).add(i * 4); + ptr.write_volatile(0b10 << 8); + } + + DISPLAY_CONTROL.set_bits(1, 1, 0x6); + DISPLAY_CONTROL.set_bits(1, 1, 0xC); + DISPLAY_CONTROL.set_bits(0, 1, 0x7); } diff --git a/agb/src/display/object/managed.rs b/agb/src/display/object/managed.rs new file mode 100644 index 00000000..9c4d889d --- /dev/null +++ b/agb/src/display/object/managed.rs @@ -0,0 +1,386 @@ +use core::{ + cell::{Cell, UnsafeCell}, + marker::PhantomData, +}; + +use agb_fixnum::Vector2D; +use slotmap::{new_key_type, SlotMap}; + +use crate::display::Priority; + +use super::{AffineMode, SpriteVram, UnmanagedOAM, UnmanagedObject}; + +new_key_type! {struct ObjectKey; } + +#[derive(Clone, Copy)] +struct Ordering { + next: Option, + previous: Option, +} + +struct ObjectItem { + object: UnsafeCell, + z_order: Cell, + z_index: Cell, +} + +struct Store { + store: UnsafeCell>, + first_z: Cell>, +} + +struct StoreIterator<'store> { + store: &'store slotmap::SlotMap, + current: Option, +} + +impl<'store> Iterator for StoreIterator<'store> { + type Item = &'store ObjectItem; + + fn next(&mut self) -> Option { + let to_output = &self.store[self.current?]; + self.current = to_output.z_order.get().next; + Some(to_output) + } +} + +impl Store { + /// SAFETY: while this exists, no other store related operations should be + /// performed. Notably this means you shouldn't drop the ObjectItem as this + /// implementation will touch this. + unsafe fn iter(&self) -> StoreIterator { + StoreIterator { + store: unsafe { &*self.store.get() }, + current: self.first_z.get(), + } + } + + fn is_all_ordered_right(&self) -> bool { + let mut previous_z = i32::MIN; + let mut current_index = self.first_z.get(); + + while let Some(ci) = current_index { + let obj = self.get_object(ci); + let this_z = obj.z_index.get(); + if this_z < previous_z { + return false; + } + previous_z = this_z; + current_index = obj.z_order.get().next; + } + + true + } + + fn insert_object(&self, object: UnmanagedObject) -> Object { + let object_item = ObjectItem { + object: UnsafeCell::new(object), + z_order: Cell::new(Ordering { + next: None, + previous: None, + }), + z_index: Cell::new(0), + }; + let idx = { + let data = unsafe { &mut *self.store.get() }; + data.insert(object_item) + }; + + if let Some(first) = self.first_z.get() { + let mut this_index = first; + while self.get_object(this_index).z_index.get() < 0 { + if let Some(idx) = self.get_object(this_index).z_order.get().next { + this_index = idx; + } else { + break; + } + } + if self.get_object(this_index).z_index.get() < 0 { + add_after_element(self, idx, this_index); + } else { + add_before_element(self, idx, this_index); + } + } else { + self.first_z.set(Some(idx)); + } + + Object { + me: idx, + store: self, + } + } + + fn remove_object(&self, object: ObjectKey) { + remove_from_linked_list(self, object); + + let data = unsafe { &mut *self.store.get() }; + data.remove(object); + } + + fn get_object(&self, key: ObjectKey) -> &ObjectItem { + &(unsafe { &*self.store.get() }[key]) + } +} + +pub struct OAMManager<'gba> { + phantom: PhantomData<&'gba ()>, + object_store: Store, +} + +impl OAMManager<'_> { + pub(crate) fn new() -> Self { + Self { + phantom: PhantomData, + object_store: Store { + store: UnsafeCell::new(SlotMap::with_key()), + first_z: Cell::new(None), + }, + } + } + + pub fn commit(&self) { + let mut count = 0; + + let mut unmanaged = UnmanagedOAM::new(); + + for (object, mut slot) in unsafe { self.object_store.iter() } + .map(|item| unsafe { &*item.object.get() }) + .filter(|object| object.is_visible()) + .zip(unmanaged.iter()) + { + slot.set(object); + count += 1; + } + + crate::println!("{}", count); + + unmanaged.clear_from(count); + } + + pub fn add_object(&self, sprite: SpriteVram) -> Object<'_> { + self.object_store + .insert_object(UnmanagedObject::new(sprite)) + } +} + +pub struct Object<'controller> { + me: ObjectKey, + store: &'controller Store, +} + +impl Drop for Object<'_> { + fn drop(&mut self) { + self.store.remove_object(self.me); + } +} + +fn remove_from_linked_list(store: &Store, to_remove: ObjectKey) { + let my_current_neighbours = store.get_object(to_remove).z_order.get(); + + if let Some(previous) = my_current_neighbours.previous { + let stored_part = &store.get_object(previous).z_order; + let mut neighbour_left = stored_part.get(); + neighbour_left.next = my_current_neighbours.next; + stored_part.set(neighbour_left); + } else { + store.first_z.set(my_current_neighbours.next); + } + + if let Some(next) = my_current_neighbours.next { + let stored_part = &store.get_object(next).z_order; + let mut neighbour_right = stored_part.get(); + neighbour_right.previous = my_current_neighbours.previous; + stored_part.set(neighbour_right); + } + + store.get_object(to_remove).z_order.set(Ordering { + next: None, + previous: None, + }); +} + +fn add_before_element(store: &Store, elem: ObjectKey, before_this: ObjectKey) { + assert_ne!(elem, before_this); + + let this_element_store = &store.get_object(elem).z_order; + let mut this_element = this_element_store.get(); + + let before_store = &store.get_object(before_this).z_order; + let mut before = before_store.get(); + + if let Some(previous) = before.previous { + let neighbour_left_store = &store.get_object(previous).z_order; + let mut neighbour_left = neighbour_left_store.get(); + neighbour_left.next = Some(elem); + neighbour_left_store.set(neighbour_left); + } else { + store.first_z.set(Some(elem)); + } + this_element.next = Some(before_this); + this_element.previous = before.previous; + + before.previous = Some(elem); + + this_element_store.set(this_element); + before_store.set(before); +} + +fn add_after_element(store: &Store, elem: ObjectKey, after_this: ObjectKey) { + assert_ne!(elem, after_this); + + let this_element_store = &store.get_object(elem).z_order; + let mut this_element = this_element_store.get(); + + let after_store = &store.get_object(after_this).z_order; + let mut after = after_store.get(); + + if let Some(next) = after.next { + let neighbour_left_store = &store.get_object(next).z_order; + let mut neighbour_right = neighbour_left_store.get(); + neighbour_right.previous = Some(elem); + neighbour_left_store.set(neighbour_right); + } + + this_element.previous = Some(after_this); + this_element.next = after.next; + + after.next = Some(elem); + + this_element_store.set(this_element); + after_store.set(after); +} + +fn move_before(store: &Store, source: ObjectKey, before_this: ObjectKey) { + assert_ne!(source, before_this); + + remove_from_linked_list(store, source); + add_before_element(store, source, before_this); +} + +fn move_after(store: &Store, source: ObjectKey, after_this: ObjectKey) { + assert_ne!(source, after_this); + + remove_from_linked_list(store, source); + add_after_element(store, source, after_this); +} + +impl Object<'_> { + pub fn set_z(&mut self, z_index: i32) -> &mut Self { + let my_object = &self.store.get_object(self.me); + + let order = z_index.cmp(&my_object.z_index.get()); + + match order { + core::cmp::Ordering::Equal => {} + core::cmp::Ordering::Less => { + let mut previous_index = self.me; + let mut current_index = self.me; + while self.store.get_object(current_index).z_index.get() > z_index { + previous_index = current_index; + let previous = self.store.get_object(current_index).z_order.get().previous; + if let Some(previous) = previous { + current_index = previous; + } else { + break; + } + } + if previous_index != self.me { + move_before(self.store, self.me, previous_index); + } + } + core::cmp::Ordering::Greater => { + let mut previous_index = self.me; + let mut current_index = self.me; + while self.store.get_object(current_index).z_index.get() < z_index { + previous_index = current_index; + let next = self.store.get_object(current_index).z_order.get().next; + if let Some(next) = next { + current_index = next; + } else { + break; + } + } + if previous_index != self.me { + move_after(self.store, self.me, previous_index); + } + } + } + + my_object.z_index.set(z_index); + + self + } + + fn object(&mut self) -> &mut UnmanagedObject { + unsafe { &mut *self.store.get_object(self.me).object.get() } + } + + fn object_shared(&self) -> &UnmanagedObject { + unsafe { &*self.store.get_object(self.me).object.get() } + } + + #[must_use] + pub fn is_visible(&self) -> bool { + self.object_shared().is_visible() + } + + pub fn show(&mut self) -> &mut Self { + self.object().show(); + + self + } + + pub fn show_affine(&mut self, affine_mode: AffineMode) -> &mut Self { + self.object().show_affine(affine_mode); + + self + } + + pub fn set_hflip(&mut self, flip: bool) -> &mut Self { + self.object().set_hflip(flip); + + self + } + + pub fn set_vflip(&mut self, flip: bool) -> &mut Self { + self.object().set_vflip(flip); + + self + } + + pub fn set_x(&mut self, x: u16) -> &mut Self { + self.object().set_x(x); + + self + } + + pub fn set_priority(&mut self, priority: Priority) -> &mut Self { + self.object().set_priority(priority); + + self + } + + pub fn hide(&mut self) -> &mut Self { + self.object().hide(); + + self + } + + pub fn set_y(&mut self, y: u16) -> &mut Self { + self.object().set_y(y); + + self + } + + pub fn set_position(&mut self, position: Vector2D) -> &mut Self { + self.object().set_position(position); + + self + } + + pub fn set_sprite(&mut self, sprite: SpriteVram) -> &mut Self { + self.object().set_sprite(sprite); + + self + } +} diff --git a/agb/src/display/object/sprites.rs b/agb/src/display/object/sprites.rs new file mode 100644 index 00000000..1c331e46 --- /dev/null +++ b/agb/src/display/object/sprites.rs @@ -0,0 +1,7 @@ +mod sprite; +mod sprite_allocator; + +const BYTES_PER_TILE_4BPP: usize = 32; + +pub use sprite::{include_aseprite, Graphics, Size, Sprite, Tag, TagMap}; +pub use sprite_allocator::{DynamicSprite, SpriteVram, StaticSpriteLoader}; diff --git a/agb/src/display/object/sprites/sprite.rs b/agb/src/display/object/sprites/sprite.rs new file mode 100644 index 00000000..49774897 --- /dev/null +++ b/agb/src/display/object/sprites/sprite.rs @@ -0,0 +1,369 @@ +use core::{alloc::Layout, slice}; + +use crate::display::palette16::Palette16; + +use super::BYTES_PER_TILE_4BPP; + +/// Sprite data. Refers to the palette, pixel data, and the size of the sprite. +pub struct Sprite { + pub(crate) palette: &'static Palette16, + pub(crate) data: &'static [u8], + pub(crate) size: Size, +} + +impl Sprite { + #[doc(hidden)] + /// Creates a sprite from it's constituent data, used internally by + /// [include_aseprite] and should generally not be used outside it. + /// + /// # Safety + /// The data should be aligned to a 2 byte boundary + #[must_use] + pub const unsafe fn new(palette: &'static Palette16, data: &'static [u8], size: Size) -> Self { + Self { + palette, + data, + size, + } + } + + #[must_use] + pub fn size(&self) -> Size { + self.size + } +} + +/// The sizes of sprite supported by the GBA. +#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] +#[allow(missing_docs)] +pub enum Size { + // stored as attr0 attr1 + S8x8 = 0b00_00, + S16x16 = 0b00_01, + S32x32 = 0b00_10, + S64x64 = 0b00_11, + + S16x8 = 0b01_00, + S32x8 = 0b01_01, + S32x16 = 0b01_10, + S64x32 = 0b01_11, + + S8x16 = 0b10_00, + S8x32 = 0b10_01, + S16x32 = 0b10_10, + S32x64 = 0b10_11, +} + +#[doc(hidden)] +#[macro_export] +macro_rules! align_bytes { + ($align_ty:ty, $data:literal) => {{ + #[repr(C)] // guarantee 'bytes' comes after '_align' + struct AlignedAs { + pub _align: [Align; 0], + pub bytes: Bytes, + } + + const ALIGNED: &AlignedAs<$align_ty, [u8]> = &AlignedAs { + _align: [], + bytes: *$data, + }; + + &ALIGNED.bytes + }}; +} + +/// Includes sprites found in the referenced aseprite files. Can include +/// multiple at once and optimises palettes of all included in the single call +/// together. See [Size] for supported sizes. Returns a reference to [Graphics]. +/// +/// ```rust,no_run +/// # #![no_std] +/// # #![no_main] +/// # use agb::{display::object::Graphics, include_aseprite}; +/// const GRAPHICS: &Graphics = include_aseprite!( +/// "examples/gfx/boss.aseprite", +/// "examples/gfx/objects.aseprite" +/// ); +/// ``` +/// The tags from the aseprite file are included so you can refer to sprites by +/// name in code. You should ensure tags are unique as this is not enforced by +/// aseprite. +/// +#[macro_export] +macro_rules! include_aseprite { + ($($aseprite_path: expr),*) => {{ + use $crate::display::object::{Size, Sprite, Tag, TagMap, Graphics}; + use $crate::display::palette16::Palette16; + use $crate::align_bytes; + + $crate::include_aseprite_inner!($($aseprite_path),*); + + &Graphics::new(SPRITES, TAGS) + }}; +} + +pub use include_aseprite; + +/// Stores sprite and tag data returned by [include_aseprite]. +pub struct Graphics { + sprites: &'static [Sprite], + tag_map: &'static TagMap, +} + +impl Graphics { + #[doc(hidden)] + /// Creates graphics data from sprite data and a tag_map. This is used + /// internally by [include_aseprite] and would be otherwise difficult to + /// use. + #[must_use] + pub const fn new(sprites: &'static [Sprite], tag_map: &'static TagMap) -> Self { + Self { sprites, tag_map } + } + #[must_use] + /// Gets the tag map from the aseprite files. This allows reference to + /// sprite sequences by name. + pub const fn tags(&self) -> &TagMap { + self.tag_map + } + /// Gets a big list of the sprites themselves. Using tags is often easier. + #[must_use] + pub const fn sprites(&self) -> &[Sprite] { + self.sprites + } +} + +/// Stores aseprite tags. Can be used to refer to animation sequences by name. +/// ```rust,no_run +/// # #![no_std] +/// # #![no_main] +/// # use agb::{display::object::{Graphics, Tag}, include_aseprite}; +/// const GRAPHICS: &Graphics = include_aseprite!( +/// "examples/gfx/boss.aseprite", +/// "examples/gfx/objects.aseprite" +/// ); +/// +/// const EMU_WALK: &Tag = GRAPHICS.tags().get("emu-walk"); +/// ``` +/// This being the whole animation associated with the walk sequence of the emu. +/// See [Tag] for details on how to use this. +pub struct TagMap { + tags: &'static [(&'static str, Tag)], +} + +const fn const_byte_compare(a: &[u8], b: &[u8]) -> bool { + if a.len() != b.len() { + return false; + } + + let mut i = 0; + while i < a.len() { + if a[i] != b[i] { + return false; + } + i += 1; + } + true +} + +impl TagMap { + #[doc(hidden)] + /// Creates a new tag map from (name, Tag) pairs. Used internally by + /// [include_aseprite] and should not really be used outside of it. + #[must_use] + pub const fn new(tags: &'static [(&'static str, Tag)]) -> TagMap { + Self { tags } + } + + #[doc(hidden)] + /// Attempts to get a tag. Generally should not be used. + #[must_use] + pub const fn try_get(&'static self, tag: &str) -> Option<&'static Tag> { + let mut i = 0; + while i < self.tags.len() { + let s = self.tags[i].0; + if const_byte_compare(s.as_bytes(), tag.as_bytes()) { + return Some(&self.tags[i].1); + } + + i += 1; + } + + None + } + + /// Gets a tag associated with the name. A tag in aseprite refers to a + /// sequence of sprites with some metadata for how to animate it. You should + /// call this in a constant context so it is evaluated at compile time. It + /// is inefficient to call this elsewhere. + /// ```rust,no_run + /// # #![no_std] + /// # #![no_main] + /// # use agb::{display::object::{Graphics, Tag}, include_aseprite}; + /// const GRAPHICS: &Graphics = include_aseprite!( + /// "examples/gfx/boss.aseprite", + /// "examples/gfx/objects.aseprite" + /// ); + /// + /// const EMU_WALK: &Tag = GRAPHICS.tags().get("emu-walk"); + /// ``` + /// + /// See [Tag] for more details. + #[must_use] + pub const fn get(&'static self, tag: &str) -> &'static Tag { + let t = self.try_get(tag); + match t { + Some(t) => t, + None => panic!("The requested tag does not exist"), + } + } + + /// Takes an iterator over all the tags in the map. Not generally useful. + pub fn values(&self) -> impl Iterator { + self.tags.iter().map(|x| &x.1) + } +} + +#[derive(Clone, Copy)] +enum Direction { + Forward, + Backward, + PingPong, +} + +impl Direction { + const fn from_usize(a: usize) -> Self { + match a { + 0 => Direction::Forward, + 1 => Direction::Backward, + 2 => Direction::PingPong, + _ => panic!("Invalid direction, this is a bug in image converter or agb"), + } + } +} + +/// A sequence of sprites from aseprite. +pub struct Tag { + sprites: *const Sprite, + len: usize, + direction: Direction, +} + +impl Tag { + /// The individual sprites that make up the animation themselves. + #[must_use] + pub fn sprites(&self) -> &'static [Sprite] { + unsafe { slice::from_raw_parts(self.sprites, self.len) } + } + + /// A single sprite referred to by index in the animation sequence. + #[must_use] + pub const fn sprite(&self, idx: usize) -> &'static Sprite { + if idx >= self.len { + panic!("out of bounds access to sprite"); + } + unsafe { &*self.sprites.add(idx) } + } + + /// A sprite that follows the animation sequence. For instance, in aseprite + /// tags can be specified to animate: + /// * Forward + /// * Backward + /// * Ping pong + /// + /// This takes the animation type in account and returns the correct sprite + /// following these requirements. + #[inline] + #[must_use] + pub fn animation_sprite(&self, idx: usize) -> &'static Sprite { + let len_sub_1 = self.len - 1; + match self.direction { + Direction::Forward => self.sprite(idx % self.len), + Direction::Backward => self.sprite(len_sub_1 - (idx % self.len)), + Direction::PingPong => self.sprite( + (((idx + len_sub_1) % (len_sub_1 * 2)) as isize - len_sub_1 as isize) + .unsigned_abs(), + ), + } + } + + #[doc(hidden)] + /// Creates a new sprite from it's constituent parts. Used internally by + /// [include_aseprite] and should generally not be used elsewhere. + #[must_use] + pub const fn new(sprites: &'static [Sprite], from: usize, to: usize, direction: usize) -> Self { + assert!(from <= to); + assert!(to < sprites.len()); + Self { + sprites: &sprites[from] as *const Sprite, + len: to - from + 1, + direction: Direction::from_usize(direction), + } + } +} + +impl Size { + pub(crate) const fn number_of_tiles(self) -> usize { + match self { + Size::S8x8 => 1, + Size::S16x16 => 4, + Size::S32x32 => 16, + Size::S64x64 => 64, + Size::S16x8 => 2, + Size::S32x8 => 4, + Size::S32x16 => 8, + Size::S64x32 => 32, + Size::S8x16 => 2, + Size::S8x32 => 4, + Size::S16x32 => 8, + Size::S32x64 => 32, + } + } + pub(crate) const fn shape_size(self) -> (u16, u16) { + (self as u16 >> 2, self as u16 & 0b11) + } + + pub(crate) fn layout(self) -> Layout { + Layout::from_size_align(self.number_of_tiles() * BYTES_PER_TILE_4BPP, 8).unwrap() + } + + #[must_use] + /// Creates a size from width and height in pixels, panics if the width and + /// height is not representable by GBA sprites. + pub const fn from_width_height(width: usize, height: usize) -> Self { + match (width, height) { + (8, 8) => Size::S8x8, + (16, 16) => Size::S16x16, + (32, 32) => Size::S32x32, + (64, 64) => Size::S64x64, + (16, 8) => Size::S16x8, + (32, 8) => Size::S32x8, + (32, 16) => Size::S32x16, + (64, 32) => Size::S64x32, + (8, 16) => Size::S8x16, + (8, 32) => Size::S8x32, + (16, 32) => Size::S16x32, + (32, 64) => Size::S32x64, + (_, _) => panic!("Bad width and height!"), + } + } + + #[must_use] + /// Returns the width and height of the size in pixels. + pub const fn to_width_height(self) -> (usize, usize) { + match self { + Size::S8x8 => (8, 8), + Size::S16x16 => (16, 16), + Size::S32x32 => (32, 32), + Size::S64x64 => (64, 64), + Size::S16x8 => (16, 8), + Size::S32x8 => (32, 8), + Size::S32x16 => (32, 16), + Size::S64x32 => (64, 32), + Size::S8x16 => (8, 16), + Size::S8x32 => (8, 32), + Size::S16x32 => (16, 32), + Size::S32x64 => (32, 64), + } + } +} diff --git a/agb/src/display/object/sprites/sprite_allocator.rs b/agb/src/display/object/sprites/sprite_allocator.rs new file mode 100644 index 00000000..072a190a --- /dev/null +++ b/agb/src/display/object/sprites/sprite_allocator.rs @@ -0,0 +1,288 @@ +use core::ptr::NonNull; + +use alloc::rc::{Rc, Weak}; + +use crate::{ + agb_alloc::{block_allocator::BlockAllocator, bump_allocator::StartEnd}, + display::palette16::Palette16, + hash_map::HashMap, +}; + +use super::{ + sprite::{Size, Sprite}, + BYTES_PER_TILE_4BPP, +}; + +const PALETTE_SPRITE: usize = 0x0500_0200; +const TILE_SPRITE: usize = 0x06010000; + +static SPRITE_ALLOCATOR: BlockAllocator = unsafe { + BlockAllocator::new(StartEnd { + start: || TILE_SPRITE, + end: || TILE_SPRITE + 1024 * 8 * 4, + }) +}; + +static PALETTE_ALLOCATOR: BlockAllocator = unsafe { + BlockAllocator::new(StartEnd { + start: || PALETTE_SPRITE, + end: || PALETTE_SPRITE + 0x200, + }) +}; + +/// The Sprite Id is a thin wrapper around the pointer to the sprite in +/// rom and is therefore a unique identifier to a sprite +#[derive(Clone, Copy, PartialEq, Eq, Hash)] +struct SpriteId(usize); + +impl SpriteId { + fn from_static_sprite(sprite: &'static Sprite) -> SpriteId { + SpriteId(sprite as *const _ as usize) + } +} + +/// The palette id is a thin wrapper around the pointer to the palette in rom +/// and is therefore a unique reference to a palette +#[derive(Copy, Clone, PartialEq, Eq, Hash)] +struct PaletteId(usize); + +impl PaletteId { + fn from_static_palette(palette: &'static Palette16) -> PaletteId { + PaletteId(palette as *const _ as usize) + } +} + +/// This holds loading of static sprites and palettes. +pub struct StaticSpriteLoader { + static_palette_map: HashMap>, + static_sprite_map: HashMap>, +} + +#[derive(Clone, Copy, Debug)] +struct Location(usize); + +impl Location { + fn from_sprite_ptr(d: NonNull) -> Self { + Self(((d.as_ptr() as usize) - TILE_SPRITE) / BYTES_PER_TILE_4BPP) + } + fn from_palette_ptr(d: NonNull) -> Self { + Self((d.as_ptr() as usize - PALETTE_SPRITE) / Palette16::layout().size()) + } + fn as_palette_ptr(self) -> *mut u8 { + (self.0 * Palette16::layout().size() + PALETTE_SPRITE) as *mut u8 + } + fn as_sprite_ptr(self) -> *mut u8 { + (self.0 * BYTES_PER_TILE_4BPP + TILE_SPRITE) as *mut u8 + } +} + +#[derive(Debug)] +struct PaletteVramData { + location: Location, +} + +#[derive(Debug)] +pub struct PaletteVram { + data: Rc, +} + +impl PaletteVram { + fn new(palette: &Palette16) -> Option { + let allocated = unsafe { PALETTE_ALLOCATOR.alloc(Palette16::layout()) }?; + + unsafe { + allocated + .as_ptr() + .cast::() + .copy_from_nonoverlapping(palette.colours.as_ptr(), palette.colours.len()); + } + + Some(PaletteVram { + data: Rc::new(PaletteVramData { + location: Location::from_palette_ptr(allocated), + }), + }) + } +} + +#[derive(Debug)] +struct SpriteVramData { + location: Location, + size: Size, + palette: PaletteVram, +} + +impl Drop for SpriteVramData { + fn drop(&mut self) { + unsafe { SPRITE_ALLOCATOR.dealloc(self.location.as_sprite_ptr(), self.size.layout()) } + } +} + +#[derive(Clone, Debug)] +pub struct SpriteVram { + data: Rc, +} + +impl SpriteVram { + fn new(data: &[u8], size: Size, palette: PaletteVram) -> Option { + let allocated = unsafe { SPRITE_ALLOCATOR.alloc(size.layout()) }?; + unsafe { + allocated + .as_ptr() + .copy_from_nonoverlapping(data.as_ptr(), data.len()); + } + Some(SpriteVram { + data: Rc::new(SpriteVramData { + location: Location::from_sprite_ptr(allocated), + size, + palette, + }), + }) + } + + pub(crate) fn location(&self) -> u16 { + self.data.location.0 as u16 + } + + pub(crate) fn size(&self) -> Size { + self.data.size + } + + pub(crate) fn palette_location(&self) -> u16 { + self.data.palette.data.location.0 as u16 + } +} + +impl StaticSpriteLoader { + fn create_sprite_no_insert( + palette_map: &mut HashMap>, + sprite: &'static Sprite, + ) -> Option<(Weak, SpriteVram)> { + let palette = Self::try_get_vram_palette_asoc(palette_map, sprite.palette)?; + + let sprite = SpriteVram::new(sprite.data, sprite.size, palette)?; + Some((Rc::downgrade(&sprite.data), sprite)) + } + + fn try_get_vram_palette_asoc( + palette_map: &mut HashMap>, + palette: &'static Palette16, + ) -> Option { + let id = PaletteId::from_static_palette(palette); + Some(match palette_map.entry(id) { + crate::hash_map::Entry::Occupied(mut entry) => match entry.get().upgrade() { + Some(data) => PaletteVram { data }, + None => { + let pv = PaletteVram::new(palette)?; + entry.insert(Rc::downgrade(&pv.data)); + pv + } + }, + crate::hash_map::Entry::Vacant(entry) => { + let pv = PaletteVram::new(palette)?; + entry.insert(Rc::downgrade(&pv.data)); + pv + } + }) + } + + pub fn try_get_vram_sprite(&mut self, sprite: &'static Sprite) -> Option { + // check if we already have the sprite in vram + + let id = SpriteId::from_static_sprite(sprite); + + Some(match self.static_sprite_map.entry(id) { + crate::hash_map::Entry::Occupied(mut entry) => match entry.get().upgrade() { + Some(data) => SpriteVram { data }, + None => { + let (weak, vram) = + Self::create_sprite_no_insert(&mut self.static_palette_map, sprite)?; + entry.insert(weak); + vram + } + }, + crate::hash_map::Entry::Vacant(entry) => { + let (weak, vram) = + Self::create_sprite_no_insert(&mut self.static_palette_map, sprite)?; + entry.insert(weak); + vram + } + }) + } + + pub fn try_get_vram_palette(&mut self, palette: &'static Palette16) -> Option { + Self::try_get_vram_palette_asoc(&mut self.static_palette_map, palette) + } + + pub fn get_vram_sprite(&mut self, sprite: &'static Sprite) -> SpriteVram { + self.try_get_vram_sprite(sprite) + .expect("no free sprite slots") + } + + pub fn get_vram_palette(&mut self, palette: &'static Palette16) -> PaletteVram { + self.try_get_vram_palette(palette) + .expect("no free palette slots") + } + + pub(crate) fn new() -> Self { + Self { + static_palette_map: HashMap::new(), + static_sprite_map: HashMap::new(), + } + } + + fn gc(&mut self) { + self.static_sprite_map + .retain(|_, v| Weak::strong_count(v) != 0); + self.static_palette_map + .retain(|_, v| Weak::strong_count(v) != 0); + } +} + +impl Default for StaticSpriteLoader { + fn default() -> Self { + Self::new() + } +} + +/// Sprite data that can be used to create sprites in vram. +pub struct DynamicSprite<'a> { + data: &'a [u8], + size: Size, +} + +impl DynamicSprite<'_> { + #[must_use] + /// Creates a new dynamic sprite from underlying bytes. Note that despite + /// being an array of u8, this must be aligned to at least a 2 byte + /// boundary. + pub fn new(data: &[u8], size: Size) -> DynamicSprite { + let ptr = &data[0] as *const _ as usize; + if ptr % 2 != 0 { + panic!("data is not aligned to a 2 byte boundary"); + } + if data.len() != size.number_of_tiles() * BYTES_PER_TILE_4BPP { + panic!( + "data is not of expected length, got {} expected {}", + data.len(), + size.number_of_tiles() * BYTES_PER_TILE_4BPP + ); + } + DynamicSprite { data, size } + } + + #[must_use] + /// Tries to copy the sprite to vram to be used to set object sprites. + /// Returns None if there is no room in sprite vram. + pub fn try_vram(&self, palette: PaletteVram) -> Option { + SpriteVram::new(self.data, self.size, palette) + } + + #[must_use] + /// Tries to copy the sprite to vram to be used to set object sprites. + /// Panics if there is no room in sprite vram. + pub fn to_vram(&self, palette: PaletteVram) -> SpriteVram { + self.try_vram(palette) + .expect("No slot for sprite available") + } +} diff --git a/agb/src/display/object/unmanaged.rs b/agb/src/display/object/unmanaged.rs new file mode 100644 index 00000000..3a9db454 --- /dev/null +++ b/agb/src/display/object/unmanaged.rs @@ -0,0 +1,5 @@ +mod attributes; +mod object; + +pub use attributes::AffineMode; +pub use object::{OAMIterator, OAMSlot, UnmanagedOAM, UnmanagedObject}; diff --git a/agb/src/display/object/unmanaged/attributes.rs b/agb/src/display/object/unmanaged/attributes.rs new file mode 100644 index 00000000..69575c4b --- /dev/null +++ b/agb/src/display/object/unmanaged/attributes.rs @@ -0,0 +1,204 @@ +use modular_bitfield::BitfieldSpecifier; + +use crate::display::Priority; + +use self::attributes::{ + ObjectAttribute0, ObjectAttribute1Affine, ObjectAttribute1Standard, ObjectAttribute2, +}; + +#[derive(PartialEq, Eq, Debug)] +pub struct Attributes { + a0: ObjectAttribute0, + a1s: ObjectAttribute1Standard, + a1a: ObjectAttribute1Affine, + a2: ObjectAttribute2, +} + +impl Default for Attributes { + fn default() -> Self { + Self { + a0: ObjectAttribute0::from_bytes([0b01, 0]), + a1s: Default::default(), + a1a: Default::default(), + a2: Default::default(), + } + } +} + +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] +pub enum AffineMode { + Affine = 1, + AffineDouble = 3, +} + +impl Attributes { + pub fn bytes(&self) -> [u8; 6] { + let mode = self.a0.object_mode(); + let attrs = match mode { + ObjectMode::Normal => [ + self.a0.into_bytes(), + self.a1s.into_bytes(), + self.a2.into_bytes(), + ], + _ => [ + self.a0.into_bytes(), + self.a1a.into_bytes(), + self.a2.into_bytes(), + ], + }; + + // Safety: length and alignment are the same, and every possible value is valid + unsafe { core::mem::transmute(attrs) } + } + + pub fn is_visible(&self) -> bool { + self.a0.object_mode() != ObjectMode::Disabled + } + + pub fn show(&mut self) -> &mut Self { + self.a0.set_object_mode(ObjectMode::Normal); + + self + } + + pub fn show_affine(&mut self, affine_mode: AffineMode) -> &mut Self { + self.a0.set_object_mode(match affine_mode { + AffineMode::Affine => ObjectMode::Affine, + AffineMode::AffineDouble => ObjectMode::AffineDouble, + }); + + self + } + + pub fn set_hflip(&mut self, flip: bool) -> &mut Self { + self.a1s.set_horizontal_flip(flip); + + self + } + + pub fn set_vflip(&mut self, flip: bool) -> &mut Self { + self.a1s.set_vertical_flip(flip); + + self + } + + pub fn set_x(&mut self, x: u16) -> &mut Self { + self.a1a.set_x(x.rem_euclid(1 << 9)); + self.a1s.set_x(x.rem_euclid(1 << 9)); + + self + } + + pub fn set_priority(&mut self, priority: Priority) -> &mut Self { + self.a2.set_priority(priority); + + self + } + + pub fn hide(&mut self) -> &mut Self { + self.a0.set_object_mode(ObjectMode::Disabled); + + self + } + + pub fn set_y(&mut self, y: u16) -> &mut Self { + self.a0.set_y(y as u8); + + self + } + + pub fn set_palette(&mut self, palette_id: u16) -> &mut Self { + self.a2.set_palette_bank(palette_id as u8); + + self + } + + pub fn set_affine_matrix(&mut self, affine_matrix_id: u16) -> &mut Self { + self.a1a.set_affine_index(affine_matrix_id as u8); + + self + } + + pub fn set_sprite(&mut self, sprite_id: u16, shape: u16, size: u16) -> &mut Self { + self.a2.set_tile_index(sprite_id); + self.a1a.set_size(size as u8); + self.a1s.set_size(size as u8); + self.a0.set_shape(shape as u8); + + self + } +} + +#[derive(BitfieldSpecifier, Clone, Copy, Debug, PartialEq, Eq)] +enum ObjectMode { + Normal, + Affine, + Disabled, + AffineDouble, +} + +#[derive(BitfieldSpecifier, Clone, Copy, Debug, PartialEq, Eq)] +#[bits = 2] +enum GraphicsMode { + Normal, + AlphaBlending, + Window, +} + +#[derive(BitfieldSpecifier, Clone, Copy, Debug, PartialEq, Eq)] +enum ColourMode { + Four, + Eight, +} + +// this mod is not public, so the internal parts don't need documenting. +#[allow(dead_code)] +#[allow(clippy::all)] +#[allow(clippy::map_unwrap_or)] +mod attributes { + use modular_bitfield::{ + bitfield, + specifiers::{B10, B2, B3, B4, B5, B8, B9}, + }; + + use crate::display::Priority; + + use super::*; + #[bitfield] + #[derive(Clone, Copy, PartialEq, Eq, Debug, Default)] + pub(super) struct ObjectAttribute0 { + pub y: B8, + pub object_mode: ObjectMode, + pub graphics_mode: GraphicsMode, + pub mosaic: bool, + pub colour_mode: ColourMode, + pub shape: B2, + } + + #[bitfield] + #[derive(Clone, Copy, PartialEq, Eq, Debug, Default)] + pub(super) struct ObjectAttribute1Standard { + pub x: B9, + #[skip] + __: B3, + pub horizontal_flip: bool, + pub vertical_flip: bool, + pub size: B2, + } + + #[bitfield] + #[derive(Clone, Copy, PartialEq, Eq, Debug, Default)] + pub(super) struct ObjectAttribute1Affine { + pub x: B9, + pub affine_index: B5, + pub size: B2, + } + + #[bitfield] + #[derive(Clone, Copy, PartialEq, Eq, Debug, Default)] + pub(super) struct ObjectAttribute2 { + pub tile_index: B10, + pub priority: Priority, + pub palette_bank: B4, + } +} diff --git a/agb/src/display/object/unmanaged/object.rs b/agb/src/display/object/unmanaged/object.rs new file mode 100644 index 00000000..9c28bd0a --- /dev/null +++ b/agb/src/display/object/unmanaged/object.rs @@ -0,0 +1,187 @@ +use core::{cell::UnsafeCell, marker::PhantomData}; + +use agb_fixnum::Vector2D; + +use crate::display::{ + object::{sprites::SpriteVram, OBJECT_ATTRIBUTE_MEMORY}, + Priority, +}; + +use super::attributes::{AffineMode, Attributes}; + +pub struct UnmanagedOAM<'gba> { + phantom: PhantomData<&'gba ()>, +} + +pub struct OAMIterator<'oam> { + phantom: PhantomData<&'oam ()>, + index: usize, +} + +pub struct OAMSlot<'oam> { + phantom: PhantomData<&'oam ()>, + slot: usize, +} + +impl OAMSlot<'_> { + pub fn set(&mut self, object: &UnmanagedObject) { + self.set_bytes(object.attributes.bytes()); + + // SAFETY: This is called here and in set_sprite, neither of which call the other. + let sprites = unsafe { &mut *object.sprites.get() }; + + sprites.previous_sprite = Some(sprites.sprite.clone()); + } + + fn set_bytes(&mut self, bytes: [u8; 6]) { + unsafe { + let address = (OBJECT_ATTRIBUTE_MEMORY as *mut u8).add(self.slot * 8); + address.copy_from_nonoverlapping(bytes.as_ptr(), bytes.len()); + } + } +} + +impl<'oam> Iterator for OAMIterator<'oam> { + type Item = OAMSlot<'oam>; + + fn next(&mut self) -> Option { + let idx = self.index; + self.index += 1; + + if idx >= 128 { + None + } else { + Some(OAMSlot { + phantom: PhantomData, + slot: idx, + }) + } + } +} + +impl UnmanagedOAM<'_> { + pub fn iter(&mut self) -> OAMIterator<'_> { + OAMIterator { + phantom: PhantomData, + index: 0, + } + } + + pub(crate) fn new() -> Self { + Self { + phantom: PhantomData, + } + } + + pub fn clear_from(&self, from: usize) { + if from >= 128 { + return; + } + + for i in from..128 { + unsafe { + let ptr = (OBJECT_ATTRIBUTE_MEMORY as *mut u16).add(i * 4); + ptr.write_volatile(0b10 << 8); + } + } + } +} + +#[derive(Debug)] +struct VramSprites { + sprite: SpriteVram, + previous_sprite: Option, +} + +#[derive(Debug)] +pub struct UnmanagedObject { + attributes: Attributes, + sprites: UnsafeCell, +} + +impl UnmanagedObject { + #[must_use] + pub fn new(sprite: SpriteVram) -> Self { + Self { + attributes: Attributes::default(), + sprites: UnsafeCell::new(VramSprites { + sprite, + previous_sprite: None, + }), + } + } + + pub fn is_visible(&self) -> bool { + self.attributes.is_visible() + } + + pub fn show(&mut self) -> &mut Self { + self.attributes.show(); + + self + } + + pub fn show_affine(&mut self, affine_mode: AffineMode) -> &mut Self { + self.attributes.show_affine(affine_mode); + + self + } + + pub fn set_hflip(&mut self, flip: bool) -> &mut Self { + self.attributes.set_hflip(flip); + + self + } + + pub fn set_vflip(&mut self, flip: bool) -> &mut Self { + self.attributes.set_vflip(flip); + + self + } + + pub fn set_x(&mut self, x: u16) -> &mut Self { + self.attributes.set_x(x); + + self + } + + pub fn set_priority(&mut self, priority: Priority) -> &mut Self { + self.attributes.set_priority(priority); + + self + } + + pub fn hide(&mut self) -> &mut Self { + self.attributes.hide(); + + self + } + + pub fn set_y(&mut self, y: u16) -> &mut Self { + self.attributes.set_y(y); + + self + } + + pub fn set_position(&mut self, position: Vector2D) -> &mut Self { + self.set_y(position.y.rem_euclid(1 << 9) as u16); + self.set_x(position.x.rem_euclid(1 << 9) as u16); + + self + } + + pub fn set_sprite(&mut self, sprite: SpriteVram) -> &mut Self { + // SAFETY: This is called here and in OAMSlot set, neither of which call the other. + let sprites = unsafe { &mut *self.sprites.get() }; + + let size = sprite.size(); + let (shape, size) = size.shape_size(); + + self.attributes.set_sprite(sprite.location(), shape, size); + self.attributes.set_palette(sprite.palette_location()); + + sprites.sprite = sprite; + + self + } +} diff --git a/agb/src/display/palette16.rs b/agb/src/display/palette16.rs index 6a4441ba..449eb31c 100644 --- a/agb/src/display/palette16.rs +++ b/agb/src/display/palette16.rs @@ -1,3 +1,5 @@ +use core::alloc::Layout; + #[repr(C)] #[derive(Clone)] pub struct Palette16 { @@ -21,4 +23,8 @@ impl Palette16 { pub fn colour(&self, index: usize) -> u16 { self.colours[index] } + + pub(crate) const fn layout() -> Layout { + Layout::new::() + } }