Update the Mode 3/4/5 abstractions

This commit is contained in:
Lokathor 2019-02-13 01:45:48 -07:00
parent 61218d99f2
commit 5d89414c13
2 changed files with 323 additions and 173 deletions

View file

@ -29,6 +29,8 @@ pub mod text;
/// being the correct thing. /// being the correct thing.
pub const VRAM_BASE_USIZE: usize = 0x600_0000; pub const VRAM_BASE_USIZE: usize = 0x600_0000;
pub const PAGE1_OFFSET: usize = 0xA000;
/// The character base blocks. /// The character base blocks.
pub const CHAR_BASE_BLOCKS: VolBlock<[u8; 0x4000], U6> = unsafe { VolBlock::new(VRAM_BASE_USIZE) }; pub const CHAR_BASE_BLOCKS: VolBlock<[u8; 0x4000], U6> = unsafe { VolBlock::new(VRAM_BASE_USIZE) };

View file

@ -1,165 +1,213 @@
//! Module for the Bitmap video modes. //! Module for the Bitmap video modes.
use super::*; use super::*;
use core::ops::{Div, Mul}; use core::ops::{Div, Mul};
use typenum::consts::{U128, U160, U2, U256, U4}; use typenum::consts::{U128, U160, U2, U256, U4};
/// Mode 3 is a bitmap mode with full color and full resolution. /// A bitmap video mode with full color and full resolution.
/// ///
/// * **Width:** 240 /// * **Width:** 240
/// * **Height:** 160 /// * **Height:** 160
/// ///
/// Because the memory requirements are so large, there's only a single page /// Because it takes so much space to have full color and full resolution at the
/// available instead of two pages like the other video modes have. /// same time, there's no alternate page available when using mode 3.
/// ///
/// As with all bitmap modes, the image itself utilizes BG2 for display, so you /// As with all the bitmap video modes, the bitmap is considered to be BG2, so
/// must have BG2 enabled in addition to being within Mode 3. /// you have to enable BG2 as well if you want to see the bitmap.
pub struct Mode3; pub struct Mode3;
impl Mode3 { impl Mode3 {
/// The physical width in pixels of the GBA screen. /// The screen's width in this mode.
pub const SCREEN_WIDTH: usize = 240; pub const WIDTH: usize = 240;
/// The physical height in pixels of the GBA screen. /// The screen's height in this mode.
pub const SCREEN_HEIGHT: usize = 160; pub const HEIGHT: usize = 160;
/// The number of pixels on the screen. const VRAM: VolBlock<Color, <U256 as Mul<U160>>::Output> =
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: VolBlock<Color, <U256 as Mul<U160>>::Output> =
unsafe { VolBlock::new(VRAM_BASE_USIZE) }; unsafe { VolBlock::new(VRAM_BASE_USIZE) };
/// private iterator over the pixels, two at a time const WORDS_BLOCK: VolBlock<u32, <<U256 as Mul<U160>>::Output as Div<U2>>::Output> =
const VRAM_BULK: VolBlock<u32, <<U256 as Mul<U160>>::Output as Div<U2>>::Output> =
unsafe { VolBlock::new(VRAM_BASE_USIZE) }; unsafe { VolBlock::new(VRAM_BASE_USIZE) };
/// Reads the pixel at the given (col,row). /// Gets the address of the pixel specified.
/// ///
/// # Failure /// ## Failure
/// ///
/// Gives `None` if out of bounds. /// Gives `None` if out of bounds
pub fn read_pixel(col: usize, row: usize) -> Option<Color> { fn get(col: usize, row: usize) -> Option<VolAddress<Color>> {
Self::VRAM.get(col + row * Self::SCREEN_WIDTH).map(VolAddress::read) Self::VRAM.get(col + row * Self::WIDTH)
} }
/// Writes the pixel at the given (col,row). /// Reads the color of the pixel specified.
/// ///
/// # Failure /// ## Failure
/// ///
/// Gives `None` if out of bounds. /// Gives `None` if out of bounds
pub fn write_pixel(col: usize, row: usize, color: Color) -> Option<()> { pub fn read(col: usize, row: usize) -> Option<Color> {
Self::VRAM.get(col + row * Self::SCREEN_WIDTH).map(|va| va.write(color)) Self::get(col, row).map(VolAddress::read)
} }
/// Clears the whole screen to the desired color. /// Writes a color to the pixel specified.
///
/// ## Failure
///
/// Gives `None` if out of bounds
pub fn write(col: usize, row: usize, color: Color) -> Option<()> {
Self::get(col, row).map(|va| va.write(color))
}
/// Clear the screen to the color specified.
///
/// Takes ~430,000 cycles (~1.5 frames).
pub fn clear_to(color: Color) { pub fn clear_to(color: Color) {
let color32 = color.0 as u32; let color32 = color.0 as u32;
let bulk_color = color32 << 16 | color32; let bulk_color = color32 << 16 | color32;
for va in Self::VRAM_BULK.iter() { for va in Self::WORDS_BLOCK.iter() {
va.write(bulk_color) va.write(bulk_color)
} }
} }
/// Clears the whole screen to the desired color using DMA3. /// Clears the screen to the color specified using DMA3.
///
/// Takes ~61,500 frames (~73% of VBlank)
pub fn dma_clear_to(color: Color) { pub fn dma_clear_to(color: Color) {
use crate::io::dma::DMA3; use crate::io::dma::DMA3;
let color32 = color.0 as u32; let color32 = color.0 as u32;
let bulk_color = color32 << 16 | color32; let bulk_color = color32 << 16 | color32;
unsafe { unsafe {
DMA3::fill32(&bulk_color, VRAM_BASE_USIZE as *mut u32, (Self::SCREEN_PIXEL_COUNT / 2) as u16) DMA3::fill32(&bulk_color, VRAM_BASE_USIZE as *mut u32, Self::WORDS_BLOCK.len() as u16)
}; };
} }
/// Draws a line between the two points given `(c1,r1,c2,r2,color)`.
///
/// Works fine with out of bounds points. It only draws to in bounds
/// locations.
pub fn draw_line(c1: isize, r1: isize, c2: isize, r2: isize, color: Color) {
let mut col = c1;
let mut row = r1;
let w = c2 - c1;
let h = r2 - r1;
let mut dx1 = 0;
let mut dx2 = 0;
let mut dy1 = 0;
let mut dy2 = 0;
let mut longest = w.abs();
let mut shortest = h.abs();
if w < 0 {
dx1 = -1;
} else if w > 0 {
dx1 = 1;
};
if h < 0 {
dy1 = -1;
} else if h > 0 {
dy1 = 1;
};
if w < 0 {
dx2 = -1;
} else if w > 0 {
dx2 = 1;
};
if !(longest > shortest) {
core::mem::swap(&mut longest, &mut shortest);
if h < 0 {
dy2 = -1;
} else if h > 0 {
dy2 = 1
};
dx2 = 0;
}
let mut numerator = longest >> 1;
(0..(longest + 1)).for_each(|_| {
Self::write(col as usize, row as usize, color);
numerator += shortest;
if !(numerator < longest) {
numerator -= longest;
col += dx1;
row += dy1;
} else {
col += dx2;
row += dy2;
}
});
}
} }
//TODO: Mode3 Iter Scanlines / Pixels? /// Used to select what page to read from or write to in Mode 4 and Mode 5.
//TODO: Mode3 Line Drawing? #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Page {
/// Page 0
Zero,
/// Page 1
One,
}
/// Mode 4 is a bitmap mode with 8bpp paletted color. /// A bitmap video mode with full resolution and paletted color.
/// ///
/// * **Width:** 240 /// * **Width:** 240
/// * **Height:** 160 /// * **Height:** 160
/// * **Pages:** 2 /// * **Pages:** 2
/// ///
/// VRAM has a minimum write size of 2 bytes at a time, so writing individual /// Because the pixels use palette indexes there's enough space to have two
/// palette entries for the pixels is more costly than with the other bitmap /// pages.
/// modes.
/// ///
/// As with all bitmap modes, the image itself utilizes BG2 for display, so you /// As with all the bitmap video modes, the bitmap is considered to be BG2, so
/// must have BG2 enabled in addition to being within Mode 4. /// you have to enable BG2 as well if you want to see the bitmap.
pub struct Mode4; pub struct Mode4;
impl Mode4 { impl Mode4 {
/// The physical width in pixels of the GBA screen. /// The screen's width in this mode.
pub const SCREEN_WIDTH: usize = 240; pub const WIDTH: usize = 240;
/// The physical height in pixels of the GBA screen. /// The screen's height in this mode.
pub const SCREEN_HEIGHT: usize = 160; pub const HEIGHT: usize = 160;
/// The number of pixels on the screen. const PAGE0_INDEXES: VolBlock<u8, <U256 as Mul<U160>>::Output> =
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_BLOCK8: VolBlock<u8, <U256 as Mul<U160>>::Output> =
unsafe { VolBlock::new(VRAM_BASE_USIZE) }; unsafe { VolBlock::new(VRAM_BASE_USIZE) };
// TODO: newtype this? const PAGE1_INDEXES: VolBlock<u8, <U256 as Mul<U160>>::Output> =
const PAGE1_BLOCK8: VolBlock<u8, <U256 as Mul<U160>>::Output> = unsafe { VolBlock::new(VRAM_BASE_USIZE + PAGE1_OFFSET) };
unsafe { VolBlock::new(VRAM_BASE_USIZE + 0xA000) };
// TODO: newtype this? const PAGE0_WORDS: VolBlock<u32, <<U256 as Mul<U160>>::Output as Div<U4>>::Output> =
const PAGE0_BLOCK16: VolBlock<u16, <<U256 as Mul<U160>>::Output as Div<U2>>::Output> =
unsafe { VolBlock::new(VRAM_BASE_USIZE) }; unsafe { VolBlock::new(VRAM_BASE_USIZE) };
// TODO: newtype this? const PAGE1_WORDS: VolBlock<u32, <<U256 as Mul<U160>>::Output as Div<U4>>::Output> =
const PAGE1_BLOCK16: VolBlock<u16, <<U256 as Mul<U160>>::Output as Div<U2>>::Output> = unsafe { VolBlock::new(VRAM_BASE_USIZE + PAGE1_OFFSET) };
unsafe { VolBlock::new(VRAM_BASE_USIZE + 0xA000) };
/// private iterator over the page0 pixels, four at a time /// Reads the color of the pixel specified.
const PAGE0_BULK32: VolBlock<u32, <<U256 as Mul<U160>>::Output as Div<U4>>::Output> =
unsafe { VolBlock::new(VRAM_BASE_USIZE) };
/// private iterator over the page1 pixels, four at a time
const PAGE1_BULK32: VolBlock<u32, <<U256 as Mul<U160>>::Output as Div<U4>>::Output> =
unsafe { VolBlock::new(VRAM_BASE_USIZE + 0xA000) };
/// Reads the pixel at the given (col,row).
/// ///
/// # Failure /// ## Failure
/// ///
/// Gives `None` if out of bounds. /// Gives `None` if out of bounds
pub fn read_pixel(page1: bool, col: usize, row: usize) -> Option<u8> { pub fn read(page: Page, col: usize, row: usize) -> Option<u8> {
// Note(Lokathor): byte _reads_ from VRAM are okay. match page {
if page1 { Page::Zero => Self::PAGE0_INDEXES,
Self::PAGE1_BLOCK8.get(col + row * Self::SCREEN_WIDTH).map(VolAddress::read) Page::One => Self::PAGE1_INDEXES,
} else {
Self::PAGE0_BLOCK8.get(col + row * Self::SCREEN_WIDTH).map(VolAddress::read)
} }
.get(col + row * Self::WIDTH)
.map(VolAddress::read)
} }
/// Writes the pixel at the given (col,row). /// Writes a color to the pixel specified.
/// ///
/// # Failure /// ## Failure
/// ///
/// Gives `None` if out of bounds. /// Gives `None` if out of bounds
pub fn write_pixel(page1: bool, col: usize, row: usize, pal8bpp: u8) -> Option<()> { pub fn write(page: Page, col: usize, row: usize, pal8bpp: u8) -> Option<()> {
// Note(Lokathor): byte _writes_ to VRAM are not permitted. We must jump // Note(Lokathor): Byte writes to VRAM aren't permitted, we have to jump
// through hoops when we attempt to write just a single byte. // through some hoops.
if col < Self::SCREEN_WIDTH && row < Self::SCREEN_HEIGHT { if col < Self::WIDTH && row < Self::HEIGHT {
let real_index = col + row * Self::SCREEN_WIDTH; let real_index = col + row * Self::WIDTH;
let rounded_down_index = real_index & !1; let rounded_down_index = real_index & !1;
let address: VolAddress<u16> = unsafe { let address: VolAddress<u16> = unsafe {
if page1 { match page {
Self::PAGE1_BLOCK8.index(rounded_down_index).cast() Page::Zero => Self::PAGE0_INDEXES,
} else { Page::One => Self::PAGE1_INDEXES,
Self::PAGE0_BLOCK8.index(rounded_down_index).cast()
} }
.index_unchecked(rounded_down_index)
.cast::<u16>()
}; };
if real_index == rounded_down_index { if real_index == rounded_down_index {
// even byte, change the high bits // even byte, change the high bits
@ -176,50 +224,93 @@ impl Mode4 {
} }
} }
/// Writes a "wide" pairing of palette entries to the location specified. /// Clear the screen to the palette index specified.
/// ///
/// The page is imagined to be a series of `u16` values rather than `u8` /// Takes ~215,000 cycles (~76% of a frame)
/// values, allowing you to write two palette entries side by side as a single pub fn clear_to(page: Page, pal8bpp: u8) {
/// 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> = if page1 {
Self::PAGE1_BLOCK16.index(wide_index)
} else {
Self::PAGE0_BLOCK16.index(wide_index)
};
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 pal8bpp_32 = pal8bpp as u32;
let bulk_color = (pal8bpp_32 << 24) | (pal8bpp_32 << 16) | (pal8bpp_32 << 8) | pal8bpp_32; let bulk_color = (pal8bpp_32 << 24) | (pal8bpp_32 << 16) | (pal8bpp_32 << 8) | pal8bpp_32;
for va in (if page1 { Self::PAGE1_BULK32 } else { Self::PAGE0_BULK32 }).iter() { let words = match page {
Page::Zero => Self::PAGE0_WORDS,
Page::One => Self::PAGE1_WORDS,
};
for va in words.iter() {
va.write(bulk_color) va.write(bulk_color)
} }
} }
/// Clears the page to the desired color using DMA3. /// Clears the screen to the palette index specified using DMA3.
pub fn dma_clear_page_to(page1: bool, pal8bpp: u8) { ///
/// Takes ~30,800 frames (~37% of VBlank)
pub fn dma_clear_to(page: Page, pal8bpp: u8) {
use crate::io::dma::DMA3; use crate::io::dma::DMA3;
let pal8bpp_32 = pal8bpp as u32; let pal8bpp_32 = pal8bpp as u32;
let bulk_color = (pal8bpp_32 << 24) | (pal8bpp_32 << 16) | (pal8bpp_32 << 8) | pal8bpp_32; let bulk_color = (pal8bpp_32 << 24) | (pal8bpp_32 << 16) | (pal8bpp_32 << 8) | pal8bpp_32;
let write_target = let words_address = unsafe {
if page1 { VRAM_BASE_USIZE as *mut u32 } else { (VRAM_BASE_USIZE + 0xA000) as *mut u32 }; match page {
unsafe { DMA3::fill32(&bulk_color, write_target, Self::SCREEN_U32_COUNT as u16) }; Page::Zero => Self::PAGE0_WORDS.index_unchecked(0).to_usize(),
Page::One => Self::PAGE1_WORDS.index_unchecked(0).to_usize(),
} }
};
unsafe { DMA3::fill32(&bulk_color, words_address as *mut u32, Self::PAGE0_WORDS.len() as u16) };
} }
//TODO: Mode4 Iter Scanlines / Pixels? /// Draws a line between the two points given `(c1,r1,c2,r2,color)`.
//TODO: Mode4 Line Drawing? ///
/// Works fine with out of bounds points. It only draws to in bounds
/// locations.
pub fn draw_line(page: Page, c1: isize, r1: isize, c2: isize, r2: isize, pal8bpp: u8) {
let mut col = c1;
let mut row = r1;
let w = c2 - c1;
let h = r2 - r1;
let mut dx1 = 0;
let mut dx2 = 0;
let mut dy1 = 0;
let mut dy2 = 0;
let mut longest = w.abs();
let mut shortest = h.abs();
if w < 0 {
dx1 = -1;
} else if w > 0 {
dx1 = 1;
};
if h < 0 {
dy1 = -1;
} else if h > 0 {
dy1 = 1;
};
if w < 0 {
dx2 = -1;
} else if w > 0 {
dx2 = 1;
};
if !(longest > shortest) {
core::mem::swap(&mut longest, &mut shortest);
if h < 0 {
dy2 = -1;
} else if h > 0 {
dy2 = 1
};
dx2 = 0;
}
let mut numerator = longest >> 1;
(0..(longest + 1)).for_each(|_| {
Self::write(page, col as usize, row as usize, pal8bpp);
numerator += shortest;
if !(numerator < longest) {
numerator -= longest;
col += dx1;
row += dy1;
} else {
col += dx2;
row += dy2;
}
});
}
}
/// Mode 5 is a bitmap mode with full color and reduced resolution. /// Mode 5 is a bitmap mode with full color and reduced resolution.
/// ///
@ -227,86 +318,143 @@ impl Mode4 {
/// * **Height:** 128 /// * **Height:** 128
/// * **Pages:** 2 /// * **Pages:** 2
/// ///
/// Because of the reduced resolution, we're allowed two pages for display. /// Because of the reduced resolutions there's enough space to have two pages.
/// ///
/// As with all bitmap modes, the image itself utilizes BG2 for display, so you /// As with all the bitmap video modes, the bitmap is considered to be BG2, so
/// must have BG2 enabled in addition to being within Mode 3. /// you have to enable BG2 as well if you want to see the bitmap.
pub struct Mode5; pub struct Mode5;
impl Mode5 { impl Mode5 {
/// The physical width in pixels of the GBA screen. /// The screen's width in this mode.
pub const SCREEN_WIDTH: usize = 160; pub const WIDTH: usize = 160;
/// The physical height in pixels of the GBA screen. /// The screen's height in this mode.
pub const SCREEN_HEIGHT: usize = 128; pub const HEIGHT: usize = 128;
/// The number of pixels on the screen. const PAGE0_PIXELS: VolBlock<Color, <U160 as Mul<U128>>::Output> =
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_BLOCK: VolBlock<Color, <U160 as Mul<U128>>::Output> =
unsafe { VolBlock::new(VRAM_BASE_USIZE) }; unsafe { VolBlock::new(VRAM_BASE_USIZE) };
// TODO: newtype this? const PAGE1_PIXELS: VolBlock<Color, <U160 as Mul<U128>>::Output> =
const PAGE1_BLOCK: VolBlock<Color, <U160 as Mul<U128>>::Output> = unsafe { VolBlock::new(VRAM_BASE_USIZE + PAGE1_OFFSET) };
unsafe { VolBlock::new(VRAM_BASE_USIZE + 0xA000) };
/// private iterator over the page0 pixels, four at a time const PAGE0_WORDS: VolBlock<u32, <<U160 as Mul<U128>>::Output as Div<U2>>::Output> =
const PAGE0_BULK32: VolBlock<u32, <<U160 as Mul<U128>>::Output as Div<U2>>::Output> =
unsafe { VolBlock::new(VRAM_BASE_USIZE) }; unsafe { VolBlock::new(VRAM_BASE_USIZE) };
/// private iterator over the page1 pixels, four at a time const PAGE1_WORDS: VolBlock<u32, <<U160 as Mul<U128>>::Output as Div<U2>>::Output> =
const PAGE1_BULK32: VolBlock<u32, <<U160 as Mul<U128>>::Output as Div<U2>>::Output> = unsafe { VolBlock::new(VRAM_BASE_USIZE + PAGE1_OFFSET) };
unsafe { VolBlock::new(VRAM_BASE_USIZE + 0xA000) };
/// Reads the pixel at the given (col,row). /// Reads the color of the pixel specified.
/// ///
/// # Failure /// ## Failure
/// ///
/// Gives `None` if out of bounds. /// Gives `None` if out of bounds
pub fn read_pixel(page1: bool, col: usize, row: usize) -> Option<Color> { pub fn read(page: Page, col: usize, row: usize) -> Option<Color> {
if page1 { match page {
Self::PAGE1_BLOCK.get(col + row * Self::SCREEN_WIDTH).map(VolAddress::read) Page::Zero => Self::PAGE0_PIXELS,
} else { Page::One => Self::PAGE1_PIXELS,
Self::PAGE0_BLOCK.get(col + row * Self::SCREEN_WIDTH).map(VolAddress::read)
} }
.get(col + row * Self::WIDTH)
.map(VolAddress::read)
} }
/// Writes the pixel at the given (col,row). /// Writes a color to the pixel specified.
/// ///
/// # Failure /// ## Failure
/// ///
/// Gives `None` if out of bounds. /// Gives `None` if out of bounds
pub fn write_pixel(page1: bool, col: usize, row: usize, color: Color) -> Option<()> { pub fn write(page: Page, col: usize, row: usize, color: Color) -> Option<()> {
if page1 { match page {
Self::PAGE1_BLOCK.get(col + row * Self::SCREEN_WIDTH).map(|va| va.write(color)) Page::Zero => Self::PAGE0_PIXELS,
} else { Page::One => Self::PAGE1_PIXELS,
Self::PAGE0_BLOCK.get(col + row * Self::SCREEN_WIDTH).map(|va| va.write(color))
} }
.get(col + row * Self::WIDTH)
.map(|va| va.write(color))
} }
/// Clears the whole screen to the desired color. /// Clear the screen to the color specified.
pub fn clear_page_to(page1: bool, color: Color) { ///
/// Takes ~215,000 cycles (~76% of a frame)
pub fn clear_to(page: Page, color: Color) {
let color32 = color.0 as u32; let color32 = color.0 as u32;
let bulk_color = color32 << 16 | color32; let bulk_color = color32 << 16 | color32;
for va in (if page1 { Self::PAGE1_BULK32 } else { Self::PAGE0_BULK32 }).iter() { let words = match page {
Page::Zero => Self::PAGE0_WORDS,
Page::One => Self::PAGE1_WORDS,
};
for va in words.iter() {
va.write(bulk_color) va.write(bulk_color)
} }
} }
/// Clears the whole screen to the desired color using DMA3. /// Clears the screen to the color specified using DMA3.
pub fn dma_clear_page_to(page1: bool, color: Color) { ///
/// Takes ~30,800 frames (~37% of VBlank)
pub fn dma_clear_to(page: Page, color: Color) {
use crate::io::dma::DMA3; use crate::io::dma::DMA3;
let color32 = color.0 as u32; let color32 = color.0 as u32;
let bulk_color = color32 << 16 | color32; let bulk_color = color32 << 16 | color32;
let write_target = let words_address = unsafe {
if page1 { VRAM_BASE_USIZE as *mut u32 } else { (VRAM_BASE_USIZE + 0xA000) as *mut u32 }; match page {
unsafe { DMA3::fill32(&bulk_color, write_target, Self::SCREEN_U32_COUNT as u16) }; Page::Zero => Self::PAGE0_WORDS.index_unchecked(0).to_usize(),
Page::One => Self::PAGE1_WORDS.index_unchecked(0).to_usize(),
} }
};
unsafe { DMA3::fill32(&bulk_color, words_address as *mut u32, Self::PAGE0_WORDS.len() as u16) };
} }
//TODO: Mode5 Iter Scanlines / Pixels? /// Draws a line between the two points given `(c1,r1,c2,r2,color)`.
//TODO: Mode5 Line Drawing? ///
/// Works fine with out of bounds points. It only draws to in bounds
/// locations.
pub fn draw_line(page: Page, c1: isize, r1: isize, c2: isize, r2: isize, color: Color) {
let mut col = c1;
let mut row = r1;
let w = c2 - c1;
let h = r2 - r1;
let mut dx1 = 0;
let mut dx2 = 0;
let mut dy1 = 0;
let mut dy2 = 0;
let mut longest = w.abs();
let mut shortest = h.abs();
if w < 0 {
dx1 = -1;
} else if w > 0 {
dx1 = 1;
};
if h < 0 {
dy1 = -1;
} else if h > 0 {
dy1 = 1;
};
if w < 0 {
dx2 = -1;
} else if w > 0 {
dx2 = 1;
};
if !(longest > shortest) {
core::mem::swap(&mut longest, &mut shortest);
if h < 0 {
dy2 = -1;
} else if h > 0 {
dy2 = 1
};
dx2 = 0;
}
let mut numerator = longest >> 1;
(0..(longest + 1)).for_each(|_| {
Self::write(page, col as usize, row as usize, color);
numerator += shortest;
if !(numerator < longest) {
numerator -= longest;
col += dx1;
row += dy1;
} else {
col += dx2;
row += dy2;
}
});
}
}