mirror of
https://github.com/italicsjenga/gba.git
synced 2025-01-11 03:21:30 +11:00
Move Mode3/4/5 into gba::video::bitmap
This commit is contained in:
parent
b183e9b6b4
commit
d2260fc117
|
@ -4,7 +4,7 @@
|
|||
|
||||
use gba::{
|
||||
io::display::{DisplayControlMode, DisplayControlSetting, DISPCNT},
|
||||
video::Mode3,
|
||||
video::bitmap::Mode3,
|
||||
Color,
|
||||
};
|
||||
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
71
src/video.rs
71
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<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
309
src/video/bitmap.rs
Normal 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?
|
Loading…
Reference in a new issue