sprite allocator

This commit is contained in:
Corwin 2022-02-07 23:01:52 +00:00
parent beb9abbb7e
commit 1849571344
6 changed files with 138 additions and 505 deletions

56
agb/Cargo.lock generated
View file

@ -24,6 +24,7 @@ dependencies = [
"agb_sound_converter",
"bare-metal",
"bitflags",
"hashbrown",
]
[[package]]
@ -64,6 +65,17 @@ dependencies = [
"syn",
]
[[package]]
name = "ahash"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
dependencies = [
"getrandom",
"once_cell",
"version_check",
]
[[package]]
name = "autocfg"
version = "1.1.0"
@ -124,6 +136,26 @@ dependencies = [
"adler32",
]
[[package]]
name = "getrandom"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d39cd93900197114fa1fcb7ae84ca742095eed9442088988ae74fa744e930e77"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "hashbrown"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c21d40587b92fa6a6c6e3c1bdbf87d75511db5672f9c93175574b3a00df1758"
dependencies = [
"ahash",
]
[[package]]
name = "hound"
version = "3.4.0"
@ -145,6 +177,12 @@ dependencies = [
"png",
]
[[package]]
name = "libc"
version = "0.2.119"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4"
[[package]]
name = "miniz_oxide"
version = "0.5.1"
@ -195,6 +233,12 @@ dependencies = [
"autocfg",
]
[[package]]
name = "once_cell"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5"
[[package]]
name = "png"
version = "0.17.5"
@ -270,3 +314,15 @@ name = "unicode-xid"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "wasi"
version = "0.10.2+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"

View file

@ -26,6 +26,7 @@ agb_sound_converter = { version = "0.1.0", path = "../agb-sound-converter" }
agb_macros = { version = "0.1.0", path = "../agb-macros" }
agb_fixnum = { version = "0.1.0", path = "../agb-fixnum" }
bare-metal = "1.0"
hashbrown = "0.12.0"
[package.metadata.docs.rs]
default-target = "thumbv6m-none-eabi"

View file

@ -49,7 +49,7 @@ pub(crate) struct BlockAllocator {
}
impl BlockAllocator {
pub(super) const unsafe fn new(start: StartEnd) -> Self {
pub const unsafe fn new(start: StartEnd) -> Self {
Self {
inner_allocator: BumpAllocator::new(start),
state: Mutex::new(RefCell::new(BlockAllocatorState {

View file

@ -6,11 +6,9 @@ use super::SendNonNull;
use crate::interrupt::free;
use bare_metal::{CriticalSection, Mutex};
pub(crate) struct AddrFn(pub fn() -> usize);
pub(crate) struct StartEnd {
pub start: AddrFn,
pub end: AddrFn,
pub start: fn() -> usize,
pub end: fn() -> usize,
}
pub(crate) struct BumpAllocator {
@ -34,7 +32,7 @@ impl BumpAllocator {
let ptr = if let Some(c) = *current_ptr {
c.as_ptr() as usize
} else {
self.start_end.borrow(*cs).start.0()
(self.start_end.borrow(*cs).start)()
};
let alignment_bitmask = layout.align() - 1;
@ -45,7 +43,7 @@ impl BumpAllocator {
let resulting_ptr = ptr + amount_to_add;
let new_current_ptr = resulting_ptr + layout.size();
if new_current_ptr as usize >= self.start_end.borrow(*cs).end.0() {
if new_current_ptr as usize >= (self.start_end.borrow(*cs).end)() {
return None;
}

View file

@ -2,12 +2,12 @@ use core::alloc::Layout;
use core::ops::{Deref, DerefMut};
use core::ptr::NonNull;
mod block_allocator;
mod bump_allocator;
pub(crate) mod block_allocator;
pub(crate) mod bump_allocator;
use block_allocator::BlockAllocator;
use self::bump_allocator::{AddrFn, StartEnd};
use self::bump_allocator::StartEnd;
struct SendNonNull<T>(NonNull<T>);
unsafe impl<T> Send for SendNonNull<T> {}
@ -37,8 +37,8 @@ const EWRAM_END: usize = 0x0204_0000;
#[global_allocator]
static GLOBAL_ALLOC: BlockAllocator = unsafe {
BlockAllocator::new(StartEnd {
start: AddrFn(get_data_end),
end: AddrFn(|| EWRAM_END),
start: get_data_end,
end: || EWRAM_END,
})
};

View file

@ -1,12 +1,21 @@
use core::cell::RefCell;
use hashbrown::{hash_map::Entry, HashMap};
use super::palette16::Palette16;
use super::{palette16, Priority, DISPLAY_CONTROL};
use crate::agb_alloc::block_allocator::BlockAllocator;
use crate::agb_alloc::bump_allocator::StartEnd;
use crate::bitarray::Bitarray;
use crate::fixnum::Vector2D;
use crate::memory_mapped::MemoryMapped1DArray;
type AffineLoan<'a> = crate::arena::Loan<'a, 32>;
type AffineArena = crate::arena::Arena<32>;
static SPRITE_ALLOCATOR: BlockAllocator = unsafe {
BlockAllocator::new(StartEnd {
start: || 0x06010000,
end: || 0x06010000 + 1024 * 8 * 4,
})
};
const OBJECT_ATTRIBUTE_MEMORY: MemoryMapped1DArray<u16, 512> =
unsafe { MemoryMapped1DArray::new(0x0700_0000) };
@ -15,523 +24,92 @@ const PALETTE_SPRITE: MemoryMapped1DArray<u16, 256> =
const TILE_SPRITE: MemoryMapped1DArray<u32, { 1024 * 8 }> =
unsafe { MemoryMapped1DArray::new(0x06010000) };
/// Handles distributing objects and matrices along with operations that effect all objects.
/// You can create an instance of this using the Gba struct.
///
/// This handles distribution of sprites, ensuring that object ids are not reused and are
/// returned to the pool once you're done handling them.
///
/// # Examples
///
/// ```
/// # #![no_std]
/// # #![no_main]
/// #
/// # use agb::Gba;
/// #
/// # #[agb::entry]
/// # fn main() -> ! {
/// let mut gba = Gba::new();
/// let mut object = gba.display.object.get();
/// #
/// # loop {}
/// # }
/// ```
pub struct ObjectControl {
objects: RefCell<Bitarray<4>>,
affines: AffineArena,
pub struct Sprite {
palette: &'static Palette16,
data: &'static [u8],
}
struct ObjectLoan<'a> {
index: u8,
objects: &'a RefCell<Bitarray<4>>,
struct SpriteBorrow<'a> {
id: SpriteId,
controller: &'a RefCell<SpriteControllerInner>,
}
/// The standard object, without rotation.
///
/// You should create this from an instance of ObjectControl created using the Gba struct. Note that
/// no changes made to this will be visible until `commit()` is called. You should call `commit()` during
/// vblank to ensure that you get no visual artifacts.
///
/// This struct implements a sort of builder pattern, allowing you to chain settings together.
///
/// # Examples
///
/// ```
/// # #![no_std]
/// # #![no_main]
/// #
/// # use agb::Gba;
/// use agb::display::object::Size;
///
/// # #[agb::entry]
/// # fn main() -> ! {
/// # let mut gba = Gba::new();
/// let mut object = gba.display.object.get();
///
/// let mut my_new_object = object.get_object_standard();
/// my_new_object.set_x(50)
/// .set_y(50)
/// .set_sprite_Size(Size::S8x8)
/// .set_tile_id(7)
/// .show();
///
/// // some time later in vblank
/// my_new_object.commit();
/// # loop {}
/// # }
/// ```
pub struct ObjectStandard<'a> {
attributes: ObjectAttribute,
loan: ObjectLoan<'a>,
struct Storage {
location: u16,
count: u16,
}
/// The affine object, with potential for using a transformation matrix to alter
/// how the sprite is rendered to screen.
pub struct ObjectAffine<'a> {
attributes: ObjectAttribute,
loan: ObjectLoan<'a>,
aff_loan: Option<AffineLoan<'a>>,
pub struct Object<'a> {
sprite: SpriteBorrow<'a>,
}
/// Refers to an affine matrix in the OAM. Includes both an index and the
/// components of the affine matrix.
pub struct AffineMatrix<'a> {
pub attributes: AffineMatrixAttributes,
loan: AffineLoan<'a>,
struct SpriteControllerInner {
palette: HashMap<PaletteId, Storage>,
sprite: HashMap<SpriteId, Storage>,
}
/// The components of the affine matrix. The components are fixed point 8:8.
/// TODO is a type that can handle fixed point arithmetic.
pub struct AffineMatrixAttributes {
pub p_a: i16,
pub p_b: i16,
pub p_c: i16,
pub p_d: i16,
pub struct SpriteController {
inner: RefCell<SpriteControllerInner>,
}
#[allow(dead_code)]
enum Mode {
Normal = 0,
Affine = 1,
Hidden = 2,
AffineDouble = 3,
}
pub struct ObjectController {}
#[derive(Clone, Copy)]
pub enum Size {
// stored as attr0 attr1
S8x8 = 0b00_00,
S16x16 = 0b00_01,
S32x32 = 0b00_10,
S64x64 = 0b00_11,
/// 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);
S16x8 = 0b01_00,
S32x8 = 0b01_01,
S32x16 = 0b01_10,
S64x32 = 0b01_11,
/// 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);
S8x16 = 0b10_00,
S8x32 = 0b10_01,
S16x32 = 0b10_10,
S32x64 = 0b10_11,
}
impl ObjectStandard<'_> {
/// Commits the object to OAM such that the updated version is displayed on
/// screen. Recommend to do this during VBlank.
pub fn commit(&self) {
unsafe { self.attributes.commit(self.loan.index) }
}
/// Sets the x coordinate of the sprite on screen.
pub fn set_x(&mut self, x: u16) -> &mut Self {
self.attributes.set_x(x);
self
}
/// Sets the y coordinate of the sprite on screen.
pub fn set_y(&mut self, y: u16) -> &mut Self {
self.attributes.set_y(y);
self
}
/// Sets the index of the tile to use as the sprite. Potentially a temporary function.
pub fn set_tile_id(&mut self, id: u16) -> &mut Self {
self.attributes.set_tile_id(id);
self
}
/// Sets whether the sprite is horizontally mirrored or not.
pub fn set_hflip(&mut self, hflip: bool) -> &mut Self {
self.attributes.set_hflip(hflip);
self
}
/// Sets the sprite size, will read tiles in x major order to construct this.
pub fn set_sprite_size(&mut self, size: Size) -> &mut Self {
self.attributes.set_size(size);
self
}
/// Show the object on screen.
pub fn show(&mut self) -> &mut Self {
self.attributes.set_mode(Mode::Normal);
self
}
/// Hide the object and do not render.
pub fn hide(&mut self) -> &mut Self {
self.attributes.set_mode(Mode::Hidden);
self
}
/// Sets the palette to use for this sprite
pub fn set_palette(&mut self, palette: u16) -> &mut Self {
self.attributes.set_palette(palette);
self
}
/// Sets the x and y position of the object, performing casts as nessesary
/// to fit within the bits allocated for this purpose.
pub fn set_position(&mut self, position: Vector2D<i32>) -> &mut Self {
let x = position.x as u16;
let y = position.y as u16;
self.attributes.set_x(x);
self.attributes.set_y(y);
self
}
/// Sets the priority (used for z ordering) of this sprite
pub fn set_priority(&mut self, p: Priority) -> &mut Self {
self.attributes.set_priority(p);
self
impl Sprite {
fn get_id(&'static self) -> SpriteId {
SpriteId(self as *const _ as usize)
}
}
impl<'a> ObjectAffine<'a> {
/// Commits the object to OAM such that the updated version is displayed on
/// screen. Recommend to do this during VBlank.
pub fn commit(&self) {
unsafe { self.attributes.commit(self.loan.index) }
}
/// Sets the x coordinate of the sprite on screen.
pub fn set_x(&mut self, x: u16) {
self.attributes.set_x(x)
}
/// Sets the y coordinate of the sprite on screen.
pub fn set_y(&mut self, y: u16) {
self.attributes.set_y(y)
}
/// Sets the index of the tile to use as the sprite. Potentially a temporary function.
pub fn set_tile_id(&mut self, id: u16) {
self.attributes.set_tile_id(id)
}
/// Sets the sprite size, will read tiles in x major order to construct this.
pub fn set_sprite_size(&mut self, size: Size) {
self.attributes.set_size(size);
}
/// Show the object on screen. Panics if affine matrix has not been set.
pub fn show(&mut self) {
if self.aff_loan.is_none() {
panic!("affine matrix should be set")
impl SpriteController {
fn get_sprite(&self, sprite: &'static Sprite) -> Option<SpriteBorrow> {
let inner = self.inner.borrow_mut();
let id = sprite.get_id();
if let Some(storage) = inner.sprite.get_mut(&id) {
storage.count += 1;
Some(SpriteBorrow {
id,
controller: &self.inner,
})
} else {
// allocate a new sprite
todo!();
}
self.attributes.set_mode(Mode::Affine)
}
/// Hide the object and do not render the sprite.
pub fn hide(&mut self) {
self.attributes.set_mode(Mode::Hidden)
}
/// Sets the affine matrix to use. Changing the affine matrix will change
/// how the sprite is rendered.
pub fn set_affine_mat(&mut self, aff: &AffineMatrix<'a>) {
self.attributes.set_affine(aff.loan.my_index);
self.aff_loan = Some(aff.loan.clone());
}
/// Sets the x and y position of the object, performing casts as nessesary
/// to fit within the bits allocated for this purpose.
pub fn set_position(&mut self, position: Vector2D<i32>) {
let x = position.x as u16;
let y = position.y as u16;
self.attributes.set_x(x);
self.attributes.set_y(y);
}
pub fn set_priority(&mut self, p: Priority) {
self.attributes.set_priority(p)
}
}
fn set_bits(current: u16, value: u16, length: u16, shift: u16) -> u16 {
let mask: u16 = (1 << length) - 1;
(current & !(mask << shift)) | ((value & mask) << shift)
}
impl Drop for ObjectLoan<'_> {
impl<'a> Drop for SpriteBorrow<'a> {
fn drop(&mut self) {
let attributes = ObjectAttribute::new();
unsafe {
attributes.commit(self.index);
}
let mut objs = self.objects.borrow_mut();
objs.set(self.index as usize, false);
}
}
let inner = self.controller.borrow_mut();
let entry = inner
.sprite
.entry(self.id)
.and_replace_entry_with(|_, mut storage| {
storage.count -= 1;
if storage.count == 0 {
None
} else {
Some(storage)
}
});
struct ObjectAttribute {
a0: u16,
a1: u16,
a2: u16,
}
impl ObjectAttribute {
unsafe fn commit(&self, index: u8) {
OBJECT_ATTRIBUTE_MEMORY.set(index as usize * 4, self.a0);
OBJECT_ATTRIBUTE_MEMORY.set(index as usize * 4 + 1, self.a1);
OBJECT_ATTRIBUTE_MEMORY.set(index as usize * 4 + 2, self.a2);
}
fn set_hflip(&mut self, hflip: bool) {
self.a1 = set_bits(self.a1, hflip as u16, 1, 0xC);
}
fn set_size(&mut self, size: Size) {
let a1 = size as u16 & 0b11;
let a0 = (size as u16 >> 2) & 0b11;
self.a0 = set_bits(self.a0, a0, 2, 0xE);
self.a1 = set_bits(self.a1, a1, 2, 0xE);
}
fn set_palette(&mut self, palette: u16) {
self.a2 = set_bits(self.a2, palette, 4, 0xC);
}
fn set_x(&mut self, x: u16) {
self.a1 = set_bits(self.a1, x, 9, 0);
}
fn set_y(&mut self, y: u16) {
self.a0 = set_bits(self.a0, y, 8, 0)
}
fn set_tile_id(&mut self, id: u16) {
self.a2 = set_bits(self.a2, id, 10, 0);
}
fn set_mode(&mut self, mode: Mode) {
self.a0 = set_bits(self.a0, mode as u16, 2, 8);
}
fn set_affine(&mut self, aff_id: u8) {
self.a1 = set_bits(self.a1, aff_id as u16, 5, 0x9);
}
fn set_priority(&mut self, p: Priority) {
self.a2 = set_bits(self.a2, p as u16, 2, 0x0A);
}
}
impl AffineMatrix<'_> {
/// Commits matrix to OAM, will cause any objects using this matrix to be updated.
pub fn commit(&self) {
unsafe { self.attributes.commit(self.loan.my_index) };
}
}
impl AffineMatrixAttributes {
#[allow(clippy::identity_op)]
unsafe fn commit(&self, index: u8) {
let index = index as usize * 4;
OBJECT_ATTRIBUTE_MEMORY.set((index + 0) * 4 + 3, self.p_a as u16);
OBJECT_ATTRIBUTE_MEMORY.set((index + 1) * 4 + 3, self.p_b as u16);
OBJECT_ATTRIBUTE_MEMORY.set((index + 2) * 4 + 3, self.p_c as u16);
OBJECT_ATTRIBUTE_MEMORY.set((index + 3) * 4 + 3, self.p_d as u16);
}
}
impl ObjectAttribute {
fn new() -> Self {
let mut o = ObjectAttribute {
a0: 0,
a1: 0,
a2: 0,
};
o.set_mode(Mode::Hidden);
o
}
}
impl ObjectControl {
pub(crate) fn new() -> Self {
let o = ObjectAttribute::new();
for index in 0..128 {
unsafe { o.commit(index) };
}
ObjectControl {
objects: RefCell::new(Bitarray::new()),
affines: AffineArena::new(),
}
}
fn set_sprite_tilemap_entry(&self, index: usize, data: u32) {
TILE_SPRITE.set(index, data);
}
/// Copies raw palettes to the background palette without any checks.
pub fn set_sprite_palette_raw(&self, colour: &[u16]) {
for (index, &entry) in colour.iter().enumerate() {
self.set_sprite_palette_entry(index, entry)
}
}
fn set_sprite_palette_entry(&self, index: usize, colour: u16) {
PALETTE_SPRITE.set(index, colour)
}
fn set_sprite_palette(&self, pal_index: u8, palette: &palette16::Palette16) {
for (colour_index, &colour) in palette.colours.iter().enumerate() {
PALETTE_SPRITE.set(pal_index as usize * 16 + colour_index, colour);
}
}
pub fn set_sprite_palettes(&self, palettes: &[palette16::Palette16]) {
for (palette_index, entry) in palettes.iter().enumerate() {
self.set_sprite_palette(palette_index as u8, entry)
}
}
/// Copies tiles to the sprite tilemap without any checks.
pub fn set_sprite_tilemap(&self, tiles: &[u32]) {
for (index, &tile) in tiles.iter().enumerate() {
self.set_sprite_tilemap_entry(index, tile)
}
}
pub fn set_sprite_tilemap_at_idx(&self, idx: usize, tiles: &[u32]) {
for (index, &tile) in tiles.iter().enumerate() {
self.set_sprite_tilemap_entry(index + idx, tile)
}
}
/// Enable objects on the GBA.
pub fn enable(&mut self) {
let disp = DISPLAY_CONTROL.get();
let disp = disp | (1 << 0x0C);
DISPLAY_CONTROL.set(disp);
}
/// Disable objects, objects won't be rendered.
pub fn disable(&mut self) {
let disp = DISPLAY_CONTROL.get();
let disp = disp & !(1 << 0x0C);
DISPLAY_CONTROL.set(disp);
}
fn get_unused_object_index(&self) -> u8 {
let mut objects = self.objects.borrow_mut();
for index in 0..128 {
if !objects.get(index).unwrap() {
objects.set(index, true);
return index as u8;
match entry {
Entry::Vacant(_) => {
// free the underlying resource.
// palette might be unused too.
}
}
panic!("object id must be less than 128");
}
/// Get an unused standard object. Panics if more than 128 objects are
/// obtained.
pub fn get_object_standard(&self) -> ObjectStandard {
let id = self.get_unused_object_index();
ObjectStandard {
attributes: ObjectAttribute::new(),
loan: ObjectLoan {
objects: &self.objects,
index: id,
},
}
}
/// Get an unused affine object. Panics if more than 128 objects are
/// obtained.
pub fn get_object_affine(&self) -> ObjectAffine {
let id = self.get_unused_object_index();
ObjectAffine {
attributes: ObjectAttribute::new(),
loan: ObjectLoan {
objects: &self.objects,
index: id,
},
aff_loan: None,
}
}
/// Get an unused affine matrix. Panics if more than 32 affine matricies are
/// obtained.
pub fn get_affine(&self) -> AffineMatrix {
AffineMatrix {
attributes: AffineMatrixAttributes {
p_a: 0,
p_b: 0,
p_c: 0,
p_d: 0,
},
loan: self
.affines
.get_next_free()
.expect("there are no affines avaliable"),
_ => {}
}
}
}
#[cfg(test)]
mod tests {
#[test_case]
fn get_and_release_object(gba: &mut crate::Gba) {
let objs = gba.display.object.get();
let _o1 = {
let o0 = objs.get_object_standard();
let o1 = objs.get_object_standard();
assert_eq!(o0.loan.index, 0);
assert_eq!(o1.loan.index, 1);
o1
};
let o0 = objs.get_object_standard();
assert_eq!(o0.loan.index, 0);
let o2 = objs.get_object_affine();
assert_eq!(o2.loan.index, 2);
}
#[test_case]
fn get_and_release_affine(gba: &mut crate::Gba) {
let objs = gba.display.object.get();
let _a1 = {
let a0 = objs.get_affine();
let a1 = objs.get_affine();
assert_eq!(a0.loan.my_index, 0);
assert_eq!(a1.loan.my_index, 1);
a1
};
let a0 = objs.get_affine();
assert_eq!(a0.loan.my_index, 0);
let a2 = objs.get_affine();
assert_eq!(a2.loan.my_index, 2);
}
}
impl ObjectController {}