mirror of
https://github.com/italicsjenga/gba.git
synced 2025-01-11 03:21:30 +11:00
module organization
This commit is contained in:
parent
2f3becee59
commit
4586d2d95f
|
@ -3,3 +3,4 @@
|
|||
It's all well and good to just show a picture, even to show an animation, but if
|
||||
we want a game we have to let the user interact with something.
|
||||
|
||||
TODO
|
||||
|
|
|
@ -31,21 +31,166 @@ macro_rules! const_rgb {
|
|||
}};
|
||||
}
|
||||
|
||||
// TODO: kill this
|
||||
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[repr(transparent)]
|
||||
pub struct VolatilePtr<T>(pub *mut T);
|
||||
impl<T> VolatilePtr<T> {
|
||||
pub unsafe fn read(&self) -> T {
|
||||
core::ptr::read_volatile(self.0)
|
||||
mod vol_address {
|
||||
#![allow(unused)]
|
||||
use core::{cmp::Ordering, iter::FusedIterator, marker::PhantomData, num::NonZeroUsize};
|
||||
/// VolAddress
|
||||
#[derive(Debug)]
|
||||
#[repr(transparent)]
|
||||
pub struct VolAddress<T> {
|
||||
address: NonZeroUsize,
|
||||
marker: PhantomData<*mut T>,
|
||||
}
|
||||
impl<T> Clone for VolAddress<T> {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
impl<T> Copy for VolAddress<T> {}
|
||||
impl<T> PartialEq for VolAddress<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.address == other.address
|
||||
}
|
||||
}
|
||||
impl<T> Eq for VolAddress<T> {}
|
||||
impl<T> PartialOrd for VolAddress<T> {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.address.cmp(&other.address))
|
||||
}
|
||||
}
|
||||
impl<T> Ord for VolAddress<T> {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
self.address.cmp(&other.address)
|
||||
}
|
||||
}
|
||||
impl<T> VolAddress<T> {
|
||||
pub const unsafe fn new_unchecked(address: usize) -> Self {
|
||||
VolAddress {
|
||||
address: NonZeroUsize::new_unchecked(address),
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
pub const unsafe fn cast<Z>(self) -> VolAddress<Z> {
|
||||
VolAddress {
|
||||
address: self.address,
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
pub unsafe fn offset(self, offset: isize) -> Self {
|
||||
// TODO: const this
|
||||
VolAddress {
|
||||
address: NonZeroUsize::new_unchecked(self.address.get().wrapping_add(offset as usize * core::mem::size_of::<T>())),
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
pub fn is_aligned(self) -> bool {
|
||||
// TODO: const this
|
||||
self.address.get() % core::mem::align_of::<T>() == 0
|
||||
}
|
||||
pub const unsafe fn iter_slots(self, slots: usize) -> VolAddressIter<T> {
|
||||
VolAddressIter { vol_address: self, slots }
|
||||
}
|
||||
pub fn read(self) -> T
|
||||
where
|
||||
T: Copy,
|
||||
{
|
||||
unsafe { (self.address.get() as *mut T).read_volatile() }
|
||||
}
|
||||
pub unsafe fn read_non_copy(self) -> T {
|
||||
(self.address.get() as *mut T).read_volatile()
|
||||
}
|
||||
pub fn write(self, val: T) {
|
||||
unsafe { (self.address.get() as *mut T).write_volatile(val) }
|
||||
}
|
||||
}
|
||||
/// VolAddressIter
|
||||
#[derive(Debug)]
|
||||
pub struct VolAddressIter<T> {
|
||||
vol_address: VolAddress<T>,
|
||||
slots: usize,
|
||||
}
|
||||
impl<T> Clone for VolAddressIter<T> {
|
||||
fn clone(&self) -> Self {
|
||||
VolAddressIter {
|
||||
vol_address: self.vol_address,
|
||||
slots: self.slots,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<T> PartialEq for VolAddressIter<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.vol_address == other.vol_address && self.slots == other.slots
|
||||
}
|
||||
}
|
||||
impl<T> Eq for VolAddressIter<T> {}
|
||||
impl<T> Iterator for VolAddressIter<T> {
|
||||
type Item = VolAddress<T>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.slots > 0 {
|
||||
let out = self.vol_address;
|
||||
unsafe {
|
||||
self.slots -= 1;
|
||||
self.vol_address = self.vol_address.offset(1);
|
||||
}
|
||||
Some(out)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<T> FusedIterator for VolAddressIter<T> {}
|
||||
/// VolAddressBlock
|
||||
#[derive(Debug)]
|
||||
pub struct VolAddressBlock<T> {
|
||||
vol_address: VolAddress<T>,
|
||||
slots: usize,
|
||||
}
|
||||
impl<T> Clone for VolAddressBlock<T> {
|
||||
fn clone(&self) -> Self {
|
||||
VolAddressBlock {
|
||||
vol_address: self.vol_address,
|
||||
slots: self.slots,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<T> PartialEq for VolAddressBlock<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.vol_address == other.vol_address && self.slots == other.slots
|
||||
}
|
||||
}
|
||||
impl<T> Eq for VolAddressBlock<T> {}
|
||||
impl<T> VolAddressBlock<T> {
|
||||
pub const unsafe fn new_unchecked(vol_address: VolAddress<T>, slots: usize) -> Self {
|
||||
VolAddressBlock { vol_address, slots }
|
||||
}
|
||||
pub const fn iter(self) -> VolAddressIter<T> {
|
||||
VolAddressIter {
|
||||
vol_address: self.vol_address,
|
||||
slots: self.slots,
|
||||
}
|
||||
}
|
||||
pub unsafe fn index_unchecked(self, slot: usize) -> VolAddress<T> {
|
||||
// TODO: const this
|
||||
self.vol_address.offset(slot as isize)
|
||||
}
|
||||
pub fn index(self, slot: usize) -> VolAddress<T> {
|
||||
if slot < self.slots {
|
||||
unsafe { self.vol_address.offset(slot as isize) }
|
||||
} else {
|
||||
panic!("Index Requested: {} >= Bound: {}", slot, self.slots)
|
||||
}
|
||||
}
|
||||
pub fn get(self, slot: usize) -> Option<VolAddress<T>> {
|
||||
if slot < self.slots {
|
||||
unsafe { Some(self.vol_address.offset(slot as isize)) }
|
||||
} else {
|
||||
None
|
||||
}
|
||||
pub unsafe fn write(&self, data: T) {
|
||||
core::ptr::write_volatile(self.0, data);
|
||||
}
|
||||
pub unsafe fn offset(self, count: isize) -> Self {
|
||||
VolatilePtr(self.0.wrapping_offset(count))
|
||||
}
|
||||
}
|
||||
use self::vol_address::*;
|
||||
|
||||
newtype! {
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
|
@ -64,7 +209,7 @@ newtype! {
|
|||
DisplayControlSetting, u16
|
||||
}
|
||||
|
||||
pub const DISPLAY_CONTROL: VolatilePtr<DisplayControlSetting> = VolatilePtr(0x0400_0000 as *mut DisplayControlSetting);
|
||||
pub const DISPLAY_CONTROL: VolAddress<DisplayControlSetting> = unsafe { VolAddress::new_unchecked(0x0400_0000) };
|
||||
pub const JUST_MODE3: DisplayControlSetting = DisplayControlSetting(3);
|
||||
pub const JUST_BG2: DisplayControlSetting = DisplayControlSetting(0b100_0000_0000);
|
||||
pub const JUST_MODE3_AND_BG2: DisplayControlSetting = DisplayControlSetting(JUST_MODE3.0 | JUST_BG2.0);
|
||||
|
@ -72,10 +217,12 @@ pub const JUST_MODE3_AND_BG2: DisplayControlSetting = DisplayControlSetting(JUST
|
|||
pub struct Mode3;
|
||||
impl Mode3 {
|
||||
const SCREEN_WIDTH: isize = 240;
|
||||
const PIXELS: VolatilePtr<Color> = VolatilePtr(0x600_0000 as *mut Color);
|
||||
const SCREEN_HEIGHT: isize = 160;
|
||||
const PIXELS: VolAddressBlock<Color> =
|
||||
unsafe { VolAddressBlock::new_unchecked(VolAddress::new_unchecked(0x600_0000), (Self::SCREEN_WIDTH * Self::SCREEN_HEIGHT) as usize) };
|
||||
|
||||
pub unsafe fn draw_pixel_unchecked(col: isize, row: isize, color: Color) {
|
||||
Self::PIXELS.offset(col + row * Self::SCREEN_WIDTH).write(color);
|
||||
pub unsafe fn draw_pixel(col: usize, row: usize, color: Color) {
|
||||
Self::PIXELS.index(col + row * Self::SCREEN_WIDTH as usize).write(color);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -88,9 +235,9 @@ fn panic(_info: &core::panic::PanicInfo) -> ! {
|
|||
fn main(_argc: isize, _argv: *const *const u8) -> isize {
|
||||
unsafe {
|
||||
DISPLAY_CONTROL.write(JUST_MODE3_AND_BG2);
|
||||
Mode3::draw_pixel_unchecked(120, 80, const_rgb!(31, 0, 0));
|
||||
Mode3::draw_pixel_unchecked(136, 80, const_rgb!(0, 31, 0));
|
||||
Mode3::draw_pixel_unchecked(120, 96, const_rgb!(0, 0, 31));
|
||||
Mode3::draw_pixel(120, 80, const_rgb!(31, 0, 0));
|
||||
Mode3::draw_pixel(136, 80, const_rgb!(0, 31, 0));
|
||||
Mode3::draw_pixel(120, 96, const_rgb!(0, 0, 31));
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
|
|
10
src/base.rs
Normal file
10
src/base.rs
Normal file
|
@ -0,0 +1,10 @@
|
|||
//! Holds fundamental types/ops which the rest of the crate it built on.
|
||||
|
||||
pub mod fixed_point;
|
||||
//pub(crate) use self::fixed_point::*;
|
||||
|
||||
pub mod volatile;
|
||||
pub(crate) use self::volatile::*;
|
||||
|
||||
pub mod builtins;
|
||||
//pub(crate) use self::builtins::*;
|
|
@ -1,6 +1,4 @@
|
|||
//! Things that I wish were in core, but aren't.
|
||||
|
||||
//TODO(Lokathor): reorganize as gba::core_extras::fixed_point and gba::core_extras::volatile ?
|
||||
//! Holds types for correct handling of volatile memory.
|
||||
|
||||
use core::{cmp::Ordering, iter::FusedIterator, marker::PhantomData, num::NonZeroUsize};
|
||||
|
|
@ -1,126 +0,0 @@
|
|||
//! The module for all things relating to the IO Register portion of the GBA's
|
||||
//! memory map.
|
||||
//!
|
||||
//! Here we define many constants for the volatile pointers to the various IO
|
||||
//! registers. Each raw register constant is named according to the name given
|
||||
//! to it in GBATEK's [GBA I/O
|
||||
//! Map](http://problemkaputt.de/gbatek.htm#gbaiomap). They program in C, and so
|
||||
//! of course all the names terrible and missing as many vowels as possible.
|
||||
//! However, being able to look it up online is the most important thing here,
|
||||
//! so oh well.
|
||||
//!
|
||||
//! In addition to the const `VolatilePtr` values, we will over time be adding
|
||||
//! safe wrappers around each register, including newtypes and such so that you
|
||||
//! can easily work with whatever each specific register is doing.
|
||||
|
||||
// TODO(lokathor): IO Register newtypes.
|
||||
|
||||
use gba_proc_macro::register_bit;
|
||||
|
||||
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 {
|
||||
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() < 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() >= SCREEN_HEIGHT as u16 {}
|
||||
}
|
118
src/lib.rs
118
src/lib.rs
|
@ -57,15 +57,9 @@ macro_rules! newtype {
|
|||
};
|
||||
}
|
||||
|
||||
pub mod builtins;
|
||||
|
||||
pub mod fixed;
|
||||
|
||||
pub mod base;
|
||||
pub(crate) use self::base::*;
|
||||
pub mod bios;
|
||||
|
||||
pub mod core_extras;
|
||||
pub(crate) use crate::core_extras::*;
|
||||
|
||||
pub mod io;
|
||||
|
||||
pub mod video_ram;
|
||||
|
@ -226,3 +220,111 @@ 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 {}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue