diff --git a/agb/examples/dma_effect.rs b/agb/examples/dma_effect.rs new file mode 100644 index 00000000..e76545d2 --- /dev/null +++ b/agb/examples/dma_effect.rs @@ -0,0 +1,45 @@ +#![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 _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/src/display/tiled/map.rs b/agb/src/display/tiled/map.rs index 2c7f779d..ec379d0c 100644 --- a/agb/src/display/tiled/map.rs +++ b/agb/src/display/tiled/map.rs @@ -5,6 +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; use crate::fixnum::Vector2D; use crate::memory_mapped::MemoryMapped; @@ -294,6 +295,11 @@ impl RegularMap { self.scroll = pos; } + #[must_use] + pub fn x_scroll_dma(&self) -> dma::DmaControllable { + dma::DmaControllable::new(self.x_register().as_ptr()) + } + fn x_register(&self) -> MemoryMapped { unsafe { MemoryMapped::new(0x0400_0010 + 4 * self.background_id as usize) } } diff --git a/agb/src/dma.rs b/agb/src/dma.rs index 7e92ede1..f73f3a8b 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: &[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..8c9e9062 100644 --- a/agb/src/lib.rs +++ b/agb/src/lib.rs @@ -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