mirror of
https://github.com/italicsjenga/gba.git
synced 2025-01-11 11:31:31 +11:00
cleanup, hello world runs 100% safe now
This commit is contained in:
parent
b927a348bd
commit
35ed03cb44
|
@ -13,7 +13,7 @@ publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
typenum = "1.10"
|
typenum = "1.10"
|
||||||
gba-proc-macro = "0.2.1"
|
gba-proc-macro = "0.3"
|
||||||
|
|
||||||
#[dev-dependencies]
|
#[dev-dependencies]
|
||||||
#quickcheck="0.7"
|
#quickcheck="0.7"
|
||||||
|
|
23
examples/hello_world.rs
Normal file
23
examples/hello_world.rs
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
#![no_std]
|
||||||
|
#![feature(start)]
|
||||||
|
|
||||||
|
use gba::{
|
||||||
|
io::display::{DisplayControlMode, DisplayControlSetting, DISPCNT},
|
||||||
|
video::Mode3,
|
||||||
|
Color,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[panic_handler]
|
||||||
|
fn panic(_info: &core::panic::PanicInfo) -> ! {
|
||||||
|
loop {}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[start]
|
||||||
|
fn main(_argc: isize, _argv: *const *const u8) -> isize {
|
||||||
|
const SETTING: DisplayControlSetting = DisplayControlSetting::new().with_mode(DisplayControlMode::Bitmap3).with_display_bg2(true);
|
||||||
|
DISPCNT.write(SETTING);
|
||||||
|
Mode3::write_pixel(120, 80, Color::from_rgb(31, 0, 0));
|
||||||
|
Mode3::write_pixel(136, 80, Color::from_rgb(0, 31, 0));
|
||||||
|
Mode3::write_pixel(120, 96, Color::from_rgb(0, 0, 31));
|
||||||
|
loop {}
|
||||||
|
}
|
|
@ -10,4 +10,5 @@ use super::*;
|
||||||
|
|
||||||
use gba_proc_macro::register_bit;
|
use gba_proc_macro::register_bit;
|
||||||
|
|
||||||
|
pub mod display;
|
||||||
pub mod keypad;
|
pub mod keypad;
|
||||||
|
|
110
src/io/display.rs
Normal file
110
src/io/display.rs
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
//! Contains types and definitions for display related IO registers.
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
/// LCD Control. Read/Write.
|
||||||
|
///
|
||||||
|
/// * [gbatek entry](http://problemkaputt.de/gbatek.htm#lcdiodisplaycontrol)
|
||||||
|
pub const DISPCNT: VolAddress<DisplayControlSetting> = unsafe { VolAddress::new_unchecked(0x400_0000) };
|
||||||
|
|
||||||
|
newtype!(
|
||||||
|
/// A newtype over the various display control options that you have on a GBA.
|
||||||
|
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)]
|
||||||
|
DisplayControlSetting,
|
||||||
|
u16
|
||||||
|
);
|
||||||
|
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
impl DisplayControlSetting {
|
||||||
|
pub const BG_MODE_MASK: u16 = 0b111;
|
||||||
|
|
||||||
|
pub fn mode(self) -> DisplayControlMode {
|
||||||
|
// TODO: constify
|
||||||
|
match self.0 & Self::BG_MODE_MASK {
|
||||||
|
0 => DisplayControlMode::Tiled0,
|
||||||
|
1 => DisplayControlMode::Tiled1,
|
||||||
|
2 => DisplayControlMode::Tiled2,
|
||||||
|
3 => DisplayControlMode::Bitmap3,
|
||||||
|
4 => DisplayControlMode::Bitmap4,
|
||||||
|
5 => DisplayControlMode::Bitmap5,
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub const fn with_mode(self, new_mode: DisplayControlMode) -> Self {
|
||||||
|
Self((self.0 & !Self::BG_MODE_MASK) | (new_mode as u16))
|
||||||
|
}
|
||||||
|
|
||||||
|
register_bit!(CGB_MODE_BIT, u16, 0b1000, cgb_mode);
|
||||||
|
register_bit!(PAGE_SELECT_BIT, u16, 0b1_0000, page1_enabled);
|
||||||
|
register_bit!(HBLANK_INTERVAL_FREE_BIT, u16, 0b10_0000, hblank_interval_free);
|
||||||
|
register_bit!(OBJECT_MEMORY_1D, u16, 0b100_0000, object_memory_1d);
|
||||||
|
register_bit!(FORCE_BLANK_BIT, u16, 0b1000_0000, force_blank);
|
||||||
|
register_bit!(DISPLAY_BG0_BIT, u16, 0b1_0000_0000, display_bg0);
|
||||||
|
register_bit!(DISPLAY_BG1_BIT, u16, 0b10_0000_0000, display_bg1);
|
||||||
|
register_bit!(DISPLAY_BG2_BIT, u16, 0b100_0000_0000, display_bg2);
|
||||||
|
register_bit!(DISPLAY_BG3_BIT, u16, 0b1000_0000_0000, display_bg3);
|
||||||
|
register_bit!(DISPLAY_OBJECT_BIT, u16, 0b1_0000_0000_0000, display_object);
|
||||||
|
register_bit!(DISPLAY_WINDOW0_BIT, u16, 0b10_0000_0000_0000, display_window0);
|
||||||
|
register_bit!(DISPLAY_WINDOW1_BIT, u16, 0b100_0000_0000_0000, display_window1);
|
||||||
|
register_bit!(OBJECT_WINDOW_BIT, u16, 0b1000_0000_0000_0000, display_object_window);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The six display modes available on the GBA.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
#[repr(u16)]
|
||||||
|
pub enum DisplayControlMode {
|
||||||
|
/// This basically allows for the most different things at once (all layers,
|
||||||
|
/// 1024 tiles, two palette modes, etc), but you can't do affine
|
||||||
|
/// transformations.
|
||||||
|
Tiled0 = 0,
|
||||||
|
/// This is a mix of `Tile0` and `Tile2`: BG0 and BG1 run as if in `Tiled0`,
|
||||||
|
/// and BG2 runs as if in `Tiled2`.
|
||||||
|
Tiled1 = 1,
|
||||||
|
/// This allows affine transformations, but only uses BG2 and BG3.
|
||||||
|
Tiled2 = 2,
|
||||||
|
/// This is the basic bitmap draw mode. The whole screen is a single bitmap.
|
||||||
|
/// Uses BG2 only.
|
||||||
|
Bitmap3 = 3,
|
||||||
|
/// This uses _paletted color_ so that there's enough space to have two pages
|
||||||
|
/// at _full resolution_, allowing page flipping. Uses BG2 only.
|
||||||
|
Bitmap4 = 4,
|
||||||
|
/// This uses _reduced resolution_ so that there's enough space to have two
|
||||||
|
/// pages with _full color_, allowing page flipping. Uses BG2 only.
|
||||||
|
Bitmap5 = 5,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Assigns the given display control setting.
|
||||||
|
pub fn set_display_control(setting: DisplayControlSetting) {
|
||||||
|
DISPCNT.write(setting);
|
||||||
|
}
|
||||||
|
/// Obtains the current display control setting.
|
||||||
|
pub fn display_control() -> DisplayControlSetting {
|
||||||
|
DISPCNT.read()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If the `VCOUNT` register reads equal to or above this then you're in vblank.
|
||||||
|
pub const VBLANK_SCANLINE: u16 = 160;
|
||||||
|
|
||||||
|
/// Vertical Counter (LY).
|
||||||
|
///
|
||||||
|
/// Gives the current scanline that the display controller is working on. If
|
||||||
|
/// this is at or above the `VBLANK_SCANLINE` value then the display controller
|
||||||
|
/// is in a "vertical blank" period.
|
||||||
|
pub const VCOUNT: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_0006) };
|
||||||
|
|
||||||
|
/// Obtains the current `VCOUNT` value.
|
||||||
|
pub fn vcount() -> u16 {
|
||||||
|
VCOUNT.read()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Performs a busy loop until VBlank starts.
|
||||||
|
pub fn spin_until_vblank() {
|
||||||
|
// TODO: make this the better version with BIOS and interrupts and such.
|
||||||
|
while vcount() < VBLANK_SCANLINE {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Performs a busy loop until VDraw starts.
|
||||||
|
pub fn spin_until_vdraw() {
|
||||||
|
// TODO: make this the better version with BIOS and interrupts and such.
|
||||||
|
while vcount() >= VBLANK_SCANLINE {}
|
||||||
|
}
|
130
src/lib.rs
130
src/lib.rs
|
@ -62,7 +62,27 @@ pub(crate) use self::base::*;
|
||||||
pub mod bios;
|
pub mod bios;
|
||||||
pub mod io;
|
pub mod io;
|
||||||
|
|
||||||
pub mod video_ram;
|
pub mod video;
|
||||||
|
|
||||||
|
newtype! {
|
||||||
|
/// A color on the GBA is an RGB 5.5.5 within a `u16`
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
Color, u16
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Color {
|
||||||
|
/// Constructs a color from the channel values provided (should be 0..=31).
|
||||||
|
///
|
||||||
|
/// No actual checks are performed, so illegal channel values can overflow
|
||||||
|
/// into each other and produce an unintended color.
|
||||||
|
pub const fn from_rgb(r: u16, g: u16, b: u16) -> Color {
|
||||||
|
Color(b << 10 | g << 5 | r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// After here is totally unsorted nonsense
|
||||||
|
//
|
||||||
|
|
||||||
/// Performs unsigned divide and remainder, gives None if dividing by 0.
|
/// Performs unsigned divide and remainder, gives None if dividing by 0.
|
||||||
pub fn divrem_u32(numer: u32, denom: u32) -> Option<(u32, u32)> {
|
pub fn divrem_u32(numer: u32, denom: u32) -> Option<(u32, u32)> {
|
||||||
|
@ -220,111 +240,3 @@ mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use gba_proc_macro::register_bit;
|
|
||||||
|
|
||||||
/// LCD Control. Read/Write.
|
|
||||||
///
|
|
||||||
/// * [gbatek entry](http://problemkaputt.de/gbatek.htm#lcdiodisplaycontrol)
|
|
||||||
pub const DISPCNT: VolAddress<DisplayControlSetting> = unsafe { VolAddress::new_unchecked(0x400_0000) };
|
|
||||||
|
|
||||||
newtype!(
|
|
||||||
/// A newtype over the various display control options that you have on a GBA.
|
|
||||||
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)]
|
|
||||||
DisplayControlSetting,
|
|
||||||
u16
|
|
||||||
);
|
|
||||||
|
|
||||||
#[allow(missing_docs)]
|
|
||||||
impl DisplayControlSetting {
|
|
||||||
pub const BG_MODE_MASK: u16 = 0b111;
|
|
||||||
|
|
||||||
pub fn mode(self) -> DisplayControlMode {
|
|
||||||
match self.0 & Self::BG_MODE_MASK {
|
|
||||||
0 => DisplayControlMode::Tiled0,
|
|
||||||
1 => DisplayControlMode::Tiled1,
|
|
||||||
2 => DisplayControlMode::Tiled2,
|
|
||||||
3 => DisplayControlMode::Bitmap3,
|
|
||||||
4 => DisplayControlMode::Bitmap4,
|
|
||||||
5 => DisplayControlMode::Bitmap5,
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn set_mode(&mut self, new_mode: DisplayControlMode) {
|
|
||||||
self.0 &= !Self::BG_MODE_MASK;
|
|
||||||
self.0 |= match new_mode {
|
|
||||||
DisplayControlMode::Tiled0 => 0,
|
|
||||||
DisplayControlMode::Tiled1 => 1,
|
|
||||||
DisplayControlMode::Tiled2 => 2,
|
|
||||||
DisplayControlMode::Bitmap3 => 3,
|
|
||||||
DisplayControlMode::Bitmap4 => 4,
|
|
||||||
DisplayControlMode::Bitmap5 => 5,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
register_bit!(CGB_MODE_BIT, u16, 0b1000, cgb_mode);
|
|
||||||
register_bit!(PAGE_SELECT_BIT, u16, 0b1_0000, page1_enabled);
|
|
||||||
register_bit!(HBLANK_INTERVAL_FREE_BIT, u16, 0b10_0000, hblank_interval_free);
|
|
||||||
register_bit!(OBJECT_MEMORY_1D, u16, 0b100_0000, object_memory_1d);
|
|
||||||
register_bit!(FORCE_BLANK_BIT, u16, 0b1000_0000, force_blank);
|
|
||||||
register_bit!(DISPLAY_BG0_BIT, u16, 0b1_0000_0000, display_bg0);
|
|
||||||
register_bit!(DISPLAY_BG1_BIT, u16, 0b10_0000_0000, display_bg1);
|
|
||||||
register_bit!(DISPLAY_BG2_BIT, u16, 0b100_0000_0000, display_bg2);
|
|
||||||
register_bit!(DISPLAY_BG3_BIT, u16, 0b1000_0000_0000, display_bg3);
|
|
||||||
register_bit!(DISPLAY_OBJECT_BIT, u16, 0b1_0000_0000_0000, display_object);
|
|
||||||
register_bit!(DISPLAY_WINDOW0_BIT, u16, 0b10_0000_0000_0000, display_window0);
|
|
||||||
register_bit!(DISPLAY_WINDOW1_BIT, u16, 0b100_0000_0000_0000, display_window1);
|
|
||||||
register_bit!(OBJECT_WINDOW_BIT, u16, 0b1000_0000_0000_0000, display_object_window);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The six display modes available on the GBA.
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
pub enum DisplayControlMode {
|
|
||||||
/// This basically allows for the most different things at once (all layers,
|
|
||||||
/// 1024 tiles, two palette modes, etc), but you can't do affine
|
|
||||||
/// transformations.
|
|
||||||
Tiled0,
|
|
||||||
/// This is a mix of `Tile0` and `Tile2`: BG0 and BG1 run as if in `Tiled0`,
|
|
||||||
/// and BG2 runs as if in `Tiled2`.
|
|
||||||
Tiled1,
|
|
||||||
/// This allows affine transformations, but only uses BG2 and BG3.
|
|
||||||
Tiled2,
|
|
||||||
/// This is the basic bitmap draw mode. The whole screen is a single bitmap.
|
|
||||||
/// Uses BG2 only.
|
|
||||||
Bitmap3,
|
|
||||||
/// This uses _paletted color_ so that there's enough space to have two pages
|
|
||||||
/// at _full resolution_, allowing page flipping. Uses BG2 only.
|
|
||||||
Bitmap4,
|
|
||||||
/// This uses _reduced resolution_ so that there's enough space to have two
|
|
||||||
/// pages with _full color_, allowing page flipping. Uses BG2 only.
|
|
||||||
Bitmap5,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Assigns the given display control setting.
|
|
||||||
pub fn set_display_control(setting: DisplayControlSetting) {
|
|
||||||
DISPCNT.write(setting);
|
|
||||||
}
|
|
||||||
/// Obtains the current display control setting.
|
|
||||||
pub fn display_control() -> DisplayControlSetting {
|
|
||||||
DISPCNT.read()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Vertical Counter (LY)
|
|
||||||
pub const VCOUNT: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_0006) };
|
|
||||||
|
|
||||||
/// Obtains the current VCount value.
|
|
||||||
pub fn vcount() -> u16 {
|
|
||||||
VCOUNT.read()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Performs a busy loop until VBlank starts.
|
|
||||||
pub fn wait_until_vblank() {
|
|
||||||
// TODO: make this the better version with BIOS and interrupts and such.
|
|
||||||
while vcount() < crate::video_ram::SCREEN_HEIGHT as u16 {}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Performs a busy loop until VDraw starts.
|
|
||||||
pub fn wait_until_vdraw() {
|
|
||||||
// TODO: make this the better version with BIOS and interrupts and such.
|
|
||||||
while vcount() >= crate::video_ram::SCREEN_HEIGHT as u16 {}
|
|
||||||
}
|
|
||||||
|
|
83
src/video.rs
Normal file
83
src/video.rs
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
//! Module for all things relating to the Video RAM.
|
||||||
|
//!
|
||||||
|
//! Note that the GBA has six different display modes available, and the
|
||||||
|
//! _meaning_ of Video RAM depends on which display mode is active. In all
|
||||||
|
//! cases, Video RAM is **96kb** from `0x0600_0000` to `0x0601_7FFF`.
|
||||||
|
//!
|
||||||
|
//! # Safety
|
||||||
|
//!
|
||||||
|
//! Note that all possible bit patterns are technically allowed within Video
|
||||||
|
//! Memory. If you write the "wrong" thing into video memory you don't crash the
|
||||||
|
//! GBA, instead you just get graphical glitches (or perhaps nothing at all).
|
||||||
|
//! Accordingly, the "safe" functions here will check that you're in bounds, but
|
||||||
|
//! they won't bother to check that you've set the video mode they're designed
|
||||||
|
//! for.
|
||||||
|
|
||||||
|
pub use super::*;
|
||||||
|
|
||||||
|
/// The start of VRAM.
|
||||||
|
///
|
||||||
|
/// Depending on what display mode is currently set there's different ways that
|
||||||
|
/// your program should interpret the VRAM space. Accordingly, we give the raw
|
||||||
|
/// 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) };
|
||||||
|
|
||||||
|
/// 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_WIDTH * Self::SCREEN_HEIGHT / 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: dma_clear_to?
|
||||||
|
}
|
|
@ -1,83 +0,0 @@
|
||||||
//! Module for all things relating to the Video RAM.
|
|
||||||
//!
|
|
||||||
//! Note that the GBA has six different display modes available, and the
|
|
||||||
//! _meaning_ of Video RAM depends on which display mode is active. In all
|
|
||||||
//! cases, Video RAM is **96kb** from `0x0600_0000` to `0x0601_7FFF`.
|
|
||||||
//!
|
|
||||||
//! # Safety
|
|
||||||
//!
|
|
||||||
//! Note that all possible bit patterns are technically allowed within Video
|
|
||||||
//! Memory. If you write the "wrong" thing into video memory you don't crash the
|
|
||||||
//! GBA, instead you just get graphical glitches (or perhaps nothing at all).
|
|
||||||
//! Accordingly, the "safe" functions here will check that you're in bounds, but
|
|
||||||
//! they won't bother to check that you've set the video mode they're designed
|
|
||||||
//! for.
|
|
||||||
|
|
||||||
pub use super::*;
|
|
||||||
|
|
||||||
// TODO: kill all this too
|
|
||||||
|
|
||||||
/// The physical width in pixels of the GBA screen.
|
|
||||||
pub const SCREEN_WIDTH: isize = 240;
|
|
||||||
|
|
||||||
/// The physical height in pixels of the GBA screen.
|
|
||||||
pub const SCREEN_HEIGHT: isize = 160;
|
|
||||||
|
|
||||||
/// The start of VRAM.
|
|
||||||
///
|
|
||||||
/// Depending on what display mode is currently set there's different ways that
|
|
||||||
/// your program should interpret the VRAM space. Accordingly, we give the raw
|
|
||||||
/// value as just being a `usize`.
|
|
||||||
pub const VRAM_BASE_ADDRESS: usize = 0x0600_0000;
|
|
||||||
|
|
||||||
const MODE3_VRAM: VolAddress<u16> = unsafe { VolAddress::new_unchecked(VRAM_BASE_ADDRESS) };
|
|
||||||
|
|
||||||
/// Draws a pixel to the screen while in Display Mode 3, with bounds checks.
|
|
||||||
///
|
|
||||||
/// # Panics
|
|
||||||
///
|
|
||||||
/// If `col` or `row` are out of bounds this will panic.
|
|
||||||
pub fn mode3_draw_pixel(col: isize, row: isize, color: u16) {
|
|
||||||
assert!(col >= 0 && col < SCREEN_WIDTH);
|
|
||||||
assert!(row >= 0 && row < SCREEN_HEIGHT);
|
|
||||||
unsafe { mode3_draw_pixel_unchecked(col, row, color) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Draws a pixel to the screen while in Display Mode 3.
|
|
||||||
///
|
|
||||||
/// Coordinates are relative to the top left corner.
|
|
||||||
///
|
|
||||||
/// If you're in another mode you'll get something weird drawn, but it's memory
|
|
||||||
/// safe in the rust sense as long as you stay in bounds.
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
///
|
|
||||||
/// * `col` must be in `0..SCREEN_WIDTH`
|
|
||||||
/// * `row` must be in `0..SCREEN_HEIGHT`
|
|
||||||
pub unsafe fn mode3_draw_pixel_unchecked(col: isize, row: isize, color: u16) {
|
|
||||||
MODE3_VRAM.offset(col + row * SCREEN_WIDTH).write(color);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reads the given pixel of video memory according to Mode 3 placement.
|
|
||||||
///
|
|
||||||
/// # Failure
|
|
||||||
///
|
|
||||||
/// If the location is out of bounds you get `None`.
|
|
||||||
pub fn mode3_read_pixel(col: isize, row: isize) -> Option<u16> {
|
|
||||||
if col >= 0 && col < SCREEN_WIDTH && row >= 0 && row < SCREEN_HEIGHT {
|
|
||||||
unsafe { Some(MODE3_VRAM.offset(col + row * SCREEN_WIDTH).read()) }
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Clears the entire screen to the color specified.
|
|
||||||
pub unsafe fn mode3_clear_screen(color: u16) {
|
|
||||||
// TODO: use DMA?
|
|
||||||
let color = color as u32;
|
|
||||||
let bulk_color = color << 16 | color;
|
|
||||||
let block: VolAddressBlock<u32> = VolAddressBlock::new_unchecked(MODE3_VRAM.cast::<u32>(), (SCREEN_HEIGHT * SCREEN_WIDTH / 2) as usize);
|
|
||||||
for b in block.iter() {
|
|
||||||
b.write(bulk_color);
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue