diff --git a/examples/hello_world.rs b/examples/hello_world.rs index 1f5a360..db402d8 100644 --- a/examples/hello_world.rs +++ b/examples/hello_world.rs @@ -4,7 +4,7 @@ use gba::{ io::display::{DisplayControlMode, DisplayControlSetting, DISPCNT}, - video::Mode3, + video::bitmap::Mode3, Color, }; diff --git a/examples/light_cycle.rs b/examples/light_cycle.rs index 6b47d57..6c6ed71 100644 --- a/examples/light_cycle.rs +++ b/examples/light_cycle.rs @@ -7,7 +7,7 @@ use gba::{ display::{spin_until_vblank, spin_until_vdraw, DisplayControlMode, DisplayControlSetting, DISPCNT}, keypad::read_key_input, }, - video::Mode3, + video::bitmap::Mode3, Color, }; diff --git a/examples/mgba_panic_handler.rs b/examples/mgba_panic_handler.rs index 17e7057..214a56a 100644 --- a/examples/mgba_panic_handler.rs +++ b/examples/mgba_panic_handler.rs @@ -4,7 +4,7 @@ use gba::{ io::display::{DisplayControlMode, DisplayControlSetting, DISPCNT}, - video::Mode3, + video::bitmap::Mode3, Color, }; @@ -12,6 +12,7 @@ use gba::{ fn panic(info: &core::panic::PanicInfo) -> ! { use core::fmt::Write; use gba::mgba::{MGBADebug, MGBADebugLevel}; + if let Some(mut mgba) = MGBADebug::new() { let _ = write!(mgba, "{}", info); mgba.send(MGBADebugLevel::Fatal); diff --git a/src/video.rs b/src/video.rs index 8e2586f..df13684 100644 --- a/src/video.rs +++ b/src/video.rs @@ -15,6 +15,9 @@ pub use super::*; +pub mod bitmap; +pub mod tiled; + /// The start of VRAM. /// /// Depending on what display mode is currently set there's different ways that @@ -22,71 +25,3 @@ pub use super::*; /// value as just being a `usize`. Specific video mode types then wrap this as /// being the correct thing. pub const VRAM_BASE_USIZE: usize = 0x600_0000; - -/// Mode 3 is a bitmap mode with full color and full resolution. -/// -/// * **Width:** 240 -/// * **Height:** 160 -/// -/// Because the memory requirements are so large, there's only a single page -/// available instead of two pages like the other video modes have. -/// -/// As with all bitmap modes, the bitmap itself utilizes BG2 for display, so you -/// must have that BG enabled in addition to being within Mode 3. -pub struct Mode3; -impl Mode3 { - /// The physical width in pixels of the GBA screen. - pub const SCREEN_WIDTH: usize = 240; - - /// The physical height in pixels of the GBA screen. - pub const SCREEN_HEIGHT: usize = 160; - - /// The Mode 3 VRAM. - /// - /// Use `col + row * SCREEN_WIDTH` to get the address of an individual pixel, - /// or use the helpers provided in this module. - pub const VRAM: VolAddressBlock = - unsafe { VolAddressBlock::new_unchecked(VolAddress::new_unchecked(VRAM_BASE_USIZE), Self::SCREEN_WIDTH * Self::SCREEN_HEIGHT) }; - - const MODE3_U32_COUNT: u16 = (Self::SCREEN_WIDTH * Self::SCREEN_HEIGHT / 2) as u16; - - /// private iterator over the pixels, two at a time - const BULK_ITER: VolAddressIter = - unsafe { VolAddressBlock::new_unchecked(VolAddress::new_unchecked(VRAM_BASE_USIZE), Self::MODE3_U32_COUNT as usize).iter() }; - - /// Reads the pixel at the given (col,row). - /// - /// # Failure - /// - /// Gives `None` if out of bounds. - pub fn read_pixel(col: usize, row: usize) -> Option { - Self::VRAM.get(col + row * Self::SCREEN_WIDTH).map(VolAddress::read) - } - - /// Writes the pixel at the given (col,row). - /// - /// # Failure - /// - /// Gives `None` if out of bounds. - pub fn write_pixel(col: usize, row: usize, color: Color) -> Option<()> { - Self::VRAM.get(col + row * Self::SCREEN_WIDTH).map(|va| va.write(color)) - } - - /// Clears the whole screen to the desired color. - pub fn clear_to(color: Color) { - let color32 = color.0 as u32; - let bulk_color = color32 << 16 | color32; - for va in Self::BULK_ITER { - va.write(bulk_color) - } - } - - /// Clears the whole screen to the desired color using DMA3. - pub fn dma_clear_to(color: Color) { - use crate::io::dma::DMA3; - - let color32 = color.0 as u32; - let bulk_color = color32 << 16 | color32; - unsafe { DMA3::fill32(&bulk_color, VRAM_BASE_USIZE as *mut u32, Self::MODE3_U32_COUNT) }; - } -} diff --git a/src/video/bitmap.rs b/src/video/bitmap.rs new file mode 100644 index 0000000..7736226 --- /dev/null +++ b/src/video/bitmap.rs @@ -0,0 +1,309 @@ +//! Module for the Bitmap video modes. + +use super::*; + +/// Mode 3 is a bitmap mode with full color and full resolution. +/// +/// * **Width:** 240 +/// * **Height:** 160 +/// +/// Because the memory requirements are so large, there's only a single page +/// available instead of two pages like the other video modes have. +/// +/// As with all bitmap modes, the image itself utilizes BG2 for display, so you +/// must have BG2 enabled in addition to being within Mode 3. +pub struct Mode3; +impl Mode3 { + /// The physical width in pixels of the GBA screen. + pub const SCREEN_WIDTH: usize = 240; + + /// The physical height in pixels of the GBA screen. + pub const SCREEN_HEIGHT: usize = 160; + + /// The number of pixels on the screen. + pub const SCREEN_PIXEL_COUNT: usize = Self::SCREEN_WIDTH * Self::SCREEN_HEIGHT; + + /// The Mode 3 VRAM. + /// + /// Use `col + row * SCREEN_WIDTH` to get the address of an individual pixel, + /// or use the helpers provided in this module. + pub const VRAM: VolAddressBlock = + unsafe { VolAddressBlock::new_unchecked(VolAddress::new_unchecked(VRAM_BASE_USIZE), Self::SCREEN_PIXEL_COUNT) }; + + /// private iterator over the pixels, two at a time + const BULK_ITER: VolAddressIter = + unsafe { VolAddressBlock::new_unchecked(VolAddress::new_unchecked(VRAM_BASE_USIZE), Self::SCREEN_PIXEL_COUNT / 2).iter() }; + + /// Reads the pixel at the given (col,row). + /// + /// # Failure + /// + /// Gives `None` if out of bounds. + pub fn read_pixel(col: usize, row: usize) -> Option { + Self::VRAM.get(col + row * Self::SCREEN_WIDTH).map(VolAddress::read) + } + + /// Writes the pixel at the given (col,row). + /// + /// # Failure + /// + /// Gives `None` if out of bounds. + pub fn write_pixel(col: usize, row: usize, color: Color) -> Option<()> { + Self::VRAM.get(col + row * Self::SCREEN_WIDTH).map(|va| va.write(color)) + } + + /// Clears the whole screen to the desired color. + pub fn clear_to(color: Color) { + let color32 = color.0 as u32; + let bulk_color = color32 << 16 | color32; + for va in Self::BULK_ITER { + va.write(bulk_color) + } + } + + /// Clears the whole screen to the desired color using DMA3. + pub fn dma_clear_to(color: Color) { + use crate::io::dma::DMA3; + + let color32 = color.0 as u32; + let bulk_color = color32 << 16 | color32; + unsafe { DMA3::fill32(&bulk_color, VRAM_BASE_USIZE as *mut u32, (Self::SCREEN_PIXEL_COUNT / 2) as u16) }; + } +} + +//TODO: Mode3 Iter Scanlines / Pixels? +//TODO: Mode3 Line Drawing? + +/// Mode 4 is a bitmap mode with 8bpp paletted color. +/// +/// * **Width:** 240 +/// * **Height:** 160 +/// * **Pages:** 2 +/// +/// VRAM has a minimum write size of 2 bytes at a time, so writing individual +/// palette entries for the pixels is more costly than with the other bitmap +/// modes. +/// +/// As with all bitmap modes, the image itself utilizes BG2 for display, so you +/// must have BG2 enabled in addition to being within Mode 4. +pub struct Mode4; +impl Mode4 { + /// The physical width in pixels of the GBA screen. + pub const SCREEN_WIDTH: usize = 240; + + /// The physical height in pixels of the GBA screen. + pub const SCREEN_HEIGHT: usize = 160; + + /// The number of pixels on the screen. + pub const SCREEN_PIXEL_COUNT: usize = Self::SCREEN_WIDTH * Self::SCREEN_HEIGHT; + + /// Used for bulk clearing operations. + const SCREEN_U32_COUNT: usize = Self::SCREEN_PIXEL_COUNT / 4; + + // TODO: newtype this? + const PAGE0_BASE: VolAddress = unsafe { VolAddress::new_unchecked(VRAM_BASE_USIZE) }; + + // TODO: newtype this? + const PAGE0_BLOCK: VolAddressBlock = unsafe { VolAddressBlock::new_unchecked(Self::PAGE0_BASE, Self::SCREEN_PIXEL_COUNT) }; + + // TODO: newtype this? + const PAGE1_BASE: VolAddress = unsafe { VolAddress::new_unchecked(VRAM_BASE_USIZE + 0x9600) }; + + // TODO: newtype this? + const PAGE1_BLOCK: VolAddressBlock = unsafe { VolAddressBlock::new_unchecked(Self::PAGE1_BASE, Self::SCREEN_PIXEL_COUNT) }; + + /// private iterator over the page0 pixels, four at a time + const BULK_ITER0: VolAddressIter = unsafe { VolAddressBlock::new_unchecked(Self::PAGE0_BASE.cast::(), Self::SCREEN_U32_COUNT).iter() }; + + /// private iterator over the page1 pixels, four at a time + const BULK_ITER1: VolAddressIter = unsafe { VolAddressBlock::new_unchecked(Self::PAGE1_BASE.cast::(), Self::SCREEN_U32_COUNT).iter() }; + + /// Reads the pixel at the given (col,row). + /// + /// # Failure + /// + /// Gives `None` if out of bounds. + pub fn read_pixel(page1: bool, col: usize, row: usize) -> Option { + // Note(Lokathor): byte _reads_ from VRAM are okay. + if page1 { + Self::PAGE1_BLOCK.get(col + row * Self::SCREEN_WIDTH).map(VolAddress::read) + } else { + Self::PAGE0_BLOCK.get(col + row * Self::SCREEN_WIDTH).map(VolAddress::read) + } + } + + /// Writes the pixel at the given (col,row). + /// + /// # Failure + /// + /// Gives `None` if out of bounds. + pub fn write_pixel(page1: bool, col: usize, row: usize, pal8bpp: u8) -> Option<()> { + // Note(Lokathor): byte _writes_ to VRAM are not permitted. We must jump + // through hoops when we attempt to write just a single byte. + if col < Self::SCREEN_WIDTH && row < Self::SCREEN_HEIGHT { + let real_index = col + row * Self::SCREEN_WIDTH; + let rounded_down_index = real_index & !1; + let address: VolAddress = unsafe { + if page1 { + Self::PAGE1_BASE.offset(rounded_down_index as isize).cast() + } else { + Self::PAGE0_BASE.offset(rounded_down_index as isize).cast() + } + }; + if real_index == rounded_down_index { + // even byte, change the high bits + let old_val = address.read(); + address.write((old_val & 0xFF) | ((pal8bpp as u16) << 8)); + } else { + // odd byte, change the low bits + let old_val = address.read(); + address.write((old_val & 0xFF00) | pal8bpp as u16); + } + Some(()) + } else { + None + } + } + + /// Writes a "wide" pairing of palette entries to the location specified. + /// + /// The page is imagined to be a series of `u16` values rather than `u8` + /// values, allowing you to write two palette entries side by side as a single + /// write operation. + pub fn write_wide_pixel(page1: bool, wide_col: usize, row: usize, wide_pal8bpp: u16) -> Option<()> { + if wide_col < Self::SCREEN_WIDTH / 2 && row < Self::SCREEN_HEIGHT { + let wide_index = wide_col + row * Self::SCREEN_WIDTH / 2; + let address: VolAddress = unsafe { + if page1 { + Self::PAGE1_BASE.cast::().offset(wide_index as isize) + } else { + Self::PAGE0_BASE.cast::().offset(wide_index as isize) + } + }; + Some(address.write(wide_pal8bpp)) + } else { + None + } + } + + /// Clears the page to the desired color. + pub fn clear_page_to(page1: bool, pal8bpp: u8) { + let pal8bpp_32 = pal8bpp as u32; + let bulk_color = (pal8bpp_32 << 24) | (pal8bpp_32 << 16) | (pal8bpp_32 << 8) | pal8bpp_32; + for va in if page1 { Self::BULK_ITER1 } else { Self::BULK_ITER0 } { + va.write(bulk_color) + } + } + + /// Clears the page to the desired color using DMA3. + pub fn dma_clear_page_to(page1: bool, pal8bpp: u8) { + use crate::io::dma::DMA3; + + let pal8bpp_32 = pal8bpp as u32; + let bulk_color = (pal8bpp_32 << 24) | (pal8bpp_32 << 16) | (pal8bpp_32 << 8) | pal8bpp_32; + let write_target = if page1 { + VRAM_BASE_USIZE as *mut u32 + } else { + (VRAM_BASE_USIZE + 0x9600) as *mut u32 + }; + unsafe { DMA3::fill32(&bulk_color, write_target, Self::SCREEN_U32_COUNT as u16) }; + } +} + +//TODO: Mode4 Iter Scanlines / Pixels? +//TODO: Mode4 Line Drawing? + +/// Mode 5 is a bitmap mode with full color and reduced resolution. +/// +/// * **Width:** 160 +/// * **Height:** 128 +/// * **Pages:** 2 +/// +/// Because of the reduced resolution, we're allowed two pages for display. +/// +/// As with all bitmap modes, the image itself utilizes BG2 for display, so you +/// must have BG2 enabled in addition to being within Mode 3. +pub struct Mode5; +impl Mode5 { + /// The physical width in pixels of the GBA screen. + pub const SCREEN_WIDTH: usize = 160; + + /// The physical height in pixels of the GBA screen. + pub const SCREEN_HEIGHT: usize = 128; + + /// The number of pixels on the screen. + pub const SCREEN_PIXEL_COUNT: usize = Self::SCREEN_WIDTH * Self::SCREEN_HEIGHT; + + /// Used for bulk clearing operations. + const SCREEN_U32_COUNT: usize = Self::SCREEN_PIXEL_COUNT / 2; + + // TODO: newtype this? + const PAGE0_BASE: VolAddress = unsafe { VolAddress::new_unchecked(VRAM_BASE_USIZE) }; + + // TODO: newtype this? + const PAGE0_BLOCK: VolAddressBlock = unsafe { VolAddressBlock::new_unchecked(Self::PAGE0_BASE, Self::SCREEN_PIXEL_COUNT) }; + + // TODO: newtype this? + const PAGE1_BASE: VolAddress = unsafe { VolAddress::new_unchecked(VRAM_BASE_USIZE + 0xA000) }; + + // TODO: newtype this? + const PAGE1_BLOCK: VolAddressBlock = unsafe { VolAddressBlock::new_unchecked(Self::PAGE1_BASE, Self::SCREEN_PIXEL_COUNT) }; + + /// private iterator over the page0 pixels, four at a time + const BULK_ITER0: VolAddressIter = unsafe { VolAddressBlock::new_unchecked(Self::PAGE0_BASE.cast::(), Self::SCREEN_U32_COUNT).iter() }; + + /// private iterator over the page1 pixels, four at a time + const BULK_ITER1: VolAddressIter = unsafe { VolAddressBlock::new_unchecked(Self::PAGE1_BASE.cast::(), Self::SCREEN_U32_COUNT).iter() }; + + /// Reads the pixel at the given (col,row). + /// + /// # Failure + /// + /// Gives `None` if out of bounds. + pub fn read_pixel(page1: bool, col: usize, row: usize) -> Option { + if page1 { + Self::PAGE1_BLOCK.get(col + row * Self::SCREEN_WIDTH).map(VolAddress::read) + } else { + Self::PAGE0_BLOCK.get(col + row * Self::SCREEN_WIDTH).map(VolAddress::read) + } + } + + /// Writes the pixel at the given (col,row). + /// + /// # Failure + /// + /// Gives `None` if out of bounds. + pub fn write_pixel(page1: bool, col: usize, row: usize, color: Color) -> Option<()> { + if page1 { + Self::PAGE1_BLOCK.get(col + row * Self::SCREEN_WIDTH).map(|va| va.write(color)) + } else { + Self::PAGE0_BLOCK.get(col + row * Self::SCREEN_WIDTH).map(|va| va.write(color)) + } + } + + /// Clears the whole screen to the desired color. + pub fn clear_page_to(page1: bool, color: Color) { + let color32 = color.0 as u32; + let bulk_color = color32 << 16 | color32; + for va in if page1 { Self::BULK_ITER1 } else { Self::BULK_ITER0 } { + va.write(bulk_color) + } + } + + /// Clears the whole screen to the desired color using DMA3. + pub fn dma_clear_page_to(page1: bool, color: Color) { + use crate::io::dma::DMA3; + + let color32 = color.0 as u32; + let bulk_color = color32 << 16 | color32; + let write_target = if page1 { + VRAM_BASE_USIZE as *mut u32 + } else { + (VRAM_BASE_USIZE + 0xA000) as *mut u32 + }; + unsafe { DMA3::fill32(&bulk_color, write_target, Self::SCREEN_U32_COUNT as u16) }; + } +} + +//TODO: Mode5 Iter Scanlines / Pixels? +//TODO: Mode5 Line Drawing?