From a0846d369012a694f9db677b438b9ab595ca6dfc Mon Sep 17 00:00:00 2001 From: Corwin Date: Sun, 4 Jan 2026 15:59:30 +0000 Subject: [PATCH 1/9] rewrite dynamic sprites to use buffers in iw/ewram --- agb/src/agbabi/mod.rs | 10 + .../sprites/sprite_allocator/dynamic.rs | 307 +++++++++--------- 2 files changed, 170 insertions(+), 147 deletions(-) diff --git a/agb/src/agbabi/mod.rs b/agb/src/agbabi/mod.rs index 8b85649eb..b79ad1068 100644 --- a/agb/src/agbabi/mod.rs +++ b/agb/src/agbabi/mod.rs @@ -6,6 +6,16 @@ global_asm!(concat!( include_str!("memset.s") )); +unsafe extern "C" { + fn __agbabi_memcpy2(dest: *mut u16, src: *const u16, n: usize); +} + +pub unsafe fn memcpy_16(src: *const u16, dest: *mut u16, count: usize) { + unsafe { + __agbabi_memcpy2(dest, src, count * 2); + } +} + #[cfg(test)] mod test { mod memset { diff --git a/agb/src/display/object/sprites/sprite_allocator/dynamic.rs b/agb/src/display/object/sprites/sprite_allocator/dynamic.rs index 8b38564a9..e1d85181f 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; + +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,184 +30,194 @@ 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); - } - unsafe { - garbage_collect_sprite_loader(); - } - - SpriteAllocator.allocate_zeroed(layout) +/// A mutable dynamic sprite buffer that can be generated at run time +pub struct DynamicSprite16 { + data: Box<[u16], 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, - } - - // 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 allocation = core::ptr::slice_from_raw_parts_mut( - allocation.as_ptr() as *mut _, - allocation.len() / 2, - ); +impl DynamicSprite16 { + fn allocation_size(size: Size) -> usize { + size.size_bytes_16() + } - let mut data = unsafe { Box::from_raw_in(allocation, SpriteAllocator) }; + fn layout(&self) -> Layout { + self.size.layout(false) + } - data.clone_from_slice(&self.data); + /// 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: usize) { + assert!(paletted_pixel < 16); - Self { - data, - size: self.size, - } - } - } + 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"); - 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 (sprite_tile_x, _) = self.size.to_tiles_width_height(); - let allocation = core::ptr::slice_from_raw_parts_mut( - allocation.as_ptr() as *mut _, - allocation.len() / 2, - ); + let (adjust_tile_x, adjust_tile_y) = (x / 8, y / 8); - let data = unsafe { Box::from_raw_in(allocation, SpriteAllocator) }; + let tile_number_to_modify = adjust_tile_x + adjust_tile_y * sprite_tile_x; - Ok(Self { data, size }) - } + let (x_in_tile, y_in_tile) = (x % 8, y % 8); - #[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") - } + let half_word_to_modify_in_tile = x_in_tile / 4 + y_in_tile * 2; - /// 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()); + 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]; - let allocation = - allocate_with_retry(layout).expect("cannot allocate dynamic sprite"); + let nibble_to_modify = (x % 4) * 4; - let allocation = core::ptr::slice_from_raw_parts_mut( - allocation.as_ptr() as *mut _, - allocation.len() / 2, - ); + half_word = (half_word & !(0b1111 << nibble_to_modify)) + | ((paletted_pixel as u16) << nibble_to_modify); + self.data[half_word_to_modify] = half_word; + } - let data = unsafe { Box::from_raw_in(allocation, SpriteAllocator) }; + /// 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) + | (paletted_pixel << 8) + | (paletted_pixel << 12)) as u16; + self.data.fill(reset); + } +} - Ok(Self { data, size }) - } +/// A mutable dynamic sprite buffer that can be generated at run time +pub struct DynamicSprite256 { + data: Box<[u16], A>, + size: Size, +} - /// 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") - } +impl DynamicSprite256 { + fn allocation_size(size: Size) -> usize { + size.size_bytes_256() + } - /// 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); + fn layout(&self) -> Layout { + self.size.layout(true) + } - 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"); + /// 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: usize) { + assert!(paletted_pixel < 256); - let (sprite_tile_x, _) = self.size.to_tiles_width_height(); + 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 (adjust_tile_x, adjust_tile_y) = (x / 8, y / 8); + let (sprite_tile_x, _) = self.size.to_tiles_width_height(); - let tile_number_to_modify = adjust_tile_x + adjust_tile_y * sprite_tile_x; + let (adjust_tile_x, adjust_tile_y) = (x / 8, y / 8); - let (x_in_tile, y_in_tile) = (x % 8, y % 8); + let tile_number_to_modify = adjust_tile_x + adjust_tile_y * sprite_tile_x; - let half_word_to_modify_in_tile = x_in_tile / 4 + y_in_tile * 2; + let (x_in_tile, y_in_tile) = (x % 8, y % 8); - 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]; + let half_word_to_modify_in_tile = x_in_tile / 2 + y_in_tile * 2; - let nibble_to_modify = (x % 4) * 4; + 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]; - 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); + let byte_to_modify = (x % 2) * 8; - 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"); + 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_tile_x, _) = self.size.to_tiles_width_height(); + /// 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); + let reset = (paletted_pixel | (paletted_pixel << 8)) as u16; + self.data.fill(reset); + } +} - let (adjust_tile_x, adjust_tile_y) = (x / 8, y / 8); +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) + } - let tile_number_to_modify = adjust_tile_x + adjust_tile_y * sprite_tile_x; + /// Creates a new sprite buffer in iwram + pub fn new(size: Size) -> Self { + Self::new_in(size, InternalAllocator) + } + } - let (x_in_tile, y_in_tile) = (x % 8, y % 8); + 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) / 2, allocator)?; + let data = unsafe { data.assume_init() }; - let half_word_to_modify_in_tile = x_in_tile / 2 + y_in_tile * 2; + Ok(Self { data, size }) + } - 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]; + /// 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 byte_to_modify = (x % 2) * 8; + /// Creates a copy of the sprite data, this can potentially be in another allocator. + pub fn try_clone_from_in( + other: $name, + allocator: A, + ) -> Result { + let mut data = + Box::<[u16], A>::try_new_uninit_slice_in(other.data.len(), allocator)?; + + let data = unsafe { + // cast the data ptr to a u16 ptr and memcpy + let raw = data.as_mut_ptr() as *mut u16; + core::ptr::copy_nonoverlapping(other.data.as_ptr(), raw, other.data.len()); + data.assume_init() + }; - half_word = (half_word & !(0b11111111 << byte_to_modify)) - | ((paletted_pixel as u16) << byte_to_modify); - self.data[half_word_to_modify] = half_word; - } + Ok(Self { + data, + size: other.size, + }) } - /// 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); - } + /// Creates a copy of the sprite data, this can potentially be in another allocator. + pub fn clone_from_in(other: $name, allocator: A) -> Self { + Self::try_clone_from_in(other, allocator) + .expect("should be able to allocate sprite buffer") } - #[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()) }; + /// 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 = data.cast().as_ptr(); + crate::agbabi::memcpy_16(self.data.as_ptr(), dest, self.data.len()); + } let palette = palette.into().palette(); @@ -216,16 +228,17 @@ macro_rules! dynamic_sprite_defn { palette.is_multi(), ) }; - SpriteVram::new(inner, palette) + Ok(SpriteVram::new(inner, palette)) } - /// Raw access to the inner data - pub fn data(&mut self) -> &mut [u16] { - &mut self.data + /// 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") } } }; } -dynamic_sprite_defn!(DynamicSprite16, false, PaletteVramSingle); -dynamic_sprite_defn!(DynamicSprite256, true, PaletteVramMulti); +common_impls!(DynamicSprite16); +common_impls!(DynamicSprite256); From 9ec20cb68f5d0fee3b6f5101b6c0197d0c3190af Mon Sep 17 00:00:00 2001 From: Corwin Date: Sun, 4 Jan 2026 17:17:36 +0000 Subject: [PATCH 2/9] add methods to make from bytes --- .../sprites/sprite_allocator/dynamic.rs | 62 ++++++++++++++++--- 1 file changed, 52 insertions(+), 10 deletions(-) diff --git a/agb/src/display/object/sprites/sprite_allocator/dynamic.rs b/agb/src/display/object/sprites/sprite_allocator/dynamic.rs index e1d85181f..7dd9772bf 100644 --- a/agb/src/display/object/sprites/sprite_allocator/dynamic.rs +++ b/agb/src/display/object/sprites/sprite_allocator/dynamic.rs @@ -31,6 +31,7 @@ fn allocate_with_retry(layout: Layout) -> Result, alloc::alloc::Al } /// A mutable dynamic sprite buffer that can be generated at run time +#[derive(Clone)] pub struct DynamicSprite16 { data: Box<[u16], A>, size: Size, @@ -93,6 +94,7 @@ impl DynamicSprite16 { } /// A mutable dynamic sprite buffer that can be generated at run time +#[derive(Clone)] pub struct DynamicSprite256 { data: Box<[u16], A>, size: Size, @@ -163,6 +165,16 @@ macro_rules! common_impls { 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 { @@ -175,35 +187,65 @@ macro_rules! common_impls { 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 mut data = + Box::<[u16], A>::try_new_uninit_slice_in(allocation_size / 2, allocator)?; + + let data = unsafe { + // cast the data ptr to a u16 ptr and memcpy + let raw = data.as_mut_ptr() as *mut u16; + let raw = raw as *mut u8; + core::ptr::copy_nonoverlapping(bytes.as_ptr(), raw, allocation_size); + data.assume_init() + }; + + Ok(Self { data, size }) + } + + /// 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") + } + /// 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") } /// Creates a copy of the sprite data, this can potentially be in another allocator. - pub fn try_clone_from_in( - other: $name, - allocator: A, - ) -> Result { + pub fn try_clone_in(&self, allocator: B) -> Result<$name, AllocError> { let mut data = - Box::<[u16], A>::try_new_uninit_slice_in(other.data.len(), allocator)?; + Box::<[u16], B>::try_new_uninit_slice_in(self.data.len(), allocator)?; let data = unsafe { // cast the data ptr to a u16 ptr and memcpy let raw = data.as_mut_ptr() as *mut u16; - core::ptr::copy_nonoverlapping(other.data.as_ptr(), raw, other.data.len()); + core::ptr::copy_nonoverlapping(self.data.as_ptr(), raw, self.data.len()); data.assume_init() }; - Ok(Self { + Ok($name { data, - size: other.size, + size: self.size, }) } /// Creates a copy of the sprite data, this can potentially be in another allocator. - pub fn clone_from_in(other: $name, allocator: A) -> Self { - Self::try_clone_from_in(other, allocator) + pub fn clone_in(&self, allocator: B) -> $name { + self.try_clone_in(allocator) .expect("should be able to allocate sprite buffer") } From a063dd29ab70e592624e8a2920230f01ac13b09f Mon Sep 17 00:00:00 2001 From: Corwin Date: Sun, 4 Jan 2026 17:17:36 +0000 Subject: [PATCH 3/9] add tests for dynamic sprites --- agb/gfx/test_output/object/dynamic_sprite.png | Bin 0 -> 1080 bytes .../sprites/sprite_allocator/dynamic.rs | 89 ++++++++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 agb/gfx/test_output/object/dynamic_sprite.png 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 0000000000000000000000000000000000000000..cceb100469b78c66d26cc909353254aa9c3c65d2 GIT binary patch literal 1080 zcmeAS@N?(olHy`uVBq!ia0vp^AAooP2NRIoyR*!Qfr0tIr;B4q1>@TT%nMW^rycwF zzx-YAi8ITKypz}E-rfd9nLYESd99ykWUTb=XW90ws%7og*;m&~6<-bg?;f!8)vEv2 zD`HBo{+A6|x9in^KCSTBul4LxSFiiEzxh;X_}}}EPgkw}_kKYUPN^1&D~4_5up zd3N(m=>M53V!qDzwVA!+mAx=>3>I9i*ZO=1s2~%hz$;;1Gf)NZ)Ybnscdq`odNfi8 gW26$1_x=e+D{@wg_D}i_ED;zyUHx3vIVCg!0FS?YEdT%j literal 0 HcmV?d00001 diff --git a/agb/src/display/object/sprites/sprite_allocator/dynamic.rs b/agb/src/display/object/sprites/sprite_allocator/dynamic.rs index 7dd9772bf..f3e4de76d 100644 --- a/agb/src/display/object/sprites/sprite_allocator/dynamic.rs +++ b/agb/src/display/object/sprites/sprite_allocator/dynamic.rs @@ -284,3 +284,92 @@ macro_rules! common_impls { common_impls!(DynamicSprite16); common_impls!(DynamicSprite256); + +#[cfg(test)] +mod tests { + use crate::{ + display::{ + HEIGHT, Palette16, Rgb, Rgb15, WIDTH, + object::{DynamicSprite16, Object, Size}, + tiled::VRAM_MANAGER, + }, + test_runner::assert_image_output, + }; + + #[test_case] + fn check_dynamic_sprite(gba: &mut crate::Gba) { + let mut gfx = gba.graphics.get(); + let mut frame = gfx.frame(); + + 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); + + 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 sprite = sprite.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.png"); + } + + #[test_case] + fn check_dynamic_sprite_copy(gba: &mut crate::Gba) { + let mut gfx = gba.graphics.get(); + let mut frame = gfx.frame(); + + 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); + + 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.png"); + } +} From 34b5a67350e63e95604072a0b6b4d8120c9109cb Mon Sep 17 00:00:00 2001 From: Corwin Date: Sun, 4 Jan 2026 20:45:11 +0000 Subject: [PATCH 4/9] use u32 buffers --- agb-image-converter/src/sprite/multi.rs | 2 +- agb-image-converter/src/sprite/regular.rs | 2 +- .../sprites/sprite_allocator/dynamic.rs | 75 ++++++++++--------- 3 files changed, 43 insertions(+), 36 deletions(-) diff --git a/agb-image-converter/src/sprite/multi.rs b/agb-image-converter/src/sprite/multi.rs index 8e6ced197..2eaa274ef 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)) } } }); 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/src/display/object/sprites/sprite_allocator/dynamic.rs b/agb/src/display/object/sprites/sprite_allocator/dynamic.rs index f3e4de76d..b0a5bde4b 100644 --- a/agb/src/display/object/sprites/sprite_allocator/dynamic.rs +++ b/agb/src/display/object/sprites/sprite_allocator/dynamic.rs @@ -33,7 +33,7 @@ fn allocate_with_retry(layout: Layout) -> Result, alloc::alloc::Al /// A mutable dynamic sprite buffer that can be generated at run time #[derive(Clone)] pub struct DynamicSprite16 { - data: Box<[u16], A>, + data: Box<[u32], A>, size: Size, } @@ -66,17 +66,16 @@ impl DynamicSprite16 { let (x_in_tile, y_in_tile) = (x % 8, y % 8); - let half_word_to_modify_in_tile = x_in_tile / 4 + y_in_tile * 2; + let byte_to_modify_in_tile = x_in_tile / 2 + y_in_tile; - 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]; + 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 % 4) * 4; + let nibble_to_modify = (x % 2) * 4; - half_word = (half_word & !(0b1111 << nibble_to_modify)) - | ((paletted_pixel as u16) << nibble_to_modify); - self.data[half_word_to_modify] = half_word; + byte = + (byte & !(0b1111 << nibble_to_modify)) | ((paletted_pixel as u8) << nibble_to_modify); + self.data_mut()[byte_to_modify] = byte; } /// Wipes the sprite clearing it with a specified pixel @@ -85,18 +84,15 @@ impl DynamicSprite16 { /// 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) - | (paletted_pixel << 8) - | (paletted_pixel << 12)) as u16; - self.data.fill(reset); + 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<[u16], A>, + data: Box<[u32], A>, size: Size, } @@ -129,17 +125,11 @@ impl DynamicSprite256 { let (x_in_tile, y_in_tile) = (x % 8, y % 8); - let half_word_to_modify_in_tile = x_in_tile / 2 + y_in_tile * 2; + let byte_to_modify_in_tile = x_in_tile / 2 + y_in_tile * 2; - 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]; + let byte_to_modify = tile_number_to_modify * BYTES_PER_TILE_8BPP + byte_to_modify_in_tile; - let byte_to_modify = (x % 2) * 8; - - half_word = (half_word & !(0b11111111 << byte_to_modify)) - | ((paletted_pixel as u16) << byte_to_modify); - self.data[half_word_to_modify] = half_word; + self.data_mut()[byte_to_modify] = paletted_pixel as u8; } /// Wipes the sprite clearing it with a specified pixel @@ -148,8 +138,7 @@ impl DynamicSprite256 { /// Panics if the pixel would be outside the range of the palette pub fn clear(&mut self, paletted_pixel: usize) { assert!(paletted_pixel < 256); - let reset = (paletted_pixel | (paletted_pixel << 8)) as u16; - self.data.fill(reset); + self.data_mut().fill(paletted_pixel as u8); } } @@ -181,7 +170,7 @@ macro_rules! common_impls { /// 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) / 2, allocator)?; + Box::try_new_zeroed_slice_in(Self::allocation_size(size) / 4, allocator)?; let data = unsafe { data.assume_init() }; Ok(Self { data, size }) @@ -201,11 +190,11 @@ macro_rules! common_impls { ); let mut data = - Box::<[u16], A>::try_new_uninit_slice_in(allocation_size / 2, allocator)?; + Box::<[u32], A>::try_new_uninit_slice_in(allocation_size / 4, allocator)?; let data = unsafe { - // cast the data ptr to a u16 ptr and memcpy - let raw = data.as_mut_ptr() as *mut u16; + // 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() @@ -228,11 +217,11 @@ macro_rules! common_impls { /// 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::<[u16], B>::try_new_uninit_slice_in(self.data.len(), allocator)?; + Box::<[u32], B>::try_new_uninit_slice_in(self.data.len(), allocator)?; let data = unsafe { - // cast the data ptr to a u16 ptr and memcpy - let raw = data.as_mut_ptr() as *mut u16; + // 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() }; @@ -258,7 +247,7 @@ macro_rules! common_impls { unsafe { let dest = data.cast().as_ptr(); - crate::agbabi::memcpy_16(self.data.as_ptr(), dest, self.data.len()); + crate::agbabi::memcpy_16(self.data.as_ptr().cast(), dest, self.data.len() * 2); } let palette = palette.into().palette(); @@ -278,6 +267,24 @@ macro_rules! common_impls { self.try_to_vram(palette) .expect("should be able to allocate sprite buffer") } + + /// 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) + } + } + + /// 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) + } + } } }; } From 7bdcf3998e3215fdb838fbe4c4126ef92e925cf6 Mon Sep 17 00:00:00 2001 From: Corwin Date: Sun, 4 Jan 2026 21:18:11 +0000 Subject: [PATCH 5/9] correct DynamicSprite256 --- agb/src/display/object/sprites.rs | 2 +- .../sprites/sprite_allocator/dynamic.rs | 94 ++++++++++++------- 2 files changed, 63 insertions(+), 33 deletions(-) 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_allocator/dynamic.rs b/agb/src/display/object/sprites/sprite_allocator/dynamic.rs index b0a5bde4b..b29ee6132 100644 --- a/agb/src/display/object/sprites/sprite_allocator/dynamic.rs +++ b/agb/src/display/object/sprites/sprite_allocator/dynamic.rs @@ -3,7 +3,7 @@ use core::{ ptr::NonNull, }; -use crate::InternalAllocator; +use crate::{InternalAllocator, display::object::PaletteVramMulti}; use alloc::{alloc::AllocError, boxed::Box}; @@ -78,6 +78,36 @@ impl DynamicSprite16 { 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 = data.cast().as_ptr(); + crate::agbabi::memcpy_16(self.data.as_ptr().cast(), dest, self.data.len() * 2); + } + + 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") + } + /// Wipes the sprite clearing it with a specified pixel /// /// # Panics @@ -125,13 +155,43 @@ impl DynamicSprite256 { let (x_in_tile, y_in_tile) = (x % 8, y % 8); - let byte_to_modify_in_tile = x_in_tile / 2 + y_in_tile * 2; + 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 as u8; } + /// 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 = data.cast().as_ptr(); + crate::agbabi::memcpy_16(self.data.as_ptr().cast(), dest, self.data.len() * 2); + } + + 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") + } + /// Wipes the sprite clearing it with a specified pixel /// /// # Panics @@ -238,36 +298,6 @@ macro_rules! common_impls { .expect("should be able to allocate sprite buffer") } - /// 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 = data.cast().as_ptr(); - crate::agbabi::memcpy_16(self.data.as_ptr().cast(), dest, self.data.len() * 2); - } - - 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") - } - /// 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] { From 56d7d31659e8d5485e513d535d80dd5c9c26d66d Mon Sep 17 00:00:00 2001 From: Corwin Date: Sun, 4 Jan 2026 21:18:11 +0000 Subject: [PATCH 6/9] correct set pixel methods --- agb/src/display/font/object.rs | 2 +- .../object/sprites/sprite_allocator/dynamic.rs | 13 +++++-------- 2 files changed, 6 insertions(+), 9 deletions(-) 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/sprite_allocator/dynamic.rs b/agb/src/display/object/sprites/sprite_allocator/dynamic.rs index b29ee6132..f15b1d262 100644 --- a/agb/src/display/object/sprites/sprite_allocator/dynamic.rs +++ b/agb/src/display/object/sprites/sprite_allocator/dynamic.rs @@ -51,7 +51,7 @@ impl DynamicSprite16 { /// # 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: usize) { + 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(); @@ -66,15 +66,14 @@ impl DynamicSprite16 { let (x_in_tile, y_in_tile) = (x % 8, y % 8); - let byte_to_modify_in_tile = x_in_tile / 2 + y_in_tile; + 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 as u8) << nibble_to_modify); + byte = (byte & !(0b1111 << nibble_to_modify)) | (paletted_pixel << nibble_to_modify); self.data_mut()[byte_to_modify] = byte; } @@ -140,9 +139,7 @@ impl DynamicSprite256 { /// # 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: usize) { - assert!(paletted_pixel < 256); - + 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"); @@ -159,7 +156,7 @@ impl DynamicSprite256 { 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 as u8; + self.data_mut()[byte_to_modify] = paletted_pixel; } /// Copies the sprite data to sprite vram From f12f2fd282d87f74528bb363bfeeeffa9bff29b4 Mon Sep 17 00:00:00 2001 From: Corwin Date: Sun, 4 Jan 2026 21:18:11 +0000 Subject: [PATCH 7/9] don't specify the first index yourself --- agb-image-converter/src/sprite/multi.rs | 4 +--- agb/src/display/object/sprites/sprite.rs | 11 ++++++++--- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/agb-image-converter/src/sprite/multi.rs b/agb-image-converter/src/sprite/multi.rs index 2eaa274ef..587c72077 100644 --- a/agb-image-converter/src/sprite/multi.rs +++ b/agb-image-converter/src/sprite/multi.rs @@ -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/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 { From aa148478503b6752029b138092d0339a7bad8e5b Mon Sep 17 00:00:00 2001 From: Corwin Date: Sun, 4 Jan 2026 21:18:11 +0000 Subject: [PATCH 8/9] test DynamicSprite256 --- .../test_output/object/dynamic_sprite_256.png | Bin 0 -> 1080 bytes .../object/dynamic_sprite_copy.png | Bin 0 -> 1080 bytes .../object/sprites/sprite_allocator.rs | 6 +++ .../sprites/sprite_allocator/dynamic.rs | 46 ++++++++++++++++-- 4 files changed, 49 insertions(+), 3 deletions(-) create mode 100644 agb/gfx/test_output/object/dynamic_sprite_256.png create mode 100644 agb/gfx/test_output/object/dynamic_sprite_copy.png 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 0000000000000000000000000000000000000000..cceb100469b78c66d26cc909353254aa9c3c65d2 GIT binary patch literal 1080 zcmeAS@N?(olHy`uVBq!ia0vp^AAooP2NRIoyR*!Qfr0tIr;B4q1>@TT%nMW^rycwF zzx-YAi8ITKypz}E-rfd9nLYESd99ykWUTb=XW90ws%7og*;m&~6<-bg?;f!8)vEv2 zD`HBo{+A6|x9in^KCSTBul4LxSFiiEzxh;X_}}}EPgkw}_kKYUPN^1&D~4_5up zd3N(m=>M53V!qDzwVA!+mAx=>3>I9i*ZO=1s2~%hz$;;1Gf)NZ)Ybnscdq`odNfi8 gW26$1_x=e+D{@wg_D}i_ED;zyUHx3vIVCg!0FS?YEdT%j literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..cceb100469b78c66d26cc909353254aa9c3c65d2 GIT binary patch literal 1080 zcmeAS@N?(olHy`uVBq!ia0vp^AAooP2NRIoyR*!Qfr0tIr;B4q1>@TT%nMW^rycwF zzx-YAi8ITKypz}E-rfd9nLYESd99ykWUTb=XW90ws%7og*;m&~6<-bg?;f!8)vEv2 zD`HBo{+A6|x9in^KCSTBul4LxSFiiEzxh;X_}}}EPgkw}_kKYUPN^1&D~4_5up zd3N(m=>M53V!qDzwVA!+mAx=>3>I9i*ZO=1s2~%hz$;;1Gf)NZ)Ybnscdq`odNfi8 gW26$1_x=e+D{@wg_D}i_ED;zyUHx3vIVCg!0FS?YEdT%j literal 0 HcmV?d00001 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 f15b1d262..35fc79ff9 100644 --- a/agb/src/display/object/sprites/sprite_allocator/dynamic.rs +++ b/agb/src/display/object/sprites/sprite_allocator/dynamic.rs @@ -324,14 +324,14 @@ mod tests { use crate::{ display::{ HEIGHT, Palette16, Rgb, Rgb15, WIDTH, - object::{DynamicSprite16, Object, Size}, + object::{DynamicSprite16, DynamicSprite256, Object, PaletteMulti, Size}, tiled::VRAM_MANAGER, }, test_runner::assert_image_output, }; #[test_case] - fn check_dynamic_sprite(gba: &mut crate::Gba) { + fn check_dynamic_sprite_16(gba: &mut crate::Gba) { let mut gfx = gba.graphics.get(); let mut frame = gfx.frame(); @@ -368,6 +368,46 @@ mod tests { assert_image_output("gfx/test_output/object/dynamic_sprite.png"); } + #[test_case] + fn check_dynamic_sprite_256(gba: &mut crate::Gba) { + let mut gfx = gba.graphics.get(); + let mut frame = gfx.frame(); + + VRAM_MANAGER.set_background_palette_colour(0, 0, Rgb::new(0xff, 0, 0xff).to_rgb15()); + + static PALETTE: PaletteMulti = const { + static PALETTE: &[Palette16] = &[const { + let palette = [Rgb15::WHITE; 16]; + Palette16::new(palette) + }]; + PaletteMulti::new(PALETTE) + }; + + let mut sprite = DynamicSprite256::new(Size::S8x8); + let colour = PALETTE.first_colour_index(); + + sprite.set_pixel(2, 2, colour); + sprite.set_pixel(6, 2, colour); + + 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); + + let sprite = sprite.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_256.png"); + } + #[test_case] fn check_dynamic_sprite_copy(gba: &mut crate::Gba) { let mut gfx = gba.graphics.get(); @@ -404,6 +444,6 @@ mod tests { frame.commit(); - assert_image_output("gfx/test_output/object/dynamic_sprite.png"); + assert_image_output("gfx/test_output/object/dynamic_sprite_copy.png"); } } From 07ed795a9cdb2f425fcef79ba7613fe09719d058 Mon Sep 17 00:00:00 2001 From: Corwin Date: Sun, 4 Jan 2026 22:11:28 +0000 Subject: [PATCH 9/9] copy to vram with a plain memcpy --- agb/src/agbabi/mod.rs | 10 ---------- .../display/object/sprites/sprite_allocator/dynamic.rs | 8 ++++---- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/agb/src/agbabi/mod.rs b/agb/src/agbabi/mod.rs index b79ad1068..8b85649eb 100644 --- a/agb/src/agbabi/mod.rs +++ b/agb/src/agbabi/mod.rs @@ -6,16 +6,6 @@ global_asm!(concat!( include_str!("memset.s") )); -unsafe extern "C" { - fn __agbabi_memcpy2(dest: *mut u16, src: *const u16, n: usize); -} - -pub unsafe fn memcpy_16(src: *const u16, dest: *mut u16, count: usize) { - unsafe { - __agbabi_memcpy2(dest, src, count * 2); - } -} - #[cfg(test)] mod test { mod memset { diff --git a/agb/src/display/object/sprites/sprite_allocator/dynamic.rs b/agb/src/display/object/sprites/sprite_allocator/dynamic.rs index 35fc79ff9..e526a36f8 100644 --- a/agb/src/display/object/sprites/sprite_allocator/dynamic.rs +++ b/agb/src/display/object/sprites/sprite_allocator/dynamic.rs @@ -85,8 +85,8 @@ impl DynamicSprite16 { let data = allocate_with_retry(self.layout())?; unsafe { - let dest = data.cast().as_ptr(); - crate::agbabi::memcpy_16(self.data.as_ptr().cast(), dest, self.data.len() * 2); + 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(); @@ -167,8 +167,8 @@ impl DynamicSprite256 { let data = allocate_with_retry(self.layout())?; unsafe { - let dest = data.cast().as_ptr(); - crate::agbabi::memcpy_16(self.data.as_ptr().cast(), dest, self.data.len() * 2); + 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();