From 869562b3b02af7afd6232e1e5adc4ba5922b3f84 Mon Sep 17 00:00:00 2001 From: Lokathor Date: Mon, 27 May 2024 04:29:21 -0600 Subject: [PATCH] a working objects demo. --- backup/examples/game.rs | 209 ------------------ .../objects.rs | 54 ++--- src/lib.rs | 1 + src/mmio.rs | 98 +++++++- {backup/src => src}/obj.rs | 138 +++++++++--- src/sample_art.rs | 126 +++++++++++ src/video.rs | 86 +++++++ 7 files changed, 450 insertions(+), 262 deletions(-) delete mode 100644 backup/examples/game.rs rename backup/examples/game_vblank_draw.rs => examples/objects.rs (84%) rename {backup/src => src}/obj.rs (53%) diff --git a/backup/examples/game.rs b/backup/examples/game.rs deleted file mode 100644 index 9e51d6aa..00000000 --- a/backup/examples/game.rs +++ /dev/null @@ -1,209 +0,0 @@ -#![no_std] -#![no_main] - -use gba::prelude::*; - -#[panic_handler] -fn panic_handler(info: &core::panic::PanicInfo) -> ! { - #[cfg(debug_assertions)] - if let Ok(mut logger) = MgbaBufferedLogger::try_new(MgbaMessageLevel::Fatal) { - use core::fmt::Write; - writeln!(logger, "{info}").ok(); - } - loop {} -} - -#[derive(Debug, Clone, Copy, Default)] -struct Position { - x: u16, - y: u16, -} -#[derive(Debug, Clone, Copy, Default)] -struct Rect { - x: u16, - y: u16, - w: u16, - h: u16, -} -impl Rect { - fn intersect(self, other: Self) -> bool { - self.x < other.x + other.w - && self.x + self.w > other.x - && self.y < other.y + other.h - && self.h + self.y > other.y - } - - fn iter_tiles(self) -> impl Iterator { - let y_range_incl = (self.y / 8)..=((self.y + self.h - 1) / 8); - let x_range_incl = (self.x / 8)..=((self.x + self.w - 1) / 8); - y_range_incl - .map(move |y_index| { - x_range_incl.clone().map(move |x_index| (x_index, y_index)) - }) - .flatten() - } -} - -#[no_mangle] -extern "C" fn main() -> ! { - // game simulation setup - let mut creatures = [Position::default(); 5]; - creatures[0].x = 11; - creatures[0].y = 14; - // - creatures[1].x = 44; - creatures[1].y = 38; - creatures[2].x = 100; - creatures[2].y = 23; - creatures[3].x = 14; - creatures[3].y = 101; - creatures[4].x = 72; - creatures[4].y = 59; - - // indexing with `[y][x]` - let mut world = [[0_u8; 32]; 32]; - for i in 0..32 { - world[0][i] = Cga8x8Thick::BOX_HORIZONTAL; - world[19][i] = Cga8x8Thick::BOX_HORIZONTAL; - world[i][0] = Cga8x8Thick::BOX_VERTICAL; - world[i][29] = Cga8x8Thick::BOX_VERTICAL; - } - world[0][0] = Cga8x8Thick::BOX_UPPER_LEFT; - world[0][29] = Cga8x8Thick::BOX_UPPER_RIGHT; - world[19][0] = Cga8x8Thick::BOX_LOWER_LEFT; - world[19][29] = Cga8x8Thick::BOX_LOWER_RIGHT; - world[1][3] = b'B'; - world[2][3] = b'G'; - world[3][3] = b'0'; - - // interrupt configuration - DISPSTAT.write(DisplayStatus::new().with_irq_vblank(true)); - IE.write(IrqBits::VBLANK); - IME.write(true); - - // bg - BG_PALETTE.index(1).write(Color::MAGENTA); - // obj - let colors = - [Color::CYAN, Color::GREEN, Color::RED, Color::BLUE, Color::YELLOW]; - for (pal, color) in colors.iter().enumerate() { - obj_palbank(pal).index(1).write(*color); - } - - Cga8x8Thick.bitunpack_4bpp(CHARBLOCK0_4BPP.as_region(), 0); - Cga8x8Thick.bitunpack_4bpp(OBJ_TILES.as_region(), 0); - - BG0CNT.write(BackgroundControl::new().with_screenblock(8)); - let screenblock = TEXT_SCREENBLOCKS.get_frame(8).unwrap(); - for y in 0..32 { - let row = screenblock.get_row(y).unwrap(); - for (x, addr) in row.iter().enumerate() { - let te = TextEntry::new().with_tile(world[y][x] as u16); - addr.write(te); - } - } - - let no_display = ObjAttr0::new().with_style(ObjDisplayStyle::NotDisplayed); - OBJ_ATTR0.iter().skip(creatures.len()).for_each(|va| va.write(no_display)); - - DISPCNT.write(DisplayControl::new().with_show_obj(true).with_show_bg0(true)); - - let mut l_was_pressed = false; - let mut r_was_pressed = false; - - loop { - // wait for vblank - VBlankIntrWait(); - - // update graphics MMIO - for (i, (creature_pos, attr_addr)) in - creatures.iter().zip(OBJ_ATTR_ALL.iter()).enumerate() - { - let mut obj = ObjAttr::new(); - obj.set_x(creature_pos.x); - obj.set_y(creature_pos.y); - obj.set_tile_id(1); - obj.set_palbank(i as u16); - attr_addr.write(obj); - } - - // handle input - let keys = KEYINPUT.read(); - if keys.l() && !l_was_pressed { - creatures.rotate_left(1); - } - if keys.r() && !r_was_pressed { - creatures.rotate_right(1); - } - l_was_pressed = keys.l(); - r_was_pressed = keys.r(); - - // the way we handle movement here is per-direction. If you're against a - // wall and you press a diagonal then one axis will progress while the other - // will be halted by the wall. This makes the player slide along the wall - // when bumping into walls. - let (player, enemies) = match &mut creatures { - [player, enemies @ ..] => (player, enemies), - }; - if keys.up() { - let new_p = Position { x: player.x, y: player.y - 1 }; - let new_r = Rect { x: new_p.x, y: new_p.y, w: 8, h: 8 }; - let terrain_clear = new_r - .iter_tiles() - .all(|(tx, ty)| allows_movement(world[ty as usize][tx as usize])); - let enemy_clear = enemies.iter().all(|enemy| { - let enemy_r = Rect { x: enemy.x, y: enemy.y, w: 8, h: 8 }; - !new_r.intersect(enemy_r) - }); - if terrain_clear && enemy_clear { - *player = new_p; - } - } - if keys.down() { - let new_p = Position { x: player.x, y: player.y + 1 }; - let new_r = Rect { x: new_p.x, y: new_p.y, w: 8, h: 8 }; - let terrain_clear = new_r - .iter_tiles() - .all(|(tx, ty)| allows_movement(world[ty as usize][tx as usize])); - let enemy_clear = enemies.iter().all(|enemy| { - let enemy_r = Rect { x: enemy.x, y: enemy.y, w: 8, h: 8 }; - !new_r.intersect(enemy_r) - }); - if terrain_clear && enemy_clear { - *player = new_p; - } - } - if keys.left() { - let new_p = Position { x: player.x - 1, y: player.y }; - let new_r = Rect { x: new_p.x, y: new_p.y, w: 8, h: 8 }; - let terrain_clear = new_r - .iter_tiles() - .all(|(tx, ty)| allows_movement(world[ty as usize][tx as usize])); - let enemy_clear = enemies.iter().all(|enemy| { - let enemy_r = Rect { x: enemy.x, y: enemy.y, w: 8, h: 8 }; - !new_r.intersect(enemy_r) - }); - if terrain_clear && enemy_clear { - *player = new_p; - } - } - if keys.right() { - let new_p = Position { x: player.x + 1, y: player.y }; - let new_r = Rect { x: new_p.x, y: new_p.y, w: 8, h: 8 }; - let terrain_clear = new_r - .iter_tiles() - .all(|(tx, ty)| allows_movement(world[ty as usize][tx as usize])); - let enemy_clear = enemies.iter().all(|enemy| { - let enemy_r = Rect { x: enemy.x, y: enemy.y, w: 8, h: 8 }; - !new_r.intersect(enemy_r) - }); - if terrain_clear && enemy_clear { - *player = new_p; - } - } - } -} - -const fn allows_movement(u: u8) -> bool { - u == 0 || u == b' ' || u == u8::MAX -} diff --git a/backup/examples/game_vblank_draw.rs b/examples/objects.rs similarity index 84% rename from backup/examples/game_vblank_draw.rs rename to examples/objects.rs index 443a6894..abb6459c 100644 --- a/backup/examples/game_vblank_draw.rs +++ b/examples/objects.rs @@ -1,17 +1,21 @@ #![no_std] #![no_main] -use gba::prelude::*; - -#[panic_handler] -fn panic_handler(info: &core::panic::PanicInfo) -> ! { - #[cfg(debug_assertions)] - if let Ok(mut logger) = MgbaBufferedLogger::try_new(MgbaMessageLevel::Fatal) { - use core::fmt::Write; - writeln!(logger, "{info}").ok(); - } - loop {} -} +use gba::{ + asm_runtime::USER_IRQ_HANDLER, + bios::VBlankIntrWait, + gba_cell::GbaCell, + mmio::{ + obj_palbank, BG0CNT, BG_PALRAM, DISPCNT, DISPSTAT, IE, IME, KEYINPUT, + OBJ_ATTR0, OBJ_ATTR_ALL, TEXT_SCREENBLOCKS, VRAM_BG_TILE4, VRAM_OBJ_TILE4, + }, + obj::{ObjAttr, ObjAttr0, ObjDisplayStyle}, + sample_art::{decompress_cga_face_to_vram_4bpp, Cga}, + video::{BackgroundControl, Color, DisplayControl, DisplayStatus, TextEntry}, + IrqBits, +}; + +gba::panic_handler!(mgba_log_err); #[derive(Debug, Clone, Copy, Default)] struct Position { @@ -88,27 +92,27 @@ extern "C" fn main() -> ! { // indexing with `[y][x]` let mut world = [[0_u8; 32]; 32]; for i in 0..32 { - world[0][i] = Cga8x8Thick::BOX_HORIZONTAL; - world[19][i] = Cga8x8Thick::BOX_HORIZONTAL; - world[i][0] = Cga8x8Thick::BOX_VERTICAL; - world[i][29] = Cga8x8Thick::BOX_VERTICAL; + world[0][i] = Cga::LEFT_RIGHT; + world[19][i] = Cga::LEFT_RIGHT; + world[i][0] = Cga::UP_DOWN; + world[i][29] = Cga::UP_DOWN; } - world[0][0] = Cga8x8Thick::BOX_UPPER_LEFT; - world[0][29] = Cga8x8Thick::BOX_UPPER_RIGHT; - world[19][0] = Cga8x8Thick::BOX_LOWER_LEFT; - world[19][29] = Cga8x8Thick::BOX_LOWER_RIGHT; + world[0][0] = Cga::DOWN_RIGHT; + world[0][29] = Cga::LEFT_DOWN; + world[19][0] = Cga::UP_RIGHT; + world[19][29] = Cga::UP_LEFT; world[1][3] = b'B'; world[2][3] = b'G'; world[3][3] = b'0'; // interrupt configuration - RUST_IRQ_HANDLER.write(Some(irq_handler)); - DISPSTAT.write(DisplayStatus::new().with_irq_vblank(true)); + USER_IRQ_HANDLER.write(Some(irq_handler)); + DISPSTAT.write(DisplayStatus::new().with_vblank_irq(true)); IE.write(IrqBits::VBLANK); IME.write(true); // bg - BG_PALETTE.index(1).write(Color::MAGENTA); + BG_PALRAM.index(1).write(Color::MAGENTA); // obj let colors = [Color::CYAN, Color::GREEN, Color::RED, Color::BLUE, Color::YELLOW]; @@ -116,8 +120,8 @@ extern "C" fn main() -> ! { obj_palbank(pal).index(1).write(*color); } - Cga8x8Thick.bitunpack_4bpp(CHARBLOCK0_4BPP.as_region(), 0); - Cga8x8Thick.bitunpack_4bpp(OBJ_TILES.as_region(), 0); + decompress_cga_face_to_vram_4bpp(VRAM_BG_TILE4.as_region()); + decompress_cga_face_to_vram_4bpp(VRAM_OBJ_TILE4.as_region()); BG0CNT.write(BackgroundControl::new().with_screenblock(8)); let screenblock = TEXT_SCREENBLOCKS.get_frame(8).unwrap(); @@ -132,7 +136,7 @@ extern "C" fn main() -> ! { let no_display = ObjAttr0::new().with_style(ObjDisplayStyle::NotDisplayed); OBJ_ATTR0.iter().skip(creatures.len()).for_each(|va| va.write(no_display)); - DISPCNT.write(DisplayControl::new().with_show_obj(true).with_show_bg0(true)); + DISPCNT.write(DisplayControl::new().with_objects(true).with_bg0(true)); let mut l_was_pressed = false; let mut r_was_pressed = false; diff --git a/src/lib.rs b/src/lib.rs index d6ac8969..18370350 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -58,6 +58,7 @@ pub mod gba_fixed; pub mod mem; pub mod mgba; pub mod mmio; +pub mod obj; pub mod panic_handlers; pub mod per_project_setup; pub mod per_system_setup; diff --git a/src/mmio.rs b/src/mmio.rs index e26a39d6..98953ec4 100644 --- a/src/mmio.rs +++ b/src/mmio.rs @@ -5,12 +5,16 @@ use core::ffi::c_void; use bitfrob::u8x2; #[allow(unused_imports)] use voladdress::VolAddress; -use voladdress::{Unsafe, VolBlock, VolGrid2d, VolGrid2dStrided}; +use voladdress::{Unsafe, VolBlock, VolGrid2d, VolGrid2dStrided, VolSeries}; use crate::{ dma::DmaControl, mgba::MgbaLogLevel, - video::{Color, DisplayControl, DisplayStatus, Tile4bpp}, + obj::{ObjAttr, ObjAttr0, ObjAttr1, ObjAttr2}, + video::{ + BackgroundControl, Color, DisplayControl, DisplayStatus, TextEntry, + Tile4bpp, + }, IrqBits, KeyInput, }; @@ -49,6 +53,22 @@ pub const DISPSTAT: PlainAddr = /// Values of 160 to 227 indicate that a vertical blank line is happening. pub const VCOUNT: RoAddr = unsafe { VolAddress::new(0x0400_0006) }; +/// Background 0 controls +pub const BG0CNT: PlainAddr = + unsafe { VolAddress::new(0x0400_0008) }; + +/// Background 1 controls +pub const BG1CNT: PlainAddr = + unsafe { VolAddress::new(0x0400_000A) }; + +/// Background 2 controls +pub const BG2CNT: PlainAddr = + unsafe { VolAddress::new(0x0400_000C) }; + +/// Background 3 controls +pub const BG3CNT: PlainAddr = + unsafe { VolAddress::new(0x0400_000E) }; + /// Source address for DMA3. /// /// The correct pointer type depends on the transfer mode used. @@ -199,7 +219,24 @@ pub const BG_PALRAM: VolBlock = /// Palette data for the objects. pub const OBJ_PALRAM: VolBlock = - unsafe { VolBlock::new(0x0500_0000) }; + unsafe { VolBlock::new(0x0500_0200) }; + +/// Gets the block for a specific palbank. +/// +/// ## Panics +/// * If the `bank` requested is 16 or greater this will panic. +#[inline] +#[must_use] +#[cfg_attr(feature = "track_caller", track_caller)] +pub const fn obj_palbank(bank: usize) -> VolBlock { + let u = OBJ_PALRAM.index(bank * 16).as_usize(); + unsafe { VolBlock::new(u) } +} + +/// The VRAM byte offset per screenblock index. +/// +/// This is the same for all background types and sizes. +pub const SCREENBLOCK_INDEX_OFFSET: usize = 2 * 1_024; /// The VRAM's background tile view, using 4bpp tiles. pub const VRAM_BG_TILE4: VolBlock = @@ -209,6 +246,25 @@ pub const VRAM_BG_TILE4: VolBlock = pub const VRAM_BG_TILE8: VolBlock = unsafe { VolBlock::new(0x0600_0000) }; +/// The text mode screenblocks. +pub const TEXT_SCREENBLOCKS: VolGrid2dStrided< + TextEntry, + SOGBA, + SOGBA, + 32, + 32, + 32, + SCREENBLOCK_INDEX_OFFSET, +> = unsafe { VolGrid2dStrided::new(0x0600_0000) }; + +/// The VRAM's object tile view, using 4bpp tiles. +pub const VRAM_OBJ_TILE4: VolBlock = + unsafe { VolBlock::new(0x0601_0000) }; + +/// The VRAM's object tile view, using 8bpp tiles. +pub const VRAM_OBJ_TILE8: VolBlock = + unsafe { VolBlock::new(0x0601_0000) }; + /// The VRAM's view in Video Mode 3. /// /// Each location is a direct color value. @@ -243,3 +299,39 @@ pub const MODE5_VRAM: VolGrid2dStrided< 2, 0xA000, > = unsafe { VolGrid2dStrided::new(0x0600_0000) }; + +/// The combined object attributes. +pub const OBJ_ATTR_ALL: VolSeries< + ObjAttr, + SOGBA, + SOGBA, + 128, + { core::mem::size_of::<[i16; 4]>() }, +> = unsafe { VolSeries::new(0x0700_0000) }; + +/// The object 0th attributes. +pub const OBJ_ATTR0: VolSeries< + ObjAttr0, + SOGBA, + SOGBA, + 128, + { core::mem::size_of::<[i16; 4]>() }, +> = unsafe { VolSeries::new(0x0700_0000) }; + +/// The object 1st attributes. +pub const OBJ_ATTR1: VolSeries< + ObjAttr1, + SOGBA, + SOGBA, + 128, + { core::mem::size_of::<[i16; 4]>() }, +> = unsafe { VolSeries::new(0x0700_0000 + 2) }; + +/// The object 2nd attributes. +pub const OBJ_ATTR2: VolSeries< + ObjAttr2, + SOGBA, + SOGBA, + 128, + { core::mem::size_of::<[i16; 4]>() }, +> = unsafe { VolSeries::new(0x0700_0000 + 4) }; diff --git a/backup/src/obj.rs b/src/obj.rs similarity index 53% rename from backup/src/obj.rs rename to src/obj.rs index 4f97982c..f5fadd7b 100644 --- a/backup/src/obj.rs +++ b/src/obj.rs @@ -31,13 +31,13 @@ //! not using don't appear on the screen. Otherwise, you'll end up with //! un-configured objects appearing in the upper left corner of the display. -use super::*; +use bitfrob::{u16_with_bit, u16_with_region, u16_with_value}; /// How the object should be displayed. /// /// Bit 9 of Attr0 changes meaning depending on Bit 8, so this merges the two /// bits into a single property. -#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Debug, Clone, Copy, Default)] #[repr(u16)] pub enum ObjDisplayStyle { /// The default, non-affine display @@ -52,7 +52,7 @@ pub enum ObjDisplayStyle { } /// What special effect the object interacts with -#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Debug, Clone, Copy, Default)] #[repr(u16)] pub enum ObjEffectMode { /// The default, no special effect interaction @@ -76,7 +76,7 @@ pub enum ObjEffectMode { /// | 1 | 16x16 | 32x8 | 8x32 | /// | 2 | 32x32 | 32x16 | 16x32 | /// | 3 | 64x64 | 64x32 | 32x64 | -#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Debug, Clone, Copy, Default)] #[repr(u16)] #[allow(missing_docs)] pub enum ObjShape { @@ -87,48 +87,136 @@ pub enum ObjShape { } /// Object Attributes, field 0 of the entry. -#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Debug, Clone, Copy, Default)] #[repr(transparent)] pub struct ObjAttr0(u16); impl ObjAttr0 { - pub_const_fn_new_zeroed!(); - u16_int_field!(0 - 7, y, with_y); - u16_enum_field!(8 - 9: ObjDisplayStyle, style, with_style); - u16_enum_field!(10 - 11: ObjEffectMode, mode, with_mode); - u16_bool_field!(12, mosaic, with_mosaic); - u16_bool_field!(13, bpp8, with_bpp8); - u16_enum_field!(14 - 15: ObjShape, shape, with_shape); + /// A new blank attr 0. + #[inline] + pub const fn new() -> Self { + Self(0) + } + /// Sets the `y` position of this object + #[inline] + pub const fn with_y(self, y: u16) -> Self { + Self(u16_with_value(0, 7, self.0, y as u16)) + } + /// The object's display styling. + #[inline] + pub const fn with_style(self, style: ObjDisplayStyle) -> Self { + Self(u16_with_region(8, 9, self.0, style as u16)) + } + /// The special effect mode of the object, if any. + #[inline] + pub const fn with_effect(self, effect: ObjEffectMode) -> Self { + Self(u16_with_region(10, 11, self.0, effect as u16)) + } + /// If the object should use the mosaic effect. + #[inline] + pub const fn with_mosaic(self, mosaic: bool) -> Self { + Self(u16_with_bit(12, self.0, mosaic)) + } + /// If the object draws using 8-bits-per-pixel. + #[inline] + pub const fn with_bpp8(self, bpp8: bool) -> Self { + Self(u16_with_bit(13, self.0, bpp8)) + } + /// The object's shape + #[inline] + pub const fn with_shape(self, shape: ObjShape) -> Self { + Self(u16_with_region(14, 15, self.0, shape as u16)) + } } /// Object Attributes, field 1 of the entry. -#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Debug, Clone, Copy, Default)] #[repr(transparent)] pub struct ObjAttr1(u16); impl ObjAttr1 { - pub_const_fn_new_zeroed!(); - u16_int_field!(0 - 8, x, with_x); - u16_int_field!(9 - 13, affine_index, with_affine_index); - u16_bool_field!(12, hflip, with_hflip); - u16_bool_field!(13, vflip, with_vflip); - u16_int_field!(14 - 15, size, with_size); + /// A new blank attr 1. + #[inline] + pub const fn new() -> Self { + Self(0) + } + /// Sets the `x` position of this object + #[inline] + pub const fn with_x(self, x: u16) -> Self { + Self(u16_with_value(0, 8, self.0, x as u16)) + } + /// The affine index of the object. + #[inline] + pub const fn with_affine_index(self, index: u16) -> Self { + Self(u16_with_value(9, 13, self.0, index as u16)) + } + /// If the object is horizontally flipped + #[inline] + pub const fn with_hflip(self, hflip: bool) -> Self { + Self(u16_with_bit(12, self.0, hflip)) + } + /// If the object is vertically flipped + #[inline] + pub const fn with_vflip(self, vflip: bool) -> Self { + Self(u16_with_bit(13, self.0, vflip)) + } + /// The object's size + /// + /// The size you set here, combined with the shape of the object, determines + /// the object's actual area. + /// + /// | Size | Square| Horizontal| Vertical| + /// |:-:|:-:|:-:|:-:| + /// | 0 | 8x8 | 16x8 | 8x16 | + /// | 1 | 16x16 | 32x8 | 8x32 | + /// | 2 | 32x32 | 32x16 | 16x32 | + /// | 3 | 64x64 | 64x32 | 32x64 | + #[inline] + pub const fn with_size(self, size: u16) -> Self { + Self(u16_with_value(14, 15, self.0, size as u16)) + } } /// Object Attributes, field 2 of the entry. -#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Debug, Clone, Copy, Default)] #[repr(transparent)] pub struct ObjAttr2(u16); impl ObjAttr2 { - pub_const_fn_new_zeroed!(); - u16_int_field!(0 - 9, tile_id, with_tile_id); - u16_int_field!(10 - 11, priority, with_priority); - u16_int_field!(12 - 15, palbank, with_palbank); + /// A new blank attr 2. + #[inline] + pub const fn new() -> Self { + Self(0) + } + /// The base tile id of the object. + /// + /// All other tiles in the object are automatically selected using the + /// following tiles, according to if + /// [`with_obj_vram_1d`][crate::video::DisplayControl::with_obj_vram_1d] it + /// set or not. + #[inline] + pub const fn with_tile_id(self, id: u16) -> Self { + Self(u16_with_value(0, 9, self.0, id as u16)) + } + /// Sets the object's priority sorting. + /// + /// Lower priority objects are closer to the viewer, and will appear in front + /// other objects that have *higher* priority, and in front of backgrounds of + /// *equal or higher* priority. If two objects have the same priority, the + /// lower index object is shown. + #[inline] + pub const fn with_priority(self, priority: u16) -> Self { + Self(u16_with_value(10, 11, self.0, priority as u16)) + } + /// Sets the palbank value of this object. + #[inline] + pub const fn with_palbank(self, palbank: u16) -> Self { + Self(u16_with_value(12, 15, self.0, palbank as u16)) + } } /// Object Attributes. /// /// The fields of this struct are all `pub` so that you can simply alter them as /// you wish. Some "setter" methods are also provided as a shorthand. -#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Debug, Clone, Copy, Default)] #[repr(C)] pub struct ObjAttr(pub ObjAttr0, pub ObjAttr1, pub ObjAttr2); #[allow(missing_docs)] diff --git a/src/sample_art.rs b/src/sample_art.rs index e56fcb9c..a6015680 100644 --- a/src/sample_art.rs +++ b/src/sample_art.rs @@ -24,6 +24,132 @@ pub fn decompress_cga_face_to_vram_4bpp(dest: VolRegion) { } } +/// An empty type that just serves as a namespace for constants. +pub struct Cga; +#[allow(missing_docs)] +impl Cga { + // + pub const BLANK: u8 = 0x00; + pub const EMPTY_FACE: u8 = 0x01; + pub const FULL_FACE: u8 = 0x02; + pub const HEART: u8 = 0x03; + pub const DIAMOND: u8 = 0x04; + pub const CLUB: u8 = 0x05; + pub const SPADE: u8 = 0x06; + pub const CENTER_DOT: u8 = 0x07; + pub const INVERT_CENTER_DOT: u8 = 0x08; + pub const RING: u8 = 0x09; + pub const INVERT_RING: u8 = 0x0A; + pub const MALE: u8 = 0x0B; + pub const FEMALE: u8 = 0x0C; + pub const MUSIC_NOTE: u8 = 0x0D; + pub const DOUBLE_MUSIC_NOTE: u8 = 0x0E; + pub const STAR: u8 = 0x0F; + // + pub const TRIANGLE_RIGHT: u8 = 0x10; + pub const TRIANGLE_LEFT: u8 = 0x11; + pub const ARROW_UP_DOWN: u8 = 0x12; + pub const DOUBLE_EXCLAMATION: u8 = 0x13; + pub const PARAGRAPH: u8 = 0x14; + pub const SUBSECTION: u8 = 0x15; + pub const UNDERLINE: u8 = 0x16; + pub const ARROW_UP_DOWN_UNDERLINED: u8 = 0x17; + pub const ARROW_UP: u8 = 0x18; + pub const ARROW_DOWN: u8 = 0x19; + pub const ARROW_RIGHT: u8 = 0x1A; + pub const ARROW_LEFT: u8 = 0x1B; + pub const CORNER_THING: u8 = 0x1C; + pub const ARROW_LEFT_RIGHT: u8 = 0x1D; + pub const TRIANGLE_UP: u8 = 0x1E; + pub const TRIANGLE_DOWN: u8 = 0x1F; + // + pub const DELETE: u8 = 0x7F; + // + pub const QUARTER_COVER: u8 = 0xB0; + pub const HALF_COVER: u8 = 0xB1; + pub const THREE_QUARTER_COVER: u8 = 0xB2; + pub const UP_DOWN: u8 = 0xB3; + pub const UP_LEFT_DOWN: u8 = 0xB4; + pub const UP_LEFT2_DOWN: u8 = 0xB5; + pub const UP2_LEFT_DOWN2: u8 = 0xB6; + pub const LEFT_DOWN2: u8 = 0xB7; + pub const LEFT2_DOWN: u8 = 0xB8; + pub const UP2_LEFT2_DOWN2: u8 = 0xB9; + pub const UP2_DOWN2: u8 = 0xBA; + pub const LEFT2_DOWN2: u8 = 0xBB; + pub const UP2_LEFT2: u8 = 0xBC; + pub const UP2_LEFT: u8 = 0xBD; + pub const UP_LEFT2: u8 = 0xBE; + pub const LEFT_DOWN: u8 = 0xBF; + // + pub const UP_RIGHT: u8 = 0xC0; + pub const UP_LEFT_RIGHT: u8 = 0xC1; + pub const LEFT_DOWN_RIGHT: u8 = 0xC2; + pub const UP_DOWN_RIGHT: u8 = 0xC3; + pub const LEFT_RIGHT: u8 = 0xC4; + pub const UP_LEFT_DOWN_RIGHT: u8 = 0xC5; + pub const UP_DOWN_RIGHT2: u8 = 0xC6; + pub const UP2_DOWN2_RIGHT: u8 = 0xC7; + pub const UP2_RIGHT2: u8 = 0xC8; + pub const DOWN2_RIGHT2: u8 = 0xC9; + pub const UP2_LEFT2_RIGHT2: u8 = 0xCA; + pub const LEFT2_DOWN2_RIGHT2: u8 = 0xCB; + pub const UP2_DOWN2_RIGHT2: u8 = 0xCC; + pub const LEFT2_RIGHT2: u8 = 0xCD; + pub const UP2_LEFT2_DOWN2_RIGHT2: u8 = 0xCE; + pub const UP_LEFT2_RIGHT2: u8 = 0xCF; + // + pub const UP2_LEFT_RIGHT: u8 = 0xD0; + pub const LEFT2_DOWN_RIGHT2: u8 = 0xD1; + pub const LEFT_DOWN2_RIGHT: u8 = 0xD2; + pub const UP2_RIGHT: u8 = 0xD3; + pub const UP_RIGHT2: u8 = 0xD4; + pub const DOWN_RIGHT2: u8 = 0xD5; + pub const DOWN2_RIGHT: u8 = 0xD6; + pub const UP2_RIGHT_DOWN2_LEFT: u8 = 0xD7; + pub const UP_LEFT2_DOWN_RIGHT2: u8 = 0xD8; + pub const UP_LEFT: u8 = 0xD9; + pub const DOWN_RIGHT: u8 = 0xDA; + pub const SOLID: u8 = 0xDB; + pub const HALF_BOTTOM: u8 = 0xDC; + pub const HALF_LEFT: u8 = 0xDD; + pub const HALF_RIGHT: u8 = 0xDE; + pub const HALF_TOP: u8 = 0xDF; + // + pub const ALPHA: u8 = 0xE0; + pub const BETA: u8 = 0xE1; + pub const GAMMA: u8 = 0xE2; + pub const PI: u8 = 0xE3; + pub const SIGMA: u8 = 0xE4; + pub const SIGMA_LOWERCASE: u8 = 0xE5; + pub const MU_LOWERCASE: u8 = 0xE6; + pub const GAMMA_LOWERCASE: u8 = 0xE7; + pub const PHI: u8 = 0xE8; + pub const THETA: u8 = 0xE9; + pub const OMEGA: u8 = 0xEA; + pub const DELTA_LOWERCASE: u8 = 0xEB; + pub const INFINITY: u8 = 0xEC; + pub const NOT_INFINITY: u8 = 0xED; + pub const EPSILON: u8 = 0xEE; + pub const HOOP: u8 = 0xEF; + // + pub const TRIPLE_LINES: u8 = 0xF0; + pub const PLUS_UNDERLINED: u8 = 0xF1; + pub const GREATER_THAN_UNDERLINED: u8 = 0xF2; + pub const LESS_THAN_UNDERLINED: u8 = 0xF3; + pub const HOOK_HIGH: u8 = 0xF4; + pub const HOOK_LOW: u8 = 0xF5; + pub const DIVISION: u8 = 0xF6; + pub const APPROXIMATE: u8 = 0xF7; + pub const DEGREE: u8 = 0xF8; + pub const CENTER_SQUARE: u8 = 0xF9; + pub const CENTER_HALF_SQUARE: u8 = 0xFA; + pub const SQUARE_ROOT: u8 = 0xFB; + pub const N_EXPONENT: u8 = 0xFC; + pub const SQUARED: u8 = 0xFD; + pub const LARGE_CENTER_SQUARE: u8 = 0xFE; +} + static CGA_8X8_THICK_LZ77: [u32; 421] = [ 0x00200010, 0xF0000030, 0x10059001, 0x42011111, 0x01060001, 0x20101001, 0x111E1107, 0x01100110, 0x1B101A00, 0x73111F10, 0x10010011, 0x00081019, diff --git a/src/video.rs b/src/video.rs index ff11b9b3..fe3f5561 100644 --- a/src/video.rs +++ b/src/video.rs @@ -211,6 +211,92 @@ impl DisplayStatus { } } +/// Background layer control data. +#[derive(Debug, Clone, Copy, Default)] +#[repr(transparent)] +pub struct BackgroundControl(u16); +impl BackgroundControl { + /// Makes a new, empty value. + #[inline] + pub const fn new() -> Self { + Self(0) + } + /// Sets the background's priority sorting. + /// + /// Lower priority backgrounds are closer to the viewer, and will appear in + /// front other objects that have *higher* priority, and in front of + /// backgrounds of *higher* priority. If two backgrounds have the same + /// priority the lower numbered background is shown. + #[inline] + pub const fn with_priority(self, priority: u8) -> Self { + Self(u16_with_value(0, 1, self.0, priority as u16)) + } + /// The base charblock value for this background. + #[inline] + pub const fn with_charblock(self, charblock: u8) -> Self { + Self(u16_with_value(2, 3, self.0, charblock as u16)) + } + /// If this background uses the mosaic effect. + #[inline] + pub const fn with_mosaic(self, mosaic: bool) -> Self { + Self(u16_with_bit(6, self.0, mosaic)) + } + /// Sets the background to 8-bits-per-pixel + #[inline] + pub const fn with_bpp8(self, bpp8: bool) -> Self { + Self(u16_with_bit(7, self.0, bpp8)) + } + /// Sets the screenblock which lays out these tiles. + #[inline] + pub const fn with_screenblock(self, screenblock: u8) -> Self { + Self(u16_with_value(8, 12, self.0, screenblock as u16)) + } + /// If affine pixels that go out of the background's area + #[inline] + pub const fn with_affine_wrap(self, wrap: bool) -> Self { + Self(u16_with_bit(13, self.0, wrap)) + } + /// The background's size. + #[inline] + pub const fn with_size(self, size: u8) -> Self { + Self(u16_with_value(14, 15, self.0, size as u16)) + } +} + +/// Textual tile mode entry. +#[derive(Debug, Clone, Copy, Default)] +#[repr(transparent)] +pub struct TextEntry(u16); +impl TextEntry { + /// Makes a new, empty value. + #[inline] + pub const fn new() -> Self { + Self(0) + } + /// The tile ID + #[inline] + pub const fn with_tile(self, id: u16) -> Self { + Self(u16_with_value(0, 9, self.0, id)) + } + /// If the tile should be horizontally flipped + #[inline] + pub const fn with_hflip(self, hflip: bool) -> Self { + Self(u16_with_bit(10, self.0, hflip)) + } + /// If the tile should be vertically flipped. + #[inline] + pub const fn with_vflip(self, vflip: bool) -> Self { + Self(u16_with_bit(11, self.0, vflip)) + } + /// The palbank for this tile. + /// + /// Only used if the background is set for 4bpp. + #[inline] + pub const fn with_palbank(self, palbank: u16) -> Self { + Self(u16_with_value(12, 15, self.0, palbank)) + } +} + /// Data for a 4-bit-per-pixel tile. /// /// The tile is 8 pixels wide and 8 pixels tall. Each pixel is 4 bits, giving an