diff --git a/src/ega.rs b/src/ega.rs index a5ef6d9..d82b71c 100644 --- a/src/ega.rs +++ b/src/ega.rs @@ -142,4 +142,29 @@ impl EgaColors { }; col } + + pub fn u8_to_textattr(num: u8) -> (bool, u8, u8) { + // In EGA/VGA text mode, each screen character is represented by two + // bytes. The low byte is the character, and the high byte is for + // the attributes of the character: + + // +-------+-------+-------+-------+-------+-------+-------+-------+ + // | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | + // +-------+-------+-------+-------+-------+-------+-------+-------+ + // | Blink | Background Color | Foreground Color | + // +-------+-----------------------+-------------------------------+ + + let blink = (num >> 7) != 0; + let back = (num >> 4) & 7; + let fore = num & 15; + (blink, back, fore) + } + + pub fn textattr_to_u8(blink: bool, background: u8, foreground: u8) -> u8 { + let add_blink: u8 = match blink { + false => 0, + true => 128, + }; + foreground + background * 16 + add_blink + } } \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 4f735cc..2e99db0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,17 +4,20 @@ use std::thread::sleep; use std::time::{Duration, SystemTime, SystemTimeError, UNIX_EPOCH}; use bitvec::prelude::*; +use sdl2::Sdl; use sdl2::event::Event; use sdl2::keyboard::Keycode; use sdl2::pixels::{Color, PixelFormatEnum}; use sdl2::rect::Rect; -use sdl2::render::{WindowCanvas, Texture}; +use sdl2::render::{WindowCanvas, Texture, TextureCreator}; +use sdl2::video::{Window, WindowContext}; pub mod cp437; pub mod ega; use cp437::GLYPHS; use ega::EgaColors; + const SCREEN_HEIGHT: u32 = 350; const SCREEN_WIDTH: u32 = 640; @@ -24,8 +27,11 @@ const GLYPH_ROWS: u8 = 25; const GLYPH_HEIGHT: u8 = 14; const GLYPH_WIDTH: u8 = 8; +const GLYPH_ATLAS_HEIGHT: usize = GLYPH_HEIGHT as usize * 8; +const GLYPH_ATLAS_WIDTH: usize = GLYPH_WIDTH as usize * 32; + // Total glyph textures = 256 glyphs * 16 foreground colors * 8 background colors -const TOTAL_GLYPH_TEXTURES: usize = 256 * 16 * 8; +const TOTAL_GLYPH_ATLASES: u8 = 16 * 8; // Total screen blocks = GLYPH_COLUMNS * GLYPH_ROWS const TOTAL_SCREEN_BLOCKS: usize = 2000; @@ -46,73 +52,47 @@ impl ScreenBlock { ScreenBlock { blink, background, foreground, glyph } } - pub fn attrs_to_index(blink: bool, background: u8, foreground: u8, glyph: u8) -> usize { - let num = (usize::from(blink) << 15) + - (usize::from(background) << 12) + - (usize::from(foreground) << 8) + - usize::from(glyph); - num - } - - pub fn index_to_attrs(num: usize) -> (bool, u8, u8, u8) { - let blink = (num >> 15) != 0; - let back = (num >> 12) as u8; - let fore = ((num >> 8) & 15) as u8; - let glyph = (num & 255) as u8; - (blink, back, fore, glyph) - } - - pub fn as_texture_index(&self) -> usize { - let num = ScreenBlock::attrs_to_index( - false, - self.background, - self.foreground, - self.glyph - ); - num - } - - pub fn from_texture_index(num: usize) -> Self { - let (blink, background, foreground, glyph) = ScreenBlock::index_to_attrs(num); - ScreenBlock { blink, background, foreground, glyph } + pub fn as_index(&self) -> usize { + EgaColors::textattr_to_u8(false, self.background, self.foreground) as usize } pub fn to_canvas( &self, canvas: &mut WindowCanvas, textures: &Vec, - x: u8, - y: u8, - blink: &bool + x: i32, + y: i32, + blink_state: &bool ) { - let tex_idx = match blink { - false => self.as_texture_index(), + let tex_idx = match blink_state { + false => self.as_index(), true => match self.blink { - false => self.as_texture_index(), - true => ScreenBlock::new(self.blink, self.background, self.background, self.glyph).as_texture_index(), + false => self.as_index(), + true => EgaColors::textattr_to_u8(false, self.background, self.background) as usize, }, }; + let src_x = (self.glyph % 32 * GLYPH_WIDTH) as i32; + let src_y = (self.glyph / 32 * GLYPH_HEIGHT) as i32; + // These next two assignments look odd mainly because of Rust's current // issue with exclusive range patterns. See here: // https://github.com/rust-lang/rust/issues/37854 let dest_x = match x { - n if (0..=GLYPH_COLUMNS - 1).contains(&n) => GLYPH_WIDTH as i32 * x as i32, - //0..=79 => GLYPH_WIDTH as i32 * x as i32, + n if (0..=GLYPH_COLUMNS as i32 - 1).contains(&n) => GLYPH_WIDTH as i32 * x, _ => panic!("x is out of range! Must be 0..79 inclusive."), }; let dest_y = match y { - n if (0..=GLYPH_ROWS - 1).contains(&n) => GLYPH_HEIGHT as i32 * y as i32, - //0..=24 => GLYPH_HEIGHT as i32 * y as i32, + n if (0..=GLYPH_ROWS as i32 - 1).contains(&n) => GLYPH_HEIGHT as i32 * y, _ => panic!("y is out of range! Must be 0..24 inclusive."), }; match canvas.copy( &textures[tex_idx], - Rect::new(0, 0, u32::from(GLYPH_WIDTH), u32::from(GLYPH_HEIGHT)), + Rect::new(src_x, src_y, u32::from(GLYPH_WIDTH), u32::from(GLYPH_HEIGHT)), Rect::new(dest_x, dest_y, u32::from(GLYPH_WIDTH), u32::from(GLYPH_HEIGHT)), ) { Ok(()) => (), @@ -121,31 +101,7 @@ impl ScreenBlock { } } -pub fn update(elapsed: u128) { - //println!("Updated after {} nanoseconds.", elapsed); -} - -pub fn render(canvas: &mut WindowCanvas, textures: &Vec, screen: &[ScreenBlock], blink: &bool) { - canvas.set_draw_color::(EgaColors::Black.into()); - canvas.clear(); - - for (idx, block) in screen.iter().enumerate() { - let x = (idx % GLYPH_COLUMNS as usize) as u8; - let y = (idx / GLYPH_COLUMNS as usize) as u8; - block.to_canvas(canvas, &textures, x, y, blink); - } - canvas.present(); -} - -pub fn get_timestamp_in_nanos() -> Result { - let current_systime = SystemTime::now(); - let since_epoch = current_systime.duration_since(UNIX_EPOCH)?; - let nanos = since_epoch.as_nanos(); - - Ok(nanos) -} - -pub fn main() { +pub fn init_sdl() -> (Sdl, Window) { let sdl_context = match sdl2::init() { Ok(sdl_context) => sdl_context, Err(err) => panic!("SDL could not initialize! SDL_Error: {}", err), @@ -163,34 +119,34 @@ pub fn main() { Ok(window) => window, Err(err) => panic!("SDL could not create a window! SDL_Error: {}", err), }; + (sdl_context, window) +} - let mut canvas = match window.into_canvas().build() { - Ok(canvas) => canvas, - Err(err) => panic!("SDL could not create a canvas! SDL_Error: {}", err), - }; - - let texture_creator = canvas.texture_creator(); - let mut glyph_textures: Vec = Vec::new(); - - for tex_idx in 0..TOTAL_GLYPH_TEXTURES { - let (_, back, fore, cp) = ScreenBlock::index_to_attrs(tex_idx); - let mut texture = match texture_creator.create_texture_streaming(PixelFormatEnum::RGB24, u32::from(GLYPH_WIDTH), u32::from(GLYPH_HEIGHT)) { +pub fn init_glyph_atlases(creator: &TextureCreator) -> Vec> { + let mut atlases: Vec = Vec::new(); + for atlas_idx in 0..TOTAL_GLYPH_ATLASES { + let (_, back, fore) = EgaColors::u8_to_textattr(atlas_idx.try_into().unwrap()); + let mut texture = match creator.create_texture_streaming(PixelFormatEnum::RGB24, GLYPH_ATLAS_WIDTH as u32, GLYPH_ATLAS_HEIGHT as u32) { Ok(texture) => texture, Err(err) => panic!("SDL could not create texture. SDL Error: {}", err), }; match texture.with_lock(None, |buffer: &mut [u8], pitch: usize| { - for (y, line) in GLYPHS[cp as usize].iter().enumerate() { - let bits = line.view_bits::(); - for (x, pixel) in bits.iter().enumerate() { - let offset = y * pitch + x * 3; - if pixel == false { - buffer[offset] = EgaColors::u8_to_rgb(back).r; - buffer[offset + 1] = EgaColors::u8_to_rgb(back).g; - buffer[offset + 2] = EgaColors::u8_to_rgb(back).b; - } else { - buffer[offset] = EgaColors::u8_to_rgb(fore).r; - buffer[offset + 1] = EgaColors::u8_to_rgb(fore).g; - buffer[offset + 2] = EgaColors::u8_to_rgb(fore).b; + for (glyph_num, glyph_data) in GLYPHS.iter().enumerate() { + let glyph_x = glyph_num % 32; + let glyph_y = glyph_num / 32; + for (local_y, line) in glyph_data.iter().enumerate() { + let bits = line.view_bits::(); + for (local_x, pixel) in bits.iter().enumerate() { + let offset = ((glyph_y * GLYPH_HEIGHT as usize * pitch) + (local_y * pitch)) + ((glyph_x * GLYPH_WIDTH as usize) + local_x) * 3; + if pixel == false { + buffer[offset] = EgaColors::u8_to_rgb(back).r; + buffer[offset + 1] = EgaColors::u8_to_rgb(back).g; + buffer[offset + 2] = EgaColors::u8_to_rgb(back).b; + } else { + buffer[offset] = EgaColors::u8_to_rgb(fore).r; + buffer[offset + 1] = EgaColors::u8_to_rgb(fore).g; + buffer[offset + 2] = EgaColors::u8_to_rgb(fore).b; + } } } } @@ -198,30 +154,72 @@ pub fn main() { Ok(()) => (), Err(err) => panic!("Unable to modify texture. SDL Error: {}", err), }; - glyph_textures.push(texture); + atlases.push(texture); } + atlases +} - let mut dos_screen: [ScreenBlock; TOTAL_SCREEN_BLOCKS] = [ScreenBlock::new(false, EgaColors::Black.into(), EgaColors::Black.into(), 0); TOTAL_SCREEN_BLOCKS]; - +pub fn create_test_screen(screen: &mut [ScreenBlock]) { for col in 0..60 { - dos_screen[col as usize] = ScreenBlock::new(false, EgaColors::Black.into(), EgaColors::BrightYellow.into(), 178); + screen[col as usize] = ScreenBlock::new(false, EgaColors::Black.into(), EgaColors::BrightYellow.into(), 178); } for row in 1..GLYPH_ROWS-1 { let idx = row as usize * GLYPH_COLUMNS as usize; - dos_screen[idx] = ScreenBlock::new(false, EgaColors::Black.into(), EgaColors::BrightYellow.into(), 178); - dos_screen[idx + 59] = ScreenBlock::new(false, EgaColors::Black.into(), EgaColors::BrightYellow.into(), 178); + screen[idx] = ScreenBlock::new(false, EgaColors::Black.into(), EgaColors::BrightYellow.into(), 178); + screen[idx + 59] = ScreenBlock::new(false, EgaColors::Black.into(), EgaColors::BrightYellow.into(), 178); } for col in 0..60 { let idx = (GLYPH_ROWS - 1) as usize * GLYPH_COLUMNS as usize + col as usize; - dos_screen[idx] = ScreenBlock::new(false, EgaColors::Black.into(), EgaColors::BrightYellow.into(), 178); + screen[idx] = ScreenBlock::new(false, EgaColors::Black.into(), EgaColors::BrightYellow.into(), 178); } - dos_screen[1030] = ScreenBlock::new(true, EgaColors::Blue.into(), EgaColors::LightGray.into(), 178); + screen[1030] = ScreenBlock::new(true, EgaColors::Blue.into(), EgaColors::LightGray.into(), 178); +} + +pub fn update(elapsed: u128) { + //println!("Updated after {} nanoseconds.", elapsed); +} + +pub fn render(canvas: &mut WindowCanvas, atlases: &Vec, screen: &[ScreenBlock], blink: &bool) { + canvas.set_draw_color::(EgaColors::Black.into()); + canvas.clear(); + + for (idx, block) in screen.iter().enumerate() { + let x = (idx % GLYPH_COLUMNS as usize) as i32; + let y = (idx / GLYPH_COLUMNS as usize) as i32; + block.to_canvas(canvas, &atlases, x, y, blink); + } + + canvas.present(); +} + +pub fn get_timestamp_in_nanos() -> Result { + let current_systime = SystemTime::now(); + let since_epoch = current_systime.duration_since(UNIX_EPOCH)?; + let nanos = since_epoch.as_nanos(); + + Ok(nanos) +} + +pub fn main() { + let (sdl_context, window) = init_sdl(); + + let mut canvas = match window.into_canvas().build() { + Ok(canvas) => canvas, + Err(err) => panic!("SDL could not create a canvas! SDL_Error: {}", err), + }; + + let texture_creator = canvas.texture_creator(); + let atlases: Vec = init_glyph_atlases(&texture_creator); + + let mut dos_screen: [ScreenBlock; TOTAL_SCREEN_BLOCKS] = [ScreenBlock::new(false, EgaColors::Black.into(), EgaColors::Black.into(), 0); TOTAL_SCREEN_BLOCKS]; + + create_test_screen(&mut dos_screen); let mut blink_tick: u128 = 0; let mut blink_state: bool = false; - render(&mut canvas, &glyph_textures, &dos_screen, &blink_state); + render(&mut canvas, &atlases, &dos_screen, &blink_state); let mut event_pump = match sdl_context.event_pump() { Ok(event_pump) => event_pump, @@ -259,7 +257,7 @@ pub fn main() { blink_tick += elapsed; } - render(&mut canvas, &glyph_textures, &dos_screen, &blink_state); + render(&mut canvas, &atlases, &dos_screen, &blink_state); //sleep(Duration::new(0, FPS_60_HERTZ)); prev_tick = curr_tick;