diff --git a/agb-image-converter/src/sprite/multi.rs b/agb-image-converter/src/sprite/multi.rs index 8e6ced197..587c72077 100644 --- a/agb-image-converter/src/sprite/multi.rs +++ b/agb-image-converter/src/sprite/multi.rs @@ -175,7 +175,7 @@ impl ToTokens for Output { let y = sprite.size.1 as usize; quote! { - unsafe { Sprite::new_multi(&PALETTE, align_bytes!(u16, #data), Size::from_width_height(#x, #y)) } + unsafe { Sprite::new_multi(&PALETTE, align_bytes!(u32, #data), Size::from_width_height(#x, #y)) } } }); @@ -202,8 +202,6 @@ impl ToTokens for Output { } }); - let start = (16 - self.palette.len() / 16) as u32; - let input_files = self.input_files.iter().map(|file| { quote! { const _: &[u8] = include_bytes!(#file); @@ -213,7 +211,7 @@ impl ToTokens for Output { tokens.extend(quote! { #(#input_files)* - static PALETTE: PaletteMulti = PaletteMulti::new(#start, &[#(#palettes),*] ); + static PALETTE: PaletteMulti = PaletteMulti::new(&[#(#palettes),*] ); static SPRITES: &[Sprite] = &[#(#sprites),*]; #(#tags)* diff --git a/agb-image-converter/src/sprite/regular.rs b/agb-image-converter/src/sprite/regular.rs index afe26dd69..f41000836 100644 --- a/agb-image-converter/src/sprite/regular.rs +++ b/agb-image-converter/src/sprite/regular.rs @@ -145,7 +145,7 @@ impl ToTokens for Output { let palette_idx = sprite.palette as usize; quote! { - unsafe { Sprite::new(&PALETTES[#palette_idx], align_bytes!(u16, #data), Size::from_width_height(#x, #y)) } + unsafe { Sprite::new(&PALETTES[#palette_idx], align_bytes!(u32, #data), Size::from_width_height(#x, #y)) } } }); diff --git a/agb/gfx/test_output/object/dynamic_sprite.png b/agb/gfx/test_output/object/dynamic_sprite.png new file mode 100644 index 000000000..cceb10046 Binary files /dev/null and b/agb/gfx/test_output/object/dynamic_sprite.png differ diff --git a/agb/gfx/test_output/object/dynamic_sprite_256.png b/agb/gfx/test_output/object/dynamic_sprite_256.png new file mode 100644 index 000000000..cceb10046 Binary files /dev/null and b/agb/gfx/test_output/object/dynamic_sprite_256.png differ diff --git a/agb/gfx/test_output/object/dynamic_sprite_copy.png b/agb/gfx/test_output/object/dynamic_sprite_copy.png new file mode 100644 index 000000000..cceb10046 Binary files /dev/null and b/agb/gfx/test_output/object/dynamic_sprite_copy.png differ diff --git a/agb/src/display/font/object.rs b/agb/src/display/font/object.rs index 17f194fb4..9a0801afb 100644 --- a/agb/src/display/font/object.rs +++ b/agb/src/display/font/object.rs @@ -74,7 +74,7 @@ impl ObjectTextRenderer { let pal_index = group.palette_index(); for pixel in group.pixels() { - sprite.set_pixel(pixel.x as usize, pixel.y as usize, pal_index as usize); + sprite.set_pixel(pixel.x as usize, pixel.y as usize, pal_index); } let mut object = Object::new(sprite.to_vram(self.palette.clone())); diff --git a/agb/src/display/object/sprites.rs b/agb/src/display/object/sprites.rs index aea7b6ffd..f94c57c3b 100644 --- a/agb/src/display/object/sprites.rs +++ b/agb/src/display/object/sprites.rs @@ -2,7 +2,7 @@ mod sprite; mod sprite_allocator; const BYTES_PER_TILE_4BPP: usize = 32; -const BYTES_PER_TILE_8BPP: usize = 16; +const BYTES_PER_TILE_8BPP: usize = 64; pub use sprite::{PaletteMulti, Size, Sprite, Tag, include_aseprite}; pub use sprite_allocator::{ diff --git a/agb/src/display/object/sprites/sprite.rs b/agb/src/display/object/sprites/sprite.rs index e615295a5..1dc33fc49 100644 --- a/agb/src/display/object/sprites/sprite.rs +++ b/agb/src/display/object/sprites/sprite.rs @@ -32,13 +32,12 @@ pub struct PaletteMulti { impl PaletteMulti { #[must_use] /// Create a new palette. The first index is the index where the palette starts. - pub const fn new(first_index: u32, palettes: &'static [Palette16]) -> Self { + pub const fn new(palettes: &'static [Palette16]) -> Self { assert!(palettes.len() <= 16); assert!(!palettes.is_empty()); - assert!(16 - palettes.len() >= first_index as usize); Self { - first_index, + first_index: (16 - palettes.len()) as u32, palettes, } } @@ -54,6 +53,12 @@ impl PaletteMulti { pub const fn first_index(&self) -> u32 { self.first_index } + + #[must_use] + /// Gets the first colour from this palette + pub const fn first_colour_index(&self) -> u8 { + (self.first_index * 16) as u8 + } } impl Sprite { diff --git a/agb/src/display/object/sprites/sprite_allocator.rs b/agb/src/display/object/sprites/sprite_allocator.rs index e058b75a6..ebc776b54 100644 --- a/agb/src/display/object/sprites/sprite_allocator.rs +++ b/agb/src/display/object/sprites/sprite_allocator.rs @@ -178,6 +178,12 @@ impl TryFrom for PaletteVramSingle { } } +impl From<&'static PaletteMulti> for PaletteVramMulti { + fn from(value: &'static PaletteMulti) -> Self { + PaletteVramMulti::new(value) + } +} + impl TryFrom for PaletteVramMulti { type Error = PaletteVram; diff --git a/agb/src/display/object/sprites/sprite_allocator/dynamic.rs b/agb/src/display/object/sprites/sprite_allocator/dynamic.rs index 8b38564a9..e526a36f8 100644 --- a/agb/src/display/object/sprites/sprite_allocator/dynamic.rs +++ b/agb/src/display/object/sprites/sprite_allocator/dynamic.rs @@ -3,7 +3,9 @@ use core::{ ptr::NonNull, }; -use alloc::boxed::Box; +use crate::{InternalAllocator, display::object::PaletteVramMulti}; + +use alloc::{alloc::AllocError, boxed::Box}; use crate::display::object::{ Size, @@ -13,7 +15,7 @@ use crate::display::object::{ }; use super::{ - LoaderError, PaletteVramMulti, PaletteVramSingle, + PaletteVramSingle, sprite::{SpriteAllocator, SpriteLocation, SpriteVram, SpriteVramInner}, }; @@ -28,204 +30,420 @@ fn allocate_with_retry(layout: Layout) -> Result, alloc::alloc::Al SpriteAllocator.allocate(layout) } -fn allocate_zeroed_with_retry(layout: Layout) -> Result, alloc::alloc::AllocError> { - if let Ok(x) = SpriteAllocator.allocate_zeroed(layout) { - return Ok(x); +/// A mutable dynamic sprite buffer that can be generated at run time +#[derive(Clone)] +pub struct DynamicSprite16 { + data: Box<[u32], A>, + size: Size, +} + +impl DynamicSprite16 { + fn allocation_size(size: Size) -> usize { + size.size_bytes_16() } - unsafe { - garbage_collect_sprite_loader(); + + fn layout(&self) -> Layout { + self.size.layout(false) + } + + /// Set the pixel of a sprite to a given colour index from the palette. + /// + /// # Panics + /// Panics if the pixel would be outside the range of the palette + /// or the coordinate is outside the sprite. + pub fn set_pixel(&mut self, x: usize, y: usize, paletted_pixel: u8) { + assert!(paletted_pixel < 16); + + let (sprite_pixel_x, sprite_pixel_y) = self.size.to_width_height(); + assert!(x < sprite_pixel_x, "x too big for sprite size"); + assert!(y < sprite_pixel_y, "y too big for sprite size"); + + let (sprite_tile_x, _) = self.size.to_tiles_width_height(); + + let (adjust_tile_x, adjust_tile_y) = (x / 8, y / 8); + + let tile_number_to_modify = adjust_tile_x + adjust_tile_y * sprite_tile_x; + + let (x_in_tile, y_in_tile) = (x % 8, y % 8); + + let byte_to_modify_in_tile = x_in_tile / 2 + y_in_tile * 4; + + let byte_to_modify = tile_number_to_modify * BYTES_PER_TILE_4BPP + byte_to_modify_in_tile; + let mut byte = self.data()[byte_to_modify]; + + let nibble_to_modify = (x % 2) * 4; + + byte = (byte & !(0b1111 << nibble_to_modify)) | (paletted_pixel << nibble_to_modify); + self.data_mut()[byte_to_modify] = byte; + } + + /// Copies the sprite data to sprite vram + pub fn try_to_vram( + &self, + palette: impl Into, + ) -> Result { + let data = allocate_with_retry(self.layout())?; + + unsafe { + let dest: *mut u32 = data.as_ptr().cast(); + core::ptr::copy_nonoverlapping(self.data.as_ptr(), dest, self.data.len()); + } + + let palette = palette.into().palette(); + + let inner = unsafe { + SpriteVramInner::new_from_allocated( + SpriteLocation::from_ptr(data.cast()), + self.size, + palette.is_multi(), + ) + }; + Ok(SpriteVram::new(inner, palette)) + } + + /// Copies the sprite data to sprite vram + pub fn to_vram(&self, palette: impl Into) -> SpriteVram { + self.try_to_vram(palette) + .expect("should be able to allocate sprite buffer") } - SpriteAllocator.allocate_zeroed(layout) + /// Wipes the sprite clearing it with a specified pixel + /// + /// # Panics + /// Panics if the pixel would be outside the range of the palette + pub fn clear(&mut self, paletted_pixel: usize) { + assert!(paletted_pixel < 16); + let reset = (paletted_pixel | (paletted_pixel << 4)) as u8; + self.data_mut().fill(reset); + } +} + +/// A mutable dynamic sprite buffer that can be generated at run time +#[derive(Clone)] +pub struct DynamicSprite256 { + data: Box<[u32], A>, + size: Size, } -macro_rules! dynamic_sprite_defn { - ($name: ident, $multi: literal, $palette: ty) => { - /// A mutable dynamic sprite that can be generated at run time - pub struct $name { - data: Box<[u16], SpriteAllocator>, - size: Size, +impl DynamicSprite256 { + fn allocation_size(size: Size) -> usize { + size.size_bytes_256() + } + + fn layout(&self) -> Layout { + self.size.layout(true) + } + + /// Set the pixel of a sprite to a given colour index from the palette. + /// + /// # Panics + /// Panics if the pixel would be outside the range of the palette + /// or the coordinate is outside the sprite. + pub fn set_pixel(&mut self, x: usize, y: usize, paletted_pixel: u8) { + let (sprite_pixel_x, sprite_pixel_y) = self.size.to_width_height(); + assert!(x < sprite_pixel_x, "x too big for sprite size"); + assert!(y < sprite_pixel_y, "y too big for sprite size"); + + let (sprite_tile_x, _) = self.size.to_tiles_width_height(); + + let (adjust_tile_x, adjust_tile_y) = (x / 8, y / 8); + + let tile_number_to_modify = adjust_tile_x + adjust_tile_y * sprite_tile_x; + + let (x_in_tile, y_in_tile) = (x % 8, y % 8); + + let byte_to_modify_in_tile = x_in_tile + y_in_tile * 8; + + let byte_to_modify = tile_number_to_modify * BYTES_PER_TILE_8BPP + byte_to_modify_in_tile; + + self.data_mut()[byte_to_modify] = paletted_pixel; + } + + /// Copies the sprite data to sprite vram + pub fn try_to_vram( + &self, + palette: impl Into, + ) -> Result { + let data = allocate_with_retry(self.layout())?; + + unsafe { + let dest: *mut u32 = data.as_ptr().cast(); + core::ptr::copy_nonoverlapping(self.data.as_ptr(), dest, self.data.len()); } - // this is explicitly written out so that the extreme alignment conditions are correcly passed to the allocator - impl Clone for $name { - fn clone(&self) -> Self { - let allocation = allocate_with_retry(self.size.layout($multi)) - .expect("cannot allocate dynamic sprite"); + let palette = palette.into().palette(); - let allocation = core::ptr::slice_from_raw_parts_mut( - allocation.as_ptr() as *mut _, - allocation.len() / 2, - ); + let inner = unsafe { + SpriteVramInner::new_from_allocated( + SpriteLocation::from_ptr(data.cast()), + self.size, + palette.is_multi(), + ) + }; + Ok(SpriteVram::new(inner, palette)) + } - let mut data = unsafe { Box::from_raw_in(allocation, SpriteAllocator) }; + /// Copies the sprite data to sprite vram + pub fn to_vram(&self, palette: impl Into) -> SpriteVram { + self.try_to_vram(palette) + .expect("should be able to allocate sprite buffer") + } - data.clone_from_slice(&self.data); + /// Wipes the sprite clearing it with a specified pixel + /// + /// # Panics + /// Panics if the pixel would be outside the range of the palette + pub fn clear(&mut self, paletted_pixel: usize) { + assert!(paletted_pixel < 256); + self.data_mut().fill(paletted_pixel as u8); + } +} - Self { - data, - size: self.size, - } +macro_rules! common_impls { + ($name: ident) => { + impl $name { + /// Creates a new sprite buffer in iwram + pub fn try_new(size: Size) -> Result { + Self::try_new_in(size, InternalAllocator) + } + + /// Creates a new sprite buffer in iwram + pub fn new(size: Size) -> Self { + Self::new_in(size, InternalAllocator) + } + + /// Copies data from the byte buffer into a new allocation + pub fn from_bytes(size: Size, bytes: &[u8]) -> Self { + Self::from_bytes_in(size, bytes, InternalAllocator) + } + + /// Copies data from the byte buffer into a new allocation + pub fn try_from_bytes(size: Size, bytes: &[u8]) -> Result { + Self::try_from_bytes_in(size, bytes, InternalAllocator) } } - impl $name { - /// Attempts to allocate a dynamic sprite returning an error should - /// there be no more space available for the sprite to be allocated - /// into. - pub fn try_new(size: Size) -> Result { - let allocation = allocate_zeroed_with_retry(size.layout($multi)) - .map_err(|_| LoaderError::SpriteFull)?; - - let allocation = core::ptr::slice_from_raw_parts_mut( - allocation.as_ptr() as *mut _, - allocation.len() / 2, + impl $name { + /// Creates a new sprite buffer in the given allocator + pub fn try_new_in(size: Size, allocator: A) -> Result { + let data = + Box::try_new_zeroed_slice_in(Self::allocation_size(size) / 4, allocator)?; + let data = unsafe { data.assume_init() }; + + Ok(Self { data, size }) + } + + /// Copies data from the byte buffer into a new allocation + pub fn try_from_bytes_in( + size: Size, + bytes: &[u8], + allocator: A, + ) -> Result { + let allocation_size = Self::allocation_size(size); + assert_eq!( + bytes.len(), + allocation_size, + "buffer length should match sprite size" ); - let data = unsafe { Box::from_raw_in(allocation, SpriteAllocator) }; + let mut data = + Box::<[u32], A>::try_new_uninit_slice_in(allocation_size / 4, allocator)?; + + let data = unsafe { + // cast the data ptr to a u32 ptr and memcpy + let raw = data.as_mut_ptr() as *mut u32; + let raw = raw as *mut u8; + core::ptr::copy_nonoverlapping(bytes.as_ptr(), raw, allocation_size); + data.assume_init() + }; Ok(Self { data, size }) } - #[must_use] - /// Creates a new dynamic sprite of a given size - /// - /// # Panics - /// Panics if there is no space to allocate sprites into - pub fn new(size: Size) -> Self { - Self::try_new(size).expect("couldn't allocate dynamic sprite") + /// Copies data from the byte buffer into a new allocation + pub fn from_bytes_in(size: Size, bytes: &[u8], allocator: A) -> Self { + Self::try_from_bytes_in(size, bytes, allocator) + .expect("should be able to allocate sprite buffer") } - /// Attempts to allocate the sprite in vram and copies the data from the given slice. - pub fn try_copy_from(size: Size, data: &[u8]) -> Result { - let layout = size.layout($multi); - assert_eq!(layout.size(), data.len()); + /// Creates a new sprite buffer in the given allocator + pub fn new_in(size: Size, allocator: A) -> Self { + Self::try_new_in(size, allocator).expect("should be able to allocate sprite buffer") + } - let allocation = - allocate_with_retry(layout).expect("cannot allocate dynamic sprite"); + /// Creates a copy of the sprite data, this can potentially be in another allocator. + pub fn try_clone_in(&self, allocator: B) -> Result<$name, AllocError> { + let mut data = + Box::<[u32], B>::try_new_uninit_slice_in(self.data.len(), allocator)?; - let allocation = core::ptr::slice_from_raw_parts_mut( - allocation.as_ptr() as *mut _, - allocation.len() / 2, - ); + let data = unsafe { + // cast the data ptr to a u32 ptr and memcpy + let raw = data.as_mut_ptr() as *mut u32; + core::ptr::copy_nonoverlapping(self.data.as_ptr(), raw, self.data.len()); + data.assume_init() + }; - let data = unsafe { Box::from_raw_in(allocation, SpriteAllocator) }; + Ok($name { + data, + size: self.size, + }) + } - Ok(Self { data, size }) + /// Creates a copy of the sprite data, this can potentially be in another allocator. + pub fn clone_in(&self, allocator: B) -> $name { + self.try_clone_in(allocator) + .expect("should be able to allocate sprite buffer") } - /// Attempts to allocate the sprite in vram and copies the data from the given slice. - /// - /// # Panics - /// Panics if there is no space to allocate sprites into - #[must_use] - pub fn copy_from(size: Size, data: &[u8]) -> Self { - Self::try_copy_from(size, data).expect("couldn't allocate dynamic sprite") + /// Access the underlying sprite buffer as a byte slice. + /// The data is guaranteed to be aligned to a 4 byte boundary. + pub fn data(&self) -> &[u8] { + unsafe { + let raw = self.data.as_ptr(); + core::slice::from_raw_parts(raw.cast(), self.data.len() * 4) + } } - /// Set the pixel of a sprite to a given colour index from the palette. - /// - /// # Panics - /// Panics if the pixel would be outside the range of the palette - pub fn set_pixel(&mut self, x: usize, y: usize, paletted_pixel: usize) { - if !$multi { - assert!(paletted_pixel < 16); + /// Access the underlying sprite buffer as a mutable byte slice. + /// The data is guaranteed to be aligned to a 4 byte boundary. + pub fn data_mut(&mut self) -> &mut [u8] { + unsafe { + let raw = self.data.as_mut_ptr(); + core::slice::from_raw_parts_mut(raw.cast(), self.data.len() * 4) + } + } + } + }; +} - let (sprite_pixel_x, sprite_pixel_y) = self.size.to_width_height(); - assert!(x < sprite_pixel_x, "x too big for sprite size"); - assert!(y < sprite_pixel_y, "y too big for sprite size"); +common_impls!(DynamicSprite16); +common_impls!(DynamicSprite256); + +#[cfg(test)] +mod tests { + use crate::{ + display::{ + HEIGHT, Palette16, Rgb, Rgb15, WIDTH, + object::{DynamicSprite16, DynamicSprite256, Object, PaletteMulti, Size}, + tiled::VRAM_MANAGER, + }, + test_runner::assert_image_output, + }; - let (sprite_tile_x, _) = self.size.to_tiles_width_height(); + #[test_case] + fn check_dynamic_sprite_16(gba: &mut crate::Gba) { + let mut gfx = gba.graphics.get(); + let mut frame = gfx.frame(); - let (adjust_tile_x, adjust_tile_y) = (x / 8, y / 8); + VRAM_MANAGER.set_background_palette_colour(0, 0, Rgb::new(0xff, 0, 0xff).to_rgb15()); - let tile_number_to_modify = adjust_tile_x + adjust_tile_y * sprite_tile_x; + static PALETTE: Palette16 = const { + let mut palette = [Rgb15::BLACK; 16]; + palette[1] = Rgb15::WHITE; + palette[2] = Rgb15(0x10_7C); + Palette16::new(palette) + }; - let (x_in_tile, y_in_tile) = (x % 8, y % 8); + let mut sprite = DynamicSprite16::new(Size::S8x8); - let half_word_to_modify_in_tile = x_in_tile / 4 + y_in_tile * 2; + sprite.set_pixel(2, 2, 1); + sprite.set_pixel(6, 2, 1); - let half_word_to_modify = tile_number_to_modify * BYTES_PER_TILE_4BPP / 2 - + half_word_to_modify_in_tile; - let mut half_word = self.data[half_word_to_modify]; + sprite.set_pixel(1, 6, 1); + sprite.set_pixel(2, 7, 1); + sprite.set_pixel(3, 7, 1); + sprite.set_pixel(4, 7, 1); + sprite.set_pixel(5, 7, 1); + sprite.set_pixel(6, 7, 1); + sprite.set_pixel(7, 6, 1); - let nibble_to_modify = (x % 4) * 4; + let sprite = sprite.to_vram(&PALETTE); - half_word = (half_word & !(0b1111 << nibble_to_modify)) - | ((paletted_pixel as u16) << nibble_to_modify); - self.data[half_word_to_modify] = half_word; - } else { - assert!(paletted_pixel < 256); + Object::new(sprite) + .set_pos((WIDTH / 2 - 4, HEIGHT / 2 - 4)) + .show(&mut frame); - let (sprite_pixel_x, sprite_pixel_y) = self.size.to_width_height(); - assert!(x < sprite_pixel_x, "x too big for sprite size"); - assert!(y < sprite_pixel_y, "y too big for sprite size"); + frame.commit(); - let (sprite_tile_x, _) = self.size.to_tiles_width_height(); + assert_image_output("gfx/test_output/object/dynamic_sprite.png"); + } - let (adjust_tile_x, adjust_tile_y) = (x / 8, y / 8); + #[test_case] + fn check_dynamic_sprite_256(gba: &mut crate::Gba) { + let mut gfx = gba.graphics.get(); + let mut frame = gfx.frame(); - let tile_number_to_modify = adjust_tile_x + adjust_tile_y * sprite_tile_x; + VRAM_MANAGER.set_background_palette_colour(0, 0, Rgb::new(0xff, 0, 0xff).to_rgb15()); - let (x_in_tile, y_in_tile) = (x % 8, y % 8); + static PALETTE: PaletteMulti = const { + static PALETTE: &[Palette16] = &[const { + let palette = [Rgb15::WHITE; 16]; + Palette16::new(palette) + }]; + PaletteMulti::new(PALETTE) + }; - let half_word_to_modify_in_tile = x_in_tile / 2 + y_in_tile * 2; + let mut sprite = DynamicSprite256::new(Size::S8x8); + let colour = PALETTE.first_colour_index(); - let half_word_to_modify = tile_number_to_modify * BYTES_PER_TILE_8BPP / 2 - + half_word_to_modify_in_tile; - let mut half_word = self.data[half_word_to_modify]; + sprite.set_pixel(2, 2, colour); + sprite.set_pixel(6, 2, colour); - let byte_to_modify = (x % 2) * 8; + sprite.set_pixel(1, 6, colour); + sprite.set_pixel(2, 7, colour); + sprite.set_pixel(3, 7, colour); + sprite.set_pixel(4, 7, colour); + sprite.set_pixel(5, 7, colour); + sprite.set_pixel(6, 7, colour); + sprite.set_pixel(7, 6, colour); - half_word = (half_word & !(0b11111111 << byte_to_modify)) - | ((paletted_pixel as u16) << byte_to_modify); - self.data[half_word_to_modify] = half_word; - } - } + let sprite = sprite.to_vram(&PALETTE); - /// Wipes the sprite clearing it with a specified pixel - /// - /// # Panics - /// Panics if the pixel would be outside the range of the palette - pub fn clear(&mut self, paletted_pixel: usize) { - if !$multi { - assert!(paletted_pixel < 16); - let reset = (paletted_pixel - | (paletted_pixel << 4) - | (paletted_pixel << 8) - | (paletted_pixel << 12)) as u16; - self.data.fill(reset); - } else { - assert!(paletted_pixel < 256); - let reset = (paletted_pixel | (paletted_pixel << 8)) as u16; - self.data.fill(reset); - } - } + Object::new(sprite) + .set_pos((WIDTH / 2 - 4, HEIGHT / 2 - 4)) + .show(&mut frame); - #[must_use] - /// Transforms the sprite to a reference counted immutable sprite usable by objects - pub fn to_vram(self, palette: impl Into<$palette>) -> SpriteVram { - let data = unsafe { NonNull::new_unchecked(Box::leak(self.data).as_mut_ptr()) }; + frame.commit(); - let palette = palette.into().palette(); + assert_image_output("gfx/test_output/object/dynamic_sprite_256.png"); + } - let inner = unsafe { - SpriteVramInner::new_from_allocated( - SpriteLocation::from_ptr(data.cast()), - self.size, - palette.is_multi(), - ) - }; - SpriteVram::new(inner, palette) - } + #[test_case] + fn check_dynamic_sprite_copy(gba: &mut crate::Gba) { + let mut gfx = gba.graphics.get(); + let mut frame = gfx.frame(); - /// Raw access to the inner data - pub fn data(&mut self) -> &mut [u16] { - &mut self.data - } - } - }; -} + VRAM_MANAGER.set_background_palette_colour(0, 0, Rgb::new(0xff, 0, 0xff).to_rgb15()); + + static PALETTE: Palette16 = const { + let mut palette = [Rgb15::BLACK; 16]; + palette[1] = Rgb15::WHITE; + palette[2] = Rgb15(0x10_7C); + Palette16::new(palette) + }; + + let mut sprite = DynamicSprite16::new(Size::S8x8); + + sprite.set_pixel(2, 2, 1); + sprite.set_pixel(6, 2, 1); -dynamic_sprite_defn!(DynamicSprite16, false, PaletteVramSingle); -dynamic_sprite_defn!(DynamicSprite256, true, PaletteVramMulti); + sprite.set_pixel(1, 6, 1); + sprite.set_pixel(2, 7, 1); + sprite.set_pixel(3, 7, 1); + sprite.set_pixel(4, 7, 1); + sprite.set_pixel(5, 7, 1); + sprite.set_pixel(6, 7, 1); + sprite.set_pixel(7, 6, 1); + + let copy = sprite.clone(); + let sprite = copy.to_vram(&PALETTE); + + Object::new(sprite) + .set_pos((WIDTH / 2 - 4, HEIGHT / 2 - 4)) + .show(&mut frame); + + frame.commit(); + + assert_image_output("gfx/test_output/object/dynamic_sprite_copy.png"); + } +}