Move Mode3/4/5 into gba::video::bitmap

This commit is contained in:
Lokathor 2018-12-25 14:44:05 -07:00
parent b183e9b6b4
commit d2260fc117
5 changed files with 316 additions and 71 deletions

View file

@ -4,7 +4,7 @@
use gba::{
io::display::{DisplayControlMode, DisplayControlSetting, DISPCNT},
video::Mode3,
video::bitmap::Mode3,
Color,
};

View file

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

View file

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

View file

@ -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<Color> =
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<u32> =
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<Color> {
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) };
}
}

309
src/video/bitmap.rs Normal file
View file

@ -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<Color> =
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<u32> =
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<Color> {
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<u8> = unsafe { VolAddress::new_unchecked(VRAM_BASE_USIZE) };
// TODO: newtype this?
const PAGE0_BLOCK: VolAddressBlock<u8> = unsafe { VolAddressBlock::new_unchecked(Self::PAGE0_BASE, Self::SCREEN_PIXEL_COUNT) };
// TODO: newtype this?
const PAGE1_BASE: VolAddress<u8> = unsafe { VolAddress::new_unchecked(VRAM_BASE_USIZE + 0x9600) };
// TODO: newtype this?
const PAGE1_BLOCK: VolAddressBlock<u8> = 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<u32> = unsafe { VolAddressBlock::new_unchecked(Self::PAGE0_BASE.cast::<u32>(), Self::SCREEN_U32_COUNT).iter() };
/// private iterator over the page1 pixels, four at a time
const BULK_ITER1: VolAddressIter<u32> = unsafe { VolAddressBlock::new_unchecked(Self::PAGE1_BASE.cast::<u32>(), 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<u8> {
// 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<u16> = 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<u16> = unsafe {
if page1 {
Self::PAGE1_BASE.cast::<u16>().offset(wide_index as isize)
} else {
Self::PAGE0_BASE.cast::<u16>().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<Color> = unsafe { VolAddress::new_unchecked(VRAM_BASE_USIZE) };
// TODO: newtype this?
const PAGE0_BLOCK: VolAddressBlock<Color> = unsafe { VolAddressBlock::new_unchecked(Self::PAGE0_BASE, Self::SCREEN_PIXEL_COUNT) };
// TODO: newtype this?
const PAGE1_BASE: VolAddress<Color> = unsafe { VolAddress::new_unchecked(VRAM_BASE_USIZE + 0xA000) };
// TODO: newtype this?
const PAGE1_BLOCK: VolAddressBlock<Color> = 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<u32> = unsafe { VolAddressBlock::new_unchecked(Self::PAGE0_BASE.cast::<u32>(), Self::SCREEN_U32_COUNT).iter() };
/// private iterator over the page1 pixels, four at a time
const BULK_ITER1: VolAddressIter<u32> = unsafe { VolAddressBlock::new_unchecked(Self::PAGE1_BASE.cast::<u32>(), 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<Color> {
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?