diff --git a/CHANGELOG.md b/CHANGELOG.md index 227e7835..f9547ff7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,9 +11,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added `.priority()`, `.set_priority()` and `.is_visible()` to `RegularMap`, `AffineMap` and `InfiniteScrolledMap`. - Replaced `.show()` and `.hide()` with `.set_visible()`in `RegularMap`, `AffineMap` and `InfiniteScrolledMap`. - Added `.hflip()`, `.vflip()`, `.priority()`, `.position()` to `ObjectUnmanaged` and `Object`. +- An abstraction over hblank DMA to allow for cool effects like gradients and circular windows. See the dma_effect* examples. - Expermental and incomplete support for MIDI files with agb-tracker. - Fixnum now implements [`num::Num`](https://docs.rs/num/0.4/num/trait.Num.html) from the [`num`](https://crates.io/crates/num) crate. +### Change +- A few functions which previously accepted a `Vector` now accept an `impl Into>` instead. + ## [0.18.1] - 2024/02/06 ### Added diff --git a/agb/examples/affine_background.rs b/agb/examples/affine_background.rs index 18987def..f2b08bd0 100644 --- a/agb/examples/affine_background.rs +++ b/agb/examples/affine_background.rs @@ -26,7 +26,7 @@ fn main(mut gba: agb::Gba) -> ! { for y in 0..32u16 { for x in 0..32u16 { - bg.set_tile(&mut vram, (x, y).into(), &tileset, 1); + bg.set_tile(&mut vram, (x, y), &tileset, 1); } } @@ -46,14 +46,14 @@ fn main(mut gba: agb::Gba) -> ! { scroll_x += input.x_tri() as i32; scroll_y += input.y_tri() as i32; - let scroll_pos = (scroll_x, scroll_y).into(); + let scroll_pos = (scroll_x, scroll_y); rotation += rotation_increase; rotation = rotation.rem_euclid(1.into()); let transformation = AffineMatrixBackground::from_scale_rotation_position( - (0, 0).into(), - (1, 1).into(), + (0, 0), + (1, 1), rotation, scroll_pos, ); diff --git a/agb/examples/animated_background.rs b/agb/examples/animated_background.rs index 0064121a..9d6a5e07 100644 --- a/agb/examples/animated_background.rs +++ b/agb/examples/animated_background.rs @@ -30,7 +30,7 @@ fn main(mut gba: agb::Gba) -> ! { for x in 0..30u16 { bg.set_tile( &mut vram, - (x, y).into(), + (x, y), &tileset, water_tiles::water_tiles.tile_settings[0], ); diff --git a/agb/examples/chicken.rs b/agb/examples/chicken.rs index 9a247f0b..e8f41316 100644 --- a/agb/examples/chicken.rs +++ b/agb/examples/chicken.rs @@ -64,7 +64,7 @@ fn main(mut gba: agb::Gba) -> ! { let i = i as u16; background.set_tile( &mut vram, - (i % 32, i / 32).into(), + (i % 32, i / 32), &tileset, TileSetting::from_raw(tile), ); diff --git a/agb/examples/dma_effect_background_colour.rs b/agb/examples/dma_effect_background_colour.rs new file mode 100644 index 00000000..6efc4ed9 --- /dev/null +++ b/agb/examples/dma_effect_background_colour.rs @@ -0,0 +1,48 @@ +#![no_std] +#![no_main] + +extern crate alloc; + +use alloc::boxed::Box; + +use agb::{ + display::{ + example_logo, + tiled::{RegularBackgroundSize, TileFormat}, + }, + interrupt::VBlank, +}; + +#[agb::entry] +fn main(mut gba: agb::Gba) -> ! { + let (gfx, mut vram) = gba.display.video.tiled0(); + + let mut map = gfx.background( + agb::display::Priority::P0, + RegularBackgroundSize::Background32x32, + TileFormat::FourBpp, + ); + + let dma = gba.dma.dma().dma0; + + example_logo::display_logo(&mut map, &mut vram); + + let vblank = VBlank::get(); + + let colours: Box<[_]> = (0..160).map(|i| ((i * 0xffff) / 160) as u16).collect(); + + let mut frame = 0; + + loop { + // hardcoding palette index 2 here which you wouldn't want to do in a real example (instead, look for + // the colour you want to replace) + let _background_color_transfer = + unsafe { dma.hblank_transfer(&vram.background_palette_colour_dma(0, 2), &colours) }; + + vblank.wait_for_vblank(); + frame += 1; + if frame > 160 { + frame = 0; + } + } +} diff --git a/agb/examples/dma_effect_background_scroll.rs b/agb/examples/dma_effect_background_scroll.rs new file mode 100644 index 00000000..eb5cdfaa --- /dev/null +++ b/agb/examples/dma_effect_background_scroll.rs @@ -0,0 +1,46 @@ +#![no_std] +#![no_main] + +extern crate alloc; + +use alloc::boxed::Box; + +use agb::{ + display::{ + example_logo, + tiled::{RegularBackgroundSize, TileFormat}, + }, + interrupt::VBlank, +}; + +#[agb::entry] +fn main(mut gba: agb::Gba) -> ! { + let (gfx, mut vram) = gba.display.video.tiled0(); + + let mut map = gfx.background( + agb::display::Priority::P0, + RegularBackgroundSize::Background32x32, + TileFormat::FourBpp, + ); + + let dma = gba.dma.dma().dma0; + + example_logo::display_logo(&mut map, &mut vram); + + let vblank = VBlank::get(); + + let offsets: Box<[_]> = (0..160 * 2).collect(); + + let mut frame = 0; + + loop { + let _x_scroll_transfer = + unsafe { dma.hblank_transfer(&map.x_scroll_dma(), &offsets[frame..]) }; + + vblank.wait_for_vblank(); + frame += 1; + if frame > 160 { + frame = 0; + } + } +} diff --git a/agb/examples/dma_effect_circular_window.rs b/agb/examples/dma_effect_circular_window.rs new file mode 100644 index 00000000..d98e6f60 --- /dev/null +++ b/agb/examples/dma_effect_circular_window.rs @@ -0,0 +1,97 @@ +#![no_std] +#![no_main] +#![feature(isqrt)] + +extern crate alloc; + +use agb::{ + display::{ + example_logo, + tiled::{RegularBackgroundSize, TileFormat}, + window::WinIn, + HEIGHT, WIDTH, + }, + fixnum::{Num, Rect, Vector2D}, + interrupt::VBlank, +}; +use alloc::{boxed::Box, vec}; + +type FNum = Num; + +#[agb::entry] +fn entry(mut gba: agb::Gba) -> ! { + main(gba) +} + +fn main(mut gba: agb::Gba) -> ! { + let (gfx, mut vram) = gba.display.video.tiled0(); + + let mut map = gfx.background( + agb::display::Priority::P0, + RegularBackgroundSize::Background32x32, + TileFormat::FourBpp, + ); + let mut window = gba.display.window.get(); + window + .win_in(WinIn::Win0) + .set_background_enable(map.background(), true) + .set_position(&Rect::new((10, 10).into(), (64, 64).into())) + .enable(); + + let dmas = gba.dma.dma(); + + example_logo::display_logo(&mut map, &mut vram); + + let mut pos: Vector2D = (10, 10).into(); + let mut velocity: Vector2D = Vector2D::new(1.into(), 1.into()); + + let vblank = VBlank::get(); + + let circle: Box<[_]> = (1..64i32) + .map(|i| { + let y = 32 - i; + let x = (32 * 32 - y * y).isqrt(); + let x1 = 32 - x; + let x2 = 32 + x; + + ((x1 as u16) << 8) | (x2 as u16) + }) + .collect(); + + let mut circle_poses = vec![0; 160]; + + loop { + pos += velocity; + + if pos.x.floor() > WIDTH - 64 || pos.x.floor() < 0 { + velocity.x *= -1; + } + + if pos.y.floor() > HEIGHT - 64 || pos.y.floor() < 0 { + velocity.y *= -1; + } + + let x_pos = pos.x.floor().max(0) as u16; + let y_pos = pos.y.floor().max(0); + let x_adjustment = x_pos << 8 | x_pos; + for (i, value) in circle_poses.iter_mut().enumerate() { + let i = i as i32; + if i <= y_pos || i >= y_pos + 64 { + *value = 0; + continue; + } + + *value = circle[(i - y_pos) as usize - 1] + x_adjustment; + } + + window + .win_in(WinIn::Win0) + .set_position(&Rect::new(pos.floor(), (64, 65).into())); + window.commit(); + + let dma_controllable = window.win_in(WinIn::Win0).horizontal_position_dma(); + let _transfer = unsafe { dmas.dma0.hblank_transfer(&dma_controllable, &circle_poses) }; + + vblank.wait_for_vblank(); + } +} diff --git a/agb/examples/dynamic_tiles.rs b/agb/examples/dynamic_tiles.rs index 81b200f2..2fb4eb9b 100644 --- a/agb/examples/dynamic_tiles.rs +++ b/agb/examples/dynamic_tiles.rs @@ -40,7 +40,7 @@ fn main(mut gba: agb::Gba) -> ! { bg.set_tile( &mut vram, - (x as u16, y as u16).into(), + (x as u16, y as u16), &dynamic_tile.tile_set(), dynamic_tile.tile_setting(), ); diff --git a/agb/examples/mixer_32768.rs b/agb/examples/mixer_32768.rs index 0593a72f..1129f959 100644 --- a/agb/examples/mixer_32768.rs +++ b/agb/examples/mixer_32768.rs @@ -31,7 +31,7 @@ fn main(mut gba: Gba) -> ! { init_background(&mut bg, &mut vram); - let mut title_renderer = FONT.render_text((0u16, 3u16).into()); + let mut title_renderer = FONT.render_text((0u16, 3u16)); let mut writer = title_renderer.writer(1, 0, &mut bg, &mut vram); writeln!(&mut writer, "Crazy Glue by Josh Woodward").unwrap(); @@ -57,7 +57,7 @@ fn main(mut gba: Gba) -> ! { let mut frame_counter = 0i32; let mut has_written_frame_time = false; - let mut stats_renderer = FONT.render_text((0u16, 6u16).into()); + let mut stats_renderer = FONT.render_text((0u16, 6u16)); loop { vblank_provider.wait_for_vblank(); bg.commit(&mut vram); @@ -106,7 +106,7 @@ fn init_background(bg: &mut RegularMap, vram: &mut VRamManager) { for x in 0..30u16 { bg.set_tile( vram, - (x, y).into(), + (x, y), &background_tile.tile_set(), background_tile.tile_setting(), ); diff --git a/agb/examples/object_text_render.rs b/agb/examples/object_text_render.rs index f3df935a..22bb20a9 100644 --- a/agb/examples/object_text_render.rs +++ b/agb/examples/object_text_render.rs @@ -58,7 +58,7 @@ fn main(mut gba: agb::Gba) -> ! { let start = timer.value(); - wr.layout((WIDTH, 40).into(), TextAlignment::Justify, 2); + wr.layout((WIDTH, 40), TextAlignment::Justify, 2); let end = timer.value(); agb::println!( @@ -83,7 +83,7 @@ fn main(mut gba: agb::Gba) -> ! { line_done = false; wr.pop_line(); } - wr.update((0, HEIGHT - 40).into()); + wr.update((0, HEIGHT - 40)); let end = timer.value(); frame += 1; diff --git a/agb/examples/sprites.rs b/agb/examples/sprites.rs index 161e4ed8..679fea10 100644 --- a/agb/examples/sprites.rs +++ b/agb/examples/sprites.rs @@ -34,7 +34,7 @@ fn all_sprites(gfx: &OamManaged, rotation_speed: Num) { let mut obj = gfx.object_sprite(&SPRITES[0]); obj.set_affine_matrix(matrix.clone()); obj.show_affine(object::AffineMode::Affine); - obj.set_position((x * 16 + 8, y * 16 + 8).into()); + obj.set_position((x * 16 + 8, y * 16 + 8)); objs.push(obj); } } @@ -87,7 +87,7 @@ fn all_tags(gfx: &OamManaged) { let (size_x, size_y) = (size_x as i32, size_y as i32); let mut obj = gfx.object_sprite(sprite); obj.show(); - obj.set_position((x * 32 + 16 - size_x / 2, y * 32 + 16 - size_y / 2).into()); + obj.set_position((x * 32 + 16 - size_x / 2, y * 32 + 16 - size_y / 2)); objs.push((obj, v)); } diff --git a/agb/examples/stereo_sound.rs b/agb/examples/stereo_sound.rs index ba0fe025..9e1be011 100644 --- a/agb/examples/stereo_sound.rs +++ b/agb/examples/stereo_sound.rs @@ -31,7 +31,7 @@ fn main(mut gba: Gba) -> ! { init_background(&mut bg, &mut vram); - let mut title_renderer = FONT.render_text((0u16, 3u16).into()); + let mut title_renderer = FONT.render_text((0u16, 3u16)); let mut writer = title_renderer.writer(1, 0, &mut bg, &mut vram); writeln!(&mut writer, "Let it in by Josh Woodward").unwrap(); @@ -55,7 +55,7 @@ fn main(mut gba: Gba) -> ! { let mut frame_counter = 0i32; let mut has_written_frame_time = false; - let mut stats_renderer = FONT.render_text((0u16, 6u16).into()); + let mut stats_renderer = FONT.render_text((0u16, 6u16)); loop { vblank_provider.wait_for_vblank(); bg.commit(&mut vram); @@ -94,7 +94,7 @@ fn init_background(bg: &mut RegularMap, vram: &mut VRamManager) { for x in 0..30u16 { bg.set_tile( vram, - (x, y).into(), + (x, y), &background_tile.tile_set(), background_tile.tile_setting(), ); diff --git a/agb/examples/text_render.rs b/agb/examples/text_render.rs index d2f91189..350b80a5 100644 --- a/agb/examples/text_render.rs +++ b/agb/examples/text_render.rs @@ -35,7 +35,7 @@ fn main(mut gba: agb::Gba) -> ! { for x in 0..30u16 { bg.set_tile( &mut vram, - (x, y).into(), + (x, y), &background_tile.tile_set(), background_tile.tile_setting(), ); @@ -44,7 +44,7 @@ fn main(mut gba: agb::Gba) -> ! { vram.remove_dynamic_tile(background_tile); - let mut renderer = FONT.render_text((0u16, 3u16).into()); + let mut renderer = FONT.render_text((0u16, 3u16)); let mut writer = renderer.writer(1, 2, &mut bg, &mut vram); writeln!(&mut writer, "Hello, World!").unwrap(); @@ -58,7 +58,7 @@ fn main(mut gba: agb::Gba) -> ! { let mut frame = 0; loop { - let mut renderer = FONT.render_text((4u16, 0u16).into()); + let mut renderer = FONT.render_text((4u16, 0u16)); let mut writer = renderer.writer(1, 2, &mut bg, &mut vram); writeln!(&mut writer, "Frame {frame}").unwrap(); diff --git a/agb/src/display/affine.rs b/agb/src/display/affine.rs index bddb6fa6..008b58ad 100644 --- a/agb/src/display/affine.rs +++ b/agb/src/display/affine.rs @@ -295,15 +295,15 @@ impl AffineMatrixBackground { /// # } /// ``` pub fn from_scale_rotation_position( - transform_origin: Vector2D>, - scale: Vector2D>, + transform_origin: impl Into>>, + scale: impl Into>>, rotation: Num, - position: Vector2D>, + position: impl Into>>, ) -> Self { crate::syscall::bg_affine_matrix( - transform_origin, - position.try_change_base::().unwrap().floor(), - scale.try_change_base().unwrap(), + transform_origin.into(), + position.into().try_change_base::().unwrap().floor(), + scale.into().try_change_base().unwrap(), rotation.rem_euclid(1.into()).try_change_base().unwrap(), ) } diff --git a/agb/src/display/font.rs b/agb/src/display/font.rs index fbc5cf17..54b3078f 100644 --- a/agb/src/display/font.rs +++ b/agb/src/display/font.rs @@ -78,12 +78,12 @@ impl Font { impl Font { #[must_use] /// Create renderer starting at the given tile co-ordinates. - pub fn render_text(&self, tile_pos: Vector2D) -> TextRenderer<'_> { + pub fn render_text(&self, tile_pos: impl Into>) -> TextRenderer<'_> { TextRenderer { current_x_pos: 0, current_y_pos: 0, font: self, - tile_pos, + tile_pos: tile_pos.into(), tiles: Default::default(), } } @@ -228,7 +228,7 @@ impl<'a, 'b> TextRenderer<'b> { for ((x, y), tile) in self.tiles.iter() { bg.set_tile( vram_manager, - (self.tile_pos.x + *x as u16, self.tile_pos.y + *y as u16).into(), + (self.tile_pos.x + *x as u16, self.tile_pos.y + *y as u16), &tile.tile_set(), tile.tile_setting(), ); @@ -292,7 +292,7 @@ mod tests { for x in 0..30u16 { bg.set_tile( &mut vram, - (x, y).into(), + (x, y), &background_tile.tile_set(), background_tile.tile_setting(), ); @@ -301,7 +301,7 @@ mod tests { vram.remove_dynamic_tile(background_tile); - let mut renderer = FONT.render_text((0u16, 3u16).into()); + let mut renderer = FONT.render_text((0u16, 3u16)); // Test twice to ensure that clearing works for _ in 0..2 { diff --git a/agb/src/display/object/font.rs b/agb/src/display/object/font.rs index a9d05da5..f7a03b41 100644 --- a/agb/src/display/object/font.rs +++ b/agb/src/display/object/font.rs @@ -229,11 +229,11 @@ impl BufferedRender<'_> { /// let mut writer = ObjectTextRender::new(&EXAMPLE_FONT, Size::S16x16, palette); /// /// let _ = writeln!(writer, "Hello, World!"); -/// writer.layout((WIDTH, 40).into(), TextAlignment::Left, 2); +/// writer.layout((WIDTH, 40), TextAlignment::Left, 2); /// /// loop { /// writer.next_letter_group(); -/// writer.update((0, 0).into()); +/// writer.update((0, 0)); /// vblank.wait_for_vblank(); /// let oam = &mut unmanaged.iter(); /// writer.commit(oam); @@ -287,7 +287,7 @@ impl ObjectTextRender<'_> { /// Force a relayout, must be called after writing. pub fn layout( &mut self, - area: Vector2D, + area: impl Into>, alignment: TextAlignment, paragraph_spacing: i32, ) { @@ -295,7 +295,7 @@ impl ObjectTextRender<'_> { self.buffer.font, &self.buffer.preprocessor, &LayoutSettings { - area, + area: area.into(), alignment, paragraph_spacing, }, @@ -331,7 +331,7 @@ impl ObjectTextRender<'_> { /// Updates the internal state of the number of letters to write and popped /// line. Should be called in the same frame as and after /// [`next_letter_group`][ObjectTextRender::next_letter_group], [`next_line`][ObjectTextRender::next_line], and [`pop_line`][ObjectTextRender::pop_line]. - pub fn update(&mut self, position: Vector2D) { + pub fn update(&mut self, position: impl Into>) { if !self.buffer.buffered_chars.is_empty() && self.buffer.letters.letters.len() <= self.number_of_objects + 5 { @@ -339,7 +339,7 @@ impl ObjectTextRender<'_> { } self.layout.update_objects_to_display_at_position( - position, + position.into(), self.buffer.letters.letters.iter(), self.number_of_objects, ); diff --git a/agb/src/display/object/managed.rs b/agb/src/display/object/managed.rs index fca35daa..11b8bb38 100644 --- a/agb/src/display/object/managed.rs +++ b/agb/src/display/object/managed.rs @@ -489,9 +489,9 @@ impl Object<'_> { /// Sets the position of the object. /// Use [position](Self::position) to get the value - pub fn set_position(&mut self, position: Vector2D) -> &mut Self { + pub fn set_position(&mut self, position: impl Into>) -> &mut Self { // safety: only have one of these, doesn't modify slotmap - unsafe { self.object().set_position(position) }; + unsafe { self.object().set_position(position.into()) }; self } diff --git a/agb/src/display/tiled/infinite_scrolled_map.rs b/agb/src/display/tiled/infinite_scrolled_map.rs index 7d6e66fb..e936808e 100644 --- a/agb/src/display/tiled/infinite_scrolled_map.rs +++ b/agb/src/display/tiled/infinite_scrolled_map.rs @@ -258,8 +258,7 @@ impl<'a> InfiniteScrolledMap<'a> { let offset = self.current_pos - (x_start * 8, y_start * 8).into(); - self.map - .set_scroll_pos((offset.x as i16, offset.y as i16).into()); + self.map.set_scroll_pos((offset.x as i16, offset.y as i16)); self.offset = (x_start, y_start).into(); let copy_from = self.copied_up_to; @@ -274,7 +273,7 @@ impl<'a> InfiniteScrolledMap<'a> { self.map.set_tile( vram, - (x_idx as u16, (y_idx + copy_from as usize) as u16).into(), + (x_idx as u16, (y_idx + copy_from as usize) as u16), tileset, tile_setting, ); @@ -374,8 +373,7 @@ impl<'a> InfiniteScrolledMap<'a> { ( size.tile_pos_x(tile_x - self.offset.x), size.tile_pos_y(tile_y - self.offset.y), - ) - .into(), + ), tileset, tile_setting, ); diff --git a/agb/src/display/tiled/map.rs b/agb/src/display/tiled/map.rs index 0c9696b1..4a52644f 100644 --- a/agb/src/display/tiled/map.rs +++ b/agb/src/display/tiled/map.rs @@ -5,7 +5,7 @@ use crate::bitarray::Bitarray; use crate::display::affine::AffineMatrixBackground; use crate::display::tile_data::TileData; use crate::display::{Priority, DISPLAY_CONTROL}; -use crate::dma::dma_copy16; +use crate::dma; use crate::fixnum::Vector2D; use crate::memory_mapped::MemoryMapped; @@ -93,9 +93,8 @@ where if *self.tiles_dirty() { unsafe { - dma_copy16( + screenblock_memory.copy_from( self.tiles_mut().as_ptr() as *const u16, - screenblock_memory, self.map_size().num_tiles(), ); } @@ -226,7 +225,7 @@ impl RegularMap { pub fn set_tile( &mut self, vram: &mut VRamManager, - pos: Vector2D, + pos: impl Into>, tileset: &TileSet<'_>, tile_setting: TileSetting, ) { @@ -238,7 +237,7 @@ impl RegularMap { self.colours() ); - let pos = self.map_size().gba_offset(pos); + let pos = self.map_size().gba_offset(pos.into()); self.set_tile_at_pos(vram, pos, tileset, tile_setting); } @@ -292,8 +291,13 @@ impl RegularMap { self.scroll } - pub fn set_scroll_pos(&mut self, pos: Vector2D) { - self.scroll = pos; + pub fn set_scroll_pos(&mut self, pos: impl Into>) { + self.scroll = pos.into(); + } + + #[must_use] + pub fn x_scroll_dma(&self) -> dma::DmaControllable { + dma::DmaControllable::new(self.x_register().as_ptr()) } fn x_register(&self) -> MemoryMapped { @@ -373,11 +377,11 @@ impl AffineMap { pub fn set_tile( &mut self, vram: &mut VRamManager, - pos: Vector2D, + pos: impl Into>, tileset: &TileSet<'_>, tile_id: u8, ) { - let pos = self.map_size().gba_offset(pos); + let pos = self.map_size().gba_offset(pos.into()); let colours = self.colours(); let old_tile = self.tiles_mut()[pos]; diff --git a/agb/src/display/tiled/vram_manager.rs b/agb/src/display/tiled/vram_manager.rs index 059e8a8e..55503332 100644 --- a/agb/src/display/tiled/vram_manager.rs +++ b/agb/src/display/tiled/vram_manager.rs @@ -5,7 +5,7 @@ use alloc::{slice, vec::Vec}; use crate::{ agb_alloc::{block_allocator::BlockAllocator, bump_allocator::StartEnd}, display::palette16, - dma::dma_copy16, + dma, hash_map::{Entry, HashMap}, memory_mapped::MemoryMapped1DArray, }; @@ -413,7 +413,9 @@ impl VRamManager { /// Copies raw palettes to the background palette without any checks. pub fn set_background_palette_raw(&mut self, palette: &[u16]) { unsafe { - dma_copy16(palette.as_ptr(), PALETTE_BACKGROUND.as_ptr(), palette.len()); + PALETTE_BACKGROUND + .as_ptr() + .copy_from_nonoverlapping(palette.as_ptr(), palette.len()); } } @@ -423,6 +425,36 @@ impl VRamManager { } } + /// The DMA register for controlling a single colour in a single background. Good for drawing gradients + #[must_use] + pub fn background_palette_colour_dma( + &self, + pal_index: usize, + colour_index: usize, + ) -> dma::DmaControllable { + assert!(pal_index < 16); + assert!(colour_index < 16); + + dma::DmaControllable::new(unsafe { + PALETTE_BACKGROUND + .as_ptr() + .add(16 * pal_index + colour_index) + }) + } + + /// Sets a single colour for a given background palette. Takes effect immediately + pub fn set_background_palette_colour( + &mut self, + pal_index: usize, + colour_index: usize, + colour: u16, + ) { + assert!(pal_index < 16); + assert!(colour_index < 16); + + PALETTE_BACKGROUND.set(colour_index + 16 * pal_index, colour); + } + /// Copies palettes to the background palettes without any checks. pub fn set_background_palettes(&mut self, palettes: &[palette16::Palette16]) { for (palette_index, entry) in palettes.iter().enumerate() { diff --git a/agb/src/display/window.rs b/agb/src/display/window.rs index 5a454b44..488f4c22 100644 --- a/agb/src/display/window.rs +++ b/agb/src/display/window.rs @@ -2,7 +2,7 @@ //! The window feature of the GBA. use core::marker::PhantomData; -use crate::{fixnum::Rect, memory_mapped::MemoryMapped}; +use crate::{dma, fixnum::Rect, memory_mapped::MemoryMapped}; use super::{tiled::BackgroundID, DISPLAY_CONTROL, HEIGHT, WIDTH}; @@ -32,7 +32,7 @@ pub enum WinIn { impl<'gba> Windows<'gba> { pub(crate) fn new() -> Self { let s = Self { - wins: [MovableWindow::new(), MovableWindow::new()], + wins: [MovableWindow::new(0), MovableWindow::new(1)], out: Window::new(), obj: Window::new(), phantom: PhantomData, @@ -63,8 +63,8 @@ impl<'gba> Windows<'gba> { /// modify them. This should be done during vblank shortly after the wait /// for next vblank call. pub fn commit(&self) { - for (id, win) in self.wins.iter().enumerate() { - win.commit(id); + for win in &self.wins { + win.commit(); } self.out.commit(2); self.obj.commit(3); @@ -85,6 +85,7 @@ pub struct Window { pub struct MovableWindow { inner: Window, rect: Rect, + id: usize, } impl Window { @@ -166,10 +167,11 @@ impl Window { } impl MovableWindow { - fn new() -> Self { + fn new(id: usize) -> Self { Self { inner: Window::new(), rect: Rect::new((0, 0).into(), (0, 0).into()), + id, } } @@ -201,7 +203,7 @@ impl MovableWindow { /// nothing rendered and represents a 0x0 rectangle at (0, 0). #[inline(always)] pub fn reset(&mut self) -> &mut Self { - *self = Self::new(); + *self = Self::new(self.id); self } @@ -227,8 +229,8 @@ impl MovableWindow { self } - fn commit(&self, id: usize) { - self.inner.commit(id); + fn commit(&self) { + self.inner.commit(self.id); let left_right = (self.rect.position.x as u16) << 8 | (self.rect.position.x + self.rect.size.x) as u16; @@ -236,8 +238,8 @@ impl MovableWindow { let top_bottom = (self.rect.position.y as u16) << 8 | (self.rect.position.y + self.rect.size.y) as u16; unsafe { - REG_HORIZONTAL_BASE.add(id).write_volatile(left_right); - REG_VERTICAL_BASE.add(id).write_volatile(top_bottom); + REG_HORIZONTAL_BASE.add(self.id).write_volatile(left_right); + REG_VERTICAL_BASE.add(self.id).write_volatile(top_bottom); } } @@ -264,4 +266,14 @@ impl MovableWindow { ); self.set_position_u8(new_rect) } + + /// DMA to control the horizontal position of the window. The lower 8 bits are + /// the left hand side, and the upper 8 bits are the right hand side. + /// + /// When you use this, you should also set the height of the window approprately using + /// [`set_position`](Self::set_position). + #[must_use] + pub fn horizontal_position_dma(&self) -> dma::DmaControllable { + dma::DmaControllable::new(unsafe { REG_HORIZONTAL_BASE.add(self.id) }) + } } diff --git a/agb/src/dma.rs b/agb/src/dma.rs index 7e92ede1..f57008e8 100644 --- a/agb/src/dma.rs +++ b/agb/src/dma.rs @@ -1,5 +1,144 @@ +use core::{marker::PhantomData, mem::size_of, pin::Pin}; + +use alloc::boxed::Box; + use crate::memory_mapped::MemoryMapped; +#[non_exhaustive] +pub struct DmaController {} + +impl DmaController { + pub(crate) const fn new() -> Self { + Self {} + } + + pub fn dma(&mut self) -> Dmas<'_> { + unsafe { Dmas::new() } + } +} + +pub struct Dmas<'gba> { + phantom: PhantomData<&'gba ()>, + + pub dma0: Dma, + pub dma3: Dma, +} + +impl<'gba> Dmas<'gba> { + unsafe fn new() -> Self { + Self { + phantom: PhantomData, + + dma0: Dma::new(0), + dma3: Dma::new(3), + } + } +} + +pub struct Dma { + number: usize, + + source_addr: MemoryMapped, + dest_addr: MemoryMapped, + ctrl_addr: MemoryMapped, +} + +impl Dma { + unsafe fn new(number: usize) -> Self { + Self { + number, + source_addr: unsafe { MemoryMapped::new(dma_source_addr(number)) }, + dest_addr: unsafe { MemoryMapped::new(dma_dest_addr(number)) }, + ctrl_addr: unsafe { MemoryMapped::new(dma_control_addr(number)) }, + } + } + + fn disable(&mut self) { + unsafe { MemoryMapped::new(dma_control_addr(self.number)) }.set(0); + } + + pub unsafe fn hblank_transfer<'a, T>( + &'a self, + location: &DmaControllable, + values: &'a [T], + ) -> DmaTransferHandle<'a, T> + where + T: Copy, + { + assert!( + values.len() >= 160, + "need to pass at least 160 values for a hblank_transfer" + ); + let handle = unsafe { DmaTransferHandle::new(self.number, values) }; + + let n_transfers = (size_of::() / 2) as u32; + + self.source_addr.set(handle.data.as_ptr() as u32); + self.dest_addr.set(location.memory_location as u32); + + self.ctrl_addr.set( + (0b10 << 0x15) | // keep destination address fixed + // (0b00 << 0x17) | // increment the source address each time + 1 << 0x19 | // repeat the copy each hblank + // 0 << 0x1a | // copy in half words (see n_transfers above) + 0b10 << 0x1c | // copy each hblank + 1 << 0x1f | // enable the dma + n_transfers, // the number of halfwords to copy + ); + + handle + } +} + +/// A struct to describe things you can modify using DMA (normally some register within the GBA) +/// +/// This is generally used to perform fancy graphics tricks like screen wobble on a per-scanline basis or +/// to be able to create a track like in mario kart. This is an advanced technique and likely not needed +/// unless you want to do fancy graphics. +pub struct DmaControllable { + memory_location: *mut Item, +} + +impl DmaControllable { + pub(crate) fn new(memory_location: *mut Item) -> Self { + Self { memory_location } + } +} + +pub struct DmaTransferHandle<'a, T> +where + T: Copy, +{ + number: usize, + data: Pin>, + + phantom: PhantomData<&'a ()>, +} + +impl<'a, T> DmaTransferHandle<'a, T> +where + T: Copy, +{ + pub(crate) unsafe fn new(number: usize, data: &[T]) -> Self { + Self { + number, + data: Box::into_pin(data.into()), + phantom: PhantomData, + } + } +} + +impl<'a, T> Drop for DmaTransferHandle<'a, T> +where + T: Copy, +{ + fn drop(&mut self) { + unsafe { + Dma::new(self.number).disable(); + } + } +} + const fn dma_source_addr(dma: usize) -> usize { 0x0400_00b0 + 0x0c * dma } diff --git a/agb/src/lib.rs b/agb/src/lib.rs index 33546a42..abc31960 100644 --- a/agb/src/lib.rs +++ b/agb/src/lib.rs @@ -87,7 +87,7 @@ /// for x in 0..30u16 { /// bg.set_tile( /// &mut vram, -/// (x, y).into(), +/// (x, y), /// &tileset, /// water_tiles::tiles.tile_settings[0], /// ); @@ -235,6 +235,8 @@ pub struct Gba { pub save: save::SaveManager, /// Manages access to the Game Boy Advance's 4 timers. pub timers: timer::TimerController, + /// Manages access to the Game Boy Advance's DMA + pub dma: dma::DmaController, } impl Gba { @@ -255,6 +257,7 @@ impl Gba { mixer: sound::mixer::MixerController::new(), save: save::SaveManager::new(), timers: timer::TimerController::new(), + dma: dma::DmaController::new(), } } } diff --git a/agb/src/memory_mapped.rs b/agb/src/memory_mapped.rs index 617dc48d..37ba63fa 100644 --- a/agb/src/memory_mapped.rs +++ b/agb/src/memory_mapped.rs @@ -20,6 +20,10 @@ impl MemoryMapped { unsafe { self.address.write_volatile(val) } } } + + pub(crate) fn as_ptr(&self) -> *mut T { + self.address + } } impl MemoryMapped diff --git a/examples/hyperspace-roll/src/background.rs b/examples/hyperspace-roll/src/background.rs index c7fb6229..cfcdc053 100644 --- a/examples/hyperspace-roll/src/background.rs +++ b/examples/hyperspace-roll/src/background.rs @@ -30,7 +30,7 @@ pub(crate) fn load_help_text( background.set_tile( vram, - (x + at_tile.0, at_tile.1).into(), + (x + at_tile.0, at_tile.1), &help_tiledata.tiles, help_tiledata.tile_settings[tile_id as usize], ) @@ -53,7 +53,7 @@ pub(crate) fn load_description( let tile_id = y * 8 + x + 8 * 11 * (face_id as u16 % 10); descriptions_map.set_tile( vram, - (x, y).into(), + (x, y), &description_data.tiles, description_data.tile_settings[tile_id as usize], ) @@ -74,15 +74,15 @@ fn create_background_map(map: &mut RegularMap, vram: &mut VRamManager, stars_til backgrounds::stars.tile_settings[tile_id as usize] }; - map.set_tile(vram, (x, y).into(), stars_tileset, tile_setting); + map.set_tile(vram, (x, y), stars_tileset, tile_setting); } } - map.set_scroll_pos((0i16, rng::gen().rem_euclid(8) as i16).into()); + map.set_scroll_pos((0i16, rng::gen().rem_euclid(8) as i16)); } pub fn show_title_screen(background: &mut RegularMap, vram: &mut VRamManager, sfx: &mut Sfx) { - background.set_scroll_pos((0i16, 0).into()); + background.set_scroll_pos((0i16, 0)); vram.set_background_palettes(backgrounds::PALETTES); background.set_visible(false); diff --git a/examples/hyperspace-roll/src/battle.rs b/examples/hyperspace-roll/src/battle.rs index d1233eaa..7820242a 100644 --- a/examples/hyperspace-roll/src/battle.rs +++ b/examples/hyperspace-roll/src/battle.rs @@ -489,7 +489,7 @@ pub(crate) fn battle_screen( agb.sfx.battle(); agb.sfx.frame(); - help_background.set_scroll_pos((-16i16, -97i16).into()); + help_background.set_scroll_pos((-16i16, -97i16)); crate::background::load_help_text(&mut agb.vram, help_background, 1, (0, 0)); crate::background::load_help_text(&mut agb.vram, help_background, 2, (0, 1)); diff --git a/examples/hyperspace-roll/src/battle/display.rs b/examples/hyperspace-roll/src/battle/display.rs index fbc1cd88..94577f1b 100644 --- a/examples/hyperspace-roll/src/battle/display.rs +++ b/examples/hyperspace-roll/src/battle/display.rs @@ -1,4 +1,5 @@ use agb::display::object::{OamManaged, Object}; +use agb::fixnum::Vector2D; use agb::rng; use alloc::vec; use alloc::vec::Vec; @@ -149,7 +150,7 @@ impl<'a> BattleScreenDisplay<'a> { let mut attack_obj = obj .object_sprite(ENEMY_ATTACK_SPRITES.sprite_for_attack(EnemyAttackType::Attack)); - let attack_obj_position = (120, 56 + 32 * i).into(); + let attack_obj_position = Vector2D::new(120, 56 + 32 * i); attack_obj.set_position(attack_obj_position).hide(); let mut attack_cooldown = diff --git a/examples/hyperspace-roll/src/customise.rs b/examples/hyperspace-roll/src/customise.rs index be04e699..513e4df7 100644 --- a/examples/hyperspace-roll/src/customise.rs +++ b/examples/hyperspace-roll/src/customise.rs @@ -163,9 +163,9 @@ pub(crate) fn customise_screen( ) -> PlayerDice { agb.sfx.customise(); agb.sfx.frame(); - descriptions_map.set_scroll_pos((-174i16, -52).into()); + descriptions_map.set_scroll_pos((-174i16, -52)); - help_background.set_scroll_pos((-148i16, -34).into()); + help_background.set_scroll_pos((-148i16, -34)); crate::background::load_help_text(&mut agb.vram, help_background, 0, (0, 0)); // create the dice diff --git a/examples/the-dungeon-puzzlers-lament/src/backgrounds.rs b/examples/the-dungeon-puzzlers-lament/src/backgrounds.rs index a965c653..0b935d67 100644 --- a/examples/the-dungeon-puzzlers-lament/src/backgrounds.rs +++ b/examples/the-dungeon-puzzlers-lament/src/backgrounds.rs @@ -26,7 +26,7 @@ pub fn load_ui(map: &mut RegularMap, vram_manager: &mut VRamManager) { let tile_pos = y * 30 + x; let tile_setting = tilemaps::UI_BACKGROUND_MAP[tile_pos as usize]; - map.set_tile(vram_manager, (x, y).into(), &ui_tileset, tile_setting); + map.set_tile(vram_manager, (x, y), &ui_tileset, tile_setting); } } } @@ -45,7 +45,7 @@ pub fn load_level_background( let tile_pos = y * 22 + x; let tile_setting = level_map[tile_pos as usize]; - map.set_tile(vram_manager, (x, y).into(), &level_tileset, tile_setting); + map.set_tile(vram_manager, (x, y), &level_tileset, tile_setting); } } } diff --git a/examples/the-hat-chooses-the-wizard/src/level_display.rs b/examples/the-hat-chooses-the-wizard/src/level_display.rs index 006a8941..2c577581 100644 --- a/examples/the-hat-chooses-the-wizard/src/level_display.rs +++ b/examples/the-hat-chooses-the-wizard/src/level_display.rs @@ -28,8 +28,8 @@ pub fn write_level( .iter() .enumerate() { - map.set_tile(vram, (i as u16, 0).into(), tileset, tile_settings[tile]); + map.set_tile(vram, (i as u16, 0), tileset, tile_settings[tile]); } - map.set_scroll_pos((-(WIDTH / 2 - 7 * 8 / 2) as i16, -(HEIGHT / 2 - 4) as i16).into()); + map.set_scroll_pos((-(WIDTH / 2 - 7 * 8 / 2) as i16, -(HEIGHT / 2 - 4) as i16)); } diff --git a/examples/the-hat-chooses-the-wizard/src/lib.rs b/examples/the-hat-chooses-the-wizard/src/lib.rs index 75665950..2bac6a4f 100644 --- a/examples/the-hat-chooses-the-wizard/src/lib.rs +++ b/examples/the-hat-chooses-the-wizard/src/lib.rs @@ -794,7 +794,7 @@ pub fn main(mut agb: agb::Gba) -> ! { for x in 0..32u16 { world_display.set_tile( &mut vram, - (x, y).into(), + (x, y), &tileset, tile_sheet::background.tile_settings[level_display::BLANK], ); diff --git a/examples/the-hat-chooses-the-wizard/src/splash_screen.rs b/examples/the-hat-chooses-the-wizard/src/splash_screen.rs index b88d8d78..82680847 100644 --- a/examples/the-hat-chooses-the-wizard/src/splash_screen.rs +++ b/examples/the-hat-chooses-the-wizard/src/splash_screen.rs @@ -17,7 +17,7 @@ pub fn show_splash_screen( map: &mut RegularMap, vram: &mut VRamManager, ) { - map.set_scroll_pos((0i16, 0i16).into()); + map.set_scroll_pos((0i16, 0i16)); let tile_data = match which { SplashScreen::Start => splash_screens::splash, SplashScreen::End => splash_screens::thanks_for_playing,