mirror of
https://github.com/italicsjenga/agb.git
synced 2024-12-23 16:21:33 +11:00
a mad redo of how objects work
This commit is contained in:
parent
3e2c57f838
commit
ab082c59a1
|
@ -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"
|
||||
|
|
|
@ -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)]),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
386
agb/src/display/object/managed.rs
Normal file
386
agb/src/display/object/managed.rs
Normal 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
|
||||
}
|
||||
}
|
7
agb/src/display/object/sprites.rs
Normal file
7
agb/src/display/object/sprites.rs
Normal 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};
|
369
agb/src/display/object/sprites/sprite.rs
Normal file
369
agb/src/display/object/sprites/sprite.rs
Normal 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),
|
||||
}
|
||||
}
|
||||
}
|
288
agb/src/display/object/sprites/sprite_allocator.rs
Normal file
288
agb/src/display/object/sprites/sprite_allocator.rs
Normal 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")
|
||||
}
|
||||
}
|
5
agb/src/display/object/unmanaged.rs
Normal file
5
agb/src/display/object/unmanaged.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
mod attributes;
|
||||
mod object;
|
||||
|
||||
pub use attributes::AffineMode;
|
||||
pub use object::{OAMIterator, OAMSlot, UnmanagedOAM, UnmanagedObject};
|
204
agb/src/display/object/unmanaged/attributes.rs
Normal file
204
agb/src/display/object/unmanaged/attributes.rs
Normal 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,
|
||||
}
|
||||
}
|
187
agb/src/display/object/unmanaged/object.rs
Normal file
187
agb/src/display/object/unmanaged/object.rs
Normal 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
|
||||
}
|
||||
}
|
|
@ -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>()
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue