From d2260fc11790fab774b4d43ef20fee0826ed93f8 Mon Sep 17 00:00:00 2001 From: Lokathor Date: Tue, 25 Dec 2018 14:44:05 -0700 Subject: [PATCH 01/24] Move Mode3/4/5 into gba::video::bitmap --- examples/hello_world.rs | 2 +- examples/light_cycle.rs | 2 +- examples/mgba_panic_handler.rs | 3 +- src/video.rs | 71 +------- src/video/bitmap.rs | 309 +++++++++++++++++++++++++++++++++ 5 files changed, 316 insertions(+), 71 deletions(-) create mode 100644 src/video/bitmap.rs diff --git a/examples/hello_world.rs b/examples/hello_world.rs index 1f5a360..db402d8 100644 --- a/examples/hello_world.rs +++ b/examples/hello_world.rs @@ -4,7 +4,7 @@ use gba::{ io::display::{DisplayControlMode, DisplayControlSetting, DISPCNT}, - video::Mode3, + video::bitmap::Mode3, Color, }; diff --git a/examples/light_cycle.rs b/examples/light_cycle.rs index 6b47d57..6c6ed71 100644 --- a/examples/light_cycle.rs +++ b/examples/light_cycle.rs @@ -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, }; diff --git a/examples/mgba_panic_handler.rs b/examples/mgba_panic_handler.rs index 17e7057..214a56a 100644 --- a/examples/mgba_panic_handler.rs +++ b/examples/mgba_panic_handler.rs @@ -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); diff --git a/src/video.rs b/src/video.rs index 8e2586f..df13684 100644 --- a/src/video.rs +++ b/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 = - 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 = - 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 { - 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) }; - } -} diff --git a/src/video/bitmap.rs b/src/video/bitmap.rs new file mode 100644 index 0000000..7736226 --- /dev/null +++ b/src/video/bitmap.rs @@ -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 = + 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 = + 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 { + 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 = unsafe { VolAddress::new_unchecked(VRAM_BASE_USIZE) }; + + // TODO: newtype this? + const PAGE0_BLOCK: VolAddressBlock = unsafe { VolAddressBlock::new_unchecked(Self::PAGE0_BASE, Self::SCREEN_PIXEL_COUNT) }; + + // TODO: newtype this? + const PAGE1_BASE: VolAddress = unsafe { VolAddress::new_unchecked(VRAM_BASE_USIZE + 0x9600) }; + + // TODO: newtype this? + const PAGE1_BLOCK: VolAddressBlock = 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 = unsafe { VolAddressBlock::new_unchecked(Self::PAGE0_BASE.cast::(), Self::SCREEN_U32_COUNT).iter() }; + + /// private iterator over the page1 pixels, four at a time + const BULK_ITER1: VolAddressIter = unsafe { VolAddressBlock::new_unchecked(Self::PAGE1_BASE.cast::(), 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 { + // 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 = 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 = unsafe { + if page1 { + Self::PAGE1_BASE.cast::().offset(wide_index as isize) + } else { + Self::PAGE0_BASE.cast::().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 = unsafe { VolAddress::new_unchecked(VRAM_BASE_USIZE) }; + + // TODO: newtype this? + const PAGE0_BLOCK: VolAddressBlock = unsafe { VolAddressBlock::new_unchecked(Self::PAGE0_BASE, Self::SCREEN_PIXEL_COUNT) }; + + // TODO: newtype this? + const PAGE1_BASE: VolAddress = unsafe { VolAddress::new_unchecked(VRAM_BASE_USIZE + 0xA000) }; + + // TODO: newtype this? + const PAGE1_BLOCK: VolAddressBlock = 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 = unsafe { VolAddressBlock::new_unchecked(Self::PAGE0_BASE.cast::(), Self::SCREEN_U32_COUNT).iter() }; + + /// private iterator over the page1 pixels, four at a time + const BULK_ITER1: VolAddressIter = unsafe { VolAddressBlock::new_unchecked(Self::PAGE1_BASE.cast::(), 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 { + 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? From 9cde11acba7efb7d329cde6bd8d470e1e0c8de2f Mon Sep 17 00:00:00 2001 From: Lokathor Date: Tue, 25 Dec 2018 14:44:55 -0700 Subject: [PATCH 02/24] Explain more about the MGBADebug --- book/src/01-quirks/01-no_std.md | 83 ++++++++++++++++++++++++--------- 1 file changed, 62 insertions(+), 21 deletions(-) diff --git a/book/src/01-quirks/01-no_std.md b/book/src/01-quirks/01-no_std.md index 44fa757..2111572 100644 --- a/book/src/01-quirks/01-no_std.md +++ b/book/src/01-quirks/01-no_std.md @@ -91,29 +91,70 @@ good fit for the GBA (I honestly haven't looked into it). ## Bare Metal Panic -TODO: expand this +If our code panics, we usually want to see that panic message. Unfortunately, +without a way to access something like `stdout` or `stderr` we've gotta do +something a little weirder. -* Write `0xC0DE` to `0x4fff780` (`u16`) to enable mGBA logging. Write any other - value to disable it. -* Read `0x4fff780` (`u16`) to check mGBA logging status. - * You get `0x1DEA` if debugging is active. - * Otherwise you get standard open bus nonsense values. -* Write your message into the virtual `[u8; 255]` array starting at `0x4fff600`. - mGBA will interpret these bytes as a CString value. -* Write `0x100` PLUS the message level to `0x4fff700` (`u16`) when you're ready - to send a message line: - * 0: Fatal (halts execution with a popup) - * 1: Error - * 2: Warning - * 3: Info - * 4: Debug -* Sending the message also automatically zeroes the output buffer. -* View the output within the "Tools" menu, "View Logs...". Note that the Fatal - message, if any doesn't get logged. +If our program is running within the `mGBA` emulator, version 0.7 or later, we +can access a special set of addresses that allow us to send out `CString` +values, which then appear within a message log that you can check. -TODO: this will probably fail without a `__clzsi2` implementation, which is a -good seg for the next section +We can capture this behavior by making an `MGBADebug` type, and then implement +`core::fmt::Write` for that type. Once done, the `write!` macro will let us +target the mGBA debug output channel. + +When used, it looks like this: + +```rust +#[panic_handler] +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); + } + loop {} +} +``` + +If you want to follow the particulars you can check the `MGBADebug` source in +the `gba` crate. Basically, there's one address you can use to try and activate +the debug output, and if it works you write your message into the "array" at +another address, and then finally write a send value to a third address. You'll +need to have read the [volatile](03-volatile_destination.md) section for the +details to make sense. ## LLVM Intrinsics -TODO: explain that we'll occasionally have to provide some intrinsics. +The above code will make your program fail to build in debug mode, saying that +`__clzsi2` can't be found. This is a special builtin function that LLVM attempts +to use when there's no hardware version of an operation it wants to do (in this +case, counting the leading zeros). It's not _actually_ necessary in this case, +which is why you only need it in debug mode. The higher optimization level of +release mode makes LLVM pre-compute more and fold more constants or whatever and +then it stops trying to call `__clzsi2`. + +Unfortunately, sometimes a build will fail with a missing intrinsic even in +release mode. + +If LLVM wants _core_ to have that intrinsic then you're in +trouble, you'll have to send a PR to the +[compiler-builtins](https://github.com/rust-lang-nursery/compiler-builtins) +repository and hope to get it into rust itself. + +If LLVM wants _your code_ to have the intrinsic then you're in less trouble. You +can look up the details and then implement it yourself. It can go anywhere in +your program, as long as it has the right ABI and name. In the case of +`__clzsi2` it takes a `usize` and returns a `usize`, so you'd write something +like: + +```rust +#[no_mangle] +pub extern "C" fn __clzsi2(mut x: usize) -> usize { + // +} +``` + +And so on for whatever other missing intrinsic. From ecc7ea940de8c4e4b98f4bfe747d70eb407da8be Mon Sep 17 00:00:00 2001 From: Lokathor Date: Tue, 25 Dec 2018 14:45:51 -0700 Subject: [PATCH 03/24] palram --- src/io.rs | 2 -- src/lib.rs | 3 +++ src/palram.rs | 69 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 src/palram.rs diff --git a/src/io.rs b/src/io.rs index 4248d94..b744a9b 100644 --- a/src/io.rs +++ b/src/io.rs @@ -8,8 +8,6 @@ use super::*; -use gba_proc_macro::register_bit; - pub mod display; pub mod dma; pub mod keypad; diff --git a/src/lib.rs b/src/lib.rs index 193139f..52116e1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,6 +20,8 @@ //! **Do not** use this crate in programs that aren't running on the GBA. If you //! do, it's a giant bag of Undefined Behavior. +pub(crate) use gba_proc_macro::register_bit; + /// Assists in defining a newtype wrapper over some base type. /// /// Note that rustdoc and derives are all the "meta" stuff, so you can write all @@ -64,6 +66,7 @@ pub mod bios; pub mod io; pub mod mgba; pub mod video; +pub mod palram; newtype! { /// A color on the GBA is an RGB 5.5.5 within a `u16` diff --git a/src/palram.rs b/src/palram.rs new file mode 100644 index 0000000..2296cf0 --- /dev/null +++ b/src/palram.rs @@ -0,0 +1,69 @@ +//! Module that allows interacting with `PALRAM`. +//! +//! The `PALRAM` is 256 `Color` values for Background use, and 256 `Color` +//! values for Object use. +//! +//! Each type of `PALRAM` can be viewed as "8 bits per pixel" (8bpp), where +//! there's a single palette of 256 entries. It can also be viewed as "4 bits +//! per pixel" (4bpp), where there's 16 "palbank" entries that each have 16 +//! slots. **Both** interpretations are correct, simultaneously. If you're a +//! real palette wizard you can carefully arrange for some things to use 4bpp +//! mode while other things use 8bpp mode and have it all look good. +//! +//! ## Transparency +//! +//! In 8bpp mode the 0th palette index is "transparent" when used in an image +//! (giving you 255 usable slots). In 4bpp mode the 0th palbank index _of each +//! palbank_ is considered a transparency pixel (giving you 15 usable slots per +//! palbank). +//! +//! ## Clear Color +//! +//! The 0th palette index of the background palette holds the color that the +//! display will show if no background or object draws over top of a given pixel +//! during rendering. + +use super::{ + base::volatile::{VolAddress, VolAddressBlock}, + Color, +}; + +// TODO: PalIndex newtypes? + +/// The `PALRAM` for background colors, 256 slot view. +pub const PALRAM_BG: VolAddressBlock = unsafe { VolAddressBlock::new_unchecked(VolAddress::new_unchecked(0x500_0000), 256) }; + +/// The `PALRAM` for object colors, 256 slot view. +pub const PALRAM_OBJ: VolAddressBlock = unsafe { VolAddressBlock::new_unchecked(VolAddress::new_unchecked(0x500_0200), 256) }; + +/// Obtains the address of the specified 8bpp background palette slot. +pub fn index_palram_bg_8bpp(slot: u8) -> VolAddress { + // TODO: const this + // Note(Lokathor): because of the `u8` limit we can't go out of bounds here. + unsafe { PALRAM_BG.index_unchecked(slot as usize) } +} + +/// Obtains the address of the specified 8bpp object palette slot. +pub fn index_palram_obj_8bpp(slot: u8) -> VolAddress { + // TODO: const this + // Note(Lokathor): because of the `u8` limit we can't go out of bounds here. + unsafe { PALRAM_OBJ.index_unchecked(slot as usize) } +} + +/// Obtains the address of the specified 4bpp background palbank and slot. +/// +/// If `palbank` or `palslot` are out of bounds they'll wrap around. +pub fn index_palram_bg_4bpp(palbank: u8, palslot: u8) -> VolAddress { + // TODO: const this + // Note(Lokathor): because of the `u8` limit we can't go out of bounds here. + unsafe { PALRAM_BG.index_unchecked(palbank.wrapping_mul(16).wrapping_add(palslot) as usize) } +} + +/// Obtains the address of the specified 4bpp object palbank and slot. +/// +/// If `palbank` or `palslot` are out of bounds they'll wrap around. +pub fn index_palram_obj_4bpp(palbank: u8, palslot: u8) -> VolAddress { + // TODO: const this + // Note(Lokathor): because of the `u8` limit we can't go out of bounds here. + unsafe { PALRAM_OBJ.index_unchecked(palbank.wrapping_mul(16).wrapping_add(palslot) as usize) } +} From 08ff34ae436c91f372602a3d887bf0489f0a7da7 Mon Sep 17 00:00:00 2001 From: Lokathor Date: Tue, 25 Dec 2018 14:46:08 -0700 Subject: [PATCH 04/24] make the BIOS test safe --- src/bios.rs | 395 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 246 insertions(+), 149 deletions(-) diff --git a/src/bios.rs b/src/bios.rs index f24b6f4..4504f22 100644 --- a/src/bios.rs +++ b/src/bios.rs @@ -8,6 +8,8 @@ //! whatever value is necessary for that function). Some functions also perform //! necessary checks to save you from yourself, such as not dividing by zero. +use super::register_bit; + //TODO: ALL functions in this module should have `if cfg!(test)` blocks. The //functions that never return must panic, the functions that return nothing //should just do so, and the math functions should just return the correct math @@ -49,13 +51,17 @@ /// perform UB, but such a scenario might exist. #[inline(always)] pub unsafe fn soft_reset() -> ! { - asm!(/* ASM */ "swi 0x00" - :/* OUT */ // none - :/* INP */ // none - :/* CLO */ // none - :/* OPT */ "volatile" - ); - core::hint::unreachable_unchecked() + if cfg!(test) { + panic!("Attempted soft reset during testing"); + } else { + asm!(/* ASM */ "swi 0x00" + :/* OUT */ // none + :/* INP */ // none + :/* CLO */ // none + :/* OPT */ "volatile" + ); + core::hint::unreachable_unchecked() + } } /// (`swi 0x01`) RegisterRamReset. @@ -84,15 +90,34 @@ pub unsafe fn soft_reset() -> ! { /// memory, except in the case that you were executing out of EWRAM and clear /// that. If you do then you return to nothing and have a bad time. #[inline(always)] -pub unsafe fn register_ram_reset(flags: u8) { - asm!(/* ASM */ "swi 0x01" - :/* OUT */ // none - :/* INP */ "{r0}"(flags) - :/* CLO */ // none - :/* OPT */ "volatile" - ); +pub unsafe fn register_ram_reset(flags: RegisterRAMResetFlags) { + if cfg!(test) { + // do nothing in test mode + } else { + asm!(/* ASM */ "swi 0x01" + :/* OUT */ // none + :/* INP */ "{r0}"(flags.0) + :/* CLO */ // none + :/* OPT */ "volatile" + ); + } +} +newtype! { + /// Flags for use with `register_ram_reset`. + #[derive(Debug, Copy, Clone, Default, PartialEq, Eq)] + RegisterRAMResetFlags, u8 +} +#[allow(missing_docs)] +impl RegisterRAMResetFlags { + register_bit!(EWRAM, u8, 0, ewram); + register_bit!(IWRAM, u8, 1 << 1, iwram); + register_bit!(PALRAM, u8, 1 << 2, palram); + register_bit!(VRAM, u8, 1 << 3, vram); + register_bit!(OAM, u8, 1 << 4, oam); + register_bit!(SIO, u8, 1 << 5, sio); + register_bit!(SOUND, u8, 1 << 6, sound); + register_bit!(OTHER_IO, u8, 1 << 7, other_io); } -//TODO(lokathor): newtype this flag business. /// (`swi 0x02`) Halts the CPU until an interrupt occurs. /// @@ -100,13 +125,17 @@ pub unsafe fn register_ram_reset(flags: u8) { /// any enabled interrupt triggers. #[inline(always)] pub fn halt() { - unsafe { - asm!(/* ASM */ "swi 0x02" - :/* OUT */ // none - :/* INP */ // none - :/* CLO */ // none - :/* OPT */ "volatile" - ); + if cfg!(test) { + // do nothing in test mode + } else { + unsafe { + asm!(/* ASM */ "swi 0x02" + :/* OUT */ // none + :/* INP */ // none + :/* CLO */ // none + :/* OPT */ "volatile" + ); + } } } @@ -120,13 +149,17 @@ pub fn halt() { /// optional externals such as rumble and infra-red. #[inline(always)] pub fn stop() { - unsafe { - asm!(/* ASM */ "swi 0x03" - :/* OUT */ // none - :/* INP */ // none - :/* CLO */ // none - :/* OPT */ "volatile" - ); + if cfg!(test) { + // do nothing in test mode + } else { + unsafe { + asm!(/* ASM */ "swi 0x03" + :/* OUT */ // none + :/* INP */ // none + :/* CLO */ // none + :/* OPT */ "volatile" + ); + } } } @@ -145,13 +178,17 @@ pub fn stop() { /// acknowledgement. #[inline(always)] pub fn interrupt_wait(ignore_current_flags: bool, target_flags: u16) { - unsafe { - asm!(/* ASM */ "swi 0x04" - :/* OUT */ // none - :/* INP */ "{r0}"(ignore_current_flags), "{r1}"(target_flags) - :/* CLO */ // none - :/* OPT */ "volatile" - ); + if cfg!(test) { + // do nothing in test mode + } else { + unsafe { + asm!(/* ASM */ "swi 0x04" + :/* OUT */ // none + :/* INP */ "{r0}"(ignore_current_flags), "{r1}"(target_flags) + :/* CLO */ // none + :/* OPT */ "volatile" + ); + } } } //TODO(lokathor): newtype this flag business. @@ -162,13 +199,17 @@ pub fn interrupt_wait(ignore_current_flags: bool, target_flags: u16) { /// must follow the same guidelines that `interrupt_wait` outlines. #[inline(always)] pub fn vblank_interrupt_wait() { - unsafe { - asm!(/* ASM */ "swi 0x04" - :/* OUT */ // none - :/* INP */ // none - :/* CLO */ "r0", "r1" // both set to 1 by the routine - :/* OPT */ "volatile" - ); + if cfg!(test) { + // do nothing in test mode + } else { + unsafe { + asm!(/* ASM */ "swi 0x04" + :/* OUT */ // none + :/* INP */ // none + :/* CLO */ "r0", "r1" // both set to 1 by the routine + :/* OPT */ "volatile" + ); + } } } @@ -219,16 +260,20 @@ pub fn rem(numerator: i32, denominator: i32) -> i32 { /// by `2n` bits to get `n` more bits of fractional precision in your output. #[inline(always)] pub fn sqrt(val: u32) -> u16 { - let out: u16; - unsafe { - asm!(/* ASM */ "swi 0x08" - :/* OUT */ "={r0}"(out) - :/* INP */ "{r0}"(val) - :/* CLO */ "r1", "r3" - :/* OPT */ - ); + if cfg!(test) { + 0 // TODO: simulate this properly during testing builds. + } else { + let out: u16; + unsafe { + asm!(/* ASM */ "swi 0x08" + :/* OUT */ "={r0}"(out) + :/* INP */ "{r0}"(val) + :/* CLO */ "r1", "r3" + :/* OPT */ + ); + } + out } - out } /// (`swi 0x09`) Gives the arctangent of `theta`. @@ -239,16 +284,20 @@ pub fn sqrt(val: u32) -> u16 { /// Accuracy suffers if `theta` is less than `-pi/4` or greater than `pi/4`. #[inline(always)] pub fn atan(theta: i16) -> i16 { - let out: i16; - unsafe { - asm!(/* ASM */ "swi 0x09" - :/* OUT */ "={r0}"(out) - :/* INP */ "{r0}"(theta) - :/* CLO */ "r1", "r3" - :/* OPT */ - ); + if cfg!(test) { + 0 // TODO: simulate this properly during testing builds. + } else { + let out: i16; + unsafe { + asm!(/* ASM */ "swi 0x09" + :/* OUT */ "={r0}"(out) + :/* INP */ "{r0}"(theta) + :/* CLO */ "r1", "r3" + :/* OPT */ + ); + } + out } - out } /// (`swi 0x0A`) Gives the atan2 of `y` over `x`. @@ -260,16 +309,20 @@ pub fn atan(theta: i16) -> i16 { /// integral, 14 bits for fractional. #[inline(always)] pub fn atan2(y: i16, x: i16) -> u16 { - let out: u16; - unsafe { - asm!(/* ASM */ "swi 0x0A" - :/* OUT */ "={r0}"(out) - :/* INP */ "{r0}"(x), "{r1}"(y) - :/* CLO */ "r3" - :/* OPT */ - ); + if cfg!(test) { + 0 // TODO: simulate this properly during testing builds. + } else { + let out: u16; + unsafe { + asm!(/* ASM */ "swi 0x0A" + :/* OUT */ "={r0}"(out) + :/* INP */ "{r0}"(x), "{r1}"(y) + :/* CLO */ "r3" + :/* OPT */ + ); + } + out } - out } /// (`swi 0x0B`) "CpuSet", `u16` memory copy. @@ -283,13 +336,17 @@ pub fn atan2(y: i16, x: i16) -> u16 { /// * Both pointers must be aligned #[inline(always)] pub unsafe fn cpu_set16(src: *const u16, dest: *mut u16, count: u32, fixed_source: bool) { - let control = count + ((fixed_source as u32) << 24); - asm!(/* ASM */ "swi 0x0B" - :/* OUT */ // none - :/* INP */ "{r0}"(src), "{r1}"(dest), "{r2}"(control) - :/* CLO */ // none - :/* OPT */ "volatile" - ); + if cfg!(test) { + // do nothing in test mode + } else { + let control = count + ((fixed_source as u32) << 24); + asm!(/* ASM */ "swi 0x0B" + :/* OUT */ // none + :/* INP */ "{r0}"(src), "{r1}"(dest), "{r2}"(control) + :/* CLO */ // none + :/* OPT */ "volatile" + ); + } } /// (`swi 0x0B`) "CpuSet", `u32` memory copy/fill. @@ -303,13 +360,17 @@ pub unsafe fn cpu_set16(src: *const u16, dest: *mut u16, count: u32, fixed_sourc /// * Both pointers must be aligned #[inline(always)] pub unsafe fn cpu_set32(src: *const u32, dest: *mut u32, count: u32, fixed_source: bool) { - let control = count + ((fixed_source as u32) << 24) + (1 << 26); - asm!(/* ASM */ "swi 0x0B" - :/* OUT */ // none - :/* INP */ "{r0}"(src), "{r1}"(dest), "{r2}"(control) - :/* CLO */ // none - :/* OPT */ "volatile" - ); + if cfg!(test) { + // do nothing in test mode + } else { + let control = count + ((fixed_source as u32) << 24) + (1 << 26); + asm!(/* ASM */ "swi 0x0B" + :/* OUT */ // none + :/* INP */ "{r0}"(src), "{r1}"(dest), "{r2}"(control) + :/* CLO */ // none + :/* OPT */ "volatile" + ); + } } /// (`swi 0x0C`) "CpuFastSet", copies memory in 32 byte chunks. @@ -324,13 +385,17 @@ pub unsafe fn cpu_set32(src: *const u32, dest: *mut u32, count: u32, fixed_sourc /// * Both pointers must be aligned #[inline(always)] pub unsafe fn cpu_fast_set(src: *const u32, dest: *mut u32, count: u32, fixed_source: bool) { - let control = count + ((fixed_source as u32) << 24); - asm!(/* ASM */ "swi 0x0C" - :/* OUT */ // none - :/* INP */ "{r0}"(src), "{r1}"(dest), "{r2}"(control) - :/* CLO */ // none - :/* OPT */ "volatile" - ); + if cfg!(test) { + // do nothing in test mode + } else { + let control = count + ((fixed_source as u32) << 24); + asm!(/* ASM */ "swi 0x0C" + :/* OUT */ // none + :/* INP */ "{r0}"(src), "{r1}"(dest), "{r2}"(control) + :/* CLO */ // none + :/* OPT */ "volatile" + ); + } } /// (`swi 0x0C`) "GetBiosChecksum" (Undocumented) @@ -343,16 +408,20 @@ pub unsafe fn cpu_fast_set(src: *const u32, dest: *mut u32, count: u32, fixed_so /// some other value I guess you're probably running on an emulator that just /// broke the fourth wall. pub fn get_bios_checksum() -> u32 { - let out: u32; - unsafe { - asm!(/* ASM */ "swi 0x0D" - :/* OUT */ "={r0}"(out) - :/* INP */ // none - :/* CLO */ // none - :/* OPT */ // none - ); + if cfg!(test) { + 0 + } else { + let out: u32; + unsafe { + asm!(/* ASM */ "swi 0x0D" + :/* OUT */ "={r0}"(out) + :/* INP */ // none + :/* CLO */ // none + :/* OPT */ // none + ); + } + out } - out } // TODO: these things will require that we build special structs @@ -376,13 +445,17 @@ pub fn get_bios_checksum() -> u32 { /// /// The final sound level setting will be `level` * `0x200`. pub fn sound_bias(level: u32) { - unsafe { - asm!(/* ASM */ "swi 0x19" - :/* OUT */ // none - :/* INP */ "{r0}"(level) - :/* CLO */ // none - :/* OPT */ "volatile" - ); + if cfg!(test) { + // do nothing in test mode + } else { + unsafe { + asm!(/* ASM */ "swi 0x19" + :/* OUT */ // none + :/* INP */ "{r0}"(level) + :/* CLO */ // none + :/* OPT */ "volatile" + ); + } } } @@ -414,13 +487,17 @@ pub fn sound_bias(level: u32) { /// * 10: 40137 /// * 11: 42048 pub fn sound_driver_mode(mode: u32) { - unsafe { - asm!(/* ASM */ "swi 0x1B" - :/* OUT */ // none - :/* INP */ "{r0}"(mode) - :/* CLO */ // none - :/* OPT */ "volatile" - ); + if cfg!(test) { + // do nothing in test mode + } else { + unsafe { + asm!(/* ASM */ "swi 0x1B" + :/* OUT */ // none + :/* INP */ "{r0}"(mode) + :/* CLO */ // none + :/* OPT */ "volatile" + ); + } } } //TODO(lokathor): newtype this mode business. @@ -434,13 +511,17 @@ pub fn sound_driver_mode(mode: u32) { /// executed." --what? #[inline(always)] pub fn sound_driver_main() { - unsafe { - asm!(/* ASM */ "swi 0x1C" - :/* OUT */ // none - :/* INP */ // none - :/* CLO */ // none - :/* OPT */ "volatile" - ); + if cfg!(test) { + // do nothing in test mode + } else { + unsafe { + asm!(/* ASM */ "swi 0x1C" + :/* OUT */ // none + :/* INP */ // none + :/* CLO */ // none + :/* OPT */ "volatile" + ); + } } } @@ -450,13 +531,17 @@ pub fn sound_driver_main() { /// vblank interrupt (every 1/60th of a second). #[inline(always)] pub fn sound_driver_vsync() { - unsafe { - asm!(/* ASM */ "swi 0x1D" - :/* OUT */ // none - :/* INP */ // none - :/* CLO */ // none - :/* OPT */ "volatile" - ); + if cfg!(test) { + // do nothing in test mode + } else { + unsafe { + asm!(/* ASM */ "swi 0x1D" + :/* OUT */ // none + :/* INP */ // none + :/* CLO */ // none + :/* OPT */ "volatile" + ); + } } } @@ -468,13 +553,17 @@ pub fn sound_driver_vsync() { /// --what? #[inline(always)] pub fn sound_channel_clear() { - unsafe { - asm!(/* ASM */ "swi 0x1E" - :/* OUT */ // none - :/* INP */ // none - :/* CLO */ // none - :/* OPT */ "volatile" - ); + if cfg!(test) { + // do nothing in test mode + } else { + unsafe { + asm!(/* ASM */ "swi 0x1E" + :/* OUT */ // none + :/* INP */ // none + :/* CLO */ // none + :/* OPT */ "volatile" + ); + } } } @@ -489,13 +578,17 @@ pub fn sound_channel_clear() { /// noise. #[inline(always)] pub fn sound_driver_vsync_off() { - unsafe { - asm!(/* ASM */ "swi 0x28" - :/* OUT */ // none - :/* INP */ // none - :/* CLO */ // none - :/* OPT */ "volatile" - ); + if cfg!(test) { + // do nothing in test mode + } else { + unsafe { + asm!(/* ASM */ "swi 0x28" + :/* OUT */ // none + :/* INP */ // none + :/* CLO */ // none + :/* OPT */ "volatile" + ); + } } } @@ -506,12 +599,16 @@ pub fn sound_driver_vsync_off() { /// interrupt followed by a `sound_driver_vsync` within 2/60th of a second. #[inline(always)] pub fn sound_driver_vsync_on() { - unsafe { - asm!(/* ASM */ "swi 0x29" - :/* OUT */ // none - :/* INP */ // none - :/* CLO */ // none - :/* OPT */ "volatile" - ); + if cfg!(test) { + // do nothing in test mode + } else { + unsafe { + asm!(/* ASM */ "swi 0x29" + :/* OUT */ // none + :/* INP */ // none + :/* CLO */ // none + :/* OPT */ "volatile" + ); + } } } From ae408931f554491dd87d5f8f0899d12768f8c87c Mon Sep 17 00:00:00 2001 From: Lokathor Date: Tue, 25 Dec 2018 14:46:34 -0700 Subject: [PATCH 05/24] make this look more normal --- src/io/display.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/io/display.rs b/src/io/display.rs index ce7cda6..1711f43 100644 --- a/src/io/display.rs +++ b/src/io/display.rs @@ -10,8 +10,7 @@ pub const DISPCNT: VolAddress = unsafe { VolAddress::new_ newtype!( /// A newtype over the various display control options that you have on a GBA. #[derive(Debug, Copy, Clone, Default, PartialEq, Eq)] - DisplayControlSetting, - u16 + DisplayControlSetting, u16 ); #[allow(missing_docs)] From cf19303a406a0380e4b756b60cbc1fd9677fb63f Mon Sep 17 00:00:00 2001 From: Lokathor Date: Tue, 25 Dec 2018 14:46:54 -0700 Subject: [PATCH 06/24] better looking literals --- book/src/02-concepts/05-palram.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/book/src/02-concepts/05-palram.md b/book/src/02-concepts/05-palram.md index 95cbdf1..0bc7e7f 100644 --- a/book/src/02-concepts/05-palram.md +++ b/book/src/02-concepts/05-palram.md @@ -16,8 +16,8 @@ the larger 16-bit location. This doesn't really affect us much with PALRAM, because palette values are all supposed to be `u16` anyway. The palette memory actually contains not one, but _two_ sets of palettes. First -there's 256 entries for the background palette data (starting at `0x5000000`), -and then there's 256 entries for object palette data (starting at `0x5000200`). +there's 256 entries for the background palette data (starting at `0x500_0000`), +and then there's 256 entries for object palette data (starting at `0x500_0200`). The GBA also has two modes for palette access: 8-bits-per-pixel (8bpp) and 4-bits-per-pixel (4bpp). From cdd330f22d5e8e9f34e0ffe5d680323b94ba18b9 Mon Sep 17 00:00:00 2001 From: Lokathor Date: Tue, 25 Dec 2018 14:47:26 -0700 Subject: [PATCH 07/24] start of tiled data types --- src/video/tiled.rs | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 src/video/tiled.rs diff --git a/src/video/tiled.rs b/src/video/tiled.rs new file mode 100644 index 0000000..a2f5d1e --- /dev/null +++ b/src/video/tiled.rs @@ -0,0 +1,41 @@ +//! Module for tiled mode types and operations. + +use super::*; + +/// An 8x8 tile with 4bpp +#[derive(Debug, Clone, Copy, Default)] +#[repr(transparent)] +pub struct Tile4bpp { + pub data: [u32; 8], +} + +/// An 8x8 tile with 8bpp +#[derive(Debug, Clone, Copy, Default)] +#[repr(transparent)] +pub struct Tile8bpp { + pub data: [u32; 16], +} + +/// A charblock of 4bpp tiles +#[derive(Clone, Copy)] +#[repr(transparent)] +pub struct Charblock4bpp { + pub data: [Tile4bpp; 512], +} + +/// A charblock of 8bpp tiles +#[derive(Clone, Copy)] +#[repr(transparent)] +pub struct Charblock8bpp { + pub data: [Tile8bpp; 256], +} + +#[derive(Debug, Clone, Copy, Default)] +#[repr(transparent)] +pub struct TextScreenblockEntry(u16); + +#[derive(Clone, Copy)] +#[repr(transparent)] +pub struct TextScreenblock { + pub data: [TextScreenblockEntry; 32 * 32], +} From 1a19831373e2174de7217e56717e0749fde0c2a3 Mon Sep 17 00:00:00 2001 From: Lokathor Date: Tue, 25 Dec 2018 16:20:51 -0700 Subject: [PATCH 08/24] Page1 always starts at +0xA000 --- src/video/bitmap.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/video/bitmap.rs b/src/video/bitmap.rs index 7736226..a716d9c 100644 --- a/src/video/bitmap.rs +++ b/src/video/bitmap.rs @@ -107,7 +107,7 @@ impl Mode4 { const PAGE0_BLOCK: VolAddressBlock = unsafe { VolAddressBlock::new_unchecked(Self::PAGE0_BASE, Self::SCREEN_PIXEL_COUNT) }; // TODO: newtype this? - const PAGE1_BASE: VolAddress = unsafe { VolAddress::new_unchecked(VRAM_BASE_USIZE + 0x9600) }; + const PAGE1_BASE: VolAddress = unsafe { VolAddress::new_unchecked(VRAM_BASE_USIZE + 0xA000) }; // TODO: newtype this? const PAGE1_BLOCK: VolAddressBlock = unsafe { VolAddressBlock::new_unchecked(Self::PAGE1_BASE, Self::SCREEN_PIXEL_COUNT) }; @@ -204,7 +204,7 @@ impl Mode4 { let write_target = if page1 { VRAM_BASE_USIZE as *mut u32 } else { - (VRAM_BASE_USIZE + 0x9600) as *mut u32 + (VRAM_BASE_USIZE + 0xA000) as *mut u32 }; unsafe { DMA3::fill32(&bulk_color, write_target, Self::SCREEN_U32_COUNT as u16) }; } From ce3ddd3bb0d6c5cc8549a9cfcc1f8a295b5d6ca0 Mon Sep 17 00:00:00 2001 From: Lokathor Date: Tue, 25 Dec 2018 16:45:10 -0700 Subject: [PATCH 09/24] Improving newtype --- book/src/01-quirks/04-newtype.md | 103 +++++++++++++++++++++++-------- src/lib.rs | 10 +-- 2 files changed, 81 insertions(+), 32 deletions(-) diff --git a/book/src/01-quirks/04-newtype.md b/book/src/01-quirks/04-newtype.md index f1c4be8..94117ec 100644 --- a/book/src/01-quirks/04-newtype.md +++ b/book/src/01-quirks/04-newtype.md @@ -96,6 +96,11 @@ style, but there are some rules and considerations here: * Parentheses macro use mostly gets treated like a function call. * Bracket macro use mostly gets treated like an array declaration. +**As a reminder:** remember that `macro_rules` macros have to appear _before_ +they're invoked in your source, so the `newtype` macro will always have to be at +the very top of your file, or if you put it in a module within your project +you'll need to declare the module before anything that uses it. + ## Upgrade That Macro! We also want to be able to add `derive` stuff and doc comments to our newtype. @@ -124,34 +129,78 @@ newtype! { } ``` -And that's about all we'll need for the examples. +Next, we can allow for the wrapping of types that aren't just a single +identifier by changing `$old_name` from `:ident` to `:ty`. We can't _also_ do +this for the `$new_type` part because declaring a new struct expects a valid +identifier that's _not_ already declared (obviously), and `:ty` is intended for +capturing types that already exist. -**As a reminder:** remember that `macro_rules` macros have to appear _before_ -they're invoked in your source, so the `newtype` macro will always have to be at -the very top of your file, or if you put it in a module within your project -you'll need to declare the module before anything that uses it. +```rust +#[macro_export] +macro_rules! newtype { + ($(#[$attr:meta])* $new_name:ident, $old_name:ty) => { + $(#[$attr])* + #[repr(transparent)] + pub struct $new_name($old_name); + }; +} +``` -## Potential Homework +Next of course we'll want to usually have a `new` method that's const and just +gives a 0 value. We won't always be making a newtype over a number value, but we +often will. It's usually silly to have a `new` method with no arguments since we +might as well just impl `Default`, but `Default::default` isn't `const`, so +having `pub const fn new() -> Self` is justified here. -If you wanted to keep going and get really fancy with it, you could potentially -add a lot more: +Here, the token `0` is given the `{integer}` type, which can be converted into +any of the integer types as needed, but it still can't be converted into an +array type or a pointer or things like that. Accordingly we've added the "no +frills" option which declares the struct and no `new` method. -* Make a `pub const fn new() -> Self` method that outputs the base value in a - const way. Combine this with builder style "setter" methods that are also - const and you can get the compiler to do quite a bit of the value building - work at compile time. -* Making the macro optionally emit a `From` impl to unwrap it back into the base - type. -* Allow for visibility modifiers to be applied to the inner field and the newly - generated type. -* Allowing for generic newtypes. You already saw the need for this once in the - volatile section. Unfortunately, this particular part gets really tricky if - you're using `macro_rules!`, so you might need to move up to a full - `proc_macro`. Having a `proc_macro` isn't bad except that they have to be - defined in a crate of their own and they're compiled before use. You can't - ever use them in the crate that defines them, so we won't be using them in any - of our single file examples. -* Allowing for optional `Deref` and `DerefMut` of the inner value. This takes - away most all the safety aspect of doing the newtype, but there may be times - for it. As an example, you could make a newtype with a different form of - Display impl that you want to otherwise treat as the base type in all places. +```rust +#[macro_export] +macro_rules! newtype { + ($(#[$attr:meta])* $new_name:ident, $old_name:ty) => { + $(#[$attr])* + #[repr(transparent)] + pub struct $new_name($old_name); + impl $new_name { + /// A `const` "zero value" constructor + pub const fn new() -> Self { + $new_name(0) + } + } + }; + ($(#[$attr:meta])* $new_name:ident, $old_name:ty, no frills) => { + $(#[$attr])* + #[repr(transparent)] + pub struct $new_name($old_name); + }; +} +``` + +Finally, we usually want to have the wrapped value be totally private, but there +_are_ occasions where that's not the case. For this, we can allow the wrapped +field to accept a visibility modifier. + +```rust +#[macro_export] +macro_rules! newtype { + ($(#[$attr:meta])* $new_name:ident, $v:vis $old_name:ty) => { + $(#[$attr])* + #[repr(transparent)] + pub struct $new_name($v $old_name); + impl $new_name { + /// A `const` "zero value" constructor + pub const fn new() -> Self { + $new_name(0) + } + } + }; + ($(#[$attr:meta])* $new_name:ident, $v:vis $old_name:ty, no frills) => { + $(#[$attr])* + #[repr(transparent)] + pub struct $new_name($v $old_name); + }; +} +``` diff --git a/src/lib.rs b/src/lib.rs index 52116e1..6b07a04 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -42,10 +42,10 @@ pub(crate) use gba_proc_macro::register_bit; /// ``` #[macro_export] macro_rules! newtype { - ($(#[$attr:meta])* $new_name:ident, $old_name:ident) => { + ($(#[$attr:meta])* $new_name:ident, $v:vis $old_name:ty) => { $(#[$attr])* #[repr(transparent)] - pub struct $new_name($old_name); + pub struct $new_name($v $old_name); impl $new_name { /// A `const` "zero value" constructor pub const fn new() -> Self { @@ -53,10 +53,10 @@ macro_rules! newtype { } } }; - ($(#[$attr:meta])* $new_name:ident, $old_name:ident, no frills) => { + ($(#[$attr:meta])* $new_name:ident, $v:vis $old_name:ty, no frills) => { $(#[$attr])* #[repr(transparent)] - pub struct $new_name($old_name); + pub struct $new_name($v $old_name); }; } @@ -65,8 +65,8 @@ pub(crate) use self::base::*; pub mod bios; pub mod io; pub mod mgba; -pub mod video; pub mod palram; +pub mod video; newtype! { /// A color on the GBA is an RGB 5.5.5 within a `u16` From 89def7331beeeb8844e287d17a910dc5fd9c733d Mon Sep 17 00:00:00 2001 From: Lokathor Date: Tue, 25 Dec 2018 16:45:26 -0700 Subject: [PATCH 10/24] Quick start of a background module --- src/io.rs | 1 + src/io/background.rs | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) create mode 100644 src/io/background.rs diff --git a/src/io.rs b/src/io.rs index b744a9b..86ab5b6 100644 --- a/src/io.rs +++ b/src/io.rs @@ -11,3 +11,4 @@ use super::*; pub mod display; pub mod dma; pub mod keypad; +pub mod background; diff --git a/src/io/background.rs b/src/io/background.rs new file mode 100644 index 0000000..926ce06 --- /dev/null +++ b/src/io/background.rs @@ -0,0 +1,17 @@ +//! Module for Background controls + +use super::*; + +newtype! { + BackgroundControlSetting, u16 +} +impl BackgroundControlSetting { + pub const fn from_screen_base_block(screen_base_block: u16) -> Self { + BackgroundControlSetting(screen_base_block << 8) // TODO: mask this for correctness + } +} + +pub struct BG0; +impl BG0 { + pub const BG0CNT: VolAddress = unsafe { VolAddress::new_unchecked(0x400_0008) }; +} From 0a6a089e70205d8fec6d44f9f4a4e064b2e6e775 Mon Sep 17 00:00:00 2001 From: Lokathor Date: Tue, 25 Dec 2018 16:45:40 -0700 Subject: [PATCH 11/24] docs fixes --- src/palram.rs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/palram.rs b/src/palram.rs index 2296cf0..e5b251a 100644 --- a/src/palram.rs +++ b/src/palram.rs @@ -1,9 +1,9 @@ -//! Module that allows interacting with `PALRAM`. +//! Module that allows interacting with palette memory, (`PALRAM`). //! -//! The `PALRAM` is 256 `Color` values for Background use, and 256 `Color` +//! The `PALRAM` contains 256 `Color` values for Background use, and 256 `Color` //! values for Object use. //! -//! Each type of `PALRAM` can be viewed as "8 bits per pixel" (8bpp), where +//! Each block of `PALRAM` can be viewed as "8 bits per pixel" (8bpp), where //! there's a single palette of 256 entries. It can also be viewed as "4 bits //! per pixel" (4bpp), where there's 16 "palbank" entries that each have 16 //! slots. **Both** interpretations are correct, simultaneously. If you're a @@ -50,18 +50,20 @@ pub fn index_palram_obj_8bpp(slot: u8) -> VolAddress { unsafe { PALRAM_OBJ.index_unchecked(slot as usize) } } -/// Obtains the address of the specified 4bpp background palbank and slot. +/// Obtains the address of the specified 4bpp background palbank and palslot. /// -/// If `palbank` or `palslot` are out of bounds they'll wrap around. +/// Accesses `palbank * 16 + palslot`, if this is out of bounds the computation +/// will wrap. pub fn index_palram_bg_4bpp(palbank: u8, palslot: u8) -> VolAddress { // TODO: const this // Note(Lokathor): because of the `u8` limit we can't go out of bounds here. unsafe { PALRAM_BG.index_unchecked(palbank.wrapping_mul(16).wrapping_add(palslot) as usize) } } -/// Obtains the address of the specified 4bpp object palbank and slot. +/// Obtains the address of the specified 4bpp object palbank and palslot. /// -/// If `palbank` or `palslot` are out of bounds they'll wrap around. +/// Accesses `palbank * 16 + palslot`, if this is out of bounds the computation +/// will wrap. pub fn index_palram_obj_4bpp(palbank: u8, palslot: u8) -> VolAddress { // TODO: const this // Note(Lokathor): because of the `u8` limit we can't go out of bounds here. From c440be746766d16cdac552d73f0cc823b0bc4111 Mon Sep 17 00:00:00 2001 From: Lokathor Date: Tue, 25 Dec 2018 16:45:51 -0700 Subject: [PATCH 12/24] reduce the use visibility --- src/video.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/video.rs b/src/video.rs index df13684..54c52f1 100644 --- a/src/video.rs +++ b/src/video.rs @@ -13,7 +13,7 @@ //! they won't bother to check that you've set the video mode they're designed //! for. -pub use super::*; +pub(crate) use super::*; pub mod bitmap; pub mod tiled; From 0f1fa8269f6f1ffd7dd2d425ac93f5b8235fb6c0 Mon Sep 17 00:00:00 2001 From: Lokathor Date: Tue, 25 Dec 2018 16:46:01 -0700 Subject: [PATCH 13/24] More tiled data types --- src/video/tiled.rs | 118 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 90 insertions(+), 28 deletions(-) diff --git a/src/video/tiled.rs b/src/video/tiled.rs index a2f5d1e..9c10009 100644 --- a/src/video/tiled.rs +++ b/src/video/tiled.rs @@ -2,40 +2,102 @@ use super::*; -/// An 8x8 tile with 4bpp -#[derive(Debug, Clone, Copy, Default)] -#[repr(transparent)] -pub struct Tile4bpp { - pub data: [u32; 8], +// Note(Lokathor): We've got several newtypes here that don't use the `newtype!` +// macro because it's insufficient at parsing array types being wrapped. + +newtype! { + /// An 8x8 tile with 4bpp, packed as `u32` values for proper alignment. + #[derive(Debug, Clone, Copy, Default)] + Tile4bpp, pub [u32; 8], no frills } -/// An 8x8 tile with 8bpp -#[derive(Debug, Clone, Copy, Default)] -#[repr(transparent)] -pub struct Tile8bpp { - pub data: [u32; 16], +newtype! { + /// An 8x8 tile with 8bpp, packed as `u32` values for proper alignment. + #[derive(Debug, Clone, Copy, Default)] + Tile8bpp, pub [u32; 16], no frills } -/// A charblock of 4bpp tiles -#[derive(Clone, Copy)] -#[repr(transparent)] -pub struct Charblock4bpp { - pub data: [Tile4bpp; 512], +newtype! { + /// A 4bpp charblock has 512 tiles in it + #[derive(Clone, Copy)] + Charblock4bpp, pub [Tile4bpp; 512], no frills } -/// A charblock of 8bpp tiles -#[derive(Clone, Copy)] -#[repr(transparent)] -pub struct Charblock8bpp { - pub data: [Tile8bpp; 256], +newtype! { + /// An 8bpp charblock has 256 tiles in it + #[derive(Clone, Copy)] + Charblock8bpp, pub [Tile4bpp; 256], no frills } -#[derive(Debug, Clone, Copy, Default)] -#[repr(transparent)] -pub struct TextScreenblockEntry(u16); - -#[derive(Clone, Copy)] -#[repr(transparent)] -pub struct TextScreenblock { - pub data: [TextScreenblockEntry; 32 * 32], +newtype! { + /// A screenblock entry for use in Text mode. + #[derive(Debug, Clone, Copy, Default)] + TextScreenblockEntry, u16 } +impl TextScreenblockEntry { + pub const fn from_tile_index(index: u16) -> Self { + TextScreenblockEntry(index & Self::TILE_INDEX_MASK) + } + + // + + pub const TILE_INDEX_MASK: u16 = 0xA - 1; + pub const fn tile_index(self) -> u16 { + self.0 & Self::TILE_INDEX_MASK + } + pub const fn with_tile_index(self, index: u16) -> Self { + TextScreenblockEntry((self.0 & !Self::TILE_INDEX_MASK) | (index & Self::TILE_INDEX_MASK)) + } + + register_bit!(HFLIP_BIT, u16, 1 << 0xA, hflip); + register_bit!(VFLIP_BIT, u16, 1 << 0xB, vflip); + + pub const PALBANK_MASK: u16 = 0b1111 << 0xC; + pub const fn palbank(self) -> u16 { + (self.0 & Self::TILE_INDEX_MASK) >> 0xC + } + pub const fn with_palbank(self, palbank: u16) -> Self { + TextScreenblockEntry((self.0 & !Self::PALBANK_MASK) | (palbank << 0xC)) + } +} + +newtype! { + /// A screenblock for use in Text mode. + #[derive(Clone, Copy)] + TextScreenblock, [TextScreenblockEntry; 32 * 32], no frills +} + +newtype! { + /// A screenblock entry for use in Affine mode. + #[derive(Debug, Clone, Copy, Default)] + AffineScreenblockEntry, u8 +} + +newtype! { + /// A screenblock for use in Affine mode. + #[derive(Clone, Copy)] + AffineScreenblock_16x16, [AffineScreenblockEntry; 16*16], no frills +} + +newtype! { + /// A screenblock for use in Affine mode. + #[derive(Clone, Copy)] + AffineScreenblock_32x32, [AffineScreenblockEntry; 32*32], no frills +} + +newtype! { + /// A screenblock for use in Affine mode. + #[derive(Clone, Copy)] + AffineScreenblock_64x64, [AffineScreenblockEntry; 64*64], no frills +} + +newtype! { + /// A screenblock for use in Affine mode. + #[derive(Clone, Copy)] + AffineScreenblock_128x128, [AffineScreenblockEntry; 128*128], no frills +} + +pub const VRAM_CHARBLOCKS: VolAddressBlock = unsafe { VolAddressBlock::new_unchecked(VolAddress::new_unchecked(VRAM_BASE_USIZE), 6) }; + +pub const VRAM_TEXT_SCREENBLOCKS: VolAddressBlock = + unsafe { VolAddressBlock::new_unchecked(VolAddress::new_unchecked(VRAM_BASE_USIZE), 32) }; From 298d45ccfbea592b0b3eb5ff690119eb48100e70 Mon Sep 17 00:00:00 2001 From: Lokathor Date: Tue, 25 Dec 2018 16:46:11 -0700 Subject: [PATCH 14/24] The BG checker demo runs now! --- examples/bg_demo.rs | 150 ++++++++++---------------------------------- 1 file changed, 33 insertions(+), 117 deletions(-) diff --git a/examples/bg_demo.rs b/examples/bg_demo.rs index 3fa219f..553a15f 100644 --- a/examples/bg_demo.rs +++ b/examples/bg_demo.rs @@ -1,6 +1,16 @@ #![no_std] #![feature(start)] +use gba::{ + io::{ + background::{BackgroundControlSetting, BG0}, + display::{DisplayControlSetting, DISPCNT}, + }, + palram::index_palram_bg_4bpp, + video::tiled::{TextScreenblockEntry, Tile4bpp, VRAM_CHARBLOCKS, VRAM_TEXT_SCREENBLOCKS}, + Color, +}; + #[panic_handler] fn panic(_info: &core::panic::PanicInfo) -> ! { loop {} @@ -8,148 +18,54 @@ fn panic(_info: &core::panic::PanicInfo) -> ! { #[start] fn main(_argc: isize, _argv: *const *const u8) -> isize { + pub const WHITE: Color = Color::from_rgb(31, 31, 31); + pub const LIGHT_GRAY: Color = Color::from_rgb(25, 25, 25); + pub const DARK_GRAY: Color = Color::from_rgb(15, 15, 15); // bg palette - set_bg_palette_4bpp(0, 1, WHITE); - set_bg_palette_4bpp(0, 2, LIGHT_GRAY); - set_bg_palette_4bpp(0, 3, DARK_GRAY); + index_palram_bg_4bpp(0, 1).write(WHITE); + index_palram_bg_4bpp(0, 2).write(LIGHT_GRAY); + index_palram_bg_4bpp(0, 3).write(DARK_GRAY); // bg tiles set_bg_tile_4bpp(0, 0, ALL_TWOS); set_bg_tile_4bpp(0, 1, ALL_THREES); // screenblock - let light_entry = RegularScreenblockEntry::from_tile_id(0); - let dark_entry = RegularScreenblockEntry::from_tile_id(1); + let light_entry = TextScreenblockEntry::from_tile_index(0); + let dark_entry = TextScreenblockEntry::from_tile_index(1); checker_screenblock(8, light_entry, dark_entry); // bg0 control - unsafe { BG0CNT.write(BackgroundControlSetting::from_base_block(8)) }; + BG0::BG0CNT.write(BackgroundControlSetting::from_screen_base_block(8)); // Display Control - unsafe { DISPCNT.write(DisplayControlSetting::JUST_ENABLE_BG0) }; + DISPCNT.write(DisplayControlSetting::new().with_display_bg0(true)); loop { // TODO the whole thing } } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[repr(transparent)] -pub struct VolatilePtr(pub *mut T); -impl VolatilePtr { - pub unsafe fn read(&self) -> T { - core::ptr::read_volatile(self.0) - } - pub unsafe fn write(&self, data: T) { - core::ptr::write_volatile(self.0, data); - } - pub fn offset(self, count: isize) -> Self { - VolatilePtr(self.0.wrapping_offset(count)) - } - pub fn cast(self) -> VolatilePtr { - VolatilePtr(self.0 as *mut Z) - } -} +pub const ALL_TWOS: Tile4bpp = Tile4bpp([ + 0x22222222, 0x22222222, 0x22222222, 0x22222222, 0x22222222, 0x22222222, 0x22222222, 0x22222222, +]); -pub const BACKGROUND_PALETTE: VolatilePtr = VolatilePtr(0x500_0000 as *mut u16); - -pub fn set_bg_palette_4bpp(palbank: usize, slot: usize, color: u16) { - assert!(palbank < 16); - assert!(slot > 0 && slot < 16); - unsafe { - BACKGROUND_PALETTE - .cast::<[u16; 16]>() - .offset(palbank as isize) - .cast::() - .offset(slot as isize) - .write(color); - } -} - -pub const fn rgb16(red: u16, green: u16, blue: u16) -> u16 { - blue << 10 | green << 5 | red -} - -pub const WHITE: u16 = rgb16(31, 31, 31); -pub const LIGHT_GRAY: u16 = rgb16(25, 25, 25); -pub const DARK_GRAY: u16 = rgb16(15, 15, 15); - -#[derive(Debug, Clone, Copy, Default)] -#[repr(transparent)] -pub struct Tile4bpp { - pub data: [u32; 8], -} - -pub const ALL_TWOS: Tile4bpp = Tile4bpp { - data: [ - 0x22222222, 0x22222222, 0x22222222, 0x22222222, 0x22222222, 0x22222222, 0x22222222, 0x22222222, - ], -}; - -pub const ALL_THREES: Tile4bpp = Tile4bpp { - data: [ - 0x33333333, 0x33333333, 0x33333333, 0x33333333, 0x33333333, 0x33333333, 0x33333333, 0x33333333, - ], -}; - -#[derive(Clone, Copy)] -#[repr(transparent)] -pub struct Charblock4bpp { - pub data: [Tile4bpp; 512], -} - -pub const VRAM: VolatilePtr = VolatilePtr(0x0600_0000 as *mut Charblock4bpp); +pub const ALL_THREES: Tile4bpp = Tile4bpp([ + 0x33333333, 0x33333333, 0x33333333, 0x33333333, 0x33333333, 0x33333333, 0x33333333, 0x33333333, +]); pub fn set_bg_tile_4bpp(charblock: usize, index: usize, tile: Tile4bpp) { assert!(charblock < 4); assert!(index < 512); - unsafe { VRAM.offset(charblock as isize).cast::().offset(index as isize).write(tile) } + unsafe { VRAM_CHARBLOCKS.index(charblock).cast::().offset(index as isize).write(tile) } } -#[derive(Debug, Clone, Copy, Default)] -#[repr(transparent)] -pub struct RegularScreenblockEntry(u16); - -impl RegularScreenblockEntry { - pub const SCREENBLOCK_ENTRY_TILE_ID_MASK: u16 = 0b11_1111_1111; - pub const fn from_tile_id(id: u16) -> Self { - RegularScreenblockEntry(id & Self::SCREENBLOCK_ENTRY_TILE_ID_MASK) - } -} - -#[derive(Clone, Copy)] -#[repr(transparent)] -pub struct RegularScreenblock { - pub data: [RegularScreenblockEntry; 32 * 32], -} - -pub fn checker_screenblock(slot: usize, a_entry: RegularScreenblockEntry, b_entry: RegularScreenblockEntry) { - let mut p = VRAM.cast::().offset(slot as isize).cast::(); +pub fn checker_screenblock(slot: usize, a_entry: TextScreenblockEntry, b_entry: TextScreenblockEntry) { + let mut p = unsafe { VRAM_TEXT_SCREENBLOCKS.index(slot).cast::() }; let mut checker = true; for _row in 0..32 { for _col in 0..32 { - unsafe { p.write(if checker { a_entry } else { b_entry }) }; - p = p.offset(1); + unsafe { + p.write(if checker { a_entry } else { b_entry }); + p = p.offset(1); + } checker = !checker; } checker = !checker; } } - -#[derive(Clone, Copy, Default, PartialEq, Eq)] -#[repr(transparent)] -pub struct BackgroundControlSetting(u16); - -impl BackgroundControlSetting { - pub const SCREEN_BASE_BLOCK_MASK: u16 = 0b1_1111; - pub const fn from_base_block(sbb: u16) -> Self { - BackgroundControlSetting((sbb & Self::SCREEN_BASE_BLOCK_MASK) << 8) - } -} - -pub const BG0CNT: VolatilePtr = VolatilePtr(0x400_0008 as *mut BackgroundControlSetting); - -#[derive(Clone, Copy, Default, PartialEq, Eq)] -#[repr(transparent)] -pub struct DisplayControlSetting(u16); - -impl DisplayControlSetting { - pub const JUST_ENABLE_BG0: DisplayControlSetting = DisplayControlSetting(1 << 8); -} - -pub const DISPCNT: VolatilePtr = VolatilePtr(0x0400_0000 as *mut DisplayControlSetting); From d6a630181518fe214e220c4f5849b0399ccb470c Mon Sep 17 00:00:00 2001 From: Lokathor Date: Tue, 25 Dec 2018 23:19:16 -0700 Subject: [PATCH 15/24] CLOSE to another release --- Cargo.toml | 2 +- examples/bg_demo.rs | 6 +-- examples/hello_world.rs | 4 +- examples/light_cycle.rs | 4 +- examples/mgba_panic_handler.rs | 4 +- src/bios.rs | 23 ++++++----- src/io/background.rs | 28 +++++++++++-- src/io/display.rs | 55 +++++++++++-------------- src/io/dma.rs | 73 ++++++++++------------------------ src/io/keypad.rs | 63 ++++++++++++++++------------- src/lib.rs | 4 +- src/video/tiled.rs | 23 ++--------- 12 files changed, 134 insertions(+), 155 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d3d18c9..e265cad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ publish = false [dependencies] typenum = "1.10" -gba-proc-macro = "0.3" +gba-proc-macro = "0.4" #[dev-dependencies] #quickcheck="0.7" diff --git a/examples/bg_demo.rs b/examples/bg_demo.rs index 553a15f..d4b3bd2 100644 --- a/examples/bg_demo.rs +++ b/examples/bg_demo.rs @@ -3,7 +3,7 @@ use gba::{ io::{ - background::{BackgroundControlSetting, BG0}, + background::{BackgroundControlSetting, BG0CNT}, display::{DisplayControlSetting, DISPCNT}, }, palram::index_palram_bg_4bpp, @@ -33,9 +33,9 @@ fn main(_argc: isize, _argv: *const *const u8) -> isize { let dark_entry = TextScreenblockEntry::from_tile_index(1); checker_screenblock(8, light_entry, dark_entry); // bg0 control - BG0::BG0CNT.write(BackgroundControlSetting::from_screen_base_block(8)); + BG0CNT.write(BackgroundControlSetting::from_screen_base_block(8)); // Display Control - DISPCNT.write(DisplayControlSetting::new().with_display_bg0(true)); + DISPCNT.write(DisplayControlSetting::new().with_bg0(true)); loop { // TODO the whole thing } diff --git a/examples/hello_world.rs b/examples/hello_world.rs index db402d8..202b23f 100644 --- a/examples/hello_world.rs +++ b/examples/hello_world.rs @@ -3,7 +3,7 @@ #![forbid(unsafe_code)] use gba::{ - io::display::{DisplayControlMode, DisplayControlSetting, DISPCNT}, + io::display::{DisplayControlSetting, DisplayMode, DISPCNT}, video::bitmap::Mode3, Color, }; @@ -15,7 +15,7 @@ fn panic(_info: &core::panic::PanicInfo) -> ! { #[start] fn main(_argc: isize, _argv: *const *const u8) -> isize { - const SETTING: DisplayControlSetting = DisplayControlSetting::new().with_mode(DisplayControlMode::Bitmap3).with_display_bg2(true); + const SETTING: DisplayControlSetting = DisplayControlSetting::new().with_mode(DisplayMode::Bitmap3).with_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)); diff --git a/examples/light_cycle.rs b/examples/light_cycle.rs index 6c6ed71..4ba4698 100644 --- a/examples/light_cycle.rs +++ b/examples/light_cycle.rs @@ -4,7 +4,7 @@ use gba::{ io::{ - display::{spin_until_vblank, spin_until_vdraw, DisplayControlMode, DisplayControlSetting, DISPCNT}, + display::{spin_until_vblank, spin_until_vdraw, DisplayControlSetting, DisplayMode, DISPCNT}, keypad::read_key_input, }, video::bitmap::Mode3, @@ -18,7 +18,7 @@ fn panic(_info: &core::panic::PanicInfo) -> ! { #[start] fn main(_argc: isize, _argv: *const *const u8) -> isize { - const SETTING: DisplayControlSetting = DisplayControlSetting::new().with_mode(DisplayControlMode::Bitmap3).with_display_bg2(true); + const SETTING: DisplayControlSetting = DisplayControlSetting::new().with_mode(DisplayMode::Bitmap3).with_bg2(true); DISPCNT.write(SETTING); let mut px = Mode3::SCREEN_WIDTH / 2; diff --git a/examples/mgba_panic_handler.rs b/examples/mgba_panic_handler.rs index 214a56a..7711460 100644 --- a/examples/mgba_panic_handler.rs +++ b/examples/mgba_panic_handler.rs @@ -3,7 +3,7 @@ #![forbid(unsafe_code)] use gba::{ - io::display::{DisplayControlMode, DisplayControlSetting, DISPCNT}, + io::display::{DisplayControlSetting, DisplayMode, DISPCNT}, video::bitmap::Mode3, Color, }; @@ -22,7 +22,7 @@ fn panic(info: &core::panic::PanicInfo) -> ! { #[start] fn main(_argc: isize, _argv: *const *const u8) -> isize { - const SETTING: DisplayControlSetting = DisplayControlSetting::new().with_mode(DisplayControlMode::Bitmap3).with_display_bg2(true); + const SETTING: DisplayControlSetting = DisplayControlSetting::new().with_mode(DisplayMode::Bitmap3).with_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)); diff --git a/src/bios.rs b/src/bios.rs index 4504f22..bfdf0da 100644 --- a/src/bios.rs +++ b/src/bios.rs @@ -8,7 +8,7 @@ //! whatever value is necessary for that function). Some functions also perform //! necessary checks to save you from yourself, such as not dividing by zero. -use super::register_bit; +use super::bool_bits; //TODO: ALL functions in this module should have `if cfg!(test)` blocks. The //functions that never return must panic, the functions that return nothing @@ -109,14 +109,19 @@ newtype! { } #[allow(missing_docs)] impl RegisterRAMResetFlags { - register_bit!(EWRAM, u8, 0, ewram); - register_bit!(IWRAM, u8, 1 << 1, iwram); - register_bit!(PALRAM, u8, 1 << 2, palram); - register_bit!(VRAM, u8, 1 << 3, vram); - register_bit!(OAM, u8, 1 << 4, oam); - register_bit!(SIO, u8, 1 << 5, sio); - register_bit!(SOUND, u8, 1 << 6, sound); - register_bit!(OTHER_IO, u8, 1 << 7, other_io); + bool_bits!( + u8, + [ + (0, ewram), + (1, iwram), + (2, palram), + (3, vram), + (4, oam), + (5, sio), + (6, sound), + (7, other_io), + ] + ); } /// (`swi 0x02`) Halts the CPU until an interrupt occurs. diff --git a/src/io/background.rs b/src/io/background.rs index 926ce06..1b38309 100644 --- a/src/io/background.rs +++ b/src/io/background.rs @@ -3,15 +3,35 @@ use super::*; newtype! { + /// A newtype over the various display control options that you have on a GBA. + #[derive(Debug, Copy, Clone, Default, PartialEq, Eq)] BackgroundControlSetting, u16 } impl BackgroundControlSetting { pub const fn from_screen_base_block(screen_base_block: u16) -> Self { BackgroundControlSetting(screen_base_block << 8) // TODO: mask this for correctness } + + // + + pub const PRIORITY_MASK: u16 = 0b11 << 0; + pub const fn priority(self) -> u16 { + self.0 & Self::PRIORITY_MASK + } + pub const fn with_priority(self, priority: u16) -> Self { + BackgroundControlSetting((self.0 & !Self::PRIORITY_MASK) | priority) + } + + pub const CHARACTER_BASE_BLOCK_MASK: u16 = 0b11 << 2; + pub const fn character_base_block(self) -> u16 { + (self.0 & Self::CHARACTER_BASE_BLOCK_MASK) >> 2 + } + pub const fn with_character_base_block(self, character_base_block: u16) -> Self { + BackgroundControlSetting((self.0 & !Self::CHARACTER_BASE_BLOCK_MASK) | ((character_base_block << 2) & Self::CHARACTER_BASE_BLOCK_MASK)) + } } -pub struct BG0; -impl BG0 { - pub const BG0CNT: VolAddress = unsafe { VolAddress::new_unchecked(0x400_0008) }; -} +pub const BG0CNT: VolAddress = unsafe { VolAddress::new_unchecked(0x400_0008) }; +pub const BG1CNT: VolAddress = unsafe { VolAddress::new_unchecked(0x400_000A) }; +pub const BG2CNT: VolAddress = unsafe { VolAddress::new_unchecked(0x400_000C) }; +pub const BG3CNT: VolAddress = unsafe { VolAddress::new_unchecked(0x400_000E) }; diff --git a/src/io/display.rs b/src/io/display.rs index 1711f43..831d315 100644 --- a/src/io/display.rs +++ b/src/io/display.rs @@ -15,43 +15,36 @@ newtype!( #[allow(missing_docs)] impl DisplayControlSetting { - pub const BG_MODE_MASK: u16 = 0b111; + bool_bits!(u16, + [ + (3, cgb_mode), + (4, frame1), + (5, hblank_interval_free), + (6, oam_memory_1d), + (7, force_vblank), + (8, bg0), + (9, bg1), + (10, bg2), + (11, bg3), + (12, obj), + (13, win0), + (14, win1), + (15, obj_window) + ] + ); - 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); + multi_bits!( + u16, + [ + (0, 3, mode, DisplayMode, Tiled0, Tiled1, Tiled2, Bitmap3, Bitmap4, Bitmap5 ) + ] + ); } /// The six display modes available on the GBA. #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(u16)] -pub enum DisplayControlMode { +pub enum DisplayMode { /// 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. diff --git a/src/io/dma.rs b/src/io/dma.rs index f796f8c..3ecd98e 100644 --- a/src/io/dma.rs +++ b/src/io/dma.rs @@ -68,56 +68,25 @@ newtype! { } #[allow(missing_docs)] impl DMAControlSetting { - pub const DEST_ADDR_CONTROL_MASK: u16 = 0b11 << 5; - pub fn dest_address_control(self) -> DMADestAddressControl { - // TODO: constify - match (self.0 & Self::DEST_ADDR_CONTROL_MASK) >> 5 { - 0 => DMADestAddressControl::Increment, - 1 => DMADestAddressControl::Decrement, - 2 => DMADestAddressControl::Fixed, - 3 => DMADestAddressControl::IncrementReload, - _ => unsafe { core::hint::unreachable_unchecked() }, - } - } - pub const fn with_dest_address_control(self, new_control: DMADestAddressControl) -> Self { - Self((self.0 & !Self::DEST_ADDR_CONTROL_MASK) | ((new_control as u16) << 5)) - } + bool_bits!(u16, [(9, dma_repeat), (10, use_32bit), (14, irq_when_done), (15, enabled)]); - pub const SRC_ADDR_CONTROL_MASK: u16 = 0b11 << 7; - pub fn src_address_control(self) -> DMASrcAddressControl { - // TODO: constify - match (self.0 & Self::SRC_ADDR_CONTROL_MASK) >> 7 { - 0 => DMASrcAddressControl::Increment, - 1 => DMASrcAddressControl::Decrement, - 2 => DMASrcAddressControl::Fixed, - _ => unreachable!(), // TODO: custom error message? - } - } - pub const fn with_src_address_control(self, new_control: DMASrcAddressControl) -> Self { - Self((self.0 & !Self::SRC_ADDR_CONTROL_MASK) | ((new_control as u16) << 7)) - } - - register_bit!(REPEAT, u16, 1 << 9, repeat); - register_bit!(TRANSFER_U32, u16, 1 << 10, transfer_u32); - // TODO: Game Pak DRQ? (bit 11) DMA3 only, and requires specific hardware - - pub const START_TIMING_MASK: u16 = 0b11 << 12; - pub fn start_timing(self) -> DMAStartTiming { - // TODO: constify - match (self.0 & Self::DEST_ADDR_CONTROL_MASK) >> 12 { - 0 => DMAStartTiming::Immediate, - 1 => DMAStartTiming::VBlank, - 2 => DMAStartTiming::HBlank, - 3 => DMAStartTiming::Special, - _ => unsafe { core::hint::unreachable_unchecked() }, - } - } - pub const fn with_start_timing(self, new_control: DMAStartTiming) -> Self { - Self((self.0 & !Self::START_TIMING_MASK) | ((new_control as u16) << 12)) - } - - register_bit!(IRQ_AT_END, u16, 1 << 14, irq_at_end); - register_bit!(ENABLE, u16, 1 << 15, enable); + multi_bits!( + u16, + [ + ( + 5, + 2, + dest_address_control, + DMADestAddressControl, + Increment, + Decrement, + Fixed, + IncrementReload + ), + (7, 2, source_address_control, DMASrcAddressControl, Increment, Decrement, Fixed), + (12, 2, start_time, DMAStartTiming, Immediate, VBlank, HBlank, Special) + ] + ); } /// Sets how the destination address should be adjusted per data transfer. @@ -234,9 +203,9 @@ impl DMA3 { /// must be valid for writing. pub unsafe fn fill32(src: *const u32, dest: *mut u32, count: u16) { const FILL_CONTROL: DMAControlSetting = DMAControlSetting::new() - .with_src_address_control(DMASrcAddressControl::Fixed) - .with_transfer_u32(true) - .with_enable(true); + .with_source_address_control(DMASrcAddressControl::Fixed) + .with_use_32bit(true) + .with_enabled(true); // TODO: destination checking against SRAM Self::DMA3SAD.write(src); Self::DMA3DAD.write(dest); diff --git a/src/io/keypad.rs b/src/io/keypad.rs index d03d242..f4fab87 100644 --- a/src/io/keypad.rs +++ b/src/io/keypad.rs @@ -31,16 +31,21 @@ newtype! { #[allow(missing_docs)] impl KeyInput { - register_bit!(A_BIT, u16, 1, a_pressed); - register_bit!(B_BIT, u16, 1 << 1, b_pressed); - register_bit!(SELECT_BIT, u16, 1 << 2, select_pressed); - register_bit!(START_BIT, u16, 1 << 3, start_pressed); - register_bit!(RIGHT_BIT, u16, 1 << 4, right_pressed); - register_bit!(LEFT_BIT, u16, 1 << 5, left_pressed); - register_bit!(UP_BIT, u16, 1 << 6, up_pressed); - register_bit!(DOWN_BIT, u16, 1 << 7, down_pressed); - register_bit!(R_BIT, u16, 1 << 8, r_pressed); - register_bit!(L_BIT, u16, 1 << 9, l_pressed); + bool_bits!( + u16, + [ + (0, a), + (1, b), + (2, select), + (3, start), + (4, right), + (5, left), + (6, up), + (7, down), + (8, r), + (9, l) + ] + ); /// Takes the set difference between these keys and another set of keys. pub fn difference(self, other: Self) -> Self { @@ -50,9 +55,9 @@ impl KeyInput { /// Gives the arrow pad value as a tribool, with Plus being increased column /// value (right). pub fn column_direction(self) -> TriBool { - if self.right_pressed() { + if self.right() { TriBool::Plus - } else if self.left_pressed() { + } else if self.left() { TriBool::Minus } else { TriBool::Neutral @@ -62,9 +67,9 @@ impl KeyInput { /// Gives the arrow pad value as a tribool, with Plus being increased row /// value (down). pub fn row_direction(self) -> TriBool { - if self.down_pressed() { + if self.down() { TriBool::Plus - } else if self.up_pressed() { + } else if self.up() { TriBool::Minus } else { TriBool::Neutral @@ -100,19 +105,23 @@ newtype! { } #[allow(missing_docs)] impl KeyInterruptSetting { - register_bit!(A_BIT, u16, 1, a_pressed); - register_bit!(B_BIT, u16, 1 << 1, b_pressed); - register_bit!(SELECT_BIT, u16, 1 << 2, select_pressed); - register_bit!(START_BIT, u16, 1 << 3, start_pressed); - register_bit!(RIGHT_BIT, u16, 1 << 4, right_pressed); - register_bit!(LEFT_BIT, u16, 1 << 5, left_pressed); - register_bit!(UP_BIT, u16, 1 << 6, up_pressed); - register_bit!(DOWN_BIT, u16, 1 << 7, down_pressed); - register_bit!(R_BIT, u16, 1 << 8, r_pressed); - register_bit!(L_BIT, u16, 1 << 9, l_pressed); - // - register_bit!(IRQ_ENABLE_BIT, u16, 1 << 14, irq_enabled); - register_bit!(IRQ_AND_BIT, u16, 1 << 15, irq_logical_and); + bool_bits!( + u16, + [ + (0, a), + (1, b), + (2, select), + (3, start), + (4, right), + (5, left), + (6, up), + (7, down), + (8, r), + (9, l), + (14, irq_enabled), + (15, irq_logical_and) + ] + ); } /// Use this to configure when a keypad interrupt happens. diff --git a/src/lib.rs b/src/lib.rs index 6b07a04..7b5b198 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,7 +3,7 @@ #![feature(const_int_wrapping)] #![feature(const_int_rotate)] #![feature(min_const_unsafe_fn)] -#![warn(missing_docs)] +//#![warn(missing_docs)] #![allow(clippy::cast_lossless)] #![deny(clippy::float_arithmetic)] @@ -20,7 +20,7 @@ //! **Do not** use this crate in programs that aren't running on the GBA. If you //! do, it's a giant bag of Undefined Behavior. -pub(crate) use gba_proc_macro::register_bit; +pub(crate) use gba_proc_macro::{bool_bits, multi_bits}; /// Assists in defining a newtype wrapper over some base type. /// diff --git a/src/video/tiled.rs b/src/video/tiled.rs index 9c10009..e91dcc1 100644 --- a/src/video/tiled.rs +++ b/src/video/tiled.rs @@ -36,29 +36,12 @@ newtype! { } impl TextScreenblockEntry { pub const fn from_tile_index(index: u16) -> Self { - TextScreenblockEntry(index & Self::TILE_INDEX_MASK) + TextScreenblockEntry(index & Self::TILE_ID_MASK) } - // + bool_bits!(u16, [(10, hflip), (11, vflip)]); - pub const TILE_INDEX_MASK: u16 = 0xA - 1; - pub const fn tile_index(self) -> u16 { - self.0 & Self::TILE_INDEX_MASK - } - pub const fn with_tile_index(self, index: u16) -> Self { - TextScreenblockEntry((self.0 & !Self::TILE_INDEX_MASK) | (index & Self::TILE_INDEX_MASK)) - } - - register_bit!(HFLIP_BIT, u16, 1 << 0xA, hflip); - register_bit!(VFLIP_BIT, u16, 1 << 0xB, vflip); - - pub const PALBANK_MASK: u16 = 0b1111 << 0xC; - pub const fn palbank(self) -> u16 { - (self.0 & Self::TILE_INDEX_MASK) >> 0xC - } - pub const fn with_palbank(self, palbank: u16) -> Self { - TextScreenblockEntry((self.0 & !Self::PALBANK_MASK) | (palbank << 0xC)) - } + multi_bits!(u16, [(0, 10, tile_id), (12, 4, palbank)]); } newtype! { From 01ac3ec09e13053098420f47f7661789e1c93ba1 Mon Sep 17 00:00:00 2001 From: Lokathor Date: Tue, 25 Dec 2018 23:52:30 -0700 Subject: [PATCH 16/24] travisCI fix --- .travis.yml | 6 +++--- src/video/tiled.rs | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9afa017..f1b9af4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,9 @@ language: rust sudo: false cache: - - cargo + # Cache ONLY .cargo, not target/ like usual + directories: + - $HOME/.cargo rust: - nightly @@ -17,8 +19,6 @@ before_script: - cargo install-update -a script: - # Travis seems to cache for some dumb reason, but we don't want that at all. - - rm -fr target # Obtain the devkitPro tools, using `target/` as a temp directory - mkdir -p target - cd target diff --git a/src/video/tiled.rs b/src/video/tiled.rs index e91dcc1..40bb383 100644 --- a/src/video/tiled.rs +++ b/src/video/tiled.rs @@ -59,25 +59,25 @@ newtype! { newtype! { /// A screenblock for use in Affine mode. #[derive(Clone, Copy)] - AffineScreenblock_16x16, [AffineScreenblockEntry; 16*16], no frills + AffineScreenblock16x16, [AffineScreenblockEntry; 16*16], no frills } newtype! { /// A screenblock for use in Affine mode. #[derive(Clone, Copy)] - AffineScreenblock_32x32, [AffineScreenblockEntry; 32*32], no frills + AffineScreenblock32x32, [AffineScreenblockEntry; 32*32], no frills } newtype! { /// A screenblock for use in Affine mode. #[derive(Clone, Copy)] - AffineScreenblock_64x64, [AffineScreenblockEntry; 64*64], no frills + AffineScreenblock64x64, [AffineScreenblockEntry; 64*64], no frills } newtype! { /// A screenblock for use in Affine mode. #[derive(Clone, Copy)] - AffineScreenblock_128x128, [AffineScreenblockEntry; 128*128], no frills + AffineScreenblock128x128, [AffineScreenblockEntry; 128*128], no frills } pub const VRAM_CHARBLOCKS: VolAddressBlock = unsafe { VolAddressBlock::new_unchecked(VolAddress::new_unchecked(VRAM_BASE_USIZE), 6) }; From 6271614335525b410d8d2d7b9a3d26bd883910c8 Mon Sep 17 00:00:00 2001 From: Lokathor Date: Wed, 26 Dec 2018 20:19:53 -0700 Subject: [PATCH 17/24] backgrounds and OAM start --- examples/hello_world.rs | 2 +- examples/light_cycle.rs | 2 +- examples/mgba_panic_handler.rs | 2 +- src/io/background.rs | 157 ++++++++++++++++++++++++++++----- src/io/display.rs | 114 +++++++++++++++++------- src/oam.rs | 100 +++++++++++++++++++++ 6 files changed, 324 insertions(+), 53 deletions(-) create mode 100644 src/oam.rs diff --git a/examples/hello_world.rs b/examples/hello_world.rs index 202b23f..f1e5336 100644 --- a/examples/hello_world.rs +++ b/examples/hello_world.rs @@ -15,7 +15,7 @@ fn panic(_info: &core::panic::PanicInfo) -> ! { #[start] fn main(_argc: isize, _argv: *const *const u8) -> isize { - const SETTING: DisplayControlSetting = DisplayControlSetting::new().with_mode(DisplayMode::Bitmap3).with_bg2(true); + const SETTING: DisplayControlSetting = DisplayControlSetting::new().with_mode(DisplayMode::Mode3).with_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)); diff --git a/examples/light_cycle.rs b/examples/light_cycle.rs index 4ba4698..29ad8b6 100644 --- a/examples/light_cycle.rs +++ b/examples/light_cycle.rs @@ -18,7 +18,7 @@ fn panic(_info: &core::panic::PanicInfo) -> ! { #[start] fn main(_argc: isize, _argv: *const *const u8) -> isize { - const SETTING: DisplayControlSetting = DisplayControlSetting::new().with_mode(DisplayMode::Bitmap3).with_bg2(true); + const SETTING: DisplayControlSetting = DisplayControlSetting::new().with_mode(DisplayMode::Mode3).with_bg2(true); DISPCNT.write(SETTING); let mut px = Mode3::SCREEN_WIDTH / 2; diff --git a/examples/mgba_panic_handler.rs b/examples/mgba_panic_handler.rs index 7711460..a51c0c9 100644 --- a/examples/mgba_panic_handler.rs +++ b/examples/mgba_panic_handler.rs @@ -22,7 +22,7 @@ fn panic(info: &core::panic::PanicInfo) -> ! { #[start] fn main(_argc: isize, _argv: *const *const u8) -> isize { - const SETTING: DisplayControlSetting = DisplayControlSetting::new().with_mode(DisplayMode::Bitmap3).with_bg2(true); + const SETTING: DisplayControlSetting = DisplayControlSetting::new().with_mode(DisplayMode::Mode3).with_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)); diff --git a/src/io/background.rs b/src/io/background.rs index 1b38309..dad56f1 100644 --- a/src/io/background.rs +++ b/src/io/background.rs @@ -2,36 +2,153 @@ use super::*; +// BG0 Control. Read/Write. +pub const BG0CNT: VolAddress = unsafe { VolAddress::new_unchecked(0x400_0008) }; +// BG1 Control. Read/Write. +pub const BG1CNT: VolAddress = unsafe { VolAddress::new_unchecked(0x400_000A) }; +// BG2 Control. Read/Write. +pub const BG2CNT: VolAddress = unsafe { VolAddress::new_unchecked(0x400_000C) }; +// BG3 Control. Read/Write. +pub const BG3CNT: VolAddress = unsafe { VolAddress::new_unchecked(0x400_000E) }; + newtype! { - /// A newtype over the various display control options that you have on a GBA. + /// Allows configuration of a background layer. + /// + /// Bits 0-1: BG Priority (lower number is higher priority, like an index) + /// Bits 2-3: Character Base Block (0 through 3, 16k each) + /// Bit 6: Mosaic mode + /// Bit 7: is 8bpp + /// Bit 8-12: Screen Base Block (0 through 31, 2k each) + /// Bit 13: Display area overflow wraps (otherwise transparent, affine only) + /// Bit 14-15: Screen Size #[derive(Debug, Copy, Clone, Default, PartialEq, Eq)] BackgroundControlSetting, u16 } impl BackgroundControlSetting { pub const fn from_screen_base_block(screen_base_block: u16) -> Self { - BackgroundControlSetting(screen_base_block << 8) // TODO: mask this for correctness + BackgroundControlSetting((screen_base_block & 31) << 8) } // - pub const PRIORITY_MASK: u16 = 0b11 << 0; - pub const fn priority(self) -> u16 { - self.0 & Self::PRIORITY_MASK - } - pub const fn with_priority(self, priority: u16) -> Self { - BackgroundControlSetting((self.0 & !Self::PRIORITY_MASK) | priority) - } + bool_bits!(u16, [(6, mosaic), (7, is_8bpp), (13, display_overflow_wrapping)]); - pub const CHARACTER_BASE_BLOCK_MASK: u16 = 0b11 << 2; - pub const fn character_base_block(self) -> u16 { - (self.0 & Self::CHARACTER_BASE_BLOCK_MASK) >> 2 - } - pub const fn with_character_base_block(self, character_base_block: u16) -> Self { - BackgroundControlSetting((self.0 & !Self::CHARACTER_BASE_BLOCK_MASK) | ((character_base_block << 2) & Self::CHARACTER_BASE_BLOCK_MASK)) - } + multi_bits!( + u16, + [ + (0, 2, bg_priority), + (2, 2, char_base_block), + (8, 5, screen_base_block), + (2, 2, size, BGSize, Zero, One, Two, Three), + ] + ); } -pub const BG0CNT: VolAddress = unsafe { VolAddress::new_unchecked(0x400_0008) }; -pub const BG1CNT: VolAddress = unsafe { VolAddress::new_unchecked(0x400_000A) }; -pub const BG2CNT: VolAddress = unsafe { VolAddress::new_unchecked(0x400_000C) }; -pub const BG3CNT: VolAddress = unsafe { VolAddress::new_unchecked(0x400_000E) }; +/// The size of a background. +/// +/// The meaning changes depending on if the background is Text or Affine mode. +/// +/// * In text mode, the screen base block determines where to start reading the +/// tile arrangement data (2k). Size Zero gives one screen block of use. Size +/// One and Two cause two of them to be used (horizontally or vertically, +/// respectively). Size Three is four blocks used, [0,1] above and then [2,3] +/// below. Each screen base block used is always a 32x32 tile grid. +/// * In affine mode, the screen base block determines where to start reading +/// data followed by the size of data as shown. The number of tiles varies +/// according to the size used. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u16)] +pub enum BGSize { + /// * Text: 256x256px (2k) + /// * Affine: 128x128px (256b) + Zero = 0, + /// * Text: 512x256px (4k) + /// * Affine: 256x256px (1k) + One = 1, + /// * Text: 256x512px (4k) + /// * Affine: 512x512px (4k) + Two = 2, + /// * Text: 512x512px (8k) + /// * Affine: 1024x1024px (16k) + Three = 3, +} + +// BG0 X-Offset. Write only. Text mode only. 9 bits. +pub const BG0HOFS: VolAddress = unsafe { VolAddress::new_unchecked(0x400_0010) }; +// BG0 Y-Offset. Write only. Text mode only. 9 bits. +pub const BG0VOFS: VolAddress = unsafe { VolAddress::new_unchecked(0x400_0012) }; + +// BG1 X-Offset. Write only. Text mode only. 9 bits. +pub const BG1HOFS: VolAddress = unsafe { VolAddress::new_unchecked(0x400_0012) }; +// BG1 Y-Offset. Write only. Text mode only. 9 bits. +pub const BG1VOFS: VolAddress = unsafe { VolAddress::new_unchecked(0x400_0012) }; + +// BG2 X-Offset. Write only. Text mode only. 9 bits. +pub const BG2HOFS: VolAddress = unsafe { VolAddress::new_unchecked(0x400_0018) }; +// BG2 Y-Offset. Write only. Text mode only. 9 bits. +pub const BG2VOFS: VolAddress = unsafe { VolAddress::new_unchecked(0x400_001A) }; + +// BG3 X-Offset. Write only. Text mode only. 9 bits. +pub const BG3HOFS: VolAddress = unsafe { VolAddress::new_unchecked(0x400_001C) }; +// BG3 Y-Offset. Write only. Text mode only. 9 bits. +pub const BG3VOFS: VolAddress = unsafe { VolAddress::new_unchecked(0x400_001E) }; + +// TODO: affine registers: +// BG2X_L +// BG2X_H +// BG2Y_L +// BG2Y_H +// BG2PA +// BG2PB +// BG2PC +// BG2PD +// BG3PA +// BG3PB +// BG3PC +// BG3PD + +// TODO: windowing +// pub const WIN0H: VolAddress = unsafe { VolAddress::new_unchecked(0x400_0040) }; +// pub const WIN1H: VolAddress = unsafe { VolAddress::new_unchecked(0x400_0042) }; +// pub const WIN0V: VolAddress = unsafe { VolAddress::new_unchecked(0x400_0044) }; +// pub const WIN1V: VolAddress = unsafe { VolAddress::new_unchecked(0x400_0046) }; +// pub const WININ: VolAddress = unsafe { VolAddress::new_unchecked(0x400_0048) }; +// pub const WINOUT: VolAddress = unsafe { VolAddress::new_unchecked(0x400_004A) }; + +/// Global mosaic effect control. Write-only. +pub const MOSAIC: VolAddress = unsafe { VolAddress::new_unchecked(0x400_004C) }; + +newtype! { + /// Allows control of the Mosaic effect. + /// + /// Values are the _increase_ for each top-left pixel to be duplicated in the + /// final result. If you want to duplicate some other pixel than the top-left, + /// you can offset the background or object by an appropriate amount. + /// + /// 0) No effect (1+0) + /// 1) Each pixel becomes 2 pixels (1+1) + /// 2) Each pixel becomes 3 pixels (1+2) + /// 3) Each pixel becomes 4 pixels (1+3) + /// + /// * Bits 0-3: BG mosaic horizontal increase + /// * Bits 4-7: BG mosaic vertical increase + /// * Bits 8-11: Object mosaic horizontal increase + /// * Bits 12-15: Object mosaic vertical increase + #[derive(Debug, Copy, Clone, Default, PartialEq, Eq)] + MosaicSetting, u16 +} +impl MosaicSetting { + multi_bits!( + u16, + [ + (0, 4, bg_horizontal_inc), + (4, 4, bg_vertical_inc), + (8, 4, obj_horizontal_inc), + (12, 4, obj_vertical_inc), + ] + ); +} + +// pub const BLDCNT: VolAddress = unsafe { VolAddress::new_unchecked(0x400_0050) }; +// pub const BLDALPHA: VolAddress = unsafe { VolAddress::new_unchecked(0x400_0052) }; +// pub const BLDY: VolAddress = unsafe { VolAddress::new_unchecked(0x400_0054) }; diff --git a/src/io/display.rs b/src/io/display.rs index 831d315..353dffa 100644 --- a/src/io/display.rs +++ b/src/io/display.rs @@ -4,18 +4,35 @@ use super::*; /// LCD Control. Read/Write. /// -/// * [gbatek entry](http://problemkaputt.de/gbatek.htm#lcdiodisplaycontrol) +/// The "force vblank" bit is always set when your rust code first executes. pub const DISPCNT: VolAddress = unsafe { VolAddress::new_unchecked(0x400_0000) }; newtype!( - /// A newtype over the various display control options that you have on a GBA. + /// Setting for the display control register. + /// + /// * 0-2: `DisplayMode` + /// * 3: CGB mode flag + /// * 4: Display frame 1 (Modes 4/5 only) + /// * 5: "hblank interval free", allows full access to OAM during hblank + /// * 6: Object tile memory 1-dimensional + /// * 7: Force vblank + /// * 8: Display bg0 layer + /// * 9: Display bg1 layer + /// * 10: Display bg2 layer + /// * 11: Display bg3 layer + /// * 12: Display objects layer + /// * 13: Window 0 display + /// * 14: Window 1 display + /// * 15: Object window #[derive(Debug, Copy, Clone, Default, PartialEq, Eq)] - DisplayControlSetting, u16 + DisplayControlSetting, + u16 ); #[allow(missing_docs)] impl DisplayControlSetting { - bool_bits!(u16, + bool_bits!( + u16, [ (3, cgb_mode), (4, frame1), @@ -33,36 +50,46 @@ impl DisplayControlSetting { ] ); - multi_bits!( - u16, - [ - (0, 3, mode, DisplayMode, Tiled0, Tiled1, Tiled2, Bitmap3, Bitmap4, Bitmap5 ) - ] - ); + multi_bits!(u16, [(0, 3, mode, DisplayMode, Mode0, Mode1, Mode2, Mode3, Mode4, Mode5)]); } /// The six display modes available on the GBA. #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(u16)] pub enum DisplayMode { - /// 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, + /// * Affine: No + /// * Layers: 0/1/2/3 + /// * Size(px): 256x256 to 512x512 + /// * Tiles: 1024 + /// * Palette Modes: 4bpp or 8bpp + Mode0 = 0, + /// * BG0 / BG1: As Mode0 + /// * BG2: As Mode2 + Mode1 = 1, + /// * Affine: Yes + /// * Layers: 2/3 + /// * Size(px): 128x128 to 1024x1024 + /// * Tiles: 256 + /// * Palette Modes: 8bpp + Mode2 = 2, + /// * Affine: Yes + /// * Layers: 2 + /// * Size(px): 240x160 (1 page) + /// * Bitmap + /// * Full Color + Mode3 = 3, + /// * Affine: Yes + /// * Layers: 2 + /// * Size(px): 240x160 (2 pages) + /// * Bitmap + /// * Palette Modes: 8bpp + Mode4 = 4, + /// * Affine: Yes + /// * Layers: 2 + /// * Size(px): 160x128 (2 pages) + /// * Bitmap + /// * Full Color + Mode5 = 5, } /// Assigns the given display control setting. @@ -74,8 +101,32 @@ 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; +/// Display Status and IRQ Control. +pub const DISPSTAT: VolAddress = unsafe { VolAddress::new_unchecked(0x400_0004) }; + +newtype!( + /// A newtype over display status and interrupt control values. + #[derive(Debug, Copy, Clone, Default, PartialEq, Eq)] + DisplayStatusSetting, + u16 +); + +#[allow(missing_docs)] +impl DisplayStatusSetting { + bool_bits!( + u16, + [ + (0, vblank_flag), + (1, hblank_flag), + (2, vcounter_flag), + (3, vblank_irq_enable), + (4, hblank_irq_enable), + (5, vcounter_irq_enable), + ] + ); + + multi_bits!(u16, [(8, 8, vcount_setting)]); +} /// Vertical Counter (LY). Read only. /// @@ -84,6 +135,9 @@ pub const VBLANK_SCANLINE: u16 = 160; /// is in a "vertical blank" period. pub const VCOUNT: VolAddress = unsafe { VolAddress::new_unchecked(0x400_0006) }; +/// If the `VCOUNT` register reads equal to or above this then you're in vblank. +pub const VBLANK_SCANLINE: u16 = 160; + /// Obtains the current `VCOUNT` value. pub fn vcount() -> u16 { VCOUNT.read() diff --git a/src/oam.rs b/src/oam.rs new file mode 100644 index 0000000..ef7cb41 --- /dev/null +++ b/src/oam.rs @@ -0,0 +1,100 @@ +//! Types and declarations for the Object Attribute Memory (`OAM`). + +newtype! { + // TODO + #[derive(Debug, Copy, Clone, Default, PartialEq, Eq)] + OBJAttr0, u16 +} +impl OBJAttr0 { + bool_bits!( + u16, + [ + (12, mosaic), + (13, is_8bpp), + ] + ); + + multi_bits!( + u16, + [ + (0, 8, y_coordinate), + (8, 2, obj_rendering, ObjectRender, Normal, Affine, Disabled, DoubleAreaAffine), + (10, 2, obj_mode, ObjectMode, Normal, SemiTransparent, OBJWindow), + (14, 2, obj_shape, ObjectShape, Square, Horizontal, Vertical), + ] + ); +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u16)] +pub enum ObjectRender { + Normal = 0, + Affine = 1, + Disabled = 2, + DoubleAreaAffine = 3, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u16)] +pub enum ObjectMode { + Normal = 0, + SemiTransparent = 1, + OBJWindow = 2 +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u16)] +pub enum ObjectShape { + Square = 0, + Horizontal = 1, + Vertical = 2 +} + +newtype! { + // TODO + #[derive(Debug, Copy, Clone, Default, PartialEq, Eq)] + OBJAttr1, u16 +} +impl OBJAttr1 { + bool_bits!( + u16, + [ + (12, hflip), + (13, vflip), + ] + ); + + multi_bits!( + u16, + [ + (0, 9, x_coordinate), + (9, 5, affine_index), + (14, 2, obj_size, ObjectSize, Zero, One, Two, Three), + ] + ); +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u16)] +pub enum ObjectSize { + Zero = 0, + One = 1, + Two = 2, + Three = 3, +} + +newtype! { + // TODO + #[derive(Debug, Copy, Clone, Default, PartialEq, Eq)] + OBJAttr2, u16 +} +impl OBJAttr2 { + multi_bits!( + u16, + [ + (0, 10, tile_id), + (10, 2, priority), + (12, 4, palbank), + ] + ); +} From 72bbe412b67058b35783e0b1a907f1e338e5d7bd Mon Sep 17 00:00:00 2001 From: Lokathor Date: Wed, 26 Dec 2018 23:13:10 -0700 Subject: [PATCH 18/24] clearer module organization --- examples/bg_demo.rs | 7 +-- examples/hello_world.rs | 2 +- examples/light_cycle.rs | 2 +- examples/mgba_panic_handler.rs | 2 +- src/base/volatile.rs | 2 + src/io/background.rs | 46 +++--------------- src/io/display.rs | 38 ++++++++++++++- src/lib.rs | 2 +- src/video/tiled.rs | 86 ---------------------------------- src/{video.rs => vram.rs} | 34 +++++++++++++- src/vram/affine.rs | 31 ++++++++++++ src/{video => vram}/bitmap.rs | 0 src/vram/text.rs | 29 ++++++++++++ 13 files changed, 145 insertions(+), 136 deletions(-) delete mode 100644 src/video/tiled.rs rename src/{video.rs => vram.rs} (53%) create mode 100644 src/vram/affine.rs rename src/{video => vram}/bitmap.rs (100%) create mode 100644 src/vram/text.rs diff --git a/examples/bg_demo.rs b/examples/bg_demo.rs index d4b3bd2..7477aa2 100644 --- a/examples/bg_demo.rs +++ b/examples/bg_demo.rs @@ -7,9 +7,10 @@ use gba::{ display::{DisplayControlSetting, DISPCNT}, }, palram::index_palram_bg_4bpp, - video::tiled::{TextScreenblockEntry, Tile4bpp, VRAM_CHARBLOCKS, VRAM_TEXT_SCREENBLOCKS}, + vram::text::{TextScreenblockEntry}, Color, }; +use gba::vram::{Tile4bpp, CHAR_BASE_BLOCKS, SCREEN_BASE_BLOCKS}; #[panic_handler] fn panic(_info: &core::panic::PanicInfo) -> ! { @@ -52,11 +53,11 @@ pub const ALL_THREES: Tile4bpp = Tile4bpp([ pub fn set_bg_tile_4bpp(charblock: usize, index: usize, tile: Tile4bpp) { assert!(charblock < 4); assert!(index < 512); - unsafe { VRAM_CHARBLOCKS.index(charblock).cast::().offset(index as isize).write(tile) } + unsafe { CHAR_BASE_BLOCKS.index(charblock).cast::().offset(index as isize).write(tile) } } pub fn checker_screenblock(slot: usize, a_entry: TextScreenblockEntry, b_entry: TextScreenblockEntry) { - let mut p = unsafe { VRAM_TEXT_SCREENBLOCKS.index(slot).cast::() }; + let mut p = unsafe { SCREEN_BASE_BLOCKS.index(slot).cast::() }; let mut checker = true; for _row in 0..32 { for _col in 0..32 { diff --git a/examples/hello_world.rs b/examples/hello_world.rs index f1e5336..b126ee9 100644 --- a/examples/hello_world.rs +++ b/examples/hello_world.rs @@ -4,7 +4,7 @@ use gba::{ io::display::{DisplayControlSetting, DisplayMode, DISPCNT}, - video::bitmap::Mode3, + vram::bitmap::Mode3, Color, }; diff --git a/examples/light_cycle.rs b/examples/light_cycle.rs index 29ad8b6..3514998 100644 --- a/examples/light_cycle.rs +++ b/examples/light_cycle.rs @@ -7,7 +7,7 @@ use gba::{ display::{spin_until_vblank, spin_until_vdraw, DisplayControlSetting, DisplayMode, DISPCNT}, keypad::read_key_input, }, - video::bitmap::Mode3, + vram::bitmap::Mode3, Color, }; diff --git a/examples/mgba_panic_handler.rs b/examples/mgba_panic_handler.rs index a51c0c9..5a9914e 100644 --- a/examples/mgba_panic_handler.rs +++ b/examples/mgba_panic_handler.rs @@ -4,7 +4,7 @@ use gba::{ io::display::{DisplayControlSetting, DisplayMode, DISPCNT}, - video::bitmap::Mode3, + vram::bitmap::Mode3, Color, }; diff --git a/src/base/volatile.rs b/src/base/volatile.rs index 5e12e28..7f74f69 100644 --- a/src/base/volatile.rs +++ b/src/base/volatile.rs @@ -2,6 +2,8 @@ use core::{cmp::Ordering, iter::FusedIterator, marker::PhantomData, num::NonZeroUsize}; +// TODO: striding block/iter + /// Abstracts the use of a volatile hardware address. /// /// If you're trying to do anything other than abstract a volatile hardware diff --git a/src/io/background.rs b/src/io/background.rs index dad56f1..d86f033 100644 --- a/src/io/background.rs +++ b/src/io/background.rs @@ -2,13 +2,13 @@ use super::*; -// BG0 Control. Read/Write. +// BG0 Control. Read/Write. Display Mode 0/1 only. pub const BG0CNT: VolAddress = unsafe { VolAddress::new_unchecked(0x400_0008) }; -// BG1 Control. Read/Write. +// BG1 Control. Read/Write. Display Mode 0/1 only. pub const BG1CNT: VolAddress = unsafe { VolAddress::new_unchecked(0x400_000A) }; -// BG2 Control. Read/Write. +// BG2 Control. Read/Write. Display Mode 0/1/2 only. pub const BG2CNT: VolAddress = unsafe { VolAddress::new_unchecked(0x400_000C) }; -// BG3 Control. Read/Write. +// BG3 Control. Read/Write. Display Mode 0/2 only. pub const BG3CNT: VolAddress = unsafe { VolAddress::new_unchecked(0x400_000E) }; newtype! { @@ -19,7 +19,7 @@ newtype! { /// Bit 6: Mosaic mode /// Bit 7: is 8bpp /// Bit 8-12: Screen Base Block (0 through 31, 2k each) - /// Bit 13: Display area overflow wraps (otherwise transparent, affine only) + /// Bit 13: Display area overflow wraps (otherwise transparent, affine BG only) /// Bit 14-15: Screen Size #[derive(Debug, Copy, Clone, Default, PartialEq, Eq)] BackgroundControlSetting, u16 @@ -93,7 +93,7 @@ pub const BG3HOFS: VolAddress = unsafe { VolAddress::new_unchecked(0x400_00 // BG3 Y-Offset. Write only. Text mode only. 9 bits. pub const BG3VOFS: VolAddress = unsafe { VolAddress::new_unchecked(0x400_001E) }; -// TODO: affine registers: +// TODO: affine backgrounds // BG2X_L // BG2X_H // BG2Y_L @@ -115,40 +115,6 @@ pub const BG3VOFS: VolAddress = unsafe { VolAddress::new_unchecked(0x400_00 // pub const WININ: VolAddress = unsafe { VolAddress::new_unchecked(0x400_0048) }; // pub const WINOUT: VolAddress = unsafe { VolAddress::new_unchecked(0x400_004A) }; -/// Global mosaic effect control. Write-only. -pub const MOSAIC: VolAddress = unsafe { VolAddress::new_unchecked(0x400_004C) }; - -newtype! { - /// Allows control of the Mosaic effect. - /// - /// Values are the _increase_ for each top-left pixel to be duplicated in the - /// final result. If you want to duplicate some other pixel than the top-left, - /// you can offset the background or object by an appropriate amount. - /// - /// 0) No effect (1+0) - /// 1) Each pixel becomes 2 pixels (1+1) - /// 2) Each pixel becomes 3 pixels (1+2) - /// 3) Each pixel becomes 4 pixels (1+3) - /// - /// * Bits 0-3: BG mosaic horizontal increase - /// * Bits 4-7: BG mosaic vertical increase - /// * Bits 8-11: Object mosaic horizontal increase - /// * Bits 12-15: Object mosaic vertical increase - #[derive(Debug, Copy, Clone, Default, PartialEq, Eq)] - MosaicSetting, u16 -} -impl MosaicSetting { - multi_bits!( - u16, - [ - (0, 4, bg_horizontal_inc), - (4, 4, bg_vertical_inc), - (8, 4, obj_horizontal_inc), - (12, 4, obj_vertical_inc), - ] - ); -} - // pub const BLDCNT: VolAddress = unsafe { VolAddress::new_unchecked(0x400_0050) }; // pub const BLDALPHA: VolAddress = unsafe { VolAddress::new_unchecked(0x400_0052) }; // pub const BLDY: VolAddress = unsafe { VolAddress::new_unchecked(0x400_0054) }; diff --git a/src/io/display.rs b/src/io/display.rs index 353dffa..6badf90 100644 --- a/src/io/display.rs +++ b/src/io/display.rs @@ -4,7 +4,7 @@ use super::*; /// LCD Control. Read/Write. /// -/// The "force vblank" bit is always set when your rust code first executes. +/// The "force vblank" bit is always set when your Rust code first executes. pub const DISPCNT: VolAddress = unsafe { VolAddress::new_unchecked(0x400_0000) }; newtype!( @@ -101,7 +101,7 @@ pub fn display_control() -> DisplayControlSetting { DISPCNT.read() } -/// Display Status and IRQ Control. +/// Display Status and IRQ Control. Read/Write. pub const DISPSTAT: VolAddress = unsafe { VolAddress::new_unchecked(0x400_0004) }; newtype!( @@ -154,3 +154,37 @@ pub fn spin_until_vdraw() { // TODO: make this the better version with BIOS and interrupts and such. while vcount() >= VBLANK_SCANLINE {} } + +/// Global mosaic effect control. Write-only. +pub const MOSAIC: VolAddress = unsafe { VolAddress::new_unchecked(0x400_004C) }; + +newtype! { + /// Allows control of the Mosaic effect. + /// + /// Values are the _increase_ for each top-left pixel to be duplicated in the + /// final result. If you want to duplicate some other pixel than the top-left, + /// you can offset the background or object by an appropriate amount. + /// + /// 0) No effect (1+0) + /// 1) Each pixel becomes 2 pixels (1+1) + /// 2) Each pixel becomes 3 pixels (1+2) + /// 3) Each pixel becomes 4 pixels (1+3) + /// + /// * Bits 0-3: BG mosaic horizontal increase + /// * Bits 4-7: BG mosaic vertical increase + /// * Bits 8-11: Object mosaic horizontal increase + /// * Bits 12-15: Object mosaic vertical increase + #[derive(Debug, Copy, Clone, Default, PartialEq, Eq)] + MosaicSetting, u16 +} +impl MosaicSetting { + multi_bits!( + u16, + [ + (0, 4, bg_horizontal_inc), + (4, 4, bg_vertical_inc), + (8, 4, obj_horizontal_inc), + (12, 4, obj_vertical_inc), + ] + ); +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 7b5b198..a65fc07 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -66,7 +66,7 @@ pub mod bios; pub mod io; pub mod mgba; pub mod palram; -pub mod video; +pub mod vram; newtype! { /// A color on the GBA is an RGB 5.5.5 within a `u16` diff --git a/src/video/tiled.rs b/src/video/tiled.rs deleted file mode 100644 index 40bb383..0000000 --- a/src/video/tiled.rs +++ /dev/null @@ -1,86 +0,0 @@ -//! Module for tiled mode types and operations. - -use super::*; - -// Note(Lokathor): We've got several newtypes here that don't use the `newtype!` -// macro because it's insufficient at parsing array types being wrapped. - -newtype! { - /// An 8x8 tile with 4bpp, packed as `u32` values for proper alignment. - #[derive(Debug, Clone, Copy, Default)] - Tile4bpp, pub [u32; 8], no frills -} - -newtype! { - /// An 8x8 tile with 8bpp, packed as `u32` values for proper alignment. - #[derive(Debug, Clone, Copy, Default)] - Tile8bpp, pub [u32; 16], no frills -} - -newtype! { - /// A 4bpp charblock has 512 tiles in it - #[derive(Clone, Copy)] - Charblock4bpp, pub [Tile4bpp; 512], no frills -} - -newtype! { - /// An 8bpp charblock has 256 tiles in it - #[derive(Clone, Copy)] - Charblock8bpp, pub [Tile4bpp; 256], no frills -} - -newtype! { - /// A screenblock entry for use in Text mode. - #[derive(Debug, Clone, Copy, Default)] - TextScreenblockEntry, u16 -} -impl TextScreenblockEntry { - pub const fn from_tile_index(index: u16) -> Self { - TextScreenblockEntry(index & Self::TILE_ID_MASK) - } - - bool_bits!(u16, [(10, hflip), (11, vflip)]); - - multi_bits!(u16, [(0, 10, tile_id), (12, 4, palbank)]); -} - -newtype! { - /// A screenblock for use in Text mode. - #[derive(Clone, Copy)] - TextScreenblock, [TextScreenblockEntry; 32 * 32], no frills -} - -newtype! { - /// A screenblock entry for use in Affine mode. - #[derive(Debug, Clone, Copy, Default)] - AffineScreenblockEntry, u8 -} - -newtype! { - /// A screenblock for use in Affine mode. - #[derive(Clone, Copy)] - AffineScreenblock16x16, [AffineScreenblockEntry; 16*16], no frills -} - -newtype! { - /// A screenblock for use in Affine mode. - #[derive(Clone, Copy)] - AffineScreenblock32x32, [AffineScreenblockEntry; 32*32], no frills -} - -newtype! { - /// A screenblock for use in Affine mode. - #[derive(Clone, Copy)] - AffineScreenblock64x64, [AffineScreenblockEntry; 64*64], no frills -} - -newtype! { - /// A screenblock for use in Affine mode. - #[derive(Clone, Copy)] - AffineScreenblock128x128, [AffineScreenblockEntry; 128*128], no frills -} - -pub const VRAM_CHARBLOCKS: VolAddressBlock = unsafe { VolAddressBlock::new_unchecked(VolAddress::new_unchecked(VRAM_BASE_USIZE), 6) }; - -pub const VRAM_TEXT_SCREENBLOCKS: VolAddressBlock = - unsafe { VolAddressBlock::new_unchecked(VolAddress::new_unchecked(VRAM_BASE_USIZE), 32) }; diff --git a/src/video.rs b/src/vram.rs similarity index 53% rename from src/video.rs rename to src/vram.rs index 54c52f1..efb0825 100644 --- a/src/video.rs +++ b/src/vram.rs @@ -15,8 +15,9 @@ pub(crate) use super::*; +pub mod affine; pub mod bitmap; -pub mod tiled; +pub mod text; /// The start of VRAM. /// @@ -25,3 +26,34 @@ pub mod tiled; /// 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; + +pub const CHAR_BASE_BLOCKS: VolAddressBlock<[u8; 0x4000]> = unsafe { VolAddressBlock::new_unchecked(VolAddress::new_unchecked(VRAM_BASE_USIZE), 6) }; + +pub const SCREEN_BASE_BLOCKS: VolAddressBlock<[u8; 0x800]> = + unsafe { VolAddressBlock::new_unchecked(VolAddress::new_unchecked(VRAM_BASE_USIZE), 32) }; + +newtype! { + /// An 8x8 tile with 4bpp, packed as `u32` values for proper alignment. + #[derive(Debug, Clone, Copy, Default)] + Tile4bpp, pub [u32; 8], no frills +} + +newtype! { + /// An 8x8 tile with 8bpp, packed as `u32` values for proper alignment. + #[derive(Debug, Clone, Copy, Default)] + Tile8bpp, pub [u32; 16], no frills +} + +/* +newtype! { + /// A 4bpp charblock has 512 tiles in it + #[derive(Clone, Copy)] + Charblock4bpp, pub [Tile4bpp; 512], no frills +} + +newtype! { + /// An 8bpp charblock has 256 tiles in it + #[derive(Clone, Copy)] + Charblock8bpp, pub [Tile4bpp; 256], no frills +} +*/ diff --git a/src/vram/affine.rs b/src/vram/affine.rs new file mode 100644 index 0000000..a29ccf1 --- /dev/null +++ b/src/vram/affine.rs @@ -0,0 +1,31 @@ +use super::*; + +newtype! { + /// A screenblock entry for use in Affine mode. + #[derive(Debug, Clone, Copy, Default)] + AffineScreenblockEntry, u8 +} + +newtype! { + /// A 16x16 screenblock for use in Affine mode. + #[derive(Clone, Copy)] + AffineScreenblock16x16, [AffineScreenblockEntry; 16*16], no frills +} + +newtype! { + /// A 32x32 screenblock for use in Affine mode. + #[derive(Clone, Copy)] + AffineScreenblock32x32, [AffineScreenblockEntry; 32*32], no frills +} + +newtype! { + /// A 64x64 screenblock for use in Affine mode. + #[derive(Clone, Copy)] + AffineScreenblock64x64, [AffineScreenblockEntry; 64*64], no frills +} + +newtype! { + /// A 128x128 screenblock for use in Affine mode. + #[derive(Clone, Copy)] + AffineScreenblock128x128, [AffineScreenblockEntry; 128*128], no frills +} diff --git a/src/video/bitmap.rs b/src/vram/bitmap.rs similarity index 100% rename from src/video/bitmap.rs rename to src/vram/bitmap.rs diff --git a/src/vram/text.rs b/src/vram/text.rs new file mode 100644 index 0000000..58f5aa4 --- /dev/null +++ b/src/vram/text.rs @@ -0,0 +1,29 @@ +//! Module for tiled mode types and operations. + +use super::*; + +newtype! { + /// A screenblock entry for use in Text mode. + #[derive(Debug, Clone, Copy, Default)] + TextScreenblockEntry, u16 +} +impl TextScreenblockEntry { + pub const fn from_tile_index(index: u16) -> Self { + TextScreenblockEntry(index & Self::TILE_ID_MASK) + } + + bool_bits!(u16, [(10, hflip), (11, vflip)]); + + multi_bits!(u16, [(0, 10, tile_id), (12, 4, palbank)]); +} + +newtype! { + /// A screenblock for use in Text mode. + #[derive(Clone, Copy)] + TextScreenblock, [TextScreenblockEntry; 32 * 32], no frills +} + +#[test] +pub fn test_text_screen_block_size() { + assert_eq!(core::mem::size_of::(), 0x800); +} From cc8d4376f817b39b30e1cf7da2d4941705c57999 Mon Sep 17 00:00:00 2001 From: Lokathor Date: Thu, 27 Dec 2018 01:05:21 -0700 Subject: [PATCH 19/24] Add timer support --- src/base/fixed_point.rs | 2 +- src/io.rs | 3 +- src/io/background.rs | 1 + src/io/timers.rs | 85 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 src/io/timers.rs diff --git a/src/base/fixed_point.rs b/src/base/fixed_point.rs index 1e66998..cc1bed6 100644 --- a/src/base/fixed_point.rs +++ b/src/base/fixed_point.rs @@ -230,7 +230,7 @@ fixed_point_unsigned_division! {u32} pub type fx8_8 = Fx; #[cfg(test)] -mod fixed_tests { +mod tests { use super::*; #[test] diff --git a/src/io.rs b/src/io.rs index 86ab5b6..366a2a4 100644 --- a/src/io.rs +++ b/src/io.rs @@ -8,7 +8,8 @@ use super::*; +pub mod background; pub mod display; pub mod dma; pub mod keypad; -pub mod background; +pub mod timers; diff --git a/src/io/background.rs b/src/io/background.rs index d86f033..03de1d7 100644 --- a/src/io/background.rs +++ b/src/io/background.rs @@ -115,6 +115,7 @@ pub const BG3VOFS: VolAddress = unsafe { VolAddress::new_unchecked(0x400_00 // pub const WININ: VolAddress = unsafe { VolAddress::new_unchecked(0x400_0048) }; // pub const WINOUT: VolAddress = unsafe { VolAddress::new_unchecked(0x400_004A) }; +// TODO: blending // pub const BLDCNT: VolAddress = unsafe { VolAddress::new_unchecked(0x400_0050) }; // pub const BLDALPHA: VolAddress = unsafe { VolAddress::new_unchecked(0x400_0052) }; // pub const BLDY: VolAddress = unsafe { VolAddress::new_unchecked(0x400_0054) }; diff --git a/src/io/timers.rs b/src/io/timers.rs new file mode 100644 index 0000000..127f641 --- /dev/null +++ b/src/io/timers.rs @@ -0,0 +1,85 @@ +//! Module for timers. +//! +//! The timers are slightly funny in that reading and writing from them works +//! somewhat differently than with basically any other part of memory. +//! +//! When you read a timer's counter you read the current value. +//! +//! When you write a timer's counter you write _the counter's reload value_. +//! This is used whenever you enable the timer or any time the timer overflows. +//! You cannot set a timer to a given counter value, but you can set a timer to +//! start at some particular value every time it reloads. +//! +//! The timer counters are `u16`, so if you want to set them to run for a +//! certain number of ticks before overflow you would write something like +//! +//! ```rust +//! let init_val: u16 = u32::wrapping_sub(0x1_0000, ticks) as u16; +//! ``` +//! +//! A timer reloads any time it overflows _or_ goes from disabled to enabled. If +//! you want to "pause" a timer _without_ making it reload when resumed then you +//! should not disable it. Instead, you should set its `TimerTickRate` to +//! `Cascade` and disable _the next lower timer_ so that it won't overflow into +//! the timer you have on hold. + +use super::*; + +/// Timer 0 Counter/Reload. Special (see module). +pub const TM0CNT_L: VolAddress = unsafe { VolAddress::new_unchecked(0x400_0100) }; + +/// Timer 1 Counter/Reload. Special (see module). +pub const TM1CNT_L: VolAddress = unsafe { VolAddress::new_unchecked(0x400_0104) }; + +/// Timer 2 Counter/Reload. Special (see module). +pub const TM2CNT_L: VolAddress = unsafe { VolAddress::new_unchecked(0x400_0108) }; + +/// Timer 3 Counter/Reload. Special (see module). +pub const TM3CNT_L: VolAddress = unsafe { VolAddress::new_unchecked(0x400_010C) }; + +/// Timer 0 Control. Read/Write. +pub const TM0CNT_H: VolAddress = unsafe { VolAddress::new_unchecked(0x400_0102) }; + +/// Timer 1 Control. Read/Write. +pub const TM1CNT_H: VolAddress = unsafe { VolAddress::new_unchecked(0x400_0106) }; + +/// Timer 2 Control. Read/Write. +pub const TM2CNT_H: VolAddress = unsafe { VolAddress::new_unchecked(0x400_010A) }; + +/// Timer 3 Control. Read/Write. +pub const TM3CNT_H: VolAddress = unsafe { VolAddress::new_unchecked(0x400_010E) }; + +newtype! { + /// Allows control of a timer unit. + /// + /// * Bits 0-2: How often the timer should tick up one unit. You can either + /// specify a number of CPU cycles or "cascade" mode, where there's a single + /// tick per overflow of the next lower timer. For example, Timer 1 would + /// tick up once per overflow of Timer 0 if it were in cascade mode. Cascade + /// mode naturally does nothing when used with Timer 0. + /// * Bit 6: Raise a timer interrupt upon overflow. + /// * Bit 7: Enable the timer. + #[derive(Debug, Copy, Clone, Default, PartialEq, Eq)] + TimerControlSetting, u16 +} +impl TimerControlSetting { + bool_bits!(u16, [(6, overflow_irq), (7, enabled)]); + + multi_bits!(u16, [(0, 3, tick_rate, TimerTickRate, CPU1, CPU64, CPU256, CPU1024, Cascade),]); +} + +/// Controls how often an enabled timer ticks upward. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u16)] +pub enum TimerTickRate { + /// Once every CPU cycle + CPU1 = 0, + /// Once per 64 CPU cycles + CPU64 = 1, + /// Once per 256 CPU cycles + CPU256 = 2, + /// Once per 1,024 CPU cycles + CPU1024 = 3, + /// Once per overflow of the next lower timer. (Useless with Timer 0) + Cascade = 4, +} From e10491eb8cd1b9b7ff30b90b62e56dd5c1aab424 Mon Sep 17 00:00:00 2001 From: Lokathor Date: Thu, 27 Dec 2018 11:51:07 -0700 Subject: [PATCH 20/24] OAM module --- src/lib.rs | 3 +- src/oam.rs | 85 +++++++++++++++++++++++++++++++++++++----------------- 2 files changed, 60 insertions(+), 28 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index a65fc07..c72a2cf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,7 +3,7 @@ #![feature(const_int_wrapping)] #![feature(const_int_rotate)] #![feature(min_const_unsafe_fn)] -//#![warn(missing_docs)] +#![warn(missing_docs)] #![allow(clippy::cast_lossless)] #![deny(clippy::float_arithmetic)] @@ -65,6 +65,7 @@ pub(crate) use self::base::*; pub mod bios; pub mod io; pub mod mgba; +pub mod oam; pub mod palram; pub mod vram; diff --git a/src/oam.rs b/src/oam.rs index ef7cb41..f08016f 100644 --- a/src/oam.rs +++ b/src/oam.rs @@ -1,18 +1,21 @@ //! Types and declarations for the Object Attribute Memory (`OAM`). +use super::*; + newtype! { - // TODO + /// 0th part of an object's attributes. + /// + /// * Bits 0-7: row-coordinate + /// * Bits 8-9: Rendering style: Normal, Affine, Disabled, Double Area Affine + /// * Bits 10-11: Object mode: Normal, SemiTransparent, Object Window + /// * Bit 12: Mosaic + /// * Bit 13: is 8bpp + /// * Bits 14-15: Object Shape: Square, Horizontal, Vertical #[derive(Debug, Copy, Clone, Default, PartialEq, Eq)] OBJAttr0, u16 } impl OBJAttr0 { - bool_bits!( - u16, - [ - (12, mosaic), - (13, is_8bpp), - ] - ); + bool_bits!(u16, [(12, mosaic), (13, is_8bpp),]); multi_bits!( u16, @@ -25,44 +28,60 @@ impl OBJAttr0 { ); } +/// What style of rendering for this object #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(u16)] pub enum ObjectRender { + /// Standard, non-affine rendering Normal = 0, + /// Affine rendering Affine = 1, + /// Object disabled (saves cycles for elsewhere!) Disabled = 2, + /// Affine with double render space (helps prevent clipping) DoubleAreaAffine = 3, } +/// What mode to ues for the object. #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(u16)] pub enum ObjectMode { + /// Show the object normally Normal = 0, + /// The object becomes the "Alpha Blending 1st target" (see Alpha Blending) SemiTransparent = 1, - OBJWindow = 2 + /// Use the object's non-transparent pixels as part of a mask for the object + /// window (see Windows). + OBJWindow = 2, } +/// What shape the object's appearance should be. +/// +/// The specifics also depend on the `ObjectSize` set. #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(u16)] pub enum ObjectShape { + /// Equal parts wide and tall Square = 0, + /// Wider than tall Horizontal = 1, - Vertical = 2 + /// Taller than wide + Vertical = 2, } newtype! { - // TODO + /// 1st part of an object's attributes. + /// + /// * Bits 0-8: column coordinate + /// * Bits 9-13: + /// * Normal render: Bit 12 holds hflip and 13 holds vflip. + /// * Affine render: The affine parameter selection. + /// * Bits 14-15: Object Size #[derive(Debug, Copy, Clone, Default, PartialEq, Eq)] OBJAttr1, u16 } impl OBJAttr1 { - bool_bits!( - u16, - [ - (12, hflip), - (13, vflip), - ] - ); + bool_bits!(u16, [(12, hflip), (13, vflip),]); multi_bits!( u16, @@ -74,27 +93,39 @@ impl OBJAttr1 { ); } +/// The object's size. +/// +/// Also depends on the `ObjectShape` set. #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(u16)] pub enum ObjectSize { + /// * Square: 8x8px + /// * Horizontal: 16x8px + /// * Vertical: 8x16px Zero = 0, + /// * Square: 16x16px + /// * Horizontal: 32x8px + /// * Vertical: 8x32px One = 1, + /// * Square: 32x32px + /// * Horizontal: 32x16px + /// * Vertical: 16x32px Two = 2, + /// * Square: 64x64px + /// * Horizontal: 64x32px + /// * Vertical: 32x64px Three = 3, } newtype! { - // TODO + /// 2nd part of an object's attributes. + /// + /// * Bits 0-9: Base Tile Index (tile offset from CBB4) + /// * Bits 10-11: Priority + /// * Bits 12-15: Palbank (if using 4bpp) #[derive(Debug, Copy, Clone, Default, PartialEq, Eq)] OBJAttr2, u16 } impl OBJAttr2 { - multi_bits!( - u16, - [ - (0, 10, tile_id), - (10, 2, priority), - (12, 4, palbank), - ] - ); + multi_bits!(u16, [(0, 10, tile_id), (10, 2, priority), (12, 4, palbank),]); } From 147a2bc45c5133f1f5236d8734f65e5a3a7cd59f Mon Sep 17 00:00:00 2001 From: Lokathor Date: Thu, 27 Dec 2018 11:51:35 -0700 Subject: [PATCH 21/24] better vram, still not the best --- src/vram.rs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/vram.rs b/src/vram.rs index efb0825..d4eaf9a 100644 --- a/src/vram.rs +++ b/src/vram.rs @@ -27,8 +27,10 @@ pub mod text; /// being the correct thing. pub const VRAM_BASE_USIZE: usize = 0x600_0000; +/// The character base blocks. pub const CHAR_BASE_BLOCKS: VolAddressBlock<[u8; 0x4000]> = unsafe { VolAddressBlock::new_unchecked(VolAddress::new_unchecked(VRAM_BASE_USIZE), 6) }; +/// The screen entry base blocks. pub const SCREEN_BASE_BLOCKS: VolAddressBlock<[u8; 0x800]> = unsafe { VolAddressBlock::new_unchecked(VolAddress::new_unchecked(VRAM_BASE_USIZE), 32) }; @@ -44,16 +46,12 @@ newtype! { Tile8bpp, pub [u32; 16], no frills } -/* -newtype! { - /// A 4bpp charblock has 512 tiles in it - #[derive(Clone, Copy)] - Charblock4bpp, pub [Tile4bpp; 512], no frills +/// Gives the specified charblock in 4bpp view. +pub fn get_4bpp_character_block(slot: usize) -> VolAddressBlock { + unsafe { VolAddressBlock::new_unchecked(CHAR_BASE_BLOCKS.index(slot).cast::(), 512) } } -newtype! { - /// An 8bpp charblock has 256 tiles in it - #[derive(Clone, Copy)] - Charblock8bpp, pub [Tile4bpp; 256], no frills +/// Gives the specified charblock in 8bpp view. +pub fn get_8bpp_character_block(slot: usize) -> VolAddressBlock { + unsafe { VolAddressBlock::new_unchecked(CHAR_BASE_BLOCKS.index(slot).cast::(), 256) } } -*/ From e475253dcfda8fcb6f24d7b9ca045c590c4ffa99 Mon Sep 17 00:00:00 2001 From: Lokathor Date: Thu, 27 Dec 2018 11:51:53 -0700 Subject: [PATCH 22/24] small cleanups --- examples/bg_demo.rs | 5 ++--- src/io/background.rs | 22 ++++++++-------------- src/io/display.rs | 2 +- src/io/timers.rs | 2 ++ src/vram/affine.rs | 2 ++ src/vram/text.rs | 5 +++-- 6 files changed, 18 insertions(+), 20 deletions(-) diff --git a/examples/bg_demo.rs b/examples/bg_demo.rs index 7477aa2..d15b0ce 100644 --- a/examples/bg_demo.rs +++ b/examples/bg_demo.rs @@ -7,10 +7,9 @@ use gba::{ display::{DisplayControlSetting, DISPCNT}, }, palram::index_palram_bg_4bpp, - vram::text::{TextScreenblockEntry}, + vram::{text::TextScreenblockEntry, Tile4bpp, CHAR_BASE_BLOCKS, SCREEN_BASE_BLOCKS}, Color, }; -use gba::vram::{Tile4bpp, CHAR_BASE_BLOCKS, SCREEN_BASE_BLOCKS}; #[panic_handler] fn panic(_info: &core::panic::PanicInfo) -> ! { @@ -34,7 +33,7 @@ fn main(_argc: isize, _argv: *const *const u8) -> isize { let dark_entry = TextScreenblockEntry::from_tile_index(1); checker_screenblock(8, light_entry, dark_entry); // bg0 control - BG0CNT.write(BackgroundControlSetting::from_screen_base_block(8)); + BG0CNT.write(BackgroundControlSetting::new().with_screen_base_block(8)); // Display Control DISPCNT.write(DisplayControlSetting::new().with_bg0(true)); loop { diff --git a/src/io/background.rs b/src/io/background.rs index 03de1d7..b4cc9a7 100644 --- a/src/io/background.rs +++ b/src/io/background.rs @@ -25,12 +25,6 @@ newtype! { BackgroundControlSetting, u16 } impl BackgroundControlSetting { - pub const fn from_screen_base_block(screen_base_block: u16) -> Self { - BackgroundControlSetting((screen_base_block & 31) << 8) - } - - // - bool_bits!(u16, [(6, mosaic), (7, is_8bpp), (13, display_overflow_wrapping)]); multi_bits!( @@ -73,24 +67,24 @@ pub enum BGSize { Three = 3, } -// BG0 X-Offset. Write only. Text mode only. 9 bits. +/// BG0 X-Offset. Write only. Text mode only. 9 bits. pub const BG0HOFS: VolAddress = unsafe { VolAddress::new_unchecked(0x400_0010) }; -// BG0 Y-Offset. Write only. Text mode only. 9 bits. +/// BG0 Y-Offset. Write only. Text mode only. 9 bits. pub const BG0VOFS: VolAddress = unsafe { VolAddress::new_unchecked(0x400_0012) }; -// BG1 X-Offset. Write only. Text mode only. 9 bits. +/// BG1 X-Offset. Write only. Text mode only. 9 bits. pub const BG1HOFS: VolAddress = unsafe { VolAddress::new_unchecked(0x400_0012) }; -// BG1 Y-Offset. Write only. Text mode only. 9 bits. +/// BG1 Y-Offset. Write only. Text mode only. 9 bits. pub const BG1VOFS: VolAddress = unsafe { VolAddress::new_unchecked(0x400_0012) }; -// BG2 X-Offset. Write only. Text mode only. 9 bits. +/// BG2 X-Offset. Write only. Text mode only. 9 bits. pub const BG2HOFS: VolAddress = unsafe { VolAddress::new_unchecked(0x400_0018) }; -// BG2 Y-Offset. Write only. Text mode only. 9 bits. +/// BG2 Y-Offset. Write only. Text mode only. 9 bits. pub const BG2VOFS: VolAddress = unsafe { VolAddress::new_unchecked(0x400_001A) }; -// BG3 X-Offset. Write only. Text mode only. 9 bits. +/// BG3 X-Offset. Write only. Text mode only. 9 bits. pub const BG3HOFS: VolAddress = unsafe { VolAddress::new_unchecked(0x400_001C) }; -// BG3 Y-Offset. Write only. Text mode only. 9 bits. +/// BG3 Y-Offset. Write only. Text mode only. 9 bits. pub const BG3VOFS: VolAddress = unsafe { VolAddress::new_unchecked(0x400_001E) }; // TODO: affine backgrounds diff --git a/src/io/display.rs b/src/io/display.rs index 6badf90..421c93b 100644 --- a/src/io/display.rs +++ b/src/io/display.rs @@ -187,4 +187,4 @@ impl MosaicSetting { (12, 4, obj_vertical_inc), ] ); -} \ No newline at end of file +} diff --git a/src/io/timers.rs b/src/io/timers.rs index 127f641..026409e 100644 --- a/src/io/timers.rs +++ b/src/io/timers.rs @@ -25,6 +25,8 @@ use super::*; +// TODO: striding blocks? + /// Timer 0 Counter/Reload. Special (see module). pub const TM0CNT_L: VolAddress = unsafe { VolAddress::new_unchecked(0x400_0100) }; diff --git a/src/vram/affine.rs b/src/vram/affine.rs index a29ccf1..57293a0 100644 --- a/src/vram/affine.rs +++ b/src/vram/affine.rs @@ -1,3 +1,5 @@ +//! Module for affine things. + use super::*; newtype! { diff --git a/src/vram/text.rs b/src/vram/text.rs index 58f5aa4..bb433f5 100644 --- a/src/vram/text.rs +++ b/src/vram/text.rs @@ -8,13 +8,14 @@ newtype! { TextScreenblockEntry, u16 } impl TextScreenblockEntry { + /// Generates a default entry with the specified tile index. pub const fn from_tile_index(index: u16) -> Self { - TextScreenblockEntry(index & Self::TILE_ID_MASK) + Self::new().with_tile_index(index) } bool_bits!(u16, [(10, hflip), (11, vflip)]); - multi_bits!(u16, [(0, 10, tile_id), (12, 4, palbank)]); + multi_bits!(u16, [(0, 10, tile_index), (12, 4, palbank)]); } newtype! { From f99da154e8497fc984391f4fe58aa5b19d4f62fb Mon Sep 17 00:00:00 2001 From: Lokathor Date: Thu, 27 Dec 2018 23:22:48 -0700 Subject: [PATCH 23/24] minor fixes --- examples/bg_demo.rs | 4 +--- src/base/volatile.rs | 2 +- src/io/keypad.rs | 4 +++- src/oam.rs | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/bg_demo.rs b/examples/bg_demo.rs index d15b0ce..472233c 100644 --- a/examples/bg_demo.rs +++ b/examples/bg_demo.rs @@ -36,9 +36,7 @@ fn main(_argc: isize, _argv: *const *const u8) -> isize { BG0CNT.write(BackgroundControlSetting::new().with_screen_base_block(8)); // Display Control DISPCNT.write(DisplayControlSetting::new().with_bg0(true)); - loop { - // TODO the whole thing - } + loop {} } pub const ALL_TWOS: Tile4bpp = Tile4bpp([ diff --git a/src/base/volatile.rs b/src/base/volatile.rs index 7f74f69..1ae8ae6 100644 --- a/src/base/volatile.rs +++ b/src/base/volatile.rs @@ -286,7 +286,7 @@ impl VolAddressBlock { if slot < self.slots { unsafe { self.vol_address.offset(slot as isize) } } else { - panic!("Index Requested: {} >= Bound: {}", slot, self.slots) + panic!("Index Requested: {} >= Slot Count: {}", slot, self.slots) } } diff --git a/src/io/keypad.rs b/src/io/keypad.rs index f4fab87..80e5d55 100644 --- a/src/io/keypad.rs +++ b/src/io/keypad.rs @@ -13,10 +13,12 @@ pub const KEYINPUT: VolAddress = unsafe { VolAddress::new_unchecked(0x400_0 /// A "tribool" value helps us interpret the arrow pad. #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(i32)] -#[allow(missing_docs)] pub enum TriBool { + /// -1 Minus = -1, + /// +0 Neutral = 0, + /// +1 Plus = 1, } diff --git a/src/oam.rs b/src/oam.rs index f08016f..f9c0cef 100644 --- a/src/oam.rs +++ b/src/oam.rs @@ -20,7 +20,7 @@ impl OBJAttr0 { multi_bits!( u16, [ - (0, 8, y_coordinate), + (0, 8, row_coordinate), (8, 2, obj_rendering, ObjectRender, Normal, Affine, Disabled, DoubleAreaAffine), (10, 2, obj_mode, ObjectMode, Normal, SemiTransparent, OBJWindow), (14, 2, obj_shape, ObjectShape, Square, Horizontal, Vertical), @@ -86,7 +86,7 @@ impl OBJAttr1 { multi_bits!( u16, [ - (0, 9, x_coordinate), + (0, 9, col_coordinate), (9, 5, affine_index), (14, 2, obj_size, ObjectSize, Zero, One, Two, Three), ] From bee2d16a5b57dee51888e7c792c0f3c01a9d5e5f Mon Sep 17 00:00:00 2001 From: Lokathor Date: Thu, 27 Dec 2018 23:34:12 -0700 Subject: [PATCH 24/24] More docs, and also less no-docs warnings --- Cargo.toml | 2 +- src/io/background.rs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e265cad..eafa519 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ publish = false [dependencies] typenum = "1.10" -gba-proc-macro = "0.4" +gba-proc-macro = "0.4.1" #[dev-dependencies] #quickcheck="0.7" diff --git a/src/io/background.rs b/src/io/background.rs index b4cc9a7..abe1a31 100644 --- a/src/io/background.rs +++ b/src/io/background.rs @@ -2,13 +2,13 @@ use super::*; -// BG0 Control. Read/Write. Display Mode 0/1 only. +/// BG0 Control. Read/Write. Display Mode 0/1 only. pub const BG0CNT: VolAddress = unsafe { VolAddress::new_unchecked(0x400_0008) }; -// BG1 Control. Read/Write. Display Mode 0/1 only. +/// BG1 Control. Read/Write. Display Mode 0/1 only. pub const BG1CNT: VolAddress = unsafe { VolAddress::new_unchecked(0x400_000A) }; -// BG2 Control. Read/Write. Display Mode 0/1/2 only. +/// BG2 Control. Read/Write. Display Mode 0/1/2 only. pub const BG2CNT: VolAddress = unsafe { VolAddress::new_unchecked(0x400_000C) }; -// BG3 Control. Read/Write. Display Mode 0/2 only. +/// BG3 Control. Read/Write. Display Mode 0/2 only. pub const BG3CNT: VolAddress = unsafe { VolAddress::new_unchecked(0x400_000E) }; newtype! {