Add basic dma effect stuff

This commit is contained in:
Gwilym Inzani 2023-09-30 17:03:57 +01:00
parent 43718773d7
commit de15538d42
5 changed files with 197 additions and 0 deletions

View file

@ -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;
}
}
}

View file

@ -5,6 +5,7 @@ use crate::bitarray::Bitarray;
use crate::display::affine::AffineMatrixBackground; use crate::display::affine::AffineMatrixBackground;
use crate::display::tile_data::TileData; use crate::display::tile_data::TileData;
use crate::display::{Priority, DISPLAY_CONTROL}; use crate::display::{Priority, DISPLAY_CONTROL};
use crate::dma;
use crate::fixnum::Vector2D; use crate::fixnum::Vector2D;
use crate::memory_mapped::MemoryMapped; use crate::memory_mapped::MemoryMapped;
@ -294,6 +295,11 @@ impl RegularMap {
self.scroll = pos; self.scroll = pos;
} }
#[must_use]
pub fn x_scroll_dma(&self) -> dma::DmaControllable<i16> {
dma::DmaControllable::new(self.x_register().as_ptr())
}
fn x_register(&self) -> MemoryMapped<i16> { fn x_register(&self) -> MemoryMapped<i16> {
unsafe { MemoryMapped::new(0x0400_0010 + 4 * self.background_id as usize) } unsafe { MemoryMapped::new(0x0400_0010 + 4 * self.background_id as usize) }
} }

View file

@ -1,5 +1,144 @@
use core::{marker::PhantomData, mem::size_of, pin::Pin};
use alloc::boxed::Box;
use crate::memory_mapped::MemoryMapped; 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<u32>,
dest_addr: MemoryMapped<u32>,
ctrl_addr: MemoryMapped<u32>,
}
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<T>,
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::<T>() / 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<Item> {
memory_location: *mut Item,
}
impl<Item> DmaControllable<Item> {
pub(crate) fn new(memory_location: *mut Item) -> Self {
Self { memory_location }
}
}
pub struct DmaTransferHandle<'a, T>
where
T: Copy,
{
number: usize,
data: Pin<Box<[T]>>,
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 { const fn dma_source_addr(dma: usize) -> usize {
0x0400_00b0 + 0x0c * dma 0x0400_00b0 + 0x0c * dma
} }

View file

@ -235,6 +235,8 @@ pub struct Gba {
pub save: save::SaveManager, pub save: save::SaveManager,
/// Manages access to the Game Boy Advance's 4 timers. /// Manages access to the Game Boy Advance's 4 timers.
pub timers: timer::TimerController, pub timers: timer::TimerController,
/// Manages access to the Game Boy Advance's DMA
pub dma: dma::DmaController,
} }
impl Gba { impl Gba {
@ -255,6 +257,7 @@ impl Gba {
mixer: sound::mixer::MixerController::new(), mixer: sound::mixer::MixerController::new(),
save: save::SaveManager::new(), save: save::SaveManager::new(),
timers: timer::TimerController::new(), timers: timer::TimerController::new(),
dma: dma::DmaController::new(),
} }
} }
} }

View file

@ -20,6 +20,10 @@ impl<T> MemoryMapped<T> {
unsafe { self.address.write_volatile(val) } unsafe { self.address.write_volatile(val) }
} }
} }
pub(crate) fn as_ptr(&self) -> *mut T {
self.address
}
} }
impl<T> MemoryMapped<T> impl<T> MemoryMapped<T>