a mad redo of how objects work

This commit is contained in:
Corwin 2023-04-02 03:10:07 +01:00
parent 3e2c57f838
commit ab082c59a1
No known key found for this signature in database
13 changed files with 1509 additions and 1351 deletions

View file

@ -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"

View file

@ -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)]),
);
}
}

View file

@ -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();
}
}

View file

@ -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,

File diff suppressed because it is too large Load diff

View file

@ -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<ObjectKey>,
previous: Option<ObjectKey>,
}
struct ObjectItem {
object: UnsafeCell<UnmanagedObject>,
z_order: Cell<Ordering>,
z_index: Cell<i32>,
}
struct Store {
store: UnsafeCell<slotmap::SlotMap<ObjectKey, ObjectItem>>,
first_z: Cell<Option<ObjectKey>>,
}
struct StoreIterator<'store> {
store: &'store slotmap::SlotMap<ObjectKey, ObjectItem>,
current: Option<ObjectKey>,
}
impl<'store> Iterator for StoreIterator<'store> {
type Item = &'store ObjectItem;
fn next(&mut self) -> Option<Self::Item> {
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<i32>) -> &mut Self {
self.object().set_position(position);
self
}
pub fn set_sprite(&mut self, sprite: SpriteVram) -> &mut Self {
self.object().set_sprite(sprite);
self
}
}

View file

@ -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};

View file

@ -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<Align, Bytes: ?Sized> {
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<Item = &'static Tag> {
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),
}
}
}

View file

@ -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<PaletteId, Weak<PaletteVramData>>,
static_sprite_map: HashMap<SpriteId, Weak<SpriteVramData>>,
}
#[derive(Clone, Copy, Debug)]
struct Location(usize);
impl Location {
fn from_sprite_ptr(d: NonNull<u8>) -> Self {
Self(((d.as_ptr() as usize) - TILE_SPRITE) / BYTES_PER_TILE_4BPP)
}
fn from_palette_ptr(d: NonNull<u8>) -> 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<PaletteVramData>,
}
impl PaletteVram {
fn new(palette: &Palette16) -> Option<PaletteVram> {
let allocated = unsafe { PALETTE_ALLOCATOR.alloc(Palette16::layout()) }?;
unsafe {
allocated
.as_ptr()
.cast::<u16>()
.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<SpriteVramData>,
}
impl SpriteVram {
fn new(data: &[u8], size: Size, palette: PaletteVram) -> Option<SpriteVram> {
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<PaletteId, Weak<PaletteVramData>>,
sprite: &'static Sprite,
) -> Option<(Weak<SpriteVramData>, 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<PaletteId, Weak<PaletteVramData>>,
palette: &'static Palette16,
) -> Option<PaletteVram> {
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<SpriteVram> {
// 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<PaletteVram> {
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> {
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")
}
}

View file

@ -0,0 +1,5 @@
mod attributes;
mod object;
pub use attributes::AffineMode;
pub use object::{OAMIterator, OAMSlot, UnmanagedOAM, UnmanagedObject};

View file

@ -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,
}
}

View file

@ -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<Self::Item> {
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<SpriteVram>,
}
#[derive(Debug)]
pub struct UnmanagedObject {
attributes: Attributes,
sprites: UnsafeCell<VramSprites>,
}
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<i32>) -> &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
}
}

View file

@ -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::<Self>()
}
}