From 63dd8375189dc92a081af58ed6716297410b909a Mon Sep 17 00:00:00 2001
From: Gwilym Kuiper <gw@ilym.me>
Date: Mon, 14 Feb 2022 20:57:12 +0000
Subject: [PATCH] Tiny performance improvement with dma to copy the tile data

---
 agb/src/display/background.rs                 | 39 +++++++++++++++++--
 .../the-hat-chooses-the-wizard/src/main.rs    |  4 +-
 2 files changed, 37 insertions(+), 6 deletions(-)

diff --git a/agb/src/display/background.rs b/agb/src/display/background.rs
index e64b5323..712450e8 100644
--- a/agb/src/display/background.rs
+++ b/agb/src/display/background.rs
@@ -20,7 +20,7 @@ use super::{
 };
 
 const TILE_BACKGROUND: MemoryMapped1DArray<u32, { 2048 * 8 }> =
-    unsafe { MemoryMapped1DArray::new(0x06000000) };
+    unsafe { MemoryMapped1DArray::new(0x0600_0000) };
 
 const PALETTE_BACKGROUND: MemoryMapped1DArray<u16, 256> =
     unsafe { MemoryMapped1DArray::new(0x0500_0000) };
@@ -219,10 +219,16 @@ impl<'a> VRamManager<'a> {
             unsafe { debug_unreachable_unchecked() };
         };
 
-        let tile_size_in_words = TileFormat::FourBpp.tile_size() / 4;
+        let tile_size_in_half_words = TileFormat::FourBpp.tile_size() / 2;
 
-        for (i, &word) in tile_slice.iter().enumerate() {
-            TILE_BACKGROUND.set(index_to_copy_into * tile_size_in_words + i, word);
+        const TILE_BACKGROUND_ADDRESS: usize = 0x0600_0000;
+        unsafe {
+            dma_copy(
+                tile_slice.as_ptr() as *const u16,
+                (TILE_BACKGROUND_ADDRESS as *mut u16)
+                    .add(index_to_copy_into * tile_size_in_half_words),
+                tile_size_in_half_words,
+            );
         }
 
         self.tile_set_to_vram.insert(
@@ -703,3 +709,28 @@ impl<'a, T> Drop for MapLoan<'a, T> {
             .set(self.background_id as usize, false);
     }
 }
+
+const fn dma_source_addr(dma: usize) -> usize {
+    0x0400_00b0 + 0x0c * dma
+}
+
+const fn dma_dest_addr(dma: usize) -> usize {
+    0x0400_00b4 + 0x0c * dma
+}
+
+const fn dma_control_addr(dma: usize) -> usize {
+    0x0400_00b8 + 0x0c * dma
+}
+
+const DMA3_SOURCE_ADDR: MemoryMapped<u32> = unsafe { MemoryMapped::new(dma_source_addr(3)) };
+const DMA3_DEST_ADDR: MemoryMapped<u32> = unsafe { MemoryMapped::new(dma_dest_addr(3)) };
+const DMA3_CONTROL: MemoryMapped<u32> = unsafe { MemoryMapped::new(dma_control_addr(3)) };
+
+unsafe fn dma_copy(src: *const u16, dest: *mut u16, count: usize) {
+    assert!(count < u16::MAX as usize);
+
+    DMA3_SOURCE_ADDR.set(src as u32);
+    DMA3_DEST_ADDR.set(dest as u32);
+
+    DMA3_CONTROL.set(count as u32 | (1 << 31));
+}
diff --git a/examples/the-hat-chooses-the-wizard/src/main.rs b/examples/the-hat-chooses-the-wizard/src/main.rs
index 73f3aa2b..5904bbc2 100644
--- a/examples/the-hat-chooses-the-wizard/src/main.rs
+++ b/examples/the-hat-chooses-the-wizard/src/main.rs
@@ -796,8 +796,8 @@ fn main(mut agb: agb::Gba) -> ! {
         object.set_sprite_palettes(object_sheet::object_sheet.palettes);
         object.set_sprite_tilemap(object_sheet::object_sheet.tiles);
 
-        for y in 0..20u16 {
-            for x in 0..30u16 {
+        for y in 0..32u16 {
+            for x in 0..32u16 {
                 world_display.set_tile(
                     &mut vram,
                     (x, y).into(),